From add205e26c27c29b5648423ac80b5e705f3a235b Mon Sep 17 00:00:00 2001 From: Peter Hanecak <115141505+phanecak-maptiler@users.noreply.github.com> Date: Fri, 22 Dec 2023 10:23:37 +0100 Subject: [PATCH] OpenMapTiles 3.15.0 SNAPSHOT (#126) * version bumped from 3.14.0 to 3.15.0-SNAPSHOT * regenerate-openmaptiles.sh 07f243c5d9efa558fa539d7a31b2ae50507aaa9d (to match content of OMT PR 1457) * SQL -> Java re-implementation of OMT PR 1457 * version bumped from 3.14.0 to 3.15.0-SNAPSHOT * WaterName.areaToMinZoom(): improved handling of rounding and precission + added unit tests * mvn spotless:apply * water label min. zoom calculation simplified * comment adjusted to be hopefully more useful * mvn spotless:apply * minzoom for CA_TRANSCANADA and US_INTERSTATE trunk now 4 (to match OMT PR 1440) * minzoom for some other Canada trunks now 4 (to match OMT PR 1446) * equals() simplified + clean-up of comments * regenerate-openmaptiles.sh 5f7b2c11b3224759a21133381ca7d959a1f3cf51 (to match content of OMT PR 1465) * GB road relations processing adjusted to match OMT PR 1465, e.g. handle also primary and secondary roads * regenerate-openmaptiles.sh edb42f2db3c2b0ec37045367720eed84d7bbd71f (to match content of OMT PR 1466) * IE road relations processing adjusted to match OMT PR 1466, e.g. handle IE roates in similar way as GB routes * fixed handling of networkType for secondary GB routes * clean-up: case statements simplified * mvn spotless:apply * clazz calculation moved up so that minzoom can be set to 3 for only lakes (to match OMT PR 1475) * unit tests adjusted + extended to cover 'minzoom=3 fore lakes' change * fixed minor typo from previous PR * render POIs for large universities at low zoom (to match OMT PR 1479) * clean-up, to make the diff/PR smaller * regenerate-openmaptiles.sh 5e9b7c475d53a5bd5ea394da361594d3f4ce2d66 (to match content of OMT PR 1485) * handle 'grade1' and 'tracktype' as per OMT PR 1485 * 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). * clean-up: mvn spotless:apply * Long ferries (as per OMT PR 1486) * regenerate-openmaptiles.sh b3d67ed5b327c9059aeea0b3304772c6b4c8c7e9 (to match content of OMT PR 1489) * Add aboriginal lands (as per OMT PR 1489) * handle duplicate route relations (to match OMT PR 1501) * regenerate-openmaptiles.sh master, to match several OMT PRs which adjusted only YML * URLs in comments adjusted to match OMT PR 1560 * Convert separated addresses to dashed addresses * add brunnel (and layer) attributes only for certain zoomlevels, depending on feature size (matching OMT PR 1579) * unit test testInterstateMotorway(): brunnel tag for test line no longer available at Z8 * unit test testInterstateMotorway() clean-up: Z13 was tested twice * minor clean-up: fixed unit test naming * partial fix for differences in transportation_name layer The difference is between OpenMapTiles/master (OMT) and planetiler-openmaptiles/omt_3_15_0 (PT-OMT) (e.g. development versions). The point is, that while PT-OMT was using limit of "8km" for Z9-Z11, OMT is using limit "ST_Length(geometry) > 8000 / POWER(2, zoom_level - 9) AND zoom_level BETWEEN 9 AND 11". Some further differences still visible, hence further commits expected. * further adjustments to better match what is done with ferries in OMT ... (as per OMT PR 1486) But FERRY_MIN_PIXEL_SIZE is "too much" in the contexct of Planetiler, since it is applied within tiles, hence causes gaps in lines if a line "strikes a little" certain tile. Hence we will need to divert a little. * ferry minLength tweak + clean-up * mvn spotless:apply * fixed minor typo * minor reformatting * ferry line length filter replaced with min. zoom calculation hence the results are much closer to what OMT is doing for Z4-Z9 * testFerry() adjusted to match previous commit ferry test polygon with area 1 now qualifies for min. zoom 5 * clea-up of unused stuff + mvn spotless:apply * mvn spotless:apply * added TODO node for follow-up pull-request/simplification * clean-up: common getMinZoom() code moved to Utils * minzoom clipping for brunnel was adjusted do Z9-Z12 -> test adjusted too * clean-up * use same tolerance for all transportation items, like OSM does * clean-up, since ferry and non-ferry procesing is now same * we need regenerate to work with master branch for now * first sub-class search for agg_stop simplified a little * contains() used instead of indexOf() for better readability * numbers as list, not array, so that getFirst() and getLast() can be used * better trimming and filtring of housenumbers * adjusted handling of large house numbers * several unit tests collapsed to one with @ParameterizedTest + @CsvSource * AGG_STOP_SUBCLASS_ORDER simplified from Map to List * fixed major omission from previous commit * clamp() used to replace min()&max() combo * agg_stop now implemented * fixed typo in the error message * prepare IE and GB boundary geometry outside of synchronized{} * fixed typo in the error message * mvn spotless:apply * switch statements for IE and GB route networks simplified * avoid RouteNetwork->String mapping, not needed for anyMatch() * fix: attr. brunnel optional based on size on Z4-Z11, attr. layer optional between Z9-Z11 * tolerance change in transportation reverted, added note to README as per why * fix: monzoom for sea&co. is Z0-Z14 based on area, for the rest it is Z3-Z14 again based on area * clean-up: avoid doing area->side->area, do just area * regenerate-openmaptiles.sh 6c31841f4674f15e15afde346a060cf7c22e6cdd (to match content of OMT PR 1591) * relevant process() functions adjusted to match changes in transportation/mapping.yaml * regenerate-openmaptiles.sh master, instead of 6c31841f4674f15e15afde346a060cf7c22e6cdd (to match content of OMT PR 1591, in a cleaner way) * introduce duplicate housenumber filtering (matching OMT PR 1391) * (less related) clean-up: use isEmpty() instead if size check * testContainsHousenumber UT adjusted, since duplicate housenumber filtering is reducing amount of house numbers * use combination of uic_ref, name, network and operator as key for agg_stop sets If we rely on only on `uic_ref` we group together also stations which are too far apart (even different cities). With this combo results seem OK, e.g. all grouped stations are within around 950m (1000 pixels at Z14) of each other (1000 being used in `PARTITION BY LabelGrid(...` in `layers/poi/poi.sql` in OpenMapTiles). * agg_stop comparison made more explicit, since we want to match same exact one * mvn spotless:apply * name now important for agg_stop processing, hence name:es (ab)used for unit tests * agg_stop: simplified processing of nearest station Results still same, only ordering is different: - previously: agg_stop=1 first - now: FIFO * agg_stop: forther code simplification * fixed major typo introduced in previous merge * setMinPixelSize() + setMinZoom() used instead of areaToMinZoom() * clean-up: unused stuff removed * mvn spotless:apply * setAttrWithMinSize() used instead of getBrunnelMinzoom() getFerryMinzoom() kept since we'd like to replicate `sql_filter: ST_Length(...` from OMT * getMinZoomForLength() no longer used, hence removed * clean-up: LOG2 not used, hence removed * added BY_TEMP_HAS_NAME comparator to avoid its repeated construction during run-time * duplicate houcenumber processing simplified further * clean-up: get(0) replaced with getFirst() * clean-up: CPU-intensive prepare() moved out of synchronized block * regenerate-openmaptiles.sh 3cf77e2a542d8a369bb08bf2538cdde0b3effb2b (to match content of OMT PR 1423) * unit test adjusted for POI office class changes * regenerate-openmaptiles.sh master (to match content of OMT PR 1544) * added charging_station implementation matching OMT PR 1544 * use setMinPixelSizeBelowZoom() instead of uniAreaToMinZoom() * use setMinPixelSizeBelowZoom() instead of getFerryMinzoom() * fixed unit test, to match recent tweaks --- README.md | 2 +- pom.xml | 2 +- scripts/regenerate-openmaptiles.sh | 3 +- .../org/openmaptiles/OpenMapTilesProfile.java | 4 +- .../generated/OpenMapTilesSchema.java | 154 ++-- .../org/openmaptiles/generated/Tables.java | 183 +++-- .../org/openmaptiles/layers/Housenumber.java | 92 ++- .../java/org/openmaptiles/layers/Park.java | 23 +- .../java/org/openmaptiles/layers/Place.java | 4 +- .../java/org/openmaptiles/layers/Poi.java | 142 +++- .../openmaptiles/layers/Transportation.java | 158 +++- .../layers/TransportationName.java | 8 +- .../org/openmaptiles/layers/WaterName.java | 81 +- .../java/org/openmaptiles/util/Utils.java | 1 - .../org/openmaptiles/OpenMapTilesTest.java | 2 +- .../layers/AbstractLayerTest.java | 10 + .../openmaptiles/layers/HousenumberTest.java | 129 ++++ .../org/openmaptiles/layers/ParkTest.java | 24 + .../java/org/openmaptiles/layers/PoiTest.java | 193 ++++- .../layers/TransportationTest.java | 710 +++++++++++++++++- .../openmaptiles/layers/WaterNameTest.java | 53 +- 21 files changed, 1719 insertions(+), 259 deletions(-) diff --git a/README.md b/README.md index 90ae11f..f6f4977 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,11 @@ available options. ## Differences from OpenMapTiles - Road name abbreviations are not implemented yet in the `transportation_name` layer -- `agg_stop` tag not implemented yet in the `poi` layer - `brunnel` tag is excluded from `transportation_name` layer to avoid breaking apart long `transportation_name` lines, to revert this behavior set `--transportation-name-brunnel=true` - `rank` field on `mountain_peak` linestrings only has 3 levels (1: has wikipedia page and name, 2: has name, 3: no name or wikipedia page or name) +- some line and polygon tolerances are different, can be tweaked with `--simplify-tolerance` parameter ## Customizing diff --git a/pom.xml b/pom.xml index 241eea8..fb9ab5e 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 diff --git a/scripts/regenerate-openmaptiles.sh b/scripts/regenerate-openmaptiles.sh index 54fb8d7..2597334 100755 --- a/scripts/regenerate-openmaptiles.sh +++ b/scripts/regenerate-openmaptiles.sh @@ -4,7 +4,8 @@ set -o errexit set -o pipefail set -o nounset -TAG="${1:-"v3.14"}" +# TODO: change to "v3.15" once that is released +TAG="${1:-"master"}" echo "tag=${TAG}" BASE_URL="${2:-"https://raw.githubusercontent.com/openmaptiles/openmaptiles/"}" diff --git a/src/main/java/org/openmaptiles/OpenMapTilesProfile.java b/src/main/java/org/openmaptiles/OpenMapTilesProfile.java index e6d20e7..d5d992f 100644 --- a/src/main/java/org/openmaptiles/OpenMapTilesProfile.java +++ b/src/main/java/org/openmaptiles/OpenMapTilesProfile.java @@ -220,12 +220,12 @@ public class OpenMapTilesProfile extends ForwardingProfile { /** * Layers should implement this interface to subscribe to elements from - * OSM lake centerlines source. + * OSM lake centerlines source. */ public interface LakeCenterlineProcessor { /** - * Process an element from the OSM lake centerlines + * Process an element from the OSM lake centerlines * source * * @see Profile#processFeature(SourceFeature, FeatureCollector) diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java index 8ae534a..03014fc 100644 --- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java +++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java @@ -49,8 +49,8 @@ import org.openmaptiles.Layer; /** * All vector tile layer definitions, attributes, and allowed values generated from the - * OpenMapTiles vector tile schema - * v3.14. + * OpenMapTiles vector tile schema + * master. */ @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 class OpenMapTilesSchema { * boundaries show up. So you might not be able to use border styling for ocean water features. * * Generated from - * water.yaml + * water.yaml */ public interface Water extends Layer { double BUFFER_SIZE = 4.0; @@ -117,11 +117,15 @@ public class OpenMapTilesSchema { /** * 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. 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 +133,7 @@ public class OpenMapTilesSchema { *

    *
  • dock *
  • river + *
  • pond *
  • lake *
  • ocean *
  • swimming_pool @@ -163,10 +168,11 @@ public class OpenMapTilesSchema { 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 +181,9 @@ public class OpenMapTilesSchema { 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", "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")))); } } @@ -188,7 +195,7 @@ public class OpenMapTilesSchema { * field applied. Waterways do not have a subclass field. * * Generated from - * waterway.yaml + * waterway.yaml */ public interface Waterway extends Layer { double BUFFER_SIZE = 4.0; @@ -273,7 +280,7 @@ public class OpenMapTilesSchema { * 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; @@ -430,7 +437,7 @@ public class OpenMapTilesSchema { * residential (urban) areas and at higher zoom levels mostly OSM landuse tags. * * Generated from - * landuse.yaml + * landuse.yaml */ public interface Landuse extends Layer { double BUFFER_SIZE = 4.0; @@ -526,7 +533,7 @@ public class OpenMapTilesSchema { * 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; @@ -594,14 +601,18 @@ public class OpenMapTilesSchema { } } /** - * 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 + * park.yaml */ public interface Park extends Layer { double BUFFER_SIZE = 4.0; @@ -615,15 +626,16 @@ public class OpenMapTilesSchema { /** 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"; /** @@ -661,7 +673,7 @@ public class OpenMapTilesSchema { * but for most styles it makes sense to just style admin_level=2 and admin_level=4. * * Generated from - * boundary.yaml + * boundary.yaml */ public interface Boundary extends Layer { double BUFFER_SIZE = 4.0; @@ -762,7 +774,7 @@ public class OpenMapTilesSchema { * in the aeroway layer. * * Generated from - * aeroway.yaml + * aeroway.yaml */ public interface Aeroway extends Layer { double BUFFER_SIZE = 4.0; @@ -822,7 +834,7 @@ public class OpenMapTilesSchema { * 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; @@ -907,8 +919,9 @@ public class OpenMapTilesSchema { * 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"; @@ -1166,7 +1179,7 @@ public class OpenMapTilesSchema { * location:underground are excluded. * * Generated from - * building.yaml + * building.yaml */ public interface Building extends Layer { double BUFFER_SIZE = 4.0; @@ -1208,7 +1221,7 @@ public class OpenMapTilesSchema { * 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; @@ -1231,11 +1244,14 @@ public class OpenMapTilesSchema { 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 +1273,11 @@ public class OpenMapTilesSchema { /** 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 +1291,7 @@ public class OpenMapTilesSchema { * 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; @@ -1316,8 +1334,14 @@ public class OpenMapTilesSchema { *
  • "us-highway" *
  • "us-state" *
  • "ca-transcanada" + *
  • "ca-provincial-arterial" + *
  • "ca-provincial" *
  • "gb-motorway" *
  • "gb-trunk" + *
  • "gb-primary" + *
  • "ie-motorway" + *
  • "ie-national" + *
  • "ie-regional" *
  • "road (default)" *
*/ @@ -1427,11 +1451,18 @@ public class OpenMapTilesSchema { 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_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", "gb-motorway", "gb-trunk", "road"); + 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"; @@ -1490,7 +1521,7 @@ public class OpenMapTilesSchema { * create a text hierarchy. * * Generated from - * place.yaml + * place.yaml */ public interface Place extends Layer { double BUFFER_SIZE = 256.0; @@ -1542,6 +1573,7 @@ public class OpenMapTilesSchema { *
  • "town" *
  • "village" *
  • "hamlet" + *
  • "borough" *
  • "suburb" *
  • "quarter" *
  • "neighbourhood" @@ -1579,13 +1611,14 @@ public class OpenMapTilesSchema { 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 { @@ -1595,10 +1628,11 @@ public class OpenMapTilesSchema { /** * 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/master/layers/housenumber/housenumber.yaml">housenumber.yaml */ public interface Housenumber extends Layer { double BUFFER_SIZE = 8.0; @@ -1611,7 +1645,10 @@ public class OpenMapTilesSchema { /** 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. */ @@ -1627,7 +1664,7 @@ public class OpenMapTilesSchema { * 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; @@ -1656,6 +1693,7 @@ public class OpenMapTilesSchema { * allowed values: *
      *
    • shop + *
    • office *
    • town_hall *
    • golf *
    • fast_food @@ -1689,6 +1727,7 @@ public class OpenMapTilesSchema { *
    • swimming *
    • castle *
    • atm + *
    • fuel *
    */ public static final String CLASS = "class"; @@ -1751,6 +1790,7 @@ public class OpenMapTilesSchema { /** Attribute values for map elements in the poi layer. */ final class FieldValues { public static final String CLASS_SHOP = "shop"; + public static final String CLASS_OFFICE = "office"; public static final String CLASS_TOWN_HALL = "town_hall"; public static final String CLASS_GOLF = "golf"; public static final String CLASS_FAST_FOOD = "fast_food"; @@ -1784,10 +1824,11 @@ public class OpenMapTilesSchema { public static final String CLASS_SWIMMING = "swimming"; public static final String CLASS_CASTLE = "castle"; public static final String CLASS_ATM = "atm"; - public static final Set CLASS_VALUES = Set.of("shop", "town_hall", "golf", "fast_food", "park", "bus", - "railway", "aerialway", "entrance", "campsite", "laundry", "grocery", "library", "college", "lodging", + public static final String CLASS_FUEL = "fuel"; + public static final Set CLASS_VALUES = Set.of("shop", "office", "town_hall", "golf", "fast_food", "park", + "bus", "railway", "aerialway", "entrance", "campsite", "laundry", "grocery", "library", "college", "lodging", "ice_cream", "post", "cafe", "school", "alcohol_shop", "bar", "harbor", "car", "hospital", "cemetery", - "attraction", "beer", "music", "stadium", "art_gallery", "clothing_store", "swimming", "castle", "atm"); + "attraction", "beer", "music", "stadium", "art_gallery", "clothing_store", "swimming", "castle", "atm", "fuel"); } /** Complex mappings to generate attribute values from OSM element tags in the poi layer. */ final class FieldMappings { @@ -1798,8 +1839,18 @@ public class OpenMapTilesSchema { "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("office", + matchAny("subclass", "accountant", "advertising_agency", "architect", "association", "bail_bond_agent", + "charity", "company", "construction_company", "consulting", "cooperative", "courier", "coworking", + "diplomatic", "educational_institution", "employment_agency", "energy_supplier", "engineer", "estate_agent", + "financial", "financial_advisor", "forestry", "foundation", "geodesist", "government", "graphic_design", + "guide", "harbour_master", "health_insurance", "insurance", "interior_design", "it", "lawyer", "logistics", + "marketing", "moving_company", "newspaper", "ngo", "notary", "physician", "political_party", + "private_investigator", "property_management", "publisher", "quango", "religion", "research", "security", + "surveyor", "tax_advisor", "taxi", "telecommunication", "therapist", "translator", "travel_agent", + "tutoring", "union", "university", "water_utility", "web_design", "wedding_planner")), MultiExpression.entry("town_hall", matchAny("subclass", "townhall", "public_building", "courthouse", "community_centre")), MultiExpression.entry("golf", matchAny("subclass", "golf", "golf_course", "miniature_golf")), @@ -1839,14 +1890,15 @@ public class OpenMapTilesSchema { MultiExpression.entry("clothing_store", matchAny("subclass", "bag", "clothes")), MultiExpression.entry("swimming", matchAny("subclass", "swimming_area", "swimming")), MultiExpression.entry("castle", matchAny("subclass", "castle", "ruins")), - MultiExpression.entry("atm", matchAny("subclass", "atm")))); + MultiExpression.entry("atm", matchAny("subclass", "atm")), + MultiExpression.entry("fuel", matchAny("subclass", "fuel", "charging_station")))); } } /** * 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 a29e15e..1d2458e 100644 --- a/src/main/java/org/openmaptiles/generated/Tables.java +++ b/src/main/java/org/openmaptiles/generated/Tables.java @@ -50,7 +50,7 @@ import java.util.Map; /** * OSM element parsers generated from the imposm3 table definitions - * in the OpenMapTiles vector tile + * 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 @@ -94,21 +94,23 @@ public class Tables { ) {} /** 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", "stream", "canal", "ditch", "drain", "pond", "basin", "wastewater")), not(matchAny("covered", "yes")), matchType("polygon")); /** @@ -246,9 +248,8 @@ public class Tables { } /** 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 @@ -316,25 +317,27 @@ public class Tables { } } /** 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); @@ -361,18 +364,16 @@ public class Tables { public record OsmRailwayLinestring(@Override String railway, @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 usage, @Override SourceFeature source) - implements Row, WithRailway, WithRef, WithNetwork, WithZOrder, WithLayer, WithLevel, WithIndoor, WithName, - WithNameEn, WithNameDe, WithShortName, WithIsTunnel, WithIsBridge, WithIsRamp, WithIsFord, WithIsOneway, WithIsArea, - WithService, WithUsage, WithSource { + @Override boolean isBridge, @Override boolean isRamp, @Override boolean isFord, @Override boolean isArea, + @Override String service, @Override String usage, @Override SourceFeature source) implements Row, WithRailway, + WithRef, WithNetwork, WithZOrder, WithLayer, WithLevel, WithIndoor, WithName, WithNameEn, WithNameDe, WithShortName, + WithIsTunnel, WithIsBridge, WithIsRamp, WithIsFord, WithIsArea, WithService, WithUsage, WithSource { public OsmRailwayLinestring(SourceFeature source, String mappingKey) { this(source.getString("railway"), 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("usage"), source); + source.getBoolean("area"), source.getString("service"), source.getString("usage"), source); } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ @@ -420,16 +421,14 @@ public class Tables { public record OsmShipwayLinestring(@Override String shipway, @Override int zOrder, @Override long layer, @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 usage, - @Override SourceFeature source) + @Override boolean isArea, @Override String service, @Override String usage, @Override SourceFeature source) implements Row, WithShipway, WithZOrder, WithLayer, WithName, WithNameEn, WithNameDe, WithShortName, WithIsTunnel, - WithIsBridge, WithIsRamp, WithIsFord, WithIsOneway, WithIsArea, WithService, WithUsage, WithSource { + WithIsBridge, WithIsRamp, WithIsFord, WithIsArea, WithService, WithUsage, WithSource { public OsmShipwayLinestring(SourceFeature source, String mappingKey) { this(source.getString("route"), source.getWayZorder(), source.getLong("layer"), 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("usage"), source); + source.getBoolean("area"), source.getString("service"), source.getString("usage"), source); } /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ @@ -521,16 +520,19 @@ public class Tables { } /** 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 @@ -656,9 +658,8 @@ public class Tables { } /** 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 @@ -669,10 +670,12 @@ public class Tables { } } /** 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". */ @@ -708,12 +711,13 @@ public class Tables { /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ public static final Expression MAPPING = and(or(matchAny("aerialway", "station"), matchAny("amenity", "arts_centre", "atm", "bank", "bar", "bbq", "bicycle_parking", "bicycle_rental", "biergarten", - "bus_station", "cafe", "cinema", "clinic", "college", "community_centre", "courthouse", "dentist", "doctors", - "drinking_water", "fast_food", "ferry_terminal", "fire_station", "food_court", "fuel", "grave_yard", "hospital", - "ice_cream", "kindergarten", "library", "marketplace", "motorcycle_parking", "nightclub", "nursing_home", - "parking", "pharmacy", "place_of_worship", "police", "parcel_locker", "post_box", "post_office", "prison", - "pub", "public_building", "recycling", "restaurant", "school", "shelter", "swimming_pool", "taxi", "telephone", - "theatre", "toilets", "townhall", "university", "veterinary", "waste_basket"), + "bus_station", "cafe", "charging_station", "cinema", "clinic", "college", "community_centre", "courthouse", + "dentist", "doctors", "drinking_water", "fast_food", "ferry_terminal", "fire_station", "food_court", "fuel", + "grave_yard", "hospital", "ice_cream", "kindergarten", "library", "marketplace", "motorcycle_parking", + "nightclub", "nursing_home", "parking", "pharmacy", "place_of_worship", "police", "parcel_locker", "post_box", + "post_office", "prison", "pub", "public_building", "recycling", "restaurant", "school", "shelter", + "swimming_pool", "taxi", "telephone", "theatre", "toilets", "townhall", "university", "veterinary", + "waste_basket"), matchAny("barrier", "bollard", "border_control", "cycle_barrier", "gate", "lift_gate", "sally_port", "stile", "toll_booth"), matchAny("building", "dormitory"), matchAny("highway", "bus_stop"), @@ -722,7 +726,15 @@ public class Tables { matchAny("leisure", "dog_park", "escape_game", "garden", "golf_course", "ice_rink", "hackerspace", "marina", "miniature_golf", "park", "pitch", "playground", "sports_centre", "stadium", "swimming_area", "swimming_pool", "water_park"), - matchAny("office", "diplomatic"), + matchAny("office", "accountant", "advertising_agency", "architect", "association", "bail_bond_agent", "charity", + "company", "construction_company", "consulting", "cooperative", "courier", "coworking", "diplomatic", + "educational_institution", "employment_agency", "energy_supplier", "engineer", "estate_agent", "financial", + "financial_advisor", "forestry", "foundation", "geodesist", "government", "graphic_design", "guide", + "harbour_master", "health_insurance", "insurance", "interior_design", "it", "lawyer", "logistics", "marketing", + "moving_company", "newspaper", "ngo", "notary", "physician", "political_party", "private_investigator", + "property_management", "publisher", "quango", "religion", "research", "security", "surveyor", "tax_advisor", + "taxi", "telecommunication", "therapist", "translator", "travel_agent", "tutoring", "union", "university", + "water_utility", "web_design", "wedding_planner"), matchAny("railway", "halt", "station", "subway_entrance", "train_station_entrance", "tram_stop"), matchAny("shop", "accessories", "alcohol", "antiques", "art", "bag", "bakery", "beauty", "bed", "beverages", "bicycle", "books", "boutique", "butcher", "camera", "car", "car_repair", "car_parts", "carpet", "charity", @@ -731,9 +743,9 @@ public class Tables { "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", @@ -778,12 +790,13 @@ public class Tables { /** Imposm3 "mapping" to filter OSM elements that should appear in this "table". */ public static final Expression MAPPING = and(or(matchAny("aerialway", "station"), matchAny("amenity", "arts_centre", "atm", "bank", "bar", "bbq", "bicycle_parking", "bicycle_rental", "biergarten", - "bus_station", "cafe", "cinema", "clinic", "college", "community_centre", "courthouse", "dentist", "doctors", - "drinking_water", "fast_food", "ferry_terminal", "fire_station", "food_court", "fuel", "grave_yard", "hospital", - "ice_cream", "kindergarten", "library", "marketplace", "motorcycle_parking", "nightclub", "nursing_home", - "parking", "pharmacy", "place_of_worship", "police", "parcel_locker", "post_box", "post_office", "prison", - "pub", "public_building", "recycling", "restaurant", "school", "shelter", "swimming_pool", "taxi", "telephone", - "theatre", "toilets", "townhall", "university", "veterinary", "waste_basket"), + "bus_station", "cafe", "charging_station", "cinema", "clinic", "college", "community_centre", "courthouse", + "dentist", "doctors", "drinking_water", "fast_food", "ferry_terminal", "fire_station", "food_court", "fuel", + "grave_yard", "hospital", "ice_cream", "kindergarten", "library", "marketplace", "motorcycle_parking", + "nightclub", "nursing_home", "parking", "pharmacy", "place_of_worship", "police", "parcel_locker", "post_box", + "post_office", "prison", "pub", "public_building", "recycling", "restaurant", "school", "shelter", + "swimming_pool", "taxi", "telephone", "theatre", "toilets", "townhall", "university", "veterinary", + "waste_basket"), matchAny("barrier", "bollard", "border_control", "cycle_barrier", "gate", "lift_gate", "sally_port", "stile", "toll_booth"), matchAny("building", "dormitory"), matchAny("highway", "bus_stop"), @@ -792,7 +805,15 @@ public class Tables { matchAny("leisure", "dog_park", "escape_game", "garden", "golf_course", "ice_rink", "hackerspace", "marina", "miniature_golf", "park", "pitch", "playground", "sports_centre", "stadium", "swimming_area", "swimming_pool", "water_park"), - matchAny("office", "diplomatic"), + matchAny("office", "accountant", "advertising_agency", "architect", "association", "bail_bond_agent", "charity", + "company", "construction_company", "consulting", "cooperative", "courier", "coworking", "diplomatic", + "educational_institution", "employment_agency", "energy_supplier", "engineer", "estate_agent", "financial", + "financial_advisor", "forestry", "foundation", "geodesist", "government", "graphic_design", "guide", + "harbour_master", "health_insurance", "insurance", "interior_design", "it", "lawyer", "logistics", "marketing", + "moving_company", "newspaper", "ngo", "notary", "physician", "political_party", "private_investigator", + "property_management", "publisher", "quango", "religion", "research", "security", "surveyor", "tax_advisor", + "taxi", "telecommunication", "therapist", "translator", "travel_agent", "tutoring", "union", "university", + "water_utility", "web_design", "wedding_planner"), matchAny("railway", "halt", "station", "subway_entrance", "train_station_entrance", "tram_stop"), matchAny("shop", "accessories", "alcohol", "antiques", "art", "bag", "bakery", "beauty", "bed", "beverages", "bicycle", "books", "boutique", "butcher", "camera", "car", "car_repair", "car_parts", "carpet", "charity", @@ -801,9 +822,9 @@ public class Tables { "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", @@ -885,6 +906,11 @@ public class Tables { String bicycle(); } + /** Rows with a String blockNumber attribute. */ + public interface WithBlockNumber { + String blockNumber(); + } + /** Rows with a String boundary attribute. */ public interface WithBoundary { String boundary(); @@ -965,6 +991,11 @@ public class Tables { String funicular(); } + /** Rows with a String hasName attribute. */ + public interface WithHasName { + String hasName(); + } + /** Rows with a String height attribute. */ public interface WithHeight { String height(); @@ -1225,6 +1256,11 @@ public class Tables { String station(); } + /** Rows with a String street attribute. */ + public interface WithStreet { + String street(); + } + /** Rows with a String subclass attribute. */ public interface WithSubclass { String subclass(); @@ -1245,6 +1281,11 @@ public class Tables { String tourism(); } + /** Rows with a String tracktype attribute. */ + public interface WithTracktype { + String tracktype(); + } + /** Rows with a String uicRef attribute. */ public interface WithUicRef { String uicRef(); diff --git a/src/main/java/org/openmaptiles/layers/Housenumber.java b/src/main/java/org/openmaptiles/layers/Housenumber.java index 6633859..2d53c42 100644 --- a/src/main/java/org/openmaptiles/layers/Housenumber.java +++ b/src/main/java/org/openmaptiles/layers/Housenumber.java @@ -43,9 +43,18 @@ import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.geo.GeometryException; import com.onthegomap.planetiler.stats.Stats; import com.onthegomap.planetiler.util.Translations; +import java.util.Arrays; +import java.util.Comparator; import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.openmaptiles.generated.OpenMapTilesSchema; import org.openmaptiles.generated.Tables; +import org.openmaptiles.util.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Defines the logic for generating map elements in the {@code housenumber} layer from source features. @@ -59,19 +68,96 @@ public class Housenumber implements Tables.OsmHousenumberPoint.Handler, ForwardingProfile.FeaturePostProcessor { - public Housenumber(Translations translations, PlanetilerConfig config, Stats stats) {} + private static final Logger LOGGER = LoggerFactory.getLogger(Housenumber.class); + private static final String OSM_SEPARATOR = ";"; + private static final String DISPLAY_SEPARATOR = "–"; + private static final Pattern NO_CONVERSION_PATTERN = Pattern.compile("[^0-9;]"); + private static final String TEMP_PARTITION = "_partition"; + private static final String TEMP_HAS_NAME = "_has_name"; + private static final Comparator BY_TEMP_HAS_NAME = Comparator + .comparing(i -> (Boolean) i.attrs().get(TEMP_HAS_NAME), Boolean::compare); + private final Stats stats; + + public Housenumber(Translations translations, PlanetilerConfig config, Stats stats) { + this.stats = stats; + } + + private static String displayHousenumberNonumeric(List numbers) { + return numbers.getFirst() + .concat(DISPLAY_SEPARATOR) + .concat(numbers.getLast()); + } + + protected static String displayHousenumber(String housenumber) { + if (!housenumber.contains(OSM_SEPARATOR)) { + return housenumber; + } + + List numbers = Arrays.stream(housenumber.split(OSM_SEPARATOR)) + .map(String::trim) + .filter(Predicate.not(String::isEmpty)) + .toList(); + if (numbers.isEmpty()) { + // not much to do with strange/invalid entries like "3;" or ";" etc. + return housenumber; + } + + Matcher matcher = NO_CONVERSION_PATTERN.matcher(housenumber); + if (matcher.find()) { + return displayHousenumberNonumeric(numbers); + } + + // numeric display house number + var statistics = numbers.stream() + .collect(Collectors.summarizingLong(Long::parseUnsignedLong)); + return String.valueOf(statistics.getMin()) + .concat(DISPLAY_SEPARATOR) + .concat(String.valueOf(statistics.getMax())); + } @Override public void process(Tables.OsmHousenumberPoint element, FeatureCollector features) { + String housenumber; + try { + housenumber = displayHousenumber(element.housenumber()); + } catch (NumberFormatException e) { + // should not be happening (thanks to NO_CONVERSION_PATTERN) but ... + stats.dataError("housenumber_range"); + LOGGER.warn("Failed to convert housenumber range: {}", element.housenumber()); + housenumber = element.housenumber(); + } + + String partition = Utils.coalesce(element.street(), "") + .concat(Utils.coalesce(element.blockNumber(), "")) + .concat(housenumber); + Boolean hasName = element.hasName() == null ? Boolean.FALSE : !element.hasName().isEmpty(); + features.centroidIfConvex(LAYER_NAME) .setBufferPixels(BUFFER_SIZE) - .setAttr(Fields.HOUSENUMBER, element.housenumber()) + .setAttr(Fields.HOUSENUMBER, housenumber) + .setAttr(TEMP_PARTITION, partition) + .setAttr(TEMP_HAS_NAME, hasName) .setMinZoom(14); } @Override public List postProcess(int zoom, List list) throws GeometryException { + // remove duplicate house numbers, features without name tag are prioritized + var items = list.stream() + .collect(Collectors.groupingBy(f -> f.attrs().get(TEMP_PARTITION))) + .values().stream() + .flatMap( + g -> g.stream().min(BY_TEMP_HAS_NAME).stream() + ) + .toList(); + + // remove temporary attributes + for (var item : items) { + item.attrs().remove(TEMP_HAS_NAME); + item.attrs().remove(TEMP_PARTITION); + } + // reduces the size of some heavy z14 tiles with many repeated housenumber values by 60% or more - return FeatureMerge.mergeMultiPoint(list); + return FeatureMerge.mergeMultiPoint(items); } } diff --git a/src/main/java/org/openmaptiles/layers/Park.java b/src/main/java/org/openmaptiles/layers/Park.java index 6bcc653..026f1f6 100644 --- a/src/main/java/org/openmaptiles/layers/Park.java +++ b/src/main/java/org/openmaptiles/layers/Park.java @@ -91,15 +91,20 @@ public class Park implements @Override public void process(Tables.OsmParkPolygon element, FeatureCollector features) { - String protectionTitle = element.protectionTitle(); - if (protectionTitle != null) { - protectionTitle = protectionTitle.replace(' ', '_').toLowerCase(Locale.ROOT); + String clazz; + if ("aboriginal_lands".equals(element.boundary())) { + clazz = "aboriginal_lands"; + } else { + String protectionTitle = element.protectionTitle(); + if (protectionTitle != null) { + protectionTitle = protectionTitle.replace(' ', '_').toLowerCase(Locale.ROOT); + } + clazz = coalesce( + nullIfEmpty(protectionTitle), + nullIfEmpty(element.boundary()), + nullIfEmpty(element.leisure()) + ); } - String clazz = coalesce( - nullIfEmpty(protectionTitle), - nullIfEmpty(element.boundary()), - nullIfEmpty(element.leisure()) - ); // park shape var outline = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) @@ -138,7 +143,7 @@ public class Park implements // sql filter: area > 70000*2^(20-zoom_level) // simplifies to: zoom_level > 20 - log(area / 70000) / log(2) int minzoom = (int) Math.floor(20 - Math.log(area / WORLD_AREA_FOR_70K_SQUARE_METERS) / LOG2); - minzoom = Math.min(14, Math.max(5, minzoom)); + minzoom = Math.clamp(minzoom, 5, 14); return minzoom; } diff --git a/src/main/java/org/openmaptiles/layers/Place.java b/src/main/java/org/openmaptiles/layers/Place.java index e9e58ed..ed1e522 100644 --- a/src/main/java/org/openmaptiles/layers/Place.java +++ b/src/main/java/org/openmaptiles/layers/Place.java @@ -235,7 +235,7 @@ public class Place implements rank = country.rank; } - rank = Math.max(1, Math.min(6, rank)); + rank = Math.clamp(rank, 1, 6); features.point(LAYER_NAME).setBufferPixels(BUFFER_SIZE) .putAttrs(names) @@ -261,7 +261,7 @@ public class Place implements if (nullOrEmpty(names.get(Fields.NAME_EN))) { names.put(Fields.NAME_EN, state.name); } - int rank = Math.min(6, Math.max(1, state.rank)); + int rank = Math.clamp(state.rank, 1, 6); features.point(LAYER_NAME).setBufferPixels(BUFFER_SIZE) .putAttrs(names) diff --git a/src/main/java/org/openmaptiles/layers/Poi.java b/src/main/java/org/openmaptiles/layers/Poi.java index b12b830..084f583 100644 --- a/src/main/java/org/openmaptiles/layers/Poi.java +++ b/src/main/java/org/openmaptiles/layers/Poi.java @@ -48,14 +48,26 @@ import com.onthegomap.planetiler.VectorTile; 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.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 @@ -68,13 +80,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), @@ -99,12 +113,25 @@ 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 List AGG_STOP_SUBCLASS_ORDER = List.of( + "subway", + "tram_stop", + "bus_station", + "bus_stop" + ); + private static final Comparator BY_SUBCLASS = Comparator + .comparingInt(s -> AGG_STOP_SUBCLASS_ORDER.indexOf(s.subclass())); + private static final Set BRAND_OPERATOR_REF_SUBCLASSES = Set.of("charging_station", "parcel_locker"); 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(); this.translations = translations; + this.stats = stats; } static int poiClassRank(String clazz) { @@ -125,19 +152,112 @@ public class Poi implements return lowZoom ? 12 : 14; } + @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.contains(element.subclass())) { + // multiple threads may update this concurrently + String aggStopKey = element.uicRef() + .concat(coalesce(nullIfEmpty(element.name()), "")) + .concat(coalesce(nullIfEmpty(element.network()), "")) + .concat(coalesce(nullIfEmpty(element.operator()), "")); + synchronized (this) { + aggStops.computeIfAbsent(aggStopKey, 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()); + + for (var aggStopSet : aggStops.values()) { + if (aggStopSet.size() == 1) { + processAggStop(aggStopSet.getFirst(), featureCollectors, emit, 1); + continue; + } + + Tables.OsmPoiPoint nearest = null; + try { + // find most important stops based on subclass + var firstSubclass = aggStopSet.stream().min(BY_SUBCLASS).get().subclass(); + var topAggStops = + aggStopSet.stream().filter(s -> firstSubclass.equals(s.subclass())).toArray(Tables.OsmPoiPoint[]::new); + + // calculate the centroid and ... + List aggStopPoints = new ArrayList<>(aggStopSet.size()); + for (var aggStop : aggStopSet) { + aggStopPoints.add(aggStop.source().worldGeometry().getCentroid()); + } + var aggStopCentroid = GeoUtils.combinePoints(aggStopPoints).getCentroid(); + + // ... find one stop nearest to the centroid + double minDistance = Double.MAX_VALUE; + for (var aggStop : topAggStops) { + double distance = aggStopCentroid.distance(aggStop.source().worldGeometry()); + if (distance < minDistance) { + minDistance = distance; + nearest = aggStop; + } + } + } catch (GeometryException e) { + e.log(stats, "agg_stop_geometry_1", + "Error getting geometry for some of the stops with UIC ref. " + aggStopSet.getFirst().uicRef() + + " (agg_stop)"); + // we're not able to calculate agg_stop, so simply dump the stops as they are + nearest = null; + } + + // now emit the stops + final Tables.OsmPoiPoint nearestFinal = nearest; // final needed for lambda + aggStopSet + .forEach(s -> processAggStop(s, featureCollectors, emit, s == nearestFinal ? 1 : 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"; @@ -157,7 +277,7 @@ public class Poi implements } // Parcel locker without name: use either brand or operator and add ref if present - if ("parcel_locker".equals(rawSubclass) && nullOrEmpty(name)) { + if (BRAND_OPERATOR_REF_SUBCLASSES.contains(rawSubclass) && nullOrEmpty(name)) { name = coalesce(nullIfEmpty(element.brand()), nullIfEmpty(element.operator())); String ref = nullIfEmpty(element.ref()); if (ref != null) { @@ -178,16 +298,24 @@ public class Poi implements int poiClassRank = poiClassRank(poiClass); int rankOrder = poiClassRank + ((nullOrEmpty(name)) ? 2000 : 0); + int minzoom = minzoom(element.subclass(), element.mappingKey()); + if (UNIVERSITY_POI_SUBCLASSES.contains(rawSubclass)) { + // universities that are at least 10% of a tile may appear from Z10 + output.setMinPixelSizeBelowZoom(13, 80); // 80x80px is ~10% of a 256x256px tile + minzoom = 10; + } + output.setBufferPixels(BUFFER_SIZE) .setAttr(Fields.CLASS, poiClass) .setAttr(Fields.SUBCLASS, subclass) .setAttr(Fields.LAYER, nullIfLong(element.layer(), 0)) .setAttr(Fields.LEVEL, Parse.parseLongOrNull(element.source().getTag("level"))) .setAttr(Fields.INDOOR, element.indoor() ? 1 : null) + .setAttr(Fields.AGG_STOP, aggStop) .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) .setPointLabelGridPixelSize(14, 64) .setSortKey(rankOrder) - .setMinZoom(minzoom(element.subclass(), element.mappingKey())); + .setMinZoom(minzoom); } @Override diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java index a8cc8e2..4aa898e 100644 --- a/src/main/java/org/openmaptiles/layers/Transportation.java +++ b/src/main/java/org/openmaptiles/layers/Transportation.java @@ -62,6 +62,7 @@ import java.util.Collections; 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; @@ -103,7 +104,8 @@ 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 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, @@ -132,11 +134,22 @@ 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" ); + private static final Set TRUNK_AS_MOTORWAY_BY_NETWORK = Set.of( + RouteNetwork.CA_TRANSCANADA, + RouteNetwork.CA_PROVINCIAL_ARTERIAL, + RouteNetwork.US_INTERSTATE + ); + 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) .put(6, 100) @@ -150,13 +163,15 @@ public class Transportation implements .thenComparingInt(r -> r.ref().length()) .thenComparing(RouteRelation::ref); private static final Set ONEWAY_VALUES = Set.of(-1, 1); + private final Map MINZOOMS; 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; @@ -239,16 +254,32 @@ public class Transportation implements @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 - synchronized (this) { - try { - Geometry boundary = feature.polygon().buffer(GeoUtils.metersToPixelAtEquator(0, 10_000) / 256d); - greatBritain = PreparedGeometryFactory.prepare(boundary); - } catch (GeometryException e) { - LOGGER.error("Failed to get Great Britain Polygon: " + e); + 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")) { + try { + var prepared = PreparedGeometryFactory.prepare( + feature.polygon().buffer(GeoUtils.metersToPixelAtEquator(0, 10_000) / 256d) + ); + synchronized (this) { + greatBritain = prepared; } + } catch (GeometryException e) { + LOGGER.error("Failed to get Great Britain Polygon: " + e); + } + } else if (feature.hasTag("iso_a2", "IE")) { + try { + var prepared = PreparedGeometryFactory.prepare( + feature.polygon().buffer(GeoUtils.metersToPixelAtEquator(0, 10_000) / 256d) + ); + synchronized (this) { + ireland = prepared; + } + } catch (GeometryException e) { + LOGGER.error("Failed to get Ireland Polygon: " + e); } } } @@ -268,6 +299,26 @@ public class Transportation implements networkType = RouteNetwork.US_STATE; } else if (network != null && network.startsWith("CA:transcanada")) { networkType = RouteNetwork.CA_TRANSCANADA; + } 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; + } else if ("QEW".equals(ref)) { + networkType = RouteNetwork.CA_PROVINCIAL_ARTERIAL; + } else { + networkType = RouteNetwork.CA_PROVINCIAL; + } + } 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; } int rank = switch (coalesce(network, "")) { @@ -307,12 +358,15 @@ public class Transportation implements 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"; - result.add(new RouteRelation(refMatcher.group(), network, networkType, (byte) -1, - 0)); + Transportation.RouteNetwork networkType = switch (element.highway()) { + case "motorway" -> Transportation.RouteNetwork.GB_MOTORWAY; + case "trunk" -> RouteNetwork.GB_TRUNK; + case "primary", "secondary" -> RouteNetwork.GB_PRIMARY; + default -> null; + }; + result.add(new RouteRelation(refMatcher.group(), + networkType == null ? null : networkType.network, + networkType, (byte) -1, 0)); } } catch (GeometryException e) { e.log(stats, "omt_transportation_name_gb_test", @@ -320,6 +374,31 @@ public class Transportation implements } } } + // 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)) { + String highway = coalesce(element.highway(), ""); + Transportation.RouteNetwork networkType = switch (highway) { + case "motorway" -> Transportation.RouteNetwork.IE_MOTORWAY; + case "trunk", "primary" -> RouteNetwork.IE_NATIONAL; + default -> RouteNetwork.IE_REGIONAL; + }; + result.add(new RouteRelation(refMatcher.group(), networkType.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; @@ -362,13 +441,11 @@ public class Transportation implements .setAttr(Fields.CLASS, highwayClass) .setAttr(Fields.SUBCLASS, highwaySubclass(highwayClass, element.publicTransport(), highway)) .setAttr(Fields.NETWORK, networkType != null ? networkType.name : null) - // TODO: including brunnel at low zooms leads to some large 300-400+kb z4-7 tiles, instead - // we should only set brunnel if the line is above a certain length - .setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord())) + .setAttrWithMinSize(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()), 4, 4, 12) // z8+ .setAttrWithMinzoom(Fields.EXPRESSWAY, element.expressway() && !"motorway".equals(highway) ? 1 : null, 8) // z9+ - .setAttrWithMinzoom(Fields.LAYER, nullIfLong(element.layer(), 0), 9) + .setAttrWithMinSize(Fields.LAYER, nullIfLong(element.layer(), 0), 4, 9, 12) .setAttrWithMinzoom(Fields.BICYCLE, nullIfEmpty(element.bicycle()), 9) .setAttrWithMinzoom(Fields.FOOT, nullIfEmpty(element.foot()), 9) .setAttrWithMinzoom(Fields.HORSE, nullIfEmpty(element.horse()), 9) @@ -381,7 +458,7 @@ public class Transportation implements // 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); @@ -415,6 +492,14 @@ public class Transportation implements 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) + .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); }; } @@ -463,7 +548,6 @@ public class Transportation implements .setAttr(Fields.CLASS, clazz) .setAttr(Fields.SUBCLASS, railway) .setAttr(Fields.SERVICE, service(service)) - .setAttr(Fields.ONEWAY, nullIfInt(element.isOneway(), 0)) .setAttr(Fields.RAMP, element.isRamp() ? 1L : null) .setAttrWithMinzoom(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()), 10) .setAttrWithMinzoom(Fields.LAYER, nullIfLong(element.layer(), 0), 9) @@ -494,13 +578,13 @@ public class Transportation implements .setAttr(Fields.CLASS, element.shipway()) // "ferry" // no subclass .setAttr(Fields.SERVICE, service(element.service())) - .setAttr(Fields.ONEWAY, nullIfInt(element.isOneway(), 0)) .setAttr(Fields.RAMP, element.isRamp() ? 1L : null) .setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord())) .setAttr(Fields.LAYER, nullIfLong(element.layer(), 0)) .setSortKey(element.zOrder()) .setMinPixelSize(0) // merge during post-processing, then limit by size - .setMinZoom(11); + .setMinZoom(4) + .setMinPixelSizeBelowZoom(10, 32); // `sql_filter: ST_Length(...)` used in OpenMapTiles translates to 32px } @Override @@ -548,17 +632,25 @@ public class Transportation implements enum RouteNetwork { - US_INTERSTATE("us-interstate"), - US_HIGHWAY("us-highway"), - US_STATE("us-state"), - CA_TRANSCANADA("ca-transcanada"), - GB_MOTORWAY("gb-motorway"), - GB_TRUNK("gb-trunk"); + US_INTERSTATE("us-interstate", null), + US_HIGHWAY("us-highway", null), + US_STATE("us-state", null), + CA_TRANSCANADA("ca-transcanada", null), + CA_PROVINCIAL_ARTERIAL("ca-provincial-arterial", null), + CA_PROVINCIAL("ca-provincial", null), + GB_MOTORWAY("gb-motorway", "omt-gb-motorway"), + GB_TRUNK("gb-trunk", "omt-gb-trunk"), + GB_PRIMARY("gb-primary", "omt-gb-primary"), + IE_MOTORWAY("ie-motorway", "omt-ie-motorway"), + IE_NATIONAL("ie-national", "omt-ie-national"), + IE_REGIONAL("ie-regional", "omt-ie-regional"); final String name; + final String network; - RouteNetwork(String name) { + RouteNetwork(String name, String network) { this.name = name; + this.network = network; } } diff --git a/src/main/java/org/openmaptiles/layers/TransportationName.java b/src/main/java/org/openmaptiles/layers/TransportationName.java index 27372a3..4e8fefd 100644 --- a/src/main/java/org/openmaptiles/layers/TransportationName.java +++ b/src/main/java/org/openmaptiles/layers/TransportationName.java @@ -111,8 +111,8 @@ public class TransportationName implements .put(7, 20_000) .put(8, 14_000) .put(9, 8_000) - .put(10, 8_000) - .put(11, 8_000); + .put(10, 4_000) + .put(11, 2_000); private final boolean brunnel; private final boolean sizeForShield; private final boolean limitMerge; @@ -279,7 +279,9 @@ public class TransportationName implements } if (brunnel) { - feature.setAttr(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord())); + // from OMT: "Drop brunnel if length of way < 2% of tile width (less than 3 pixels)" + feature.setAttrWithMinSize(Fields.BRUNNEL, brunnel(element.isBridge(), element.isTunnel(), element.isFord()), + 3, 4, 12); } /* diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java index 2bc19fc..cebaeff 100644 --- a/src/main/java/org/openmaptiles/layers/WaterName.java +++ b/src/main/java/org/openmaptiles/layers/WaterName.java @@ -35,6 +35,7 @@ See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for deta */ 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 @@ import com.onthegomap.planetiler.stats.Stats; 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,7 @@ 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 +141,10 @@ public class WaterName implements @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 +162,25 @@ public class WaterName implements 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) { + // FIXME: While this looks like matching properly stuff in https://github.com/openmaptiles/openmaptiles/pull/1457/files#diff-201daa1c61c99073fe3280d440c9feca5ed2236b251ad454caa14cc203f952d1R74 , + // it includes not just https://www.openstreetmap.org/relation/13360255 but also https://www.openstreetmap.org/node/1385157299 (and some others). + // Hence check how that OpenMapTiles code works for "James Bay" and: + // a) if same as here then, fix there and then here + // b) if OK (while here NOK), fix only here + 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); } @@ -172,31 +189,35 @@ public class WaterName implements @Override public void process(Tables.OsmWaterPolygon element, FeatureCollector features) { if (nullIfEmpty(element.name()) != null) { - try { - Geometry centerlineGeometry = lakeCenterlines.get(element.source().id()); - FeatureCollector.Feature feature; - int minzoom = 9; - if (centerlineGeometry != null) { - // prefer lake centerline if it exists - feature = features.geometry(LAYER_NAME, centerlineGeometry) - .setMinPixelSizeBelowZoom(13, 6d * element.name().length()); - } else { - // otherwise just use a label point inside the lake - 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)); - } - feature - .setAttr(Fields.CLASS, FieldValues.CLASS_LAKE) - .setBufferPixels(BUFFER_SIZE) - .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) - .setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0) - .setMinZoom(minzoom); - } catch (GeometryException e) { - e.log(stats, "omt_water_polygon", "Unable to get geometry for water polygon " + element.source().id()); + Geometry centerlineGeometry = lakeCenterlines.get(element.source().id()); + FeatureCollector.Feature feature; + int minzoom = 9; + String place = element.place(); + 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) + .setMinPixelSizeBelowZoom(13, 6d * element.name().length()); + } else { + // otherwise just use a label point inside the lake + feature = features.pointOnSurface(LAYER_NAME) + .setMinZoom(place != null && SEA_OR_OCEAN_PLACE.contains(place) ? 0 : 3) + .setMinPixelSize(128); // tiles are 256x256, so 128x128 is 1/4 of a tile + } + feature + .setAttr(Fields.CLASS, clazz) + .setBufferPixels(BUFFER_SIZE) + .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) + .setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0) + .setMinZoom(minzoom); } } } diff --git a/src/main/java/org/openmaptiles/util/Utils.java b/src/main/java/org/openmaptiles/util/Utils.java index de9f4bf..993ca09 100644 --- a/src/main/java/org/openmaptiles/util/Utils.java +++ b/src/main/java/org/openmaptiles/util/Utils.java @@ -74,5 +74,4 @@ public class Utils { public static String brunnel(boolean isBridge, boolean isTunnel, boolean isFord) { return isBridge ? "bridge" : isTunnel ? "tunnel" : isFord ? "ford" : null; } - } diff --git a/src/test/java/org/openmaptiles/OpenMapTilesTest.java b/src/test/java/org/openmaptiles/OpenMapTilesTest.java index 44d428d..187fccf 100644 --- a/src/test/java/org/openmaptiles/OpenMapTilesTest.java +++ b/src/test/java/org/openmaptiles/OpenMapTilesTest.java @@ -137,7 +137,7 @@ class OpenMapTilesTest { assertFeatureNear(mbtiles, "housenumber", Map.of( "housenumber", "27" ), 7.42117, 43.73652, 14, 14); - assertNumFeatures("housenumber", Map.of(), 14, 274, Point.class); + assertNumFeatures("housenumber", Map.of(), 14, 231, Point.class); } @Test diff --git a/src/test/java/org/openmaptiles/layers/AbstractLayerTest.java b/src/test/java/org/openmaptiles/layers/AbstractLayerTest.java index 05e94d9..e3f649e 100644 --- a/src/test/java/org/openmaptiles/layers/AbstractLayerTest.java +++ b/src/test/java/org/openmaptiles/layers/AbstractLayerTest.java @@ -137,6 +137,16 @@ public abstract class AbstractLayerTest { ); } + SourceFeature lineFeatureWithLength(double length, Map props) { + return SimpleFeature.create( + GeoUtils.worldToLatLonCoords(newLineString(0, 0, 0, length)), + new HashMap<>(props), + OpenMapTilesProfile.OSM_SOURCE, + null, + 0 + ); + } + SourceFeature closedWayFeature(Map props) { return SimpleFeature.createFakeOsmFeature( newLineString(0, 0, 1, 0, 1, 1, 0, 1, 0, 0), diff --git a/src/test/java/org/openmaptiles/layers/HousenumberTest.java b/src/test/java/org/openmaptiles/layers/HousenumberTest.java index 6ead5e9..2fa3129 100644 --- a/src/test/java/org/openmaptiles/layers/HousenumberTest.java +++ b/src/test/java/org/openmaptiles/layers/HousenumberTest.java @@ -1,8 +1,14 @@ package org.openmaptiles.layers; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.onthegomap.planetiler.geo.GeometryException; import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; class HousenumberTest extends AbstractLayerTest { @@ -27,4 +33,127 @@ class HousenumberTest extends AbstractLayerTest { "addr:housenumber", "10" )))); } + + @ParameterizedTest + @CsvSource({ + "1, 1", + "1;1a;2;2/b;20;3, 1–3", + "1;1a;2;2/b;20;3;, 1–3", + "1;2;20;3, 1–20", + "1;2;20;3;, 1–20", + ";, ;", + ";;, ;;", + "2712;935803935803, 2712–935803935803", + }) + void testDisplayHousenumber(String outlier, String expected) { + assertEquals(expected, Housenumber.displayHousenumber(outlier)); + } + + @Test + void testTempAttrs() { + assertFeatures(14, List.of(Map.of( + "_has_name", Boolean.TRUE, + "_partition", "streetX765/6" + )), process(polygonFeature(Map.of( + "addr:housenumber", "765/6", + "addr:block_number", "X", + "addr:street", "street", + "name", "name" + )))); + } + + @Test + void testNonduplicateHousenumber() throws GeometryException { + var layerName = Housenumber.LAYER_NAME; + var hn1 = pointFeature( + layerName, + Map.of( + "housenumber", "764/2", + "_partition", "764/2" + ), + 1 + ); + var hn2 = pointFeature( + layerName, + Map.of( + "housenumber", "765/6", + "_partition", "765/6" + ), + 1 + ); + + Assertions.assertEquals( + 2, + profile.postProcessLayerFeatures(layerName, 14, List.of(hn1, hn2)).size() + ); + } + + @Test + void testNonduplicateStreet() throws GeometryException { + var layerName = Housenumber.LAYER_NAME; + var housenumber = "765/6"; + var hn1 = pointFeature( + layerName, + Map.of( + "housenumber", housenumber, + "_partition", "street 1" + housenumber + ), + 1 + ); + var hn2 = pointFeature( + layerName, + Map.of( + "housenumber", housenumber, + "_partition", "street 2" + housenumber + ), + 1 + ); + + var result = profile.postProcessLayerFeatures(layerName, 14, List.of(hn1, hn2)); + + Assertions.assertEquals( + 1, // same housenumber => two points merged into one multipoint + result.size() + ); + Assertions.assertEquals( + 5, // two point in multipoint => 5 commands + result.getFirst().geometry().commands().length); + } + + @Test + void testDuplicateHousenumber() throws GeometryException { + var layerName = Housenumber.LAYER_NAME; + var housenumber = "765/6"; + var hn1 = pointFeature( + layerName, + Map.of( + "housenumber", housenumber + " (no name)", + "_has_name", false, + "_partition", housenumber + ), + 1 + ); + var hn2 = pointFeature( + layerName, + Map.of( + "housenumber", housenumber + " (with name)", + "_has_name", true, + "_partition", housenumber + ), + 1 + ); + + var result = profile.postProcessLayerFeatures(layerName, 14, List.of(hn1, hn2)); + + Assertions.assertEquals(List.of( + pointFeature( + layerName, + Map.of("housenumber", "765/6 (no name)"), + 1 + ) + ), result); + Assertions.assertEquals( + 3, // only one point in multipoint => 3 commands + result.getFirst().geometry().commands().length); + } } diff --git a/src/test/java/org/openmaptiles/layers/ParkTest.java b/src/test/java/org/openmaptiles/layers/ParkTest.java index a393b21..2383a13 100644 --- a/src/test/java/org/openmaptiles/layers/ParkTest.java +++ b/src/test/java/org/openmaptiles/layers/ParkTest.java @@ -45,6 +45,30 @@ class ParkTest extends AbstractLayerTest { )))); } + @Test + void testAbotiginalLand() { + assertFeatures(13, List.of(Map.of( + "_layer", "park", + "_type", "polygon", + "class", "aboriginal_lands", + "name", "Hualapai Tribe", + "_minpixelsize", 2d, + "_minzoom", 4, + "_maxzoom", 14 + ), Map.of( + "_layer", "park", + "_type", "point", + "class", "aboriginal_lands", + "name", "Hualapai Tribe", + "_minzoom", 5, + "_maxzoom", 14 + )), process(polygonFeature(Map.of( + "boundary", "aboriginal_lands", + "name", "Hualapai Tribe", + "protection_title", "National Park" + )))); + } + @Test void testSmallerPark() { double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11); diff --git a/src/test/java/org/openmaptiles/layers/PoiTest.java b/src/test/java/org/openmaptiles/layers/PoiTest.java index 11a9a70..9027cf5 100644 --- a/src/test/java/org/openmaptiles/layers/PoiTest.java +++ b/src/test/java/org/openmaptiles/layers/PoiTest.java @@ -1,13 +1,19 @@ package org.openmaptiles.layers; +import static com.onthegomap.planetiler.TestUtils.newPoint; + +import com.onthegomap.planetiler.FeatureCollector; 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.List; import java.util.Map; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.openmaptiles.OpenMapTilesProfile; class PoiTest extends AbstractLayerTest { @@ -63,6 +69,167 @@ class PoiTest extends AbstractLayerTest { )))); } + private List 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", + "name:es", "test 1", + "uic_ref", "1" + )), + pointFeature(Map.of( + "railway", "tram_stop", + "name", "station", + "name:es", "test 2", + "uic_ref", "1" + )) + )); + assertFeatures(14, List.of( + Map.of( + "_layer", "poi", + "name:es", "test 1", + "class", "railway", + "subclass", "tram_stop", + "agg_stop", 1, + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name:es", "test 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", + "name:es", "test 1", + "uic_ref", "1" + )), + pointFeature(Map.of( + "highway", "bus_stop", + "name", "station", + "name:es", "test 2", + "uic_ref", "1" + )), + pointFeature(Map.of( + "railway", "tram_stop", + "name", "station", + "name:es", "test 3", + "uic_ref", "1" + )) + )); + assertFeatures(14, List.of( + Map.of( + "_layer", "poi", + "name:es", "test 1", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name:es", "test 2", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name:es", "test 3", + "class", "railway", + "subclass", "tram_stop", + "agg_stop", 1, + "_minzoom", 14 + ) + ), result); + } + + @Test + void testAggStopThreeWithSameSubclass() { + var result = testAggStops(List.of( + SimpleFeature.create(newPoint(0, 0), Map.of( + "highway", "bus_stop", + "name", "station", + "name:es", "test 1", + "uic_ref", "1" + ), OpenMapTilesProfile.OSM_SOURCE, null, 0), + SimpleFeature.create(newPoint(1, 0), Map.of( + "highway", "bus_stop", + "name", "station", + "name:es", "test 2", + "uic_ref", "1" + ), OpenMapTilesProfile.OSM_SOURCE, null, 1), + SimpleFeature.create(newPoint(2, 0), Map.of( + "highway", "bus_stop", + "name", "station", + "name:es", "test 3", + "uic_ref", "1" + ), OpenMapTilesProfile.OSM_SOURCE, null, 2) + )); + assertFeatures(14, List.of( + Map.of( + "_layer", "poi", + "name:es", "test 1", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name:es", "test 2", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", 1, + "_minzoom", 14 + ), + Map.of( + "_layer", "poi", + "name:es", "test 3", + "class", "bus", + "subclass", "bus_stop", + "agg_stop", "", + "_minzoom", 14 + ) + ), result); + } + @ParameterizedTest @ValueSource(booleans = {false, true}) void testPlaceOfWorshipFromReligionTag(boolean area) { @@ -185,7 +352,7 @@ class PoiTest extends AbstractLayerTest { void testEmbassy() { assertFeatures(7, List.of(Map.of( "_layer", "poi", - "class", "diplomatic", + "class", "office", "subclass", "diplomatic", "name", "The Embassy" )), process(pointFeature(Map.of( @@ -274,4 +441,28 @@ class PoiTest extends AbstractLayerTest { "ref", "Corner Case" )))); } + + @Test + void testChargingStation() { + List> expected = List.of(Map.of( + "_layer", "poi", + "class", "fuel", + "subclass", "charging_station", + "name", "Some Charging Station Operator" + )); + assertFeatures(14, expected, process(pointFeature(Map.of( + "amenity", "charging_station", + "brand", "Some Charging Station Operator" + )))); + assertFeatures(14, expected, process(pointFeature(Map.of( + "amenity", "charging_station", + "operator", "Some Charging Station Operator" + )))); + assertFeatures(14, expected, process(pointFeature(Map.of( + "amenity", "charging_station", + "operator", "Some Charging Station", + "ref", "Operator" + )))); + } + } diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java index e3fda66..fe32a98 100644 --- a/src/test/java/org/openmaptiles/layers/TransportationTest.java +++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java @@ -3,6 +3,7 @@ package org.openmaptiles.layers; 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; @@ -17,6 +18,7 @@ import java.util.ArrayList; 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; @@ -267,31 +269,6 @@ class TransportationTest extends AbstractLayerTest { "bridge", "yes" ))); - assertFeatures(13, List.of(mapOf( - "_layer", "transportation", - "class", "motorway", - "surface", "paved", - "oneway", 1, - "ramp", "", - "bicycle", "no", - "foot", "no", - "horse", "no", - "brunnel", "bridge", - "network", "us-interstate", - "_minzoom", 4 - ), Map.of( - "_layer", "transportation_name", - "class", "motorway", - "name", "Massachusetts Turnpike", - "name_en", "Massachusetts Turnpike", - "ref", "90", - "ref_length", 2, - "network", "us-interstate", - "brunnel", "", - "route_1", "US:I=90", - "_minzoom", 6 - )), features); - assertFeatures(13, List.of(Map.of( "_layer", "transportation", "class", "motorway", @@ -341,6 +318,51 @@ class TransportationTest extends AbstractLayerTest { )), features); } + @Test + void testDuplicateRoute() { + var rel1 = new OsmElement.Relation(1); + rel1.setTag("type", "route"); + rel1.setTag("route", "road"); + rel1.setTag("network", "US:OK"); + rel1.setTag("ref", "104"); + rel1.setTag("direction", "north"); + var rel2 = new OsmElement.Relation(2); + rel2.setTag("type", "route"); + rel2.setTag("route", "road"); + rel2.setTag("network", "US:OK"); + rel2.setTag("ref", "104"); + rel2.setTag("direction", "south"); + + FeatureCollector features = process(lineFeatureWithRelation( + Stream.concat( + profile.preprocessOsmRelation(rel2).stream(), + profile.preprocessOsmRelation(rel1).stream() + ).toList(), + Map.of( + "highway", "trunk", + "ref", "US 23;SR 104", + "lanes", 5, + "maxspeed", "55 mph", + "expressway", "no" + ))); + + assertFeatures(13, List.of(mapOf( + "_layer", "transportation", + "class", "trunk", + "network", "us-state", + "_minzoom", 5 + ), Map.of( + "_layer", "transportation_name", + "class", "trunk", + "ref", "104", + "ref_length", 3, + "network", "us-state", + "route_1", "US:OK=104", + "route_2", "", + "_minzoom", 8 + )), features); + } + @Test void testRouteWithoutNetworkType() { var rel1 = new OsmElement.Relation(1); @@ -951,6 +973,316 @@ class TransportationTest extends AbstractLayerTest { )), 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 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( @@ -1014,6 +1346,307 @@ class TransportationTest extends AbstractLayerTest { ))); } + @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", "gb-primary", + "_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 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); @@ -1176,8 +1809,9 @@ class TransportationTest extends AbstractLayerTest { "_layer", "transportation", "class", "ferry", - "_minzoom", 11, + "_minzoom", 4, "_maxzoom", 14, + "_minpixelsize", 32d, "_type", "line" ), Map.of( "_layer", "transportation_name", @@ -1358,6 +1992,30 @@ class TransportationTest extends AbstractLayerTest { )))); } + @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" + )))); + } + @Test void testIssue58() { // test subject: https://www.openstreetmap.org/way/222564359 diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java index 54e0b00..02b7c98 100644 --- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java +++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java @@ -27,7 +27,7 @@ class WaterNameTest extends AbstractLayerTest { "_layer", "water_name", "_type", "point", - "_minzoom", 9, + "_minzoom", 3, "_maxzoom", 14 )), process(polygonFeatureWithArea(1, Map.of( "name", "waterway", @@ -36,19 +36,6 @@ class WaterNameTest extends AbstractLayerTest { "water", "pond", "intermittent", "1" )))); - double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11); - assertFeatures(10, List.of(Map.of( - "_layer", "water" - ), Map.of( - "_layer", "water_name", - "_type", "point", - "_minzoom", 11, - "_maxzoom", 14 - )), process(polygonFeatureWithArea(z11area, Map.of( - "name", "waterway", - "natural", "water", - "water", "pond" - )))); } @Test @@ -71,7 +58,7 @@ class WaterNameTest extends AbstractLayerTest { "_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( @@ -121,7 +108,7 @@ class WaterNameTest extends AbstractLayerTest { newLineString(0, 0, 1, 1), newLineString(2, 2, 3, 3) }))), - "_minzoom", 9, + "_minzoom", 3, "_maxzoom", 14, "_minpixelsize", "waterway".length() * 6d )), process(SimpleFeature.create( @@ -138,6 +125,40 @@ class WaterNameTest extends AbstractLayerTest { ))); } + @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(