mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-23 14:23:14 +03:00
Let the user make edits to switch between marked/unmarked crosswalks!
This commit is contained in:
parent
96d7010582
commit
dff4e17b28
106
apps/game/src/edit/crosswalks.rs
Normal file
106
apps/game/src/edit/crosswalks.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use geom::Distance;
|
||||
use map_model::{EditCmd, IntersectionID, TurnID, TurnType};
|
||||
use widgetry::mapspace::{ObjectID, World, WorldOutcome};
|
||||
use widgetry::{
|
||||
Color, EventCtx, GfxCtx, HorizontalAlignment, Key, Line, Outcome, Panel, State, TextExt,
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::app::App;
|
||||
use crate::app::Transition;
|
||||
use crate::edit::apply_map_edits;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
struct ID(TurnID);
|
||||
|
||||
impl ObjectID for ID {}
|
||||
|
||||
pub struct CrosswalkEditor {
|
||||
id: IntersectionID,
|
||||
world: World<ID>,
|
||||
panel: Panel,
|
||||
}
|
||||
|
||||
impl CrosswalkEditor {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &mut App, id: IntersectionID) -> Box<dyn State<App>> {
|
||||
app.primary.current_selection = None;
|
||||
|
||||
let map = &app.primary.map;
|
||||
let mut world = World::bounded(map.get_bounds());
|
||||
for turn in &map.get_i(id).turns {
|
||||
if turn.turn_type.pedestrian_crossing() {
|
||||
let width = Distance::meters(3.0);
|
||||
let hitbox = if let Some(line) = turn.crosswalk_line() {
|
||||
line.make_polygons(width)
|
||||
} else {
|
||||
turn.geom.make_polygons(width)
|
||||
};
|
||||
world
|
||||
.add(ID(turn.id))
|
||||
.hitbox(hitbox)
|
||||
.draw_color(Color::RED.alpha(0.5))
|
||||
.hover_alpha(0.3)
|
||||
.clickable()
|
||||
.build(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(Self {
|
||||
id,
|
||||
world,
|
||||
panel: Panel::new_builder(Widget::col(vec![
|
||||
Line("Crosswalks editor").small_heading().into_widget(ctx),
|
||||
"Click a crosswalk to toggle it between marked and unmarked".text_widget(ctx),
|
||||
Line("Pedestrians can cross using both, but have priority over vehicles at marked zebra crossings").secondary().into_widget(ctx),
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for CrosswalkEditor {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
if let WorldOutcome::ClickedObject(ID(turn)) = self.world.event(ctx) {
|
||||
let mut edits = app.primary.map.get_edits().clone();
|
||||
let old = app.primary.map.get_i_crosswalks_edit(self.id);
|
||||
let mut new = old.clone();
|
||||
new.0.insert(
|
||||
turn,
|
||||
if old.0[&turn] == TurnType::Crosswalk {
|
||||
TurnType::UnmarkedCrossing
|
||||
} else {
|
||||
TurnType::Crosswalk
|
||||
},
|
||||
);
|
||||
edits.commands.push(EditCmd::ChangeCrosswalks {
|
||||
i: self.id,
|
||||
old,
|
||||
new,
|
||||
});
|
||||
apply_map_edits(ctx, app, edits);
|
||||
return Transition::Replace(Self::new_state(ctx, app, self.id));
|
||||
}
|
||||
|
||||
if let Outcome::Clicked(ref x) = self.panel.event(ctx) {
|
||||
match x.as_ref() {
|
||||
"Finish" => {
|
||||
return Transition::Pop;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, _: &App) {
|
||||
self.panel.draw(g);
|
||||
self.world.draw(g);
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ use crate::common::{tool_panel, CommonState, Warping};
|
||||
use crate::debug::DebugMode;
|
||||
use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen};
|
||||
|
||||
mod crosswalks;
|
||||
mod heuristics;
|
||||
mod multiple_roads;
|
||||
mod roads;
|
||||
@ -904,6 +905,7 @@ fn cmd_to_id(cmd: &EditCmd) -> Option<ID> {
|
||||
match cmd {
|
||||
EditCmd::ChangeRoad { r, .. } => Some(ID::Road(*r)),
|
||||
EditCmd::ChangeIntersection { i, .. } => Some(ID::Intersection(*i)),
|
||||
EditCmd::ChangeCrosswalks { i, .. } => Some(ID::Intersection(*i)),
|
||||
EditCmd::ChangeRouteSchedule { .. } => None,
|
||||
}
|
||||
}
|
||||
|
@ -50,29 +50,37 @@ impl StopSignEditor {
|
||||
|
||||
let panel = Panel::new_builder(Widget::col(vec![
|
||||
Line("Stop sign editor").small_heading().into_widget(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("reset to default")
|
||||
.hotkey(Key::R)
|
||||
.disabled(
|
||||
&ControlStopSign::new(&app.primary.map, id)
|
||||
== app.primary.map.get_stop_sign(id),
|
||||
)
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("close intersection for construction")
|
||||
.hotkey(Key::C)
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("convert to traffic signal")
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx),
|
||||
Widget::row(vec![
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("reset to default")
|
||||
.hotkey(Key::R)
|
||||
.disabled(
|
||||
&ControlStopSign::new(&app.primary.map, id)
|
||||
== app.primary.map.get_stop_sign(id),
|
||||
)
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("Change crosswalks")
|
||||
.hotkey(Key::C)
|
||||
.build_def(ctx),
|
||||
]),
|
||||
Widget::row(vec![
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("close intersection for construction")
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("convert to traffic signal")
|
||||
.build_def(ctx),
|
||||
]),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx);
|
||||
@ -154,6 +162,9 @@ impl SimpleState<App> for StopSignEditor {
|
||||
self.mode.clone(),
|
||||
))
|
||||
}
|
||||
"Change crosswalks" => Transition::Replace(
|
||||
super::crosswalks::CrosswalkEditor::new_state(ctx, app, self.id),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -355,6 +355,14 @@ impl State<App> for TrafficSignalEditor {
|
||||
);
|
||||
}
|
||||
}
|
||||
"Change crosswalks" => {
|
||||
// TODO Probably need to follow everything Cancel does
|
||||
return Transition::Replace(super::crosswalks::CrosswalkEditor::new_state(
|
||||
ctx,
|
||||
app,
|
||||
*self.members.iter().next().unwrap(),
|
||||
));
|
||||
}
|
||||
"Preview" => {
|
||||
// Might have to do this first!
|
||||
app.primary
|
||||
@ -535,37 +543,29 @@ impl State<App> for TrafficSignalEditor {
|
||||
}
|
||||
|
||||
fn make_top_panel(ctx: &mut EventCtx, app: &App, can_undo: bool, can_redo: bool) -> Panel {
|
||||
let row = vec![
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
.hotkey(Key::Enter)
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("Preview")
|
||||
.hotkey(lctrl(Key::P))
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/undo.svg")
|
||||
.disabled(!can_undo)
|
||||
.hotkey(lctrl(Key::Z))
|
||||
.build_widget(ctx, "undo"),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/redo.svg")
|
||||
.disabled(!can_redo)
|
||||
// TODO ctrl+shift+Z!
|
||||
.hotkey(lctrl(Key::Y))
|
||||
.build_widget(ctx, "redo"),
|
||||
ctx.style()
|
||||
.btn_plain_destructive
|
||||
.text("Cancel")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx)
|
||||
.align_right(),
|
||||
];
|
||||
let mut second_row = vec![ctx
|
||||
.style()
|
||||
.btn_outline
|
||||
.text("Change crosswalks")
|
||||
.hotkey(Key::C)
|
||||
.build_def(ctx)];
|
||||
if app.opts.dev {
|
||||
second_row.push(
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("Export")
|
||||
.tooltip(Text::from_multiline(vec![
|
||||
Line("This will create a JSON file in traffic_signal_data/.").small(),
|
||||
Line(
|
||||
"Contribute this to map how this traffic signal is currently timed in \
|
||||
real life.",
|
||||
)
|
||||
.small(),
|
||||
]))
|
||||
.build_def(ctx),
|
||||
);
|
||||
}
|
||||
|
||||
Panel::new_builder(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
Line("Traffic signal editor")
|
||||
@ -578,23 +578,38 @@ fn make_top_panel(ctx: &mut EventCtx, app: &App, can_undo: bool, can_redo: bool)
|
||||
.hotkey(Key::M)
|
||||
.build_widget(ctx, "Edit multiple signals"),
|
||||
]),
|
||||
Widget::row(row),
|
||||
if app.opts.dev {
|
||||
Widget::row(vec![
|
||||
ctx.style()
|
||||
.btn_solid_primary
|
||||
.text("Finish")
|
||||
.hotkey(Key::Enter)
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
.text("Export")
|
||||
.tooltip(Text::from_multiline(vec![
|
||||
Line("This will create a JSON file in traffic_signal_data/.").small(),
|
||||
Line(
|
||||
"Contribute this to map how this traffic signal is currently timed in \
|
||||
real life.",
|
||||
)
|
||||
.small(),
|
||||
]))
|
||||
.text("Preview")
|
||||
.hotkey(lctrl(Key::P))
|
||||
.build_def(ctx),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/undo.svg")
|
||||
.disabled(!can_undo)
|
||||
.hotkey(lctrl(Key::Z))
|
||||
.build_widget(ctx, "undo"),
|
||||
ctx.style()
|
||||
.btn_plain
|
||||
.icon("system/assets/tools/redo.svg")
|
||||
.disabled(!can_redo)
|
||||
// TODO ctrl+shift+Z!
|
||||
.hotkey(lctrl(Key::Y))
|
||||
.build_widget(ctx, "redo"),
|
||||
ctx.style()
|
||||
.btn_plain_destructive
|
||||
.text("Cancel")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx)
|
||||
} else {
|
||||
Widget::nothing()
|
||||
},
|
||||
.align_right(),
|
||||
]),
|
||||
Widget::row(second_row),
|
||||
]))
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx)
|
||||
|
@ -191,6 +191,12 @@ impl GameplayMode {
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
EditCmd::ChangeCrosswalks { .. } => {
|
||||
// TODO Another hack to see if we can only edit signal timing
|
||||
if !self.can_edit_stop_signs() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
EditCmd::ChangeRouteSchedule { .. } => {}
|
||||
}
|
||||
}
|
||||
|
@ -502,7 +502,7 @@ pub fn make_crosswalk(batch: &mut GeomBatch, turn: &Turn, map: &Map, cs: &ColorS
|
||||
// crosswalk line itself. Center the lines inside these two boundaries.
|
||||
let boundary = width;
|
||||
let tile_every = width * 0.6;
|
||||
let line = if let Some(l) = crosswalk_line(turn) {
|
||||
let line = if let Some(l) = turn.crosswalk_line() {
|
||||
l
|
||||
} else {
|
||||
return;
|
||||
@ -596,7 +596,7 @@ fn make_unmarked_crossing(batch: &mut GeomBatch, turn: &Turn, map: &Map, cs: &Co
|
||||
let color = cs.general_road_marking.alpha(0.5);
|
||||
let band_width = Distance::meters(0.1);
|
||||
let total_width = map.get_l(turn.id.src).width;
|
||||
if let Some(line) = crosswalk_line(turn) {
|
||||
if let Some(line) = turn.crosswalk_line() {
|
||||
if let Ok(slice) = line.slice(total_width, line.length() - total_width) {
|
||||
batch.push(
|
||||
color,
|
||||
@ -614,20 +614,6 @@ fn make_unmarked_crossing(batch: &mut GeomBatch, turn: &Turn, map: &Map, cs: &Co
|
||||
}
|
||||
}
|
||||
|
||||
// The geometry of crosswalks will first cross part of a sidewalk corner, then actually enter the
|
||||
// road. Extract the piece that's in the road.
|
||||
fn crosswalk_line(turn: &Turn) -> Option<Line> {
|
||||
let pts = turn.geom.points();
|
||||
if pts.len() < 3 {
|
||||
warn!(
|
||||
"Not rendering crosswalk for {}; its geometry was squished earlier",
|
||||
turn.id
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Line::new(pts[1], pts[2]).ok()
|
||||
}
|
||||
|
||||
// TODO copied from DrawLane
|
||||
fn perp_line(l: Line, length: Distance) -> Line {
|
||||
let pt1 = l.shift_right(length / 2.0).pt1();
|
||||
|
@ -16,7 +16,8 @@ use crate::make::{match_points_to_lanes, snap_driveway, trim_path};
|
||||
use crate::{
|
||||
connectivity, AccessRestrictions, BuildingID, ControlStopSign, ControlTrafficSignal, Direction,
|
||||
IntersectionID, IntersectionType, LaneID, LaneSpec, LaneType, Map, MapConfig, Movement,
|
||||
ParkingLotID, PathConstraints, Pathfinder, Road, RoadID, TransitRouteID, TurnID, Zone,
|
||||
ParkingLotID, PathConstraints, Pathfinder, Road, RoadID, TransitRouteID, TurnID, TurnType,
|
||||
Zone,
|
||||
};
|
||||
|
||||
mod compat;
|
||||
@ -38,6 +39,7 @@ pub struct MapEdits {
|
||||
/// Derived from commands, kept up to date by update_derived
|
||||
pub changed_roads: BTreeSet<RoadID>,
|
||||
pub original_intersections: BTreeMap<IntersectionID, EditIntersection>,
|
||||
pub original_crosswalks: BTreeMap<IntersectionID, EditCrosswalks>,
|
||||
pub changed_routes: BTreeSet<TransitRouteID>,
|
||||
|
||||
/// Some edits are included in the game by default, in data/system/proposals, as "community
|
||||
@ -62,6 +64,11 @@ pub struct EditRoad {
|
||||
pub access_restrictions: AccessRestrictions,
|
||||
}
|
||||
|
||||
/// This must contain all crossing turns at one intersection, each mapped either to Crosswalk or
|
||||
/// UnmarkedCrossing
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct EditCrosswalks(pub BTreeMap<TurnID, TurnType>);
|
||||
|
||||
impl EditRoad {
|
||||
pub fn get_orig_from_osm(r: &Road, cfg: &MapConfig) -> EditRoad {
|
||||
EditRoad {
|
||||
@ -189,12 +196,18 @@ pub enum EditCmd {
|
||||
old: Vec<Time>,
|
||||
new: Vec<Time>,
|
||||
},
|
||||
ChangeCrosswalks {
|
||||
i: IntersectionID,
|
||||
old: EditCrosswalks,
|
||||
new: EditCrosswalks,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct EditEffects {
|
||||
pub changed_roads: BTreeSet<RoadID>,
|
||||
pub deleted_lanes: BTreeSet<LaneID>,
|
||||
pub changed_intersections: BTreeSet<IntersectionID>,
|
||||
// TODO Will we need modified turns?
|
||||
pub added_turns: BTreeSet<TurnID>,
|
||||
pub deleted_turns: BTreeSet<TurnID>,
|
||||
pub changed_parking_lots: BTreeSet<ParkingLotID>,
|
||||
@ -212,6 +225,7 @@ impl MapEdits {
|
||||
|
||||
changed_roads: BTreeSet::new(),
|
||||
original_intersections: BTreeMap::new(),
|
||||
original_crosswalks: BTreeMap::new(),
|
||||
changed_routes: BTreeSet::new(),
|
||||
}
|
||||
}
|
||||
@ -271,6 +285,7 @@ impl MapEdits {
|
||||
fn update_derived(&mut self, map: &Map) {
|
||||
self.changed_roads.clear();
|
||||
self.original_intersections.clear();
|
||||
self.original_crosswalks.clear();
|
||||
self.changed_routes.clear();
|
||||
|
||||
for cmd in &self.commands {
|
||||
@ -283,6 +298,11 @@ impl MapEdits {
|
||||
self.original_intersections.insert(*i, old.clone());
|
||||
}
|
||||
}
|
||||
EditCmd::ChangeCrosswalks { i, ref old, .. } => {
|
||||
if !self.original_crosswalks.contains_key(i) {
|
||||
self.original_crosswalks.insert(*i, old.clone());
|
||||
}
|
||||
}
|
||||
EditCmd::ChangeRouteSchedule { id, .. } => {
|
||||
self.changed_routes.insert(*id);
|
||||
}
|
||||
@ -294,6 +314,8 @@ impl MapEdits {
|
||||
});
|
||||
self.original_intersections
|
||||
.retain(|i, orig| map.get_i_edit(*i) != orig.clone());
|
||||
self.original_crosswalks
|
||||
.retain(|i, orig| map.get_i_crosswalks_edit(*i) != orig.clone());
|
||||
self.changed_routes.retain(|br| {
|
||||
let r = map.get_tr(*br);
|
||||
r.spawn_times != r.orig_spawn_times
|
||||
@ -316,6 +338,13 @@ impl MapEdits {
|
||||
new: map.get_i_edit(*i),
|
||||
});
|
||||
}
|
||||
for (i, old) in &self.original_crosswalks {
|
||||
self.commands.push(EditCmd::ChangeCrosswalks {
|
||||
i: *i,
|
||||
old: old.clone(),
|
||||
new: map.get_i_crosswalks_edit(*i),
|
||||
});
|
||||
}
|
||||
for r in &self.changed_routes {
|
||||
let r = map.get_tr(*r);
|
||||
self.commands.push(EditCmd::ChangeRouteSchedule {
|
||||
@ -393,6 +422,7 @@ impl EditCmd {
|
||||
EditIntersection::TrafficSignal(_) => format!("traffic signal #{}", i.0),
|
||||
EditIntersection::Closed => format!("close {}", i),
|
||||
},
|
||||
EditCmd::ChangeCrosswalks { i, .. } => format!("crosswalks at {}", i),
|
||||
EditCmd::ChangeRouteSchedule { id, .. } => {
|
||||
format!("reschedule route {}", map.get_tr(*id).short_name)
|
||||
}
|
||||
@ -469,6 +499,15 @@ impl EditCmd {
|
||||
recalculate_turns(*i, map, effects);
|
||||
}
|
||||
}
|
||||
EditCmd::ChangeCrosswalks { i, ref new, .. } => {
|
||||
if map.get_i_crosswalks_edit(*i) == new.clone() {
|
||||
return;
|
||||
}
|
||||
effects.changed_intersections.insert(*i);
|
||||
for (turn, turn_type) in &new.0 {
|
||||
map.mut_turn(*turn).turn_type = *turn_type;
|
||||
}
|
||||
}
|
||||
EditCmd::ChangeRouteSchedule { id, new, .. } => {
|
||||
map.transit_routes[id.0].spawn_times = new.clone();
|
||||
}
|
||||
@ -487,6 +526,11 @@ impl EditCmd {
|
||||
old: new,
|
||||
new: old,
|
||||
},
|
||||
EditCmd::ChangeCrosswalks { i, old, new } => EditCmd::ChangeCrosswalks {
|
||||
i,
|
||||
old: new,
|
||||
new: old,
|
||||
},
|
||||
EditCmd::ChangeRouteSchedule { id, old, new } => EditCmd::ChangeRouteSchedule {
|
||||
id,
|
||||
old: new,
|
||||
@ -794,6 +838,16 @@ impl Map {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_i_crosswalks_edit(&self, i: IntersectionID) -> EditCrosswalks {
|
||||
let mut turns = BTreeMap::new();
|
||||
for turn in &self.get_i(i).turns {
|
||||
if turn.turn_type.pedestrian_crossing() {
|
||||
turns.insert(turn.id, turn.turn_type);
|
||||
}
|
||||
}
|
||||
EditCrosswalks(turns)
|
||||
}
|
||||
|
||||
pub fn save_edits(&self) {
|
||||
// Don't overwrite the current edits with the compressed first. Otherwise, undo/redo order
|
||||
// in the UI gets messed up.
|
||||
|
@ -7,9 +7,9 @@ use abstio::MapName;
|
||||
use abstutil::{deserialize_btreemap, serialize_btreemap};
|
||||
use geom::Time;
|
||||
|
||||
use crate::edits::{EditCmd, EditIntersection, EditRoad, MapEdits};
|
||||
use crate::edits::{EditCmd, EditCrosswalks, EditIntersection, EditRoad, MapEdits};
|
||||
use crate::raw::OriginalRoad;
|
||||
use crate::{osm, ControlStopSign, IntersectionID, Map};
|
||||
use crate::{osm, ControlStopSign, IntersectionID, Map, MovementID, TurnType};
|
||||
|
||||
/// MapEdits are converted to this before serializing. Referencing things like LaneID in a Map won't
|
||||
/// work if the basemap is rebuilt from new OSM data, so instead we use stabler OSM IDs that're less
|
||||
@ -44,6 +44,15 @@ pub enum PermanentEditIntersection {
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct PermanentEditCrosswalks {
|
||||
#[serde(
|
||||
serialize_with = "serialize_btreemap",
|
||||
deserialize_with = "deserialize_btreemap"
|
||||
)]
|
||||
turns: BTreeMap<traffic_signal_data::Turn, TurnType>,
|
||||
}
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub enum PermanentEditCmd {
|
||||
@ -57,6 +66,11 @@ pub enum PermanentEditCmd {
|
||||
new: PermanentEditIntersection,
|
||||
old: PermanentEditIntersection,
|
||||
},
|
||||
ChangeCrosswalks {
|
||||
i: osm::NodeID,
|
||||
new: PermanentEditCrosswalks,
|
||||
old: PermanentEditCrosswalks,
|
||||
},
|
||||
ChangeRouteSchedule {
|
||||
gtfs_id: String,
|
||||
old: Vec<Time>,
|
||||
@ -77,6 +91,11 @@ impl EditCmd {
|
||||
new: new.to_permanent(map),
|
||||
old: old.to_permanent(map),
|
||||
},
|
||||
EditCmd::ChangeCrosswalks { i, new, old } => PermanentEditCmd::ChangeCrosswalks {
|
||||
i: map.get_i(*i).orig_id,
|
||||
new: new.to_permanent(map),
|
||||
old: old.to_permanent(map),
|
||||
},
|
||||
EditCmd::ChangeRouteSchedule { id, old, new } => {
|
||||
PermanentEditCmd::ChangeRouteSchedule {
|
||||
gtfs_id: map.get_tr(*id).gtfs_id.clone(),
|
||||
@ -118,6 +137,18 @@ impl PermanentEditCmd {
|
||||
.with_context(|| format!("old ChangeIntersection of {} invalid", i))?,
|
||||
})
|
||||
}
|
||||
PermanentEditCmd::ChangeCrosswalks { i, new, old } => {
|
||||
let id = map.find_i_by_osm_id(i)?;
|
||||
Ok(EditCmd::ChangeCrosswalks {
|
||||
i: id,
|
||||
new: new
|
||||
.with_permanent(id, map)
|
||||
.with_context(|| format!("new ChangeCrosswalks of {} invalid", i))?,
|
||||
old: old
|
||||
.with_permanent(id, map)
|
||||
.with_context(|| format!("old ChangeCrosswalks of {} invalid", i))?,
|
||||
})
|
||||
}
|
||||
PermanentEditCmd::ChangeRouteSchedule { gtfs_id, old, new } => {
|
||||
let id = map
|
||||
.find_tr_by_gtfs(>fs_id)
|
||||
@ -161,6 +192,7 @@ impl PermanentMapEdits {
|
||||
|
||||
changed_roads: BTreeSet::new(),
|
||||
original_intersections: BTreeMap::new(),
|
||||
original_crosswalks: BTreeMap::new(),
|
||||
changed_routes: BTreeSet::new(),
|
||||
};
|
||||
edits.update_derived(map);
|
||||
@ -189,6 +221,7 @@ impl PermanentMapEdits {
|
||||
|
||||
changed_roads: BTreeSet::new(),
|
||||
original_intersections: BTreeMap::new(),
|
||||
original_crosswalks: BTreeMap::new(),
|
||||
changed_routes: BTreeSet::new(),
|
||||
};
|
||||
edits.update_derived(map);
|
||||
@ -257,3 +290,40 @@ impl PermanentEditIntersection {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditCrosswalks {
|
||||
fn to_permanent(&self, map: &Map) -> PermanentEditCrosswalks {
|
||||
PermanentEditCrosswalks {
|
||||
turns: self
|
||||
.0
|
||||
.iter()
|
||||
.map(|(id, turn_type)| (id.to_movement(map).to_permanent(map), *turn_type))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PermanentEditCrosswalks {
|
||||
fn with_permanent(self, i: IntersectionID, map: &Map) -> Result<EditCrosswalks> {
|
||||
let mut turns = BTreeMap::new();
|
||||
for (id, turn_type) in self.turns {
|
||||
let movement = MovementID::from_permanent(id, map)?;
|
||||
// Find all TurnIDs that map to this MovementID
|
||||
let mut turn_ids = Vec::new();
|
||||
for turn in &map.get_i(i).turns {
|
||||
if turn.id.to_movement(map) == movement {
|
||||
turn_ids.push(turn.id);
|
||||
}
|
||||
}
|
||||
if turn_ids.len() != 1 {
|
||||
bail!(
|
||||
"{:?} didn't map to exactly 1 crossing turn: {:?}",
|
||||
movement,
|
||||
turn_ids
|
||||
);
|
||||
}
|
||||
turns.insert(turn_ids.pop().unwrap(), turn_type);
|
||||
}
|
||||
Ok(EditCrosswalks(turns))
|
||||
}
|
||||
}
|
||||
|
@ -260,6 +260,14 @@ impl Map {
|
||||
pub(crate) fn mut_road(&mut self, id: RoadID) -> &mut Road {
|
||||
&mut self.roads[id.0]
|
||||
}
|
||||
pub(crate) fn mut_turn(&mut self, id: TurnID) -> &mut Turn {
|
||||
for turn in &mut self.intersections[id.parent.0].turns {
|
||||
if turn.id == id {
|
||||
return turn;
|
||||
}
|
||||
}
|
||||
panic!("Couldn't find {id}");
|
||||
}
|
||||
|
||||
pub fn get_i(&self, id: IntersectionID) -> &Intersection {
|
||||
&self.intersections[id.0]
|
||||
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use abstutil::MultiMap;
|
||||
use geom::{Angle, Distance, PolyLine, Pt2D};
|
||||
use raw_map::{osm, OriginalRoad};
|
||||
|
||||
use crate::{DirectedRoadID, Direction, IntersectionID, Map, TurnID, TurnType};
|
||||
|
||||
@ -210,3 +211,50 @@ fn movement_geom(
|
||||
}
|
||||
PolyLine::deduping_new(pts)
|
||||
}
|
||||
|
||||
impl MovementID {
|
||||
pub fn to_permanent(&self, map: &Map) -> traffic_signal_data::Turn {
|
||||
let from = map.get_r(self.from.road).orig_id;
|
||||
let to = map.get_r(self.to.road).orig_id;
|
||||
|
||||
traffic_signal_data::Turn {
|
||||
from: traffic_signal_data::DirectedRoad {
|
||||
osm_way_id: from.osm_way_id.0,
|
||||
osm_node1: from.i1.0,
|
||||
osm_node2: from.i2.0,
|
||||
is_forwards: self.from.dir == Direction::Fwd,
|
||||
},
|
||||
to: traffic_signal_data::DirectedRoad {
|
||||
osm_way_id: to.osm_way_id.0,
|
||||
osm_node1: to.i1.0,
|
||||
osm_node2: to.i2.0,
|
||||
is_forwards: self.to.dir == Direction::Fwd,
|
||||
},
|
||||
intersection_osm_node_id: map.get_i(self.parent).orig_id.0,
|
||||
is_crosswalk: self.crosswalk,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_permanent(id: traffic_signal_data::Turn, map: &Map) -> Result<MovementID> {
|
||||
Ok(MovementID {
|
||||
from: find_r(id.from, map)?,
|
||||
to: find_r(id.to, map)?,
|
||||
parent: map.find_i_by_osm_id(osm::NodeID(id.intersection_osm_node_id))?,
|
||||
crosswalk: id.is_crosswalk,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn find_r(id: traffic_signal_data::DirectedRoad, map: &Map) -> Result<DirectedRoadID> {
|
||||
Ok(DirectedRoadID {
|
||||
road: map.find_r_by_osm_id(OriginalRoad::new(
|
||||
id.osm_way_id,
|
||||
(id.osm_node1, id.osm_node2),
|
||||
))?,
|
||||
dir: if id.is_forwards {
|
||||
Direction::Fwd
|
||||
} else {
|
||||
Direction::Back
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -6,10 +6,8 @@ use serde::{Deserialize, Serialize};
|
||||
use geom::{Distance, Duration, Speed};
|
||||
|
||||
use crate::make::traffic_signals::get_possible_policies;
|
||||
use crate::raw::OriginalRoad;
|
||||
use crate::{
|
||||
osm, DirectedRoadID, Direction, Intersection, IntersectionID, Map, Movement, MovementID,
|
||||
RoadID, TurnID, TurnPriority,
|
||||
Intersection, IntersectionID, Map, Movement, MovementID, RoadID, TurnID, TurnPriority,
|
||||
};
|
||||
|
||||
// The pace to use for crosswalk pace in m/s
|
||||
@ -391,12 +389,12 @@ impl ControlTrafficSignal {
|
||||
protected_turns: s
|
||||
.protected_movements
|
||||
.iter()
|
||||
.map(|t| export_movement(t, map))
|
||||
.map(|mvmnt| mvmnt.to_permanent(map))
|
||||
.collect(),
|
||||
permitted_turns: s
|
||||
.yield_movements
|
||||
.iter()
|
||||
.map(|t| export_movement(t, map))
|
||||
.map(|mvmnt| mvmnt.to_permanent(map))
|
||||
.collect(),
|
||||
stage_type: match s.stage_type {
|
||||
StageType::Fixed(d) => {
|
||||
@ -429,7 +427,7 @@ impl ControlTrafficSignal {
|
||||
let mut errors = Vec::new();
|
||||
let mut protected_movements = BTreeSet::new();
|
||||
for t in s.protected_turns {
|
||||
match import_movement(t, map) {
|
||||
match MovementID::from_permanent(t, map) {
|
||||
Ok(mvmnt) => {
|
||||
protected_movements.insert(mvmnt);
|
||||
}
|
||||
@ -440,7 +438,7 @@ impl ControlTrafficSignal {
|
||||
}
|
||||
let mut permitted_movements = BTreeSet::new();
|
||||
for t in s.permitted_turns {
|
||||
match import_movement(t, map) {
|
||||
match MovementID::from_permanent(t, map) {
|
||||
Ok(mvmnt) => {
|
||||
permitted_movements.insert(mvmnt);
|
||||
}
|
||||
@ -479,48 +477,3 @@ impl ControlTrafficSignal {
|
||||
Ok(ts)
|
||||
}
|
||||
}
|
||||
|
||||
fn export_movement(id: &MovementID, map: &Map) -> traffic_signal_data::Turn {
|
||||
let from = map.get_r(id.from.road).orig_id;
|
||||
let to = map.get_r(id.to.road).orig_id;
|
||||
|
||||
traffic_signal_data::Turn {
|
||||
from: traffic_signal_data::DirectedRoad {
|
||||
osm_way_id: from.osm_way_id.0,
|
||||
osm_node1: from.i1.0,
|
||||
osm_node2: from.i2.0,
|
||||
is_forwards: id.from.dir == Direction::Fwd,
|
||||
},
|
||||
to: traffic_signal_data::DirectedRoad {
|
||||
osm_way_id: to.osm_way_id.0,
|
||||
osm_node1: to.i1.0,
|
||||
osm_node2: to.i2.0,
|
||||
is_forwards: id.to.dir == Direction::Fwd,
|
||||
},
|
||||
intersection_osm_node_id: map.get_i(id.parent).orig_id.0,
|
||||
is_crosswalk: id.crosswalk,
|
||||
}
|
||||
}
|
||||
|
||||
fn import_movement(id: traffic_signal_data::Turn, map: &Map) -> Result<MovementID> {
|
||||
Ok(MovementID {
|
||||
from: find_r(id.from, map)?,
|
||||
to: find_r(id.to, map)?,
|
||||
parent: map.find_i_by_osm_id(osm::NodeID(id.intersection_osm_node_id))?,
|
||||
crosswalk: id.is_crosswalk,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_r(id: traffic_signal_data::DirectedRoad, map: &Map) -> Result<DirectedRoadID> {
|
||||
Ok(DirectedRoadID {
|
||||
road: map.find_r_by_osm_id(OriginalRoad::new(
|
||||
id.osm_way_id,
|
||||
(id.osm_node1, id.osm_node2),
|
||||
))?,
|
||||
dir: if id.is_forwards {
|
||||
Direction::Fwd
|
||||
} else {
|
||||
Direction::Back
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use geom::{Angle, PolyLine};
|
||||
use geom::{Angle, Line, PolyLine};
|
||||
|
||||
use crate::raw::RestrictionType;
|
||||
use crate::{
|
||||
@ -282,6 +282,17 @@ impl Turn {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Only appropriat for pedestrian crossings. The geometry of crosswalks will first cross part
|
||||
/// of a sidewalk corner, then actually enter the road. Extract the piece that's in the road.
|
||||
pub fn crosswalk_line(&self) -> Option<Line> {
|
||||
let pts = self.geom.points();
|
||||
if pts.len() < 3 {
|
||||
warn!("Crosswalk {} was squished earlier", self.id);
|
||||
return None;
|
||||
}
|
||||
Line::new(pts[1], pts[2]).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl TurnID {
|
||||
|
Loading…
Reference in New Issue
Block a user