Skip to content

Commit

Permalink
use VW when rendering lines/polygons
Browse files Browse the repository at this point in the history
  • Loading branch information
msbarry committed Nov 22, 2024
1 parent df08c9d commit 339362f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.geo.GeometryType;
import com.onthegomap.planetiler.geo.SimplifyStrategy;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.Struct;
import com.onthegomap.planetiler.render.FeatureRenderer;
Expand Down Expand Up @@ -502,6 +503,9 @@ public final class Feature implements WithZoomRange<Feature>, WithAttrs<Feature>
private double pixelToleranceAtMaxZoom = config.simplifyToleranceAtMaxZoom();
private ZoomFunction<Number> pixelTolerance = null;

private SimplifyStrategy defaultSimplifyStrategy = SimplifyStrategy.DOUGLAS_PEUCKER;
private ZoomFunction<SimplifyStrategy> simplifyStrategy = null;

private String numPointsAttr = null;
private List<OverrideCommand> partialOverrides = null;

Expand Down Expand Up @@ -714,6 +718,28 @@ public double getPixelToleranceAtZoom(int zoom) {
ZoomFunction.applyAsDoubleOrElse(pixelTolerance, zoom, defaultPixelTolerance);
}

/**
* Sets the fallback line and polygon simplify method when not overriden by
* {@link #setSimplifyStrategyOverrides(ZoomFunction)}.
*/
public FeatureCollector.Feature setSimplifyStrategy(SimplifyStrategy strategy) {
defaultSimplifyStrategy = strategy;
return this;
}

/** Set simplify strategy to use at different zoom levels. */
public FeatureCollector.Feature setSimplifyStrategyOverrides(ZoomFunction<SimplifyStrategy> overrides) {
simplifyStrategy = overrides;
return this;
}

/**
* Returns the simplification strategy for lines and polygons in tile pixels at {@code zoom}.
*/
public SimplifyStrategy getSimplifyStrategyAtZoom(int zoom) {
return ZoomFunction.applyOrElse(simplifyStrategy, zoom, defaultSimplifyStrategy);
}

/**
* Sets the simplification tolerance for lines and polygons in tile pixels below the maximum zoom-level of the map.
* <p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.onthegomap.planetiler.geo;

public enum SimplifyStrategy {
RETAIN_IMPORTANT_POINTS,
RETAIN_EFFECTIVE_AREAS,
RETAIN_WEIGHTED_EFFECTIVE_AREAS;

public static final SimplifyStrategy DOUGLAS_PEUCKER = RETAIN_IMPORTANT_POINTS;
public static final SimplifyStrategy VISVALINGAM_WHYATT = RETAIN_EFFECTIVE_AREAS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import com.onthegomap.planetiler.geo.DouglasPeuckerSimplifier;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.geo.SimplifyStrategy;
import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.geo.TileExtents;
import com.onthegomap.planetiler.geo.VWSimplifier;
import com.onthegomap.planetiler.stats.Stats;
import java.io.Closeable;
import java.io.IOException;
Expand Down Expand Up @@ -203,14 +205,23 @@ private void renderLineOrPolygonGeometry(FeatureCollector.Feature feature, Geome
int z, double minSize, boolean area) {
double scale = 1 << z;
double tolerance = feature.getPixelToleranceAtZoom(z) / 256d;
SimplifyStrategy strategy = feature.getSimplifyStrategyAtZoom(z);
double buffer = feature.getBufferPixelsAtZoom(z) / 256;
TileExtents.ForZoom extents = config.bounds().tileExtents().getForZoom(z);

// TODO potential optimization: iteratively simplify z+1 to get z instead of starting with original geom each time
// simplify only takes 4-5 minutes of wall time when generating the planet though, so not a big deal
Geometry scaled = AffineTransformation.scaleInstance(scale, scale).transform(input);
TiledGeometry sliced;
Geometry geom = DouglasPeuckerSimplifier.simplify(scaled, tolerance);
// TODO replace with geometry pipeline when available
Geometry geom = switch (strategy) {
case RETAIN_IMPORTANT_POINTS -> DouglasPeuckerSimplifier.simplify(scaled, tolerance);
// Douglas Peucker tolerance is distance off the line, and VW tolerance is area, so square what the user entered
// to convert from DP to VW tolerance
case RETAIN_EFFECTIVE_AREAS -> new VWSimplifier().setTolerance(tolerance * tolerance).transform(scaled);
case RETAIN_WEIGHTED_EFFECTIVE_AREAS ->
new VWSimplifier().setWeight(0.7).setTolerance(tolerance * tolerance).transform(scaled);
};
List<List<CoordinateSequence>> groups = GeometryCoordinateSequences.extractGroups(geom, minSize);
try {
sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ static int applyAsIntOrElse(ZoomFunction<? extends Number> fn, int zoom, int def
return result == null ? defaultValue : result.intValue();
}

/** Invoke a function at a zoom level and returns {@code defaultValue} if the function or result were null. */
static <T> T applyOrElse(ZoomFunction<T> fn, int zoom, T defaultValue) {
if (fn == null) {
return defaultValue;
}
T result = fn.apply(zoom);
return result == null ? defaultValue : result;
}

/**
* Returns a zoom function that returns the value from the next higher key in {@code thresholds} or {@code null} if
* over the max key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.onthegomap.planetiler.files.ReadableFilesArchive;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.geo.SimplifyStrategy;
import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.geo.TileOrder;
import com.onthegomap.planetiler.mbtiles.Mbtiles;
Expand Down Expand Up @@ -429,26 +430,37 @@ void testLabelGridLimit() throws Exception {
}

@ParameterizedTest
@ValueSource(booleans = {false, true})
void testLineString(boolean anyGeom) throws Exception {
@CsvSource({
"false,RETAIN_IMPORTANT_POINTS",
"false,RETAIN_EFFECTIVE_AREAS",
"false,RETAIN_WEIGHTED_EFFECTIVE_AREAS",
"true,RETAIN_IMPORTANT_POINTS",
})
void testLineString(boolean anyGeom, SimplifyStrategy simplifyStrategy) throws Exception {
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 ymid = (y1 + y2) / 2;
double xmid = (x1 + x2) / 2;
double lat1 = GeoUtils.getWorldLat(y1);
double lng1 = GeoUtils.getWorldLon(x1);
double latMid = GeoUtils.getWorldLat(ymid);
double lngMid = GeoUtils.getWorldLon(xmid);
double lat2 = GeoUtils.getWorldLat(y2);
double lng2 = GeoUtils.getWorldLon(x2);

var results = runWithReaderFeatures(
Map.of("threads", "1"),
List.of(
newReaderFeature(newLineString(lng1, lat1, lng2, lat2), Map.of(
newReaderFeature(newLineString(lng1, lat1, lngMid, latMid, lng2, lat2), Map.of(
"attr", "value"
))
),
(in, features) -> (anyGeom ? features.anyGeometry("layer") : features.line("layer"))
.setZoomRange(13, 14)
.setPixelTolerance(1)
.setSimplifyStrategy(simplifyStrategy)
.setBufferPixels(4)
);

Expand Down Expand Up @@ -2549,8 +2561,13 @@ void testBoundFilters() throws Exception {
assertEquals(bboxResult.tiles, polyResult.tiles);
}

@Test
void testSimplePolygon() throws Exception {
@ParameterizedTest
@CsvSource({
"RETAIN_IMPORTANT_POINTS",
"RETAIN_EFFECTIVE_AREAS",
"RETAIN_WEIGHTED_EFFECTIVE_AREAS",
})
void testSimplePolygon(SimplifyStrategy strategy) throws Exception {
List<Coordinate> points = z14PixelRectangle(0, 40);

var results = runWithReaderFeatures(
Expand All @@ -2561,6 +2578,8 @@ void testSimplePolygon() throws Exception {
(in, features) -> features.polygon("layer")
.setZoomRange(0, 14)
.setBufferPixels(0)
.setPixelTolerance(1)
.setSimplifyStrategy(strategy)
.setMinPixelSize(10) // should only show up z14 (40) z13 (20) and z12 (10)
);

Expand Down

0 comments on commit 339362f

Please sign in to comment.