Skip to content

Commit

Permalink
Rename stuff and express as an operation
Browse files Browse the repository at this point in the history
  • Loading branch information
dabreegster committed Mar 10, 2023
1 parent 48e3c2f commit fdec96d
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 281 deletions.
6 changes: 3 additions & 3 deletions osm2streets-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use osm2streets::{
pub struct ImportOptions {
debug_each_step: bool,
dual_carriageway_experiment: bool,
cycletrack_snapping_experiment: bool,
sidepath_zipping_experiment: bool,
inferred_sidewalks: bool,
osm2lanes: bool,
}
Expand Down Expand Up @@ -67,8 +67,8 @@ impl JsStreetNetwork {
transformations.retain(|t| !matches!(t, Transformation::CollapseShortRoads));
transformations.push(Transformation::MergeDualCarriageways);
}
if input.cycletrack_snapping_experiment {
transformations.push(Transformation::SnapCycleways);
if input.sidepath_zipping_experiment {
transformations.push(Transformation::ZipSidepaths);
//transformations.push(Transformation::TrimDeadendCycleways);
//transformations.push(Transformation::CollapseDegenerateIntersections);
}
Expand Down
1 change: 1 addition & 0 deletions osm2streets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub use self::lanes::{
get_lane_specs_ltr, BufferType, Direction, LaneSpec, LaneType, Placement,
NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS,
};
pub use self::operations::zip_sidepath::Sidepath;
pub use self::road::{Road, StopLine, TrafficInterruption};
pub use self::transform::Transformation;
pub use self::types::{DrivingSide, MapConfig, NamePerLanguage};
Expand Down
1 change: 1 addition & 0 deletions osm2streets/src/operations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
mod collapse_intersection;
mod collapse_short_road;
mod update_geometry;
pub mod zip_sidepath;
248 changes: 248 additions & 0 deletions osm2streets/src/operations/zip_sidepath.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
use std::collections::BTreeSet;

use geom::{Distance, PolyLine};

use crate::{BufferType, Direction, IntersectionID, LaneSpec, LaneType, RoadID, StreetNetwork};

// We're only pattern matching on one type of parallel sidepath right now. This represents a single
// Road that's parallel to one or more main_roads.
//
// X--X
// S M
// S M
// S M
// S X
// S M
// S M
// X--X
//
// S is the sidepath segment. X are intersections. M are main roads -- note there are two matching
// up to this one sidepath. The '-'s are short connector roads between the two.
pub struct Sidepath {
sidepath: RoadID,
sidepath_center: PolyLine,
main_road_src_i: IntersectionID,
main_road_dst_i: IntersectionID,
main_roads: Vec<RoadID>,
connector_src_i: Option<RoadID>,
connector_dst_i: Option<RoadID>,
}

impl Sidepath {
pub fn new(streets: &StreetNetwork, start: RoadID) -> Option<Self> {
const SHORT_ROAD_THRESHOLD: Distance = Distance::const_meters(10.0);

let sidepath_road = &streets.roads[&start];

// Look at other roads connected to both endpoints. One of them should be "very short."
let mut main_road_endpoints = Vec::new();
for i in sidepath_road.endpoints() {
let mut connector_candidates = Vec::new();
for road in streets.roads_per_intersection(i) {
if road.id != sidepath_road.id && road.center_line.length() < SHORT_ROAD_THRESHOLD {
connector_candidates.push(road.id);
}
}
if connector_candidates.len() == 1 {
let connector = connector_candidates[0];
main_road_endpoints
.push((streets.roads[&connector].other_side(i), Some(connector)));
} else if connector_candidates.is_empty() {
// Maybe this intersection has been merged already. Use it directly.
main_road_endpoints.push((i, None));
}
}

if main_road_endpoints.len() != 2 {
return None;
}

// Often the main road parallel to this sidepath segment is just one road, but it might
// be more.
let (main_road_src_i, connector_src_i) = main_road_endpoints[0];
let (main_road_dst_i, connector_dst_i) = main_road_endpoints[1];
// It may be none at all, when the main road intersection gets merged
if main_road_src_i == main_road_dst_i {
return None;
}

// Find all main road segments "parallel to" this sidepath, by pathfinding between the
// main road intersections. We don't care about the order, but simple_path does. In
// case it's one-way for driving, try both.
if let Some(path) = streets
.simple_path(main_road_src_i, main_road_dst_i, &[LaneType::Driving])
.or_else(|| streets.simple_path(main_road_dst_i, main_road_src_i, &[LaneType::Driving]))
{
return Some(Self {
sidepath: sidepath_road.id,
sidepath_center: sidepath_road.center_line.clone(),
main_road_src_i,
main_road_dst_i,
main_roads: path.into_iter().map(|(r, _)| r).collect(),
connector_src_i,
connector_dst_i,
});
}

None
}

pub fn debug(&self, streets: &mut StreetNetwork, label: String) {
streets.debug_road(self.sidepath, format!("sidepath {label}"));
streets.debug_intersection(self.main_road_src_i, format!("src_i of {label}"));
streets.debug_intersection(self.main_road_dst_i, format!("dst_i of {label}"));
for x in &self.main_roads {
streets.debug_road(*x, format!("main road along {label}"));
}
if let Some(r) = self.connector_src_i {
streets.debug_road(r, format!("src_i connector of {label}"));
}
if let Some(r) = self.connector_dst_i {
streets.debug_road(r, format!("dst_i connector of {label}"));
}
}

pub fn zip(self, streets: &mut StreetNetwork) {
// The caller may find a bunch of these and zip, which sometimes could delete one of the
// located pieces
if !streets.roads.contains_key(&self.sidepath) {
return;
}

// Remove the sidepath, but remember the lanes it contained
let mut sidepath_lanes = streets.remove_road(self.sidepath).lane_specs_ltr;

// TODO Preserve osm_ids

// TODO Re-evaluate this!
// The sidepath likely had shoulder lanes assigned to it by get_lane_specs_ltr, because we have
// many partially competing strategies for representing shared walking/cycling roads. Remove
// those.
if sidepath_lanes[0].lt == LaneType::Shoulder {
sidepath_lanes.remove(0);
}
if sidepath_lanes.last().as_ref().unwrap().lt == LaneType::Shoulder {
sidepath_lanes.pop();
}

// The sidepath was tagged as a separate way due to some kind of physical separation. We'll
// represent that with a buffer lane.
let buffer = LaneSpec {
// TODO Use https://wiki.openstreetmap.org/wiki/Proposed_features/cycleway:separation if
// available
lt: LaneType::Buffer(BufferType::Planters),
dir: Direction::Fwd,
width: LaneSpec::typical_lane_width(LaneType::Buffer(BufferType::Planters)),
allowed_turns: Default::default(),
};

// For every main road segment corresponding to the sidepath, we need to insert these
// sidepath_lanes somewhere.
//
// - Fixing the direction of the lanes
// - Appending them on the left or right side (and "inside" the inferred sidewalk on the road)
// - Inserting the buffer
let mut intersections = BTreeSet::new();
for r in self.main_roads {
let main_road = &streets.roads[&r];
// Which side is closer to the sidepath?
let (left, right) = main_road
.get_untrimmed_sides(streets.config.driving_side)
.unwrap();
// TODO georust has a way to check distance of linestrings. But for now, just check the
// middles
let snap_to_left = self.sidepath_center.middle().dist_to(left.middle())
< self.sidepath_center.middle().dist_to(right.middle());

// Does the sidepath point the same direction as this main road? We can use the left or
// right side, doesn't matter.
// TODO Check this logic very carefully; angles always lead to bugs. 90 is a very generous
// definition of parallel. But we have a binary decision to make, so maybe we should even
// use 180.
let oriented_same_way = self
.sidepath_center
.overall_angle()
.approx_eq(left.overall_angle(), 90.0);

// Where should we insert the sidepath lanes? If the main road already has a sidewalk,
// let's assume it should stay at the outermost part of the road. (That isn't always true,
// but it's an assumption we'll take for now.)
let insert_idx = if snap_to_left {
if main_road.lane_specs_ltr[0].lt.is_walkable() {
1
} else {
0
}
} else {
if main_road
.lane_specs_ltr
.last()
.as_ref()
.unwrap()
.lt
.is_walkable()
{
main_road.lane_specs_ltr.len() - 1
} else {
main_road.lane_specs_ltr.len()
}
};

streets.debug_road(r, format!("snap_to_left = {snap_to_left}, oriented_same_way = {oriented_same_way}, insert_idx = {insert_idx}"));

// This logic thankfully doesn't depend on driving side at all!
let mut insert_lanes = Vec::new();
for mut lane in sidepath_lanes.clone() {
if !oriented_same_way {
lane.dir = lane.dir.opposite();
}
insert_lanes.push(lane);
}
// TODO Do we ever need to reverse the order of the lanes?
let mut buffer_lane = buffer.clone();
if snap_to_left {
// TODO I'm not sure what direction the buffer lane should face. This is a very strong
// argument for Direction::Both.
buffer_lane.dir = insert_lanes.last().as_ref().unwrap().dir;
insert_lanes.push(buffer_lane);
} else {
buffer_lane.dir = insert_lanes[0].dir;
insert_lanes.insert(0, buffer_lane);
}

let main_road = streets.roads.get_mut(&r).unwrap();
splice_in(&mut main_road.lane_specs_ltr, insert_idx, insert_lanes);

intersections.extend(main_road.endpoints());
}

// Recalculate geometry along all of the main roads we just thickened
for i in intersections {
streets.update_i(i);
}

// After this transformation, we should run CollapseDegenerateIntersections to handle the
// intersection where the side road originally crossed the sidepath, and TrimDeadendCycleways
// to clean up any small cycle connection roads.
//
// ALTERNATIVELY, remove the connector segments immediately.
if let Some(r) = self.connector_src_i {
if streets.roads.contains_key(&r) {
// Ignore errors
let _ = streets.collapse_short_road(r);
}
}
if let Some(r) = self.connector_dst_i {
if streets.roads.contains_key(&r) {
let _ = streets.collapse_short_road(r);
}
}
}
}

// Insert all of `insert` at `idx` in `target`
fn splice_in<T>(target: &mut Vec<T>, idx: usize, insert: Vec<T>) {
let tail = target.split_off(idx);
target.extend(insert);
target.extend(tail);
}
16 changes: 8 additions & 8 deletions osm2streets/src/transform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use crate::StreetNetwork;
mod collapse_intersections;
mod collapse_short_road;
mod dual_carriageways;
mod parallel_sidepaths;
mod remove_disconnected;
mod sausage_links;
mod separate_cycletracks;

/// An in-place transformation of a `StreetNetwork`.
pub enum Transformation {
TrimDeadendCycleways,
SnapCycleways,
ZipSidepaths,
RemoveDisconnectedRoads,
CollapseShortRoads,
CollapseDegenerateIntersections,
Expand Down Expand Up @@ -43,9 +43,9 @@ impl Transformation {
// Not working yet
if false {
let mut prepend = vec![
Transformation::SnapCycleways,
// More dead-ends can be created after snapping cycleways. But also, snapping can be
// easier to do after trimming some dead-ends. So... just run it twice.
Transformation::ZipSidepaths,
// More dead-ends can be created after zipping. But also, zipping can be easier to
// do after trimming some dead-ends. So... just run it twice.
Transformation::TrimDeadendCycleways,
Transformation::RemoveDisconnectedRoads,
];
Expand All @@ -61,7 +61,7 @@ impl Transformation {
fn name(&self) -> &'static str {
match self {
Transformation::TrimDeadendCycleways => "trim dead-end cycleways",
Transformation::SnapCycleways => "snap separate cycleways",
Transformation::ZipSidepaths => "zip parallel sidepaths",
Transformation::RemoveDisconnectedRoads => "remove disconnected roads",
Transformation::CollapseShortRoads => "collapse short roads",
Transformation::CollapseDegenerateIntersections => "collapse degenerate intersections",
Expand All @@ -76,8 +76,8 @@ impl Transformation {
Transformation::TrimDeadendCycleways => {
collapse_intersections::trim_deadends(streets);
}
Transformation::SnapCycleways => {
separate_cycletracks::snap_cycleways(streets);
Transformation::ZipSidepaths => {
parallel_sidepaths::zip_sidepaths(streets);
}
Transformation::RemoveDisconnectedRoads => {
remove_disconnected::remove_disconnected_roads(streets);
Expand Down
19 changes: 19 additions & 0 deletions osm2streets/src/transform/parallel_sidepaths.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{Sidepath, StreetNetwork};

/// Find sidepath segments that exist as separate objects, parallel to a main road. Zip (or "snap")
/// them into the main road, inserting a buffer lane to represent the physical division.
pub fn zip_sidepaths(streets: &mut StreetNetwork) {
let mut sidepaths = Vec::new();
for r in streets.roads.values() {
// TODO Or footpath
if r.is_cycleway() {
sidepaths.extend(Sidepath::new(streets, r.id));
}
}

for (idx, sidepath) in sidepaths.into_iter().enumerate() {
streets.maybe_start_debug_step(format!("snap sidepath {idx}"));
sidepath.debug(streets, idx.to_string());
sidepath.zip(streets);
}
}
Loading

0 comments on commit fdec96d

Please sign in to comment.