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
This commit is contained in:
kew
2026-03-11 00:54:51 +05:30
committed by GitHub
parent 3a10da0486
commit 147b69b944
49 changed files with 8662 additions and 3927 deletions

View File

@ -1,5 +1,4 @@
import type maplibregl from "maplibre-gl";
import { MercatorCoordinate } from "maplibre-gl";
import maplibregl from "maplibre-gl";
export const FPV_DISTANCE_ZOOM_OFFSET = 1.1;
@ -32,42 +31,76 @@ export function fpvZoomForAltitude(altMeters: number): number {
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 {
type Transform3DLike = {
_pixelMatrix3D?: unknown;
centerPoint?: { x: number; y: number };
coordinatePoint: (
coord: MercatorCoordinate,
elevation: number,
pixelMatrix3D: unknown,
) => { x: number; y: 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?: Transform3DLike }).transform;
if (!tr || typeof tr.coordinatePoint !== "function") return null;
const tr = (map as unknown as { transform?: TransformLike }).transform;
const pixelMatrix3D = tr._pixelMatrix3D;
const centerPoint = tr.centerPoint;
if (!pixelMatrix3D || !centerPoint) return null;
const canvas = map.getCanvas();
const cx = canvas.clientWidth / 2;
const cy = canvas.clientHeight / 2;
let p: { x: number; y: number } | null = null;
try {
p = tr.coordinatePoint(
MercatorCoordinate.fromLngLat({ lng, lat }),
elevationMeters,
pixelMatrix3D,
);
} catch {
return null;
// 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
}
}
if (!p || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return null;
return { dx: p.x - centerPoint.x, dy: p.y - centerPoint.y };
// 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(