extern crate flate2;
extern crate rustc_serialize;
extern crate byteorder;
use std::{cmp, mem, sync};
use std::sync::atomic;
use std::io::prelude::*;
use std::io::BufReader;
use byteorder::{BigEndian, ReadBytesExt};
#[allow(warnings)]
mod tables;
pub fn lookup(lat: f64, lon: f64) -> Option<String> {
static SHARED: atomic::AtomicUsize = atomic::ATOMIC_USIZE_INIT;
static ONCE: sync::Once = sync::ONCE_INIT;
ONCE.call_once(|| {
let s = Box::new(TzSearch::new());
SHARED.store(unsafe {mem::transmute(s)}, atomic::Ordering::Relaxed);
});
let ptr = SHARED.load(atomic::Ordering::Relaxed);
assert!(ptr != 0);
let s = unsafe {&*(ptr as *const TzSearch)};
s.lookup(lat, lon)
}
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
struct TileKey(u32);
impl TileKey {
fn new(size: u8, x: u16, y: u16) -> TileKey {
let size = size as u32;
let x = x as u32;
let y = y as u32;
TileKey((size % 8) << 28 | (y % (1<<14)) << 14 | (x % (1<<14)))
}
}
#[derive(Debug)]
struct TileLooker {
tile: TileKey,
idx: u16,
}
#[derive(Debug)]
struct ZoomLevel {
tiles: Vec<TileLooker>
}
#[derive(Debug)]
pub struct TzSearch {
leaves: Vec<Zone>,
zoom_levels: Vec<ZoomLevel>,
}
enum Zone {
StaticZone(String),
OneBitTile([u16; 2], [u8; 8]),
Pixmap([u8; 128])
}
impl std::fmt::Debug for Zone {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Zone::StaticZone(ref s) => write!(f, "StaticZone({:?})", s),
Zone::OneBitTile(a, b) => write!(f, "OneBitTile({:?}, {:?})", &a[..], &b[..]),
Zone::Pixmap(c) => write!(f, "Pixmap({:?})", &c[..]),
}
}
}
impl TzSearch {
pub fn new() -> TzSearch {
let mut zoom_levels = vec![];
for data in tables::REV_ZOOM_LEVELS.iter().rev() {
let unb64d = rustc_serialize::base64::FromBase64::from_base64(*data).unwrap();
let mut slurp = vec![];
flate2::read::GzDecoder::new(&*unb64d).unwrap().read_to_end(&mut slurp).unwrap();
assert_eq!(slurp.len() % 6, 0);
let count = slurp.len() / 6;
let mut slurp = &slurp[..];
let mut tiles = Vec::with_capacity(count);
for _ in 0..count {
tiles.push(TileLooker {
tile: TileKey(slurp.read_u32::<BigEndian>().unwrap()),
idx: slurp.read_u16::<BigEndian>().unwrap(),
})
}
zoom_levels.push(ZoomLevel { tiles: tiles })
}
let mut leaves = Vec::with_capacity(tables::NUM_LEAVES);
let unb64d = rustc_serialize::base64::FromBase64::from_base64(tables::UNIQUE_LEAVES_PACKED)
.unwrap();
let mut ungz = BufReader::new(flate2::read::GzDecoder::new(&*unb64d).unwrap());
let mut buf = [0; 128];
for _ in 0..tables::NUM_LEAVES {
let zone = match ungz.read_u8().unwrap() {
b'S' => {
let mut zone_name = vec![];
ungz.read_until(0, &mut zone_name).unwrap();
if zone_name.last() == Some(&0) { zone_name.pop(); }
Zone::StaticZone(String::from_utf8(zone_name).unwrap())
}
b'2' => {
let idx = [ungz.read_u16::<BigEndian>().unwrap(),
ungz.read_u16::<BigEndian>().unwrap()];
let bits = ungz.read_u64::<BigEndian>().unwrap();
let mut rows = [0; 8];
for (y, place) in rows.iter_mut().enumerate() {
for x in 0..8 {
if bits & (1 << (y * 8 + x)) != 0 {
*place |= 1 << x
}
}
}
Zone::OneBitTile(idx, rows)
}
b'P' => {
let mut i = 0;
while i < 128 {
i += ungz.read(&mut buf[i..]).unwrap()
}
Zone::Pixmap(buf)
}
_ => panic!("unknown leaf type")
};
leaves.push(zone)
}
TzSearch {
zoom_levels: zoom_levels,
leaves: leaves
}
}
pub fn lookup(&self, lat: f64, long: f64) -> Option<String> {
fn clamp(x: isize, lim: isize) -> usize {
cmp::max(0, cmp::min(lim * tables::DEG_PIXELS as isize, x)) as usize
}
assert!(-90.0 <= lat && lat <= 90.0);
assert!(-180.0 <= long && long <= 180.0);
let x = ((long + 180.0) * (tables::DEG_PIXELS as f64)) as isize;
let y = ((90.0 - lat) * (tables::DEG_PIXELS as f64)) as isize;
let x = clamp(x, 360);
let y = clamp(y, 180);
self.lookup_pixel(x, y)
}
fn lookup_pixel(&self, x: usize, y: usize) -> Option<String> {
for level in (0..6).rev() {
let shift = 3 + level;
let xt = x >> shift;
let yt = y >> shift;
let tk = TileKey::new(level, xt as u16, yt as u16);
if let Some(ret) = self.zoom_level_lookup(&self.zoom_levels[level as usize], x, y, tk) {
return ret
}
}
None
}
fn zone_lookup(&self, zone: &Zone, x: usize, y: usize, tk: TileKey) -> Option<Option<String>> {
match *zone {
Zone::StaticZone(ref s) => Some(Some(s.clone())),
Zone::OneBitTile(idxs, rows) => {
let idx = if rows[y & 7] & (1 << (x & 7)) != 0 {
idxs[1]
} else {
idxs[0]
};
self.zone_lookup(&self.leaves[idx as usize], x, y, tk)
}
Zone::Pixmap(ref p) => {
let xx = x & 7;
let yy = y & 7;
let i = 2 * (yy * 8 + xx);
let idx = ((p[i] as usize) << 8) + p[i+1] as usize;
const OCEAN_INDEX: usize = 0xFFFF;
if idx == OCEAN_INDEX {
Some(None)
} else {
self.zone_lookup(&self.leaves[idx], x, y, tk)
}
}
}
}
fn zoom_level_lookup(&self, zl: &ZoomLevel, x: usize, y: usize, tk: TileKey)
-> Option<Option<String>>
{
let pos = zl.tiles.binary_search_by(|t| t.tile.cmp(&tk)).unwrap_or_else(|x| x);
match zl.tiles.get(pos) {
Some(tl) if tl.tile == tk => self.zone_lookup(&self.leaves[tl.idx as usize], x, y, tk),
_ => None
}
}
}
#[cfg(test)]
mod tests {
use super::{lookup, TzSearch};
#[test]
fn loads_ok() {
let _searcher = TzSearch::new();
}
#[test]
fn test_lookup_lat_long() {
let searcher = TzSearch::new();
let tests = [(37.7833, -122.4167, Some("America/Los_Angeles")),
(-33.8885, 151.1908, Some("Australia/Sydney")),
(0.0, 0.0, None)];
for &(lat, lon, want) in &tests {
let want = want.map(|s| s.to_string());
assert_eq!(searcher.lookup(lat, lon), want);
assert_eq!(lookup(lat, lon), want)
}
}
#[test]
fn test_lookup_pixel() {
let searcher = TzSearch::new();
let tests = [
(9200, 2410, Some("Asia/Phnom_Penh")),
(9047, 2488, Some("Asia/Phnom_Penh")),
(9290, 530, Some("Asia/Krasnoyarsk")),
(9290, 531, Some("Asia/Yakutsk")),
(2985, 1654, Some("America/Indiana/Vincennes")),
(2986, 1654, Some("America/Indiana/Marengo")),
(2986, 1655, Some("America/Indiana/Tell_City")),
(4000, 2000, None),
(3687, 1845, Some("Atlantic/Bermuda")),
(1747, 1486, Some("America/Los_Angeles")),
(2924, 2316, Some("America/Belize")),
];
for &(lat, lon, ref want) in &tests {
assert_eq!(searcher.lookup_pixel(lat, lon), want.map(|s| s.to_string()));
}
}
}