-
-
Notifications
You must be signed in to change notification settings - Fork 72
wip multiexpression #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip multiexpression #109
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,178 +1,90 @@ | ||
package com.protomaps.basemap.layers; | ||
|
||
import static com.onthegomap.planetiler.expression.Expression.*; | ||
import static com.onthegomap.planetiler.expression.MultiExpression.entry; | ||
|
||
import com.onthegomap.planetiler.FeatureCollector; | ||
import com.onthegomap.planetiler.FeatureMerge; | ||
import com.onthegomap.planetiler.ForwardingProfile; | ||
import com.onthegomap.planetiler.VectorTile; | ||
import com.onthegomap.planetiler.expression.MultiExpression; | ||
import com.onthegomap.planetiler.geo.GeometryException; | ||
import com.onthegomap.planetiler.reader.SourceFeature; | ||
import com.protomaps.basemap.feature.FeatureId; | ||
import com.protomaps.basemap.postprocess.Area; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
public class Landuse implements ForwardingProfile.FeatureProcessor, ForwardingProfile.FeaturePostProcessor { | ||
|
||
@Override | ||
public void processFeature(SourceFeature sf, FeatureCollector features) { | ||
if (sf.canBePolygon() && (sf.hasTag("aeroway", "aerodrome", "runway") || | ||
sf.hasTag("area:aeroway", "taxiway", "runway") || | ||
sf.hasTag("amenity", "hospital", "school", "kindergarten", "university", "college") || | ||
sf.hasTag("boundary", "national_park", "protected_area") || | ||
sf.hasTag("landuse", "recreation_ground", "industrial", "brownfield", "railway", "cemetery", "commercial", | ||
"grass", "orchard", "farmland", "farmyard", "residential", "military") || | ||
sf.hasTag("leisure", "park", "garden", "golf_course", "dog_park", "playground", "pitch", "nature_reserve") || | ||
sf.hasTag("man_made", "pier", "bridge") || | ||
sf.hasTag("natural", "beach") || | ||
// TODO: (nvkelso 20230622) This use of the place tag here is dubious, though paired with "residential" | ||
sf.hasTag("place", "neighbourhood") || | ||
sf.hasTag("railway", "platform") || | ||
sf.hasTag("tourism", "zoo") || | ||
(sf.hasTag("area", "yes") && | ||
(sf.hasTag("highway", "pedestrian", "footway"))))) { | ||
String kind = "other"; | ||
if (sf.hasTag("aeroway", "aerodrome")) { | ||
kind = sf.getString("aeroway"); | ||
} else if (sf.hasTag("amenity", "university", "college", "hospital", "library", "post_office", "school", | ||
"townhall")) { | ||
kind = sf.getString("amenity"); | ||
} else if (sf.hasTag("amenity", "cafe")) { | ||
kind = sf.getString("amenity"); | ||
} else if (sf.hasTag("highway")) { | ||
kind = "pedestrian"; | ||
} else if (sf.hasTag("landuse", "cemetery")) { | ||
kind = sf.getString("landuse"); | ||
} else if (sf.hasTag("landuse", "orchard", "farmland", "farmyard")) { | ||
kind = "farmland"; | ||
} else if (sf.hasTag("landuse", "residential")) { | ||
kind = "residential"; | ||
} else if (sf.hasTag("landuse", "industrial", "brownfield")) { | ||
kind = "industrial"; | ||
} else if (sf.hasTag("landuse", "military")) { | ||
kind = "military"; | ||
if (sf.hasTag("military", "naval_base", "airfield")) { | ||
kind = sf.getString("military"); | ||
} | ||
} else if (sf.hasTag("leisure", "golf_course", "marina", "park", "stadium")) { | ||
kind = sf.getString("leisure"); | ||
} else if (sf.hasTag("man_made", "bridge")) { | ||
kind = "pedestrian"; | ||
} else if (sf.hasTag("man_made", "pier")) { | ||
kind = "pier"; | ||
} else if (sf.hasTag("shop", "grocery", "supermarket")) { | ||
kind = sf.getString("shop"); | ||
} else if (sf.hasTag("tourism", "attraction", "camp_site", "hotel")) { | ||
kind = sf.getString("tourism"); | ||
} else { | ||
// Avoid problem of too many "other" kinds | ||
// All these will default to min_zoom of 15 | ||
// If a more specific min_zoom is needed (or sanitize kind values) | ||
// then add new logic in section above | ||
if (sf.hasTag("amenity")) { | ||
kind = sf.getString("amenity"); | ||
} else if (sf.hasTag("craft")) { | ||
kind = sf.getString("craft"); | ||
} else if (sf.hasTag("aeroway")) { | ||
kind = sf.getString("aeroway"); | ||
} else if (sf.hasTag("historic")) { | ||
kind = sf.getString("historic"); | ||
} else if (sf.hasTag("landuse")) { | ||
kind = sf.getString("landuse"); | ||
} else if (sf.hasTag("leisure")) { | ||
kind = sf.getString("leisure"); | ||
} else if (sf.hasTag("man_made")) { | ||
kind = sf.getString("man_made"); | ||
} else if (sf.hasTag("natural")) { | ||
kind = sf.getString("natural"); | ||
} else if (sf.hasTag("railway")) { | ||
kind = sf.getString("railway"); | ||
} else if (sf.hasTag("shop")) { | ||
kind = sf.getString("shop"); | ||
} else if (sf.hasTag("tourism")) { | ||
kind = sf.getString("tourism"); | ||
// Boundary is most generic, so place last else we loose out | ||
// on nature_reserve detail versus all the protected_area | ||
} else if (sf.hasTag("boundary")) { | ||
kind = sf.getString("boundary"); | ||
} | ||
} | ||
public static Stream<MultiExpression.Entry<String>> valueEntries(String field, String... values) { | ||
return Stream.of(values).map(v -> entry(v, matchAny(field, v))); | ||
} | ||
|
||
public static MultiExpression.Index<String> compose(Stream<MultiExpression.Entry<String>>... values) { | ||
return MultiExpression | ||
.of(Stream.of(values).reduce(Stream::concat).orElseGet(Stream::empty).toList()).index(); | ||
} | ||
|
||
// National forests | ||
if (sf.hasTag("boundary", "national_park") && | ||
sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service", | ||
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service", | ||
"United State Forest Service", "U.S. National Forest Service")) { | ||
kind = "forest"; | ||
} else if (sf.hasTag("boundary", "national_park") && | ||
sf.hasTag("protect_class", "6") && | ||
sf.hasTag("protection_title", "National Forest")) { | ||
kind = "forest"; | ||
} else if (sf.hasTag("landuse", "forest") && | ||
sf.hasTag("protect_class", "6")) { | ||
kind = "forest"; | ||
} else if (sf.hasTag("landuse", "forest") && | ||
sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service", | ||
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service", | ||
"United State Forest Service", "U.S. National Forest Service")) { | ||
kind = "forest"; | ||
} else if (sf.hasTag("landuse", "forest")) { | ||
kind = "forest"; | ||
} else if (sf.hasTag("boundary", "protected_area") && | ||
sf.hasTag("protect_class", "6") && | ||
sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service", | ||
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service", | ||
"United State Forest Service", "U.S. National Forest Service")) { | ||
kind = "forest"; | ||
} | ||
public static final String LANDUSE_KEY = "landuse"; | ||
public static final String PEDESTRIAN = "pedestrian"; | ||
// TODO craft and historic | ||
private static final MultiExpression.Index<String> LANDUSE_KIND = compose( | ||
valueEntries("aeroway", "aerodrome", "runway"), | ||
valueEntries("area:aeroway", "taxiway", "runway"), | ||
valueEntries("amenity", "university", "college", "hospital", "library", "school"), // townhall? post_office? | ||
Stream.of(entry(PEDESTRIAN, and( | ||
matchAny("area", "yes"), | ||
matchAny("highway", PEDESTRIAN, "footway") | ||
))), | ||
valueEntries(LANDUSE_KEY, "cemetery"), | ||
Stream.of(entry("farmland", matchAny(LANDUSE_KEY, "orchard", "farmland", "farmyard"))), | ||
valueEntries(LANDUSE_KEY, "residential"), | ||
Stream.of(entry("industrial", matchAny(LANDUSE_KEY, "industrial", "brownfield"))), | ||
valueEntries(LANDUSE_KEY, "military"), | ||
valueEntries("military", "naval_base", "airfield"), | ||
valueEntries("leisure", "golf_course", "park", "stadium", "garden", "dog_park", "playground", "pitch", | ||
"nature_reserve"), | ||
Stream.of(entry(PEDESTRIAN, matchAny("man_made", "bridge"))), | ||
valueEntries("man_made", "pier", "bridge"), | ||
valueEntries("natural", "beach"), | ||
valueEntries("shop", "grocery", "supermarket"), // not in tilezen? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nvkelso to research |
||
valueEntries("tourism", "attraction", "camp_site", "hotel", "zoo"), | ||
valueEntries("railway", "platform"), | ||
// Boundary is most generic, so place last else we loose out | ||
// on nature_reserve detail versus all the protected_area | ||
valueEntries("boundary", "national_park", "protected_area"), | ||
valueEntries(LANDUSE_KEY, "recreation_ground", "railway", "commercial", "grass") | ||
); | ||
|
||
// National parks | ||
if (sf.hasTag("boundary", "national_park")) { | ||
if (!(sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service", | ||
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service", | ||
"United State Forest Service", "U.S. National Forest Service") || | ||
sf.hasTag("protection_title", "Conservation Area", "Conservation Park", "Environmental use", "Forest Reserve", | ||
"National Forest", "National Wildlife Refuge", "Nature Refuge", "Nature Reserve", "Protected Site", | ||
"Provincial Park", "Public Access Land", "Regional Reserve", "Resources Reserve", "State Forest", | ||
"State Game Land", "State Park", "Watershed Recreation Unit", "Wild Forest", "Wilderness Area", | ||
"Wilderness Study Area", "Wildlife Management", "Wildlife Management Area", "Wildlife Sanctuary")) && | ||
(sf.hasTag("protect_class", "2", "3") || | ||
sf.hasTag("operator", "United States National Park Service", "National Park Service", | ||
"US National Park Service", "U.S. National Park Service", "US National Park service") || | ||
sf.hasTag("operator:en", "Parks Canada") || | ||
sf.hasTag("designation", "national_park") || | ||
sf.hasTag("protection_title", "National Park"))) { | ||
kind = "national_park"; | ||
} else { | ||
kind = "park"; | ||
} | ||
} | ||
static final MatchAny US_OPERATOR = | ||
matchAny("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service", | ||
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service", | ||
"United State Forest Service", "U.S. National Forest Service"); | ||
|
||
features.polygon(this.name()) | ||
.setId(FeatureId.create(sf)) | ||
// Core Tilezen schema properties | ||
.setAttr("pmap:kind", kind) | ||
// Core OSM tags for different kinds of places | ||
// DEPRECATION WARNING: Marked for deprecation in v4 schema, do not use these for styling | ||
// If an explicate value is needed it should bea kind, or included in kind_detail | ||
.setAttr("aeroway", sf.getString("aeroway")) | ||
.setAttr("amenity", sf.getString("amenity")) | ||
.setAttr("area:aeroway", sf.getString("area:aeroway")) | ||
.setAttr("boundary", sf.getString("boundary")) | ||
.setAttr("highway", sf.getString("highway")) | ||
.setAttr("landuse", sf.getString("landuse")) | ||
.setAttr("leisure", sf.getString("leisure")) | ||
.setAttr("man_made", sf.getString("man_made")) | ||
.setAttr("natural", sf.getString("natural")) | ||
.setAttr("place", sf.getString("place")) | ||
.setAttr("railway", sf.getString("railway")) | ||
.setAttr("sport", sf.getString("sport")) | ||
// NOTE: (nvkelso 20230622) Consider zoom 5 instead... | ||
// But to match Protomaps v2 we do earlier | ||
.setZoomRange(2, 15) | ||
.setMinPixelSize(2.0); | ||
static final MatchAny PROTECTION_TITLE = | ||
matchAny("protection_title", "Conservation Area", "Conservation Park", "Environmental use", "Forest Reserve", | ||
"National Forest", "National Wildlife Refuge", "Nature Refuge", "Nature Reserve", "Protected Site", | ||
"Provincial Park", "Public Access Land", "Regional Reserve", "Resources Reserve", "State Forest", | ||
"State Game Land", "State Park", "Watershed Recreation Unit", "Wild Forest", "Wilderness Area", | ||
"Wilderness Study Area", "Wildlife Management", "Wildlife Management Area", "Wildlife Sanctuary"); | ||
|
||
// NOTE: (nvkelso 20230622) landuse labels for polygons are found in the pois layer | ||
//OsmNames.setOsmNames(poly, sf, 0); | ||
} | ||
@Override | ||
public void processFeature(SourceFeature sf, FeatureCollector features) { | ||
if (!sf.canBePolygon()) | ||
return; | ||
List<String> matches = LANDUSE_KIND.getMatches(sf); | ||
if (matches.isEmpty()) | ||
return; | ||
String kind = matches.get(0); | ||
|
||
features.polygon(this.name()) | ||
.setId(FeatureId.create(sf)) | ||
.setAttr("pmap:kind", kind) | ||
// NOTE: (nvkelso 20230622) Consider zoom 5 instead... | ||
// But to match Protomaps v2 we do earlier | ||
.setZoomRange(2, 15) | ||
.setMinPixelSize(2.0); | ||
} | ||
|
||
@Override | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
package com.protomaps.basemap.layers; | ||
|
||
import static com.onthegomap.planetiler.TestUtils.newPolygon; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
import com.onthegomap.planetiler.reader.SimpleFeature; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.StreamSupport; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class LanduseTest extends LayerTest { | ||
|
@@ -21,4 +23,64 @@ void simple() { | |
0 | ||
))); | ||
} | ||
|
||
void assertKind(String expected, Map<String, String> keys) { | ||
assertFeatures(15, | ||
List.of(Map.of("pmap:kind", expected)), | ||
process(SimpleFeature.create( | ||
newPolygon(0, 0, 0, 1, 1, 1, 0, 0), | ||
new HashMap<>(keys), | ||
"osm", | ||
null, | ||
0 | ||
))); | ||
} | ||
|
||
void assertNone(Map<String, String> keys) { | ||
assertEquals(0, | ||
StreamSupport.stream(process(SimpleFeature.create( | ||
newPolygon(0, 0, 0, 1, 1, 1, 0, 0), | ||
new HashMap<>(keys), | ||
"osm", | ||
null, | ||
0 | ||
)).spliterator(), false).toList().size()); | ||
} | ||
|
||
|
||
@Test | ||
void kinds() { | ||
assertKind("aerodrome", Map.of("aeroway", "aerodrome")); | ||
assertKind("runway", Map.of("aeroway", "runway")); | ||
assertKind("taxiway", Map.of("area:aeroway", "taxiway")); | ||
assertKind("runway", Map.of("area:aeroway", "runway")); | ||
assertKind("university", Map.of("amenity", "university")); | ||
assertKind("college", Map.of("amenity", "college")); | ||
// assertKind("townhall", Map.of("amenity", "townhall")); | ||
assertKind("pedestrian", Map.of("highway", "pedestrian", "area", "yes")); | ||
assertNone(Map.of("highway", "pedestrian")); | ||
assertKind("pedestrian", Map.of("highway", "footway", "area", "yes")); | ||
assertNone(Map.of("highway", "footway")); | ||
assertKind("cemetery", Map.of("landuse", "cemetery")); | ||
assertKind("farmland", Map.of("landuse", "orchard")); | ||
assertKind("farmland", Map.of("landuse", "farmland")); | ||
assertKind("farmland", Map.of("landuse", "farmyard")); | ||
assertKind("residential", Map.of("landuse", "residential")); | ||
assertKind("industrial", Map.of("landuse", "industrial")); | ||
assertKind("industrial", Map.of("landuse", "brownfield")); | ||
assertKind("military", Map.of("landuse", "military")); | ||
// assertKind("naval_base", Map.of("military", "naval_base")); | ||
// assertKind("airfield", Map.of("military", "airfield")); | ||
assertKind("golf_course", Map.of("leisure", "golf_course")); | ||
// assertKind("marina", Map.of("leisure", "marina")); | ||
assertKind("park", Map.of("leisure", "park")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To test the order of operations, there should at least be one test that includes other tags than just the expected minimum tags... eg add a boundary key-value in. |
||
// assertKind("stadium", Map.of("leisure", "stadium")); | ||
// assertKind("pedestrian", Map.of("man_made", "bridge")); | ||
assertKind("pier", Map.of("man_made", "pier")); | ||
// assertKind("grocery", Map.of("shop", "grocery")); | ||
// assertKind("supermarket", Map.of("shop", "supermarket")); | ||
assertKind("attraction", Map.of("tourism", "attraction")); | ||
assertKind("camp_site", Map.of("tourism", "camp_site")); | ||
assertKind("hotel", Map.of("tourism", "hotel")); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.