starting a new crate with a JSON schema for traffic signals. exporting

to it.
This commit is contained in:
Dustin Carlino 2020-03-04 11:59:41 -08:00
parent 0f5ab2f410
commit d44303de5c
8 changed files with 577 additions and 16 deletions

View File

@ -15,6 +15,7 @@ members = [
"precompute",
"sim",
"tests",
"traffic_signals",
]
# See https://doc.rust-lang.org/cargo/reference/profiles.html#overrides. This

View File

@ -48,7 +48,7 @@ impl TrafficSignalEditor {
i: id,
current_phase: 0,
composite: make_diagram(id, 0, app, ctx),
top_panel: make_top_panel(false, false, ctx),
top_panel: make_top_panel(ctx, app, false, false),
groups: DrawTurnGroup::for_i(id, &app.primary.map),
group_selected: None,
suspended_sim,
@ -158,7 +158,7 @@ impl State for TrafficSignalEditor {
phase.edit_group(&orig_signal.turn_groups[&id], pri);
self.command_stack.push(orig_signal.clone());
self.redo_stack.clear();
self.top_panel = make_top_panel(true, false, ctx);
self.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
self.change_phase(self.current_phase, app, ctx);
return Transition::Keep;
@ -176,6 +176,9 @@ impl State for TrafficSignalEditor {
ctx,
);
}
"Export" => {
orig_signal.export(&app.primary.map);
}
"Preview" => {
// Might have to do this first!
app.primary
@ -192,14 +195,14 @@ impl State for TrafficSignalEditor {
"undo" => {
self.redo_stack.push(orig_signal.clone());
change_traffic_signal(self.command_stack.pop().unwrap(), app, ctx);
self.top_panel = make_top_panel(!self.command_stack.is_empty(), true, ctx);
self.top_panel = make_top_panel(ctx, app, !self.command_stack.is_empty(), true);
self.change_phase(0, app, ctx);
return Transition::Keep;
}
"redo" => {
self.command_stack.push(orig_signal.clone());
change_traffic_signal(self.redo_stack.pop().unwrap(), app, ctx);
self.top_panel = make_top_panel(true, !self.redo_stack.is_empty(), ctx);
self.top_panel = make_top_panel(ctx, app, true, !self.redo_stack.is_empty());
self.change_phase(0, app, ctx);
return Transition::Keep;
}
@ -289,8 +292,9 @@ impl State for TrafficSignalEditor {
}
}
fn make_top_panel(can_undo: bool, can_redo: bool, ctx: &mut EventCtx) -> Composite {
let row = vec![
fn make_top_panel(ctx: &mut EventCtx, app: &App, can_undo: bool, can_redo: bool) -> Composite {
let mut row = vec![
WrappedComposite::text_button(ctx, "Finish", hotkey(Key::Escape)),
WrappedComposite::text_button(ctx, "Finish", hotkey(Key::Escape)),
WrappedComposite::text_button(ctx, "Preview", lctrl(Key::P)),
(if can_undo {
@ -325,6 +329,9 @@ fn make_top_panel(can_undo: bool, can_redo: bool, ctx: &mut EventCtx) -> Composi
})
.margin(15),
];
if app.opts.dev {
row.push(WrappedComposite::text_button(ctx, "Export", None));
}
Composite::new(ManagedWidget::row(row).bg(colors::PANEL_BG))
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
.build(ctx)
@ -521,7 +528,7 @@ fn edit_entire_signal(app: &App, i: IntersectionID, suspended_sim: Sim) -> Box<d
.command_stack
.push(app.primary.map.get_traffic_signal(editor.i).clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(0, app, ctx);
})))
@ -534,7 +541,7 @@ fn edit_entire_signal(app: &App, i: IntersectionID, suspended_sim: Sim) -> Box<d
if new_signal.convert_to_ped_scramble() {
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(0, app, ctx);
}
@ -566,7 +573,7 @@ fn edit_entire_signal(app: &App, i: IntersectionID, suspended_sim: Sim) -> Box<d
let mut signal = app.primary.map.get_traffic_signal(editor.i).clone();
editor.command_stack.push(signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
signal.offset = Duration::seconds(new_duration as f64);
change_traffic_signal(signal, app, ctx);
editor.change_phase(editor.current_phase, app, ctx);
@ -582,7 +589,7 @@ fn edit_entire_signal(app: &App, i: IntersectionID, suspended_sim: Sim) -> Box<d
.1;
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
// Don't use change_phase; it tries to preserve scroll
editor.current_phase = 0;
@ -639,7 +646,7 @@ fn edit_phase(app: &App, i: IntersectionID, idx: usize) -> Box<dyn State> {
new_signal.phases[idx].duration = Duration::seconds(new_duration as f64);
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(idx, app, ctx);
})))
@ -653,7 +660,7 @@ fn edit_phase(app: &App, i: IntersectionID, idx: usize) -> Box<dyn State> {
new_signal.phases.insert(idx, Phase::new());
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(idx, app, ctx);
})))
@ -667,7 +674,7 @@ fn edit_phase(app: &App, i: IntersectionID, idx: usize) -> Box<dyn State> {
new_signal.phases.insert(idx + 1, Phase::new());
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(idx + 1, app, ctx);
})))
@ -680,7 +687,7 @@ fn edit_phase(app: &App, i: IntersectionID, idx: usize) -> Box<dyn State> {
new_signal.phases.swap(idx, idx - 1);
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(idx - 1, app, ctx);
}))),
@ -693,7 +700,7 @@ fn edit_phase(app: &App, i: IntersectionID, idx: usize) -> Box<dyn State> {
new_signal.phases.swap(idx, idx + 1);
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
editor.change_phase(idx + 1, app, ctx);
})))
@ -708,7 +715,7 @@ fn edit_phase(app: &App, i: IntersectionID, idx: usize) -> Box<dyn State> {
let num_phases = new_signal.phases.len();
editor.command_stack.push(orig_signal.clone());
editor.redo_stack.clear();
editor.top_panel = make_top_panel(true, false, ctx);
editor.top_panel = make_top_panel(ctx, app, true, false);
change_traffic_signal(new_signal, app, ctx);
// Don't use change_phase; it tries to preserve scroll
editor.current_phase = if idx == num_phases { idx - 1 } else { idx };

View File

@ -15,3 +15,4 @@ petgraph = "0.4.13"
serde = "1.0.89"
serde_derive = "1.0.98"
thread_local = "0.3.6"
traffic_signals = { path = "../traffic_signals" }

View File

@ -616,3 +616,56 @@ fn make_phases(
phases
}
impl ControlTrafficSignal {
pub fn export(&self, map: &Map) {
let ts = traffic_signals::TrafficSignal {
intersection_osm_node_id: map.get_i(self.id).orig_id.osm_node_id,
phases: self
.phases
.iter()
.map(|p| traffic_signals::Phase {
protected_turns: p
.protected_groups
.iter()
.map(|t| export_turn_group(t, map))
.collect(),
permitted_turns: p
.yield_groups
.iter()
.map(|t| export_turn_group(t, map))
.collect(),
duration_seconds: p.duration.inner_seconds() as usize,
})
.collect(),
offset_seconds: self.offset.inner_seconds() as usize,
};
abstutil::write_json(
format!("traffic_signal_data/{}.json", ts.intersection_osm_node_id),
&ts,
);
}
}
fn export_turn_group(id: &TurnGroupID, map: &Map) -> traffic_signals::Turn {
let from = map.get_r(id.from.id).orig_id;
let to = map.get_r(id.to.id).orig_id;
traffic_signals::Turn {
from: traffic_signals::DirectedRoad {
osm_way_id: from.osm_way_id,
osm_node1: from.i1.osm_node_id,
osm_node2: from.i2.osm_node_id,
is_forwards: id.from.forwards,
},
to: traffic_signals::DirectedRoad {
osm_way_id: to.osm_way_id,
osm_node1: to.i1.osm_node_id,
osm_node2: to.i2.osm_node_id,
is_forwards: id.to.forwards,
},
intersection_osm_node_id: map.get_i(id.parent).orig_id.osm_node_id,
is_crosswalk: id.crosswalk,
}
}

View File

@ -0,0 +1,11 @@
[package]
name = "traffic_signals"
version = "0.1.0"
authors = ["Dustin Carlino <dabreegster@gmail.com>"]
edition = "2018"
[dependencies]
include_dir = "0.5.0"
serde = "1.0.89"
serde_derive = "1.0.98"
serde_json = "1.0.40"

11
traffic_signals/README.md Normal file
View File

@ -0,0 +1,11 @@
# Traffic signal mapping
## Using the data
You can conveniently import in Rust by depending on the crate.
Or...
## License
The minimal code in this repository are Apache licensed. The data collected are [ODbL](https://www.openstreetmap.org/copyright), the same as OpenStreetMap.

View File

@ -0,0 +1,404 @@
{
"intersection_osm_node_id": 53219808,
"phases": [
{
"protected_turns": [
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": false
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": false
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": true
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": false
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
}
],
"permitted_turns": [
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": false
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": false
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": false
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
}
],
"duration_seconds": 50
},
{
"protected_turns": [
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": false
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": false
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": true
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": false
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": true
}
],
"permitted_turns": [
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": false
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": false
},
"to": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53219808,
"osm_node2": 53084814,
"is_forwards": false
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53219808,
"osm_node2": 1709143541,
"is_forwards": true
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
},
{
"from": {
"osm_way_id": 149669465,
"osm_node1": 53231631,
"osm_node2": 53219808,
"is_forwards": true
},
"to": {
"osm_way_id": 6470476,
"osm_node1": 53128048,
"osm_node2": 53219808,
"is_forwards": false
},
"intersection_osm_node_id": 53219808,
"is_crosswalk": false
}
],
"duration_seconds": 15
}
],
"offset_seconds": 0
}

View File

@ -0,0 +1,73 @@
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Serialize, Deserialize)]
pub struct TrafficSignal {
/// The ID of the OSM node representing the intersection with the traffic signal. This node
/// should be tagged `highway = traffic_signals` in OSM.
pub intersection_osm_node_id: i64,
/// The traffic signal repeatedly cycles through these phases. During each phase, only some
/// turns are protected and permitted through the intersection.
pub phases: Vec<Phase>,
// TODO What should this be relative to?
pub offset_seconds: usize,
}
#[derive(Serialize, Deserialize)]
pub struct Phase {
/// During this phase, these turns can be performed with the highest priority, protected by a
/// green light. No two protected turns in the same phase should cross; that would be a
/// conflict.
pub protected_turns: BTreeSet<Turn>,
/// During this phase, these turns can be performed after yielding. For example, an unprotected
/// left turn after yielding to oncoming traffic, or a right turn on red after yielding to
/// oncoming traffic and crosswalks.
pub permitted_turns: BTreeSet<Turn>,
/// The phase lasts this long before moving to the next one.
pub duration_seconds: usize,
}
/// A movement through an intersection.
///
/// TODO Diagram of the 4 crosswalk cases.
#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Turn {
/// The movement begins at the end of this road segment.
pub from: DirectedRoad,
/// The movement ends at the beginning of this road segment.
pub to: DirectedRoad,
/// The ID of the OSM node representing the intersection. This is redundant for turns performed
/// by vehicles, but is necessary for disambiguating the 4 cases of crosswalks.
pub intersection_osm_node_id: i64,
/// True iff the movement is along a crosswalk. Note that moving over a crosswalk has a
/// different `Turn` for each direction.
pub is_crosswalk: bool,
}
/// A road segment connecting two intersections, and a direction along the segment.
#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct DirectedRoad {
/// The ID of the OSM way representing the road.
pub osm_way_id: i64,
/// The ID of the OSM node at the start of this road segment.
pub osm_node1: i64,
/// The ID of the OSM node at the end of this road segment.
pub osm_node2: i64,
/// The direction along the road segment. See
/// https://wiki.openstreetmap.org/wiki/Forward_%26_backward,_left_%26_right for details.
pub is_forwards: bool,
}
static DATA: include_dir::Dir = include_dir::include_dir!("data");
/// Returns all traffic signal data compiled into this build, keyed by OSM node ID.
pub fn load_all_data() -> Result<BTreeMap<i64, TrafficSignal>, std::io::Error> {
let mut results = BTreeMap::new();
if let Some(dir) = DATA.get_dir("data") {
for f in dir.files() {
let ts: TrafficSignal = serde_json::from_slice(&f.contents())?;
results.insert(ts.intersection_osm_node_id, ts);
}
}
Ok(results)
}