* feat: add first person view (FPV) functionality and HUD - Updated FlightCard component to include FPV toggle button and state management. - Introduced FpvHud component for displaying flight data in FPV mode. - Enhanced useFlights hook to support FPV bounding box logic for fetching flights. - Added keyboard shortcuts for toggling FPV mode. - Updated settings to include FPV-related configurations (pitch, chase distance). - Implemented major airports caching for improved performance. - Added fetchFlightByIcao24 function for single aircraft state retrieval. * Refactor CameraController and ControlPanel components; enhance flight search functionality - Simplified CameraController by removing unused refs and effects, and centralized map interaction management. - Updated ControlPanel to support flight lookup with new props and integrated flight search results. - Enhanced SearchContent to include flight matching logic and improved user feedback for flight searches. - Introduced caching for flight callsign lookups in OpenSky API integration to optimize performance. - Removed unnecessary settings related to FPV pitch and free camera mode from use-settings hook. * feat: enhance FPV functionality and improve flight data handling - Added `projectLngLatElevationPixelDelta` function to calculate pixel deltas based on longitude, latitude, and elevation. - Updated `CameraController` to utilize new FPV parameters and improve camera behavior during flight. - Enhanced flight data handling in `FlightLayers` to ensure proper tracking and display of flight information. - Improved UI components for better user experience, including adjustments to the FPV HUD and flight card. - Added error handling for image loading in the control panel. - Refactored altitude and speed calculations to ensure they handle non-finite values gracefully. - Adjusted map attribution behavior for better responsiveness on different screen sizes.
94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
import type maplibregl from "maplibre-gl";
|
|
import { MercatorCoordinate } from "maplibre-gl";
|
|
|
|
export const FPV_DISTANCE_ZOOM_OFFSET = 1.1;
|
|
|
|
export function clamp01(value: number): number {
|
|
return Math.max(0, Math.min(1, value));
|
|
}
|
|
|
|
export function smoothstep(t: number): number {
|
|
return t * t * (3 - 2 * t);
|
|
}
|
|
|
|
export function lerp(from: number, to: number, t: number): number {
|
|
return from + (to - from) * t;
|
|
}
|
|
|
|
export function normalizeLng(lng: number): number {
|
|
return ((lng + 540) % 360) - 180;
|
|
}
|
|
|
|
export function lerpLng(from: number, to: number, t: number): number {
|
|
const delta = ((to - from + 540) % 360) - 180;
|
|
return normalizeLng(from + delta * t);
|
|
}
|
|
|
|
export function fpvZoomForAltitude(altMeters: number): number {
|
|
if (!Number.isFinite(altMeters)) return 12;
|
|
const alt = Math.max(altMeters, 0);
|
|
if (alt < 50) return 16.2;
|
|
const zoom = 18.1 - 2.0 * Math.log10(Math.max(alt, 50));
|
|
return Math.max(10.1, Math.min(16.2, zoom));
|
|
}
|
|
|
|
export function projectLngLatElevationPixelDelta(
|
|
map: maplibregl.Map,
|
|
lng: number,
|
|
lat: number,
|
|
elevationMeters: number,
|
|
): { dx: number; dy: number } | null {
|
|
type Transform3DLike = {
|
|
_pixelMatrix3D?: unknown;
|
|
centerPoint?: { x: number; y: number };
|
|
coordinatePoint: (
|
|
coord: MercatorCoordinate,
|
|
elevation: number,
|
|
pixelMatrix3D: unknown,
|
|
) => { x: number; y: number } | null;
|
|
};
|
|
|
|
const tr = (map as unknown as { transform?: Transform3DLike }).transform;
|
|
if (!tr || typeof tr.coordinatePoint !== "function") return null;
|
|
|
|
const pixelMatrix3D = tr._pixelMatrix3D;
|
|
const centerPoint = tr.centerPoint;
|
|
if (!pixelMatrix3D || !centerPoint) return null;
|
|
|
|
let p: { x: number; y: number } | null = null;
|
|
try {
|
|
p = tr.coordinatePoint(
|
|
MercatorCoordinate.fromLngLat({ lng, lat }),
|
|
elevationMeters,
|
|
pixelMatrix3D,
|
|
);
|
|
} catch {
|
|
return null;
|
|
}
|
|
|
|
if (!p || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return null;
|
|
return { dx: p.x - centerPoint.x, dy: p.y - centerPoint.y };
|
|
}
|
|
|
|
export function setMapInteractionsEnabled(
|
|
map: maplibregl.Map,
|
|
enabled: boolean,
|
|
): void {
|
|
if (enabled) {
|
|
map.dragPan.enable();
|
|
map.dragRotate.enable();
|
|
map.scrollZoom.enable();
|
|
map.touchZoomRotate.enable();
|
|
map.doubleClickZoom.enable();
|
|
map.keyboard.enable();
|
|
return;
|
|
}
|
|
|
|
map.dragPan.disable();
|
|
map.dragRotate.disable();
|
|
map.scrollZoom.disable();
|
|
map.touchZoomRotate.disable();
|
|
map.doubleClickZoom.disable();
|
|
map.keyboard.disable();
|
|
}
|