leaflet_svelte/src/lib/map/layers.ts
2026-04-22 01:27:38 +09:00

124 lines
3.1 KiB
TypeScript

import type { Prediction, Telemetry } from '$domain';
import { toLngLat } from '$domain';
import type { IMap, Scene } from './core';
/**
* Plot helpers for high-level domain objects. These live outside MapLibreMap
* so they can be reused against any IMap implementation.
*
* Icons are served from /static; pass explicit overrides if a workspace
* should use custom markers.
*/
export interface TrajectoryStyle {
color?: string;
width?: number;
opacity?: number;
launchIcon?: string;
landingIcon?: string;
burstIcon?: string;
iconSize?: [number, number];
}
const DEFAULT_STYLE: Required<Omit<TrajectoryStyle, 'launchIcon' | 'landingIcon' | 'burstIcon'>> &
Pick<TrajectoryStyle, 'launchIcon' | 'landingIcon' | 'burstIcon'> = {
color: '#000000',
width: 3,
opacity: 1,
iconSize: [12, 12],
launchIcon: '/target-blue.png',
landingIcon: '/target-red.png',
burstIcon: '/pop-marker.png',
};
export function plotPrediction(
scene: Scene,
prediction: Prediction,
style: TrajectoryStyle = {},
): void {
const s = { ...DEFAULT_STYLE, ...style };
scene.clear();
scene.addLine('path', {
coords: prediction.flight_path,
color: s.color,
width: s.width,
opacity: s.opacity,
});
scene.addMarker('launch', {
lngLat: toLngLat(prediction.launch.latlng),
iconUrl: s.launchIcon,
iconSize: s.iconSize,
popupHtml: `<b>Launch</b><br>${prediction.launch.latlng.lat.toFixed(6)}, ${prediction.launch.latlng.lng.toFixed(6)}`,
});
scene.addMarker('landing', {
lngLat: toLngLat(prediction.landing.latlng),
iconUrl: s.landingIcon,
iconSize: s.iconSize,
popupHtml: `<b>Landing</b><br>${prediction.landing.latlng.lat.toFixed(6)}, ${prediction.landing.latlng.lng.toFixed(6)}`,
});
scene.addMarker('burst', {
lngLat: toLngLat(prediction.burst.latlng),
iconUrl: s.burstIcon,
iconSize: [s.iconSize[0] + 4, s.iconSize[1] + 4],
popupHtml: `<b>Burst</b><br>${prediction.burst.latlng.lat.toFixed(6)}, ${prediction.burst.latlng.lng.toFixed(6)}`,
});
}
export function plotTelemetry(
map: IMap,
scene: Scene,
telemetry: Telemetry,
style: TrajectoryStyle = {},
): void {
const s = { ...DEFAULT_STYLE, ...style };
scene.clear();
scene.addLine('path', {
coords: telemetry.flight_path,
color: s.color,
width: s.width,
opacity: s.opacity,
});
scene.addMarker('launch', {
lngLat: toLngLat(telemetry.launch.latlng),
iconUrl: s.launchIcon,
iconSize: s.iconSize,
});
telemetry.datapoints.forEach((p, i) => {
scene.addMarker(`point-${i}`, {
lngLat: [p.longitude, p.latitude],
iconUrl: '/marker-sm-red.png',
iconSize: [8, 8],
popupHtml: `<b>${p.datetime}</b><br>${p.latitude.toFixed(6)}, ${p.longitude.toFixed(6)}`,
});
});
if (telemetry.flight_path.length > 0) {
map.fitBounds(telemetry.flight_path, 50);
}
}
export function plotAnimatedMarker(scene: Scene, lng: number, lat: number): void {
scene.clear();
scene.addCircle('marker-ring', {
center: [lng, lat],
radiusPx: 14,
color: '#FF6B6B',
opacity: 0.3,
strokeColor: '#FF1744',
strokeWidth: 0,
});
scene.addCircle('marker-core', {
center: [lng, lat],
radiusPx: 6,
color: '#FF1744',
strokeColor: '#ffffff',
strokeWidth: 2,
});
}