use super::{IndexMode, bits};
use crate::{
Boundary, CellIndex, Direction, EARTH_RADIUS_KM, coord::FaceIJK, error,
grid,
};
use core::{cmp::Ordering, fmt, num::NonZeroU64, str::FromStr};
const MIN: u8 = 1;
const MAX: u8 = 6;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Edge(u8);
impl Edge {
pub fn iter() -> impl Iterator<Item = Self> {
(MIN..=MAX).map(Self::new_unchecked)
}
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)
}
}
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DirectedEdgeIndex(NonZeroU64);
impl DirectedEdgeIndex {
#[must_use]
pub fn edge(self) -> Edge {
Edge::new_unchecked(bits::get_edge(self.0.get()))
}
#[must_use]
pub fn origin(self) -> CellIndex {
let bits = bits::set_mode(self.0.get(), IndexMode::Cell);
CellIndex::new_unchecked(bits::clr_edge(bits))
}
#[must_use]
pub fn destination(self) -> CellIndex {
let direction = Direction::from(self.edge());
let origin = self.origin();
grid::neighbor_rotations(origin, direction, 0)
.expect("destination cell index")
.0
}
#[must_use]
pub fn cells(self) -> (CellIndex, CellIndex) {
(self.origin(), self.destination())
}
#[must_use]
pub fn boundary(self) -> Boundary {
let direction = Direction::from(self.edge());
let origin = self.origin();
let start_vertex = direction.vertex(origin);
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)
}
}
#[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()
}
#[must_use]
pub fn length_km(self) -> f64 {
self.length_rads() * EARTH_RADIUS_KM
}
#[must_use]
pub fn length_m(self) -> f64 {
self.length_km() * 1000.
}
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 {
const MASK: u64 = 0xf80f_ffff_ffff_ffff;
(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"));
}
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))?;
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"));
}
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 = "geo")]
impl From<DirectedEdgeIndex> for geo::Line {
fn from(value: DirectedEdgeIndex) -> Self {
let coords: Vec<geo::Coord> =
value.boundary().iter().copied().map(Into::into).collect();
assert_eq!(coords.len(), 2);
Self::new(coords[0], coords[1])
}
}
#[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;