// Package geohash provides encoding and decoding of string and integer // geohashes. package geohash import ( "errors" "fmt" "math" ) // Direction represents directions in the latitute/longitude space. type Direction int // Cardinal and intercardinal directions const ( North Direction = iota NorthEast East SouthEast South SouthWest West NorthWest ) // Encode the point (lat, lng) as a string geohash with the standard 12 // characters of precision. func Encode(lat, lng float64) string { return EncodeWithPrecision(lat, lng, 12) } // EncodeWithPrecision encodes the point (lat, lng) as a string geohash with // the specified number of characters of precision (max 12). func EncodeWithPrecision(lat, lng float64, chars uint) string { bits := 5 * chars inthash := EncodeIntWithPrecision(lat, lng, bits) enc := base32encoding.Encode(inthash) return enc[12-chars:] } // EncodeInt encodes the point (lat, lng) to a 64-bit integer geohash. func EncodeInt(lat, lng float64) uint64 // encodeInt provides a Go implementation of integer geohash. This is the // default implementation of EncodeInt, but optimized versions are provided // for certain architectures. func encodeInt(lat, lng float64) uint64 { latInt := encodeRange(lat, 90) lngInt := encodeRange(lng, 180) return interleave(latInt, lngInt) } // EncodeIntWithPrecision encodes the point (lat, lng) to an integer with the // specified number of bits. func EncodeIntWithPrecision(lat, lng float64, bits uint) uint64 { hash := EncodeInt(lat, lng) return hash >> (64 - bits) } // Box represents a rectangle in latitude/longitude space. type Box struct { MinLat float64 MaxLat float64 MinLng float64 MaxLng float64 } // Center returns the center of the box. func (b Box) Center() (lat, lng float64) { lat = (b.MinLat + b.MaxLat) / 2.0 lng = (b.MinLng + b.MaxLng) / 2.0 return } // Contains decides whether (lat, lng) is contained in the box. The // containment test is inclusive of the edges and corners. func (b Box) Contains(lat, lng float64) bool { return (b.MinLat <= lat && lat <= b.MaxLat && b.MinLng <= lng && lng <= b.MaxLng) } // 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) } // Round returns a point inside the box, making an effort to round to minimal // precision. func (b Box) 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 } // 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 } // BoundingBox returns the region encoded by the given string geohash. func BoundingBox(hash string) Box { bits := uint(5 * len(hash)) inthash := base32encoding.Decode(hash) return BoundingBoxIntWithPrecision(inthash, bits) } // BoundingBoxIntWithPrecision returns the region encoded by the integer // geohash with the specified precision. func BoundingBoxIntWithPrecision(hash uint64, bits uint) Box { fullHash := hash << (64 - bits) latInt, lngInt := deinterleave(fullHash) lat := decodeRange(latInt, 90) lng := decodeRange(lngInt, 180) latErr, lngErr := errorWithPrecision(bits) return Box{ MinLat: lat, MaxLat: lat + latErr, MinLng: lng, MaxLng: lng + lngErr, } } // BoundingBoxInt returns the region encoded by the given 64-bit integer // geohash. func BoundingBoxInt(hash uint64) Box { return BoundingBoxIntWithPrecision(hash, 64) } // Validate the string geohash. func Validate(hash string) error { // Check length. if 5*len(hash) > 64 { return errors.New("too long") } // Check characters. for i := 0; i < len(hash); i++ { if !base32encoding.ValidByte(hash[i]) { return fmt.Errorf("invalid character %q", hash[i]) } } return nil } // Decode the string geohash to a (lat, lng) point. func Decode(hash string) (lat, lng float64) { box := BoundingBox(hash) return box.Round() } // DecodeCenter decodes the string geohash to the central point of the bounding box. func DecodeCenter(hash string) (lat, lng float64) { box := BoundingBox(hash) return box.Center() } // DecodeIntWithPrecision decodes the provided integer geohash with bits of // precision to a (lat, lng) point. func DecodeIntWithPrecision(hash uint64, bits uint) (lat, lng float64) { box := BoundingBoxIntWithPrecision(hash, bits) return box.Round() } // DecodeInt decodes the provided 64-bit integer geohash to a (lat, lng) point. func DecodeInt(hash uint64) (lat, lng float64) { return DecodeIntWithPrecision(hash, 64) } // ConvertStringToInt converts a string geohash to the equivalent integer // geohash. Returns the integer hash and its precision. func ConvertStringToInt(hash string) (uint64, uint) { return base32encoding.Decode(hash), uint(5 * len(hash)) } // ConvertIntToString converts an integer geohash to the equivalent string // geohash with chars characters. The provided integer geohash is interpreted // to have 5*chars bits of precision. func ConvertIntToString(hash uint64, chars uint) string { enc := base32encoding.Encode(hash) return enc[12-chars:] } // Neighbors returns a slice of geohash strings that correspond to the provided // geohash's neighbors. func Neighbors(hash string) []string { box := BoundingBox(hash) lat, lng := box.Center() latDelta := box.MaxLat - box.MinLat lngDelta := box.MaxLng - box.MinLng precision := uint(len(hash)) return []string{ // N EncodeWithPrecision(lat+latDelta, lng, precision), // NE, EncodeWithPrecision(lat+latDelta, lng+lngDelta, precision), // E, EncodeWithPrecision(lat, lng+lngDelta, precision), // SE, EncodeWithPrecision(lat-latDelta, lng+lngDelta, precision), // S, EncodeWithPrecision(lat-latDelta, lng, precision), // SW, EncodeWithPrecision(lat-latDelta, lng-lngDelta, precision), // W, EncodeWithPrecision(lat, lng-lngDelta, precision), // NW EncodeWithPrecision(lat+latDelta, lng-lngDelta, precision), } } // NeighborsInt returns a slice of uint64s that correspond to the provided hash's // neighbors at 64-bit precision. func NeighborsInt(hash uint64) []uint64 { return NeighborsIntWithPrecision(hash, 64) } // NeighborsIntWithPrecision returns a slice of uint64s that correspond to the // provided hash's neighbors at the given precision. func NeighborsIntWithPrecision(hash uint64, bits uint) []uint64 { box := BoundingBoxIntWithPrecision(hash, bits) lat, lng := box.Center() latDelta := box.MaxLat - box.MinLat lngDelta := box.MaxLng - box.MinLng return []uint64{ // N EncodeIntWithPrecision(lat+latDelta, lng, bits), // NE, EncodeIntWithPrecision(lat+latDelta, lng+lngDelta, bits), // E, EncodeIntWithPrecision(lat, lng+lngDelta, bits), // SE, EncodeIntWithPrecision(lat-latDelta, lng+lngDelta, bits), // S, EncodeIntWithPrecision(lat-latDelta, lng, bits), // SW, EncodeIntWithPrecision(lat-latDelta, lng-lngDelta, bits), // W, EncodeIntWithPrecision(lat, lng-lngDelta, bits), // NW EncodeIntWithPrecision(lat+latDelta, lng-lngDelta, bits), } } // Neighbor returns a geohash string that corresponds to the provided // geohash's neighbor in the provided direction func Neighbor(hash string, direction Direction) string { return Neighbors(hash)[direction] } // NeighborInt returns a uint64 that corresponds to the provided hash's // neighbor in the provided direction at 64-bit precision. func NeighborInt(hash uint64, direction Direction) uint64 { return NeighborsIntWithPrecision(hash, 64)[direction] } // NeighborIntWithPrecision returns a uint64s that corresponds to the // provided hash's neighbor in the provided direction at the given precision. func NeighborIntWithPrecision(hash uint64, bits uint, direction Direction) uint64 { return NeighborsIntWithPrecision(hash, bits)[direction] } // precalculated for performance var exp232 = math.Exp2(32) // 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 } // Spread out the 32 bits of x into 64 bits, where the bits of x occupy even // bit positions. func spread(x uint32) uint64 { X := uint64(x) 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 return X } // Interleave the bits of x and y. In the result, x and y occupy even and odd // bitlevels, respectively. func interleave(x, y uint32) uint64 { return 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. 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) }