Add ADS-B, AIS, and ALPR map layers with live CoT streaming (#36)
Push / release (push) Successful in 13s
Push / publish (push) Successful in 1m4s

## Summary

- **ADS-B & AIS:** OpenSky and AISStream OSINT feeds upsert into the CoT store; tactical tracks still arrive via adsbcot/aiscot on `:8089`. Map clients subscribe via `GET /api/cot/stream` (SSE) with viewport bbox filtering and Air / Surface / Team layer toggles.
- **ALPR (Flock/OSM):** Toggleable license-plate reader layer sourced from OpenStreetMap, with SQLite cache, Overpass fallback, tiled viewport fetching, and clustered markers with direction cones.
- **Map performance:** Ring-based tile selection (fixes zoom-out crash), immutable tile cache, incremental marker sync, split cluster load/query, and padded SSE bbox to reduce reconnect churn.

## Docs

- `docs/tracking.md` — ADS-B/AIS accuracy tiers, freshness, self-hosted receivers, optional OSINT API keys
- `docs/map-and-cameras.md` — ALPR layer and map behavior updates

---------

Co-authored-by: Madison Grubb <madison@elastiflow.com>
Reviewed-on: #36
This commit was merged in pull request #36.
This commit is contained in:
2026-06-24 20:54:50 +00:00
parent a6b87305a1
commit bb01e9a06c
64 changed files with 5762 additions and 2119 deletions
+23 -21
View File
@@ -16,34 +16,36 @@
"test:e2e:ui": "playwright test --ui test/e2e",
"test:e2e:debug": "playwright test --debug test/e2e",
"test:e2e:install": "playwright install --with-deps webkit chromium firefox",
"lint": "eslint . --max-warnings 0"
"lint": "eslint . --max-warnings 0",
"import:alpr": "node scripts/import-alpr.js"
},
"dependencies": {
"@nuxt/icon": "^2.2.1",
"@nuxt/icon": "^2.2.3",
"@nuxtjs/tailwindcss": "^6.14.0",
"fast-xml-parser": "^5.3.6",
"hls.js": "^1.5.0",
"fast-xml-parser": "^5.9.3",
"hls.js": "^1.6.16",
"jszip": "^3.10.1",
"leaflet": "^1.9.4",
"leaflet.offline": "^3.2.0",
"mediasoup": "^3.19.14",
"mediasoup-client": "^3.18.6",
"nuxt": "^4.0.0",
"openid-client": "^6.8.2",
"leaflet.offline": "^3.2.1",
"mediasoup": "^3.20.9",
"mediasoup-client": "^3.21.0",
"nuxt": "^4.4.8",
"openid-client": "^6.8.4",
"qrcode": "^1.5.4",
"vue": "^3.4.0",
"vue-router": "^5.0.0",
"ws": "^8.18.0"
"supercluster": "^8.0.1",
"vue": "^3.5.38",
"vue-router": "^5.1.0",
"ws": "^8.21.0"
},
"devDependencies": {
"@iconify-json/tabler": "^1.2.26",
"@nuxt/eslint": "^1.15.0",
"@nuxt/test-utils": "^4.0.0",
"@playwright/test": "^1.58.2",
"@vitest/coverage-v8": "^4.0.0",
"@vue/test-utils": "^2.4.0",
"eslint": "^10.0.0",
"happy-dom": "^20.6.1",
"vitest": "^4.0.0"
"@iconify-json/tabler": "^1.2.35",
"@nuxt/eslint": "^1.16.0",
"@nuxt/test-utils": "^4.0.3",
"@playwright/test": "^1.61.1",
"@vitest/coverage-v8": "^4.1.9",
"@vue/test-utils": "^2.4.11",
"eslint": "^10.5.0",
"happy-dom": "^20.10.6",
"vitest": "^4.1.9"
}
}