use crate::coord::Coordinate;
use crate::error::N3gbError;
use crate::index::constants::{
CELL_RADIUS as RADIUS, CELL_WIDTHS as WIDTHS, GRID_EXTENTS as EXTENTS, MAX_ZOOM_LEVEL,
};
use geo_types::Point;
pub fn point_to_row_col<C: Coordinate>(coord: &C, z: u8) -> Result<(i64, i64), N3gbError> {
if z > MAX_ZOOM_LEVEL {
return Err(N3gbError::InvalidZoomLevel(z));
}
let hex_width = WIDTHS[z as usize];
let r = RADIUS[z as usize];
let dx = hex_width;
let dy = 1.5 * r;
let qx = (coord.x() - EXTENTS[0]) / dx;
let ry = (coord.y() - EXTENTS[1]) / dy;
let row = ry.round() as i64;
let col = (qx - row.rem_euclid(2) as f64).round() as i64;
Ok((row, col))
}
pub fn row_col_to_center(row: i64, col: i64, z: u8) -> Result<Point<f64>, N3gbError> {
if z > MAX_ZOOM_LEVEL {
return Err(N3gbError::InvalidZoomLevel(z));
}
let hex_width = WIDTHS[z as usize];
let r = RADIUS[z as usize];
let dx = hex_width;
let dy = 1.5 * r;
let x = EXTENTS[0] + col as f64 * dx + ((row % 2) as f64 * (dx / 2.0));
let y = EXTENTS[1] + row as f64 * dy;
Ok(Point::new(x, y))
}
pub(crate) fn offset_to_cube(row: i64, col: i64) -> (i64, i64, i64) {
let q = col - row / 2;
let r = row;
let s = -q - r;
(q, r, s)
}
#[cfg(test)]
mod tests {
use super::*;
use geo_types::point;
#[test]
fn test_point_to_row_col_and_back() -> Result<(), N3gbError> {
let easting = 457996.0;
let northing = 339874.0;
let zoom = 10;
let (row, col) = point_to_row_col(&(easting, northing), zoom)?;
let point = row_col_to_center(row, col, zoom)?;
assert!((point.x() - 457925.0).abs() < 100.0);
assert!((point.y() - 339888.99).abs() < 100.0);
Ok(())
}
#[test]
fn test_point_to_row_col_with_point() -> Result<(), N3gbError> {
let pt = point! { x: 457996.0, y: 339874.0 };
let zoom = 10;
let (row, col) = point_to_row_col(&pt, zoom)?;
let center = row_col_to_center(row, col, zoom)?;
assert!((center.x() - 457925.0).abs() < 100.0);
assert!((center.y() - 339888.99).abs() < 100.0);
Ok(())
}
#[test]
fn test_invalid_zoom_level() {
let result = point_to_row_col(&(457996.0, 339874.0), 20);
assert!(matches!(result, Err(N3gbError::InvalidZoomLevel(20))));
}
#[test]
fn test_row_col_to_center_invalid_zoom() {
let result = row_col_to_center(100, 100, 16);
assert!(result.is_err());
}
}