use geolocation::GeoLocation;
use boundingbox::BoundingBox;
static 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',
];
static BASE32_INDICES: [u8; 75]=[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 10, 11, 12, 13, 14, 15, 16, 0xFF, 17, 18, 0xFF, 19, 20, 0xFF, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 10, 11, 12, 13, 14, 15, 16, 0xFF, 17, 18, 0xFF, 19, 20, 0xFF, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ];
#[derive(Default, Clone, Copy, PartialEq)]
pub struct BinaryHash {
bits : u64,
precision : u8,
}
impl BinaryHash {
pub fn new() -> BinaryHash {
BinaryHash{
bits: 0u64,
precision: 0u8,
}
}
#[cfg(unstable)]
pub fn from_BitVec(bv: &BitVec) -> BinaryHash {
let mut output=BinaryHash::new();
for b in bv.iter() {
output.push(b)
}
output
}
pub fn from_string(s: &str) -> BinaryHash {
let mut output=BinaryHash::new();
for c in s.chars() {
match c {
'0' => output.push(false),
'1' => output.push(true),
_ => panic!("Invalid binary code"),
}
}
output
}
pub fn encode(l: &GeoLocation, precision: u8) -> BinaryHash {
let mut bbox = BoundingBox::from_coordinates(-90.0, 90.0, -180.0, 180.0);
let mut islon = true;
let mut output=BinaryHash::new();
while output.len() < precision {
if islon {
let mid = (bbox.max_lon + bbox.min_lon) / 2.0;
if l.longitude > mid {
output.push(true);
bbox.min_lon=mid;
} else {
output.push(false);
bbox.max_lon=mid;
}
} else {
let mid = (bbox.max_lat + bbox.min_lat) / 2.0;
if l.latitude > mid {
output.push(true);
bbox.min_lat = mid;
} else {
output.push(false);
bbox.max_lat = mid;
}
}
islon = !islon;
}
output
}
pub fn decode(&self) -> BoundingBox {
let mut output = BoundingBox::from_coordinates(-90.0, 90.0, -180.0, 180.0);
let mut islon = true;
for n in 0u8..self.precision {
if islon {
let mid = (output.max_lon + output.min_lon) / 2.0;
if self.test(n) {
output.min_lon = mid;
} else {
output.max_lon = mid;
}
} else {
let mid = (output.max_lat + output.min_lat) / 2.0;
if self.test(n) {
output.min_lat = mid;
} else {
output.max_lat = mid;
}
}
islon = !islon;
}
output
}
pub fn decode_string(s: &str) -> BoundingBox {
BinaryHash::from_string(s).decode()
}
#[cfg(unstable)]
pub fn to_BitVec(&self) -> BitVec {
let mut output=BitVec::with_capacity(self.precision as usize);
for n in (0u8..self.precision) {
output.push(self.test(n))
}
output
}
pub fn to_string(&self) -> String {
let mut output=String::with_capacity(self.precision as usize);
for n in 0..self.precision {
output.push(if self.test(n) {'1'} else {'0'})
}
output
}
pub fn len(&self) -> u8 {
self.precision
}
pub fn empty(&self) -> bool {
self.precision == 0
}
pub fn test(&self, n: u8) -> bool {
(self.bits & (1u64 << (self.precision-n-1))) != 0
}
pub fn push(&mut self, b: bool) {
self.bits <<= 1u64;
self.bits |= if b {1u64} else {0u64};
self.precision += 1u8;
}
}
pub fn encode(l: &GeoLocation, precision: u8) -> String {
let mut bbox = BoundingBox::from_coordinates(-90.0, 90.0, -180.0, 180.0);
let mut islon = true;
let mut num_bits = 0;
let mut hash_index = 0;
let mut output=String::with_capacity(precision as usize);
while output.len() < (precision as usize) {
if islon {
let mid = (bbox.max_lon + bbox.min_lon) / 2.0;
if l.longitude > mid {
hash_index = (hash_index << 1) + 1;
bbox.min_lon=mid;
} else {
hash_index = (hash_index << 1) + 0;
bbox.max_lon=mid;
}
} else {
let mid = (bbox.max_lat + bbox.min_lat) / 2.0;
if l.latitude > mid {
hash_index = (hash_index << 1) + 1;
bbox.min_lat = mid;
} else {
hash_index = (hash_index << 1) + 0;
bbox.max_lat = mid;
}
}
islon = !islon;
num_bits+=1;
if num_bits%5==0 {
output.push(BASE32_CODES[hash_index]);
hash_index = 0;
}
}
output
}
pub fn decode(hash: &str) -> BoundingBox {
let mut output = BoundingBox::from_coordinates(-90.0, 90.0, -180.0, 180.0);
let mut islon = true;
for c in hash.chars() {
assert!(c>='0' && c<='z');
let char_index = BASE32_INDICES[(c as usize)-48];
assert!(char_index<32);
for bits in (0..5).rev() {
let bit = ((char_index >> bits) & 1)==1;
if islon {
let mid = (output.max_lon + output.min_lon) / 2.0;
if bit {
output.min_lon = mid;
} else {
output.max_lon = mid;
}
} else {
let mid = (output.max_lat + output.min_lat) / 2.0;
if bit {
output.min_lat = mid;
} else {
output.max_lat = mid;
}
}
islon = !islon;
}
}
println!("min_lat:{}, max_lat:{}, min_long:{}, max_lon:{}", output.min_lat, output.max_lat, output.min_lon, output.max_lon);
output
}
pub fn neighbor(hash: &str, direction: (i8, i8)) -> String {
let b = decode(hash);
let cp = b.center();
let gl=match direction {
(dlat, dlon) => GeoLocation::from_coordinates(
cp.latitude + b.latitude_range() * (dlat as f64),
cp.longitude + b.longitude_range() * (dlon as f64),
)
};
encode(&gl, hash.len() as u8)
}
pub fn neighbors(hash: &str) -> Box<Vec<String>> {
Box::new(vec![
hash.to_string(),
neighbor(hash, (-1, -1)),
neighbor(hash, (-1, 0)),
neighbor(hash, (-1, 1)),
neighbor(hash, ( 0, -1)),
neighbor(hash, ( 0, 1)),
neighbor(hash, ( 1, -1)),
neighbor(hash, ( 1, 0)),
neighbor(hash, ( 1, 1)),
])
}