Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 105 additions & 15 deletions backend/src/crossings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ impl Speedwalk {
pub fn connect_all_crossings(&self, include_crossing_no: bool) -> CreateNewGeometry {
info!("Finding crossings to connect");
let mut crossings = Vec::new();
for (id, node) in &self.derived_nodes {
let mut half_crossings = Vec::new();
for (crossing_id, node) in &self.derived_nodes {
if !(node.is_crossing() || (include_crossing_no && node.is_explicit_crossing_no())) {
continue;
}
let ways = node
.way_ids
.iter()
.map(|w| &self.derived_ways[w])
.collect::<Vec<_>>();
// When do we generate a crossing way from a node? Have tried a few heuristics here:
//
// - when it's not part of a crossing way already (but then nodes on driveways are
Expand All @@ -25,22 +34,49 @@ impl Speedwalk {
// - if the node is only attached to one way (in the middle), it needs a crossing
// - if the node is attached to two ways AND those ways are nearly
// parallel/anti-parallel, then it needs a crossing
if node.is_crossing() || (include_crossing_no && node.is_explicit_crossing_no()) {
let ways = node
.way_ids
.iter()
.map(|w| &self.derived_ways[w])
.collect::<Vec<_>>();
if ways.len() == 1 && ways[0].kind.is_road() {
crossings.push(*id);
} else if ways.len() == 2
&& ways[0].kind.is_road()
&& ways[1].kind.is_road()
&& nearly_parallel(&ways[0].linestring, &ways[1].linestring, 10.0)
{
crossings.push(*id);
if ways.len() == 1 && ways[0].kind.is_road() {
crossings.push(*crossing_id);
continue;
} else if ways.len() == 2
&& ways[0].kind.is_road()
&& ways[1].kind.is_road()
&& nearly_parallel(&ways[0].linestring, &ways[1].linestring, 10.0)
{
crossings.push(*crossing_id);
continue;
}

// There are also cases where the crossing node is already connected to a sidewalk or
// footway on one side, and we only need to generate a new way on the other side.
let mut roads = Vec::new();
let mut crossings = Vec::new();
let mut other = Vec::new();
for way in ways {
if way.kind.is_road() {
roads.push(way);
} else if way.kind == Kind::Crossing {
crossings.push(way);
} else if way.kind == Kind::Other {
// If the crossing node is an endpoint of this footway, then this is indeed a
// half-crossing. Otherwise, it's likely a mistagged crossing way (that may
// need to be split).
if let Some((idx, _)) = way
.node_ids
.iter()
.enumerate()
.find(|(_, n)| **n == *crossing_id)
{
if idx == 0 || idx == way.node_ids.len() - 1 {
other.push(way);
}
}
}
}
// TODO This may be too restrictive, but it's a start for cases like
// https://www.openstreetmap.org/node/12270311650
if crossings.is_empty() && roads.len() >= 1 && other.len() == 1 {
half_crossings.push((*crossing_id, roads[0], other[0]));
}
}

info!(
Expand All @@ -59,6 +95,8 @@ impl Speedwalk {
let mut new_crossings = Vec::new();
let mut insert_new_nodes = HashMap::new();
for crossing_node_id in crossings {
// TODO tmp, to just see the new half-crossings
break;
let project_away_meters = 10.0;

let crossing_node = &self.derived_nodes[&crossing_node_id];
Expand Down Expand Up @@ -108,6 +146,54 @@ impl Speedwalk {
.push((endpt2, Tags::empty()));
}

info!("Generating {} half-crossings", half_crossings.len());
for (crossing_node_id, road, existing_footway) in half_crossings {
let project_away_meters = 10.0;

let crossing_node = &self.derived_nodes[&crossing_node_id];
let crossing_pt = crossing_node.pt;

// Make two perpendicular lines at the node
// TODO It might be more straightforward to figure out the incoming angle of
// existing_footway and continue in that direction
let angle = angle_of_pt_on_line(&road.linestring, crossing_pt);
let mut candidates = Vec::new();
for offset in [-90.0, 90.0] {
let test_line = Line::new(
crossing_pt,
project_away(crossing_pt, angle + offset, project_away_meters),
);
if let Some((sidewalk, endpt)) = find_sidewalk_hit(&closest_sidewalk, test_line) {
candidates.push((sidewalk, endpt));
}
}

// Which one is the missing side? Check the distance between this candidate endpoint
// and the existing footway
if let Some((sidewalk, endpt)) = candidates.into_iter().max_by_key(|(_, endpt)| {
to_cm(Euclidean.distance(&existing_footway.linestring, &Point::from(*endpt)))
}) {
let mut new_tags = Tags::empty();
new_tags.insert("highway", "footway");
new_tags.insert("footway", "crossing");
// Store OSM reference: use crossing node ID (primary) and road way ID (always available as fallback)
new_tags.insert("tmp:osm_node_id", format!("node/{}", crossing_node_id.0));
// TODO Plumb road id
//new_tags.insert("tmp:osm_way_id", format!("way/{}", road_way_id.0));
// Copy one tag from the crossing node to the new crossing way
if let Some(value) = crossing_node.tags.get("crossing") {
new_tags.insert("crossing", value);
}

new_crossings.push((LineString::new(vec![endpt, crossing_pt]), new_tags));

insert_new_nodes
.entry(sidewalk)
.or_insert_with(Vec::new)
.push((endpt, Tags::empty()));
}
}

CreateNewGeometry {
new_ways: new_crossings,
new_kind: Kind::Crossing,
Expand Down Expand Up @@ -177,3 +263,7 @@ pub fn shortest_rotation(angle1: f64, angle2: f64) -> f64 {
fn nearly_parallel(ls1: &LineString, ls2: &LineString, epsilon_degrees: f64) -> bool {
shortest_rotation(average_angle(ls1), average_angle(ls2)).abs() < epsilon_degrees
}

fn to_cm(x: f64) -> usize {
(x * 100.0).round() as usize
}