bundling all map edits together

This commit is contained in:
Dustin Carlino 2018-10-02 14:36:04 -07:00
parent bbfe00ce63
commit 7b718b4621
17 changed files with 176 additions and 131 deletions

2
.gitignore vendored
View File

@ -3,7 +3,7 @@
*.swp
editor/editor_state
editor/road_edits.json
editor/map_edits.json
data/input/*
data/maps/*.abst

View File

@ -22,7 +22,11 @@ pub struct ControlMap {
}
impl ControlMap {
pub fn new(map: &Map) -> ControlMap {
pub fn new(
map: &Map,
stop_signs: &BTreeMap<IntersectionID, ModifiedStopSign>,
traffic_signals: &BTreeMap<IntersectionID, ModifiedTrafficSignal>,
) -> ControlMap {
let mut ctrl = ControlMap {
traffic_signals: HashMap::new(),
stop_signs: HashMap::new(),
@ -38,11 +42,18 @@ impl ControlMap {
}
}
for (i, s) in traffic_signals {
ctrl.traffic_signals.get_mut(i).unwrap().load_savestate(s);
}
for (i, s) in stop_signs {
ctrl.stop_signs.get_mut(i).unwrap().load_savestate(s);
}
ctrl
}
pub fn get_traffic_signals_savestate(&self) -> HashMap<IntersectionID, ModifiedTrafficSignal> {
let mut h = HashMap::new();
pub fn get_traffic_signals_savestate(&self) -> BTreeMap<IntersectionID, ModifiedTrafficSignal> {
let mut h = BTreeMap::new();
for (i, s) in &self.traffic_signals {
if let Some(state) = s.get_savestate() {
h.insert(*i, state);
@ -51,8 +62,8 @@ impl ControlMap {
h
}
pub fn get_stop_signs_savestate(&self) -> HashMap<IntersectionID, ModifiedStopSign> {
let mut h = HashMap::new();
pub fn get_stop_signs_savestate(&self) -> BTreeMap<IntersectionID, ModifiedStopSign> {
let mut h = BTreeMap::new();
for (i, s) in &self.stop_signs {
if let Some(state) = s.get_savestate() {
h.insert(*i, state);
@ -60,19 +71,6 @@ impl ControlMap {
}
h
}
pub fn load_savestate(
&mut self,
traffic_signals: &HashMap<IntersectionID, ModifiedTrafficSignal>,
stop_signs: &HashMap<IntersectionID, ModifiedStopSign>,
) {
for (i, s) in traffic_signals {
self.traffic_signals.get_mut(i).unwrap().load_savestate(s);
}
for (i, s) in stop_signs {
self.stop_signs.get_mut(i).unwrap().load_savestate(s);
}
}
}
// General problem: TurnIDs change as code does. Serialized state is kinda tied to code version.

View File

@ -257,3 +257,23 @@ current TreeMenu:
Back up and think about ideal for these background controls...
## Managing different map edits
Should be simple -- I want to bundle together map edits as named things, to
prep for A/B tests. But loading a different set of edits could be kind of
tough...
- new control map state has to propagate to intersection editors.
- easy fix: pass them mut ref from control map every tick. then just have to reload control map.
- road edits have to propogate
- theres a way to do that live right now, but it's kind of brittle and funky. probably safer to load from scratch.
- but then have to reload things like steepness visualizer plugin... actually, just that, seemingly.
- er, but also the hider plugin -- it holds onto laneIDs, which may change!
Alright, I think this is the sequence of things to do:
1) make a few plugins less stateful anyway, by taking refs to map/control map stuff instead of caching stuff. thats useful regardless.
- but wait, then road editor kind of cant work, because mut borrow edits from map while holding immutable lane/road refs. theyre really indep things, so cant store together.
2) make it possible to completely reload UI and everything from scratch, from a plugin. rationale: it'd be nice to switch maps from inside the editor anyway. not necessary, but useful.
3) make road edits propogate correctly, and somehow have a strategy for ensuring nothing is forgotten. impl today is VERY incomplete.

View File

@ -1,20 +1,23 @@
use control::ControlMap;
use ezgui::UserInput;
use map_model::{EditReason, Edits, LaneID, LaneType, Map};
use map_model::{EditReason, LaneID, LaneType, Map, RoadEdits};
use objects::{EDIT_MAP, ID};
use piston::input::Key;
use plugins::Colorizer;
use render::DrawMap;
use sim::Sim;
pub enum RoadEditor {
Inactive(Edits),
Active(Edits),
pub struct RoadEditor {
edits: RoadEdits,
active: bool,
}
impl RoadEditor {
pub fn new(edits: Edits) -> RoadEditor {
RoadEditor::Inactive(edits)
pub fn new(edits: RoadEdits) -> RoadEditor {
RoadEditor {
edits,
active: false,
}
}
pub fn event(
@ -26,63 +29,60 @@ impl RoadEditor {
control_map: &ControlMap,
sim: &mut Sim,
) -> bool {
let mut new_state: Option<RoadEditor> = None;
// TODO a bit awkward that we can't pull this info from Edits easily
// TODO a bit awkward that we can't pull this info from RoadEdits easily
let mut changed: Option<(LaneID, LaneType)> = None;
match self {
RoadEditor::Inactive(edits) => match selected {
None => {
if input.unimportant_key_pressed(Key::E, EDIT_MAP, "Start editing roads") {
// TODO cloning edits sucks! want to consume self
new_state = Some(RoadEditor::Active(edits.clone()));
}
}
_ => {}
},
RoadEditor::Active(edits) => {
if input.key_pressed(Key::Return, "stop editing roads") {
new_state = Some(RoadEditor::Inactive(edits.clone()));
} else if let Some(ID::Lane(id)) = selected {
let lane = map.get_l(id);
let road = map.get_r(lane.parent);
let reason = EditReason::BasemapWrong; // TODO be able to choose
if !self.active && selected.is_none() {
if input.unimportant_key_pressed(Key::E, EDIT_MAP, "Start editing roads") {
self.active = true;
}
}
if self.active {
if input.key_pressed(Key::Return, "stop editing roads") {
self.active = false;
} else if let Some(ID::Lane(id)) = selected {
let lane = map.get_l(id);
let road = map.get_r(lane.parent);
let reason = EditReason::BasemapWrong; // TODO be able to choose
if lane.lane_type != LaneType::Sidewalk {
if lane.lane_type != LaneType::Driving
&& input.key_pressed(Key::D, "make this a driving lane")
if lane.lane_type != LaneType::Sidewalk {
if lane.lane_type != LaneType::Driving
&& input.key_pressed(Key::D, "make this a driving lane")
{
if self
.edits
.change_lane_type(reason, road, lane, LaneType::Driving)
{
if edits.change_lane_type(reason, road, lane, LaneType::Driving) {
changed = Some((lane.id, LaneType::Driving));
}
changed = Some((lane.id, LaneType::Driving));
}
if lane.lane_type != LaneType::Parking
&& input.key_pressed(Key::P, "make this a parking lane")
}
if lane.lane_type != LaneType::Parking
&& input.key_pressed(Key::P, "make this a parking lane")
{
if self
.edits
.change_lane_type(reason, road, lane, LaneType::Parking)
{
if edits.change_lane_type(reason, road, lane, LaneType::Parking) {
changed = Some((lane.id, LaneType::Parking));
}
changed = Some((lane.id, LaneType::Parking));
}
if lane.lane_type != LaneType::Biking
&& input.key_pressed(Key::B, "make this a bike lane")
}
if lane.lane_type != LaneType::Biking
&& input.key_pressed(Key::B, "make this a bike lane")
{
if self
.edits
.change_lane_type(reason, road, lane, LaneType::Biking)
{
if edits.change_lane_type(reason, road, lane, LaneType::Biking) {
changed = Some((lane.id, LaneType::Biking));
}
changed = Some((lane.id, LaneType::Biking));
}
if input.key_pressed(Key::Backspace, "delete this lane") {
if edits.delete_lane(road, lane) {
warn!(
"Have to reload the map from scratch to pick up this change!"
);
}
}
if input.key_pressed(Key::Backspace, "delete this lane") {
if self.edits.delete_lane(road, lane) {
warn!("Have to reload the map from scratch to pick up this change!");
}
}
}
}
};
if let Some(s) = new_state {
*self = s;
}
if let Some((id, new_type)) = changed {
let intersections = map.get_l(id).intersections();
@ -114,17 +114,11 @@ impl RoadEditor {
}
}
match self {
RoadEditor::Inactive(_) => false,
_ => true,
}
self.active
}
pub fn get_edits(&self) -> &Edits {
match self {
RoadEditor::Inactive(edits) => edits,
RoadEditor::Active(edits) => edits,
}
pub fn get_edits(&self) -> &RoadEdits {
&self.edits
}
}

View File

@ -5,7 +5,6 @@
use abstutil;
use colors::{ColorScheme, Colors};
use control::ControlMap;
use control::{ModifiedStopSign, ModifiedTrafficSignal};
use ezgui::{Canvas, EventLoopMode, GfxCtx, Text, ToggleableLayer, UserInput, BOTTOM_LEFT, GUI};
use flame;
use graphics::types::Color;
@ -37,8 +36,7 @@ use plugins::warp::WarpState;
use plugins::Colorizer;
use render::{DrawMap, RenderOptions};
use sim;
use sim::Sim;
use std::collections::HashMap;
use sim::{MapEdits, Sim};
use std::process;
// TODO ideally these would be tuned kind of dynamically based on rendering speed
@ -77,13 +75,12 @@ impl UIWrapper {
let logs = DisplayLogs::new();
flame::start("setup");
let (map, edits, control_map, sim) = sim::load(
let (map, control_map, sim) = sim::load(
load,
scenario_name,
rng_seed,
Some(sim::Tick::from_seconds(30)),
);
let extra_shapes = if let Some(path) = kml {
kml::load(&path, &map.get_gps_bounds()).expect("Couldn't load extra KML shapes")
} else {
@ -98,6 +95,7 @@ impl UIWrapper {
flame::dump_stdout();
let steepness_viz = SteepnessVisualizer::new(&map);
let road_editor = RoadEditor::new(map.get_road_edits().clone());
let mut ui = UI {
// TODO organize this by section
@ -107,6 +105,7 @@ impl UIWrapper {
sim,
steepness_viz,
road_editor,
sim_ctrl: SimController::new(),
layers: ToggleableLayers::new(),
@ -123,7 +122,6 @@ impl UIWrapper {
osm_classifier: OsmClassifier::new(),
traffic_signal_editor: TrafficSignalEditor::new(),
stop_sign_editor: StopSignEditor::new(),
road_editor: RoadEditor::new(edits),
color_picker: ColorPicker::new(),
geom_validator: Validator::new(),
turn_cycler: TurnCyclerState::new(),
@ -143,8 +141,6 @@ impl UIWrapper {
ui.canvas.cam_x = state.cam_x;
ui.canvas.cam_y = state.cam_y;
ui.canvas.cam_zoom = state.cam_zoom;
ui.control_map
.load_savestate(&state.traffic_signals, &state.stop_signs);
}
_ => {
warn!("Couldn't load editor_state or it's for a different map, so just centering initial view");
@ -384,15 +380,21 @@ impl UI {
cam_x: self.canvas.cam_x,
cam_y: self.canvas.cam_y,
cam_zoom: self.canvas.cam_zoom,
traffic_signals: self.control_map.get_traffic_signals_savestate(),
stop_signs: self.control_map.get_stop_signs_savestate(),
};
// TODO maybe make state line up with the map, so loading from a new map doesn't break
abstutil::write_json("editor_state", &state).expect("Saving editor_state failed");
abstutil::write_json("color_scheme", &self.cs).expect("Saving color_scheme failed");
abstutil::write_json("road_edits.json", self.road_editor.get_edits())
.expect("Saving road_edits.json failed");
info!("Saved editor_state, color_scheme, and road_edits.json");
abstutil::write_json(
"map_edits.json",
&MapEdits {
edits_name: "nameless".to_string(),
map_name: self.map.get_name().to_string(),
road_edits: self.road_editor.get_edits().clone(),
stop_signs: self.control_map.get_stop_signs_savestate(),
traffic_signals: self.control_map.get_traffic_signals_savestate(),
},
).expect("Saving map_edits.json failed");
info!("Saved editor_state, color_scheme, and map_edits.json");
process::exit(0);
}
@ -538,9 +540,6 @@ pub struct EditorState {
pub cam_x: f64,
pub cam_y: f64,
pub cam_zoom: f64,
pub traffic_signals: HashMap<IntersectionID, ModifiedTrafficSignal>,
pub stop_signs: HashMap<IntersectionID, ModifiedStopSign>,
}
pub struct ToggleableLayers {

View File

@ -44,7 +44,7 @@ fn main() {
log::set_max_level(LevelFilter::Debug);
log::set_logger(&LOG_ADAPTER).unwrap();
let (map, _, control_map, mut sim) = sim::load(
let (map, control_map, mut sim) = sim::load(
flags.load.clone(),
flags.scenario_name,
flags.rng_seed,

View File

@ -4,14 +4,14 @@ use {Lane, LaneType, Road, RoadID};
// TODO bring in the intersection modifications from the control crate here. for now, road edits
// are here, since map construction maybe needs to know these?
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Edits {
pub struct RoadEdits {
// TODO detect when we wind up editing back to the original thing
pub(crate) roads: BTreeMap<RoadID, RoadEdit>,
}
impl Edits {
pub fn new() -> Edits {
Edits {
impl RoadEdits {
pub fn new() -> RoadEdits {
RoadEdits {
roads: BTreeMap::new(),
}
}

View File

@ -34,7 +34,7 @@ mod turn;
pub use area::{Area, AreaID, AreaType};
pub use building::{Building, BuildingID, FrontPath};
pub use bus_stop::{BusRoute, BusStop, BusStopID};
pub use edits::{EditReason, Edits};
pub use edits::{EditReason, RoadEdits};
pub use intersection::{Intersection, IntersectionID};
pub use lane::{Lane, LaneID, LaneType, PARKING_SPOT_LENGTH};
pub use map::Map;

View File

@ -1,4 +1,4 @@
use edits::Edits;
use edits::RoadEdits;
use lane::LaneType;
use raw_data;
use road::RoadID;
@ -75,7 +75,7 @@ impl LaneSpec {
}
}
pub(crate) fn get_lane_specs(r: &raw_data::Road, id: RoadID, edits: &Edits) -> Vec<LaneSpec> {
pub(crate) fn get_lane_specs(r: &raw_data::Road, id: RoadID, edits: &RoadEdits) -> Vec<LaneSpec> {
let (side1_types, side2_types) = if let Some(e) = edits.roads.get(&id) {
info!("Using edits for {}", id);
(e.forwards_lanes.clone(), e.backwards_lanes.clone())

View File

@ -1,7 +1,7 @@
// Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0
use abstutil;
use edits::Edits;
use edits::RoadEdits;
use flame;
use geom::{Bounds, HashablePt2D, PolyLine, Pt2D};
use make;
@ -30,10 +30,11 @@ pub struct Map {
bounds: Bounds,
name: String,
road_edits: RoadEdits,
}
impl Map {
pub fn new(path: &str, edits: &Edits) -> Result<Map, Error> {
pub fn new(path: &str, road_edits: RoadEdits) -> Result<Map, Error> {
// TODO I think I want something a bit different than flame:
// - Print as each phase occurs
// - Print with nicely formatted durations
@ -48,14 +49,15 @@ impl Map {
.into_string()
.unwrap(),
data,
edits,
road_edits,
))
}
pub fn create_from_raw(name: String, data: raw_data::Map, edits: &Edits) -> Map {
pub fn create_from_raw(name: String, data: raw_data::Map, road_edits: RoadEdits) -> Map {
let bounds = data.get_gps_bounds();
let mut m = Map {
name,
road_edits,
bounds,
roads: Vec::new(),
lanes: Vec::new(),
@ -108,7 +110,7 @@ impl Map {
let i2 = pt_to_intersection[&HashablePt2D::from(road_center_pts.last_pt())];
// TODO move this to make/lanes.rs too
for lane in make::get_lane_specs(r, road_id, edits) {
for lane in make::get_lane_specs(r, road_id, &m.road_edits) {
let id = LaneID(counter);
counter += 1;
@ -394,6 +396,10 @@ impl Map {
&self.name
}
pub fn get_road_edits(&self) -> &RoadEdits {
&self.road_edits
}
pub fn all_bus_stops(&self) -> &BTreeMap<BusStopID, BusStop> {
&self.bus_stops
}

25
sim/src/edits.rs Normal file
View File

@ -0,0 +1,25 @@
use control::{ModifiedStopSign, ModifiedTrafficSignal};
use map_model::{IntersectionID, RoadEdits};
use std::collections::BTreeMap;
#[derive(Serialize, Deserialize)]
pub struct MapEdits {
pub edits_name: String,
pub map_name: String,
pub road_edits: RoadEdits,
pub stop_signs: BTreeMap<IntersectionID, ModifiedStopSign>,
pub traffic_signals: BTreeMap<IntersectionID, ModifiedTrafficSignal>,
}
impl MapEdits {
pub fn new() -> MapEdits {
MapEdits {
edits_name: "unnamed".to_string(),
map_name: "TODO".to_string(), // TODO er
road_edits: RoadEdits::new(),
stop_signs: BTreeMap::new(),
traffic_signals: BTreeMap::new(),
}
}
}

View File

@ -2,10 +2,10 @@ use abstutil;
use control::ControlMap;
use flame;
use geom::Polygon;
use map_model::{BuildingID, BusRoute, BusStopID, Edits, LaneID, Map};
use map_model::{BuildingID, BusRoute, BusStopID, LaneID, Map};
use rand::Rng;
use std::collections::VecDeque;
use {CarID, Event, PedestrianID, RouteID, Scenario, Sim, Tick};
use {CarID, Event, MapEdits, PedestrianID, RouteID, Scenario, Sim, Tick};
// Convenience method to setup everything.
pub fn load(
@ -13,8 +13,9 @@ pub fn load(
scenario_name: String,
rng_seed: Option<u8>,
savestate_every: Option<Tick>,
) -> (Map, Edits, ControlMap, Sim) {
let edits: Edits = abstutil::read_json("road_edits.json").unwrap_or(Edits::new());
) -> (Map, ControlMap, Sim) {
// TODO read a specific one
let edits: MapEdits = abstutil::read_json("map_edits.json").unwrap_or(MapEdits::new());
if input.contains("data/save/") {
info!("Resuming from {}", input);
@ -23,17 +24,17 @@ pub fn load(
flame::end("read sim savestate");
// TODO assuming the relative path :(
let map_path = format!("../data/maps/{}.abst", sim.map_name);
let map =
Map::new(&map_path, &edits).expect(&format!("Couldn't load map from {}", map_path));
let control_map = ControlMap::new(&map);
(map, edits, control_map, sim)
let map = Map::new(&map_path, edits.road_edits.clone())
.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 input.contains("data/scenarios/") {
info!("Seeding the simulation from scenario {}", input);
let scenario: Scenario = abstutil::read_json(&input).expect("loading scenario failed");
let map_path = format!("../data/maps/{}.abst", scenario.map_name);
let map =
Map::new(&map_path, &edits).expect(&format!("Couldn't load map from {}", map_path));
let control_map = ControlMap::new(&map);
let map = Map::new(&map_path, edits.road_edits.clone())
.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,
scenario.scenario_name.clone(),
@ -41,15 +42,15 @@ pub fn load(
savestate_every,
);
scenario.instantiate(&mut sim, &map);
(map, edits, control_map, sim)
(map, control_map, sim)
} else {
info!("Loading map {}", input);
let map = Map::new(&input, &edits).expect("Couldn't load map");
let control_map = ControlMap::new(&map);
let map = Map::new(&input, edits.road_edits.clone()).expect("Couldn't load map");
let control_map = ControlMap::new(&map, &edits.stop_signs, &edits.traffic_signals);
flame::start("create sim");
let sim = Sim::new(&map, scenario_name, rng_seed, savestate_every);
flame::end("create sim");
(map, edits, control_map, sim)
(map, control_map, sim)
}
}

View File

@ -33,6 +33,7 @@ extern crate serde_derive;
mod macros;
mod driving;
mod edits;
mod events;
mod helpers;
mod instrument;
@ -51,6 +52,7 @@ mod walking;
use abstutil::Cloneable;
use dimensioned::si;
pub use edits::MapEdits;
pub use events::Event;
use geom::{Angle, Pt2D};
pub use helpers::load;

View File

@ -5,7 +5,7 @@ extern crate sim;
#[test]
fn aorta_model_completes() {
let (map, _, control_map, mut sim) = sim::load(
let (map, control_map, mut sim) = sim::load(
"../data/maps/small.abst".to_string(),
"aorta_model_completes".to_string(),
Some(42),

View File

@ -5,7 +5,7 @@ extern crate sim;
#[test]
fn serialization() {
let (map, _, _, mut sim) = sim::load(
let (map, _, mut sim) = sim::load(
"../data/maps/small.abst".to_string(),
"serialization".to_string(),
Some(42),
@ -22,7 +22,7 @@ fn serialization() {
#[test]
fn from_scratch() {
println!("Creating two simulations");
let (map, _, control_map, mut sim1) = sim::load(
let (map, control_map, mut sim1) = sim::load(
"../data/maps/small.abst".to_string(),
"from_scratch_1".to_string(),
Some(42),
@ -49,7 +49,7 @@ fn from_scratch() {
#[test]
fn with_savestating() {
println!("Creating two simulations");
let (map, _, control_map, mut sim1) = sim::load(
let (map, control_map, mut sim1) = sim::load(
"../data/maps/small.abst".to_string(),
"with_savestating_1".to_string(),
Some(42),

View File

@ -5,6 +5,7 @@ extern crate map_model;
extern crate sim;
use map_model::LaneID;
use std::collections::BTreeMap;
// TODO refactor a few more things to make these more succinct?
@ -62,7 +63,7 @@ fn setup(
map: map_model::Map,
) -> (map_model::Map, control::ControlMap, sim::Sim) {
let rng_seed = 123;
let control_map = control::ControlMap::new(&map);
let control_map = control::ControlMap::new(&map, &BTreeMap::new(), &BTreeMap::new());
let sim = sim::Sim::new(&map, scenario_name.to_string(), Some(rng_seed), None);
(map, control_map, sim)
}
@ -71,7 +72,6 @@ fn setup(
fn make_test_map() -> map_model::Map {
use dimensioned::si;
use map_model::{raw_data, LaneType};
use std::collections::BTreeMap;
let left = geom::LonLat::new(100.0, 50.0);
let right = geom::LonLat::new(200.0, 50.0);
@ -116,7 +116,7 @@ fn make_test_map() -> map_model::Map {
areas: Vec::new(),
coordinates_in_world_space: true,
},
&map_model::Edits::new(),
map_model::RoadEdits::new(),
);
assert_eq!(map.all_roads().len(), 1);

View File

@ -5,7 +5,7 @@ extern crate sim;
#[test]
fn bus_reaches_stops() {
let (map, _, control_map, mut sim) = sim::load(
let (map, control_map, mut sim) = sim::load(
"../data/maps/small.abst".to_string(),
"bus_reaches_stops".to_string(),
Some(42),
@ -33,7 +33,7 @@ fn bus_reaches_stops() {
// TODO this test is strictly more complicated than bus_reaches_stops, should it subsume it?
#[test]
fn ped_uses_bus() {
let (map, _, control_map, mut sim) = sim::load(
let (map, control_map, mut sim) = sim::load(
"../data/maps/small.abst".to_string(),
"bus_reaches_stops".to_string(),
Some(42),