From db0eb8afb8924877ea41d2862f317c4eec11620b Mon Sep 17 00:00:00 2001 From: Michael Barry Date: Fri, 28 Jun 2024 07:00:56 -0400 Subject: [PATCH] Encode OSM node/way/relation in vector tile feature id (#826) --- .../planetiler/FeatureCollector.java | 6 +++- .../planetiler/config/Arguments.java | 6 +++- .../planetiler/config/PlanetilerConfig.java | 9 ++++-- .../planetiler/reader/SourceFeature.java | 4 +++ .../planetiler/reader/osm/OsmElement.java | 3 +- .../planetiler/reader/osm/OsmReader.java | 10 +++++++ .../planetiler/PlanetilerTests.java | 15 +++++----- .../com/onthegomap/planetiler/TestUtils.java | 28 +++++++++++++++++-- .../planetiler/config/ArgumentsTest.java | 12 ++++++++ 9 files changed, 79 insertions(+), 14 deletions(-) diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java index 734f24b0ea..3f8caa9329 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/FeatureCollector.java @@ -64,7 +64,11 @@ public Iterator iterator() { * @return a feature that can be configured further. */ public Feature geometry(String layer, Geometry geometry) { - Feature feature = new Feature(layer, geometry, source.id()); + // TODO args could also provide a list of source IDs to put into slot 4, 5, 6, etc.. + // to differentiate between other sources besides just OSM and "other" + long vectorTileId = config.featureSourceIdMultiplier() < 4 ? source.id() : + source.vectorTileFeatureId(config.featureSourceIdMultiplier()); + Feature feature = new Feature(layer, geometry, vectorTileId); output.add(feature); return feature; } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/config/Arguments.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/config/Arguments.java index 9124552c29..6955334468 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/config/Arguments.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/config/Arguments.java @@ -445,7 +445,11 @@ public Stats getStats() { */ public int getInteger(String key, String description, int defaultValue) { String value = getArg(key, Integer.toString(defaultValue)); - int parsed = Integer.parseInt(value); + int parsed = switch (value.toLowerCase(Locale.ROOT)) { + case "false" -> 0; + case "true" -> defaultValue; + default -> Integer.parseInt(value); + }; logArgValue(key, description, parsed); return parsed; } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/config/PlanetilerConfig.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/config/PlanetilerConfig.java index c337d7fb6e..43c0e5dd10 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/config/PlanetilerConfig.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/config/PlanetilerConfig.java @@ -60,7 +60,8 @@ public record PlanetilerConfig( Path tmpDir, Path tileWeights, double maxPointBuffer, - boolean logJtsExceptions + boolean logJtsExceptions, + int featureSourceIdMultiplier ) { public static final int MIN_MINZOOM = 0; @@ -213,7 +214,11 @@ public static PlanetilerConfig from(Arguments arguments) { "clients that handle label collisions across tiles (most web and native clients). NOTE: Do not reduce if you need to support " + "raster tile rendering", Double.POSITIVE_INFINITY), - arguments.getBoolean("log_jts_exceptions", "Emit verbose details to debug JTS geometry errors", false) + arguments.getBoolean("log_jts_exceptions", "Emit verbose details to debug JTS geometry errors", false), + arguments.getInteger("feature_source_id_multiplier", + "Set vector tile feature IDs to (featureId * thisValue) + sourceId " + + "where sourceId is 1 for OSM nodes, 2 for ways, 3 for relations, and 0 for other sources. Set to false to disable.", + 10) ); } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java index b5c71f0f6a..68caa9d6a1 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/SourceFeature.java @@ -320,6 +320,10 @@ public final long id() { return id; } + /** By default, the feature id is taken as-is from the input data source id. */ + public long vectorTileFeatureId(int multiplier) { + return multiplier * id; + } /** Returns true if this element has any OSM relation info. */ public boolean hasRelationInfo() { diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmElement.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmElement.java index 57c5e203ba..8d2d89c9b4 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmElement.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmElement.java @@ -27,6 +27,7 @@ public interface OsmElement extends WithTags { Type type(); enum Type { + OTHER, NODE, WAY, RELATION @@ -46,7 +47,7 @@ public int cost() { @Override public Type type() { - return null; + return Type.OTHER; } } diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java index 72abb5e792..de4eabf7d5 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/osm/OsmReader.java @@ -644,6 +644,16 @@ public OsmFeature(OsmElement elem, boolean point, boolean line, boolean polygon, this.polygon = polygon; } + @Override + public long vectorTileFeatureId(int multiplier) { + return (id() * multiplier) + switch (originalElement.type()) { + case OTHER -> 0; + case NODE -> 1; + case WAY -> 2; + case RELATION -> 3; + }; + } + @Override public Geometry latLonGeometry() throws GeometryException { return latLonGeom != null ? latLonGeom : (latLonGeom = GeoUtils.worldToLatLonCoords(worldGeometry())); diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index 05437a51b8..eb9fb3c303 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -942,7 +942,7 @@ void testOsmPoint() throws Exception { feature(newPoint(128, 128), Map.of( "attr", "value", "name", "name value" - )) + ), 11) ) ), results.tiles); } @@ -1031,7 +1031,7 @@ void testOsmLine() throws Exception { feature(newLineString(128, 128, 192, 192), Map.of( "attr", "value", "name", "name value" - )) + ), 32) ) ), results.tiles); } @@ -1157,7 +1157,7 @@ record TestRelationInfo(long id, String name) implements OsmRelationInfo {} "attr", "value", "name", "name value", "relname", "rel name" - )) + ), 173) ) ), results.tiles); } @@ -1219,20 +1219,21 @@ record TestRelationInfo(long id, String name) implements OsmRelationInfo {} @Test void testPreprocessOsmNodesAndWays() throws Exception { - Set nodes1 = new HashSet<>(); + HashMap nodes1 = new HashMap<>(); Set nodes2 = new HashSet<>(); var profile = new Profile.NullProfile() { @Override public void preprocessOsmNode(OsmElement.Node node) { if (node.hasTag("a", "b")) { - nodes1.add(node.id()); + nodes1.put(node.id(), node.id()); } } @Override public void preprocessOsmWay(OsmElement.Way way) { - if (nodes1.contains(way.nodes().get(0))) { - nodes2.add(way.nodes().get(0)); + Long featureId = nodes1.get(way.nodes().get(0)); + if (featureId != null) { + nodes2.add(featureId); } } diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/TestUtils.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/TestUtils.java index ba7e3161f7..b162f5ccd7 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/TestUtils.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/TestUtils.java @@ -284,7 +284,9 @@ public static Map> getTileMap(ReadableTileArc case UNKNOWN -> throw new IllegalArgumentException("cannot decompress \"UNKNOWN\""); }; var decoded = VectorTile.decode(bytes).stream() - .map(feature -> feature(decodeSilently(feature.geometry()), feature.layer(), feature.tags())).toList(); + .map( + feature -> feature(decodeSilently(feature.geometry()), feature.layer(), feature.tags(), feature.id())) + .toList(); tiles.put(tile.coord(), decoded); } return tiles; @@ -490,12 +492,21 @@ public int hashCode() { public record ComparableFeature( GeometryComparision geometry, String layer, - Map attrs + Map attrs, + Long id ) { + ComparableFeature( + GeometryComparision geometry, + String layer, + Map attrs + ) { + this(geometry, layer, attrs, null); + } @Override public boolean equals(Object o) { return o == this || (o instanceof ComparableFeature other && + (id == null || other.id == null || id.equals(other.id)) && geometry.equals(other.geometry) && attrs.equals(other.attrs) && (layer == null || other.layer == null || Objects.equals(layer, other.layer))); @@ -507,12 +518,25 @@ public int hashCode() { result = 31 * result + attrs.hashCode(); return result; } + + ComparableFeature withId(long id) { + return new ComparableFeature(geometry, layer, attrs, id); + } + } + + + public static ComparableFeature feature(Geometry geom, String layer, Map attrs, long id) { + return new ComparableFeature(new NormGeometry(geom), layer, attrs, id); } public static ComparableFeature feature(Geometry geom, String layer, Map attrs) { return new ComparableFeature(new NormGeometry(geom), layer, attrs); } + public static ComparableFeature feature(Geometry geom, Map attrs, long id) { + return new ComparableFeature(new NormGeometry(geom), null, attrs, id); + } + public static ComparableFeature feature(Geometry geom, Map attrs) { return new ComparableFeature(new NormGeometry(geom), null, attrs); } diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/config/ArgumentsTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/config/ArgumentsTest.java index 9f9e51dd72..12c6d8d57e 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/config/ArgumentsTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/config/ArgumentsTest.java @@ -363,4 +363,16 @@ void testSubset() { assertEquals("val_1", args.getArg("key-1")); assertNull(args.getArg("key-3")); } + + @Test + void testFalseForInt() { + var args = Arguments.of(Map.of( + "true", "true", + "false", "false", + "3", "3" + )); + assertEquals(1, args.getInteger("true", "", 1)); + assertEquals(0, args.getInteger("false", "", 1)); + assertEquals(3, args.getInteger("3", "", 1)); + } }