map_editor/
model.rs

1use std::collections::HashMap;
2use std::io::Write;
3
4use abstio::{CityName, MapName};
5use abstutil::{Tags, Timer};
6use geom::{
7    Circle, Distance, FindClosest, GPSBounds, HashablePt2D, LonLat, PolyLine, Polygon, Pt2D,
8};
9use osm2streets::{osm, IntersectionControl, IntersectionID, IntersectionKind, Road, RoadID};
10use raw_map::{RawBuilding, RawMap};
11use widgetry::mapspace::{ObjectID, World};
12use widgetry::{Color, EventCtx, GeomBatch, Key};
13
14const INTERSECTION_RADIUS: Distance = Distance::const_meters(2.5);
15const BUILDING_LENGTH: Distance = Distance::const_meters(30.0);
16
17// The caller should generally call world.initialize_hover after a mutation.
18pub struct Model {
19    // map and world are pub. The main crate should use them directly for simple stuff, to avoid
20    // boilerplate delegation methods. Complex changes should be proper methods on the model.
21    pub map: RawMap,
22    showing_pts: Option<RoadID>,
23    pub world: World<ID>,
24
25    pub include_bldgs: bool,
26    pub intersection_geom: bool,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30pub enum ID {
31    Building(osm::OsmID),
32    Intersection(IntersectionID),
33    Road(RoadID),
34    RoadPoint(RoadID, usize),
35}
36impl ObjectID for ID {}
37
38// Construction
39impl Model {
40    pub fn blank() -> Model {
41        Model {
42            map: RawMap::blank(MapName {
43                city: CityName {
44                    country: String::new(),
45                    city: String::new(),
46                },
47                map: String::new(),
48            }),
49            showing_pts: None,
50
51            include_bldgs: false,
52            world: World::new(),
53            intersection_geom: false,
54        }
55    }
56
57    pub fn from_map(ctx: &EventCtx, map: RawMap, include_bldgs: bool, timer: &mut Timer) -> Model {
58        let mut model = Model::blank();
59        model.include_bldgs = include_bldgs;
60        model.map = map;
61        model.recreate_world(ctx, timer);
62        model
63    }
64
65    pub fn recreate_world(&mut self, ctx: &EventCtx, timer: &mut Timer) {
66        self.showing_pts = None;
67        self.world = World::new();
68
69        if self.include_bldgs {
70            for id in self.map.buildings.keys().cloned().collect::<Vec<_>>() {
71                self.bldg_added(ctx, id);
72            }
73        }
74        timer.start_iter(
75            "fill out world with intersections",
76            self.map.streets.intersections.len(),
77        );
78        for id in self
79            .map
80            .streets
81            .intersections
82            .keys()
83            .cloned()
84            .collect::<Vec<_>>()
85        {
86            timer.next();
87            self.intersection_added(ctx, id);
88        }
89        timer.start_iter("fill out world with roads", self.map.streets.roads.len());
90        for id in self.map.streets.roads.keys().cloned().collect::<Vec<_>>() {
91            timer.next();
92            self.road_added(ctx, id);
93        }
94
95        self.world.initialize_hover(ctx);
96        // No need to restore dragging
97    }
98}
99
100// General
101impl Model {
102    pub fn export_to_osm(&mut self) {
103        dump_to_osm(&self.map).unwrap();
104    }
105
106    pub fn set_boundary(&mut self, ctx: &EventCtx, top_left: Pt2D, bottom_right: Pt2D) {
107        // Shift the map to treat top_left as (0, 0)
108        for b in self.map.buildings.values_mut() {
109            b.polygon = b.polygon.translate(-top_left.x(), -top_left.y());
110        }
111        for i in self.map.streets.intersections.values_mut() {
112            i.polygon = i.polygon.translate(-top_left.x(), -top_left.y());
113        }
114        for r in self.map.streets.roads.values_mut() {
115            r.reference_line = PolyLine::must_new(
116                r.reference_line
117                    .points()
118                    .iter()
119                    .map(|pt| pt.offset(-top_left.x(), -top_left.y()))
120                    .collect(),
121            );
122            r.update_center_line(self.map.streets.config.driving_side);
123        }
124        let pt1 = Pt2D::new(0.0, 0.0);
125        let pt2 = bottom_right.offset(-top_left.x(), -top_left.y());
126
127        self.map.streets.boundary_polygon = Polygon::rectangle_two_corners(pt1, pt2).unwrap();
128
129        // Make gps_bounds sane
130        let mut seattle_bounds = GPSBounds::new();
131        seattle_bounds.update(LonLat::new(-122.453224, 47.723277));
132        seattle_bounds.update(LonLat::new(-122.240505, 47.495342));
133
134        self.map.streets.gps_bounds = GPSBounds::new();
135        self.map
136            .streets
137            .gps_bounds
138            .update(pt1.to_gps(&seattle_bounds));
139        self.map
140            .streets
141            .gps_bounds
142            .update(pt2.to_gps(&seattle_bounds));
143
144        self.recreate_world(ctx, &mut Timer::throwaway());
145    }
146}
147
148// Intersections
149impl Model {
150    fn intersection_added(&mut self, ctx: &EventCtx, id: IntersectionID) {
151        let i = &self.map.streets.intersections[&id];
152        let color = if i.kind == IntersectionKind::MapEdge {
153            Color::BLUE
154        } else {
155            match i.control {
156                IntersectionControl::Signalled => Color::GREEN,
157                IntersectionControl::Signed | IntersectionControl::Uncontrolled => Color::RED,
158                IntersectionControl::Construction => Color::ORANGE,
159            }
160        };
161
162        let poly =
163            if self.intersection_geom && !self.map.streets.roads_per_intersection(id).is_empty() {
164                self.map.streets.intersections[&id].polygon.clone()
165            } else {
166                Circle::new(i.polygon.center(), INTERSECTION_RADIUS).to_polygon()
167            };
168
169        self.world
170            .add(ID::Intersection(id))
171            .hitbox(poly)
172            .zorder(1)
173            .draw_color(color)
174            .hover_alpha(0.5)
175            .draggable()
176            .hotkey(Key::R, "start a road here")
177            .hotkey(Key::Backspace, "delete")
178            .hotkey(Key::T, "toggle stop sign / traffic signal")
179            .hotkey(Key::P, "debug intersection geometry")
180            .hotkey(Key::D, "debug in OSM")
181            .build(ctx);
182    }
183
184    pub fn create_i(&mut self, ctx: &EventCtx, point: Pt2D) {
185        let id = self.map.streets.insert_intersection(
186            Vec::new(),
187            point,
188            // The kind will change as we connect things to this intersection
189            IntersectionKind::Intersection,
190            IntersectionControl::Signed,
191        );
192        self.intersection_added(ctx, id);
193    }
194
195    pub fn move_i(&mut self, ctx: &EventCtx, id: IntersectionID, point: Pt2D) {
196        self.world.delete_before_replacement(ID::Intersection(id));
197
198        let i = self.map.streets.intersections.get_mut(&id).unwrap();
199        let old_center = i.polygon.center();
200        i.polygon = i
201            .polygon
202            .translate(point.x() - old_center.x(), point.y() - old_center.y());
203
204        // Update all the roads.
205        let mut fixed = Vec::new();
206        for r in &self.map.streets.intersections[&id].roads {
207            fixed.push(*r);
208            let road = self.map.streets.roads.get_mut(r).unwrap();
209            let mut pts = road.reference_line.clone().into_points();
210            if road.src_i == id {
211                pts[0] = point;
212            } else {
213                assert_eq!(road.dst_i, id);
214                *pts.last_mut().unwrap() = point;
215            }
216            // TODO This could panic if someone moves the intersection a certain way
217            road.reference_line = PolyLine::must_new(pts);
218            road.update_center_line(self.map.streets.config.driving_side);
219        }
220
221        for r in fixed {
222            self.road_deleted(r);
223            self.road_added(ctx, r);
224        }
225        self.intersection_added(ctx, id);
226    }
227
228    pub fn delete_i(&mut self, id: IntersectionID) {
229        if !self.map.streets.intersections[&id].roads.is_empty() {
230            error!("Can't delete intersection used by roads");
231            return;
232        }
233        self.map.streets.remove_intersection(id);
234        self.world.delete(ID::Intersection(id));
235    }
236
237    pub fn toggle_i(&mut self, ctx: &EventCtx, id: IntersectionID) {
238        self.world.delete_before_replacement(ID::Intersection(id));
239
240        let i = self.map.streets.intersections.get_mut(&id).unwrap();
241        if i.control == IntersectionControl::Signalled {
242            i.control = IntersectionControl::Signed;
243        } else if i.control == IntersectionControl::Signed {
244            i.control = IntersectionControl::Signalled;
245        }
246
247        self.intersection_added(ctx, id);
248    }
249
250    pub fn show_intersection_geometry(&mut self, ctx: &mut EventCtx, show: bool) {
251        self.intersection_geom = show;
252
253        ctx.loading_screen("show intersection geometry", |ctx, timer| {
254            if self.intersection_geom {
255                // TODO Restore behavior here
256            }
257
258            timer.start_iter(
259                "intersection geometry",
260                self.map.streets.intersections.len(),
261            );
262            for id in self
263                .map
264                .streets
265                .intersections
266                .keys()
267                .cloned()
268                .collect::<Vec<_>>()
269            {
270                timer.next();
271                self.world.delete_before_replacement(ID::Intersection(id));
272                self.intersection_added(ctx, id);
273            }
274        });
275    }
276}
277
278// Roads
279impl Model {
280    pub fn road_added(&mut self, ctx: &EventCtx, id: RoadID) {
281        let road = &self.map.streets.roads[&id];
282        let center = road.center_line.clone();
283        let total_width = road.total_width();
284        let hitbox = center.make_polygons(total_width);
285        let mut draw = GeomBatch::new();
286        draw.push(
287            if road.internal_junction_road {
288                Color::PINK
289            } else {
290                Color::grey(0.8)
291            },
292            hitbox.clone(),
293        );
294        if let Some(outline) = center.to_thick_boundary(total_width, Distance::meters(1.0)) {
295            draw.push(Color::BLACK, outline);
296        }
297
298        self.world
299            .add(ID::Road(id))
300            .hitbox(hitbox)
301            .zorder(0)
302            .draw(draw)
303            .hover_alpha(0.5)
304            .clickable()
305            .hotkey(Key::Backspace, "delete")
306            .hotkey(Key::P, "insert a new point here")
307            .hotkey(Key::X, "remove interior points")
308            .hotkey(Key::M, "merge")
309            .hotkey(Key::J, "mark/unmark as a junction")
310            .hotkey(Key::D, "debug in OSM")
311            .build(ctx);
312    }
313
314    pub fn road_deleted(&mut self, id: RoadID) {
315        self.world.delete(ID::Road(id));
316    }
317
318    pub fn create_r(&mut self, ctx: &EventCtx, i1: IntersectionID, i2: IntersectionID) {
319        // Ban cul-de-sacs, since they get stripped out later anyway.
320        if self
321            .map
322            .streets
323            .roads
324            .values()
325            .any(|r| (r.src_i == i1 && r.dst_i == i2) || (r.src_i == i2 && r.dst_i == i1))
326        {
327            error!("Road already exists");
328            return;
329        }
330
331        let mut osm_tags = Tags::empty();
332        osm_tags.insert("highway", "residential");
333        osm_tags.insert("parking:both:lane", "parallel");
334        osm_tags.insert("sidewalk", "both");
335        osm_tags.insert("lanes", "2");
336        // Reasonable defaults.
337        osm_tags.insert("name", "Streety McStreetFace");
338        osm_tags.insert("maxspeed", "25 mph");
339
340        let reference_line = match PolyLine::new(vec![
341            self.map.streets.intersections[&i1].polygon.center(),
342            self.map.streets.intersections[&i2].polygon.center(),
343        ]) {
344            Ok(pl) => pl,
345            Err(err) => {
346                error!("Can't create road: {err}");
347                return;
348            }
349        };
350
351        self.world.delete_before_replacement(ID::Intersection(i1));
352        self.world.delete_before_replacement(ID::Intersection(i2));
353
354        let id = self.map.streets.next_road_id();
355        self.map.streets.insert_road(Road::new(
356            id,
357            Vec::new(),
358            i1,
359            i2,
360            reference_line,
361            osm_tags,
362            &self.map.streets.config,
363        ));
364        self.road_added(ctx, id);
365
366        self.intersection_added(ctx, i1);
367        self.intersection_added(ctx, i2);
368    }
369
370    pub fn delete_r(&mut self, ctx: &EventCtx, id: RoadID) {
371        self.stop_showing_pts(id);
372        self.road_deleted(id);
373        let road = self.map.streets.remove_road(id);
374        self.world
375            .delete_before_replacement(ID::Intersection(road.src_i));
376        self.world
377            .delete_before_replacement(ID::Intersection(road.dst_i));
378
379        self.intersection_added(ctx, road.src_i);
380        self.intersection_added(ctx, road.dst_i);
381    }
382
383    pub fn show_r_points(&mut self, ctx: &EventCtx, id: RoadID) {
384        if self.showing_pts == Some(id) {
385            return;
386        }
387        if let Some(other) = self.showing_pts {
388            self.stop_showing_pts(other);
389        }
390        self.showing_pts = Some(id);
391
392        let r = &self.map.streets.roads[&id];
393        for (idx, pt) in r.reference_line.points().iter().enumerate() {
394            // Don't show handles for the intersections
395            if idx != 0 && idx != r.reference_line.points().len() - 1 {
396                self.world
397                    .add(ID::RoadPoint(id, idx))
398                    .hitbox(Circle::new(*pt, INTERSECTION_RADIUS / 2.0).to_polygon())
399                    .zorder(3)
400                    .draw_color(Color::GREEN)
401                    .hover_alpha(0.5)
402                    .draggable()
403                    .hotkey(Key::Backspace, "delete")
404                    .build(ctx);
405            }
406        }
407    }
408
409    pub fn stop_showing_pts(&mut self, id: RoadID) {
410        if self.showing_pts != Some(id) {
411            return;
412        }
413        self.showing_pts = None;
414        for idx in 1..=self.map.streets.roads[&id].reference_line.points().len() - 2 {
415            self.world.delete(ID::RoadPoint(id, idx));
416        }
417    }
418
419    pub fn move_r_pt(&mut self, ctx: &EventCtx, id: RoadID, idx: usize, point: Pt2D) {
420        assert_eq!(self.showing_pts, Some(id));
421        // stop_showing_pts deletes the points, but we want to use delete_before_replacement
422        self.showing_pts = None;
423        for idx in 1..=self.map.streets.roads[&id].reference_line.points().len() - 2 {
424            self.world.delete_before_replacement(ID::RoadPoint(id, idx));
425        }
426
427        self.road_deleted(id);
428        let endpts = self.map.streets.roads[&id].endpoints();
429        self.world
430            .delete_before_replacement(ID::Intersection(endpts[0]));
431        self.world
432            .delete_before_replacement(ID::Intersection(endpts[1]));
433
434        let road = self.map.streets.roads.get_mut(&id).unwrap();
435        let mut pts = road.reference_line.clone().into_points();
436        pts[idx] = point;
437        road.reference_line = PolyLine::must_new(pts);
438        road.update_center_line(self.map.streets.config.driving_side);
439
440        self.road_added(ctx, id);
441        self.intersection_added(ctx, endpts[0]);
442        self.intersection_added(ctx, endpts[1]);
443        self.show_r_points(ctx, id);
444    }
445
446    fn change_r_points<F: FnMut(&mut Vec<Pt2D>)>(
447        &mut self,
448        ctx: &EventCtx,
449        id: RoadID,
450        mut transform: F,
451    ) {
452        assert_eq!(self.showing_pts, Some(id));
453
454        self.stop_showing_pts(id);
455        self.road_deleted(id);
456        let endpts = self.map.streets.roads[&id].endpoints();
457        self.world
458            .delete_before_replacement(ID::Intersection(endpts[0]));
459        self.world
460            .delete_before_replacement(ID::Intersection(endpts[1]));
461
462        let road = self.map.streets.roads.get_mut(&id).unwrap();
463        let mut pts = road.reference_line.clone().into_points();
464        transform(&mut pts);
465        road.reference_line = PolyLine::must_new(pts);
466        road.update_center_line(self.map.streets.config.driving_side);
467
468        self.road_added(ctx, id);
469        self.intersection_added(ctx, endpts[0]);
470        self.intersection_added(ctx, endpts[1]);
471        self.show_r_points(ctx, id);
472    }
473
474    pub fn delete_r_pt(&mut self, ctx: &EventCtx, id: RoadID, idx: usize) {
475        self.change_r_points(ctx, id, |pts| {
476            pts.remove(idx);
477        });
478    }
479
480    pub fn insert_r_pt(&mut self, ctx: &EventCtx, id: RoadID, pt: Pt2D) {
481        let mut closest = FindClosest::new();
482        self.change_r_points(ctx, id, move |pts| {
483            for (idx, pair) in pts.windows(2).enumerate() {
484                closest.add(idx + 1, &[pair[0], pair[1]]);
485            }
486            if let Some((idx, _)) = closest.closest_pt(pt, Distance::meters(5.0)) {
487                pts.insert(idx, pt);
488            } else {
489                warn!("Couldn't figure out where to insert new point");
490            }
491        });
492    }
493
494    pub fn clear_r_pts(&mut self, ctx: &EventCtx, id: RoadID) {
495        self.change_r_points(ctx, id, |pts| {
496            *pts = vec![pts[0], *pts.last().unwrap()];
497        });
498    }
499
500    pub fn merge_r(&mut self, ctx: &EventCtx, id: RoadID) {
501        if let Err(err) = self.map.streets.collapse_short_road(id) {
502            warn!("Can't merge this road: {}", err);
503            return;
504        }
505        info!("Merged {id}");
506
507        // This is very blunt and slow. Multiple roads and intersections might've vanished.
508        self.recreate_world(ctx, &mut Timer::throwaway());
509    }
510
511    pub fn toggle_junction(&mut self, ctx: &EventCtx, id: RoadID) {
512        self.road_deleted(id);
513
514        let road = self.map.streets.roads.get_mut(&id).unwrap();
515        road.internal_junction_road = !road.internal_junction_road;
516
517        self.road_added(ctx, id);
518    }
519}
520
521// Buildings
522impl Model {
523    fn bldg_added(&mut self, ctx: &EventCtx, id: osm::OsmID) {
524        let b = &self.map.buildings[&id];
525        self.world
526            .add(ID::Building(id))
527            .hitbox(b.polygon.clone())
528            .zorder(2)
529            .draw_color(Color::BLUE)
530            .hover_alpha(0.5)
531            .draggable()
532            .hotkey(Key::Backspace, "delete")
533            .build(ctx);
534    }
535
536    pub fn create_b(&mut self, ctx: &EventCtx, center: Pt2D) -> ID {
537        // Bit brittle, but not a big deal
538        let id = osm::OsmID::Way(osm::WayID(-1 * self.map.buildings.len() as i64));
539        self.map.buildings.insert(
540            id,
541            RawBuilding {
542                polygon: Polygon::rectangle_centered(center, BUILDING_LENGTH, BUILDING_LENGTH),
543                osm_tags: Tags::empty(),
544                public_garage_name: None,
545                num_parking_spots: 0,
546                amenities: Vec::new(),
547            },
548        );
549        self.bldg_added(ctx, id);
550        ID::Building(id)
551    }
552
553    pub fn move_b(&mut self, ctx: &EventCtx, id: osm::OsmID, dx: f64, dy: f64) {
554        self.world.delete_before_replacement(ID::Building(id));
555
556        let b = self.map.buildings.get_mut(&id).unwrap();
557        b.polygon = b.polygon.translate(dx, dy);
558
559        self.bldg_added(ctx, id);
560    }
561
562    pub fn delete_b(&mut self, id: osm::OsmID) {
563        self.world.delete(ID::Building(id));
564        self.map.buildings.remove(&id).unwrap();
565    }
566}
567
568/// Express a RawMap as a .osm file. Why not just save the RawMap? The format may change over time,
569/// and even if a RawMap is saved as JSON, manually updating it is annoying. This is used to create
570/// synthetic maps that will never go bad -- there will always be a pipeline to import a .osm file,
571/// so actually, .osm is a stable-over-time format.
572///
573/// If this RawMap was created from real OSM, this will absolutely mangle any original IDs.
574fn dump_to_osm(map: &RawMap) -> Result<(), std::io::Error> {
575    let mut f = fs_err::File::create("synthetic_export.osm")?;
576    writeln!(f, r#"<?xml version='1.0' encoding='UTF-8'?>"#)?;
577    writeln!(f, r#"<osm>"#)?;
578    writeln!(
579        f,
580        r#"<!-- If you couldn't tell, this is a fake .osm file not representing the real world. -->"#
581    )?;
582    let b = &map.streets.gps_bounds;
583    writeln!(
584        f,
585        r#"    <bounds minlon="{}" maxlon="{}" minlat="{}" maxlat="{}"/>"#,
586        b.min_lon, b.max_lon, b.min_lat, b.max_lat
587    )?;
588
589    let mut pt_to_node: HashMap<HashablePt2D, osm::NodeID> = HashMap::new();
590    for r in map.streets.roads.values() {
591        for pt in r.reference_line.points() {
592            let id = osm::NodeID(pt_to_node.len() as i64);
593            pt_to_node.insert(pt.to_hashable(), id);
594            writeln!(
595                f,
596                r#"    <node id="{}" lon="{}" lat="{}"/>"#,
597                id.0,
598                pt.x(),
599                pt.y()
600            )?;
601        }
602    }
603
604    for (id, r) in &map.streets.roads {
605        writeln!(f, r#"    <way id="{}">"#, id.0)?;
606        for pt in r.reference_line.points() {
607            writeln!(
608                f,
609                r#"        <nd ref="{}"/>"#,
610                pt_to_node[&pt.to_hashable()].0
611            )?;
612        }
613        // TODO Brittle. Instead we should effectively do lanes2osm
614        if let Some(tags) = map.road_to_osm_tags(*id) {
615            for (k, v) in tags.inner() {
616                writeln!(f, r#"        <tag k="{}" v="{}"/>"#, k, v)?;
617            }
618        }
619        writeln!(f, r#"    </way>"#)?;
620    }
621    writeln!(f, r#"</osm>"#)?;
622    Ok(())
623}