@diegoaltoworks/london-toll-checker
v1.0.1
Published
Calculates whether a given set of lat/lng coordinates lies within the boundary of TFL London's Congestion Charging scheme
Readme
london-toll-checker
London toll zone boundary check microservice. Determines whether a given set of latitude/longitude coordinates falls within the Congestion Charge (CC) zone and/or the Ultra Low Emission Zone (ULEZ) using point-in-polygon checks against TfL boundary data.
Live at https://london-toll-checker.diegoalto.app

Quick start
bun install
bun run dev # starts with --watch on http://localhost:8080Use as a package
london-toll-checker can be imported as a library by other Bun projects:
import { ZoneChecker, zoneData, checkCoords } from "@diegoaltoworks/london-toll-checker";
// Use the built-in London zone data
const cc = new ZoneChecker(zoneData.cc);
console.log(cc.check("51.5087191,-0.1420653")); // { result: true }
// Or use the standalone function
console.log(checkCoords(zoneData.ulez, "51.5,-0.3")); // { result: true }
// Mount the full API router in your own Express app
import { createApiRouter } from "@diegoaltoworks/london-toll-checker/api";
app.use("/api", createApiRouter(zoneData));Exports
| Import path | What you get |
|-------------|--------------|
| @diegoaltoworks/london-toll-checker | ZoneChecker, checkCoords, zoneData, createApiRouter, types |
| @diegoaltoworks/london-toll-checker/models | ZoneChecker, checkCoords, CheckResult type |
| @diegoaltoworks/london-toll-checker/api | createApiRouter |
| @diegoaltoworks/london-toll-checker/db | zoneData, ZoneData type |
Scripts
| Command | Description |
|---------|-------------|
| bun run dev | Start dev server with auto-reload |
| bun run start | Start production server |
| bun test | Run all tests (52 tests) |
| bun run build | Build hashed assets into dist/public/ |
| bun run docker:build | Build Docker image |
| bun run docker:run | Run Docker container on port 3000 |
REST API
Check London toll zones via HTTP. Full reference in API.md.
curl "https://london-toll-checker.diegoalto.app/api/check?coords=51.5087191,-0.1420653"
# {"cc":{"result":true},"ulez":{"result":true}}| Endpoint | Description |
|----------|-------------|
| GET /api/check?coords=lat,lng | Check both CC and ULEZ zones |
| GET /api/check/cc?coords=lat,lng | Check CC zone only |
| GET /api/check/ulez?coords=lat,lng | Check ULEZ zone only |
| GET /api/geojson/cc, /api/geojson/ulez | Raw GeoJSON boundaries |
| GET /docs | OpenAPI interactive docs |
MCP Server
AI assistants (Claude, ChatGPT, etc.) can connect to the public MCP endpoint to check toll zones. Full reference in MCP.md.
https://london-toll-checker.diegoalto.app/mcp{
"mcpServers": {
"london-ulez-and-congestion-charge-checker": { "url": "https://london-toll-checker.diegoalto.app/mcp" }
}
}Tools: check_toll_zones, check_congestion_charge, check_ulez
NPM Package
Use london-toll-checker as a library in your own projects. Full reference in NPM.md.
import { ZoneChecker, zoneData } from "@diegoaltoworks/london-toll-checker";
const cc = new ZoneChecker(zoneData.cc);
console.log(cc.check("51.5087191,-0.1420653")); // { result: true }Embeddable widget
<div id="london-toll-checker-embed"></div>
<script src="https://london-toll-checker.diegoalto.app/public/embed.js"></script>See docs/embedding.md for details.
Asset hashing
Public assets (JS, CSS) are built with content hashes in filenames for reliable CDN cache busting:
bun run build
# form.js → form.a1b2c3d4e5.js
# form.css → form.f6g7h8i9j0.css
# embed.js → embed.k1l2m3n4o5.jsThe server reads dist/public/manifest.json at startup and serves hashed assets with Cache-Control: public, max-age=31536000, immutable. When code changes, the hash changes, and CDNs fetch the new version. Without a build, the server falls back to serving unhashed assets from src/public/.
Project structure
src/
index.ts # Package public API (exports for library use)
index.test.ts # Package export tests
server.ts # Express HTTP server entry point
db.ts # Loads GeoJSON zone data into memory
config.json # Port, body limit
landing.ts # /about, /health, /robots.txt, /sitemap.xml
models/
zone-checker.ts # ZoneChecker class (point-in-polygon)
zone-checker.test.ts
index.ts # Re-exports
api/
index.ts # API router factory (mounts sub-routers)
api.test.ts # HTTP integration tests
zone-router.ts # Generic zone router factory (check + geojson)
check.ts # /api/check combined endpoint
geojson.ts # /api/geojson endpoint
data/
cc.json # CC zone boundary GeoJSON (42 KB)
ulez.json # ULEZ boundary GeoJSON (2.3 MB)
public/
index.html # Standalone toll checker page
form.js # Google Maps + Places autocomplete logic
form.test.ts # Coordinate regex validation tests
form.css # Styles
embed.js # Embeddable widget loader
openapi/
openapi.yml # OpenAPI 3.0 spec
scripts/
build-assets.ts # Asset hashing build script
dist/ # Built hashed assets (gitignored)Tech stack
- Language: TypeScript
- Runtime: Bun
- Framework: Express 4.x
- Geospatial: @turf/boolean-point-in-polygon 7.x
- Tests: bun:test (52 tests, colocated with modules)
- Container: Multi-stage Docker build with
oven/bun:latest - Deployment: Google Cloud Run
Data sources
- ULEZ boundary: London Datastore - Ultra Low Emission Zone 2023
- Congestion Charge boundary: TfL Congestion Charge Zone
