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