h3ron_graph/graph/
h3edge.rs

1use std::ops::Add;
2
3use geo_types::MultiPolygon;
4use serde::{Deserialize, Serialize};
5
6use crate::algorithm::covered_area::{cells_covered_area, CoveredArea};
7use h3ron::collections::hashbrown::hash_map::Entry;
8use h3ron::collections::{H3CellMap, H3EdgeMap, RandomState};
9use h3ron::{H3Cell, H3DirectedEdge, HasH3Resolution};
10
11use crate::error::Error;
12use crate::graph::node::NodeType;
13use crate::graph::{EdgeWeight, GetEdge, GetStats};
14
15use super::GraphStats;
16
17#[derive(Serialize, Deserialize, Clone)]
18pub struct H3EdgeGraph<W> {
19    pub edges: H3EdgeMap<W>,
20    pub h3_resolution: u8,
21}
22
23impl<W> H3EdgeGraph<W>
24where
25    W: PartialOrd + PartialEq + Add + Copy,
26{
27    pub fn new(h3_resolution: u8) -> Self {
28        Self {
29            h3_resolution,
30            edges: Default::default(),
31        }
32    }
33
34    ///
35    /// This has to generate the node list first, so its rather
36    /// expensive to call.
37    pub fn num_nodes(&self) -> Result<usize, Error> {
38        Ok(self.nodes()?.len())
39    }
40
41    pub fn num_edges(&self) -> usize {
42        self.edges.len()
43    }
44
45    pub fn edge_weight(&self, edge: &H3DirectedEdge) -> Option<&W> {
46        self.edges.get(edge)
47    }
48
49    /// get all edges in the graph leading from this edge to neighbors
50    pub fn edges_from_cell(&self, cell: &H3Cell) -> Result<Vec<(&H3DirectedEdge, &W)>, Error> {
51        let edges = cell
52            .directed_edges()?
53            .iter()
54            .filter_map(|edge| self.edges.get_key_value(&edge))
55            .collect();
56        Ok(edges)
57    }
58
59    /// get all edges in the graph leading to this cell from its neighbors
60    pub fn edges_to_cell(&self, cell: &H3Cell) -> Result<Vec<(&H3DirectedEdge, &W)>, Error> {
61        let mut edges = vec![];
62        for disk_cell in cell.grid_disk(1)?.iter() {
63            if &disk_cell == cell {
64                continue;
65            }
66            edges.extend(
67                disk_cell
68                    .directed_edges()?
69                    .iter()
70                    .filter_map(|edge| self.edges.get_key_value(&edge)),
71            )
72        }
73        Ok(edges)
74    }
75
76    pub fn add_edge_using_cells(
77        &mut self,
78        cell_from: H3Cell,
79        cell_to: H3Cell,
80        weight: W,
81    ) -> Result<(), Error> {
82        let edge = cell_from.directed_edge_to(cell_to)?;
83        self.add_edge(edge, weight)
84    }
85
86    pub fn add_edge_using_cells_bidirectional(
87        &mut self,
88        cell_from: H3Cell,
89        cell_to: H3Cell,
90        weight: W,
91    ) -> Result<(), Error> {
92        self.add_edge_using_cells(cell_from, cell_to, weight)?;
93        self.add_edge_using_cells(cell_to, cell_from, weight)
94    }
95
96    pub fn add_edge(&mut self, edge: H3DirectedEdge, weight: W) -> Result<(), Error> {
97        match self.edges.entry(edge) {
98            Entry::Occupied(mut occ) => {
99                if &weight < occ.get() {
100                    // lower weight takes precedence
101                    occ.insert(weight);
102                }
103            }
104            Entry::Vacant(vac) => {
105                vac.insert(weight);
106            }
107        }
108        Ok(())
109    }
110
111    pub fn try_add(&mut self, mut other: Self) -> Result<(), Error> {
112        if self.h3_resolution != other.h3_resolution {
113            return Err(Error::MixedH3Resolutions(
114                self.h3_resolution,
115                other.h3_resolution,
116            ));
117        }
118        for (edge, weight) in other.edges.drain() {
119            self.add_edge(edge, weight)?;
120        }
121        Ok(())
122    }
123
124    /// cells which are valid targets to route to
125    ///
126    /// This is a rather expensive operation as nodes are not stored anywhere
127    /// and need to be extracted from the edges.
128    pub fn nodes(&self) -> Result<H3CellMap<NodeType>, Error> {
129        log::debug!(
130            "extracting nodes from the graph edges @ r={}",
131            self.h3_resolution
132        );
133        extract_nodes(&self.edges)
134    }
135
136    pub fn iter_edges(&self) -> impl Iterator<Item = (H3DirectedEdge, &W)> {
137        self.edges.iter().map(|(edge, weight)| (*edge, weight))
138    }
139}
140
141fn extract_nodes<W>(partition: &H3EdgeMap<W>) -> Result<H3CellMap<NodeType>, Error> {
142    let mut cells = H3CellMap::with_capacity_and_hasher(partition.len(), RandomState::default());
143    for edge in partition.keys() {
144        let cell_from = edge.origin_cell()?;
145        cells
146            .entry(cell_from)
147            .and_modify(|node_type| *node_type += NodeType::Origin)
148            .or_insert(NodeType::Origin);
149
150        let cell_to = edge.destination_cell()?;
151        cells
152            .entry(cell_to)
153            .and_modify(|node_type| *node_type += NodeType::Destination)
154            .or_insert(NodeType::Destination);
155    }
156    Ok(cells)
157}
158
159impl<W> HasH3Resolution for H3EdgeGraph<W> {
160    fn h3_resolution(&self) -> u8 {
161        self.h3_resolution
162    }
163}
164
165impl<W> GetStats for H3EdgeGraph<W>
166where
167    W: PartialEq + PartialOrd + Add + Copy,
168{
169    fn get_stats(&self) -> Result<GraphStats, Error> {
170        Ok(GraphStats {
171            h3_resolution: self.h3_resolution,
172            num_nodes: self.num_nodes()?,
173            num_edges: self.num_edges(),
174        })
175    }
176}
177
178impl<W> GetEdge for H3EdgeGraph<W>
179where
180    W: Copy,
181{
182    type EdgeWeightType = W;
183
184    fn get_edge(
185        &self,
186        edge: &H3DirectedEdge,
187    ) -> Result<Option<EdgeWeight<Self::EdgeWeightType>>, Error> {
188        Ok(self.edges.get(edge).map(|w| EdgeWeight::from(*w)))
189    }
190}
191
192impl<W> CoveredArea for H3EdgeGraph<W>
193where
194    W: PartialOrd + PartialEq + Add + Copy,
195{
196    type Error = Error;
197
198    fn covered_area(&self, reduce_resolution_by: u8) -> Result<MultiPolygon<f64>, Self::Error> {
199        cells_covered_area(
200            self.nodes()?.iter().map(|(cell, _)| cell),
201            self.h3_resolution(),
202            reduce_resolution_by,
203        )
204    }
205}
206
207/// change the resolution of a graph to a lower resolution
208///
209/// the `weight_selector_fn` decides which weight is assigned to a downsampled edge
210/// by selecting a weight from all full-resolution edges crossing the new cells boundary.
211///
212/// This has the potential to change the graphs topology as multiple edges get condensed into one.
213/// So for example routing results may differ in parts, but the computation time will be reduced by
214/// the reduced number of nodes and edges.
215pub fn downsample_graph<W, F>(
216    graph: &H3EdgeGraph<W>,
217    target_h3_resolution: u8,
218    weight_selector_fn: F,
219) -> Result<H3EdgeGraph<W>, Error>
220where
221    W: Sync + Send + Copy,
222    F: Fn(W, W) -> W + Sync + Send,
223{
224    if target_h3_resolution >= graph.h3_resolution {
225        return Err(Error::TooHighH3Resolution(target_h3_resolution));
226    }
227    log::debug!(
228        "downsampling graph from r={} to r={}",
229        graph.h3_resolution,
230        target_h3_resolution
231    );
232
233    let mut downsampled_edges = H3EdgeMap::with_capacity_and_hasher(
234        graph.edges.len()
235            / (graph.h3_resolution.saturating_sub(target_h3_resolution) as usize).pow(7),
236        RandomState::default(),
237    );
238
239    for (edge, weight) in graph.edges.iter() {
240        let edge_cells = edge.cells()?;
241        let cell_from = edge_cells.origin.get_parent(target_h3_resolution)?;
242        let cell_to = edge_cells.destination.get_parent(target_h3_resolution)?;
243        if cell_from != cell_to {
244            let downsampled_edge = cell_from.directed_edge_to(cell_to)?;
245
246            match downsampled_edges.entry(downsampled_edge) {
247                Entry::Occupied(mut occ) => {
248                    occ.insert(weight_selector_fn(*occ.get(), *weight));
249                }
250                Entry::Vacant(vac) => {
251                    vac.insert(*weight);
252                }
253            }
254        }
255    }
256    Ok(H3EdgeGraph {
257        edges: downsampled_edges,
258        h3_resolution: target_h3_resolution,
259    })
260}
261
262pub trait H3EdgeGraphBuilder<W>
263where
264    W: PartialOrd + PartialEq + Add + Copy,
265{
266    fn build_graph(self) -> Result<H3EdgeGraph<W>, Error>;
267}
268
269#[cfg(test)]
270mod tests {
271    use std::cmp::min;
272
273    use geo_types::{Coord, LineString};
274
275    use h3ron::H3Cell;
276
277    use super::{downsample_graph, H3EdgeGraph, NodeType};
278
279    #[test]
280    fn test_downsample() {
281        let full_h3_res = 8;
282        let cells: Vec<_> = h3ron::line(
283            &LineString::from(vec![Coord::from((23.3, 12.3)), Coord::from((24.2, 12.2))]),
284            full_h3_res,
285        )
286        .unwrap()
287        .into();
288        assert!(cells.len() > 100);
289
290        let mut graph = H3EdgeGraph::new(full_h3_res);
291        for w in cells.windows(2) {
292            graph.add_edge_using_cells(w[0], w[1], 20).unwrap();
293        }
294        assert!(graph.num_edges() > 50);
295        let downsampled_graph =
296            downsample_graph(&graph, full_h3_res.saturating_sub(3), min).unwrap();
297        assert!(downsampled_graph.num_edges() > 0);
298        assert!(downsampled_graph.num_edges() < 20);
299    }
300
301    #[test]
302    fn test_graph_nodes() {
303        let res = 8;
304        let origin = H3Cell::from_coordinate(Coord::from((23.3, 12.3)), res).unwrap();
305        let edges: Vec<_> = origin
306            .directed_edges()
307            .unwrap()
308            .drain()
309            .map(|edge| (edge, edge.destination_cell().unwrap()))
310            .collect();
311
312        let mut graph = H3EdgeGraph::new(res);
313        graph.add_edge(edges[0].0, 1).unwrap();
314        graph.add_edge(edges[1].0, 1).unwrap();
315
316        let edges2: Vec<_> = edges[1]
317            .1
318            .directed_edges()
319            .unwrap()
320            .drain()
321            .map(|edge| (edge, edge.destination_cell().unwrap()))
322            .collect();
323        graph.add_edge(edges2[0].0, 1).unwrap();
324
325        let nodes = graph.nodes().unwrap();
326        assert_eq!(nodes.len(), 4);
327        assert_eq!(nodes.get(&origin), Some(&NodeType::Origin));
328        assert_eq!(nodes.get(&edges[0].1), Some(&NodeType::Destination));
329        assert_eq!(
330            nodes.get(&edges[1].1),
331            Some(&NodeType::OriginAndDestination)
332        );
333        assert_eq!(nodes.get(&edges2[0].1), Some(&NodeType::Destination));
334    }
335}