Skip to content

Commit

Permalink
wip multiexpression
Browse files Browse the repository at this point in the history
  • Loading branch information
bdon committed Sep 2, 2023
1 parent 0e36bb5 commit 2273738
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 157 deletions.
245 changes: 88 additions & 157 deletions tiles/src/main/java/com/protomaps/basemap/layers/Landuse.java
Original file line number Diff line number Diff line change
@@ -1,178 +1,109 @@
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.Collectors;
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") ||
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") || sf.hasTag("man_made", "bridge"))))) {
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)));
}

// 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 MultiExpression.Index<String> compose(Stream<MultiExpression.Entry<String>>... values) {
return MultiExpression
.of(Stream.of(values).reduce(Stream::concat).orElseGet(Stream::empty).collect(Collectors.toList())).index();
}

// 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";
}
}
// TODO craft and historic
public static 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", "cemetery"),
Stream.of(entry("farmland", matchAny("landuse", "orchard", "farmland", "farmyard"))),
valueEntries("landuse", "residential"),
Stream.of(entry("industrial", matchAny("landuse", "industrial", "brownfield"))),
valueEntries("landuse", "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"),
valueEntries("natural", "beach"),
valueEntries("shop", "grocery", "supermarket"), // not in tilezen?
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", "recreation_ground", "railway", "commercial", "grass")
);

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 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");

// NOTE: (nvkelso 20230622) landuse labels for polygons are found in the pois layer
//OsmNames.setOsmNames(poly, sf, 0);
}
static 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");

// public static Stream<MultiExpression.Entry<String>> US_LANDUSE_KIND = compose(
// Stream.of(entry("forest", and(
// matchAny("boundary", "national_park"),
// or(US_OPERATOR,
// and(
// matchAny("protect_class", "6"),
// matchAny("protection_title", "National Forest")
// )
// )
// ))),
// Stream.of(entry("forest", and(
// matchAny("landuse", "forest"),
// or(
// US_OPERATOR,
// and(
// matchAny("protect_class", "6"),
// matchAny("protection_title", "National Forest")
// )
// )
// )))
// );

@Override
public void processFeature(SourceFeature sf, FeatureCollector features) {
if (!sf.canBePolygon()) return;
List<String> matches = LANDUSE_KIND.getMatches(sf);
if (matches.size() == 0) 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
Expand Down
62 changes: 62 additions & 0 deletions tiles/src/test/java/com/protomaps/basemap/layers/LanduseTest.java
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 {
Expand All @@ -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"));
// 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"));
}
}

0 comments on commit 2273738

Please sign in to comment.