Skip to main content

transit_grid/graphs/physical/
mod.rs

1use std::collections::HashMap;
2
3use crate::core::{EdgeId, NodeId, TransitEdge, TransitNode};
4use geo::{Coord, CoordNum, Distance, Euclidean};
5use petgraph::{
6    graph::EdgeIndex,
7    graph::{NodeIndex, UnGraph},
8};
9
10/// Represents the physical layout of the transit network.
11///
12/// `PhysicalGraph` is an undirected graph where each node represents a transit node (a point in the transit network where a vehicle can stop) and each edge represents a transit edge (a path between two transit nodes).
13/// The `PhysicalGraph` uses the `UnGraph` structure from the `petgraph` crate to internally represent this data. The `PhysicalGraph` maintains mappings between `NodeId`s and `NodeIndex`es (from `petgraph`), allowing for efficient conversion between the two.
14///
15/// # Examples
16///
17/// Creating a new `PhysicalGraph` and adding a `TransitNode`:
18/// ```
19/// use transit_grid::core::TransitNode;
20/// use transit_grid::prelude::PhysicalGraph;
21/// use geo::{coord, Coord};
22///
23/// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
24/// let node = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
25/// graph.add_transit_node(node);
26/// ```
27///
28/// Adding a `TransitEdge` to the `PhysicalGraph`:
29/// ```
30/// use transit_grid::core::{TransitNode, TransitEdge};
31/// use transit_grid::prelude::PhysicalGraph;
32/// use geo::{coord, Coord, LineString};
33///
34/// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
35/// let node1 = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
36/// let node2 = TransitNode { id: 2, location: coord! { x:1.0, y:1.0 } };
37/// let node1_id = graph.add_transit_node(node1);
38/// let node2_id = graph.add_transit_node(node2);
39/// let edge = TransitEdge {
40///     id: 1,
41///     source: 1,
42///     target: 2,
43///     length: 1.0,
44///     path: LineString(vec![coord! { x:0.0, y:0.0 }, coord! { x:1.0, y:1.0 }]),
45/// };
46/// graph.add_transit_edge(edge);
47/// ```
48#[derive(Debug, Clone)]
49pub struct PhysicalGraph<R, T: CoordNum> {
50    /// Underlying undirected graph.
51    pub graph: UnGraph<TransitNode<R>, TransitEdge<T>, u32>,
52
53    /// Mapping of NodeId to petgraph's NodeIndex.
54    id_to_index: HashMap<NodeId, NodeIndex>,
55
56    /// Mapping of petgraph's NodeIndex to NodeId.
57    index_to_id: HashMap<NodeIndex, NodeId>,
58}
59
60impl<R: Copy, T: CoordNum> PhysicalGraph<R, T> {
61    /// Creates a new, empty `PhysicalGraph`.
62    pub fn new() -> Self {
63        PhysicalGraph {
64            graph: UnGraph::<TransitNode<R>, TransitEdge<T>, u32>::new_undirected(),
65            id_to_index: HashMap::new(),
66            index_to_id: HashMap::new(),
67        }
68    }
69
70    /// Converts a `NodeIndex` to a `NodeId`.
71    ///
72    /// This method provides a way to map from the petgraph's `NodeIndex` to
73    /// the `NodeId` used in the `TransitNode`.
74    ///
75    /// # Arguments
76    ///
77    /// * `index` - The `NodeIndex` to be converted.
78    ///
79    /// # Returns
80    ///
81    /// * `NodeId` - The corresponding `NodeId` of the provided `NodeIndex`.
82    ///
83    /// # Example
84    ///
85    /// ```
86    /// use transit_grid::prelude::PhysicalGraph;
87    /// use transit_grid::core::TransitNode;
88    /// use geo::{coord, Coord};
89    ///
90    /// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
91    /// let node = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
92    /// let node_index = graph.add_transit_node(node);
93    /// let node_id = graph.index_to_id(node_index);
94    /// assert_eq!(node_id, Some(&1));
95    /// ```
96    pub fn index_to_id(&self, index: NodeIndex) -> Option<&NodeId> {
97        self.index_to_id.get(&index)
98    }
99
100    /// Converts a `NodeId` to a `NodeIndex`.
101    ///
102    /// This method provides a way to map from a `NodeId` used in the `TransitNode` to
103    /// the petgraph's `NodeIndex`.
104    ///
105    /// # Arguments
106    ///
107    /// * `id` - The `NodeId` to be converted.
108    ///
109    /// # Returns
110    ///
111    /// * `NodeIndex` - The corresponding `NodeIndex` of the provided `NodeId`.
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use transit_grid::prelude::PhysicalGraph;
117    /// use transit_grid::core::TransitNode;
118    /// use geo::{coord, Coord};
119    ///
120    /// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
121    /// let node = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
122    /// let node_index = graph.add_transit_node(node);
123    /// let queried_index = graph.id_to_index(1);
124    /// assert_eq!(Some(&node_index), queried_index);
125    /// ```
126    pub fn id_to_index(&self, id: NodeId) -> Option<&NodeIndex> {
127        self.id_to_index.get(&id)
128    }
129
130    /// Adds a `TransitNode` to the `PhysicalGraph`.
131    ///
132    /// # Example
133    /// ```
134    /// use transit_grid::prelude::PhysicalGraph;
135    /// use transit_grid::core::TransitNode;
136    /// use geo::{coord, Coord};
137    ///
138    /// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
139    /// let node = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
140    /// graph.add_transit_node(node);
141    /// ```
142    pub fn add_transit_node(&mut self, node: TransitNode<R>) -> NodeIndex {
143        let index = self.graph.add_node(node);
144        self.id_to_index.insert(node.id, index);
145        self.index_to_id.insert(index, node.id);
146        index
147    }
148
149    /// Adds a `TransitEdge` to the `PhysicalGraph`.
150    ///
151    /// # Example
152    /// ```
153    /// use transit_grid::prelude::PhysicalGraph;
154    /// use transit_grid::core::{TransitNode, TransitEdge};
155    /// use geo::{coord, Coord, LineString};
156    /// use petgraph::csr::IndexType;
157    ///
158    /// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
159    /// let node1 = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
160    /// let node2 = TransitNode { id: 2, location: coord! { x:1.0, y:1.0 } };
161    ///
162    /// let node1_id = graph.add_transit_node(node1);
163    /// let node2_id = graph.add_transit_node(node2);
164    ///
165    /// let edge = TransitEdge {
166    ///     id: 1,
167    ///     source: 1,
168    ///     target: 2,
169    ///     length: 1.0,
170    ///     path: LineString(vec![coord! { x:0.0, y:0.0 }, coord! { x:1.0, y:1.0 }]),
171    /// };
172    ///
173    /// graph.add_transit_edge(edge);
174    /// ```
175    pub fn add_transit_edge(&mut self, edge: TransitEdge<T>) -> EdgeIndex {
176        let from = self.id_to_index(edge.source);
177        let to = self.id_to_index(edge.target);
178        if let (Some(from), Some(to)) = (from, to) {
179            self.graph.add_edge(*from, *to, edge)
180        } else {
181            panic!("Invalid node ID")
182        }
183    }
184
185    /// Returns a reference to the `TransitEdge` connecting the two nodes specified by `node1` and `node2`.
186    ///
187    /// # Arguments
188    ///
189    /// * `node1` - The `NodeId` of the first node.
190    /// * `node2` - The `NodeId` of the second node.
191    ///
192    /// # Returns
193    ///
194    /// A reference to the `TransitEdge` connecting `node1` and `node2`. This function will panic if there is no edge between the nodes.
195    ///
196    /// # Panics
197    ///
198    /// This function will panic in the following cases:
199    ///
200    /// * If `node1` or `node2` are not valid node IDs in the graph.
201    /// * If there is no edge between `node1` and `node2`.
202    pub fn get_transit_edge(&self, node1: NodeId, node2: NodeId) -> Option<&TransitEdge<T>> {
203        let node1_index = self.id_to_index(node1);
204        let node2_index = self.id_to_index(node2);
205        if let (Some(node1_index), Some(node2_index)) = (node1_index, node2_index) {
206            if let Some(edge_index) = self.graph.find_edge(*node1_index, *node2_index) {
207                self.graph.edge_weight(edge_index)
208            } else {
209                None
210            }
211        } else {
212            None
213        }
214    }
215
216    /// Returns a reference to the `TransitEdge` with the specified `EdgeId`.
217    pub fn get_transit_edge_by_id(&self, edge_id: EdgeId) -> Option<&TransitEdge<T>> {
218        self.graph
219            .edge_references()
220            .find(|edge| edge.weight().id == edge_id)
221            .map(|edge| edge.weight())
222    }
223
224    /// Repairs a physical edge in the `PhysicalGraph` based on its nodes' locations.
225    ///
226    /// # Arguments
227    ///
228    /// * `edge` - The `TransitEdge` to be repaired.
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// use transit_grid::prelude::PhysicalGraph;
234    /// use transit_grid::core::{TransitNode, TransitEdge};
235    /// use geo::{coord, Coord, LineString};
236    ///
237    /// let mut graph: PhysicalGraph<Coord, f64> = PhysicalGraph::new();
238    /// let node1 = TransitNode { id: 1, location: coord! { x:0.0, y:0.0 } };
239    /// let node2 = TransitNode { id: 2, location: coord! { x:1.0, y:1.0 } };
240    ///
241    /// let node1_id = graph.add_transit_node(node1);
242    /// let node2_id = graph.add_transit_node(node2);
243    ///
244    /// let mut edge = TransitEdge {
245    ///     id: 1,
246    ///     source: 1,
247    ///     target: 2,
248    ///     length: 1.0,
249    ///     path: LineString(vec![coord! { x:1.0, y:1.0 }, coord! { x:0.0, y:0.0 }]),  // Note that the direction is initially reversed
250    /// };
251    ///
252    /// graph.add_transit_edge(edge.clone());
253    /// graph.repair_edge(1, 2);
254    /// let edge = graph.get_transit_edge(1, 2).unwrap();
255    /// assert_eq!(
256    ///    edge.path,
257    ///    LineString(vec![Coord { x: 0.0, y: 0.0 }, Coord { x: 1.0, y: 1.0 }])
258    /// );
259    ///
260    /// // After repair, the edge path should be from 0,0 to 1,1
261    /// //assert_eq!(edge.path.0.first().unwrap(), &coord! { x:0.0, y:0.0 });
262    /// //assert_eq!(edge.path.0.last().unwrap(), &coord! { x:1.0, y:1.0 });
263    /// ```
264    pub fn repair_edge(&mut self, node1: NodeId, node2: NodeId)
265    where
266        Euclidean: Distance<T, R, Coord<T>>,
267    {
268        let node1_index = self.id_to_index(node1);
269        let node2_index = self.id_to_index(node2);
270        if let (Some(node1_index), Some(node2_index)) = (node1_index, node2_index) {
271            let from_node_location = {
272                let from_node: &TransitNode<R> = self.graph.node_weight(*node1_index).unwrap();
273                from_node.location
274            };
275
276            let edge_index = self.graph.find_edge(*node1_index, *node2_index).unwrap();
277            let edge = self.graph.edge_weight_mut(edge_index).unwrap();
278
279            let first_point = *edge.path.0.first().unwrap();
280            let last_point = *edge.path.0.last().unwrap();
281
282            let dist_to_first = Euclidean.distance(from_node_location, first_point);
283            let dist_to_last = Euclidean.distance(from_node_location, last_point);
284
285            if dist_to_first > dist_to_last {
286                edge.path.0.reverse();
287            }
288        }
289    }
290}
291
292impl<R: Copy, T: CoordNum> Default for PhysicalGraph<R, T> {
293    fn default() -> Self {
294        Self::new()
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use geo::{coord, Coord, LineString};
302
303    #[test]
304    fn test_graph() {
305        let mut graph = PhysicalGraph::new();
306
307        let node1 = TransitNode {
308            id: 1,
309            location: coord! { x:0.0, y:0.0 },
310        };
311
312        let node2 = TransitNode {
313            id: 2,
314            location: coord! { x:1.0, y:1.0 },
315        };
316
317        let node3 = TransitNode {
318            id: 3,
319            location: coord! { x:1.0, y:1.0 },
320        };
321
322        let _node1_id = graph.add_transit_node(node1);
323        let _node2_id = graph.add_transit_node(node2);
324        let _node3_id = graph.add_transit_node(node3);
325
326        let edge = TransitEdge {
327            id: 1,
328            source: 1,
329            target: 2,
330            length: 1.0,
331            path: LineString(vec![coord! { x:0.0, y:0.0 }, coord! { x:1.0, y:1.0 }]),
332        };
333
334        let _ = graph.add_transit_edge(edge);
335
336        assert_eq!(graph.graph.node_count(), 3);
337        assert_eq!(graph.graph.edge_count(), 1);
338
339        assert!(graph.get_transit_edge(1, 2).is_some());
340        assert!(graph.get_transit_edge(1, 3).is_none());
341    }
342
343    #[test]
344    fn test_index_to_id() {
345        let mut graph = PhysicalGraph::<Coord, f64>::new();
346
347        let node1 = TransitNode {
348            id: 1,
349            location: coord! { x:0.0, y:0.0 },
350        };
351
352        let node2 = TransitNode {
353            id: 2,
354            location: coord! { x:1.0, y:1.0 },
355        };
356
357        let node1_index = graph.add_transit_node(node1);
358        let node2_index = graph.add_transit_node(node2);
359
360        let node1_id = graph.index_to_id(node1_index);
361        let node2_id = graph.index_to_id(node2_index);
362
363        assert_eq!(node1_id, Some(&1));
364        assert_eq!(node2_id, Some(&2));
365    }
366
367    #[test]
368    fn test_id_to_index() {
369        let mut graph = PhysicalGraph::<Coord, f64>::new();
370
371        let node1 = TransitNode {
372            id: 1,
373            location: coord! { x:0.0, y:0.0 },
374        };
375
376        let node2 = TransitNode {
377            id: 2,
378            location: coord! { x:1.0, y:1.0 },
379        };
380
381        let node1_index = graph.add_transit_node(node1);
382        let node2_index = graph.add_transit_node(node2);
383
384        let queried_node1_index = graph.id_to_index(1);
385        let queried_node2_index = graph.id_to_index(2);
386
387        assert_eq!(Some(&node1_index), queried_node1_index);
388        assert_eq!(Some(&node2_index), queried_node2_index);
389    }
390
391    #[test]
392    fn test_default() {
393        let graph: PhysicalGraph<u32, f64> = PhysicalGraph::default();
394
395        assert_eq!(graph.graph.node_count(), 0);
396        assert_eq!(graph.graph.edge_count(), 0);
397    }
398
399    #[test]
400    fn test_repair_physical() {
401        let mut graph = PhysicalGraph::<Coord, f64>::new();
402        let node1 = TransitNode {
403            id: 1,
404            location: Coord { x: 0.0, y: 0.0 },
405        };
406        let node2 = TransitNode {
407            id: 2,
408            location: Coord { x: 1.0, y: 1.0 },
409        };
410
411        graph.add_transit_node(node1);
412        graph.add_transit_node(node2);
413
414        let edge = TransitEdge {
415            id: 1,
416            source: 1,
417            target: 2,
418            length: 1.0,
419            path: LineString(vec![Coord { x: 1.0, y: 1.0 }, Coord { x: 0.0, y: 0.0 }]),
420        };
421
422        graph.add_transit_edge(edge.clone());
423
424        graph.repair_edge(1, 2);
425
426        let edge = graph.get_transit_edge(1, 2).unwrap();
427
428        assert_eq!(
429            edge.path,
430            LineString(vec![Coord { x: 0.0, y: 0.0 }, Coord { x: 1.0, y: 1.0 }])
431        );
432    }
433}