mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 15:02:59 +03:00
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:
parent
b93850406f
commit
f22590ae0c
@ -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;
|
||||
|
@ -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
115
synthpop/src/borders.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user