From 0bb61f730620c4d8bc4703103b4cd5d10481aca1 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Wed, 5 Jul 2023 13:37:21 +0200 Subject: [PATCH 01/28] version bumped from 3.14.0 to 3.15.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f392ffb..57cd3327 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.openmaptiles planetiler-openmaptiles - 3.14.0 + 3.15.0-SNAPSHOT OpenMapTiles Vector Tile Schema implementation for Planetiler tool From d32501e85263303bc805cc3aba47d3209e08b4af Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Wed, 5 Jul 2023 13:39:04 +0200 Subject: [PATCH 02/28] regenerate-openmaptiles.sh 07f243c5d9efa558fa539d7a31b2ae50507aaa9d (to match content of OMT PR 1457) --- .../generated/OpenMapTilesSchema.java | 83 +++++++++++-------- .../org/openmaptiles/generated/Tables.java | 34 ++++---- 2 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java index 8ae534a5..3dd59c39 100644 --- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java +++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java @@ -48,9 +48,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import org.openmaptiles.Layer; /** - * All vector tile layer definitions, attributes, and allowed values generated from the - * OpenMapTiles vector tile schema - * v3.14. + * All vector tile layer definitions, attributes, and allowed values generated from the OpenMapTiles + * vector tile schema 07f243c5d9efa558fa539d7a31b2ae50507aaa9d. */ @SuppressWarnings("unused") public class OpenMapTilesSchema { @@ -95,8 +95,8 @@ public static List createInstances(Translations translations, PlanetilerC * polygons to improve rendering performance. This however can lead to less rendering options in clients since these * boundaries show up. So you might not be able to use border styling for ocean water features. * - * Generated from - * water.yaml + * Generated from water.yaml */ public interface Water extends Layer { double BUFFER_SIZE = 4.0; @@ -121,7 +121,7 @@ final class Fields { * water=river tag are classified as * river. Wet and dry docks tagged * waterway=dock are classified as - * a dock. Swimming pools tagged + * a dock. Various minor waterbodies are classified as a pond. Swimming pools tagged * leisure=swimming_pool * are classified as a swimming_pool All other water bodies are classified as lake. *

@@ -129,6 +129,7 @@ final class Fields { *

    *
  • dock *
  • river + *
  • pond *
  • lake *
  • ocean *
  • swimming_pool @@ -163,10 +164,11 @@ final class Fields { final class FieldValues { public static final String CLASS_DOCK = "dock"; public static final String CLASS_RIVER = "river"; + public static final String CLASS_POND = "pond"; public static final String CLASS_LAKE = "lake"; public static final String CLASS_OCEAN = "ocean"; public static final String CLASS_SWIMMING_POOL = "swimming_pool"; - public static final Set CLASS_VALUES = Set.of("dock", "river", "lake", "ocean", "swimming_pool"); + public static final Set CLASS_VALUES = Set.of("dock", "river", "pond", "lake", "ocean", "swimming_pool"); public static final String BRUNNEL_BRIDGE = "bridge"; public static final String BRUNNEL_TUNNEL = "tunnel"; public static final Set BRUNNEL_VALUES = Set.of("bridge", "tunnel"); @@ -175,8 +177,9 @@ final class FieldValues { final class FieldMappings { public static final MultiExpression Class = MultiExpression.of(List.of(MultiExpression.entry("dock", matchAny("waterway", "dock")), - MultiExpression.entry("river", matchAny("water", "river")), MultiExpression.entry("lake", FALSE), - MultiExpression.entry("ocean", FALSE), + MultiExpression.entry("river", matchAny("water", "river")), + MultiExpression.entry("pond", matchAny("water", "pond", "basin", "wastewater")), + MultiExpression.entry("lake", FALSE), MultiExpression.entry("ocean", FALSE), MultiExpression.entry("swimming_pool", matchAny("leisure", "swimming_pool")))); } } @@ -187,8 +190,8 @@ final class FieldMappings { * there is also canal generated, starting z13 there is no generalization according to class * field applied. Waterways do not have a subclass field. * - * Generated from - * waterway.yaml + * Generated from waterway.yaml */ public interface Waterway extends Layer { double BUFFER_SIZE = 4.0; @@ -273,7 +276,7 @@ final class FieldMappings { * layer is to style wood (class=wood) and grass (class=grass) areas. * * Generated from landcover.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/landcover/landcover.yaml">landcover.yaml */ public interface Landcover extends Layer { double BUFFER_SIZE = 4.0; @@ -429,8 +432,8 @@ final class FieldMappings { * Landuse is used to describe use of land by humans. At lower zoom levels this is from Natural Earth data for * residential (urban) areas and at higher zoom levels mostly OSM landuse tags. * - * Generated from - * landuse.yaml + * Generated from landuse.yaml */ public interface Landuse extends Layer { double BUFFER_SIZE = 4.0; @@ -526,7 +529,7 @@ final class FieldMappings { * Natural peaks * * Generated from mountain_peak.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/mountain_peak/mountain_peak.yaml">mountain_peak.yaml */ public interface MountainPeak extends Layer { double BUFFER_SIZE = 64.0; @@ -600,8 +603,8 @@ final class FieldMappings { * "http://wiki.openstreetmap.org/wiki/Tag:boundary%3Dprotected_area">boundary=protected_area, or * leisure=nature_reserve. * - * Generated from - * park.yaml + * Generated from park.yaml */ public interface Park extends Layer { double BUFFER_SIZE = 4.0; @@ -660,8 +663,8 @@ final class FieldMappings { * admin_level * but for most styles it makes sense to just style admin_level=2 and admin_level=4. * - * Generated from - * boundary.yaml + * Generated from boundary.yaml */ public interface Boundary extends Layer { double BUFFER_SIZE = 4.0; @@ -761,8 +764,8 @@ final class FieldMappings { * buildings are contained in the building layer but all other airport related polygons can be found * in the aeroway layer. * - * Generated from - * aeroway.yaml + * Generated from aeroway.yaml */ public interface Aeroway extends Layer { double BUFFER_SIZE = 4.0; @@ -822,7 +825,7 @@ final class FieldMappings { * features like plazas. * * Generated from transportation.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/transportation/transportation.yaml">transportation.yaml */ public interface Transportation extends Layer { double BUFFER_SIZE = 4.0; @@ -1165,8 +1168,8 @@ final class FieldMappings { * (building= ). Only buildings with tag * location:underground are excluded. * - * Generated from - * building.yaml + * Generated from building.yaml */ public interface Building extends Layer { double BUFFER_SIZE = 4.0; @@ -1208,7 +1211,7 @@ final class FieldMappings { * from OSM water bodies. Only the most important lakes contain labels. * * Generated from water_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/water_name/water_name.yaml">water_name.yaml */ public interface WaterName extends Layer { double BUFFER_SIZE = 256.0; @@ -1231,11 +1234,14 @@ final class Fields { public static final String NAME_DE = "name_de"; /** - * Distinguish between lake, ocean and sea. + * Distinguish between lake, ocean, bay, strait, and + * sea. *

    * allowed values: *

      *
    • "lake" + *
    • "bay" + *
    • "strait" *
    • "sea" *
    • "ocean" *
    @@ -1257,9 +1263,11 @@ final class Fields { /** Attribute values for map elements in the water_name layer. */ final class FieldValues { public static final String CLASS_LAKE = "lake"; + public static final String CLASS_BAY = "bay"; + public static final String CLASS_STRAIT = "strait"; public static final String CLASS_SEA = "sea"; public static final String CLASS_OCEAN = "ocean"; - public static final Set CLASS_VALUES = Set.of("lake", "sea", "ocean"); + public static final Set CLASS_VALUES = Set.of("lake", "bay", "strait", "sea", "ocean"); } /** Complex mappings to generate attribute values from OSM element tags in the water_name layer. */ final class FieldMappings { @@ -1273,7 +1281,7 @@ final class FieldMappings { * while for other roads you should use name. * * Generated from transportation_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/transportation_name/transportation_name.yaml">transportation_name.yaml */ public interface TransportationName extends Layer { double BUFFER_SIZE = 8.0; @@ -1316,6 +1324,8 @@ final class Fields { *
  • "us-highway" *
  • "us-state" *
  • "ca-transcanada" + *
  • "ca-provincial-arterial" + *
  • "ca-provincial" *
  • "gb-motorway" *
  • "gb-trunk" *
  • "road (default)" @@ -1427,11 +1437,13 @@ final class FieldValues { public static final String NETWORK_US_HIGHWAY = "us-highway"; public static final String NETWORK_US_STATE = "us-state"; public static final String NETWORK_CA_TRANSCANADA = "ca-transcanada"; + public static final String NETWORK_CA_PROVINCIAL_ARTERIAL = "ca-provincial-arterial"; + public static final String NETWORK_CA_PROVINCIAL = "ca-provincial"; public static final String NETWORK_GB_MOTORWAY = "gb-motorway"; public static final String NETWORK_GB_TRUNK = "gb-trunk"; public static final String NETWORK_ROAD = "road"; - public static final Set NETWORK_VALUES = - Set.of("us-interstate", "us-highway", "us-state", "ca-transcanada", "gb-motorway", "gb-trunk", "road"); + public static final Set NETWORK_VALUES = Set.of("us-interstate", "us-highway", "us-state", + "ca-transcanada", "ca-provincial-arterial", "ca-provincial", "gb-motorway", "gb-trunk", "road"); public static final String CLASS_MOTORWAY = "motorway"; public static final String CLASS_TRUNK = "trunk"; public static final String CLASS_PRIMARY = "primary"; @@ -1489,8 +1501,8 @@ final class FieldMappings { * of the more important layers to create a beautiful map. We suggest you use different font styles and sizes to * create a text hierarchy. * - * Generated from - * place.yaml + * Generated from place.yaml */ public interface Place extends Layer { double BUFFER_SIZE = 256.0; @@ -1598,7 +1610,7 @@ final class FieldMappings { * housenumber. * * Generated from housenumber.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/housenumber/housenumber.yaml">housenumber.yaml */ public interface Housenumber extends Layer { double BUFFER_SIZE = 8.0; @@ -1627,7 +1639,8 @@ final class FieldMappings { * Points of interests containing a of a variety * of OpenStreetMap tags. Mostly contains amenities, sport, shop and tourist POIs. * - * Generated from poi.yaml + * Generated from poi.yaml */ public interface Poi extends Layer { double BUFFER_SIZE = 64.0; @@ -1846,7 +1859,7 @@ final class FieldMappings { * Aerodrome labels * * Generated from aerodrome_label.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/07f243c5d9efa558fa539d7a31b2ae50507aaa9d/layers/aerodrome_label/aerodrome_label.yaml">aerodrome_label.yaml */ public interface AerodromeLabel extends Layer { double BUFFER_SIZE = 64.0; diff --git a/src/main/java/org/openmaptiles/generated/Tables.java b/src/main/java/org/openmaptiles/generated/Tables.java index a29e15e7..a090d736 100644 --- a/src/main/java/org/openmaptiles/generated/Tables.java +++ b/src/main/java/org/openmaptiles/generated/Tables.java @@ -50,8 +50,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * OSM element parsers generated from the imposm3 table definitions - * in the OpenMapTiles vector tile - * schema. + * in the OpenMapTiles + * vector tile schema. * * These filter and parse the raw OSM key/value attribute pairs on tags into records with fields that match the columns * in the tables that imposm3 would generate. Layer implementations can "subscribe" to elements from each "table" but @@ -94,21 +95,23 @@ public record RowHandlerAndClass ( ) {} /** An OSM element that would appear in the {@code osm_water_polygon} table generated by imposm3. */ public record OsmWaterPolygon(@Override String name, @Override String nameEn, @Override String nameDe, - @Override String natural, @Override String landuse, @Override String waterway, @Override String leisure, - @Override String water, @Override boolean isIntermittent, @Override boolean isTunnel, @Override boolean isBridge, - @Override SourceFeature source) implements Row, WithName, WithNameEn, WithNameDe, WithNatural, WithLanduse, - WithWaterway, WithLeisure, WithWater, WithIsIntermittent, WithIsTunnel, WithIsBridge, WithSource { + @Override String place, @Override String natural, @Override String landuse, @Override String waterway, + @Override String leisure, @Override String water, @Override boolean isIntermittent, @Override boolean isTunnel, + @Override boolean isBridge, @Override SourceFeature source) + implements Row, WithName, WithNameEn, WithNameDe, WithPlace, WithNatural, WithLanduse, WithWaterway, WithLeisure, + WithWater, WithIsIntermittent, WithIsTunnel, WithIsBridge, WithSource { public OsmWaterPolygon(SourceFeature source, String mappingKey) { this(source.getString("name"), source.getString("name:en"), source.getString("name:de"), - source.getString("natural"), source.getString("landuse"), source.getString("waterway"), - source.getString("leisure"), source.getString("water"), source.getBoolean("intermittent"), - source.getBoolean("tunnel"), source.getBoolean("bridge"), source); + source.getString("place"), source.getString("natural"), source.getString("landuse"), + source.getString("waterway"), source.getString("leisure"), source.getString("water"), + source.getBoolean("intermittent"), source.getBoolean("tunnel"), source.getBoolean("bridge"), source); } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ public static final Expression MAPPING = and( or(matchAny("landuse", "reservoir", "basin", "salt_pond"), matchAny("leisure", "swimming_pool"), - matchAny("natural", "water", "bay", "spring"), matchAny("waterway", "dock"), matchAny("water", "river")), + matchAny("natural", "water", "bay", "spring"), matchAny("waterway", "dock"), + matchAny("water", "river", "pond", "basin", "wastewater")), not(matchAny("covered", "yes")), matchType("polygon")); /** @@ -521,16 +524,19 @@ public interface Handler { } /** An OSM element that would appear in the {@code osm_marine_point} table generated by imposm3. */ public record OsmMarinePoint(@Override String name, @Override String nameEn, @Override String nameDe, - @Override String place, @Override long rank, @Override boolean isIntermittent, @Override SourceFeature source) - implements Row, WithName, WithNameEn, WithNameDe, WithPlace, WithRank, WithIsIntermittent, WithSource { + @Override String place, @Override String natural, @Override long rank, @Override boolean isIntermittent, + @Override SourceFeature source) + implements Row, WithName, WithNameEn, WithNameDe, WithPlace, WithNatural, WithRank, WithIsIntermittent, WithSource { public OsmMarinePoint(SourceFeature source, String mappingKey) { this(source.getString("name"), source.getString("name:en"), source.getString("name:de"), - source.getString("place"), source.getLong("rank"), source.getBoolean("intermittent"), source); + source.getString("place"), source.getString("natural"), source.getLong("rank"), + source.getBoolean("intermittent"), source); } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ public static final Expression MAPPING = - and(matchAny("place", "ocean", "sea"), matchField("name"), matchType("point")); + and(or(matchAny("place", "ocean", "sea"), matchAny("natural", "bay", "strait")), matchField("name"), + matchType("point")); /** * Interface for layer implementations to extend to subscribe to OSM elements filtered and parsed as From 862e50aef1743cc3fe557cc00f4e1f31a4c68ae0 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Thu, 6 Jul 2023 18:23:07 +0200 Subject: [PATCH 03/28] SQL -> Java re-implementation of OMT PR 1457 --- .../org/openmaptiles/layers/WaterName.java | 50 ++++++++++++++++--- .../openmaptiles/layers/WaterNameTest.java | 40 ++++++++++++++- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index 2bc19fc9..8159ed28 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -35,6 +35,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE */ package org.openmaptiles.layers; +import static org.openmaptiles.util.Utils.coalesce; import static org.openmaptiles.util.Utils.nullIfEmpty; import com.carrotsearch.hppc.LongObjectMap; @@ -48,6 +49,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.onthegomap.planetiler.util.Parse; import com.onthegomap.planetiler.util.Translations; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentSkipListMap; import org.locationtech.jts.geom.Geometry; import org.openmaptiles.OpenMapTilesProfile; @@ -80,9 +82,8 @@ public class WaterName implements */ private static final Logger LOGGER = LoggerFactory.getLogger(WaterName.class); - private static final double WORLD_AREA_FOR_70K_SQUARE_METERS = - Math.pow(GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d, 2); private static final double LOG2 = Math.log(2); + private static final Set SEA_OR_OCEAN_PLACE = Set.of("sea", "ocean"); private final Translations translations; // need to synchronize updates from multiple threads private final LongObjectMap lakeCenterlines = Hppc.newLongObjectHashMap(); @@ -141,7 +142,10 @@ public void processNaturalEarth(String table, SourceFeature feature, FeatureColl @Override public void process(Tables.OsmMarinePoint element, FeatureCollector features) { if (!element.name().isBlank()) { - String place = element.place(); + String clazz = coalesce( + nullIfEmpty(element.natural()), + nullIfEmpty(element.place()) + ); var source = element.source(); // use name from OSM, but get min zoom from natural earth based on fuzzy name match... Integer rank = Parse.parseIntOrNull(source.getTag("rank")); @@ -159,11 +163,20 @@ public void process(Tables.OsmMarinePoint element, FeatureCollector features) { rank = next.getValue(); } } - int minZoom = "ocean".equals(place) ? 0 : rank != null ? rank : 8; + int minZoom; + if ("ocean".equals(element.place())) { + minZoom = 0; + } else if (rank != null) { + minZoom = rank; + } else if ("bay".equals(element.natural())) { + minZoom = 13; + } else { + minZoom = 8; + } features.point(LAYER_NAME) .setBufferPixels(BUFFER_SIZE) .putAttrs(OmtLanguageUtils.getNames(source.tags(), translations)) - .setAttr(Fields.CLASS, place) + .setAttr(Fields.CLASS, clazz) .setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0) .setMinZoom(minZoom); } @@ -176,6 +189,7 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { Geometry centerlineGeometry = lakeCenterlines.get(element.source().id()); FeatureCollector.Feature feature; int minzoom = 9; + String place = element.place(); if (centerlineGeometry != null) { // prefer lake centerline if it exists feature = features.geometry(LAYER_NAME, centerlineGeometry) @@ -185,11 +199,22 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { feature = features.pointOnSurface(LAYER_NAME); Geometry geometry = element.source().worldGeometry(); double area = geometry.getArea(); - minzoom = (int) Math.floor(20 - Math.log(area / WORLD_AREA_FOR_70K_SQUARE_METERS) / LOG2); - minzoom = Math.min(14, Math.max(9, minzoom)); + if (place != null && SEA_OR_OCEAN_PLACE.contains(place)) { + minzoom = 0; + } else { + minzoom = areaToMinZoom(area); + } + } + String clazz; + if ("bay".equals(element.natural())) { + clazz = FieldValues.CLASS_BAY; + } else if ("sea".equals(place)) { + clazz = FieldValues.CLASS_SEA; + } else { + clazz = FieldValues.CLASS_LAKE; } feature - .setAttr(Fields.CLASS, FieldValues.CLASS_LAKE) + .setAttr(Fields.CLASS, clazz) .setBufferPixels(BUFFER_SIZE) .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) .setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0) @@ -199,4 +224,13 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { } } } + + public static int areaToMinZoom(double areaWorld) { + double oneSideWorld = Math.sqrt(areaWorld); + // 1/4 of area => 1/2 of side => 256 / 2 = 128 + int zoom = (int) Math.round( + Math.log(128d / oneSideWorld) / LOG2) - + 8; + return Math.min(14, Math.max(3, zoom)); + } } diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java index 54e0b00d..abd06c64 100644 --- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java +++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java @@ -27,7 +27,7 @@ void testWaterNamePoint() { "_layer", "water_name", "_type", "point", - "_minzoom", 9, + "_minzoom", 3, "_maxzoom", 14 )), process(polygonFeatureWithArea(1, Map.of( "name", "waterway", @@ -36,7 +36,8 @@ void testWaterNamePoint() { "water", "pond", "intermittent", "1" )))); - double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11); + // 1/4 th of tile area is the threshold, 1/4 = 0.25 => area->side:0.25->0.5 => slightly bigger -> 0.51 + double z11area = Math.pow(0.51d / Math.pow(2, 11), 2); assertFeatures(10, List.of(Map.of( "_layer", "water" ), Map.of( @@ -51,6 +52,41 @@ void testWaterNamePoint() { )))); } + // https://zelonewolf.github.io/openstreetmap-americana/#map=13/41.43989/-71.5716 + @Test + void testWordenPondNamePoint() { + assertFeatures(10, List.of(Map.of( + "_layer", "water" + ), Map.of( + "_layer", "water_name", + "_type", "point", + "_minzoom", 13, + "_maxzoom", 14 + )), process(polygonFeatureWithArea(4.930387948170328E-9, Map.of( + "name", "waterway", + "natural", "water", + "water", "pond" + )))); + } + + // FIXME later + // https://zelonewolf.github.io/openstreetmap-americana/#map=12/41.43989/-71.5716 + //@Test + void testPointJudithPondNamePoint() { + assertFeatures(10, List.of(Map.of( + "_layer", "water" + ), Map.of( + "_layer", "water_name", + "_type", "point", + "_minzoom", 12, + "_maxzoom", 14 + )), process(polygonFeatureWithArea(7.024280929916055E-9, Map.of( + "name", "waterway", + "natural", "water", + "water", "pond" + )))); + } + @Test void testWaterNameLakeline() { assertFeatures(11, List.of(), process(SimpleFeature.create( From 8998ddddc605154e4add876a84589d299ef5f718 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Wed, 5 Jul 2023 13:37:21 +0200 Subject: [PATCH 04/28] version bumped from 3.14.0 to 3.15.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5f392ffb..57cd3327 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.openmaptiles planetiler-openmaptiles - 3.14.0 + 3.15.0-SNAPSHOT OpenMapTiles Vector Tile Schema implementation for Planetiler tool From 14c204d9dc9435a4b457fab6674e0db90582cad2 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 7 Jul 2023 16:25:06 +0200 Subject: [PATCH 05/28] WaterName.areaToMinZoom(): improved handling of rounding and precission + added unit tests --- .../org/openmaptiles/layers/WaterName.java | 12 ++-- .../openmaptiles/layers/WaterNameTest.java | 71 ++++++++++++++----- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index 8159ed28..870076be 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -228,9 +228,13 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { public static int areaToMinZoom(double areaWorld) { double oneSideWorld = Math.sqrt(areaWorld); // 1/4 of area => 1/2 of side => 256 / 2 = 128 - int zoom = (int) Math.round( - Math.log(128d / oneSideWorld) / LOG2) - - 8; - return Math.min(14, Math.max(3, zoom)); + double zoom = (Math.log(128d / oneSideWorld) / LOG2) - 8; + + // Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold, + // hence Z13.01 and Z13.00 will be rounded to Z14 and Z12.99 to Z13 (e.g. `floor() + 1`). + // And to accommodate for some precision errors (observed for Z9-Z11) we do also `- 0.1e-11`. + int result = (int) Math.floor(zoom - 0.1e-11) + 1; + + return Math.min(14, Math.max(3, result)); } } diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java index abd06c64..ccc72050 100644 --- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java +++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java @@ -5,7 +5,10 @@ import com.onthegomap.planetiler.TestUtils; 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 java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -69,24 +72,6 @@ void testWordenPondNamePoint() { )))); } - // FIXME later - // https://zelonewolf.github.io/openstreetmap-americana/#map=12/41.43989/-71.5716 - //@Test - void testPointJudithPondNamePoint() { - assertFeatures(10, List.of(Map.of( - "_layer", "water" - ), Map.of( - "_layer", "water_name", - "_type", "point", - "_minzoom", 12, - "_maxzoom", 14 - )), process(polygonFeatureWithArea(7.024280929916055E-9, Map.of( - "name", "waterway", - "natural", "water", - "water", "pond" - )))); - } - @Test void testWaterNameLakeline() { assertFeatures(11, List.of(), process(SimpleFeature.create( @@ -237,4 +222,54 @@ void testMarinePoint() { "place", "sea" )))); } + + private record TestEntry( + SourceFeature feature, + int expectedZoom + ) {} + + private void createAreaForMinZoomTest(List testEntries, double side, int expectedZoom, String name) { + double area = Math.pow(side, 2); + var feature = polygonFeatureWithArea(area, Map.of( + "name", name, + "natural", "water" + )); + testEntries.add(new TestEntry( + feature, + Math.min(14, Math.max(3, expectedZoom)) + )); + } + + @Test + void testAreaToMinZoom() throws GeometryException { + // threshold is 1/4 of tile area, hence ... + // ... side if 1/2 tile side: from pixels to world coord, for say Z14 ... + //final double HALF_OF_TILE_SIDE = 128d / Math.pow(2d, 14d + 8d); + // ... and then for some lower zoom: + //double testAreaSide = HALF_OF_TILE_SIDE * Math.pow(2, 14 - zoom); + // all this then simplified to `testAreaSide` calculation bellow + + final List testEntries = new ArrayList<>(); + for (int zoom = 14; zoom >= 0; zoom--) { + double testAreaSide = Math.pow(2, - zoom - 1); + + // slightly bellow the threshold + createAreaForMinZoomTest(testEntries, testAreaSide * 0.999, zoom + 1, "waterway-"); + // precisely at the threshold + createAreaForMinZoomTest(testEntries, testAreaSide, zoom, "waterway="); + // slightly over the threshold + createAreaForMinZoomTest(testEntries, testAreaSide * 1.001, zoom, "waterway+"); + } + + for (var entry : testEntries) { + assertFeatures(10, List.of(Map.of( + "_layer", "water" + ), Map.of( + "_layer", "water_name", + "_type", "point", + "_minzoom", entry.expectedZoom, + "_maxzoom", 14 + )), process(entry.feature)); + } + } } From fcfe11595307ed915e668e81c3ce1e9a9fd7e8ba Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 7 Jul 2023 16:26:11 +0200 Subject: [PATCH 06/28] mvn spotless:apply --- .../openmaptiles/layers/WaterNameTest.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java index ccc72050..77ec32e3 100644 --- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java +++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java @@ -59,16 +59,16 @@ void testWaterNamePoint() { @Test void testWordenPondNamePoint() { assertFeatures(10, List.of(Map.of( - "_layer", "water" + "_layer", "water" ), Map.of( - "_layer", "water_name", - "_type", "point", - "_minzoom", 13, - "_maxzoom", 14 + "_layer", "water_name", + "_type", "point", + "_minzoom", 13, + "_maxzoom", 14 )), process(polygonFeatureWithArea(4.930387948170328E-9, Map.of( - "name", "waterway", - "natural", "water", - "water", "pond" + "name", "waterway", + "natural", "water", + "water", "pond" )))); } @@ -224,19 +224,19 @@ void testMarinePoint() { } private record TestEntry( - SourceFeature feature, - int expectedZoom + SourceFeature feature, + int expectedZoom ) {} private void createAreaForMinZoomTest(List testEntries, double side, int expectedZoom, String name) { double area = Math.pow(side, 2); var feature = polygonFeatureWithArea(area, Map.of( - "name", name, - "natural", "water" + "name", name, + "natural", "water" )); testEntries.add(new TestEntry( - feature, - Math.min(14, Math.max(3, expectedZoom)) + feature, + Math.min(14, Math.max(3, expectedZoom)) )); } @@ -251,7 +251,7 @@ void testAreaToMinZoom() throws GeometryException { final List testEntries = new ArrayList<>(); for (int zoom = 14; zoom >= 0; zoom--) { - double testAreaSide = Math.pow(2, - zoom - 1); + double testAreaSide = Math.pow(2, -zoom - 1); // slightly bellow the threshold createAreaForMinZoomTest(testEntries, testAreaSide * 0.999, zoom + 1, "waterway-"); @@ -263,12 +263,12 @@ void testAreaToMinZoom() throws GeometryException { for (var entry : testEntries) { assertFeatures(10, List.of(Map.of( - "_layer", "water" + "_layer", "water" ), Map.of( - "_layer", "water_name", - "_type", "point", - "_minzoom", entry.expectedZoom, - "_maxzoom", 14 + "_layer", "water_name", + "_type", "point", + "_minzoom", entry.expectedZoom, + "_maxzoom", 14 )), process(entry.feature)); } } From 91b18423236990aa20c8687767bb60ccf62ae5a1 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Sun, 9 Jul 2023 20:28:06 +0200 Subject: [PATCH 07/28] water label min. zoom calculation simplified --- src/main/java/org/openmaptiles/layers/WaterName.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index 870076be..d031bdbe 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -197,8 +197,7 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { } else { // otherwise just use a label point inside the lake feature = features.pointOnSurface(LAYER_NAME); - Geometry geometry = element.source().worldGeometry(); - double area = geometry.getArea(); + double area = element.source().area(); if (place != null && SEA_OR_OCEAN_PLACE.contains(place)) { minzoom = 0; } else { @@ -228,7 +227,7 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { public static int areaToMinZoom(double areaWorld) { double oneSideWorld = Math.sqrt(areaWorld); // 1/4 of area => 1/2 of side => 256 / 2 = 128 - double zoom = (Math.log(128d / oneSideWorld) / LOG2) - 8; + double zoom = - ( Math.log(oneSideWorld) / LOG2) - 1; // Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold, // hence Z13.01 and Z13.00 will be rounded to Z14 and Z12.99 to Z13 (e.g. `floor() + 1`). From 05b91a7dcb363467068d40b0c48adf901c5b2f2f Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Sun, 9 Jul 2023 20:34:27 +0200 Subject: [PATCH 08/28] comment adjusted to be hopefully more useful --- src/main/java/org/openmaptiles/layers/WaterName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index d031bdbe..b1341b76 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -226,7 +226,7 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { public static int areaToMinZoom(double areaWorld) { double oneSideWorld = Math.sqrt(areaWorld); - // 1/4 of area => 1/2 of side => 256 / 2 = 128 + // full(-er) formula (along with comments) is in WaterNameTest.testAreaToMinZoom(), here is simplified reverse of that double zoom = - ( Math.log(oneSideWorld) / LOG2) - 1; // Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold, From 096b1a450f280c629b31a30b1130baf017829147 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 10 Jul 2023 15:50:49 +0200 Subject: [PATCH 09/28] mvn spotless:apply --- src/main/java/org/openmaptiles/layers/WaterName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index b1341b76..04c4f96b 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -227,7 +227,7 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { public static int areaToMinZoom(double areaWorld) { double oneSideWorld = Math.sqrt(areaWorld); // full(-er) formula (along with comments) is in WaterNameTest.testAreaToMinZoom(), here is simplified reverse of that - double zoom = - ( Math.log(oneSideWorld) / LOG2) - 1; + double zoom = -(Math.log(oneSideWorld) / LOG2) - 1; // Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold, // hence Z13.01 and Z13.00 will be rounded to Z14 and Z12.99 to Z13 (e.g. `floor() + 1`). From fda198f475ae52eb63083f5adb9bfea0323ed227 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 10 Jul 2023 15:52:22 +0200 Subject: [PATCH 10/28] minzoom for CA_TRANSCANADA and US_INTERSTATE trunk now 4 (to match OMT PR 1440) --- .../openmaptiles/layers/Transportation.java | 13 ++++++++++++ .../layers/TransportationTest.java | 20 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index 585b0384..95d3e4ff 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -62,6 +62,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; @@ -137,6 +138,9 @@ public class Transportation implements private static final Set ACCESS_NO_VALUES = Set.of( "private", "no" ); + private static final Set TRUNK_AS_MOTORWAY_BY_NETWORK = Set.of( + RouteNetwork.CA_TRANSCANADA.toString(), RouteNetwork.US_INTERSTATE.toString() + ); private static final ZoomFunction.MeterToPixelThresholds MIN_LENGTH = ZoomFunction.meterThresholds() .put(7, 50) .put(6, 100) @@ -413,6 +417,15 @@ int getMinzoom(Tables.OsmHighwayLinestring element, String highwayClass) { case FieldValues.CLASS_SERVICE -> isDrivewayOrParkingAisle(service(element.service())) ? 14 : 13; case FieldValues.CLASS_TRACK, FieldValues.CLASS_PATH -> routeRank == 1 ? 12 : (z13Paths || !nullOrEmpty(element.name()) || routeRank <= 2 || !nullOrEmpty(element.sacScale())) ? 13 : 14; + case FieldValues.CLASS_TRUNK -> { + // trunks in some networks to have same min. zoom as highway = "motorway" + String clazz = routeRelations.stream() + .map(RouteRelation::networkType) + .filter(Objects::nonNull) + .map(Enum::toString) + .anyMatch(TRUNK_AS_MOTORWAY_BY_NETWORK::contains) ? FieldValues.CLASS_MOTORWAY : FieldValues.CLASS_TRUNK; + yield MINZOOMS.getOrDefault(clazz, Integer.MAX_VALUE); + } default -> MINZOOMS.getOrDefault(baseClass, Integer.MAX_VALUE); }; } diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index 5f1e6636..5a2ac50b 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -909,6 +909,26 @@ void testTransCanadaHighway() { )), features); } + @Test + void testTransCanadaTrunk() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:transcanada:namedRoute"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "_minzoom", 4 + )), features); + } + @Test void testGreatBritainHighway() { process(SimpleFeature.create( From 74f655b8bad093f52a90126541766cc859051a10 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 10 Jul 2023 19:00:25 +0200 Subject: [PATCH 11/28] minzoom for some other Canada trunks now 4 (to match OMT PR 1446) --- .../openmaptiles/layers/Transportation.java | 33 +- .../layers/TransportationTest.java | 292 ++++++++++++++++++ 2 files changed, 324 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index 95d3e4ff..d220c961 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -139,7 +139,15 @@ public class Transportation implements "private", "no" ); private static final Set TRUNK_AS_MOTORWAY_BY_NETWORK = Set.of( - RouteNetwork.CA_TRANSCANADA.toString(), RouteNetwork.US_INTERSTATE.toString() + RouteNetwork.CA_TRANSCANADA.toString(), + RouteNetwork.CA_PROVINCIAL_ARTERIAL.toString(), + RouteNetwork.US_INTERSTATE.toString() + ); + private static final Set CA_AB_PRIMARY_AS_ARTERIAL_BY_REF = Set.of( + "2", "3", "4" + ); + private static final Set CA_BC_AS_ARTERIAL_BY_REF = Set.of( + "3", "5", "99" ); private static final ZoomFunction.MeterToPixelThresholds MIN_LENGTH = ZoomFunction.meterThresholds() .put(7, 50) @@ -272,6 +280,27 @@ public List preprocessOsmRelation(OsmElement.Relation relation) networkType = RouteNetwork.US_STATE; } else if (network != null && network.startsWith("CA:transcanada")) { networkType = RouteNetwork.CA_TRANSCANADA; + } else if (network != null && network.equals("CA:QC:A")) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:QC:A + } else if (network != null && network.equals("CA:ON:primary")) { + if (ref != null && ref.length() == 3 && ref.startsWith("4")) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:ON:primary|420 + } else if ("QEW".equals(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:ON:primary|QEW + } else { + networkType = RouteNetwork.CA_PROVINCIAL; // CA:ON:primary|85 + } + } else if (network != null && network.equals("CA:MB:PTH") && "75".equals(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:MB:PTH|75 + } else if (network != null && network.equals("CA:AB:primary") && ref != null && + CA_AB_PRIMARY_AS_ARTERIAL_BY_REF.contains(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:AB:primary|4 + } else if (network != null && network.equals("CA:BC") && ref != null && CA_BC_AS_ARTERIAL_BY_REF.contains(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:BC|5 + } else if (network != null && ((network.length() == 5 && network.startsWith("CA:")) || + (network.length() >= 6 && network.startsWith("CA:") && network.charAt(5) == ':'))) { + // in SQL: LIKE 'CA:__' OR network LIKE 'CA:__:%'; but wanted to avoid regexp hence more ugly + networkType = RouteNetwork.CA_PROVINCIAL; // network|ref: CA:AB:primary|11 CA:ON:private_toll|407 } int rank = switch (coalesce(network, "")) { @@ -563,6 +592,8 @@ enum RouteNetwork { US_HIGHWAY("us-highway"), US_STATE("us-state"), CA_TRANSCANADA("ca-transcanada"), + CA_PROVINCIAL_ARTERIAL("ca-provincial-arterial"), + CA_PROVINCIAL("ca-provincial"), GB_MOTORWAY("gb-motorway"), GB_TRUNK("gb-trunk"); diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index 5a2ac50b..ff954c82 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -3,6 +3,7 @@ import static com.onthegomap.planetiler.TestUtils.newLineString; import static com.onthegomap.planetiler.TestUtils.newPoint; import static com.onthegomap.planetiler.TestUtils.rectangle; +import static org.junit.jupiter.api.Assertions.assertFalse; import com.onthegomap.planetiler.FeatureCollector; import com.onthegomap.planetiler.config.Arguments; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -929,6 +931,296 @@ void testTransCanadaTrunk() { )), features); } + @Test + void testTransCanadaProvincialCaQcA() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:QC:A"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial-arterial", + "_minzoom", 4 + )), features); + } + + @Test + void testTransCanadaProvincialCaOnPrimaryRef4xx() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:ON:primary"); + rel.setTag("ref", "420"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial-arterial", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "420", + "network", "ca-provincial-arterial" + )), features); + } + + @Test + void testTransCanadaProvincialCaOnPrimaryRefQew() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:ON:primary"); + rel.setTag("ref", "QEW"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial-arterial", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "QEW", + "network", "ca-provincial-arterial" + )), features); + } + + @Test + void testTransCanadaProvincialCaOnPrimaryRefOther() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:ON:primary"); + rel.setTag("ref", "85"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "85", + "network", "ca-provincial" + )), features); + } + + @Test + void testTransCanadaProvincialCaMbPthRef75() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:MB:PTH"); + rel.setTag("ref", "75"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial-arterial", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "75", + "network", "ca-provincial-arterial" + )), features); + } + + @Test + void testTransCanadaProvincialCaMbPthRefOther() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:MB:PTH"); + rel.setTag("ref", "77"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "77", + "network", "ca-provincial" + )), features); + } + + @Test + void testTransCanadaProvincialCaAbPrimaryRef3() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:AB:primary"); + rel.setTag("ref", "3"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial-arterial", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "3", + "network", "ca-provincial-arterial" + )), features); + } + + @Test + void testTransCanadaProvincialCaAbPrimaryRefOther() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:AB:primary"); + rel.setTag("ref", "10"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "10", + "network", "ca-provincial" + )), features); + } + + @Test + void testTransCanadaProvincialCaBcRef3() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:BC"); + rel.setTag("ref", "3"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial-arterial", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "3", + "network", "ca-provincial-arterial" + )), features); + } + + @Test + void testTransCanadaProvincialCaBcRefOther() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:BC"); + rel.setTag("ref", "10"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "network", "ca-provincial", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "10", + "network", "ca-provincial" + )), features); + } + + @Test + void testTransCanadaProvincialCaOther() { + var rel = new OsmElement.Relation(1); + rel.setTag("type", "route"); + rel.setTag("route", "road"); + rel.setTag("network", "CA:yellowhead"); + + FeatureCollector features = process(lineFeatureWithRelation( + profile.preprocessOsmRelation(rel), + Map.of( + "highway", "trunk" + ))); + + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "_minzoom", 5 + )), features); + boolean caProvPresent = StreamSupport.stream(features.spliterator(), false) + .flatMap(f -> f.getAttrsAtZoom(13).entrySet().stream()) + .filter(e -> "network".equals(e.getKey())) + .map(Map.Entry::getValue) + .anyMatch(v -> "ca-provincial".equals(v) || "ca-provincial-arterial".equals(v)); + assertFalse(caProvPresent, "ca-provincial present"); + } + @Test void testGreatBritainHighway() { process(SimpleFeature.create( From cf92b5e26c5b1abaeeacf80c580f8acf0f25ef94 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 10 Jul 2023 19:04:52 +0200 Subject: [PATCH 12/28] equals() simplified + clean-up of comments --- .../openmaptiles/layers/Transportation.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index d220c961..6ea5c7c8 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -280,27 +280,26 @@ public List preprocessOsmRelation(OsmElement.Relation relation) networkType = RouteNetwork.US_STATE; } else if (network != null && network.startsWith("CA:transcanada")) { networkType = RouteNetwork.CA_TRANSCANADA; - } else if (network != null && network.equals("CA:QC:A")) { - networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:QC:A - } else if (network != null && network.equals("CA:ON:primary")) { + } else if ("CA:QC:A".equals(network)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; + } else if ("CA:ON:primary".equals(network)) { if (ref != null && ref.length() == 3 && ref.startsWith("4")) { - networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:ON:primary|420 + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; } else if ("QEW".equals(ref)) { - networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:ON:primary|QEW + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; } else { - networkType = RouteNetwork.CA_PROVINCIAL; // CA:ON:primary|85 + networkType = RouteNetwork.CA_PROVINCIAL; } - } else if (network != null && network.equals("CA:MB:PTH") && "75".equals(ref)) { - networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:MB:PTH|75 - } else if (network != null && network.equals("CA:AB:primary") && ref != null && - CA_AB_PRIMARY_AS_ARTERIAL_BY_REF.contains(ref)) { - networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:AB:primary|4 - } else if (network != null && network.equals("CA:BC") && ref != null && CA_BC_AS_ARTERIAL_BY_REF.contains(ref)) { - networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; // CA:BC|5 + } else if ("CA:MB:PTH".equals(network) && "75".equals(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; + } else if ("CA:AB:primary".equals(network) && ref != null && CA_AB_PRIMARY_AS_ARTERIAL_BY_REF.contains(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; + } else if ("CA:BC".equals(network) && ref != null && CA_BC_AS_ARTERIAL_BY_REF.contains(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; } else if (network != null && ((network.length() == 5 && network.startsWith("CA:")) || (network.length() >= 6 && network.startsWith("CA:") && network.charAt(5) == ':'))) { // in SQL: LIKE 'CA:__' OR network LIKE 'CA:__:%'; but wanted to avoid regexp hence more ugly - networkType = RouteNetwork.CA_PROVINCIAL; // network|ref: CA:AB:primary|11 CA:ON:private_toll|407 + networkType = RouteNetwork.CA_PROVINCIAL; } int rank = switch (coalesce(network, "")) { From 32c59d1b87590b0407e717a7086b6767839fa1d3 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 09:47:04 +0200 Subject: [PATCH 13/28] regenerate-openmaptiles.sh 5f7b2c11b3224759a21133381ca7d959a1f3cf51 (to match content of OMT PR 1465) --- .../generated/OpenMapTilesSchema.java | 40 ++++++++++--------- .../org/openmaptiles/generated/Tables.java | 2 +- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java index 3dd59c39..163f803f 100644 --- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java +++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java @@ -49,8 +49,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * All vector tile layer definitions, attributes, and allowed values generated from the OpenMapTiles - * vector tile schema 07f243c5d9efa558fa539d7a31b2ae50507aaa9d. + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/openmaptiles.yaml">OpenMapTiles + * vector tile schema 5f7b2c11b3224759a21133381ca7d959a1f3cf51. */ @SuppressWarnings("unused") public class OpenMapTilesSchema { @@ -96,7 +96,7 @@ public static List createInstances(Translations translations, PlanetilerC * boundaries show up. So you might not be able to use border styling for ocean water features. * * Generated from water.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/water/water.yaml">water.yaml */ public interface Water extends Layer { double BUFFER_SIZE = 4.0; @@ -191,7 +191,7 @@ final class FieldMappings { * field applied. Waterways do not have a subclass field. * * Generated from waterway.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/waterway/waterway.yaml">waterway.yaml */ public interface Waterway extends Layer { double BUFFER_SIZE = 4.0; @@ -276,7 +276,7 @@ final class FieldMappings { * layer is to style wood (class=wood) and grass (class=grass) areas. * * Generated from landcover.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/landcover/landcover.yaml">landcover.yaml */ public interface Landcover extends Layer { double BUFFER_SIZE = 4.0; @@ -433,7 +433,7 @@ final class FieldMappings { * residential (urban) areas and at higher zoom levels mostly OSM landuse tags. * * Generated from landuse.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/landuse/landuse.yaml">landuse.yaml */ public interface Landuse extends Layer { double BUFFER_SIZE = 4.0; @@ -529,7 +529,7 @@ final class FieldMappings { * Natural peaks * * Generated from mountain_peak.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/mountain_peak/mountain_peak.yaml">mountain_peak.yaml */ public interface MountainPeak extends Layer { double BUFFER_SIZE = 64.0; @@ -604,7 +604,7 @@ final class FieldMappings { * leisure=nature_reserve. * * Generated from park.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/park/park.yaml">park.yaml */ public interface Park extends Layer { double BUFFER_SIZE = 4.0; @@ -664,7 +664,7 @@ final class FieldMappings { * but for most styles it makes sense to just style admin_level=2 and admin_level=4. * * Generated from boundary.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/boundary/boundary.yaml">boundary.yaml */ public interface Boundary extends Layer { double BUFFER_SIZE = 4.0; @@ -765,7 +765,7 @@ final class FieldMappings { * in the aeroway layer. * * Generated from aeroway.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/aeroway/aeroway.yaml">aeroway.yaml */ public interface Aeroway extends Layer { double BUFFER_SIZE = 4.0; @@ -825,7 +825,7 @@ final class FieldMappings { * features like plazas. * * Generated from transportation.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/transportation/transportation.yaml">transportation.yaml */ public interface Transportation extends Layer { double BUFFER_SIZE = 4.0; @@ -1169,7 +1169,7 @@ final class FieldMappings { * location:underground are excluded. * * Generated from building.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/building/building.yaml">building.yaml */ public interface Building extends Layer { double BUFFER_SIZE = 4.0; @@ -1211,7 +1211,7 @@ final class FieldMappings { * from OSM water bodies. Only the most important lakes contain labels. * * Generated from water_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/water_name/water_name.yaml">water_name.yaml */ public interface WaterName extends Layer { double BUFFER_SIZE = 256.0; @@ -1281,7 +1281,7 @@ final class FieldMappings { * while for other roads you should use name. * * Generated from transportation_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/transportation_name/transportation_name.yaml">transportation_name.yaml */ public interface TransportationName extends Layer { double BUFFER_SIZE = 8.0; @@ -1328,6 +1328,7 @@ final class Fields { *
  • "ca-provincial" *
  • "gb-motorway" *
  • "gb-trunk" + *
  • "gb-primary" *
  • "road (default)" *
*/ @@ -1441,9 +1442,10 @@ final class FieldValues { public static final String NETWORK_CA_PROVINCIAL = "ca-provincial"; public static final String NETWORK_GB_MOTORWAY = "gb-motorway"; public static final String NETWORK_GB_TRUNK = "gb-trunk"; + public static final String NETWORK_GB_PRIMARY = "gb-primary"; public static final String NETWORK_ROAD = "road"; public static final Set NETWORK_VALUES = Set.of("us-interstate", "us-highway", "us-state", - "ca-transcanada", "ca-provincial-arterial", "ca-provincial", "gb-motorway", "gb-trunk", "road"); + "ca-transcanada", "ca-provincial-arterial", "ca-provincial", "gb-motorway", "gb-trunk", "gb-primary", "road"); public static final String CLASS_MOTORWAY = "motorway"; public static final String CLASS_TRUNK = "trunk"; public static final String CLASS_PRIMARY = "primary"; @@ -1502,7 +1504,7 @@ final class FieldMappings { * create a text hierarchy. * * Generated from place.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/place/place.yaml">place.yaml */ public interface Place extends Layer { double BUFFER_SIZE = 256.0; @@ -1610,7 +1612,7 @@ final class FieldMappings { * housenumber. * * Generated from housenumber.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/housenumber/housenumber.yaml">housenumber.yaml */ public interface Housenumber extends Layer { double BUFFER_SIZE = 8.0; @@ -1640,7 +1642,7 @@ final class FieldMappings { * of OpenStreetMap tags. Mostly contains amenities, sport, shop and tourist POIs. * * Generated from poi.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/poi/poi.yaml">poi.yaml */ public interface Poi extends Layer { double BUFFER_SIZE = 64.0; @@ -1859,7 +1861,7 @@ final class FieldMappings { * Aerodrome labels * * Generated from aerodrome_label.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/layers/aerodrome_label/aerodrome_label.yaml">aerodrome_label.yaml */ public interface AerodromeLabel extends Layer { double BUFFER_SIZE = 64.0; diff --git a/src/main/java/org/openmaptiles/generated/Tables.java b/src/main/java/org/openmaptiles/generated/Tables.java index a090d736..c49bf3c7 100644 --- a/src/main/java/org/openmaptiles/generated/Tables.java +++ b/src/main/java/org/openmaptiles/generated/Tables.java @@ -51,7 +51,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * OSM element parsers generated from the imposm3 table definitions * in the OpenMapTiles + * "https://github.com/openmaptiles/openmaptiles/blob/5f7b2c11b3224759a21133381ca7d959a1f3cf51/openmaptiles.yaml">OpenMapTiles * vector tile schema. * * These filter and parse the raw OSM key/value attribute pairs on tags into records with fields that match the columns From 4e3f0fdbd4fb22734837e12b168473d5c668a14b Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 14:14:31 +0200 Subject: [PATCH 14/28] GB road relations processing adjusted to match OMT PR 1465, e.g. handle also primary and secondary roads --- .../openmaptiles/layers/Transportation.java | 33 ++++- .../layers/TransportationTest.java | 136 ++++++++++++++++++ 2 files changed, 163 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index 6ea5c7c8..e0c23298 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -104,7 +104,7 @@ public class Transportation implements */ private static final Logger LOGGER = LoggerFactory.getLogger(Transportation.class); - private static final Pattern GREAT_BRITAIN_REF_NETWORK_PATTERN = Pattern.compile("^[AM][0-9AM()]+"); + private static final Pattern GREAT_BRITAIN_REF_NETWORK_PATTERN = Pattern.compile("^[ABM][0-9ABM()]+"); private static final MultiExpression.Index classMapping = FieldMappings.Class.index(); private static final Set RAILWAY_RAIL_VALUES = Set.of( FieldValues.SUBCLASS_RAIL, @@ -339,10 +339,30 @@ List getRouteRelations(Tables.OsmHighwayLinestring element) { try { Geometry wayGeometry = element.source().worldGeometry(); if (greatBritain.intersects(wayGeometry)) { - Transportation.RouteNetwork networkType = - "motorway".equals(element.highway()) ? Transportation.RouteNetwork.GB_MOTORWAY : - Transportation.RouteNetwork.GB_TRUNK; - String network = "motorway".equals(element.highway()) ? "omt-gb-motorway" : "omt-gb-trunk"; + Transportation.RouteNetwork networkType; + String network; + switch (element.highway()) { + case "motorway" -> { + networkType = Transportation.RouteNetwork.GB_MOTORWAY; + network = "omt-gb-motorway"; + } + case "trunk" -> { + networkType = RouteNetwork.GB_TRUNK; + network = "omt-gb-trunk"; + } + case "primary" -> { + networkType = RouteNetwork.GB_PRIMARY; + network = "omt-gb-primary"; + } + case "secondary" -> { + networkType = null; + network = "omt-gb-primary"; + } + default -> { + networkType = null; + network = null; + } + } result.add(new RouteRelation(refMatcher.group(), network, networkType, (byte) -1, 0)); } @@ -594,7 +614,8 @@ enum RouteNetwork { CA_PROVINCIAL_ARTERIAL("ca-provincial-arterial"), CA_PROVINCIAL("ca-provincial"), GB_MOTORWAY("gb-motorway"), - GB_TRUNK("gb-trunk"); + GB_TRUNK("gb-trunk"), + GB_PRIMARY("gb-primary"); final String name; diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index ff954c82..ccd11bd9 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -1284,6 +1284,142 @@ void testGreatBritainHighway() { ))); } + @Test + void testGreatBritainTrunk() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "GB"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in GB + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "A272", + "ref_length", 4, + "network", "gb-trunk", + "_minzoom", 8 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "trunk", + "ref", "A272" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + + @Test + void testGreatBritainPrimary() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "GB"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in GB + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "primary", + "_minzoom", 7 + ), Map.of( + "_layer", "transportation_name", + "class", "primary", + "ref", "A598", + "ref_length", 4, + "network", "gb-primary", + "_minzoom", 12 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "primary", + "ref", "A598" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + + @Test + void testGreatBritainSecondary() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "GB"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in GB + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "secondary", + "_minzoom", 9 + ), Map.of( + "_layer", "transportation_name", + "class", "secondary", + "ref", "B4558", + "ref_length", 5, + "network", "road", + "_minzoom", 12 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "secondary", + "ref", "B4558" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + + @Test + void testGreatBritainTertiary() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "GB"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in GB + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "tertiary", + "_minzoom", 11 + ), Map.of( + "_layer", "transportation_name", + "class", "tertiary", + "ref", "B4086", + "ref_length", 5, + "network", "road", + "_minzoom", 12 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "tertiary", + "ref", "B4086" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + @Test void testMergesDisconnectedRoadNameFeatures() throws GeometryException { testMergesLinestrings(Map.of("class", "motorway"), TransportationName.LAYER_NAME, 10, 14); From da044caf34d68ea4feffe57bc5dc048bb6c67ac9 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 17:35:33 +0200 Subject: [PATCH 15/28] regenerate-openmaptiles.sh edb42f2db3c2b0ec37045367720eed84d7bbd71f (to match content of OMT PR 1466) --- .../generated/OpenMapTilesSchema.java | 68 +++++++++++-------- .../org/openmaptiles/generated/Tables.java | 9 ++- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java index 163f803f..ee3d08e1 100644 --- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java +++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java @@ -49,8 +49,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * All vector tile layer definitions, attributes, and allowed values generated from the OpenMapTiles - * vector tile schema 5f7b2c11b3224759a21133381ca7d959a1f3cf51. + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/openmaptiles.yaml">OpenMapTiles + * vector tile schema edb42f2db3c2b0ec37045367720eed84d7bbd71f. */ @SuppressWarnings("unused") public class OpenMapTilesSchema { @@ -96,7 +96,7 @@ public static List createInstances(Translations translations, PlanetilerC * boundaries show up. So you might not be able to use border styling for ocean water features. * * Generated from water.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/water/water.yaml">water.yaml */ public interface Water extends Layer { double BUFFER_SIZE = 4.0; @@ -117,9 +117,13 @@ final class Fields { /** * All water polygons from OpenStreetMapData have the class - * ocean. Water bodies with the - * water=river tag are classified as - * river. Wet and dry docks tagged + * ocean. The water-covered areas of flowing water bodies with the + * water=river, + * water=canal, + * water=stream, + * water=ditch, or + * water=drain tags are classified + * as river. Wet and dry docks tagged * waterway=dock are classified as * a dock. Various minor waterbodies are classified as a pond. Swimming pools tagged * leisure=swimming_pool @@ -177,7 +181,7 @@ final class FieldValues { final class FieldMappings { public static final MultiExpression Class = MultiExpression.of(List.of(MultiExpression.entry("dock", matchAny("waterway", "dock")), - MultiExpression.entry("river", matchAny("water", "river")), + MultiExpression.entry("river", matchAny("water", "river", "stream", "canal", "ditch", "drain")), MultiExpression.entry("pond", matchAny("water", "pond", "basin", "wastewater")), MultiExpression.entry("lake", FALSE), MultiExpression.entry("ocean", FALSE), MultiExpression.entry("swimming_pool", matchAny("leisure", "swimming_pool")))); @@ -191,7 +195,7 @@ final class FieldMappings { * field applied. Waterways do not have a subclass field. * * Generated from waterway.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/waterway/waterway.yaml">waterway.yaml */ public interface Waterway extends Layer { double BUFFER_SIZE = 4.0; @@ -276,7 +280,7 @@ final class FieldMappings { * layer is to style wood (class=wood) and grass (class=grass) areas. * * Generated from landcover.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/landcover/landcover.yaml">landcover.yaml */ public interface Landcover extends Layer { double BUFFER_SIZE = 4.0; @@ -433,7 +437,7 @@ final class FieldMappings { * residential (urban) areas and at higher zoom levels mostly OSM landuse tags. * * Generated from landuse.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/landuse/landuse.yaml">landuse.yaml */ public interface Landuse extends Layer { double BUFFER_SIZE = 4.0; @@ -529,7 +533,7 @@ final class FieldMappings { * Natural peaks * * Generated from mountain_peak.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/mountain_peak/mountain_peak.yaml">mountain_peak.yaml */ public interface MountainPeak extends Layer { double BUFFER_SIZE = 64.0; @@ -604,7 +608,7 @@ final class FieldMappings { * leisure=nature_reserve. * * Generated from park.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/park/park.yaml">park.yaml */ public interface Park extends Layer { double BUFFER_SIZE = 4.0; @@ -664,7 +668,7 @@ final class FieldMappings { * but for most styles it makes sense to just style admin_level=2 and admin_level=4. * * Generated from boundary.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/boundary/boundary.yaml">boundary.yaml */ public interface Boundary extends Layer { double BUFFER_SIZE = 4.0; @@ -765,7 +769,7 @@ final class FieldMappings { * in the aeroway layer. * * Generated from aeroway.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/aeroway/aeroway.yaml">aeroway.yaml */ public interface Aeroway extends Layer { double BUFFER_SIZE = 4.0; @@ -825,7 +829,7 @@ final class FieldMappings { * features like plazas. * * Generated from transportation.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/transportation/transportation.yaml">transportation.yaml */ public interface Transportation extends Layer { double BUFFER_SIZE = 4.0; @@ -910,8 +914,9 @@ final class Fields { * The network type derived mainly from * network tag of the road. See more * info about us- , - * ca-transcanada, or - * gb- . + * ca-transcanada, + * gb- , + * or ie- . */ public static final String NETWORK = "network"; @@ -1169,7 +1174,7 @@ final class FieldMappings { * location:underground are excluded. * * Generated from building.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/building/building.yaml">building.yaml */ public interface Building extends Layer { double BUFFER_SIZE = 4.0; @@ -1211,7 +1216,7 @@ final class FieldMappings { * from OSM water bodies. Only the most important lakes contain labels. * * Generated from water_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/water_name/water_name.yaml">water_name.yaml */ public interface WaterName extends Layer { double BUFFER_SIZE = 256.0; @@ -1281,7 +1286,7 @@ final class FieldMappings { * while for other roads you should use name. * * Generated from transportation_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/transportation_name/transportation_name.yaml">transportation_name.yaml */ public interface TransportationName extends Layer { double BUFFER_SIZE = 8.0; @@ -1329,6 +1334,9 @@ final class Fields { *
  • "gb-motorway" *
  • "gb-trunk" *
  • "gb-primary" + *
  • "ie-motorway" + *
  • "ie-national" + *
  • "ie-regional" *
  • "road (default)" * */ @@ -1443,9 +1451,13 @@ final class FieldValues { public static final String NETWORK_GB_MOTORWAY = "gb-motorway"; public static final String NETWORK_GB_TRUNK = "gb-trunk"; public static final String NETWORK_GB_PRIMARY = "gb-primary"; + public static final String NETWORK_IE_MOTORWAY = "ie-motorway"; + public static final String NETWORK_IE_NATIONAL = "ie-national"; + public static final String NETWORK_IE_REGIONAL = "ie-regional"; public static final String NETWORK_ROAD = "road"; - public static final Set NETWORK_VALUES = Set.of("us-interstate", "us-highway", "us-state", - "ca-transcanada", "ca-provincial-arterial", "ca-provincial", "gb-motorway", "gb-trunk", "gb-primary", "road"); + public static final Set NETWORK_VALUES = + Set.of("us-interstate", "us-highway", "us-state", "ca-transcanada", "ca-provincial-arterial", "ca-provincial", + "gb-motorway", "gb-trunk", "gb-primary", "ie-motorway", "ie-national", "ie-regional", "road"); public static final String CLASS_MOTORWAY = "motorway"; public static final String CLASS_TRUNK = "trunk"; public static final String CLASS_PRIMARY = "primary"; @@ -1504,7 +1516,7 @@ final class FieldMappings { * create a text hierarchy. * * Generated from place.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/place/place.yaml">place.yaml */ public interface Place extends Layer { double BUFFER_SIZE = 256.0; @@ -1556,6 +1568,7 @@ final class Fields { *
  • "town" *
  • "village" *
  • "hamlet" + *
  • "borough" *
  • "suburb" *
  • "quarter" *
  • "neighbourhood" @@ -1593,13 +1606,14 @@ final class FieldValues { public static final String CLASS_TOWN = "town"; public static final String CLASS_VILLAGE = "village"; public static final String CLASS_HAMLET = "hamlet"; + public static final String CLASS_BOROUGH = "borough"; public static final String CLASS_SUBURB = "suburb"; public static final String CLASS_QUARTER = "quarter"; public static final String CLASS_NEIGHBOURHOOD = "neighbourhood"; public static final String CLASS_ISOLATED_DWELLING = "isolated_dwelling"; public static final String CLASS_ISLAND = "island"; public static final Set CLASS_VALUES = Set.of("continent", "country", "state", "province", "city", "town", - "village", "hamlet", "suburb", "quarter", "neighbourhood", "isolated_dwelling", "island"); + "village", "hamlet", "borough", "suburb", "quarter", "neighbourhood", "isolated_dwelling", "island"); } /** Complex mappings to generate attribute values from OSM element tags in the place layer. */ final class FieldMappings { @@ -1612,7 +1626,7 @@ final class FieldMappings { * housenumber. * * Generated from housenumber.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/housenumber/housenumber.yaml">housenumber.yaml */ public interface Housenumber extends Layer { double BUFFER_SIZE = 8.0; @@ -1642,7 +1656,7 @@ final class FieldMappings { * of OpenStreetMap tags. Mostly contains amenities, sport, shop and tourist POIs. * * Generated from poi.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/poi/poi.yaml">poi.yaml */ public interface Poi extends Layer { double BUFFER_SIZE = 64.0; @@ -1861,7 +1875,7 @@ final class FieldMappings { * Aerodrome labels * * Generated from aerodrome_label.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/layers/aerodrome_label/aerodrome_label.yaml">aerodrome_label.yaml */ public interface AerodromeLabel extends Layer { double BUFFER_SIZE = 64.0; diff --git a/src/main/java/org/openmaptiles/generated/Tables.java b/src/main/java/org/openmaptiles/generated/Tables.java index c49bf3c7..e8cb1f9a 100644 --- a/src/main/java/org/openmaptiles/generated/Tables.java +++ b/src/main/java/org/openmaptiles/generated/Tables.java @@ -51,7 +51,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * OSM element parsers generated from the imposm3 table definitions * in the OpenMapTiles + * "https://github.com/openmaptiles/openmaptiles/blob/edb42f2db3c2b0ec37045367720eed84d7bbd71f/openmaptiles.yaml">OpenMapTiles * vector tile schema. * * These filter and parse the raw OSM key/value attribute pairs on tags into records with fields that match the columns @@ -111,7 +111,7 @@ public OsmWaterPolygon(SourceFeature source, String mappingKey) { public static final Expression MAPPING = and( or(matchAny("landuse", "reservoir", "basin", "salt_pond"), matchAny("leisure", "swimming_pool"), matchAny("natural", "water", "bay", "spring"), matchAny("waterway", "dock"), - matchAny("water", "river", "pond", "basin", "wastewater")), + matchAny("water", "river", "stream", "canal", "ditch", "drain", "pond", "basin", "wastewater")), not(matchAny("covered", "yes")), matchType("polygon")); /** @@ -662,9 +662,8 @@ public OsmCityPoint(SourceFeature source, String mappingKey) { } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ - public static final Expression MAPPING = and( - matchAny("place", "city", "town", "village", "hamlet", "suburb", "quarter", "neighbourhood", "isolated_dwelling"), - matchField("name"), matchType("point")); + public static final Expression MAPPING = and(matchAny("place", "city", "town", "village", "hamlet", "borough", + "suburb", "quarter", "neighbourhood", "isolated_dwelling"), matchField("name"), matchType("point")); /** * Interface for layer implementations to extend to subscribe to OSM elements filtered and parsed as From 436e3b384623af70c4176d0af2439979372608c9 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 18:35:36 +0200 Subject: [PATCH 16/28] IE road relations processing adjusted to match OMT PR 1466, e.g. handle IE roates in similar way as GB routes --- .../openmaptiles/layers/Transportation.java | 63 ++++++- .../layers/TransportationTest.java | 165 ++++++++++++++++++ 2 files changed, 224 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index e0c23298..36e9dde7 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -105,6 +105,7 @@ public class Transportation implements private static final Logger LOGGER = LoggerFactory.getLogger(Transportation.class); private static final Pattern GREAT_BRITAIN_REF_NETWORK_PATTERN = Pattern.compile("^[ABM][0-9ABM()]+"); + private static final Pattern IRELAND_REF_NETWORK_PATTERN = Pattern.compile("^[MNRL][0-9]+"); private static final MultiExpression.Index classMapping = FieldMappings.Class.index(); private static final Set RAILWAY_RAIL_VALUES = Set.of( FieldValues.SUBCLASS_RAIL, @@ -164,11 +165,13 @@ public class Transportation implements private static final Set ONEWAY_VALUES = Set.of(-1, 1); private static final String LIMIT_MERGE_TAG = "__limit_merge"; private final AtomicBoolean loggedNoGb = new AtomicBoolean(false); + private final AtomicBoolean loggedNoIreland = new AtomicBoolean(false); private final boolean z13Paths; private final Map MINZOOMS; private final Stats stats; private final PlanetilerConfig config; private PreparedGeometry greatBritain = null; + private PreparedGeometry ireland = null; public Transportation(Translations translations, PlanetilerConfig config, Stats stats) { this.config = config; @@ -251,9 +254,12 @@ private static boolean isBridgeOrPier(String manMade) { @Override public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) { - if ("ne_10m_admin_0_countries".equals(table) && feature.hasTag("iso_a2", "GB")) { - // multiple threads call this method concurrently, GB polygon *should* only be found - // once, but just to be safe synchronize updates to that field + if (!"ne_10m_admin_0_countries".equals(table)) { + return; + } + // multiple threads call this method concurrently, GB (or IE) polygon *should* only be found + // once, but just to be safe synchronize updates to that field + if (feature.hasTag("iso_a2", "GB")) { synchronized (this) { try { Geometry boundary = feature.polygon().buffer(GeoUtils.metersToPixelAtEquator(0, 10_000) / 256d); @@ -262,6 +268,15 @@ public void processNaturalEarth(String table, SourceFeature feature, LOGGER.error("Failed to get Great Britain Polygon: " + e); } } + } else if (feature.hasTag("iso_a2", "IE")) { + synchronized (this) { + try { + Geometry boundary = feature.polygon().buffer(GeoUtils.metersToPixelAtEquator(0, 10_000) / 256d); + ireland = PreparedGeometryFactory.prepare(boundary); + } catch (GeometryException e) { + LOGGER.error("Failed to get Great Britain Polygon: " + e); + } + } } } @@ -372,6 +387,43 @@ List getRouteRelations(Tables.OsmHighwayLinestring element) { } } } + // Similarly Ireland. + refMatcher = IRELAND_REF_NETWORK_PATTERN.matcher(ref); + if (refMatcher.find()) { + if (ireland == null) { + if (!loggedNoIreland.get() && loggedNoIreland.compareAndSet(false, true)) { + LOGGER.warn("No IE polygon for inferring route network types"); + } + } else { + try { + Geometry wayGeometry = element.source().worldGeometry(); + if (ireland.intersects(wayGeometry)) { + Transportation.RouteNetwork networkType; + String network; + String highway = coalesce(element.highway(), ""); + switch (highway) { + case "motorway" -> { + networkType = Transportation.RouteNetwork.IE_MOTORWAY; + network = "omt-ie-motorway"; + } + case "trunk", "primary" -> { + networkType = RouteNetwork.IE_NATIONAL; + network = "omt-ie-national"; + } + default -> { + networkType = RouteNetwork.IE_REGIONAL; + network = "omt-ie-regional"; + } + } + result.add(new RouteRelation(refMatcher.group(), network, networkType, (byte) -1, + 0)); + } + } catch (GeometryException e) { + e.log(stats, "omt_transportation_name_ie_test", + "Unable to test highway against IE route network: " + element.source().id()); + } + } + } } Collections.sort(result); return result; @@ -615,7 +667,10 @@ enum RouteNetwork { CA_PROVINCIAL("ca-provincial"), GB_MOTORWAY("gb-motorway"), GB_TRUNK("gb-trunk"), - GB_PRIMARY("gb-primary"); + GB_PRIMARY("gb-primary"), + IE_MOTORWAY("ie-motorway"), + IE_NATIONAL("ie-national"), + IE_REGIONAL("ie-regional"); final String name; diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index ccd11bd9..f38a7c2c 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -1420,6 +1420,171 @@ void testGreatBritainTertiary() { ))); } + @Test + void testIrelandHighway() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in IE + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "motorway", + "oneway", 1, + "ramp", "", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "motorway", + "ref", "M18", + "ref_length", 3, + "network", "ie-motorway", + "_minzoom", 6 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "motorway", + "oneway", "yes", + "ref", "M18" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + + // not in IE + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "motorway", + "oneway", 1, + "ramp", "", + "_minzoom", 4 + ), Map.of( + "_layer", "transportation_name", + "class", "motorway", + "ref", "M18", + "ref_length", 3, + "network", "road", + "_minzoom", 6 + )), process(SimpleFeature.create( + newLineString(1, 0, 0, 1), + Map.of( + "highway", "motorway", + "oneway", "yes", + "ref", "M18" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + + @Test + void testIrelandTrunk() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in IE + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "trunk", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "N8", + "ref_length", 2, + "network", "ie-national", + "_minzoom", 8 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "trunk", + "ref", "N8" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + + @Test + void testIrelandPrimary() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in IE + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "primary", + "_minzoom", 7 + ), Map.of( + "_layer", "transportation_name", + "class", "primary", + "ref", "N59", + "ref_length", 3, + "network", "ie-national", + "_minzoom", 12 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "primary", + "ref", "N59" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + + @Test + void testIrelandSecondary() { + process(SimpleFeature.create( + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 + )); + + // in IE + assertFeatures(13, List.of(Map.of( + "_layer", "transportation", + "class", "secondary", + "_minzoom", 9 + ), Map.of( + "_layer", "transportation_name", + "class", "secondary", + "ref", "R813", + "ref_length", 4, + "network", "ie-regional", + "_minzoom", 12 + )), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + Map.of( + "highway", "secondary", + "ref", "R813" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ))); + } + @Test void testMergesDisconnectedRoadNameFeatures() throws GeometryException { testMergesLinestrings(Map.of("class", "motorway"), TransportationName.LAYER_NAME, 10, 14); From 6354264ff15dfdb5007526d1ea4ec383e343eaa0 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 18:40:05 +0200 Subject: [PATCH 17/28] fixed handling of networkType for secondary GB routes --- src/main/java/org/openmaptiles/layers/Transportation.java | 2 +- src/test/java/org/openmaptiles/layers/TransportationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index e0c23298..0b0a1fa8 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -355,7 +355,7 @@ List getRouteRelations(Tables.OsmHighwayLinestring element) { network = "omt-gb-primary"; } case "secondary" -> { - networkType = null; + networkType = RouteNetwork.GB_PRIMARY; network = "omt-gb-primary"; } default -> { diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index ccd11bd9..d9fd38c9 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -1372,7 +1372,7 @@ void testGreatBritainSecondary() { "class", "secondary", "ref", "B4558", "ref_length", 5, - "network", "road", + "network", "gb-primary", "_minzoom", 12 )), process(SimpleFeature.create( newLineString(0, 0, 1, 1), From 984b7f2733fe1beb92c3353d7a7648d136cfec4d Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 18:41:32 +0200 Subject: [PATCH 18/28] clean-up: case statements simplified --- src/main/java/org/openmaptiles/layers/Transportation.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index 0b0a1fa8..f894dcdf 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -350,11 +350,7 @@ List getRouteRelations(Tables.OsmHighwayLinestring element) { networkType = RouteNetwork.GB_TRUNK; network = "omt-gb-trunk"; } - case "primary" -> { - networkType = RouteNetwork.GB_PRIMARY; - network = "omt-gb-primary"; - } - case "secondary" -> { + case "primary", "secondary" -> { networkType = RouteNetwork.GB_PRIMARY; network = "omt-gb-primary"; } From 1e1b896f573549f75d422d73f92b4bb68aa27f64 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 20:21:19 +0200 Subject: [PATCH 19/28] mvn spotless:apply --- .../openmaptiles/layers/Transportation.java | 4 +- .../layers/TransportationTest.java | 222 +++++++++--------- 2 files changed, 113 insertions(+), 113 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index ba386492..ea3e90fd 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -412,11 +412,11 @@ List getRouteRelations(Tables.OsmHighwayLinestring element) { } } result.add(new RouteRelation(refMatcher.group(), network, networkType, (byte) -1, - 0)); + 0)); } } catch (GeometryException e) { e.log(stats, "omt_transportation_name_ie_test", - "Unable to test highway against IE route network: " + element.source().id()); + "Unable to test highway against IE route network: " + element.source().id()); } } } diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index c2f9e6b7..108925b8 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -1423,165 +1423,165 @@ void testGreatBritainTertiary() { @Test void testIrelandHighway() { process(SimpleFeature.create( - rectangle(0, 0.1), - Map.of("iso_a2", "IE"), - OpenMapTilesProfile.NATURAL_EARTH_SOURCE, - "ne_10m_admin_0_countries", - 0 + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 )); // in IE assertFeatures(13, List.of(Map.of( - "_layer", "transportation", - "class", "motorway", - "oneway", 1, - "ramp", "", - "_minzoom", 4 + "_layer", "transportation", + "class", "motorway", + "oneway", 1, + "ramp", "", + "_minzoom", 4 ), Map.of( - "_layer", "transportation_name", - "class", "motorway", - "ref", "M18", - "ref_length", 3, - "network", "ie-motorway", - "_minzoom", 6 + "_layer", "transportation_name", + "class", "motorway", + "ref", "M18", + "ref_length", 3, + "network", "ie-motorway", + "_minzoom", 6 )), process(SimpleFeature.create( - newLineString(0, 0, 1, 1), - Map.of( - "highway", "motorway", - "oneway", "yes", - "ref", "M18" - ), - OpenMapTilesProfile.OSM_SOURCE, - null, - 0 + newLineString(0, 0, 1, 1), + Map.of( + "highway", "motorway", + "oneway", "yes", + "ref", "M18" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 ))); // not in IE assertFeatures(13, List.of(Map.of( - "_layer", "transportation", - "class", "motorway", - "oneway", 1, - "ramp", "", - "_minzoom", 4 + "_layer", "transportation", + "class", "motorway", + "oneway", 1, + "ramp", "", + "_minzoom", 4 ), Map.of( - "_layer", "transportation_name", - "class", "motorway", - "ref", "M18", - "ref_length", 3, - "network", "road", - "_minzoom", 6 + "_layer", "transportation_name", + "class", "motorway", + "ref", "M18", + "ref_length", 3, + "network", "road", + "_minzoom", 6 )), process(SimpleFeature.create( - newLineString(1, 0, 0, 1), - Map.of( - "highway", "motorway", - "oneway", "yes", - "ref", "M18" - ), - OpenMapTilesProfile.OSM_SOURCE, - null, - 0 + newLineString(1, 0, 0, 1), + Map.of( + "highway", "motorway", + "oneway", "yes", + "ref", "M18" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 ))); } @Test void testIrelandTrunk() { process(SimpleFeature.create( - rectangle(0, 0.1), - Map.of("iso_a2", "IE"), - OpenMapTilesProfile.NATURAL_EARTH_SOURCE, - "ne_10m_admin_0_countries", - 0 + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 )); // in IE assertFeatures(13, List.of(Map.of( - "_layer", "transportation", - "class", "trunk", - "_minzoom", 5 + "_layer", "transportation", + "class", "trunk", + "_minzoom", 5 ), Map.of( - "_layer", "transportation_name", - "class", "trunk", - "ref", "N8", - "ref_length", 2, - "network", "ie-national", - "_minzoom", 8 + "_layer", "transportation_name", + "class", "trunk", + "ref", "N8", + "ref_length", 2, + "network", "ie-national", + "_minzoom", 8 )), process(SimpleFeature.create( - newLineString(0, 0, 1, 1), - Map.of( - "highway", "trunk", - "ref", "N8" - ), - OpenMapTilesProfile.OSM_SOURCE, - null, - 0 + newLineString(0, 0, 1, 1), + Map.of( + "highway", "trunk", + "ref", "N8" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 ))); } @Test void testIrelandPrimary() { process(SimpleFeature.create( - rectangle(0, 0.1), - Map.of("iso_a2", "IE"), - OpenMapTilesProfile.NATURAL_EARTH_SOURCE, - "ne_10m_admin_0_countries", - 0 + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 )); // in IE assertFeatures(13, List.of(Map.of( - "_layer", "transportation", - "class", "primary", - "_minzoom", 7 + "_layer", "transportation", + "class", "primary", + "_minzoom", 7 ), Map.of( - "_layer", "transportation_name", - "class", "primary", - "ref", "N59", - "ref_length", 3, - "network", "ie-national", - "_minzoom", 12 + "_layer", "transportation_name", + "class", "primary", + "ref", "N59", + "ref_length", 3, + "network", "ie-national", + "_minzoom", 12 )), process(SimpleFeature.create( - newLineString(0, 0, 1, 1), - Map.of( - "highway", "primary", - "ref", "N59" - ), - OpenMapTilesProfile.OSM_SOURCE, - null, - 0 + newLineString(0, 0, 1, 1), + Map.of( + "highway", "primary", + "ref", "N59" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 ))); } @Test void testIrelandSecondary() { process(SimpleFeature.create( - rectangle(0, 0.1), - Map.of("iso_a2", "IE"), - OpenMapTilesProfile.NATURAL_EARTH_SOURCE, - "ne_10m_admin_0_countries", - 0 + rectangle(0, 0.1), + Map.of("iso_a2", "IE"), + OpenMapTilesProfile.NATURAL_EARTH_SOURCE, + "ne_10m_admin_0_countries", + 0 )); // in IE assertFeatures(13, List.of(Map.of( - "_layer", "transportation", - "class", "secondary", - "_minzoom", 9 + "_layer", "transportation", + "class", "secondary", + "_minzoom", 9 ), Map.of( - "_layer", "transportation_name", - "class", "secondary", - "ref", "R813", - "ref_length", 4, - "network", "ie-regional", - "_minzoom", 12 + "_layer", "transportation_name", + "class", "secondary", + "ref", "R813", + "ref_length", 4, + "network", "ie-regional", + "_minzoom", 12 )), process(SimpleFeature.create( - newLineString(0, 0, 1, 1), - Map.of( - "highway", "secondary", - "ref", "R813" - ), - OpenMapTilesProfile.OSM_SOURCE, - null, - 0 + newLineString(0, 0, 1, 1), + Map.of( + "highway", "secondary", + "ref", "R813" + ), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 ))); } From 8750c782ad404bd7ba616977e23b1d7fd89350e4 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 21:35:17 +0200 Subject: [PATCH 20/28] clazz calculation moved up so that minzoom can be set to 3 for only lakes (to match OMT PR 1475) --- .../org/openmaptiles/layers/WaterName.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index 04c4f96b..9b7b1b73 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -188,8 +188,17 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { try { Geometry centerlineGeometry = lakeCenterlines.get(element.source().id()); FeatureCollector.Feature feature; - int minzoom = 9; String place = element.place(); + int minzoom = 9; + String clazz; + if ("bay".equals(element.natural())) { + clazz = FieldValues.CLASS_BAY; + } else if ("sea".equals(place)) { + clazz = FieldValues.CLASS_SEA; + } else { + clazz = FieldValues.CLASS_LAKE; + minzoom = 3; + } if (centerlineGeometry != null) { // prefer lake centerline if it exists feature = features.geometry(LAYER_NAME, centerlineGeometry) @@ -204,14 +213,6 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { minzoom = areaToMinZoom(area); } } - String clazz; - if ("bay".equals(element.natural())) { - clazz = FieldValues.CLASS_BAY; - } else if ("sea".equals(place)) { - clazz = FieldValues.CLASS_SEA; - } else { - clazz = FieldValues.CLASS_LAKE; - } feature .setAttr(Fields.CLASS, clazz) .setBufferPixels(BUFFER_SIZE) From 74181174327fa672dd661552c1531b23c208898a Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 14 Jul 2023 21:55:01 +0200 Subject: [PATCH 21/28] unit tests adjusted + extended to cover 'minzoom=3 fore lakes' change --- .../openmaptiles/layers/WaterNameTest.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java index 77ec32e3..364e8e23 100644 --- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java +++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java @@ -92,7 +92,7 @@ void testWaterNameLakeline() { "_layer", "water_name", "_type", "line", "_geom", new TestUtils.NormGeometry(GeoUtils.latLonToWorldCoords(newLineString(0, 0, 1, 1))), - "_minzoom", 9, + "_minzoom", 3, "_maxzoom", 14, "_minpixelsize", "waterway".length() * 6d )), process(SimpleFeature.create( @@ -142,7 +142,7 @@ void testWaterNameMultipleLakelines() { newLineString(0, 0, 1, 1), newLineString(2, 2, 3, 3) }))), - "_minzoom", 9, + "_minzoom", 3, "_maxzoom", 14, "_minpixelsize", "waterway".length() * 6d )), process(SimpleFeature.create( @@ -159,6 +159,40 @@ void testWaterNameMultipleLakelines() { ))); } + @Test + void testWaterNameBay() { + assertFeatures(11, List.of(), process(SimpleFeature.create( + newLineString(0, 0, 1, 1), + new HashMap<>(Map.of( + "OSM_ID", -10 + )), + OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, + null, + 0 + ))); + assertFeatures(10, List.of(Map.of( + "name", "bay", + "name:es", "bay es", + + "_layer", "water_name", + "_type", "line", + "_geom", new TestUtils.NormGeometry(GeoUtils.latLonToWorldCoords(newLineString(0, 0, 1, 1))), + "_minzoom", 9, + "_maxzoom", 14, + "_minpixelsize", "bay".length() * 6d + )), process(SimpleFeature.create( + GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), + new HashMap<>(Map.of( + "name", "bay", + "name:es", "bay es", + "natural", "bay" + )), + OpenMapTilesProfile.OSM_SOURCE, + null, + 10 + ))); + } + @Test void testMarinePoint() { assertFeatures(11, List.of(), process(SimpleFeature.create( From bfce42eca5751e12a352688800972b337beb1208 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 17 Jul 2023 18:07:39 +0200 Subject: [PATCH 22/28] fixed minor typo from previous PR --- src/test/java/org/openmaptiles/layers/WaterNameTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java index 364e8e23..fee396b1 100644 --- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java +++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java @@ -277,7 +277,7 @@ private void createAreaForMinZoomTest(List testEntries, double side, @Test void testAreaToMinZoom() throws GeometryException { // threshold is 1/4 of tile area, hence ... - // ... side if 1/2 tile side: from pixels to world coord, for say Z14 ... + // ... side is 1/2 tile side: from pixels to world coord, for say Z14 ... //final double HALF_OF_TILE_SIDE = 128d / Math.pow(2d, 14d + 8d); // ... and then for some lower zoom: //double testAreaSide = HALF_OF_TILE_SIDE * Math.pow(2, 14 - zoom); From 213dd1436cfb75eaa3a6285819e474e80adc3ab2 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 17 Jul 2023 22:20:44 +0200 Subject: [PATCH 23/28] render POIs for large universities at low zoom (to match OMT PR 1479) --- .../java/org/openmaptiles/layers/Poi.java | 34 +++++++++++- .../java/org/openmaptiles/layers/PoiTest.java | 53 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/openmaptiles/layers/Poi.java b/src/main/java/org/openmaptiles/layers/Poi.java index b12b8307..81b4d2d3 100644 --- a/src/main/java/org/openmaptiles/layers/Poi.java +++ b/src/main/java/org/openmaptiles/layers/Poi.java @@ -48,11 +48,13 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.onthegomap.planetiler.collection.Hppc; import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.expression.MultiExpression; +import com.onthegomap.planetiler.geo.GeometryException; import com.onthegomap.planetiler.stats.Stats; import com.onthegomap.planetiler.util.Parse; import com.onthegomap.planetiler.util.Translations; import java.util.List; import java.util.Map; +import java.util.Set; import org.openmaptiles.generated.OpenMapTilesSchema; import org.openmaptiles.generated.Tables; import org.openmaptiles.util.OmtLanguageUtils; @@ -99,12 +101,17 @@ public class Poi implements entry(FieldValues.CLASS_CLOTHING_STORE, 700), entry(FieldValues.CLASS_BAR, 800) ); + private static final Set UNIVERSITY_POI_SUBCLASSES = Set.of("university", "college"); + private static final double LOG2 = Math.log(2); + private static final double SQRT10 = Math.sqrt(10); private final MultiExpression.Index classMapping; private final Translations translations; + private final Stats stats; public Poi(Translations translations, PlanetilerConfig config, Stats stats) { this.classMapping = FieldMappings.Class.index(); this.translations = translations; + this.stats = stats; } static int poiClassRank(String clazz) { @@ -125,6 +132,19 @@ private int minzoom(String subclass, String mappingKey) { return lowZoom ? 12 : 14; } + public static int uniAreaToMinZoom(double areaWorld) { + double oneSideWorld = Math.sqrt(areaWorld); + // full(-er) formula (along with comments) is in PoiTest.testUniAreaToMinZoom(), here is simplified reverse of that + double zoom = -(Math.log(oneSideWorld * SQRT10) / LOG2); + + // Say Z13.01 means bellow threshold, Z13.00 is exactly threshold, Z12.99 is over threshold, + // hence Z13.01 and Z13.00 will be rounded to Z14 and Z12.99 to Z13 (e.g. `floor() + 1`). + // And to accommodate for some precision errors (observed for Z9-Z11) we do also `- 0.1e-10`. + int result = (int) Math.floor(zoom - 0.1e-10) + 1; + + return Math.min(14, Math.max(10, result)); + } + @Override public void process(Tables.OsmPoiPoint element, FeatureCollector features) { // TODO handle uic_ref => agg_stop @@ -178,6 +198,18 @@ private testEntries, double side, int expectedZoom, String name) { + double area = Math.pow(side, 2); + var feature = polygonFeatureWithArea(area, Map.of( + "name", name, + "amenity", "university" + )); + testEntries.add(new TestEntry( + feature, + Math.min(14, Math.max(10, expectedZoom)) + )); + } + + @Test + void testUniAreaToMinZoom() throws GeometryException { + // threshold is 1/10 of tile area, hence ... + // ... side is 1/sqrt(10) tile side: from pixels to world coord, for say Z14 ... + //final double PORTION_OF_TILE_SIDE = (256d / Math.sqrt(10)) / Math.pow(2d, 14d + 8d); + // ... and then for some lower zoom: + //double testAreaSide = PORTION_OF_TILE_SIDE * Math.pow(2, 14 - zoom); + // all this then simplified to `testAreaSide` calculation bellow + + final double SQRT10 = Math.sqrt(10); + final List testEntries = new ArrayList<>(); + for (int zoom = 14; zoom >= 0; zoom--) { + double testAreaSide = Math.pow(2, -zoom) / SQRT10; + + // slightly bellow the threshold + createUniAreaForMinZoomTest(testEntries, testAreaSide * 0.999, zoom + 1, "uni-"); + // precisely at the threshold + createUniAreaForMinZoomTest(testEntries, testAreaSide, zoom, "uni="); + // slightly over the threshold + createUniAreaForMinZoomTest(testEntries, testAreaSide * 1.001, zoom, "uni+"); + } + + for (var entry : testEntries) { + assertFeatures(14, List.of(Map.of( + "_layer", "landuse", + "class", "university" + ), Map.of( + "_layer", "poi", + "_type", "point", + "_minzoom", entry.expectedZoom, + "_maxzoom", 14 + )), process(entry.feature)); + } + } } From 03f2b04ad817e2d1817073f0a4698fa0fdd616c6 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Tue, 18 Jul 2023 09:45:27 +0200 Subject: [PATCH 24/28] clean-up, to make the diff/PR smaller --- src/main/java/org/openmaptiles/layers/WaterName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index 9b7b1b73..18621048 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -188,8 +188,8 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { try { Geometry centerlineGeometry = lakeCenterlines.get(element.source().id()); FeatureCollector.Feature feature; - String place = element.place(); int minzoom = 9; + String place = element.place(); String clazz; if ("bay".equals(element.natural())) { clazz = FieldValues.CLASS_BAY; From e46c44c464f756ade05f70b8786c2fc732371b2f Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 25 Sep 2023 18:49:20 +0200 Subject: [PATCH 25/28] regenerate-openmaptiles.sh 5e9b7c475d53a5bd5ea394da361594d3f4ce2d66 (to match content of OMT PR 1485) --- .../generated/OpenMapTilesSchema.java | 49 +++++++------- .../org/openmaptiles/generated/Tables.java | 66 +++++++++++++------ 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java index ee3d08e1..bff7ff4b 100644 --- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java +++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java @@ -49,8 +49,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * All vector tile layer definitions, attributes, and allowed values generated from the OpenMapTiles - * vector tile schema edb42f2db3c2b0ec37045367720eed84d7bbd71f. + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/openmaptiles.yaml">OpenMapTiles + * vector tile schema 5e9b7c475d53a5bd5ea394da361594d3f4ce2d66. */ @SuppressWarnings("unused") public class OpenMapTilesSchema { @@ -59,11 +59,11 @@ public class OpenMapTilesSchema { public static final String VERSION = "3.14.0"; public static final String ATTRIBUTION = "© OpenMapTiles © OpenStreetMap contributors"; - public static final List LANGUAGES = List.of("am", "ar", "az", "be", "bg", "br", "bs", "ca", "co", "cs", "cy", - "da", "de", "el", "en", "eo", "es", "et", "eu", "fi", "fr", "fy", "ga", "gd", "he", "hi", "hr", "hu", "hy", "id", - "is", "it", "ja", "ja_kana", "ja_rm", "ja-Latn", "ja-Hira", "ka", "kk", "kn", "ko", "ko-Latn", "ku", "la", "lb", - "lt", "lv", "mk", "mt", "ml", "nl", "no", "oc", "pl", "pt", "rm", "ro", "ru", "sk", "sl", "sq", "sr", "sr-Latn", - "sv", "ta", "te", "th", "tr", "uk", "zh"); + public static final List LANGUAGES = List.of("am", "ar", "az", "be", "bg", "bn", "br", "bs", "ca", "co", "cs", + "cy", "da", "de", "el", "en", "eo", "es", "et", "eu", "fa", "fi", "fr", "fy", "ga", "gd", "he", "hi", "hr", "hu", + "hy", "id", "is", "it", "ja", "ja_kana", "ja_rm", "ja-Latn", "ja-Hira", "ka", "kk", "kn", "ko", "ko-Latn", "ku", + "la", "lb", "lt", "lv", "mk", "mt", "ml", "nl", "no", "oc", "pa", "pnb", "pl", "pt", "rm", "ro", "ru", "sk", "sl", + "sq", "sr", "sr-Latn", "sv", "ta", "te", "th", "tr", "uk", "ur", "vi", "zh", "zh-Hant", "zh-Hans"); /** Returns a list of expected layer implementation instances from the {@code layers} package. */ public static List createInstances(Translations translations, PlanetilerConfig config, Stats stats) { @@ -96,7 +96,7 @@ public static List createInstances(Translations translations, PlanetilerC * boundaries show up. So you might not be able to use border styling for ocean water features. * * Generated from water.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/water/water.yaml">water.yaml */ public interface Water extends Layer { double BUFFER_SIZE = 4.0; @@ -195,7 +195,7 @@ final class FieldMappings { * field applied. Waterways do not have a subclass field. * * Generated from waterway.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/waterway/waterway.yaml">waterway.yaml */ public interface Waterway extends Layer { double BUFFER_SIZE = 4.0; @@ -280,7 +280,7 @@ final class FieldMappings { * layer is to style wood (class=wood) and grass (class=grass) areas. * * Generated from landcover.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/landcover/landcover.yaml">landcover.yaml */ public interface Landcover extends Layer { double BUFFER_SIZE = 4.0; @@ -437,7 +437,7 @@ final class FieldMappings { * residential (urban) areas and at higher zoom levels mostly OSM landuse tags. * * Generated from landuse.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/landuse/landuse.yaml">landuse.yaml */ public interface Landuse extends Layer { double BUFFER_SIZE = 4.0; @@ -533,7 +533,7 @@ final class FieldMappings { * Natural peaks * * Generated from mountain_peak.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/mountain_peak/mountain_peak.yaml">mountain_peak.yaml */ public interface MountainPeak extends Layer { double BUFFER_SIZE = 64.0; @@ -608,7 +608,7 @@ final class FieldMappings { * leisure=nature_reserve. * * Generated from park.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/park/park.yaml">park.yaml */ public interface Park extends Layer { double BUFFER_SIZE = 4.0; @@ -668,7 +668,7 @@ final class FieldMappings { * but for most styles it makes sense to just style admin_level=2 and admin_level=4. * * Generated from boundary.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/boundary/boundary.yaml">boundary.yaml */ public interface Boundary extends Layer { double BUFFER_SIZE = 4.0; @@ -769,7 +769,7 @@ final class FieldMappings { * in the aeroway layer. * * Generated from aeroway.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/aeroway/aeroway.yaml">aeroway.yaml */ public interface Aeroway extends Layer { double BUFFER_SIZE = 4.0; @@ -829,7 +829,7 @@ final class FieldMappings { * features like plazas. * * Generated from transportation.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/transportation/transportation.yaml">transportation.yaml */ public interface Transportation extends Layer { double BUFFER_SIZE = 4.0; @@ -1174,7 +1174,7 @@ final class FieldMappings { * location:underground are excluded. * * Generated from building.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/building/building.yaml">building.yaml */ public interface Building extends Layer { double BUFFER_SIZE = 4.0; @@ -1216,7 +1216,7 @@ final class FieldMappings { * from OSM water bodies. Only the most important lakes contain labels. * * Generated from water_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/water_name/water_name.yaml">water_name.yaml */ public interface WaterName extends Layer { double BUFFER_SIZE = 256.0; @@ -1286,7 +1286,7 @@ final class FieldMappings { * while for other roads you should use name. * * Generated from transportation_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/transportation_name/transportation_name.yaml">transportation_name.yaml */ public interface TransportationName extends Layer { double BUFFER_SIZE = 8.0; @@ -1516,7 +1516,7 @@ final class FieldMappings { * create a text hierarchy. * * Generated from place.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/place/place.yaml">place.yaml */ public interface Place extends Layer { double BUFFER_SIZE = 256.0; @@ -1623,10 +1623,11 @@ final class FieldMappings { /** * Everything in OpenStreetMap which contains a addr:housenumber tag useful for labelling housenumbers on * a map. This adds significant size to z14. For buildings the centroid of the building is used as - * housenumber. + * housenumber. Duplicates within a tile are dropped if they have the same street/block_number (records without name + * tag are prioritized for preservation). * * Generated from housenumber.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/housenumber/housenumber.yaml">housenumber.yaml */ public interface Housenumber extends Layer { double BUFFER_SIZE = 8.0; @@ -1656,7 +1657,7 @@ final class FieldMappings { * of OpenStreetMap tags. Mostly contains amenities, sport, shop and tourist POIs. * * Generated from poi.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/poi/poi.yaml">poi.yaml */ public interface Poi extends Layer { double BUFFER_SIZE = 64.0; @@ -1875,7 +1876,7 @@ final class FieldMappings { * Aerodrome labels * * Generated from aerodrome_label.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/layers/aerodrome_label/aerodrome_label.yaml">aerodrome_label.yaml */ public interface AerodromeLabel extends Layer { double BUFFER_SIZE = 64.0; diff --git a/src/main/java/org/openmaptiles/generated/Tables.java b/src/main/java/org/openmaptiles/generated/Tables.java index e8cb1f9a..00e4bc50 100644 --- a/src/main/java/org/openmaptiles/generated/Tables.java +++ b/src/main/java/org/openmaptiles/generated/Tables.java @@ -51,7 +51,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * OSM element parsers generated from the imposm3 table definitions * in the OpenMapTiles + * "https://github.com/openmaptiles/openmaptiles/blob/5e9b7c475d53a5bd5ea394da361594d3f4ce2d66/openmaptiles.yaml">OpenMapTiles * vector tile schema. * * These filter and parse the raw OSM key/value attribute pairs on tags into records with fields that match the columns @@ -319,25 +319,27 @@ public interface Handler { } } /** An OSM element that would appear in the {@code osm_highway_linestring} table generated by imposm3. */ - public record OsmHighwayLinestring(@Override String highway, @Override String construction, @Override String ref, - @Override String network, @Override int zOrder, @Override long layer, @Override long level, - @Override boolean indoor, @Override String name, @Override String nameEn, @Override String nameDe, - @Override String shortName, @Override boolean isTunnel, @Override boolean isBridge, @Override boolean isRamp, - @Override boolean isFord, @Override int isOneway, @Override boolean isArea, @Override String service, - @Override String access, @Override boolean toll, @Override String usage, @Override String publicTransport, - @Override String manMade, @Override String bicycle, @Override String foot, @Override String horse, - @Override String mtbScale, @Override String sacScale, @Override String surface, @Override boolean expressway, - @Override SourceFeature source) implements Row, WithHighway, WithConstruction, WithRef, WithNetwork, WithZOrder, - WithLayer, WithLevel, WithIndoor, WithName, WithNameEn, WithNameDe, WithShortName, WithIsTunnel, WithIsBridge, - WithIsRamp, WithIsFord, WithIsOneway, WithIsArea, WithService, WithAccess, WithToll, WithUsage, WithPublicTransport, + public record OsmHighwayLinestring(@Override String highway, @Override String construction, + @Override String tracktype, @Override String ref, @Override String network, @Override int zOrder, + @Override long layer, @Override long level, @Override boolean indoor, @Override String name, + @Override String nameEn, @Override String nameDe, @Override String shortName, @Override boolean isTunnel, + @Override boolean isBridge, @Override boolean isRamp, @Override boolean isFord, @Override int isOneway, + @Override boolean isArea, @Override String service, @Override String access, @Override boolean toll, + @Override String usage, @Override String publicTransport, @Override String manMade, @Override String bicycle, + @Override String foot, @Override String horse, @Override String mtbScale, @Override String sacScale, + @Override String surface, @Override boolean expressway, @Override SourceFeature source) + implements Row, WithHighway, WithConstruction, WithTracktype, WithRef, WithNetwork, WithZOrder, WithLayer, + WithLevel, WithIndoor, WithName, WithNameEn, WithNameDe, WithShortName, WithIsTunnel, WithIsBridge, WithIsRamp, + WithIsFord, WithIsOneway, WithIsArea, WithService, WithAccess, WithToll, WithUsage, WithPublicTransport, WithManMade, WithBicycle, WithFoot, WithHorse, WithMtbScale, WithSacScale, WithSurface, WithExpressway, WithSource { public OsmHighwayLinestring(SourceFeature source, String mappingKey) { - this(source.getString("highway"), source.getString("construction"), source.getString("ref"), - source.getString("network"), source.getWayZorder(), source.getLong("layer"), source.getLong("level"), - source.getBoolean("indoor"), source.getString("name"), source.getString("name:en"), source.getString("name:de"), - source.getString("short_name"), source.getBoolean("tunnel"), source.getBoolean("bridge"), - source.getBoolean("ramp"), source.getBoolean("ford"), source.getDirection("oneway"), source.getBoolean("area"), - source.getString("service"), source.getString("access"), source.getBoolean("toll"), source.getString("usage"), + this(source.getString("highway"), source.getString("construction"), source.getString("tracktype"), + source.getString("ref"), source.getString("network"), source.getWayZorder(), source.getLong("layer"), + source.getLong("level"), source.getBoolean("indoor"), source.getString("name"), source.getString("name:en"), + source.getString("name:de"), source.getString("short_name"), source.getBoolean("tunnel"), + source.getBoolean("bridge"), source.getBoolean("ramp"), source.getBoolean("ford"), + source.getDirection("oneway"), source.getBoolean("area"), source.getString("service"), + source.getString("access"), source.getBoolean("toll"), source.getString("usage"), source.getString("public_transport"), source.getString("man_made"), source.getString("bicycle"), source.getString("foot"), source.getString("horse"), source.getString("mtb:scale"), source.getString("sac_scale"), source.getString("surface"), source.getBoolean("expressway"), source); @@ -674,10 +676,12 @@ public interface Handler { } } /** An OSM element that would appear in the {@code osm_housenumber_point} table generated by imposm3. */ - public record OsmHousenumberPoint(@Override String housenumber, @Override SourceFeature source) - implements Row, WithHousenumber, WithSource { + public record OsmHousenumberPoint(@Override String housenumber, @Override String street, @Override String blockNumber, + @Override String hasName, @Override SourceFeature source) + implements Row, WithHousenumber, WithStreet, WithBlockNumber, WithHasName, WithSource { public OsmHousenumberPoint(SourceFeature source, String mappingKey) { - this(source.getString("addr:housenumber"), source); + this(source.getString("addr:housenumber"), source.getString("addr:street"), source.getString("addr:block_number"), + source.getString("name"), source); } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ @@ -890,6 +894,11 @@ public interface WithBicycle { String bicycle(); } + /** Rows with a String blockNumber attribute. */ + public interface WithBlockNumber { + String blockNumber(); + } + /** Rows with a String boundary attribute. */ public interface WithBoundary { String boundary(); @@ -970,6 +979,11 @@ public interface WithFunicular { String funicular(); } + /** Rows with a String hasName attribute. */ + public interface WithHasName { + String hasName(); + } + /** Rows with a String height attribute. */ public interface WithHeight { String height(); @@ -1230,6 +1244,11 @@ public interface WithStation { String station(); } + /** Rows with a String street attribute. */ + public interface WithStreet { + String street(); + } + /** Rows with a String subclass attribute. */ public interface WithSubclass { String subclass(); @@ -1250,6 +1269,11 @@ public interface WithTourism { String tourism(); } + /** Rows with a String tracktype attribute. */ + public interface WithTracktype { + String tracktype(); + } + /** Rows with a String uicRef attribute. */ public interface WithUicRef { String uicRef(); From 258db805918deece934ff566a79662424dfa6fda Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Mon, 25 Sep 2023 19:04:44 +0200 Subject: [PATCH 26/28] handle 'grade1' and 'tracktype' as per OMT PR 1485 --- .../openmaptiles/layers/Transportation.java | 4 ++-- .../layers/TransportationTest.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index ea3e90fd..64d1cd6e 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -134,7 +134,7 @@ public class Transportation implements ); private static final Set SURFACE_PAVED_VALUES = Set.of( "paved", "asphalt", "cobblestone", "concrete", "concrete:lanes", "concrete:plates", "metal", - "paving_stones", "sett", "unhewn_cobblestone", "wood" + "paving_stones", "sett", "unhewn_cobblestone", "wood", "grade1" ); private static final Set ACCESS_NO_VALUES = Set.of( "private", "no" @@ -479,7 +479,7 @@ public void process(Tables.OsmHighwayLinestring element, FeatureCollector featur // z12+ .setAttrWithMinzoom(Fields.SERVICE, service, 12) .setAttrWithMinzoom(Fields.ONEWAY, nullIfInt(element.isOneway(), 0), 12) - .setAttrWithMinzoom(Fields.SURFACE, surface(element.surface()), 12) + .setAttrWithMinzoom(Fields.SURFACE, surface(coalesce(element.surface(), element.tracktype())), 12) .setMinPixelSize(0) // merge during post-processing, then limit by size .setSortKey(element.zOrder()) .setMinZoom(minzoom); diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index 108925b8..943c10aa 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -1928,4 +1928,28 @@ void testIssue86() { "service", "driveway" )))); } + + @Test + void testGrade1SurfacePath() { + assertFeatures(14, List.of(Map.of( + "_layer", "transportation", + "class", "track", + "surface", "paved" + )), process(lineFeature(Map.of( + "surface", "grade1", + "highway", "track" + )))); + } + + @Test + void testGrade1TracktypePath() { + assertFeatures(14, List.of(Map.of( + "_layer", "transportation", + "class", "track", + "surface", "paved" + )), process(lineFeature(Map.of( + "tracktype", "grade1", + "highway", "track" + )))); + } } From d3429c46372ea7cb2cc6f6e7200eef2a4e189c2e Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Tue, 26 Sep 2023 15:23:45 +0200 Subject: [PATCH 27/28] added implementation of agg_stop It is based on OMT PR 1480 (which contains latest the fix) and the rest of older code (which was not worling properly until the fix). --- .../java/org/openmaptiles/layers/Poi.java | 141 +++++++++++++++- .../java/org/openmaptiles/layers/PoiTest.java | 158 ++++++++++++++++++ 2 files changed, 294 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/openmaptiles/layers/Poi.java b/src/main/java/org/openmaptiles/layers/Poi.java index 81b4d2d3..6885de18 100644 --- a/src/main/java/org/openmaptiles/layers/Poi.java +++ b/src/main/java/org/openmaptiles/layers/Poi.java @@ -48,16 +48,27 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import com.onthegomap.planetiler.collection.Hppc; import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.expression.MultiExpression; +import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.geo.GeometryException; +import com.onthegomap.planetiler.reader.SimpleFeature; import com.onthegomap.planetiler.stats.Stats; import com.onthegomap.planetiler.util.Parse; import com.onthegomap.planetiler.util.Translations; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; +import org.openmaptiles.OpenMapTilesProfile; import org.openmaptiles.generated.OpenMapTilesSchema; import org.openmaptiles.generated.Tables; import org.openmaptiles.util.OmtLanguageUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Defines the logic for generating map elements for things like shops, parks, and schools in the {@code poi} layer from @@ -70,13 +81,15 @@ public class Poi implements OpenMapTilesSchema.Poi, Tables.OsmPoiPoint.Handler, Tables.OsmPoiPolygon.Handler, - ForwardingProfile.FeaturePostProcessor { + ForwardingProfile.FeaturePostProcessor, + OpenMapTilesProfile.FinishHandler { /* * process() creates the raw POI feature from OSM elements and postProcess() * assigns the feature rank from order in the tile at render-time. */ + private static final Logger LOGGER = LoggerFactory.getLogger(Poi.class); private static final Map CLASS_RANKS = Map.ofEntries( entry(FieldValues.CLASS_HOSPITAL, 20), entry(FieldValues.CLASS_RAILWAY, 40), @@ -102,11 +115,20 @@ public class Poi implements entry(FieldValues.CLASS_BAR, 800) ); private static final Set UNIVERSITY_POI_SUBCLASSES = Set.of("university", "college"); + private static final Map AGG_STOP_SUBCLASS_ORDER = Map.ofEntries( + entry("subway", 0), + entry("tram_stop", 1), + entry("bus_station", 2), + entry("bus_stop", 3) + ); + private static final Comparator BY_SUBCLASS = Comparator + .comparingInt(s -> AGG_STOP_SUBCLASS_ORDER.get(s.subclass())); private static final double LOG2 = Math.log(2); private static final double SQRT10 = Math.sqrt(10); private final MultiExpression.Index classMapping; private final Translations translations; private final Stats stats; + private final Map> aggStops = new HashMap<>(); public Poi(Translations translations, PlanetilerConfig config, Stats stats) { this.classMapping = FieldMappings.Class.index(); @@ -145,19 +167,127 @@ public static int uniAreaToMinZoom(double areaWorld) { return Math.min(14, Math.max(10, result)); } + @Override + public void release() { + aggStops.clear(); + } + @Override public void process(Tables.OsmPoiPoint element, FeatureCollector features) { - // TODO handle uic_ref => agg_stop - setupPoiFeature(element, features.point(LAYER_NAME)); + if (element.uicRef() != null && AGG_STOP_SUBCLASS_ORDER.containsKey(element.subclass())) { + // multiple threads may update this concurrently + synchronized (this) { + aggStops.computeIfAbsent(element.uicRef(), key -> new ArrayList<>()).add(element); + } + } else { + setupPoiFeature(element, features.point(LAYER_NAME), null); + } + } + + private void processAggStop(Tables.OsmPoiPoint element, FeatureCollector.Factory featureCollectors, + Consumer emit, Integer aggStop) { + try { + var features = featureCollectors.get(SimpleFeature.fromWorldGeometry(element.source().worldGeometry())); + setupPoiFeature(element, features.point(LAYER_NAME), aggStop); + for (var feature : features) { + emit.accept(feature); + } + } catch (GeometryException e) { + e.log(stats, "agg_stop_geometry_2", + "Error getting geometry for the stop " + element.source().id() + " (agg_stop)"); + } + } + + /** + * We've put aside some stops for {@code agg_stop} processing and we do that processing here. + *

    + * The main point is to group together stops with same {@code uid_ref} and then order them first based on subclass + * (see {@code AGG_STOP_ORDER}) and then based on distance from centroid (calculated from all the stops). The first + * one gets {@code agg_stop=1}, the rest will be "normal" (e.g. no {@code agg_stop} attribute). + *

    + * ref: poi_stop_agg.sql + */ + @Override + public void finish(String sourceName, FeatureCollector.Factory featureCollectors, + Consumer emit) { + if (OpenMapTilesProfile.OSM_SOURCE.equals(sourceName)) { + var timer = stats.startStage("agg_stop"); + LOGGER.info("Processing {} agg_stop sets", aggStops.size()); + + // possible TODO: run in parallel? + for (var aggStopSet : aggStops.values()) { + if (aggStopSet.size() == 1) { + processAggStop(aggStopSet.get(0), featureCollectors, emit, 1); + continue; + } + try { + // find first based on subclass + var firstSubclass = aggStopSet.stream().sorted(BY_SUBCLASS).toArray(Tables.OsmPoiPoint[]::new)[0].subclass(); + var topAggStops = + aggStopSet.stream().filter(s -> firstSubclass.equals(s.subclass())).toArray(Tables.OsmPoiPoint[]::new); + if (topAggStops.length <= 2) { + // one found => straightforward: flag it and emit + // two found => both will be same distance from centroid: pick first one, flag it and emit + processAggStop(topAggStops[0], featureCollectors, emit, 1); + // and emit the rest + aggStopSet.stream() + .filter(s -> !topAggStops[0].equals(s)) + .forEach(s -> processAggStop(s, featureCollectors, emit, null)); + continue; + } + + // easy cases handled, now we need also centroid + List aggStopPoints = new ArrayList<>(aggStopSet.size()); + for (var aggStop : aggStopSet) { + aggStopPoints.add(aggStop.source().worldGeometry().getCentroid()); + } + var aggStopCentroid = GeoUtils.combinePoints(aggStopPoints).getCentroid(); + + // find nearest + double minDistance = Double.MAX_VALUE; + Tables.OsmPoiPoint nearest = null; + for (var aggStop : topAggStops) { + double distance = aggStopCentroid.distance(aggStop.source().worldGeometry()); + if (distance < minDistance) { + minDistance = distance; + nearest = aggStop; + } + } + + // emit nearest + if (nearest != null) { + processAggStop(nearest, featureCollectors, emit, 1); + } else { + stats.dataError("agg_stop_nearest"); + LOGGER.warn("Failed to find nearest stop for agg_stop UIC ref. {}", aggStopSet.get(0).uicRef()); + } + + // emit the rest + final var alreadyDone = nearest; + aggStopSet.stream() + .filter(s -> !s.equals(alreadyDone)) + .forEach(s -> processAggStop(s, featureCollectors, emit, null)); + } catch (GeometryException e) { + e.log(stats, "agg_stop_geometry_1", + "Error getting geometry for some of the stops with UIC ref. " + aggStopSet.get(0).uicRef() + " (agg_stop)"); + // we're not able to calculate agg_stop, so simply dump them as-is + aggStopSet + .forEach(s -> processAggStop(s, featureCollectors, emit, null)); + } + } + + timer.stop(); + } } @Override public void process(Tables.OsmPoiPolygon element, FeatureCollector features) { - setupPoiFeature(element, features.centroidIfConvex(LAYER_NAME)); + setupPoiFeature(element, features.centroidIfConvex(LAYER_NAME), null); } private void setupPoiFeature( - T element, FeatureCollector.Feature output) { + T element, FeatureCollector.Feature output, Integer aggStop) { String rawSubclass = element.subclass(); if ("station".equals(rawSubclass) && "subway".equals(element.station())) { rawSubclass = "subway"; @@ -216,6 +346,7 @@ private testAggStops(List sourceFeatures) { + sourceFeatures.forEach(this::process); + + List features = new ArrayList<>(); + profile.finish(OpenMapTilesProfile.OSM_SOURCE, featureCollectorFactory, features::add); + + return features; + } + + @Test + void testAggStopJustOne() { + var result = testAggStops(List.of(pointFeature(Map.of( + "highway", "bus_stop", + "name", "station", + "uic_ref", "1" + )))); + assertFeatures(14, List.of(Map.of( + "_layer", "poi", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", 1, + "_minzoom", 14 + )), result); + } + + @Test + void testAggStopTwoWithSameSubclass() { + var result = testAggStops(List.of( + pointFeature(Map.of( + "railway", "tram_stop", + "name", "station 1", + "uic_ref", "1" + )), + pointFeature(Map.of( + "railway", "tram_stop", + "name", "station 2", + "uic_ref", "1" + )) + )); + assertFeatures(14, List.of( + Map.of( + "_layer", "poi", + "name", "station 1", + "class", "railway", + "subclass", "tram_stop", + "agg_stop", 1, + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name", "station 2", + "class", "railway", + "subclass", "tram_stop", + "agg_stop", "", + "_minzoom", 14 + ) + ), result); + } + + @Test + void testAggStopThreeWithMixedSubclass() { + var result = testAggStops(List.of( + pointFeature(Map.of( + "highway", "bus_stop", + "name", "station 1", + "uic_ref", "1" + )), + pointFeature(Map.of( + "highway", "bus_stop", + "name", "station 2", + "uic_ref", "1" + )), + pointFeature(Map.of( + "railway", "tram_stop", + "name", "station 3", + "uic_ref", "1" + )) + )); + assertFeatures(14, List.of( + Map.of( + "_layer", "poi", + "name", "station 3", + "class", "railway", + "subclass", "tram_stop", + "agg_stop", 1, + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name", "station 1", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name", "station 2", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ) + ), result); + } + + @Test + void testAggStopThreeWithSameSubclass() { + var result = testAggStops(List.of( + SimpleFeature.create(newPoint(0, 0), Map.of( + "highway", "bus_stop", + "name", "station 1", + "uic_ref", "1" + ), OpenMapTilesProfile.OSM_SOURCE, null, 0), + SimpleFeature.create(newPoint(1, 0), Map.of( + "highway", "bus_stop", + "name", "station 2", + "uic_ref", "1" + ), OpenMapTilesProfile.OSM_SOURCE, null, 1), + SimpleFeature.create(newPoint(2, 0), Map.of( + "highway", "bus_stop", + "name", "station 3", + "uic_ref", "1" + ), OpenMapTilesProfile.OSM_SOURCE, null, 2) + )); + assertFeatures(14, List.of( + Map.of( + "_layer", "poi", + "name", "station 2", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", 1, + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name", "station 1", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name", "station 3", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ) + ), result); + } + @ParameterizedTest @ValueSource(booleans = {false, true}) void testPlaceOfWorshipFromReligionTag(boolean area) { From b597ecf678f5c4a4cab9e1d59ad136ec262396e6 Mon Sep 17 00:00:00 2001 From: Peter Hanecak Date: Fri, 6 Oct 2023 10:44:43 +0200 Subject: [PATCH 28/28] regenerate-openmaptiles.sh master, to match several OMT PRs which adjusted only YML --- .../generated/OpenMapTilesSchema.java | 83 ++++++++++--------- .../org/openmaptiles/generated/Tables.java | 22 +++-- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java index bff7ff4b..55ec4e54 100644 --- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java +++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java @@ -48,9 +48,9 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE import org.openmaptiles.Layer; /** - * All vector tile layer definitions, attributes, and allowed values generated from the OpenMapTiles - * vector tile schema 5e9b7c475d53a5bd5ea394da361594d3f4ce2d66. + * All vector tile layer definitions, attributes, and allowed values generated from the + * OpenMapTiles vector tile schema + * master. */ @SuppressWarnings("unused") public class OpenMapTilesSchema { @@ -95,8 +95,8 @@ public static List createInstances(Translations translations, PlanetilerC * polygons to improve rendering performance. This however can lead to less rendering options in clients since these * boundaries show up. So you might not be able to use border styling for ocean water features. * - * Generated from water.yaml + * Generated from + * water.yaml */ public interface Water extends Layer { double BUFFER_SIZE = 4.0; @@ -194,8 +194,8 @@ final class FieldMappings { * there is also canal generated, starting z13 there is no generalization according to class * field applied. Waterways do not have a subclass field. * - * Generated from waterway.yaml + * Generated from + * waterway.yaml */ public interface Waterway extends Layer { double BUFFER_SIZE = 4.0; @@ -280,7 +280,7 @@ final class FieldMappings { * layer is to style wood (class=wood) and grass (class=grass) areas. * * Generated from landcover.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/landcover/landcover.yaml">landcover.yaml */ public interface Landcover extends Layer { double BUFFER_SIZE = 4.0; @@ -436,8 +436,8 @@ final class FieldMappings { * Landuse is used to describe use of land by humans. At lower zoom levels this is from Natural Earth data for * residential (urban) areas and at higher zoom levels mostly OSM landuse tags. * - * Generated from landuse.yaml + * Generated from + * landuse.yaml */ public interface Landuse extends Layer { double BUFFER_SIZE = 4.0; @@ -533,7 +533,7 @@ final class FieldMappings { * Natural peaks * * Generated from mountain_peak.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/mountain_peak/mountain_peak.yaml">mountain_peak.yaml */ public interface MountainPeak extends Layer { double BUFFER_SIZE = 64.0; @@ -601,14 +601,18 @@ final class FieldMappings { } } /** - * The park layer contains parks from OpenStreetMap tagged with - * boundary=national_park, + * The park layer in OpenMapTiles contains natural and protected areas from OpenStreetMap, such as parks tagged with + * boundary=national_park, * boundary=protected_area, or - * leisure=nature_reserve. + * "https://wiki.openstreetmap.org/wiki/Tag:boundary%3Dprotected_area">boundary=protected_area, or + * leisure=nature_reserve. + * This layer also includes boundaries for indigenous lands tagged with boundary=aboriginal_lands. + * Indigenous boundaries are not parks, but they are included in this layer for technical reasons related to data + * processing. These boundaries represent areas with special legal and administrative status for indigenous peoples. * - * Generated from park.yaml + * Generated from + * park.yaml */ public interface Park extends Layer { double BUFFER_SIZE = 4.0; @@ -622,15 +626,16 @@ default String name() { /** Attribute names for map elements in the park layer. */ final class Fields { /** - * Use the class to differentiate between different parks. The class for - * boundary=protected_area parks is the lower-case of the + * Use the class to differentiate between different kinds of features in the parks + * layer, for example between parks and non-parks. The class for boundary=protected_area parks is the + * lower-case of the * protection_title value with * blanks replaced by _. national_park is the class of * protection_title=National Park and boundary=national_park. * nature_reserve is the class of protection_title=Nature Reserve and * leisure=nature_reserve. The class for other * protection_title values is - * similarly assigned. + * similarly assigned. The class for boundary=aboriginal_lands is aboriginal_lands. */ public static final String CLASS = "class"; /** @@ -667,8 +672,8 @@ final class FieldMappings { * admin_level * but for most styles it makes sense to just style admin_level=2 and admin_level=4. * - * Generated from boundary.yaml + * Generated from + * boundary.yaml */ public interface Boundary extends Layer { double BUFFER_SIZE = 4.0; @@ -768,8 +773,8 @@ final class FieldMappings { * buildings are contained in the building layer but all other airport related polygons can be found * in the aeroway layer. * - * Generated from aeroway.yaml + * Generated from + * aeroway.yaml */ public interface Aeroway extends Layer { double BUFFER_SIZE = 4.0; @@ -829,7 +834,7 @@ final class FieldMappings { * features like plazas. * * Generated from transportation.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/transportation/transportation.yaml">transportation.yaml */ public interface Transportation extends Layer { double BUFFER_SIZE = 4.0; @@ -1173,8 +1178,8 @@ final class FieldMappings { * (building= ). Only buildings with tag * location:underground are excluded. * - * Generated from building.yaml + * Generated from + * building.yaml */ public interface Building extends Layer { double BUFFER_SIZE = 4.0; @@ -1216,7 +1221,7 @@ final class FieldMappings { * from OSM water bodies. Only the most important lakes contain labels. * * Generated from water_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/water_name/water_name.yaml">water_name.yaml */ public interface WaterName extends Layer { double BUFFER_SIZE = 256.0; @@ -1286,7 +1291,7 @@ final class FieldMappings { * while for other roads you should use name. * * Generated from transportation_name.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/transportation_name/transportation_name.yaml">transportation_name.yaml */ public interface TransportationName extends Layer { double BUFFER_SIZE = 8.0; @@ -1515,8 +1520,8 @@ final class FieldMappings { * of the more important layers to create a beautiful map. We suggest you use different font styles and sizes to * create a text hierarchy. * - * Generated from place.yaml + * Generated from + * place.yaml */ public interface Place extends Layer { double BUFFER_SIZE = 256.0; @@ -1627,7 +1632,7 @@ final class FieldMappings { * tag are prioritized for preservation). * * Generated from housenumber.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/housenumber/housenumber.yaml">housenumber.yaml */ public interface Housenumber extends Layer { double BUFFER_SIZE = 8.0; @@ -1640,7 +1645,10 @@ default String name() { /** Attribute names for map elements in the housenumber layer. */ final class Fields { - /** Value of the addr:housenumber tag. */ + /** + * Value of the addr:housenumber tag. If + * there are multiple values separated by semi-colons, the first and last value separated by a dash. + */ public static final String HOUSENUMBER = "housenumber"; } /** Attribute values for map elements in the housenumber layer. */ @@ -1656,8 +1664,7 @@ final class FieldMappings { * Points of interests containing a of a variety * of OpenStreetMap tags. Mostly contains amenities, sport, shop and tourist POIs. * - * Generated from poi.yaml + * Generated from poi.yaml */ public interface Poi extends Layer { double BUFFER_SIZE = 64.0; @@ -1828,8 +1835,8 @@ final class FieldMappings { "erotic", "electronics", "fabric", "florist", "frozen_food", "furniture", "video_games", "video", "general", "gift", "hardware", "hearing_aids", "hifi", "ice_cream", "interior_decoration", "jewelry", "kiosk", "locksmith", "lamps", "mall", "massage", "motorcycle", "mobile_phone", "newsagent", "optician", "outdoor", - "perfumery", "perfume", "pet", "photo", "second_hand", "shoes", "sports", "stationery", "tailor", "tattoo", - "ticket", "tobacco", "toys", "travel_agency", "watches", "weapons", "wholesale")), + "paint", "perfumery", "perfume", "pet", "photo", "second_hand", "shoes", "sports", "stationery", "tailor", + "tattoo", "ticket", "tobacco", "toys", "travel_agency", "watches", "weapons", "wholesale")), MultiExpression.entry("town_hall", matchAny("subclass", "townhall", "public_building", "courthouse", "community_centre")), MultiExpression.entry("golf", matchAny("subclass", "golf", "golf_course", "miniature_golf")), @@ -1876,7 +1883,7 @@ final class FieldMappings { * Aerodrome labels * * Generated from aerodrome_label.yaml + * "https://github.com/openmaptiles/openmaptiles/blob/master/layers/aerodrome_label/aerodrome_label.yaml">aerodrome_label.yaml */ public interface AerodromeLabel extends Layer { double BUFFER_SIZE = 64.0; diff --git a/src/main/java/org/openmaptiles/generated/Tables.java b/src/main/java/org/openmaptiles/generated/Tables.java index 00e4bc50..5effebba 100644 --- a/src/main/java/org/openmaptiles/generated/Tables.java +++ b/src/main/java/org/openmaptiles/generated/Tables.java @@ -50,9 +50,8 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /** * OSM element parsers generated from the imposm3 table definitions - * in the OpenMapTiles - * vector tile schema. + * in the OpenMapTiles vector tile + * schema. * * These filter and parse the raw OSM key/value attribute pairs on tags into records with fields that match the columns * in the tables that imposm3 would generate. Layer implementations can "subscribe" to elements from each "table" but @@ -249,9 +248,8 @@ public OsmParkPolygon(SourceFeature source, String mappingKey) { } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ - public static final Expression MAPPING = - and(or(matchAny("leisure", "nature_reserve"), matchAny("boundary", "national_park", "protected_area")), - matchType("polygon")); + public static final Expression MAPPING = and(or(matchAny("leisure", "nature_reserve"), + matchAny("boundary", "national_park", "protected_area", "aboriginal_lands")), matchType("polygon")); /** * Interface for layer implementations to extend to subscribe to OSM elements filtered and parsed as @@ -740,9 +738,9 @@ public OsmPoiPoint(SourceFeature source, String mappingKey) { "erotic", "fabric", "florist", "frozen_food", "furniture", "garden_centre", "general", "gift", "greengrocer", "hairdresser", "hardware", "hearing_aids", "hifi", "ice_cream", "interior_decoration", "jewelry", "kiosk", "lamps", "laundry", "locksmith", "mall", "massage", "mobile_phone", "motorcycle", "music", "musical_instrument", - "newsagent", "optician", "outdoor", "perfume", "perfumery", "pet", "photo", "second_hand", "shoes", "sports", - "stationery", "supermarket", "tailor", "tattoo", "ticket", "tobacco", "toys", "travel_agency", "video", - "video_games", "watches", "weapons", "wholesale", "wine"), + "newsagent", "optician", "outdoor", "paint", "perfume", "perfumery", "pet", "photo", "second_hand", "shoes", + "sports", "stationery", "supermarket", "tailor", "tattoo", "ticket", "tobacco", "toys", "travel_agency", + "video", "video_games", "watches", "weapons", "wholesale", "wine"), matchAny("sport", "american_football", "archery", "athletics", "australian_football", "badminton", "baseball", "basketball", "beachvolleyball", "billiards", "bmx", "boules", "bowls", "boxing", "canadian_football", "canoe", "chess", "climbing", "climbing_adventure", "cricket", "cricket_nets", "croquet", "curling", "cycling", @@ -810,9 +808,9 @@ public OsmPoiPolygon(SourceFeature source, String mappingKey) { "erotic", "fabric", "florist", "frozen_food", "furniture", "garden_centre", "general", "gift", "greengrocer", "hairdresser", "hardware", "hearing_aids", "hifi", "ice_cream", "interior_decoration", "jewelry", "kiosk", "lamps", "laundry", "locksmith", "mall", "massage", "mobile_phone", "motorcycle", "music", "musical_instrument", - "newsagent", "optician", "outdoor", "perfume", "perfumery", "pet", "photo", "second_hand", "shoes", "sports", - "stationery", "supermarket", "tailor", "tattoo", "ticket", "tobacco", "toys", "travel_agency", "video", - "video_games", "watches", "weapons", "wholesale", "wine"), + "newsagent", "optician", "outdoor", "paint", "perfume", "perfumery", "pet", "photo", "second_hand", "shoes", + "sports", "stationery", "supermarket", "tailor", "tattoo", "ticket", "tobacco", "toys", "travel_agency", + "video", "video_games", "watches", "weapons", "wholesale", "wine"), matchAny("sport", "american_football", "archery", "athletics", "australian_football", "badminton", "baseball", "basketball", "beachvolleyball", "billiards", "bmx", "boules", "bowls", "boxing", "canadian_football", "canoe", "chess", "climbing", "climbing_adventure", "cricket", "cricket_nets", "croquet", "curling", "cycling",