pub mod base32;
pub type Direction = usize;
pub const NORTH: Direction = 0;
pub const NORTH_EAST: Direction = 1;
pub const EAST: Direction = 2;
pub const SOUTH_EAST: Direction = 3;
pub const SOUTH: Direction = 4;
pub const SOUTH_WEST: Direction = 5;
pub const WEST: Direction = 6;
pub const NORTH_WEST: Direction = 7;
pub fn encode(lat: f64, lng: f64) -> String {
encode_with_precision(lat, lng, 12)
}
pub fn encode_with_precision(lat: f64, lng: f64, chars: usize) -> String {
let bits = 5 * chars;
let inthash = encode_int_with_precision(lat, lng, bits);
let enc = base32::encode(inthash);
std::str::from_utf8(&enc[12 - chars..]).unwrap().to_owned()
}
pub fn encode_int(lat: f64, lng: f64) -> u64 {
let lat_int = encode_range(lat, 90.0);
let lng_int = encode_range(lng, 180.0);
interleave(lat_int, lng_int)
}
pub fn encode_int_with_precision(lat: f64, lng: f64, bits: usize) -> u64 {
let hash = encode_int(lat, lng);
hash >> (64 - bits)
}
#[derive(Debug)]
pub struct Box {
pub min_lat: f64,
pub max_lat: f64,
pub min_lng: f64,
pub max_lng: f64,
}
impl Box {
pub fn center(&self) -> (f64, f64) {
(
(self.min_lat + self.max_lat) / 2.0,
(self.min_lng + self.max_lng) / 2.0,
)
}
pub fn contains(&self, lat: f64, lng: f64) -> bool {
self.min_lat <= lat && lat <= self.max_lat && self.min_lng <= lng && lng <= self.max_lng
}
pub fn round(&self) -> (f64, f64) {
let x = max_decimal_power(self.max_lat - self.min_lat);
let lat = (self.min_lat / x).ceil() * x;
let x = max_decimal_power(self.max_lng - self.min_lng);
let lng = (self.min_lng / x).ceil() * x;
(lat, lng)
}
}
fn max_decimal_power(r: f64) -> f64 {
10f64.powf(r.log10().floor())
}
fn ldexp(x: f64, exp: i32) -> f64 {
x * 2.0f64.powi(exp)
}
pub fn error_with_precision(bits: usize) -> (f64, f64) {
let b = bits as i32;
let lat_bits = b / 2;
let lng_bits = b - lat_bits;
let lat_err = ldexp(180.0, -lat_bits);
let lng_err = ldexp(360.0, -lng_bits);
(lat_err, lng_err)
}
pub fn bounding_box(hash: &str) -> Box {
let bits = 5 * hash.len();
let inthash = base32::decode(hash.as_bytes());
bounding_box_int_with_precision(inthash, bits)
}
pub fn bounding_box_int_with_precision(hash: u64, bits: usize) -> Box {
let full_hash = hash << (64 - bits);
let (lat_int, lng_int) = deinterleave(full_hash);
let lat = decode_range(lat_int, 90.0);
let lng = decode_range(lng_int, 180.0);
let (lat_err, lng_err) = error_with_precision(bits);
let b = Box {
min_lat: lat,
max_lat: lat + lat_err,
min_lng: lng,
max_lng: lng + lng_err,
};
b
}
pub fn bounding_box_int(hash: u64) -> Box {
bounding_box_int_with_precision(hash, 64)
}
pub fn validate(hash: &str) -> Result<bool, String> {
if 5 * hash.len() > 64 {
return Err("too long".to_owned());
}
for b in hash.bytes() {
if !base32::valid_byte(b) {
return Err(format!("invalid character {}", b));
}
}
Ok(true)
}
pub fn decode(hash: &str) -> (f64, f64) {
let b = bounding_box(hash);
b.round()
}
pub fn decode_center(hash: &str) -> (f64, f64) {
let b = bounding_box(hash);
b.center()
}
pub fn decode_int_with_precision(hash: u64, bits: usize) -> (f64, f64) {
let b = bounding_box_int_with_precision(hash, bits);
return b.round();
}
pub fn decode_int(hash: u64) -> (f64, f64) {
decode_int_with_precision(hash, 64)
}
pub fn neighbors(hash: &str) -> [String; 8] {
let b = bounding_box(hash);
let (lat, lng) = b.center();
let lat_delta = b.max_lat - b.min_lat;
let lng_delta = b.max_lng - b.min_lng;
let precision = hash.len();
[
encode_with_precision(lat + lat_delta, lng, precision),
encode_with_precision(lat + lat_delta, lng + lng_delta, precision),
encode_with_precision(lat, lng + lng_delta, precision),
encode_with_precision(lat - lat_delta, lng + lng_delta, precision),
encode_with_precision(lat - lat_delta, lng, precision),
encode_with_precision(lat - lat_delta, lng - lng_delta, precision),
encode_with_precision(lat, lng - lng_delta, precision),
encode_with_precision(lat + lat_delta, lng - lng_delta, precision),
]
}
pub fn neighbors_int(hash: u64) -> [u64; 8] {
neighbors_int_with_precision(hash, 64)
}
pub fn neighbors_int_with_precision(hash: u64, bits: usize) -> [u64; 8] {
let b = bounding_box_int_with_precision(hash, bits);
let (lat, lng) = b.center();
let lat_delta = b.max_lat - b.min_lat;
let lng_delta = b.max_lng - b.min_lng;
[
encode_int_with_precision(lat + lat_delta, lng, bits),
encode_int_with_precision(lat + lat_delta, lng + lng_delta, bits),
encode_int_with_precision(lat, lng + lng_delta, bits),
encode_int_with_precision(lat - lat_delta, lng + lng_delta, bits),
encode_int_with_precision(lat - lat_delta, lng, bits),
encode_int_with_precision(lat - lat_delta, lng - lng_delta, bits),
encode_int_with_precision(lat, lng - lng_delta, bits),
encode_int_with_precision(lat + lat_delta, lng - lng_delta, bits),
]
}
pub fn neighbor(hash: &str, direction: Direction) -> String {
neighbors(hash)[direction].to_owned()
}
pub fn neighbor_int(hash: u64, direction: Direction) -> u64 {
neighbors_int_with_precision(hash, 64)[direction]
}
pub fn neighbor_int_with_precision(hash: u64, bits: usize, direction: Direction) -> u64 {
neighbors_int_with_precision(hash, bits)[direction]
}
const EXP_232: f64 = 4.294967296e+09;
fn encode_range(x: f64, r: f64) -> u32 {
let p = (x + r) / (2.0 * r);
(p * EXP_232) as u32
}
fn decode_range(x: u32, r: f64) -> f64 {
let p = x as f64 / EXP_232;
let x = 2.0 * r * p - r;
x
}
fn spread(x: u32) -> u64 {
let mut x = x as u64;
x = (x | (x << 16)) & 0x0000ffff0000ffff;
x = (x | (x << 8)) & 0x00ff00ff00ff00ff;
x = (x | (x << 4)) & 0x0f0f0f0f0f0f0f0f;
x = (x | (x << 2)) & 0x3333333333333333;
x = (x | (x << 1)) & 0x5555555555555555;
x
}
fn interleave(x: u32, y: u32) -> u64 {
spread(x) | (spread(y) << 1)
}
fn squash(x: u64) -> u32 {
let mut x = x;
x &= 0x5555555555555555;
x = (x | (x >> 1)) & 0x3333333333333333;
x = (x | (x >> 2)) & 0x0f0f0f0f0f0f0f0f;
x = (x | (x >> 4)) & 0x00ff00ff00ff00ff;
x = (x | (x >> 8)) & 0x0000ffff0000ffff;
x = (x | (x >> 16)) & 0x00000000ffffffff;
x as u32
}
fn deinterleave(x: u64) -> (u32, u32) {
(squash(x), squash(x >> 1))
}
#[cfg(test)]
mod tests;