Get Country and City (and Address) from lat and lon respecting api limits
Find a file
kekskurse 9e6cccd03d
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/cron/woodpecker Pipeline is running
page
2026-04-14 11:33:11 +02:00
api init 2026-04-11 16:30:38 +02:00
cmd/geo-proxy init 2026-04-11 16:30:38 +02:00
internal/pkg page 2026-04-14 11:33:11 +02:00
.woodpecker.yml init 2026-04-11 16:30:38 +02:00
docker-compose-dev.yml init 2026-04-11 16:30:38 +02:00
docker-compose.yml init 2026-04-11 16:30:38 +02:00
Dockerfile chore: update go version in docker 2026-04-11 16:38:37 +02:00
go.mod init 2026-04-11 16:30:38 +02:00
go.sum init 2026-04-11 16:30:38 +02:00
README.md chore: update readme 2026-04-11 16:37:14 +02:00

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

  1. Check Redis cache — if a match within 10 meters is found, return immediately
  2. If WithFastResponse: call Nominatim with rate limit check (Allow)
    • Rate limited → return best Redis match
    • Other error → return best Redis match + error
  3. Otherwise: call Nominatim with blocking wait (Wait) — always returns a result
  4. 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.