use crate::h3_index::is_pentagon;
use crate::traversal::neighbors::h3_neighbor_rotations;
use crate::types::{Direction, H3Error, H3Index, H3_NULL};
const K_ALL_CELLS_AT_RES_15: i32 = 13_780_510;
pub fn max_grid_disk_size(k: i32) -> Result<i64, H3Error> {
if k < 0 {
return Err(H3Error::Domain);
}
if k >= K_ALL_CELLS_AT_RES_15 {
return Ok(crate::constants::NUM_CELLS_MAX_RES); }
let k_i64 = k as i64;
Ok(3 * k_i64 * (k_i64 + 1) + 1)
}
fn _grid_disk_distances_internal(
origin: H3Index,
k: i32,
out: &mut [H3Index],
distances: &mut [i32],
max_idx: i64, current_k: i32,
) -> Result<(), H3Error> {
if origin == H3_NULL {
return Ok(());
}
let mut offset = (origin.0 % max_idx as u64) as usize;
loop {
if out[offset] == H3_NULL {
break;
}
if out[offset] == origin {
if distances[offset] <= current_k {
return Ok(());
}
break;
}
offset = (offset + 1) % (max_idx as usize);
}
out[offset] = origin;
distances[offset] = current_k;
if current_k >= k {
return Ok(()); }
for dir_enum_val in 0..6 {
const ACTUAL_C_DIRECTIONS: [Direction; 6] = [
Direction::JAxes,
Direction::JkAxes,
Direction::KAxes,
Direction::IkAxes,
Direction::IAxes,
Direction::IjAxes,
];
let actual_dir = ACTUAL_C_DIRECTIONS[dir_enum_val];
let mut rotations = 0;
let mut next_neighbor = H3_NULL;
let neighbor_result = h3_neighbor_rotations(origin, actual_dir, &mut rotations, &mut next_neighbor);
if neighbor_result.is_ok() {
_grid_disk_distances_internal(next_neighbor, k, out, distances, max_idx, current_k + 1)?;
} else if matches!(neighbor_result, Err(H3Error::Pentagon)) {
continue;
} else {
return neighbor_result;
}
}
Ok(())
}
pub fn grid_disk_distances(
origin: H3Index,
k: i32,
out_cells: &mut [H3Index],
mut out_distances_opt: Option<&mut [i32]>,
) -> Result<(), H3Error> {
let max_k_size = max_grid_disk_size(k)?;
if out_cells.len() < max_k_size as usize {
return Err(H3Error::MemoryBounds);
}
if let Some(ref distances_slice) = out_distances_opt {
if distances_slice.len() < max_k_size as usize {
return Err(H3Error::MemoryBounds);
}
}
if let Some(ref mut distances_slice) = out_distances_opt {
_grid_disk_distances_internal(origin, k, out_cells, distances_slice, max_k_size, 0)
} else {
let mut temp_distances = vec![0i32; max_k_size as usize];
_grid_disk_distances_internal(origin, k, out_cells, &mut temp_distances, max_k_size, 0)
}
}
pub fn grid_disk(origin: H3Index, k: i32, out_cells: &mut [H3Index]) -> Result<(), H3Error> {
grid_disk_distances(origin, k, out_cells, None)
}
pub fn grid_disk_distances_unsafe(
mut origin: H3Index,
k: i32,
out_cells: &mut [H3Index],
mut out_distances_opt: Option<&mut [i32]>,
) -> Result<(), H3Error> {
if k < 0 {
return Err(H3Error::Domain);
}
let max_size = max_grid_disk_size(k)? as usize;
if out_cells.len() < max_size {
return Err(H3Error::MemoryBounds);
}
if let Some(ref dists) = out_distances_opt {
if dists.len() < max_size {
return Err(H3Error::MemoryBounds);
}
}
let mut idx = 0;
out_cells[idx] = origin; if let Some(ref mut dists_slice) = out_distances_opt {
dists_slice[idx] = 0;
}
idx += 1;
if is_pentagon(origin) {
if k > 0 {
for i in idx..out_cells.len() {
out_cells[i] = H3_NULL;
}
if let Some(ref mut ds) = out_distances_opt {
for i in idx..ds.len() {
ds[i] = -1;
}
}
return Err(H3Error::Pentagon);
}
if k == 0 {
for i in idx..out_cells.len() {
out_cells[i] = H3_NULL;
}
if let Some(ref mut ds) = out_distances_opt {
for i in idx..ds.len() {
ds[i] = -1;
}
}
return Ok(());
}
}
if k == 0 {
for i in idx..out_cells.len() {
out_cells[i] = H3_NULL;
}
if let Some(ref mut ds) = out_distances_opt {
for i in idx..ds.len() {
ds[i] = -1;
}
}
return Ok(());
}
let mut ring_k_num = 1; let mut c_direction_idx = 0; let mut pos_on_side = 0;
let mut path_accumulated_rotations: i32 = 0;
const C_NEXT_RING_DIRECTION: Direction = Direction::IAxes;
const C_SIDE_DIRECTIONS: [Direction; 6] = [
Direction::JAxes,
Direction::JkAxes,
Direction::KAxes,
Direction::IkAxes,
Direction::IAxes,
Direction::IjAxes,
];
while ring_k_num <= k {
if c_direction_idx == 0 && pos_on_side == 0 {
let mut step_rot = 0; let neighbor_result = h3_neighbor_rotations(
origin,
C_NEXT_RING_DIRECTION,
&mut step_rot,
&mut origin, );
if neighbor_result.is_err() {
return neighbor_result;
} path_accumulated_rotations = (path_accumulated_rotations + step_rot) % 6;
if path_accumulated_rotations < 0 {
path_accumulated_rotations += 6;
}
if is_pentagon(origin) {
return Err(H3Error::Pentagon);
}
}
let mut step_rot_side = 0;
let mut dir_for_side_step = C_SIDE_DIRECTIONS[c_direction_idx as usize];
for _ in 0..path_accumulated_rotations {
dir_for_side_step = crate::coords::ijk::_rotate60_ccw(dir_for_side_step);
}
let neighbor_result_side = h3_neighbor_rotations(
origin, dir_for_side_step,
&mut step_rot_side,
&mut origin, );
if neighbor_result_side.is_err() {
return neighbor_result_side;
}
path_accumulated_rotations = (path_accumulated_rotations + step_rot_side) % 6;
if path_accumulated_rotations < 0 {
path_accumulated_rotations += 6;
}
if idx >= max_size {
return Err(H3Error::MemoryBounds);
}
out_cells[idx] = origin;
if let Some(ref mut dists_slice) = out_distances_opt {
dists_slice[idx] = ring_k_num;
}
idx += 1;
pos_on_side += 1;
if pos_on_side == ring_k_num {
pos_on_side = 0;
c_direction_idx += 1;
if c_direction_idx == 6 {
c_direction_idx = 0;
ring_k_num += 1; }
}
if is_pentagon(origin) {
return Err(H3Error::Pentagon);
}
}
if idx != max_size {
return Err(H3Error::Failed);
}
Ok(())
}
pub fn grid_disk_unsafe(origin: H3Index, k: i32, out: &mut [H3Index]) -> Result<(), H3Error> {
grid_disk_distances_unsafe(origin, k, out, None)
}
pub fn grid_ring_unsafe(origin: H3Index, k: i32, out_cells: &mut [H3Index]) -> Result<(), H3Error> {
if k < 0 {
return Err(H3Error::Domain);
}
let expected_size = if k == 0 { 1 } else { (6 * k) as usize };
if out_cells.len() < expected_size {
return Err(H3Error::MemoryBounds);
}
if k == 0 {
out_cells[0] = origin;
return Ok(());
}
let mut idx = 0;
let mut rotations: i32 = 0;
let mut current_h = origin;
if is_pentagon(current_h) {
return Err(H3Error::Pentagon);
}
for _ring_num in 0..k {
let err = h3_neighbor_rotations(current_h, Direction::IAxes, &mut rotations, &mut current_h);
if err.is_err() {
return err;
}
if is_pentagon(current_h) {
return Err(H3Error::Pentagon);
}
}
let first_cell_in_ring = current_h;
out_cells[idx] = current_h;
idx += 1;
const RING_TRAVERSAL_DIRECTIONS: [Direction; 6] = [
Direction::JAxes,
Direction::JkAxes,
Direction::KAxes,
Direction::IkAxes,
Direction::IAxes,
Direction::IjAxes,
];
for side_idx in 0..6 {
for _pos_on_side in 0..k {
let err = h3_neighbor_rotations(
current_h,
RING_TRAVERSAL_DIRECTIONS[side_idx],
&mut rotations,
&mut current_h,
);
if err.is_err() {
return err;
}
if is_pentagon(current_h) {
return Err(H3Error::Pentagon);
}
if idx < expected_size {
out_cells[idx] = current_h;
idx += 1;
} else {
if current_h != first_cell_in_ring {
return Err(H3Error::Pentagon);
}
}
}
}
if idx != expected_size || (k > 0 && current_h != first_cell_in_ring) {
return Err(H3Error::Pentagon); }
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::indexing::lat_lng_to_cell;
use crate::latlng::_set_geo_degs;
use crate::types::LatLng;
use std::collections::HashSet;
#[test]
fn test_max_grid_disk_size() {
assert_eq!(max_grid_disk_size(0), Ok(1));
assert_eq!(max_grid_disk_size(1), Ok(7));
assert_eq!(max_grid_disk_size(2), Ok(19));
assert_eq!(max_grid_disk_size(-1), Err(H3Error::Domain));
assert_eq!(
max_grid_disk_size(K_ALL_CELLS_AT_RES_15),
Ok(crate::constants::NUM_CELLS_MAX_RES)
);
assert_eq!(
max_grid_disk_size(K_ALL_CELLS_AT_RES_15 + 1000),
Ok(crate::constants::NUM_CELLS_MAX_RES)
);
}
#[test]
fn test_grid_disk_distances_k0() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 5).unwrap();
let k = 0;
let max_size = max_grid_disk_size(k).unwrap() as usize;
let mut cells = vec![H3_NULL; max_size];
let mut distances = vec![0i32; max_size];
assert!(grid_disk_distances(origin, k, &mut cells, Some(&mut distances)).is_ok());
let mut count = 0;
for i in 0..max_size {
if cells[i] != H3_NULL {
count += 1;
assert_eq!(cells[i], origin);
assert_eq!(distances[i], 0);
}
}
assert_eq!(count, 1, "k=0 should return only origin");
}
#[test]
fn test_grid_disk_k1() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 5).unwrap();
let k = 1;
let max_size = max_grid_disk_size(k).unwrap() as usize;
let mut cells = vec![H3_NULL; max_size];
assert!(grid_disk(origin, k, &mut cells).is_ok());
let mut found_cells = HashSet::new();
let mut origin_found = false;
for i in 0..max_size {
if cells[i] != H3_NULL {
assert!(crate::h3_index::inspection::is_valid_cell(cells[i]));
found_cells.insert(cells[i]);
if cells[i] == origin {
origin_found = true;
}
}
}
assert_eq!(found_cells.len(), 7, "k=1 should return 7 unique cells for a hexagon");
assert!(origin_found, "Origin cell should be in k=1 disk");
}
#[test]
fn test_grid_disk_distances_pentagon_k1() {
let pent_origin_res0 = crate::base_cells::baseCellNumToCell(4);
let pent_origin = crate::hierarchy::cell_to_center_child(pent_origin_res0, 1).unwrap();
assert!(is_pentagon(pent_origin));
let k = 1;
let max_size = max_grid_disk_size(k).unwrap() as usize; let mut cells = vec![H3_NULL; max_size];
let mut distances = vec![0i32; max_size];
assert!(grid_disk_distances(pent_origin, k, &mut cells, Some(&mut distances)).is_ok());
let mut found_cells_map = std::collections::HashMap::new();
let mut cell_count = 0;
for i in 0..max_size {
if cells[i] != H3_NULL {
cell_count += 1;
assert!(crate::h3_index::inspection::is_valid_cell(cells[i]));
found_cells_map.insert(cells[i], distances[i]);
}
}
assert_eq!(cell_count, 6, "k=1 from pentagon should return 6 cells");
assert_eq!(
found_cells_map.len(),
6,
"k=1 from pentagon should return 6 unique cells"
);
assert_eq!(
found_cells_map.get(&pent_origin),
Some(&0),
"Origin pentagon should be at distance 0"
);
for (cell, dist) in found_cells_map {
if cell != pent_origin {
assert_eq!(dist, 1, "Neighbor cells should be at distance 1");
}
}
}
#[test]
fn test_grid_disk_unsafe_k1_hex() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 5).unwrap();
assert!(!is_pentagon(origin));
let k = 1;
let max_size = max_grid_disk_size(k).unwrap() as usize;
let mut cells = vec![H3_NULL; max_size];
let mut distances = vec![0i32; max_size];
assert!(grid_disk_distances_unsafe(origin, k, &mut cells, Some(&mut distances)).is_ok());
let mut found_cells = HashSet::new();
let mut cell_count = 0;
let mut dist_sum = 0;
for i in 0..max_size {
if cells[i] != H3_NULL {
cell_count += 1;
assert!(crate::h3_index::inspection::is_valid_cell(cells[i]));
found_cells.insert(cells[i]);
if cells[i] == origin {
assert_eq!(distances[i], 0);
} else {
assert_eq!(distances[i], 1);
}
dist_sum += distances[i];
}
}
assert_eq!(cell_count, 7);
assert_eq!(found_cells.len(), 7);
assert_eq!(dist_sum, 6); }
#[test]
fn test_grid_disk_unsafe_pentagon_origin() {
let pent_origin_res0 = crate::base_cells::baseCellNumToCell(4);
let pent_origin = crate::hierarchy::parent_child::cell_to_center_child(pent_origin_res0, 1).unwrap();
assert!(is_pentagon(pent_origin));
let k = 1;
let max_size = max_grid_disk_size(k).unwrap() as usize;
let mut cells = vec![H3_NULL; max_size];
assert_eq!(grid_disk_unsafe(pent_origin, k, &mut cells), Err(H3Error::Pentagon));
}
#[test]
fn test_grid_disk_unsafe_near_pentagon() {
let pent_origin_res0 = crate::base_cells::baseCellNumToCell(4); let pent_origin_res1 = crate::hierarchy::parent_child::cell_to_center_child(pent_origin_res0, 1).unwrap();
let mut pent_neighbors = vec![H3_NULL; 7];
grid_disk(pent_origin_res1, 1, &mut pent_neighbors).unwrap();
let mut hex_near_pent = H3_NULL;
for neighbor in pent_neighbors {
if neighbor != H3_NULL && neighbor != pent_origin_res1 {
hex_near_pent = neighbor;
break;
}
}
assert_ne!(hex_near_pent, H3_NULL, "Could not find a hex neighbor of the pentagon");
assert!(!is_pentagon(hex_near_pent));
let k = 1;
let max_size_k1 = max_grid_disk_size(k).unwrap() as usize;
let mut cells_k1 = vec![H3_NULL; max_size_k1];
assert_eq!(
grid_disk_unsafe(hex_near_pent, k, &mut cells_k1),
Err(H3Error::Pentagon),
"k=1 from hex near pentagon should error in unsafe because the disk includes the pentagon"
);
let k_unsafe_fail = 2;
let max_size_k_unsafe_fail = max_grid_disk_size(k_unsafe_fail).unwrap() as usize;
let mut cells_k_unsafe_fail = vec![H3_NULL; max_size_k_unsafe_fail];
assert_eq!(
grid_disk_unsafe(hex_near_pent, k_unsafe_fail, &mut cells_k_unsafe_fail),
Err(H3Error::Pentagon),
"k=2 from hex near pentagon should error in unsafe as pentagon is in output"
);
}
#[test]
fn test_grid_ring_unsafe_k0() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 9).unwrap();
let mut ring_cells = [H3_NULL; 1];
assert!(grid_ring_unsafe(origin, 0, &mut ring_cells).is_ok());
assert_eq!(ring_cells[0], origin);
}
#[test]
fn test_grid_ring_unsafe_k1_hex() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419);
let origin = lat_lng_to_cell(&geo, 9).unwrap();
assert!(!is_pentagon(origin));
let k = 1;
let expected_size = (6 * k) as usize;
let mut ring_cells = vec![H3_NULL; expected_size];
assert!(grid_ring_unsafe(origin, k, &mut ring_cells).is_ok());
let mut found_cells = HashSet::new();
let mut cell_count = 0;
for i in 0..expected_size {
if ring_cells[i] != H3_NULL {
cell_count += 1;
assert!(crate::h3_index::inspection::is_valid_cell(ring_cells[i]));
assert_ne!(ring_cells[i], origin, "Ring cells should not be the origin for k>0");
found_cells.insert(ring_cells[i]);
}
}
assert_eq!(cell_count, expected_size);
assert_eq!(found_cells.len(), expected_size, "All ring cells should be unique");
let max_disk_size = max_grid_disk_size(k).unwrap() as usize;
let mut disk_cells = vec![H3_NULL; max_disk_size];
let mut disk_distances = vec![0i32; max_disk_size];
assert!(grid_disk_distances(origin, k, &mut disk_cells, Some(&mut disk_distances)).is_ok());
for cell_in_disk in disk_cells.iter().zip(disk_distances.iter()) {
if *cell_in_disk.0 != H3_NULL && *cell_in_disk.0 != origin {
assert_eq!(*cell_in_disk.1, k, "Cell from disk should be at distance k");
assert!(
found_cells.contains(cell_in_disk.0),
"Cell from disk's k-ring found in grid_ring_unsafe output"
);
}
}
}
#[test]
fn test_grid_ring_unsafe_k2_hex() {
let mut geo = LatLng::default();
_set_geo_degs(&mut geo, 37.779, -122.419); let origin = lat_lng_to_cell(&geo, 5).unwrap();
assert!(!is_pentagon(origin));
let k = 2;
let expected_size = (6 * k) as usize; let mut ring_cells = vec![H3_NULL; expected_size];
assert!(grid_ring_unsafe(origin, k, &mut ring_cells).is_ok());
let mut found_cells = HashSet::new();
for i in 0..expected_size {
assert_ne!(ring_cells[i], H3_NULL, "Cell {} should be populated", i);
assert!(crate::h3_index::inspection::is_valid_cell(ring_cells[i]));
assert_ne!(ring_cells[i], origin);
assert!(
found_cells.insert(ring_cells[i]),
"Cell {:x} was duplicate in ring",
ring_cells[i].0
);
}
assert_eq!(
found_cells.len(),
expected_size,
"All ring cells should be unique for k=2 hex"
);
}
#[test]
fn test_grid_ring_unsafe_on_pentagon_origin() {
let pent_origin_res0 = crate::base_cells::baseCellNumToCell(4);
let pent_origin = crate::hierarchy::parent_child::cell_to_center_child(pent_origin_res0, 1).unwrap();
assert!(is_pentagon(pent_origin));
let k_val: i32 = 1;
let vec_size: usize = if k_val == 0 { 1 } else { (6 * k_val) as usize };
let mut ring_cells = vec![H3_NULL; vec_size];
assert_eq!(
grid_ring_unsafe(pent_origin, k_val, &mut ring_cells),
Err(H3Error::Pentagon)
);
}
#[test]
fn test_grid_ring_unsafe_near_pentagon_failure() {
let pent_origin_res0 = crate::base_cells::baseCellNumToCell(4); let pent_origin_res1 = crate::hierarchy::parent_child::cell_to_center_child(pent_origin_res0, 1).unwrap();
let mut pent_neighbors_disk = vec![H3_NULL; max_grid_disk_size(1).unwrap() as usize];
grid_disk(pent_origin_res1, 1, &mut pent_neighbors_disk).unwrap();
let mut hex_near_pent = H3_NULL;
for neighbor_h3 in pent_neighbors_disk {
if neighbor_h3 != H3_NULL && neighbor_h3 != pent_origin_res1 {
hex_near_pent = neighbor_h3;
break;
}
}
assert_ne!(hex_near_pent, H3_NULL, "Could not find a hex neighbor of the pentagon");
assert!(!is_pentagon(hex_near_pent));
let k1_val: i32 = 1; let ring_k1_size: usize = (6 * k1_val) as usize; let mut ring_k1 = vec![H3_NULL; ring_k1_size];
assert_eq!(
grid_ring_unsafe(hex_near_pent, k1_val, &mut ring_k1),
Err(H3Error::Pentagon),
"k=1 ring from hex near pentagon should error in unsafe because the ring includes the pentagon"
);
let k2_val: i32 = 2; let ring_k2_size: usize = (6 * k2_val) as usize; let mut ring_k2 = vec![H3_NULL; ring_k2_size];
assert_eq!(
grid_ring_unsafe(hex_near_pent, k2_val, &mut ring_k2),
Err(H3Error::Pentagon),
"Line 825 error site"
);
}
#[test]
fn test_grid_ring_unsafe_invalid_k() {
let origin = H3Index(0x85283473fffffff);
let mut cells = [H3_NULL; 1];
assert_eq!(grid_ring_unsafe(origin, -1, &mut cells), Err(H3Error::Domain));
}
#[test]
fn test_grid_ring_unsafe_output_too_small() {
let origin = H3Index(0x85283473fffffff);
let mut cells = [H3_NULL; 5]; assert_eq!(grid_ring_unsafe(origin, 1, &mut cells), Err(H3Error::MemoryBounds));
}
}