Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permit post-process merging in custommap schemas #626

Merged
merged 19 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions planetiler-custommap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ A layer contains a thematically-related set of features from one or more input s

- `id` - Unique name of this layer
- `features` - A list of features contained in this layer. See [Layer Features](#layer-feature)
- `tile_post_process` - Optional processing operations to merge features with the same attributes in a rendered tile.
See [Tile Post Process](#tile-post-process)

For example:

Expand All @@ -201,6 +203,11 @@ layers:
features:
- { ... }
- { ... }
tile_post_process:
merge_line_strings:
min_length: 1
tolerance: 1
buffer: 5
```

## Layer Feature
Expand Down Expand Up @@ -279,6 +286,34 @@ tag_value: voltage
type: integer
```

## Tile Post Process

Specific tile post processing operations for merging features may be defined:

- `merge_line_strings` - Combines linestrings with the same set of attributes into a multilinestring where segments with
touching endpoints are merged.
- `merge_polygons` - Combines polygons with the same set of attributes into a multipolygon where overlapping/touching polygons
are combined into fewer polygons covering the same area.

The follow attributes for `merge_line_strings` may be set:
- `min_length` - Minimum tile pixel length of features to emit, or 0 to emit all merged linestrings.
- `tolerance` - After merging, simplify linestrings using this pixel tolerance, or -1 to skip simplification step.
- `buffer` - Number of pixels outside the visible tile area to include detail for, or -1 to skip clipping step.

The follow attribute for `merge_polygons` may be set:
- `min_area` - Minimum area in square tile pixels of polygons to emit.

For example:

```yaml
merge_line_strings:
min_length: 1
tolerance: 1
buffer: 5
merge_polygons:
min_area: 1
```

## Data Type

A string enum that defines how to map from an input. Allowed values:
Expand Down
37 changes: 37 additions & 0 deletions planetiler-custommap/planetiler.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@
"items": {
"$ref": "#/$defs/feature"
}
},
"tile_post_process": {
"description": "Optional processing operations to merge features with the same attributes in a rendered tile",
"$ref": "#/$defs/tile_post_process"
}
}
}
Expand Down Expand Up @@ -399,6 +403,39 @@
}
}
},
"tile_post_process": {
"type": "object",
"properties": {
"merge_line_strings": {
"description": "Combines linestrings with the same set of attributes into a multilinestring where segments with touching endpoints are merged",
"type": "object",
"properties": {
"min_length": {
"description": "Minimum tile pixel length of features to emit, or 0 to emit all merged linestrings",
"type": "number"
},
"tolerance": {
"description": "After merging, simplify linestrings using this pixel tolerance, or -1 to skip simplification step",
"type": "number"
},
"buffer": {
"description": "Number of pixels outside the visible tile area to include detail for, or -1 to skip clipping step",
"type": "number"
}
}
},
"merge_polygons": {
"description": "Combines polygons with the same set of attributes into a multipolygon where overlapping/touching polygons are combined into fewer polygons covering the same area",
"type": "object",
"properties": {
"min_area": {
"description": "Minimum area in square tile pixels of polygons to emit",
"type": "number"
}
}
}
}
},
"zoom_level": {
"type": "integer",
"minimum": 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
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;
Expand All @@ -25,6 +28,8 @@ public class ConfiguredProfile implements Profile {

private final SchemaConfig schema;

private final Collection<FeatureLayer> layers;
private final Map<String, FeatureLayer> layersById = new HashMap<>();
private final Map<String, Index<ConfiguredFeature>> featureLayerMatcher;
private final TagValueProducer tagValueProducer;
private final Contexts.Root rootContext;
Expand All @@ -33,7 +38,7 @@ public ConfiguredProfile(SchemaConfig schema, Contexts.Root rootContext) {
this.schema = schema;
this.rootContext = rootContext;

Collection<FeatureLayer> layers = schema.layers();
layers = schema.layers();
if (layers == null || layers.isEmpty()) {
throw new IllegalArgumentException("No layers defined");
}
Expand All @@ -44,6 +49,7 @@ public ConfiguredProfile(SchemaConfig schema, Contexts.Root rootContext) {

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());
Expand Down Expand Up @@ -84,6 +90,36 @@ public void processFeature(SourceFeature sourceFeature, FeatureCollector feature
}
}

@Override
public List<VectorTile.Feature> postProcessLayerFeatures(String layer, int zoom,
List<VectorTile.Feature> items) throws GeometryException {
FeatureLayer featureLayer = findFeatureLayer(layer);
msbarry marked this conversation as resolved.
Show resolved Hide resolved

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();
Expand All @@ -98,4 +134,8 @@ public List<Source> sources() {
});
return sources;
}

public FeatureLayer findFeatureLayer(String layerId) {
return layersById.get(layerId);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.onthegomap.planetiler.custommap.configschema;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collection;

public record FeatureLayer(
String id,
Collection<FeatureItem> features
Collection<FeatureItem> features,
@JsonProperty("tile_post_process") PostProcess postProcess
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks good! Only thing left is to mention these features in planetiler.schema.json and planetiler-custommap/README.md

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change pushed.

) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.onthegomap.planetiler.custommap.configschema;

import com.fasterxml.jackson.annotation.JsonProperty;

public record MergeLineStrings(
@JsonProperty("min_length") double minLength,
@JsonProperty("tolerance") double tolerance,
@JsonProperty("buffer") double buffer
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.onthegomap.planetiler.custommap.configschema;

import com.fasterxml.jackson.annotation.JsonProperty;

public record MergePolygons(
@JsonProperty("min_area") double minArea
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.onthegomap.planetiler.custommap.configschema;

import com.fasterxml.jackson.annotation.JsonProperty;

public record PostProcess(
@JsonProperty("merge_line_strings") MergeLineStrings mergeLineStrings,
@JsonProperty("merge_polygons") MergePolygons mergePolygons
) {}
43 changes: 43 additions & 0 deletions planetiler-custommap/src/main/resources/samples/railways.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
schema_name: Railways
schema_description: Railways (layers outputting merged & un-merged lines)
attribution: <a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>
args:
area:
description: Geofabrik area to download
default: greater-london
osm_url:
description: OSM URL to download
default: '${ args.area == "planet" ? "aws:latest" : ("geofabrik:" + args.area) }'
sources:
osm:
type: osm
url: '${ args.osm_url }'
layers:
- id: railways_merged
features:
- source: osm
geometry: line
min_zoom: 4
min_size: 0
include_when:
__all__:
- railway: rail
- usage: main
exclude_when:
service: __any__
tile_post_process:
merge_line_strings:
min_length: 0
tolerance: -1
buffer: -1
- id: railways_unmerged
features:
- source: osm
geometry: line
min_zoom: 4
include_when:
__all__:
- railway: rail
- usage: main
exclude_when:
service: __any__
Loading