diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/Centroid.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/Centroid.java index d8aa8558d4..50712ec4a0 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/Centroid.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/Centroid.java @@ -15,8 +15,6 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; /** @@ -92,22 +90,22 @@ private void add(Geometry geom) { if (geom.isEmpty()) return; - if (geom instanceof Point) { + if (geom instanceof GeometryCollection) { + GeometryCollection gc = (GeometryCollection) geom; + for (int i = 0; i < gc.getNumGeometries(); i++) { + add(gc.getGeometryN(i)); + } + } + else if (geom.getDimension() == 0) { addPoint(geom.getCoordinate()); } - else if (geom instanceof LineString) { + else if (geom.getDimension() == 1) { addLineSegments(geom.getCoordinates()); } - else if (geom instanceof Polygon) { + else if (geom.getDimension() == 2) { Polygon poly = (Polygon) geom; add(poly); } - else if (geom instanceof GeometryCollection) { - GeometryCollection gc = (GeometryCollection) geom; - for (int i = 0; i < gc.getNumGeometries(); i++) { - add(gc.getGeometryN(i)); - } - } } /** diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointLine.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointLine.java index 1971d5da6f..c550758083 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointLine.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointLine.java @@ -57,7 +57,7 @@ public Coordinate getInteriorPoint() */ private void addInterior(Geometry geom) { - if (geom instanceof LineString) { + if (geom.getDimension() == 1) { addInterior(geom.getCoordinates()); } else if (geom instanceof GeometryCollection) { diff --git a/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointPoint.java b/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointPoint.java index 71e14ce811..30afc1719a 100644 --- a/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointPoint.java +++ b/modules/core/src/main/java/org/locationtech/jts/algorithm/InteriorPointPoint.java @@ -15,7 +15,6 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryCollection; -import org.locationtech.jts.geom.Point; /** * Computes a point in the interior of an point geometry. @@ -44,15 +43,15 @@ public InteriorPointPoint(Geometry g) */ private void add(Geometry geom) { - if (geom instanceof Point) { - add(geom.getCoordinate()); - } - else if (geom instanceof GeometryCollection) { + if (geom instanceof GeometryCollection) { GeometryCollection gc = (GeometryCollection) geom; for (int i = 0; i < gc.getNumGeometries(); i++) { add(gc.getGeometryN(i)); } } + else if (geom.getDimension() == 0) { + add(geom.getCoordinate()); + } } private void add(Coordinate point) { diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java b/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java index 13624b3859..84768c38de 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/LineString.java @@ -102,11 +102,16 @@ public Coordinate getCoordinate() } public int getDimension() { - return 1; + // Test if geometry is non empty and has several distinct coordinates + if (new CoordinateList(points.toCoordinateArray(),false).size()==1) + return 0; + else + // valid or empty LineString return 1 + return 1; } public int getBoundaryDimension() { - if (isClosed()) { + if (isClosed() || getDimension() == 0) { return Dimension.FALSE; } return 0; diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/MultiLineString.java b/modules/core/src/main/java/org/locationtech/jts/geom/MultiLineString.java index 15ce502a0b..f7ec4dc50f 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/MultiLineString.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/MultiLineString.java @@ -55,10 +55,16 @@ public MultiLineString(LineString[] lineStrings, GeometryFactory factory) { super(lineStrings, factory); } + public int getDimension() { - return 1; + if (isEmpty()) + return 1; + else + // super will get the heighest dimension of components + return super.getDimension(); } + public int getBoundaryDimension() { if (isClosed()) { return Dimension.FALSE; diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/MultiPolygon.java b/modules/core/src/main/java/org/locationtech/jts/geom/MultiPolygon.java index 67df633248..41a4a09404 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/MultiPolygon.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/MultiPolygon.java @@ -67,7 +67,11 @@ public MultiPolygon(Polygon[] polygons, GeometryFactory factory) { } public int getDimension() { - return 2; + if (isEmpty()) + return 2; + else + // super will get the heighest dimension of components + return super.getDimension(); } public int getBoundaryDimension() { diff --git a/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java b/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java index bf389c6460..3ef47963c4 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java +++ b/modules/core/src/main/java/org/locationtech/jts/geom/Polygon.java @@ -164,11 +164,20 @@ public int getNumPoints() { } public int getDimension() { - return 2; + if (getExteriorRing().getDimension() == 0) + return 0; + else + // Does not handle the case of 1-dimension collapsed polygon + // if exteriorRing is empty, returns 2 (not sure why) + return 2; } public int getBoundaryDimension() { - return 1; + if (isEmpty()) + // Return the same dimension as an empty LinearRing + return 1; + else + return getExteriorRing().getDimension(); } public boolean isEmpty() { diff --git a/modules/core/src/main/java/org/locationtech/jts/geomgraph/GeometryGraph.java b/modules/core/src/main/java/org/locationtech/jts/geomgraph/GeometryGraph.java index caa8e83175..c583ffd840 100644 --- a/modules/core/src/main/java/org/locationtech/jts/geomgraph/GeometryGraph.java +++ b/modules/core/src/main/java/org/locationtech/jts/geomgraph/GeometryGraph.java @@ -292,6 +292,10 @@ private void addLineString(LineString line) if (coord.length < 2) { hasTooFewPoints = true; invalidPoint = coord[0]; + // If line is made of a single point, geometry is invalid (hasTooFewPoint), + // but the point must still be added to the graph in order to be able to be + // compute correct predicate (e.g. intersects) with another GeometryGraphe + addPoint(coord[0]); return; } diff --git a/modules/core/src/test/java/org/locationtech/jts/algorithm/InteriorPointTest.java b/modules/core/src/test/java/org/locationtech/jts/algorithm/InteriorPointTest.java index 1419157229..f4ff867675 100644 --- a/modules/core/src/test/java/org/locationtech/jts/algorithm/InteriorPointTest.java +++ b/modules/core/src/test/java/org/locationtech/jts/algorithm/InteriorPointTest.java @@ -89,4 +89,34 @@ private void checkInteriorPoint(Geometry g) assertTrue(g.contains(ip)); } + /** + public void testPointInteriorPoint() throws ParseException { + Geometry point = rdr.read("Point(10 10)"); + assertTrue(point.getInteriorPoint().equals(rdr.read("POINT(10 10)"))); + } + + public void testMultiPointInteriorPoint() throws ParseException { + Geometry point = rdr.read("MULTIPOINT ((60 300), (200 200), (240 240), (200 300), (40 140), (80 240), (140 240), (100 160), (140 200), (60 200))"); + assertTrue(point.getInteriorPoint().equals(rdr.read("POINT (140 240)"))); + } + + public void testRelate() throws ParseException { + Geometry point = rdr.read("POINT (10 10)"); + Geometry line = rdr.read("LINESTRING (10 10, 10 10)"); + assertTrue(point.equalsTopo(line)); + } + + public void testGeometryCollection() throws ParseException { + Geometry gc = rdr.read("GEOMETRYCOLLECTION (POLYGON ((10 10, 10 10, 10 10, 10 10)), \n" + + " LINESTRING (20 20, 30 30))"); + assertTrue(gc.getInteriorPoint().equals(rdr.read("POINT(20 20)"))); + } + + + public void testZeroLengthLineStringInteriorPoint() throws ParseException { + Geometry line = rdr.read("LineString(10 10, 10 10)"); + assertTrue(line.getInteriorPoint().equals(rdr.read("POINT(10 10)"))); + } + **/ + } diff --git a/modules/core/src/test/java/org/locationtech/jts/geom/CollapsedGeometryDimensionTest.java b/modules/core/src/test/java/org/locationtech/jts/geom/CollapsedGeometryDimensionTest.java new file mode 100644 index 0000000000..5c84d91499 --- /dev/null +++ b/modules/core/src/test/java/org/locationtech/jts/geom/CollapsedGeometryDimensionTest.java @@ -0,0 +1,118 @@ +package org.locationtech.jts.geom; + +import junit.framework.TestCase; +import junit.textui.TestRunner; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; + + +public class CollapsedGeometryDimensionTest extends TestCase { + + public static void main(String args[]) { + TestRunner.run(CollapsedGeometryDimensionTest.class); + } + + private GeometryFactory factory = new GeometryFactory(); + private WKTReader reader = new WKTReader(factory); + + public CollapsedGeometryDimensionTest(String name) { super(name); } + + public void testMock() + { + assertTrue(true); + } + + public void testPoint() throws ParseException + { + // empty + assertTrue(reader.read("Point Empty").getDimension() == 0); + // normal + assertTrue(reader.read("Point(0 0)").getDimension() == 0); + // collapsed + assertTrue(reader.read("Point Empty").getDimension() == 0); + } + + public void testLineString() throws ParseException + { + // empty + assertTrue(reader.read("LineString Empty").getDimension() == 1); + // normal (open/closed) + assertTrue(reader.read("LineString(0 0, 1 0)").getDimension() == 1); + assertTrue(reader.read("LineString(0 0, 1 0, 0.5 0.5, 0 0)").getDimension() == 1); + // collapsed + assertTrue(reader.read("LineString(0 0, 0 0)").getDimension() == 0); + } + + public void testPolygon() throws ParseException + { + // empty + assertTrue(reader.read("Polygon Empty").getDimension() == 2); + // normal + assertTrue(reader.read("Polygon((0 0, 1 0, 0.5 0.5, 0 0))").getDimension() == 2); + // collapsed in dimension 1 (not yet detected -> dimension 2) + assertTrue(reader.read("Polygon((0 0, 1 0, 2 0, 0 0))").getDimension() == 2); + // collapsed + assertTrue(reader.read("Polygon((0 0, 0 0, 0 0, 0 0))").getDimension() == 0); + // cannot be initialized (linear ring not closed) + //assertTrue(reader.read("Polygon((0 0, 1 0, 1 0, 1 0))").getDimension() == 1); + } + + public void testLinearRing() throws ParseException + { + // empty + assertTrue(reader.read("LinearRing Empty").getDimension() == 1); + // normal + assertTrue(reader.read("LinearRing(0 0, 1 0, 0.5 0.5, 0 0)").getDimension() == 1); + assertTrue(reader.read("LinearRing(0 0, 1 0, 2 0, 0 0)").getDimension() == 1); + // collapsed + assertTrue(reader.read("LinearRing(0 0, 0 0, 0 0, 0 0)").getDimension() == 0); + } + + public void testMultiPoint() throws ParseException + { + // empty + assertTrue(reader.read("MultiPoint Empty").getDimension() == 0); + // normal + assertTrue(reader.read("MultiPoint((0 0), (1 1))").getDimension() == 0); + // normal + assertTrue(reader.read("MultiPoint((0 0), (0 0))").getDimension() == 0); + } + + public void testMultiLineString() throws ParseException + { + // empty + assertTrue(reader.read("MultiLineString Empty").getDimension() == 1); + // normal (open/closed) + assertTrue(reader.read("MultiLineString((0 0, 1 0), (2 0, 3 0))").getDimension() == 1); + assertTrue(reader.read("MultiLineString((0 0, 1 0, 0.5 0.5, 0 0))").getDimension() == 1); + // partially collapsed + assertTrue(reader.read("MultiLineString((0 0, 0 0), (1 0, 2 0))").getDimension() == 1); + // collapsed + assertTrue(reader.read("MultiLineString((0 0, 0 0), (1 0, 1 0))").getDimension() == 0); + } + + public void testMultiPolygon() throws ParseException + { + // empty + assertTrue(reader.read("MultiPolygon Empty").getDimension() == 2); + // normal + assertTrue(reader.read("MultiPolygon(((0 0, 1 0, 0.5 0.5, 0 0)),((10 0, 11 0, 10.5 0.5, 10 0)))").getDimension() == 2); + // partially collapsed + assertTrue(reader.read("MultiPolygon(((0 0, 1 0, 0.5 0.5, 0 0)),((0 0, 0 0, 0 0, 0 0)))").getDimension() == 2); + // collapsed + assertTrue(reader.read("MultiPolygon(((0 0, 0 0, 0 0, 0 0)),((1 1, 1 1, 1 1, 1 1)))").getDimension() == 0); + } + + public void tesGeometryCollection() throws ParseException + { + // empty + assertTrue(reader.read("GeometryCollection Empty").getDimension() == 0); + // normal + assertTrue(reader.read("GeometryCollection (Point(0 0),LineString(0 0, 1 0)").getDimension() == 1); + assertTrue(reader.read("GeometryCollection (Point(0 0),LineString(0 0, 1 0), Polygon((0 0, 1 0, 0.5 0.5, 0 0))").getDimension() == 2); + // collapsed + assertTrue(reader.read("GeometryCollection (Point(0 0),LineString(0 0, 0 0)").getDimension() == 0); + assertTrue(reader.read("GeometryCollection (Point(0 0),Polygon((0 0, 0 0, 0 0, 0 0))").getDimension() == 0); + } + +} diff --git a/modules/core/src/test/java/org/locationtech/jts/operation/relate/ZeroLengthLineStringTest.java b/modules/core/src/test/java/org/locationtech/jts/operation/relate/ZeroLengthLineStringTest.java new file mode 100644 index 0000000000..cf8b3c6110 --- /dev/null +++ b/modules/core/src/test/java/org/locationtech/jts/operation/relate/ZeroLengthLineStringTest.java @@ -0,0 +1,135 @@ +package org.locationtech.jts.operation.relate; + +import junit.framework.TestCase; +import junit.textui.TestRunner; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.io.WKTReader; + +public class ZeroLengthLineStringTest extends TestCase { + + public static void main(String args[]) { + TestRunner.run(ZeroLengthLineStringTest.class); + } + + private GeometryFactory factory = new GeometryFactory(); + private WKTReader reader = new WKTReader(factory); + + public ZeroLengthLineStringTest(String name) + { + super(name); + } + + /** + * From JTS #345 + * + * 0-length LineString is invalid (not clear from the spec, refers + * to the ticket about this question) + * + * @throws Exception + */ + public void testZeroLengthLineStringInvalid() + throws Exception + { + String a = "LINESTRING (0 0, 0 0)"; + Geometry geom1 = reader.read(a); + assertTrue(!geom1.isValid()); + } + + + /** + * From JTS #345 + * + * Intersects a geom with itself should return true, even if geom + * is a 0-length (degenerated) LineString + * + * @throws Exception + */ + public void testIntersectsZeroLengthLineStringWithItself() + throws Exception + { + String a = "LINESTRING (0 0, 0 0)"; + Geometry geom = reader.read(a); + assertTrue(geom.intersects(geom)); + } + + /** + * From JTS #345 + * + * Intersects geom with a buffer around it should return true, + * even if geom is a 0-length (degenerated) LineString + * + * @throws Exception + */ + public void testIntersectsZeroLengthLineStringWithBuffer() + throws Exception + { + String a = "LINESTRING (0 0, 0 0)"; + Geometry geom = reader.read(a); + assertTrue(geom.intersects(geom.buffer(1.0))); + } + + /** + * From JTS #345 + * + * Boundary of a zero-length LineString is empty + * + * @throws Exception + */ + public void testZeroLengthLineStringBoundary() + throws Exception + { + String a = "LINESTRING (0 0, 0 0)"; + Geometry geom = reader.read(a); + assertTrue(geom.getBoundary().isEmpty()); + } + + /** + * From JTS #345 + * + * Intersects a valid LineString with a 0-dimensional LineString + * located on one of its boundary should return true + * + * @throws Exception + */ + public void testIntersectsBetweenLineStringAndItsBoundary() + throws Exception + { + String a = "LINESTRING (0 0, 1 0)"; + String b = "LINESTRING (0 0, 0 0)"; + Geometry geom1 = reader.read(a); + Geometry geom2 = reader.read(b); + assertTrue(geom1.intersects(geom2)); + } + + /** + * From JTS #345 + * + * WARNING touches between a LineString and a 0-length LineString lying + * on its boundary returns false but it should return true as for the + * punctal case. + * The test with 0-length linestring is deactivated. + * One way to return true as for the Point is to say that 0-length LineString + * has dimension 1. IMHO, it LineString#getDimension() should return one, but + * it breaks other tests which check that empty LineString#getDimension return + * 1 (which can probably be discussed). + * + * @throws Exception + */ + + public void testTouchesBetweenLineStringAndItsBoundary() + throws Exception + { + String a = "LINESTRING (0 0, 1 0)"; + String b = "LINESTRING (0 0, 0 0)"; + String c = "POINT (0 0)"; + Geometry geom1 = reader.read(a); + Geometry geom2 = reader.read(b); + Geometry geom3 = reader.read(c); + assertTrue(geom1.touches(geom2)); + assertTrue(geom2.touches(geom1)); + assertTrue(geom1.touches(geom3)); + assertTrue(geom3.touches(geom1)); + } + +} diff --git a/modules/tests/src/test/resources/testxml/general/TestInteriorPoint.xml b/modules/tests/src/test/resources/testxml/general/TestInteriorPoint.xml index 0d765cacab..9368171187 100644 --- a/modules/tests/src/test/resources/testxml/general/TestInteriorPoint.xml +++ b/modules/tests/src/test/resources/testxml/general/TestInteriorPoint.xml @@ -103,7 +103,9 @@ GEOMETRYCOLLECTION (POLYGON ((10 10, 10 10, 10 10, 10 10)), LINESTRING (20 20, 30 30)) - POINT (10 10) + + POINT (20 20) diff --git a/modules/tests/src/test/resources/testxml/validate/TestRelatePL.xml b/modules/tests/src/test/resources/testxml/validate/TestRelatePL.xml index 2e11fb7b9e..d24c7f2f43 100644 --- a/modules/tests/src/test/resources/testxml/validate/TestRelatePL.xml +++ b/modules/tests/src/test/resources/testxml/validate/TestRelatePL.xml @@ -40,7 +40,9 @@ true false false - false + + true true false false