Skip to content

Commit

Permalink
min zoom for pixel size
Browse files Browse the repository at this point in the history
  • Loading branch information
msbarry committed Nov 15, 2023
1 parent fdee80d commit 762c796
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,27 @@ public Feature setAttrWithMinzoom(String key, Object value, int minzoom) {
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.
* <p>
* If you need more flexibility, use {@link #getMinZoomForPixelSize(double)} directly.
*/
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}.
Expand Down Expand Up @@ -766,9 +787,20 @@ 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());
e.log(stats, "source_feature_pixel_size_at_zoom_failure",
"Error getting source feature pixel size at zoom from geometry " + source.id());
return 0;
}
}

/** 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();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -534,6 +536,18 @@ public static Geometry combine(Geometry... geometries) {
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.
* <p>
* 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<PolyAndArea> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -2434,6 +2436,74 @@ void testCentroidWithLineMinSize() throws Exception {
), results.tiles);
}

@Test
void testAttributeMinSizeLine() throws Exception {
List<Coordinate> 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<Coordinate> 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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,4 +447,30 @@ void testSnapAndFixIssue546_3() throws GeometryException, ParseException {
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));
}
}

0 comments on commit 762c796

Please sign in to comment.