Source

clojure-geohash / src / geohash / core.clj

Full commit
(ns geohash.core)

(def base32-codes "0123456789bcdefghjkmnpqrstuvwxyz")

(defn- base32-index [ch]
  (. base32-codes indexOf (str ch)))

(defstruct geocode-data
  :maxlon :minlon :maxlat :minlat
  :islon :bits :hashcode :hashstring)

(defn- do-encode
  [lat lon prec geocode-data]
  (if (= (count (:hashstring geocode-data)) prec)
    (:hashstring geocode-data)
    (if (true? (:islon geocode-data))
      (let [
            mid (/ (+ (:maxlon geocode-data) (:minlon geocode-data)) 2)
            maxlon (if (> lon mid) (:maxlon geocode-data) mid)
            minlon (if (> lon mid) mid (:minlon geocode-data))
            hashcode (if (> lon mid) 
                       (+ (bit-shift-left (:hashcode geocode-data) 1) 1) 
                       (+ (bit-shift-left (:hashcode geocode-data) 1) 0))
            bits (inc (:bits geocode-data))
            hashstring (if (= bits 5) 
                         (str (:hashstring geocode-data) (nth base32-codes hashcode)) 
                         (:hashstring geocode-data))]
        (recur lat lon prec
          (assoc geocode-data
            :maxlon maxlon
            :minlon minlon
            :hashcode (if (= bits 5) 0 hashcode)
            :bits (if (= bits 5) 0 bits)
            :hashstring hashstring
            :islon (not (:islon geocode-data)))))
      (let [
            mid (/ (+ (:maxlat geocode-data) (:minlat geocode-data)) 2)
            maxlat (if (> lat mid) (:maxlat geocode-data) mid)
            minlat (if (> lat mid) mid (:minlat geocode-data))
            hashcode (if (> lat mid) 
                       (+ (bit-shift-left (:hashcode geocode-data) 1) 1) 
                       (+ (bit-shift-left (:hashcode geocode-data) 1) 0))
            bits (inc (:bits geocode-data))
            hashstring (if (= bits 5) 
                         (str (:hashstring geocode-data) (nth base32-codes hashcode)) 
                         (:hashstring geocode-data))]
        (recur lat lon prec
          (assoc geocode-data
            :maxlat maxlat
            :minlat minlat
            :hashcode (if (= bits 5) 0 hashcode)
            :bits (if (= bits 5) 0 bits)
            :hashstring hashstring
            :islon (not (:islon geocode-data))))))))

(defn- do-decode-hashcode
  [bits hash-code geocode-data]
  (if (< bits 0) geocode-data
    (if (true? (:islon geocode-data))
      (let [
            local-bits (dec bits)
            bit (bit-and (bit-shift-right hash-code local-bits) 1)
            mid (/ (+ (:maxlon geocode-data) (:minlon geocode-data)) 2)
            minlon (if (= bit 1) mid (:minlon geocode-data))
            maxlon (if (= bit 1) (:maxlon geocode-data) mid)]
        (recur local-bits hash-code
          (assoc geocode-data
            :minlon minlon
            :maxlon maxlon
            :islon (not (:islon geocode-data)))))
      (let [
            local-bits (dec bits)
            bit (bit-and (bit-shift-right hash-code local-bits) 1)
            mid (/ (+ (:maxlat geocode-data) (:minlat geocode-data)) 2)
            minlat (if (= bit 1) mid (:minlat geocode-data))
            maxlat (if (= bit 1) (:maxlat geocode-data) mid)]
        (recur local-bits hash-code
          (assoc geocode-data
            :minlat minlat
            :maxlat maxlat
            :islon (not (:islon geocode-data))))))))

(defn- do-decode [hash-codes geocode-data]
  (if (empty? hash-codes)
    (do (println geocode-data)
      geocode-data)
    (let [local-geocode-data (do-decode-hashcode 5 (first hash-codes) geocode-data)]
      (recur (rest hash-codes) local-geocode-data))))

(defn decode [hashstring]
  (let [
        hashcodes (map base32-index hashstring)
        bbox (do-decode hashcodes (struct geocode-data 180.0 -180.0 90.0 -90.0 true nil nil nil))
        lat (/ (+ (:maxlat bbox) (:minlat bbox)) 2)
        lon (/ (+ (:maxlon bbox) (:minlon bbox)) 2)]
    {:lat lat :lon lon}))

(defn encode [lat lon prec]
  (do-encode lat lon prec 
    (struct geocode-data 180 -180 90 -90 true 0 0 "")))