1#[macro_use]
2extern crate log;
3
4use std::collections::{HashMap, HashSet};
5
6use anyhow::Result;
7
8use abstio::MapName;
9use abstutil::{Tags, Timer};
10use geom::{Distance, HashablePt2D, LonLat, PolyLine, Polygon};
11use osm2streets::{osm, MapConfig, Road, RoadID};
12use raw_map::{CrossingType, ExtraRoadData, RawMap};
13
14mod elevation;
15mod extract;
16mod gtfs;
17mod parking;
18
19pub struct Options {
21 pub map_config: MapConfig,
22
23 pub onstreet_parking: OnstreetParking,
24 pub public_offstreet_parking: PublicOffstreetParking,
25 pub private_offstreet_parking: PrivateOffstreetParking,
26 pub extra_buildings: Option<String>,
28 pub gtfs_url: Option<String>,
30 pub elevation_geotiff: Option<String>,
32 pub filter_crosswalks: bool,
34}
35
36impl Options {
37 pub fn default() -> Self {
38 Self {
39 map_config: MapConfig::default(),
40 onstreet_parking: OnstreetParking::JustOSM,
41 public_offstreet_parking: PublicOffstreetParking::None,
42 private_offstreet_parking: PrivateOffstreetParking::FixedPerBldg(1),
43 extra_buildings: None,
44 gtfs_url: None,
45 elevation_geotiff: None,
46 filter_crosswalks: false,
47 }
48 }
49}
50
51pub enum OnstreetParking {
54 JustOSM,
56 Blockface(String),
59}
60
61pub enum PublicOffstreetParking {
63 None,
64 Gis(String),
68}
69
70pub enum PrivateOffstreetParking {
73 FixedPerBldg(usize),
74 }
76
77pub fn convert(
79 osm_input_path: String,
80 name: MapName,
81 clip_path: Option<String>,
82 opts: Options,
83 timer: &mut Timer,
84) -> RawMap {
85 timer.start("create RawMap from input data");
86
87 let mut map = RawMap::blank(name);
88 map.streets.config = opts.map_config.clone();
91
92 let clip_pts = clip_path.map(|path| LonLat::read_geojson_polygon(&path).unwrap());
93 timer.start("extract all from OSM");
94 let extract = extract::extract_osm(&mut map, &osm_input_path, clip_pts, &opts, timer);
95 timer.stop("extract all from OSM");
96 let pt_to_road =
97 streets_reader::split_ways::split_up_roads(&mut map.streets, extract.osm, timer);
98
99 map.streets.retain_roads(|r| r.src_i != r.dst_i);
101
102 map.bus_routes_on_roads = extract.bus_routes_on_roads;
103 map.extra_pois = extract.extra_pois;
104
105 clip_map(&mut map, timer);
106
107 for i in map.streets.intersections.keys() {
108 map.elevation_per_intersection.insert(*i, Distance::ZERO);
109 }
110 for r in map.streets.roads.keys() {
111 map.extra_road_data.insert(*r, ExtraRoadData::default());
112 }
113
114 timer.start("preserve OSM tags");
116 let mut way_ids = HashSet::new();
117 for r in map.streets.roads.values() {
118 for id in &r.osm_ids {
119 way_ids.insert(*id);
120 }
121 }
122 for (id, way) in extract.doc.ways {
123 if way_ids.contains(&id) {
124 map.osm_tags.insert(id, way.tags);
125 }
126 }
127 timer.stop("preserve OSM tags");
128
129 parking::apply_parking(&mut map, &opts, timer);
130
131 timer.start("use barrier and crossing nodes");
132 use_barrier_nodes(&mut map, extract.barrier_nodes, &pt_to_road);
133 use_crossing_nodes(&mut map, &extract.crossing_nodes, &pt_to_road);
134 timer.stop("use barrier and crossing nodes");
135
136 if opts.filter_crosswalks {
137 filter_crosswalks(&mut map, extract.crossing_nodes, pt_to_road, timer);
138 }
139
140 if let Some(ref path) = opts.elevation_geotiff {
141 timer.start("add elevation data");
142 if let Err(err) = elevation::add_data(&mut map, path, timer) {
143 error!("No elevation data: {}", err);
144 }
145 timer.stop("add elevation data");
146 }
147 if let Some(ref path) = opts.extra_buildings {
148 add_extra_buildings(&mut map, path).unwrap();
149 }
150
151 if opts.gtfs_url.is_some() {
152 gtfs::import(&mut map).unwrap();
153 }
154
155 timer.start("Add census data");
156 if let Err(err) = add_census(&mut map) {
157 error!("Skipping census data: {err}");
158 }
159 timer.stop("Add census data");
160
161 if map.name == MapName::new("gb", "bristol", "east") {
162 bristol_hack(&mut map);
163 }
164
165 timer.stop("create RawMap from input data");
166
167 map
168}
169
170fn add_extra_buildings(map: &mut RawMap, path: &str) -> Result<()> {
171 let require_in_bounds = true;
172 let mut id = -1;
173 for (polygon, _) in Polygon::from_geojson_bytes(
174 &abstio::slurp_file(path)?,
175 &map.streets.gps_bounds,
176 require_in_bounds,
177 )? {
178 map.buildings.insert(
180 osm::OsmID::Way(osm::WayID(id)),
181 raw_map::RawBuilding {
182 polygon,
183 osm_tags: Tags::empty(),
184 public_garage_name: None,
185 num_parking_spots: 1,
186 amenities: Vec::new(),
187 },
188 );
189 id -= -1;
192 }
193 Ok(())
194}
195
196fn bristol_hack(map: &mut RawMap) {
199 let mut tags = Tags::empty();
200 tags.insert("highway", "service");
201 tags.insert("name", "Fake road");
202 tags.insert("oneway", "yes");
203 tags.insert("sidewalk", "none");
204 tags.insert("lanes", "1");
205 tags.insert("maxspeed", "1 mph");
209 tags.insert("bicycle", "no");
210
211 let src_i = map
212 .streets
213 .intersections
214 .values()
215 .find(|i| i.osm_ids.contains(&osm::NodeID(364061012)))
216 .unwrap()
217 .id;
218 let dst_i = map
219 .streets
220 .intersections
221 .values()
222 .find(|i| i.osm_ids.contains(&osm::NodeID(1215755208)))
223 .unwrap()
224 .id;
225
226 let id = map.streets.next_road_id();
227 map.streets.insert_road(Road::new(
228 id,
229 Vec::new(),
230 src_i,
231 dst_i,
232 PolyLine::must_new(vec![
233 map.streets.intersections[&src_i].polygon.center(),
234 map.streets.intersections[&dst_i].polygon.center(),
235 ]),
236 tags,
237 &map.streets.config,
238 ));
239 map.extra_road_data.insert(id, ExtraRoadData::default());
240}
241
242fn clip_map(map: &mut RawMap, timer: &mut Timer) {
243 let boundary_polygon = map.streets.boundary_polygon.clone();
244
245 map.buildings = timer.retain_parallelized(
246 "clip buildings to boundary",
247 std::mem::take(&mut map.buildings),
248 |b| {
249 b.polygon
250 .get_outer_ring()
251 .points()
252 .iter()
253 .all(|pt| boundary_polygon.contains_pt(*pt))
254 },
255 );
256
257 map.areas = timer
258 .parallelize(
259 "clip areas to boundary",
260 std::mem::take(&mut map.areas),
261 |orig_area| {
262 let mut result = Vec::new();
263 if let Ok(list) = map
265 .streets
266 .boundary_polygon
267 .intersection(&orig_area.polygon)
268 {
269 for polygon in list {
270 let mut area = orig_area.clone();
271 area.polygon = polygon;
272 result.push(area);
273 }
274 }
275 result
276 },
277 )
278 .into_iter()
279 .flatten()
280 .collect();
281
282 }
285
286fn use_barrier_nodes(
287 map: &mut RawMap,
288 barrier_nodes: Vec<(osm::NodeID, HashablePt2D)>,
289 pt_to_road: &HashMap<HashablePt2D, RoadID>,
290) {
291 let mut node_to_intersection = HashMap::new();
293 for i in map.streets.intersections.values() {
294 for node in &i.osm_ids {
295 node_to_intersection.insert(*node, i.id);
296 }
297 }
298
299 for (node, pt) in barrier_nodes {
300 if let Some(road) = pt_to_road.get(&pt).and_then(|r| map.streets.roads.get(r)) {
302 if road.is_driveable() {
304 map.extra_road_data
305 .get_mut(&road.id)
306 .unwrap()
307 .barrier_nodes
308 .push(pt.to_pt2d());
309 }
310 } else if let Some(i) = node_to_intersection.get(&node) {
311 let roads = &map.streets.intersections[i].roads;
312 if roads.len() == 2 {
313 map.extra_road_data
315 .get_mut(&roads[0])
316 .unwrap()
317 .barrier_nodes
318 .push(pt.to_pt2d());
319 } else {
320 warn!(
324 "There's a barrier at {i}, but there are {} roads connected",
325 roads.len()
326 );
327 }
328 }
329 }
330}
331
332fn use_crossing_nodes(
333 map: &mut RawMap,
334 crossing_nodes: &HashSet<(HashablePt2D, CrossingType)>,
335 pt_to_road: &HashMap<HashablePt2D, RoadID>,
336) {
337 for (pt, kind) in crossing_nodes {
338 if let Some(road) = pt_to_road
340 .get(pt)
341 .and_then(|r| map.extra_road_data.get_mut(r))
342 {
343 road.crossing_nodes.push((pt.to_pt2d(), *kind));
344 }
345 }
346}
347
348fn filter_crosswalks(
349 map: &mut RawMap,
350 crosswalks: HashSet<(HashablePt2D, CrossingType)>,
351 pt_to_road: HashMap<HashablePt2D, RoadID>,
352 timer: &mut Timer,
353) {
354 for road in map.extra_road_data.values_mut() {
357 road.crosswalk_forward = false;
358 road.crosswalk_backward = false;
359 }
360
361 timer.start_iter("filter crosswalks", crosswalks.len());
363 for (pt, _) in crosswalks {
364 timer.next();
365 if let Some(road) = pt_to_road.get(&pt).and_then(|r| map.streets.roads.get(r)) {
368 if let Some((dist, _)) = road.reference_line.dist_along_of_point(pt.to_pt2d()) {
371 let pct = dist / road.reference_line.length();
372 let data = map.extra_road_data.get_mut(&road.id).unwrap();
376 if pct <= 0.5 {
377 data.crosswalk_backward = true;
378 } else {
379 data.crosswalk_forward = true;
380 }
381
382 }
385 }
386 }
387}
388
389fn add_census(map: &mut RawMap) -> Result<()> {
390 if map.name.city.country != "gb" {
392 return Ok(());
393 }
394 let input_path = "data/input/shared/popgetter/england.topojson";
395 let boundary = map
396 .streets
397 .boundary_polygon
398 .to_geo_wgs84(&map.streets.gps_bounds);
399 for (geo_polygon, census_zone) in popgetter::clip_zones(input_path, boundary)? {
400 match Polygon::from_geo_wgs84(geo_polygon, &map.streets.gps_bounds) {
401 Ok(polygon) => {
402 map.census_zones.push((polygon, census_zone));
403 }
404 Err(err) => {
405 warn!("Skipping census zone {}: {}", census_zone.id, err);
406 }
407 }
408 }
409 Ok(())
410}