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::debug::DebugMode;
|
||||||
use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen};
|
use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen};
|
||||||
|
|
||||||
|
mod crosswalks;
|
||||||
mod heuristics;
|
mod heuristics;
|
||||||
mod multiple_roads;
|
mod multiple_roads;
|
||||||
mod roads;
|
mod roads;
|
||||||
@ -904,6 +905,7 @@ fn cmd_to_id(cmd: &EditCmd) -> Option<ID> {
|
|||||||
match cmd {
|
match cmd {
|
||||||
EditCmd::ChangeRoad { r, .. } => Some(ID::Road(*r)),
|
EditCmd::ChangeRoad { r, .. } => Some(ID::Road(*r)),
|
||||||
EditCmd::ChangeIntersection { i, .. } => Some(ID::Intersection(*i)),
|
EditCmd::ChangeIntersection { i, .. } => Some(ID::Intersection(*i)),
|
||||||
|
EditCmd::ChangeCrosswalks { i, .. } => Some(ID::Intersection(*i)),
|
||||||
EditCmd::ChangeRouteSchedule { .. } => None,
|
EditCmd::ChangeRouteSchedule { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,29 +50,37 @@ impl StopSignEditor {
|
|||||||
|
|
||||||
let panel = Panel::new_builder(Widget::col(vec![
|
let panel = Panel::new_builder(Widget::col(vec![
|
||||||
Line("Stop sign editor").small_heading().into_widget(ctx),
|
Line("Stop sign editor").small_heading().into_widget(ctx),
|
||||||
ctx.style()
|
Widget::row(vec![
|
||||||
.btn_outline
|
ctx.style()
|
||||||
.text("reset to default")
|
.btn_solid_primary
|
||||||
.hotkey(Key::R)
|
.text("Finish")
|
||||||
.disabled(
|
.hotkey(Key::Escape)
|
||||||
&ControlStopSign::new(&app.primary.map, id)
|
.build_def(ctx),
|
||||||
== app.primary.map.get_stop_sign(id),
|
ctx.style()
|
||||||
)
|
.btn_outline
|
||||||
.build_def(ctx),
|
.text("reset to default")
|
||||||
ctx.style()
|
.hotkey(Key::R)
|
||||||
.btn_outline
|
.disabled(
|
||||||
.text("close intersection for construction")
|
&ControlStopSign::new(&app.primary.map, id)
|
||||||
.hotkey(Key::C)
|
== app.primary.map.get_stop_sign(id),
|
||||||
.build_def(ctx),
|
)
|
||||||
ctx.style()
|
.build_def(ctx),
|
||||||
.btn_outline
|
ctx.style()
|
||||||
.text("convert to traffic signal")
|
.btn_outline
|
||||||
.build_def(ctx),
|
.text("Change crosswalks")
|
||||||
ctx.style()
|
.hotkey(Key::C)
|
||||||
.btn_solid_primary
|
.build_def(ctx),
|
||||||
.text("Finish")
|
]),
|
||||||
.hotkey(Key::Escape)
|
Widget::row(vec![
|
||||||
.build_def(ctx),
|
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)
|
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||||
.build(ctx);
|
.build(ctx);
|
||||||
@ -154,6 +162,9 @@ impl SimpleState<App> for StopSignEditor {
|
|||||||
self.mode.clone(),
|
self.mode.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
"Change crosswalks" => Transition::Replace(
|
||||||
|
super::crosswalks::CrosswalkEditor::new_state(ctx, app, self.id),
|
||||||
|
),
|
||||||
_ => unreachable!(),
|
_ => 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" => {
|
"Preview" => {
|
||||||
// Might have to do this first!
|
// Might have to do this first!
|
||||||
app.primary
|
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 {
|
fn make_top_panel(ctx: &mut EventCtx, app: &App, can_undo: bool, can_redo: bool) -> Panel {
|
||||||
let row = vec![
|
let mut second_row = vec![ctx
|
||||||
ctx.style()
|
.style()
|
||||||
.btn_solid_primary
|
.btn_outline
|
||||||
.text("Finish")
|
.text("Change crosswalks")
|
||||||
.hotkey(Key::Enter)
|
.hotkey(Key::C)
|
||||||
.build_def(ctx),
|
.build_def(ctx)];
|
||||||
ctx.style()
|
if app.opts.dev {
|
||||||
.btn_outline
|
second_row.push(
|
||||||
.text("Preview")
|
ctx.style()
|
||||||
.hotkey(lctrl(Key::P))
|
.btn_outline
|
||||||
.build_def(ctx),
|
.text("Export")
|
||||||
ctx.style()
|
.tooltip(Text::from_multiline(vec![
|
||||||
.btn_plain
|
Line("This will create a JSON file in traffic_signal_data/.").small(),
|
||||||
.icon("system/assets/tools/undo.svg")
|
Line(
|
||||||
.disabled(!can_undo)
|
"Contribute this to map how this traffic signal is currently timed in \
|
||||||
.hotkey(lctrl(Key::Z))
|
real life.",
|
||||||
.build_widget(ctx, "undo"),
|
)
|
||||||
ctx.style()
|
.small(),
|
||||||
.btn_plain
|
]))
|
||||||
.icon("system/assets/tools/redo.svg")
|
.build_def(ctx),
|
||||||
.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(),
|
|
||||||
];
|
|
||||||
Panel::new_builder(Widget::col(vec![
|
Panel::new_builder(Widget::col(vec![
|
||||||
Widget::row(vec![
|
Widget::row(vec![
|
||||||
Line("Traffic signal editor")
|
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)
|
.hotkey(Key::M)
|
||||||
.build_widget(ctx, "Edit multiple signals"),
|
.build_widget(ctx, "Edit multiple signals"),
|
||||||
]),
|
]),
|
||||||
Widget::row(row),
|
Widget::row(vec![
|
||||||
if app.opts.dev {
|
ctx.style()
|
||||||
|
.btn_solid_primary
|
||||||
|
.text("Finish")
|
||||||
|
.hotkey(Key::Enter)
|
||||||
|
.build_def(ctx),
|
||||||
ctx.style()
|
ctx.style()
|
||||||
.btn_outline
|
.btn_outline
|
||||||
.text("Export")
|
.text("Preview")
|
||||||
.tooltip(Text::from_multiline(vec![
|
.hotkey(lctrl(Key::P))
|
||||||
Line("This will create a JSON file in traffic_signal_data/.").small(),
|
.build_def(ctx),
|
||||||
Line(
|
ctx.style()
|
||||||
"Contribute this to map how this traffic signal is currently timed in \
|
.btn_plain
|
||||||
real life.",
|
.icon("system/assets/tools/undo.svg")
|
||||||
)
|
.disabled(!can_undo)
|
||||||
.small(),
|
.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)
|
.build_def(ctx)
|
||||||
} else {
|
.align_right(),
|
||||||
Widget::nothing()
|
]),
|
||||||
},
|
Widget::row(second_row),
|
||||||
]))
|
]))
|
||||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||||
.build(ctx)
|
.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 { .. } => {}
|
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.
|
// crosswalk line itself. Center the lines inside these two boundaries.
|
||||||
let boundary = width;
|
let boundary = width;
|
||||||
let tile_every = width * 0.6;
|
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
|
l
|
||||||
} else {
|
} else {
|
||||||
return;
|
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 color = cs.general_road_marking.alpha(0.5);
|
||||||
let band_width = Distance::meters(0.1);
|
let band_width = Distance::meters(0.1);
|
||||||
let total_width = map.get_l(turn.id.src).width;
|
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) {
|
if let Ok(slice) = line.slice(total_width, line.length() - total_width) {
|
||||||
batch.push(
|
batch.push(
|
||||||
color,
|
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
|
// TODO copied from DrawLane
|
||||||
fn perp_line(l: Line, length: Distance) -> Line {
|
fn perp_line(l: Line, length: Distance) -> Line {
|
||||||
let pt1 = l.shift_right(length / 2.0).pt1();
|
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::{
|
use crate::{
|
||||||
connectivity, AccessRestrictions, BuildingID, ControlStopSign, ControlTrafficSignal, Direction,
|
connectivity, AccessRestrictions, BuildingID, ControlStopSign, ControlTrafficSignal, Direction,
|
||||||
IntersectionID, IntersectionType, LaneID, LaneSpec, LaneType, Map, MapConfig, Movement,
|
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;
|
mod compat;
|
||||||
@ -38,6 +39,7 @@ pub struct MapEdits {
|
|||||||
/// Derived from commands, kept up to date by update_derived
|
/// Derived from commands, kept up to date by update_derived
|
||||||
pub changed_roads: BTreeSet<RoadID>,
|
pub changed_roads: BTreeSet<RoadID>,
|
||||||
pub original_intersections: BTreeMap<IntersectionID, EditIntersection>,
|
pub original_intersections: BTreeMap<IntersectionID, EditIntersection>,
|
||||||
|
pub original_crosswalks: BTreeMap<IntersectionID, EditCrosswalks>,
|
||||||
pub changed_routes: BTreeSet<TransitRouteID>,
|
pub changed_routes: BTreeSet<TransitRouteID>,
|
||||||
|
|
||||||
/// Some edits are included in the game by default, in data/system/proposals, as "community
|
/// 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,
|
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 {
|
impl EditRoad {
|
||||||
pub fn get_orig_from_osm(r: &Road, cfg: &MapConfig) -> EditRoad {
|
pub fn get_orig_from_osm(r: &Road, cfg: &MapConfig) -> EditRoad {
|
||||||
EditRoad {
|
EditRoad {
|
||||||
@ -189,12 +196,18 @@ pub enum EditCmd {
|
|||||||
old: Vec<Time>,
|
old: Vec<Time>,
|
||||||
new: Vec<Time>,
|
new: Vec<Time>,
|
||||||
},
|
},
|
||||||
|
ChangeCrosswalks {
|
||||||
|
i: IntersectionID,
|
||||||
|
old: EditCrosswalks,
|
||||||
|
new: EditCrosswalks,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditEffects {
|
pub struct EditEffects {
|
||||||
pub changed_roads: BTreeSet<RoadID>,
|
pub changed_roads: BTreeSet<RoadID>,
|
||||||
pub deleted_lanes: BTreeSet<LaneID>,
|
pub deleted_lanes: BTreeSet<LaneID>,
|
||||||
pub changed_intersections: BTreeSet<IntersectionID>,
|
pub changed_intersections: BTreeSet<IntersectionID>,
|
||||||
|
// TODO Will we need modified turns?
|
||||||
pub added_turns: BTreeSet<TurnID>,
|
pub added_turns: BTreeSet<TurnID>,
|
||||||
pub deleted_turns: BTreeSet<TurnID>,
|
pub deleted_turns: BTreeSet<TurnID>,
|
||||||
pub changed_parking_lots: BTreeSet<ParkingLotID>,
|
pub changed_parking_lots: BTreeSet<ParkingLotID>,
|
||||||
@ -212,6 +225,7 @@ impl MapEdits {
|
|||||||
|
|
||||||
changed_roads: BTreeSet::new(),
|
changed_roads: BTreeSet::new(),
|
||||||
original_intersections: BTreeMap::new(),
|
original_intersections: BTreeMap::new(),
|
||||||
|
original_crosswalks: BTreeMap::new(),
|
||||||
changed_routes: BTreeSet::new(),
|
changed_routes: BTreeSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -271,6 +285,7 @@ impl MapEdits {
|
|||||||
fn update_derived(&mut self, map: &Map) {
|
fn update_derived(&mut self, map: &Map) {
|
||||||
self.changed_roads.clear();
|
self.changed_roads.clear();
|
||||||
self.original_intersections.clear();
|
self.original_intersections.clear();
|
||||||
|
self.original_crosswalks.clear();
|
||||||
self.changed_routes.clear();
|
self.changed_routes.clear();
|
||||||
|
|
||||||
for cmd in &self.commands {
|
for cmd in &self.commands {
|
||||||
@ -283,6 +298,11 @@ impl MapEdits {
|
|||||||
self.original_intersections.insert(*i, old.clone());
|
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, .. } => {
|
EditCmd::ChangeRouteSchedule { id, .. } => {
|
||||||
self.changed_routes.insert(*id);
|
self.changed_routes.insert(*id);
|
||||||
}
|
}
|
||||||
@ -294,6 +314,8 @@ impl MapEdits {
|
|||||||
});
|
});
|
||||||
self.original_intersections
|
self.original_intersections
|
||||||
.retain(|i, orig| map.get_i_edit(*i) != orig.clone());
|
.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| {
|
self.changed_routes.retain(|br| {
|
||||||
let r = map.get_tr(*br);
|
let r = map.get_tr(*br);
|
||||||
r.spawn_times != r.orig_spawn_times
|
r.spawn_times != r.orig_spawn_times
|
||||||
@ -316,6 +338,13 @@ impl MapEdits {
|
|||||||
new: map.get_i_edit(*i),
|
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 {
|
for r in &self.changed_routes {
|
||||||
let r = map.get_tr(*r);
|
let r = map.get_tr(*r);
|
||||||
self.commands.push(EditCmd::ChangeRouteSchedule {
|
self.commands.push(EditCmd::ChangeRouteSchedule {
|
||||||
@ -393,6 +422,7 @@ impl EditCmd {
|
|||||||
EditIntersection::TrafficSignal(_) => format!("traffic signal #{}", i.0),
|
EditIntersection::TrafficSignal(_) => format!("traffic signal #{}", i.0),
|
||||||
EditIntersection::Closed => format!("close {}", i),
|
EditIntersection::Closed => format!("close {}", i),
|
||||||
},
|
},
|
||||||
|
EditCmd::ChangeCrosswalks { i, .. } => format!("crosswalks at {}", i),
|
||||||
EditCmd::ChangeRouteSchedule { id, .. } => {
|
EditCmd::ChangeRouteSchedule { id, .. } => {
|
||||||
format!("reschedule route {}", map.get_tr(*id).short_name)
|
format!("reschedule route {}", map.get_tr(*id).short_name)
|
||||||
}
|
}
|
||||||
@ -469,6 +499,15 @@ impl EditCmd {
|
|||||||
recalculate_turns(*i, map, effects);
|
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, .. } => {
|
EditCmd::ChangeRouteSchedule { id, new, .. } => {
|
||||||
map.transit_routes[id.0].spawn_times = new.clone();
|
map.transit_routes[id.0].spawn_times = new.clone();
|
||||||
}
|
}
|
||||||
@ -487,6 +526,11 @@ impl EditCmd {
|
|||||||
old: new,
|
old: new,
|
||||||
new: old,
|
new: old,
|
||||||
},
|
},
|
||||||
|
EditCmd::ChangeCrosswalks { i, old, new } => EditCmd::ChangeCrosswalks {
|
||||||
|
i,
|
||||||
|
old: new,
|
||||||
|
new: old,
|
||||||
|
},
|
||||||
EditCmd::ChangeRouteSchedule { id, old, new } => EditCmd::ChangeRouteSchedule {
|
EditCmd::ChangeRouteSchedule { id, old, new } => EditCmd::ChangeRouteSchedule {
|
||||||
id,
|
id,
|
||||||
old: new,
|
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) {
|
pub fn save_edits(&self) {
|
||||||
// Don't overwrite the current edits with the compressed first. Otherwise, undo/redo order
|
// Don't overwrite the current edits with the compressed first. Otherwise, undo/redo order
|
||||||
// in the UI gets messed up.
|
// in the UI gets messed up.
|
||||||
|
@ -7,9 +7,9 @@ use abstio::MapName;
|
|||||||
use abstutil::{deserialize_btreemap, serialize_btreemap};
|
use abstutil::{deserialize_btreemap, serialize_btreemap};
|
||||||
use geom::Time;
|
use geom::Time;
|
||||||
|
|
||||||
use crate::edits::{EditCmd, EditIntersection, EditRoad, MapEdits};
|
use crate::edits::{EditCmd, EditCrosswalks, EditIntersection, EditRoad, MapEdits};
|
||||||
use crate::raw::OriginalRoad;
|
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
|
/// 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
|
/// 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,
|
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)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub enum PermanentEditCmd {
|
pub enum PermanentEditCmd {
|
||||||
@ -57,6 +66,11 @@ pub enum PermanentEditCmd {
|
|||||||
new: PermanentEditIntersection,
|
new: PermanentEditIntersection,
|
||||||
old: PermanentEditIntersection,
|
old: PermanentEditIntersection,
|
||||||
},
|
},
|
||||||
|
ChangeCrosswalks {
|
||||||
|
i: osm::NodeID,
|
||||||
|
new: PermanentEditCrosswalks,
|
||||||
|
old: PermanentEditCrosswalks,
|
||||||
|
},
|
||||||
ChangeRouteSchedule {
|
ChangeRouteSchedule {
|
||||||
gtfs_id: String,
|
gtfs_id: String,
|
||||||
old: Vec<Time>,
|
old: Vec<Time>,
|
||||||
@ -77,6 +91,11 @@ impl EditCmd {
|
|||||||
new: new.to_permanent(map),
|
new: new.to_permanent(map),
|
||||||
old: old.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 } => {
|
EditCmd::ChangeRouteSchedule { id, old, new } => {
|
||||||
PermanentEditCmd::ChangeRouteSchedule {
|
PermanentEditCmd::ChangeRouteSchedule {
|
||||||
gtfs_id: map.get_tr(*id).gtfs_id.clone(),
|
gtfs_id: map.get_tr(*id).gtfs_id.clone(),
|
||||||
@ -118,6 +137,18 @@ impl PermanentEditCmd {
|
|||||||
.with_context(|| format!("old ChangeIntersection of {} invalid", i))?,
|
.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 } => {
|
PermanentEditCmd::ChangeRouteSchedule { gtfs_id, old, new } => {
|
||||||
let id = map
|
let id = map
|
||||||
.find_tr_by_gtfs(>fs_id)
|
.find_tr_by_gtfs(>fs_id)
|
||||||
@ -161,6 +192,7 @@ impl PermanentMapEdits {
|
|||||||
|
|
||||||
changed_roads: BTreeSet::new(),
|
changed_roads: BTreeSet::new(),
|
||||||
original_intersections: BTreeMap::new(),
|
original_intersections: BTreeMap::new(),
|
||||||
|
original_crosswalks: BTreeMap::new(),
|
||||||
changed_routes: BTreeSet::new(),
|
changed_routes: BTreeSet::new(),
|
||||||
};
|
};
|
||||||
edits.update_derived(map);
|
edits.update_derived(map);
|
||||||
@ -189,6 +221,7 @@ impl PermanentMapEdits {
|
|||||||
|
|
||||||
changed_roads: BTreeSet::new(),
|
changed_roads: BTreeSet::new(),
|
||||||
original_intersections: BTreeMap::new(),
|
original_intersections: BTreeMap::new(),
|
||||||
|
original_crosswalks: BTreeMap::new(),
|
||||||
changed_routes: BTreeSet::new(),
|
changed_routes: BTreeSet::new(),
|
||||||
};
|
};
|
||||||
edits.update_derived(map);
|
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 {
|
pub(crate) fn mut_road(&mut self, id: RoadID) -> &mut Road {
|
||||||
&mut self.roads[id.0]
|
&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 {
|
pub fn get_i(&self, id: IntersectionID) -> &Intersection {
|
||||||
&self.intersections[id.0]
|
&self.intersections[id.0]
|
||||||
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use abstutil::MultiMap;
|
use abstutil::MultiMap;
|
||||||
use geom::{Angle, Distance, PolyLine, Pt2D};
|
use geom::{Angle, Distance, PolyLine, Pt2D};
|
||||||
|
use raw_map::{osm, OriginalRoad};
|
||||||
|
|
||||||
use crate::{DirectedRoadID, Direction, IntersectionID, Map, TurnID, TurnType};
|
use crate::{DirectedRoadID, Direction, IntersectionID, Map, TurnID, TurnType};
|
||||||
|
|
||||||
@ -210,3 +211,50 @@ fn movement_geom(
|
|||||||
}
|
}
|
||||||
PolyLine::deduping_new(pts)
|
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 geom::{Distance, Duration, Speed};
|
||||||
|
|
||||||
use crate::make::traffic_signals::get_possible_policies;
|
use crate::make::traffic_signals::get_possible_policies;
|
||||||
use crate::raw::OriginalRoad;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
osm, DirectedRoadID, Direction, Intersection, IntersectionID, Map, Movement, MovementID,
|
Intersection, IntersectionID, Map, Movement, MovementID, RoadID, TurnID, TurnPriority,
|
||||||
RoadID, TurnID, TurnPriority,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The pace to use for crosswalk pace in m/s
|
// The pace to use for crosswalk pace in m/s
|
||||||
@ -391,12 +389,12 @@ impl ControlTrafficSignal {
|
|||||||
protected_turns: s
|
protected_turns: s
|
||||||
.protected_movements
|
.protected_movements
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| export_movement(t, map))
|
.map(|mvmnt| mvmnt.to_permanent(map))
|
||||||
.collect(),
|
.collect(),
|
||||||
permitted_turns: s
|
permitted_turns: s
|
||||||
.yield_movements
|
.yield_movements
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| export_movement(t, map))
|
.map(|mvmnt| mvmnt.to_permanent(map))
|
||||||
.collect(),
|
.collect(),
|
||||||
stage_type: match s.stage_type {
|
stage_type: match s.stage_type {
|
||||||
StageType::Fixed(d) => {
|
StageType::Fixed(d) => {
|
||||||
@ -429,7 +427,7 @@ impl ControlTrafficSignal {
|
|||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let mut protected_movements = BTreeSet::new();
|
let mut protected_movements = BTreeSet::new();
|
||||||
for t in s.protected_turns {
|
for t in s.protected_turns {
|
||||||
match import_movement(t, map) {
|
match MovementID::from_permanent(t, map) {
|
||||||
Ok(mvmnt) => {
|
Ok(mvmnt) => {
|
||||||
protected_movements.insert(mvmnt);
|
protected_movements.insert(mvmnt);
|
||||||
}
|
}
|
||||||
@ -440,7 +438,7 @@ impl ControlTrafficSignal {
|
|||||||
}
|
}
|
||||||
let mut permitted_movements = BTreeSet::new();
|
let mut permitted_movements = BTreeSet::new();
|
||||||
for t in s.permitted_turns {
|
for t in s.permitted_turns {
|
||||||
match import_movement(t, map) {
|
match MovementID::from_permanent(t, map) {
|
||||||
Ok(mvmnt) => {
|
Ok(mvmnt) => {
|
||||||
permitted_movements.insert(mvmnt);
|
permitted_movements.insert(mvmnt);
|
||||||
}
|
}
|
||||||
@ -479,48 +477,3 @@ impl ControlTrafficSignal {
|
|||||||
Ok(ts)
|
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 serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use geom::{Angle, PolyLine};
|
use geom::{Angle, Line, PolyLine};
|
||||||
|
|
||||||
use crate::raw::RestrictionType;
|
use crate::raw::RestrictionType;
|
||||||
use crate::{
|
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 {
|
impl TurnID {
|
||||||
|
Loading…
Reference in New Issue
Block a user