| import type { FleetTelemetryPayload, HazardType } from "./api"; |
|
|
| export type FleetHazardType = Exclude<HazardType, "other">; |
|
|
| export interface FleetDetection { |
| hazard_type: FleetHazardType; |
| confidence: number; |
| bbox: [number, number, number, number]; |
| } |
|
|
| export interface FleetLocation { |
| lat: number; |
| lon: number; |
| speed: number | null; |
| at: number; |
| } |
|
|
| export interface LastFleetReport { |
| lat: number; |
| lon: number; |
| at: number; |
| } |
|
|
| export const FLEET_CONFIDENCE = 0.55; |
| export const TRAFFIC_BLOCK_CONFIDENCE = 0.45; |
| export const TRAFFIC_BLOCK_SPEED_MPS = 2; |
| export const TRAFFIC_BLOCK_DWELL_MS = 10_000; |
| export const FLEET_DEBOUNCE_METERS = 10; |
|
|
| const EARTH_RADIUS_M = 6_371_000; |
|
|
| export function haversineMeters(a: Pick<FleetLocation, "lat" | "lon">, b: Pick<FleetLocation, "lat" | "lon">): number { |
| const dLat = ((b.lat - a.lat) * Math.PI) / 180; |
| const dLon = ((b.lon - a.lon) * Math.PI) / 180; |
| const lat1 = (a.lat * Math.PI) / 180; |
| const lat2 = (b.lat * Math.PI) / 180; |
| const h = |
| Math.sin(dLat / 2) ** 2 + |
| Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2; |
| return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(h)); |
| } |
|
|
| export function createClientEventId(): string { |
| if ("randomUUID" in crypto) return crypto.randomUUID(); |
| return `fleet-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; |
| } |
|
|
| export function shouldSendFleetReport( |
| detection: FleetDetection, |
| location: FleetLocation, |
| lastReports: Map<FleetHazardType, LastFleetReport>, |
| ): boolean { |
| const previous = lastReports.get(detection.hazard_type); |
| if (!previous) return true; |
| return haversineMeters(previous, location) > FLEET_DEBOUNCE_METERS; |
| } |
|
|
| export function bboxAreaFraction(bbox: [number, number, number, number], frameSize: number): number { |
| const [x1, y1, x2, y2] = bbox; |
| const w = Math.max(0, x2 - x1); |
| const h = Math.max(0, y2 - y1); |
| return Math.min(1, (w * h) / (frameSize * frameSize)); |
| } |
|
|
| export function toFleetPayload(opts: { |
| detection: FleetDetection; |
| location: FleetLocation; |
| thumbnailB64?: string; |
| frameSize: number; |
| }): FleetTelemetryPayload { |
| return { |
| lat: opts.location.lat, |
| lon: opts.location.lon, |
| hazard_type: opts.detection.hazard_type, |
| confidence: opts.detection.confidence, |
| timestamp: new Date().toISOString(), |
| speed: opts.location.speed ?? undefined, |
| bbox: opts.detection.bbox, |
| bbox_area_frac: bboxAreaFraction(opts.detection.bbox, opts.frameSize), |
| thumbnail_b64: opts.thumbnailB64, |
| client_event_id: createClientEventId(), |
| }; |
| } |
|
|