Refactor MapBorders, used to snap trips starting/ending off-map to a

border. Try weighting by road classification. #848

No behavioral change for anything except UK scenarios. Still not
regenerating those.
This commit is contained in:
Dustin Carlino 2022-02-03 14:20:03 +00:00
parent b93850406f
commit f22590ae0c
5 changed files with 163 additions and 132 deletions

View File

@ -1,12 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use abstutil::{prettyprint_usize, MultiMap, Timer}; use abstutil::{prettyprint_usize, MultiMap, Timer};
use geom::{LonLat, PolyLine}; use geom::PolyLine;
use map_model::{ use map_model::{osm, BuildingID, Map, Path, PathConstraints, PathRequest, PathStep};
osm, BuildingID, IntersectionID, Map, Path, PathConstraints, PathRequest, PathStep,
};
use synthpop::{ use synthpop::{
IndividTrip, MapBorders, OrigPersonID, PersonSpec, Scenario, TripEndpoint, TripMode, IndividTrip, MapBorder, MapBorders, OrigPersonID, PersonSpec, Scenario, TripEndpoint, TripMode,
}; };
use crate::soundcast::popdat::{Endpoint, OrigTrip, PopDat}; use crate::soundcast::popdat::{Endpoint, OrigTrip, PopDat};
@ -30,10 +28,7 @@ fn endpoints(
to: &Endpoint, to: &Endpoint,
map: &Map, map: &Map,
osm_id_to_bldg: &HashMap<osm::OsmID, BuildingID>, osm_id_to_bldg: &HashMap<osm::OsmID, BuildingID>,
(in_borders, out_borders): ( (in_borders, out_borders): (&Vec<MapBorder>, &Vec<MapBorder>),
&Vec<(IntersectionID, LonLat)>,
&Vec<(IntersectionID, LonLat)>,
),
constraints: PathConstraints, constraints: PathConstraints,
maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>, maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>,
only_passthrough_trips: bool, only_passthrough_trips: bool,
@ -93,8 +88,8 @@ fn endpoints(
// Fallback to finding the nearest border with straight-line distance // Fallback to finding the nearest border with straight-line distance
in_borders in_borders
.iter() .iter()
.min_by_key(|(_, pt)| pt.fast_dist(from.pos)) .min_by_key(|border| border.gps_pos.fast_dist(from.pos))
.map(|(id, _)| TripEndpoint::Border(*id)) .map(|border| TripEndpoint::Border(border.i))
})?; })?;
let to_endpt = to_bldg let to_endpt = to_bldg
.or_else(|| snapper.snap_border(out_borders, false, map, maybe_huge_map)) .or_else(|| snapper.snap_border(out_borders, false, map, maybe_huge_map))
@ -102,8 +97,8 @@ fn endpoints(
// Fallback to finding the nearest border with straight-line distance // Fallback to finding the nearest border with straight-line distance
out_borders out_borders
.iter() .iter()
.min_by_key(|(_, pt)| pt.fast_dist(to.pos)) .min_by_key(|border| border.gps_pos.fast_dist(from.pos))
.map(|(id, _)| TripEndpoint::Border(*id)) .map(|border| TripEndpoint::Border(border.i))
})?; })?;
if from_endpt == to_endpt { if from_endpt == to_endpt {
@ -141,7 +136,7 @@ impl BorderSnapper {
fn snap_border( fn snap_border(
&self, &self,
usable_borders: &[(IntersectionID, LonLat)], usable_borders: &[MapBorder],
incoming: bool, incoming: bool,
map: &Map, map: &Map,
maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>, maybe_huge_map: Option<&(&Map, HashMap<osm::OsmID, BuildingID>)>,
@ -150,8 +145,8 @@ impl BorderSnapper {
// Do any of the usable borders match the path? // Do any of the usable borders match the path?
// TODO Calculate this once // TODO Calculate this once
let mut node_id_to_border = HashMap::new(); let mut node_id_to_border = HashMap::new();
for (i, _) in usable_borders { for border in usable_borders {
node_id_to_border.insert(map.get_i(*i).orig_id, *i); node_id_to_border.insert(map.get_i(border.i).orig_id, border.i);
} }
let mut iter1; let mut iter1;
let mut iter2; let mut iter2;

View File

@ -121,9 +121,8 @@ pub fn disaggregate(
home_zone.pick_home(desire.mode, map, rng), home_zone.pick_home(desire.mode, map, rng),
work_zone.pick_workplace(desire.mode, map, rng), work_zone.pick_workplace(desire.mode, map, rng),
) { ) {
// TODO Soundcast had a bug with the off-map exit/entrance being different // remove_weird_schedules would clean this up later, but simpler to skip upfront
// sometimes; revisit that subtlety. if leave_home == goto_work || leave_work == goto_home {
if leave_home == goto_work {
continue; continue;
} }
@ -176,7 +175,7 @@ pub fn disaggregate(
(pass_through, "just pass through"), (pass_through, "just pass through"),
] { ] {
info!( info!(
"{} people ({}%) {}", "{} people ({}) {}",
prettyprint_usize(x), prettyprint_usize(x),
Percent::of(x, total), Percent::of(x, total),
label label
@ -234,26 +233,22 @@ fn create_zones(
// Multiple zones might all use the same border. // Multiple zones might all use the same border.
let center = polygon.center(); let center = polygon.center();
let mut borders = all_borders.clone(); let mut borders = all_borders.clone();
for list in vec![ // TODO For remote zones, we should at least prune for borders on the correct
&mut borders.incoming_walking, // "side" of the map. Or we can let fast_dist later take care of it.
&mut borders.incoming_driving, if is_remote {
&mut borders.incoming_biking, for list in vec![
&mut borders.outgoing_walking, &mut borders.incoming_walking,
&mut borders.outgoing_driving, &mut borders.incoming_driving,
&mut borders.outgoing_biking, &mut borders.incoming_biking,
] { &mut borders.outgoing_walking,
if is_remote { &mut borders.outgoing_driving,
// For remote zones... keep the one closest border per category? &mut borders.outgoing_biking,
// TODO See what Soundcast does ] {
list.sort_by_key(|(i, _)| { // If the zone partly overlaps, only keep borders physically in the
map.get_i(*i).polygon.center().fast_dist(center) // zone polygon
}); // TODO If the intersection geometry happens to leak out of the map
list.truncate(1); // boundary a bit, this could be wrong!
} else { list.retain(|border| polygon.contains_pt(border.pos));
// If the zone partly overlaps, only keep borders physically in the zone polygon
list.retain(|(i, _)| {
polygon.contains_pt(map.get_i(*i).polygon.center())
});
} }
} }
Some(( Some((
@ -359,9 +354,16 @@ impl Zone {
rng: &mut XorShiftRng, rng: &mut XorShiftRng,
) -> Option<(TripEndpoint, TripEndpoint)> { ) -> Option<(TripEndpoint, TripEndpoint)> {
let (incoming, outgoing) = self.borders.for_mode(mode); let (incoming, outgoing) = self.borders.for_mode(mode);
let leave_i = incoming.choose(rng)?.0;
let leave_i = incoming
.choose_weighted(rng, |border| {
(border.weight as f64) * self.center.fast_dist(border.pos).into_inner()
})
.ok()?
.i;
// If we can use the same border on the way back, prefer that. // If we can use the same border on the way back, prefer that.
if outgoing.iter().any(|(i, _)| *i == leave_i) { if outgoing.iter().any(|border| border.i == leave_i) {
return Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(leave_i))); return Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(leave_i)));
} }
// Otherwise, we might have to use a separate border to re-enter. Prefer the one closest to // Otherwise, we might have to use a separate border to re-enter. Prefer the one closest to
@ -369,8 +371,8 @@ impl Zone {
let leave_pt = map.get_i(leave_i).polygon.center(); let leave_pt = map.get_i(leave_i).polygon.center();
let goto_i = outgoing let goto_i = outgoing
.iter() .iter()
.min_by_key(|(i, _)| map.get_i(*i).polygon.center().dist_to(leave_pt))? .min_by_key(|border| map.get_i(border.i).polygon.center().dist_to(leave_pt))?
.0; .i;
Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(goto_i))) Some((TripEndpoint::Border(leave_i), TripEndpoint::Border(goto_i)))
} }
} }

115
synthpop/src/borders.rs Normal file
View File

@ -0,0 +1,115 @@
use geom::{LonLat, Pt2D};
use map_model::osm::RoadRank;
use map_model::{Intersection, IntersectionID, Map, PathConstraints};
use crate::TripMode;
/// Lists all border intersections of the map, broken down by mode and whether they support
/// incoming or outgoing traffic.
#[derive(Clone)]
pub struct MapBorders {
pub incoming_walking: Vec<MapBorder>,
pub incoming_driving: Vec<MapBorder>,
pub incoming_biking: Vec<MapBorder>,
pub outgoing_walking: Vec<MapBorder>,
pub outgoing_driving: Vec<MapBorder>,
pub outgoing_biking: Vec<MapBorder>,
}
#[derive(Clone)]
pub struct MapBorder {
pub i: IntersectionID,
pub pos: Pt2D,
pub gps_pos: LonLat,
/// Based on the classification of the connecting road, a weight for how likely this border is
/// to be used for traffic.
pub weight: usize,
}
impl MapBorders {
pub fn new(map: &Map) -> MapBorders {
let incoming_walking = map
.all_incoming_borders()
.into_iter()
.filter(|i| {
!i.get_outgoing_lanes(map, PathConstraints::Pedestrian)
.is_empty()
})
.map(|i| MapBorder::new(map, i))
.collect::<Vec<_>>();
let incoming_driving = map
.all_incoming_borders()
.into_iter()
.filter(|i| !i.get_outgoing_lanes(map, PathConstraints::Car).is_empty())
.map(|i| MapBorder::new(map, i))
.collect::<Vec<_>>();
let incoming_biking = map
.all_incoming_borders()
.into_iter()
.filter(|i| !i.get_outgoing_lanes(map, PathConstraints::Bike).is_empty())
.map(|i| MapBorder::new(map, i))
.collect::<Vec<_>>();
let outgoing_walking = map
.all_outgoing_borders()
.into_iter()
.filter(|i| {
!i.get_incoming_lanes(map, PathConstraints::Pedestrian)
.is_empty()
})
.map(|i| MapBorder::new(map, i))
.collect::<Vec<_>>();
let outgoing_driving = map
.all_outgoing_borders()
.into_iter()
.filter(|i| !i.get_incoming_lanes(map, PathConstraints::Car).is_empty())
.map(|i| MapBorder::new(map, i))
.collect::<Vec<_>>();
let outgoing_biking = map
.all_outgoing_borders()
.into_iter()
.filter(|i| !i.get_incoming_lanes(map, PathConstraints::Bike).is_empty())
.map(|i| MapBorder::new(map, i))
.collect::<Vec<_>>();
MapBorders {
incoming_walking,
incoming_driving,
incoming_biking,
outgoing_walking,
outgoing_driving,
outgoing_biking,
}
}
/// Returns the (incoming, outgoing) borders for the specififed mode.
pub fn for_mode(&self, mode: TripMode) -> (&Vec<MapBorder>, &Vec<MapBorder>) {
match mode {
TripMode::Walk | TripMode::Transit => (&self.incoming_walking, &self.outgoing_walking),
TripMode::Drive => (&self.incoming_driving, &self.outgoing_driving),
TripMode::Bike => (&self.incoming_biking, &self.outgoing_biking),
}
}
}
impl MapBorder {
fn new(map: &Map, i: &Intersection) -> Self {
// TODO Mostly untuned, and agnostic to TripMode
let road = map.get_r(*i.roads.iter().next().unwrap());
let mut weight = match road.get_rank() {
RoadRank::Local => 3,
RoadRank::Arterial => 5,
RoadRank::Highway => 8,
};
// TODO We should consider more values for RoadRank
if road.is_service() {
weight = 1;
}
let pos = i.polygon.center();
Self {
i: i.id,
pos,
gps_pos: pos.to_gps(map.get_gps_bounds()),
weight,
}
}
}

View File

@ -5,9 +5,9 @@ use anyhow::Result;
use serde::Deserialize; use serde::Deserialize;
use geom::{Distance, FindClosest, LonLat, Time}; use geom::{Distance, FindClosest, LonLat, Time};
use map_model::{IntersectionID, Map, PathConstraints}; use map_model::Map;
use crate::{IndividTrip, PersonSpec, TripEndpoint, TripMode, TripPurpose}; use crate::{IndividTrip, MapBorders, PersonSpec, TripEndpoint, TripMode, TripPurpose};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ExternalPerson { pub struct ExternalPerson {
@ -62,9 +62,9 @@ impl ExternalPerson {
Ok(TripEndpoint::Border( Ok(TripEndpoint::Border(
candidates candidates
.iter() .iter()
.min_by_key(|(_, border)| border.fast_dist(gps)) .min_by_key(|border| border.gps_pos.fast_dist(gps))
.ok_or_else(|| anyhow!("No border for {}", mode.ongoing_verb()))? .ok_or_else(|| anyhow!("No border for {}", mode.ongoing_verb()))?
.0, .i,
)) ))
} }
} }
@ -110,86 +110,3 @@ impl ExternalPerson {
Ok(results) Ok(results)
} }
} }
/// Lists all border intersections of the map, broken down by mode and whether they support
/// incoming or outgoing traffic.
#[derive(Clone)]
pub struct MapBorders {
pub incoming_walking: Vec<(IntersectionID, LonLat)>,
pub incoming_driving: Vec<(IntersectionID, LonLat)>,
pub incoming_biking: Vec<(IntersectionID, LonLat)>,
pub outgoing_walking: Vec<(IntersectionID, LonLat)>,
pub outgoing_driving: Vec<(IntersectionID, LonLat)>,
pub outgoing_biking: Vec<(IntersectionID, LonLat)>,
}
impl MapBorders {
pub fn new(map: &Map) -> MapBorders {
let bounds = map.get_gps_bounds();
let incoming_walking: Vec<(IntersectionID, LonLat)> = map
.all_incoming_borders()
.into_iter()
.filter(|i| {
!i.get_outgoing_lanes(map, PathConstraints::Pedestrian)
.is_empty()
})
.map(|i| (i.id, i.polygon.center().to_gps(bounds)))
.collect();
let incoming_driving: Vec<(IntersectionID, LonLat)> = map
.all_incoming_borders()
.into_iter()
.filter(|i| !i.get_outgoing_lanes(map, PathConstraints::Car).is_empty())
.map(|i| (i.id, i.polygon.center().to_gps(bounds)))
.collect();
let incoming_biking: Vec<(IntersectionID, LonLat)> = map
.all_incoming_borders()
.into_iter()
.filter(|i| !i.get_outgoing_lanes(map, PathConstraints::Bike).is_empty())
.map(|i| (i.id, i.polygon.center().to_gps(bounds)))
.collect();
let outgoing_walking: Vec<(IntersectionID, LonLat)> = map
.all_outgoing_borders()
.into_iter()
.filter(|i| {
!i.get_incoming_lanes(map, PathConstraints::Pedestrian)
.is_empty()
})
.map(|i| (i.id, i.polygon.center().to_gps(bounds)))
.collect();
let outgoing_driving: Vec<(IntersectionID, LonLat)> = map
.all_outgoing_borders()
.into_iter()
.filter(|i| !i.get_incoming_lanes(map, PathConstraints::Car).is_empty())
.map(|i| (i.id, i.polygon.center().to_gps(bounds)))
.collect();
let outgoing_biking: Vec<(IntersectionID, LonLat)> = map
.all_outgoing_borders()
.into_iter()
.filter(|i| !i.get_incoming_lanes(map, PathConstraints::Bike).is_empty())
.map(|i| (i.id, i.polygon.center().to_gps(bounds)))
.collect();
MapBorders {
incoming_walking,
incoming_driving,
incoming_biking,
outgoing_walking,
outgoing_driving,
outgoing_biking,
}
}
/// Returns the (incoming, outgoing) borders for the specififed mode.
pub fn for_mode(
&self,
mode: TripMode,
) -> (
&Vec<(IntersectionID, LonLat)>,
&Vec<(IntersectionID, LonLat)>,
) {
match mode {
TripMode::Walk | TripMode::Transit => (&self.incoming_walking, &self.outgoing_walking),
TripMode::Drive => (&self.incoming_driving, &self.outgoing_driving),
TripMode::Bike => (&self.incoming_biking, &self.outgoing_biking),
}
}
}

View File

@ -15,12 +15,14 @@ use serde::{Deserialize, Serialize};
use abstutil::{deserialize_usize, serialize_usize}; use abstutil::{deserialize_usize, serialize_usize};
use map_model::PathConstraints; use map_model::PathConstraints;
pub use self::borders::{MapBorder, MapBorders};
pub use self::counts::TrafficCounts; pub use self::counts::TrafficCounts;
pub use self::endpoint::TripEndpoint; pub use self::endpoint::TripEndpoint;
pub use self::external::{ExternalPerson, ExternalTrip, ExternalTripEndpoint, MapBorders}; pub use self::external::{ExternalPerson, ExternalTrip, ExternalTripEndpoint};
pub use self::modifier::ScenarioModifier; pub use self::modifier::ScenarioModifier;
pub use self::scenario::{IndividTrip, PersonSpec, Scenario, TripPurpose}; pub use self::scenario::{IndividTrip, PersonSpec, Scenario, TripPurpose};
mod borders;
mod counts; mod counts;
mod endpoint; mod endpoint;
mod external; mod external;