Files
aeris/src/components/map/aircraft-appearance.ts
kew eb1103f63f feat: 3D aircraft model overhaul and multi-source flight data proxy (Resolves #15) (#16)
* 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
2026-03-23 01:25:11 +05:30

235 lines
5.9 KiB
TypeScript

// ── Category Styling ───────────────────────────────────────────────────
export const CATEGORY_TINT: Record<number, [number, number, number]> = {
2: [100, 235, 180],
3: [120, 225, 235],
4: [255, 210, 120],
5: [255, 185, 110],
6: [255, 160, 120],
7: [255, 120, 200],
8: [140, 220, 160],
9: [170, 210, 255],
10: [220, 170, 255],
11: [255, 150, 180],
12: [180, 230, 160],
14: [195, 165, 255],
};
export function categorySizeMultiplier(category: number | null): number {
switch (category) {
case 2:
return 0.92;
case 3:
return 0.96;
case 4:
return 1.04;
case 5:
return 1.08;
case 6:
return 1.12;
case 7:
return 1.0;
case 8:
return 0.9;
case 9:
case 12:
return 0.86;
case 10:
return 1.06;
case 14:
return 0.82;
default:
return 1;
}
}
export function tintAircraftColor(
base: [number, number, number, number],
category: number | null,
): [number, number, number, number] {
const tint = category !== null ? CATEGORY_TINT[category] : undefined;
if (!tint) return base;
return [
Math.round(base[0] * 0.58 + tint[0] * 0.42),
Math.round(base[1] * 0.58 + tint[1] * 0.42),
Math.round(base[2] * 0.58 + tint[2] * 0.42),
base[3],
];
}
// ── Selection pulse timing ─────────────────────────────────────────────
export const PULSE_PERIOD_MS = 7000;
export const RING_PERIOD_MS = 5500;
// ── Canvas Atlas Generators ────────────────────────────────────────────
export function createHaloAtlas(): HTMLCanvasElement {
const size = 256;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d")!;
ctx.clearRect(0, 0, size, size);
const c = size / 2;
for (let r = 0; r < c; r++) {
const norm = r / c;
let alpha = 0;
if (norm < 0.18) {
alpha = 0;
} else if (norm < 0.35) {
const t = (norm - 0.18) / 0.17;
alpha = t * t * 0.7;
} else if (norm < 0.55) {
alpha = 0.7 - ((norm - 0.35) / 0.2) * 0.3;
} else {
const t = (norm - 0.55) / 0.45;
alpha = 0.4 * (1 - t) * (1 - t);
}
if (alpha < 0.003) continue;
ctx.strokeStyle = `rgba(255,255,255,${alpha})`;
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(c, c, r, 0, Math.PI * 2);
ctx.stroke();
}
return canvas;
}
export function createSoftRingAtlas(): HTMLCanvasElement {
const size = 256;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d")!;
ctx.clearRect(0, 0, size, size);
const c = size / 2;
const ringCenter = c * 0.75;
const ringWidth = c * 0.18;
for (let r = 0; r < c; r++) {
const dist = Math.abs(r - ringCenter);
const falloff = Math.max(0, 1 - (dist / ringWidth) ** 2);
const alpha = falloff * 0.85;
if (alpha < 0.005) continue;
ctx.strokeStyle = `rgba(255,255,255,${alpha})`;
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.arc(c, c, r, 0, Math.PI * 2);
ctx.stroke();
}
return canvas;
}
export function createAircraftAtlas(): HTMLCanvasElement {
const size = 128;
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d")!;
ctx.clearRect(0, 0, size, size);
ctx.fillStyle = "#ffffff";
ctx.beginPath();
ctx.moveTo(64, 6);
ctx.lineTo(71, 19);
ctx.lineTo(71, 33);
ctx.lineTo(100, 44);
ctx.lineTo(106, 52);
ctx.lineTo(80, 53);
ctx.lineTo(72, 56);
ctx.lineTo(72, 88);
ctx.lineTo(90, 101);
ctx.lineTo(88, 108);
ctx.lineTo(69, 99);
ctx.lineTo(69, 121);
ctx.lineTo(64, 126);
ctx.lineTo(59, 121);
ctx.lineTo(59, 99);
ctx.lineTo(40, 108);
ctx.lineTo(38, 101);
ctx.lineTo(56, 88);
ctx.lineTo(56, 56);
ctx.lineTo(48, 53);
ctx.lineTo(22, 52);
ctx.lineTo(28, 44);
ctx.lineTo(57, 33);
ctx.lineTo(57, 19);
ctx.closePath();
ctx.fill();
ctx.globalCompositeOperation = "destination-out";
ctx.beginPath();
ctx.moveTo(64, 13);
ctx.lineTo(67, 19);
ctx.lineTo(64, 24);
ctx.lineTo(61, 19);
ctx.closePath();
ctx.fill();
ctx.globalCompositeOperation = "source-over";
return canvas;
}
// ── Icon Mappings ──────────────────────────────────────────────────────
export const HALO_MAPPING = {
halo: {
x: 0,
y: 0,
width: 256,
height: 256,
anchorX: 128,
anchorY: 128,
mask: true,
},
};
export const RING_MAPPING = {
ring: {
x: 0,
y: 0,
width: 256,
height: 256,
anchorX: 128,
anchorY: 128,
mask: true,
},
};
export const AIRCRAFT_ICON_MAPPING = {
aircraft: {
x: 0,
y: 0,
width: 128,
height: 128,
anchorX: 64,
anchorY: 64,
mask: true,
},
};
// ── Cached Atlas Data URLs ─────────────────────────────────────────────
let _haloCache: string | undefined;
export function getHaloUrl(): string {
if (typeof document === "undefined") return "";
if (!_haloCache) _haloCache = createHaloAtlas().toDataURL();
return _haloCache;
}
let _ringCache: string | undefined;
export function getRingUrl(): string {
if (typeof document === "undefined") return "";
if (!_ringCache) _ringCache = createSoftRingAtlas().toDataURL();
return _ringCache;
}
let _atlasCache: string | undefined;
export function getAircraftAtlasUrl(): string {
if (typeof document === "undefined") return "";
if (!_atlasCache) _atlasCache = createAircraftAtlas().toDataURL();
return _atlasCache;
}