extern crate md5;
extern crate chrono;
use chrono::{Datelike, NaiveDate};
#[cfg(feature="online")]
extern crate reqwest;
#[cfg(feature="online")]
extern crate regex;
#[cfg(feature="online")]
use regex::Regex;
fn bytes_to_f64(hex: &[u8]) -> f64 {
let mut curvalue = 0.0;
for (i, c) in hex.iter().enumerate() {
let mut d1 = (c / 16) as f64;
let mut d2: f64 = 16.0;
d2 = d2.powf((i * 2 + 1) as f64);
curvalue = curvalue + (d1 / d2);
d1 = (c % 16) as f64;
d2 = 16.0;
d2 = d2.powf((i * 2 + 2) as f64);
curvalue = curvalue + (d1 / d2);
}
curvalue
}
#[cfg(feature="offline")]
const DJIA: [((u64, u8, u8), &str); 32785] = include!("djia.txt");
#[cfg(feature="offline")]
fn search_djia_offline(date: NaiveDate) -> Option<&'static str> {
let year = date.year();
let month = date.month();
let day = date.day();
let date = (year as u64, month as u8, day as u8);
DJIA.binary_search_by_key(&date, |x| x.0).ok().map(|idx| DJIA[idx].1)
}
#[cfg(feature="online")]
fn search_djia_online(date: NaiveDate) -> Option<String> {
let year = date.year();
let month = date.month();
let day = date.day();
let client = reqwest::Client::new();
let djia_format = Regex::new(r#"^\d+\.\d+$"#).unwrap();
let url = format!("http://geo.crox.net/djia/{}/{:02}/{:02}", year, month, day);
if let Some(djia) = client.get(&url).send().map(|mut x| x.text().ok()).ok().unwrap_or(None) {
if djia_format.is_match(&djia) {
return Some(djia);
}
}
let url = format!("http://carabiner.peeron.com/xkcd/map/data/{}/{:02}/{:02}", year, month, day);
if let Some(djia) = client.get(&url).send().map(|mut x| x.text().ok()).ok().unwrap_or(None) {
if djia_format.is_match(&djia) {
return Some(djia);
}
}
None
}
#[derive(Debug)]
pub enum Error {
#[cfg(feature="offline")]
NotInOfflineData,
#[cfg(feature="online")]
NetworkError,
#[cfg(not(any(feature="online", feature="offline")))]
NoDataSource
}
#[allow(unreachable_code)]
pub fn search_djia(date: NaiveDate) -> Result<String, Error> {
#[cfg(feature="offline")]
{
if let Some(djia) = search_djia_offline(date) {
return Ok(djia.to_owned());
}
}
#[cfg(feature="online")]
{
if let Some(djia) = search_djia_online(date) {
return Ok(djia.to_owned());
} else {
return Err(Error::NetworkError);
}
}
#[cfg(feature="offline")]
{
return Err(Error::NotInOfflineData);
}
#[cfg(not(any(feature="online", feature="offline")))]
{
Err(Error::NoDataSource)
}
}
pub fn get_geohash(date: NaiveDate, (lat, lon): (f64, f64)) -> Option<(f64, f64)> {
let djia = if lon > -30.0 && date >= NaiveDate::from_ymd(2008, 5, 27) {
search_djia(date.pred()).ok()
} else {
search_djia(date).ok()
}?;
let (lat_offset, lon_offset) = djia_to_hash(date, &djia);
if lon < 0.0 {
Some((lat+lat_offset, lon-lon_offset))
} else {
Some((lat+lat_offset, lon+lon_offset))
}
}
pub fn get_globalhash(date: NaiveDate) -> Option<(f64, f64)> {
let djia = search_djia(date.pred()).ok()?;
Some(djia_to_globalhash(date, &djia))
}
pub fn djia_to_hash(date: NaiveDate, djia: &str) -> (f64, f64) {
let md5 = md5::compute(format!("{}-{:02}-{:02}-{}", date.year(), date.month(), date.day(), djia));
(bytes_to_f64(&md5[0..8]), bytes_to_f64(&md5[8..]))
}
pub fn djia_to_globalhash(date: NaiveDate, djia: &str) -> (f64, f64) {
let md5 = md5::compute(format!("{}-{:02}-{:02}-{}", date.year(), date.month(), date.day(), djia));
(bytes_to_f64(&md5[0..8]) * 180.0 - 90.0, bytes_to_f64(&md5[8..]) * 360.0 - 180.0)
}