using NetTopologySuite.Geometries;
using NetTopologySuite.Index.Strtree;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
public class PolygonHasher
private Geohasher _geohasher = new Geohasher();
public enum GeohashInclusionCriteria
public HashSet<string> GetHashes(Polygon polygon, int geohashPrecision, GeohashInclusionCriteria geohashInclusionCriteria = GeohashInclusionCriteria.Contains, IProgress<double> progress = null)
var envelope = polygon.EnvelopeInternal;
double latStep = 180.0 / Math.Pow(2, (5 * geohashPrecision - geohashPrecision % 2) / 2);
double lngStep = 360.0 / Math.Pow(2, (5 * geohashPrecision + geohashPrecision % 2) / 2);
envelope.ExpandBy(latStep / 2, lngStep / 2);
HashSet<string> geohashes = new HashSet<string>();
bool checkContains = geohashInclusionCriteria == GeohashInclusionCriteria.Contains;
bool checkIntersects = geohashInclusionCriteria == GeohashInclusionCriteria.Intersects;
int totalSteps = (int)Math.Ceiling((envelope.MaxY - envelope.MinY) / latStep);
int progressBatchSize = Math.Max(1, totalSteps / 100);
Parallel.For((int)(envelope.MinY / latStep), (int)(envelope.MaxY / latStep) + 1, () => new HashSet<string>(),
(latIdx, state, localGeohashes) =>
double lat = latIdx * latStep;
Coordinate[] coords = new Coordinate[5];
for (double lng = envelope.MinX; lng <= envelope.MaxX; lng += lngStep)
string curGeohash = _geohasher.Encode(lat, lng, geohashPrecision);
var bbox = _geohasher.GetBoundingBox(curGeohash);
coords[0] = new Coordinate(bbox.MinLng, bbox.MinLat);
coords[1] = new Coordinate(bbox.MinLng, bbox.MaxLat);
coords[2] = new Coordinate(bbox.MaxLng, bbox.MaxLat);
coords[3] = new Coordinate(bbox.MaxLng, bbox.MinLat);
var geohashPoly = new Polygon(new LinearRing(coords));
if ((checkContains && polygon.Contains(geohashPoly)) ||
(checkIntersects && polygon.Intersects(geohashPoly)))
localGeohashes.Add(curGeohash);
if ((latIdx + 1) % progressBatchSize == 0)
progress?.Report(Math.Min(1.0, (double)latIdx / totalSteps));
foreach (var geohash in localGeohashes)
public class PolygonHasherTests
private readonly PolygonHasher _polygonHasher;
public PolygonHasherTests()
_polygonHasher = new PolygonHasher();
public void Should_Create_Hashes()
var polygon = new Polygon(new LinearRing(
new Coordinate(180, -90),
new Coordinate(-180, -90),
new Coordinate(-180, 90),
var expectedHashes = new HashSet<string>
"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", "z"
var hashes = _polygonHasher.GetHashes(polygon, 1, PolygonHasher.GeohashInclusionCriteria.Intersects);
Assert.True(hashes.IsSubsetOf(expectedHashes), "Unexpected geohashes found.");
Assert.Equal(expectedHashes.Count, hashes.Count);