Skip to content

Commit

Permalink
Merge pull request #123 from a-b-street/intersectiontype
Browse files Browse the repository at this point in the history
Reshape IntersectionComplexity into IntersectionType
  • Loading branch information
BudgieInWA authored Nov 23, 2022
2 parents 38aa927 + 3f1c72b commit 3d64f99
Show file tree
Hide file tree
Showing 38 changed files with 1,929 additions and 1,965 deletions.
6 changes: 3 additions & 3 deletions docs/how_it_works.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ As of November 2022, and probably incomplete. This describes how the codebase cu

## The model

At its heart, a graph of roads and intersections. (Roads lead between exactly two intersections -- "road segments" might be more precise.) Roads have their lanes listed from left-to-right, with a type, width, and direction. Note osm2streets doesn't model bidirectional lanes yet -- sidewalks and shared center turn lanes are either forwards or backwards right now, and something downstream interprets them in a special way. (osm2lanes has more nuance, but isn't used in osm2streets yet.)
At its heart, a graph of roads and intersections. A `Road` is a segment of road that leads between exactly two `Intersection`s. An `Intersection`'s kind tells you if it represents a real-life intersection or some other kind of node in the graph. A `MapEdge` connects a single `Road` the edge of the map, a `Terminus` marks an actual dead end. A `Connection` joins multiple `Road`s together where there is no traffic interaction at all, whereas a `Fork` joins multiple roads that merge or diverge without any stop line. Finally, an `IntersectionKind::Intersection` represents everything that you would actually call an "intersection", where traffic merges with, diverges from or crosses other traffic.

Intersections have a `ControlType` -- stop signs, traffic signals, uncontrolled, etc. This is orthogonal to `IntersectionComplexity` and `ConflictType`... TODO, narrow down valid combinations and give examples. MultiConnection vs Merge, please!
Roads have their lanes listed from left-to-right, each with a type, width, and direction. A lane represents any longitudinal feature of a road: travel lanes on the carriageway, separated bike and footpaths, street-side parking, and buffers, medians and verges. Note osm2streets doesn't model bidirectional lanes yet -- sidewalks and shared center turn lanes are either forwards or backwards right now, and something downstream interprets them in a special way. (osm2lanes has more nuance, but isn't used in osm2streets yet.)

### IDs

Expand Down Expand Up @@ -106,7 +106,7 @@ This calls the collapse operation on anything marked by `FindShortRoads`. Comple

### CollapseDegenerateIntersections

A "degenerate" intersection (`IntersectionComplexity::Connection`) has only two roads connected. Sometimes that intersection can be collapsed and the two roads joined. Currently this happens:
A "degenerate" intersection has only two roads connected. Sometimes that intersection can be collapsed and the two roads joined. Currently this happens:

- between two cycleways
- when the lanes match and only "unimportant" OSM tags differ
Expand Down
10 changes: 6 additions & 4 deletions experimental/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,16 @@ impl From<&osm2streets::Intersection> for Intersection {
Self {
// int.intersection_type has some useful info, bit is often misleading.
t: match int.control {
osm2streets::ControlType::Border => IntersectionType::MapEdge,
osm2streets::ControlType::TrafficSignal
| osm2streets::ControlType::Construction => IntersectionType::RoadIntersection,
osm2streets::IntersectionControl::Border => IntersectionType::MapEdge,
osm2streets::IntersectionControl::TrafficSignal
| osm2streets::IntersectionControl::Construction => {
IntersectionType::RoadIntersection
}
_ => IntersectionType::Unknown,
},
control: match int.control {
// IntersectionType::StopSign => ControlType::Signed, // wrong when it should be uncontrolled
osm2streets::ControlType::TrafficSignal => ControlType::Lights,
osm2streets::IntersectionControl::TrafficSignal => ControlType::Lights,
_ => ControlType::Uncontrolled,
},
}
Expand Down
18 changes: 7 additions & 11 deletions osm2streets/src/intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ use geom::{Distance, Polygon, Pt2D};
use serde::{Deserialize, Serialize};

use crate::{
osm, ConflictType, ControlType, IntersectionComplexity, IntersectionID, Movement, RoadID,
StreetNetwork,
osm, IntersectionControl, IntersectionID, IntersectionKind, Movement, RoadID, StreetNetwork,
};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Expand All @@ -21,9 +20,8 @@ pub struct Intersection {
pub point: Pt2D,
/// This will be a placeholder until `Transformation::GenerateIntersectionGeometry` runs.
pub polygon: Polygon,
pub complexity: IntersectionComplexity,
pub conflict_level: ConflictType,
pub control: ControlType,
pub kind: IntersectionKind,
pub control: IntersectionControl,
pub elevation: Distance,

/// All roads connected to this intersection. They may be incoming or outgoing relative to this
Expand All @@ -49,9 +47,8 @@ impl StreetNetwork {
&mut self,
osm_ids: Vec<osm::NodeID>,
point: Pt2D,
complexity: IntersectionComplexity,
conflict_level: ConflictType,
control: ControlType,
t: IntersectionKind,
control: IntersectionControl,
) -> IntersectionID {
let id = self.next_intersection_id();
self.intersections.insert(
Expand All @@ -61,8 +58,7 @@ impl StreetNetwork {
osm_ids,
point,
polygon: Polygon::dummy(),
complexity,
conflict_level,
kind: t,
control,
// Filled out later
roads: Vec::new(),
Expand All @@ -77,6 +73,6 @@ impl StreetNetwork {

impl Intersection {
pub fn is_border(&self) -> bool {
self.control == ControlType::Border
self.control == IntersectionControl::Border
}
}
12 changes: 5 additions & 7 deletions osm2streets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ pub use self::lanes::{
pub use self::road::Road;
pub use self::transform::Transformation;
pub use self::types::{
ConflictType, ControlType, DrivingSide, IntersectionComplexity, MapConfig, Movement,
NamePerLanguage,
DrivingSide, IntersectionControl, IntersectionKind, MapConfig, Movement, NamePerLanguage,
TrafficConflict,
};

mod edit;
Expand Down Expand Up @@ -288,15 +288,13 @@ impl StreetNetwork {

/// Recalculate movements, complexity, and conflict_level of an intersection.
fn recalculate_movements(&mut self, i: IntersectionID) {
let (complexity, conflict_level, movements) =
crate::transform::classify_intersections::guess_complexity(self, i);
let (t, movements) = crate::transform::classify_intersections::guess_complexity(self, i);
let int = self.intersections.get_mut(&i).unwrap();
int.movements = movements;
int.conflict_level = conflict_level;
// The fact that an intersection represents a road leaving the map bounds is stored in the
// complexity field but guess_complexity ignores that. Make sure we don't overwrite it.
if int.complexity != IntersectionComplexity::MapEdge {
int.complexity = complexity;
if int.kind != IntersectionKind::MapEdge {
int.kind = t;
}
}
}
Expand Down
16 changes: 3 additions & 13 deletions osm2streets/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ use std::path::Path;
use anyhow::Result;
use geom::{ArrowCap, Distance, Line, PolyLine};

use crate::{
DebugStreets, Direction, DrivingSide, IntersectionComplexity, LaneType, StreetNetwork,
};
use crate::{DebugStreets, Direction, DrivingSide, LaneType, StreetNetwork};

impl StreetNetwork {
/// Saves the plain GeoJSON rendering to a file.
Expand Down Expand Up @@ -60,16 +58,8 @@ impl StreetNetwork {
),
),
(
"complexity",
if intersection.complexity == IntersectionComplexity::MultiConnection {
format!(
"{:?} {:?}",
intersection.complexity, intersection.conflict_level
)
} else {
format!("{:?}", intersection.complexity)
}
.into(),
"intersection_kind",
format!("{:?}", intersection.kind).into(),
),
("control", format!("{:?}", intersection.control).into()),
]),
Expand Down
50 changes: 23 additions & 27 deletions osm2streets/src/transform/classify_intersections.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
use std::cmp::{max, min};

use crate::{
ConflictType, Direction, DrivingSide, IntersectionComplexity, IntersectionID, Movement,
RestrictionType, Road, StreetNetwork,
Direction, DrivingSide, IntersectionID, IntersectionKind, Movement, RestrictionType, Road,
StreetNetwork, TrafficConflict,
};
use ConflictType::*;
use IntersectionComplexity::*;
use IntersectionKind::*;
use TrafficConflict::*;

/// Determines the initial complexity of all intersections. Intersections marked "Crossing" are
/// considered "unclassified" and will be updated with a guess, others will be left unchanged.
/// Determines the initial type of all intersections. Intersections not marked "MapEdge" are
/// considered unclassified and will be updated.
pub fn classify_intersections(streets: &mut StreetNetwork) {
let mut changes: Vec<_> = Vec::new();
for i in streets.intersections.values() {
if i.complexity == Crossing {
if i.kind != MapEdge {
changes.push((i.id, guess_complexity(streets, i.id)));
}
}

for (id, (complexity, conflict_level, movements)) in changes {
for (id, (t, movements)) in changes {
let intersection = streets.intersections.get_mut(&id).unwrap();
intersection.complexity = complexity;
intersection.conflict_level = conflict_level;
intersection.kind = t;
intersection.movements = movements;
}
}
Expand All @@ -31,7 +30,7 @@ pub fn classify_intersections(streets: &mut StreetNetwork) {
pub fn guess_complexity(
streets: &StreetNetwork,
intersection_id: IntersectionID,
) -> (IntersectionComplexity, ConflictType, Vec<Movement>) {
) -> (IntersectionKind, Vec<Movement>) {
let roads: Vec<_> = streets
.roads_per_intersection(intersection_id)
.into_iter()
Expand All @@ -40,7 +39,7 @@ pub fn guess_complexity(

// A terminus is characterised by a single connected road.
if roads.len() == 1 {
return (Terminus, Uncontested, Vec::new());
return (Terminus, Vec::new());
}

// Calculate all the possible movements, (except U-turns, for now).
Expand Down Expand Up @@ -91,7 +90,7 @@ pub fn guess_complexity(
);

// Stop looking if we've already found the worst.
if worst_conflict == ConflictType::Cross {
if worst_conflict == TrafficConflict::Cross {
break;
}
}
Expand All @@ -101,18 +100,15 @@ pub fn guess_complexity(
.iter()
.map(|(s, d)| (roads[*s].id, roads[*d].id))
.collect();
match worst_conflict {
Cross => (Crossing, Cross, movements),
c => (
if roads.len() == 2 {
Connection
} else {
MultiConnection
},
c,
movements,
),
}
(
match worst_conflict {
Uncontested => Connection,
Diverge => Fork,
Merge => Fork, // TODO check for give way signs or count lanes to detect Intersections.
Cross => Intersection,
},
movements,
)
}

fn can_drive_out_of(road: &Road, which_end: IntersectionID) -> bool {
Expand Down Expand Up @@ -159,8 +155,8 @@ fn turn_is_allowed(src: &Road, dst: &Road) -> bool {
!has_exclusive_allows
}

fn calc_conflict(a: &(usize, usize), b: &(usize, usize), side: DrivingSide) -> ConflictType {
use ConflictType::*;
fn calc_conflict(a: &(usize, usize), b: &(usize, usize), side: DrivingSide) -> TrafficConflict {
use TrafficConflict::*;

// If the traffic starts and ends at the same place in the same direction...
if a.0 == b.0 && a.1 == b.1 {
Expand Down
4 changes: 2 additions & 2 deletions osm2streets/src/transform/collapse_intersections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anyhow::Result;

use geom::{Distance, PolyLine, Pt2D};

use crate::{osm, ControlType, IntersectionID, Road, RoadID, StreetNetwork};
use crate::{osm, IntersectionControl, IntersectionID, Road, RoadID, StreetNetwork};

/// Collapse degenerate intersections:
/// - between two cycleways
Expand Down Expand Up @@ -222,7 +222,7 @@ pub fn trim_deadends(streets: &mut StreetNetwork) {
let mut remove_intersections = BTreeSet::new();
for i in streets.intersections.values() {
let roads = streets.roads_per_intersection(i.id);
if roads.len() != 1 || i.control == ControlType::Border {
if roads.len() != 1 || i.control == IntersectionControl::Border {
continue;
}
let road = &roads[0];
Expand Down
11 changes: 6 additions & 5 deletions osm2streets/src/transform/collapse_short_road.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, VecDeque};

use anyhow::Result;

use crate::{ControlType, IntersectionID, RestrictionType, RoadID, StreetNetwork};
use crate::{IntersectionControl, IntersectionID, RestrictionType, RoadID, StreetNetwork};

// TODO After collapsing a road, trying to drag the surviving intersection in map_editor crashes. I
// bet the underlying problem there would help debug automated transformations near merged roads
Expand Down Expand Up @@ -36,8 +36,8 @@ impl StreetNetwork {
}

// First a sanity check.
if self.intersections[&keep_i].control == ControlType::Border
|| self.intersections[&destroy_i].control == ControlType::Border
if self.intersections[&keep_i].control == IntersectionControl::Border
|| self.intersections[&destroy_i].control == IntersectionControl::Border
{
bail!("{short_r} touches a border");
}
Expand Down Expand Up @@ -109,8 +109,9 @@ impl StreetNetwork {
let destroy_i = self.intersections.remove(&destroy_i).unwrap();

// If the intersection types differ, upgrade the surviving interesting.
if destroy_i.control == ControlType::TrafficSignal {
self.intersections.get_mut(&keep_i).unwrap().control = ControlType::TrafficSignal;
if destroy_i.control == IntersectionControl::TrafficSignal {
self.intersections.get_mut(&keep_i).unwrap().control =
IntersectionControl::TrafficSignal;
}

// Remember the merge
Expand Down
6 changes: 3 additions & 3 deletions osm2streets/src/transform/find_short_roads.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use geom::Distance;

use crate::{osm, ControlType, Road, RoadID, StreetNetwork};
use crate::{osm, IntersectionControl, Road, RoadID, StreetNetwork};

/// Combines a few different sources/methods to decide which roads are short. Marks them for
/// merging.
Expand Down Expand Up @@ -87,8 +87,8 @@ impl StreetNetwork {
if src_i.is_border() || dst_i.is_border() {
continue;
}
if src_i.control != ControlType::TrafficSignal
&& dst_i.control != ControlType::TrafficSignal
if src_i.control != IntersectionControl::TrafficSignal
&& dst_i.control != IntersectionControl::TrafficSignal
{
continue;
}
Expand Down
6 changes: 3 additions & 3 deletions osm2streets/src/transform/intersection_geometry.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use abstutil::Timer;
use geom::{Circle, Distance};

use crate::{ControlType, StreetNetwork};
use crate::{IntersectionControl, StreetNetwork};

pub fn generate(streets: &mut StreetNetwork, timer: &mut Timer) {
// Initialize trimmed_center_line to the corrected center
Expand Down Expand Up @@ -58,7 +58,7 @@ pub fn generate(streets: &mut StreetNetwork, timer: &mut Timer) {
streets.intersections.get_mut(&i).unwrap().polygon = polygon;
}
for i in make_stop_signs {
streets.intersections.get_mut(&i).unwrap().control = ControlType::StopSign;
streets.intersections.get_mut(&i).unwrap().control = IntersectionControl::StopSign;
}
for i in remove_dangling_nodes {
streets.intersections.remove(&i).unwrap();
Expand All @@ -74,7 +74,7 @@ fn fix_borders(streets: &mut StreetNetwork) {
let min_len = Distance::meters(5.0);
let mut set_polygons = Vec::new();
for i in streets.intersections.values() {
if i.control != ControlType::Border {
if i.control != IntersectionControl::Border {
continue;
}
let r = i.roads.iter().next().unwrap();
Expand Down
Loading

0 comments on commit 3d64f99

Please sign in to comment.