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

Access OSM metadata in yaml profiles #739

Merged
merged 3 commits into from
Dec 2, 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
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
Loading