use crate::coords::ijk::{_ijk_normalize, ijk_to_cube};
use crate::local_ij::cell_to_local_ijk;
use crate::local_ij::local_ijk_to_cell;
use crate::traversal::distance::grid_distance;
use crate::types::{CoordIJK, H3Error, H3Index};
pub fn grid_path_cells_size(start: H3Index, end: H3Index) -> Result<i64, H3Error> {
match grid_distance(start, end) {
Ok(dist) => Ok(dist + 1),
Err(e) => Err(e), }
}
fn c99_round(val: f64) -> f64 {
if val == 0.0 {
0.0
} else if val > 0.0 {
(val + 0.5).floor()
} else {
(val - 0.5).ceil()
}
}
fn cube_round(i_f: f64, j_f: f64, k_f: f64, out_ijk: &mut CoordIJK) {
let mut ri = c99_round(i_f);
let mut rj = c99_round(j_f);
let mut rk = c99_round(k_f);
let i_diff = (ri - i_f).abs();
let j_diff = (rj - j_f).abs();
let k_diff = (rk - k_f).abs();
if i_diff > j_diff && i_diff > k_diff {
ri = -rj - rk;
} else if j_diff > k_diff {
rj = -ri - rk;
} else {
rk = -ri - rj;
}
out_ijk.i = ri as i32;
out_ijk.j = rj as i32;
out_ijk.k = rk as i32;
}
fn ijk_round_to_axial_hex_center(i_f: f64, j_f: f64, k_f: f64, out_ijk: &mut CoordIJK) {
cube_round(i_f, j_f, k_f, out_ijk);
}
pub fn grid_path_cells(start: H3Index, end: H3Index, out_path: &mut [H3Index]) -> Result<(), H3Error> {
let distance = match grid_distance(start, end) {
Ok(d) => d,
Err(e) => return Err(e),
};
let mut start_ijk_local_h3plus = CoordIJK::default(); cell_to_local_ijk(start, start, &mut start_ijk_local_h3plus)?;
let mut end_ijk_local_h3plus = CoordIJK::default();
cell_to_local_ijk(start, end, &mut end_ijk_local_h3plus)?;
let mut start_axial = start_ijk_local_h3plus;
ijk_to_cube(&mut start_axial);
let mut end_axial = end_ijk_local_h3plus;
ijk_to_cube(&mut end_axial);
let inv_distance_f = 1.0 / (distance as f64); let i_step = (end_axial.i - start_axial.i) as f64 * inv_distance_f; let j_step = (end_axial.j - start_axial.j) as f64 * inv_distance_f; let k_step = (end_axial.k - start_axial.k) as f64 * inv_distance_f;
let mut current_rounded_axial_ijk = CoordIJK::default();
for n in 0..=distance {
let mut ijk_for_cell_conversion: CoordIJK = CoordIJK::default();
if n == distance {
ijk_for_cell_conversion = end_ijk_local_h3plus;
} else {
let i_f = start_axial.i as f64 + i_step * (n as f64);
let j_f = start_axial.j as f64 + j_step * (n as f64);
let k_f = start_axial.k as f64 + k_step * (n as f64);
ijk_round_to_axial_hex_center(i_f, j_f, k_f, &mut current_rounded_axial_ijk);
ijk_for_cell_conversion.i = current_rounded_axial_ijk.i;
ijk_for_cell_conversion.j = current_rounded_axial_ijk.j;
ijk_for_cell_conversion.k = 0; _ijk_normalize(&mut ijk_for_cell_conversion);
}
local_ijk_to_cell(start, &ijk_for_cell_conversion, &mut out_path[n as usize])?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coords::ijk::cube_to_ijk;
use crate::indexing::lat_lng_to_cell;
use crate::latlng::_set_geo_degs;
use crate::traversal::neighbors::are_neighbor_cells;
use crate::types::{LatLng, H3_NULL};
#[test]
fn test_grid_path_cells_size_and_path_identity() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let h = lat_lng_to_cell(&geo, 5).unwrap();
assert_eq!(grid_path_cells_size(h, h), Ok(1));
let mut path = [H3_NULL; 1];
assert!(grid_path_cells(h, h, &mut path).is_ok());
assert_eq!(path[0], h);
}
#[test]
fn test_grid_path_cells_direct_neighbor() {
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();
let neighbor = k1_ring.iter().find(|&&cell| cell != H3_NULL && cell != origin).unwrap();
let path_size = grid_path_cells_size(origin, *neighbor).unwrap();
assert_eq!(path_size, 2, "Path size to direct neighbor is 2");
let mut path = vec![H3_NULL; path_size as usize];
assert!(grid_path_cells(origin, *neighbor, &mut path).is_ok());
assert_eq!(path[0], origin);
assert_eq!(path[1], *neighbor);
}
#[test]
fn test_grid_path_cells_res_mismatch() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let h_res5 = lat_lng_to_cell(&geo, 5).unwrap();
let h_res6 = lat_lng_to_cell(&geo, 6).unwrap();
assert_eq!(grid_path_cells_size(h_res5, h_res6), Err(H3Error::ResMismatch));
let mut path = [H3_NULL; 1]; assert_eq!(grid_path_cells(h_res5, h_res6, &mut path), Err(H3Error::ResMismatch));
}
#[test]
fn test_grid_path_cells_properties_longer_path() {
let start = lat_lng_to_cell(
&LatLng {
lat: (20.0_f64).to_radians(),
lng: (10.0_f64).to_radians(),
},
5,
)
.unwrap();
let end = lat_lng_to_cell(
&LatLng {
lat: (20.0_f64).to_radians(),
lng: (10.5_f64).to_radians(),
},
5,
)
.unwrap();
let path_size_res = grid_path_cells_size(start, end);
assert!(path_size_res.is_ok(), "Path size calculation failed unexpectedly");
let path_size = path_size_res.unwrap();
assert!(path_size > 2, "Test path should be longer than direct neighbor");
let mut path = vec![H3_NULL; path_size as usize];
assert!(grid_path_cells(start, end, &mut path).is_ok());
assert_eq!(path[0], start, "Path starts with start index");
assert_eq!(path[(path_size - 1) as usize], end, "Path ends with end index");
for i in 1..(path_size as usize) {
let are_direct_neighbors_res = are_neighbor_cells(path[i], path[i - 1]);
assert!(
are_direct_neighbors_res.unwrap_or(false), "Index {:x} is NOT neighbor of previous {:x}",
path[i].0,
path[i - 1].0
);
if i > 1 {
let are_zigzag_neighbors_res = are_neighbor_cells(path[i], path[i - 2]);
assert!(
!are_zigzag_neighbors_res.unwrap_or(true), "Index {:x} IS incorrectly a neighbor of {:x} (should not be on a straight line path)",
path[i].0,
path[i - 2].0
);
}
}
}
fn lround_c99_style_for_test(val: f64) -> f64 {
if val == 0.0 {
0.0
} else if val > 0.0 {
(val + 0.5).floor()
} else {
(val - 0.5).ceil()
}
}
fn ijk_round_to_axial_hex_center_test_version(i_f: f64, j_f: f64, k_f: f64, out_ijk: &mut CoordIJK) {
let mut ri = i_f.round();
let mut rj = j_f.round();
let mut rk = k_f.round();
let i_diff = (ri - i_f).abs();
let j_diff = (rj - j_f).abs();
let k_diff = (rk - k_f).abs();
if i_diff > j_diff && i_diff > k_diff {
ri = -rj - rk;
} else if j_diff > k_diff {
rj = -ri - rk;
} else {
rk = -ri - rj;
}
out_ijk.i = ri as i32;
out_ijk.j = rj as i32;
out_ijk.k = rk as i32;
}
#[test]
fn test_grid_distance_vs_are_neighbors() {
let h1 = H3Index(0x855943cbfffffff); let h2 = H3Index(0x855943d3fffffff);
let distance_result1 = grid_distance(h1, h2);
let are_neighbors_result1 = are_neighbor_cells(h1, h2);
match (distance_result1, are_neighbors_result1) {
(Ok(dist), Ok(neighbors_bool)) => {
if neighbors_bool {
assert_eq!(
dist, 1,
"If are_neighbor_cells is true, grid_distance must be 1. H1={:x}, H2={:x}",
h1.0, h2.0
);
} else {
assert_ne!(
dist, 1,
"If are_neighbor_cells is false, grid_distance must not be 1. H1={:x}, H2={:x}",
h1.0, h2.0
);
}
}
_ => {
panic!(
"One of the functions errored for H1/H2: dist_res={:?}, neighbor_res={:?}",
distance_result1, are_neighbors_result1
);
}
}
let h_far1_valid = H3Index(0x851d9963fffffff); let h_far2_valid = H3Index(0x851d994bfffffff); let expected_dist_far_pair = 3;
let distance_result2 = grid_distance(h_far1_valid, h_far2_valid);
let are_neighbors_result2 = are_neighbor_cells(h_far1_valid, h_far2_valid);
assert_eq!(
distance_result2,
Ok(expected_dist_far_pair), "Expected distance for h_far1_valid/h_far2_valid is {}",
expected_dist_far_pair
);
assert_eq!(
are_neighbors_result2,
Ok(false),
"h_far1_valid and h_far2_valid should not be direct neighbors"
);
assert_eq!(
distance_result2.unwrap_or(-1),
expected_dist_far_pair, "Distance for far valid pair should be {}",
expected_dist_far_pair
);
assert!(
distance_result2.unwrap_or(-1) > 1,
"Distance for far valid pair should be > 1"
);
}
#[test]
fn test_rounding_behavior() {
let mut ijk = CoordIJK::default();
ijk_round_to_axial_hex_center_test_version(2.5, 0.0, -2.5, &mut ijk); assert!(
ijk.i == 3 && ijk.j == 0 && ijk.k == -3,
"lround_c99_style ties 2.5 to 3, -2.5 to -3"
);
ijk_round_to_axial_hex_center_test_version(3.5, 0.0, -3.5, &mut ijk);
assert!(
ijk.i == 4 && ijk.j == 0 && ijk.k == -4,
"lround_c99_style ties 3.5 to 4, -3.5 to -4"
);
let mut ri = lround_c99_style_for_test(2.5) as i32;
let mut rj = lround_c99_style_for_test(0.0) as i32;
let mut rk = lround_c99_style_for_test(-2.5) as i32;
assert!(
ri == 3 && rj == 0 && rk == -3,
"lround_c99_style ties 2.5 to 3, -2.5 to -3"
);
ri = lround_c99_style_for_test(3.5) as i32;
rj = lround_c99_style_for_test(0.0) as i32;
rk = lround_c99_style_for_test(-3.5) as i32;
assert!(
ri == 4 && rj == 0 && rk == -4,
"lround_c99_style ties 3.5 to 4, -3.5 to -4"
);
}
#[test]
fn test_local_ijk_roundtrip_simple() {
let origin = H3Index(0x85283473fffffff); let mut ijk_origin_local = CoordIJK::default();
assert!(cell_to_local_ijk(origin, origin, &mut ijk_origin_local).is_ok());
assert_eq!(
ijk_origin_local,
CoordIJK { i: 0, j: 0, k: 0 },
"Origin to self is 0,0,0"
);
let mut origin_rt = H3_NULL;
assert!(local_ijk_to_cell(origin, &ijk_origin_local, &mut origin_rt).is_ok());
assert_eq!(origin_rt, origin, "Roundtrip origin via local IJK {{0,0,0}}");
let mut neighbors = [H3_NULL; 7];
assert!(crate::grid_disk(origin, 1, &mut neighbors).is_ok());
let neighbor_h3 = neighbors.iter().find(|&&h| h != H3_NULL && h != origin).unwrap();
let mut ijk_neighbor_local = CoordIJK::default();
assert!(cell_to_local_ijk(origin, *neighbor_h3, &mut ijk_neighbor_local).is_ok());
let dist_one_check = ijk_neighbor_local
.i
.abs()
.max(ijk_neighbor_local.j.abs())
.max(ijk_neighbor_local.k.abs());
assert_eq!(
dist_one_check, 1,
"Local IJK of neighbor should be distance 1: {:?}",
ijk_neighbor_local
);
let mut neighbor_rt = H3_NULL;
assert!(local_ijk_to_cell(origin, &ijk_neighbor_local, &mut neighbor_rt).is_ok());
assert_eq!(neighbor_rt, *neighbor_h3, "Roundtrip neighbor via local IJK");
}
#[test]
fn test_cube_ijk_conversion_asymmetry() {
let ijk_plus_orig = CoordIJK { i: 1, j: 2, k: 0 };
let mut cube_coords = ijk_plus_orig;
ijk_to_cube(&mut cube_coords);
let mut ijk_plus_rt = cube_coords;
cube_to_ijk(&mut ijk_plus_rt);
let expected_rt = CoordIJK { i: 0, j: 3, k: 1 };
assert_eq!(
ijk_plus_rt, expected_rt,
"Cube to IJK+ conversion leads to different representation"
);
assert_ne!(
ijk_plus_orig, ijk_plus_rt,
"Original IJK+ and round-tripped IJK+ are different"
);
}
#[test]
fn test_specific_pair_distance_vs_neighbors() {
let h_start = H3Index(0x855943cbfffffff); let h_end = H3Index(0x85594303fffffff);
let distance_result = grid_distance(h_start, h_end);
let are_neighbors_result = are_neighbor_cells(h_start, h_end);
match (distance_result, are_neighbors_result) {
(Ok(dist), Ok(neighbors_bool)) => {
if neighbors_bool {
assert_eq!(dist, 1, "If are_neighbor_cells is true, grid_distance must be 1.");
} else {
assert_ne!(dist, 1, "If are_neighbor_cells is false, grid_distance must not be 1.");
}
}
_ => {
panic!(
"One of the functions errored: dist_res={:?}, neighbor_res={:?}",
distance_result, are_neighbors_result
);
}
}
if are_neighbors_result == Ok(true) {
assert_eq!(
distance_result,
Ok(1),
"Path test implies H_START and H_END are neighbors, so distance should be 1."
);
}
}
}