Skip to content

Commit

Permalink
Work on cycleway snapping for St George:
Browse files Browse the repository at this point in the history
- relax is_cycleway definition to allow sidewalks
- dropdown shortcut for debug steps
- the cycleway itself may be short, ignore it
- the short road between the main road and intersection may have been merged
- Remove connector segments immediately
- Remember to regenerate intersection geometry
  • Loading branch information
dabreegster committed Mar 10, 2023
1 parent 0a98533 commit 48e3c2f
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 15 deletions.
4 changes: 2 additions & 2 deletions osm2streets-js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ impl JsStreetNetwork {
}
if input.cycletrack_snapping_experiment {
transformations.push(Transformation::SnapCycleways);
transformations.push(Transformation::TrimDeadendCycleways);
transformations.push(Transformation::CollapseDegenerateIntersections);
//transformations.push(Transformation::TrimDeadendCycleways);
//transformations.push(Transformation::CollapseDegenerateIntersections);
}
if input.debug_each_step {
street_network.apply_transformations_stepwise_debugging(transformations, &mut timer);
Expand Down
12 changes: 8 additions & 4 deletions osm2streets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,19 @@ impl StreetNetwork {

pub(crate) fn debug_intersection<I: Into<String>>(&mut self, i: IntersectionID, label: I) {
if let Some(step) = self.debug_steps.last_mut() {
step.points
.push((self.intersections[&i].polygon.center(), label.into()));
if let Some(intersection) = self.intersections.get(&i) {
step.points
.push((intersection.polygon.center(), label.into()));
}
}
}

pub(crate) fn debug_road<I: Into<String>>(&mut self, r: RoadID, label: I) {
if let Some(step) = self.debug_steps.last_mut() {
step.polylines
.push((self.roads[&r].center_line.clone(), label.into()));
if let Some(road) = self.roads.get(&r) {
step.polylines
.push((road.center_line.clone(), label.into()));
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion osm2streets/src/road.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ impl Road {
for spec in &self.lane_specs_ltr {
if spec.lt == LaneType::Biking {
bike = true;
} else if spec.lt != LaneType::Shoulder {
} else if !spec.lt.is_walkable() {
return false;
}
}
Expand Down
65 changes: 57 additions & 8 deletions osm2streets/src/transform/separate_cycletracks.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::BTreeSet;

use geom::{Distance, PolyLine};

use crate::{BufferType, Direction, IntersectionID, LaneSpec, LaneType, RoadID, StreetNetwork};
Expand Down Expand Up @@ -34,6 +36,8 @@ struct Cycleway {
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 Cycleway {
Expand All @@ -44,6 +48,12 @@ impl Cycleway {
for x in &self.main_roads {
streets.debug_road(*x, format!("main road along {}", self.debug_idx));
}
if let Some(r) = self.connector_src_i {
streets.debug_road(r, format!("src_i connector of {}", self.debug_idx));
}
if let Some(r) = self.connector_dst_i {
streets.debug_road(r, format!("dst_i connector of {}", self.debug_idx));
}
}
}

Expand All @@ -56,22 +66,33 @@ fn find_cycleways(streets: &StreetNetwork) -> Vec<Cycleway> {
// 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 cycleway_road.endpoints() {
let mut candidates = Vec::new();
let mut connector_candidates = Vec::new();
for road in streets.roads_per_intersection(i) {
if road.untrimmed_length() < SHORT_ROAD_THRESHOLD {
candidates.push(road.id);
if road.id != cycleway_road.id
&& road.center_line.length() < SHORT_ROAD_THRESHOLD
{
connector_candidates.push(road.id);
}
}
if candidates.len() == 1 {
main_road_endpoints.push(streets.roads[&candidates[0]].other_side(i));
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 {
// Often the main road parallel to this cycleway segment is just one road, but it
// might be more.
let main_road_src_i = main_road_endpoints[0];
let main_road_dst_i = main_road_endpoints[1];
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 {
continue;
}
// Find all main road segments "parallel to" this cycleway, 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.
Expand All @@ -88,6 +109,8 @@ fn find_cycleways(streets: &StreetNetwork) -> Vec<Cycleway> {
main_road_src_i,
main_road_dst_i,
main_roads: path.into_iter().map(|(r, _)| r).collect(),
connector_src_i,
connector_dst_i,
});
}
}
Expand All @@ -98,7 +121,12 @@ fn find_cycleways(streets: &StreetNetwork) -> Vec<Cycleway> {

fn snap(streets: &mut StreetNetwork, input: Cycleway) {
// This analysis shouldn't modify other cycleways when it works on one
assert!(streets.roads.contains_key(&input.cycleway));
//assert!(streets.roads.contains_key(&input.cycleway));
// TODO Not true anymore; sometimes the short connector segment is a cycleway and matches stuff
// here incorrectly, but then a previous pass deletes it
if !streets.roads.contains_key(&input.cycleway) {
return;
}

// Remove the cycleway, but remember the lanes it contained
let mut cycleway_lanes = streets.remove_road(input.cycleway).lane_specs_ltr;
Expand Down Expand Up @@ -132,6 +160,7 @@ fn snap(streets: &mut StreetNetwork, input: Cycleway) {
// - 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 input.main_roads {
let main_road = &streets.roads[&r];
// Which side is closer to the cycleway?
Expand Down Expand Up @@ -201,11 +230,31 @@ fn snap(streets: &mut StreetNetwork, input: Cycleway) {

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 cycleway, and TrimDeadendCycleways
// to clean up any small cycle connection roads.
//
// ALTERNATIVELY, remove the connector segments immediately.
if let Some(r) = input.connector_src_i {
if streets.roads.contains_key(&r) {
// Ignore errors
let _ = streets.collapse_short_road(r);
}
}
if let Some(r) = input.connector_dst_i {
if streets.roads.contains_key(&r) {
let _ = streets.collapse_short_road(r);
}
}
}

// Insert all of `insert` at `idx` in `target`
Expand Down

0 comments on commit 48e3c2f

Please sign in to comment.