package com.onthegomap.planetiler.custommap; import static com.onthegomap.planetiler.expression.MultiExpression.Entry; import static java.util.Map.entry; import com.onthegomap.planetiler.FeatureCollector; import com.onthegomap.planetiler.FeatureMerge; import com.onthegomap.planetiler.Profile; import com.onthegomap.planetiler.VectorTile; import com.onthegomap.planetiler.custommap.configschema.FeatureLayer; import com.onthegomap.planetiler.custommap.configschema.SchemaConfig; import com.onthegomap.planetiler.expression.MultiExpression; import com.onthegomap.planetiler.expression.MultiExpression.Index; import com.onthegomap.planetiler.geo.GeometryException; import com.onthegomap.planetiler.reader.SourceFeature; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * A profile configured from a yml file. */ public class ConfiguredProfile implements Profile { private final SchemaConfig schema; private final Collection layers; private final Map layersById = new HashMap<>(); private final Map> featureLayerMatcher; private final TagValueProducer tagValueProducer; private final Contexts.Root rootContext; public ConfiguredProfile(SchemaConfig schema, Contexts.Root rootContext) { this.schema = schema; this.rootContext = rootContext; layers = schema.layers(); if (layers == null || layers.isEmpty()) { throw new IllegalArgumentException("No layers defined"); } tagValueProducer = new TagValueProducer(schema.inputMappings()); Map>> configuredFeatureEntries = new HashMap<>(); for (var layer : layers) { String layerId = layer.id(); layersById.put(layerId, layer); for (var feature : layer.features()) { var configuredFeature = new ConfiguredFeature(layerId, tagValueProducer, feature, rootContext); var entry = new Entry<>(configuredFeature, configuredFeature.matchExpression()); for (var source : feature.source()) { var list = configuredFeatureEntries.computeIfAbsent(source, s -> new ArrayList<>()); list.add(entry); } } } featureLayerMatcher = configuredFeatureEntries.entrySet().stream() .map(entry -> entry(entry.getKey(), MultiExpression.of(entry.getValue()).index())) .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } @Override public String name() { return schema.schemaName(); } @Override public String attribution() { return schema.attribution(); } @Override public void processFeature(SourceFeature sourceFeature, FeatureCollector featureCollector) { var context = rootContext.createProcessFeatureContext(sourceFeature, tagValueProducer); var index = featureLayerMatcher.get(sourceFeature.getSource()); if (index != null) { var matches = index.getMatchesWithTriggers(context); for (var configuredFeature : matches) { configuredFeature.match().processFeature( context.createPostMatchContext(configuredFeature.keys()), featureCollector ); } } } @Override public List postProcessLayerFeatures(String layer, int zoom, List items) throws GeometryException { FeatureLayer featureLayer = findFeatureLayer(layer); if (featureLayer.postProcess() == null) { return items; } if (featureLayer.postProcess().mergeLineStrings() != null) { var merge = featureLayer.postProcess().mergeLineStrings(); items = FeatureMerge.mergeLineStrings(items, merge.minLength(), // after merging, remove lines that are still less than {minLength}px long merge.tolerance(), // simplify output linestrings using a {tolerance}px tolerance merge.buffer() // remove any detail more than {buffer}px outside the tile boundary ); } if (featureLayer.postProcess().mergePolygons() != null) { var merge = featureLayer.postProcess().mergePolygons(); items = FeatureMerge.mergeOverlappingPolygons(items, merge.minArea() // after merging, remove polygons that are still less than {minArea} in square tile pixels ); } return items; } @Override public String description() { return schema.schemaDescription(); } public List sources() { List sources = new ArrayList<>(); schema.sources().forEach((key, value) -> { var url = ConfigExpressionParser.tryStaticEvaluate(rootContext, value.url(), String.class).get(); var path = ConfigExpressionParser.tryStaticEvaluate(rootContext, value.localPath(), String.class).get(); sources.add(new Source(key, value.type(), url, path == null ? null : Path.of(path))); }); return sources; } public FeatureLayer findFeatureLayer(String layerId) { return layersById.get(layerId); } }