Files
aeris/src/components/map/camera-controller-utils.ts
kew 147b69b944 feat(map): enhance globe projection handling and improve altitude color representation (#14)
* feat(map): enhance globe projection handling and improve altitude color representation

- Implemented elevation-aware pixel projection for globe mode in `projectLngLatElevationPixelDelta`.
- Refactored north-up animation in `CameraController` to use `setBearing` for smoother transitions.
- Added native GeoJSON support for globe zoom in `FlightLayers`, including dynamic opacity adjustments based on zoom levels.
- Introduced globe-specific pitch and projection settings in `Map` component, ensuring consistent rendering.
- Enhanced UI control panel with a visual separator for better organization.
- Minor formatting adjustments in `altitudeToColor` function for improved readability.

* feat(map): refactor elevation-aware projection handling for improved accuracy

* feat(map): add dark terrain profile support and enhance map styling

* feat: implement trail stitching for merging historical and live flight data

- Added a new module `trail-stitching.ts` to handle the merging of sparse historical track data with high-frequency live trail data.
- Introduced constants for thresholds and parameters to improve code readability and maintainability.
- Implemented a main function `stitchHistoricalTrail` that processes flight tracks, applies smoothing, and merges live tail data.
- Included utility functions for spherical interpolation and cubic easing for altitude transitions.
- Ensured the final path is cleaned of spikes and sharp corners for a smoother representation.

* feat: add centripetal Catmull-Rom spline interpolation for 3D flight trails

- Implemented `catmullRomSpline3D` function to interpolate waypoints into a smooth 3D path.
- Added helper functions for segment density calculation, safe linear interpolation, and endpoint reflection.
- Included support for variable tension based on heading changes to enhance smoothness.
- Introduced utility functions for linear interpolation between elevated points.

* feat(map): enhance layer visibility handling for flight and selection layers

* feat: enhance control panel with new tabs and settings

- Added "Changelog" and "About" tabs to the control panel.
- Introduced new icons for the added tabs using lucide-react.
- Updated the styling of the control panel buttons and dialog.
- Improved accessibility with aria-labels for buttons.

feat: integrate hero banner in flight card

- Added a HeroBanner component to display aircraft photos in the FlightCard.
- Implemented loading and error states for the photo display.
- Enhanced the layout and styling of the FlightCard for better user experience.

fix: update keyboard shortcuts for search functionality

- Added shortcut "⌘K" to open search from anywhere in the application.
- Adjusted keyboard shortcut handling to prevent conflicts with input fields.

fix: optimize flight tracking cache management

- Introduced a maximum cache size for flight tracking to prevent memory growth.
- Implemented a cache eviction strategy for stale entries.

feat: add great-circle utilities for geographical calculations

- Implemented functions for calculating haversine distance, great-circle interpolation, and densifying paths.
- Added functionality to handle antimeridian crossings in geographical paths.

refactor: streamline map styles and terrain handling

- Consolidated terrain DEM source for both terrain mesh and hillshade.
- Adjusted hillshade layer properties for better performance and visual fidelity.

fix: improve bounding box calculations for flight queries

- Enhanced longitude calculations to account for converging meridians at higher latitudes.
- Ensured bounding box calculations are accurate across different latitudes.

* feat(map): refine globe mode functionality and update trail settings
2026-03-11 00:54:51 +05:30

127 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import maplibregl 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));
}
/**
* Project a geographic position at a given elevation to a screenspace
* pixel offset from the map's visual centre.
*
* Uses MapLibre's internal transform.locationToScreenPoint with a synthetic
* terrain provider so the correct projection (Globe, Mercator, or the
* automatic transition between them) handles elevation natively.
*
* There is no public MapLibre API for elevation-aware screen projection
* (map.project() is 2D only). This internal access is tested against
* MapLibre GL JS v5.18.x. A public-API fallback (without elevation) is
* provided for resilience against future internal refactors.
*/
export function projectLngLatElevationPixelDelta(
map: maplibregl.Map,
lng: number,
lat: number,
elevationMeters: number,
): { dx: number; dy: number } | null {
// MapLibre's transform has separate Globe and Mercator implementations of
// locationToScreenPoint(lnglat, terrain). Both support elevation when a
// terrain-like provider is supplied:
// Mercator: coordinatePoint(coord, elevation, _pixelMatrix3D)
// Globe: scales surface point by (1 + elevation/earthRadius), then projects
// By providing a duck-typed provider that returns our altitude, we get
// elevation-aware projection in every mode without touching internals.
type TransformLike = {
locationToScreenPoint: (
lnglat: maplibregl.LngLat,
terrain: unknown,
) => { x: number; y: number };
};
const tr = (map as unknown as { transform?: TransformLike }).transform;
const canvas = map.getCanvas();
const cx = canvas.clientWidth / 2;
const cy = canvas.clientHeight / 2;
// Try elevation-aware internal API first
if (tr && typeof tr.locationToScreenPoint === "function") {
const fakeTerrain = {
getElevationForLngLat: () => elevationMeters,
getElevationForLngLatZoom: () => elevationMeters,
};
try {
const lnglat = new maplibregl.LngLat(lng, lat);
const screenPt = tr.locationToScreenPoint(lnglat, fakeTerrain);
if (Number.isFinite(screenPt.x) && Number.isFinite(screenPt.y)) {
return { dx: screenPt.x - cx, dy: screenPt.y - cy };
}
} catch {
// Point may be behind the globe horizon — fall through to public API
}
}
// Fallback: public map.project() without elevation awareness.
// This gives correct 2D placement but ignores altitude offset.
try {
const projected = map.project(new maplibregl.LngLat(lng, lat));
if (Number.isFinite(projected.x) && Number.isFinite(projected.y)) {
return { dx: projected.x - cx, dy: projected.y - cy };
}
} catch {
// Point may be behind the globe horizon
}
return null;
}
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();
}