Task: Geohash




Go: package main

import (
    "fmt"
    "strings"
)

type Location struct{ lat, lng float64 }

func (loc Location) String() string { return fmt.Sprintf("[%f, %f]", loc.lat, loc.lng) }

type Range struct{ lower, upper float64 }

var gBase32 = "0123456789bcdefghjkmnpqrstuvwxyz"

func encodeGeohash(loc Location, prec int) string {
    latRange := Range{-90, 90}
    lngRange := Range{-180, 180}
    var hash strings.Builder
    hashVal := 0
    bits := 0
    even := true
    for hash.Len() < prec {
        val := loc.lat
        rng := latRange
        if even {
            val = loc.lng
            rng = lngRange
        }
        mid := (rng.lower + rng.upper) / 2
        if val > mid {
            hashVal = (hashVal << 1) + 1
            rng = Range{mid, rng.upper}
            if even {
                lngRange = Range{mid, lngRange.upper}
            } else {
                latRange = Range{mid, latRange.upper}
            }
        } else {
            hashVal <<= 1
            if even {
                lngRange = Range{lngRange.lower, mid}
            } else {
                latRange = Range{latRange.lower, mid}
            }
        }
        even = !even
        if bits < 4 {
            bits++
        } else {
            bits = 0
            hash.WriteByte(gBase32[hashVal])
            hashVal = 0
        }
    }
    return hash.String()
}

func main() {
    locs := []Location{
        {51.433718, -0.214126},
        {51.433718, -0.214126},
        {57.64911, 10.40744},
    }
    precs := []int{2, 9, 11}

    for i, loc := range locs {
        geohash := encodeGeohash(loc, precs[i])
        fmt.Printf("geohash for %v, precision %-2d = %s\n", loc, precs[i], geohash)
    }
}




Python: ch32 = "0123456789bcdefghjkmnpqrstuvwxyz"
bool2ch = {f"{i:05b}": ch for i, ch in enumerate(ch32)}
ch2bool = {v : k for k, v in bool2ch.items()}

def bisect(val, mn, mx, bits):
    mid = (mn + mx) / 2
    if val < mid:
        bits <<= 1                        
        mx = mid                          
    else:
        bits = bits << 1 | 1              
        mn = mid                          

    return mn, mx, bits

def encoder(lat, lng, pre):
    latmin, latmax = -90, 90
    lngmin, lngmax = -180, 180
    bits = 0
    for i in range(pre * 5):
        if i % 2:
            
            latmin, latmax, bits = bisect(lat, latmin, latmax, bits)
        else:
            
            lngmin, lngmax, bits = bisect(lng, lngmin, lngmax, bits)
    
    b = f"{bits:0{pre * 5}b}"
    geo = (bool2ch[b[i*5: (i+1)*5]] for i in range(pre))

    return ''.join(geo)

def decoder(geo):
    minmaxes, latlong = [[-90.0, 90.0], [-180.0, 180.0]], True
    for c in geo:
        for bit in ch2bool[c]:
            minmaxes[latlong][bit != '1'] = sum(minmaxes[latlong]) / 2
            latlong = not latlong

    return minmaxes

if __name__ == '__main__':
    for (lat, lng), pre in [([51.433718, -0.214126],  2),
                            ([51.433718, -0.214126],  9),
                            ([57.64911,  10.40744] , 11),
                            ([57.64911,  10.40744] , 22)]:
        print("encoder(lat=%f, lng=%f, pre=%i) = %r"
              % (lat, lng, pre, encoder(lat, lng, pre)))