stapel-geo
Geohash proximity search and geocoding, no GDAL/PostGIS/spatial database: a hierarchical location tree (flat lat/lon points with an auto-encoded geohash and a stable cross-service UUID), a proximity search facade (nearby/radius/bbox) behind one swappable backend, and a geocoder proxy (forward/structured/reverse) behind a provider merge-registry, throttled, cached and spend-ledgered per call.
Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.
Install
pip install stapel-geo
At a glance
| Fact | Value |
|---|---|
| Version | 0.4.0 |
| Python | >=3.11 (3.11, 3.12, 3.13, 3.14) |
| HTTP operations | 16 |
| Config axes | 2 |
| Usage surface | 21 |
| Extension points | 4 |
| Error codes | 50 |
| Documented flows | 5 |
| Fleet dependencies | stapel-core |
Documentation
Flows: English · Errors: English · Русский · OpenAPI · capabilities.json · llms.txt (for agents)
What this is
-
Location tree — hierarchical reference places (
django-treenode): flat lat/lon points with an auto-encoded, indexed geohash and a stable cross-service UUID. No polygons. -
Proximity search facade —
nearby(top-K) /radius(membership) /bbox(viewport, antimeridian-aware) behind one swappable backend key. The default runs on your primary database via geohash prefix expansion (correct across the equator, the antimeridian and the poles, ranked by exact haversine); a RedisGEOSEARCHside-index backend ships for the hot set; Elasticsearch/Solr are named stubs. -
Geocoder proxy — forward / structured / reverse / resolve behind a provider merge-registry (
photonself-hosted default,nominatimkeyless dev/fallback,google/yandexkey-gated stubs), throttled, cached (30-day TTL) and spend-ledgered per call. Forward search takes the map's own narrowings: a hardbboxand a soft viewport bias. -
The location picker's server half — because a place is chosen by a human, not typed as two decimals:
- every feature carries
properties.formatted, the display line, in the country's own postal order; geocoding/resolve?lat=&lon=turns one coordinate pair into a confirmable place (label, components, geohash, alternatives) in one round trip — the whole server side of "detect my position" and of a dropped map pin;map/config(public) hands the frontend its tile template, the attribution the ODbL licence obliges the map to display, the zoom envelope, the operating bbox and the debounce discipline.
The React pair builds against
docs/frontend-contract.md. - every feature carries
-
comm surface —
geo.nearby/geo.radius/geo.bbox/geo.geohash_encode/geo.resolve/geo.geocode/geo.reverse_geocode/geo.map_config: consumers (listings, calendar) query geo by name, never importing it.
Quick start
pip install "stapel-geo[redis]" # + the Redis search backend
INSTALLED_APPS = [
# ...
"stapel_geo",
]
# urls.py — the canonical versioned surface /geo/api/v1/...
path("geo/", include("stapel_geo.urls"))
# ... or mount only the geocoder proxy:
path("geo/api/v1/geocoding/", include("stapel_geo.geocoding.urls"))
Plain manage.py migrate — any Django database backend works.
HTTP surface (/geo/api/v1/)
| Route | What |
|---|---|
locations/ |
List roots / search by name (?search=) |
locations/{id-or-uuid}/ |
Location detail (lat/lon/geohash, tree parent) |
locations/countries/ |
Root level of the tree |
locations/by-parent/{id}/ |
Children of a node |
locations/nearby-by-coords/?lat=&lon= |
Top-K nearest (exact distance_km) |
locations/nearby-by-geohash/?geohash= |
Same, geohash input |
locations/validate-uuid/{uuid}/ |
Cross-service reference check |
geocoding/search?q= |
Forward geocoding, ?bbox= + ?bias_lat=&bias_lon= (guarded + throttled) |
geocoding/structured?city=&street= |
Structured address search |
geocoding/reverse?lat=&lon= |
Reverse geocoding (raw candidates) |
geocoding/resolve?lat=&lon= |
One coordinate pair → one confirmable place |
map/config |
Basemap + picker configuration (public) |
Settings (STAPEL_GEO)
| Key | Default | Meaning |
|---|---|---|
SEARCH_BACKEND |
…search.postgres.PostgresGeoSearchBackend |
Search engine behind nearby/radius/bbox (dotted path). |
REDIS_URL / REDIS_GEO_KEY |
redis://localhost:6379/0 / stapel:geo:locations |
Redis backend connection + side-index key. |
GEOHASH_PRECISION |
8 |
Stored geohash precision (1-12 chars). |
NEARBY_PRECISION |
6 |
Default precision for coordinate nearby search. |
NEARBY_LIMIT / NEARBY_MAX_LIMIT |
10 / 50 |
Default / max search results. |
GEOCODER |
"photon" |
Default geocoder name (registry key). |
GEOCODERS |
{} |
Extra providers, merged over the built-ins (None removes). |
PHOTON_URL |
http://localhost:2322 |
Photon instance the default provider proxies. |
PHOTON_LANGUAGES |
[default,en,de,fr] |
What the Photon index actually carries — not a preference list. Photon 400s on anything else. |
PHOTON_LANGUAGE_FALLBACK |
"default" |
Where an unindexed language clamps. default = Photon's local-name mode (Russian in Russia). |
NOMINATIM_URL |
https://nominatim.openstreetmap.org |
Nominatim base (public: 1 rps, dev/fallback). |
GEOCODER_TIMEOUT |
10 |
Geocoder HTTP timeout (s). |
GEOCODER_THROTTLE / GEOCODER_ANON_THROTTLE |
30/min / 10/min |
Scoped throttle rates (identified / anonymous). |
GEOCODER_PERMISSIONS |
[IsNotAnonymousUser] |
Guard of the proxy verbs. Set to AllowAny for a public address search. |
GEOCODE_CACHE_POLICY |
…geocoding.cache.LedgerCachePolicy |
Cache seam (dotted path). |
GEOCODE_CACHE_TTL_DAYS |
30 |
Default cache TTL. |
ADDRESS_FORMATTER |
…geocoding.format.format_address |
Builds properties.formatted (seam). |
MAP_TILE_URL / MAP_TILE_ATTRIBUTION_* |
OSM public tiles / OSM credit | Basemap and its mandatory attribution. The default tile server is a dev default (W007). |
MAP_BBOX |
None |
The product's operating area; also the default hard restriction on forward geocoding. |
MAP_* (zoom, centre, debounce) |
see CONFIG.MD |
The rest of the picker's configuration. |
Sending
lang? Senddefault, or nothing.PHOTON_LANGUAGESis what the index on disk carries, and Photon refuses anything else with HTTP 400 rather than degrading. Requests for an unindexed language clamp toPHOTON_LANGUAGE_FALLBACK("default"= the local name on the map, which for a single-country product is already the right language), and the response'slangfield tells you what was really used. To index another language for real, build the Photon database from the JSON dump withphoton.jar import -languages …; listing it here without rebuilding turns every request into a 502.manage.py checksays all of this (stapel_geo.W005/W006).
comm Functions
from stapel_core.comm import call
call("geo.nearby", {"lat": 49.61, "lon": 6.13, "limit": 5})
call("geo.radius", {"lat": 49.61, "lon": 6.13, "radius_km": 25})
call("geo.bbox", {"min_lat": 49, "min_lon": 5, "max_lat": 50, "max_lon": 7})
call("geo.geohash_encode", {"lat": 49.61, "lon": 6.13}) # -> {"geohash": ...}
call("geo.resolve", {"uuid": "<location-uuid>"})
min_lon > max_lon in geo.bbox means the box crosses the antimeridian.
Swapping the search backend
STAPEL_GEO = {"SEARCH_BACKEND": "stapel_geo.search.redis.RedisGeoSearchBackend"}
The Redis backend is a side index: the primary DB stays the source of
truth; post_save/post_delete keep it in sync and
RedisGeoSearchBackend().rebuild() re-indexes from scratch. Implement
stapel_geo.search.base.GeoSearchBackend (three verbs) to bring your own
engine — see MODULE.md.
Extension points
See MODULE.md — the agent-facing map of every fork-free seam, and CHANGELOG.md — including what 0.3.0 removed and why.
License
MIT — see LICENSE.
This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stapel_geo-0.4.0.tar.gz.
File metadata
- Download URL: stapel_geo-0.4.0.tar.gz
- Upload date:
- Size: 107.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2daa77fb78b970f8b32afbae1d4557514a142721ed037ebaed7d9291c4c0de30
|
|
| MD5 |
a0e274a39e4c39ec82a1ad1d21e62cdc
|
|
| BLAKE2b-256 |
6b66a46032c38be3ac0816cabd732ecb22de0fe7520db0021231455bdae3ff0f
|
Provenance
The following attestation bundles were made for stapel_geo-0.4.0.tar.gz:
Publisher:
publish.yml on usestapel/stapel-geo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stapel_geo-0.4.0.tar.gz -
Subject digest:
2daa77fb78b970f8b32afbae1d4557514a142721ed037ebaed7d9291c4c0de30 - Sigstore transparency entry: 2580976049
- Sigstore integration time:
-
Permalink:
usestapel/stapel-geo@108f792e86059d2d8c520558b21ce77fa91955ec -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/usestapel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@108f792e86059d2d8c520558b21ce77fa91955ec -
Trigger Event:
push
-
Statement type:
File details
Details for the file stapel_geo-0.4.0-py3-none-any.whl.
File metadata
- Download URL: stapel_geo-0.4.0-py3-none-any.whl
- Upload date:
- Size: 105.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3284054fbe31b94a78f93ba2e267bf9429590f2f838a54a1f885a2f5daf79e4b
|
|
| MD5 |
88dad885e165f95d56798e59468f8e10
|
|
| BLAKE2b-256 |
e9d4fb3eeba7dac1d3228a8cb8337b12ff2dae37735f18e8a6fb83c8d338a199
|
Provenance
The following attestation bundles were made for stapel_geo-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on usestapel/stapel-geo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
stapel_geo-0.4.0-py3-none-any.whl -
Subject digest:
3284054fbe31b94a78f93ba2e267bf9429590f2f838a54a1f885a2f5daf79e4b - Sigstore transparency entry: 2580976102
- Sigstore integration time:
-
Permalink:
usestapel/stapel-geo@108f792e86059d2d8c520558b21ce77fa91955ec -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/usestapel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@108f792e86059d2d8c520558b21ce77fa91955ec -
Trigger Event:
push
-
Statement type: