ReverseGeocoder is a self-hosted geolocation toolkit for village-level lookups. It provides:
- reverse geocoding (
lat/lon→ nearest place) - text geocoding (
q→ matching places) - IP to country (
IPv4/IPv6→ ISO-3166 country code)
It is designed as a lightweight alternative to commercial geolocation APIs when village-level detail is precise enough, and avoiding massive credit card bills is preferred.
I use it to convert the location data provided by the Geolocation API into a human-readable location on isitgoingtorain.com, and to do the location search on the same site.
data/: data acquisition and transformation pipelinephp/: PHP endpoint implementationaws/: AWS Lambda + Serverless implementationAGENTS.md: contribution playbook for coding agents
You can get from clone to a working lookup in about half an hour or so.
- Build the dataset and database using
data/README.md. - Pick a runtime:
- PHP: follow
php/README.md - AWS Lambda: follow
aws/README.md
- PHP: follow
- Call one endpoint to verify behavior.
All runtimes implement the same three GET endpoints:
/reverse_geocode?lat=<lat>&lon=<lon>/geocode?q=<query>&prefer_country=<ISO2 optional>/ip2country?ip=<ip optional>
Required query parameters:
lat: decimal latitude in the range-90to90lon: decimal longitude in the range-180to180
Behavior:
- returns
400if either coordinate is missing or out of range - searches for nearby places using an approximately 8km bounding box
- handles longitude wrap-around at
±180 - returns the closest matching place if one is found
- returns a fallback name of
<lat>, <lon>if no nearby place is found
Example:
/reverse_geocode?lat=51.7546&lon=-1.2588
Typical successful response:
{
"name": "Oxford",
"admin": "Oxfordshire, England",
"country": "GB",
"latitude": 51.752,
"longitude": -1.258
}Required query parameters:
q: text search term
Optional query parameters:
prefer_country: 2-letter ISO-3166 country code used to narrow/prioritize results
Behavior:
- returns
400ifqis missing - returns
400ifprefer_countryis present but invalid - matches place names by prefix and substring
- ranks matches using
place.sortdescending within match groups - returns up to 10 results
Example:
/geocode?q=oxfo&prefer_country=GB
Typical successful response:
[
{
"name": "Oxford",
"admin": "Oxfordshire, England",
"country": "GB",
"latitude": 51.752,
"longitude": -1.258
}
]Optional query parameters:
ip: IPv4 or IPv6 address
Behavior:
- when
ipis supplied, looks up that address and returns a cacheable result - when
ipis omitted, the runtime falls back to the request/client IP by whatever route that runtime has available - returns
400for invalid IP input - supports both IPv4 and IPv6 range lookups
- returns
country: nullwhen no matching range is found
Example:
/ip2country?ip=2001:4860:4860::8888
Typical successful response:
{
"ip": "2001:4860:4860::8888",
"country": "US"
}For runtime-specific setup and operational notes, see:
| Column | Data Type | Description |
|---|---|---|
| country | char(2) | ISO-3166 2-letter country code |
| name | text | Name of the point |
| admin | integer | ID of the administrative area of the point |
| sort | integer | Bigger = higher priority when searching |
| latitude | decimal(6,3) | Latitude in decimal degrees (WGS84) |
| longitude | decimal(6,3) | Longitude in decimal degrees (WGS84) |
| Column | Data Type | Description |
|---|---|---|
| id | integer | ID column |
| name | text | Name of the administrative area |
| Column | Data Type | Description |
|---|---|---|
| network_start | integer | Decimal IP address starting a range |
| network_end | integer | Decimal IP address ending a range |
| country | char(2) | ISO-3166 2-letter country code of the range |
| Column | Data Type | Description |
|---|---|---|
| network_start | text | Expanded IPv6 address starting a range |
| network_end | text | Expanded IPv6 address ending a range |
| country | char(2) | ISO-3166 2-letter country code of the range |
The included pipeline builds these from GeoNames and GeoLite2 data sources.
- root onboarding: this file
- canonical API contract: this file
- data pipeline:
data/README.md,data/PIPELINE.md - PHP runtime and API:
php/README.md - AWS runtime and API:
aws/README.md - agent contributor rules:
AGENTS.md
- GeoNames data is CC-BY licensed.
- GeoLite2 data is provided by MaxMind (requires account/license key for download).