use crate::base_cells::{
_get_base_cell_direction,
_is_base_cell_pentagon,
};
use crate::coords::face_ijk::{ADJACENT_FACE_DIR, FACE_NEIGHBORS, INVALID_FACE}; use crate::coords::ijk::{
_down_ap7, _down_ap7r, _ijk_add, _ijk_normalize, _ijk_rotate60_ccw, _ijk_rotate60_cw,
_ijk_sub, ij_to_ijk, ijk_to_ij,
};
use crate::h3_index::inspection::is_valid_cell;
use crate::h3_index::{
_face_ijk_to_h3, _h3_leading_non_zero_digit, _h3_rotate_pent60_ccw,
_h3_rotate_pent60_cw, _h3_to_face_ijk, get_base_cell, get_resolution, is_pentagon,
is_resolution_class_iii,
};
use crate::types::{CoordIJ, CoordIJK, Direction, FaceIJK, H3Error, H3Index, H3_NULL};
#[rustfmt::skip]
static PENTAGON_ROTATIONS: [[i32; 7]; 7] = [
[0, -1, 0, 0, 0, 0, 0], [-1, -1, -1, -1, -1, -1, -1], [0, -1, 0, 0, 0, 1, 0], [0, -1, 0, 0, 1, 1, 0], [0, -1, 0, 5, 0, 0, 0], [0, -1, 5, 5, 0, 0, 0], [0, -1, 0, 0, 0, 0, 0], ];
#[rustfmt::skip]
static PENTAGON_ROTATIONS_REVERSE: [[i32; 7]; 7] = [
[0, 0, 0, 0, 0, 0, 0], [-1, -1, -1, -1, -1, -1, -1], [0, 1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 1, 0], [0, 5, 0, 0, 0, 0, 0], [0, 5, 0, 5, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], ];
#[rustfmt::skip]
static PENTAGON_ROTATIONS_REVERSE_NONPOLAR: [[i32; 7]; 7] = [
[0, 0, 0, 0, 0, 0, 0],
[-1, -1, -1, -1, -1, -1, -1],
[0, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 1, 0],
[0, 5, 0, 0, 0, 0, 0],
[0, 1, 0, 5, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0],
];
#[rustfmt::skip]
static PENTAGON_ROTATIONS_REVERSE_POLAR: [[i32; 7]; 7] = [
[0, 0, 0, 0, 0, 0, 0],
[-1, -1, -1, -1, -1, -1, -1],
[0, 1, 1, 1, 1, 1, 1],
[0, 1, 0, 0, 0, 1, 0],
[0, 1, 0, 0, 1, 1, 1],
[0, 1, 0, 5, 1, 1, 0],
[0, 1, 1, 0, 1, 1, 1],
];
#[rustfmt::skip]
static FAILED_DIRECTIONS: [[bool; 7]; 7] = [
[false, false, false, false, false, false, false], [false, false, false, false, false, false, false], [false, false, false, false, true, true, false], [false, false, false, false, true, false, true ], [false, false, true, true, false, false, false], [false, false, true, false, false, false, true ], [false, false, false, true, false, true, false], ];
pub fn cell_to_local_ijk(origin: H3Index, index: H3Index, out_ijk: &mut CoordIJK) -> Result<(), H3Error> {
let res = get_resolution(origin);
if res != get_resolution(index) {
return Err(H3Error::ResMismatch);
}
if !is_valid_cell(origin) || !is_valid_cell(index) {
return Err(H3Error::CellInvalid);
}
let origin_base_cell = get_base_cell(origin);
let index_base_cell = get_base_cell(index);
let mut fijk_origin_canonical = FaceIJK::default();
_h3_to_face_ijk(origin, &mut fijk_origin_canonical)?;
let mut fijk_index_canonical = FaceIJK::default();
_h3_to_face_ijk(index, &mut fijk_index_canonical)?;
if fijk_origin_canonical.face == fijk_index_canonical.face {
_ijk_sub(&fijk_index_canonical.coord, &fijk_origin_canonical.coord, out_ijk);
} else {
let mut fijk_index_on_origin_face = fijk_index_canonical;
if _face_to_face_ijk_inplace(&mut fijk_index_on_origin_face, res, fijk_origin_canonical.face).is_err() {
return Err(H3Error::Failed); }
_ijk_sub(&fijk_index_on_origin_face.coord, &fijk_origin_canonical.coord, out_ijk);
}
let origin_is_pent = is_pentagon(origin);
let index_is_pent = is_pentagon(index);
if origin_is_pent || index_is_pent {
let origin_center_digit_for_rotation_logic = _h3_leading_non_zero_digit(origin);
let index_related_digit_for_rotation_logic: Direction;
if origin_base_cell == index_base_cell {
index_related_digit_for_rotation_logic = _h3_leading_non_zero_digit(index);
} else {
index_related_digit_for_rotation_logic = _get_base_cell_direction(origin_base_cell, index_base_cell);
if index_related_digit_for_rotation_logic == Direction::InvalidDigit && origin_base_cell != index_base_cell {
return Err(H3Error::Failed);
}
}
if origin_is_pent && index_is_pent {
if origin_base_cell != index_base_cell {
return Err(H3Error::NotNeighbors);
}
if FAILED_DIRECTIONS[origin_center_digit_for_rotation_logic as usize]
[index_related_digit_for_rotation_logic as usize]
{
return Err(H3Error::Pentagon);
}
let num_rotations = PENTAGON_ROTATIONS[origin_center_digit_for_rotation_logic as usize]
[index_related_digit_for_rotation_logic as usize];
if num_rotations == -1 {
return Err(H3Error::Pentagon);
}
for _ in 0..num_rotations {
_ijk_rotate60_cw(out_ijk);
}
} else if origin_is_pent {
if FAILED_DIRECTIONS[origin_center_digit_for_rotation_logic as usize]
[index_related_digit_for_rotation_logic as usize]
{
return Err(H3Error::Pentagon);
}
let num_rotations = PENTAGON_ROTATIONS[origin_center_digit_for_rotation_logic as usize]
[index_related_digit_for_rotation_logic as usize];
if num_rotations == -1 {
return Err(H3Error::Pentagon);
}
for _ in 0..num_rotations {
_ijk_rotate60_cw(out_ijk);
}
} else {
let dir_from_pent_to_origin_bc = _get_base_cell_direction(index_base_cell, origin_base_cell);
if dir_from_pent_to_origin_bc == Direction::InvalidDigit && origin_base_cell != index_base_cell {
return Err(H3Error::Failed);
}
let origin_related_digit_for_pent_rotation = if origin_base_cell == index_base_cell {
origin_center_digit_for_rotation_logic } else {
dir_from_pent_to_origin_bc
};
let index_leading_digit = _h3_leading_non_zero_digit(index);
if FAILED_DIRECTIONS[index_leading_digit as usize][origin_related_digit_for_pent_rotation as usize] {
return Err(H3Error::Pentagon);
}
let rotations_table_ref = if crate::base_cells::_is_base_cell_polar_pentagon(index_base_cell) {
&PENTAGON_ROTATIONS_REVERSE_POLAR
} else {
&PENTAGON_ROTATIONS_REVERSE_NONPOLAR
};
let num_rotations =
rotations_table_ref[origin_related_digit_for_pent_rotation as usize][index_leading_digit as usize];
if num_rotations == -1 {
return Err(H3Error::Pentagon);
}
for _ in 0..num_rotations {
_ijk_rotate60_ccw(out_ijk);
}
}
}
Ok(())
}
fn _face_to_face_ijk_inplace(fijk_target: &mut FaceIJK, res: i32, target_face: i32) -> Result<(), H3Error> {
if fijk_target.face == target_face {
return Ok(()); }
let dir_to_target = ADJACENT_FACE_DIR[fijk_target.face as usize][target_face as usize];
if dir_to_target == INVALID_FACE {
return Err(H3Error::Failed); }
let orient = &FACE_NEIGHBORS[fijk_target.face as usize][dir_to_target as usize];
for _ in 0..orient.ccw_rot60 {
_ijk_rotate60_ccw(&mut fijk_target.coord);
}
let mut scaled_translate = orient.translate;
if res == 0 {
let original_coord = fijk_target.coord;
_ijk_add(&original_coord, &scaled_translate, &mut fijk_target.coord);
} else {
for r_val in 1..=res {
if is_resolution_class_iii(r_val) {
_down_ap7(&mut scaled_translate);
} else {
_down_ap7r(&mut scaled_translate);
}
}
let original_coord = fijk_target.coord;
_ijk_add(&original_coord, &scaled_translate, &mut fijk_target.coord);
}
_ijk_normalize(&mut fijk_target.coord);
fijk_target.face = target_face;
Ok(())
}
pub fn local_ijk_to_cell(origin: H3Index, ijk: &CoordIJK, out_h3: &mut H3Index) -> Result<(), H3Error> {
let res = get_resolution(origin);
let origin_base_cell = get_base_cell(origin);
let origin_is_pent = _is_base_cell_pentagon(origin_base_cell);
let mut fijk_origin_canonical = FaceIJK::default();
_h3_to_face_ijk(origin, &mut fijk_origin_canonical)?;
let mut fijk_target_on_origin_plane = fijk_origin_canonical;
let temp_coord = fijk_target_on_origin_plane.coord; _ijk_add(&temp_coord, ijk, &mut fijk_target_on_origin_plane.coord);
_ijk_normalize(&mut fijk_target_on_origin_plane.coord);
*out_h3 = _face_ijk_to_h3(&fijk_target_on_origin_plane, res);
if *out_h3 == H3_NULL {
return Err(H3Error::Failed);
}
let index_base_cell = get_base_cell(*out_h3);
let index_is_pent = _is_base_cell_pentagon(index_base_cell);
if origin_is_pent || index_is_pent {
let origin_center_digit = _h3_leading_non_zero_digit(origin);
if origin_base_cell == index_base_cell {
if origin_is_pent {
let index_center_digit = _h3_leading_non_zero_digit(*out_h3);
if FAILED_DIRECTIONS[origin_center_digit as usize][index_center_digit as usize] {
return Err(H3Error::Pentagon); }
let num_rotations_cw_orig = PENTAGON_ROTATIONS[origin_center_digit as usize][index_center_digit as usize];
if num_rotations_cw_orig == -1 {
return Err(H3Error::Pentagon);
}
for _ in 0..num_rotations_cw_orig {
*out_h3 = _h3_rotate_pent60_ccw(*out_h3);
} }
} else {
let dir_to_index_bc = _get_base_cell_direction(origin_base_cell, index_base_cell);
if dir_to_index_bc == Direction::InvalidDigit {
return Err(H3Error::Failed);
}
if origin_is_pent {
if FAILED_DIRECTIONS[origin_center_digit as usize][dir_to_index_bc as usize] {
return Err(H3Error::Pentagon);
}
let num_rotations_cw_orig = PENTAGON_ROTATIONS[origin_center_digit as usize][dir_to_index_bc as usize];
if num_rotations_cw_orig == -1 {
return Err(H3Error::Pentagon);
}
for _ in 0..num_rotations_cw_orig {
*out_h3 = _h3_rotate_pent60_ccw(*out_h3);
}
} else {
let index_leading_digit = _h3_leading_non_zero_digit(*out_h3); let dir_from_pent_to_origin_bc = _get_base_cell_direction(index_base_cell, origin_base_cell);
if dir_from_pent_to_origin_bc == Direction::InvalidDigit {
return Err(H3Error::Failed);
}
if FAILED_DIRECTIONS[index_leading_digit as usize][dir_from_pent_to_origin_bc as usize] {
return Err(H3Error::Pentagon);
}
let rotations_table_ref = if crate::base_cells::_is_base_cell_polar_pentagon(index_base_cell) {
&PENTAGON_ROTATIONS_REVERSE_POLAR
} else {
&PENTAGON_ROTATIONS_REVERSE_NONPOLAR
};
let num_rotations_ccw_orig =
rotations_table_ref[dir_from_pent_to_origin_bc as usize][index_leading_digit as usize];
if num_rotations_ccw_orig == -1 {
return Err(H3Error::Pentagon);
}
for _ in 0..num_rotations_ccw_orig {
*out_h3 = _h3_rotate_pent60_cw(*out_h3);
} }
}
}
Ok(())
}
pub fn cell_to_local_ij(origin: H3Index, index: H3Index, _mode: u32, out_ij: &mut CoordIJ) -> Result<(), H3Error> {
if _mode != 0 {
return Err(H3Error::OptionInvalid);
}
let mut ijk = CoordIJK::default();
cell_to_local_ijk(origin, index, &mut ijk)?; ijk_to_ij(&ijk, out_ij); Ok(())
}
pub fn local_ij_to_cell(origin: H3Index, ij: &CoordIJ, _mode: u32, out_h3: &mut H3Index) -> Result<(), H3Error> {
if _mode != 0 {
return Err(H3Error::OptionInvalid);
}
let mut ijk = CoordIJK::default();
ij_to_ijk(ij, &mut ijk)?; local_ijk_to_cell(origin, &ijk, out_h3) }
#[cfg(test)]
mod tests {
use super::*;
use crate::indexing::lat_lng_to_cell;
use crate::latlng::_set_geo_degs;
use crate::types::{CoordIJ, LatLng, H3_NULL};
#[test]
fn test_cell_to_local_ijk_identity() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 5).unwrap();
let mut ijk = CoordIJK::default();
assert!(cell_to_local_ijk(origin, origin, &mut ijk).is_ok());
let expected_ijk = CoordIJK { i: 0, j: 0, k: 0 };
assert_eq!(
ijk, expected_ijk,
"IJK of origin relative to self is {:?}",
expected_ijk
);
}
#[test]
fn test_local_ijk_to_cell_identity() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 5).unwrap();
let ijk = CoordIJK { i: 0, j: 0, k: 0 };
let mut h3_out = H3_NULL;
assert!(local_ijk_to_cell(origin, &ijk, &mut h3_out).is_ok());
assert_eq!(h3_out, origin, "H3 from local IJK {:?} should be origin", ijk);
}
fn assert_local_ijk_roundtrip(origin: H3Index, target: H3Index) {
let mut ijk = CoordIJK::default();
let to_ijk_res = cell_to_local_ijk(origin, target, &mut ijk);
if to_ijk_res.is_err() {
return;
}
let mut h3_rt = H3_NULL;
let to_h3_res = local_ijk_to_cell(origin, &ijk, &mut h3_rt);
assert!(
to_h3_res.is_ok(),
"localIjkToCell failed. Origin: {:x}, IJK: {:?}, Target: {:x}, Error: {:?}",
origin.0,
ijk,
target.0,
to_h3_res.unwrap_err()
);
assert_eq!(
h3_rt, target,
"Roundtrip H3->IJK->H3 mismatch. Origin: {:x}, Target: {:x}, IJK: {:?}, Got: {:x}",
origin.0, target.0, ijk, h3_rt.0
);
}
#[test]
fn test_local_ijk_roundtrip_neighbors() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419); let origin = lat_lng_to_cell(&geo, 5).unwrap();
let mut k1_ring = [H3_NULL; 7];
crate::traversal::grid_disk::grid_disk(origin, 1, &mut k1_ring).unwrap();
assert_local_ijk_roundtrip(origin, origin); for neighbor_h3 in k1_ring {
if neighbor_h3 != H3_NULL && neighbor_h3 != origin {
assert_local_ijk_roundtrip(origin, neighbor_h3);
}
}
}
#[test]
fn test_local_ij_roundtrip() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 5).unwrap();
let mut ij = CoordIJ::default();
let to_ij_res = cell_to_local_ij(origin, origin, 0, &mut ij);
assert!(to_ij_res.is_ok());
assert_eq!(ij, CoordIJ { i: 0, j: 0 }, "IJ of origin to self");
let mut h3_rt = H3_NULL;
let to_h3_res = local_ij_to_cell(origin, &ij, 0, &mut h3_rt);
assert!(to_h3_res.is_ok());
assert_eq!(h3_rt, origin, "Roundtrip IJ origin");
}
}