// The code here was obtained from: // https://github.com/mmcloughlin/geohash // The MIT License (MIT) // Copyright (c) 2015 Michael McLoughlin // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package geo import ( "math" ) // encoding encapsulates an encoding defined by a given base32 alphabet. type encoding struct { enc string dec [256]byte } // newEncoding constructs a new encoding defined by the given alphabet, // which must be a 32-byte string. func newEncoding(encoder string) *encoding { e := new(encoding) e.enc = encoder for i := 0; i < len(e.dec); i++ { e.dec[i] = 0xff } for i := 0; i < len(encoder); i++ { e.dec[encoder[i]] = byte(i) } return e } // Decode string into bits of a 64-bit word. The string s may be at most 12 // characters. func (e *encoding) decode(s string) uint64 { x := uint64(0) for i := 0; i < len(s); i++ { x = (x << 5) | uint64(e.dec[s[i]]) } return x } // Encode bits of 64-bit word into a string. func (e *encoding) encode(x uint64) string { b := [12]byte{} for i := 0; i < 12; i++ { b[11-i] = e.enc[x&0x1f] x >>= 5 } return string(b[:]) } // Base32Encoding with the Geohash alphabet. var base32encoding = newEncoding("0123456789bcdefghjkmnpqrstuvwxyz") // BoundingBox returns the region encoded by the given string geohash. func geoBoundingBox(hash string) geoBox { bits := uint(5 * len(hash)) inthash := base32encoding.decode(hash) return geoBoundingBoxIntWithPrecision(inthash, bits) } // Box represents a rectangle in latitude/longitude space. type geoBox struct { minLat float64 maxLat float64 minLng float64 maxLng float64 } // Round returns a point inside the box, making an effort to round to minimal // precision. func (b geoBox) round() (lat, lng float64) { x := maxDecimalPower(b.maxLat - b.minLat) lat = math.Ceil(b.minLat/x) * x x = maxDecimalPower(b.maxLng - b.minLng) lng = math.Ceil(b.minLng/x) * x return } // precalculated for performance var exp232 = math.Exp2(32) // errorWithPrecision returns the error range in latitude and longitude for in // integer geohash with bits of precision. func errorWithPrecision(bits uint) (latErr, lngErr float64) { b := int(bits) latBits := b / 2 lngBits := b - latBits latErr = math.Ldexp(180.0, -latBits) lngErr = math.Ldexp(360.0, -lngBits) return } // minDecimalPlaces 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. func maxDecimalPower(r float64) float64 { m := int(math.Floor(math.Log10(r))) return math.Pow10(m) } // Encode the position of x within the range -r to +r as a 32-bit integer. func encodeRange(x, r float64) uint32 { p := (x + r) / (2 * r) return uint32(p * exp232) } // Decode the 32-bit range encoding X back to a value in the range -r to +r. func decodeRange(X uint32, r float64) float64 { p := float64(X) / exp232 x := 2*r*p - r return x } // Squash the even bitlevels of X into a 32-bit word. Odd bitlevels of X are // ignored, and may take any value. func squash(X uint64) uint32 { 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 return uint32(X) } // Deinterleave the bits of X into 32-bit words containing the even and odd // bitlevels of X, respectively. func deinterleave(X uint64) (uint32, uint32) { return squash(X), squash(X >> 1) } // BoundingBoxIntWithPrecision returns the region encoded by the integer // geohash with the specified precision. func geoBoundingBoxIntWithPrecision(hash uint64, bits uint) geoBox { fullHash := hash << (64 - bits) latInt, lngInt := deinterleave(fullHash) lat := decodeRange(latInt, 90) lng := decodeRange(lngInt, 180) latErr, lngErr := errorWithPrecision(bits) return geoBox{ minLat: lat, maxLat: lat + latErr, minLng: lng, maxLng: lng + lngErr, } } // ---------------------------------------------------------------------- // Decode the string geohash to a (lat, lng) point. func GeoHashDecode(hash string) (lat, lng float64) { box := geoBoundingBox(hash) return box.round() }