Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OverlayNG support for simple GeometryCollection inputs #915

Merged
merged 1 commit into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}