* Refactor aircraft photo and hero banner components to reset loading state on photo change - Updated Lightbox component to reset image loading state when navigating between photos. - Modified HeroBanner component to reset loading state when the photo changes. Clean up control panel search logic - Removed unnecessary hasResults variable in SearchContent component. Implement flight API client with fallback mechanism - Added flight-api-client to handle fetching flight data from multiple sources (airplanes.live, adsb.lol, OpenSky). - Introduced flight-api-parsing module to convert raw API responses into standardized FlightState objects. - Created flight-api-types for shared types between API responses. Refactor useFlights hook to utilize new flight API client - Updated useFlights hook to fetch flights using the new flight API client. - Removed credit management logic as it is no longer applicable with the new API structure. Fix useFlightMonitors to fetch flight data by hex address - Changed useFlightMonitors to use fetchFlightByHex instead of fetchFlightByIcao24. Update geo utility function for better readability - Refactored splitAtAntimeridian function to improve variable naming and clarity. Enhance OpenSky types with additional fields - Added typeCode and registration fields to FlightState type for better integration with readsb data. * fix: correct 6 files that diverged during rebase (iata code, globe mode ref, terrain attribution, cache eviction, opensky parsing) * fix: improve keyboard shortcuts help focus trapping feat: add showAirspace option to MapAttribution component fix: clear hideTimer on ScrollArea cleanup refactor: change pendingFpvRef to MutableRefObject in useFlightMonitors fix: handle sessionStorage availability in useFlightTrack refactor: increase POLL_INTERVAL_MS in useFlights for better performance fix: optimize keyboard shortcuts dialog check refactor: optimize useMergedTrails by caching selected flight position feat: extend Settings type with airspace options refactor: improve airline logo normalization functions refactor: enhance flight API client with serialized rate limiting refactor: optimize registration country lookup with pre-built maps refactor: enhance logo cache management with size limits feat: update map attribution to include airspace option fix: validate rawState in parseStateRow function refactor: improve utility functions with clamp implementation * feat: add ATC lookup functionality and GPU memory monitoring - Implemented ATC lookup functions in `atc-lookup.ts` for converting IATA to ICAO codes, finding nearby ATC feeds, and looking up ATC feeds by code. - Introduced `atc-types.ts` to define types and priorities for ATC feeds. - Added GPU memory monitoring in `gpu-memory-monitor.ts` to track WebGL resource allocations and provide memory reports. - Enhanced trail stitching logic in `trail-stitching.ts` by adding a function to clear the splined track cache and optimizing altitude checks. * feat: enhance flight data handling and improve API resilience - Implemented a maximum empty response streak guard in useFlights to prevent data loss during transient API failures. - Added immediate fetch on network reconnect in useFlights to ensure timely data retrieval. - Updated useMergedTrails to include timestamps for trail points. - Removed smoothAnimations setting from useSettings as it is no longer needed. - Enhanced useTrailHistory to preserve last-known trails during empty flight responses and added dynamic jump detection for tab resume scenarios. - Improved flight API client with a circuit breaker mechanism to handle provider failures and prevent excessive retries. - Updated flight API parsing to reject non-JSON responses from OpenSky and other providers. - Enhanced trail smoothing and stitching logic to ensure better continuity at junctions between historical and live data. * feat: migrate aircraft models to Cloudinary CDN and update mapping logic * fix: adjust UI component styles and improve trail smoothing parameters * fix: adjust base aircraft size for improved rendering * feat: update changelog with recent enhancements and modify data source attribution * fix: update model optimization details and remove Draco compression dependency * feat: update changelog with recent code review fixes and fallback provider adjustments
228 lines
5.7 KiB
TypeScript
228 lines
5.7 KiB
TypeScript
import type { AtcFeed, AtcFeedType } from "./atc-types";
|
|
import { FEED_TYPE_PRIORITY } from "./atc-types";
|
|
import { ATC_FEEDS, getFeedsByIcao } from "./atc-feeds";
|
|
import { AIRPORTS, type Airport } from "./airports";
|
|
|
|
// ── Constants ──────────────────────────────────────────────────────────
|
|
|
|
/** Approximate nautical miles per degree of latitude. */
|
|
const NM_PER_DEG = 60;
|
|
|
|
/** Maximum search radius in nautical miles for auto-feed discovery. */
|
|
const MAX_SEARCH_RADIUS_NM = 60;
|
|
|
|
// ── IATA → ICAO mapping ───────────────────────────────────────────────
|
|
|
|
/**
|
|
* Common IATA → ICAO mapping for airports in the feed database.
|
|
* Only includes airports that have ATC feeds to keep the map small.
|
|
*/
|
|
const IATA_TO_ICAO: Record<string, string> = {
|
|
// United States
|
|
JFK: "KJFK",
|
|
LAX: "KLAX",
|
|
ORD: "KORD",
|
|
ATL: "KATL",
|
|
DFW: "KDFW",
|
|
DEN: "KDEN",
|
|
SFO: "KSFO",
|
|
LAS: "KLAS",
|
|
MIA: "KMIA",
|
|
EWR: "KEWR",
|
|
SEA: "KSEA",
|
|
BOS: "KBOS",
|
|
MSP: "KMSP",
|
|
PHX: "KPHX",
|
|
DTW: "KDTW",
|
|
FLL: "KFLL",
|
|
IAD: "KIAD",
|
|
CLT: "KCLT",
|
|
DCA: "KDCA",
|
|
HNL: "PHNL",
|
|
ANC: "PANC",
|
|
// Europe
|
|
LHR: "EGLL",
|
|
CDG: "LFPG",
|
|
AMS: "EHAM",
|
|
FRA: "EDDF",
|
|
MAD: "LEMD",
|
|
MUC: "EDDM",
|
|
IST: "LTFM",
|
|
LGW: "EGKK",
|
|
BCN: "LEBL",
|
|
FCO: "LIRF",
|
|
ZRH: "LSZH",
|
|
DUB: "EIDW",
|
|
VIE: "LOWW",
|
|
OSL: "ENGM",
|
|
CPH: "EKCH",
|
|
ARN: "ESSA",
|
|
WAW: "EPWA",
|
|
LIS: "LPPT",
|
|
// Middle East
|
|
DXB: "OMDB",
|
|
DOH: "OTHH",
|
|
JED: "OEJN",
|
|
// Asia Pacific
|
|
HND: "RJTT",
|
|
NRT: "RJAA",
|
|
SIN: "WSSS",
|
|
HKG: "VHHH",
|
|
ICN: "RKSI",
|
|
BKK: "VTBS",
|
|
KUL: "WMKK",
|
|
DEL: "VIDP",
|
|
BOM: "VABB",
|
|
// Australia / Oceania
|
|
SYD: "YSSY",
|
|
MEL: "YMML",
|
|
AKL: "NZAA",
|
|
// Americas (non-US)
|
|
YYZ: "CYYZ",
|
|
YVR: "CYVR",
|
|
MEX: "MMMX",
|
|
GRU: "SBGR",
|
|
EZE: "SAEZ",
|
|
SCL: "SCEL",
|
|
BOG: "SKBO",
|
|
// Africa
|
|
JNB: "FAOR",
|
|
CAI: "HECA",
|
|
};
|
|
|
|
const ICAO_TO_IATA: Record<string, string> = {};
|
|
for (const [iata, icao] of Object.entries(IATA_TO_ICAO)) {
|
|
ICAO_TO_IATA[icao] = iata;
|
|
}
|
|
|
|
/**
|
|
* Convert IATA code to ICAO code for airports in the feed database.
|
|
*/
|
|
export function iataToIcao(iata: string): string | null {
|
|
return IATA_TO_ICAO[iata.toUpperCase()] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Convert ICAO code to IATA code for airports in the feed database.
|
|
*/
|
|
export function icaoToIata(icao: string): string | null {
|
|
return ICAO_TO_IATA[icao.toUpperCase()] ?? null;
|
|
}
|
|
|
|
/** ICAO codes of airports that have ATC feeds. */
|
|
const ICAO_SET = new Set(Object.keys(ATC_FEEDS));
|
|
|
|
/** Precomputed list of airports that have ATC feeds. */
|
|
const ATC_AIRPORTS: Airport[] = AIRPORTS.filter((a) => {
|
|
// Match by converting IATA → ICAO convention for known airports
|
|
// LiveATC uses ICAO codes; our airport DB uses IATA
|
|
const icao = iataToIcao(a.iata);
|
|
return icao !== null && ICAO_SET.has(icao);
|
|
});
|
|
|
|
// ── Lookup Functions ───────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Simple distance approximation in nautical miles (good enough for feed lookup).
|
|
* Uses equirectangular approximation — accurate to ~1% within 60nm.
|
|
*/
|
|
function approxDistanceNm(
|
|
lat1: number,
|
|
lng1: number,
|
|
lat2: number,
|
|
lng2: number,
|
|
): number {
|
|
const dLat = (lat2 - lat1) * NM_PER_DEG;
|
|
const dLng =
|
|
(lng2 - lng1) *
|
|
NM_PER_DEG *
|
|
Math.cos(((lat1 + lat2) / 2) * (Math.PI / 180));
|
|
return Math.sqrt(dLat * dLat + dLng * dLng);
|
|
}
|
|
|
|
export type NearbyAtcResult = {
|
|
/** Airport ICAO code */
|
|
icao: string;
|
|
/** Airport IATA code (if known) */
|
|
iata: string | null;
|
|
/** Airport name */
|
|
name: string;
|
|
/** Distance in nautical miles */
|
|
distanceNm: number;
|
|
/** Available ATC feeds, sorted by type priority */
|
|
feeds: AtcFeed[];
|
|
};
|
|
|
|
/**
|
|
* Find airports with ATC feeds near a given latitude/longitude.
|
|
* Returns results sorted by distance (nearest first).
|
|
*
|
|
* @param lat Latitude in degrees
|
|
* @param lng Longitude in degrees
|
|
* @param radiusNm Search radius in nautical miles (default: 60)
|
|
* @param limit Maximum results to return (default: 5)
|
|
*/
|
|
export function findNearbyAtcFeeds(
|
|
lat: number,
|
|
lng: number,
|
|
radiusNm: number = MAX_SEARCH_RADIUS_NM,
|
|
limit: number = 5,
|
|
): NearbyAtcResult[] {
|
|
const clampedRadius = Math.min(radiusNm, MAX_SEARCH_RADIUS_NM);
|
|
const results: NearbyAtcResult[] = [];
|
|
|
|
for (const airport of ATC_AIRPORTS) {
|
|
const dist = approxDistanceNm(lat, lng, airport.lat, airport.lng);
|
|
if (dist > clampedRadius) continue;
|
|
|
|
const icao = iataToIcao(airport.iata);
|
|
if (!icao) continue;
|
|
|
|
const feeds = getFeedsByIcao(icao).sort(
|
|
(a, b) => FEED_TYPE_PRIORITY[a.type] - FEED_TYPE_PRIORITY[b.type],
|
|
);
|
|
|
|
if (feeds.length === 0) continue;
|
|
|
|
results.push({
|
|
icao,
|
|
iata: airport.iata,
|
|
name: airport.name,
|
|
distanceNm: Math.round(dist * 10) / 10,
|
|
feeds,
|
|
});
|
|
}
|
|
|
|
results.sort((a, b) => a.distanceNm - b.distanceNm);
|
|
return results.slice(0, limit);
|
|
}
|
|
|
|
/**
|
|
* Find the single nearest airport with ATC feeds.
|
|
* Convenience wrapper around findNearbyAtcFeeds.
|
|
*/
|
|
export function findNearestAtcFeed(
|
|
lat: number,
|
|
lng: number,
|
|
): NearbyAtcResult | null {
|
|
const results = findNearbyAtcFeeds(lat, lng, MAX_SEARCH_RADIUS_NM, 1);
|
|
return results[0] ?? null;
|
|
}
|
|
|
|
/**
|
|
* Look up ATC feeds by IATA or ICAO code.
|
|
*/
|
|
export function lookupAtcFeeds(code: string): AtcFeed[] {
|
|
const upper = code.toUpperCase();
|
|
|
|
// Try ICAO first
|
|
const icaoFeeds = getFeedsByIcao(upper);
|
|
if (icaoFeeds.length > 0) return icaoFeeds;
|
|
|
|
// Try IATA → ICAO
|
|
const icao = iataToIcao(upper);
|
|
if (icao) return getFeedsByIcao(icao);
|
|
|
|
return [];
|
|
}
|