bb01e9a06c
## 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
112 lines
3.5 KiB
JavaScript
112 lines
3.5 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
inferSourceFromId,
|
|
cotCategoryFromType,
|
|
isInBbox,
|
|
matchesLayerFilter,
|
|
openSkyStateToCot,
|
|
aisStreamMessageToCot,
|
|
unionBboxes,
|
|
clampBbox,
|
|
parseBboxParam,
|
|
parseLayersParam,
|
|
} from '../../../server/utils/cotEntityUtils.js'
|
|
|
|
describe('cotEntityUtils', () => {
|
|
it('infers source from UID prefix', () => {
|
|
expect(inferSourceFromId('ICAO.abc123')).toBe('adsb')
|
|
expect(inferSourceFromId('MMSI.366123456')).toBe('ais')
|
|
expect(inferSourceFromId('ANDROID-deadbeef')).toBe('tak')
|
|
})
|
|
|
|
it('maps CoT type to category', () => {
|
|
expect(cotCategoryFromType('a-f-A-C-F')).toBe('air')
|
|
expect(cotCategoryFromType('a-f-S-C')).toBe('surface')
|
|
expect(cotCategoryFromType('a-f-G-U-C')).toBe('ground')
|
|
})
|
|
|
|
it('checks bbox membership', () => {
|
|
const bbox = { west: -123, south: 37, east: -122, north: 38 }
|
|
expect(isInBbox({ lat: 37.5, lng: -122.5 }, bbox)).toBe(true)
|
|
expect(isInBbox({ lat: 40, lng: -122.5 }, bbox)).toBe(false)
|
|
})
|
|
|
|
it('filters by layer set', () => {
|
|
const airOnly = new Set(['air'])
|
|
expect(matchesLayerFilter(airOnly, { type: 'a-f-A-C-F' })).toBe(true)
|
|
expect(matchesLayerFilter(airOnly, { type: 'a-f-S-C' })).toBe(false)
|
|
})
|
|
|
|
it('maps OpenSky state vector to CoT', () => {
|
|
const state = ['abc123', 'UAL123 ', 'United States', 1, 2, -122.4, 37.7, 10000, false, 200, 90, 5, null, null, 1200, false, 0, 0]
|
|
const cot = openSkyStateToCot(state)
|
|
expect(cot).toMatchObject({
|
|
id: 'ICAO.abc123',
|
|
lat: 37.7,
|
|
lng: -122.4,
|
|
label: 'UAL123',
|
|
source: 'adsb',
|
|
type: 'a-f-A-C-F',
|
|
icao: 'abc123',
|
|
originCountry: 'United States',
|
|
heading: 90,
|
|
speed: 200,
|
|
altitude: 10000,
|
|
verticalRate: 5,
|
|
squawk: '1200',
|
|
})
|
|
})
|
|
|
|
it('maps OpenSky rotorcraft to helicopter CoT type', () => {
|
|
const state = ['heli01', 'N123HC ', 'United States', 1, 2, -122.4, 37.7, 500, false, 50, 180, 0, null, null, null, false, 0, 8]
|
|
const cot = openSkyStateToCot(state)
|
|
expect(cot?.type).toBe('a-f-A-C-H')
|
|
})
|
|
|
|
it('maps AISStream message to CoT', () => {
|
|
const cot = aisStreamMessageToCot({
|
|
MetaData: { MMSI: 366123456, ShipName: 'TEST SHIP' },
|
|
Message: {
|
|
PositionReport: {
|
|
UserID: 366123456,
|
|
Latitude: 37.8,
|
|
Longitude: -122.3,
|
|
Sog: 12.5,
|
|
Cog: 180,
|
|
},
|
|
},
|
|
})
|
|
expect(cot).toMatchObject({
|
|
id: 'MMSI.366123456',
|
|
lat: 37.8,
|
|
lng: -122.3,
|
|
label: 'TEST SHIP',
|
|
source: 'ais',
|
|
type: 'a-f-S-C',
|
|
})
|
|
})
|
|
|
|
it('unions bboxes', () => {
|
|
expect(unionBboxes([
|
|
{ west: -123, south: 37, east: -122, north: 38 },
|
|
{ west: -124, south: 36, east: -121, north: 39 },
|
|
])).toEqual({ west: -124, south: 36, east: -121, north: 39 })
|
|
})
|
|
|
|
it('clamps oversized bbox to max span', () => {
|
|
const huge = { west: -125, south: 32, east: -115, north: 42 }
|
|
const clamped = clampBbox(huge, 10)
|
|
expect(clamped.north - clamped.south).toBeCloseTo(10)
|
|
expect(clamped.east - clamped.west).toBeCloseTo(10)
|
|
expect((clamped.north + clamped.south) / 2).toBeCloseTo(37)
|
|
expect((clamped.east + clamped.west) / 2).toBeCloseTo(-120)
|
|
})
|
|
|
|
it('parses bbox and layers query params', () => {
|
|
expect(parseBboxParam('-123,37,-122,38')).toEqual({ west: -123, south: 37, east: -122, north: 38 })
|
|
expect(parseBboxParam('bad')).toBeNull()
|
|
expect(parseLayersParam('air,surface')).toEqual(new Set(['air', 'surface']))
|
|
expect(parseLayersParam('none').size).toBe(0)
|
|
})
|
|
})
|