Skip to content

Commit bf1f200

Browse files
authored
Migrate POI kind and kindDetail assigment from plain Java to MultiExpression rules (#537)
* Moved kind=aerodrome to MultiExpression rules * Moved kind=amenity to MultiExpression rules * Moved most national parks and forests to MultiExpression rules, with some kind=other slipping through * Cleaned up remaining forests and parks * Move non-"other" to MultiExpression rules * Moved remaining "kind" assignments to rules * Moved kindDetail assignments to MultiExpression rules * Combined several natural park rules using new withAnyOf expression * Combined several forest rules using new withAllOf expression * Switched to upstream Expression.and/or * Stripped commented-out kind assignments * Undo lint changes to Matcher.java * Added required version bump * Uppercase a constant * Logged Tiles 4.13.5 changes
1 parent 256014f commit bf1f200

File tree

3 files changed

+123
-98
lines changed

3 files changed

+123
-98
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
Tiles 4.13.5
2+
------
3+
- Translate POI kind= assignments to MultiExpression rules [#537]
4+
15
Tiles 4.13.4
26
------
37
- remove kind=protected_area which made historical city centers appear as parks [#531]

tiles/src/main/java/com/protomaps/basemap/Basemap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public String description() {
119119

120120
@Override
121121
public String version() {
122-
return "4.13.4";
122+
return "4.13.5";
123123
}
124124

125125
@Override

tiles/src/main/java/com/protomaps/basemap/layers/Pois.java

Lines changed: 118 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package com.protomaps.basemap.layers;
22

33
import static com.onthegomap.planetiler.util.Parse.parseDoubleOrNull;
4+
import static com.protomaps.basemap.feature.Matcher.fromTag;
5+
import static com.protomaps.basemap.feature.Matcher.getString;
6+
import static com.protomaps.basemap.feature.Matcher.rule;
7+
import static com.protomaps.basemap.feature.Matcher.use;
8+
import static com.protomaps.basemap.feature.Matcher.with;
9+
import static com.protomaps.basemap.feature.Matcher.without;
410

511
import com.onthegomap.planetiler.FeatureCollector;
612
import com.onthegomap.planetiler.ForwardingProfile;
713
import com.onthegomap.planetiler.VectorTile;
14+
import com.onthegomap.planetiler.expression.Expression;
15+
import com.onthegomap.planetiler.expression.MultiExpression;
816
import com.onthegomap.planetiler.geo.GeoUtils;
917
import com.onthegomap.planetiler.geo.GeometryException;
1018
import com.onthegomap.planetiler.reader.SourceFeature;
@@ -35,15 +43,125 @@ public Pois(QrankDb qrankDb) {
3543

3644
public static final String LAYER_NAME = "pois";
3745

46+
private static final Expression WITH_OPERATOR_USFS = with("operator", "United States Forest Service",
47+
"US Forest Service", "U.S. Forest Service", "USDA Forest Service", "United States Department of Agriculture",
48+
"US National Forest Service", "United State Forest Service", "U.S. National Forest Service");
49+
50+
private static final MultiExpression.Index<Map<String, Object>> index = MultiExpression.of(List.of(
51+
52+
// Everything is "other"/"" at first
53+
rule(use("kind", "other"), use("kindDetail", "")),
54+
55+
// Boundary is most generic, so place early else we lose out
56+
// on nature_reserve detail versus all the protected_area
57+
rule(with("boundary"), use("kind", fromTag("boundary"))),
58+
59+
// More specific kinds
60+
61+
rule(with("historic"), without("historic", "yes"), use("kind", fromTag("historic"))),
62+
rule(with("tourism"), use("kind", fromTag("tourism"))),
63+
rule(with("shop"), use("kind", fromTag("shop"))),
64+
rule(with("highway"), use("kind", fromTag("highway"))),
65+
rule(with("railway"), use("kind", fromTag("railway"))),
66+
rule(with("natural"), use("kind", fromTag("natural"))),
67+
rule(with("leisure"), use("kind", fromTag("leisure"))),
68+
rule(with("landuse"), use("kind", fromTag("landuse"))),
69+
rule(with("aeroway"), use("kind", fromTag("aeroway"))),
70+
rule(with("craft"), use("kind", fromTag("craft"))),
71+
rule(with("attraction"), use("kind", fromTag("attraction"))),
72+
rule(with("amenity"), use("kind", fromTag("amenity"))),
73+
74+
// National forests
75+
76+
rule(
77+
Expression.or(
78+
with("landuse", "forest"),
79+
Expression.and(with("boundary", "national_park"), WITH_OPERATOR_USFS),
80+
Expression.and(
81+
with("boundary", "national_park"),
82+
with("protect_class", "6"),
83+
with("protection_title", "National Forest")
84+
),
85+
Expression.and(
86+
with("boundary", "protected_area"),
87+
with("protect_class", "6"),
88+
WITH_OPERATOR_USFS
89+
)
90+
),
91+
use("kind", "forest")
92+
),
93+
94+
// National parks
95+
96+
rule(with("boundary", "national_park"), use("kind", "park")),
97+
rule(
98+
with("boundary", "national_park"),
99+
Expression.not(WITH_OPERATOR_USFS),
100+
without("protection_title", "Conservation Area", "Conservation Park", "Environmental use", "Forest Reserve",
101+
"National Forest", "National Wildlife Refuge", "Nature Refuge", "Nature Reserve", "Protected Site",
102+
"Provincial Park", "Public Access Land", "Regional Reserve", "Resources Reserve", "State Forest",
103+
"State Game Land", "State Park", "Watershed Recreation Unit", "Wild Forest", "Wilderness Area",
104+
"Wilderness Study Area", "Wildlife Management", "Wildlife Management Area", "Wildlife Sanctuary"),
105+
Expression.or(
106+
with("protect_class", "2", "3"),
107+
with("operator", "United States National Park Service", "National Park Service", "US National Park Service",
108+
"U.S. National Park Service", "US National Park service"),
109+
with("operator:en", "Parks Canada"),
110+
with("designation", "national_park"),
111+
with("protection_title", "National Park")
112+
),
113+
use("kind", "national_park")
114+
),
115+
116+
// Remaining things
117+
118+
rule(with("natural", "peak"), use("kind", fromTag("natural"))),
119+
rule(with("highway", "bus_stop"), use("kind", fromTag("highway"))),
120+
rule(with("tourism", "attraction", "camp_site", "hotel"), use("kind", fromTag("tourism"))),
121+
rule(with("shop", "grocery", "supermarket"), use("kind", fromTag("shop"))),
122+
rule(with("leisure", "golf_course", "marina", "stadium", "park"), use("kind", fromTag("leisure"))),
123+
124+
rule(with("landuse", "military"), use("kind", "military")),
125+
rule(
126+
with("landuse", "military"),
127+
with("military", "naval_base", "airfield"),
128+
use("kind", fromTag("military"))
129+
),
130+
131+
rule(with("landuse", "cemetery"), use("kind", fromTag("landuse"))),
132+
133+
rule(
134+
with("aeroway", "aerodrome"),
135+
use("kind", "aerodrome"),
136+
use("kindDetail", fromTag("aerodrome"))
137+
),
138+
139+
// Additional details for certain classes of POI
140+
141+
rule(with("sport"), use("kindDetail", fromTag("sport"))),
142+
rule(with("religion"), use("kindDetail", fromTag("religion"))),
143+
rule(with("cuisine"), use("kindDetail", fromTag("cuisine")))
144+
145+
)).index();
146+
38147
@Override
39148
public String name() {
40149
return LAYER_NAME;
41150
}
42151

152+
// ~= pow((sqrt(70k) / (40m / 256)) / 256, 2) ~= 4.4e-11
43153
private static final double WORLD_AREA_FOR_70K_SQUARE_METERS =
44154
Math.pow(GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d, 2);
45155

46156
public void processOsm(SourceFeature sf, FeatureCollector features) {
157+
var matches = index.getMatches(sf);
158+
if (matches.isEmpty()) {
159+
return;
160+
}
161+
162+
String kind = getString(sf, matches, "kind", "undefined");
163+
String kindDetail = getString(sf, matches, "kindDetail", "undefined");
164+
47165
if ((sf.isPoint() || sf.canBePolygon()) && (sf.hasTag("aeroway", "aerodrome") ||
48166
sf.hasTag("amenity") ||
49167
sf.hasTag("attraction") ||
@@ -59,8 +177,6 @@ public void processOsm(SourceFeature sf, FeatureCollector features) {
59177
sf.hasTag("shop") ||
60178
sf.hasTag("tourism") &&
61179
(!sf.hasTag("historic", "district")))) {
62-
String kind = "other";
63-
String kindDetail = "";
64180
Integer minZoom = 15;
65181
long qrank = 0;
66182

@@ -70,123 +186,39 @@ public void processOsm(SourceFeature sf, FeatureCollector features) {
70186
}
71187

72188
if (sf.hasTag("aeroway", "aerodrome")) {
73-
kind = sf.getString("aeroway");
74189
minZoom = 13;
75190

76191
// Emphasize large international airports earlier
77192
if (kind.equals("aerodrome") && sf.hasTag("iata")) {
78193
minZoom -= 2;
79194
}
80-
81-
if (sf.hasTag("aerodrome")) {
82-
kindDetail = sf.getString("aerodrome");
83-
}
84195
} else if (sf.hasTag("amenity", "university", "college")) {
85-
kind = sf.getString("amenity");
86196
// One would think University should be earlier, but there are lots of dinky node only places
87197
// So if the university has a large area, it'll naturally improve it's zoom in the next section...
88198
minZoom = 14;
89199
} else if (sf.hasTag("amenity", "hospital")) {
90-
kind = sf.getString("amenity");
91200
minZoom = 12;
92201
} else if (sf.hasTag("amenity", "library", "post_office", "townhall")) {
93-
kind = sf.getString("amenity");
94202
minZoom = 13;
95203
} else if (sf.hasTag("amenity", "school")) {
96-
kind = sf.getString("amenity");
97204
minZoom = 15;
98205
} else if (sf.hasTag("amenity", "cafe")) {
99-
kind = sf.getString("amenity");
100206
minZoom = 15;
101207
} else if (sf.hasTag("landuse", "cemetery")) {
102-
kind = sf.getString("landuse");
103208
minZoom = 14;
104-
} else if (sf.hasTag("landuse", "military")) {
105-
kind = "military";
106-
if (sf.hasTag("military", "naval_base", "airfield")) {
107-
kind = sf.getString("military");
108-
}
109209
} else if (sf.hasTag("leisure", "park")) {
110-
kind = "park";
111210
// Lots of pocket parks and NODE parks, show those later than rest of leisure
112211
minZoom = 14;
113212
} else if (sf.hasTag("leisure", "golf_course", "marina", "stadium")) {
114-
kind = sf.getString("leisure");
115213
minZoom = 13;
116214
} else if (sf.hasTag("shop", "grocery", "supermarket")) {
117-
kind = sf.getString("shop");
118215
minZoom = 14;
119216
} else if (sf.hasTag("tourism", "attraction", "camp_site", "hotel")) {
120-
kind = sf.getString("tourism");
121217
minZoom = 15;
122218
} else if (sf.hasTag("highway", "bus_stop")) {
123-
kind = sf.getString("highway");
124219
minZoom = 17;
125220
} else if (sf.hasTag("natural", "peak")) {
126-
kind = sf.getString("natural");
127221
minZoom = 13;
128-
} else {
129-
// Avoid problem of too many "other" kinds
130-
// All these will default to min_zoom of 15
131-
// If a more specific min_zoom is needed (or sanitize kind values)
132-
// then add new logic in section above
133-
if (sf.hasTag("amenity")) {
134-
kind = sf.getString("amenity");
135-
} else if (sf.hasTag("attraction")) {
136-
kind = sf.getString("attraction");
137-
} else if (sf.hasTag("craft")) {
138-
kind = sf.getString("craft");
139-
} else if (sf.hasTag("aeroway")) {
140-
kind = sf.getString("aeroway");
141-
} else if (sf.hasTag("landuse")) {
142-
kind = sf.getString("landuse");
143-
} else if (sf.hasTag("leisure")) {
144-
kind = sf.getString("leisure");
145-
} else if (sf.hasTag("natural")) {
146-
kind = sf.getString("natural");
147-
} else if (sf.hasTag("railway")) {
148-
kind = sf.getString("railway");
149-
} else if (sf.hasTag("highway")) {
150-
kind = sf.getString("highway");
151-
} else if (sf.hasTag("shop")) {
152-
kind = sf.getString("shop");
153-
} else if (sf.hasTag("tourism")) {
154-
kind = sf.getString("tourism");
155-
// Boundary is most generic, so place last else we loose out
156-
// on nature_reserve detail versus all the protected_area
157-
} else if (sf.hasTag("historic") && !sf.hasTag("historic", "yes")) {
158-
kind = sf.getString("historic");
159-
} else if (sf.hasTag("boundary")) {
160-
kind = sf.getString("boundary");
161-
}
162-
}
163-
164-
// National forests
165-
if (sf.hasTag("boundary", "national_park") &&
166-
sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service",
167-
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service",
168-
"United State Forest Service", "U.S. National Forest Service")) {
169-
kind = "forest";
170-
} else if (sf.hasTag("boundary", "national_park") &&
171-
sf.hasTag("protect_class", "6") &&
172-
sf.hasTag("protection_title", "National Forest")) {
173-
kind = "forest";
174-
} else if (sf.hasTag("landuse", "forest") &&
175-
sf.hasTag("protect_class", "6")) {
176-
kind = "forest";
177-
} else if (sf.hasTag("landuse", "forest") &&
178-
sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service",
179-
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service",
180-
"United State Forest Service", "U.S. National Forest Service")) {
181-
kind = "forest";
182-
} else if (sf.hasTag("landuse", "forest")) {
183-
kind = "forest";
184-
} else if (sf.hasTag("boundary", "protected_area") &&
185-
sf.hasTag("protect_class", "6") &&
186-
sf.hasTag("operator", "United States Forest Service", "US Forest Service", "U.S. Forest Service",
187-
"USDA Forest Service", "United States Department of Agriculture", "US National Forest Service",
188-
"United State Forest Service", "U.S. National Forest Service")) {
189-
kind = "forest";
190222
}
191223

192224
// National parks
@@ -205,21 +237,10 @@ public void processOsm(SourceFeature sf, FeatureCollector features) {
205237
sf.hasTag("operator:en", "Parks Canada") ||
206238
sf.hasTag("designation", "national_park") ||
207239
sf.hasTag("protection_title", "National Park"))) {
208-
kind = "national_park";
209240
minZoom = 11;
210-
} else {
211-
kind = "park";
212241
}
213242
}
214243

215-
if (sf.hasTag("cuisine")) {
216-
kindDetail = sf.getString("cuisine");
217-
} else if (sf.hasTag("religion")) {
218-
kindDetail = sf.getString("religion");
219-
} else if (sf.hasTag("sport")) {
220-
kindDetail = sf.getString("sport");
221-
}
222-
223244
// try first for polygon -> point representations
224245
if (sf.canBePolygon() && sf.hasTag("name") && sf.getString("name") != null) {
225246
Double wayArea = 0.0;

0 commit comments

Comments
 (0)