Skip to content

Instantly share code, notes, and snippets.

@alexbosworth
Last active April 13, 2020 04:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save alexbosworth/dc78eba30f129a709cc1 to your computer and use it in GitHub Desktop.
Save alexbosworth/dc78eba30f129a709cc1 to your computer and use it in GitHub Desktop.
Swift Geohash
//
// Geohash.swift
// mn_ios
//
// Created by Alex Bosworth on 11/26/14.
// Copyright (c) 2014 adylitica. All rights reserved.
//
import Foundation
import MapKit
typealias GeoInterval = (CLLocationDegrees, CLLocationDegrees)
class Geohash {
private class func _joinInterval(interval: GeoInterval) -> Double {
return (interval.0 + interval.1) / 2
}
private class func _refineInterval(interval: GeoInterval, cd: Int, mask: Int) -> GeoInterval {
let joined = _joinInterval(interval)
return cd & mask != 0 ? (joined, interval.1) : (interval.0, joined)
}
private struct Constants {
static let interval = (lat: GeoInterval(-90, 90), long: GeoInterval(-180, 180))
static let base32 = "0123456789bcdefghjkmnpqrstuvwxyz"
static let bits = [16, 8, 4, 2, 1]
}
/** Convert a CLLocation into a geohash
*/
class func encode(coordinate: CLLocationCoordinate2D) -> String {
var isEven = true
var bit = 0
var ch = 0
var precision = 12
var geohash = [String]()
var mid: Double = 0
var lat = Constants.interval.lat
var lon = Constants.interval.long
while geohash.count < precision {
if isEven {
mid = _joinInterval(lon)
if coordinate.longitude > mid {
ch |= Constants.bits[bit]
lon.0 = mid
}
else {
lon.1 = mid
}
}
else {
mid = _joinInterval(lat)
if coordinate.latitude > mid {
ch |= Constants.bits[bit]
lat.0 = mid
}
else {
lat.1 = mid
}
}
isEven = !isEven
if bit < 4 {
bit++
}
else {
geohash += [String(Constants.base32[advance(Constants.base32.startIndex, ch)])]
bit = 0
ch = 0
}
}
return join("", geohash.map { String($0) })
}
/** Convert a geohash into a CLLocation
*/
class func decode(geohash: String) -> CLLocationCoordinate2D? {
var isEven = true
var interval = Constants.interval
let base32 = Constants.base32
let bits = Constants.bits
for char in geohash {
let index = find(base32, char)
// Exit early when the geohash is invalid
if index == nil { return nil }
let value = distance(base32.startIndex, index!)
for mask in bits {
if isEven {
interval.long = _refineInterval(interval.long, cd: value, mask: mask)
}
else {
interval.lat = _refineInterval(interval.lat, cd: value, mask: mask)
}
isEven = !isEven
}
}
return CLLocationCoordinate2D(latitude: _joinInterval(interval.lat), longitude: _joinInterval(interval.long))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment