├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── README.tmpl ├── asm_x86.s ├── base32.go ├── benchmark_test.go ├── decodecases_test.go ├── example_test.go ├── extensive_test.go ├── geohash.go ├── geohash_test.go ├── geohash_x86.go ├── go.mod ├── neighbors_testcases_test.go ├── script ├── bootstrap └── generate ├── stubs.s ├── testcases_test.go └── util_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | go-version: [1.22.x] 17 | platform: [ubuntu-latest, macos-latest, windows-latest] 18 | include: 19 | - go-version: 1.21.x 20 | platform: ubuntu-latest 21 | - go-version: 1.3.x 22 | platform: ubuntu-latest 23 | runs-on: ${{ matrix.platform }} 24 | env: 25 | GOPATH: ${{ github.workspace }} 26 | defaults: 27 | run: 28 | working-directory: ${{ env.GOPATH }}/src/github.com/${{ github.repository }} 29 | steps: 30 | - name: Install Go 31 | uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3 32 | with: 33 | go-version: ${{ matrix.go-version }} 34 | - name: Checkout code 35 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 36 | with: 37 | persist-credentials: false 38 | path: ${{ env.GOPATH }}/src/github.com/${{ github.repository }} 39 | - name: Build 40 | run: go build 41 | - name: Test 42 | run: go test -v -covermode count -coverprofile coverage.out 43 | - name: Upload Coverage 44 | uses: codecov/codecov-action@51d810878be5422784e86451c0e7c14e5860ec47 # v2.0.2 45 | with: 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | files: coverage.out 48 | flags: unittests 49 | 50 | generate: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Install Go 54 | uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8 # v2.1.3 55 | with: 56 | go-version: 1.22.x 57 | - name: Checkout code 58 | uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4 59 | with: 60 | persist-credentials: false 61 | - name: Bootstrap 62 | run: ./script/bootstrap 63 | - name: Generate 64 | run: ./script/generate 65 | - name: Git Status 66 | run: | 67 | git diff 68 | test -z "$(git status --porcelain)" 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | coverage.out 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael McLoughlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geohash 2 | 3 | Go [geohash](https://en.wikipedia.org/wiki/Geohash) library offering encoding 4 | and decoding for string and integer geohashes. 5 | 6 | [![go.dev Reference](https://img.shields.io/badge/doc-reference-007d9b?logo=go&style=flat-square)](https://pkg.go.dev/github.com/mmcloughlin/geohash) 7 | ![Build status](https://img.shields.io/github/actions/workflow/status/mmcloughlin/geohash/ci.yml?style=flat-square) 8 | [![Coverage](https://img.shields.io/codecov/c/github/mmcloughlin/geohash?style=flat-square)](https://codecov.io/github/mmcloughlin/geohash) 9 | [![Go Report Card](https://goreportcard.com/badge/github.com/mmcloughlin/geohash?style=flat-square)](https://goreportcard.com/report/github.com/mmcloughlin/geohash) 10 | 11 | ## Install 12 | 13 | Fetch the package with 14 | 15 | ``` 16 | go get github.com/mmcloughlin/geohash 17 | ``` 18 | 19 | And import it into your programs with 20 | 21 | ```go 22 | import "github.com/mmcloughlin/geohash" 23 | ``` 24 | 25 | ## Usage 26 | 27 | #### func ConvertIntToString 28 | 29 | ```go 30 | func ConvertIntToString(hash uint64, chars uint) string 31 | ``` 32 | ConvertIntToString converts an integer geohash to the equivalent string geohash 33 | with chars characters. The provided integer geohash is interpreted to have 34 | 5*chars bits of precision. 35 | 36 | #### func ConvertStringToInt 37 | 38 | ```go 39 | func ConvertStringToInt(hash string) (uint64, uint) 40 | ``` 41 | ConvertStringToInt converts a string geohash to the equivalent integer geohash. 42 | Returns the integer hash and its precision. 43 | 44 | #### func Decode 45 | 46 | ```go 47 | func Decode(hash string) (lat, lng float64) 48 | ``` 49 | Decode the string geohash to a (lat, lng) point. 50 | 51 | #### func DecodeCenter 52 | 53 | ```go 54 | func DecodeCenter(hash string) (lat, lng float64) 55 | ``` 56 | DecodeCenter decodes the string geohash to the central point of the bounding 57 | box. 58 | 59 | #### func DecodeInt 60 | 61 | ```go 62 | func DecodeInt(hash uint64) (lat, lng float64) 63 | ``` 64 | DecodeInt decodes the provided 64-bit integer geohash to a (lat, lng) point. 65 | 66 | #### func DecodeIntWithPrecision 67 | 68 | ```go 69 | func DecodeIntWithPrecision(hash uint64, bits uint) (lat, lng float64) 70 | ``` 71 | DecodeIntWithPrecision decodes the provided integer geohash with bits of 72 | precision to a (lat, lng) point. 73 | 74 | #### func Encode 75 | 76 | ```go 77 | func Encode(lat, lng float64) string 78 | ``` 79 | Encode the point (lat, lng) as a string geohash with the standard 12 characters 80 | of precision. 81 | 82 | #### func EncodeInt 83 | 84 | ```go 85 | func EncodeInt(lat, lng float64) uint64 86 | ``` 87 | EncodeInt encodes the point (lat, lng) to a 64-bit integer geohash. 88 | 89 | #### func EncodeIntWithPrecision 90 | 91 | ```go 92 | func EncodeIntWithPrecision(lat, lng float64, bits uint) uint64 93 | ``` 94 | EncodeIntWithPrecision encodes the point (lat, lng) to an integer with the 95 | specified number of bits. 96 | 97 | #### func EncodeWithPrecision 98 | 99 | ```go 100 | func EncodeWithPrecision(lat, lng float64, chars uint) string 101 | ``` 102 | EncodeWithPrecision encodes the point (lat, lng) as a string geohash with the 103 | specified number of characters of precision (max 12). 104 | 105 | #### func Neighbor 106 | 107 | ```go 108 | func Neighbor(hash string, direction Direction) string 109 | ``` 110 | Neighbor returns a geohash string that corresponds to the provided geohash's 111 | neighbor in the provided direction 112 | 113 | #### func NeighborInt 114 | 115 | ```go 116 | func NeighborInt(hash uint64, direction Direction) uint64 117 | ``` 118 | NeighborInt returns a uint64 that corresponds to the provided hash's neighbor in 119 | the provided direction at 64-bit precision. 120 | 121 | #### func NeighborIntWithPrecision 122 | 123 | ```go 124 | func NeighborIntWithPrecision(hash uint64, bits uint, direction Direction) uint64 125 | ``` 126 | NeighborIntWithPrecision returns a uint64s that corresponds to the provided 127 | hash's neighbor in the provided direction at the given precision. 128 | 129 | #### func Neighbors 130 | 131 | ```go 132 | func Neighbors(hash string) []string 133 | ``` 134 | Neighbors returns a slice of geohash strings that correspond to the provided 135 | geohash's neighbors. 136 | 137 | #### func NeighborsInt 138 | 139 | ```go 140 | func NeighborsInt(hash uint64) []uint64 141 | ``` 142 | NeighborsInt returns a slice of uint64s that correspond to the provided hash's 143 | neighbors at 64-bit precision. 144 | 145 | #### func NeighborsIntWithPrecision 146 | 147 | ```go 148 | func NeighborsIntWithPrecision(hash uint64, bits uint) []uint64 149 | ``` 150 | NeighborsIntWithPrecision returns a slice of uint64s that correspond to the 151 | provided hash's neighbors at the given precision. 152 | 153 | #### func Validate 154 | 155 | ```go 156 | func Validate(hash string) error 157 | ``` 158 | Validate the string geohash. 159 | 160 | #### type Box 161 | 162 | ```go 163 | type Box struct { 164 | MinLat float64 165 | MaxLat float64 166 | MinLng float64 167 | MaxLng float64 168 | } 169 | ``` 170 | 171 | Box represents a rectangle in latitude/longitude space. 172 | 173 | #### func BoundingBox 174 | 175 | ```go 176 | func BoundingBox(hash string) Box 177 | ``` 178 | BoundingBox returns the region encoded by the given string geohash. 179 | 180 | #### func BoundingBoxInt 181 | 182 | ```go 183 | func BoundingBoxInt(hash uint64) Box 184 | ``` 185 | BoundingBoxInt returns the region encoded by the given 64-bit integer geohash. 186 | 187 | #### func BoundingBoxIntWithPrecision 188 | 189 | ```go 190 | func BoundingBoxIntWithPrecision(hash uint64, bits uint) Box 191 | ``` 192 | BoundingBoxIntWithPrecision returns the region encoded by the integer geohash 193 | with the specified precision. 194 | 195 | #### func (Box) Center 196 | 197 | ```go 198 | func (b Box) Center() (lat, lng float64) 199 | ``` 200 | Center returns the center of the box. 201 | 202 | #### func (Box) Contains 203 | 204 | ```go 205 | func (b Box) Contains(lat, lng float64) bool 206 | ``` 207 | Contains decides whether (lat, lng) is contained in the box. The containment 208 | test is inclusive of the edges and corners. 209 | 210 | #### func (Box) Round 211 | 212 | ```go 213 | func (b Box) Round() (lat, lng float64) 214 | ``` 215 | Round returns a point inside the box, making an effort to round to minimal 216 | precision. 217 | 218 | #### type Direction 219 | 220 | ```go 221 | type Direction int 222 | ``` 223 | 224 | Direction represents directions in the latitute/longitude space. 225 | 226 | ```go 227 | const ( 228 | North Direction = iota 229 | NorthEast 230 | East 231 | SouthEast 232 | South 233 | SouthWest 234 | West 235 | NorthWest 236 | ) 237 | ``` 238 | Cardinal and intercardinal directions 239 | -------------------------------------------------------------------------------- /README.tmpl: -------------------------------------------------------------------------------- 1 | # geohash 2 | 3 | Go [geohash](https://en.wikipedia.org/wiki/Geohash) library offering encoding 4 | and decoding for string and integer geohashes. 5 | 6 | [![go.dev Reference](https://img.shields.io/badge/doc-reference-007d9b?logo=go&style=flat-square)](https://pkg.go.dev/github.com/mmcloughlin/geohash) 7 | ![Build status](https://img.shields.io/github/actions/workflow/status/mmcloughlin/geohash/ci.yml?style=flat-square) 8 | [![Coverage](https://img.shields.io/codecov/c/github/mmcloughlin/geohash?style=flat-square)](https://codecov.io/github/mmcloughlin/geohash) 9 | [![Go Report Card](https://goreportcard.com/badge/github.com/mmcloughlin/geohash?style=flat-square)](https://goreportcard.com/report/github.com/mmcloughlin/geohash) 10 | 11 | ## Install 12 | 13 | Fetch the package with 14 | 15 | ``` 16 | go get github.com/mmcloughlin/{{ .Name }} 17 | ``` 18 | 19 | And import it into your programs with 20 | 21 | ```go 22 | import "github.com/mmcloughlin/{{ .Name }}" 23 | ``` 24 | 25 | {{ .EmitUsage }} 26 | -------------------------------------------------------------------------------- /asm_x86.s: -------------------------------------------------------------------------------- 1 | // +build amd64,go1.6 2 | 3 | #include "textflag.h" 4 | 5 | // func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) 6 | TEXT ·cpuid(SB), NOSPLIT, $0-24 7 | MOVL eaxArg+0(FP), AX 8 | MOVL ecxArg+4(FP), CX 9 | CPUID 10 | MOVL AX, eax+8(FP) 11 | MOVL BX, ebx+12(FP) 12 | MOVL CX, ecx+16(FP) 13 | MOVL DX, edx+20(FP) 14 | RET 15 | 16 | // func EncodeInt(lat, lng float64) uint64 17 | TEXT ·EncodeInt(SB), NOSPLIT, $0 18 | CMPB ·useAsm(SB), $1 19 | JNE fallback 20 | 21 | #define LATF X0 22 | #define LATI R8 23 | #define LNGF X1 24 | #define LNGI R9 25 | #define TEMP R10 26 | #define GHSH R11 27 | #define MASK BX 28 | 29 | MOVSD lat+0(FP), LATF 30 | MOVSD lng+8(FP), LNGF 31 | 32 | MOVQ $0x5555555555555555, MASK 33 | 34 | MULSD $(0.005555555555555556), LATF 35 | ADDSD $(1.5), LATF 36 | 37 | MULSD $(0.002777777777777778), LNGF 38 | ADDSD $(1.5), LNGF 39 | 40 | MOVQ LNGF, LNGI 41 | SHRQ $20, LNGI 42 | 43 | MOVQ LATF, LATI 44 | SHRQ $20, LATI 45 | PDEPQ MASK, LATI, GHSH 46 | 47 | PDEPQ MASK, LNGI, TEMP 48 | 49 | SHLQ $1, TEMP 50 | XORQ TEMP, GHSH 51 | 52 | MOVQ GHSH, ret+16(FP) 53 | RET 54 | 55 | fallback: 56 | JMP ·encodeInt(SB) 57 | -------------------------------------------------------------------------------- /base32.go: -------------------------------------------------------------------------------- 1 | package geohash 2 | 3 | // invalid is a placeholder for invalid character decodings. 4 | const invalid = 0xff 5 | 6 | // encoding encapsulates an encoding defined by a given base32 alphabet. 7 | type encoding struct { 8 | encode string 9 | decode [256]byte 10 | } 11 | 12 | // newEncoding constructs a new encoding defined by the given alphabet, 13 | // which must be a 32-byte string. 14 | func newEncoding(encoder string) *encoding { 15 | e := new(encoding) 16 | e.encode = encoder 17 | for i := 0; i < len(e.decode); i++ { 18 | e.decode[i] = invalid 19 | } 20 | for i := 0; i < len(encoder); i++ { 21 | e.decode[encoder[i]] = byte(i) 22 | } 23 | return e 24 | } 25 | 26 | // ValidByte reports whether b is part of the encoding. 27 | func (e *encoding) ValidByte(b byte) bool { 28 | return e.decode[b] != invalid 29 | } 30 | 31 | // Decode string into bits of a 64-bit word. The string s may be at most 12 32 | // characters. 33 | func (e *encoding) Decode(s string) uint64 { 34 | x := uint64(0) 35 | for i := 0; i < len(s); i++ { 36 | x = (x << 5) | uint64(e.decode[s[i]]) 37 | } 38 | return x 39 | } 40 | 41 | // Encode bits of 64-bit word into a string. 42 | func (e *encoding) Encode(x uint64) string { 43 | b := [12]byte{} 44 | for i := 0; i < 12; i++ { 45 | b[11-i] = e.encode[x&0x1f] 46 | x >>= 5 47 | } 48 | return string(b[:]) 49 | } 50 | 51 | // Base32Encoding with the Geohash alphabet. 52 | var base32encoding = newEncoding("0123456789bcdefghjkmnpqrstuvwxyz") 53 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package geohash 4 | 5 | import ( 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkEncodeInt(b *testing.B) { 11 | points := RandomPoints(1024) 12 | b.ResetTimer() 13 | for i := 0; i < b.N; i++ { 14 | EncodeInt(points[i%1024][0], points[i%1024][1]) 15 | } 16 | } 17 | 18 | func BenchmarkEncode(b *testing.B) { 19 | points := RandomPoints(1024) 20 | b.ResetTimer() 21 | for i := 0; i < b.N; i++ { 22 | Encode(points[i%1024][0], points[i%1024][1]) 23 | } 24 | } 25 | 26 | func BenchmarkEncodeWithPrecision(b *testing.B) { 27 | points := RandomPoints(1024) 28 | for chars := uint(1); chars <= 12; chars++ { 29 | name := strconv.FormatUint(uint64(chars), 10) 30 | b.Run(name, func(b *testing.B) { 31 | for i := 0; i < b.N; i++ { 32 | EncodeWithPrecision(points[i%1024][0], points[i%1024][1], chars) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | func BenchmarkDecodeInt(b *testing.B) { 39 | geohashes := RandomIntGeohashes(1024) 40 | b.ResetTimer() 41 | for i := 0; i < b.N; i++ { 42 | DecodeInt(geohashes[i%1024]) 43 | } 44 | } 45 | 46 | func BenchmarkDecode(b *testing.B) { 47 | for chars := uint(1); chars <= 12; chars++ { 48 | name := strconv.FormatUint(uint64(chars), 10) 49 | b.Run(name, func(b *testing.B) { 50 | geohashes := RandomStringGeohashesWithPrecision(1024, chars) 51 | b.ResetTimer() 52 | for i := 0; i < b.N; i++ { 53 | Decode(geohashes[i%1024]) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package geohash_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mmcloughlin/geohash" 7 | ) 8 | 9 | func Example() { 10 | // Uluru in Australian Outback 11 | lat, lng := -25.345457, 131.036192 12 | 13 | // Encode a full 12 character string geohash 14 | fmt.Println(geohash.Encode(lat, lng)) 15 | 16 | // Or at lower precision 17 | fmt.Println(geohash.EncodeWithPrecision(lat, lng, 6)) 18 | 19 | // As an integer 20 | fmt.Printf("%016x\n", geohash.EncodeInt(lat, lng)) 21 | 22 | // Decode to a point 23 | fmt.Println(geohash.Decode("qgmpvf18")) 24 | 25 | // or to a bounding box 26 | fmt.Println(geohash.BoundingBox("qgmpvf18")) 27 | 28 | // Output: 29 | // qgmpvf18h86e 30 | // qgmpvf 31 | // b3e75db828820cd5 32 | // -25.3454 131.036 33 | // {-25.345458984375 -25.345287322998047 131.03599548339844 131.03633880615234} 34 | } 35 | 36 | func ExampleEncode() { 37 | fmt.Println(geohash.Encode(48.858, 2.294)) 38 | // Output: u09tunq6qp66 39 | } 40 | 41 | func ExampleEncodeInt() { 42 | fmt.Printf("%016x\n", geohash.EncodeInt(48.858, 2.294)) 43 | // Output: d0139d52c6b54c69 44 | } 45 | 46 | func ExampleEncodeIntWithPrecision() { 47 | fmt.Printf("%08x\n", geohash.EncodeIntWithPrecision(48.858, 2.294, 32)) 48 | // Output: d0139d52 49 | } 50 | 51 | func ExampleEncodeWithPrecision() { 52 | fmt.Println(geohash.EncodeWithPrecision(48.858, 2.294, 5)) 53 | // Output: u09tu 54 | } 55 | 56 | func ExampleDecode() { 57 | lat, lng := geohash.Decode("u09tunq6") 58 | fmt.Printf("%.3f %.3f\n", lat, lng) 59 | // Output: 48.858 2.294 60 | } 61 | 62 | func ExampleDecodeInt() { 63 | lat, lng := geohash.DecodeInt(0xd0139d52c6b54c69) 64 | fmt.Printf("%.3f %.3f\n", lat, lng) 65 | // Output: 48.858 2.294 66 | } 67 | 68 | func ExampleDecodeIntWithPrecision() { 69 | lat, lng := geohash.DecodeIntWithPrecision(0xd013, uint(16)) 70 | fmt.Printf("%.3f %.3f\n", lat, lng) 71 | // Output: 48.600 2.000 72 | } 73 | -------------------------------------------------------------------------------- /extensive_test.go: -------------------------------------------------------------------------------- 1 | package geohash 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | // TestCase objects are generated from independent code to verify we get the 9 | // same results. See testcases_test.go. 10 | type TestCase struct { 11 | hashInt uint64 12 | hash string 13 | lat, lng float64 14 | } 15 | 16 | // Test we get the same string geohashes. 17 | func TestEncode(t *testing.T) { 18 | for _, c := range testcases { 19 | hash := Encode(c.lat, c.lng) 20 | if c.hash != hash { 21 | t.Errorf("incorrect encode string result for (%v,%v): %s != %s", 22 | c.lat, c.lng, c.hash, hash) 23 | } 24 | } 25 | } 26 | 27 | // Test we get the same integer geohashes. 28 | func TestEncodeInt(t *testing.T) { 29 | for _, c := range testcases { 30 | hashInt := EncodeInt(c.lat, c.lng) 31 | if c.hashInt != hashInt { 32 | t.Errorf("incorrect encode integer result for (%v,%v): %016x != %016x xor %016x", 33 | c.lat, c.lng, c.hashInt, hashInt, c.hashInt^hashInt) 34 | } 35 | } 36 | } 37 | 38 | // Verify the prefix property. 39 | func TestPrefixProperty(t *testing.T) { 40 | for _, c := range testcases { 41 | for chars := uint(1); chars <= 12; chars++ { 42 | hash := EncodeWithPrecision(c.lat, c.lng, chars) 43 | pre := c.hash[:chars] 44 | if pre != hash { 45 | t.Errorf("incorrect encode string result for (%v,%v) at precision %d: %s != %s", 46 | c.lat, c.lng, chars, pre, hash) 47 | } 48 | } 49 | } 50 | } 51 | 52 | // Verify all string hashes pass validation. 53 | func TestValidate(t *testing.T) { 54 | for _, c := range testcases { 55 | if err := Validate(c.hash); err != nil { 56 | t.Fatalf("unexpected error for %q: %s", c.hash, err) 57 | } 58 | } 59 | } 60 | 61 | // Test bounding boxes for string geohashes. 62 | func TestBoundingBox(t *testing.T) { 63 | for _, c := range testcases { 64 | box := BoundingBox(c.hash) 65 | if !box.Contains(c.lat, c.lng) { 66 | t.Errorf("incorrect bounding box for %s", c.hash) 67 | } 68 | } 69 | } 70 | 71 | // Test bounding boxes for integer geohashes. 72 | func TestBoundingBoxInt(t *testing.T) { 73 | for _, c := range testcases { 74 | box := BoundingBoxInt(c.hashInt) 75 | if !box.Contains(c.lat, c.lng) { 76 | t.Errorf("incorrect bounding box for 0x%x", c.hashInt) 77 | } 78 | } 79 | } 80 | 81 | // Crude test of integer decoding. 82 | func TestDecodeInt(t *testing.T) { 83 | for _, c := range testcases { 84 | lat, lng := DecodeInt(c.hashInt) 85 | if math.Abs(lat-c.lat) > 0.0000001 { 86 | t.Errorf("large error in decoded latitude for 0x%x", c.hashInt) 87 | } 88 | if math.Abs(lng-c.lng) > 0.0000001 { 89 | t.Errorf("large error in decoded longitude for 0x%x", c.hashInt) 90 | } 91 | } 92 | } 93 | 94 | func TestConvertStringToInt(t *testing.T) { 95 | for _, c := range testcases { 96 | for chars := uint(1); chars <= 12; chars++ { 97 | hash := c.hash[:chars] 98 | inthash, bits := ConvertStringToInt(hash) 99 | expect := c.hashInt >> (64 - bits) 100 | if inthash != expect { 101 | t.Fatalf("incorrect conversion to integer for %q: got %#x expect %#x", hash, inthash, expect) 102 | } 103 | } 104 | } 105 | } 106 | 107 | func TestConvertIntToString(t *testing.T) { 108 | for _, c := range testcases { 109 | for chars := uint(1); chars <= 12; chars++ { 110 | bits := 5 * chars 111 | inthash := c.hashInt >> (64 - bits) 112 | hash := ConvertIntToString(inthash, chars) 113 | expect := c.hash[:chars] 114 | if hash != expect { 115 | t.Fatalf("incorrect conversion to string for %#x: got %q expect %q", inthash, hash, expect) 116 | } 117 | } 118 | } 119 | } 120 | 121 | type DecodeTestCase struct { 122 | hash string 123 | box Box 124 | } 125 | 126 | // Test decoding at various precisions. 127 | func TestDecode(t *testing.T) { 128 | for _, c := range decodecases { 129 | lat, lng := Decode(c.hash) 130 | if !c.box.Contains(lat, lng) { 131 | t.Errorf("hash %s decoded to %f,%f should lie in %+v", 132 | c.hash, lat, lng, c.box) 133 | } 134 | } 135 | } 136 | 137 | // Test decoding at various precisions. 138 | func TestDecodeCenter(t *testing.T) { 139 | for _, c := range decodecases { 140 | lat, lng := DecodeCenter(c.hash) 141 | if !c.box.Contains(lat, lng) { 142 | t.Errorf("hash %s decoded to %f,%f should lie in %+v", 143 | c.hash, lat, lng, c.box) 144 | } 145 | } 146 | } 147 | 148 | // Test roundtrip decoding then encoding again. 149 | func TestDecodeThenEncode(t *testing.T) { 150 | for _, c := range decodecases { 151 | precision := uint(len(c.hash)) 152 | lat, lng := Decode(c.hash) 153 | rehashed := EncodeWithPrecision(lat, lng, precision) 154 | if c.hash != rehashed { 155 | t.Errorf("hash %s decoded and re-encoded to %s", c.hash, rehashed) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /geohash.go: -------------------------------------------------------------------------------- 1 | // Package geohash provides encoding and decoding of string and integer 2 | // geohashes. 3 | package geohash 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "math" 9 | ) 10 | 11 | // Direction represents directions in the latitute/longitude space. 12 | type Direction int 13 | 14 | // Cardinal and intercardinal directions 15 | const ( 16 | North Direction = iota 17 | NorthEast 18 | East 19 | SouthEast 20 | South 21 | SouthWest 22 | West 23 | NorthWest 24 | ) 25 | 26 | // Encode the point (lat, lng) as a string geohash with the standard 12 27 | // characters of precision. 28 | func Encode(lat, lng float64) string { 29 | return EncodeWithPrecision(lat, lng, 12) 30 | } 31 | 32 | // EncodeWithPrecision encodes the point (lat, lng) as a string geohash with 33 | // the specified number of characters of precision (max 12). 34 | func EncodeWithPrecision(lat, lng float64, chars uint) string { 35 | bits := 5 * chars 36 | inthash := EncodeIntWithPrecision(lat, lng, bits) 37 | enc := base32encoding.Encode(inthash) 38 | return enc[12-chars:] 39 | } 40 | 41 | // EncodeInt encodes the point (lat, lng) to a 64-bit integer geohash. 42 | func EncodeInt(lat, lng float64) uint64 43 | 44 | // encodeInt provides a Go implementation of integer geohash. This is the 45 | // default implementation of EncodeInt, but optimized versions are provided 46 | // for certain architectures. 47 | func encodeInt(lat, lng float64) uint64 { 48 | latInt := encodeRange(lat, 90) 49 | lngInt := encodeRange(lng, 180) 50 | return interleave(latInt, lngInt) 51 | } 52 | 53 | // EncodeIntWithPrecision encodes the point (lat, lng) to an integer with the 54 | // specified number of bits. 55 | func EncodeIntWithPrecision(lat, lng float64, bits uint) uint64 { 56 | hash := EncodeInt(lat, lng) 57 | return hash >> (64 - bits) 58 | } 59 | 60 | // Box represents a rectangle in latitude/longitude space. 61 | type Box struct { 62 | MinLat float64 63 | MaxLat float64 64 | MinLng float64 65 | MaxLng float64 66 | } 67 | 68 | // Center returns the center of the box. 69 | func (b Box) Center() (lat, lng float64) { 70 | lat = (b.MinLat + b.MaxLat) / 2.0 71 | lng = (b.MinLng + b.MaxLng) / 2.0 72 | return 73 | } 74 | 75 | // Contains decides whether (lat, lng) is contained in the box. The 76 | // containment test is inclusive of the edges and corners. 77 | func (b Box) Contains(lat, lng float64) bool { 78 | return (b.MinLat <= lat && lat <= b.MaxLat && 79 | b.MinLng <= lng && lng <= b.MaxLng) 80 | } 81 | 82 | // minDecimalPlaces returns the minimum number of decimal places such that 83 | // there must exist an number with that many places within any range of width 84 | // r. This is intended for returning minimal precision coordinates inside a 85 | // box. 86 | func maxDecimalPower(r float64) float64 { 87 | m := int(math.Floor(math.Log10(r))) 88 | return math.Pow10(m) 89 | } 90 | 91 | // Round returns a point inside the box, making an effort to round to minimal 92 | // precision. 93 | func (b Box) Round() (lat, lng float64) { 94 | x := maxDecimalPower(b.MaxLat - b.MinLat) 95 | lat = math.Ceil(b.MinLat/x) * x 96 | x = maxDecimalPower(b.MaxLng - b.MinLng) 97 | lng = math.Ceil(b.MinLng/x) * x 98 | return 99 | } 100 | 101 | // errorWithPrecision returns the error range in latitude and longitude for in 102 | // integer geohash with bits of precision. 103 | func errorWithPrecision(bits uint) (latErr, lngErr float64) { 104 | b := int(bits) 105 | latBits := b / 2 106 | lngBits := b - latBits 107 | latErr = math.Ldexp(180.0, -latBits) 108 | lngErr = math.Ldexp(360.0, -lngBits) 109 | return 110 | } 111 | 112 | // BoundingBox returns the region encoded by the given string geohash. 113 | func BoundingBox(hash string) Box { 114 | bits := uint(5 * len(hash)) 115 | inthash := base32encoding.Decode(hash) 116 | return BoundingBoxIntWithPrecision(inthash, bits) 117 | } 118 | 119 | // BoundingBoxIntWithPrecision returns the region encoded by the integer 120 | // geohash with the specified precision. 121 | func BoundingBoxIntWithPrecision(hash uint64, bits uint) Box { 122 | fullHash := hash << (64 - bits) 123 | latInt, lngInt := deinterleave(fullHash) 124 | lat := decodeRange(latInt, 90) 125 | lng := decodeRange(lngInt, 180) 126 | latErr, lngErr := errorWithPrecision(bits) 127 | return Box{ 128 | MinLat: lat, 129 | MaxLat: lat + latErr, 130 | MinLng: lng, 131 | MaxLng: lng + lngErr, 132 | } 133 | } 134 | 135 | // BoundingBoxInt returns the region encoded by the given 64-bit integer 136 | // geohash. 137 | func BoundingBoxInt(hash uint64) Box { 138 | return BoundingBoxIntWithPrecision(hash, 64) 139 | } 140 | 141 | // Validate the string geohash. 142 | func Validate(hash string) error { 143 | // Check length. 144 | if 5*len(hash) > 64 { 145 | return errors.New("too long") 146 | } 147 | 148 | // Check characters. 149 | for i := 0; i < len(hash); i++ { 150 | if !base32encoding.ValidByte(hash[i]) { 151 | return fmt.Errorf("invalid character %q", hash[i]) 152 | } 153 | } 154 | 155 | return nil 156 | } 157 | 158 | // Decode the string geohash to a (lat, lng) point. 159 | func Decode(hash string) (lat, lng float64) { 160 | box := BoundingBox(hash) 161 | return box.Round() 162 | } 163 | 164 | // DecodeCenter decodes the string geohash to the central point of the bounding box. 165 | func DecodeCenter(hash string) (lat, lng float64) { 166 | box := BoundingBox(hash) 167 | return box.Center() 168 | } 169 | 170 | // DecodeIntWithPrecision decodes the provided integer geohash with bits of 171 | // precision to a (lat, lng) point. 172 | func DecodeIntWithPrecision(hash uint64, bits uint) (lat, lng float64) { 173 | box := BoundingBoxIntWithPrecision(hash, bits) 174 | return box.Round() 175 | } 176 | 177 | // DecodeInt decodes the provided 64-bit integer geohash to a (lat, lng) point. 178 | func DecodeInt(hash uint64) (lat, lng float64) { 179 | return DecodeIntWithPrecision(hash, 64) 180 | } 181 | 182 | // ConvertStringToInt converts a string geohash to the equivalent integer 183 | // geohash. Returns the integer hash and its precision. 184 | func ConvertStringToInt(hash string) (uint64, uint) { 185 | return base32encoding.Decode(hash), uint(5 * len(hash)) 186 | } 187 | 188 | // ConvertIntToString converts an integer geohash to the equivalent string 189 | // geohash with chars characters. The provided integer geohash is interpreted 190 | // to have 5*chars bits of precision. 191 | func ConvertIntToString(hash uint64, chars uint) string { 192 | enc := base32encoding.Encode(hash) 193 | return enc[12-chars:] 194 | } 195 | 196 | // Neighbors returns a slice of geohash strings that correspond to the provided 197 | // geohash's neighbors. 198 | func Neighbors(hash string) []string { 199 | box := BoundingBox(hash) 200 | lat, lng := box.Center() 201 | latDelta := box.MaxLat - box.MinLat 202 | lngDelta := box.MaxLng - box.MinLng 203 | precision := uint(len(hash)) 204 | return []string{ 205 | // N 206 | EncodeWithPrecision(lat+latDelta, lng, precision), 207 | // NE, 208 | EncodeWithPrecision(lat+latDelta, lng+lngDelta, precision), 209 | // E, 210 | EncodeWithPrecision(lat, lng+lngDelta, precision), 211 | // SE, 212 | EncodeWithPrecision(lat-latDelta, lng+lngDelta, precision), 213 | // S, 214 | EncodeWithPrecision(lat-latDelta, lng, precision), 215 | // SW, 216 | EncodeWithPrecision(lat-latDelta, lng-lngDelta, precision), 217 | // W, 218 | EncodeWithPrecision(lat, lng-lngDelta, precision), 219 | // NW 220 | EncodeWithPrecision(lat+latDelta, lng-lngDelta, precision), 221 | } 222 | } 223 | 224 | // NeighborsInt returns a slice of uint64s that correspond to the provided hash's 225 | // neighbors at 64-bit precision. 226 | func NeighborsInt(hash uint64) []uint64 { 227 | return NeighborsIntWithPrecision(hash, 64) 228 | } 229 | 230 | // NeighborsIntWithPrecision returns a slice of uint64s that correspond to the 231 | // provided hash's neighbors at the given precision. 232 | func NeighborsIntWithPrecision(hash uint64, bits uint) []uint64 { 233 | box := BoundingBoxIntWithPrecision(hash, bits) 234 | lat, lng := box.Center() 235 | latDelta := box.MaxLat - box.MinLat 236 | lngDelta := box.MaxLng - box.MinLng 237 | return []uint64{ 238 | // N 239 | EncodeIntWithPrecision(lat+latDelta, lng, bits), 240 | // NE, 241 | EncodeIntWithPrecision(lat+latDelta, lng+lngDelta, bits), 242 | // E, 243 | EncodeIntWithPrecision(lat, lng+lngDelta, bits), 244 | // SE, 245 | EncodeIntWithPrecision(lat-latDelta, lng+lngDelta, bits), 246 | // S, 247 | EncodeIntWithPrecision(lat-latDelta, lng, bits), 248 | // SW, 249 | EncodeIntWithPrecision(lat-latDelta, lng-lngDelta, bits), 250 | // W, 251 | EncodeIntWithPrecision(lat, lng-lngDelta, bits), 252 | // NW 253 | EncodeIntWithPrecision(lat+latDelta, lng-lngDelta, bits), 254 | } 255 | } 256 | 257 | // Neighbor returns a geohash string that corresponds to the provided 258 | // geohash's neighbor in the provided direction 259 | func Neighbor(hash string, direction Direction) string { 260 | return Neighbors(hash)[direction] 261 | } 262 | 263 | // NeighborInt returns a uint64 that corresponds to the provided hash's 264 | // neighbor in the provided direction at 64-bit precision. 265 | func NeighborInt(hash uint64, direction Direction) uint64 { 266 | return NeighborsIntWithPrecision(hash, 64)[direction] 267 | } 268 | 269 | // NeighborIntWithPrecision returns a uint64s that corresponds to the 270 | // provided hash's neighbor in the provided direction at the given precision. 271 | func NeighborIntWithPrecision(hash uint64, bits uint, direction Direction) uint64 { 272 | return NeighborsIntWithPrecision(hash, bits)[direction] 273 | } 274 | 275 | // precalculated for performance 276 | var exp232 = math.Exp2(32) 277 | 278 | // Encode the position of x within the range -r to +r as a 32-bit integer. 279 | func encodeRange(x, r float64) uint32 { 280 | p := (x + r) / (2 * r) 281 | return uint32(p * exp232) 282 | } 283 | 284 | // Decode the 32-bit range encoding X back to a value in the range -r to +r. 285 | func decodeRange(X uint32, r float64) float64 { 286 | p := float64(X) / exp232 287 | x := 2*r*p - r 288 | return x 289 | } 290 | 291 | // Spread out the 32 bits of x into 64 bits, where the bits of x occupy even 292 | // bit positions. 293 | func spread(x uint32) uint64 { 294 | X := uint64(x) 295 | X = (X | (X << 16)) & 0x0000ffff0000ffff 296 | X = (X | (X << 8)) & 0x00ff00ff00ff00ff 297 | X = (X | (X << 4)) & 0x0f0f0f0f0f0f0f0f 298 | X = (X | (X << 2)) & 0x3333333333333333 299 | X = (X | (X << 1)) & 0x5555555555555555 300 | return X 301 | } 302 | 303 | // Interleave the bits of x and y. In the result, x and y occupy even and odd 304 | // bitlevels, respectively. 305 | func interleave(x, y uint32) uint64 { 306 | return spread(x) | (spread(y) << 1) 307 | } 308 | 309 | // Squash the even bitlevels of X into a 32-bit word. Odd bitlevels of X are 310 | // ignored, and may take any value. 311 | func squash(X uint64) uint32 { 312 | X &= 0x5555555555555555 313 | X = (X | (X >> 1)) & 0x3333333333333333 314 | X = (X | (X >> 2)) & 0x0f0f0f0f0f0f0f0f 315 | X = (X | (X >> 4)) & 0x00ff00ff00ff00ff 316 | X = (X | (X >> 8)) & 0x0000ffff0000ffff 317 | X = (X | (X >> 16)) & 0x00000000ffffffff 318 | return uint32(X) 319 | } 320 | 321 | // Deinterleave the bits of X into 32-bit words containing the even and odd 322 | // bitlevels of X, respectively. 323 | func deinterleave(X uint64) (uint32, uint32) { 324 | return squash(X), squash(X >> 1) 325 | } 326 | -------------------------------------------------------------------------------- /geohash_test.go: -------------------------------------------------------------------------------- 1 | package geohash 2 | 3 | import ( 4 | "testing" 5 | "testing/quick" 6 | ) 7 | 8 | func TestInterleaving(t *testing.T) { 9 | cases := []struct { 10 | x, y uint32 11 | X uint64 12 | }{ 13 | {0x00000000, 0x00000000, 0x0000000000000000}, 14 | {0xffffffff, 0x00000000, 0x5555555555555555}, 15 | {0x789e22e9, 0x8ed4182e, 0x95e8e37406845ce9}, 16 | {0xb96346bb, 0xf8a80f02, 0xefc19c8510be454d}, 17 | {0xa1dfc6c2, 0x01c886f9, 0x4403f1d5d03cfa86}, 18 | {0xfb59e296, 0xad2c6c02, 0xdde719e17ca4411c}, 19 | {0x94e0bbf2, 0xb520e8b2, 0xcb325c00edc5df0c}, 20 | {0x1638ca5f, 0x5e16a514, 0x23bc0768d8661375}, 21 | {0xe15bbbf7, 0x0f6bf376, 0x54ab39cfef4f7f3d}, 22 | {0x06a476a7, 0x94f35ec7, 0x8234ee1a37bce43f}, 23 | } 24 | for _, c := range cases { 25 | res := interleave(c.x, c.y) 26 | if c.X != res { 27 | t.Errorf("incorrect interleave result") 28 | } 29 | 30 | x, y := deinterleave(c.X) 31 | if c.x != x || c.y != y { 32 | t.Errorf("incorrect deinterleave result") 33 | } 34 | } 35 | } 36 | 37 | func TestBase32Decode(t *testing.T) { 38 | x := base32encoding.Decode("ezs42") 39 | if 0xdfe082 != x { 40 | t.Errorf("incorrect base64 decoding") 41 | } 42 | } 43 | 44 | func TestBase32Encode(t *testing.T) { 45 | s := base32encoding.Encode(0xdfe082) 46 | if "0000000ezs42" != s { 47 | t.Errorf("incorrect base64 encoding") 48 | } 49 | } 50 | 51 | func TestBoxRound(t *testing.T) { 52 | f := func() bool { 53 | b := RandomBox() 54 | lat, lng := b.Round() 55 | return b.Contains(lat, lng) 56 | } 57 | if err := quick.Check(f, nil); err != nil { 58 | t.Error(err) 59 | } 60 | } 61 | 62 | func TestBoxCenter(t *testing.T) { 63 | b := Box{ 64 | MinLat: 1, 65 | MaxLat: 2, 66 | MinLng: 3, 67 | MaxLng: 4, 68 | } 69 | lat, lng := b.Center() 70 | if 1.5 != lat || 3.5 != lng { 71 | t.Errorf("incorrect box center") 72 | } 73 | } 74 | 75 | func TestBoxContains(t *testing.T) { 76 | b := Box{ 77 | MinLat: 1, 78 | MaxLat: 2, 79 | MinLng: 3, 80 | MaxLng: 4, 81 | } 82 | cases := []struct { 83 | lat, lng float64 84 | expect bool 85 | }{ 86 | {1.5, 3.5, true}, 87 | {0.5, 3.5, false}, 88 | {7.0, 3.5, false}, 89 | {1.5, 1.5, false}, 90 | {1.5, 9.5, false}, 91 | {1, 3, true}, 92 | {1, 4, true}, 93 | {2, 3, true}, 94 | {2, 4, true}, 95 | } 96 | for _, c := range cases { 97 | if c.expect != b.Contains(c.lat, c.lng) { 98 | t.Errorf("contains %f,%f should be %t", c.lat, c.lng, c.expect) 99 | } 100 | } 101 | } 102 | 103 | func TestWikipediaExample(t *testing.T) { 104 | h := EncodeWithPrecision(42.6, -5.6, 5) 105 | if "ezs42" != h { 106 | t.Errorf("incorrect encoding") 107 | } 108 | } 109 | 110 | func TestLeadingZero(t *testing.T) { 111 | h := EncodeWithPrecision(-74.761330, -140.309714, 6) 112 | if 6 != len(h) { 113 | t.Errorf("incorrect geohash length") 114 | } 115 | if "0fsnxn" != h { 116 | t.Errorf("incorrect encoding") 117 | } 118 | } 119 | 120 | func TestValidateErrors(t *testing.T) { 121 | cases := []struct { 122 | Hash string 123 | ExpectError string 124 | }{ 125 | { 126 | Hash: "0123456789bcdefghjkmnpqrstuvwxyz", 127 | ExpectError: "too long", 128 | }, 129 | { 130 | Hash: "bcopqr", 131 | ExpectError: "invalid character 'o'", 132 | }, 133 | } 134 | for _, c := range cases { 135 | err := Validate(c.Hash) 136 | if err == nil { 137 | t.Fatalf("%s: got nil, expected %q", c.Hash, c.ExpectError) 138 | } 139 | if err.Error() != c.ExpectError { 140 | t.Errorf("%s: got %q, expected %q", c.Hash, err, c.ExpectError) 141 | } 142 | } 143 | } 144 | 145 | func TestNeighbors(t *testing.T) { 146 | for _, c := range neighborsTestCases { 147 | neighbors := Neighbors(c.hashStr) 148 | for i, neighbor := range neighbors { 149 | expected := c.hashStrNeighbors[i] 150 | if neighbor != expected { 151 | t.Errorf("actual: %v \n expected: %v\n", neighbors, c.hashStrNeighbors) 152 | break 153 | } 154 | } 155 | } 156 | } 157 | 158 | func TestNeighborsInt(t *testing.T) { 159 | cases := []struct { 160 | hash uint64 161 | neighbors []uint64 162 | }{ 163 | { 164 | hash: 6456360425798343065, 165 | neighbors: []uint64{ 166 | 6456360425798343068, 167 | 6456360425798343070, 168 | 6456360425798343067, 169 | 6456360425798343066, 170 | 6456360425798343064, 171 | 6456360425798343058, 172 | 6456360425798343059, 173 | 6456360425798343062, 174 | }, 175 | }, 176 | } 177 | 178 | for _, c := range cases { 179 | neighbors := NeighborsInt(c.hash) 180 | for i, neighbor := range neighbors { 181 | expected := c.neighbors[i] 182 | if neighbor != c.neighbors[i] { 183 | t.Errorf("neighbor: %v does not match expected: %v", neighbor, expected) 184 | } 185 | } 186 | } 187 | } 188 | 189 | func TestNeighborsIntWithPrecision(t *testing.T) { 190 | for _, c := range neighborsTestCases { 191 | neighbors := NeighborsIntWithPrecision(c.hashInt, c.hashIntBitDepth) 192 | for i, neighbor := range neighbors { 193 | expected := c.hashIntNeighbors[i] 194 | if neighbor != expected { 195 | t.Errorf("actual: %v \n expected: %v\n", neighbors, c.hashIntNeighbors) 196 | break 197 | } 198 | } 199 | } 200 | } 201 | 202 | func TestNeighbor(t *testing.T) { 203 | c := neighborsTestCases[0] 204 | neighbor := Neighbor(c.hashStr, North) 205 | expected := c.hashStrNeighbors[North] 206 | if neighbor != expected { 207 | t.Errorf("actual: %v \n expected: %v\n", neighbor, expected) 208 | } 209 | } 210 | 211 | func TestNeighborInt(t *testing.T) { 212 | cases := []struct { 213 | hash uint64 214 | neighborEast uint64 215 | }{ 216 | { 217 | hash: 6456360425798343065, 218 | neighborEast: 6456360425798343067, 219 | }, 220 | } 221 | 222 | for _, c := range cases { 223 | neighbor := NeighborInt(c.hash, East) 224 | expected := c.neighborEast 225 | if neighbor != expected { 226 | t.Errorf("actual: %v \n expected: %v\n", neighbor, expected) 227 | } 228 | } 229 | } 230 | 231 | func TestNeighborIntWithPrecision(t *testing.T) { 232 | c := neighborsTestCases[0] 233 | neighbor := NeighborIntWithPrecision(c.hashInt, c.hashIntBitDepth, South) 234 | expected := c.hashIntNeighbors[South] 235 | if neighbor != expected { 236 | t.Errorf("actual: %v \n expected: %v\n", neighbor, expected) 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /geohash_x86.go: -------------------------------------------------------------------------------- 1 | // +build amd64,go1.6 2 | 3 | package geohash 4 | 5 | // useAsm flag determines whether the assembly version of EncodeInt will be 6 | // used. By Default we fall back to encodeInt. 7 | var useAsm bool 8 | 9 | // cpuid executes the CPUID instruction to obtain processor identification and 10 | // feature information. 11 | func cpuid(eaxArg, ecxArg uint32) (eax, ebx, ecx, edx uint32) 12 | 13 | // hasBMI2 returns whether the CPU supports Bit Manipulation Instruction Set 14 | // 2. 15 | func hasBMI2() bool { 16 | _, ebx, _, _ := cpuid(7, 0) 17 | return ebx&(1<<8) != 0 18 | } 19 | 20 | // init determines whether to use assembly version by performing CPU feature 21 | // check. 22 | func init() { 23 | useAsm = hasBMI2() 24 | } 25 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mmcloughlin/geohash 2 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | # godocdown: templated documentation generator. 6 | go install github.com/robertkrimen/godocdown/godocdown@v0.0.0-20130622164427-0bfa04905481 7 | -------------------------------------------------------------------------------- /script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | # Update README. 6 | godocdown -template README.tmpl -o README.md 7 | -------------------------------------------------------------------------------- /stubs.s: -------------------------------------------------------------------------------- 1 | // +build !amd64 !go1.6 2 | 3 | // Define NOSPLIT ourselves since "textflag.h" is missing in old Go versions. 4 | #define NOSPLIT 4 5 | 6 | TEXT ·EncodeInt(SB), NOSPLIT, $0 7 | JMP ·encodeInt(SB) 8 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package geohash 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | ) 7 | 8 | func RandomPoint() (lat, lng float64) { 9 | lat = -90 + 180*rand.Float64() 10 | lng = -180 + 360*rand.Float64() 11 | return 12 | } 13 | 14 | func RandomPoints(n int) [][2]float64 { 15 | points := make([][2]float64, n) 16 | for i := 0; i < n; i++ { 17 | lat, lng := RandomPoint() 18 | points[i] = [2]float64{lat, lng} 19 | } 20 | return points 21 | } 22 | 23 | func RandomBox() Box { 24 | lat1, lng1 := RandomPoint() 25 | lat2, lng2 := RandomPoint() 26 | return Box{ 27 | MinLat: math.Min(lat1, lat2), 28 | MaxLat: math.Max(lat1, lat2), 29 | MinLng: math.Min(lng1, lng2), 30 | MaxLng: math.Max(lng1, lng2), 31 | } 32 | } 33 | 34 | func RandomStringGeohashWithPrecision(chars uint) string { 35 | const alphabet = "0123456789bcdefghjkmnpqrstuvwxyz" 36 | b := make([]byte, chars) 37 | for i := uint(0); i < chars; i++ { 38 | b[i] = alphabet[rand.Intn(32)] 39 | } 40 | return string(b) 41 | } 42 | 43 | func RandomStringGeohashesWithPrecision(n int, chars uint) []string { 44 | geohashes := make([]string, n) 45 | for i := 0; i < n; i++ { 46 | geohashes[i] = RandomStringGeohashWithPrecision(chars) 47 | } 48 | return geohashes 49 | } 50 | 51 | func RandomIntGeohash() uint64 { 52 | return (uint64(rand.Uint32()) << 32) | uint64(rand.Uint32()) 53 | } 54 | 55 | func RandomIntGeohashes(n int) []uint64 { 56 | geohashes := make([]uint64, n) 57 | for i := 0; i < n; i++ { 58 | geohashes[i] = RandomIntGeohash() 59 | } 60 | return geohashes 61 | } 62 | --------------------------------------------------------------------------------