use super::{Children, GridPathCells, Triangle};
use crate::{
BaseCell, Boundary, CCW, CW, DEFAULT_CELL_INDEX, DirectedEdgeIndex,
Direction, EARTH_RADIUS_KM, Edge, ExtendedResolution, FaceSet, LatLng,
LocalIJ, NUM_HEX_VERTS, NUM_PENT_VERTS, Resolution, Vertex, VertexIndex,
coord::{CoordIJ, CoordIJK, FaceIJK, LocalIJK, Overage},
error::{
CompactionError, HexGridError, InvalidCellIndex, LocalIjError,
ResolutionMismatch,
},
grid,
index::{IndexMode, bits},
};
use alloc::vec::Vec;
use core::{
cmp::Ordering,
fmt, iter,
num::{NonZeroU8, NonZeroU64},
str::FromStr,
};
use either::Either;
#[cfg(feature = "geo")]
use geo::ToDegrees as _;
const HEXAGON_CHILDREN_COUNTS: [u64; 16] = [
1,
7,
49,
343,
2401,
16_807,
117_649,
823_543,
5_764_801,
40_353_607,
282_475_249,
1_977_326_743,
13_841_287_201,
96_889_010_407,
678_223_072_849,
4_747_561_509_943,
];
const PENTAGON_CHILDREN_COUNTS: [u64; 16] = [
1,
6,
41,
286,
2001,
14_006,
98_041,
686_286,
4_804_001,
33_628_006,
235_396_041,
1_647_772_286,
11_534_406_001,
80_740_842_006,
565_185_894_041,
3_956_301_258_286,
];
const REV_NEIGHBOR_DIRECTIONS_HEX: [u8; 6] = [5, 3, 4, 1, 0, 2];
const NEIGHBOR_SET_CW: [Direction; 7] = [
Direction::Center,
Direction::JK,
Direction::IJ,
Direction::J,
Direction::IK,
Direction::K,
Direction::I,
];
const NEIGHBOR_SET_CCW: [Direction; 7] = [
Direction::Center,
Direction::IK,
Direction::JK,
Direction::K,
Direction::IJ,
Direction::I,
Direction::J,
];
#[rustfmt::skip]
const PENTAGON_ROTATIONS: [[u8; 7]; 7] = [
[ 0, 0xff, 0, 0, 0, 0, 0], [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], [ 0, 0xff, 0, 0, 0, 1, 0], [ 0, 0xff, 0, 0, 1, 1, 0], [ 0, 0xff, 0, 5, 0, 0, 0], [ 0, 0xff, 5, 5, 0, 0, 0], [ 0, 0xff, 0, 0, 0, 0, 0], ];
const FAILED_DIRECTIONS: u64 =
0b0101000_1000100_0001100_1010000_0110000_0000000_0000000;
const fn validate_direction(
origin_dir: u8,
index_dir: u8,
) -> Result<(), LocalIjError> {
let offset = origin_dir * 7 + index_dir;
if (FAILED_DIRECTIONS & (1 << offset)) != 0 {
return Err(LocalIjError::Pentagon);
}
Ok(())
}
pub const DIRECTIONS: [Direction; NUM_HEX_VERTS as usize] = [
Direction::J,
Direction::JK,
Direction::K,
Direction::IK,
Direction::I,
Direction::IJ,
];
const BASE_CELL: BaseCell = BaseCell::new_unchecked(2);
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(transparent)]
pub struct CellIndex(NonZeroU64);
impl CellIndex {
#[must_use]
pub const fn resolution(self) -> Resolution {
bits::get_resolution(self.0.get())
}
#[must_use]
pub const fn base_cell(self) -> BaseCell {
let value = h3o_bit::get_base_cell(self.0.get());
BaseCell::new_unchecked(value)
}
#[must_use]
pub fn area_rads2(self) -> f64 {
let center = LatLng::from(self);
let boundary = self.boundary();
(0..boundary.len())
.map(|i| {
let j = (i + 1) % boundary.len();
Triangle::new(boundary[i], boundary[j], center).area()
})
.sum()
}
#[must_use]
pub fn area_km2(self) -> f64 {
self.area_rads2() * EARTH_RADIUS_KM * EARTH_RADIUS_KM
}
#[must_use]
pub fn area_m2(self) -> f64 {
self.area_km2() * 1000. * 1000.
}
#[must_use]
pub fn icosahedron_faces(self) -> FaceSet {
let resolution = self.resolution();
let is_pentagon = self.is_pentagon();
if is_pentagon && !resolution.is_class3() {
let child_resolution = resolution.succ().expect("child resolution");
let bits = bits::set_resolution(self.0.get(), child_resolution);
return Self::new_unchecked(bits::set_direction(
bits,
0,
child_resolution,
))
.icosahedron_faces();
}
let mut fijk = FaceIJK::from(self);
let mut vertices = [FaceIJK::default(); NUM_HEX_VERTS as usize];
let (vertex_count, resolution) = if is_pentagon {
(
usize::from(NUM_PENT_VERTS),
fijk.vertices(
resolution,
&mut vertices[..usize::from(NUM_PENT_VERTS)],
),
)
} else {
(
usize::from(NUM_HEX_VERTS),
fijk.vertices(resolution, &mut vertices),
)
};
let mut faces = FaceSet::new();
for vertex in &mut vertices[..vertex_count] {
if is_pentagon {
vertex.adjust_pentagon_vertex_overage(resolution);
} else {
vertex.adjust_overage_class2::<true>(resolution, false);
}
faces.insert(vertex.face);
}
faces
}
#[must_use]
pub fn is_pentagon(self) -> bool {
let bits = self.0.get();
let base = self.base_cell();
let resolution = usize::from(bits::get_resolution(bits));
let unused_count = usize::from(h3o_bit::MAX_RESOLUTION) - resolution;
let unused_bitsize = unused_count * h3o_bit::DIRECTION_BITSIZE;
let dirs_mask = (1 << (resolution * h3o_bit::DIRECTION_BITSIZE)) - 1;
let dirs = (bits >> unused_bitsize) & dirs_mask;
base.is_pentagon() && dirs == 0
}
#[must_use]
pub fn max_face_count(self) -> usize {
if self.is_pentagon() {
5
} else {
2
}
}
#[must_use]
pub fn direction_at(self, resolution: Resolution) -> Option<Direction> {
(resolution != Resolution::Zero && resolution <= self.resolution())
.then(|| {
let value = bits::get_direction(self.0.get(), resolution);
Direction::new_unchecked(value)
})
}
#[must_use]
pub fn parent(self, resolution: Resolution) -> Option<Self> {
(resolution <= self.resolution()).then(|| {
let bits = bits::set_resolution(self.0.get(), resolution);
Self::new_unchecked(bits::set_unused(bits, resolution))
})
}
#[must_use]
pub fn center_child(self, resolution: Resolution) -> Option<Self> {
(resolution >= self.resolution()).then(|| {
let start = self.resolution().direction_offset();
let stop = resolution.direction_offset();
let mask = (1 << (start - stop)) - 1;
let bits = bits::set_resolution(self.0.get(), resolution);
Self::new_unchecked(bits & !(mask << stop))
})
}
#[must_use]
#[expect(clippy::useless_let_if_seq, reason = "12.5% faster")]
pub fn children_count(self, resolution: Resolution) -> u64 {
let resolution = usize::from(resolution);
let curr_resolution = usize::from(bits::get_resolution(self.0.get()));
if curr_resolution > resolution {
return 0;
}
if curr_resolution == resolution {
return 1;
}
let n = resolution - curr_resolution;
let mut res = HEXAGON_CHILDREN_COUNTS[n];
if self.is_pentagon() {
res = PENTAGON_CHILDREN_COUNTS[n];
}
res
}
pub fn child_position(self, resolution: Resolution) -> Option<u64> {
let Some(parent_is_pentagon) =
self.parent(resolution).map(Self::is_pentagon)
else {
return None;
};
Some(if parent_is_pentagon {
Resolution::range(resolution, self.resolution())
.skip(1)
.map(|res| {
let parent_is_pentagon = self
.parent(res.pred().expect("resolution > 0"))
.map(Self::is_pentagon)
.expect("valid parent");
let mut digit = bits::get_direction(self.0.get(), res);
if parent_is_pentagon && digit > 0 {
digit -= 1;
}
if digit == 0 {
return 0;
}
let diff = u8::from(self.resolution()) - u8::from(res);
let hex_count = HEXAGON_CHILDREN_COUNTS[usize::from(diff)];
let count0 = if parent_is_pentagon {
PENTAGON_CHILDREN_COUNTS[usize::from(diff)]
} else {
hex_count
};
u64::from(digit - 1) * hex_count + count0
})
.sum()
} else {
Resolution::range(resolution, self.resolution())
.skip(1)
.map(|res| {
let diff = u8::from(self.resolution()) - u8::from(res);
let hex_count = HEXAGON_CHILDREN_COUNTS[usize::from(diff)];
let digit = bits::get_direction(self.0.get(), res);
u64::from(digit) * hex_count
})
.sum()
})
}
#[must_use]
pub fn child_at(
self,
mut position: u64,
resolution: Resolution,
) -> Option<Self> {
#[expect(
clippy::cast_possible_truncation,
reason = "safe thx to assert"
)]
fn set_direction(bits: u64, digit: u64, resolution: Resolution) -> u64 {
assert!(digit < 7);
bits::set_direction(bits, digit as u8, resolution)
}
if resolution < self.resolution()
|| position >= self.children_count(resolution)
{
return None;
}
let mut child = bits::set_resolution(self.0.get(), resolution);
let mut cur_res = self.resolution();
if self.is_pentagon() {
for res in Resolution::range(self.resolution(), resolution).skip(1)
{
cur_res = res;
let diff = u8::from(resolution) - u8::from(res);
let pent_count = PENTAGON_CHILDREN_COUNTS[usize::from(diff)];
if position < pent_count {
child = bits::set_direction(child, 0, res);
} else {
let count = HEXAGON_CHILDREN_COUNTS[usize::from(diff)];
position -= pent_count;
child = set_direction(child, (position / count) + 2, res);
position %= count;
break;
}
}
}
for res in Resolution::range(cur_res, resolution).skip(1) {
let diff = u8::from(resolution) - u8::from(res);
let count = HEXAGON_CHILDREN_COUNTS[usize::from(diff)];
child = set_direction(child, position / count, res);
position %= count;
}
Some(Self::new_unchecked(child))
}
pub fn children(
self,
resolution: Resolution,
) -> impl Iterator<Item = Self> {
Children::new(self, resolution)
}
pub fn compact(cells: &mut Vec<Self>) -> Result<(), CompactionError> {
let Some(first) = cells.first() else {
return Ok(()); };
let resolution = first.resolution();
if cells.iter().any(|cell| cell.resolution() != resolution) {
return Err(CompactionError::HeterogeneousResolution);
}
let old_len = cells.len();
cells.sort_unstable();
cells.dedup();
if cells.len() < old_len {
return Err(CompactionError::DuplicateInput);
}
let mut cursor = Cursor::new(cells);
'next_cell: while let Some(&cell) = cursor.peek() {
if resolution != Resolution::Zero
&& bits::get_direction(cell.into(), resolution) == 0
{
for res in Resolution::range(Resolution::Zero, resolution) {
let parent = cell.parent(res).expect("parent exists");
let count =
usize::try_from(parent.children_count(resolution))
.expect("child overflow");
let expected = compute_last_sibling(cell, res);
if cursor.peek_at(count - 1) == Some(&expected) {
cursor.consume(count);
cursor.write(parent);
continue 'next_cell;
}
}
}
cursor.consume(1);
cursor.write(cell);
}
cursor.flush();
Ok(())
}
pub fn uncompact_size(
compacted: impl IntoIterator<Item = Self>,
resolution: Resolution,
) -> u64 {
compacted
.into_iter()
.map(move |index| index.children_count(resolution))
.sum()
}
pub fn uncompact(
compacted: impl IntoIterator<Item = Self>,
resolution: Resolution,
) -> impl Iterator<Item = Self> {
compacted
.into_iter()
.flat_map(move |index| index.children(resolution))
}
#[must_use]
pub fn boundary(self) -> Boundary {
let fijk = FaceIJK::from(self);
let start = Vertex::new_unchecked(0);
if self.is_pentagon() {
fijk.pentagon_boundary(self.resolution(), start, NUM_PENT_VERTS)
} else {
fijk.hexagon_boundary(self.resolution(), start, NUM_HEX_VERTS)
}
}
pub fn base_cells() -> impl Iterator<Item = Self> {
(0..BaseCell::count()).map(|base_cell| {
Self::new_unchecked(h3o_bit::set_base_cell(
DEFAULT_CELL_INDEX,
base_cell,
))
})
}
#[must_use]
pub fn edge(self, destination: Self) -> Option<DirectedEdgeIndex> {
grid::direction_for_neighbor(self, destination).map(|direction| {
let bits = bits::set_mode(u64::from(self), IndexMode::DirectedEdge);
DirectedEdgeIndex::new_unchecked(bits::set_edge(
bits,
Edge::new_unchecked(direction.into()),
))
})
}
pub fn edges(self) -> impl Iterator<Item = DirectedEdgeIndex> {
let template = bits::set_mode(self.0.get(), IndexMode::DirectedEdge);
let deleted_edge = self.is_pentagon().then_some(1);
Edge::iter()
.filter(move |&edge| Some(u8::from(edge)) != deleted_edge)
.map(move |edge| {
DirectedEdgeIndex::new_unchecked(bits::set_edge(template, edge))
})
}
#[must_use]
pub fn vertex(self, vertex: Vertex) -> Option<VertexIndex> {
let vertex_count = self.vertex_count();
let resolution = self.resolution();
if u8::from(vertex) >= vertex_count {
return None;
}
let mut owner = self;
let mut owner_vertex = vertex;
if resolution == Resolution::Zero
|| bits::get_direction(self.into(), resolution)
!= u8::from(Direction::Center)
{
let left = vertex.to_direction(self);
let (left_cell, left_rotation) =
grid::neighbor_rotations(self, left, 0).expect("left neighbor");
if left_cell < owner {
owner = left_cell;
}
if resolution == Resolution::Zero
|| bits::get_direction(left_cell.into(), resolution)
!= u8::from(Direction::Center)
{
let right_vertex = Vertex::new_unchecked(
(u8::from(vertex) + vertex_count - 1) % vertex_count,
);
let right = right_vertex.to_direction(self);
let (right_cell, right_rotation) =
grid::neighbor_rotations(self, right, 0)
.expect("right neighbor");
if right_cell < owner {
owner = right_cell;
let direction = if owner.is_pentagon() {
grid::direction_for_neighbor(owner, self)
.expect("direction to the right")
} else {
debug_assert_ne!(right, Direction::Center);
let offset = (REV_NEIGHBOR_DIRECTIONS_HEX
[usize::from(right) - 1]
+ right_rotation)
% NUM_HEX_VERTS;
DIRECTIONS[usize::from(offset)]
};
owner_vertex = direction.vertex(owner);
}
}
if owner == left_cell {
let direction = if owner.is_pentagon() {
grid::direction_for_neighbor(owner, self)
.expect("direction to the left")
} else {
debug_assert_ne!(left, Direction::Center);
let offset = (REV_NEIGHBOR_DIRECTIONS_HEX
[usize::from(left) - 1]
+ left_rotation)
% NUM_HEX_VERTS;
DIRECTIONS[usize::from(offset)]
};
owner_vertex = Vertex::new_unchecked(
(u8::from(direction.vertex(owner)) + 1)
% owner.vertex_count(),
);
}
}
let bits = bits::set_mode(owner.into(), IndexMode::Vertex);
Some(VertexIndex::new_unchecked(bits::set_vertex(
bits,
owner_vertex,
)))
}
pub fn vertexes(self) -> impl Iterator<Item = VertexIndex> {
(0..self.vertex_count()).map(move |vertex| {
let vertex = Vertex::new_unchecked(vertex);
self.vertex(vertex).expect("cell vertex")
})
}
#[must_use]
pub fn grid_disk<T>(self, k: u32) -> T
where
T: FromIterator<Self>,
{
self.grid_disk_fast(k)
.collect::<Option<T>>()
.unwrap_or_else(|| self.grid_disk_safe(k).collect())
}
pub fn grid_disk_safe(self, k: u32) -> impl Iterator<Item = Self> {
if k == 0 {
return Either::Right(iter::once(self));
}
Either::Left(
grid::DiskDistancesSafe::new(self, k).map(|(cell, _)| cell),
)
}
pub fn grid_disk_fast(self, k: u32) -> impl Iterator<Item = Option<Self>> {
if k == 0 {
return Either::Right(if self.is_pentagon() {
Either::Right(iter::once(None))
} else {
Either::Left(iter::once(Some(self)))
});
}
Either::Left(
grid::DiskDistancesUnsafe::new(self, k)
.map(|value| value.map(|(cell, _)| cell)),
)
}
#[must_use]
pub fn grid_disk_distances<T>(self, k: u32) -> T
where
T: FromIterator<(Self, u32)>,
{
self.grid_disk_distances_fast(k)
.collect::<Option<T>>()
.unwrap_or_else(|| self.grid_disk_distances_safe(k).collect())
}
pub fn grid_disk_distances_safe(
self,
k: u32,
) -> impl Iterator<Item = (Self, u32)> {
if k == 0 {
return Either::Right(iter::once((self, 0)));
}
Either::Left(grid::DiskDistancesSafe::new(self, k))
}
pub fn grid_disk_distances_fast(
self,
k: u32,
) -> impl Iterator<Item = Option<(Self, u32)>> {
if k == 0 {
return Either::Right(if self.is_pentagon() {
Either::Right(iter::once(None))
} else {
Either::Left(iter::once(Some((self, 0))))
});
}
Either::Left(grid::DiskDistancesUnsafe::new(self, k))
}
pub fn grid_disks_fast(
indexes: impl IntoIterator<Item = Self>,
k: u32,
) -> impl Iterator<Item = Option<Self>> {
indexes
.into_iter()
.flat_map(move |index| index.grid_disk_fast(k))
}
pub fn grid_ring_fast(self, k: u32) -> impl Iterator<Item = Option<Self>> {
if k == 0 {
return Either::Right(iter::once(Some(self)));
}
Either::Left(
grid::RingUnsafe::new(self, k)
.map_or_else(|| Either::Left(iter::once(None)), Either::Right),
)
}
#[must_use]
pub fn grid_ring<T>(self, k: u32) -> T
where
T: FromIterator<Self>,
{
self.grid_ring_fast(k)
.collect::<Option<T>>()
.unwrap_or_else(|| {
self.grid_disk_distances_safe(k)
.filter_map(|(cell, dist)| (dist == k).then_some(cell))
.collect()
})
}
pub fn grid_distance(self, to: Self) -> Result<i32, LocalIjError> {
let src = self.to_local_ijk(self)?;
let dst = to.to_local_ijk(self)?;
Ok(src.coord().distance(dst.coord()))
}
pub fn grid_path_cells_size(self, to: Self) -> Result<i32, LocalIjError> {
self.grid_distance(to).map(|distance| distance + 1)
}
pub fn grid_path_cells(
self,
to: Self,
) -> Result<impl Iterator<Item = Result<Self, LocalIjError>>, LocalIjError>
{
GridPathCells::new(self, to)
}
pub fn is_neighbor_with(
self,
index: Self,
) -> Result<bool, ResolutionMismatch> {
if self == index {
return Ok(false);
}
let cur_res = self.resolution();
let idx_res = index.resolution();
if cur_res != idx_res {
return Err(ResolutionMismatch);
}
if cur_res > Resolution::Zero {
let parent_res = cur_res.pred().expect("parent resolution");
let cur_parent = self.parent(parent_res).expect("current's parent");
let idx_parent = index.parent(parent_res).expect("index's parent");
if cur_parent == idx_parent {
let cur_res_digit = bits::get_direction(self.into(), cur_res);
let idx_res_digit = bits::get_direction(index.into(), idx_res);
if cur_res_digit == u8::from(Direction::Center)
|| idx_res_digit == u8::from(Direction::Center)
{
return Ok(true);
}
if u8::from(NEIGHBOR_SET_CW[usize::from(cur_res_digit)])
== idx_res_digit
|| u8::from(NEIGHBOR_SET_CCW[usize::from(cur_res_digit)])
== idx_res_digit
{
return Ok(true);
}
}
}
Ok(self
.grid_disk_fast(1)
.try_fold(false, |acc, item| {
item.map(|neighbor| acc || index == neighbor)
})
.unwrap_or_else(|| {
self.grid_disk_safe(1).any(|neighbor| index == neighbor)
}))
}
pub fn to_local_ij(self, origin: Self) -> Result<LocalIJ, LocalIjError> {
let lijk = self.to_local_ijk(origin)?;
let coord = CoordIJ::from(&lijk.coord);
Ok(LocalIJ::new(lijk.anchor, coord))
}
pub fn succ(self) -> Option<Self> {
const IJ_MASK: u64 = 0o666666666666666;
let resolution = self.resolution();
let res_offset = self.resolution().direction_offset();
let mut bits = u64::from(self) >> res_offset;
let bitpos = (bits ^ IJ_MASK).trailing_zeros() as usize;
let respos = bitpos / h3o_bit::DIRECTION_BITSIZE;
let mask = !((1 << (respos * h3o_bit::DIRECTION_BITSIZE)) - 1);
bits &= mask;
bits = bits::set_unused(bits << res_offset, resolution);
if respos < usize::from(resolution) {
let one = 1 << (res_offset + respos * h3o_bit::DIRECTION_BITSIZE);
bits += one;
return Some(Self::try_from(bits).unwrap_or_else(|_| {
bits += one;
Self::new_unchecked(bits)
}));
}
let base_cell = u8::from(self.base_cell());
(base_cell != 121)
.then(|| h3o_bit::set_base_cell(bits, base_cell + 1))
.map(Self::new_unchecked)
}
pub fn pred(self) -> Option<Self> {
let resolution = self.resolution();
let res_offset = self.resolution().direction_offset();
let mut bits = u64::from(self) >> res_offset;
let bitpos = bits.trailing_zeros() as usize;
let respos = bitpos / h3o_bit::DIRECTION_BITSIZE;
let mask = (1 << (respos * h3o_bit::DIRECTION_BITSIZE)) - 1;
bits |= 0o666666666666666 & mask;
bits = bits::set_unused(bits << res_offset, resolution);
if respos < usize::from(resolution) {
let one = 1 << (res_offset + respos * h3o_bit::DIRECTION_BITSIZE);
bits -= one;
return Some(Self::try_from(bits).unwrap_or_else(|_| {
bits -= one;
Self::new_unchecked(bits)
}));
}
let base_cell = u8::from(self.base_cell());
(base_cell != 0)
.then(|| h3o_bit::set_base_cell(bits, base_cell - 1))
.map(Self::new_unchecked)
}
#[must_use]
pub fn first(resolution: Resolution) -> Self {
let bits = bits::set_resolution(0x0800_0000_0000_0000, resolution);
Self::new_unchecked(bits::set_unused(bits, resolution))
}
#[must_use]
pub fn last(resolution: Resolution) -> Self {
let bits = bits::set_resolution(0x080f_3b6d_b6db_6db6, resolution);
Self::new_unchecked(bits::set_unused(bits, resolution))
}
pub(crate) fn new_unchecked(value: u64) -> Self {
debug_assert!(Self::try_from(value).is_ok(), "invalid cell index");
Self(NonZeroU64::new(value).expect("valid cell index"))
}
pub(crate) fn vertex_rotations(self) -> u8 {
let fijk = FaceIJK::from(self);
let base_cell = self.base_cell();
let leading_dir = bits::first_axe(self.into());
let base_fijk = FaceIJK::from(base_cell);
let mut ccw_rot60 = base_cell.rotation_count(fijk.face);
if base_cell.is_pentagon() {
let jk = Direction::JK.axe().expect("JK");
let ik = Direction::IK.axe().expect("IK");
let dir_faces = base_cell.pentagon_direction_faces();
if fijk.face != base_fijk.face
&& (base_cell.is_polar_pentagon()
|| fijk.face == dir_faces[usize::from(ik.get()) - 2])
{
ccw_rot60 = (ccw_rot60 + 1) % 6;
}
if leading_dir == Some(jk)
&& fijk.face == dir_faces[usize::from(ik.get()) - 2]
{
ccw_rot60 = (ccw_rot60 + 5) % 6;
} else if leading_dir == Some(ik)
&& fijk.face == dir_faces[usize::from(jk.get()) - 2]
{
ccw_rot60 = (ccw_rot60 + 1) % 6;
}
}
ccw_rot60
}
pub(super) fn to_local_ijk(
mut self,
origin: Self,
) -> Result<LocalIJK, LocalIjError> {
let origin_res = origin.resolution();
let index_res = self.resolution();
if origin_res != index_res {
return Err(LocalIjError::ResolutionMismatch);
}
let origin_base_cell = origin.base_cell();
let base_cell = self.base_cell();
let mut dir = Direction::Center;
let mut rev_dir = if origin_base_cell == base_cell {
Direction::Center
} else {
dir = origin_base_cell.direction(base_cell).ok_or_else(|| {
HexGridError::new(
"cannot unfold (base cells are not neighbors)",
)
})?;
base_cell
.direction(origin_base_cell)
.expect("reverse direction")
};
let origin_on_pent = origin_base_cell.is_pentagon();
let index_on_pent = base_cell.is_pentagon();
if dir != Direction::Center {
let base_cell_rotations =
origin_base_cell.neighbor_rotation(dir).into();
self = Self::new_unchecked(if index_on_pent {
(0..base_cell_rotations).fold(self.into(), |acc, _| {
rev_dir = if rev_dir == Direction::IK {
rev_dir.rotate60::<CW>(2)
} else {
rev_dir.rotate60_once::<CW>()
};
bits::pentagon_rotate60::<CW>(acc)
})
} else {
rev_dir = rev_dir.rotate60::<CW>(base_cell_rotations);
bits::rotate60::<CW>(self.into(), base_cell_rotations)
});
}
let mut ijk = FaceIJK::from_bits(self.into(), index_res, BASE_CELL)
.0
.coord;
if dir != Direction::Center {
debug_assert_ne!(base_cell, origin_base_cell);
debug_assert!(!(origin_on_pent && index_on_pent));
let (pentagon_rotations, direction_rotations) = if origin_on_pent {
let leading_dir = bits::first_axe(origin.into())
.map_or_else(|| 0, NonZeroU8::get);
validate_direction(leading_dir, dir.into())?;
let rotations = PENTAGON_ROTATIONS[usize::from(leading_dir)]
[usize::from(dir)];
(rotations, rotations)
} else if index_on_pent {
let leading_dir = bits::first_axe(self.into())
.map_or_else(|| 0, NonZeroU8::get);
validate_direction(leading_dir, rev_dir.into())?;
(
PENTAGON_ROTATIONS[usize::from(rev_dir)]
[usize::from(leading_dir)],
0,
)
} else {
(0, 0)
};
debug_assert!(pentagon_rotations != 0xff);
ijk = (0..pentagon_rotations)
.fold(ijk, |acc, _| acc.rotate60::<CW>());
let mut offset = CoordIJK::new(0, 0, 0).neighbor(dir);
for res in Resolution::range(Resolution::One, origin_res).rev() {
offset = if res.is_class3() {
offset.down_aperture7::<CCW>()
} else {
offset.down_aperture7::<CW>()
};
}
offset = (0..direction_rotations)
.fold(offset, |acc, _| acc.rotate60::<CW>());
ijk = (ijk + offset).normalize();
} else if origin_on_pent && index_on_pent {
debug_assert_eq!(base_cell, origin_base_cell);
let origin_leading_dir = bits::first_axe(origin.into())
.map_or_else(|| 0, NonZeroU8::get);
let index_leading_dir =
bits::first_axe(self.into()).map_or_else(|| 0, NonZeroU8::get);
validate_direction(origin_leading_dir, index_leading_dir)?;
let rotations = PENTAGON_ROTATIONS[usize::from(origin_leading_dir)]
[usize::from(index_leading_dir)];
ijk = (0..rotations).fold(ijk, |acc, _| acc.rotate60::<CW>());
}
Ok(LocalIJK {
anchor: origin,
coord: ijk,
})
}
fn vertex_count(self) -> u8 {
if self.is_pentagon() {
NUM_PENT_VERTS
} else {
NUM_HEX_VERTS
}
}
}
impl Ord for CellIndex {
fn cmp(&self, other: &Self) -> Ordering {
(h3o_bit::clr_resolution(self.0.get()))
.cmp(&h3o_bit::clr_resolution(other.0.get()))
}
}
impl PartialOrd for CellIndex {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<CellIndex> for u64 {
fn from(value: CellIndex) -> Self {
value.0.get()
}
}
impl From<CellIndex> for LatLng {
fn from(value: CellIndex) -> Self {
FaceIJK::from(value).to_latlng(value.resolution())
}
}
impl From<CellIndex> for FaceIJK {
fn from(value: CellIndex) -> Self {
let mut bits = value.0.get();
let base_cell = value.base_cell();
let resolution = value.resolution();
if base_cell.is_pentagon()
&& bits::first_axe(bits) == Direction::IK.axe()
{
bits = bits::rotate60::<{ CW }>(bits, 1);
}
let (mut fijk, overage) = Self::from_bits(bits, resolution, base_cell);
if !overage {
return fijk;
}
let original_coord = fijk.coord;
let class2_res = if resolution.is_class3() {
fijk.coord = fijk.coord.down_aperture7::<{ CW }>();
ExtendedResolution::down(resolution)
} else {
resolution.into()
};
let is_pent4 = base_cell.is_pentagon()
&& bits::first_axe(bits) == Direction::I.axe();
if fijk.adjust_overage_class2::<{ CW }>(class2_res, is_pent4)
!= Overage::None
{
if base_cell.is_pentagon() {
while fijk.adjust_overage_class2::<{ CW }>(class2_res, false)
!= Overage::None
{}
}
if class2_res != resolution.into() {
fijk.coord = fijk.coord.up_aperture7::<{ CW }>();
}
} else if class2_res != resolution.into() {
fijk.coord = original_coord;
}
fijk
}
}
impl TryFrom<u64> for CellIndex {
type Error = InvalidCellIndex;
fn try_from(value: u64) -> Result<Self, Self::Error> {
if (value >> 56) & 0b1000_0111 != 0 {
return Err(Self::Error::new(Some(value), "tainted reserved bits"));
}
if bits::get_mode(value) != u8::from(IndexMode::Cell) {
return Err(Self::Error::new(Some(value), "invalid index mode"));
}
let base = BaseCell::try_from(h3o_bit::get_base_cell(value))
.map_err(|_| Self::Error::new(Some(value), "invalid base cell"))?;
let resolution = usize::from(bits::get_resolution(value));
let unused_count = usize::from(h3o_bit::MAX_RESOLUTION) - resolution;
let unused_bitsize = unused_count * h3o_bit::DIRECTION_BITSIZE;
let unused_mask = (1 << unused_bitsize) - 1;
if (!value) & unused_mask != 0 {
return Err(Self::Error::new(
Some(value),
"invalid unused direction pattern",
));
}
let dirs_mask = (1 << (resolution * h3o_bit::DIRECTION_BITSIZE)) - 1;
let dirs = (value >> unused_bitsize) & dirs_mask;
if has_unused_direction(dirs) {
return Err(Self::Error::new(
Some(value),
"unexpected unused direction",
));
}
if base.is_pentagon() && resolution != 0 {
let offset = 64 - (resolution * h3o_bit::DIRECTION_BITSIZE);
if ((dirs << offset).leading_zeros() + 1).is_multiple_of(3) {
return Err(Self::Error::new(
Some(value),
"pentagonal cell index with a deleted subsequence",
));
}
}
Ok(Self(NonZeroU64::new(value).expect("non-zero cell index")))
}
}
#[cfg(feature = "geo")]
impl From<CellIndex> for geo::MultiPolygon {
fn from(cell: CellIndex) -> Self {
let mut polygons = crate::geom::cell_boundary(cell);
polygons.to_degrees_in_place();
polygons
}
}
impl FromStr for CellIndex {
type Err = InvalidCellIndex;
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 CellIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}-{:015o} ({})",
self.base_cell(),
u64::from(*self) & bits::DIRECTIONS_MASK,
self
)
}
}
impl fmt::Display for CellIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:x}")
}
}
impl fmt::Binary for CellIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Binary::fmt(&self.0, f)
}
}
impl fmt::Octal for CellIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Octal::fmt(&self.0, f)
}
}
impl fmt::LowerHex for CellIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
}
}
impl fmt::UpperHex for CellIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::UpperHex::fmt(&self.0, f)
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for CellIndex {
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)
})
}
}
#[inline(always)]
#[rustfmt::skip] const fn has_unused_direction(dirs: u64) -> bool {
const LO_MAGIC: u64 = 0b001_001_001_001_001_001_001_001_001_001_001_001_001_001_001;
const HI_MAGIC: u64 = 0b100_100_100_100_100_100_100_100_100_100_100_100_100_100_100;
((!dirs - LO_MAGIC) & (dirs & HI_MAGIC)) != 0
}
fn compute_last_sibling(cell: CellIndex, res: Resolution) -> CellIndex {
let diff = usize::from(u8::from(cell.resolution()) - u8::from(res));
let mask = (1_u64 << (diff * h3o_bit::DIRECTION_BITSIZE)) - 1;
let offset = cell.resolution().direction_offset();
let new_dirs = (0x0000_1b6d_b6db_6db6 & mask) << offset;
CellIndex::new_unchecked((u64::from(cell) & !(mask << offset)) | new_dirs)
}
struct Cursor<'a> {
buffer: &'a mut Vec<CellIndex>,
rd_idx: usize,
wr_idx: usize,
}
impl<'a> Cursor<'a> {
const fn new(buffer: &'a mut Vec<CellIndex>) -> Self {
Self {
buffer,
rd_idx: 0,
wr_idx: 0,
}
}
fn peek(&self) -> Option<&CellIndex> {
self.buffer.get(self.rd_idx)
}
fn peek_at(&self, index: usize) -> Option<&CellIndex> {
self.buffer.get(self.rd_idx + index)
}
fn consume(&mut self, count: usize) {
self.rd_idx += count;
assert!(self.rd_idx <= self.buffer.len(), "read overflow");
}
fn write(&mut self, cell: CellIndex) {
assert!(self.rd_idx > self.wr_idx, "write overflow");
self.buffer[self.wr_idx] = cell;
self.wr_idx += 1;
}
fn flush(self) {
self.buffer.truncate(self.wr_idx);
}
}
#[cfg(test)]
#[path = "./cell_tests.rs"]
mod tests;