Skip to content

Commit

Permalink
Access OSM metadata in yaml profiles (#739)
Browse files Browse the repository at this point in the history
  • Loading branch information
msbarry authored Dec 2, 2023
1 parent 4efc2bb commit 40bf33e
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.onthegomap.planetiler.reader;

import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import com.onthegomap.planetiler.reader.osm.OsmReader;
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
import com.onthegomap.planetiler.reader.osm.OsmSourceFeature;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -76,29 +78,87 @@ public static SimpleFeature create(Geometry latLonGeometry, Map<String, Object>
return new SimpleFeature(latLonGeometry, null, tags, null, null, idGenerator.incrementAndGet(), null);
}

private static class SimpleOsmFeature extends SimpleFeature implements OsmSourceFeature {

private final String area;
private final OsmElement.Info info;

private SimpleOsmFeature(Geometry latLonGeometry, Geometry worldGeometry, Map<String, Object> tags, String source,
String sourceLayer, long id, List<OsmReader.RelationMember<OsmRelationInfo>> relations, OsmElement.Info info) {
super(latLonGeometry, worldGeometry, tags, source, sourceLayer, id, relations);
this.area = (String) tags.get("area");
this.info = info;
}

@Override
public boolean canBePolygon() {
return latLonGeometry() instanceof Polygonal || (latLonGeometry() instanceof LineString line &&
OsmReader.canBePolygon(line.isClosed(), area, latLonGeometry().getNumPoints()));
}

@Override
public boolean canBeLine() {
return latLonGeometry() instanceof MultiLineString || (latLonGeometry() instanceof LineString line &&
OsmReader.canBeLine(line.isClosed(), area, latLonGeometry().getNumPoints()));
}

@Override
protected Geometry computePolygon() {
var geom = worldGeometry();
return geom instanceof LineString line ? GeoUtils.JTS_FACTORY.createPolygon(line.getCoordinates()) : geom;
}


@Override
public OsmElement originalElement() {
return new OsmElement() {
@Override
public long id() {
return SimpleOsmFeature.this.id();
}

@Override
public Info info() {
return info;
}

@Override
public int cost() {
return 1;
}

@Override
public Map<String, Object> tags() {
return tags();
}
};
}

@Override
public boolean equals(Object o) {
return this == o || (o instanceof SimpleOsmFeature other && super.equals(other) &&
Objects.equals(area, other.area) && Objects.equals(info, other.info));
}

@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (area != null ? area.hashCode() : 0);
result = 31 * result + (info != null ? info.hashCode() : 0);
return result;
}
}

/** Returns a new feature with OSM relation info. Useful for setting up inputs for OSM unit tests. */
public static SimpleFeature createFakeOsmFeature(Geometry latLonGeometry, Map<String, Object> tags, String source,
String sourceLayer, long id, List<OsmReader.RelationMember<OsmRelationInfo>> relations) {
String area = (String) tags.get("area");
return new SimpleFeature(latLonGeometry, null, tags, source, sourceLayer, id, relations) {
@Override
public boolean canBePolygon() {
return latLonGeometry instanceof Polygonal || (latLonGeometry instanceof LineString line &&
OsmReader.canBePolygon(line.isClosed(), area, latLonGeometry.getNumPoints()));
}

@Override
public boolean canBeLine() {
return latLonGeometry instanceof MultiLineString || (latLonGeometry instanceof LineString line &&
OsmReader.canBeLine(line.isClosed(), area, latLonGeometry.getNumPoints()));
}

@Override
protected Geometry computePolygon() {
var geom = worldGeometry();
return geom instanceof LineString line ? GeoUtils.JTS_FACTORY.createPolygon(line.getCoordinates()) : geom;
}
};
return createFakeOsmFeature(latLonGeometry, tags, source, sourceLayer, id, relations, null);
}

/** Returns a new feature with OSM relation info and metadata. Useful for setting up inputs for OSM unit tests. */
public static SimpleFeature createFakeOsmFeature(Geometry latLonGeometry, Map<String, Object> tags, String source,
String sourceLayer, long id, List<OsmReader.RelationMember<OsmRelationInfo>> relations, OsmElement.Info info) {
return new SimpleOsmFeature(latLonGeometry, null, tags, source, sourceLayer, id, relations, info);
}

@Override
Expand Down
5 changes: 5 additions & 0 deletions planetiler-custommap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,11 @@ nested, so each child context can also access the variables from its parent.
>> - `feature.id` - numeric ID of the input feature
>> - `feature.source` - string source ID this feature came from
>> - `feature.source_layer` - optional layer within the source the feature came from
>> - `feature.osm_changeset` - optional OSM changeset ID for this feature
>> - `feature.osm_version` - optional OSM element version for this feature
>> - `feature.osm_timestamp` - optional OSM last modified timestamp for this feature
>> - `feature.osm_user_id` - optional ID of the OSM user that last modified this feature
>> - `feature.osm_user_name` - optional name of the OSM user that last modified this feature
>>
>>> ##### post-match context
>>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.WithGeometryType;
import com.onthegomap.planetiler.reader.WithTags;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import com.onthegomap.planetiler.reader.osm.OsmSourceFeature;
import com.onthegomap.planetiler.util.Try;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -340,6 +342,11 @@ public record ProcessFeature(
private static final String FEATURE_ID = "feature.id";
private static final String FEATURE_SOURCE = "feature.source";
private static final String FEATURE_SOURCE_LAYER = "feature.source_layer";
private static final String FEATURE_OSM_CHANGESET = "feature.osm_changeset";
private static final String FEATURE_OSM_VERSION = "feature.osm_version";
private static final String FEATURE_OSM_TIMESTAMP = "feature.osm_timestamp";
private static final String FEATURE_OSM_USER_ID = "feature.osm_user_id";
private static final String FEATURE_OSM_USER_NAME = "feature.osm_user_name";

public static ScriptEnvironment<ProcessFeature> description(Root root) {
return root.description()
Expand All @@ -348,7 +355,12 @@ public static ScriptEnvironment<ProcessFeature> description(Root root) {
Decls.newVar(FEATURE_TAGS, Decls.newMapType(Decls.String, Decls.Any)),
Decls.newVar(FEATURE_ID, Decls.Int),
Decls.newVar(FEATURE_SOURCE, Decls.String),
Decls.newVar(FEATURE_SOURCE_LAYER, Decls.String)
Decls.newVar(FEATURE_SOURCE_LAYER, Decls.String),
Decls.newVar(FEATURE_OSM_CHANGESET, Decls.Int),
Decls.newVar(FEATURE_OSM_VERSION, Decls.Int),
Decls.newVar(FEATURE_OSM_TIMESTAMP, Decls.Int),
Decls.newVar(FEATURE_OSM_USER_ID, Decls.Int),
Decls.newVar(FEATURE_OSM_USER_NAME, Decls.String)
);
}

Expand All @@ -360,7 +372,17 @@ public Object apply(String key) {
case FEATURE_ID -> feature.id();
case FEATURE_SOURCE -> feature.getSource();
case FEATURE_SOURCE_LAYER -> wrapNullable(feature.getSourceLayer());
default -> null;
default -> {
OsmElement.Info info = feature instanceof OsmSourceFeature osm ? osm.originalElement().info() : null;
yield info == null ? null : switch (key) {
case FEATURE_OSM_CHANGESET -> info.changeset();
case FEATURE_OSM_VERSION -> info.version();
case FEATURE_OSM_TIMESTAMP -> info.timestamp();
case FEATURE_OSM_USER_ID -> info.userId();
case FEATURE_OSM_USER_NAME -> wrapNullable(info.user());
default -> null;
};
}
};
} else {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.reader.SimpleFeature;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import com.onthegomap.planetiler.stats.Stats;
import java.nio.file.Path;
import java.util.List;
Expand All @@ -40,6 +41,7 @@ class ConfiguredFeatureTest {
private static final Function<String, Path> TEST_RESOURCE = TestConfigurableUtils::pathToTestResource;
private static final Function<String, Path> SAMPLE_RESOURCE = TestConfigurableUtils::pathToSample;
private static final Function<String, Path> TEST_INVALID_RESOURCE = TestConfigurableUtils::pathToTestInvalidResource;
private static final OsmElement.Info OSM_INFO = new OsmElement.Info(2, 3, 4, 5, "user");

private static final Map<String, Object> waterTags = Map.of(
"natural", "water",
Expand Down Expand Up @@ -130,36 +132,38 @@ private void testFeature(SourceFeature sf, Consumer<Feature> test, int expectedM
private void testPolygon(String config, Map<String, Object> tags,
Consumer<Feature> test, int expectedMatchCount) {
var sf =
SimpleFeature.createFakeOsmFeature(newPolygon(0, 0, 1, 0, 1, 1, 0, 0), tags, "osm", null, 1, emptyList());
SimpleFeature.createFakeOsmFeature(newPolygon(0, 0, 1, 0, 1, 1, 0, 0), tags, "osm", null, 1, emptyList(),
OSM_INFO);
testFeature(config, sf, test, expectedMatchCount);
}

private void testPoint(String config, Map<String, Object> tags,
Consumer<Feature> test, int expectedMatchCount) {
var sf =
SimpleFeature.createFakeOsmFeature(newPoint(0, 0), tags, "osm", null, 1, emptyList());
SimpleFeature.createFakeOsmFeature(newPoint(0, 0), tags, "osm", null, 1, emptyList(), OSM_INFO);
testFeature(config, sf, test, expectedMatchCount);
}


private void testLinestring(String config,
Map<String, Object> tags, Consumer<Feature> test, int expectedMatchCount) {
var sf =
SimpleFeature.createFakeOsmFeature(newLineString(0, 0, 1, 0, 1, 1), tags, "osm", null, 1, emptyList());
SimpleFeature.createFakeOsmFeature(newLineString(0, 0, 1, 0, 1, 1), tags, "osm", null, 1, emptyList(), OSM_INFO);
testFeature(config, sf, test, expectedMatchCount);
}

private void testPolygon(Function<String, Path> pathFunction, String schemaFilename, Map<String, Object> tags,
Consumer<Feature> test, int expectedMatchCount) {
var sf =
SimpleFeature.createFakeOsmFeature(newPolygon(0, 0, 1, 0, 1, 1, 0, 0), tags, "osm", null, 1, emptyList());
SimpleFeature.createFakeOsmFeature(newPolygon(0, 0, 1, 0, 1, 1, 0, 0), tags, "osm", null, 1, emptyList(),
OSM_INFO);
testFeature(pathFunction, schemaFilename, sf, test, expectedMatchCount);
}

private void testLinestring(Function<String, Path> pathFunction, String schemaFilename,
Map<String, Object> tags, Consumer<Feature> test, int expectedMatchCount) {
var sf =
SimpleFeature.createFakeOsmFeature(newLineString(0, 0, 1, 0, 1, 1), tags, "osm", null, 1, emptyList());
SimpleFeature.createFakeOsmFeature(newLineString(0, 0, 1, 0, 1, 1), tags, "osm", null, 1, emptyList(), OSM_INFO);
testFeature(pathFunction, schemaFilename, sf, test, expectedMatchCount);
}

Expand Down Expand Up @@ -547,6 +551,11 @@ void testCoerceAttributeValue() {
"\\\\${feature.id}|\\${feature.id}",
"${feature.source}|osm",
"${feature.source_layer}|null",
"${feature.osm_changeset}|2",
"${feature.osm_timestamp}|3",
"${feature.osm_user_id}|4",
"${feature.osm_version}|5",
"${feature.osm_user_name}|user",
"${coalesce(feature.source_layer, 'missing')}|missing",
"{match: {test: {natural: water}}}|test",
"{match: {test: {natural: not_water}}}|null",
Expand Down

0 comments on commit 40bf33e

Please sign in to comment.