diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/BoundaryOp.java b/modules/core/src/main/java/org/locationtech/jts/operation/BoundaryOp.java
index 427aba9f36..e4f165f76e 100644
--- a/modules/core/src/main/java/org/locationtech/jts/operation/BoundaryOp.java
+++ b/modules/core/src/main/java/org/locationtech/jts/operation/BoundaryOp.java
@@ -21,6 +21,7 @@
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateArrays;
import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.Dimension;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.GeometryFactory;
@@ -69,6 +70,37 @@ public static Geometry getBoundary(Geometry g, BoundaryNodeRule bnRule)
return bop.getBoundary();
}
+ /**
+ * Tests if a geometry has a boundary (it is non-empty).
+ * The semantics are:
+ *
+ * - Empty geometries do not have boundaries.
+ *
- Points do not have boundaries.
+ *
- For linear geometries the existence of the boundary
+ * is determined by the {@link BoundaryNodeRule}.
+ *
- Non-empty polygons always have a boundary.
+ *
+ *
+ * @param geom the geometry providing the boundary
+ * @param boundaryNodeRule the Boundary Node Rule to use
+ * @return true if the boundary exists
+ */
+ public static boolean hasBoundary(Geometry geom, BoundaryNodeRule boundaryNodeRule) {
+ // Note that this does not handle geometry collections with a non-empty linear element
+ if (geom.isEmpty()) return false;
+ switch (geom.getDimension()) {
+ case Dimension.P: return false;
+ /**
+ * Linear geometries might have an empty boundary due to boundary node rule.
+ */
+ case Dimension.L:
+ Geometry boundary = BoundaryOp.getBoundary(geom, boundaryNodeRule);
+ return ! boundary.isEmpty();
+ case Dimension.A: return true;
+ }
+ return true;
+ }
+
private Geometry geom;
private GeometryFactory geomFact;
private BoundaryNodeRule bnRule;
diff --git a/modules/core/src/main/java/org/locationtech/jts/operation/relate/RelateComputer.java b/modules/core/src/main/java/org/locationtech/jts/operation/relate/RelateComputer.java
index 05f06fbb66..f559a1b0ef 100644
--- a/modules/core/src/main/java/org/locationtech/jts/operation/relate/RelateComputer.java
+++ b/modules/core/src/main/java/org/locationtech/jts/operation/relate/RelateComputer.java
@@ -21,10 +21,12 @@
import java.util.Iterator;
import java.util.List;
+import org.locationtech.jts.algorithm.BoundaryNodeRule;
import org.locationtech.jts.algorithm.LineIntersector;
import org.locationtech.jts.algorithm.PointLocator;
import org.locationtech.jts.algorithm.RobustLineIntersector;
import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Dimension;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.IntersectionMatrix;
import org.locationtech.jts.geom.Location;
@@ -36,6 +38,7 @@
import org.locationtech.jts.geomgraph.Node;
import org.locationtech.jts.geomgraph.NodeMap;
import org.locationtech.jts.geomgraph.index.SegmentIntersector;
+import org.locationtech.jts.operation.BoundaryOp;
import org.locationtech.jts.util.Assert;
/**
@@ -79,7 +82,7 @@ public IntersectionMatrix computeIM()
// if the Geometries don't overlap there is nothing to do
if (! arg[0].getGeometry().getEnvelopeInternal().intersects(
arg[1].getGeometry().getEnvelopeInternal()) ) {
- computeDisjointIM(im);
+ computeDisjointIM(im, arg[0].getBoundaryNodeRule());
return im;
}
arg[0].computeSelfNodes(li, false);
@@ -268,21 +271,56 @@ private void labelIntersectionNodes(int argIndex)
/**
* If the Geometries are disjoint, we need to enter their dimension and
* boundary dimension in the Ext rows in the IM
+ *
+ * @param boundaryNodeRule the Boundary Node Rule to use
*/
- private void computeDisjointIM(IntersectionMatrix im)
+ private void computeDisjointIM(IntersectionMatrix im, BoundaryNodeRule boundaryNodeRule)
{
Geometry ga = arg[0].getGeometry();
if (! ga.isEmpty()) {
im.set(Location.INTERIOR, Location.EXTERIOR, ga.getDimension());
- im.set(Location.BOUNDARY, Location.EXTERIOR, ga.getBoundaryDimension());
+ im.set(Location.BOUNDARY, Location.EXTERIOR, getBoundaryDim(ga, boundaryNodeRule));
}
Geometry gb = arg[1].getGeometry();
if (! gb.isEmpty()) {
im.set(Location.EXTERIOR, Location.INTERIOR, gb.getDimension());
- im.set(Location.EXTERIOR, Location.BOUNDARY, gb.getBoundaryDimension());
+ im.set(Location.EXTERIOR, Location.BOUNDARY, getBoundaryDim(gb, boundaryNodeRule));
}
}
-
+
+ /**
+ * Compute the IM entry for the intersection of the boundary
+ * of a geometry with the Exterior.
+ * This is the nominal dimension of the boundary
+ * unless the boundary is empty, in which case it is {@link Dimension#FALSE}.
+ * For linear geometries the Boundary Node Rule determines
+ * whether the boundary is empty.
+ *
+ * @param geom the geometry providing the boundary
+ * @param boundaryNodeRule the Boundary Node Rule to use
+ * @return the IM dimension entry
+ */
+ private static int getBoundaryDim(Geometry geom, BoundaryNodeRule boundaryNodeRule)
+ {
+ /**
+ * If the geometry has a non-empty boundary
+ * the intersection is the nominal dimension.
+ */
+ if (BoundaryOp.hasBoundary(geom, boundaryNodeRule)) {
+ /**
+ * special case for lines, since Geometry.getBoundaryDimension is not aware
+ * of Boundary Node Rule.
+ */
+ if (geom.getDimension() == 1)
+ return Dimension.P;
+ return geom.getBoundaryDimension();
+ }
+ /**
+ * Otherwise intersection is F
+ */
+ return Dimension.FALSE;
+ }
+
private void labelNodeEdges()
{
for (Iterator ni = nodes.iterator(); ni.hasNext(); ) {
diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/BoundaryTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/BoundaryTest.java
index c9dc607fb1..a458508674 100644
--- a/modules/core/src/test/java/org/locationtech/jts/operation/BoundaryTest.java
+++ b/modules/core/src/test/java/org/locationtech/jts/operation/BoundaryTest.java
@@ -17,8 +17,8 @@
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
-import junit.framework.TestCase;
import junit.textui.TestRunner;
+import test.jts.GeometryTestCase;
/**
@@ -28,7 +28,7 @@
* @version 1.7
*/
public class BoundaryTest
- extends TestCase
+ extends GeometryTestCase
{
private static final double TOLERANCE = 0.00005;
@@ -121,8 +121,48 @@ public void testRing()
"POINT (100 100)" );
}
-
-
+ public void testHasBoundaryPoint()
+ throws Exception
+ {
+ checkHasBoundary( "POINT (0 0)", false);
+ }
+
+ public void testHasBoundaryPointEmpty()
+ throws Exception
+ {
+ checkHasBoundary( "POINT EMPTY", false);
+ }
+
+ public void testHasBoundaryRingClosed()
+ throws Exception
+ {
+ checkHasBoundary( "LINESTRING (100 100, 20 20, 200 20, 100 100)", false);
+ }
+
+ public void testHasBoundaryMultiLineStringClosed()
+ throws Exception
+ {
+ checkHasBoundary( "MULTILINESTRING ((0 0, 0 1), (0 1, 1 1, 1 0, 0 0))", false);
+ }
+
+ public void testHasBoundaryMultiLineStringOpen()
+ throws Exception
+ {
+ checkHasBoundary( "MULTILINESTRING ((0 0, 0 2), (0 1, 1 1, 1 0, 0 0))");
+ }
+
+ public void testHasBoundaryPolygon()
+ throws Exception
+ {
+ checkHasBoundary( "POLYGON ((1 9, 9 9, 9 1, 1 1, 1 9))");
+ }
+
+ public void testHasBoundaryPolygonEmpty()
+ throws Exception
+ {
+ checkHasBoundary( "POLYGON EMPTY", false);
+ }
+
private void runBoundaryTest(String wkt, BoundaryNodeRule bnRule, String wktExpected)
throws ParseException
{
@@ -136,4 +176,20 @@ private void runBoundaryTest(String wkt, BoundaryNodeRule bnRule, String wktExpe
assertTrue(boundary.equalsExact(expected));
}
+ private void checkHasBoundary(String wkt)
+ {
+ checkHasBoundary(wkt, BoundaryNodeRule.MOD2_BOUNDARY_RULE, true);
+ }
+
+ private void checkHasBoundary(String wkt, boolean expected)
+ {
+ checkHasBoundary(wkt, BoundaryNodeRule.MOD2_BOUNDARY_RULE, expected);
+ }
+
+ private void checkHasBoundary(String wkt, BoundaryNodeRule bnRule, boolean expected)
+ {
+ Geometry g = read(wkt);
+ assertEquals(expected, BoundaryOp.hasBoundary(g, bnRule));
+ }
+
}
diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relate/RelateBoundaryNodeRuleTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relate/RelateBoundaryNodeRuleTest.java
index 91aeff0e09..18f2e0a822 100644
--- a/modules/core/src/test/java/org/locationtech/jts/operation/relate/RelateBoundaryNodeRuleTest.java
+++ b/modules/core/src/test/java/org/locationtech/jts/operation/relate/RelateBoundaryNodeRuleTest.java
@@ -105,6 +105,32 @@ public void testLineRingTouchAtEndpointAndInterior()
runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "F01FF0102" );
}
+ public void testPolygonEmptyRing()
+ throws Exception
+ {
+ String a = "POLYGON EMPTY";
+ 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" );
+
+ // closed line has boundary under ENDPOINT rule
+ runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FFFFFF102" );
+ }
+
+ public void testPolygonEmptyMultiLineStringClosed()
+ throws Exception
+ {
+ String a = "POLYGON EMPTY";
+ 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" );
+
+ // closed line has boundary under ENDPOINT rule
+ runRelateTest(a, b, BoundaryNodeRule.ENDPOINT_BOUNDARY_RULE, "FFFFFF102" );
+ }
+
void runRelateTest(String wkt1, String wkt2, BoundaryNodeRule bnRule, String expectedIM)
throws ParseException
{
@@ -113,6 +139,6 @@ void runRelateTest(String wkt1, String wkt2, BoundaryNodeRule bnRule, String exp
IntersectionMatrix im = RelateOp.relate(g1, g2, bnRule);
String imStr = im.toString();
//System.out.println(imStr);
- assertTrue(im.matches(expectedIM));
+ assertTrue("Expected " + expectedIM + ", found " + im, im.matches(expectedIM));
}
}
diff --git a/modules/tests/src/test/resources/testxml/general/TestRelateLA.xml b/modules/tests/src/test/resources/testxml/general/TestRelateLA.xml
index 8872ace71b..acc895468f 100644
--- a/modules/tests/src/test/resources/testxml/general/TestRelateLA.xml
+++ b/modules/tests/src/test/resources/testxml/general/TestRelateLA.xml
@@ -187,4 +187,30 @@
+
+LA - closed line / empty polygon
+
+ LINESTRING(110 60, 20 150, 200 150, 110 60)
+
+
+ POLYGON EMPTY
+
+
+ true
+
+
+
+
+LA - closed multiline / empty polygon
+
+ MULTILINESTRING ((0 0, 0 1), (0 1, 1 1, 1 0, 0 0))
+
+
+ POLYGON EMPTY
+
+
+ true
+
+
+
diff --git a/modules/tests/src/test/resources/testxml/general/TestRelateLL.xml b/modules/tests/src/test/resources/testxml/general/TestRelateLL.xml
index 7606d01ff5..4c05b66642 100644
--- a/modules/tests/src/test/resources/testxml/general/TestRelateLL.xml
+++ b/modules/tests/src/test/resources/testxml/general/TestRelateLL.xml
@@ -307,5 +307,17 @@
+
+LA - closed multiline / empty line
+
+ MULTILINESTRING ((0 0, 0 1), (0 1, 1 1, 1 0, 0 0))
+
+
+ LINESTRING EMPTY
+
+
+ true
+
+