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

View File

@ -121,9 +121,8 @@ pub fn disaggregate(
home_zone.pick_home(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
// sometimes; revisit that subtlety.
if leave_home == goto_work {
// remove_weird_schedules would clean this up later, but simpler to skip upfront
if leave_home == goto_work || leave_work == goto_home {
continue;
}
@ -176,7 +175,7 @@ pub fn disaggregate(
(pass_through, "just pass through"),
] {
info!(
"{} people ({}%) {}",
"{} people ({}) {}",
prettyprint_usize(x),
Percent::of(x, total),
label
@ -234,26 +233,22 @@ fn create_zones(
// Multiple zones might all use the same border.
let center = polygon.center();
let mut borders = all_borders.clone();
for list in vec![
&mut borders.incoming_walking,
&mut borders.incoming_driving,
&mut borders.incoming_biking,
&mut borders.outgoing_walking,
&mut borders.outgoing_driving,
&mut borders.outgoing_biking,
] {
if is_remote {
// For remote zones... keep the one closest border per category?
// TODO See what Soundcast does
list.sort_by_key(|(i, _)| {
map.get_i(*i).polygon.center().fast_dist(center)
});
list.truncate(1);
} else {
// 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())
});
// TODO For remote zones, we should at least prune for borders on the correct
// "side" of the map. Or we can let fast_dist later take care of it.
if is_remote {
for list in vec![
&mut borders.incoming_walking,
&mut borders.incoming_driving,
&mut borders.incoming_biking,
&mut borders.outgoing_walking,
&mut borders.outgoing_driving,
&mut borders.outgoing_biking,
] {
// If the zone partly overlaps, only keep borders physically in the
// zone polygon
// TODO If the intersection geometry happens to leak out of the map
// boundary a bit, this could be wrong!
list.retain(|border| polygon.contains_pt(border.pos));
}
}
Some((
@ -359,9 +354,16 @@ impl Zone {
rng: &mut XorShiftRng,
) -> Option<(TripEndpoint, TripEndpoint)> {
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 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)));
}
// 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 goto_i = outgoing
.iter()
.min_by_key(|(i, _)| map.get_i(*i).polygon.center().dist_to(leave_pt))?
.0;
.min_by_key(|border| map.get_i(border.i).polygon.center().dist_to(leave_pt))?
.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 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)]
pub struct ExternalPerson {
@ -62,9 +62,9 @@ impl ExternalPerson {
Ok(TripEndpoint::Border(
candidates
.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()))?
.0,
.i,
))
}
}
@ -110,86 +110,3 @@ impl ExternalPerson {
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 map_model::PathConstraints;
pub use self::borders::{MapBorder, MapBorders};
pub use self::counts::TrafficCounts;
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::scenario::{IndividTrip, PersonSpec, Scenario, TripPurpose};
mod borders;
mod counts;
mod endpoint;
mod external;