11package com .protomaps .basemap .layers ;
22
33import 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
511import com .onthegomap .planetiler .FeatureCollector ;
612import com .onthegomap .planetiler .ForwardingProfile ;
713import com .onthegomap .planetiler .VectorTile ;
14+ import com .onthegomap .planetiler .expression .Expression ;
15+ import com .onthegomap .planetiler .expression .MultiExpression ;
816import com .onthegomap .planetiler .geo .GeoUtils ;
917import com .onthegomap .planetiler .geo .GeometryException ;
1018import 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