Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
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.
174 lines
5.2 KiB
174 lines
5.2 KiB
// 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() |
|
}
|
|
|