use crate::neighbors::Direction;
use crate::{Coord, GeohashError, Neighbors, Rect};
use alloc::string::{String, ToString};
use libm::ldexp;
#[rustfmt::skip]
const BASE32_CODES: [char; 32] = [
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];
#[rustfmt::skip]
const DECODER: [u8; 256] = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0xff, 0x11, 0x12, 0xff, 0x13, 0x14, 0xff,
0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
];
#[inline]
fn spread(x: u32) -> u64 {
let mut new_x = x as u64;
new_x = (new_x | (new_x << 16)) & 0x0000ffff0000ffff;
new_x = (new_x | (new_x << 8)) & 0x00ff00ff00ff00ff;
new_x = (new_x | (new_x << 4)) & 0x0f0f0f0f0f0f0f0f;
new_x = (new_x | (new_x << 2)) & 0x3333333333333333;
new_x = (new_x | (new_x << 1)) & 0x5555555555555555;
new_x
}
#[inline]
fn interleave(x: u32, y: u32) -> u64 {
spread(x) | (spread(y) << 1)
}
#[inline]
fn squash(x: u64) -> u32 {
let mut new_x = x & 0x5555555555555555;
new_x = (new_x | (new_x >> 1)) & 0x3333333333333333;
new_x = (new_x | (new_x >> 2)) & 0x0f0f0f0f0f0f0f0f;
new_x = (new_x | (new_x >> 4)) & 0x00ff00ff00ff00ff;
new_x = (new_x | (new_x >> 8)) & 0x0000ffff0000ffff;
new_x = (new_x | (new_x >> 16)) & 0x00000000ffffffff;
new_x as u32
}
#[inline]
fn deinterleave(x: u64) -> (u32, u32) {
(squash(x), squash(x >> 1))
}
pub fn encode(c: Coord<f64>, len: usize) -> Result<String, GeohashError> {
let max_lat = 90f64;
let min_lat = -90f64;
let max_lon = 180f64;
let min_lon = -180f64;
if !(min_lon..=max_lon).contains(&c.x) || !(min_lat..=max_lat).contains(&c.y) {
return Err(GeohashError::InvalidCoordinateRange(c));
}
if !(1..=12).contains(&len) {
return Err(GeohashError::InvalidLength(len));
}
let lat32 = ((c.y * 0.005555555555555556 + 1.5).to_bits() >> 20) as u32;
let lon32 = ((c.x * 0.002777777777777778 + 1.5).to_bits() >> 20) as u32;
let mut interleaved_int = interleave(lat32, lon32);
let mut out = String::with_capacity(len);
for _ in 0..len {
let code = (interleaved_int >> 59) as usize & (0x1f);
out.push(BASE32_CODES[code]);
interleaved_int <<= 5;
}
Ok(out)
}
pub fn decode_bbox(hash_str: &str) -> Result<Rect<f64>, GeohashError> {
let bits = hash_str.len() * 5;
if hash_str.len() > 12 {
return Err(GeohashError::InvalidHash(
"Length of hash string greater than maximum allowed length".to_string(),
));
}
let mut int_hash: u64 = 0;
for c in hash_str.bytes() {
let hash_value = DECODER[c as usize];
if hash_value == 0xff {
return Err(GeohashError::InvalidHashCharacter(c as char));
}
int_hash <<= 5;
int_hash |= hash_value as u64;
}
Ok(bbox_int_with_precision(int_hash, bits as u32))
}
fn decode_range(x: u32, r: f64) -> f64 {
let p = f64::from_bits(((x as u64) << 20) | (1023 << 52));
2.0 * r * (p - 1.0) - r
}
fn error_with_precision(bits: u32) -> (f64, f64) {
let lat_bits = bits / 2;
let long_bits = bits - lat_bits;
let lat_err = ldexp(180.0, -(lat_bits as i32));
let long_err = ldexp(360.0, -(long_bits as i32));
(lat_err, long_err)
}
fn bbox_int_with_precision(hash: u64, bits: u32) -> Rect<f64> {
let full_hash = hash << (64 - bits);
let (lat_int, long_int) = deinterleave(full_hash);
let lat = decode_range(lat_int, 90.0);
let long = decode_range(long_int, 180.0);
let (lat_err, long_err) = error_with_precision(bits);
Rect::new(
Coord { x: long, y: lat },
Coord {
x: long + long_err,
y: lat + lat_err,
},
)
}
#[cfg(not(feature = "std"))]
fn rem_euclid(x: f64, rhs: f64) -> f64 {
let r = x % rhs;
if r < 0.0 {
r + rhs.abs()
} else {
r
}
}
pub fn decode(hash_str: &str) -> Result<(Coord<f64>, f64, f64), GeohashError> {
let rect = decode_bbox(hash_str)?;
let c0 = rect.min();
let c1 = rect.max();
Ok((
Coord {
x: (c0.x + c1.x) / 2f64,
y: (c0.y + c1.y) / 2f64,
},
(c1.x - c0.x) / 2f64,
(c1.y - c0.y) / 2f64,
))
}
pub fn neighbor(hash_str: &str, direction: Direction) -> Result<String, GeohashError> {
let (coord, lon_err, lat_err) = decode(hash_str)?;
let (dlat, dlng) = direction.to_tuple();
#[cfg(not(feature = "std"))]
let neighbor_coord = Coord {
x: rem_euclid((coord.x + 2f64 * lon_err.abs() * dlng) + 180.0, 360.0) - 180.0,
y: rem_euclid((coord.y + 2f64 * lat_err.abs() * dlat) + 90.0, 180.0) - 90.0,
};
#[cfg(feature = "std")]
let neighbor_coord = Coord {
x: ((coord.x + 2f64 * lon_err.abs() * dlng) + 180.0).rem_euclid(360.0) - 180.0,
y: ((coord.y + 2f64 * lat_err.abs() * dlat) + 90.0).rem_euclid(180.0) - 90.0,
};
encode(neighbor_coord, hash_str.len())
}
pub fn neighbors(hash_str: &str) -> Result<Neighbors, GeohashError> {
Ok(Neighbors {
sw: neighbor(hash_str, Direction::SW)?,
s: neighbor(hash_str, Direction::S)?,
se: neighbor(hash_str, Direction::SE)?,
w: neighbor(hash_str, Direction::W)?,
e: neighbor(hash_str, Direction::E)?,
nw: neighbor(hash_str, Direction::NW)?,
n: neighbor(hash_str, Direction::N)?,
ne: neighbor(hash_str, Direction::NE)?,
})
}