Skip to content

Instantly share code, notes, and snippets.

@ayende
Created September 13, 2020 09:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ayende/99980d392a6c2f338cfcb793e500b94b to your computer and use it in GitHub Desktop.
Save ayende/99980d392a6c2f338cfcb793e500b94b to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
public class GeoHash
{
// from https://github.com/alexframe/GeoHash.Net/
static int[] _bits = new[] { 16, 8, 4, 2, 1 };
static char[] _base32 = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' };
private static Dictionary<char, int> _decodeMap =
_base32.Select((x, i) => (x, i)).ToDictionary((t) => t.x, t => t.i);
public static string Encode(double latitude, double longitude, int precision = 12)
{
if (latitude < -90 || latitude > 90)
throw new ArgumentOutOfRangeException($"{nameof(latitude)} must be between -90 and 90.");
if (longitude < -180 || longitude > 180)
throw new ArgumentOutOfRangeException($"{nameof(longitude)} must be between -180 and 180.");
double latMin = -90, latMax = 90;
double lngMin = -180, lngMax = 180;
var geoHash = new StringBuilder();
var isEven = true;
int bit = 0, ch = 0;
while (geoHash.Length < precision)
{
double midPoint;
if (isEven)
{
midPoint = (lngMin + lngMax) / 2;
if (longitude > midPoint)
{
ch |= _bits[bit];
lngMin = midPoint;
}
else
{
lngMax = midPoint;
}
}
else
{
midPoint = (latMin + latMax) / 2;
if (latitude > midPoint)
{
ch |= _bits[bit];
latMin = midPoint;
}
else
{
latMax = midPoint;
}
}
isEven = !isEven;
if (bit < 4)
{
bit++;
}
else
{
geoHash.Append(_base32[ch]);
bit = 0;
ch = 0;
}
}
return geoHash.ToString();
}
public struct DecodedCoordinates
{
public double Latitude;
public double Longitude;
public double LatitudeError;
public double LongitudeError;
}
public static DecodedCoordinates Decode(string geohash)
{
double latMin = -90, latMax = 90;
double lngMin = -180, lngMax = 180;
var latError = 90.0;
var lngError = 180.0;
var isEven = true;
var size = geohash.Length;
var bitsSize = _bits.Length;
for (var i = 0; i < size; i++)
{
var cd = _decodeMap[geohash[i]];
for (var j = 0; j < bitsSize; j++)
{
var mask = _bits[j];
if (isEven)
{
lngError /= 2;
if ((cd & mask) != 0)
{
lngMin = (lngMin + lngMax) / 2;
}
else
{
lngMax = (lngMin + lngMax) / 2;
}
}
else
{
latError /= 2;
if ((cd & mask) != 0)
{
latMin = (latMin + latMax) / 2;
}
else
{
latMax = (latMin + latMax) / 2;
}
}
isEven = !isEven;
}
}
var latitude = (latMin + latMax) / 2;
var longitude = (lngMin + lngMax) / 2;
return new DecodedCoordinates
{
Latitude = latitude,
Longitude = longitude,
LatitudeError = latError,
LongitudeError = lngError
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment