h3o/index/
edge.rs

1use super::{IndexMode, bits};
2use crate::{
3    Boundary, CellIndex, Direction, EARTH_RADIUS_KM, coord::FaceIJK, error,
4    grid,
5};
6use core::{cmp::Ordering, fmt, num::NonZeroU64, str::FromStr};
7
8/// Minimum value for a cell edge.
9const MIN: u8 = 1;
10
11/// Maximum value for a cell edge.
12const MAX: u8 = 6;
13
14// -----------------------------------------------------------------------------
15
16/// Edge of an H3 cell.
17#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct Edge(u8);
20
21impl Edge {
22    /// Iterates over the valid directions.
23    ///
24    /// # Example
25    ///
26    /// ```
27    /// let edges = h3o::Edge::iter().collect::<Vec<_>>();
28    /// ```
29    pub fn iter() -> impl Iterator<Item = Self> {
30        // SAFETY: values from 0 to MAX are valid directions.
31        (MIN..=MAX).map(Self::new_unchecked)
32    }
33
34    /// Initializes a new cell edge using a value that may be out of range.
35    ///
36    /// # Safety
37    ///
38    /// The value must be a valid cell edge.
39    pub(crate) fn new_unchecked(value: u8) -> Self {
40        debug_assert!((MIN..=MAX).contains(&value), "cell edge out of range");
41        Self(value)
42    }
43}
44
45impl TryFrom<u8> for Edge {
46    type Error = error::InvalidEdge;
47
48    fn try_from(value: u8) -> Result<Self, Self::Error> {
49        if !(MIN..=MAX).contains(&value) {
50            return Err(Self::Error::new(value, "out of range"));
51        }
52        Ok(Self(value))
53    }
54}
55
56impl From<Edge> for u8 {
57    fn from(value: Edge) -> Self {
58        value.0
59    }
60}
61
62impl From<Edge> for u64 {
63    fn from(value: Edge) -> Self {
64        Self::from(value.0)
65    }
66}
67
68impl fmt::Display for Edge {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{}", self.0)
71    }
72}
73
74// -----------------------------------------------------------------------------
75
76/// Represents a single directed edge between two cells (an "origin" cell and a
77/// neighboring "destination" cell).
78///
79/// The index is encoded on 64-bit with the following bit layout:
80///
81/// ```text
82///  ┏━┳━━━┳━━━┳━━━━━━━━━━━━━━━━━━━━━━┈┈┈┈┈┈┈┈━━━━━━━┓
83///  ┃U┃ M ┃ E ┃                O                    ┃
84///  ┗━┻━━━┻━━━┻━━━━━━━━━━━━━━━━━━━━━━┈┈┈┈┈┈┈┈━━━━━━━┛
85/// 64 63 59   56                                    0
86/// ```
87///
88/// Where:
89/// - `U` is an unused reserved bit, always set to 0 (bit 63).
90/// - `M` is the index mode, always set to 2, coded on 4 bits (59-62).
91/// - `E` is the edge of the origin cell, in [1; 6], coded on 3 bits (56-58).
92/// - `O` is the origin cell index, coded on 56 bits (0-55).
93///
94/// References:
95/// - [H3 Index Representations](https://h3geo.org/docs/core-library/h3Indexing)
96/// - [H3 Index Bit Layout](https://observablehq.com/@nrabinowitz/h3-index-bit-layout?collection=@nrabinowitz/h3)
97/// - [H3 Index Inspector](https://observablehq.com/@nrabinowitz/h3-index-inspector?collection=@nrabinowitz/h3)
98#[derive(Clone, Copy, Eq, PartialEq, Hash)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub struct DirectedEdgeIndex(NonZeroU64);
101
102impl DirectedEdgeIndex {
103    /// Returns the cell edge.
104    ///
105    /// # Example
106    ///
107    /// ```
108    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a194e699ab7fff)?;
109    /// assert_eq!(index.edge(), h3o::Edge::try_from(3)?);
110    /// # Ok::<(), Box<dyn std::error::Error>>(())
111    /// ```
112    #[must_use]
113    pub fn edge(self) -> Edge {
114        // SAFETY: `EdgeIndex` only contains valid cell edge (invariant).
115        Edge::new_unchecked(bits::get_edge(self.0.get()))
116    }
117
118    /// Returns the origin hexagon from the directed edge index.
119    ///
120    /// # Example
121    ///
122    /// ```
123    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a194e699ab7fff)?;
124    /// assert_eq!(index.origin(), h3o::CellIndex::try_from(0x8a194e699ab7fff)?);
125    /// # Ok::<(), Box<dyn std::error::Error>>(())
126    /// ```
127    #[must_use]
128    pub fn origin(self) -> CellIndex {
129        let bits = bits::set_mode(self.0.get(), IndexMode::Cell);
130        CellIndex::new_unchecked(bits::clr_edge(bits))
131    }
132
133    /// Returns the destination hexagon from the directed edge index.
134    ///
135    /// # Example
136    ///
137    /// ```
138    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a1_94e6_99ab_7fff)?;
139    /// assert_eq!(index.destination(), h3o::CellIndex::try_from(0x8a194e699a97fff)?);
140    /// # Ok::<(), Box<dyn std::error::Error>>(())
141    /// ```
142    #[must_use]
143    pub fn destination(self) -> CellIndex {
144        let direction = Direction::from(self.edge());
145        let origin = self.origin();
146        // Every edge has a destination.
147        grid::neighbor_rotations(origin, direction, 0)
148            .expect("destination cell index")
149            .0
150    }
151
152    /// Returns the `(origin, destination)` pair of cell index for this edge.
153    ///
154    /// # Example
155    ///
156    /// ```
157    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a1_94e6_99ab_7fff)?;
158    /// assert_eq!(index.cells(), (
159    ///     h3o::CellIndex::try_from(0x8a194e699ab7fff)?,
160    ///     h3o::CellIndex::try_from(0x8a194e699a97fff)?,
161    /// ));
162    /// # Ok::<(), Box<dyn std::error::Error>>(())
163    /// ```
164    #[must_use]
165    pub fn cells(self) -> (CellIndex, CellIndex) {
166        (self.origin(), self.destination())
167    }
168
169    /// Returns the coordinates defining the directed edge.
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a194e699ab7fff)?;
175    /// let boundary = index.boundary();
176    /// # Ok::<(), Box<dyn std::error::Error>>(())
177    /// ```
178    #[must_use]
179    pub fn boundary(self) -> Boundary {
180        // Get the origin and neighbor direction from the edge.
181        let direction = Direction::from(self.edge());
182        let origin = self.origin();
183
184        // Get the start vertex for the edge.
185        let start_vertex = direction.vertex(origin);
186
187        // Get the geo boundary for the appropriate vertexes of the origin. Note
188        // that while there are always 2 topological vertexes per edge, the
189        // resulting edge boundary may have an additional distortion vertex if
190        // it crosses an edge of the icosahedron.
191        let fijk = FaceIJK::from(origin);
192        let resolution = origin.resolution();
193        if origin.is_pentagon() {
194            fijk.pentagon_boundary(resolution, start_vertex, 2)
195        } else {
196            fijk.hexagon_boundary(resolution, start_vertex, 2)
197        }
198    }
199
200    /// Computes the length of this directed edge, in radians.
201    ///
202    /// # Example
203    ///
204    /// ```
205    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a194e699ab7fff)?;
206    /// float_eq::assert_float_eq!(
207    ///     index.length_rads(),
208    ///     1.1795418098325597e-5,
209    ///     abs <= 1e-11
210    /// );
211    /// # Ok::<(), Box<dyn std::error::Error>>(())
212    /// ```
213    #[must_use]
214    pub fn length_rads(self) -> f64 {
215        let boundary = self.boundary();
216
217        (0..boundary.len() - 1)
218            .map(|i| boundary[i].distance_rads(boundary[i + 1]))
219            .sum()
220    }
221
222    /// Computes the length of this directed edge, in kilometers.
223    ///
224    /// # Example
225    ///
226    /// ```
227    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a194e699ab7fff)?;
228    /// float_eq::assert_float_eq!(
229    ///     index.length_km(),
230    ///     0.07514869340636812,
231    ///     abs <= 1e-11
232    /// );
233    /// # Ok::<(), Box<dyn std::error::Error>>(())
234    /// ```
235    #[must_use]
236    pub fn length_km(self) -> f64 {
237        self.length_rads() * EARTH_RADIUS_KM
238    }
239
240    /// Computes the length of this directed edge, in meters.
241    ///
242    /// # Example
243    ///
244    /// ```
245    /// let index = h3o::DirectedEdgeIndex::try_from(0x13a194e699ab7fff)?;
246    /// float_eq::assert_float_eq!(
247    ///     index.length_m(),
248    ///     75.14869340636812,
249    ///     abs <= 1e-8
250    /// );
251    /// # Ok::<(), Box<dyn std::error::Error>>(())
252    /// ```
253    #[must_use]
254    pub fn length_m(self) -> f64 {
255        self.length_km() * 1000.
256    }
257
258    /// Initializes a new edge index a value that may be invalid.
259    ///
260    /// # Safety
261    ///
262    /// The value must be a valid edge index.
263    pub(crate) fn new_unchecked(value: u64) -> Self {
264        debug_assert!(Self::try_from(value).is_ok(), "invalid edge index");
265        Self(NonZeroU64::new(value).expect("valid edge index"))
266    }
267}
268
269impl Ord for DirectedEdgeIndex {
270    fn cmp(&self, other: &Self) -> Ordering {
271        /// Bitmask to hide the resolution and edge.
272        const MASK: u64 = 0xf80f_ffff_ffff_ffff;
273
274        // Order by index first, then by edge.
275        (self.0.get() & MASK, self.edge())
276            .cmp(&(other.0.get() & MASK, other.edge()))
277    }
278}
279
280impl PartialOrd for DirectedEdgeIndex {
281    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
282        Some(self.cmp(other))
283    }
284}
285
286impl From<DirectedEdgeIndex> for u64 {
287    fn from(value: DirectedEdgeIndex) -> Self {
288        value.0.get()
289    }
290}
291
292impl TryFrom<u64> for DirectedEdgeIndex {
293    type Error = error::InvalidDirectedEdgeIndex;
294
295    fn try_from(value: u64) -> Result<Self, Self::Error> {
296        if bits::get_mode(value) != u8::from(IndexMode::DirectedEdge) {
297            return Err(Self::Error::new(Some(value), "invalid index mode"));
298        }
299
300        // Clear the highest byte and validate the index part.
301        let bits = bits::set_mode(value, IndexMode::Cell);
302        let bits = bits::clr_edge(bits);
303        CellIndex::try_from(bits)
304            .map_err(|err| Self::Error::new(Some(value), err.reason))?;
305
306        // An hexagon has 6 edges (1-6), while a pentagon only has 5 (2-6).
307        let min_edge =
308            1 + u8::from(CellIndex::new_unchecked(bits).is_pentagon());
309        if !(min_edge..=MAX).contains(&bits::get_edge(value)) {
310            return Err(Self::Error::new(Some(value), "invalid cell edge"));
311        }
312
313        // XXX: 0 is rejected by the mode check (mode cannot be 0).
314        Ok(Self(NonZeroU64::new(value).expect("non-zero edge index")))
315    }
316}
317
318impl FromStr for DirectedEdgeIndex {
319    type Err = error::InvalidDirectedEdgeIndex;
320
321    fn from_str(s: &str) -> Result<Self, Self::Err> {
322        u64::from_str_radix(s, 16)
323            .map_err(|_| Self::Err {
324                value: None,
325                reason: "invalid 64-bit hex number",
326            })
327            .and_then(Self::try_from)
328    }
329}
330
331impl fmt::Debug for DirectedEdgeIndex {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(
334            f,
335            "{}-{:015o}_{} ({})",
336            self.origin().base_cell(),
337            u64::from(*self) & bits::DIRECTIONS_MASK,
338            self.edge(),
339            self
340        )
341    }
342}
343
344impl fmt::Display for DirectedEdgeIndex {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        write!(f, "{self:x}")
347    }
348}
349
350impl fmt::Binary for DirectedEdgeIndex {
351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
352        fmt::Binary::fmt(&self.0, f)
353    }
354}
355
356impl fmt::Octal for DirectedEdgeIndex {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        fmt::Octal::fmt(&self.0, f)
359    }
360}
361
362impl fmt::LowerHex for DirectedEdgeIndex {
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        fmt::LowerHex::fmt(&self.0, f)
365    }
366}
367
368impl fmt::UpperHex for DirectedEdgeIndex {
369    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370        fmt::UpperHex::fmt(&self.0, f)
371    }
372}
373
374#[cfg(feature = "geo")]
375impl From<DirectedEdgeIndex> for geo::Line {
376    fn from(value: DirectedEdgeIndex) -> Self {
377        let coords: Vec<geo::Coord> =
378            value.boundary().iter().copied().map(Into::into).collect();
379
380        // We only have two point (start and end) as boundary for an edge.
381        assert_eq!(coords.len(), 2);
382
383        Self::new(coords[0], coords[1])
384    }
385}
386
387#[cfg(feature = "arbitrary")]
388impl<'a> arbitrary::Arbitrary<'a> for DirectedEdgeIndex {
389    fn arbitrary(
390        data: &mut arbitrary::Unstructured<'a>,
391    ) -> arbitrary::Result<Self> {
392        u64::arbitrary(data).and_then(|byte| {
393            Self::try_from(byte).map_err(|_| arbitrary::Error::IncorrectFormat)
394        })
395    }
396}
397
398#[cfg(test)]
399#[path = "./edge_tests.rs"]
400mod tests;