Access OSM metadata in yaml profiles (#739)

pull/740/head
Michael Barry 2023-12-02 15:34:35 -05:00 zatwierdzone przez GitHub
rodzic 4efc2bbd41
commit 40bf33e887
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
4 zmienionych plików z 122 dodań i 26 usunięć

Wyświetl plik

@ -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;
@ -76,29 +78,87 @@ public class SimpleFeature extends SourceFeature {
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()));
}
return createFakeOsmFeature(latLonGeometry, tags, source, sourceLayer, id, relations, null);
}
@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;
}
};
/** 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

Wyświetl plik

@ -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
>>>

Wyświetl plik

@ -13,6 +13,8 @@ import com.onthegomap.planetiler.expression.DataType;
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;
@ -340,6 +342,11 @@ public class Contexts {
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()
@ -348,7 +355,12 @@ public class Contexts {
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)
);
}
@ -360,7 +372,17 @@ public class Contexts {
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;

Wyświetl plik

@ -22,6 +22,7 @@ import com.onthegomap.planetiler.geo.GeoUtils;
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;
@ -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",
@ -130,14 +132,15 @@ class ConfiguredFeatureTest {
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);
}
@ -145,21 +148,22 @@ class ConfiguredFeatureTest {
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);
}
@ -547,6 +551,11 @@ class ConfiguredFeatureTest {
"\\\\${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",