Add ADS-B, AIS, and ALPR map layers with live CoT streaming.
Ingest aircraft and vessel tracks via OSINT feeds and tactical CoT, expose viewport-filtered SSE to the map, and add an OSM ALPR layer with tiled caching and performant marker sync.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/** SSE subscriber registry; bbox union drives OSINT feeds. */
|
||||
|
||||
import { getActiveEntitiesInBbox } from './cotStore.js'
|
||||
import { isInBbox, matchesLayerFilter, unionBboxes } from './cotEntityUtils.js'
|
||||
|
||||
/** @typedef {{ west: number, south: number, east: number, north: number }} Bbox */
|
||||
/** @typedef {(event: string, data: string) => Promise<void> | void} PushFn */
|
||||
|
||||
/** @type {Map<string, { bbox: Bbox | null, layers: Set<string>, push: PushFn }>} */
|
||||
const subscribers = new Map()
|
||||
let nextId = 1
|
||||
|
||||
/**
|
||||
* @param {{ bbox: Bbox | null, layers: Set<string>, push: PushFn }} sub
|
||||
* @returns {() => void} Unregister function.
|
||||
*/
|
||||
export function registerSubscriber(sub) {
|
||||
const id = String(nextId++)
|
||||
subscribers.set(id, sub)
|
||||
return () => subscribers.delete(id)
|
||||
}
|
||||
|
||||
/** @returns {Bbox | null} Union of all subscriber bboxes. */
|
||||
export function getSubscriberBboxUnion() {
|
||||
return unionBboxes([...subscribers.values()].map(s => s.bbox))
|
||||
}
|
||||
|
||||
export function getSubscriberCount() {
|
||||
return subscribers.size
|
||||
}
|
||||
|
||||
export function clearSubscribers() {
|
||||
subscribers.clear()
|
||||
}
|
||||
|
||||
export async function notifySubscribersForEntity(event, payload, entity) {
|
||||
const data = JSON.stringify(payload)
|
||||
const tasks = []
|
||||
for (const sub of subscribers.values()) {
|
||||
if (sub.bbox && !isInBbox(entity, sub.bbox)) continue
|
||||
if (!matchesLayerFilter(sub.layers, entity)) continue
|
||||
tasks.push(Promise.resolve(sub.push(event, data)))
|
||||
}
|
||||
await Promise.all(tasks)
|
||||
}
|
||||
|
||||
export async function notifySubscribersRemove(id) {
|
||||
const data = JSON.stringify({ id })
|
||||
await Promise.all(
|
||||
[...subscribers.values()].map(sub => Promise.resolve(sub.push('remove', data))),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a filtered snapshot to each active SSE subscriber.
|
||||
* @param {{ ttlMs?: number, osintTtlMs?: number, takFilterBbox?: boolean, maxEntities?: number }} snapshotOpts
|
||||
*/
|
||||
export async function broadcastSubscriberSnapshots(snapshotOpts) {
|
||||
const tasks = []
|
||||
for (const sub of subscribers.values()) {
|
||||
tasks.push((async () => {
|
||||
const entities = await getActiveEntitiesInBbox(sub.bbox, {
|
||||
...snapshotOpts,
|
||||
layers: sub.layers,
|
||||
})
|
||||
await sub.push('snapshot', JSON.stringify({ entities }))
|
||||
})())
|
||||
}
|
||||
await Promise.all(tasks)
|
||||
}
|
||||
Reference in New Issue
Block a user