diff --git a/LICENSE.md b/LICENSE.md
index 860a7563..973211fd 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,4 +1,4 @@
-Copyright (c) 2022, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
The vector tile schema has been developed by Klokan Technologies GmbH and
diff --git a/README.md b/README.md
index ee3ae320..24d43f9d 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,8 @@ available options.
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
+- Some line and polygon tolerances are different, can be tweaked with `--simplify-tolerance` parameter
+- For bigger bays whose label points show above Z9, centerline is used for Z9+
## Customizing
@@ -149,7 +150,7 @@ script with the
OpenMapTiles release tag:
```bash
-./scripts/regenerate-openmaptiles.sh v3.14
+./scripts/regenerate-openmaptiles.sh v3.15
```
Then follow the instructions it prints for reformatting generated code.
@@ -157,7 +158,7 @@ Then follow the instructions it prints for reformatting generated code.
If you want to regenerate from a different repository than the default openmaptiles, you can specify the url like this:
```bash
-./scripts/regenerate-openmaptiles.sh v3.14 https://raw.githubusercontent.com/openmaptiles/openmaptiles/
+./scripts/regenerate-openmaptiles.sh v3.15 https://raw.githubusercontent.com/openmaptiles/openmaptiles/
```
## License
@@ -165,8 +166,8 @@ If you want to regenerate from a different repository than the default openmapti
All code in this repository is under the [BSD license](./LICENSE.md) and the cartography decisions encoded in the schema
and SQL are licensed under [CC-BY](./LICENSE.md).
-Products or services using maps derived from OpenMapTiles schema need to visibly credit "OpenMapTiles.org" or
-reference "OpenMapTiles" with a link to https://openmaptiles.org/. Exceptions to attribution requirement can be granted
+Products or services using maps derived from OpenMapTiles schema need to **visibly credit "OpenMapTiles.org"** or
+**reference "OpenMapTiles"** with a link to https://openmaptiles.org/. Exceptions to attribution requirement can be granted
on request.
For a browsable electronic map based on OpenMapTiles and OpenStreetMap data, the
diff --git a/scripts/regenerate-openmaptiles.sh b/scripts/regenerate-openmaptiles.sh
index 2597334f..a598d3b8 100755
--- a/scripts/regenerate-openmaptiles.sh
+++ b/scripts/regenerate-openmaptiles.sh
@@ -4,8 +4,7 @@ set -o errexit
set -o pipefail
set -o nounset
-# TODO: change to "v3.15" once that is released
-TAG="${1:-"master"}"
+TAG="${1:-"v3.15"}"
echo "tag=${TAG}"
BASE_URL="${2:-"https://raw.githubusercontent.com/openmaptiles/openmaptiles/"}"
diff --git a/src/main/java/org/openmaptiles/Generate.java b/src/main/java/org/openmaptiles/Generate.java
index c8af4db9..69782ffd 100644
--- a/src/main/java/org/openmaptiles/Generate.java
+++ b/src/main/java/org/openmaptiles/Generate.java
@@ -61,7 +61,7 @@ public class Generate {
private static final String LINE_SEPARATOR = System.lineSeparator();
private static final String GENERATED_FILE_HEADER = """
/*
- Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+ Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -133,7 +133,7 @@ static JsonNode parseYaml(String string) {
public static void main(String[] args) throws IOException {
Arguments arguments = Arguments.fromArgsOrConfigFile(args);
PlanetilerConfig planetilerConfig = PlanetilerConfig.from(arguments);
- String tag = arguments.getString("tag", "openmaptiles tag to use", "v3.14.0");
+ String tag = arguments.getString("tag", "openmaptiles tag to use", "v3.15.0");
String baseUrl = arguments.getString("base-url", "the url used to download the openmaptiles.yml",
"https://raw.githubusercontent.com/openmaptiles/openmaptiles/");
String base = baseUrl + tag + "/";
diff --git a/src/main/java/org/openmaptiles/OpenMapTilesMain.java b/src/main/java/org/openmaptiles/OpenMapTilesMain.java
index 6b82c0d3..b51156f1 100644
--- a/src/main/java/org/openmaptiles/OpenMapTilesMain.java
+++ b/src/main/java/org/openmaptiles/OpenMapTilesMain.java
@@ -4,12 +4,16 @@
import com.onthegomap.planetiler.config.Arguments;
import java.nio.file.Path;
import org.openmaptiles.generated.OpenMapTilesSchema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Main entrypoint for generating a map using the OpenMapTiles schema.
*/
public class OpenMapTilesMain {
+ private static final Logger LOGGER = LoggerFactory.getLogger(OpenMapTilesMain.class);
+
public static void main(String[] args) throws Exception {
run(Arguments.fromArgsOrConfigFile(args));
}
@@ -52,5 +56,15 @@ static void run(Arguments arguments) throws Exception {
// override with --mbtiles=... argument or MBTILES=... env var or mbtiles=... in a config file
.setOutput("mbtiles", dataDir.resolve("output.mbtiles"))
.run();
+
+ LOGGER.info("""
+ Acknowledgments
+ Generated vector tiles are produced work of OpenStreetMap data.
+ Such tiles are reusable under CC-BY license granted by OpenMapTiles team:
+ - https://github.com/openmaptiles/openmaptiles/#license
+ Maps made with these vector tiles must display a visible credit:
+ - © OpenMapTiles © OpenStreetMap contributors
+ Thanks to all free, open source software developers and Open Data Contributors!
+ """);
}
}
diff --git a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java
index 676b5a46..a8f2e4d1 100644
--- a/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java
+++ b/src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -49,14 +49,14 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
/**
* All vector tile layer definitions, attributes, and allowed values generated from the
- * OpenMapTiles vector tile schema
- * master.
+ * OpenMapTiles vector tile schema
+ * v3.15.
*/
@SuppressWarnings("unused")
public class OpenMapTilesSchema {
public static final String NAME = "OpenMapTiles";
public static final String DESCRIPTION = "A tileset showcasing all layers in OpenMapTiles. https://openmaptiles.org";
- public static final String VERSION = "3.14.0";
+ public static final String VERSION = "3.15.0";
public static final String ATTRIBUTION =
"© OpenMapTiles © OpenStreetMap contributors";
public static final List LANGUAGES = List.of("am", "ar", "az", "be", "bg", "bn", "br", "bs", "ca", "co", "cs",
@@ -96,7 +96,7 @@ public static List createInstances(Translations translations, PlanetilerC
* boundaries show up. So you might not be able to use border styling for ocean water features.
*
* Generated from
- * water.yaml
+ * water.yaml
*/
public interface Water extends Layer {
double BUFFER_SIZE = 4.0;
@@ -182,7 +182,7 @@ final class FieldMappings {
public static final MultiExpression Class =
MultiExpression.of(List.of(MultiExpression.entry("dock", matchAny("waterway", "dock")),
MultiExpression.entry("river", matchAny("water", "river", "stream", "canal", "ditch", "drain")),
- MultiExpression.entry("pond", matchAny("water", "pond", "basin", "wastewater")),
+ MultiExpression.entry("pond", matchAny("water", "pond", "basin", "wastewater", "salt_pond")),
MultiExpression.entry("lake", FALSE), MultiExpression.entry("ocean", FALSE),
MultiExpression.entry("swimming_pool", matchAny("leisure", "swimming_pool"))));
}
@@ -195,7 +195,7 @@ final class FieldMappings {
* 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;
@@ -287,7 +287,7 @@ final class FieldMappings {
* layer is to style wood (class=wood
) and grass (class=grass
) areas.
*
* Generated from landcover.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/layers/landcover/landcover.yaml">landcover.yaml
*/
public interface Landcover extends Layer {
double BUFFER_SIZE = 4.0;
@@ -444,7 +444,7 @@ final class FieldMappings {
* 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;
@@ -540,7 +540,7 @@ final class FieldMappings {
* Natural peaks
*
* Generated from mountain_peak.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/layers/mountain_peak/mountain_peak.yaml">mountain_peak.yaml
*/
public interface MountainPeak extends Layer {
double BUFFER_SIZE = 64.0;
@@ -563,7 +563,10 @@ final class Fields {
* removed in a future release in favor of name:en
.
*/
public static final String NAME_EN = "name_en";
- /** German name name:de
if available, otherwise name
or name:en
. */
+ /**
+ * German name name:de
if available, otherwise name
or name:en
. This is
+ * deprecated and will be removed in a future release in favor of name:de
.
+ */
public static final String NAME_DE = "name_de";
/**
@@ -621,7 +624,7 @@ final class FieldMappings {
* leisure=nature_reserve
.
*
* Generated from
- * park.yaml
+ * park.yaml
*/
public interface Park extends Layer {
double BUFFER_SIZE = 4.0;
@@ -684,7 +687,7 @@ final class FieldMappings {
* but for most styles it makes sense to just style admin_level=2
and admin_level=4
.
*
* Generated from
- * boundary.yaml
+ * boundary.yaml
*/
public interface Boundary extends Layer {
double BUFFER_SIZE = 4.0;
@@ -794,7 +797,7 @@ final class FieldMappings {
* in the aeroway layer.
*
* Generated from
- * aeroway.yaml
+ * aeroway.yaml
*/
public interface Aeroway extends Layer {
double BUFFER_SIZE = 4.0;
@@ -854,7 +857,7 @@ final class FieldMappings {
* features like plazas.
*
* Generated from transportation.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/layers/transportation/transportation.yaml">transportation.yaml
*/
public interface Transportation extends Layer {
double BUFFER_SIZE = 4.0;
@@ -1199,7 +1202,7 @@ final class FieldMappings {
* location:underground are excluded.
*
* Generated from
- * building.yaml
+ * building.yaml
*/
public interface Building extends Layer {
double BUFFER_SIZE = 4.0;
@@ -1241,7 +1244,7 @@ final class FieldMappings {
* from OSM water bodies. Only the most important lakes contain labels.
*
* Generated from water_name.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/layers/water_name/water_name.yaml">water_name.yaml
*/
public interface WaterName extends Layer {
double BUFFER_SIZE = 256.0;
@@ -1318,7 +1321,7 @@ final class FieldMappings {
* while for other roads you should use name
.
*
* Generated from transportation_name.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/layers/transportation_name/transportation_name.yaml">transportation_name.yaml
*/
public interface TransportationName extends Layer {
double BUFFER_SIZE = 8.0;
@@ -1590,7 +1593,7 @@ final class FieldMappings {
* create a text hierarchy.
*
* Generated from
- * place.yaml
+ * place.yaml
*/
public interface Place extends Layer {
double BUFFER_SIZE = 256.0;
@@ -1714,7 +1717,7 @@ final class FieldMappings {
* tag are prioritized for preservation).
*
* Generated from housenumber.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/layers/housenumber/housenumber.yaml">housenumber.yaml
*/
public interface Housenumber extends Layer {
double BUFFER_SIZE = 8.0;
@@ -1746,7 +1749,7 @@ final class FieldMappings {
* Points of interests containing a of a variety
* of OpenStreetMap tags. Mostly contains amenities, sport, shop and tourist POIs.
*
- * Generated from poi.yaml
+ * Generated from poi.yaml
*/
public interface Poi extends Layer {
double BUFFER_SIZE = 64.0;
@@ -1990,7 +1993,7 @@ final class FieldMappings {
* Aerodrome labels
*
* Generated from aerodrome_label.yaml
+ * "https://github.com/openmaptiles/openmaptiles/blob/v3.15/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 88fa4ec2..7c87d28a 100644
--- a/src/main/java/org/openmaptiles/generated/Tables.java
+++ b/src/main/java/org/openmaptiles/generated/Tables.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -50,7 +50,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
/**
* OSM element parsers generated from the imposm3 table definitions
- * in the OpenMapTiles 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
diff --git a/src/main/java/org/openmaptiles/layers/Boundary.java b/src/main/java/org/openmaptiles/layers/Boundary.java
index 99a29977..2f72e4fe 100644
--- a/src/main/java/org/openmaptiles/layers/Boundary.java
+++ b/src/main/java/org/openmaptiles/layers/Boundary.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -121,6 +121,8 @@ public class Boundary implements
private static final Logger LOGGER = LoggerFactory.getLogger(Boundary.class);
private static final double COUNTRY_TEST_OFFSET = GeoUtils.metersToPixelAtEquator(0, 10) / 256d;
+ private static final String COUNTRY_KE = "Kenya";
+ private static final String COUNTRY_SS = "South Sudan";
private final Stats stats;
private final boolean addCountryNames;
private final boolean onlyOsmBoundaries;
@@ -179,8 +181,21 @@ record BoundaryInfo(int adminLevel, int minzoom, int maxzoom) {}
BoundaryInfo info = switch (table) {
case "ne_110m_admin_0_boundary_lines_land" -> new BoundaryInfo(2, 0, 0);
case "ne_50m_admin_0_boundary_lines_land" -> new BoundaryInfo(2, 1, 3);
- case "ne_10m_admin_0_boundary_lines_land" -> feature.hasTag("featurecla", "Lease Limit") ? null :
- new BoundaryInfo(2, 4, 4);
+ case "ne_10m_admin_0_boundary_lines_land" -> {
+ boolean isDisputedSouthSudanAndKenya = false;
+ if (disputed) {
+ String left = feature.getString("adm0_left");
+ String right = feature.getString("adm0_right");
+ if (COUNTRY_SS.equals(left)) {
+ isDisputedSouthSudanAndKenya = COUNTRY_KE.equals(right);
+ } else if (COUNTRY_KE.equals(left)) {
+ isDisputedSouthSudanAndKenya = COUNTRY_SS.equals(right);
+ }
+ }
+ yield isDisputedSouthSudanAndKenya ? new BoundaryInfo(2, 1, 4) :
+ feature.hasTag("featurecla", "Lease limit") ? null :
+ new BoundaryInfo(2, 4, 4);
+ }
case "ne_10m_admin_1_states_provinces_lines" -> {
Double minZoom = Parse.parseDoubleOrNull(feature.getTag("min_zoom"));
yield minZoom != null && minZoom <= 7 ? new BoundaryInfo(4, 1, 4) :
diff --git a/src/main/java/org/openmaptiles/layers/Building.java b/src/main/java/org/openmaptiles/layers/Building.java
index 49dc6b25..c1a1a3dc 100644
--- a/src/main/java/org/openmaptiles/layers/Building.java
+++ b/src/main/java/org/openmaptiles/layers/Building.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2023, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Housenumber.java b/src/main/java/org/openmaptiles/layers/Housenumber.java
index 2d53c42a..8a0bddd5 100644
--- a/src/main/java/org/openmaptiles/layers/Housenumber.java
+++ b/src/main/java/org/openmaptiles/layers/Housenumber.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Landcover.java b/src/main/java/org/openmaptiles/layers/Landcover.java
index 7bc66d78..43a5b647 100644
--- a/src/main/java/org/openmaptiles/layers/Landcover.java
+++ b/src/main/java/org/openmaptiles/layers/Landcover.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2023, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Landuse.java b/src/main/java/org/openmaptiles/layers/Landuse.java
index 16dfcfb0..1faeb60f 100644
--- a/src/main/java/org/openmaptiles/layers/Landuse.java
+++ b/src/main/java/org/openmaptiles/layers/Landuse.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -52,6 +52,7 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import org.openmaptiles.OpenMapTilesProfile;
import org.openmaptiles.generated.OpenMapTilesSchema;
import org.openmaptiles.generated.Tables;
@@ -74,6 +75,14 @@ public class Landuse implements
7, 2,
6, 1
));
+ private static final TreeMap MINDIST_AND_BUFFER_SIZES = new TreeMap<>(Map.of(
+ 5, 0.1,
+ // there is quite huge jump between Z5:NE and Z6:OSM => bigger generalization needed to make the transition more smooth
+ 6, 0.5,
+ 7, 0.25,
+ 8, 0.125,
+ Integer.MAX_VALUE, 0.1
+ ));
private static final Set Z6_CLASSES = Set.of(
FieldValues.CLASS_RESIDENTIAL,
FieldValues.CLASS_SUBURB,
@@ -87,11 +96,10 @@ public Landuse(Translations translations, PlanetilerConfig config, Stats stats)
public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) {
if ("ne_50m_urban_areas".equals(table)) {
Double scalerank = Parse.parseDoubleOrNull(feature.getTag("scalerank"));
- if (scalerank != null && scalerank <= 2) {
- features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
- .setAttr(Fields.CLASS, FieldValues.CLASS_RESIDENTIAL)
- .setZoomRange(4, 5);
- }
+ int minzoom = (scalerank != null && scalerank <= 2) ? 4 : 5;
+ features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE)
+ .setAttr(Fields.CLASS, FieldValues.CLASS_RESIDENTIAL)
+ .setZoomRange(minzoom, 5);
}
}
@@ -135,10 +143,14 @@ public List postProcess(int zoom,
result.add(item);
}
}
- var merged = zoom <= 12 ?
- FeatureMerge.mergeNearbyPolygons(toMerge, 1, 1, 0.1, 0.1) :
+ List merged;
+ if (zoom <= 12) {
+ double minDistAndBuffer = MINDIST_AND_BUFFER_SIZES.ceilingEntry(zoom).getValue();
+ merged = FeatureMerge.mergeNearbyPolygons(toMerge, 1, 1, minDistAndBuffer, minDistAndBuffer);
+ } else {
// reduces size of some heavy z13-14 tiles with lots of small polygons
- FeatureMerge.mergeMultiPolygon(toMerge);
+ merged = FeatureMerge.mergeMultiPolygon(toMerge);
+ }
result.addAll(merged);
return result;
}
diff --git a/src/main/java/org/openmaptiles/layers/Park.java b/src/main/java/org/openmaptiles/layers/Park.java
index 648df0f3..438745fb 100644
--- a/src/main/java/org/openmaptiles/layers/Park.java
+++ b/src/main/java/org/openmaptiles/layers/Park.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Place.java b/src/main/java/org/openmaptiles/layers/Place.java
index f764d15f..19e84942 100644
--- a/src/main/java/org/openmaptiles/layers/Place.java
+++ b/src/main/java/org/openmaptiles/layers/Place.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Poi.java b/src/main/java/org/openmaptiles/layers/Poi.java
index e511cc50..22736837 100644
--- a/src/main/java/org/openmaptiles/layers/Poi.java
+++ b/src/main/java/org/openmaptiles/layers/Poi.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Transportation.java b/src/main/java/org/openmaptiles/layers/Transportation.java
index ba828a44..17f17e2a 100644
--- a/src/main/java/org/openmaptiles/layers/Transportation.java
+++ b/src/main/java/org/openmaptiles/layers/Transportation.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -139,13 +139,23 @@ public class Transportation implements
private static final Set ACCESS_NO_VALUES = Set.of(
"private", "no"
);
- private static final Set TRUNK_AS_MOTORWAY_BY_NETWORK = Set.of(
+ // ... and also Z4_MOTORWAY_NY_NETWORK, except those in Z5_MOTORWAYS_BY_NETWORK:
+ private static final Set Z5_TRUNK_BY_NETWORK = Set.of(
RouteNetwork.CA_TRANSCANADA,
RouteNetwork.CA_PROVINCIAL_ARTERIAL,
RouteNetwork.US_INTERSTATE,
+ RouteNetwork.US_HIGHWAY,
+ RouteNetwork.GB_MOTORWAY,
+ RouteNetwork.GB_TRUNK,
+ RouteNetwork.IE_MOTORWAY,
+ RouteNetwork.IE_NATIONAL,
RouteNetwork.E_ROAD,
RouteNetwork.A_ROAD
);
+ private static final Set Z5_MOTORWAYS_BY_NETWORK = Set.of(
+ RouteNetwork.GB_TRUNK,
+ RouteNetwork.US_HIGHWAY
+ );
private static final Set CA_AB_PRIMARY_AS_ARTERIAL_BY_REF = Set.of(
"2", "3", "4"
);
@@ -194,7 +204,7 @@ public Transportation(Translations translations, PlanetilerConfig config, Stats
entry(FieldValues.CLASS_BUS_GUIDEWAY, 11),
entry(FieldValues.CLASS_SECONDARY, 9),
entry(FieldValues.CLASS_PRIMARY, 7),
- entry(FieldValues.CLASS_TRUNK, 5),
+ entry(FieldValues.CLASS_TRUNK, 6),
entry(FieldValues.CLASS_MOTORWAY, 4)
);
}
@@ -245,6 +255,40 @@ private static boolean isResidentialOrUnclassified(String highway) {
return "residential".equals(highway) || "unclassified".equals(highway);
}
+ private static boolean isTrunkForZ5(String highway, List routeRelations) {
+ // Allow trunk roads that are part of a nation's most important route network to show at z5
+ if (!"trunk".equals(highway)) {
+ return false;
+ }
+ return routeRelations.stream()
+ .map(RouteRelation::networkType)
+ .filter(Objects::nonNull)
+ .anyMatch(Z5_TRUNK_BY_NETWORK::contains);
+ }
+
+ private static boolean isMotorwayWithNetworkForZ4(List routeRelations) {
+ // All roads in network included in osm_national_network except gb-trunk and us-highway
+ return routeRelations.stream()
+ .map(RouteRelation::networkType)
+ .filter(Objects::nonNull)
+ .filter(nt -> !Z5_MOTORWAYS_BY_NETWORK.contains(nt))
+ .anyMatch(Z5_TRUNK_BY_NETWORK::contains);
+ }
+
+ private static boolean isMotorwayWoNetworkForZ4(List routeRelations) {
+ // All motorways without network (e.g. EU, Asia, South America)
+ return routeRelations.stream()
+ .map(RouteRelation::networkType)
+ .noneMatch(Objects::nonNull);
+ }
+
+ private static boolean isMotorwayForZ4(List routeRelations) {
+ if (isMotorwayWoNetworkForZ4(routeRelations)) {
+ return true;
+ }
+ return isMotorwayWithNetworkForZ4(routeRelations);
+ }
+
private static boolean isDrivewayOrParkingAisle(String service) {
return FieldValues.SERVICE_PARKING_AISLE.equals(service) || FieldValues.SERVICE_DRIVEWAY.equals(service);
}
@@ -296,11 +340,7 @@ public List preprocessOsmRelation(OsmElement.Relation relation)
String colour = coalesce(
nullIfEmpty(relation.getString("colour")), nullIfEmpty(relation.getString("ref:colour")));
- if ("e-road".equals(network)) {
- networkType = RouteNetwork.E_ROAD;
- } else if ("AsianHighway".equals(network)) {
- networkType = RouteNetwork.A_ROAD;
- } else if ("US:I".equals(network)) {
+ if ("US:I".equals(network)) {
networkType = RouteNetwork.US_INTERSTATE;
} else if ("US:US".equals(network)) {
networkType = RouteNetwork.US_HIGHWAY;
@@ -495,6 +535,7 @@ int getMinzoom(Tables.OsmHighwayLinestring element, String highwayClass) {
}
}
String highway = element.highway();
+ String construction = element.construction();
int minzoom;
if ("pier".equals(element.manMade())) {
@@ -508,18 +549,22 @@ int getMinzoom(Tables.OsmHighwayLinestring element, String highwayClass) {
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);
+ boolean z5trunk = isTrunkForZ5(highway, routeRelations);
+ // and if it is good for Z5, it may be good also for Z4 (see CLASS_MOTORWAY bellow):
+ String clazz = FieldValues.CLASS_TRUNK;
+ if (z5trunk && isMotorwayWithNetworkForZ4(routeRelations)) {
+ clazz = FieldValues.CLASS_MOTORWAY;
+ z5trunk = false;
+ }
+ yield (z5trunk) ? 5 : MINZOOMS.getOrDefault(clazz, Integer.MAX_VALUE);
}
+ case FieldValues.CLASS_MOTORWAY -> isMotorwayForZ4(routeRelations) ?
+ MINZOOMS.getOrDefault(FieldValues.CLASS_MOTORWAY, Integer.MAX_VALUE) : 5;
default -> MINZOOMS.getOrDefault(baseClass, Integer.MAX_VALUE);
};
}
- if (isLink(highway)) {
+ if (isLink(highway) || isLink(construction)) {
minzoom = Math.max(minzoom, 9);
}
return minzoom;
diff --git a/src/main/java/org/openmaptiles/layers/TransportationName.java b/src/main/java/org/openmaptiles/layers/TransportationName.java
index 2f82c2ec..2525f52c 100644
--- a/src/main/java/org/openmaptiles/layers/TransportationName.java
+++ b/src/main/java/org/openmaptiles/layers/TransportationName.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
diff --git a/src/main/java/org/openmaptiles/layers/Water.java b/src/main/java/org/openmaptiles/layers/Water.java
index 8a3d9a9c..e3683a1a 100644
--- a/src/main/java/org/openmaptiles/layers/Water.java
+++ b/src/main/java/org/openmaptiles/layers/Water.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -42,14 +42,24 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.expression.MultiExpression;
import com.onthegomap.planetiler.geo.GeometryException;
+import com.onthegomap.planetiler.geo.PolygonIndex;
+import com.onthegomap.planetiler.reader.SimpleFeature;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.stats.Stats;
import com.onthegomap.planetiler.util.Translations;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.util.GeometryFixer;
import org.openmaptiles.OpenMapTilesProfile;
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 for oceans and lakes in the {@code water} layer from source features.
@@ -62,7 +72,8 @@ public class Water implements
Tables.OsmWaterPolygon.Handler,
OpenMapTilesProfile.NaturalEarthProcessor,
OpenMapTilesProfile.OsmWaterPolygonProcessor,
- ForwardingProfile.FeaturePostProcessor {
+ ForwardingProfile.FeaturePostProcessor,
+ OpenMapTilesProfile.FinishHandler {
/*
* At low zoom levels, use natural earth for oceans and major lakes, and at high zoom levels
@@ -71,12 +82,22 @@ public class Water implements
* which infers ocean polygons by preprocessing all coastline elements.
*/
+ private static final Logger LOGGER = LoggerFactory.getLogger(Water.class);
+ // smallest NE lake is around 4.42E-13, smallest matching OSM lake is 9.34E-13, this is slightly bellow that
+ // and approx. 33% of OSM features are smaller than this, hence to save some CPU cycles:
+ private static final double OSM_ID_MATCH_AREA_LIMIT = Math.pow(4, -20);
+
private final MultiExpression.Index classMapping;
private final PlanetilerConfig config;
+ private final Stats stats;
+ private PolygonIndex neLakeIndex = PolygonIndex.create();
+ private final Map> neLakeNameMaps = new ConcurrentHashMap<>();
+ private final List neAllLakeInfos = new ArrayList<>();
public Water(Translations translations, PlanetilerConfig config, Stats stats) {
this.classMapping = FieldMappings.Class.index();
this.config = config;
+ this.stats = stats;
}
@Override
@@ -86,21 +107,60 @@ record WaterInfo(int minZoom, int maxZoom, String clazz) {}
case "ne_110m_ocean" -> new WaterInfo(0, 1, FieldValues.CLASS_OCEAN);
case "ne_50m_ocean" -> new WaterInfo(2, 4, FieldValues.CLASS_OCEAN);
case "ne_10m_ocean" -> new WaterInfo(5, 5, FieldValues.CLASS_OCEAN);
-
- // TODO: get OSM ID from low-zoom natural earth lakes
- case "ne_110m_lakes" -> new WaterInfo(0, 1, FieldValues.CLASS_LAKE);
- case "ne_50m_lakes" -> new WaterInfo(2, 3, FieldValues.CLASS_LAKE);
- case "ne_10m_lakes" -> new WaterInfo(4, 5, FieldValues.CLASS_LAKE);
default -> null;
};
if (info != null) {
- features.polygon(LAYER_NAME)
- .setBufferPixels(BUFFER_SIZE)
- .setZoomRange(info.minZoom, info.maxZoom)
- .setAttr(Fields.CLASS, info.clazz);
+ setupNeWaterFeature(features, info.minZoom, info.maxZoom, info.clazz, null);
+ return;
+ }
+
+ LakeInfo lakeInfo = switch (table) {
+ case "ne_110m_lakes" -> new LakeInfo(0, 1, FieldValues.CLASS_LAKE);
+ case "ne_50m_lakes" -> new LakeInfo(2, 3, FieldValues.CLASS_LAKE);
+ case "ne_10m_lakes" -> new LakeInfo(4, 5, FieldValues.CLASS_LAKE);
+ default -> null;
+ };
+ if (lakeInfo != null) {
+ try {
+ var geom = feature.worldGeometry();
+ if (geom.isValid()) {
+ lakeInfo.geom = geom;
+ } else {
+ LOGGER.debug("Fixing geometry of NE lake {}", feature.getLong("ne_id"));
+ lakeInfo.geom = GeometryFixer.fix(geom);
+ }
+ lakeInfo.name = feature.getString("name");
+ lakeInfo.neId = feature.getLong("ne_id");
+
+ var neLakeNameMap = neLakeNameMaps.computeIfAbsent(table, t -> new ConcurrentHashMap<>());
+
+ // need to externally synchronize inserts into ArrayList
+ synchronized (this) {
+ neAllLakeInfos.add(lakeInfo);
+ }
+ neLakeIndex.put(geom, lakeInfo);
+ if (lakeInfo.name != null) {
+ // on name collision, bigger lake gets on the name list
+ neLakeNameMap.merge(lakeInfo.name, lakeInfo,
+ (prev, next) -> next.geom.getArea() > prev.geom.getArea() ? next : prev);
+ }
+ } catch (GeometryException e) {
+ e.log(stats, "omt_water_ne",
+ "Error getting geometry for natural earth feature " + table + " " + feature.getTag("ogc_fid"));
+ // make sure we have this NE lake even if without OSM ID
+ setupNeWaterFeature(features, lakeInfo.minZoom, lakeInfo.maxZoom, lakeInfo.clazz, null);
+ }
}
}
+ private void setupNeWaterFeature(FeatureCollector features, int minZoom, int maxZoom, String clazz, Long osmId) {
+ features.polygon(LAYER_NAME)
+ .setBufferPixels(BUFFER_SIZE)
+ .setZoomRange(minZoom, maxZoom)
+ .setAttr(Fields.CLASS, clazz)
+ .setAttr(Fields.ID, osmId);
+ }
+
@Override
public void processOsmWater(SourceFeature feature, FeatureCollector features) {
features.polygon(LAYER_NAME)
@@ -121,6 +181,87 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) {
.setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0)
.setAttrWithMinzoom(Fields.BRUNNEL, Utils.brunnel(element.isBridge(), element.isTunnel()), 12)
.setAttr(Fields.CLASS, clazz);
+
+ try {
+ attemptNeLakeIdMapping(element);
+ } catch (GeometryException e) {
+ e.log(stats, "omt_water",
+ "Unable to add OSM ID to natural earth water feature", config.logJtsExceptions());
+ }
+ }
+ }
+
+ void attemptNeLakeIdMapping(Tables.OsmWaterPolygon element) throws GeometryException {
+ // if OSM lake is too small for Z6 (e.g. area bellow ~4px) we assume there is no matching NE lake
+ var geom = element.source().worldGeometry();
+ if (geom.getArea() < OSM_ID_MATCH_AREA_LIMIT) {
+ return;
+ }
+
+ if (!geom.isValid()) {
+ geom = GeometryFixer.fix(geom);
+ stats.dataError("omt_fix_water_before_ne_intersect");
+ LOGGER.debug("Fixing geometry of OSM element {} before attempt to add ID to natural earth water feature",
+ element.source().id());
+ }
+
+ // match by name:
+ boolean match = false;
+ if (element.name() != null) {
+ for (var map : neLakeNameMaps.values()) {
+ var lakeInfo = map.get(element.name());
+ if (lakeInfo != null) {
+ match = true;
+ fillOsmIdIntoNeLake(element, geom, lakeInfo, true);
+ }
+ }
+ }
+ if (match) {
+ return;
+ }
+
+ // match by intersection:
+ List items = neLakeIndex.getIntersecting(geom);
+ for (var lakeInfo : items) {
+ fillOsmIdIntoNeLake(element, geom, lakeInfo, false);
+ }
+ }
+
+ /*
+ * When we match lakes with `neLakeIndexes` then `intersetsCheckNeeded` should be `false`,
+ * otherwise `true`, to make sure we DO check the intersection but to avoid checking it twice.
+ */
+ void fillOsmIdIntoNeLake(Tables.OsmWaterPolygon element, Geometry geom, LakeInfo lakeInfo,
+ boolean intersetsCheckNeeded) {
+ final Geometry neGeom = lakeInfo.geom;
+ if (intersetsCheckNeeded && !neGeom.intersects(geom)) {
+ return;
+ }
+ final var intersection = neGeom.intersection(geom);
+
+ // Should match following in OpenMapTiles: Distinct on keeps just the first occurence -> order by 'area_ratio DESC'
+ // With a twist: NE geometry is always the same, hence we can make it a little bit faster by dropping "ratio"
+ // and compare only the intersection area: bigger area -> bigger ratio.
+ double area = intersection.getArea();
+ lakeInfo.mergeId(element.source().id(), area);
+ }
+
+ @Override
+ public void finish(String sourceName, FeatureCollector.Factory featureCollectors,
+ Consumer emit) {
+ if (OpenMapTilesProfile.OSM_SOURCE.equals(sourceName)) {
+ var timer = stats.startStage("ne_lakes");
+ for (var item : neAllLakeInfos) {
+ var features = featureCollectors.get(SimpleFeature.fromWorldGeometry(item.geom));
+ setupNeWaterFeature(features, item.minZoom, item.maxZoom, item.clazz, item.osmId);
+ for (var feature : features) {
+ emit.accept(feature);
+ }
+ }
+ neLakeNameMaps.clear();
+ neLakeIndex = null;
+ neAllLakeInfos.clear();
+ timer.stop();
}
}
@@ -128,4 +269,35 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) {
public List postProcess(int zoom, List items) throws GeometryException {
return items.size() > 1 ? FeatureMerge.mergeOverlappingPolygons(items, config.minFeatureSize(zoom)) : items;
}
+
+ /**
+ * Information to hold onto from processing an NE lake to determine OSM ID later.
+ */
+ private static class LakeInfo {
+ String name;
+ int minZoom;
+ int maxZoom;
+ String clazz;
+ Geometry geom;
+ Long osmId;
+ long neId;
+ double area;
+
+ public LakeInfo(int minZoom, int maxZoom, String clazz) {
+ this.name = null;
+ this.minZoom = minZoom;
+ this.maxZoom = maxZoom;
+ this.clazz = clazz;
+ this.osmId = null;
+ this.neId = -1;
+ this.area = 0;
+ }
+
+ public synchronized void mergeId(Long newId, double newArea) {
+ if (newArea > area) {
+ osmId = newId;
+ area = newArea;
+ }
+ }
+ }
}
diff --git a/src/main/java/org/openmaptiles/layers/WaterName.java b/src/main/java/org/openmaptiles/layers/WaterName.java
index cd3a1a8b..bd48b18d 100644
--- a/src/main/java/org/openmaptiles/layers/WaterName.java
+++ b/src/main/java/org/openmaptiles/layers/WaterName.java
@@ -1,5 +1,5 @@
/*
-Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors.
+Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors.
All rights reserved.
Code license: BSD 3-Clause License
@@ -85,6 +85,9 @@ public class WaterName implements
private static final Set SEA_OR_OCEAN_PLACE = Set.of("sea", "ocean");
private static final double IMPORTANT_MARINE_REGIONS_JOIN_DISTANCE =
GeoUtils.metersToPixelAtEquator(0, 50_000) / 256d;
+ private static final int MINZOOM_BAY = 9;
+ private static final int MINZOOM_LAKE = 3;
+ private static final int MINZOOM_SEA_AND_OCEAN = 0;
private final Translations translations;
// need to synchronize updates from multiple threads
private final LongObjectMap lakeCenterlines = Hppc.newLongObjectHashMap();
@@ -215,8 +218,7 @@ public void process(Tables.OsmMarinePoint element, FeatureCollector features) {
public void process(Tables.OsmWaterPolygon element, FeatureCollector features) {
if (nullIfEmpty(element.name()) != null) {
Geometry centerlineGeometry = lakeCenterlines.get(element.source().id());
- FeatureCollector.Feature feature;
- int minzoom = 9;
+ int minzoomCL = MINZOOM_BAY;
String place = element.place();
String clazz;
if ("bay".equals(element.natural())) {
@@ -225,27 +227,41 @@ public void process(Tables.OsmWaterPolygon element, FeatureCollector features) {
clazz = FieldValues.CLASS_SEA;
} else {
clazz = FieldValues.CLASS_LAKE;
- minzoom = 3;
+ minzoomCL = MINZOOM_LAKE;
}
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
+ // prefer lake centerline if it exists, but point will be also used if minzoom below 9 is calculated from area
+ // note: Here we're diverging from OpenMapTiles: For bays with minzoom (based on area) point is used between
+ // minzoom and Z8 and for Z9+ centerline is used, while OpenMaptiles sticks with points.
+ setupOsmWaterPolygonFeature(
+ element, features.geometry(LAYER_NAME, centerlineGeometry), clazz, minzoomCL)
+ .setMinPixelSizeBelowZoom(13, 6d * element.name().length());
+ }
+
+ int minzoom = place != null && SEA_OR_OCEAN_PLACE.contains(place) ? MINZOOM_SEA_AND_OCEAN : MINZOOM_LAKE;
+ if (centerlineGeometry == null || minzoom < minzoomCL) {
+ // use a label point inside the lake but ...
+ // ... if centerline already created, adjust maxzoom here to make sure we're not having both at same zoom level
+ int maxzoom = centerlineGeometry != null ? minzoomCL - 1 : 14;
+ setupOsmWaterPolygonFeature(element, features.pointOnSurface(LAYER_NAME), clazz, minzoom)
+ .setMaxZoom(maxzoom)
+ // Show a label if a water feature covers at least 1/4 of a tile or z14+
+ .setMinPixelSizeBelowZoom(13, 128);
}
- feature
- .setAttr(Fields.CLASS, clazz)
- .setBufferPixels(BUFFER_SIZE)
- .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations))
- .setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0)
- .setMinZoom(minzoom);
}
}
+ private FeatureCollector.Feature setupOsmWaterPolygonFeature(Tables.OsmWaterPolygon element,
+ FeatureCollector.Feature output, String clazz, int minzoom) {
+ output
+ .setAttr(Fields.CLASS, clazz)
+ .setBufferPixels(BUFFER_SIZE)
+ .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations))
+ .setAttr(Fields.INTERMITTENT, element.isIntermittent() ? 1 : 0)
+ .setMinZoom(minzoom);
+ return output;
+ }
+
private record NaturalEarthRegion(
Geometry geometry,
int scalerank
diff --git a/src/test/java/org/openmaptiles/layers/BoundaryTest.java b/src/test/java/org/openmaptiles/layers/BoundaryTest.java
index 95e56d09..1aa9e676 100644
--- a/src/test/java/org/openmaptiles/layers/BoundaryTest.java
+++ b/src/test/java/org/openmaptiles/layers/BoundaryTest.java
@@ -104,6 +104,7 @@ void testNaturalEarthCountryBoundaries() {
assertFeatures(0, List.of(Map.of(
"_layer", "boundary",
"_type", "line",
+ "_minzoom", 4,
"admin_level", 2
)), process(SimpleFeature.create(
newLineString(0, 0, 1, 1),
@@ -115,10 +116,65 @@ void testNaturalEarthCountryBoundaries() {
0
)));
+ assertFeatures(0, List.of(Map.of(
+ "_layer", "boundary",
+ "_type", "line",
+ "_minzoom", 1,
+ "admin_level", 2
+ )), process(SimpleFeature.create(
+ newLineString(0, 0, 1, 1),
+ Map.of(
+ "featurecla", "Disputed (please verify)",
+ "adm0_left", "South Sudan",
+ "adm0_right", "Kenya"
+ ),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_admin_0_boundary_lines_land",
+ 0
+ )));
+
assertFeatures(0, List.of(), process(SimpleFeature.create(
newLineString(0, 0, 1, 1),
Map.of(
- "featurecla", "Lease Limit"
+ "featurecla", "Lease limit"
+ ),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_admin_0_boundary_lines_land",
+ 0
+ )));
+ }
+
+ @Test
+ void testNaturalEarthCountryKeSsBoundaryReversed() {
+ assertFeatures(0, List.of(Map.of(
+ "_layer", "boundary",
+ "_minzoom", 1,
+ "admin_level", 2
+ )), process(SimpleFeature.create(
+ newLineString(0, 0, 1, 1),
+ Map.of(
+ "featurecla", "Disputed (please verify)",
+ "adm0_right", "South Sudan",
+ "adm0_left", "Kenya"
+ ),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_admin_0_boundary_lines_land",
+ 0
+ )));
+ }
+
+ @Test
+ void testNaturalEarthCountryNotKeSsBoundary() {
+ assertFeatures(0, List.of(Map.of(
+ "_layer", "boundary",
+ "_minzoom", 4,
+ "admin_level", 2
+ )), process(SimpleFeature.create(
+ newLineString(0, 0, 1, 1),
+ Map.of(
+ "featurecla", "Disputed (please verify)",
+ "adm0_left", "South Sudan",
+ "adm0_right", "Uganda"
),
OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
"ne_10m_admin_0_boundary_lines_land",
@@ -382,6 +438,29 @@ void testOsmBoundaryDisputedFromWay() {
));
}
+ @Test
+ void testOsmAl2BoundaryDisputedMinZoom() {
+ var relation = new OsmElement.Relation(1);
+ relation.setTag("type", "boundary");
+ relation.setTag("admin_level", "2");
+ relation.setTag("boundary", "administrative");
+
+ assertFeatures(3, List.of(Map.of(
+ "_layer", "boundary",
+ "_type", "line",
+ "_minzoom", 5,
+
+ "disputed", 1,
+ "maritime", 0,
+ "admin_level", 2
+ )), process(lineFeatureWithRelation(
+ profile.preprocessOsmRelation(relation),
+ Map.of(
+ "disputed", "yes"
+ ))
+ ));
+ }
+
@Test
void testCountryBoundaryEmittedIfNoName() {
var relation = new OsmElement.Relation(1);
diff --git a/src/test/java/org/openmaptiles/layers/LanduseTest.java b/src/test/java/org/openmaptiles/layers/LanduseTest.java
index c3449364..57d7ea8e 100644
--- a/src/test/java/org/openmaptiles/layers/LanduseTest.java
+++ b/src/test/java/org/openmaptiles/layers/LanduseTest.java
@@ -19,7 +19,8 @@ void testNaturalEarthUrbanAreas() {
assertFeatures(0, List.of(Map.of(
"_layer", "landuse",
"class", "residential",
- "_buffer", 4d
+ "_buffer", 4d,
+ "_minzoom", 4
)), process(SimpleFeature.create(
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
Map.of("scalerank", 1.9),
@@ -27,7 +28,12 @@ void testNaturalEarthUrbanAreas() {
"ne_50m_urban_areas",
0
)));
- assertFeatures(0, List.of(), process(SimpleFeature.create(
+ assertFeatures(0, List.of(Map.of(
+ "_layer", "landuse",
+ "class", "residential",
+ "_buffer", 4d,
+ "_minzoom", 5
+ )), process(SimpleFeature.create(
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
Map.of("scalerank", 2.1),
OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
diff --git a/src/test/java/org/openmaptiles/layers/TransportationTest.java b/src/test/java/org/openmaptiles/layers/TransportationTest.java
index 2210aa65..729b3b05 100644
--- a/src/test/java/org/openmaptiles/layers/TransportationTest.java
+++ b/src/test/java/org/openmaptiles/layers/TransportationTest.java
@@ -353,7 +353,7 @@ void testDuplicateRoute() {
"_layer", "transportation",
"class", "trunk",
"network", "us-state",
- "_minzoom", 5
+ "_minzoom", 6
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
@@ -580,8 +580,8 @@ void testPolishHighwayIssue165() {
"_layer", "transportation_name",
"class", "trunk",
"name", "",
- "ref", "E 28",
- "ref_length", 4,
+ "ref", "S7",
+ "ref_length", 2,
"route_1_network", "e-road",
"route_1_ref", "E 28",
"route_2_network", "e-road",
@@ -670,6 +670,26 @@ void testInterstateMotorwayWithoutWayInfo() {
)), features);
}
+ @Test
+ void testMotorwayRoadConstruction() {
+ assertFeatures(13, List.of(Map.of(
+ "_layer", "transportation",
+ "class", "motorway_construction",
+ "oneway", 1,
+ "_minzoom", 4
+ ), Map.of(
+ "_layer", "transportation_name",
+ "name", "D4 II/118 – Milín",
+ "class", "motorway_construction",
+ "_minzoom", 6
+ )), process(lineFeature(Map.of(
+ "highway", "construction",
+ "construction", "motorway",
+ "name", "D4 II/118 – Milín",
+ "oneway", "yes"
+ ))));
+ }
+
@Test
void testPrimaryRoadConstruction() {
assertFeatures(13, List.of(Map.of(
@@ -1154,7 +1174,7 @@ void testTransCanadaProvincialCaOnPrimaryRefOther() {
"_layer", "transportation",
"class", "trunk",
"network", "ca-provincial",
- "_minzoom", 5
+ "_minzoom", 6
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
@@ -1208,7 +1228,7 @@ void testTransCanadaProvincialCaMbPthRefOther() {
"_layer", "transportation",
"class", "trunk",
"network", "ca-provincial",
- "_minzoom", 5
+ "_minzoom", 6
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
@@ -1262,7 +1282,7 @@ void testTransCanadaProvincialCaAbPrimaryRefOther() {
"_layer", "transportation",
"class", "trunk",
"network", "ca-provincial",
- "_minzoom", 5
+ "_minzoom", 6
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
@@ -1316,7 +1336,7 @@ void testTransCanadaProvincialCaBcRefOther() {
"_layer", "transportation",
"class", "trunk",
"network", "ca-provincial",
- "_minzoom", 5
+ "_minzoom", 6
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
@@ -1341,7 +1361,7 @@ void testTransCanadaProvincialCaOther() {
assertFeatures(13, List.of(Map.of(
"_layer", "transportation",
"class", "trunk",
- "_minzoom", 5
+ "_minzoom", 6
)), features);
boolean caProvPresent = StreamSupport.stream(features.spliterator(), false)
.flatMap(f -> f.getAttrsAtZoom(13).entrySet().stream())
@@ -1627,7 +1647,7 @@ void testIrelandTrunk() {
assertFeatures(13, List.of(Map.of(
"_layer", "transportation",
"class", "trunk",
- "_minzoom", 5
+ "_minzoom", 4
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
@@ -2121,19 +2141,20 @@ void testARoad() {
FeatureCollector features = process(lineFeatureWithRelation(
profile.preprocessOsmRelation(rel),
Map.of(
- "highway", "trunk"
+ "highway", "trunk",
+ "name", "National Highway 7",
+ "ref", "7"
)));
assertFeatures(13, List.of(Map.of(
"_layer", "transportation",
"class", "trunk",
- "network", "a-road",
- "_minzoom", 4
+ "_minzoom", 6
), Map.of(
"_layer", "transportation_name",
"class", "trunk",
- "ref", "AH11",
- "network", "a-road"
+ "ref", "7",
+ "route_1_ref", "AH11"
)), features);
}
@@ -2143,24 +2164,24 @@ void testERoad() {
rel.setTag("type", "route");
rel.setTag("route", "road");
rel.setTag("network", "e-road");
- rel.setTag("ref", "E 50");
+ rel.setTag("ref", "E 77");
FeatureCollector features = process(lineFeatureWithRelation(
profile.preprocessOsmRelation(rel),
Map.of(
- "highway", "motorway"
+ "highway", "motorway",
+ "ref", "S7"
)));
assertFeatures(13, List.of(Map.of(
"_layer", "transportation",
"class", "motorway",
- "network", "e-road",
"_minzoom", 4
), Map.of(
"_layer", "transportation_name",
"class", "motorway",
- "ref", "E 50",
- "network", "e-road"
+ "ref", "S7",
+ "route_1_ref", "E 77"
)), features);
}
}
diff --git a/src/test/java/org/openmaptiles/layers/WaterNameTest.java b/src/test/java/org/openmaptiles/layers/WaterNameTest.java
index 02b7c984..6ee686b6 100644
--- a/src/test/java/org/openmaptiles/layers/WaterNameTest.java
+++ b/src/test/java/org/openmaptiles/layers/WaterNameTest.java
@@ -62,7 +62,7 @@ void testWaterNameLakeline() {
"_maxzoom", 14,
"_minpixelsize", "waterway".length() * 6d
)), process(SimpleFeature.create(
- GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
+ GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1E-7))),
new HashMap<>(Map.of(
"name", "waterway",
"name:es", "waterway es",
@@ -112,7 +112,7 @@ void testWaterNameMultipleLakelines() {
"_maxzoom", 14,
"_minpixelsize", "waterway".length() * 6d
)), process(SimpleFeature.create(
- GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
+ GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1E-7))),
new HashMap<>(Map.of(
"name", "waterway",
"name:es", "waterway es",
@@ -126,7 +126,7 @@ void testWaterNameMultipleLakelines() {
}
@Test
- void testWaterNameBay() {
+ void testWaterNameBaySmall() {
assertFeatures(11, List.of(), process(SimpleFeature.create(
newLineString(0, 0, 1, 1),
new HashMap<>(Map.of(
@@ -146,6 +146,55 @@ void testWaterNameBay() {
"_minzoom", 9,
"_maxzoom", 14,
"_minpixelsize", "bay".length() * 6d
+ ), Map.of(
+ "name", "bay",
+ "name:es", "bay es",
+
+ "_layer", "water_name",
+ "_type", "point",
+ "_minzoom", 3,
+ "_maxzoom", 8,
+ "_minpixelsize", 128d
+ )), process(SimpleFeature.create(
+ GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1E-7))),
+ new HashMap<>(Map.of(
+ "name", "bay",
+ "name:es", "bay es",
+ "natural", "bay"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 10
+ )));
+ }
+
+ @Test
+ void testWaterNameBayBig() {
+ 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",
+ "_minzoom", 9,
+ "_maxzoom", 14
+ ), Map.of(
+ "name", "bay",
+ "name:es", "bay es",
+
+ "_layer", "water_name",
+ "_type", "point",
+ "_minzoom", 3,
+ "_maxzoom", 8
)), process(SimpleFeature.create(
GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))),
new HashMap<>(Map.of(
diff --git a/src/test/java/org/openmaptiles/layers/WaterTest.java b/src/test/java/org/openmaptiles/layers/WaterTest.java
index ee900dc5..e413538e 100644
--- a/src/test/java/org/openmaptiles/layers/WaterTest.java
+++ b/src/test/java/org/openmaptiles/layers/WaterTest.java
@@ -2,8 +2,10 @@
import static com.onthegomap.planetiler.TestUtils.rectangle;
+import com.onthegomap.planetiler.FeatureCollector;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.reader.SimpleFeature;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -16,7 +18,7 @@ class WaterTest extends AbstractLayerTest {
@Test
void testWaterNaturalEarth() {
assertFeatures(0, List.of(Map.of(
- "class", "lake",
+ "class", "ocean",
"intermittent", "",
"_layer", "water",
"_type", "polygon",
@@ -25,49 +27,353 @@ void testWaterNaturalEarth() {
rectangle(0, 10),
Map.of(),
OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
- "ne_110m_lakes",
+ "ne_110m_ocean",
0
)));
- assertFeatures(0, List.of(Map.of(
+ assertFeatures(6, List.of(Map.of(
"class", "ocean",
- "intermittent", "",
"_layer", "water",
"_type", "polygon",
- "_minzoom", 0
+ "_maxzoom", 5
)), process(SimpleFeature.create(
rectangle(0, 10),
Map.of(),
OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
- "ne_110m_ocean",
+ "ne_10m_ocean",
0
)));
+ }
- assertFeatures(6, List.of(Map.of(
+ @Test
+ void testLakeNaturalEarthByIntersection() {
+ final var polygon = rectangle(0, 0.1);
+ // NE lakes:
+ process(SimpleFeature.create(
+ polygon,
+ Map.of(),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_110m_lakes",
+ 0
+ ));
+ process(SimpleFeature.create(
+ polygon,
+ Map.of(),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_lakes",
+ 0
+ ));
+ // OSM lake to take the ID from:
+ process(SimpleFeature.create(
+ polygon,
+ new HashMap<>(Map.of(
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
+
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
"class", "lake",
+ "intermittent", "",
+ "id", 123L,
+ "_layer", "water",
+ "_type", "polygon",
+ "_minzoom", 0,
+ "_maxzoom", 1
+ ), Map.of(
+ "class", "lake",
+ "intermittent", "",
+ "id", 123L,
"_layer", "water",
"_type", "polygon",
+ "_minzoom", 4,
"_maxzoom", 5
- )), process(SimpleFeature.create(
- rectangle(0, 10),
+ )), features);
+ }
+
+ @Test
+ void testLakeNaturalEarthIntersectionMiss() {
+ final var polygon1 = rectangle(0, 0.1);
+ final var polygon2 = rectangle(0.2, 0.3);
+ // NE lakes:
+ process(SimpleFeature.create(
+ polygon1,
+ Map.of(),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_110m_lakes",
+ 0
+ ));
+ process(SimpleFeature.create(
+ polygon1,
Map.of(),
OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
"ne_10m_lakes",
0
- )));
+ ));
+ // OSM lake to take the ID from:
+ process(SimpleFeature.create(
+ polygon2,
+ new HashMap<>(Map.of(
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
- assertFeatures(6, List.of(Map.of(
- "class", "ocean",
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
+ "class", "lake",
+ "id", "",
+ "_layer", "water",
+ "_type", "polygon"
+ ), Map.of(
+ "class", "lake",
+ "id", "",
+ "_layer", "water",
+ "_type", "polygon"
+ )), features);
+ }
+
+ @Test
+ void testLakeNaturalEarthByBiggerIntersection() {
+ final var polygon1 = rectangle(0, 0.1);
+ final var polygon2 = rectangle(0, 0.2);
+ // NE lakes:
+ process(SimpleFeature.create(
+ polygon2,
+ Map.of(),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_110m_lakes",
+ 0
+ ));
+ process(SimpleFeature.create(
+ polygon2,
+ Map.of(),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_lakes",
+ 0
+ ));
+ // OSM lakes to take the ID from:
+ process(SimpleFeature.create(
+ polygon1,
+ new HashMap<>(Map.of(
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
+ process(SimpleFeature.create(
+ polygon2,
+ new HashMap<>(Map.of(
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 234
+ ));
+
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
+ "class", "lake",
+ "intermittent", "",
+ "id", 234L,
"_layer", "water",
"_type", "polygon",
+ "_minzoom", 0,
+ "_maxzoom", 1
+ ), Map.of(
+ "class", "lake",
+ "intermittent", "",
+ "id", 234L,
+ "_layer", "water",
+ "_type", "polygon",
+ "_minzoom", 4,
"_maxzoom", 5
- )), process(SimpleFeature.create(
- rectangle(0, 10),
- Map.of(),
+ )), features);
+ }
+
+ @Test
+ void testLakeNaturalEarthByName() {
+ final var polygon = rectangle(0, 0.1);
+ // NE lakes:
+ process(SimpleFeature.create(
+ polygon,
+ Map.of("name", "Test Lake"),
OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
- "ne_10m_ocean",
+ "ne_50m_lakes",
0
- )));
+ ));
+ process(SimpleFeature.create(
+ polygon,
+ Map.of("name", "Test Lake"),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_lakes",
+ 0
+ ));
+ // OSM lake to take the ID from:
+ process(SimpleFeature.create(
+ polygon,
+ new HashMap<>(Map.of(
+ "name", "Test Lake",
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
+
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
+ "class", "lake",
+ "id", 123L,
+ "_layer", "water",
+ "_minzoom", 2,
+ "_maxzoom", 3
+ ), Map.of(
+ "class", "lake",
+ "id", 123L,
+ "_layer", "water",
+ "_minzoom", 4,
+ "_maxzoom", 5
+ )), features);
+ }
+
+ @Test
+ void testLakeNaturalEarthByNameIntersectionMiss() {
+ final var polygon1 = rectangle(0, 0.1);
+ final var polygon2 = rectangle(0.2, 0.3);
+ // NE lake:
+ process(SimpleFeature.create(
+ polygon1,
+ Map.of("name", "Test Lake"),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_50m_lakes",
+ 0
+ ));
+ // OSM lake to take the ID from:
+ process(SimpleFeature.create(
+ polygon2,
+ new HashMap<>(Map.of(
+ "name", "Test Lake",
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
+
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
+ "class", "lake",
+ "id", "",
+ "_layer", "water"
+ )), features);
+ }
+
+ @Test
+ void testLakeNaturalEarthByNameAndBiggerIntersection() {
+ final var polygon1 = rectangle(0, 0.1);
+ final var polygon2 = rectangle(0, 0.2);
+ // NE lake:
+ process(SimpleFeature.create(
+ polygon2,
+ Map.of("name", "Test Lake"),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_50m_lakes",
+ 0
+ ));
+ // OSM lakes to take the ID from:
+ process(SimpleFeature.create(
+ polygon1,
+ new HashMap<>(Map.of(
+ "name", "Test Lake",
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
+ process(SimpleFeature.create(
+ polygon2,
+ new HashMap<>(Map.of(
+ "name", "Test Lake",
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 234
+ ));
+
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
+ "class", "lake",
+ "id", 234L,
+ "_layer", "water"
+ )), features);
+ }
+
+ @Test
+ void testLakeNaturalEarthByNameWithColision() {
+ final var polygonSmaller = rectangle(0, 0.1);
+ final var polygonBigger = rectangle(0, 0.2);
+ // NE lakes:
+ process(SimpleFeature.create(
+ polygonSmaller,
+ Map.of("name", "Test Lake"),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_lakes",
+ 0
+ ));
+ process(SimpleFeature.create(
+ polygonBigger,
+ Map.of("name", "Test Lake"),
+ OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
+ "ne_10m_lakes",
+ 0
+ ));
+ // OSM lake to take the ID from:
+ process(SimpleFeature.create(
+ polygonBigger,
+ new HashMap<>(Map.of(
+ "name", "Test Lake",
+ "natural", "water",
+ "water", "reservoir"
+ )),
+ OpenMapTilesProfile.OSM_SOURCE,
+ null,
+ 123
+ ));
+
+ List features = new ArrayList<>();
+ profile.finish(OpenMapTilesProfile.OSM_SOURCE, new FeatureCollector.Factory(params, stats), features::add);
+ assertFeatures(0, List.of(Map.of(
+ "class", "lake",
+ "id", "",
+ "_layer", "water"
+ ), Map.of(
+ "class", "lake",
+ "id", 123L,
+ "_layer", "water"
+ )), features);
}
@Test
@@ -255,28 +561,7 @@ void testOceanZoomLevels() {
@Test
void testLakeZoomLevels() {
- assertCoversZoomRange(0, 14, "water",
- process(SimpleFeature.create(
- rectangle(0, 10),
- Map.of(),
- OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
- "ne_110m_lakes",
- 0
- )),
- process(SimpleFeature.create(
- rectangle(0, 10),
- Map.of(),
- OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
- "ne_50m_lakes",
- 0
- )),
- process(SimpleFeature.create(
- rectangle(0, 10),
- Map.of(),
- OpenMapTilesProfile.NATURAL_EARTH_SOURCE,
- "ne_10m_lakes",
- 0
- )),
+ assertCoversZoomRange(6, 14, "water",
process(SimpleFeature.create(
rectangle(0, 10),
Map.of(