Skip to content
wmorinPublic

About

Geospatial Flask API for converting GeoJSON, cities, countries, points, and geohashes into geohash coverage polygons using OpenStreetMap Nominatim.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

Geohash'it

Test

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:

Paris city geohash polygons

Requirements

  • Python 3.13 or 3.14
  • uv

Installation

uv sync --dev

Run The API

./start

The server listens on http://127.0.0.1:5000/.

Debug mode is disabled by default. For local development only:

FLASK_DEBUG=1 ./start

For production, run the Flask app behind a WSGI server instead of Flask's built-in development server.

Docker

Build and run the production image locally:

docker build -t geohashit .
docker run --rm -p 5000:5000 geohashit

Released images are published to GitHub Container Registry as ghcr.io/wmorin/geohashit.

API

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
  }
}

GET /

Returns service metadata and a list of available API endpoints.

GET /health

Returns {"status":"ok"} for uptime checks.

GET /openapi.json

Returns the OpenAPI 3.2.0 description for the API.

GET /multipolygons/point

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"

GET /multipolygons/city

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

GET /multipolygons/geohash

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

POST /geohashes/geojson

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]}'

POST /multipolygons/geojson

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 Codes

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

Nominatim

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" \
./start

The 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.

Tests

uv run pytest

See TESTING.md for setup details. GitHub Actions uses uv and runs the suite on Python 3.13 and 3.14.

Benchmarks

Run the geohash coverage benchmark against checked-in city-sized and country-sized GeoJSON fixtures:

uv run python benchmarks/benchmark_cover.py

Use JSON output for trend collection:

uv run python benchmarks/benchmark_cover.py --json

About

Geospatial Flask API for converting GeoJSON, cities, countries, points, and geohashes into geohash coverage polygons using OpenStreetMap Nominatim.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages