test, int expectedMatchCount)
+ throws Exception {
+ var sf =
+ SimpleFeature.createFakeOsmFeature(newLineString(0, 0, 1, 0, 1, 1), tags, "osm", null, 1, emptyList());
+ testFeature(pathFunction, schemaFilename, sf,
+ ConfiguredFeatureTest::linestringFeatureCollector, test, expectedMatchCount);
+ }
+
+ @Test
+ void testStaticAttributeTest() throws Exception {
+ testPolygon(TEST_RESOURCE, "static_attribute.yml", waterTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals("aTestConstantValue", attr.get("natural"));
+ }, 1);
+ }
+
+ @Test
+ void testTagValueAttributeTest() throws Exception {
+ testPolygon(TEST_RESOURCE, "tag_attribute.yml", waterTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals("water", attr.get("natural"));
+ }, 1);
+ }
+
+ @Test
+ void testTagIncludeAttributeTest() throws Exception {
+ testPolygon(TEST_RESOURCE, "tag_include.yml", waterTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals("ok", attr.get("test_include"));
+ assertFalse(attr.containsKey("test_exclude"));
+ }, 1);
+ }
+
+ @Test
+ void testZoomAttributeTest() throws Exception {
+ testPolygon(TEST_RESOURCE, "tag_include.yml", waterTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals("test_zoom_value", attr.get("test_zoom_tag"));
+
+ attr = f.getAttrsAtZoom(11);
+ assertNotEquals("test_zoom_value", attr.get("test_zoom_tag"));
+
+ attr = f.getAttrsAtZoom(9);
+ assertNotEquals("test_zoom_value", attr.get("test_zoom_tag"));
+ }, 1);
+ }
+
+ @Test
+ void testTagHighwayLinestringTest() throws Exception {
+ testLinestring(TEST_RESOURCE, "road_motorway.yml", motorwayTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals("motorway", attr.get("highway"));
+ }, 1);
+ }
+
+ @Test
+ void testTagTypeConversionTest() throws Exception {
+ testLinestring(TEST_RESOURCE, "road_motorway.yml", motorwayTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+
+ assertTrue(attr.containsKey("layer"), "Produce attribute layer");
+ assertTrue(attr.containsKey("bridge"), "Produce attribute bridge");
+ assertTrue(attr.containsKey("tunnel"), "Produce attribute tunnel");
+
+ assertEquals(1L, attr.get("layer"), "Extract layer as LONG");
+ assertEquals(true, attr.get("bridge"), "Extract bridge as tagValue BOOLEAN");
+ assertEquals(true, attr.get("tunnel"), "Extract tunnel as constantValue BOOLEAN");
+ }, 1);
+ }
+
+ @Test
+ void testZoomFilterAttributeTest() throws Exception {
+ testLinestring(TEST_RESOURCE, "road_motorway.yml", motorwayTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertTrue(attr.containsKey("bridge"), "Produce attribute bridge at z14");
+
+ attr = f.getAttrsAtZoom(10);
+ assertFalse(attr.containsKey("bridge"), "Don't produce attribute bridge at z10");
+ }, 1);
+ }
+
+ @Test
+ void testZoomFilterConditionalTest() throws Exception {
+ testLinestring(TEST_RESOURCE, "zoom_filter.yml", motorwayTags, f -> {
+ var attr = f.getAttrsAtZoom(4);
+ assertEquals("motorway", attr.get("highway"), "Produce attribute highway at z4");
+ }, 1);
+
+ testLinestring(TEST_RESOURCE, "zoom_filter.yml", trunkTags, f -> {
+ assertEquals(5, f.getMinZoom());
+ var attr = f.getAttrsAtZoom(5);
+ assertEquals("trunk", attr.get("highway"), "Produce highway=trunk at z5");
+ assertNull(attr.get("toll"), "Skip toll at z5");
+
+ attr = f.getAttrsAtZoom(6);
+ assertEquals("trunk", attr.get("highway"), "Produce highway=trunk at z6");
+
+ attr = f.getAttrsAtZoom(8);
+ assertEquals("yes", attr.get("toll"), "render toll at z8");
+ }, 1);
+
+ testLinestring(TEST_RESOURCE, "zoom_filter.yml", primaryTags, f -> {
+ var attr = f.getAttrsAtZoom(6);
+ assertNull(attr.get("highway"), "Skip highway=primary at z6");
+ assertNull(attr.get("lanes"));
+
+ attr = f.getAttrsAtZoom(7);
+ assertEquals("primary", attr.get("highway"), "Produce highway=primary at z7");
+ assertNull(attr.get("lanes"));
+
+ attr = f.getAttrsAtZoom(12);
+ assertEquals("primary", attr.get("highway"), "Produce highway=primary at z12");
+ assertEquals(2L, attr.get("lanes"));
+ }, 1);
+ }
+
+ @Test
+ void testAllValuesInKey() throws Exception {
+ //Show that a key in includeWhen with no values matches all values
+ testPolygon(SAMPLE_RESOURCE, "highway_areas.yml", highwayAreaTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals(true, attr.get("bridge"), "Produce bridge attribute");
+ assertEquals("motorway", attr.get("highway"), "Produce highway area attribute");
+ assertEquals("asphalt", attr.get("surface"), "Produce surface attribute");
+ assertEquals(1L, attr.get("layer"), "Produce layer attribute");
+ }, 1);
+ }
+
+ @Test
+ void testInputMapping() throws Exception {
+ //Show that a key in includeWhen with no values matches all values
+ testLinestring(TEST_RESOURCE, "data_type_attributes.yml", inputMappingTags, f -> {
+ var attr = f.getAttrsAtZoom(14);
+ assertEquals(true, attr.get("b_type"), "Produce boolean");
+ assertEquals("string_val", attr.get("s_type"), "Produce string");
+ assertEquals(1, attr.get("d_type"), "Produce direction");
+ assertEquals(1L, attr.get("l_type"), "Produce long");
+
+ assertEquals("yes", attr.get("intermittent"), "Produce raw attribute");
+ assertEquals(true, attr.get("is_intermittent"), "Produce and rename boolean");
+ assertEquals(true, attr.get("bridge"), "Produce boolean from full structure");
+ }, 1);
+ }
+
+ @Test
+ void testGeometryTypeMismatch() throws Exception {
+ //Validate that a schema that filters on lines does not match on a polygon feature
+ var sf =
+ SimpleFeature.createFakeOsmFeature(newPolygon(0, 0, 1, 0, 1, 1, 0, 0), motorwayTags, "osm", null, 1,
+ emptyList());
+
+ testFeature(TEST_RESOURCE, "road_motorway.yml", sf,
+ ConfiguredFeatureTest::linestringFeatureCollector, f -> {
+ }, 0);
+ }
+
+ @Test
+ void testSourceTypeMismatch() throws Exception {
+ //Validate that a schema only matches on the specified data source
+ var sf =
+ SimpleFeature.createFakeOsmFeature(newLineString(0, 0, 1, 0, 1, 1, 0, 0), highwayAreaTags, "not_osm", null, 1,
+ emptyList());
+
+ testFeature(SAMPLE_RESOURCE, "highway_areas.yml", sf,
+ ConfiguredFeatureTest::linestringFeatureCollector, f -> {
+ }, 0);
+ }
+
+ @Test
+ void testInvalidSchemas() throws Exception {
+ testInvalidSchema("bad_geometry_type.yml", "Profile defined with invalid geometry type");
+ testInvalidSchema("no_layers.yml", "Profile defined with no layers");
+ }
+
+ private void testInvalidSchema(String filename, String message) {
+ assertThrows(RuntimeException.class, () -> loadConfig(TEST_INVALID_RESOURCE, filename), message);
+ }
+}
diff --git a/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/ConfiguredMapTest.java b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/ConfiguredMapTest.java
new file mode 100644
index 0000000000..fe179519bf
--- /dev/null
+++ b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/ConfiguredMapTest.java
@@ -0,0 +1,103 @@
+package com.onthegomap.planetiler.custommap;
+
+import static com.onthegomap.planetiler.TestUtils.assertContains;
+import static com.onthegomap.planetiler.custommap.util.VerifyMonaco.MONACO_BOUNDS;
+import static com.onthegomap.planetiler.util.Gzip.gunzip;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.onthegomap.planetiler.TestUtils;
+import com.onthegomap.planetiler.VectorTile;
+import com.onthegomap.planetiler.custommap.util.TestConfigurableUtils;
+import com.onthegomap.planetiler.mbtiles.Mbtiles;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Set;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Polygon;
+
+/**
+ * End-to-end tests for custommap generation.
+ *
+ * Generates an entire map for the smallest openstreetmap extract available (Monaco) and asserts that expected output
+ * features exist
+ */
+class ConfiguredMapTest {
+
+ @TempDir
+ static Path tmpDir;
+ private static Mbtiles mbtiles;
+
+ @BeforeAll
+ public static void runPlanetiler() throws Exception {
+ Path dbPath = tmpDir.resolve("output.mbtiles");
+ ConfiguredMapMain.main(
+ "generate-custom",
+ // Use local data extracts instead of downloading
+ "--schema=" + TestConfigurableUtils.pathToSample("owg_simple.yml"),
+ "--osm_path=" + TestUtils.pathToResource("monaco-latest.osm.pbf"),
+ "--water_polygons_path=" + TestUtils.pathToResource("water-polygons-split-3857.zip"),
+
+ // Override temp dir location
+ "--tmp=" + tmpDir,
+
+ // Override output location
+ "--mbtiles=" + dbPath
+ );
+ mbtiles = Mbtiles.newReadOnlyDatabase(dbPath);
+ }
+
+ @AfterAll
+ public static void close() throws IOException {
+ mbtiles.close();
+ }
+
+ @Test
+ void testMetadata() {
+ Map metadata = mbtiles.metadata().getAll();
+ assertEquals("OWG Simple Schema", metadata.get("name"));
+ assertEquals("0", metadata.get("minzoom"));
+ assertEquals("14", metadata.get("maxzoom"));
+ assertEquals("baselayer", metadata.get("type"));
+ assertEquals("pbf", metadata.get("format"));
+ assertEquals("7.40921,43.72335,7.44864,43.75169", metadata.get("bounds"));
+ assertEquals("7.42892,43.73752,14", metadata.get("center"));
+ assertContains("Simple", metadata.get("description"));
+ assertContains("www.openstreetmap.org/copyright", metadata.get("attribution"));
+ }
+
+ @Test
+ void ensureValidGeometries() throws Exception {
+ Set parsedTiles = TestUtils.getAllTiles(mbtiles);
+ for (var tileEntry : parsedTiles) {
+ var decoded = VectorTile.decode(gunzip(tileEntry.bytes()));
+ for (VectorTile.Feature feature : decoded) {
+ TestUtils.validateGeometry(feature.geometry().decode());
+ }
+ }
+ }
+
+ // @Test --TODO FIX after adding water layer
+ void testContainsOceanPolyons() {
+ assertMinFeatures("water", Map.of(
+ "natural", "water"
+ ), 0, 1, Polygon.class);
+ }
+
+ @Test
+ void testRoad() {
+ assertMinFeatures("road", Map.of(
+ "highway", "primary"
+ ), 14, 200, LineString.class);
+ }
+
+ private static void assertMinFeatures(String layer, Map attrs, int zoom,
+ int expected, Class extends Geometry> clazz) {
+ TestUtils.assertMinFeatureCount(mbtiles, layer, zoom, attrs, MONACO_BOUNDS, expected, clazz);
+ }
+}
diff --git a/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/SchemaYAMLLoadTest.java b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/SchemaYAMLLoadTest.java
new file mode 100644
index 0000000000..e0c7919023
--- /dev/null
+++ b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/SchemaYAMLLoadTest.java
@@ -0,0 +1,38 @@
+package com.onthegomap.planetiler.custommap;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.jupiter.api.Test;
+
+class SchemaYAMLLoadTest {
+
+ /**
+ * Test to ensure that all bundled schemas load to POJOs.
+ *
+ * @throws Exception
+ */
+ @Test
+ void testSchemaLoad() throws IOException {
+ testSchemasInFolder(Paths.get("src", "main", "resources", "samples"));
+ testSchemasInFolder(Paths.get("src", "test", "resources", "validSchema"));
+ }
+
+ private void testSchemasInFolder(Path path) throws IOException {
+ var schemaFiles = Files.walk(path)
+ .filter(p -> p.getFileName().toString().endsWith(".yml"))
+ .toList();
+
+ assertFalse(schemaFiles.isEmpty(), "No files found");
+
+ for (Path schemaFile : schemaFiles) {
+ var schemaConfig = ConfiguredMapMain.loadConfig(schemaFile);
+ assertNotNull(schemaConfig, () -> "Failed to unmarshall " + schemaFile.toString());
+ assertNotNull(new ConfiguredProfile(schemaConfig), () -> "Failed to load profile from " + schemaFile.toString());
+ }
+ }
+}
diff --git a/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/util/TestConfigurableUtils.java b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/util/TestConfigurableUtils.java
new file mode 100644
index 0000000000..1c7f003c5d
--- /dev/null
+++ b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/util/TestConfigurableUtils.java
@@ -0,0 +1,22 @@
+package com.onthegomap.planetiler.custommap.util;
+
+import java.nio.file.Path;
+
+public class TestConfigurableUtils {
+ public static Path pathToTestResource(String resource) {
+ return resolve(Path.of("planetiler-custommap", "src", "test", "resources", "validSchema", resource));
+ }
+
+ public static Path pathToTestInvalidResource(String resource) {
+ return resolve(Path.of("planetiler-custommap", "src", "test", "resources", "invalidSchema", resource));
+ }
+
+ public static Path pathToSample(String resource) {
+ return resolve(Path.of("planetiler-custommap", "src", "main", "resources", "samples", resource));
+ }
+
+ private static Path resolve(Path pathFromRoot) {
+ Path cwd = Path.of("").toAbsolutePath();
+ return cwd.resolveSibling(pathFromRoot);
+ }
+}
diff --git a/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/util/VerifyMonaco.java b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/util/VerifyMonaco.java
new file mode 100644
index 0000000000..694dc79224
--- /dev/null
+++ b/planetiler-custommap/src/test/java/com/onthegomap/planetiler/custommap/util/VerifyMonaco.java
@@ -0,0 +1,44 @@
+package com.onthegomap.planetiler.custommap.util;
+
+import com.onthegomap.planetiler.mbtiles.Mbtiles;
+import com.onthegomap.planetiler.mbtiles.Verify;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Map;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+/**
+ * A utility to check the contents of an mbtiles file generated for Monaco.
+ */
+public class VerifyMonaco {
+
+ public static final Envelope MONACO_BOUNDS = new Envelope(7.40921, 7.44864, 43.72335, 43.75169);
+
+ /**
+ * Returns a verification result with a basic set of checks against an openmaptiles map built from an extract for
+ * Monaco.
+ */
+ public static Verify verify(Mbtiles mbtiles) {
+ Verify verify = Verify.verify(mbtiles);
+ verify.checkMinFeatureCount(MONACO_BOUNDS, "building", Map.of(), 13, 14, 100, Polygon.class);
+ verify.checkMinFeatureCount(MONACO_BOUNDS, "transportation", Map.of(), 10, 14, 5, LineString.class);
+ verify.checkMinFeatureCount(MONACO_BOUNDS, "landcover", Map.of(
+ "class", "grass",
+ "subclass", "park"
+ ), 14, 10, Polygon.class);
+ verify.checkMinFeatureCount(MONACO_BOUNDS, "water", Map.of("class", "ocean"), 0, 14, 1, Polygon.class);
+ verify.checkMinFeatureCount(MONACO_BOUNDS, "place", Map.of("class", "country"), 2, 14, 1, Point.class);
+ return verify;
+ }
+
+ public static void main(String[] args) throws IOException {
+ try (var mbtiles = Mbtiles.newReadOnlyDatabase(Path.of(args[0]))) {
+ var result = verify(mbtiles);
+ result.print();
+ result.failIfErrors();
+ }
+ }
+}
diff --git a/planetiler-custommap/src/test/resources/invalidSchema/bad_geometry_type.yml b/planetiler-custommap/src/test/resources/invalidSchema/bad_geometry_type.yml
new file mode 100644
index 0000000000..82f03eb290
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/invalidSchema/bad_geometry_type.yml
@@ -0,0 +1,18 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ geometry: smurf
+ include_when:
+ natural: water
+ attributes:
+ - key: water
+ - constant_value: wet
\ No newline at end of file
diff --git a/planetiler-custommap/src/test/resources/invalidSchema/no_layers.yml b/planetiler-custommap/src/test/resources/invalidSchema/no_layers.yml
new file mode 100644
index 0000000000..03041a9f67
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/invalidSchema/no_layers.yml
@@ -0,0 +1,7 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
\ No newline at end of file
diff --git a/planetiler-custommap/src/test/resources/validSchema/data_type_attributes.yml b/planetiler-custommap/src/test/resources/validSchema/data_type_attributes.yml
new file mode 100644
index 0000000000..7121eee8e8
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/data_type_attributes.yml
@@ -0,0 +1,31 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+tag_mappings:
+ b_type: boolean
+ l_type: long
+ d_type: direction
+ s_type: string
+ intermittent:
+ output: is_intermittent
+ type: boolean
+ bridge:
+ type: boolean
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ geometry: line
+ attributes:
+ - key: b_type
+ - key: l_type
+ - key: d_type
+ - key: s_type
+ - key: intermittent
+ - key: is_intermittent
+ - key: bridge
diff --git a/planetiler-custommap/src/test/resources/validSchema/local_path.yml b/planetiler-custommap/src/test/resources/validSchema/local_path.yml
new file mode 100644
index 0000000000..4178031d71
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/local_path.yml
@@ -0,0 +1,18 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+ local_path: data/rhode-island.osm.pbf
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ geometry: polygon
+ include_when:
+ natural: water
+ attributes:
+ - key: natural
diff --git a/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml b/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml
new file mode 100644
index 0000000000..85b21ef5e9
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml
@@ -0,0 +1,39 @@
+schema_name: OWG Simple Schema
+schema_description: Simple vector tile schema
+attribution: ©
+ OpenStreetMap contributors
+sources:
+ water_polygons:
+ type: shapefile
+ url: https://osmdata.openstreetmap.de/download/water-polygons-split-3857.zip
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+tag_mappings:
+ bridge: boolean # input=bridge, output=bridge, type=boolean
+ layer: long
+ tunnel: boolean
+layers:
+- name: road
+ features:
+ - sources:
+ - osm
+ min_zoom: 4
+ geometry: line
+ include_when:
+ highway: motorway
+ attributes:
+ - key: highway
+ - key: bridge
+ include_when:
+ bridge: true
+ min_zoom: 11
+ - key: tunnel
+ constant_value: true
+ include_when:
+ tunnel: true
+ min_zoom: 11
+ - key: name
+ min_zoom: 12
+ - key: layer
+ min_zoom: 13
diff --git a/planetiler-custommap/src/test/resources/validSchema/static_attribute.yml b/planetiler-custommap/src/test/resources/validSchema/static_attribute.yml
new file mode 100644
index 0000000000..0b20eb0ea6
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/static_attribute.yml
@@ -0,0 +1,18 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ geometry: polygon
+ include_when:
+ natural: water
+ attributes:
+ - key: natural
+ constant_value: aTestConstantValue
\ No newline at end of file
diff --git a/planetiler-custommap/src/test/resources/validSchema/tag_attribute.yml b/planetiler-custommap/src/test/resources/validSchema/tag_attribute.yml
new file mode 100644
index 0000000000..6ae3384c6d
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/tag_attribute.yml
@@ -0,0 +1,17 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ geometry: polygon
+ include_when:
+ natural: water
+ attributes:
+ - key: natural
diff --git a/planetiler-custommap/src/test/resources/validSchema/tag_include.yml b/planetiler-custommap/src/test/resources/validSchema/tag_include.yml
new file mode 100644
index 0000000000..8adac37d0f
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/tag_include.yml
@@ -0,0 +1,27 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ min_zoom: 10
+ geometry: polygon
+ include_when:
+ natural: water
+ attributes:
+ - key: test_include
+ constant_value: ok
+ include_when:
+ natural: water
+ - key: test_exclude
+ constant_value: bad
+ include_when:
+ natural: mud
+ - key: test_zoom_tag
+ min_zoom: 12
\ No newline at end of file
diff --git a/planetiler-custommap/src/test/resources/validSchema/zoom_filter.yml b/planetiler-custommap/src/test/resources/validSchema/zoom_filter.yml
new file mode 100644
index 0000000000..ecde0bbc1a
--- /dev/null
+++ b/planetiler-custommap/src/test/resources/validSchema/zoom_filter.yml
@@ -0,0 +1,37 @@
+schema_name: Test Case Schema
+schema_description: Test case tile schema
+attribution: Test attribution
+sources:
+ osm:
+ type: osm
+ url: geofabrik:rhode-island
+tag_mappings:
+ lanes: long
+layers:
+- name: testLayer
+ features:
+ - sources:
+ - osm
+ geometry: line
+ min_zoom: 4
+ zoom_override:
+ - min: 5
+ tag:
+ highway: trunk
+ - min: 7
+ tag:
+ highway: primary
+ include_when:
+ highway:
+ attributes:
+ - key: highway
+ min_zoom_by_value:
+ trunk: 5
+ primary: 7
+ - key: lanes
+ min_zoom_by_value:
+ 4: 9
+ 3: 9
+ 2: 10
+ - key: toll
+ min_zoom: 8
\ No newline at end of file
diff --git a/planetiler-dist/pom.xml b/planetiler-dist/pom.xml
index 0f5894bad4..fe31089a9b 100644
--- a/planetiler-dist/pom.xml
+++ b/planetiler-dist/pom.xml
@@ -32,6 +32,11 @@
planetiler-basemap
${project.parent.version}