From ec691b33844da1ddee5e31b8f8293fea320d5839 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Fri, 15 Jan 2021 17:18:57 -0800 Subject: [PATCH] Preserve line order in OverlayNG (#665) * Fix OverlayNG edge ordering to preserve order of input lines Signed-off-by: Martin Davis --- .../jts/operation/overlayng/EdgeMerger.java | 26 ++++++++----------- .../jts/operation/overlayng/LineBuilder.java | 4 +++ .../operation/overlayng/OverlayNGTest.java | 8 ++++++ .../test/java/test/jts/GeometryTestCase.java | 17 +++++++++++- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/EdgeMerger.java b/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/EdgeMerger.java index 82f6c82a14..112e87db3c 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/EdgeMerger.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/EdgeMerger.java @@ -12,13 +12,11 @@ package org.locationtech.jts.operation.overlayng; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.locationtech.jts.util.Assert; -import org.locationtech.jts.util.Debug; /** * Performs merging on the noded edges of the input geometries. @@ -39,25 +37,22 @@ * no other coincident edge, or if all coincident edges have the same direction). * This ensures that the overlay output line direction will be as consistent * as possible with input lines. + *

+ * The merger also preserves the order of the edges in the input. + * This means that for polygon-line overlay + * the result lines will be in the same order as in the input + * (possibly with multiple result lines for a single input line). * * @author mdavis * */ class EdgeMerger { - + public static List merge(List edges) { - EdgeMerger merger = new EdgeMerger(edges); - return merger.merge(); - } + // use a list to collect the final edges, to preserve order + List mergedEdges = new ArrayList(); + Map edgeMap = new HashMap(); - private Collection edges; - private Map edgeMap = new HashMap(); - - public EdgeMerger(List edges) { - this.edges = edges; - } - - public ArrayList merge() { for (Edge edge : edges) { EdgeKey edgeKey = EdgeKey.create(edge); Edge baseEdge = edgeMap.get(edgeKey); @@ -66,6 +61,7 @@ public ArrayList merge() { edgeMap.put(edgeKey, edge); //Debug.println("edge added: " + edge); //Debug.println(edge.toLineString()); + mergedEdges.add(edge); } else { // found an existing edge @@ -80,7 +76,7 @@ public ArrayList merge() { //Debug.println(edge.toLineString()); } } - return new ArrayList(edgeMap.values()); + return mergedEdges; } } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/LineBuilder.java b/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/LineBuilder.java index 0e2212bac2..16c011b332 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/LineBuilder.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/overlayng/LineBuilder.java @@ -305,6 +305,10 @@ private void addResultLinesRings() { * by the start edge direction. This implies * that if all edges are reversed, the created line * will be reversed to match. + * This ensures the orientation of linework is faithful to the input + * in the case of polygon-line overlay. + * However, this does not provide a consistent orientation + * in the case of line-line intersection(where A and B might have different orientations). * (Other more complex strategies would be possible. * E.g. using the direction of the majority of segments, * or preferring the direction of the A edges.) diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/overlayng/OverlayNGTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/overlayng/OverlayNGTest.java index 62716761f4..5a73e519a8 100644 --- a/modules/core/src/test/java/org/locationtech/jts/operation/overlayng/OverlayNGTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/operation/overlayng/OverlayNGTest.java @@ -569,6 +569,14 @@ public void testPolygonFlatCollapseIntersection() { checkEqual(expected, actual); } + public void testPolygonLineIntersectionOrder() { + Geometry a = read("POLYGON ((1 1, 1 9, 9 9, 9 7, 3 7, 3 3, 9 3, 9 1, 1 1))"); + Geometry b = read("MULTILINESTRING ((2 10, 2 0), (4 10, 4 0))"); + Geometry expected = read("MULTILINESTRING ((2 9, 2 1), (4 9, 4 7), (4 3, 4 1))"); + Geometry actual = intersection(a, b, 1); + checkEqualExact(expected, actual); + } + //============================================================ diff --git a/modules/core/src/test/java/test/jts/GeometryTestCase.java b/modules/core/src/test/java/test/jts/GeometryTestCase.java index 3b7b233f07..8e163e9c49 100644 --- a/modules/core/src/test/java/test/jts/GeometryTestCase.java +++ b/modules/core/src/test/java/test/jts/GeometryTestCase.java @@ -57,7 +57,7 @@ protected GeometryTestCase(String name, CoordinateSequenceFactory coordinateSequ /** * Checks that the normalized values of the expected and actual - * geometries are exactly equals. + * geometries are exactly equal. * * @param expected the expected value * @param actual the actual value @@ -72,6 +72,21 @@ protected void checkEqual(Geometry expected, Geometry actual) { assertTrue(equal); } + /** + * Checks that the values of the expected and actual + * geometries are exactly equal. + * + * @param expected the expected value + * @param actual the actual value + */ + protected void checkEqualExact(Geometry expected, Geometry actual) { + boolean equal = actual.equalsExact(expected); + if (! equal) { + System.out.format(CHECK_EQUAL_FAIL, expected, actual ); + } + assertTrue(equal); + } + protected void checkEqual(Geometry expected, Geometry actual, double tolerance) { Geometry actualNorm = actual.norm(); Geometry expectedNorm = expected.norm();