kopia lustrzana https://github.com/onthegomap/planetiler
Add tile post-process hook to forwarding profile (#813)
rodzic
f97b5f9cb9
commit
7c03592358
|
@ -1,6 +1,7 @@
|
||||||
package com.onthegomap.planetiler;
|
package com.onthegomap.planetiler;
|
||||||
|
|
||||||
import com.onthegomap.planetiler.geo.GeometryException;
|
import com.onthegomap.planetiler.geo.GeometryException;
|
||||||
|
import com.onthegomap.planetiler.geo.TileCoord;
|
||||||
import com.onthegomap.planetiler.reader.SourceFeature;
|
import com.onthegomap.planetiler.reader.SourceFeature;
|
||||||
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
||||||
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
||||||
|
@ -20,7 +21,8 @@ import java.util.function.Consumer;
|
||||||
* <li>{@link FeatureProcessor} to handle features from a particular source (added through
|
* <li>{@link FeatureProcessor} to handle features from a particular source (added through
|
||||||
* {@link #registerSourceHandler(String, FeatureProcessor)})</li>
|
* {@link #registerSourceHandler(String, FeatureProcessor)})</li>
|
||||||
* <li>{@link FinishHandler} to be notified whenever we finish processing each source</li>
|
* <li>{@link FinishHandler} to be notified whenever we finish processing each source</li>
|
||||||
* <li>{@link FeaturePostProcessor} to post-process features in a layer before rendering the output tile</li>
|
* <li>{@link LayerPostProcesser} to post-process features in a layer before rendering the output tile</li>
|
||||||
|
* <li>{@link TilePostProcessor} to post-process features in a tile before rendering the output tile</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* See {@code OpenMapTilesProfile} for a full implementation using this framework.
|
* See {@code OpenMapTilesProfile} for a full implementation using this framework.
|
||||||
*/
|
*/
|
||||||
|
@ -35,8 +37,10 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
private final List<OsmRelationPreprocessor> osmRelationPreprocessors = new ArrayList<>();
|
private final List<OsmRelationPreprocessor> osmRelationPreprocessors = new ArrayList<>();
|
||||||
/** Handlers that get a callback when each source is finished reading. */
|
/** Handlers that get a callback when each source is finished reading. */
|
||||||
private final List<FinishHandler> finishHandlers = new ArrayList<>();
|
private final List<FinishHandler> finishHandlers = new ArrayList<>();
|
||||||
/** Map from layer name to its handler if it implements {@link FeaturePostProcessor}. */
|
/** Map from layer name to its handler if it implements {@link LayerPostProcesser}. */
|
||||||
private final Map<String, List<FeaturePostProcessor>> postProcessors = new HashMap<>();
|
private final Map<String, List<LayerPostProcesser>> layerPostProcessors = new HashMap<>();
|
||||||
|
/** List of handlers that implement {@link TilePostProcessor}. */
|
||||||
|
private final List<TilePostProcessor> tilePostProcessors = new ArrayList<>();
|
||||||
/** Map from source ID to its handler if it implements {@link FeatureProcessor}. */
|
/** Map from source ID to its handler if it implements {@link FeatureProcessor}. */
|
||||||
private final Map<String, List<FeatureProcessor>> sourceElementProcessors = new HashMap<>();
|
private final Map<String, List<FeatureProcessor>> sourceElementProcessors = new HashMap<>();
|
||||||
|
|
||||||
|
@ -53,7 +57,7 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call {@code handler} for different events based on which interfaces {@code handler} implements:
|
* Call {@code handler} for different events based on which interfaces {@code handler} implements:
|
||||||
* {@link OsmRelationPreprocessor}, {@link FinishHandler}, or {@link FeaturePostProcessor}.
|
* {@link OsmRelationPreprocessor}, {@link FinishHandler}, {@link TilePostProcessor} or {@link LayerPostProcesser}.
|
||||||
*/
|
*/
|
||||||
public void registerHandler(Handler handler) {
|
public void registerHandler(Handler handler) {
|
||||||
this.handlers.add(handler);
|
this.handlers.add(handler);
|
||||||
|
@ -69,10 +73,13 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
if (handler instanceof FinishHandler finishHandler) {
|
if (handler instanceof FinishHandler finishHandler) {
|
||||||
finishHandlers.add(finishHandler);
|
finishHandlers.add(finishHandler);
|
||||||
}
|
}
|
||||||
if (handler instanceof FeaturePostProcessor postProcessor) {
|
if (handler instanceof LayerPostProcesser postProcessor) {
|
||||||
postProcessors.computeIfAbsent(postProcessor.name(), name -> new ArrayList<>())
|
layerPostProcessors.computeIfAbsent(postProcessor.name(), name -> new ArrayList<>())
|
||||||
.add(postProcessor);
|
.add(postProcessor);
|
||||||
}
|
}
|
||||||
|
if (handler instanceof TilePostProcessor postProcessor) {
|
||||||
|
tilePostProcessors.add(postProcessor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,10 +136,10 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
public List<VectorTile.Feature> postProcessLayerFeatures(String layer, int zoom, List<VectorTile.Feature> items)
|
public List<VectorTile.Feature> postProcessLayerFeatures(String layer, int zoom, List<VectorTile.Feature> items)
|
||||||
throws GeometryException {
|
throws GeometryException {
|
||||||
// delegate feature post-processing to each layer, if it implements FeaturePostProcessor
|
// delegate feature post-processing to each layer, if it implements FeaturePostProcessor
|
||||||
List<FeaturePostProcessor> handlers = postProcessors.get(layer);
|
List<LayerPostProcesser> postProcessers = layerPostProcessors.get(layer);
|
||||||
List<VectorTile.Feature> result = items;
|
List<VectorTile.Feature> result = items;
|
||||||
if (handlers != null) {
|
if (postProcessers != null) {
|
||||||
for (FeaturePostProcessor handler : handlers) {
|
for (var handler : postProcessers) {
|
||||||
var thisResult = handler.postProcess(zoom, result);
|
var thisResult = handler.postProcess(zoom, result);
|
||||||
if (thisResult != null) {
|
if (thisResult != null) {
|
||||||
result = thisResult;
|
result = thisResult;
|
||||||
|
@ -142,6 +149,20 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, List<VectorTile.Feature>> postProcessTileFeatures(TileCoord tileCoord,
|
||||||
|
Map<String, List<VectorTile.Feature>> layers) throws GeometryException {
|
||||||
|
var result = layers;
|
||||||
|
for (TilePostProcessor postProcessor : tilePostProcessors) {
|
||||||
|
// TODO catch failures to isolate from other tile postprocessors?
|
||||||
|
var thisResult = postProcessor.postProcessTile(tileCoord, result);
|
||||||
|
if (thisResult != null) {
|
||||||
|
result = thisResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
public void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
||||||
Consumer<FeatureCollector.Feature> next) {
|
Consumer<FeatureCollector.Feature> next) {
|
||||||
|
@ -217,11 +238,11 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation);
|
List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Handlers should implement this interface to post-process vector tile features before emitting an output tile. */
|
/** Handlers should implement this interface to post-process vector tile features before emitting an output layer. */
|
||||||
public interface FeaturePostProcessor extends HandlerForLayer {
|
public interface LayerPostProcesser extends HandlerForLayer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply any post-processing to features in this output layer of a tile before writing it to the output file.
|
* Apply any post-processing to features in this output layer of a tile before writing it to the output archive.
|
||||||
*
|
*
|
||||||
* @throws GeometryException if the input elements cannot be deserialized, or output elements cannot be serialized
|
* @throws GeometryException if the input elements cannot be deserialized, or output elements cannot be serialized
|
||||||
* @see Profile#postProcessLayerFeatures(String, int, List)
|
* @see Profile#postProcessLayerFeatures(String, int, List)
|
||||||
|
@ -229,6 +250,26 @@ public abstract class ForwardingProfile implements Profile {
|
||||||
List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) throws GeometryException;
|
List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) throws GeometryException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated use {@link LayerPostProcesser} or {@link TilePostProcessor} instead */
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
|
public interface FeaturePostProcessor extends LayerPostProcesser {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handlers should implement this interface to post-process all features in a vector tile before writing to an
|
||||||
|
* archive.
|
||||||
|
*/
|
||||||
|
public interface TilePostProcessor extends Handler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply any post-processing to features in layers in this output tile before writing it to the output archive.
|
||||||
|
*
|
||||||
|
* @throws GeometryException if the input elements cannot be deserialized, or output elements cannot be serialized
|
||||||
|
* @see Profile#postProcessTileFeatures(TileCoord, Map)
|
||||||
|
*/
|
||||||
|
Map<String, List<VectorTile.Feature>> postProcessTile(TileCoord tileCoord,
|
||||||
|
Map<String, List<VectorTile.Feature>> layers) throws GeometryException;
|
||||||
|
}
|
||||||
|
|
||||||
/** Handlers should implement this interface to process input features from a given source ID. */
|
/** Handlers should implement this interface to process input features from a given source ID. */
|
||||||
public interface FeatureProcessor {
|
public interface FeatureProcessor {
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,13 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
import com.onthegomap.planetiler.geo.GeoUtils;
|
import com.onthegomap.planetiler.geo.GeoUtils;
|
||||||
import com.onthegomap.planetiler.geo.GeometryException;
|
import com.onthegomap.planetiler.geo.GeometryException;
|
||||||
|
import com.onthegomap.planetiler.geo.TileCoord;
|
||||||
import com.onthegomap.planetiler.reader.SimpleFeature;
|
import com.onthegomap.planetiler.reader.SimpleFeature;
|
||||||
import com.onthegomap.planetiler.reader.SourceFeature;
|
import com.onthegomap.planetiler.reader.SourceFeature;
|
||||||
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
||||||
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -130,7 +132,7 @@ class ForwardingProfileTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFeaturePostProcessor() throws GeometryException {
|
void testLayerPostProcesser() throws GeometryException {
|
||||||
VectorTile.Feature feature = new VectorTile.Feature(
|
VectorTile.Feature feature = new VectorTile.Feature(
|
||||||
"layer",
|
"layer",
|
||||||
1,
|
1,
|
||||||
|
@ -140,7 +142,7 @@ class ForwardingProfileTests {
|
||||||
assertEquals(List.of(feature), profile.postProcessLayerFeatures("layer", 0, List.of(feature)));
|
assertEquals(List.of(feature), profile.postProcessLayerFeatures("layer", 0, List.of(feature)));
|
||||||
|
|
||||||
// ignore null response
|
// ignore null response
|
||||||
profile.registerHandler(new ForwardingProfile.FeaturePostProcessor() {
|
profile.registerHandler(new ForwardingProfile.LayerPostProcesser() {
|
||||||
@Override
|
@Override
|
||||||
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -154,7 +156,7 @@ class ForwardingProfileTests {
|
||||||
assertEquals(List.of(feature), profile.postProcessLayerFeatures("a", 0, List.of(feature)));
|
assertEquals(List.of(feature), profile.postProcessLayerFeatures("a", 0, List.of(feature)));
|
||||||
|
|
||||||
// empty list removes
|
// empty list removes
|
||||||
profile.registerHandler(new ForwardingProfile.FeaturePostProcessor() {
|
profile.registerHandler(new ForwardingProfile.LayerPostProcesser() {
|
||||||
@Override
|
@Override
|
||||||
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
||||||
return List.of();
|
return List.of();
|
||||||
|
@ -170,7 +172,7 @@ class ForwardingProfileTests {
|
||||||
assertEquals(List.of(feature), profile.postProcessLayerFeatures("b", 0, List.of(feature)));
|
assertEquals(List.of(feature), profile.postProcessLayerFeatures("b", 0, List.of(feature)));
|
||||||
|
|
||||||
// 2 handlers for same layer run one after another
|
// 2 handlers for same layer run one after another
|
||||||
var skip1 = new ForwardingProfile.FeaturePostProcessor() {
|
var skip1 = new ForwardingProfile.LayerPostProcesser() {
|
||||||
@Override
|
@Override
|
||||||
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
||||||
return items.stream().skip(1).toList();
|
return items.stream().skip(1).toList();
|
||||||
|
@ -183,7 +185,7 @@ class ForwardingProfileTests {
|
||||||
};
|
};
|
||||||
profile.registerHandler(skip1);
|
profile.registerHandler(skip1);
|
||||||
profile.registerHandler(skip1);
|
profile.registerHandler(skip1);
|
||||||
profile.registerHandler(new ForwardingProfile.FeaturePostProcessor() {
|
profile.registerHandler(new ForwardingProfile.LayerPostProcesser() {
|
||||||
@Override
|
@Override
|
||||||
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
public List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) {
|
||||||
return null; // ensure that returning null after initial post-processors run keeps the postprocessed result
|
return null; // ensure that returning null after initial post-processors run keeps the postprocessed result
|
||||||
|
@ -199,4 +201,63 @@ class ForwardingProfileTests {
|
||||||
assertEquals(List.of(feature, feature, feature, feature),
|
assertEquals(List.of(feature, feature, feature, feature),
|
||||||
profile.postProcessLayerFeatures("c", 0, List.of(feature, feature, feature, feature)));
|
profile.postProcessLayerFeatures("c", 0, List.of(feature, feature, feature, feature)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTilePostProcesser() throws GeometryException {
|
||||||
|
VectorTile.Feature feature = new VectorTile.Feature(
|
||||||
|
"layer",
|
||||||
|
1,
|
||||||
|
VectorTile.encodeGeometry(GeoUtils.point(0, 0)),
|
||||||
|
Map.of()
|
||||||
|
);
|
||||||
|
assertEquals(Map.of("layer", List.of(feature)), profile.postProcessTileFeatures(TileCoord.ofXYZ(0, 0, 0), Map.of(
|
||||||
|
"layer", List.of(feature)
|
||||||
|
)));
|
||||||
|
|
||||||
|
// ignore null response
|
||||||
|
profile.registerHandler((ForwardingProfile.TilePostProcessor) (tileCoord, layers) -> null);
|
||||||
|
assertEquals(Map.of("a", List.of(feature)),
|
||||||
|
profile.postProcessTileFeatures(TileCoord.ofXYZ(0, 0, 0), Map.of("a", List.of(feature))));
|
||||||
|
|
||||||
|
// empty map removes
|
||||||
|
profile.registerHandler((ForwardingProfile.TilePostProcessor) (tileCoord, layers) -> Map.of());
|
||||||
|
assertEquals(Map.of(),
|
||||||
|
profile.postProcessTileFeatures(TileCoord.ofXYZ(0, 0, 0), Map.of("a", List.of(feature))));
|
||||||
|
// also touches elements in another layer
|
||||||
|
assertEquals(Map.of(),
|
||||||
|
profile.postProcessTileFeatures(TileCoord.ofXYZ(0, 0, 0), Map.of("b", List.of(feature))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStackedTilePostProcessors() throws GeometryException {
|
||||||
|
VectorTile.Feature feature = new VectorTile.Feature(
|
||||||
|
"layer",
|
||||||
|
1,
|
||||||
|
VectorTile.encodeGeometry(GeoUtils.point(0, 0)),
|
||||||
|
Map.of()
|
||||||
|
);
|
||||||
|
var skip1 = new ForwardingProfile.TilePostProcessor() {
|
||||||
|
@Override
|
||||||
|
public Map<String, List<VectorTile.Feature>> postProcessTile(TileCoord tileCoord,
|
||||||
|
Map<String, List<VectorTile.Feature>> layers) {
|
||||||
|
Map<String, List<VectorTile.Feature>> result = new HashMap<>();
|
||||||
|
for (var key : layers.keySet()) {
|
||||||
|
result.put(key, layers.get(key).stream().skip(1).toList());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
profile.registerHandler(skip1);
|
||||||
|
profile.registerHandler(skip1);
|
||||||
|
profile.registerHandler((ForwardingProfile.TilePostProcessor) (tileCoord, layers) -> {
|
||||||
|
// ensure that returning null after initial post-processors run keeps the postprocessed result
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
assertEquals(Map.of("b", List.of(feature, feature)),
|
||||||
|
profile.postProcessTileFeatures(TileCoord.ofXYZ(0, 0, 0),
|
||||||
|
Map.of("b", List.of(feature, feature, feature, feature))));
|
||||||
|
assertEquals(Map.of("c", List.of(feature, feature)),
|
||||||
|
profile.postProcessTileFeatures(TileCoord.ofXYZ(0, 0, 0),
|
||||||
|
Map.of("c", List.of(feature, feature, feature, feature))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue