diff --git a/styles/src/base_layers.ts b/styles/src/base_layers.ts index 65980120f..713e0352a 100644 --- a/styles/src/base_layers.ts +++ b/styles/src/base_layers.ts @@ -1066,7 +1066,11 @@ export function nolabels_layers( type: "line", source: source, "source-layer": "boundaries", - filter: ["<=", "kind_detail", 2], + filter: [ + "any", + ["<=", "kind_detail", 2], + ["all", ["==", "kind_detail", 3], ["==", "disputed", true]], + ], paint: { "line-color": t.boundaries, "line-width": 0.7, @@ -1084,7 +1088,11 @@ export function nolabels_layers( type: "line", source: source, "source-layer": "boundaries", - filter: [">", "kind_detail", 2], + filter: [ + "all", + [">", "kind_detail", 2], + ["none", ["all", ["==", "kind_detail", 3], ["==", "disputed", true]]], + ], paint: { "line-color": t.boundaries, "line-width": 0.4, diff --git a/styles/test/base_layers.test.ts b/styles/test/base_layers.test.ts index 65ab917b3..d5b171792 100644 --- a/styles/test/base_layers.test.ts +++ b/styles/test/base_layers.test.ts @@ -20,3 +20,22 @@ test("the nolayers label has no labels", () => { assert.notEqual("symbol", layer.type); } }); + +test("country boundary styling includes disputed level three boundaries", () => { + const boundaryLayers = nolabels_layers("dummy", LIGHT); + const country = boundaryLayers.find( + (layer) => layer.id === "boundaries_country", + ); + const other = boundaryLayers.find((layer) => layer.id === "boundaries"); + + assert.deepEqual(country?.filter, [ + "any", + ["<=", "kind_detail", 2], + ["all", ["==", "kind_detail", 3], ["==", "disputed", true]], + ]); + assert.deepEqual(other?.filter, [ + "all", + [">", "kind_detail", 2], + ["none", ["all", ["==", "kind_detail", 3], ["==", "disputed", true]]], + ]); +}); diff --git a/tiles/src/main/java/com/protomaps/basemap/layers/Boundaries.java b/tiles/src/main/java/com/protomaps/basemap/layers/Boundaries.java index fb9c1ade2..e1d6553d0 100644 --- a/tiles/src/main/java/com/protomaps/basemap/layers/Boundaries.java +++ b/tiles/src/main/java/com/protomaps/basemap/layers/Boundaries.java @@ -226,7 +226,10 @@ public List preprocessOsmRelation(OsmElement.Relation relation) if (relation.hasTag("type", "boundary") && relation.hasTag("boundary", "administrative", "disputed", "claim")) { Integer adminLevel = Parse.parseIntOrNull(relation.getString("admin_level")); - Integer disputed = relation.hasTag("boundary", "disputed") || relation.hasTag("disputed", "yes") || + boolean occupiedBoundary = adminLevel != null && adminLevel == 3 && + relation.hasTag("border_type", "occupied"); + Integer disputed = occupiedBoundary || relation.hasTag("boundary", "disputed") || + relation.hasTag("disputed", "yes") || relation.hasTag("disputed_by") || relation.hasTag("claimed_by") ? 1 : 0; if (adminLevel == null || adminLevel > 8) diff --git a/tiles/src/test/java/com/protomaps/basemap/layers/BoundariesTest.java b/tiles/src/test/java/com/protomaps/basemap/layers/BoundariesTest.java index 9f103599c..bc4f59094 100644 --- a/tiles/src/test/java/com/protomaps/basemap/layers/BoundariesTest.java +++ b/tiles/src/test/java/com/protomaps/basemap/layers/BoundariesTest.java @@ -74,6 +74,62 @@ void testDisputedOsm() { collector); } + @Test + void testOccupiedAdminLevelThreeIsDisputedRegionBoundary() { + var infos = profile.preprocessOsmRelation( + new OsmElement.Relation(1, + Map.of("type", "boundary", "boundary", "administrative", "admin_level", "3", "border_type", "occupied"), + List.of(new OsmElement.Relation.Member(OsmElement.Type.WAY, 123, "")))); + + var way = SimpleFeature.createFakeOsmFeature( + newLineString(0, 0, 1, 1), + new HashMap<>(), + "osm", + null, + 123, + infos.stream().map(r -> new OsmReader.RelationMember<>("", r)).toList() + ); + + var collector = featureCollectorFactory.get(way); + profile.processFeature(way, collector); + + assertFeatures(6, + List.of(Map.of( + "kind", "region", + "kind_detail", 3, + "disputed", true, + "sort_rank", 288, + "_minzoom", 6)), + collector); + } + + @Test + void testOrdinaryAdminLevelThreeRemainsRegionBoundary() { + var infos = profile.preprocessOsmRelation( + new OsmElement.Relation(1, Map.of("type", "boundary", "boundary", "administrative", "admin_level", "3"), + List.of(new OsmElement.Relation.Member(OsmElement.Type.WAY, 123, "")))); + + var way = SimpleFeature.createFakeOsmFeature( + newLineString(0, 0, 1, 1), + new HashMap<>(), + "osm", + null, + 123, + infos.stream().map(r -> new OsmReader.RelationMember<>("", r)).toList() + ); + + var collector = featureCollectorFactory.get(way); + profile.processFeature(way, collector); + + assertFeatures(6, + List.of(Map.of( + "kind", "region", + "kind_detail", 3, + "sort_rank", 289, + "_minzoom", 6)), + collector); + } + @ParameterizedTest @CsvSource(value = { "disputed,yes",