2021-12-23 10:42:24 +00:00
|
|
|
package com.onthegomap.planetiler;
|
2021-09-18 10:05:50 +00:00
|
|
|
|
2024-06-11 12:49:45 +00:00
|
|
|
import com.onthegomap.planetiler.config.PlanetilerConfig;
|
|
|
|
import com.onthegomap.planetiler.expression.Expression;
|
|
|
|
import com.onthegomap.planetiler.expression.MultiExpression;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.geo.GeometryException;
|
2024-02-03 11:14:08 +00:00
|
|
|
import com.onthegomap.planetiler.geo.TileCoord;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.reader.SourceFeature;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
2021-09-18 10:05:50 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2024-06-11 12:49:45 +00:00
|
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
2021-09-18 10:05:50 +00:00
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A framework for building complex {@link Profile Profiles} that need to be broken apart into multiple handlers (i.e.
|
|
|
|
* one per layer).
|
|
|
|
* <p>
|
2022-03-09 02:08:03 +00:00
|
|
|
* Individual handlers added with {@link #registerHandler(Handler)} can listen on events by implementing these handlers:
|
2021-09-18 10:05:50 +00:00
|
|
|
* <ul>
|
2022-03-09 02:08:03 +00:00
|
|
|
* <li>{@link OsmRelationPreprocessor} to process every OSM relation during first pass through OSM file</li>
|
|
|
|
* <li>{@link FeatureProcessor} to handle features from a particular source (added through
|
|
|
|
* {@link #registerSourceHandler(String, FeatureProcessor)})</li>
|
|
|
|
* <li>{@link FinishHandler} to be notified whenever we finish processing each source</li>
|
2024-02-03 11:14:08 +00:00
|
|
|
* <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>
|
2021-09-18 10:05:50 +00:00
|
|
|
* </ul>
|
2022-07-29 10:40:15 +00:00
|
|
|
* See {@code OpenMapTilesProfile} for a full implementation using this framework.
|
2021-09-18 10:05:50 +00:00
|
|
|
*/
|
|
|
|
public abstract class ForwardingProfile implements Profile {
|
|
|
|
|
|
|
|
private final List<Handler> handlers = new ArrayList<>();
|
2022-01-16 15:00:57 +00:00
|
|
|
/** Handlers that pre-process OSM nodes during pass 1 through the data. */
|
|
|
|
private final List<OsmNodePreprocessor> osmNodePreprocessors = new ArrayList<>();
|
|
|
|
/** Handlers that pre-process OSM ways during pass 1 through the data. */
|
|
|
|
private final List<OsmWayPreprocessor> osmWayPreprocessors = new ArrayList<>();
|
2021-09-18 10:05:50 +00:00
|
|
|
/** Handlers that pre-process OSM relations during pass 1 through the data. */
|
|
|
|
private final List<OsmRelationPreprocessor> osmRelationPreprocessors = new ArrayList<>();
|
|
|
|
/** Handlers that get a callback when each source is finished reading. */
|
|
|
|
private final List<FinishHandler> finishHandlers = new ArrayList<>();
|
2024-02-03 11:14:08 +00:00
|
|
|
/** Map from layer name to its handler if it implements {@link LayerPostProcesser}. */
|
|
|
|
private final Map<String, List<LayerPostProcesser>> layerPostProcessors = new HashMap<>();
|
|
|
|
/** List of handlers that implement {@link TilePostProcessor}. */
|
|
|
|
private final List<TilePostProcessor> tilePostProcessors = new ArrayList<>();
|
2024-06-11 12:49:45 +00:00
|
|
|
/** List of handlers that implements {@link FeatureProcessor} along with a filter expression. */
|
|
|
|
private final List<MultiExpression.Entry<FeatureProcessor>> sourceElementProcessors = new CopyOnWriteArrayList<>();
|
|
|
|
private final List<String> onlyLayers;
|
|
|
|
private final List<String> excludeLayers;
|
|
|
|
@SuppressWarnings("java:S3077")
|
|
|
|
private volatile MultiExpression.Index<FeatureProcessor> indexedSourceElementProcessors = null;
|
|
|
|
|
|
|
|
protected ForwardingProfile(PlanetilerConfig config, Handler... handlers) {
|
|
|
|
onlyLayers = config.arguments().getList("only_layers", "Include only certain layers", List.of());
|
|
|
|
excludeLayers = config.arguments().getList("exclude_layers", "Exclude certain layers", List.of());
|
|
|
|
for (var handler : handlers) {
|
|
|
|
registerHandler(handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected ForwardingProfile() {
|
|
|
|
onlyLayers = List.of();
|
|
|
|
excludeLayers = List.of();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected ForwardingProfile(Handler... handlers) {
|
|
|
|
onlyLayers = List.of();
|
|
|
|
excludeLayers = List.of();
|
|
|
|
for (var handler : handlers) {
|
|
|
|
registerHandler(handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean caresAboutLayer(String layer) {
|
|
|
|
return (onlyLayers.isEmpty() || onlyLayers.contains(layer)) && !excludeLayers.contains(layer);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean caresAboutLayer(Object obj) {
|
|
|
|
return !(obj instanceof HandlerForLayer l) || caresAboutLayer(l.name());
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Call {@code processor} for every element in {@code source}.
|
|
|
|
*
|
|
|
|
* @param source string ID of the source
|
|
|
|
* @param processor handler that will process elements in that source
|
|
|
|
*/
|
|
|
|
public void registerSourceHandler(String source, FeatureProcessor processor) {
|
2024-06-11 12:49:45 +00:00
|
|
|
if (!caresAboutLayer(processor)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sourceElementProcessors
|
|
|
|
.add(MultiExpression.entry(processor, Expression.and(Expression.matchSource(source), processor.filter())));
|
|
|
|
synchronized (sourceElementProcessors) {
|
|
|
|
indexedSourceElementProcessors = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Call {@code processor} for every element.
|
|
|
|
*
|
|
|
|
* @param processor handler that will process elements in that source
|
|
|
|
*/
|
|
|
|
public void registerFeatureHandler(FeatureProcessor processor) {
|
|
|
|
if (!caresAboutLayer(processor)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sourceElementProcessors.add(MultiExpression.entry(processor, processor.filter()));
|
|
|
|
synchronized (sourceElementProcessors) {
|
|
|
|
indexedSourceElementProcessors = null;
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-09 02:08:03 +00:00
|
|
|
* Call {@code handler} for different events based on which interfaces {@code handler} implements:
|
2024-02-03 11:14:08 +00:00
|
|
|
* {@link OsmRelationPreprocessor}, {@link FinishHandler}, {@link TilePostProcessor} or {@link LayerPostProcesser}.
|
2021-09-18 10:05:50 +00:00
|
|
|
*/
|
|
|
|
public void registerHandler(Handler handler) {
|
2024-06-11 12:49:45 +00:00
|
|
|
if (!caresAboutLayer(handler)) {
|
|
|
|
return;
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
this.handlers.add(handler);
|
2022-01-16 15:00:57 +00:00
|
|
|
if (handler instanceof OsmNodePreprocessor osmNodePreprocessor) {
|
|
|
|
osmNodePreprocessors.add(osmNodePreprocessor);
|
|
|
|
}
|
|
|
|
if (handler instanceof OsmWayPreprocessor osmWayPreprocessor) {
|
|
|
|
osmWayPreprocessors.add(osmWayPreprocessor);
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
if (handler instanceof OsmRelationPreprocessor osmRelationPreprocessor) {
|
|
|
|
osmRelationPreprocessors.add(osmRelationPreprocessor);
|
|
|
|
}
|
|
|
|
if (handler instanceof FinishHandler finishHandler) {
|
|
|
|
finishHandlers.add(finishHandler);
|
|
|
|
}
|
2024-02-03 11:14:08 +00:00
|
|
|
if (handler instanceof LayerPostProcesser postProcessor) {
|
|
|
|
layerPostProcessors.computeIfAbsent(postProcessor.name(), name -> new ArrayList<>())
|
2021-09-18 10:05:50 +00:00
|
|
|
.add(postProcessor);
|
|
|
|
}
|
2024-02-03 11:14:08 +00:00
|
|
|
if (handler instanceof TilePostProcessor postProcessor) {
|
|
|
|
tilePostProcessors.add(postProcessor);
|
|
|
|
}
|
2024-06-11 12:49:45 +00:00
|
|
|
if (handler instanceof FeatureProcessor processor) {
|
|
|
|
registerFeatureHandler(processor);
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
|
2022-01-16 15:00:57 +00:00
|
|
|
@Override
|
|
|
|
public void preprocessOsmNode(OsmElement.Node node) {
|
|
|
|
for (OsmNodePreprocessor osmNodePreprocessor : osmNodePreprocessors) {
|
|
|
|
osmNodePreprocessor.preprocessOsmNode(node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void preprocessOsmWay(OsmElement.Way way) {
|
|
|
|
for (OsmWayPreprocessor osmWayPreprocessor : osmWayPreprocessors) {
|
|
|
|
osmWayPreprocessor.preprocessOsmWay(way);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-18 10:05:50 +00:00
|
|
|
@Override
|
|
|
|
public List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation) {
|
|
|
|
// delegate OSM relation pre-processing to each layer, if it implements FeaturePostProcessor
|
|
|
|
List<OsmRelationInfo> result = null;
|
|
|
|
for (OsmRelationPreprocessor osmRelationPreprocessor : osmRelationPreprocessors) {
|
|
|
|
List<OsmRelationInfo> thisResult = osmRelationPreprocessor
|
|
|
|
.preprocessOsmRelation(relation);
|
|
|
|
if (thisResult != null) {
|
|
|
|
if (result == null) {
|
|
|
|
result = new ArrayList<>(thisResult);
|
|
|
|
} else {
|
|
|
|
result.addAll(thisResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
|
|
|
|
// delegate source feature processing to each handler for that source
|
2024-06-11 12:49:45 +00:00
|
|
|
for (var handler : getHandlerIndex().getMatches(sourceFeature)) {
|
|
|
|
handler.processFeature(sourceFeature, features);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private MultiExpression.Index<FeatureProcessor> getHandlerIndex() {
|
|
|
|
MultiExpression.Index<FeatureProcessor> result = indexedSourceElementProcessors;
|
|
|
|
if (result == null) {
|
|
|
|
synchronized (sourceElementProcessors) {
|
|
|
|
result = indexedSourceElementProcessors;
|
|
|
|
if (result == null) {
|
|
|
|
indexedSourceElementProcessors = result = MultiExpression.of(sourceElementProcessors).index();
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
}
|
2024-06-11 12:49:45 +00:00
|
|
|
return result;
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean caresAboutSource(String name) {
|
2024-06-11 12:49:45 +00:00
|
|
|
return caresAbout(Expression.PartialInput.ofSource(name));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean caresAbout(Expression.PartialInput input) {
|
|
|
|
return sourceElementProcessors.stream().anyMatch(e -> e.expression()
|
|
|
|
.partialEvaluate(input)
|
|
|
|
.simplify() != Expression.FALSE);
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public List<VectorTile.Feature> postProcessLayerFeatures(String layer, int zoom, List<VectorTile.Feature> items)
|
|
|
|
throws GeometryException {
|
|
|
|
// delegate feature post-processing to each layer, if it implements FeaturePostProcessor
|
2024-02-03 11:14:08 +00:00
|
|
|
List<LayerPostProcesser> postProcessers = layerPostProcessors.get(layer);
|
2021-09-18 10:05:50 +00:00
|
|
|
List<VectorTile.Feature> result = items;
|
2024-02-03 11:14:08 +00:00
|
|
|
if (postProcessers != null) {
|
|
|
|
for (var handler : postProcessers) {
|
2021-09-18 10:05:50 +00:00
|
|
|
var thisResult = handler.postProcess(zoom, result);
|
|
|
|
if (thisResult != null) {
|
|
|
|
result = thisResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-03 11:14:08 +00:00
|
|
|
@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;
|
|
|
|
}
|
|
|
|
|
2021-09-18 10:05:50 +00:00
|
|
|
@Override
|
|
|
|
public void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
|
|
|
Consumer<FeatureCollector.Feature> next) {
|
|
|
|
// delegate finish handling to every layer that implements FinishHandler
|
|
|
|
for (var handler : finishHandlers) {
|
|
|
|
handler.finish(sourceName, featureCollectors, next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void release() {
|
|
|
|
// release resources used by each handler
|
|
|
|
handlers.forEach(Handler::release);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Interface for handlers that this profile forwards to should implement. */
|
|
|
|
public interface Handler {
|
|
|
|
|
|
|
|
/** Free any resources associated with this profile (i.e. shared data structures) */
|
2022-03-09 02:08:03 +00:00
|
|
|
default void release() {}
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public interface HandlerForLayer extends Handler {
|
|
|
|
|
|
|
|
/** The layer name this handler is for */
|
|
|
|
String name();
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Handlers should implement this interface to get notified when a source finishes processing. */
|
|
|
|
public interface FinishHandler extends Handler {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invoked once for each source after all elements for a source have been processed.
|
|
|
|
*
|
|
|
|
* @see Profile#finish(String, FeatureCollector.Factory, Consumer)
|
|
|
|
*/
|
|
|
|
void finish(String sourceName, FeatureCollector.Factory featureCollectors,
|
|
|
|
Consumer<FeatureCollector.Feature> emit);
|
|
|
|
}
|
|
|
|
|
2022-01-16 15:00:57 +00:00
|
|
|
/** Handlers should implement this interface to pre-process OSM nodes during pass 1 through the data. */
|
|
|
|
public interface OsmNodePreprocessor extends Handler {
|
|
|
|
|
|
|
|
/**
|
2022-03-09 02:08:03 +00:00
|
|
|
* Extracts information from an OSM node during pass 1 of the input OSM data that the profile may need during pass2.
|
2022-01-16 15:00:57 +00:00
|
|
|
*
|
|
|
|
* @see Profile#preprocessOsmNode(OsmElement.Node)
|
|
|
|
*/
|
|
|
|
void preprocessOsmNode(OsmElement.Node node);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Handlers should implement this interface to pre-process OSM ways during pass 1 through the data. */
|
|
|
|
public interface OsmWayPreprocessor extends Handler {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extracts information from an OSM way during pass 1 of the input OSM data that the profile may need during pass2.
|
|
|
|
*
|
|
|
|
* @see Profile#preprocessOsmWay(OsmElement.Way)
|
|
|
|
*/
|
|
|
|
void preprocessOsmWay(OsmElement.Way way);
|
|
|
|
}
|
|
|
|
|
2021-09-18 10:05:50 +00:00
|
|
|
/** Handlers should implement this interface to pre-process OSM relations during pass 1 through the data. */
|
|
|
|
public interface OsmRelationPreprocessor extends Handler {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns information extracted from an OSM relation during pass 1 of the input OSM data to make available when
|
|
|
|
* processing elements in that relation during pass 2.
|
|
|
|
*
|
|
|
|
* @see Profile#preprocessOsmRelation(OsmElement.Relation)
|
|
|
|
*/
|
|
|
|
List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation);
|
|
|
|
}
|
|
|
|
|
2024-02-03 11:14:08 +00:00
|
|
|
/** Handlers should implement this interface to post-process vector tile features before emitting an output layer. */
|
|
|
|
public interface LayerPostProcesser extends HandlerForLayer {
|
2021-09-18 10:05:50 +00:00
|
|
|
|
|
|
|
/**
|
2024-02-03 11:14:08 +00:00
|
|
|
* Apply any post-processing to features in this output layer of a tile before writing it to the output archive.
|
2021-09-18 10:05:50 +00:00
|
|
|
*
|
|
|
|
* @throws GeometryException if the input elements cannot be deserialized, or output elements cannot be serialized
|
|
|
|
* @see Profile#postProcessLayerFeatures(String, int, List)
|
|
|
|
*/
|
|
|
|
List<VectorTile.Feature> postProcess(int zoom, List<VectorTile.Feature> items) throws GeometryException;
|
|
|
|
}
|
|
|
|
|
2024-02-03 11:14:08 +00:00
|
|
|
/** @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;
|
|
|
|
}
|
|
|
|
|
2021-09-18 10:05:50 +00:00
|
|
|
/** Handlers should implement this interface to process input features from a given source ID. */
|
2024-06-11 12:49:45 +00:00
|
|
|
@FunctionalInterface
|
|
|
|
public interface FeatureProcessor extends com.onthegomap.planetiler.FeatureProcessor<SourceFeature>, Handler {
|
2021-09-18 10:05:50 +00:00
|
|
|
|
2024-06-11 12:49:45 +00:00
|
|
|
/** Returns an {@link Expression} that limits the features that this processor gets called for. */
|
|
|
|
default Expression filter() {
|
|
|
|
return Expression.TRUE;
|
|
|
|
}
|
2021-09-18 10:05:50 +00:00
|
|
|
}
|
|
|
|
}
|