solverforge_maps/routing/
matrix.rs1use rayon::prelude::*;
4use std::collections::HashMap;
5use tokio::sync::mpsc::{self, Sender};
6
7use super::algo::dijkstra_with_secondary;
8use super::coord::Coord;
9use super::graph::NodeIdx;
10use super::network::{RoadNetwork, SnappedCoord};
11use super::progress::RoutingProgress;
12
13pub const UNREACHABLE: i64 = i64::MAX;
14
15#[derive(Debug, Clone, Default)]
16pub struct TravelTimeMatrix {
17 data: Vec<i64>,
18 distance_data: Vec<i64>,
19 size: usize,
20 locations: Vec<SnappedCoord>,
21}
22
23impl TravelTimeMatrix {
24 pub(crate) fn new(
25 data: Vec<i64>,
26 distance_data: Vec<i64>,
27 size: usize,
28 locations: Vec<SnappedCoord>,
29 ) -> Self {
30 debug_assert_eq!(data.len(), size * size);
31 debug_assert_eq!(distance_data.len(), size * size);
32 debug_assert_eq!(locations.len(), size);
33 Self {
34 data,
35 distance_data,
36 size,
37 locations,
38 }
39 }
40
41 #[inline]
46 pub fn get(&self, from: usize, to: usize) -> Option<i64> {
47 if from < self.size && to < self.size {
48 Some(self.data[from * self.size + to])
49 } else {
50 None
51 }
52 }
53
54 #[inline]
55 pub fn is_reachable(&self, from: usize, to: usize) -> bool {
56 self.get(from, to)
57 .map(|v| v != UNREACHABLE)
58 .unwrap_or(false)
59 }
60
61 #[inline]
66 pub fn distance_meters(&self, from: usize, to: usize) -> Option<i64> {
67 if from < self.size && to < self.size {
68 Some(self.distance_data[from * self.size + to])
69 } else {
70 None
71 }
72 }
73
74 #[inline]
75 pub fn size(&self) -> usize {
76 self.size
77 }
78
79 #[inline]
80 pub fn locations(&self) -> &[SnappedCoord] {
81 &self.locations
82 }
83
84 #[inline]
85 pub fn row(&self, i: usize) -> Option<&[i64]> {
86 if i < self.size {
87 let start = i * self.size;
88 Some(&self.data[start..start + self.size])
89 } else {
90 None
91 }
92 }
93
94 #[inline]
95 pub fn row_distances(&self, i: usize) -> Option<&[i64]> {
96 if i < self.size {
97 let start = i * self.size;
98 Some(&self.distance_data[start..start + self.size])
99 } else {
100 None
101 }
102 }
103
104 pub fn min(&self) -> Option<i64> {
105 let mut min_val = None;
106 for i in 0..self.size {
107 for j in 0..self.size {
108 if i == j {
109 continue;
110 }
111 let val = self.data[i * self.size + j];
112 if val != UNREACHABLE {
113 min_val = Some(min_val.map(|m: i64| m.min(val)).unwrap_or(val));
114 }
115 }
116 }
117 min_val
118 }
119
120 pub fn max(&self) -> Option<i64> {
121 let mut max_val = None;
122 for i in 0..self.size {
123 for j in 0..self.size {
124 if i == j {
125 continue;
126 }
127 let val = self.data[i * self.size + j];
128 if val != UNREACHABLE {
129 max_val = Some(max_val.map(|m: i64| m.max(val)).unwrap_or(val));
130 }
131 }
132 }
133 max_val
134 }
135
136 pub fn mean(&self) -> Option<f64> {
137 let mut sum = 0i64;
138 let mut count = 0usize;
139 for i in 0..self.size {
140 for j in 0..self.size {
141 if i == j {
142 continue;
143 }
144 let val = self.data[i * self.size + j];
145 if val != UNREACHABLE {
146 sum = sum.saturating_add(val);
147 count += 1;
148 }
149 }
150 }
151 if count > 0 {
152 Some(sum as f64 / count as f64)
153 } else {
154 None
155 }
156 }
157
158 pub fn reachability_ratio(&self) -> f64 {
159 if self.size <= 1 {
160 return 1.0;
161 }
162 let total_pairs = self.size * (self.size - 1);
163 let reachable = self.count_reachable();
164 reachable as f64 / total_pairs as f64
165 }
166
167 fn count_reachable(&self) -> usize {
168 let mut count = 0;
169 for i in 0..self.size {
170 for j in 0..self.size {
171 if i != j && self.data[i * self.size + j] != UNREACHABLE {
172 count += 1;
173 }
174 }
175 }
176 count
177 }
178
179 pub fn unreachable_pairs(&self) -> Vec<(usize, usize)> {
180 let mut pairs = Vec::new();
181 for i in 0..self.size {
182 for j in 0..self.size {
183 if i != j && self.data[i * self.size + j] == UNREACHABLE {
184 pairs.push((i, j));
185 }
186 }
187 }
188 pairs
189 }
190
191 pub fn as_slice(&self) -> &[i64] {
192 &self.data
193 }
194
195 pub fn distances_as_slice(&self) -> &[i64] {
196 &self.distance_data
197 }
198}
199
200impl RoadNetwork {
201 pub async fn compute_matrix(
202 &self,
203 locations: &[Coord],
204 progress: Option<&Sender<RoutingProgress>>,
205 ) -> TravelTimeMatrix {
206 let n = locations.len();
207 if n == 0 {
208 return TravelTimeMatrix::new(vec![], vec![], 0, vec![]);
209 }
210
211 let node_snapped: Vec<Option<SnappedCoord>> = locations
212 .iter()
213 .map(|&coord| self.snap_to_road_detailed(coord).ok())
214 .collect();
215
216 let snapped_locations: Vec<SnappedCoord> = locations
217 .iter()
218 .zip(node_snapped.iter())
219 .map(|(&coord, edge_snap)| {
220 if let Some(snapped) = edge_snap {
221 *snapped
222 } else {
223 SnappedCoord {
224 original: coord,
225 snapped: coord,
226 snap_distance_m: f64::INFINITY,
227 node_index: NodeIdx::new(0),
228 }
229 }
230 })
231 .collect();
232
233 let row_progress = progress.map(|tx| {
234 let (progress_tx, mut progress_rx) = mpsc::unbounded_channel::<usize>();
235 let tx = tx.clone();
236 let handle = tokio::spawn(async move {
237 let mut completed_rows = 0usize;
238 while progress_rx.recv().await.is_some() {
239 completed_rows += 1;
240 let percent = 55 + ((completed_rows * 44) / n.max(1)) as u8;
241 let _ = tx
242 .send(RoutingProgress::ComputingMatrix {
243 percent,
244 row: completed_rows,
245 total: n,
246 })
247 .await;
248 }
249 });
250 (progress_tx, handle)
251 });
252
253 let graph = &self.graph;
255 let progress_tx = row_progress.as_ref().map(|(tx, _)| tx.clone());
256 let rows: Vec<(Vec<i64>, Vec<i64>)> = (0..n)
257 .into_par_iter()
258 .map(|i| {
259 let mut row = vec![0i64; n];
260 let mut distance_row = vec![0i64; n];
261
262 let Some(from) = &node_snapped[i] else {
263 for (j, cell) in row.iter_mut().enumerate() {
264 if i != j {
265 *cell = UNREACHABLE;
266 distance_row[j] = UNREACHABLE;
267 }
268 }
269 if let Some(tx) = &progress_tx {
270 let _ = tx.send(i);
271 }
272 return (row, distance_row);
273 };
274
275 let costs = dijkstra_with_secondary(
276 graph,
277 from.node_index,
278 None,
279 |e| e.travel_time_s,
280 |e| e.distance_m,
281 );
282
283 for j in 0..n {
284 if i == j {
285 continue;
286 }
287
288 let Some(to) = &node_snapped[j] else {
289 row[j] = UNREACHABLE;
290 distance_row[j] = UNREACHABLE;
291 continue;
292 };
293
294 if let Some(&(duration, distance)) = costs.get(&to.node_index) {
295 row[j] = duration.round() as i64;
296 distance_row[j] = distance.round() as i64;
297 } else {
298 row[j] = UNREACHABLE;
299 distance_row[j] = UNREACHABLE;
300 }
301 }
302
303 if let Some(tx) = &progress_tx {
304 let _ = tx.send(i);
305 }
306 (row, distance_row)
307 })
308 .collect();
309
310 if let Some((progress_tx, handle)) = row_progress {
311 drop(progress_tx);
312 let _ = handle.await;
313 }
314
315 if let Some(tx) = progress {
316 let _ = tx.send(RoutingProgress::Complete).await;
317 }
318
319 let mut data = Vec::with_capacity(n * n);
320 let mut distance_data = Vec::with_capacity(n * n);
321 for (row, distance_row) in rows {
322 data.extend(row);
323 distance_data.extend(distance_row);
324 }
325 TravelTimeMatrix::new(data, distance_data, n, snapped_locations)
326 }
327
328 pub async fn compute_geometries(
329 &self,
330 locations: &[Coord],
331 progress: Option<&Sender<RoutingProgress>>,
332 ) -> HashMap<(usize, usize), Vec<Coord>> {
333 let n = locations.len();
334 let total_pairs = n * (n - 1);
335 let mut geometries = HashMap::new();
336 let mut pair_count = 0usize;
337
338 for i in 0..n {
339 for j in 0..n {
340 if i == j {
341 continue;
342 }
343 if let Ok(result) = self.route(locations[i], locations[j]) {
344 geometries.insert((i, j), result.geometry);
345 }
346 pair_count += 1;
347
348 if let Some(tx) = progress {
349 if pair_count.is_multiple_of(10) || pair_count == total_pairs {
350 let percent = 80 + (pair_count * 15 / total_pairs.max(1)) as u8;
351 let _ = tx
352 .send(RoutingProgress::ComputingGeometries {
353 percent,
354 pair: pair_count,
355 total: total_pairs,
356 })
357 .await;
358 }
359 }
360 }
361 }
362
363 if let Some(tx) = progress {
364 let _ = tx.send(RoutingProgress::Complete).await;
365 }
366
367 geometries
368 }
369}