From 1ce81bb07288c434560d7aefdd23e061432634e3 Mon Sep 17 00:00:00 2001 From: Martin Davis Date: Thu, 18 Jan 2024 17:29:57 -0800 Subject: [PATCH] Add BoundaryNodeRule support --- .../function/SpatialPredicateNGFunctions.java | 2 +- .../jts/algorithm/BoundaryNodeRule.java | 16 ++++++++ .../operation/relateng/LinearBoundary.java | 8 ++-- .../operation/relateng/RelateGeometry.java | 22 ++++++++--- .../jts/operation/relateng/RelateNG.java | 38 +++++++++++++++---- .../operation/relateng/TopologyBuilder.java | 1 + .../RelateNGBoundaryNodeRuleTest.java | 36 +++++++++--------- 7 files changed, 89 insertions(+), 34 deletions(-) diff --git a/modules/app/src/main/java/org/locationtech/jtstest/function/SpatialPredicateNGFunctions.java b/modules/app/src/main/java/org/locationtech/jtstest/function/SpatialPredicateNGFunctions.java index 964e3e8dae..9fefe11189 100644 --- a/modules/app/src/main/java/org/locationtech/jtstest/function/SpatialPredicateNGFunctions.java +++ b/modules/app/src/main/java/org/locationtech/jtstest/function/SpatialPredicateNGFunctions.java @@ -47,7 +47,7 @@ public static boolean relate(Geometry a, Geometry b, String mask) { return RelateNG.relate(a, b, mask); } public static String relateIM(Geometry a, Geometry b) { - return RelateNG.relate(a, b); + return RelateNG.relate(a, b).toString(); } } diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/BoundaryNodeRule.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/BoundaryNodeRule.java index 7770f6273a..8b20bef1b3 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/BoundaryNodeRule.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/BoundaryNodeRule.java @@ -121,6 +121,10 @@ public boolean isInBoundary(int boundaryCount) // the "Mod-2 Rule" return boundaryCount % 2 == 1; } + + public String toString() { + return "Mod2 Boundary Node Rule"; + } } /** @@ -152,6 +156,10 @@ public boolean isInBoundary(int boundaryCount) { return boundaryCount > 0; } + + public String toString() { + return "EndPoint Boundary Node Rule"; + } } /** @@ -171,6 +179,10 @@ public boolean isInBoundary(int boundaryCount) { return boundaryCount > 1; } + + public String toString() { + return "MultiValent EndPoint Boundary Node Rule"; + } } /** @@ -189,6 +201,10 @@ public boolean isInBoundary(int boundaryCount) { return boundaryCount == 1; } + + public String toString() { + return "MonoValent EndPoint Boundary Node Rule"; + } } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/LinearBoundary.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/LinearBoundary.java index b6f2418a7d..7eafbbaa5e 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/LinearBoundary.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/LinearBoundary.java @@ -26,16 +26,18 @@ public class LinearBoundary { private Map vertexDegree = new HashMap(); private boolean hasBoundary; + private BoundaryNodeRule boundaryNodeRule; - public LinearBoundary(Geometry geom) { + public LinearBoundary(Geometry geom, BoundaryNodeRule bnRule) { //assert: dim(geom) == 1 + this.boundaryNodeRule = bnRule; vertexDegree = computeBoundaryPoints(geom); hasBoundary = checkBoundary(vertexDegree); } private boolean checkBoundary(Map vertexDegree) { for (int degree : vertexDegree.values()) { - if (BoundaryNodeRule.MOD2_BOUNDARY_RULE.isInBoundary(degree)) { + if (boundaryNodeRule.isInBoundary(degree)) { return true; } } @@ -47,7 +49,7 @@ public boolean isBoundary(Coordinate pt) { return false; int degree = vertexDegree.get(pt); //TODO: add support for settable BoundaryNodeRule - return BoundaryNodeRule.MOD2_BOUNDARY_RULE.isInBoundary(degree); + return boundaryNodeRule.isInBoundary(degree); } private static Map computeBoundaryPoints(Geometry geom) { diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java index 8b87bba198..ee78972b42 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateGeometry.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Set; +import org.locationtech.jts.algorithm.BoundaryNodeRule; import org.locationtech.jts.algorithm.Orientation; import org.locationtech.jts.algorithm.PointLocator; import org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator; @@ -51,21 +52,32 @@ public class RelateGeometry { private boolean isPrepared = false; private List pts; private Set uniquePoints; + private BoundaryNodeRule boundaryNodeRule; public RelateGeometry(Geometry input) { - this(input, false); + this(input, false, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE); } - + public RelateGeometry(Geometry input, boolean isPrepared) { + this(input, false, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE); + } + + public RelateGeometry(Geometry input, BoundaryNodeRule bnRule) { + this(input, false, bnRule); + } + + public RelateGeometry(Geometry input, boolean isPrepared, BoundaryNodeRule bnRule) { this.geom = input; this.isPrepared = isPrepared; + this.boundaryNodeRule = bnRule; dim = input.getDimension(); if (dim == 1) { - lineBoundary = new LinearBoundary(input); + lineBoundary = new LinearBoundary(input, boundaryNodeRule); } } - + + public Envelope getEnvelope() { return geom.getEnvelopeInternal(); } @@ -148,7 +160,7 @@ private int LocateOnPoint(Coordinate pt) { private int locateOnLine(Coordinate pt) { if (lineLocator == null) { //TODO: index the lines in prepared mode? - lineLocator = new PointLocator(); + lineLocator = new PointLocator(boundaryNodeRule); } return lineLocator.locate(pt, geom); } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java index fb4ea19bc2..114a833918 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/RelateNG.java @@ -22,6 +22,7 @@ import org.locationtech.jts.geom.Dimension; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.IntersectionMatrix; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Polygon; import org.locationtech.jts.geom.prep.PreparedGeometry; @@ -57,10 +58,9 @@ public static boolean evaluate(TopologyPredicate pred, Geometry a, Geometry b) { return rng.evaluate(pred, b); } - public static String relate(Geometry a, Geometry b) { - RelatePredicate rel = new RelatePredicate(); - RelateNG.evaluate(rel, a, b); - return rel.getIM().toString(); + private static boolean evaluate(RelatePredicate pred, Geometry a, Geometry b, BoundaryNodeRule bnRule) { + RelateNG rng = new RelateNG(a, bnRule); + return rng.evaluate(pred, b); } public static boolean relate(Geometry a, Geometry b, String mask) { @@ -68,23 +68,46 @@ public static boolean relate(Geometry a, Geometry b, String mask) { return RelateNG.evaluate(rel, a, b); } + public static IntersectionMatrix relate(Geometry a, Geometry b) { + RelatePredicate rel = new RelatePredicate(); + RelateNG.evaluate(rel, a, b); + return rel.getIM(); + } + public static IntersectionMatrix relate(Geometry a, Geometry b, BoundaryNodeRule bnRule) { + RelatePredicate rel = new RelatePredicate(); + RelateNG.evaluate(rel, a, b, bnRule); + return rel.getIM(); + } + + private RelateGeometry geomA; private boolean isPrepared = false; private EdgeSetMutualIntersector edgeMutualInt; + private BoundaryNodeRule boundaryNodeRule; public RelateNG(Geometry inputA) { - geomA = new RelateGeometry(inputA); + this(inputA, false, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE); + } + + public RelateNG(Geometry inputA, BoundaryNodeRule bnRule) { + this(inputA, false, bnRule); } public RelateNG(Geometry inputA, boolean isPrepared) { - geomA = new RelateGeometry(inputA, isPrepared); + this(inputA, isPrepared, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE); + } + + public RelateNG(Geometry inputA, boolean isPrepared, BoundaryNodeRule bnRule) { + this.boundaryNodeRule = bnRule; + geomA = new RelateGeometry(inputA, isPrepared, boundaryNodeRule); + this.boundaryNodeRule = bnRule; } public boolean evaluate(TopologyPredicate predicate, Geometry inputB) { - RelateGeometry geomB = new RelateGeometry(inputB); + RelateGeometry geomB = new RelateGeometry(inputB, boundaryNodeRule); int dimA = geomA.getDimension(); int dimB = geomB.getDimension(); @@ -287,4 +310,5 @@ private void computeEdgesMutual(List edgesB, Envelope envInt, Edg } + } diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java index adc5660f15..5f43c25ea1 100644 --- a/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java +++ b/modules/core/src/main/java/org/locationtech/jts/operation/relateng/TopologyBuilder.java @@ -49,6 +49,7 @@ public TopologyBuilder(TopologyPredicate predicate, RelateGeometry geomA, Relate */ private void initExteriorDims() { int dimAEff = geomA.getEffectiveDimension(); + //int dimABdyEff = geomA.getEffectiveDimension(); int dimBEff = geomB.getEffectiveDimension(); /** * For P/A case, the Area Int and Bdy intersect the Point exterior. diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGBoundaryNodeRuleTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGBoundaryNodeRuleTest.java index 6b262ad969..e076d4bc93 100644 --- a/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGBoundaryNodeRuleTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/operation/relateng/RelateNGBoundaryNodeRuleTest.java @@ -13,6 +13,8 @@ package org.locationtech.jts.operation.relateng; import org.locationtech.jts.algorithm.BoundaryNodeRule; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.IntersectionMatrix; import junit.textui.TestRunner; import test.jts.GeometryTestCase; @@ -42,7 +44,7 @@ public void testMultiLineStringSelfIntTouchAtEndpoint() String b = "LINESTRING (60 60, 20 60)"; // under EndPoint, A has a boundary node - A.bdy / B.bdy = 0 - runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FF1F00102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FF1F00102" ); } public void testLineStringSelfIntTouchAtEndpoint() @@ -51,8 +53,8 @@ public void testLineStringSelfIntTouchAtEndpoint() String b = "LINESTRING (60 60, 20 60)"; // results for both rules are the same - runRelateTest(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FF0102" ); - runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "F01FF0102" ); + runRelate(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FF0102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "F01FF0102" ); } public void testMultiLineStringTouchAtEndpoint() @@ -63,7 +65,7 @@ public void testMultiLineStringTouchAtEndpoint() // under Mod2, A has no boundary - A.int / B.bdy = 0 // runRelateTest(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FFF102" ); // under EndPoint, A has a boundary node - A.bdy / B.bdy = 0 - runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FF1F00102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FF1F00102" ); // under MultiValent, A has a boundary node but B does not - A.bdy / B.bdy = F and A.int // runRelateTest(a, b, BoundaryNodeRule.MULTIVALENT_ENDPOINT_BOUNDARY_RULE, "0F1FFF1F2" ); } @@ -74,11 +76,11 @@ public void testLineRingTouchAtEndpoints() String b = "LINESTRING (20 20, 20 100)"; // under Mod2, A has no boundary - A.int / B.bdy = 0 -// runRelateTest(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FFF102" ); + runRelate(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FFF102" ); // under EndPoint, A has a boundary node - A.bdy / B.bdy = 0 -// runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FF1F0F102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FF1F0F102" ); // under MultiValent, A has a boundary node but B does not - A.bdy / B.bdy = F and A.int - runRelateTest(a, b, BoundaryNodeRule.MULTIVALENT_ENDPOINT_BOUNDARY_RULE, "0F1FFF1F2" ); + runRelate(a, b, BoundaryNodeRule.MULTIVALENT_ENDPOINT_BOUNDARY_RULE, "FF10FF1F2" ); } public void testLineRingTouchAtEndpointAndInterior() @@ -87,9 +89,9 @@ public void testLineRingTouchAtEndpointAndInterior() String b = "LINESTRING (20 20, 40 100)"; // this is the same result as for the above test - runRelateTest(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FFF102" ); + runRelate(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "F01FFF102" ); // this result is different - the A node is now on the boundary, so A.bdy/B.ext = 0 - runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "F01FF0102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "F01FF0102" ); } public void testPolygonEmptyRing() @@ -98,10 +100,10 @@ public void testPolygonEmptyRing() String b = "LINESTRING (20 100, 20 220, 120 100, 20 100)"; // closed line has no boundary under SFS rule - runRelateTest(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "FFFFFF1F2" ); + runRelate(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "FFFFFF1F2" ); // closed line has boundary under ENDPOINT rule - runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FFFFFF102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FFFFFF102" ); } public void testPolygonEmptyMultiLineStringClosed() @@ -110,22 +112,20 @@ public void testPolygonEmptyMultiLineStringClosed() String b = "MULTILINESTRING ((0 0, 0 1), (0 1, 1 1, 1 0, 0 0))"; // closed line has no boundary under SFS rule - runRelateTest(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "FFFFFF1F2" ); + runRelate(a, b, BoundaryNodeRule.OGC_SFS_BOUNDARY_RULE, "FFFFFF1F2" ); // closed line has boundary under ENDPOINT rule - runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FFFFFF102" ); + runRelate(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FFFFFF102" ); } - void runRelateTest(String wkt1, String wkt2, BoundaryNodeRule bnRule, String expectedIM) + void runRelate(String wkt1, String wkt2, BoundaryNodeRule bnRule, String expectedIM) { - /* - Geometry g1 = rdr.read(wkt1); - Geometry g2 = rdr.read(wkt2); + Geometry g1 = read(wkt1); + Geometry g2 = read(wkt2); IntersectionMatrix im = RelateNG.relate(g1, g2, bnRule); String imStr = im.toString(); //System.out.println(imStr); assertTrue("Expected " + expectedIM + ", found " + im, im.matches(expectedIM)); - */ } }