Let the user make edits to switch between marked/unmarked crosswalks!

This commit is contained in:
Dustin Carlino 2022-04-26 16:35:16 +01:00
parent 96d7010582
commit dff4e17b28
12 changed files with 410 additions and 140 deletions

View 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);
}
}

View File

@ -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,
}
}

View File

@ -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!(),
}
}

View File

@ -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)

View File

@ -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 { .. } => {}
}
}

View File

@ -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();

View File

@ -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.

View File

@ -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(&gtfs_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))
}
}

View File

@ -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]

View File

@ -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
},
})
}

View File

@ -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
},
})
}

View File

@ -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 {