1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
pub mod base32;

/// Crate geohash provides encoding and decoding of string and integer
/// geohashes.

/// Direction represents directions in the latitute/longitude space.
pub type Direction = usize;

/// Cardinal and intercardinal directions
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;

/// Encode the point (lat, lng) as a string geohash with the standard 12
/// characters of precision.
pub fn encode(lat: f64, lng: f64) -> String {
    encode_with_precision(lat, lng, 12)
}

/// encode_with_precision encodes the point (lat, lng) as a string geohash with
/// the specified number of characters of precision (max 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()
}

/// encode_int encodes the point (lat, lng) to a 64-bit integer geohash.
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)
}

/// encode_int_with_precision encodes the point (lat, lng) to an integer with the
/// specified number of bits.
pub fn encode_int_with_precision(lat: f64, lng: f64, bits: usize) -> u64 {
    let hash = encode_int(lat, lng);
    hash >> (64 - bits)
}

/// Box represents a rectangle in latitude/longitude space.
#[derive(Debug)]
pub struct Box {
    pub min_lat: f64,
    pub max_lat: f64,
    pub min_lng: f64,
    pub max_lng: f64,
}

impl Box {
    /// center returns the center of the box (lat, lng).
    pub fn center(&self) -> (f64, f64) {
        (
            (self.min_lat + self.max_lat) / 2.0,
            (self.min_lng + self.max_lng) / 2.0,
        )
    }
    /// contains decides whether (lat, lng) is contained in the box. The
    /// containment test is inclusive of the edges and corners.
    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
    }

    /// round returns a point inside the box, making an effort to round to minimal
    /// precision.
    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)
    }
}

/// max_decimal_power returns the minimum number of decimal places such that
/// there must exist an number with that many places within any range of width
/// r. This is intended for returning minimal precision coordinates inside a
/// box.
fn max_decimal_power(r: f64) -> f64 {
    10f64.powf(r.log10().floor())
}

fn ldexp(x: f64, exp: i32) -> f64 {
    x * 2.0f64.powi(exp)
}

/// error_with_precision returns the error range in latitude and longitude for in
/// integer geohash with bits of precision. (lat_err, lng_err)
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)
}

/// bounding_box returns the region encoded by the given string geohash.
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)
}

/// bounding_box_int_with_precision returns the region encoded by the integer
/// geohash with the specified precision.
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
}

/// bounding_box_int returns the region encoded by the given 64-bit integer
/// geohash.
pub fn bounding_box_int(hash: u64) -> Box {
    bounding_box_int_with_precision(hash, 64)
}

/// Validavalidatete the string geohash.
pub fn validate(hash: &str) -> Result<bool, String> {
    // Check length.
    if 5 * hash.len() > 64 {
        return Err("too long".to_owned());
    }

    // Check characters.
    for b in hash.bytes() {
        if !base32::valid_byte(b) {
            return Err(format!("invalid character {}", b));
        }
    }
    Ok(true)
}

/// decode the string geohash to a (lat, lng) point.
pub fn decode(hash: &str) -> (f64, f64) {
    let b = bounding_box(hash);
    b.round()
}

/// decode_center decodes the string geohash to the central point (lat, lng) of the bounding box.
pub fn decode_center(hash: &str) -> (f64, f64) {
    let b = bounding_box(hash);
    b.center()
}

/// decode_int_with_precision decodes the provided integer geohash with bits of
/// precision to a (lat, lng) point.
pub fn decode_int_with_precision(hash: u64, bits: usize) -> (f64, f64) {
    let b = bounding_box_int_with_precision(hash, bits);
    return b.round();
}

/// decode_int_with_precision decodes the provided 64-bit integer geohash to a (lat, lng) point.
pub fn decode_int(hash: u64) -> (f64, f64) {
    decode_int_with_precision(hash, 64)
}

/// neighbors returns a slice of geohash strings that correspond to the provided
/// geohash's neighbors.
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();
    [
        // N
        encode_with_precision(lat + lat_delta, lng, precision),
        // NE,
        encode_with_precision(lat + lat_delta, lng + lng_delta, precision),
        // E,
        encode_with_precision(lat, lng + lng_delta, precision),
        // SE,
        encode_with_precision(lat - lat_delta, lng + lng_delta, precision),
        // S,
        encode_with_precision(lat - lat_delta, lng, precision),
        // SW,
        encode_with_precision(lat - lat_delta, lng - lng_delta, precision),
        // W,
        encode_with_precision(lat, lng - lng_delta, precision),
        // NW
        encode_with_precision(lat + lat_delta, lng - lng_delta, precision),
    ]
}

/// neighbors_int returns a slice of uint64s that correspond to the provided hash's
/// neighbors at 64-bit precision.
pub fn neighbors_int(hash: u64) -> [u64; 8] {
    neighbors_int_with_precision(hash, 64)
}

/// neighbors_int_with_precision returns a slice of uint64s that correspond to the
/// provided hash's neighbors at the given precision.
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;
    [
        // N
        encode_int_with_precision(lat + lat_delta, lng, bits),
        // NE,
        encode_int_with_precision(lat + lat_delta, lng + lng_delta, bits),
        // E,
        encode_int_with_precision(lat, lng + lng_delta, bits),
        // SE,
        encode_int_with_precision(lat - lat_delta, lng + lng_delta, bits),
        // S,
        encode_int_with_precision(lat - lat_delta, lng, bits),
        // SW,
        encode_int_with_precision(lat - lat_delta, lng - lng_delta, bits),
        // W,
        encode_int_with_precision(lat, lng - lng_delta, bits),
        // NW
        encode_int_with_precision(lat + lat_delta, lng - lng_delta, bits),
    ]
}

/// neighbor returns a geohash string that corresponds to the provided
/// geohash's neighbor in the provided direction
pub fn neighbor(hash: &str, direction: Direction) -> String {
    neighbors(hash)[direction].to_owned()
}

/// neighbor_int returns a uint64 that corresponds to the provided hash's
/// neighbor in the provided direction at 64-bit precision.
pub fn neighbor_int(hash: u64, direction: Direction) -> u64 {
    neighbors_int_with_precision(hash, 64)[direction]
}

/// neighbor_int_with_precision returns a uint64s that corresponds to the
/// provided hash's neighbor in the provided direction at the given precision.
pub fn neighbor_int_with_precision(hash: u64, bits: usize, direction: Direction) -> u64 {
    neighbors_int_with_precision(hash, bits)[direction]
}

/// precalculated for performance
const EXP_232: f64 = 4.294967296e+09; // math.Exp2(32)

/// encode_range the position of x within the range -r to +r as a 32-bit integer.
fn encode_range(x: f64, r: f64) -> u32 {
    let p = (x + r) / (2.0 * r);
    (p * EXP_232) as u32
}

/// decode_range the 32-bit range encoding X back to a value in the range -r to +r.
fn decode_range(x: u32, r: f64) -> f64 {
    let p = x as f64 / EXP_232;
    let x = 2.0 * r * p - r;
    x
}

/// spread out the 32 bits of x into 64 bits, where the bits of x occupy even
/// bit positions.
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
}

/// interleave the bits of x and y. In the result, x and y occupy even and odd
/// bitlevels, respectively.
fn interleave(x: u32, y: u32) -> u64 {
    spread(x) | (spread(y) << 1)
}

/// squash the even bitlevels of X into a 32-bit word. Odd bitlevels of X are
/// ignored, and may take any value.
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
}

/// deinterleave the bits of X into 32-bit words containing the even and odd
/// bitlevels of X, respectively.
fn deinterleave(x: u64) -> (u32, u32) {
    (squash(x), squash(x >> 1))
}

#[cfg(test)]
mod tests;