h3ron/
to_geo.rs

1use crate::collections::H3CellMap;
2use std::os::raw::c_int;
3
4use geo::algorithm::euclidean_distance::EuclideanDistance;
5use geo_types::{Coord, Line, LineString, MultiLineString, Point, Polygon};
6
7use h3ron_h3_sys::H3Index;
8
9use crate::algorithm::smoothen_h3_linked_polygon;
10use crate::collections::indexvec::IndexVec;
11use crate::collections::CompactedCellVec;
12use crate::{Error, H3Cell};
13
14pub trait ToPolygon {
15    type Error;
16
17    fn to_polygon(&self) -> Result<Polygon<f64>, Self::Error>;
18}
19
20pub trait ToCoordinate {
21    type Error;
22
23    fn to_coordinate(&self) -> Result<Coord<f64>, Self::Error>;
24}
25
26pub trait ToLine {
27    type Error;
28
29    fn to_line(&self) -> Result<Line<f64>, Self::Error>;
30}
31
32pub trait ToLineString {
33    type Error;
34
35    fn to_linestring(&self) -> Result<LineString<f64>, Self::Error>;
36}
37
38pub trait ToMultiLineString {
39    type Error;
40
41    fn to_multilinestring(&self) -> Result<MultiLineString<f64>, Self::Error>;
42}
43
44/// join hexagon polygons to larger polygons where hexagons are touching each other
45pub trait ToLinkedPolygons {
46    type Error;
47
48    fn to_linked_polygons(&self, smoothen: bool) -> Result<Vec<Polygon<f64>>, Self::Error>;
49}
50
51impl ToLinkedPolygons for Vec<H3Cell> {
52    type Error = Error;
53
54    fn to_linked_polygons(&self, smoothen: bool) -> Result<Vec<Polygon<f64>>, Self::Error> {
55        let mut cells = self.clone();
56        cells.sort_unstable();
57        cells.dedup();
58        to_linked_polygons(&cells, smoothen)
59    }
60}
61
62impl ToLinkedPolygons for IndexVec<H3Cell> {
63    type Error = Error;
64
65    fn to_linked_polygons(&self, smoothen: bool) -> Result<Vec<Polygon<f64>>, Self::Error> {
66        let mut cells = self.iter().collect::<Vec<_>>();
67        cells.sort_unstable();
68        cells.dedup();
69        to_linked_polygons(&cells, smoothen)
70    }
71}
72
73impl ToLinkedPolygons for CompactedCellVec {
74    type Error = Error;
75
76    fn to_linked_polygons(&self, smoothen: bool) -> Result<Vec<Polygon<f64>>, Self::Error> {
77        match self.finest_resolution_contained() {
78            Some(resolution) => {
79                let mut cells: Vec<_> = self
80                    .iter_uncompacted_cells(resolution)
81                    .collect::<Result<Vec<_>, _>>()?;
82                cells.sort_unstable();
83                cells.dedup();
84                to_linked_polygons(&cells, smoothen)
85            }
86            None => Ok(Vec::new()),
87        }
88    }
89}
90
91/// join hexagon polygons to larger polygons where hexagons are touching each other
92///
93/// The cells will be grouped by the `align_to_h3_resolution`, so this will generate polygons
94/// not exceeding the area of that parent resolution.
95///
96/// Corners will be aligned to the corners of the parent resolution when they are less than an
97/// edge length away from them. This is to avoid gaps when `smoothen` is set to true.
98///
99/// This algorithm still needs some optimization to improve the runtime.
100pub trait ToAlignedLinkedPolygons {
101    type Error;
102
103    fn to_aligned_linked_polygons(
104        &self,
105        align_to_h3_resolution: u8,
106        smoothen: bool,
107    ) -> Result<Vec<Polygon<f64>>, Self::Error>;
108}
109
110impl ToAlignedLinkedPolygons for Vec<H3Cell> {
111    type Error = Error;
112
113    fn to_aligned_linked_polygons(
114        &self,
115        align_to_h3_resolution: u8,
116        smoothen: bool,
117    ) -> Result<Vec<Polygon<f64>>, Self::Error> {
118        let mut cells_grouped = H3CellMap::default();
119        for cell in self.iter() {
120            let parent_cell = cell.get_parent(align_to_h3_resolution)?;
121            cells_grouped
122                .entry(parent_cell)
123                .or_insert_with(Self::new)
124                .push(*cell);
125        }
126
127        let mut polygons = Vec::new();
128        for (parent_cell, cells) in cells_grouped.drain() {
129            if smoothen {
130                //
131                // align to the corners of the parent index
132                //
133
134                let parent_poly_vertices: Vec<_> = parent_cell
135                    .to_polygon()?
136                    .exterior()
137                    .0
138                    .iter()
139                    .map(|c| Point::from(*c))
140                    .collect();
141
142                // edge length of the child indexes
143                let edge_length = {
144                    let ring = cells[0].to_polygon()?;
145                    let p1 = Point::from(ring.exterior().0[0]);
146                    let p2 = Point::from(ring.exterior().0[1]);
147                    p1.euclidean_distance(&p2)
148                };
149
150                for poly in to_linked_polygons(&cells, true)?.drain(..) {
151                    let points_new: Vec<_> = poly
152                        .exterior()
153                        .0
154                        .iter()
155                        .map(|c| {
156                            let p = Point::from(*c);
157                            parent_poly_vertices
158                                .iter()
159                                .find(|pv| p.euclidean_distance(*pv) < edge_length)
160                                .map_or_else(|| *c, |pv| pv.0)
161                        })
162                        .collect();
163                    polygons.push(Polygon::new(
164                        LineString::from(points_new),
165                        poly.interiors().to_vec(),
166                    ));
167                }
168            } else {
169                polygons.append(&mut to_linked_polygons(&cells, false)?);
170            }
171        }
172        Ok(polygons)
173    }
174}
175
176/// convert cells to linked polygons
177///
178/// With `smoothen` an optional smoothing can be applied to the polygons to remove
179/// H3 artifacts.
180///
181/// for this case, the slice must already be deduplicated, and all h3 cells must be the same resolutions
182pub fn to_linked_polygons(cells: &[H3Cell], smoothen: bool) -> Result<Vec<Polygon<f64>>, Error> {
183    if cells.is_empty() {
184        return Ok(vec![]);
185    }
186    unsafe {
187        let mut lgp = h3ron_h3_sys::LinkedGeoPolygon {
188            first: std::ptr::null_mut(),
189            last: std::ptr::null_mut(),
190            next: std::ptr::null_mut(),
191        };
192        // the following requires `repr(transparent)` on H3Cell
193        let h3index_slice =
194            std::slice::from_raw_parts(cells.as_ptr().cast::<H3Index>(), cells.len());
195        Error::check_returncode(h3ron_h3_sys::cellsToLinkedMultiPolygon(
196            h3index_slice.as_ptr(),
197            h3index_slice.len() as c_int,
198            &mut lgp,
199        ))?;
200
201        let mut polygons = vec![];
202        let mut cur_linked_geo_polygon = Some(&lgp);
203        while let Some(poly) = cur_linked_geo_polygon.as_ref() {
204            let mut exterior = None;
205            let mut interiors = vec![];
206            let mut linked_loop_i = 0;
207            let mut cur_linked_geo_loop = poly.first.as_ref();
208            while let Some(linked_loop) = cur_linked_geo_loop {
209                let mut coordinates = vec![];
210                let mut cur_linked_geo_coord = linked_loop.first.as_ref();
211                while let Some(linked_coord) = cur_linked_geo_coord {
212                    coordinates.push((
213                        linked_coord.vertex.lng.to_degrees(),
214                        linked_coord.vertex.lat.to_degrees(),
215                    ));
216                    cur_linked_geo_coord = linked_coord.next.as_ref();
217                }
218
219                if coordinates.len() >= 3 {
220                    let linestring = LineString::from(coordinates);
221                    if linked_loop_i == 0 {
222                        exterior = Some(linestring);
223                    } else {
224                        interiors.push(linestring);
225                    }
226                }
227
228                linked_loop_i += 1;
229                cur_linked_geo_loop = linked_loop.next.as_ref();
230            }
231            if let Some(ext) = exterior {
232                let poly = Polygon::new(ext, interiors);
233                if smoothen {
234                    polygons.push(smoothen_h3_linked_polygon(&poly));
235                } else {
236                    polygons.push(poly);
237                }
238            }
239            cur_linked_geo_polygon = poly.next.as_ref();
240        }
241        h3ron_h3_sys::destroyLinkedMultiPolygon(&mut lgp);
242        Ok(polygons)
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use geo_types::Coord;
249
250    use crate::{H3Cell, ToLinkedPolygons};
251
252    #[test]
253    fn donut_linked_polygon() {
254        let ring = H3Cell::from_coordinate(Coord::from((23.3, 12.3)), 6)
255            .unwrap()
256            .grid_ring_unsafe(1)
257            .unwrap();
258        let polygons = ring.to_linked_polygons(false).unwrap();
259        assert_eq!(polygons.len(), 1);
260        assert_eq!(polygons[0].exterior().0.len(), 19);
261        assert_eq!(polygons[0].interiors().len(), 1);
262        assert_eq!(polygons[0].interiors()[0].0.len(), 7);
263    }
264}