-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from georust/feature/revamp
Revamp
- Loading branch information
Showing
7 changed files
with
424 additions
and
391 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
use ::neighbors::Direction; | ||
use ::{Coordinate, GeohashError, Neighbors, Rect}; | ||
|
||
use failure::Error; | ||
|
||
static BASE32_CODES: &'static [char] = &[ | ||
'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', | ||
]; | ||
|
||
/// Encode a coordinate to a geohash with length `len`. | ||
/// | ||
/// ### Examples | ||
/// | ||
/// Encoding a coordinate to a length five geohash: | ||
/// | ||
/// ```rust | ||
/// let coord = geohash::Coordinate { x: -120.6623, y: 35.3003 }; | ||
/// | ||
/// let geohash_string = geohash::encode(coord, 5).expect("Invalid coordinate"); | ||
/// | ||
/// assert_eq!(geohash_string, "9q60y"); | ||
/// ``` | ||
/// | ||
/// Encoding a coordinate to a length ten geohash: | ||
/// | ||
/// ```rust | ||
/// let coord = geohash::Coordinate { x: -120.6623, y: 35.3003 }; | ||
/// | ||
/// let geohash_string = geohash::encode(coord, 10).expect("Invalid coordinate"); | ||
/// | ||
/// assert_eq!(geohash_string, "9q60y60rhs"); | ||
/// ``` | ||
pub fn encode(c: Coordinate<f64>, len: usize) -> Result<String, Error> { | ||
let mut out = String::with_capacity(len); | ||
|
||
let mut bits: i8 = 0; | ||
let mut bits_total: i8 = 0; | ||
let mut hash_value: usize = 0; | ||
let mut max_lat = 90f64; | ||
let mut min_lat = -90f64; | ||
let mut max_lon = 180f64; | ||
let mut min_lon = -180f64; | ||
let mut mid: f64; | ||
|
||
if c.x < min_lon || c.x > max_lon || c.y < min_lat || c.y > max_lat { | ||
bail!(GeohashError::InvalidCoordinateRange { c }); | ||
} | ||
|
||
while out.len() < len { | ||
if bits_total % 2 == 0 { | ||
mid = (max_lon + min_lon) / 2f64; | ||
if c.x > mid { | ||
hash_value = (hash_value << 1) + 1usize; | ||
min_lon = mid; | ||
} else { | ||
hash_value <<= 1; | ||
max_lon = mid; | ||
} | ||
} else { | ||
mid = (max_lat + min_lat) / 2f64; | ||
if c.y > mid { | ||
hash_value = (hash_value << 1) + 1usize; | ||
min_lat = mid; | ||
} else { | ||
hash_value <<= 1; | ||
max_lat = mid; | ||
} | ||
} | ||
|
||
bits += 1; | ||
bits_total += 1; | ||
|
||
if bits == 5 { | ||
let code: char = BASE32_CODES[hash_value]; | ||
out.push(code); | ||
bits = 0; | ||
hash_value = 0; | ||
} | ||
} | ||
Ok(out) | ||
} | ||
|
||
/// Decode geohash string into latitude, longitude | ||
/// | ||
/// Parameters: | ||
/// Geohash encoded `&str` | ||
/// | ||
/// Returns: | ||
/// A four-element tuple describs a bound box: | ||
/// * min_lat | ||
/// * max_lat | ||
/// * min_lon | ||
/// * max_lon | ||
pub fn decode_bbox(hash_str: &str) -> Result<Rect<f64>, Error> { | ||
let mut is_lon = true; | ||
let mut max_lat = 90f64; | ||
let mut min_lat = -90f64; | ||
let mut max_lon = 180f64; | ||
let mut min_lon = -180f64; | ||
let mut mid: f64; | ||
let mut hash_value: usize; | ||
|
||
for c in hash_str.chars() { | ||
hash_value = BASE32_CODES | ||
.iter() | ||
.position(|n| *n == c) | ||
.ok_or_else(|| GeohashError::InvalidHashCharacter { character: c })?; | ||
|
||
for bs in 0..5 { | ||
let bit = (hash_value >> (4 - bs)) & 1usize; | ||
if is_lon { | ||
mid = (max_lon + min_lon) / 2f64; | ||
|
||
if bit == 1 { | ||
min_lon = mid; | ||
} else { | ||
max_lon = mid; | ||
} | ||
} else { | ||
mid = (max_lat + min_lat) / 2f64; | ||
|
||
if bit == 1 { | ||
min_lat = mid; | ||
} else { | ||
max_lat = mid; | ||
} | ||
} | ||
is_lon = !is_lon; | ||
} | ||
} | ||
|
||
Ok(Rect { | ||
min: Coordinate { | ||
x: min_lon, | ||
y: min_lat, | ||
}, | ||
max: Coordinate { | ||
x: max_lon, | ||
y: max_lat, | ||
}, | ||
}) | ||
} | ||
|
||
/// Decode a geohash into a coordinate with some longitude/latitude error. The | ||
/// return value is `(<coordinate>, <longitude error>, <latitude error>)`. | ||
/// | ||
/// ### Examples | ||
/// | ||
/// Decoding a length five geohash: | ||
/// | ||
/// ```rust | ||
/// let geohash_str = "9q60y"; | ||
/// | ||
/// let decoded = geohash::decode(geohash_str).expect("Invalid hash string"); | ||
/// | ||
/// assert_eq!( | ||
/// decoded, | ||
/// ( | ||
/// geohash::Coordinate { | ||
/// x: -120.65185546875, | ||
/// y: 35.31005859375, | ||
/// }, | ||
/// 0.02197265625, | ||
/// 0.02197265625, | ||
/// ), | ||
/// ); | ||
/// ``` | ||
/// | ||
/// Decoding a length ten geohash: | ||
/// | ||
/// ```rust | ||
/// let geohash_str = "9q60y60rhs"; | ||
/// | ||
/// let decoded = geohash::decode(geohash_str).expect("Invalid hash string"); | ||
/// | ||
/// assert_eq!( | ||
/// decoded, | ||
/// ( | ||
/// geohash::Coordinate { | ||
/// x: -120.66229999065399, | ||
/// y: 35.300298035144806, | ||
/// }, | ||
/// 0.000005364418029785156, | ||
/// 0.000002682209014892578, | ||
/// ), | ||
/// ); | ||
/// ``` | ||
pub fn decode(hash_str: &str) -> Result<(Coordinate<f64>, f64, f64), Error> { | ||
let rect = decode_bbox(hash_str)?; | ||
let c0 = rect.min; | ||
let c1 = rect.max; | ||
Ok(( | ||
Coordinate { | ||
x: (c0.x + c1.x) / 2f64, | ||
y: (c0.y + c1.y) / 2f64, | ||
}, | ||
(c1.x - c0.x) / 2f64, | ||
(c1.y - c0.y) / 2f64, | ||
)) | ||
} | ||
|
||
/// Find neighboring geohashes for the given geohash and direction. | ||
pub fn neighbor(hash_str: &str, direction: Direction) -> Result<String, Error> { | ||
let (coord, lon_err, lat_err) = decode(hash_str)?; | ||
let neighbor_coord = match direction.to_tuple() { | ||
(dlat, dlng) => Coordinate { | ||
x: coord.x + 2f64 * lon_err.abs() * dlng, | ||
y: coord.y + 2f64 * lat_err.abs() * dlat, | ||
}, | ||
}; | ||
encode(neighbor_coord, hash_str.len()) | ||
} | ||
|
||
/// Find all neighboring geohashes for the given geohash. | ||
/// | ||
/// ### Examples | ||
/// | ||
/// ``` | ||
/// let geohash_str = "9q60y60rhs"; | ||
/// | ||
/// let neighbors = geohash::neighbors(geohash_str).expect("Invalid hash string"); | ||
/// | ||
/// assert_eq!( | ||
/// neighbors, | ||
/// geohash::Neighbors { | ||
/// n: "9q60y60rht".to_owned(), | ||
/// ne: "9q60y60rhv".to_owned(), | ||
/// e: "9q60y60rhu".to_owned(), | ||
/// se: "9q60y60rhg".to_owned(), | ||
/// s: "9q60y60rhe".to_owned(), | ||
/// sw: "9q60y60rh7".to_owned(), | ||
/// w: "9q60y60rhk".to_owned(), | ||
/// nw: "9q60y60rhm".to_owned(), | ||
/// } | ||
/// ); | ||
/// ``` | ||
pub fn neighbors(hash_str: &str) -> Result<Neighbors, Error> { | ||
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)?, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
use ::Coordinate; | ||
|
||
#[derive(Debug, Fail)] | ||
pub enum GeohashError { | ||
#[fail(display = "invalid hash character: {}", character)] | ||
InvalidHashCharacter { character: char }, | ||
#[fail(display = "invalid coordinate range: {:?}", c)] | ||
InvalidCoordinateRange { c: Coordinate<f64> }, | ||
} |
Oops, something went wrong.