ThamesTracker tracks Tower Bridge lift events and vessel movements on the River Thames. It exposes a JSON API, iCalendar feeds, and provides CLI tools.
- JSON API for bridge lifts, vessel movements, and location stats
- iCalendar feeds for bridge lifts and vessel events
- Query filtering (name, type/category, location, after, before, unique, etc.)
- Health check endpoint
- Prometheus metrics endpoint (enabled with
METRICS_PUBLIC=true) - OpenAPI spec served at
/docs - Per-IP rate limiting (configurable)
- Circuit breaker for external API calls
- Redis (or in-memory fallback) caching
- CLI for scraping and fetching data/feeds
# Clone and install deps
git clone https://github.com/Takenobou/thamestracker.git
cd thamestracker
go mod download
# Run server (defaults to port 8080)
go run ./cmd/server/main.goversion: '3.8'
services:
thamestracker:
image: ghcr.io/takenobou/thamestracker:latest
ports:
- "8080:8080"
environment:
PORT: 8080
PORT_OF_LONDON: https://pla.co.uk/pla-proxy/five-minute?url=ships/lists
TOWER_BRIDGE: https://www.towerbridge.org.uk/flat/lift-times
REDIS_ADDRESS: redis://redis:6379
REDIS_INSECURE_SKIP_VERIFY: false
CB_MAX_FAILURES: 5
CB_COOL_OFF: 60
CACHE_MAX_ENTRIES: 1000
CACHE_TTL_SECONDS: 3600
REQUESTS_PER_MIN: 60
METRICS_PUBLIC: false
depends_on:
- redis
redis:
image: redis:latest
ports:
- "6379:6379"All settings via environment variables. Defaults shown:
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
HTTP port for server |
PORT_OF_LONDON |
https://pla.co.uk/pla-proxy/five-minute?url=ships/lists |
Base URL for Port of London ship API |
TOWER_BRIDGE |
https://www.towerbridge.org.uk/flat/lift-times |
URL for Tower Bridge lift times page |
REDIS_ADDRESS |
localhost:6379 |
Redis connection address |
REDIS_INSECURE_SKIP_VERIFY |
false |
Skip TLS certificate verification for rediss:// (not recommended) |
CB_MAX_FAILURES |
5 |
Circuit-breaker max consecutive failures |
CB_COOL_OFF |
60 |
Circuit-breaker open timeout (sec) |
CACHE_MAX_ENTRIES |
1000 |
Max entries in in-memory fallback cache |
CACHE_TTL_SECONDS |
3600 |
TTL for in-memory fallback cache (sec) |
REQUESTS_PER_MIN |
60 |
Per-IP rate-limit (requests per minute) |
METRICS_PUBLIC |
false |
Expose /metrics endpoint if true |
BRIDGE_FILTER_PERCENTILE |
0.10 |
Percentile threshold for filtering most frequent bridge lifts when unique=true |
BRIDGE_FILTER_MAX_COUNT |
8 |
Max times a vessel can appear in bridge lifts when unique=true |
Returns upcoming Tower Bridge lift events in JSON.
Query parameters:
unique(boolean, defaultfalse): remove duplicate lifts by vessel namename(string, optional): filter lifts by vessel name substringafter(RFC3339, optional): only events after this timestampbefore(RFC3339, optional): only events before this timestamplocation(string, optional): filter by location
Response Example:
[
{
"timestamp": "2025-04-05T17:45:00Z",
"vessel_name": "Paddle Steamer Dixie Queen",
"category": "bridge",
"direction": "Up river",
"location": "Tower Bridge Road, London"
}
]Example:
curl -s "http://localhost:8080/bridge-lifts?unique=true&name=queen" | jq .Returns vessel movements in JSON.
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
type |
string | all |
one of inport, arrivals, departures, forecast, or all |
name |
string | — | filter by vessel name |
location |
string | — | filter by port/all location fields |
nationality |
string | — | filter by vessel nationality |
after |
RFC3339 | — | include events after this timestamp |
before |
RFC3339 | — | include events before this timestamp |
unique |
boolean | false |
remove duplicate vessel names |
Response Example:
[
{
"timestamp": "2025-01-25T20:33:47Z",
"vessel_name": "SILVER STURGEON",
"category": "inport",
"voyage_number": "S7670",
"location": "WOODS QUAY"
}
]Example:
curl -s "http://localhost:8080/vessels?type=arrivals&unique=true&after=2025-04-01T00:00:00Z" | jq .Returns an iCalendar feed for Tower Bridge lift events.
Query parameters:
unique(boolean, defaultfalse): remove duplicate lifts by vessel namename(string, optional): filter lifts by vessel name substringafter(RFC3339, optional): only events after this timestampbefore(RFC3339, optional): only events before this timestamplocation(string, optional): filter by location
Example:
curl -s "http://localhost:8080/bridge-lifts/calendar.ics?unique=true&after=2025-04-01T00:00:00Z" > bridge.icsReturns an iCalendar feed for vessel movements.
Query parameters:
type(string, defaultall): one ofinport,arrivals,departures,forecast, orallname,location,nationality,after,before, andunique(same as/vessels)
Example:
curl -s "http://localhost:8080/vessels/calendar.ics?type=arrivals&unique=true&after=2025-04-01T00:00:00Z" > vessels.icsServes the OpenAPI JSON specification for the API.
Example:
curl -s "http://localhost:8080/docs" | jq .Prometheus metrics endpoint, enabled only when the environment variable METRICS_PUBLIC=true is set.
Response: Prometheus metrics text
Liveness probe. Returns HTTP 200 when the process is running.
Response:
{ "status": "ok" }Readiness probe. Returns HTTP 200 when dependencies are reachable (Redis, upstream API), HTTP 503 otherwise.
Response:
{ "status": "ok" }or
{ "status": "fail", "error": "health check error" }Returns aggregated vessel counts per location.
Query parameters:
minTotal(integer, default0): include only locations withtotal>=minTotalq(string, optional): case-insensitive substring filter on the locationname
Response Example:
[
{
"name": "PortA",
"code": "",
"inport": 1,
"arrivals": 2,
"departures": 3,
"forecast": 0,
"total": 6
}
]Example:
curl -s "http://localhost:8080/locations?minTotal=5&q=port" | jq .- 400 Bad Request: Invalid query parameters
- 503 Service Unavailable: Circuit breaker open or dependency unavailable
- 500 Internal Server Error: Unexpected errors
Requests are rate-limited per IP (default: 60/minute, configurable via REQUESTS_PER_MIN).
- Redis is used for caching if configured, otherwise an in-memory fallback cache is used.
- Circuit breaker protects external API calls.
The CLI replicates the service layer and fetches data from the APIs:
# Bridge lifts (JSON)
thamestracker bridge-lifts
# Vessels in port (JSON)
thamestracker vessels
# Vessel arrivals / departures / forecast
thamestracker arrivals
thamestracker departures
thamestracker forecast
# iCalendar feeds
thamestracker bridge-ics > bridge.ics
thamestracker vessels-ics > vessels.icsWhen using unique=true on bridge lift endpoints, the service will filter out vessels that are in the top BRIDGE_FILTER_PERCENTILE most frequent lifts, or that appear more than BRIDGE_FILTER_MAX_COUNT times. These thresholds can be tuned at runtime via environment variables, no code changes required.
MIT