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 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;
|
||||||
|
@ -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
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 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user