Skip to content

Commit cd745b1

Browse files
authored
Merge pull request #117 from NatLabRockies/yep/lakewood_tests
Improvements to island detection and performance of OMF import
2 parents ce236ef + 9dbae3f commit cd745b1

File tree

4 files changed

+235
-76
lines changed

4 files changed

+235
-76
lines changed

rust/bambam-omf/src/app/network.rs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::path::Path;
1+
use std::{collections::HashSet, path::Path, sync::Arc};
22

3-
use geo::{Contains, Geometry};
3+
use geo::{BoundingRect, Geometry, Intersects};
44
use rayon::prelude::*;
55
use routee_compass_core::model::unit::DistanceUnit;
66
use serde::{Deserialize, Serialize};
@@ -135,31 +135,53 @@ fn run_collector(
135135
}
136136

137137
/// filters segments and connectors in a transportation collection
138-
/// using an arbitrary extent with the `contains` predicate. empty geometries are ignored (filtered out)
138+
/// using an arbitrary extent with the `intersect` predicate over the segments collection.
139+
/// a second pass is performed over the connectors collection to reomve all connectors not referenced
140+
/// by the remaining segments.
141+
/// empty geometries are ignored (filtered out).
139142
fn apply_extent_to_collection(
140143
collection: TransportationCollection,
141144
extent: Geometry<f32>,
142145
) -> TransportationCollection {
143-
let filtered_segments = collection
146+
log::info!("Started applying extent to segments");
147+
let extent_arc = Arc::new(extent);
148+
let filtered_segments: Vec<crate::collection::TransportationSegmentRecord> = collection
144149
.segments
145150
.into_par_iter()
146-
.filter(|segment| {
147-
segment
148-
.get_linestring()
149-
.map(|linestring| extent.contains(linestring))
150-
.unwrap_or(false)
151+
.filter(|segment| match segment.get_linestring() {
152+
Ok(ls) => {
153+
let Some(bbox) = ls.bounding_rect() else {
154+
return false;
155+
};
156+
157+
// Short-circuit condition for bbox
158+
extent_arc.intersects(&bbox) && extent_arc.intersects(ls)
159+
}
160+
Err(_) => false,
151161
})
152162
.collect();
153163

164+
log::info!("Collecting all connector IDs");
165+
let connector_ids = filtered_segments
166+
.par_iter()
167+
.flat_map(|segment| {
168+
segment
169+
.connectors
170+
.as_ref()
171+
.unwrap_or(&vec![])
172+
.iter()
173+
.map(|con_ref| con_ref.connector_id.clone())
174+
.collect::<Vec<String>>()
175+
})
176+
.collect::<HashSet<String>>();
177+
178+
let arc_ids = Arc::new(connector_ids);
179+
180+
log::info!("Started applying extent to connectors");
154181
let filtered_connectors = collection
155182
.connectors
156183
.into_par_iter()
157-
.filter(|connector| {
158-
connector
159-
.get_geometry()
160-
.map(|geometry| extent.contains(geometry))
161-
.unwrap_or(false)
162-
})
184+
.filter(|connector| arc_ids.contains(&connector.id))
163185
.collect();
164186

165187
TransportationCollection {

rust/bambam-omf/src/graph/component_algorithm.rs

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,22 @@ pub fn island_detection_algorithm(
9494
component.push((current_el_id, current_e_id));
9595
let current_edge = edge_lists[current_el_id.0].0[current_e_id.0];
9696

97-
let current_distance = is_component_island_sequential(
97+
// Update the max_distance
98+
let current_midpoint = compute_midpoint(&current_edge, vertices);
99+
let current_distance =
100+
Haversine.length(&line_string![start_midpoint.0, current_midpoint.0]);
101+
let current_distance_uom =
102+
uom_length::new::<uom::si::length::meter>(current_distance as f64);
103+
max_distance_reached = max_distance_reached.max(current_distance_uom);
104+
105+
visit_edge(
98106
&current_edge,
99-
start_midpoint,
100107
&mut visited,
101108
&mut queue,
102-
vertices,
103109
&forward_adjacency,
104110
&backward_adjacency,
105111
);
106112

107-
// Update the max_distance
108-
let current_distance_uom =
109-
uom_length::new::<uom::si::length::meter>(current_distance as f64);
110-
max_distance_reached = max_distance_reached.max(current_distance_uom);
111-
112113
// Update bar
113114
if let Err(e) = pb.update(1) {
114115
log::warn!("error during update of progress bar: {e}")
@@ -131,42 +132,49 @@ pub fn island_detection_algorithm(
131132
island_edges
132133
}
133134

134-
/// returns the f32 distance in meters from the current edge midpoint to the initial edge midpoint.
135-
fn is_component_island_sequential(
135+
/// visit operation for weakly-connected BFS traversal
136+
fn visit_edge(
136137
edge: &Edge,
137-
initial_midpoint: Point<f32>,
138138
visited: &mut HashSet<(EdgeListId, EdgeId)>,
139139
queue: &mut VecDeque<(EdgeListId, EdgeId)>,
140-
vertices: &[Vertex],
141140
forward_adjacency: &DenseAdjacencyList,
142141
backward_adjacency: &DenseAdjacencyList,
143-
) -> f32 {
142+
) -> () {
144143
let (edge_list_id, edge_id) = (edge.edge_list_id, edge.edge_id);
145144

146-
// Update counter
147-
let current_midpoint = compute_midpoint(edge, vertices);
148-
let current_distance_to_start_meters =
149-
Haversine.length(&line_string![initial_midpoint.0, current_midpoint.0]);
150-
151145
// get all neighbors, add them to queue
146+
// forward_adjacency[dst]: edges leaving dst (v → *)
152147
let outward_edges: Vec<&(EdgeListId, EdgeId)> =
153148
forward_adjacency[edge.dst_vertex_id.0].keys().collect();
154149
for (edge_list_id, edge_id) in outward_edges {
155150
queue.push_back((*edge_list_id, *edge_id));
156151
}
152+
// backward_adjacency[src]: edges entering src (* → u)
157153
let inward_edges: Vec<&(EdgeListId, EdgeId)> =
158154
backward_adjacency[edge.src_vertex_id.0].keys().collect();
159155
for (edge_list_id, edge_id) in inward_edges {
160156
queue.push_back((*edge_list_id, *edge_id));
161157
}
158+
// forward_adjacency[src]: other edges leaving src (u → *) — catches pure source vertices
159+
let sibling_outward_edges: Vec<&(EdgeListId, EdgeId)> =
160+
forward_adjacency[edge.src_vertex_id.0].keys().collect();
161+
for (edge_list_id, edge_id) in sibling_outward_edges {
162+
queue.push_back((*edge_list_id, *edge_id));
163+
}
164+
// backward_adjacency[dst]: other edges entering dst (* → v) — catches pure sink vertices
165+
let sibling_inward_edges: Vec<&(EdgeListId, EdgeId)> =
166+
backward_adjacency[edge.dst_vertex_id.0].keys().collect();
167+
for (edge_list_id, edge_id) in sibling_inward_edges {
168+
queue.push_back((*edge_list_id, *edge_id));
169+
}
162170

163171
// mark as visited
164172
visited.insert((edge_list_id, edge_id));
165-
166-
current_distance_to_start_meters
167173
}
168174

169-
/// parallelizable implementation
175+
/// parallelizable implementation. Explores the entire component this
176+
/// edge belongs to up to a given distance threshold and returns whether or
177+
/// not the component is an island
170178
fn is_component_island_parallel(
171179
edge: &Edge,
172180
distance_threshold: f64,
@@ -176,9 +184,9 @@ fn is_component_island_parallel(
176184
forward_adjacency: &DenseAdjacencyList,
177185
backward_adjacency: &DenseAdjacencyList,
178186
) -> Result<bool, OvertureMapsCollectionError> {
179-
let mut visited = HashSet::<(&EdgeListId, &EdgeId)>::new();
180-
let mut visit_queue: VecDeque<(&EdgeListId, &EdgeId)> = VecDeque::new();
181-
visit_queue.push_back((&edge.edge_list_id, &edge.edge_id));
187+
let mut visited = HashSet::<(EdgeListId, EdgeId)>::new();
188+
let mut visit_queue: VecDeque<(EdgeListId, EdgeId)> = VecDeque::new();
189+
visit_queue.push_back((edge.edge_list_id, edge.edge_id));
182190

183191
let edge_midpoint = compute_midpoint(edge, vertices);
184192
let mut max_distance_reached = uom_length::new::<uom::si::length::meter>(0 as f64);
@@ -193,28 +201,20 @@ fn is_component_island_parallel(
193201
{
194202
continue;
195203
}
196-
visited.insert((current_edge_list_id, current_edge_id));
197204

198205
// Retrieve current edge information
199206
let current_edge = edge_lists.get(current_edge_list_id.0)
200-
.and_then(|el| el.get(current_edge_id))
201-
.ok_or(OvertureMapsCollectionError::InternalError(format!("edge list {current_edge_list_id:?} or edge {current_edge_id:?} not found during island detection starting at edge {edge:?}")))?;
207+
.and_then(|el| el.get(&current_edge_id))
208+
.ok_or(OvertureMapsCollectionError::InternalError(format!("edge list {current_edge_list_id:?} or edge {current_edge_id:?} not found during island detection starting at edge {edge:?}")))?;
202209

203210
// Expand queue
204-
let outward_edges: Vec<&(EdgeListId, EdgeId)> = forward_adjacency
205-
[current_edge.dst_vertex_id.0]
206-
.keys()
207-
.collect();
208-
for (edge_list_id, edge_id) in outward_edges {
209-
visit_queue.push_back((edge_list_id, edge_id));
210-
}
211-
let inward_edges: Vec<&(EdgeListId, EdgeId)> = backward_adjacency
212-
[current_edge.src_vertex_id.0]
213-
.keys()
214-
.collect();
215-
for (edge_list_id, edge_id) in inward_edges {
216-
visit_queue.push_back((edge_list_id, edge_id));
217-
}
211+
visit_edge(
212+
&current_edge,
213+
&mut visited,
214+
&mut visit_queue,
215+
forward_adjacency,
216+
backward_adjacency,
217+
);
218218

219219
// Update counter
220220
let current_midpoint = compute_midpoint(current_edge, vertices);

rust/bambam-omf/src/graph/omf_graph.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ use crate::{
1111
SegmentFullType, TransportationCollection, TransportationSegmentRecord,
1212
},
1313
graph::{
14-
component_algorithm::island_detection_algorithm, segment_ops,
15-
serialize_ops::clean_omf_edge_list, vertex_serializable::VertexSerializable,
14+
component_algorithm::island_detection_algorithm,
15+
segment_ops,
16+
serialize_ops::{clean_omf_edge_list, compute_vertex_remapping},
17+
vertex_serializable::VertexSerializable,
1618
OmfGraphSummary,
1719
},
1820
};
@@ -61,9 +63,11 @@ impl OmfGraphVectorized {
6163
island_detection_configuration: Option<IslandDetectionAlgorithmConfiguration>,
6264
) -> Result<Self, OvertureMapsCollectionError> {
6365
// process all connectors into vertices
66+
log::info!("Creating vertex lookup");
6467
let (mut vertices, mut vertex_lookup) =
6568
ops::create_vertices_and_lookup(&collection.connectors, None)?;
6669

70+
log::info!("Processing edge lists");
6771
// for each mode configuration, create an edge list
6872
let mut edge_lists: Vec<OmfEdgeList> = vec![];
6973
for (index, edge_list_config) in configuration.iter().enumerate() {
@@ -73,6 +77,7 @@ impl OmfGraphVectorized {
7377
let mut filter = edge_list_config.filter.clone();
7478
filter.sort(); // sort for performance
7579

80+
log::info!("Filtering edge list {edge_list_id}");
7681
// filter to the segments that match our travel mode filter(s)
7782
let segments: Vec<&TransportationSegmentRecord> = collection
7883
.segments
@@ -84,6 +89,7 @@ impl OmfGraphVectorized {
8489
// the splits are locations in each segment record where we want to define a vertex
8590
// which may not yet exist on the graph. this is where we begin to impose directivity
8691
// in our records.
92+
log::info!("Creating splits");
8793
let mut splits = vec![];
8894
for heading in [SegmentHeading::Forward, SegmentHeading::Backward] {
8995
let mut when: SegmentAccessRestrictionWhen = edge_list_config.into();
@@ -99,6 +105,7 @@ impl OmfGraphVectorized {
99105

100106
// depending on the split method, we may need to create additional vertices at locations
101107
// which are not OvertureMaps-defined connector types.
108+
log::info!("Extending vertices");
102109
ops::extend_vertices(
103110
&splits,
104111
&segments,
@@ -108,6 +115,7 @@ impl OmfGraphVectorized {
108115
)?;
109116

110117
// create all edges based on the above split points using all vertices.
118+
log::info!("Creating edges");
111119
let edges = ops::create_edges(
112120
&segments,
113121
&segment_lookup,
@@ -116,11 +124,16 @@ impl OmfGraphVectorized {
116124
&vertex_lookup,
117125
edge_list_id,
118126
)?;
127+
log::info!("Creating geometries");
119128
let geometries = ops::create_geometries(&segments, &segment_lookup, &splits)?;
129+
log::info!("Creating bearings");
120130
let bearings = ops::bearing_deg_from_geometries(&geometries)?;
131+
log::info!("Creating classes");
121132
let classes = ops::create_segment_full_types(&segments, &segment_lookup, &splits)?;
122133

134+
log::info!("Creating speeds");
123135
let speeds = ops::create_speeds(&segments, &segment_lookup, &splits)?;
136+
log::info!("Creating speed lookup");
124137
let speed_lookup = ops::create_speed_by_segment_type_lookup(
125138
&speeds,
126139
&segments,
@@ -130,13 +143,16 @@ impl OmfGraphVectorized {
130143
)?;
131144

132145
// insert global speed value for reference
146+
log::info!("Computing global speed");
133147
let global_speed =
134148
ops::get_global_average_speed(&speeds, &segments, &segment_lookup, &splits)?;
135149

136150
// omf ids
151+
log::info!("Computing omf_ids");
137152
let omf_segment_ids = ops::get_segment_omf_ids(&segments, &segment_lookup, &splits)?;
138153

139154
// match speeds according to classes
155+
log::info!("Completing speeds vector with default global");
140156
let speeds = speeds
141157
.into_par_iter()
142158
.zip(&classes)
@@ -172,6 +188,7 @@ impl OmfGraphVectorized {
172188
}
173189

174190
// Compute islands in resulting edge lists and remove island edges
191+
log::info!("Compute islands");
175192
if let Some(algorithm_config) = island_detection_configuration {
176193
let ref_edge_lists = edge_lists
177194
.iter()
@@ -187,11 +204,31 @@ impl OmfGraphVectorized {
187204

188205
// Refactor Vec into Hashmap
189206
let mut edges_lookup: HashMap<EdgeListId, Vec<EdgeId>> = HashMap::new();
190-
for (a, b) in island_edges {
191-
edges_lookup.entry(a).or_default().push(b);
207+
for (a, b) in &island_edges {
208+
edges_lookup.entry(*a).or_default().push(*b);
209+
}
210+
211+
// Compute and apply vertex remapping
212+
let vertex_remapping = compute_vertex_remapping(&vertices, &edge_lists, &island_edges)?;
213+
vertices = vertices
214+
.into_iter()
215+
.filter_map(|vertex| {
216+
vertex_remapping[vertex.vertex_id.0].map(|vertex_id| Vertex {
217+
vertex_id,
218+
..vertex
219+
})
220+
})
221+
.collect();
222+
223+
// dropping entries for removed vertices.
224+
vertex_lookup.retain(|_, v| vertex_remapping[*v].is_some());
225+
// Update vertex_lookup to reflect the remapped vertex indices,
226+
for v in vertex_lookup.values_mut() {
227+
*v = vertex_remapping[*v].ok_or(OvertureMapsCollectionError::InternalError(format!("vertex index {v} expected after island computation but was flagged for deletion")))?.0;
192228
}
193229

194230
// Clean the edge lists
231+
log::info!("Apply islands algorithm result");
195232
edge_lists = edge_lists
196233
.into_iter()
197234
.map(|omf_list| {
@@ -209,9 +246,9 @@ impl OmfGraphVectorized {
209246
.map(|edge| !edges_to_remove.contains(&edge.edge_id))
210247
.collect::<Vec<bool>>();
211248

212-
clean_omf_edge_list(omf_list, mask)
249+
clean_omf_edge_list(omf_list, mask, &vertex_remapping)
213250
})
214-
.collect::<Vec<OmfEdgeList>>();
251+
.collect::<Result<Vec<OmfEdgeList>, OvertureMapsCollectionError>>()?;
215252
};
216253

217254
let result = Self {

0 commit comments

Comments
 (0)