mirror of https://github.com/tidwall/tile38.git
258 lines
4.2 KiB
Go
258 lines
4.2 KiB
Go
// Derived from javascript at http://www.movable-type.co.uk/scripts/geohash.html
|
|
//
|
|
// Original copyright states...
|
|
// "Geohash encoding/decoding and associated functions (c) Chris Veness 2014 / MIT Licence"
|
|
|
|
package geohash
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"strings"
|
|
)
|
|
|
|
// Encode latitude/longitude to geohash, either to specified precision or to automatically evaluated precision.
|
|
func Encode(lat, lon float64, precision int) (string, error) {
|
|
var idx = 0 // index into base32 map
|
|
var bit = 0 // each char holds 5 bits
|
|
var evenBit = true
|
|
var latMin = -90.0
|
|
var latMax = 90.0
|
|
var lonMin = -180.0
|
|
var lonMax = 180.0
|
|
if precision < 1 {
|
|
return "", errors.New("invalid precision")
|
|
}
|
|
var geohash bytes.Buffer
|
|
for geohash.Len() < precision {
|
|
if evenBit {
|
|
// bisect E-W longitude
|
|
var lonMid = (lonMin + lonMax) / 2
|
|
if lon > lonMid {
|
|
idx = idx*2 + 1
|
|
lonMin = lonMid
|
|
} else {
|
|
idx = idx * 2
|
|
lonMax = lonMid
|
|
}
|
|
} else {
|
|
// bisect N-S latitude
|
|
var latMid = (latMin + latMax) / 2
|
|
if lat > latMid {
|
|
idx = idx*2 + 1
|
|
latMin = latMid
|
|
} else {
|
|
idx = idx * 2
|
|
latMax = latMid
|
|
}
|
|
}
|
|
evenBit = !evenBit
|
|
|
|
bit = bit + 1
|
|
if bit == 5 {
|
|
// 5 bits gives us a character: append it and start over
|
|
b := base32F(idx)
|
|
if b == '?' {
|
|
return "", errors.New("encoding error")
|
|
}
|
|
geohash.WriteByte(b)
|
|
bit = 0
|
|
idx = 0
|
|
}
|
|
}
|
|
return geohash.String(), nil
|
|
}
|
|
|
|
// Decode geohash to latitude/longitude (location is approximate centre of geohash cell, to reasonable precision).
|
|
func Decode(geohash string) (lat, lon float64, err error) {
|
|
swLat, swLon, neLat, neLon, err1 := Bounds(geohash) // <-- the hard work
|
|
if err1 != nil {
|
|
return 0, 0, err1
|
|
}
|
|
return (neLat-swLat)/2 + swLat, (neLon-swLon)/2 + swLon, nil
|
|
}
|
|
|
|
// Returns SW/NE latitude/longitude bounds of specified geohash.
|
|
func Bounds(geohash string) (swLat, swLon, neLat, neLon float64, err error) {
|
|
geohash = strings.ToLower(geohash)
|
|
var evenBit = true
|
|
var latMin = -90.0
|
|
var latMax = 90.0
|
|
var lonMin = -180.0
|
|
var lonMax = 180.0
|
|
for i := 0; i < len(geohash); i++ {
|
|
var chr = geohash[i]
|
|
var idx = base32R(chr)
|
|
if idx == -1 {
|
|
return 0, 0, 0, 0, errors.New("invalid geohash")
|
|
}
|
|
for n := uint(4); ; n-- {
|
|
var bitN = idx >> n & 1
|
|
if evenBit {
|
|
// longitude
|
|
var lonMid = (lonMin + lonMax) / 2
|
|
if bitN == 1 {
|
|
lonMin = lonMid
|
|
} else {
|
|
lonMax = lonMid
|
|
}
|
|
} else {
|
|
// latitude
|
|
var latMid = (latMin + latMax) / 2
|
|
if bitN == 1 {
|
|
latMin = latMid
|
|
} else {
|
|
latMax = latMid
|
|
}
|
|
}
|
|
evenBit = !evenBit
|
|
if n == 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return latMin, lonMin, latMax, lonMax, nil
|
|
}
|
|
|
|
func base32R(b byte) int {
|
|
switch b {
|
|
default:
|
|
return -1
|
|
case '0':
|
|
return 0
|
|
case '1':
|
|
return 1
|
|
case '2':
|
|
return 2
|
|
case '3':
|
|
return 3
|
|
case '4':
|
|
return 4
|
|
case '5':
|
|
return 5
|
|
case '6':
|
|
return 6
|
|
case '7':
|
|
return 7
|
|
case '8':
|
|
return 8
|
|
case '9':
|
|
return 9
|
|
case 'b':
|
|
return 10
|
|
case 'c':
|
|
return 11
|
|
case 'd':
|
|
return 12
|
|
case 'e':
|
|
return 13
|
|
case 'f':
|
|
return 14
|
|
case 'g':
|
|
return 15
|
|
case 'h':
|
|
return 16
|
|
case 'j':
|
|
return 17
|
|
case 'k':
|
|
return 18
|
|
case 'm':
|
|
return 19
|
|
case 'n':
|
|
return 20
|
|
case 'p':
|
|
return 21
|
|
case 'q':
|
|
return 22
|
|
case 'r':
|
|
return 23
|
|
case 's':
|
|
return 24
|
|
case 't':
|
|
return 25
|
|
case 'u':
|
|
return 26
|
|
case 'v':
|
|
return 27
|
|
case 'w':
|
|
return 28
|
|
case 'x':
|
|
return 29
|
|
case 'y':
|
|
return 30
|
|
case 'z':
|
|
return 31
|
|
}
|
|
}
|
|
|
|
func base32F(i int) byte {
|
|
switch i {
|
|
default:
|
|
return '?'
|
|
case 0:
|
|
return '0'
|
|
case 1:
|
|
return '1'
|
|
case 2:
|
|
return '2'
|
|
case 3:
|
|
return '3'
|
|
case 4:
|
|
return '4'
|
|
case 5:
|
|
return '5'
|
|
case 6:
|
|
return '6'
|
|
case 7:
|
|
return '7'
|
|
case 8:
|
|
return '8'
|
|
case 9:
|
|
return '9'
|
|
case 10:
|
|
return 'b'
|
|
case 11:
|
|
return 'c'
|
|
case 12:
|
|
return 'd'
|
|
case 13:
|
|
return 'e'
|
|
case 14:
|
|
return 'f'
|
|
case 15:
|
|
return 'g'
|
|
case 16:
|
|
return 'h'
|
|
case 17:
|
|
return 'j'
|
|
case 18:
|
|
return 'k'
|
|
case 19:
|
|
return 'm'
|
|
case 20:
|
|
return 'n'
|
|
case 21:
|
|
return 'p'
|
|
case 22:
|
|
return 'q'
|
|
case 23:
|
|
return 'r'
|
|
case 24:
|
|
return 's'
|
|
case 25:
|
|
return 't'
|
|
case 26:
|
|
return 'u'
|
|
case 27:
|
|
return 'v'
|
|
case 28:
|
|
return 'w'
|
|
case 29:
|
|
return 'x'
|
|
case 30:
|
|
return 'y'
|
|
case 31:
|
|
return 'z'
|
|
}
|
|
}
|