113 lines
4.2 KiB
JavaScript
113 lines
4.2 KiB
JavaScript
// OpenSky Network — Real-time flight tracking
|
|
// Free for research. 4,000 API credits/day (no auth), 8,000 with account.
|
|
// Tracks all aircraft with ADS-B transponders including many military.
|
|
|
|
import { safeFetch } from '../utils/fetch.mjs';
|
|
|
|
const BASE = 'https://opensky-network.org/api';
|
|
|
|
// Get all current flights (global state vector)
|
|
export async function getAllFlights() {
|
|
return safeFetch(`${BASE}/states/all`, { timeout: 30000 });
|
|
}
|
|
|
|
// Get flights in a bounding box (lat/lon)
|
|
export async function getFlightsInArea(lamin, lomin, lamax, lomax) {
|
|
const params = new URLSearchParams({
|
|
lamin: String(lamin),
|
|
lomin: String(lomin),
|
|
lamax: String(lamax),
|
|
lomax: String(lomax),
|
|
});
|
|
return safeFetch(`${BASE}/states/all?${params}`, { timeout: 20000 });
|
|
}
|
|
|
|
// Get flights by specific aircraft (ICAO24 hex codes)
|
|
export async function getFlightsByIcao(icao24List) {
|
|
const icao = Array.isArray(icao24List) ? icao24List : [icao24List];
|
|
const params = icao.map(i => `icao24=${i}`).join('&');
|
|
return safeFetch(`${BASE}/states/all?${params}`, { timeout: 20000 });
|
|
}
|
|
|
|
// Get departures from an airport in a time range
|
|
export async function getDepartures(airportIcao, begin, end) {
|
|
const params = new URLSearchParams({
|
|
airport: airportIcao,
|
|
begin: String(Math.floor(begin / 1000)),
|
|
end: String(Math.floor(end / 1000)),
|
|
});
|
|
return safeFetch(`${BASE}/flights/departure?${params}`);
|
|
}
|
|
|
|
// Get arrivals at an airport
|
|
export async function getArrivals(airportIcao, begin, end) {
|
|
const params = new URLSearchParams({
|
|
airport: airportIcao,
|
|
begin: String(Math.floor(begin / 1000)),
|
|
end: String(Math.floor(end / 1000)),
|
|
});
|
|
return safeFetch(`${BASE}/flights/arrival?${params}`);
|
|
}
|
|
|
|
// Key hotspot regions for monitoring
|
|
const HOTSPOTS = {
|
|
middleEast: { lamin: 12, lomin: 30, lamax: 42, lomax: 65, label: 'Middle East' },
|
|
taiwan: { lamin: 20, lomin: 115, lamax: 28, lomax: 125, label: 'Taiwan Strait' },
|
|
ukraine: { lamin: 44, lomin: 22, lamax: 53, lomax: 41, label: 'Ukraine Region' },
|
|
baltics: { lamin: 53, lomin: 19, lamax: 60, lomax: 29, label: 'Baltic Region' },
|
|
southChinaSea: { lamin: 5, lomin: 105, lamax: 23, lomax: 122, label: 'South China Sea' },
|
|
koreanPeninsula: { lamin: 33, lomin: 124, lamax: 43, lomax: 132, label: 'Korean Peninsula' },
|
|
caribbean: { lamin: 18, lomin: -90, lamax: 30, lomax: -72, label: 'Caribbean' },
|
|
gulfOfGuinea: { lamin: -2, lomin: -5, lamax: 8, lomax: 10, label: 'Gulf of Guinea' },
|
|
capeRoute: { lamin: -38, lomin: 12, lamax: -28, lomax: 24, label: 'Cape Route' },
|
|
hornOfAfrica: { lamin: 5, lomin: 40, lamax: 15, lomax: 55, label: 'Horn of Africa' },
|
|
};
|
|
|
|
// Briefing — check hotspot regions for flight activity
|
|
export async function briefing() {
|
|
const hotspotEntries = Object.entries(HOTSPOTS);
|
|
const results = await Promise.all(
|
|
hotspotEntries.map(async ([key, box]) => {
|
|
const data = await getFlightsInArea(box.lamin, box.lomin, box.lamax, box.lomax);
|
|
const error = data?.error || null;
|
|
const states = data?.states || [];
|
|
return {
|
|
region: box.label,
|
|
key,
|
|
totalAircraft: states.length,
|
|
// states format: [icao24, callsign, origin_country, ...]
|
|
byCountry: states.reduce((acc, s) => {
|
|
const country = s[2] || 'Unknown';
|
|
acc[country] = (acc[country] || 0) + 1;
|
|
return acc;
|
|
}, {}),
|
|
// Flag potentially interesting (military often have no callsign or specific patterns)
|
|
noCallsign: states.filter(s => !s[1]?.trim()).length,
|
|
highAltitude: states.filter(s => s[7] && s[7] > 12000).length, // >12km altitude
|
|
...(error ? { error } : {}),
|
|
};
|
|
})
|
|
);
|
|
|
|
const hotspotErrors = results
|
|
.filter(r => r.error)
|
|
.map(r => ({ region: r.region, error: r.error }));
|
|
|
|
return {
|
|
source: 'OpenSky',
|
|
timestamp: new Date().toISOString(),
|
|
hotspots: results,
|
|
...(hotspotErrors.length ? {
|
|
error: hotspotErrors.length === results.length
|
|
? `OpenSky unavailable across all hotspots: ${hotspotErrors[0].error}`
|
|
: `OpenSky unavailable for ${hotspotErrors.length}/${results.length} hotspots`,
|
|
hotspotErrors,
|
|
} : {}),
|
|
};
|
|
}
|
|
|
|
if (process.argv[1]?.endsWith('opensky.mjs')) {
|
|
const data = await briefing();
|
|
console.log(JSON.stringify(data, null, 2));
|
|
}
|