From 7fa75b5103c2e01a6c31bb6fb890ca100fcad264 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Mon, 12 Apr 2021 16:16:56 -0700 Subject: [PATCH] Repair parking lot connections after road widening. #597 --- game/src/edit/mod.rs | 3 + map_gui/src/render/parking_lot.rs | 4 ++ map_model/src/edits/mod.rs | 63 +++++++++++++++++-- map_model/src/make/mod.rs | 1 + map_model/src/make/parking_lots.rs | 98 +++++++++++++++++------------- 5 files changed, 121 insertions(+), 48 deletions(-) diff --git a/game/src/edit/mod.rs b/game/src/edit/mod.rs index d444205fd6..f4bab920a6 100644 --- a/game/src/edit/mod.rs +++ b/game/src/edit/mod.rs @@ -709,6 +709,9 @@ pub fn apply_map_edits(ctx: &mut EventCtx, app: &mut App, edits: MapEdits) { .draw_map .recreate_building_paths(ctx, &app.primary.map, &app.cs, &app.opts); } + for pl in effects.changed_parking_lots { + app.primary.draw_map.get_pl(pl).clear_rendering(); + } if app.primary.layer.as_ref().and_then(|l| l.name()) == Some("map edits") { app.primary.layer = Some(Box::new(crate::layer::map::Static::edits(ctx, app))); diff --git a/map_gui/src/render/parking_lot.rs b/map_gui/src/render/parking_lot.rs index c8f368b8a4..fbd692e6ac 100644 --- a/map_gui/src/render/parking_lot.rs +++ b/map_gui/src/render/parking_lot.rs @@ -92,6 +92,10 @@ impl DrawParkingLot { batch } + + pub fn clear_rendering(&self) { + *self.draw.borrow_mut() = None; + } } impl Renderable for DrawParkingLot { diff --git a/map_model/src/edits/mod.rs b/map_model/src/edits/mod.rs index 5a4c512587..0457c809ab 100644 --- a/map_model/src/edits/mod.rs +++ b/map_model/src/edits/mod.rs @@ -12,11 +12,11 @@ use geom::{Distance, HashablePt2D, Line, Speed, Time}; pub use self::perma::PermanentMapEdits; use crate::make::initial::lane_specs::get_lane_specs_ltr; -use crate::make::{match_points_to_lanes, trim_path}; +use crate::make::{match_points_to_lanes, snap_driveway, trim_path}; use crate::{ connectivity, AccessRestrictions, BuildingID, BusRouteID, ControlStopSign, ControlTrafficSignal, IntersectionID, IntersectionType, LaneID, LaneSpec, Map, MapConfig, - PathConstraints, Pathfinder, Road, RoadID, TurnID, Zone, + ParkingLotID, PathConstraints, Pathfinder, Road, RoadID, TurnID, Zone, }; mod compat; @@ -139,6 +139,7 @@ pub struct EditEffects { pub added_turns: BTreeSet, pub deleted_turns: BTreeSet, pub resnapped_buildings: bool, + pub changed_parking_lots: BTreeSet, } impl MapEdits { @@ -537,18 +538,31 @@ fn modify_lanes(map: &mut Map, r: RoadID, lanes_ltr: Vec, effects: &mu modified_lanes.insert(id); } } + modified_lanes.extend(effects.deleted_lanes.clone()); // Find all buildings connected to modified/deleted sidewalks let mut recalc_buildings = Vec::new(); for b in map.all_buildings() { - if effects.deleted_lanes.contains(&b.sidewalk()) || modified_lanes.contains(&b.sidewalk()) { + if modified_lanes.contains(&b.sidewalk()) { recalc_buildings.push(b.id); + effects.resnapped_buildings = true; } - effects.resnapped_buildings = true; } fix_buildings(map, recalc_buildings); - // TODO We need to update bus stops and parking lots -- they may refer to an old ID. + // Same for parking lots + let mut recalc_parking_lots = Vec::new(); + for pl in map.all_parking_lots() { + if modified_lanes.contains(&pl.driving_pos.lane()) + || modified_lanes.contains(&pl.sidewalk_pos.lane()) + { + recalc_parking_lots.push(pl.id); + effects.changed_parking_lots.insert(pl.id); + } + } + fix_parking_lots(map, recalc_parking_lots); + + // TODO We need to update bus stops -- they may refer to an old ID. } // Returns the other roads affected by this change, not counting changed_road. @@ -651,6 +665,44 @@ fn fix_buildings(map: &mut Map, input: Vec) { } } +fn fix_parking_lots(map: &mut Map, input: Vec) { + // TODO Partly copying from make/parking_lots.rs + let mut center_per_lot: Vec<(ParkingLotID, HashablePt2D)> = Vec::new(); + let mut query: HashSet = HashSet::new(); + for id in input { + let center = map.get_pl(id).polygon.center().to_hashable(); + center_per_lot.push((id, center)); + query.insert(center); + } + + let sidewalk_buffer = Distance::meters(7.5); + let sidewalk_pts = match_points_to_lanes( + map.get_bounds(), + query, + map.all_lanes(), + |l| l.is_walkable(), + sidewalk_buffer, + Distance::meters(1000.0), + &mut Timer::throwaway(), + ); + + for (id, center) in center_per_lot { + match snap_driveway(center, &map.get_pl(id).polygon, &sidewalk_pts, map) { + Ok((driveway_line, driving_pos, sidewalk_line, sidewalk_pos)) => { + let pl = &mut map.parking_lots[id.0]; + pl.driveway_line = driveway_line; + pl.driving_pos = driving_pos; + pl.sidewalk_line = sidewalk_line; + pl.sidewalk_pos = sidewalk_pos; + } + Err(err) => { + // TODO Not sure what to do here yet. + error!("{} isn't snapped to a sidewalk now: {}", id, err); + } + } + } +} + impl Map { pub fn new_edits(&self) -> MapEdits { let mut edits = MapEdits::new(); @@ -731,6 +783,7 @@ impl Map { added_turns: BTreeSet::new(), deleted_turns: BTreeSet::new(), resnapped_buildings: false, + changed_parking_lots: BTreeSet::new(), }; // Short-circuit to avoid marking pathfinder_dirty diff --git a/map_model/src/make/mod.rs b/map_model/src/make/mod.rs index 6061ecafdc..e45d33efea 100644 --- a/map_model/src/make/mod.rs +++ b/map_model/src/make/mod.rs @@ -9,6 +9,7 @@ use geom::{ Bounds, Distance, FindClosest, GPSBounds, HashablePt2D, Line, Polygon, Speed, EPSILON_DIST, }; +pub use self::parking_lots::snap_driveway; use crate::pathfind::Pathfinder; use crate::raw::{OriginalRoad, RawMap}; use crate::{ diff --git a/map_model/src/make/parking_lots.rs b/map_model/src/make/parking_lots.rs index e989f3bd40..20a8db9905 100644 --- a/map_model/src/make/parking_lots.rs +++ b/map_model/src/make/parking_lots.rs @@ -1,4 +1,6 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; + +use anyhow::Result; use abstutil::{Parallelism, Timer}; use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring}; @@ -29,7 +31,6 @@ pub fn make_all_parking_lots( } let sidewalk_buffer = Distance::meters(7.5); - let driveway_buffer = Distance::meters(7.0); let sidewalk_pts = match_points_to_lanes( map.get_bounds(), query, @@ -44,40 +45,8 @@ pub fn make_all_parking_lots( timer.start_iter("create parking lot driveways", center_per_lot.len()); for (lot_center, orig) in center_per_lot.into_iter().zip(input.iter()) { timer.next(); - // TODO Refactor this - if let Some(sidewalk_pos) = sidewalk_pts.get(&lot_center) { - let sidewalk_line = match Line::new(lot_center.to_pt2d(), sidewalk_pos.pt(map)) { - Some(l) => trim_path(&orig.polygon, l), - None => { - warn!( - "Skipping parking lot {} because front path has 0 length", - orig.osm_id - ); - continue; - } - }; - - // Can this lot have a driveway? If it's not next to a driving lane, then no. - let mut driveway: Option<(PolyLine, Position)> = None; - let sidewalk_lane = sidewalk_pos.lane(); - if let Some(driving_pos) = map - .get_parent(sidewalk_lane) - .find_closest_lane(sidewalk_lane, |l| PathConstraints::Car.can_use(l, map), map) - .and_then(|l| { - sidewalk_pos - .equiv_pos(l, map) - .buffer_dist(driveway_buffer, map) - }) - { - if let Ok(pl) = PolyLine::new(vec![ - sidewalk_line.pt1(), - sidewalk_line.pt2(), - driving_pos.pt(map), - ]) { - driveway = Some((pl, driving_pos)); - } - } - if let Some((driveway_line, driving_pos)) = driveway { + match snap_driveway(lot_center, &orig.polygon, &sidewalk_pts, map) { + Ok((driveway_line, driving_pos, sidewalk_line, sidewalk_pos)) => { let id = ParkingLotID(results.len()); results.push(ParkingLot { id, @@ -90,14 +59,11 @@ pub fn make_all_parking_lots( driveway_line, driving_pos, sidewalk_line, - sidewalk_pos: *sidewalk_pos, + sidewalk_pos, }); - } else { - warn!( - "Parking lot from {}, near sidewalk {}, can't have a driveway.", - orig.osm_id, - sidewalk_pos.lane() - ); + } + Err(err) => { + warn!("Skipping parking lot {}: {}", orig.osm_id, err); } } } @@ -169,6 +135,52 @@ pub fn make_all_parking_lots( results } +/// Returns (driveway_line, driving_pos, sidewalk_line, sidewalk_pos) +pub fn snap_driveway( + center: HashablePt2D, + polygon: &Polygon, + sidewalk_pts: &HashMap, + map: &Map, +) -> Result<(PolyLine, Position, Line, Position)> { + let driveway_buffer = Distance::meters(7.0); + + let sidewalk_pos = sidewalk_pts + .get(¢er) + .ok_or(anyhow!("parking lot center didn't snap to a sidewalk"))?; + let sidewalk_line = match Line::new(center.to_pt2d(), sidewalk_pos.pt(map)) { + Some(l) => trim_path(polygon, l), + None => { + bail!("front path has 0 length"); + } + }; + + // Can this lot have a driveway? If it's not next to a driving lane, then no. + let mut driveway: Option<(PolyLine, Position)> = None; + let sidewalk_lane = sidewalk_pos.lane(); + if let Some(driving_pos) = map + .get_parent(sidewalk_lane) + .find_closest_lane(sidewalk_lane, |l| PathConstraints::Car.can_use(l, map), map) + .and_then(|l| { + sidewalk_pos + .equiv_pos(l, map) + .buffer_dist(driveway_buffer, map) + }) + { + if let Ok(pl) = PolyLine::new(vec![ + sidewalk_line.pt1(), + sidewalk_line.pt2(), + driving_pos.pt(map), + ]) { + driveway = Some((pl, driving_pos)); + } + } + let (driveway_line, driving_pos) = driveway.ok_or(anyhow!( + "snapped to sidewalk {}, but no driving connection", + sidewalk_pos.lane() + ))?; + Ok((driveway_line, driving_pos, sidewalk_line, *sidewalk_pos)) +} + fn infer_spots(lot_polygon: &Polygon, aisles: &Vec>) -> Vec<(Pt2D, Angle)> { let mut spots = Vec::new(); let mut finalized_lines = Vec::new();