Geohash'it is a small Flask API that turns places or GeoJSON shapes into geohash coverage polygons. It can resolve a point or city through OpenStreetMap Nominatim, cover the resulting shape with geohashes, and return either the geohash list or a GeoJSON MultiPolygon.
For example, starting from a geopoint, you can produce a geohashed city boundary:
- Python 3.13 or 3.14
uv
uv sync --dev./startThe server listens on http://127.0.0.1:5000/.
Debug mode is disabled by default. For local development only:
FLASK_DEBUG=1 ./startFor production, run the Flask app behind a WSGI server instead of Flask's built-in development server.
Build and run the production image locally:
docker build -t geohashit .
docker run --rm -p 5000:5000 geohashitReleased images are published to GitHub Container Registry as
ghcr.io/wmorin/geohashit.
All responses are JSON. Errors return a stable envelope with a machine-readable code, human-readable message, and HTTP status:
{
"error": {
"code": "validation_error",
"message": "precision must be between 1 and 8",
"status": 400
}
}Returns service metadata and a list of available API endpoints.
Returns {"status":"ok"} for uptime checks.
Returns the OpenAPI 3.2.0 description for the API.
Returns geohash cells as a GeoJSON polygon collection for the city or country at a latitude/longitude.
Query parameters:
| Name | Required | Description |
|---|---|---|
lat |
yes | Latitude from -90 to 90 |
lon |
yes | Longitude from -180 to 180 |
type |
yes | city or country |
precision |
yes | Geohash precision from 1 to 8 |
simplify |
no | true, false, 1, or 0; defaults to false |
Example:
curl "http://127.0.0.1:5000/multipolygons/point?lat=48.8566&lon=2.3522&type=city&precision=5"Returns geohash cells as a GeoJSON polygon collection for a named city.
Query parameters:
| Name | Required | Description |
|---|---|---|
city_name |
yes | City name to search through Nominatim |
country_code |
yes | Two-letter country code |
precision |
no | Geohash precision from 1 to 8; defaults to 5 |
Decodes a geohash to a point, resolves the city containing that point, and returns geohash cells as a GeoJSON polygon collection.
Query parameters:
| Name | Required | Description |
|---|---|---|
geohash |
yes | Valid geohash |
precision |
no | Geohash precision from 1 to 8; defaults to 5 |
Returns a list of geohashes covering the submitted GeoJSON shape.
Parameters:
| Name | Required | Description |
|---|---|---|
precision |
no | Geohash precision from 1 to 8; defaults to 5. Accepted as a query parameter, form field, or JSON envelope field. |
Body:
Send either a geojson form field, a raw GeoJSON JSON body, or a JSON envelope
with geojson and optional precision fields.
Coverage requests are capped at 50,000 returned geohashes. Very large shapes at
high precision return validation_error instead of tying up the server with an
oversized response.
Example:
curl -X POST "http://127.0.0.1:5000/geohashes/geojson?precision=5" \
-F 'geojson={"type":"Point","coordinates":[2.3522,48.8566]}'curl -X POST "http://127.0.0.1:5000/geohashes/geojson" \
-H "Content-Type: application/json" \
-d '{"type":"Point","coordinates":[2.3522,48.8566]}'Returns the submitted GeoJSON shape's geohash cells as a GeoJSON polygon collection.
Parameters:
| Name | Required | Description |
|---|---|---|
precision |
no | Geohash precision from 1 to 8; defaults to 5. Accepted as a query parameter, form field, or JSON envelope field. |
Body:
Send either a geojson form field, a raw GeoJSON JSON body, or a JSON envelope
with geojson and optional precision fields.
Coverage requests are capped at 50,000 returned geohashes before the multipolygon is built.
Error response code values:
| Code | Status | Meaning |
|---|---|---|
validation_error |
400 |
Invalid request parameter or invalid GeoJSON |
place_not_found |
404 |
Nominatim could not find a matching place polygon |
not_found |
404 |
The route does not exist |
method_not_allowed |
405 |
The route exists, but the HTTP method is not allowed |
payload_too_large |
413 |
Request body is larger than 1 MB |
upstream_error |
502 |
Nominatim failed or returned an invalid upstream response |
HTTP status meanings:
| Status | Meaning |
|---|---|
400 |
Invalid request parameter or invalid GeoJSON |
404 |
Nominatim could not find a matching place polygon, or the route does not exist |
405 |
The route exists, but the HTTP method is not allowed |
413 |
Request body is larger than 1 MB |
502 |
Nominatim failed or returned an invalid upstream response |
By default, requests go to https://nominatim.openstreetmap.org with a project
specific User-Agent. You can override both:
NOMINATIM_URL="https://your-nominatim.example.com" \
NOMINATIM_USER_AGENT="your-app/1.0 your-email@example.com" \
./startThe client caches identical requests in memory for up to one hour, capped at 512 entries per process, and rate-limits outbound Nominatim requests to one request per second.
uv run pytestSee TESTING.md for setup details. GitHub Actions uses uv and runs
the suite on Python 3.13 and 3.14.
Run the geohash coverage benchmark against checked-in city-sized and country-sized GeoJSON fixtures:
uv run python benchmarks/benchmark_cover.pyUse JSON output for trend collection:
uv run python benchmarks/benchmark_cover.py --json