1use std::collections::HashSet;
2
3use abstutil::{MultiMap, Tags, Timer};
4use geom::{Distance, FindClosest, GPSBounds, HashablePt2D, LonLat, Polygon, Pt2D, Ring};
5use osm2streets::osm::{OsmID, RelationID, WayID};
6use osm2streets::{osm, NamePerLanguage};
7use raw_map::{
8 Amenity, AreaType, CrossingType, ExtraPOI, ExtraPOIType, RawArea, RawBuilding, RawMap,
9 RawParkingLot,
10};
11
12use crate::Options;
13use streets_reader::osm_reader::glue_multipolygon;
14use streets_reader::OsmExtract;
15
16pub struct Extract {
17 pub osm: OsmExtract,
18 pub doc: streets_reader::osm_reader::Document,
19 pub bus_routes_on_roads: MultiMap<WayID, String>,
20 pub crossing_nodes: HashSet<(HashablePt2D, CrossingType)>,
22 pub barrier_nodes: Vec<(osm::NodeID, HashablePt2D)>,
24 pub extra_pois: Vec<ExtraPOI>,
25}
26
27pub fn extract_osm(
28 map: &mut RawMap,
29 osm_input_path: &str,
30 clip_pts: Option<Vec<LonLat>>,
31 opts: &Options,
32 timer: &mut Timer,
33) -> Extract {
34 let osm_input_bytes = fs_err::read(osm_input_path).unwrap();
35 let mut doc = streets_reader::osm_reader::Document::read(
36 &osm_input_bytes,
37 clip_pts.as_ref().map(|pts| GPSBounds::from(pts.clone())),
38 timer,
39 )
40 .unwrap();
41 map.streets.gps_bounds = doc.gps_bounds.clone().unwrap();
43
44 timer.start("clip OSM document to boundary");
45 if let Some(pts) = clip_pts {
46 map.streets.boundary_polygon = Ring::deduping_new(map.streets.gps_bounds.convert(&pts))
47 .unwrap()
48 .into_polygon();
49 doc.clip(&map.streets.boundary_polygon, timer);
50 } else {
51 map.streets.boundary_polygon = map.streets.gps_bounds.to_bounds().get_rectangle();
52 }
54 timer.stop("clip OSM document to boundary");
55
56 streets_reader::detect_country_code(&mut map.streets);
57
58 let mut out = OsmExtract::new();
59 let mut amenity_points = Vec::new();
60 let mut bus_routes_on_roads: MultiMap<WayID, String> = MultiMap::new();
61 let mut crossing_nodes = HashSet::new();
62 let mut barrier_nodes = Vec::new();
63 let mut extra_pois = Vec::new();
64
65 timer.start_iter("processing OSM nodes", doc.nodes.len());
66 for (id, node) in &doc.nodes {
67 timer.next();
68 out.handle_node(*id, node);
69 for amenity in get_bldg_amenities(&node.tags) {
70 amenity_points.push((node.pt, amenity));
71 }
72 if node.tags.is(osm::HIGHWAY, "crossing") {
73 let kind = if node.tags.is("crossing", "traffic_signals") {
76 CrossingType::Signalized
77 } else {
78 CrossingType::Unsignalized
79 };
80 crossing_nodes.insert((node.pt.to_hashable(), kind));
81 }
82 if node.tags.is("barrier", "bollard") {
84 barrier_nodes.push((*id, node.pt.to_hashable()));
85 }
86
87 if node.tags.is("railway", "station") {
88 if let Some(network) = node.tags.get("network") {
89 if let Some(name) = node.tags.get("name") {
90 if network.contains("London Underground") {
93 extra_pois.push(ExtraPOI {
94 pt: node.pt,
95 kind: ExtraPOIType::LondonUndergroundStation(name.to_string()),
96 });
97 } else if network.contains("National Rail") {
98 extra_pois.push(ExtraPOI {
99 pt: node.pt,
100 kind: ExtraPOIType::NationalRailStation(name.to_string()),
101 });
102 }
103 }
104 }
105 }
106 }
107
108 let mut coastline_groups: Vec<(WayID, Vec<Pt2D>)> = Vec::new();
109 let mut memorial_areas: Vec<Polygon> = Vec::new();
110 let mut amenity_areas: Vec<(Polygon, Amenity)> = Vec::new();
111 timer.start_iter("processing OSM ways", doc.ways.len());
112 for (id, way) in &mut doc.ways {
113 timer.next();
114 let id = *id;
115
116 if out.handle_way(id, &way, &opts.map_config) {
117 continue;
118 } else if way.tags.is(osm::HIGHWAY, "service") {
119 map.parking_aisles.push((id, way.pts.clone()));
121 } else if way.tags.is("natural", "coastline") && !way.tags.is("place", "island") {
122 coastline_groups.push((id, way.pts.clone()));
123 continue;
124 }
125
126 let mut deduped = way.pts.clone();
128 deduped.dedup();
129 let polygon = if let Ok(ring) = Ring::new(deduped) {
130 ring.into_polygon()
131 } else {
132 continue;
133 };
134
135 if is_bldg(&way.tags) {
136 map.buildings.insert(
137 OsmID::Way(id),
138 RawBuilding {
139 polygon,
140 public_garage_name: None,
141 num_parking_spots: 0,
142 amenities: get_bldg_amenities(&way.tags),
143 osm_tags: way.tags.clone(),
144 },
145 );
146 } else if let Some(at) = get_area_type(&way.tags) {
147 map.areas.push(RawArea {
148 area_type: at,
149 osm_id: OsmID::Way(id),
150 polygon,
151 osm_tags: way.tags.clone(),
152 });
153 } else if way.tags.is("amenity", "parking") {
154 map.parking_lots.push(RawParkingLot {
155 osm_id: OsmID::Way(id),
156 polygon,
157 osm_tags: way.tags.clone(),
158 });
159 } else if way.tags.is("historic", "memorial") {
160 memorial_areas.push(polygon);
161 } else if way.tags.contains_key("amenity") {
162 let amenity = Amenity {
163 names: NamePerLanguage::new(&way.tags).unwrap_or_else(NamePerLanguage::unnamed),
164 amenity_type: way.tags.get("amenity").unwrap().clone(),
165 osm_tags: way.tags.clone(),
166 };
167 amenity_areas.push((polygon, amenity));
168 }
169 }
170
171 let boundary = map.streets.boundary_polygon.get_outer_ring();
172
173 timer.start_iter("processing OSM relations", doc.relations.len());
174 for (id, rel) in &doc.relations {
175 timer.next();
176 let id = *id;
177
178 if out.handle_relation(id, rel) {
179 continue;
180 } else if let Some(area_type) = get_area_type(&rel.tags) {
181 if rel.tags.is("type", "multipolygon") {
182 for polygon in
183 glue_multipolygon(id, doc.get_multipolygon_members(id, rel), Some(&boundary))
184 {
185 map.areas.push(RawArea {
186 area_type,
187 osm_id: OsmID::Relation(id),
188 polygon,
189 osm_tags: rel.tags.clone(),
190 });
191 }
192 }
193 } else if is_bldg(&rel.tags) {
194 match doc.multipoly_geometry(id, rel) {
195 Ok(polygons) => {
196 for polygon in polygons {
197 map.buildings.insert(
198 OsmID::Relation(id),
199 RawBuilding {
200 polygon,
201 public_garage_name: None,
202 num_parking_spots: 0,
203 amenities: get_bldg_amenities(&rel.tags),
204 osm_tags: rel.tags.clone(),
205 },
206 );
207 }
208 }
209 Err(err) => println!("Skipping building {}: {}", id, err),
210 }
211 } else if rel.tags.is("amenity", "parking") {
212 for polygon in
213 glue_multipolygon(id, doc.get_multipolygon_members(id, rel), Some(&boundary))
214 {
215 map.parking_lots.push(RawParkingLot {
216 osm_id: OsmID::Relation(id),
217 polygon,
218 osm_tags: rel.tags.clone(),
219 });
220 }
221 } else if rel.tags.is("type", "multipolygon") && rel.tags.contains_key("amenity") {
222 let amenity = Amenity {
223 names: NamePerLanguage::new(&rel.tags).unwrap_or_else(NamePerLanguage::unnamed),
224 amenity_type: rel.tags.get("amenity").unwrap().clone(),
225 osm_tags: rel.tags.clone(),
226 };
227 for (role, member) in &rel.members {
228 if role != "outer" {
229 continue;
230 }
231 if let OsmID::Way(w) = member {
232 if let Some(b) = map.buildings.get_mut(member) {
234 b.amenities.push(amenity.clone());
235 } else if let Ok(ring) = Ring::new(doc.ways[w].pts.clone()) {
236 amenity_areas.push((ring.into_polygon(), amenity.clone()));
238 }
239 }
240 }
241 } else if rel.tags.is("type", "route") && rel.tags.is("route", "bus") {
242 if let Some(name) = rel.tags.get("name") {
243 for (role, member) in &rel.members {
244 if let OsmID::Way(w) = member {
245 if role.is_empty() {
246 bus_routes_on_roads.insert(*w, name.to_string());
247 }
248 }
249 }
250 }
251 }
252 }
253
254 for polygon in glue_multipolygon(RelationID(-1), coastline_groups, Some(&boundary)) {
256 let mut osm_tags = Tags::empty();
257 osm_tags.insert("water", "ocean");
258 map.areas.insert(
260 0,
261 RawArea {
262 area_type: AreaType::Water,
263 osm_id: OsmID::Relation(RelationID(-1)),
264 polygon,
265 osm_tags,
266 },
267 );
268 }
269
270 timer.start_iter("match buildings to memorial areas", memorial_areas.len());
272 for area in memorial_areas {
273 timer.next();
274 map.buildings
275 .retain(|_, b| !area.contains_pt(b.polygon.center()));
276 }
277
278 let mut closest_bldg: FindClosest<OsmID> = FindClosest::new();
279 for (id, b) in &map.buildings {
280 closest_bldg.add_polygon(*id, &b.polygon);
281 }
282
283 timer.start_iter("match building amenities", amenity_points.len());
284 for (pt, amenity) in amenity_points {
285 timer.next();
286 if let Some((id, _)) = closest_bldg.closest_pt(pt, Distance::meters(50.0)) {
287 let b = map.buildings.get_mut(&id).unwrap();
288 if b.polygon.contains_pt(pt) {
289 b.amenities.push(amenity);
290 }
291 }
292 }
293
294 timer.start_iter("match buildings to amenity areas", amenity_areas.len());
295 for (poly, amenity) in amenity_areas {
296 timer.next();
297 for b in closest_bldg.all_points_inside(&poly) {
298 map.buildings
299 .get_mut(&b)
300 .unwrap()
301 .amenities
302 .push(amenity.clone());
303 }
304 }
305
306 map.areas.sort_by_key(|a| match a.area_type {
309 AreaType::Island => 2,
310 AreaType::Water => 1,
311 _ => 0,
312 });
313
314 timer.start("find service roads crossing parking lots");
315 if map.name != abstio::MapName::new("au", "melbourne", "maribyrnong") {
317 find_parking_aisles(map, &mut out.roads);
318 }
319 timer.stop("find service roads crossing parking lots");
320
321 Extract {
322 osm: out,
323 doc,
324 bus_routes_on_roads,
325 crossing_nodes,
326 barrier_nodes,
327 extra_pois,
328 }
329}
330
331fn is_bldg(tags: &Tags) -> bool {
332 tags.contains_key("building") && !tags.contains_key("abandoned:man_made")
334}
335
336fn get_bldg_amenities(tags: &Tags) -> Vec<Amenity> {
337 let mut amenities = Vec::new();
338 for key in ["amenity", "shop", "craft", "office", "tourism", "leisure"] {
339 if let Some(amenity) = tags.get(key) {
340 amenities.push(Amenity {
341 names: NamePerLanguage::new(tags).unwrap_or_else(NamePerLanguage::unnamed),
342 amenity_type: amenity.clone(),
343 osm_tags: tags.clone(),
344 });
345 }
346 }
347 amenities
348}
349
350fn get_area_type(tags: &Tags) -> Option<AreaType> {
351 if tags.is_any("leisure", vec!["garden", "park", "golf_course"]) {
352 return Some(AreaType::Park);
353 }
354 if tags.is_any("natural", vec!["wood", "scrub"]) {
355 return Some(AreaType::Park);
356 }
357 if tags.is_any(
358 "landuse",
359 vec![
360 "cemetery",
361 "flowerbed",
362 "forest",
363 "grass",
364 "meadow",
365 "recreation_ground",
366 "village_green",
367 ],
368 ) || tags.is("amenity", "graveyard")
369 {
370 return Some(AreaType::Park);
371 }
372
373 if tags.is("natural", "water") || tags.is("waterway", "riverbank") {
374 return Some(AreaType::Water);
375 }
376
377 if tags.is("place", "island") {
378 return Some(AreaType::Island);
379 }
380
381 None
382}
383
384fn find_parking_aisles(map: &mut RawMap, roads: &mut Vec<(WayID, Vec<Pt2D>, Tags)>) {
387 let mut closest: FindClosest<usize> = FindClosest::new();
388 for (idx, lot) in map.parking_lots.iter().enumerate() {
389 closest.add_polygon(idx, &lot.polygon);
390 }
391 let mut keep_roads = Vec::new();
392 let mut parking_aisles = Vec::new();
393 for (id, pts, osm_tags) in roads.drain(..) {
394 if !osm_tags.is(osm::HIGHWAY, "service") {
395 keep_roads.push((id, pts, osm_tags));
396 continue;
397 }
398 let candidates: Vec<usize> = closest
402 .all_close_pts(Pt2D::center(&pts), Distance::meters(500.0))
403 .into_iter()
404 .map(|(idx, _, _)| idx)
405 .collect();
406 if service_road_crosses_parking_lot(map, &pts, candidates) {
407 parking_aisles.push((id, pts));
408 } else {
409 keep_roads.push((id, pts, osm_tags));
410 }
411 }
412 roads.extend(keep_roads);
413 for (id, pts) in parking_aisles {
414 map.parking_aisles.push((id, pts));
415 }
416}
417
418fn service_road_crosses_parking_lot(map: &RawMap, pts: &[Pt2D], candidates: Vec<usize>) -> bool {
419 if let Ok((polylines, rings)) = Ring::split_points(pts) {
420 for pl in polylines {
421 for idx in &candidates {
422 if map.parking_lots[*idx].polygon.clip_polyline(&pl).is_some() {
423 return true;
424 }
425 }
426 }
427 for ring in rings {
428 for idx in &candidates {
429 if map.parking_lots[*idx].polygon.clip_ring(&ring).is_some() {
430 return true;
431 }
432 }
433 }
434 }
435 false
436}