mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-03 01:52:16 +03:00
rearranging some sim code into make/
This commit is contained in:
parent
ecae90eb8a
commit
df5a151f8b
@ -1,161 +1,13 @@
|
|||||||
use abstutil;
|
|
||||||
use control::ControlMap;
|
use control::ControlMap;
|
||||||
use driving::DrivingGoal;
|
use driving::DrivingGoal;
|
||||||
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map, RoadID};
|
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map, RoadID};
|
||||||
use std::collections::{BTreeSet, VecDeque};
|
use std::collections::{BTreeSet, VecDeque};
|
||||||
use walking::SidewalkSpot;
|
use walking::SidewalkSpot;
|
||||||
use {
|
use {
|
||||||
BorderSpawnOverTime, CarID, Event, MapEdits, OriginDestination, PedestrianID, RouteID,
|
BorderSpawnOverTime, CarID, Event, OriginDestination, PedestrianID, RouteID, Scenario,
|
||||||
Scenario, SeedParkedCars, Sim, SpawnOverTime, Tick, WeightedUsizeChoice,
|
SeedParkedCars, Sim, SpawnOverTime, Tick, WeightedUsizeChoice,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(StructOpt, Debug, Clone)]
|
|
||||||
#[structopt(name = "sim_flags")]
|
|
||||||
pub struct SimFlags {
|
|
||||||
/// Map, scenario, or savestate to load
|
|
||||||
#[structopt(name = "load")]
|
|
||||||
pub load: String,
|
|
||||||
|
|
||||||
/// Optional RNG seed
|
|
||||||
#[structopt(long = "rng_seed")]
|
|
||||||
pub rng_seed: Option<u8>,
|
|
||||||
|
|
||||||
/// Run name for savestating
|
|
||||||
#[structopt(long = "run_name", default_value = "unnamed")]
|
|
||||||
pub run_name: String,
|
|
||||||
|
|
||||||
/// Name of map edits. Shouldn't be a full path or have the ".json"
|
|
||||||
#[structopt(long = "edits_name", default_value = "no_edits")]
|
|
||||||
pub edits_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SimFlags {
|
|
||||||
pub fn for_test(run_name: &str) -> SimFlags {
|
|
||||||
SimFlags {
|
|
||||||
load: "../data/raw_maps/montlake.abst".to_string(),
|
|
||||||
rng_seed: Some(42),
|
|
||||||
run_name: run_name.to_string(),
|
|
||||||
edits_name: "no_edits".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience method to setup everything.
|
|
||||||
pub fn load(
|
|
||||||
flags: SimFlags,
|
|
||||||
savestate_every: Option<Tick>,
|
|
||||||
timer: &mut abstutil::Timer,
|
|
||||||
) -> (Map, ControlMap, Sim) {
|
|
||||||
if flags.load.contains("data/save/") {
|
|
||||||
assert_eq!(flags.edits_name, "no_edits");
|
|
||||||
|
|
||||||
info!("Resuming from {}", flags.load);
|
|
||||||
timer.start("read sim savestate");
|
|
||||||
let sim: Sim = abstutil::read_json(&flags.load).expect("loading sim state failed");
|
|
||||||
timer.stop("read sim savestate");
|
|
||||||
|
|
||||||
let edits: MapEdits = if sim.edits_name == "no_edits" {
|
|
||||||
MapEdits::new()
|
|
||||||
} else {
|
|
||||||
abstutil::read_json(&format!(
|
|
||||||
"../data/edits/{}/{}.json",
|
|
||||||
sim.map_name, sim.edits_name
|
|
||||||
)).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try loading the pre-baked map first
|
|
||||||
let map: Map = abstutil::read_binary(
|
|
||||||
&format!("../data/maps/{}_{}.abst", sim.map_name, sim.edits_name),
|
|
||||||
timer,
|
|
||||||
).unwrap_or_else(|_| {
|
|
||||||
let map_path = format!("../data/raw_maps/{}.abst", sim.map_name);
|
|
||||||
Map::new(&map_path, edits.road_edits.clone(), timer)
|
|
||||||
.expect(&format!("Couldn't load map from {}", map_path))
|
|
||||||
});
|
|
||||||
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
|
||||||
|
|
||||||
(map, control_map, sim)
|
|
||||||
} else if flags.load.contains("data/scenarios/") {
|
|
||||||
info!("Seeding the simulation from scenario {}", flags.load);
|
|
||||||
let scenario: Scenario = abstutil::read_json(&flags.load).expect("loading scenario failed");
|
|
||||||
let edits = load_edits(&scenario.map_name, &flags);
|
|
||||||
|
|
||||||
// Try loading the pre-baked map first
|
|
||||||
let map: Map = abstutil::read_binary(
|
|
||||||
&format!(
|
|
||||||
"../data/maps/{}_{}.abst",
|
|
||||||
scenario.map_name, edits.edits_name
|
|
||||||
),
|
|
||||||
timer,
|
|
||||||
).unwrap_or_else(|_| {
|
|
||||||
let map_path = format!("../data/raw_maps/{}.abst", scenario.map_name);
|
|
||||||
Map::new(&map_path, edits.road_edits.clone(), timer)
|
|
||||||
.expect(&format!("Couldn't load map from {}", map_path))
|
|
||||||
});
|
|
||||||
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
|
||||||
let mut sim = Sim::new(
|
|
||||||
&map,
|
|
||||||
// TODO or the scenario name if no run name
|
|
||||||
flags.run_name,
|
|
||||||
flags.rng_seed,
|
|
||||||
savestate_every,
|
|
||||||
);
|
|
||||||
scenario.instantiate(&mut sim, &map);
|
|
||||||
(map, control_map, sim)
|
|
||||||
} else if flags.load.contains("data/raw_maps/") {
|
|
||||||
// TODO relative dir is brittle; match more cautiously
|
|
||||||
let map_name = flags
|
|
||||||
.load
|
|
||||||
.trim_left_matches("../data/raw_maps/")
|
|
||||||
.trim_right_matches(".abst")
|
|
||||||
.to_string();
|
|
||||||
info!("Loading map {}", flags.load);
|
|
||||||
let edits = load_edits(&map_name, &flags);
|
|
||||||
let map =
|
|
||||||
Map::new(&flags.load, edits.road_edits.clone(), timer).expect("Couldn't load map");
|
|
||||||
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
|
||||||
timer.start("create sim");
|
|
||||||
let sim = Sim::new(&map, flags.run_name, flags.rng_seed, savestate_every);
|
|
||||||
timer.stop("create sim");
|
|
||||||
(map, control_map, sim)
|
|
||||||
} else if flags.load.contains("data/maps/") {
|
|
||||||
assert_eq!(flags.edits_name, "no_edits");
|
|
||||||
|
|
||||||
info!("Loading map {}", flags.load);
|
|
||||||
let map: Map = abstutil::read_binary(&flags.load, timer).expect("Couldn't load map");
|
|
||||||
// TODO Bit sad to load edits to reconstitute ControlMap, but this is necessary right now
|
|
||||||
let edits: MapEdits = abstutil::read_json(&format!(
|
|
||||||
"../data/edits/{}/{}.json",
|
|
||||||
map.get_name(),
|
|
||||||
map.get_road_edits().edits_name
|
|
||||||
)).unwrap();
|
|
||||||
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
|
||||||
timer.start("create sim");
|
|
||||||
let sim = Sim::new(&map, flags.run_name, flags.rng_seed, savestate_every);
|
|
||||||
timer.stop("create sim");
|
|
||||||
(map, control_map, sim)
|
|
||||||
} else {
|
|
||||||
panic!("Don't know how to load {}", flags.load);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_edits(map_name: &str, flags: &SimFlags) -> MapEdits {
|
|
||||||
if flags.edits_name == "no_edits" {
|
|
||||||
return MapEdits::new();
|
|
||||||
}
|
|
||||||
if flags.edits_name.contains("data/") || flags.edits_name.contains(".json") {
|
|
||||||
panic!(
|
|
||||||
"{} should just be a plain name, not a full path",
|
|
||||||
flags.edits_name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let edits: MapEdits = abstutil::read_json(&format!(
|
|
||||||
"../data/edits/{}/{}.json",
|
|
||||||
map_name, flags.edits_name
|
|
||||||
)).unwrap();
|
|
||||||
edits
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers to run the sim
|
// Helpers to run the sim
|
||||||
impl Sim {
|
impl Sim {
|
||||||
// TODO share the helpers for spawning specific parking spots and stuff?
|
// TODO share the helpers for spawning specific parking spots and stuff?
|
||||||
|
@ -30,18 +30,16 @@ extern crate structopt;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
mod a_b_test;
|
|
||||||
mod driving;
|
mod driving;
|
||||||
mod edits;
|
|
||||||
mod events;
|
mod events;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod instrument;
|
mod instrument;
|
||||||
mod intersections;
|
mod intersections;
|
||||||
// TODO pub only for tests...
|
// TODO pub only for tests...
|
||||||
pub mod kinematics;
|
pub mod kinematics;
|
||||||
|
mod make;
|
||||||
mod parking;
|
mod parking;
|
||||||
mod router;
|
mod router;
|
||||||
mod scenario;
|
|
||||||
mod sim;
|
mod sim;
|
||||||
mod spawn;
|
mod spawn;
|
||||||
mod stats;
|
mod stats;
|
||||||
@ -50,20 +48,17 @@ mod trips;
|
|||||||
mod view;
|
mod view;
|
||||||
mod walking;
|
mod walking;
|
||||||
|
|
||||||
pub use a_b_test::{ABTest, ABTestResults};
|
|
||||||
use abstutil::Cloneable;
|
use abstutil::Cloneable;
|
||||||
use dimensioned::si;
|
use dimensioned::si;
|
||||||
pub use edits::MapEdits;
|
|
||||||
pub use events::Event;
|
pub use events::Event;
|
||||||
use geom::{Angle, Pt2D};
|
use geom::{Angle, Pt2D};
|
||||||
pub use helpers::{load, SimFlags};
|
|
||||||
pub use instrument::save_backtraces;
|
pub use instrument::save_backtraces;
|
||||||
|
pub use make::{
|
||||||
|
load, ABTest, ABTestResults, BorderSpawnOverTime, MapEdits, Neighborhood, NeighborhoodBuilder,
|
||||||
|
OriginDestination, Scenario, SeedParkedCars, SimFlags, SpawnOverTime,
|
||||||
|
};
|
||||||
use map_model::{BuildingID, LaneID, Trace, TurnID};
|
use map_model::{BuildingID, LaneID, Trace, TurnID};
|
||||||
use rand::{RngCore, SeedableRng, XorShiftRng};
|
use rand::{RngCore, SeedableRng, XorShiftRng};
|
||||||
pub use scenario::{
|
|
||||||
BorderSpawnOverTime, Neighborhood, NeighborhoodBuilder, OriginDestination, Scenario,
|
|
||||||
SeedParkedCars, SpawnOverTime,
|
|
||||||
};
|
|
||||||
pub use sim::{Benchmark, Sim, Summary};
|
pub use sim::{Benchmark, Sim, Summary};
|
||||||
pub use stats::SimStats;
|
pub use stats::SimStats;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
151
sim/src/make/load.rs
Normal file
151
sim/src/make/load.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
use abstutil;
|
||||||
|
use control::ControlMap;
|
||||||
|
use map_model::Map;
|
||||||
|
use {MapEdits, Scenario, Sim, Tick};
|
||||||
|
|
||||||
|
#[derive(StructOpt, Debug, Clone)]
|
||||||
|
#[structopt(name = "sim_flags")]
|
||||||
|
pub struct SimFlags {
|
||||||
|
/// Map, scenario, or savestate to load
|
||||||
|
#[structopt(name = "load")]
|
||||||
|
pub load: String,
|
||||||
|
|
||||||
|
/// Optional RNG seed
|
||||||
|
#[structopt(long = "rng_seed")]
|
||||||
|
pub rng_seed: Option<u8>,
|
||||||
|
|
||||||
|
/// Run name for savestating
|
||||||
|
#[structopt(long = "run_name", default_value = "unnamed")]
|
||||||
|
pub run_name: String,
|
||||||
|
|
||||||
|
/// Name of map edits. Shouldn't be a full path or have the ".json"
|
||||||
|
#[structopt(long = "edits_name", default_value = "no_edits")]
|
||||||
|
pub edits_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimFlags {
|
||||||
|
pub fn for_test(run_name: &str) -> SimFlags {
|
||||||
|
SimFlags {
|
||||||
|
load: "../data/raw_maps/montlake.abst".to_string(),
|
||||||
|
rng_seed: Some(42),
|
||||||
|
run_name: run_name.to_string(),
|
||||||
|
edits_name: "no_edits".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience method to setup everything.
|
||||||
|
pub fn load(
|
||||||
|
flags: SimFlags,
|
||||||
|
savestate_every: Option<Tick>,
|
||||||
|
timer: &mut abstutil::Timer,
|
||||||
|
) -> (Map, ControlMap, Sim) {
|
||||||
|
if flags.load.contains("data/save/") {
|
||||||
|
assert_eq!(flags.edits_name, "no_edits");
|
||||||
|
|
||||||
|
info!("Resuming from {}", flags.load);
|
||||||
|
timer.start("read sim savestate");
|
||||||
|
let sim: Sim = abstutil::read_json(&flags.load).expect("loading sim state failed");
|
||||||
|
timer.stop("read sim savestate");
|
||||||
|
|
||||||
|
let edits: MapEdits = if sim.edits_name == "no_edits" {
|
||||||
|
MapEdits::new()
|
||||||
|
} else {
|
||||||
|
abstutil::read_json(&format!(
|
||||||
|
"../data/edits/{}/{}.json",
|
||||||
|
sim.map_name, sim.edits_name
|
||||||
|
)).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try loading the pre-baked map first
|
||||||
|
let map: Map = abstutil::read_binary(
|
||||||
|
&format!("../data/maps/{}_{}.abst", sim.map_name, sim.edits_name),
|
||||||
|
timer,
|
||||||
|
).unwrap_or_else(|_| {
|
||||||
|
let map_path = format!("../data/raw_maps/{}.abst", sim.map_name);
|
||||||
|
Map::new(&map_path, edits.road_edits.clone(), timer)
|
||||||
|
.expect(&format!("Couldn't load map from {}", map_path))
|
||||||
|
});
|
||||||
|
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
||||||
|
|
||||||
|
(map, control_map, sim)
|
||||||
|
} else if flags.load.contains("data/scenarios/") {
|
||||||
|
info!("Seeding the simulation from scenario {}", flags.load);
|
||||||
|
let scenario: Scenario = abstutil::read_json(&flags.load).expect("loading scenario failed");
|
||||||
|
let edits = load_edits(&scenario.map_name, &flags);
|
||||||
|
|
||||||
|
// Try loading the pre-baked map first
|
||||||
|
let map: Map = abstutil::read_binary(
|
||||||
|
&format!(
|
||||||
|
"../data/maps/{}_{}.abst",
|
||||||
|
scenario.map_name, edits.edits_name
|
||||||
|
),
|
||||||
|
timer,
|
||||||
|
).unwrap_or_else(|_| {
|
||||||
|
let map_path = format!("../data/raw_maps/{}.abst", scenario.map_name);
|
||||||
|
Map::new(&map_path, edits.road_edits.clone(), timer)
|
||||||
|
.expect(&format!("Couldn't load map from {}", map_path))
|
||||||
|
});
|
||||||
|
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
||||||
|
let mut sim = Sim::new(
|
||||||
|
&map,
|
||||||
|
// TODO or the scenario name if no run name
|
||||||
|
flags.run_name,
|
||||||
|
flags.rng_seed,
|
||||||
|
savestate_every,
|
||||||
|
);
|
||||||
|
scenario.instantiate(&mut sim, &map);
|
||||||
|
(map, control_map, sim)
|
||||||
|
} else if flags.load.contains("data/raw_maps/") {
|
||||||
|
// TODO relative dir is brittle; match more cautiously
|
||||||
|
let map_name = flags
|
||||||
|
.load
|
||||||
|
.trim_left_matches("../data/raw_maps/")
|
||||||
|
.trim_right_matches(".abst")
|
||||||
|
.to_string();
|
||||||
|
info!("Loading map {}", flags.load);
|
||||||
|
let edits = load_edits(&map_name, &flags);
|
||||||
|
let map =
|
||||||
|
Map::new(&flags.load, edits.road_edits.clone(), timer).expect("Couldn't load map");
|
||||||
|
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
||||||
|
timer.start("create sim");
|
||||||
|
let sim = Sim::new(&map, flags.run_name, flags.rng_seed, savestate_every);
|
||||||
|
timer.stop("create sim");
|
||||||
|
(map, control_map, sim)
|
||||||
|
} else if flags.load.contains("data/maps/") {
|
||||||
|
assert_eq!(flags.edits_name, "no_edits");
|
||||||
|
|
||||||
|
info!("Loading map {}", flags.load);
|
||||||
|
let map: Map = abstutil::read_binary(&flags.load, timer).expect("Couldn't load map");
|
||||||
|
// TODO Bit sad to load edits to reconstitute ControlMap, but this is necessary right now
|
||||||
|
let edits: MapEdits = abstutil::read_json(&format!(
|
||||||
|
"../data/edits/{}/{}.json",
|
||||||
|
map.get_name(),
|
||||||
|
map.get_road_edits().edits_name
|
||||||
|
)).unwrap();
|
||||||
|
let control_map = ControlMap::new(&map, edits.stop_signs, edits.traffic_signals);
|
||||||
|
timer.start("create sim");
|
||||||
|
let sim = Sim::new(&map, flags.run_name, flags.rng_seed, savestate_every);
|
||||||
|
timer.stop("create sim");
|
||||||
|
(map, control_map, sim)
|
||||||
|
} else {
|
||||||
|
panic!("Don't know how to load {}", flags.load);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_edits(map_name: &str, flags: &SimFlags) -> MapEdits {
|
||||||
|
if flags.edits_name == "no_edits" {
|
||||||
|
return MapEdits::new();
|
||||||
|
}
|
||||||
|
if flags.edits_name.contains("data/") || flags.edits_name.contains(".json") {
|
||||||
|
panic!(
|
||||||
|
"{} should just be a plain name, not a full path",
|
||||||
|
flags.edits_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let edits: MapEdits = abstutil::read_json(&format!(
|
||||||
|
"../data/edits/{}/{}.json",
|
||||||
|
map_name, flags.edits_name
|
||||||
|
)).unwrap();
|
||||||
|
edits
|
||||||
|
}
|
16
sim/src/make/mod.rs
Normal file
16
sim/src/make/mod.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// This roughly contains code to specify and instantiate a simulation, not the mechanics of running
|
||||||
|
// it.
|
||||||
|
|
||||||
|
mod a_b_test;
|
||||||
|
mod edits;
|
||||||
|
mod load;
|
||||||
|
mod neighborhood;
|
||||||
|
mod scenario;
|
||||||
|
|
||||||
|
pub use self::a_b_test::{ABTest, ABTestResults};
|
||||||
|
pub use self::edits::MapEdits;
|
||||||
|
pub use self::load::{load, SimFlags};
|
||||||
|
pub use self::neighborhood::{Neighborhood, NeighborhoodBuilder};
|
||||||
|
pub use self::scenario::{
|
||||||
|
BorderSpawnOverTime, OriginDestination, Scenario, SeedParkedCars, SpawnOverTime,
|
||||||
|
};
|
117
sim/src/make/neighborhood.rs
Normal file
117
sim/src/make/neighborhood.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use abstutil;
|
||||||
|
use geom::{GPSBounds, LonLat, Polygon, Pt2D};
|
||||||
|
use map_model::{BuildingID, Map, RoadID};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
|
// This form is used by the editor plugin to edit and for serialization. Storing points in GPS is
|
||||||
|
// more compatible with slight changes to the bounding box of a map over time.
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct NeighborhoodBuilder {
|
||||||
|
pub map_name: String,
|
||||||
|
pub name: String,
|
||||||
|
pub points: Vec<LonLat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeighborhoodBuilder {
|
||||||
|
pub fn finalize(&self, gps_bounds: &GPSBounds) -> Neighborhood {
|
||||||
|
assert!(self.points.len() >= 3);
|
||||||
|
Neighborhood {
|
||||||
|
map_name: self.map_name.clone(),
|
||||||
|
name: self.name.clone(),
|
||||||
|
polygon: Polygon::new(
|
||||||
|
&self
|
||||||
|
.points
|
||||||
|
.iter()
|
||||||
|
.map(|pt| {
|
||||||
|
Pt2D::from_gps(*pt, gps_bounds)
|
||||||
|
.expect(&format!("Polygon {} has bad pt {}", self.name, pt))
|
||||||
|
}).collect(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self) {
|
||||||
|
abstutil::save_object("neighborhoods", &self.map_name, &self.name, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://wiki.openstreetmap.org/wiki/Osmosis/Polygon_Filter_File_Format
|
||||||
|
pub fn save_as_osmosis(&self) -> Result<(), Error> {
|
||||||
|
let path = format!("../data/polygons/{}.poly", self.name);
|
||||||
|
let mut f = File::create(&path)?;
|
||||||
|
|
||||||
|
write!(f, "{}\n", self.name);
|
||||||
|
write!(f, "1\n");
|
||||||
|
for gps in &self.points {
|
||||||
|
write!(f, " {} {}\n", gps.longitude, gps.latitude);
|
||||||
|
}
|
||||||
|
// Have to repeat the first point
|
||||||
|
{
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" {} {}\n",
|
||||||
|
self.points[0].longitude, self.points[0].latitude
|
||||||
|
);
|
||||||
|
}
|
||||||
|
write!(f, "END\n");
|
||||||
|
write!(f, "END\n");
|
||||||
|
|
||||||
|
println!("Exported {}", path);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Neighborhood {
|
||||||
|
pub map_name: String,
|
||||||
|
pub name: String,
|
||||||
|
pub polygon: Polygon,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neighborhood {
|
||||||
|
pub fn load_all(map_name: &str, gps_bounds: &GPSBounds) -> Vec<(String, Neighborhood)> {
|
||||||
|
abstutil::load_all_objects::<NeighborhoodBuilder>("neighborhoods", map_name)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, builder)| (name, builder.finalize(gps_bounds)))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO This should use quadtrees and/or not just match the center of each building.
|
||||||
|
pub fn find_matching_buildings(&self, map: &Map) -> Vec<BuildingID> {
|
||||||
|
let mut results: Vec<BuildingID> = Vec::new();
|
||||||
|
for b in map.all_buildings() {
|
||||||
|
if self.polygon.contains_pt(Pt2D::center(&b.points)) {
|
||||||
|
results.push(b.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO This should use quadtrees and/or not just match one point of each road.
|
||||||
|
pub fn find_matching_roads(&self, map: &Map) -> BTreeSet<RoadID> {
|
||||||
|
let mut results: BTreeSet<RoadID> = BTreeSet::new();
|
||||||
|
for r in map.all_roads() {
|
||||||
|
if self.polygon.contains_pt(r.center_pts.first_pt()) {
|
||||||
|
results.insert(r.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_everywhere(map: &Map) -> Neighborhood {
|
||||||
|
let bounds = map.get_bounds();
|
||||||
|
|
||||||
|
Neighborhood {
|
||||||
|
map_name: map.get_name().to_string(),
|
||||||
|
name: "_everywhere_".to_string(),
|
||||||
|
polygon: Polygon::new(&vec![
|
||||||
|
Pt2D::new(0.0, 0.0),
|
||||||
|
Pt2D::new(bounds.max_x, 0.0),
|
||||||
|
Pt2D::new(bounds.max_x, bounds.max_y),
|
||||||
|
Pt2D::new(0.0, bounds.max_y),
|
||||||
|
Pt2D::new(0.0, 0.0),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,11 @@
|
|||||||
use abstutil;
|
use abstutil;
|
||||||
use driving::DrivingGoal;
|
use driving::DrivingGoal;
|
||||||
use geom::{GPSBounds, LonLat, Polygon, Pt2D};
|
|
||||||
use map_model::{BuildingID, IntersectionID, LaneType, Map, RoadID};
|
use map_model::{BuildingID, IntersectionID, LaneType, Map, RoadID};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rand::XorShiftRng;
|
use rand::XorShiftRng;
|
||||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Error, Write};
|
|
||||||
use walking::SidewalkSpot;
|
use walking::SidewalkSpot;
|
||||||
use {CarID, Sim, Tick, WeightedUsizeChoice};
|
use {CarID, Neighborhood, Sim, Tick, WeightedUsizeChoice};
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct Scenario {
|
pub struct Scenario {
|
||||||
@ -20,69 +17,6 @@ pub struct Scenario {
|
|||||||
pub border_spawn_over_time: Vec<BorderSpawnOverTime>,
|
pub border_spawn_over_time: Vec<BorderSpawnOverTime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
|
||||||
pub enum OriginDestination {
|
|
||||||
Neighborhood(String),
|
|
||||||
// TODO A serialized Scenario won't last well as the map changes...
|
|
||||||
Border(IntersectionID),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OriginDestination {
|
|
||||||
fn pick_driving_goal(
|
|
||||||
&self,
|
|
||||||
map: &Map,
|
|
||||||
bldgs_per_neighborhood: &HashMap<String, Vec<BuildingID>>,
|
|
||||||
rng: &mut XorShiftRng,
|
|
||||||
) -> Option<DrivingGoal> {
|
|
||||||
match self {
|
|
||||||
OriginDestination::Neighborhood(ref n) => {
|
|
||||||
if let Some(bldgs) = bldgs_per_neighborhood.get(n) {
|
|
||||||
Some(DrivingGoal::ParkNear(*rng.choose(bldgs).unwrap()))
|
|
||||||
} else {
|
|
||||||
panic!("Neighborhood {} isn't defined", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OriginDestination::Border(i) => {
|
|
||||||
let lanes = map.get_i(*i).get_incoming_lanes(map, LaneType::Driving);
|
|
||||||
if lanes.is_empty() {
|
|
||||||
warn!(
|
|
||||||
"Can't spawn a car ending at border {}; no driving lane there",
|
|
||||||
i
|
|
||||||
);
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// TODO ideally could use any
|
|
||||||
Some(DrivingGoal::Border(*i, lanes[0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pick_walking_goal(
|
|
||||||
&self,
|
|
||||||
map: &Map,
|
|
||||||
bldgs_per_neighborhood: &HashMap<String, Vec<BuildingID>>,
|
|
||||||
rng: &mut XorShiftRng,
|
|
||||||
) -> Option<SidewalkSpot> {
|
|
||||||
match self {
|
|
||||||
OriginDestination::Neighborhood(ref n) => {
|
|
||||||
if let Some(bldgs) = bldgs_per_neighborhood.get(n) {
|
|
||||||
Some(SidewalkSpot::building(*rng.choose(bldgs).unwrap(), map))
|
|
||||||
} else {
|
|
||||||
panic!("Neighborhood {} isn't defined", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OriginDestination::Border(i) => {
|
|
||||||
let goal = SidewalkSpot::end_at_border(*i, map);
|
|
||||||
if goal.is_none() {
|
|
||||||
warn!("Can't end_at_border for {} without a sidewalk", i);
|
|
||||||
}
|
|
||||||
goal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SpawnOverTime and BorderSpawnOverTime should be kept separate. Agents in SpawnOverTime pick
|
// SpawnOverTime and BorderSpawnOverTime should be kept separate. Agents in SpawnOverTime pick
|
||||||
// their mode (use a car, walk, bus) based on the situation. When spawning directly a border,
|
// their mode (use a car, walk, bus) based on the situation. When spawning directly a border,
|
||||||
// agents have to start as a car or pedestrian already.
|
// agents have to start as a car or pedestrian already.
|
||||||
@ -114,117 +48,6 @@ pub struct SeedParkedCars {
|
|||||||
pub cars_per_building: WeightedUsizeChoice,
|
pub cars_per_building: WeightedUsizeChoice,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This form is used by the editor plugin to edit and for serialization. Storing points in GPS is
|
|
||||||
// more compatible with slight changes to the bounding box of a map over time.
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct NeighborhoodBuilder {
|
|
||||||
pub map_name: String,
|
|
||||||
pub name: String,
|
|
||||||
pub points: Vec<LonLat>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NeighborhoodBuilder {
|
|
||||||
pub fn finalize(&self, gps_bounds: &GPSBounds) -> Neighborhood {
|
|
||||||
assert!(self.points.len() >= 3);
|
|
||||||
Neighborhood {
|
|
||||||
map_name: self.map_name.clone(),
|
|
||||||
name: self.name.clone(),
|
|
||||||
polygon: Polygon::new(
|
|
||||||
&self
|
|
||||||
.points
|
|
||||||
.iter()
|
|
||||||
.map(|pt| {
|
|
||||||
Pt2D::from_gps(*pt, gps_bounds)
|
|
||||||
.expect(&format!("Polygon {} has bad pt {}", self.name, pt))
|
|
||||||
}).collect(),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&self) {
|
|
||||||
abstutil::save_object("neighborhoods", &self.map_name, &self.name, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://wiki.openstreetmap.org/wiki/Osmosis/Polygon_Filter_File_Format
|
|
||||||
pub fn save_as_osmosis(&self) -> Result<(), Error> {
|
|
||||||
let path = format!("../data/polygons/{}.poly", self.name);
|
|
||||||
let mut f = File::create(&path)?;
|
|
||||||
|
|
||||||
write!(f, "{}\n", self.name);
|
|
||||||
write!(f, "1\n");
|
|
||||||
for gps in &self.points {
|
|
||||||
write!(f, " {} {}\n", gps.longitude, gps.latitude);
|
|
||||||
}
|
|
||||||
// Have to repeat the first point
|
|
||||||
{
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
" {} {}\n",
|
|
||||||
self.points[0].longitude, self.points[0].latitude
|
|
||||||
);
|
|
||||||
}
|
|
||||||
write!(f, "END\n");
|
|
||||||
write!(f, "END\n");
|
|
||||||
|
|
||||||
println!("Exported {}", path);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Neighborhood {
|
|
||||||
pub map_name: String,
|
|
||||||
pub name: String,
|
|
||||||
pub polygon: Polygon,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Neighborhood {
|
|
||||||
pub fn load_all(map_name: &str, gps_bounds: &GPSBounds) -> Vec<(String, Neighborhood)> {
|
|
||||||
abstutil::load_all_objects::<NeighborhoodBuilder>("neighborhoods", map_name)
|
|
||||||
.into_iter()
|
|
||||||
.map(|(name, builder)| (name, builder.finalize(gps_bounds)))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This should use quadtrees and/or not just match the center of each building.
|
|
||||||
fn find_matching_buildings(&self, map: &Map) -> Vec<BuildingID> {
|
|
||||||
let mut results: Vec<BuildingID> = Vec::new();
|
|
||||||
for b in map.all_buildings() {
|
|
||||||
if self.polygon.contains_pt(Pt2D::center(&b.points)) {
|
|
||||||
results.push(b.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This should use quadtrees and/or not just match one point of each road.
|
|
||||||
fn find_matching_roads(&self, map: &Map) -> BTreeSet<RoadID> {
|
|
||||||
let mut results: BTreeSet<RoadID> = BTreeSet::new();
|
|
||||||
for r in map.all_roads() {
|
|
||||||
if self.polygon.contains_pt(r.center_pts.first_pt()) {
|
|
||||||
results.insert(r.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_everywhere(map: &Map) -> Neighborhood {
|
|
||||||
let bounds = map.get_bounds();
|
|
||||||
|
|
||||||
Neighborhood {
|
|
||||||
map_name: map.get_name().to_string(),
|
|
||||||
name: "_everywhere_".to_string(),
|
|
||||||
polygon: Polygon::new(&vec![
|
|
||||||
Pt2D::new(0.0, 0.0),
|
|
||||||
Pt2D::new(bounds.max_x, 0.0),
|
|
||||||
Pt2D::new(bounds.max_x, bounds.max_y),
|
|
||||||
Pt2D::new(0.0, bounds.max_y),
|
|
||||||
Pt2D::new(0.0, 0.0),
|
|
||||||
]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scenario {
|
impl Scenario {
|
||||||
pub fn describe(&self) -> Vec<String> {
|
pub fn describe(&self) -> Vec<String> {
|
||||||
abstutil::to_json(self)
|
abstutil::to_json(self)
|
||||||
@ -379,3 +202,66 @@ impl Scenario {
|
|||||||
abstutil::save_object("scenarios", &self.map_name, &self.scenario_name, self);
|
abstutil::save_object("scenarios", &self.map_name, &self.scenario_name, self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub enum OriginDestination {
|
||||||
|
Neighborhood(String),
|
||||||
|
// TODO A serialized Scenario won't last well as the map changes...
|
||||||
|
Border(IntersectionID),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginDestination {
|
||||||
|
fn pick_driving_goal(
|
||||||
|
&self,
|
||||||
|
map: &Map,
|
||||||
|
bldgs_per_neighborhood: &HashMap<String, Vec<BuildingID>>,
|
||||||
|
rng: &mut XorShiftRng,
|
||||||
|
) -> Option<DrivingGoal> {
|
||||||
|
match self {
|
||||||
|
OriginDestination::Neighborhood(ref n) => {
|
||||||
|
if let Some(bldgs) = bldgs_per_neighborhood.get(n) {
|
||||||
|
Some(DrivingGoal::ParkNear(*rng.choose(bldgs).unwrap()))
|
||||||
|
} else {
|
||||||
|
panic!("Neighborhood {} isn't defined", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OriginDestination::Border(i) => {
|
||||||
|
let lanes = map.get_i(*i).get_incoming_lanes(map, LaneType::Driving);
|
||||||
|
if lanes.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"Can't spawn a car ending at border {}; no driving lane there",
|
||||||
|
i
|
||||||
|
);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// TODO ideally could use any
|
||||||
|
Some(DrivingGoal::Border(*i, lanes[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_walking_goal(
|
||||||
|
&self,
|
||||||
|
map: &Map,
|
||||||
|
bldgs_per_neighborhood: &HashMap<String, Vec<BuildingID>>,
|
||||||
|
rng: &mut XorShiftRng,
|
||||||
|
) -> Option<SidewalkSpot> {
|
||||||
|
match self {
|
||||||
|
OriginDestination::Neighborhood(ref n) => {
|
||||||
|
if let Some(bldgs) = bldgs_per_neighborhood.get(n) {
|
||||||
|
Some(SidewalkSpot::building(*rng.choose(bldgs).unwrap(), map))
|
||||||
|
} else {
|
||||||
|
panic!("Neighborhood {} isn't defined", n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OriginDestination::Border(i) => {
|
||||||
|
let goal = SidewalkSpot::end_at_border(*i, map);
|
||||||
|
if goal.is_none() {
|
||||||
|
warn!("Can't end_at_border for {} without a sidewalk", i);
|
||||||
|
}
|
||||||
|
goal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user