Get Country and City (and Address) from lat and lon respecting api limits
| api | ||
| cmd/geo-proxy | ||
| internal/pkg | ||
| .woodpecker.yml | ||
| docker-compose-dev.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
geo-proxy
A reverse geocoding proxy that translates GPS coordinates into addresses. Built with Go, Gin, Redis, and the Nominatim API.
Features
- Reverse geocoding via Nominatim (OpenStreetMap)
- Redis cache with nearest-neighbor lookup using Haversine distance
- Rate limiting for Nominatim requests (max 1 request per 2 seconds)
- Async processing via background worker and callback URLs
- Cache pre-seeding on startup (continents + all European countries)
Architecture
cmd/geo-proxy/
├── main.go # Entry point
└── seed.go # Cache pre-seeding on startup
internal/pkg/
├── api/ # HTTP handlers (Gin)
├── geoclient/ # Cache logic (Redis + Nominatim orchestration)
├── nominatim/ # Nominatim API client
└── worker/ # Background async queue processor
Getting Started
Start Redis:
docker compose -f docker-compose-dev.yml up -d
Run the server:
go run ./cmd/geo-proxy/
The server starts on :8082. On startup, coordinates for all continents and European countries are pushed into the async queue to pre-fill the cache.
API
Full documentation: api/swagger.yaml
GET /reverse
Reverse geocode GPS coordinates.
| Parameter | Required | Description |
|---|---|---|
lat |
yes | Latitude |
lon |
yes | Longitude |
callback |
no | URL to POST the result to when processed asynchronously |
tolerance |
no | Distance in meters above which the result is async |
Response:
{
"success": true,
"async": false,
"data": {
"distance": 3.45,
"address": {
"country": "Germany",
"state": "Hamburg",
"city": "Hamburg",
"address": "Lohm\u00fchlenstra\u00dfe, Steindamm, St. Georg, Hamburg-Mitte, Hamburg, 20099, Germany"
}
}
}
When async: true, the request was added to the background queue. Once processed, the result is POSTed to the callback URL:
{
"data": {
"distance": 0,
"address": { ... }
}
}
GET /cache
Returns all entries currently stored in the Redis cache.
DELETE /cache
Clears the entire Redis cache.
Lookup Flow
- Check Redis cache — if a match within 10 meters is found, return immediately
- If
WithFastResponse: call Nominatim with rate limit check (Allow)- Rate limited → return best Redis match
- Other error → return best Redis match + error
- Otherwise: call Nominatim with blocking wait (
Wait) — always returns a result - Store result in Redis cache
Rate Limiting
Nominatim enforces a maximum of 1 request per second for public usage. This proxy is configured to send 1 request every 2 seconds.