Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ALPR cameras → OsmAnd

datascry.github.io/alpr2osmand

Turn OpenStreetMap's crowdsourced ALPR (automated license plate reader) camera data — Flock Safety, Genetec, Motorola/Vigilant and others — into offline map layers you can load into OsmAnd.

Two layers, meant to be used together:

Layer What it shows
<region>.gpx One point per camera, coloured and grouped by vendor
<region>-sightlines.gpx A wedge per camera showing which way it faces

The sightline layer is the point of this project. A dot tells you a camera exists; the wedge tells you which approach to an intersection is covered and which direction of travel gets read. direction is tagged on roughly 94–98% of cameras in well-surveyed metros.

Use it two ways:

  • Web app — pick an area on a map in the browser, preview, download. No install.
  • CLI — for whole states, scripting, and repeat runs.

Both produce byte-identical files; a golden test enforces that.

Is this a duplicate of DeFlock?

No, but it's close enough to be worth explaining, because you may want DeFlock instead:

  • To browse or report cameras → use DeFlock. It's the canonical map and the reason this data exists in OSM at all. This project is downstream of their community's work.
  • To have cameras offline in a dedicated mobile app → use the DeFlock app (iOS/Android, AGPL). It does offline regions, on-device caching and editing natively, and does it better than a GPX file ever will.
  • For generic OSM data exportOverpass Turbo already exports GeoJSON/KML/GPX from a bounding box.

What none of those do is produce OsmAnd-native layers — vendor groups as toggleable Favorites, styled icons and colours, rich per-camera descriptions — or synthesise coverage geometry from the direction tag. That gap is the entire reason this exists. DeFlock declined to build it in-site (issue #1: "I don't think enough people would use it to warrant adding it straight to the site"), pointing people at Overpass Turbo instead.

If you want cameras sitting alongside your own maps, routes and tracks in OsmAnd, with coverage wedges, this is that.

Web app

datascry.github.io/alpr2osmand — mobile-first, no install.

Cameras load automatically for whatever you are looking at, so there is no "fetch" step. Pan or search to an area, then pick what to export:

Tool How
▣ Visible area Default — download exactly what is on screen
◯ Circle Tap to drop a centre pin, then set the radius with a slider
⬔ Points Tap two or more points; the box is their bounding box

All three are tap-driven, so they work the same on a phone as on a desktop. The download button stays pinned in the bottom sheet and never scrolls out of reach.

Every camera is drawn as its own dot — no clustering, no grouped bubbles, at any zoom. Zoomed all the way out that is ~42,000 dots on screen at once, which pans at roughly 40fps; a city view is effectively free.

That works because all the dots are drawn in a single canvas pass (docs/js/dots.js), batched into one path per vendor colour, rather than one Leaflet layer per camera. Measured on a phone-sized viewport:

View Cameras Draw Pan
City (z13) 42 0.1 ms 0.1 ms
Metro (z11) 2,088 1.0 ms 1.3 ms
Region (z9) 6,147 2.5 ms 2.8 ms
Whole US (z5) 42,234 14.4 ms 23.9 ms

Tapping is handled by a distance search rather than per-marker hit areas, so a small dot is still an easy target (~18 px tolerance).

Run node tests/e2e/perf.mjs against a local server to re-measure.

Theme. The ◐ button cycles auto → light → dark and remembers the choice. It switches the map tiles too, not just the interface: OpenStreetMap standard in light, CARTO Dark Matter in dark. "Auto" follows the system setting live.

How the map data is served

Querying Overpass on every pan would be slow and abusive, so the site serves a pre-baked snapshot instead — the same approach DeFlock uses, adapted:

  • A scheduled GitHub Action runs one global Overpass query per day and segments the result into a 2° grid (build/build_tiles.py).
  • data/index.json lists which tiles actually contain cameras, so the browser never requests an empty one, plus an expiration_utc and a ?v= cache buster.
  • The browser loads only the tiles covering the viewport, de-duplicates in-flight requests, and caches tiles for the session.

Tiles are built in CI and deployed as a Pages artifact — they are not committed, so the repo stays small. Worldwide that is ~150k cameras in 571 tiles; the largest is ~93 KB gzipped and the median is ~1 KB.

The one thing we could not lift is DeFlock's CDN itself: cdn.deflock.me is CORS-locked to deflock.org, so serving our own tiles from the same origin as the app is what makes this work at all.

Because the snapshot is up to a day old, Data freshness → "Re-check this area against OSM now" re-queries Overpass live for just the visible area.

CLI

No dependencies — Python 3 standard library only.

./alpr2osmand.py --place "Philadelphia, Pennsylvania"
./alpr2osmand.py --place "Pennsylvania" --place "New Jersey" -o pa-nj
./alpr2osmand.py --bbox 39.90,-75.30,40.10,-75.05 -o my-area
./alpr2osmand.py --place "Atlanta, Georgia" --sightline-m 120 --fov-deg 40
Flag Effect
-o NAME / -d DIR Output basename / directory
--sightline-m N Wedge length in metres (default 80)
--fov-deg N Wedge angle in degrees (default 30)
--no-sightlines Skip the direction layer
--cache PATH Save/reuse the raw Overpass JSON

Each run prints a breakdown by vendor, zone, mount and facing, plus how many cameras are field-verified (check_date).

Loading into OsmAnd

The two GPX files go to different places on purpose.

1 · Cameras → Favorites. My Places → Favorites → ⋮ → Import Favorites are searchable offline by name, and each vendor becomes its own toggleable group. Show them with Configure map → Favorites.

2 · Sightlines → Tracks. My Places → Tracks → ⋮ → Import Enable under Configure map → Tracks. Keeping these as a track means one switch hides all the wedges while the camera points stay put.

3 · Offline base map. Maps & Resources → Downloads → the region's vector map. After that, none of this needs a connection.

Reading the map

  • Dot colour = vendor — Flock red, Genetec orange, Motorola/Vigilant amber, Leonardo/ELSAG blue, unattributed grey.
  • Label is vendor + facing, e.g. Flock NE.
  • Tap a dot for operator, mount, height, power, camera type, survey date and a link back to the OSM node.
  • Wedge = facing direction, apex at the camera. A camera tagged with two directions (reading both ways on a road) gets one wedge per direction.

About the wedge size

The default 80 m × 30° wedge is an illustrative default, not a measured specification. Real plate-read range depends on the unit, lens, mounting height, lighting and vehicle speed. Treat it as "roughly this way", not a coverage boundary, and adjust with --sightline-m / --fov-deg if you have better local information.

Data, and what it doesn't tell you

Source is OpenStreetMap via the Overpass API. Coverage is crowdsourced and varies enormously: metros DeFlock volunteers have swept are close to exhaustive, rural areas can be near-empty. An empty area is not evidence there are no cameras there. Data is ODbL — keep attribution if you redistribute.

Other bulk sources exist but can't be used from the browser:

Source Freshness Usable from a static site?
Overpass API live Yesoverpass-api.de sends Access-Control-Allow-Origin: *
cdn.deflock.me/regions/*.json hourly No — CORS allowlisted to deflock.org
alprwatch.org/pub/extract/ varies No — CORS restricted to own origin

The latter two are fine to use server-side if you're scripting bulk pulls.

Development

alpr2osmand.py          CLI
build/build_tiles.py    bakes the global snapshot into a 2° tile grid
docs/                   the GitHub Pages site
  js/alpr.js            query, classify, parse directions, geometry
  js/export.js          GPX / GeoJSON / CSV writers
  js/tiles.js           static tile store (index, cache, in-flight dedupe)
  js/app.js             UI
  data/                 built in CI, gitignored
tests/run-golden.sh     diffs CLI output against browser output, byte for byte
tests/e2e/smoke.mjs     drives the real page in Chromium, touch included
.github/workflows/      golden test, daily tile build, Pages deploy

Build tiles locally to work on the map offline:

./build/fetch_global.sh build/global-raw.json          # ~56 MB, a few minutes
python3 build/build_tiles.py --input build/global-raw.json --outdir docs/data
python3 -m http.server -d docs 8000

Without docs/data the site falls back to querying Overpass live, so the app still works — just slower.

js/alpr.js and js/export.js deliberately mirror alpr2osmand.py. If you change a writer in one, change it in the other — the golden test will catch you if you don't:

./tests/run-golden.sh     # uses local node, or docker if there isn't one

Serve the site locally with python3 -m http.server -d docs 8000. It uses ES modules, so file:// won't work.

Contributing back

If you spot a camera that isn't mapped, adding it improves everyone's copy. DeFlock has a guided submission flow, or tag it directly in OSM:

man_made=surveillance
surveillance:type=ALPR
surveillance=public
surveillance:zone=traffic
manufacturer=Flock Safety
direction=<degrees the camera faces, 0=N>
camera:mount=pole

direction is the single most valuable tag to get right — it's what makes the scope layer meaningful.

Credits

Camera data is crowdsourced into OpenStreetMap, largely by DeFlock contributors. Map tiles © OpenStreetMap contributors. Dark map tiles by CARTO. Mapping libraries: Leaflet and Leaflet.markercluster, both BSD-2-Clause and vendored. Queries served by the Overpass API.

The tile-caching scheme (grid segmentation, an index of non-empty tiles, index expiry and cache-busting) is adapted from DeFlock's alpr_cache.py and their frontend tile store, both MIT.

About

Turn OpenStreetMap ALPR camera data into offline OsmAnd layers, including field-of-view wedges. Web app + CLI.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages