aa8a0bd83f
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.
78 lines
2.5 KiB
JavaScript
78 lines
2.5 KiB
JavaScript
export const MAX_BBOX_DEGREES = 0.5
|
|
|
|
export function tileKey(row, col) {
|
|
return `${row},${col}`
|
|
}
|
|
|
|
export function bboxToTileKey(bbox) {
|
|
const row = Math.floor(bbox.south / MAX_BBOX_DEGREES)
|
|
const col = Math.floor(bbox.west / MAX_BBOX_DEGREES)
|
|
return tileKey(row, col)
|
|
}
|
|
|
|
function tileBox(row, col, step = MAX_BBOX_DEGREES) {
|
|
const south = row * step
|
|
const west = col * step
|
|
return { south, west, north: south + step, east: west + step }
|
|
}
|
|
|
|
export function bboxFetchKey(bounds) {
|
|
const zoom = bounds.zoom ?? 14
|
|
const step = zoom >= 14 ? 0.025 : zoom >= 11 ? 0.1 : zoom >= 8 ? 0.25 : 1
|
|
const q = v => Math.round(v / step) * step
|
|
return [q(bounds.south), q(bounds.west), q(bounds.north), q(bounds.east)].join(',')
|
|
}
|
|
|
|
function ringOffsets(radius) {
|
|
if (radius === 0) return [[0, 0]]
|
|
return Array.from({ length: 2 * radius + 1 }, (_, i) => i - radius)
|
|
.flatMap(dr => Array.from({ length: 2 * radius + 1 }, (_, j) => j - radius)
|
|
.filter(dc => Math.abs(dr) === radius || Math.abs(dc) === radius)
|
|
.map(dc => [dr, dc]))
|
|
}
|
|
|
|
function collectTiles(state) {
|
|
const {
|
|
centerRow, centerCol, minRow, maxRow, minCol, maxCol, step, limit, radius, seen, tiles,
|
|
} = state
|
|
if (tiles.length >= limit) return tiles
|
|
|
|
const inBounds = (row, col) => row >= minRow && row <= maxRow && col >= minCol && col <= maxCol
|
|
const { nextSeen, added } = ringOffsets(radius)
|
|
.map(([dr, dc]) => [centerRow + dr, centerCol + dc])
|
|
.filter(([row, col]) => inBounds(row, col))
|
|
.reduce((acc, [row, col]) => {
|
|
const key = `${row},${col}`
|
|
if (acc.nextSeen.has(key)) return acc
|
|
return {
|
|
nextSeen: new Set([...acc.nextSeen, key]),
|
|
added: [...acc.added, tileBox(row, col, step)],
|
|
}
|
|
}, { nextSeen: seen, added: [] })
|
|
|
|
if (added.length === 0 && radius > 0) return tiles
|
|
|
|
const nextTiles = [...tiles, ...added].slice(0, limit)
|
|
if (nextTiles.length >= limit) return nextTiles
|
|
return collectTiles({ ...state, radius: radius + 1, seen: nextSeen, tiles: nextTiles })
|
|
}
|
|
|
|
export function tilesNearCenter(bounds, limit) {
|
|
const step = MAX_BBOX_DEGREES
|
|
const lat = (bounds.south + bounds.north) / 2
|
|
const lng = (bounds.west + bounds.east) / 2
|
|
return collectTiles({
|
|
centerRow: Math.floor(lat / step),
|
|
centerCol: Math.floor(lng / step),
|
|
minRow: Math.floor(bounds.south / step),
|
|
maxRow: Math.ceil(bounds.north / step) - 1,
|
|
minCol: Math.floor(bounds.west / step),
|
|
maxCol: Math.ceil(bounds.east / step) - 1,
|
|
step,
|
|
limit,
|
|
radius: 0,
|
|
seen: new Set(),
|
|
tiles: [],
|
|
})
|
|
}
|