From ec6430dc4922795f31d85bd0c22034bf8bceac3b Mon Sep 17 00:00:00 2001 From: Michael Barry Date: Mon, 13 Nov 2023 07:16:55 -0500 Subject: [PATCH] Min polygon area centroid (#720) --- .../planetiler/FeatureCollector.java | 14 +++ .../planetiler/reader/SourceFeature.java | 10 ++ .../planetiler/render/FeatureRenderer.java | 6 +- .../planetiler/FeatureCollectorTest.java | 1 - .../planetiler/PlanetilerTests.java | 93 +++++++++++++++++++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java index 49729ee7..99dc51b1 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java @@ -230,6 +230,10 @@ public class FeatureCollector implements Iterable { this.geom = geom; this.geometryType = GeometryType.typeOf(geom); this.id = id; + if (geometryType == GeometryType.POINT) { + minPixelSizeAtMaxZoom = 0; + defaultMinPixelSize = 0; + } } /** Returns the original ID of the source feature that this feature came from (i.e. OSM node/way ID). */ @@ -728,5 +732,15 @@ public class FeatureCollector implements Iterable { ", attrs=" + attrs + '}'; } + + /** Returns the actual pixel size of the source feature at {@code zoom} (length if line, sqrt(area) if polygon). */ + public double getSourceFeaturePixelSizeAtZoom(int zoom) { + try { + return source.size() * (256 << zoom); + } catch (GeometryException e) { + e.log(stats, "point_get_size_failure", "Error getting min size for point from geometry " + source.id()); + return 0; + } + } } } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java index 4a474a97..55525645 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java @@ -39,6 +39,7 @@ public abstract class SourceFeature implements WithTags, WithGeometryType { private Geometry validPolygon = null; private double area = Double.NaN; private double length = Double.NaN; + private double size = Double.NaN; /** * Constructs a new input feature. @@ -245,6 +246,14 @@ public abstract class SourceFeature implements WithTags, WithGeometryType { (isPoint() || canBePolygon() || canBeLine()) ? worldGeometry().getLength() : 0) : length; } + /** + * Returns and caches sqrt of {@link #area()} if polygon or {@link #length()} if a line string. + */ + public double size() throws GeometryException { + return Double.isNaN(size) ? (size = canBePolygon() ? Math.sqrt(Math.abs(area())) : canBeLine() ? length() : 0) : + size; + } + /** Returns the ID of the source that this feature came from. */ public String getSource() { return source; @@ -292,4 +301,5 @@ public abstract class SourceFeature implements WithTags, WithGeometryType { public boolean hasRelationInfo() { return relationInfos != null && !relationInfos.isEmpty(); } + } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java index 98df7cf6..545dcad9 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java @@ -97,6 +97,10 @@ public class FeatureRenderer implements Consumer, Clos coords[i] = origCoords[i].copy(); } for (int zoom = feature.getMaxZoom(); zoom >= feature.getMinZoom(); zoom--) { + double minSize = feature.getMinPixelSizeAtZoom(zoom); + if (minSize > 0 && feature.getSourceFeaturePixelSizeAtZoom(zoom) < minSize) { + continue; + } Map attrs = feature.getAttrsAtZoom(zoom); double buffer = feature.getBufferPixelsAtZoom(zoom) / 256; int tilesAtZoom = 1 << zoom; @@ -207,7 +211,7 @@ public class FeatureRenderer implements Consumer, Clos } Map attrs = feature.getAttrsAtZoom(sliced.zoomLevel()); if (numPointsAttr != null) { - // if profile wants the original number of points that the simplified but untiled geometry started with + // if profile wants the original number off points that the simplified but untiled geometry started with attrs = new HashMap<>(attrs); attrs.put(numPointsAttr, geom.getNumPoints()); } diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureCollectorTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureCollectorTest.java index 672fe9ef..49eaca54 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureCollectorTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/FeatureCollectorTest.java @@ -614,5 +614,4 @@ class FeatureCollectorTest { ) ), collector); } - } 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 8cd3303c..f44ada35 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -592,6 +592,15 @@ class PlanetilerTests { return points; } + public List z14PixelRectangle(double min, double max) { + List points = rectangleCoordList(min / 256d, max / 256d); + points.forEach(c -> { + c.x = GeoUtils.getWorldLon(0.5 + c.x * Z14_WIDTH); + c.y = GeoUtils.getWorldLat(0.5 + c.y * Z14_WIDTH); + }); + return points; + } + public List z14CoordinatePixelList(double... coords) { return z14CoordinateList(DoubleStream.of(coords).map(c -> c / 256d).toArray()); } @@ -2341,6 +2350,90 @@ class PlanetilerTests { assertEquals(bboxResult.tiles, polyResult.tiles); } + @Test + void testSimplePolygon() throws Exception { + List points = z14PixelRectangle(0, 40); + + var results = runWithReaderFeatures( + Map.of("threads", "1"), + List.of( + newReaderFeature(newPolygon(points), Map.of()) + ), + (in, features) -> features.polygon("layer") + .setZoomRange(0, 14) + .setBufferPixels(0) + .setMinPixelSize(10) // should only show up z14 (40) z13 (20) and z12 (10) + ); + + assertEquals(Map.ofEntries( + newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of( + feature(newPolygon(rectangleCoordList(0, 10)), Map.of()) + )), + newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of( + feature(newPolygon(rectangleCoordList(0, 20)), Map.of()) + )), + newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of( + feature(newPolygon(rectangleCoordList(0, 40)), Map.of()) + )) + ), results.tiles); + } + + @Test + void testCentroidWithPolygonMinSize() throws Exception { + List points = z14PixelRectangle(0, 40); + + var results = runWithReaderFeatures( + Map.of("threads", "1"), + List.of( + newReaderFeature(newPolygon(points), Map.of()) + ), + (in, features) -> features.centroid("layer") + .setZoomRange(0, 14) + .setBufferPixels(0) + .setMinPixelSize(10) // should only show up z14 (40) z13 (20) and z12 (10) + ); + + assertEquals(Map.ofEntries( + newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of( + feature(newPoint(5, 5), Map.of()) + )), + newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of( + feature(newPoint(10, 10), Map.of()) + )), + newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of( + feature(newPoint(20, 20), Map.of()) + )) + ), results.tiles); + } + + @Test + void testCentroidWithLineMinSize() throws Exception { + List points = z14CoordinatePixelList(0, 4, 40, 4); + + var results = runWithReaderFeatures( + Map.of("threads", "1"), + List.of( + newReaderFeature(newLineString(points), Map.of()) + ), + (in, features) -> features.centroid("layer") + .setZoomRange(0, 14) + .setBufferPixels(0) + .setMinPixelSize(10) // should only show up z14 (40) z13 (20) and z12 (10) + ); + + assertEquals(Map.ofEntries( + newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of( + feature(newPoint(5, 1), Map.of()) + )), + newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of( + feature(newPoint(10, 2), Map.of()) + )), + newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of( + feature(newPoint(20, 4), Map.of()) + )) + ), results.tiles); + } + @Test void testBoundFiltersFill() throws Exception { var polyResultz8 = runForBoundsTest(8, 8, "polygon", TestUtils.pathToResource("bottomrightearth.poly").toString());