Add `setAttrWithMinSize` to feature API (#725)

pull/728/head^2
Michael Barry 2023-11-20 06:15:52 -05:00 zatwierdzone przez GitHub
rodzic c22d379734
commit 1df1bf04e4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
4 zmienionych plików z 164 dodań i 14 usunięć

Wyświetl plik

@ -200,6 +200,28 @@ public class FeatureCollector implements Iterable<FeatureCollector.Feature> {
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<FeatureCollector.Feature> {
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, 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<FeatureCollector.Feature> {
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<FeatureCollector.Feature> {
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<FeatureCollector.Feature> {
/** 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);
}
}
}

Wyświetl plik

@ -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.
* <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> {

Wyświetl plik

@ -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<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());

Wyświetl plik

@ -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));
}
}