87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
# Note: the alphabet in geohash differs from the common base32
|
|
# alphabet described in IETF's RFC 4648
|
|
# (http://tools.ietf.org/html/rfc4648)
|
|
from typing import Tuple
|
|
|
|
base32 = "0123456789bcdefghjkmnpqrstuvwxyz"
|
|
decodemap = {base32[i]: i for i in range(len(base32))}
|
|
|
|
|
|
def decode(geohash: str) -> Tuple[float, float, float, float]:
|
|
"""
|
|
Decode the geohash to its exact values, including the error
|
|
margins of the result. Returns four float values: latitude,
|
|
longitude, the plus/minus error for latitude (as a positive
|
|
number) and the plus/minus error for longitude (as a positive
|
|
number).
|
|
"""
|
|
lat_interval, lon_interval = (-90.0, 90.0), (-180.0, 180.0)
|
|
lat_err, lon_err = 90.0, 180.0
|
|
is_longitude = True
|
|
for c in geohash:
|
|
cd = decodemap[c]
|
|
for mask in [16, 8, 4, 2, 1]:
|
|
if is_longitude: # adds longitude info
|
|
lon_err /= 2
|
|
if cd & mask:
|
|
lon_interval = (
|
|
(lon_interval[0] + lon_interval[1]) / 2,
|
|
lon_interval[1],
|
|
)
|
|
else:
|
|
lon_interval = (
|
|
lon_interval[0],
|
|
(lon_interval[0] + lon_interval[1]) / 2,
|
|
)
|
|
else: # adds latitude info
|
|
lat_err /= 2
|
|
if cd & mask:
|
|
lat_interval = (
|
|
(lat_interval[0] + lat_interval[1]) / 2,
|
|
lat_interval[1],
|
|
)
|
|
else:
|
|
lat_interval = (
|
|
lat_interval[0],
|
|
(lat_interval[0] + lat_interval[1]) / 2,
|
|
)
|
|
is_longitude = not is_longitude
|
|
lat = (lat_interval[0] + lat_interval[1]) / 2
|
|
lon = (lon_interval[0] + lon_interval[1]) / 2
|
|
return lat, lon, lat_err, lon_err
|
|
|
|
|
|
def encode(latitude: float, longitude: float, precision: int = 12) -> str:
|
|
"""
|
|
Encode a position given in float arguments latitude, longitude to
|
|
a geohash which will have the character count precision.
|
|
"""
|
|
lat_interval, lon_interval = (-90.0, 90.0), (-180.0, 180.0)
|
|
geohash, bits = [], [16, 8, 4, 2, 1] # type: ignore
|
|
bit, ch = 0, 0
|
|
is_longitude = True
|
|
|
|
def next_interval(
|
|
curr: float, interval: Tuple[float, float], ch: int
|
|
) -> Tuple[Tuple[float, float], int]:
|
|
mid = (interval[0] + interval[1]) / 2
|
|
if curr > mid:
|
|
ch |= bits[bit]
|
|
return (mid, interval[1]), ch
|
|
else:
|
|
return (interval[0], mid), ch
|
|
|
|
while len(geohash) < precision:
|
|
if is_longitude:
|
|
lon_interval, ch = next_interval(longitude, lon_interval, ch)
|
|
else:
|
|
lat_interval, ch = next_interval(latitude, lat_interval, ch)
|
|
is_longitude = not is_longitude
|
|
if bit < 4:
|
|
bit += 1
|
|
else:
|
|
geohash += base32[ch]
|
|
bit = 0
|
|
ch = 0
|
|
return "".join(geohash)
|