From c9f61df4cb77aca4ada6809f766bd404d449dbcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:31:27 -0500 Subject: [PATCH 1/3] Bump org.xerial:sqlite-jdbc from 3.43.2.2 to 3.44.0.0 (#724) --- planetiler-core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planetiler-core/pom.xml b/planetiler-core/pom.xml index 238e82c4..efb00ff6 100644 --- a/planetiler-core/pom.xml +++ b/planetiler-core/pom.xml @@ -67,7 +67,7 @@ org.xerial sqlite-jdbc - 3.43.2.2 + 3.44.0.0 org.msgpack From c22d3797342a25d27c4275d302cce883632f2e32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 04:47:38 -0500 Subject: [PATCH 2/3] Bump jackson.version from 2.15.3 to 2.16.0 (#726) --- .../stream/WriteableJsonStreamArchive.java | 13 +++++-------- pom.xml | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/stream/WriteableJsonStreamArchive.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/stream/WriteableJsonStreamArchive.java index 40f389ce..6ec617a0 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/stream/WriteableJsonStreamArchive.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/stream/WriteableJsonStreamArchive.java @@ -209,17 +209,14 @@ public final class WriteableJsonStreamArchive extends WriteableStreamArchive { record FinishEntry(TileArchiveMetadata metadata) implements Entry {} - private interface TileArchiveMetadataMixin { + private record TileArchiveMetadataMixin( - @JsonIgnore(false) - Envelope bounds(); + @JsonIgnore(false) Envelope bounds, - @JsonIgnore(false) - CoordinateXY center(); + @JsonIgnore(false) CoordinateXY center, - @JsonIgnore(false) - List vectorLayers(); - } + @JsonIgnore(false) List vectorLayers + ) {} @JsonIncludeProperties({"minX", "maxX", "minY", "maxY"}) private abstract static class EnvelopeMixin { diff --git a/pom.xml b/pom.xml index 77904e87..c465c280 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 21 21 true - 2.15.3 + 2.16.0 5.10.1 https://sonarcloud.io onthegomap From 1df1bf04e4a1ff12e3f94e7da8d34f0f9aec4e7e Mon Sep 17 00:00:00 2001 From: Michael Barry Date: Mon, 20 Nov 2023 06:15:52 -0500 Subject: [PATCH 3/3] Add `setAttrWithMinSize` to feature API (#725) --- .../planetiler/FeatureCollector.java | 68 ++++++++++++++---- .../onthegomap/planetiler/geo/GeoUtils.java | 14 ++++ .../planetiler/PlanetilerTests.java | 70 +++++++++++++++++++ .../planetiler/geo/GeoUtilsTest.java | 26 +++++++ 4 files changed, 164 insertions(+), 14 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 680b393f..f1e76d8a 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java @@ -200,6 +200,28 @@ public class FeatureCollector implements Iterable { return innermostPoint(layer, 0.1); } + /** Returns the minimum zoom level at which this feature is at least {@code pixelSize} pixels large. */ + public int getMinZoomForPixelSize(double pixelSize) { + try { + return GeoUtils.minZoomForPixelSize(source.size(), pixelSize); + } catch (GeometryException e) { + e.log(stats, "min_zoom_for_size_failure", "Error getting min zoom for size from geometry " + source.id()); + return config.maxzoom(); + } + } + + + /** Returns the actual pixel size of the source feature at {@code zoom} (length if line, sqrt(area) if polygon). */ + public double getPixelSizeAtZoom(int zoom) { + try { + return source.size() * (256 << zoom); + } catch (GeometryException e) { + e.log(stats, "source_feature_pixel_size_at_zoom_failure", + "Error getting source feature pixel size at zoom from geometry " + source.id()); + return 0; + } + } + /** * Creates new feature collector instances for each source feature that we encounter. */ @@ -703,6 +725,29 @@ public class FeatureCollector implements Iterable { return setAttr(key, ZoomFunction.minZoom(minzoom, value)); } + /** + * Sets the value for {@code key} only at zoom levels where the feature is at least {@code minPixelSize} pixels in + * size. + */ + public Feature setAttrWithMinSize(String key, Object value, double minPixelSize) { + return setAttrWithMinzoom(key, value, getMinZoomForPixelSize(minPixelSize)); + } + + /** + * Sets the value for {@code key} so that it always shows when {@code zoom_level >= minZoomToShowAlways} but only + * shows when {@code minZoomIfBigEnough <= zoom_level < minZoomToShowAlways} when it is at least + * {@code minPixelSize} pixels in size. + *

+ * If you need more flexibility, use {@link #getMinZoomForPixelSize(double)} directly, or create a + * {@link ZoomFunction} that calculates {@link #getPixelSizeAtZoom(int)} and applies a custom threshold based on the + * zoom level. + */ + public Feature setAttrWithMinSize(String key, Object value, double minPixelSize, int minZoomIfBigEnough, + int minZoomToShowAlways) { + return setAttrWithMinzoom(key, value, + Math.clamp(getMinZoomForPixelSize(minPixelSize), minZoomIfBigEnough, minZoomToShowAlways)); + } + /** * Inserts all key/value pairs in {@code attrs} into the set of attribute to emit on the output feature at or above * {@code minzoom}. @@ -735,6 +780,14 @@ public class FeatureCollector implements Iterable { return this; } + /** + * Returns the attribute key that the renderer should use to store the number of points in the simplified geometry + * before slicing it into tiles. + */ + public String getNumPointsAttr() { + return numPointsAttr; + } + /** * Sets a special attribute key that the renderer will use to store the number of points in the simplified geometry * before slicing it into tiles. @@ -744,14 +797,6 @@ public class FeatureCollector implements Iterable { return this; } - /** - * Returns the attribute key that the renderer should use to store the number of points in the simplified geometry - * before slicing it into tiles. - */ - public String getNumPointsAttr() { - return numPointsAttr; - } - @Override public String toString() { return "Feature{" + @@ -763,12 +808,7 @@ public class FeatureCollector implements Iterable { /** 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; - } + return getPixelSizeAtZoom(zoom); } } } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeoUtils.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeoUtils.java index 0809f4de..f6d0aceb 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeoUtils.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/GeoUtils.java @@ -1,6 +1,7 @@ package com.onthegomap.planetiler.geo; import com.onthegomap.planetiler.collection.LongLongMap; +import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.stats.Stats; import java.util.ArrayList; import java.util.List; @@ -51,6 +52,7 @@ public class GeoUtils { public static final double WORLD_CIRCUMFERENCE_METERS = Math.PI * 2 * WORLD_RADIUS_METERS; private static final double RADIANS_PER_DEGREE = Math.PI / 180; private static final double DEGREES_PER_RADIAN = 180 / Math.PI; + private static final double LOG2 = Math.log(2); /** * Transform web mercator coordinates where top-left corner of the planet is (0,0) and bottom-right is (1,1) to * latitude/longitude coordinates. @@ -534,6 +536,18 @@ public class GeoUtils { JTS_FACTORY.createGeometryCollection(innerGeometries.toArray(Geometry[]::new)); } + /** + * For a feature of size {@code worldGeometrySize} (where 1=full planet), determine the minimum zoom level at which + * the feature appears at least {@code minPixelSize} pixels large. + *

+ * The result will be clamped to the range [0, {@link PlanetilerConfig#MAX_MAXZOOM}]. + */ + public static int minZoomForPixelSize(double worldGeometrySize, double minPixelSize) { + double worldPixels = worldGeometrySize * 256; + return Math.clamp((int) Math.ceil(Math.log(minPixelSize / worldPixels) / LOG2), 0, + PlanetilerConfig.MAX_MAXZOOM); + } + /** Helper class to sort polygons by area of their outer shell. */ private record PolyAndArea(Polygon poly, double area) implements Comparable { 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 f44ada35..d1224780 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -87,6 +87,8 @@ class PlanetilerTests { private static final double Z13_WIDTH = 1d / Z13_TILES; private static final int Z12_TILES = 1 << 12; private static final double Z12_WIDTH = 1d / Z12_TILES; + private static final int Z11_TILES = 1 << 11; + private static final double Z11_WIDTH = 1d / Z11_TILES; private static final int Z4_TILES = 1 << 4; private static final Polygon WORLD_POLYGON = newPolygon( worldCoordinateList( @@ -2434,6 +2436,74 @@ class PlanetilerTests { ), results.tiles); } + @Test + void testAttributeMinSizeLine() 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.line("layer") + .setZoomRange(11, 14) + .setBufferPixels(0) + .setAttrWithMinSize("a", "1", 10) + .setAttrWithMinSize("b", "2", 20) + .setAttrWithMinSize("c", "3", 40) + .setAttrWithMinSize("d", "4", 40, 0, 13) // should show up at z13 and above + ); + + assertEquals(Map.ofEntries( + newTileEntry(Z11_TILES / 2, Z11_TILES / 2, 11, List.of( + feature(newLineString(0, 0.5, 5, 0.5), Map.of()) + )), + newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of( + feature(newLineString(0, 1, 10, 1), Map.of("a", "1")) + )), + newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of( + feature(newLineString(0, 2, 20, 2), Map.of("a", "1", "b", "2", "d", "4")) + )), + newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of( + feature(newLineString(0, 4, 40, 4), Map.of("a", "1", "b", "2", "c", "3", "d", "4")) + )) + ), results.tiles); + } + + @Test + void testAttributeMinSizePoint() 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(11, 14) + .setBufferPixels(0) + .setAttrWithMinSize("a", "1", 10) + .setAttrWithMinSize("b", "2", 20) + .setAttrWithMinSize("c", "3", 40) + .setAttrWithMinSize("d", "4", 40, 0, 13) // should show up at z13 and above + ); + + assertEquals(Map.ofEntries( + newTileEntry(Z11_TILES / 2, Z11_TILES / 2, 11, List.of( + feature(newPoint(2.5, 0.5), Map.of()) + )), + newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of( + feature(newPoint(5, 1), Map.of("a", "1")) + )), + newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of( + feature(newPoint(10, 2), Map.of("a", "1", "b", "2", "d", "4")) + )), + newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of( + feature(newPoint(20, 4), Map.of("a", "1", "b", "2", "c", "3", "d", "4")) + )) + ), results.tiles); + } + @Test void testBoundFiltersFill() throws Exception { var polyResultz8 = runForBoundsTest(8, 8, "polygon", TestUtils.pathToResource("bottomrightearth.poly").toString()); diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/geo/GeoUtilsTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/geo/GeoUtilsTest.java index f1b528f9..dead3141 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/geo/GeoUtilsTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/geo/GeoUtilsTest.java @@ -447,4 +447,30 @@ class GeoUtilsTest { assertTrue(result.isValid()); assertFalse(result.contains(point)); } + + @ParameterizedTest + @CsvSource({ + "1,0,0", + "1,10,0", + "1,255,0", + + "0.5,0,0", + "0.5,128,0", + "0.5,129,1", + "0.5,256,1", + + "0.25,0,0", + "0.25,128,1", + "0.25,129,2", + "0.25,256,2", + }) + void minZoomForPixelSize(double worldGeometrySize, double minPixelSize, int expectedMinZoom) { + assertEquals(expectedMinZoom, GeoUtils.minZoomForPixelSize(worldGeometrySize, minPixelSize)); + } + + @Test + void minZoomForPixelSizesAtZ9_10() { + assertEquals(10, GeoUtils.minZoomForPixelSize(3.1 / (256 << 10), 3)); + assertEquals(9, GeoUtils.minZoomForPixelSize(6.1 / (256 << 10), 3)); + } }