本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
5.2KB

  1. // The code here was obtained from:
  2. // https://github.com/mmcloughlin/geohash
  3. // The MIT License (MIT)
  4. // Copyright (c) 2015 Michael McLoughlin
  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. // The above copyright notice and this permission notice shall be included in all
  12. // copies or substantial portions of the Software.
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. // SOFTWARE.
  20. package geo
  21. import (
  22. "math"
  23. )
  24. // encoding encapsulates an encoding defined by a given base32 alphabet.
  25. type encoding struct {
  26. enc string
  27. dec [256]byte
  28. }
  29. // newEncoding constructs a new encoding defined by the given alphabet,
  30. // which must be a 32-byte string.
  31. func newEncoding(encoder string) *encoding {
  32. e := new(encoding)
  33. e.enc = encoder
  34. for i := 0; i < len(e.dec); i++ {
  35. e.dec[i] = 0xff
  36. }
  37. for i := 0; i < len(encoder); i++ {
  38. e.dec[encoder[i]] = byte(i)
  39. }
  40. return e
  41. }
  42. // Decode string into bits of a 64-bit word. The string s may be at most 12
  43. // characters.
  44. func (e *encoding) decode(s string) uint64 {
  45. x := uint64(0)
  46. for i := 0; i < len(s); i++ {
  47. x = (x << 5) | uint64(e.dec[s[i]])
  48. }
  49. return x
  50. }
  51. // Encode bits of 64-bit word into a string.
  52. func (e *encoding) encode(x uint64) string {
  53. b := [12]byte{}
  54. for i := 0; i < 12; i++ {
  55. b[11-i] = e.enc[x&0x1f]
  56. x >>= 5
  57. }
  58. return string(b[:])
  59. }
  60. // Base32Encoding with the Geohash alphabet.
  61. var base32encoding = newEncoding("0123456789bcdefghjkmnpqrstuvwxyz")
  62. // BoundingBox returns the region encoded by the given string geohash.
  63. func geoBoundingBox(hash string) geoBox {
  64. bits := uint(5 * len(hash))
  65. inthash := base32encoding.decode(hash)
  66. return geoBoundingBoxIntWithPrecision(inthash, bits)
  67. }
  68. // Box represents a rectangle in latitude/longitude space.
  69. type geoBox struct {
  70. minLat float64
  71. maxLat float64
  72. minLng float64
  73. maxLng float64
  74. }
  75. // Round returns a point inside the box, making an effort to round to minimal
  76. // precision.
  77. func (b geoBox) round() (lat, lng float64) {
  78. x := maxDecimalPower(b.maxLat - b.minLat)
  79. lat = math.Ceil(b.minLat/x) * x
  80. x = maxDecimalPower(b.maxLng - b.minLng)
  81. lng = math.Ceil(b.minLng/x) * x
  82. return
  83. }
  84. // precalculated for performance
  85. var exp232 = math.Exp2(32)
  86. // errorWithPrecision returns the error range in latitude and longitude for in
  87. // integer geohash with bits of precision.
  88. func errorWithPrecision(bits uint) (latErr, lngErr float64) {
  89. b := int(bits)
  90. latBits := b / 2
  91. lngBits := b - latBits
  92. latErr = math.Ldexp(180.0, -latBits)
  93. lngErr = math.Ldexp(360.0, -lngBits)
  94. return
  95. }
  96. // minDecimalPlaces returns the minimum number of decimal places such that
  97. // there must exist an number with that many places within any range of width
  98. // r. This is intended for returning minimal precision coordinates inside a
  99. // box.
  100. func maxDecimalPower(r float64) float64 {
  101. m := int(math.Floor(math.Log10(r)))
  102. return math.Pow10(m)
  103. }
  104. // Encode the position of x within the range -r to +r as a 32-bit integer.
  105. func encodeRange(x, r float64) uint32 {
  106. p := (x + r) / (2 * r)
  107. return uint32(p * exp232)
  108. }
  109. // Decode the 32-bit range encoding X back to a value in the range -r to +r.
  110. func decodeRange(X uint32, r float64) float64 {
  111. p := float64(X) / exp232
  112. x := 2*r*p - r
  113. return x
  114. }
  115. // Squash the even bitlevels of X into a 32-bit word. Odd bitlevels of X are
  116. // ignored, and may take any value.
  117. func squash(X uint64) uint32 {
  118. X &= 0x5555555555555555
  119. X = (X | (X >> 1)) & 0x3333333333333333
  120. X = (X | (X >> 2)) & 0x0f0f0f0f0f0f0f0f
  121. X = (X | (X >> 4)) & 0x00ff00ff00ff00ff
  122. X = (X | (X >> 8)) & 0x0000ffff0000ffff
  123. X = (X | (X >> 16)) & 0x00000000ffffffff
  124. return uint32(X)
  125. }
  126. // Deinterleave the bits of X into 32-bit words containing the even and odd
  127. // bitlevels of X, respectively.
  128. func deinterleave(X uint64) (uint32, uint32) {
  129. return squash(X), squash(X >> 1)
  130. }
  131. // BoundingBoxIntWithPrecision returns the region encoded by the integer
  132. // geohash with the specified precision.
  133. func geoBoundingBoxIntWithPrecision(hash uint64, bits uint) geoBox {
  134. fullHash := hash << (64 - bits)
  135. latInt, lngInt := deinterleave(fullHash)
  136. lat := decodeRange(latInt, 90)
  137. lng := decodeRange(lngInt, 180)
  138. latErr, lngErr := errorWithPrecision(bits)
  139. return geoBox{
  140. minLat: lat,
  141. maxLat: lat + latErr,
  142. minLng: lng,
  143. maxLng: lng + lngErr,
  144. }
  145. }
  146. // ----------------------------------------------------------------------
  147. // Decode the string geohash to a (lat, lng) point.
  148. func GeoHashDecode(hash string) (lat, lng float64) {
  149. box := geoBoundingBox(hash)
  150. return box.round()
  151. }
上海开阖软件有限公司 沪ICP备12045867号-1