From 9b185bbece6b3110d4f860c7c42187cf55061662 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 19 Jan 2026 21:39:27 +0100 Subject: [PATCH 01/11] handle GEOMETRYCOLLECTION simple(-r) cases with either only points, only lines or ony polygons are in the collection --- .../planetiler/geo/GeometryType.java | 14 ++ .../planetiler/reader/SimpleFeature.java | 61 ++++++++- .../planetiler/reader/SimpleFeatureTest.java | 122 ++++++++++++++++-- .../render/FeatureRendererTest.java | 87 +++++++++---- 4 files changed, 246 insertions(+), 38 deletions(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java index 942b82ad3e..0c4de6b972 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java @@ -7,6 +7,7 @@ import java.util.Locale; import java.util.regex.Pattern; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.Lineal; import org.locationtech.jts.geom.Polygonal; import org.locationtech.jts.geom.Puntal; @@ -37,6 +38,19 @@ public Expression featureTest() { } public static GeometryType typeOf(Geometry geom) { + if (geom instanceof GeometryCollection collection && collection.getNumGeometries() >= 1) { + var result = typeOfPrimitive(collection.getGeometryN(0)); + for (int i = 1; i < collection.getNumGeometries(); i++) { + if (!result.equals(typeOfPrimitive(collection.getGeometryN(1)))) { + return UNKNOWN; + } + } + return result; + } + return typeOfPrimitive(geom); + } + + private static GeometryType typeOfPrimitive(Geometry geom) { return geom instanceof Puntal ? POINT : geom instanceof Lineal ? LINE : geom instanceof Polygonal ? POLYGON : UNKNOWN; } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java index 995886933f..b2452313ed 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Objects; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.Lineal; import org.locationtech.jts.geom.MultiLineString; @@ -191,19 +192,73 @@ public Map tags() { return tags; } + private boolean isCollectionOfPoints() { + Geometry geom = latLonGeometry != null ? latLonGeometry : worldGeometry; + if (geom instanceof GeometryCollection collection) { + for (int i = 0; i < collection.getNumGeometries(); i++) { + if (!isPoint(collection.getGeometryN(i))) { + return false; + } + } + return true; + } else { + return false; + } + } + + private static boolean isPoint(Geometry geom) { + return geom instanceof Puntal; + } + @Override public boolean isPoint() { - return latLonGeometry instanceof Puntal || worldGeometry instanceof Puntal; + return isPoint(latLonGeometry != null ? latLonGeometry : worldGeometry) || isCollectionOfPoints(); + } + + private boolean isCollectionOfPolygons() { + Geometry geom = latLonGeometry != null ? latLonGeometry : worldGeometry; + if (geom instanceof GeometryCollection collection) { + for (int i = 0; i < collection.getNumGeometries(); i++) { + if (!canBePolygon(collection.getGeometryN(i))) { + return false; + } + } + return true; + } else { + return false; + } + } + + private static boolean canBePolygon(Geometry geom) { + return geom instanceof Polygonal; } @Override public boolean canBePolygon() { - return latLonGeometry instanceof Polygonal || worldGeometry instanceof Polygonal; + return canBePolygon(latLonGeometry != null ? latLonGeometry : worldGeometry) || isCollectionOfPolygons(); + } + + private boolean isCollectionOfLines() { + Geometry geom = latLonGeometry != null ? latLonGeometry : worldGeometry; + if (geom instanceof GeometryCollection collection) { + for (int i = 0; i < collection.getNumGeometries(); i++) { + if (!canBeLine(collection.getGeometryN(i))) { + return false; + } + } + return true; + } else { + return false; + } + } + + private static boolean canBeLine(Geometry geom) { + return geom instanceof Lineal; } @Override public boolean canBeLine() { - return latLonGeometry instanceof Lineal || worldGeometry instanceof Lineal; + return canBeLine(latLonGeometry != null ? latLonGeometry : worldGeometry) || isCollectionOfLines(); } @Override diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java index d64ad323d8..654d3230c6 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java @@ -8,8 +8,13 @@ import com.onthegomap.planetiler.TestUtils; import com.onthegomap.planetiler.geo.GeoUtils; import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.MultiPolygon; class SimpleFeatureTest { @@ -48,10 +53,20 @@ void testFromWorldGeom() { assertSameNormalizedFeature(worldGeom, TestUtils.round(GeoUtils.latLonToWorldCoords(feature.latLonGeometry()))); } - @Test - void testIsLine() { - var world = SimpleFeature.fromWorldGeometry(newLineString(0, 0, 1, 1)); - var latLon = SimpleFeature.fromLatLonGeometry(newLineString(0, 0, 1, 1)); + static List lines() { + var line = newLineString(0, 0, 1, 1); + var lines = List.of(line); + return List.of( + line, + GeoUtils.JTS_FACTORY.createGeometryCollection(lines.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("lines") + void testIsLine(Geometry line) { + var world = SimpleFeature.fromWorldGeometry(line); + var latLon = SimpleFeature.fromLatLonGeometry(line); assertTrue(world.canBeLine()); assertTrue(latLon.canBeLine()); @@ -62,10 +77,20 @@ void testIsLine() { assertFalse(latLon.isPoint()); } - @Test - void testIsPolygon() { - var world = SimpleFeature.fromWorldGeometry(newPolygon(0, 0, 1, 1, 1, 0, 0, 0)); - var latLon = SimpleFeature.fromLatLonGeometry(newPolygon(0, 0, 1, 1, 1, 0, 0, 0)); + static List polygons() { + var polygon = newPolygon(0, 0, 1, 1, 1, 0, 0, 0); + var polygons = List.of(polygon); + return List.of( + polygon, + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("polygons") + void testIsPolygon(Geometry polygon) { + var world = SimpleFeature.fromWorldGeometry(polygon); + var latLon = SimpleFeature.fromLatLonGeometry(polygon); assertFalse(world.canBeLine()); assertFalse(latLon.canBeLine()); @@ -76,10 +101,20 @@ void testIsPolygon() { assertFalse(latLon.isPoint()); } - @Test - void testIsPoint() { - var world = SimpleFeature.fromWorldGeometry(newPoint(0, 0)); - var latLon = SimpleFeature.fromLatLonGeometry(newPoint(0, 0)); + static List points() { + var point = newPoint(0, 0); + var points = List.of(point); + return List.of( + point, + GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("points") + void testIsPoint(Geometry point) { + var world = SimpleFeature.fromWorldGeometry(point); + var latLon = SimpleFeature.fromLatLonGeometry(point); assertFalse(world.canBeLine()); assertFalse(latLon.canBeLine()); @@ -89,4 +124,67 @@ void testIsPoint() { assertTrue(world.isPoint()); assertTrue(latLon.isPoint()); } + + static List mixedCollection() { + var point = newPoint(0, 0); + var line = newLineString(0, 0, 1, 1); + var polygon = newPolygon(0, 0, 1, 1, 1, 0, 0, 0); + var pl = List.of(point, line); + var lp = List.of(line, polygon); + var plp = List.of(point, line, polygon); + return List.of( + GeoUtils.JTS_FACTORY.createGeometryCollection(pl.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(lp.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(plp.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("mixedCollection") + void testMixedCollections(Geometry point) { + var world = SimpleFeature.fromWorldGeometry(point); + var latLon = SimpleFeature.fromLatLonGeometry(point); + assertFalse(world.canBeLine()); + assertFalse(latLon.canBeLine()); + + assertFalse(world.canBePolygon()); + assertFalse(latLon.canBePolygon()); + + assertFalse(world.isPoint()); + assertFalse(latLon.isPoint()); + } + + static Stream collections() { + var point1 = newPoint(0, 0); + var point2 = newPoint(1, 1); + var points1 = List.of(point1); + var points2 = List.of(point1, point2); + + var line1 = newLineString(0, 0, 1, 1); + var line2 = newLineString(1, 1, 0, 0); + var lines1 = List.of(line1); + var lines2 = List.of(line1, line2); + + var polygon1 = newPolygon(0, 0, 0, 1, 1, 1, 1, 0, 0, 0); + var polygon2 = newPolygon(0.25, 0.25, 0.25, 0.75, 0.75, 0.75, 0.75, 0.25, 0.25, 0.25); + var polygons1 = List.of(polygon1); + var polygons2 = List.of(polygon1, polygon2); + + return Stream.of( + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(new Geometry[0])), 0, 0), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(new Geometry[0])), 0.5, 0.5) + ); + } + + @ParameterizedTest + @MethodSource("collections") + void testCollections(Geometry gc, double expectedX, double expectedY) { + var centroid = gc.getCentroid(); + assertEquals(expectedX, centroid.getX(), 1e-5); + assertEquals(expectedY, centroid.getY(), 1e-5); + } } diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java index 834991f27e..f5ea916b27 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateXY; @@ -102,9 +103,19 @@ void testEmptyGeometry() { * POINT TESTS */ - @Test - void testSinglePoint() { - var feature = pointFeature(newPoint(0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2)) + static List points() { + var point = newPoint(0.5 + Z14_WIDTH / 2, 0.5 + Z14_WIDTH / 2); + var points = List.of(point); + return List.of( + point, + GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("points") + void testSinglePoint(Geometry point) { + var feature = pointFeature(point) .setZoomRange(14, 14); assertSameNormalizedFeatures(Map.of( TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of( @@ -232,12 +243,24 @@ void testZ0FullTileBuffer() { ), renderGeometry(feature)); } - @Test - void testMultipointNoLabelGrid() { - var feature = pointFeature(newMultiPoint( - newPoint(0.25, 0.25), - newPoint(0.25 + 1d / 256, 0.25 + 1d / 256) - )) + static List multiPoints() { + var point1 = newPoint(0.25, 0.25); + var point2 = newPoint(0.25 + 1d / 256, 0.25 + 1d / 256); + //var points = List.of(point1, point2); + return List.of( + newMultiPoint(point1, point2) + /* For now disabled since that generates set of two points, while multipoint would be more logical, given that + * multiline is generated for collection of lines and multi-polygon for collection of polygons. + * TODO: Discuss whether it is OK as is or tweak other parts of planetiler-core to generate multipoint. + GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0])) + */ + ); + } + + @ParameterizedTest + @MethodSource("multiPoints") + void testMultipointNoLabelGrid(Geometry multiPoint) { + var feature = pointFeature(multiPoint) .setZoomRange(0, 1) .setBufferPixels(4); assertSameNormalizedFeatures(Map.of( @@ -322,13 +345,23 @@ private FeatureCollector.Feature lineFeature(Geometry geom) { return collector(geom).line("layer"); } - @Test - void testSplitLineFeatureSingleTile() { + static List lines() { double z14hypot = Math.sqrt(Z14_WIDTH * Z14_WIDTH); - var feature = lineFeature(newLineString( + var line = newLineString( 0.5 + z14hypot / 4, 0.5 + z14hypot / 4, 0.5 + z14hypot * 3 / 4, 0.5 + z14hypot * 3 / 4 - )) + ); + var lines = List.of(line); + return List.of( + line, + GeoUtils.JTS_FACTORY.createGeometryCollection(lines.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("lines") + void testSplitLineFeatureSingleTile(Geometry line) { + var feature = lineFeature(line) .setZoomRange(14, 14) .setBufferPixels(8); assertExactSameFeatures(Map.of( @@ -587,16 +620,24 @@ private FeatureCollector.Feature polygonFeature(Geometry geom) { return collector(geom).polygon("layer"); } - @Test - void testSimpleTriangleCCW() { - var feature = polygonFeature( - newPolygon( - 0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10, - 0.5 + Z14_PX * 20, 0.5 + Z14_PX * 10, - 0.5 + Z14_PX * 10, 0.5 + Z14_PX * 20, - 0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10 - ) - ) + static List polygons() { + var polygon = newPolygon( + 0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10, + 0.5 + Z14_PX * 20, 0.5 + Z14_PX * 10, + 0.5 + Z14_PX * 10, 0.5 + Z14_PX * 20, + 0.5 + Z14_PX * 10, 0.5 + Z14_PX * 10 + ); + var polygons = List.of(polygon); + return List.of( + polygon, + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons.toArray(new Geometry[0])) + ); + } + + @ParameterizedTest + @MethodSource("polygons") + void testSimpleTriangleCCW(Geometry polygon) { + var feature = polygonFeature(polygon) .setMinPixelSize(1) .setZoomRange(14, 14) .setBufferPixels(0); From 8e3d10294d25ac0c8c29e5aaa5611a8ce0ad6fcd Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 14:19:07 +0100 Subject: [PATCH 02/11] fixed typo in geometry iteration --- .../main/java/com/onthegomap/planetiler/geo/GeometryType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java index 0c4de6b972..04fcc9d5ed 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java @@ -41,7 +41,7 @@ public static GeometryType typeOf(Geometry geom) { if (geom instanceof GeometryCollection collection && collection.getNumGeometries() >= 1) { var result = typeOfPrimitive(collection.getGeometryN(0)); for (int i = 1; i < collection.getNumGeometries(); i++) { - if (!result.equals(typeOfPrimitive(collection.getGeometryN(1)))) { + if (!result.equals(typeOfPrimitive(collection.getGeometryN(i)))) { return UNKNOWN; } } From e1d4cc508fff9567b6780bfaef66afd71914e229 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 15:28:50 +0100 Subject: [PATCH 03/11] handle also collections in collections --- .../main/java/com/onthegomap/planetiler/geo/GeometryType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java index 04fcc9d5ed..a87132a692 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeometryType.java @@ -39,9 +39,9 @@ public Expression featureTest() { public static GeometryType typeOf(Geometry geom) { if (geom instanceof GeometryCollection collection && collection.getNumGeometries() >= 1) { - var result = typeOfPrimitive(collection.getGeometryN(0)); + var result = typeOf(collection.getGeometryN(0)); for (int i = 1; i < collection.getNumGeometries(); i++) { - if (!result.equals(typeOfPrimitive(collection.getGeometryN(i)))) { + if (!result.equals(typeOf(collection.getGeometryN(i)))) { return UNKNOWN; } } From c15076d6aab28948c775d8aa7efca5d89431d38a Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 15:33:27 +0100 Subject: [PATCH 04/11] handle also collections in collections --- .../planetiler/reader/SimpleFeatureTest.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java index 654d3230c6..6e00093cec 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java @@ -8,6 +8,7 @@ import com.onthegomap.planetiler.TestUtils; import com.onthegomap.planetiler.geo.GeoUtils; import java.util.List; +import java.util.Set; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -135,7 +136,12 @@ static List mixedCollection() { return List.of( GeoUtils.JTS_FACTORY.createGeometryCollection(pl.toArray(new Geometry[0])), GeoUtils.JTS_FACTORY.createGeometryCollection(lp.toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(plp.toArray(new Geometry[0])) + GeoUtils.JTS_FACTORY.createGeometryCollection(plp.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(point).toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(line).toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(polygon).toArray(new Geometry[0])) + ).toArray(new Geometry[0])) ); } @@ -173,10 +179,22 @@ static Stream collections() { return Stream.of( Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(new Geometry[0])), 0, 0), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(new Geometry[0])) + ).toArray(new Geometry[0])), 1.0 / 3.0, 1.0 / 3.0), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(new Geometry[0])), 0.5, 0.5), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(new Geometry[0])) + ).toArray(new Geometry[0])), 0.5, 0.5), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(new Geometry[0])), 0.5, 0.5), - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(new Geometry[0])), 0.5, 0.5) + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(new Geometry[0])) + ).toArray(new Geometry[0])), 0.5, 0.5) ); } From b04ee562d30ee2599e5053668e6b60e8aa8a6d21 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 15:34:38 +0100 Subject: [PATCH 05/11] re-use GeometryType.typeof() in SimpleFeature's isPoint(), canBePolygon() and canBeLine() --- .../planetiler/reader/SimpleFeature.java | 64 ++----------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java index b2452313ed..af573706cd 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SimpleFeature.java @@ -2,6 +2,7 @@ import com.onthegomap.planetiler.VectorTile; import com.onthegomap.planetiler.geo.GeoUtils; +import com.onthegomap.planetiler.geo.GeometryType; import com.onthegomap.planetiler.reader.osm.OsmElement; import com.onthegomap.planetiler.reader.osm.OsmReader; import com.onthegomap.planetiler.reader.osm.OsmRelationInfo; @@ -10,12 +11,9 @@ import java.util.Map; import java.util.Objects; import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.Lineal; import org.locationtech.jts.geom.MultiLineString; import org.locationtech.jts.geom.Polygonal; -import org.locationtech.jts.geom.Puntal; /** * An input feature read from a data source with geometry and tags known at creation-time. @@ -192,73 +190,19 @@ public Map tags() { return tags; } - private boolean isCollectionOfPoints() { - Geometry geom = latLonGeometry != null ? latLonGeometry : worldGeometry; - if (geom instanceof GeometryCollection collection) { - for (int i = 0; i < collection.getNumGeometries(); i++) { - if (!isPoint(collection.getGeometryN(i))) { - return false; - } - } - return true; - } else { - return false; - } - } - - private static boolean isPoint(Geometry geom) { - return geom instanceof Puntal; - } - @Override public boolean isPoint() { - return isPoint(latLonGeometry != null ? latLonGeometry : worldGeometry) || isCollectionOfPoints(); - } - - private boolean isCollectionOfPolygons() { - Geometry geom = latLonGeometry != null ? latLonGeometry : worldGeometry; - if (geom instanceof GeometryCollection collection) { - for (int i = 0; i < collection.getNumGeometries(); i++) { - if (!canBePolygon(collection.getGeometryN(i))) { - return false; - } - } - return true; - } else { - return false; - } - } - - private static boolean canBePolygon(Geometry geom) { - return geom instanceof Polygonal; + return GeometryType.POINT.equals(GeometryType.typeOf(latLonGeometry != null ? latLonGeometry : worldGeometry)); } @Override public boolean canBePolygon() { - return canBePolygon(latLonGeometry != null ? latLonGeometry : worldGeometry) || isCollectionOfPolygons(); - } - - private boolean isCollectionOfLines() { - Geometry geom = latLonGeometry != null ? latLonGeometry : worldGeometry; - if (geom instanceof GeometryCollection collection) { - for (int i = 0; i < collection.getNumGeometries(); i++) { - if (!canBeLine(collection.getGeometryN(i))) { - return false; - } - } - return true; - } else { - return false; - } - } - - private static boolean canBeLine(Geometry geom) { - return geom instanceof Lineal; + return GeometryType.POLYGON.equals(GeometryType.typeOf(latLonGeometry != null ? latLonGeometry : worldGeometry)); } @Override public boolean canBeLine() { - return canBeLine(latLonGeometry != null ? latLonGeometry : worldGeometry) || isCollectionOfLines(); + return GeometryType.LINE.equals(GeometryType.typeOf(latLonGeometry != null ? latLonGeometry : worldGeometry)); } @Override From 2ee806a07cae9b4fc2ab2ebab4a7eec6d7835293 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 15:36:22 +0100 Subject: [PATCH 06/11] improved toArray() calls --- .../planetiler/reader/SimpleFeatureTest.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java index 6e00093cec..a11b0309d5 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/SimpleFeatureTest.java @@ -59,7 +59,7 @@ static List lines() { var lines = List.of(line); return List.of( line, - GeoUtils.JTS_FACTORY.createGeometryCollection(lines.toArray(new Geometry[0])) + GeoUtils.JTS_FACTORY.createGeometryCollection(lines.toArray(Geometry[]::new)) ); } @@ -83,7 +83,7 @@ static List polygons() { var polygons = List.of(polygon); return List.of( polygon, - GeoUtils.JTS_FACTORY.createGeometryCollection(polygons.toArray(new Geometry[0])) + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons.toArray(Geometry[]::new)) ); } @@ -107,7 +107,7 @@ static List points() { var points = List.of(point); return List.of( point, - GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0])) + GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(Geometry[]::new)) ); } @@ -134,14 +134,14 @@ static List mixedCollection() { var lp = List.of(line, polygon); var plp = List.of(point, line, polygon); return List.of( - GeoUtils.JTS_FACTORY.createGeometryCollection(pl.toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(lp.toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(plp.toArray(new Geometry[0])), + GeoUtils.JTS_FACTORY.createGeometryCollection(pl.toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(lp.toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(plp.toArray(Geometry[]::new)), GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( - GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(point).toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(line).toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(polygon).toArray(new Geometry[0])) - ).toArray(new Geometry[0])) + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(point).toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(line).toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(polygon).toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)) ); } @@ -177,24 +177,24 @@ static Stream collections() { var polygons2 = List.of(polygon1, polygon2); return Stream.of( - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(new Geometry[0])), 0, 0), - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(new Geometry[0])), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(Geometry[]::new)), 0, 0), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(Geometry[]::new)), 0.5, 0.5), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( - GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(new Geometry[0])) - ).toArray(new Geometry[0])), 1.0 / 3.0, 1.0 / 3.0), - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(new Geometry[0])), 0.5, 0.5), - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(new Geometry[0])), 0.5, 0.5), + GeoUtils.JTS_FACTORY.createGeometryCollection(points1.toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(points2.toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)), 1.0 / 3.0, 1.0 / 3.0), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(Geometry[]::new)), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(Geometry[]::new)), 0.5, 0.5), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( - GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(new Geometry[0])) - ).toArray(new Geometry[0])), 0.5, 0.5), - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(new Geometry[0])), 0.5, 0.5), - Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(new Geometry[0])), 0.5, 0.5), + GeoUtils.JTS_FACTORY.createGeometryCollection(lines1.toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(lines2.toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(Geometry[]::new)), 0.5, 0.5), + Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(Geometry[]::new)), 0.5, 0.5), Arguments.of(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( - GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(new Geometry[0])), - GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(new Geometry[0])) - ).toArray(new Geometry[0])), 0.5, 0.5) + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons1.toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(polygons2.toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)), 0.5, 0.5) ); } From d9da47dfee857e44094b0cabaad1d88b18899a1b Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 16:09:52 +0100 Subject: [PATCH 07/11] added tests for collection of points and collection of lines --- .../planetiler/PlanetilerTests.java | 140 +++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index 2f3c2b738e..7743e4d1c7 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -61,6 +61,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; @@ -235,7 +236,7 @@ private PlanetilerResults runWithOsmElements( ); } - private SimpleFeature newReaderFeature(Geometry geometry, Map attrs) { + private static SimpleFeature newReaderFeature(Geometry geometry, Map attrs) { return SimpleFeature.create(geometry, attrs); } @@ -450,6 +451,75 @@ void testMultiPoint() throws Exception { ), results.tiles); } + static Stream pointCollections() { + double x1 = 0.5 + Z14_WIDTH / 2; + double y1 = 0.5 + Z14_WIDTH / 2; + double x2 = x1 + Z13_WIDTH / 256d; + double y2 = y1 + Z13_WIDTH / 256d; + double lat1 = GeoUtils.getWorldLat(y1); + double lng1 = GeoUtils.getWorldLon(x1); + double lat2 = GeoUtils.getWorldLat(y2); + double lng2 = GeoUtils.getWorldLon(x2); + + return Stream.of( + // one collection with several points + org.junit.jupiter.params.provider.Arguments.of(List.of( + newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + newPoint(lng1, lat1), + newPoint(lng2, lat2) + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + )) + )), + // nested collections, i.e. several collection, each with one point, in one collection + org.junit.jupiter.params.provider.Arguments.of(List.of( + newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(newPoint(lng1, lat1)).toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of(newPoint(lng2, lat2)).toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + )) + )) + ); + } + + // note: Same as testMultiPoint() but we get list of points in the result, not one multipoint + @ParameterizedTest + @MethodSource("pointCollections") + void testPointCollection(List points) throws Exception { + var results = runWithReaderFeatures( + Map.of("threads", "1"), + points, + (in, features) -> features.point("layer") + .setZoomRange(13, 14) + .setAttr("name", "name value") + .inheritAttrFromSource("attr") + ); + + assertSubmap(Map.of( + TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of( + feature(newPoint(128, 128), Map.of( + "attr", "value", + "name", "name value" + )), + feature(newPoint(130, 130), Map.of( + "attr", "value", + "name", "name value" + )) + ), + TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of( + feature(newPoint(64, 64), Map.of( + "attr", "value", + "name", "name value" + )), + feature(newPoint(65, 65), Map.of( + "attr", "value", + "name", "name value" + )) + ) + ), results.tiles); + } + @Test void testLabelGridLimit() throws Exception { double y = 0.5 + Z14_WIDTH / 2; @@ -717,6 +787,7 @@ void testNumPointsAttr() throws Exception { ), results.tiles); } + // TODO: multilines are handles same as collection of lines (e.g. multiline is the result) => merge this into lineCollections() @Test void testMultiLineString() throws Exception { double x1 = 0.5 + Z14_WIDTH / 2; @@ -765,6 +836,73 @@ void testMultiLineString() throws Exception { ), results.tiles); } + static Stream lineCollections() { + double x1 = 0.5 + Z14_WIDTH / 2; + double y1 = 0.5 + Z14_WIDTH / 2; + double x2 = x1 + Z14_WIDTH; + double y2 = y1 + Z14_WIDTH; + double lat1 = GeoUtils.getWorldLat(y1); + double lng1 = GeoUtils.getWorldLon(x1); + double lat2 = GeoUtils.getWorldLat(y2); + double lng2 = GeoUtils.getWorldLon(x2); + + return Stream.of( + // one collection with several lines + org.junit.jupiter.params.provider.Arguments.of(List.of( + newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + newLineString(lng1, lat1, lng2, lat2), + newLineString(lng2, lat2, lng1, lat1) + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + )) + )), + // nested collections, i.e. several collection, each with one line, in one collection + org.junit.jupiter.params.provider.Arguments.of(List.of( + newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY + .createGeometryCollection(Set.of(newLineString(lng1, lat1, lng2, lat2)).toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY + .createGeometryCollection(Set.of(newLineString(lng2, lat2, lng1, lat1)).toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + )) + )) + ); + } + + @ParameterizedTest + @MethodSource("lineCollections") + void testLineCollection(List lines) throws Exception { + var results = runWithReaderFeatures( + Map.of("threads", "1"), + lines, + (in, features) -> features.line("layer") + .setZoomRange(13, 14) + .setBufferPixels(4) + ); + + assertSubmap(Map.of( + TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of( + feature(newMultiLineString( + newLineString(128, 128, 260, 260), + newLineString(260, 260, 128, 128) + ), Map.of()) + ), + TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of( + feature(newMultiLineString( + newLineString(-4, -4, 128, 128), + newLineString(128, 128, -4, -4) + ), Map.of()) + ), + TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of( + feature(newMultiLineString( + newLineString(64, 64, 192, 192), + newLineString(192, 192, 64, 64) + ), Map.of()) + ) + ), results.tiles); + } + public List z14CoordinateList(double... coords) { List points = newCoordinateList(coords); points.forEach(c -> { From 055628b1611013b801f1b1cf0aa4ec3c3782b740 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 16:13:18 +0100 Subject: [PATCH 08/11] testMultiLineString() merged with testLineCollection() --- .../planetiler/PlanetilerTests.java | 52 +++---------------- 1 file changed, 6 insertions(+), 46 deletions(-) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index 7743e4d1c7..50b546ad20 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -787,9 +787,7 @@ void testNumPointsAttr() throws Exception { ), results.tiles); } - // TODO: multilines are handles same as collection of lines (e.g. multiline is the result) => merge this into lineCollections() - @Test - void testMultiLineString() throws Exception { + static Stream lineCollections() { double x1 = 0.5 + Z14_WIDTH / 2; double y1 = 0.5 + Z14_WIDTH / 2; double x2 = x1 + Z14_WIDTH; @@ -799,54 +797,16 @@ void testMultiLineString() throws Exception { double lat2 = GeoUtils.getWorldLat(y2); double lng2 = GeoUtils.getWorldLon(x2); - var results = runWithReaderFeatures( - Map.of("threads", "1"), - List.of( + return Stream.of( + // simple multiline + org.junit.jupiter.params.provider.Arguments.of(List.of( newReaderFeature(newMultiLineString( newLineString(lng1, lat1, lng2, lat2), newLineString(lng2, lat2, lng1, lat1) ), Map.of( "attr", "value" )) - ), - (in, features) -> features.line("layer") - .setZoomRange(13, 14) - .setBufferPixels(4) - ); - - assertSubmap(Map.of( - TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of( - feature(newMultiLineString( - newLineString(128, 128, 260, 260), - newLineString(260, 260, 128, 128) - ), Map.of()) - ), - TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of( - feature(newMultiLineString( - newLineString(-4, -4, 128, 128), - newLineString(128, 128, -4, -4) - ), Map.of()) - ), - TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of( - feature(newMultiLineString( - newLineString(64, 64, 192, 192), - newLineString(192, 192, 64, 64) - ), Map.of()) - ) - ), results.tiles); - } - - static Stream lineCollections() { - double x1 = 0.5 + Z14_WIDTH / 2; - double y1 = 0.5 + Z14_WIDTH / 2; - double x2 = x1 + Z14_WIDTH; - double y2 = y1 + Z14_WIDTH; - double lat1 = GeoUtils.getWorldLat(y1); - double lng1 = GeoUtils.getWorldLon(x1); - double lat2 = GeoUtils.getWorldLat(y2); - double lng2 = GeoUtils.getWorldLon(x2); - - return Stream.of( + )), // one collection with several lines org.junit.jupiter.params.provider.Arguments.of(List.of( newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( @@ -872,7 +832,7 @@ static Stream lineCollections() { @ParameterizedTest @MethodSource("lineCollections") - void testLineCollection(List lines) throws Exception { + void testMultiLineString(List lines) throws Exception { var results = runWithReaderFeatures( Map.of("threads", "1"), lines, From fb87d7f3d9881a43d7176a9b9bd5bf8efb0645f0 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 23 Jan 2026 16:58:35 +0100 Subject: [PATCH 09/11] added test for collection of polygons --- .../planetiler/PlanetilerTests.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index 50b546ad20..66c664b6f4 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -1539,6 +1539,93 @@ record TestRelationInfo(long id, String name) implements OsmRelationInfo {} ), results.tiles); } + static Stream polygonCollections() { + double x1 = 0.125; + double y1 = 0.125; + double x2 = 0.875; + double y2 = 0.875; + double x3 = 0.25; + double y3 = 0.25; + double x4 = 0.75; + double y4 = 0.75; + double x5 = 0.375; + double y5 = 0.375; + double x6 = 0.625; + double y6 = 0.625; + double lat1 = GeoUtils.getWorldLat(y1); + double lng1 = GeoUtils.getWorldLon(x1); + double lat2 = GeoUtils.getWorldLat(y2); + double lng2 = GeoUtils.getWorldLon(x2); + double lat3 = GeoUtils.getWorldLat(y3); + double lng3 = GeoUtils.getWorldLon(x3); + double lat4 = GeoUtils.getWorldLat(y4); + double lng4 = GeoUtils.getWorldLon(x4); + double lat5 = GeoUtils.getWorldLat(y5); + double lng5 = GeoUtils.getWorldLon(x5); + double lat6 = GeoUtils.getWorldLat(y6); + double lng6 = GeoUtils.getWorldLon(x6); + + var polygon1 = newPolygon( + rectangleCoordList(lng1, lat1, lng2, lat2), + List.of(rectangleCoordList(lng3, lat3, lng4, lat4)) + ); + var polygon2 = rectangle(lng5, lat5, lng6, lat6); + + return Stream.of( + // one collection with several polygons + org.junit.jupiter.params.provider.Arguments.of(List.of( + newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + polygon1, + polygon2 + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + )) + )), + // nested collections, i.e. several collection, each with one polygon, in one collection + org.junit.jupiter.params.provider.Arguments.of(List.of( + newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + GeoUtils.JTS_FACTORY + .createGeometryCollection(Set.of(polygon1).toArray(Geometry[]::new)), + GeoUtils.JTS_FACTORY + .createGeometryCollection(Set.of(polygon2).toArray(Geometry[]::new)) + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + )) + )) + ); + } + + @ParameterizedTest + @MethodSource("polygonCollections") + void testPolygonCollection(List polygons) throws Exception { + var results = runWithReaderFeatures( + Map.of("threads", "1"), + polygons, + (in, features) -> features.polygon("layer") + .setZoomRange(0, 0) + .setAttr("name", "name value") + .inheritAttrFromSource("attr") + ); + + assertSubmap(Map.of( + TileCoord.ofXYZ(0, 0, 0), List.of( + feature(rectangle(0.375 * 256, 0.625 * 256), Map.of( + "attr", "value", + "name", "name value" + )), + feature(newPolygon( + rectangleCoordList(0.125 * 256, 0.875 * 256), + List.of( + rectangleCoordList(0.25 * 256, 0.75 * 256) + ) + ), Map.of( + "attr", "value", + "name", "name value" + )) + ) + ), results.tiles); + } + @Test void testOsmLineInRelation() throws Exception { record TestRelationInfo(long id, String name) implements OsmRelationInfo {} From 09d887e433a66fc04f1bd7dc6e0c701eabfe561a Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Sun, 25 Jan 2026 11:49:34 +0100 Subject: [PATCH 10/11] added test for collection of features with mixed types --- .../planetiler/PlanetilerTests.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index 66c664b6f4..e1730d0952 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -65,6 +65,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; import org.locationtech.jts.geom.MultiPolygon; import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; @@ -1626,6 +1627,75 @@ void testPolygonCollection(List polygons) throws Exception { ), results.tiles); } + @Test + void testMixedCollection() throws Exception { + double lat1 = GeoUtils.getWorldLat(0.125); + double lng1 = GeoUtils.getWorldLon(0.125); + var point = newPoint(lng1, lat1); + + double lat2 = GeoUtils.getWorldLat(0.25); + double lng2 = GeoUtils.getWorldLon(0.25); + double lat3 = GeoUtils.getWorldLat(0.75); + double lng3 = GeoUtils.getWorldLon(0.75); + var line = newLineString(lng2, lat2, lng3, lat3); + + double lat5 = GeoUtils.getWorldLat(0.375); + double lng5 = GeoUtils.getWorldLon(0.375); + double lat6 = GeoUtils.getWorldLat(0.625); + double lng6 = GeoUtils.getWorldLon(0.625); + var polygon = rectangle(lng5, lat5, lng6, lat6); + + // TODO collection of those + var input = List.of(newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( + point, + line, + polygon + ).toArray(Geometry[]::new)), Map.of( + "attr", "value" + ))); + + var results = runWithReaderFeatures( + Map.of("threads", "1"), + input, + (in, features) -> { + try { + // FeatureCollector.anyGeometry() & co. expect just one feature hence to process several possibly even mixed + // type features from collection we need a profile to do the iteration itself: + var geom = in.worldGeometry(); + if (geom instanceof GeometryCollection collection) { + for (int i = 0; i < collection.getNumGeometries(); i++) { + features.geometry("layer", collection.getGeometryN(i)) + .setZoomRange(0, 0) + .setAttr("name", "name value") + .inheritAttrFromSource("attr"); + } + } else { + throw new RuntimeException("only collections expected in this test"); + } + } catch (GeometryException e) { + throw new RuntimeException(e); + } + } + ); + + assertSubmap(Map.of( + TileCoord.ofXYZ(0, 0, 0), List.of( + feature(newPoint(32, 32), Map.of( + "attr", "value", + "name", "name value" + )), + feature(rectangle(0.375 * 256, 0.625 * 256), Map.of( + "attr", "value", + "name", "name value" + )), + feature(newLineString(64, 64, 192, 192), Map.of( + "attr", "value", + "name", "name value" + )) + ) + ), results.tiles); + } + @Test void testOsmLineInRelation() throws Exception { record TestRelationInfo(long id, String name) implements OsmRelationInfo {} From 6b37ed8aabd35aea868580a4443ec8b823c37693 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Sun, 25 Jan 2026 11:51:18 +0100 Subject: [PATCH 11/11] clean-up: removed forgotten comment --- .../src/test/java/com/onthegomap/planetiler/PlanetilerTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index e1730d0952..fae448f760 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -1645,7 +1645,6 @@ void testMixedCollection() throws Exception { double lng6 = GeoUtils.getWorldLon(0.625); var polygon = rectangle(lng5, lat5, lng6, lat6); - // TODO collection of those var input = List.of(newReaderFeature(GeoUtils.JTS_FACTORY.createGeometryCollection(Set.of( point, line,