mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +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 driving::DrivingGoal;
|
||||
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map, RoadID};
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use walking::SidewalkSpot;
|
||||
use {
|
||||
BorderSpawnOverTime, CarID, Event, MapEdits, OriginDestination, PedestrianID, RouteID,
|
||||
Scenario, SeedParkedCars, Sim, SpawnOverTime, Tick, WeightedUsizeChoice,
|
||||
BorderSpawnOverTime, CarID, Event, OriginDestination, PedestrianID, RouteID, Scenario,
|
||||
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
|
||||
impl Sim {
|
||||
// TODO share the helpers for spawning specific parking spots and stuff?
|
||||
|
@ -30,18 +30,16 @@ extern crate structopt;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod a_b_test;
|
||||
mod driving;
|
||||
mod edits;
|
||||
mod events;
|
||||
mod helpers;
|
||||
mod instrument;
|
||||
mod intersections;
|
||||
// TODO pub only for tests...
|
||||
pub mod kinematics;
|
||||
mod make;
|
||||
mod parking;
|
||||
mod router;
|
||||
mod scenario;
|
||||
mod sim;
|
||||
mod spawn;
|
||||
mod stats;
|
||||
@ -50,20 +48,17 @@ mod trips;
|
||||
mod view;
|
||||
mod walking;
|
||||
|
||||
pub use a_b_test::{ABTest, ABTestResults};
|
||||
use abstutil::Cloneable;
|
||||
use dimensioned::si;
|
||||
pub use edits::MapEdits;
|
||||
pub use events::Event;
|
||||
use geom::{Angle, Pt2D};
|
||||
pub use helpers::{load, SimFlags};
|
||||
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 rand::{RngCore, SeedableRng, XorShiftRng};
|
||||
pub use scenario::{
|
||||
BorderSpawnOverTime, Neighborhood, NeighborhoodBuilder, OriginDestination, Scenario,
|
||||
SeedParkedCars, SpawnOverTime,
|
||||
};
|
||||
pub use sim::{Benchmark, Sim, Summary};
|
||||
pub use stats::SimStats;
|
||||
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 driving::DrivingGoal;
|
||||
use geom::{GPSBounds, LonLat, Polygon, Pt2D};
|
||||
use map_model::{BuildingID, IntersectionID, LaneType, Map, RoadID};
|
||||
use rand::Rng;
|
||||
use rand::XorShiftRng;
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::fs::File;
|
||||
use std::io::{Error, Write};
|
||||
use walking::SidewalkSpot;
|
||||
use {CarID, Sim, Tick, WeightedUsizeChoice};
|
||||
use {CarID, Neighborhood, Sim, Tick, WeightedUsizeChoice};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Scenario {
|
||||
@ -20,69 +17,6 @@ pub struct Scenario {
|
||||
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
|
||||
// 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.
|
||||
@ -114,117 +48,6 @@ pub struct SeedParkedCars {
|
||||
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 {
|
||||
pub fn describe(&self) -> Vec<String> {
|
||||
abstutil::to_json(self)
|
||||
@ -379,3 +202,66 @@ impl Scenario {
|
||||
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