Skip to content

Commit

Permalink
Add OverlayNG support for simple GeometryCollection inputs (#915)
Browse files Browse the repository at this point in the history
Signed-off-by: Martin Davis <[email protected]>
  • Loading branch information
dr-jts authored Oct 12, 2022
1 parent 4f0b44f commit 01396ab
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,13 @@ public class IndexedPointInAreaLocator

/**
* Creates a new locator for a given {@link Geometry}.
* {@link Polygonal} and {@link LinearRing} geometries
* Geometries containing {@link Polygon}s and {@link LinearRing} geometries
* are supported.
*
* @param g the Geometry to locate in
*/
public IndexedPointInAreaLocator(Geometry g)
{
if (! (g instanceof Polygonal || g instanceof LinearRing))
throw new IllegalArgumentException("Argument must be Polygonal or LinearRing");
geom = g;
}

Expand Down Expand Up @@ -144,6 +142,10 @@ private void init(Geometry geom)
List lines = LinearComponentExtracter.getLines(geom);
for (Iterator i = lines.iterator(); i.hasNext(); ) {
LineString line = (LineString) i.next();
//-- only include rings of Polygons or LinearRings
if (! line.isClosed())
continue;

Coordinate[] pts = line.getCoordinates();
addLine(pts);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator;
import org.locationtech.jts.algorithm.locate.PointOnGeometryLocator;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateFilter;
import org.locationtech.jts.geom.CoordinateList;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
Expand Down Expand Up @@ -217,13 +218,15 @@ private Geometry copyNonPoint() {

private static Coordinate[] extractCoordinates(Geometry points, PrecisionModel pm) {
CoordinateList coords = new CoordinateList();
int n = points.getNumGeometries();
for (int i = 0; i < n; i++) {
Point point = (Point) points.getGeometryN(i);
if (point.isEmpty()) continue;
Coordinate coord = OverlayUtil.round(point, pm);
coords.add(coord, true);
}
points.apply(new CoordinateFilter() {

@Override
public void filter(Coordinate coord) {
Coordinate p = OverlayUtil.round(coord, pm);
coords.add(p, false);
}

});
return coords.toCoordinateArray();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
* </ul>
* Input geometries may have different dimension.
* Input collections must be homogeneous (all elements must have the same dimension).
* Inputs may be <b>simple</b> {@link GeometryCollection}s.
* A GeometryCollection is simple if it can be flattened into a valid Multi-geometry;
* i.e. it is homogeneous and does not contain any overlapping Polygons.
* <p>
* The precision model used for the computation can be supplied
* independent of the precision model of the input geometry.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import java.util.Map.Entry;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateFilter;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryComponentFilter;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.PrecisionModel;
Expand Down Expand Up @@ -154,25 +156,29 @@ private Point copyPoint(Point pt) {
return geometryFactory.createPoint(seq2);
}

private HashMap<Coordinate, Point> buildPointMap(Geometry geom) {
private HashMap<Coordinate, Point> buildPointMap(Geometry geoms) {
HashMap<Coordinate, Point> map = new HashMap<Coordinate, Point>();
for (int i = 0; i < geom.getNumGeometries(); i++) {
Geometry elt = geom.getGeometryN(i);
if (! (elt instanceof Point) ) {
throw new IllegalArgumentException("Non-point geometry input to point overlay");
geoms.apply(new GeometryComponentFilter() {

@Override
public void filter(Geometry geom) {
if (! (geom instanceof Point))
return;
if (geom.isEmpty())
return;

Point pt = (Point) geom;
Coordinate p = roundCoord(pt, pm);
/**
* Only add first occurrence of a point.
* This provides the merging semantics of overlay
*/
if (! map.containsKey(p))
map.put(p, pt);
}
// don't add empty points
if (elt.isEmpty()) continue;

Point pt = (Point) elt;
Coordinate p = roundCoord(pt, pm);
/**
* Only add first occurrence of a point.
* This provides the merging semantics of overlay
*/
if (! map.containsKey(p))
map.put(p, pt);
}
});

return map;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,21 @@ private static String labelForResult(OverlayEdge edge) {
*/
public static Coordinate round(Point pt, PrecisionModel pm) {
if (pt.isEmpty()) return null;
Coordinate p = pt.getCoordinate().copy();
return round( pt.getCoordinate(), pm );
}

/**
* Rounds a coordinate if precision model is fixed.
* Note: return value is only copied if rounding is performed.
*
* @param p the coordinate to round
* @return the rounded coordinate
*/
public static Coordinate round(Coordinate p, PrecisionModel pm) {
if (! isFloating(pm)) {
pm.makePrecise(p);
Coordinate pRound = p.copy();
pm.makePrecise(pRound);
return pRound;
}
return p;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
* <li>Input geometries may have different dimension.</li>
* <li>Collections must be homogeneous
* (all elements must have the same dimension).</li>
* <li>Inputs may be <b>simple</b> {@link GeometryCollection}s.
* A GeometryCollection is simple if it can be flattened into a valid Multi-geometry;
* i.e. it is homogeneous and does not contain any overlapping Polygons.</li>
* <li>In general, inputs must be valid geometries.</li>
* <li>However, polygonal inputs may contain the following two kinds of "mild" invalid topology:
* <ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.locationtech.jts.operation.overlayng;

import junit.textui.TestRunner;

/**
* Tests supported OverlayNG semantics for GeometryCollection inputs.
*
* Note: currently only "simple" GCs are supported.
* Simple GCs are ones which can be flattened to a valid Multi-geometry.
*
* @author mdavis
*
*/
public class OverlayNGGeometryCollectionTest extends OverlayNGTestCase {

public static void main(String args[]) {
TestRunner.run(OverlayNGGeometryCollectionTest.class);
}

public OverlayNGGeometryCollectionTest(String name) { super(name); }

public void testSimpleA_mP() {
String a = "POLYGON ((0 0, 0 1, 1 1, 0 0))";
String b = "GEOMETRYCOLLECTION ( MULTIPOINT ((0 0), (99 99)) )";
checkIntersection(a, b,
"POINT (0 0)");
checkUnion(a, b,
"GEOMETRYCOLLECTION (POINT (99 99), POLYGON ((0 0, 0 1, 1 1, 0 0)))");
}

public void testSimpleP_mP() {
String a = "POINT(0 0)";
String b = "GEOMETRYCOLLECTION ( MULTIPOINT ((0 0), (99 99)) )";
checkIntersection(a, b,
"POINT (0 0)");
checkUnion(a, b,
"MULTIPOINT ((0 0), (99 99))");
}

public void testSimpleP_mL() {
String a = "POINT(5 5)";
String b = "GEOMETRYCOLLECTION ( MULTILINESTRING ((1 9, 9 1), (1 1, 9 9)) )";
checkIntersection(a, b,
"POINT (5 5)");
checkUnion(a, b,
"MULTILINESTRING ((1 1, 5 5), (1 9, 5 5), (5 5, 9 1), (5 5, 9 9))");
}

public void testSimpleP_mA() {
String a = "POINT(5 5)";
String b = "GEOMETRYCOLLECTION ( MULTIPOLYGON (((1 1, 1 5, 5 5, 5 1, 1 1)), ((9 9, 9 5, 5 5, 5 9, 9 9))) )";
checkIntersection(a, b,
"POINT (5 5)");
checkUnion(a, b,
"MULTIPOLYGON (((1 1, 1 5, 5 5, 5 1, 1 1)), ((9 9, 9 5, 5 5, 5 9, 9 9)))");
}

public void testSimpleP_AA() {
String a = "POINT(5 5)";
String b = "GEOMETRYCOLLECTION ( POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1)), POLYGON ((9 9, 9 5, 5 5, 5 9, 9 9)) )";
checkIntersection(a, b,
"POINT (5 5)");
checkUnion(a, b,
"MULTIPOLYGON (((1 1, 1 5, 5 5, 5 1, 1 1)), ((9 9, 9 5, 5 5, 5 9, 9 9)))");
}

public void testSimpleL_AA() {
String a = "LINESTRING (0 0, 10 10)";
String b = "GEOMETRYCOLLECTION ( POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1)), POLYGON ((9 9, 9 5, 5 5, 5 9, 9 9)) )";
checkIntersection(a, b,
"MULTILINESTRING ((1 1, 5 5), (5 5, 9 9))");
checkUnion(a, b,
"GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (9 9, 10 10), POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1)), POLYGON ((5 5, 5 9, 9 9, 9 5, 5 5)))");
}

public void testSimpleA_AA() {
String a = "POLYGON ((2 8, 8 8, 8 2, 2 2, 2 8))";
String b = "GEOMETRYCOLLECTION ( POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1)), POLYGON ((9 9, 9 5, 5 5, 5 9, 9 9)) )";
checkIntersection(a, b,
"MULTIPOLYGON (((2 2, 2 5, 5 5, 5 2, 2 2)), ((5 5, 5 8, 8 8, 8 5, 5 5)))");
checkUnion(a, b,
"POLYGON ((1 1, 1 5, 2 5, 2 8, 5 8, 5 9, 9 9, 9 5, 8 5, 8 2, 5 2, 5 1, 1 1))");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.locationtech.jts.operation.overlayng;

import static org.locationtech.jts.operation.overlayng.OverlayNG.INTERSECTION;
import static org.locationtech.jts.operation.overlayng.OverlayNG.UNION;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.PrecisionModel;

import test.jts.GeometryTestCase;

class OverlayNGTestCase extends GeometryTestCase {

protected OverlayNGTestCase(String name) {
super(name);
}

protected void checkIntersection(String wktA, String wktB, String wktExpected) {
checkOverlay(wktA, wktB, INTERSECTION, wktExpected);
}

protected void checkUnion(String wktA, String wktB, String wktExpected) {
checkOverlay(wktA, wktB, UNION, wktExpected);
}

protected void checkOverlay(String wktA, String wktB, int overlayOp, String wktExpected) {
Geometry a = read(wktA);
Geometry b = read(wktB);
PrecisionModel pm = new PrecisionModel();
Geometry actual = OverlayNG.overlay(a, b, overlayOp, pm);
Geometry expected = read(wktExpected);
checkEqual(expected, actual);
}
}

0 comments on commit 01396ab

Please sign in to comment.