@zendir/ui 0.1.14 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/react/astro/SimulationControls.js +3 -3
- package/dist/react/astro/SimulationControls.js.map +1 -1
- package/dist/react/astro/UnifiedTimeline.d.ts +70 -8
- package/dist/react/astro/UnifiedTimeline.js +886 -260
- package/dist/react/astro/UnifiedTimeline.js.map +1 -1
- package/dist/react/astro/index.d.ts +2 -1
- package/dist/react/charts/GroundTrackMap.d.ts +40 -1
- package/dist/react/charts/GroundTrackMap.js +98 -47
- package/dist/react/charts/GroundTrackMap.js.map +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.d.ts +11 -2
- package/dist/react/charts/GroundTrackMapLeaflet.js +128 -15
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
- package/dist/react/charts/index.d.ts +1 -1
- package/dist/react/charts/unified/theme.d.ts +7 -7
- package/dist/react/context/CategoryContext.d.ts +51 -0
- package/dist/react/context/CategoryContext.js +36 -0
- package/dist/react/context/CategoryContext.js.map +1 -0
- package/dist/react/context/index.d.ts +2 -0
- package/dist/react/index.d.ts +6 -4
- package/dist/react/types.d.ts +26 -0
- package/dist/react/types.js.map +1 -1
- package/dist/react/utils/categoryPalette.d.ts +43 -0
- package/dist/react/utils/categoryPalette.js +104 -0
- package/dist/react/utils/categoryPalette.js.map +1 -0
- package/dist/react/utils/index.d.ts +1 -0
- package/dist/react/utils/index.js.map +1 -1
- package/dist/react.js +6 -0
- package/dist/react.js.map +1 -1
- package/dist/style.css +49 -0
- package/package.json +1 -1
|
@@ -102,16 +102,17 @@ function createStationDivIcon(statusColor, type, status) {
|
|
|
102
102
|
tooltipAnchor: [0, -14]
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
-
function calculateTerminatorContinuous(date, numPoints = 72) {
|
|
105
|
+
function calculateTerminatorContinuous(date, depressionDeg = 0, numPoints = 72) {
|
|
106
106
|
const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 864e5);
|
|
107
107
|
const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
|
|
108
108
|
const decRad = declination * Math.PI / 180;
|
|
109
109
|
const hourAngle = (date.getUTCHours() + date.getUTCMinutes() / 60) / 24 * 360 - 180;
|
|
110
|
+
const depRad = depressionDeg * Math.PI / 180;
|
|
110
111
|
const points = [];
|
|
111
112
|
let prevLon = null;
|
|
112
113
|
for (let i = 0; i <= numPoints; i++) {
|
|
113
114
|
const latRad = (i / numPoints * 180 - 90) * (Math.PI / 180);
|
|
114
|
-
const cosH = -Math.
|
|
115
|
+
const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
|
|
115
116
|
let lon;
|
|
116
117
|
if (cosH < -1) lon = hourAngle + 180;
|
|
117
118
|
else if (cosH > 1) lon = hourAngle;
|
|
@@ -125,6 +126,14 @@ function calculateTerminatorContinuous(date, numPoints = 72) {
|
|
|
125
126
|
}
|
|
126
127
|
return points;
|
|
127
128
|
}
|
|
129
|
+
function buildBandPolygon(outerSunset, innerSunset) {
|
|
130
|
+
if (!outerSunset.length || !innerSunset.length) return [];
|
|
131
|
+
const outerSunrise = outerSunset.map(([lat, lon]) => [lat, lon - 180]);
|
|
132
|
+
const innerSunrise = innerSunset.map(([lat, lon]) => [lat, lon - 180]);
|
|
133
|
+
const sunsetBand = [...outerSunset, ...[...innerSunset].reverse()];
|
|
134
|
+
const sunriseBand = [...innerSunrise, ...[...outerSunrise].reverse()];
|
|
135
|
+
return [...sunsetBand, ...sunriseBand];
|
|
136
|
+
}
|
|
128
137
|
function buildNightPolygon(sunsetTerminator) {
|
|
129
138
|
if (!sunsetTerminator.length) return [];
|
|
130
139
|
const sunrise = sunsetTerminator.map(([lat, lon]) => [lat, lon - 180]);
|
|
@@ -134,6 +143,7 @@ function GroundTrackMapLeaflet({
|
|
|
134
143
|
allSatellites,
|
|
135
144
|
groundStations,
|
|
136
145
|
showTerminator = true,
|
|
146
|
+
terminatorTime,
|
|
137
147
|
showGrid = false,
|
|
138
148
|
showLegend = true,
|
|
139
149
|
showEquator = false,
|
|
@@ -147,7 +157,12 @@ function GroundTrackMapLeaflet({
|
|
|
147
157
|
tileUrl = DEFAULT_TILE,
|
|
148
158
|
className = "",
|
|
149
159
|
onSatelliteClick,
|
|
150
|
-
onStationClick
|
|
160
|
+
onStationClick,
|
|
161
|
+
pins,
|
|
162
|
+
pinsEditable = false,
|
|
163
|
+
onPinAdd,
|
|
164
|
+
onPinUpdate,
|
|
165
|
+
onPinRemove
|
|
151
166
|
}) {
|
|
152
167
|
const { tokens } = useTheme();
|
|
153
168
|
const containerRef = useRef(null);
|
|
@@ -155,6 +170,7 @@ function GroundTrackMapLeaflet({
|
|
|
155
170
|
const tileLayerRef = useRef(null);
|
|
156
171
|
const overlayGroupRef = useRef(null);
|
|
157
172
|
const controlsRef = useRef([]);
|
|
173
|
+
const pinsGroupRef = useRef(null);
|
|
158
174
|
const [ready, setReady] = useState(false);
|
|
159
175
|
const clearLayers = useCallback(() => {
|
|
160
176
|
var _a;
|
|
@@ -224,6 +240,9 @@ function GroundTrackMapLeaflet({
|
|
|
224
240
|
const overlayGroup = L.layerGroup();
|
|
225
241
|
overlayGroup.addTo(map);
|
|
226
242
|
overlayGroupRef.current = overlayGroup;
|
|
243
|
+
const pinsGroup = L.layerGroup();
|
|
244
|
+
pinsGroup.addTo(map);
|
|
245
|
+
pinsGroupRef.current = pinsGroup;
|
|
227
246
|
mapRef.current = map;
|
|
228
247
|
setReady(true);
|
|
229
248
|
const onSize = () => {
|
|
@@ -233,10 +252,13 @@ function GroundTrackMapLeaflet({
|
|
|
233
252
|
const t1 = setTimeout(onSize, 150);
|
|
234
253
|
const t2 = setTimeout(onSize, 500);
|
|
235
254
|
return () => {
|
|
255
|
+
var _a;
|
|
236
256
|
clearTimeout(t1);
|
|
237
257
|
clearTimeout(t2);
|
|
238
258
|
cancelAnimationFrame(raf);
|
|
239
259
|
clearLayers();
|
|
260
|
+
(_a = pinsGroupRef.current) == null ? void 0 : _a.clearLayers();
|
|
261
|
+
pinsGroupRef.current = null;
|
|
240
262
|
tileLayerRef.current = null;
|
|
241
263
|
map.remove();
|
|
242
264
|
mapRef.current = null;
|
|
@@ -248,26 +270,41 @@ function GroundTrackMapLeaflet({
|
|
|
248
270
|
const map = mapRef.current;
|
|
249
271
|
clearLayers();
|
|
250
272
|
if (showTerminator) {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
|
|
273
|
+
const now = terminatorTime ?? /* @__PURE__ */ new Date();
|
|
274
|
+
const geometric = calculateTerminatorContinuous(now, 0);
|
|
275
|
+
const civil = calculateTerminatorContinuous(now, 6);
|
|
276
|
+
const nautical = calculateTerminatorContinuous(now, 12);
|
|
277
|
+
const astro = calculateTerminatorContinuous(now, 18);
|
|
278
|
+
const twilightBands = [
|
|
279
|
+
// Civil twilight zone (geometric → 6°): faintest tint
|
|
280
|
+
{ poly: buildBandPolygon(civil, geometric), opacity: 0.08 },
|
|
281
|
+
// Nautical twilight zone (6° → 12°): moderate
|
|
282
|
+
{ poly: buildBandPolygon(nautical, civil), opacity: 0.1 },
|
|
283
|
+
// Astronomical twilight zone (12° → 18°): darker
|
|
284
|
+
{ poly: buildBandPolygon(astro, nautical), opacity: 0.12 },
|
|
285
|
+
// Full night (inside 18° boundary): deepest
|
|
286
|
+
{ poly: buildNightPolygon(astro), opacity: 0.22 }
|
|
287
|
+
];
|
|
288
|
+
twilightBands.forEach(({ poly, opacity }) => {
|
|
289
|
+
if (poly.length < 3) return;
|
|
254
290
|
[0, 360, -360].forEach((offset) => {
|
|
255
|
-
const shifted =
|
|
291
|
+
const shifted = poly.map(([lat, lon]) => [lat, lon + offset]);
|
|
256
292
|
addLayer(L.polygon(shifted, {
|
|
257
293
|
color: "transparent",
|
|
258
|
-
fillColor: "#
|
|
259
|
-
fillOpacity:
|
|
294
|
+
fillColor: "#000a28",
|
|
295
|
+
fillOpacity: opacity,
|
|
260
296
|
interactive: false
|
|
261
297
|
}));
|
|
262
298
|
});
|
|
263
|
-
}
|
|
264
|
-
const
|
|
265
|
-
[
|
|
299
|
+
});
|
|
300
|
+
const sunriseGeometric = geometric.map(([lat, lon]) => [lat, lon - 180]);
|
|
301
|
+
[geometric, sunriseGeometric].forEach((line) => {
|
|
266
302
|
[0, 360, -360].forEach((offset) => {
|
|
267
303
|
const shifted = line.map(([lat, lon]) => [lat, lon + offset]);
|
|
268
304
|
addLayer(L.polyline(shifted, {
|
|
269
|
-
color: "rgba(88, 166, 255, 0.
|
|
305
|
+
color: "rgba(88, 166, 255, 0.18)",
|
|
270
306
|
weight: 1,
|
|
307
|
+
dashArray: "4 6",
|
|
271
308
|
interactive: false
|
|
272
309
|
}));
|
|
273
310
|
});
|
|
@@ -466,6 +503,7 @@ function GroundTrackMapLeaflet({
|
|
|
466
503
|
allSatellites,
|
|
467
504
|
groundStations,
|
|
468
505
|
showTerminator,
|
|
506
|
+
terminatorTime,
|
|
469
507
|
showGrid,
|
|
470
508
|
showEquator,
|
|
471
509
|
showLegend,
|
|
@@ -475,6 +513,81 @@ function GroundTrackMapLeaflet({
|
|
|
475
513
|
onSatelliteClick,
|
|
476
514
|
onStationClick
|
|
477
515
|
]);
|
|
516
|
+
useEffect(() => {
|
|
517
|
+
if (!ready || !mapRef.current) return;
|
|
518
|
+
const group = pinsGroupRef.current;
|
|
519
|
+
if (!group) return;
|
|
520
|
+
group.clearLayers();
|
|
521
|
+
if (!pins || pins.length === 0) return;
|
|
522
|
+
const accentColor = tokens.colors.accent.primary;
|
|
523
|
+
pins.forEach((pin) => {
|
|
524
|
+
const pinColor = pin.color || accentColor;
|
|
525
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="36" viewBox="0 0 24 36">
|
|
526
|
+
<defs>
|
|
527
|
+
<filter id="pin-shadow-${pin.id}" x="-30%" y="-10%" width="160%" height="140%">
|
|
528
|
+
<feDropShadow dx="0" dy="2" stdDeviation="2" flood-color="#000" flood-opacity="0.35"/>
|
|
529
|
+
</filter>
|
|
530
|
+
</defs>
|
|
531
|
+
<g filter="url(#pin-shadow-${pin.id})">
|
|
532
|
+
<path d="M12 2 C6.5 2 2 6.5 2 12 C2 19 12 34 12 34 C12 34 22 19 22 12 C22 6.5 17.5 2 12 2Z"
|
|
533
|
+
fill="${pinColor}" stroke="rgba(255,255,255,0.5)" stroke-width="1"/>
|
|
534
|
+
<circle cx="12" cy="12" r="4" fill="#0d1323" stroke="rgba(255,255,255,0.7)" stroke-width="0.8"/>
|
|
535
|
+
</g>
|
|
536
|
+
</svg>`;
|
|
537
|
+
const icon = L.divIcon({
|
|
538
|
+
html: svg,
|
|
539
|
+
className: "zendir-pin-icon",
|
|
540
|
+
iconSize: [24, 36],
|
|
541
|
+
iconAnchor: [12, 36],
|
|
542
|
+
tooltipAnchor: [0, -36]
|
|
543
|
+
});
|
|
544
|
+
const marker = L.marker([pin.latitude, pin.longitude], {
|
|
545
|
+
icon,
|
|
546
|
+
draggable: pinsEditable,
|
|
547
|
+
title: pin.label ?? ""
|
|
548
|
+
});
|
|
549
|
+
const tooltipLines = [
|
|
550
|
+
pin.label ? `<strong>${pin.label}</strong>` : "",
|
|
551
|
+
pin.description ? `<div style="opacity:0.7;font-size:11px">${pin.description}</div>` : "",
|
|
552
|
+
`<div style="font-size:10px;opacity:0.5;font-family:monospace">${pin.latitude.toFixed(4)}°, ${pin.longitude.toFixed(4)}°</div>`,
|
|
553
|
+
pin.createdBy ? `<div style="font-size:10px;opacity:0.4;margin-top:2px">by ${pin.createdBy}</div>` : ""
|
|
554
|
+
].filter(Boolean).join("");
|
|
555
|
+
marker.bindTooltip(tooltipLines, {
|
|
556
|
+
className: "zendir-pin-tooltip",
|
|
557
|
+
direction: "top",
|
|
558
|
+
offset: [0, -4]
|
|
559
|
+
});
|
|
560
|
+
if (pinsEditable) {
|
|
561
|
+
marker.on("dragend", () => {
|
|
562
|
+
const latlng = marker.getLatLng();
|
|
563
|
+
onPinUpdate == null ? void 0 : onPinUpdate({ ...pin, latitude: latlng.lat, longitude: latlng.lng });
|
|
564
|
+
});
|
|
565
|
+
marker.on("contextmenu", (e) => {
|
|
566
|
+
L.DomEvent.stopPropagation(e);
|
|
567
|
+
onPinRemove == null ? void 0 : onPinRemove(pin.id);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
group.addLayer(marker);
|
|
571
|
+
});
|
|
572
|
+
}, [ready, pins, pinsEditable, onPinUpdate, onPinRemove, tokens.colors.accent.primary]);
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
if (!ready || !mapRef.current || !pinsEditable || !onPinAdd) return;
|
|
575
|
+
const map = mapRef.current;
|
|
576
|
+
const handleClick = (e) => {
|
|
577
|
+
var _a, _b, _c;
|
|
578
|
+
if ((_c = (_b = (_a = e.originalEvent) == null ? void 0 : _a.target) == null ? void 0 : _b.closest) == null ? void 0 : _c.call(_b, ".leaflet-marker-icon")) return;
|
|
579
|
+
onPinAdd({
|
|
580
|
+
latitude: e.latlng.lat,
|
|
581
|
+
longitude: e.latlng.lng,
|
|
582
|
+
label: "",
|
|
583
|
+
color: tokens.colors.accent.primary
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
map.on("click", handleClick);
|
|
587
|
+
return () => {
|
|
588
|
+
map.off("click", handleClick);
|
|
589
|
+
};
|
|
590
|
+
}, [ready, pinsEditable, onPinAdd, tokens.colors.accent.primary]);
|
|
478
591
|
const handleRecenter = useCallback(() => {
|
|
479
592
|
const map = mapRef.current;
|
|
480
593
|
if (!map) return;
|
|
@@ -491,11 +604,11 @@ function GroundTrackMapLeaflet({
|
|
|
491
604
|
}
|
|
492
605
|
}, [allSatellites, groundStations, defaultCenter, defaultZoom]);
|
|
493
606
|
const resolvedMinHeight = minHeight || (typeof height === "number" ? `${height}px` : "400px");
|
|
494
|
-
const isEmpty = allSatellites.length === 0 && groundStations.length === 0;
|
|
607
|
+
const isEmpty = allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0);
|
|
495
608
|
return /* @__PURE__ */ jsxs(
|
|
496
609
|
"div",
|
|
497
610
|
{
|
|
498
|
-
className: `zendir-ground-track-map zendir-ground-track-map-leaflet ${className}`,
|
|
611
|
+
className: `zendir-ground-track-map zendir-ground-track-map-leaflet${pinsEditable ? " zendir-pins-editable" : ""} ${className}`,
|
|
499
612
|
"data-map-backend": "leaflet",
|
|
500
613
|
style: {
|
|
501
614
|
width,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GroundTrackMapLeaflet.js","sources":["../../../src/react/charts/GroundTrackMapLeaflet.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - GroundTrackMap Leaflet Provider\r\n *\r\n * Leaflet-based 2D world map for ground tracks, stations, and terminator.\r\n * Uses Zendir hybrid theme; all interactions enabled (zoom, scroll, drag, etc.).\r\n * Import leaflet-zendir.css when using this component.\r\n */\r\n\r\nimport React, { useRef, useEffect, useCallback, useMemo, useState } from 'react';\r\nimport { useTheme } from '../theme';\r\nimport type { GroundStation } from '../types';\r\nimport type { SatelliteTrack, GroundStationEnhanced, SROGroundStation } from './GroundTrackMap';\r\n\r\n// Optional Leaflet — consumer must install leaflet when using mapProvider=\"leaflet\"\r\nimport L from 'leaflet';\r\nimport 'leaflet/dist/leaflet.css';\r\nimport './leaflet-zendir.css';\r\nimport {\r\n splitPolylineAtAntimeridian,\r\n segmentsWithWorldCopies,\r\n geodesicCirclePoints,\r\n splitRingAtAntimeridian,\r\n} from './groundTrackMapLeafletUtils';\r\nimport {\r\n DEFAULT_TILE,\r\n FALLBACK_TILE,\r\n TILE_ERROR_THRESHOLD,\r\n CARTO_ATTRIBUTION,\r\n OSM_ATTRIBUTION,\r\n} from './groundTrackMapLeafletTiles';\r\n\r\nconst STATUS_COLOR_MAP: Record<string, string> = {\r\n normal: '#56f000',\r\n standby: '#2dccff',\r\n caution: '#fce83a',\r\n serious: '#ffb302',\r\n critical: '#ff3838',\r\n off: '#a4abb6',\r\n};\r\n\r\nconst DEFAULT_TRACK_COLORS = [\r\n '#2dccff', '#3E3CFF', '#9D70FF', '#56f000', '#fce83a', '#ff7849', '#2dd4bf', '#ff3838',\r\n];\r\n\r\n// =============================================================================\r\n// Icon helpers — produce L.DivIcon with inline SVG (no external assets needed)\r\n// =============================================================================\r\n\r\n/** Satellite spacecraft icon with solar panels, matching Astro UXDS style. */\r\nfunction createSatDivIcon(statusColor: string, trackColor: string): L.DivIcon {\r\n // Status badge shape (Astro UXDS spec)\r\n const badge = statusColor === '#56f000'\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"3\" fill=\"${statusColor}\"/>` // normal: filled dot\r\n : statusColor === '#2dccff'\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"2.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\"/>` // standby: ring\r\n : statusColor === '#fce83a'\r\n ? `<rect x=\"4.5\" y=\"4.5\" width=\"5\" height=\"5\" fill=\"${statusColor}\"/>` // caution: square\r\n : statusColor === '#ffb302'\r\n ? `<polygon points=\"7,3.5 10.5,7 7,10.5 3.5,7\" fill=\"${statusColor}\"/>` // serious: diamond (Astro UXDS)\r\n : statusColor === '#ff3838'\r\n ? `<polygon points=\"7,10 4,4 10,4\" fill=\"${statusColor}\"/>` // critical: triangle down\r\n : `<circle cx=\"7\" cy=\"7\" r=\"2\" fill=\"${statusColor}\"/>`; // off: small dot\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"34\" height=\"34\" viewBox=\"0 0 34 34\">\r\n <!-- Glow ring -->\r\n <circle cx=\"17\" cy=\"17\" r=\"15\" fill=\"${statusColor}\" fill-opacity=\"0.12\"/>\r\n <!-- Main body circle -->\r\n <circle cx=\"17\" cy=\"17\" r=\"11\" fill=\"#0d1323\" stroke=\"${trackColor}\" stroke-width=\"1.5\"/>\r\n <!-- Left solar panel -->\r\n <rect x=\"2\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"5.5\" y1=\"15\" x2=\"5.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Connector left -->\r\n <line x1=\"11\" y1=\"17\" x2=\"13\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Satellite bus body -->\r\n <rect x=\"13\" y=\"14.5\" width=\"8\" height=\"5\" rx=\"1\" fill=\"${statusColor}\"/>\r\n <!-- Connector right -->\r\n <line x1=\"21\" y1=\"17\" x2=\"23\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Right solar panel -->\r\n <rect x=\"23\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"26.5\" y1=\"15\" x2=\"26.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"7\" cy=\"7\" r=\"5.5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: 'zendir-sat-icon',\r\n iconSize: [34, 34] as unknown as L.PointExpression,\r\n iconAnchor: [17, 17] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -18] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/** Ground station icon — dish/antenna shape, matching Astro UXDS style. */\r\nfunction createStationDivIcon(statusColor: string, type: string, status: string): L.DivIcon {\r\n // Status badge\r\n const badge = status === 'normal'\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2.5\" fill=\"${statusColor}\"/>`\r\n : status === 'standby'\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`\r\n : status === 'caution'\r\n ? `<rect x=\"3\" y=\"3\" width=\"4\" height=\"4\" fill=\"${statusColor}\"/>`\r\n : status === 'serious'\r\n ? `<polygon points=\"5,2.5 7.5,5 5,7.5 2.5,5\" fill=\"${statusColor}\"/>`\r\n : status === 'critical'\r\n ? `<polygon points=\"5,7.5 2.5,2.5 7.5,2.5\" fill=\"${statusColor}\"/>`\r\n : `<circle cx=\"5\" cy=\"5\" r=\"1.5\" fill=\"${statusColor}\"/>`;\r\n\r\n let iconInner = '';\r\n if (type === 'phased-array') {\r\n // Grid pattern (phased array)\r\n iconInner = `\r\n <rect x=\"9\" y=\"7\" width=\"10\" height=\"8\" rx=\"0.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"15\" y1=\"7\" x2=\"15\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"10\" x2=\"19\" y2=\"10\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"13\" x2=\"19\" y2=\"13\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"20\" x2=\"17\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n } else if (type === 'relay') {\r\n // Relay dish with signal arcs\r\n iconInner = `\r\n <path d=\"M 8 16 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 8 16 m 2 -2 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <line x1=\"14\" y1=\"16\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <path d=\"M 16 8 a 3 3 0 0 1 0 4\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <path d=\"M 17.5 6.5 a 5 5 0 0 1 0 7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1\" stroke-linecap=\"round\"/>`;\r\n } else {\r\n // Default: dish antenna with concentric arcs (Astro UXDS antenna pattern)\r\n iconInner = `\r\n <path d=\"M 14 14 m -7 0 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -5 0 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -3 0 a 3 3 0 0 1 3 -3\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <circle cx=\"14\" cy=\"14\" r=\"1.5\" fill=\"${statusColor}\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n }\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 28 28\">\r\n <!-- Glow ring -->\r\n <circle cx=\"14\" cy=\"14\" r=\"13\" fill=\"${statusColor}\" fill-opacity=\"0.1\"/>\r\n <!-- Background -->\r\n <circle cx=\"14\" cy=\"14\" r=\"11\" fill=\"#0d1323\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-opacity=\"0.7\"/>\r\n ${iconInner}\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"5\" cy=\"5\" r=\"5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: 'zendir-station-icon',\r\n iconSize: [28, 28] as unknown as L.PointExpression,\r\n iconAnchor: [14, 14] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -14] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/**\r\n * Compute the sunset terminator with CONTINUOUS (unwrapped) longitudes.\r\n * Keeping lons continuous avoids antimeridian discontinuities that break\r\n * polygon construction in Leaflet. Each world-copy offset shifts the\r\n * entire polygon, so no single polygon ever straddles ±180.\r\n */\r\nfunction calculateTerminatorContinuous(date: Date, numPoints: number = 72): Array<[number, number]> {\r\n const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000);\r\n const declination = -23.44 * Math.cos((2 * Math.PI / 365) * (dayOfYear + 10));\r\n const decRad = (declination * Math.PI) / 180;\r\n const hourAngle = ((date.getUTCHours() + date.getUTCMinutes() / 60) / 24) * 360 - 180;\r\n\r\n const points: Array<[number, number]> = [];\r\n let prevLon: number | null = null;\r\n for (let i = 0; i <= numPoints; i++) {\r\n const latRad = ((i / numPoints) * 180 - 90) * (Math.PI / 180);\r\n const cosH = -Math.tan(latRad) * Math.tan(decRad);\r\n let lon: number;\r\n if (cosH < -1) lon = hourAngle + 180;\r\n else if (cosH > 1) lon = hourAngle;\r\n else lon = hourAngle + (Math.acos(cosH) * 180) / Math.PI;\r\n\r\n if (prevLon != null) {\r\n while (lon - prevLon > 180) lon -= 360;\r\n while (lon - prevLon < -180) lon += 360;\r\n }\r\n prevLon = lon;\r\n points.push([(i / numPoints) * 180 - 90, lon]);\r\n }\r\n return points;\r\n}\r\n\r\n/**\r\n * Build a single night-side polygon from the continuous sunset terminator.\r\n *\r\n * Night = area between the sunset terminator (going east) and the sunrise\r\n * terminator (sunset − 180°). The polygon traces:\r\n * sunset (lat −90 → +90) → sunrise reversed (lat +90 → −90)\r\n *\r\n * Because longitudes are continuous (unwrapped), the polygon never crosses\r\n * the antimeridian internally — no splitting needed.\r\n */\r\nfunction buildNightPolygon(sunsetTerminator: Array<[number, number]>): [number, number][] {\r\n if (!sunsetTerminator.length) return [];\r\n const sunrise = sunsetTerminator.map(([lat, lon]) => [lat, lon - 180] as [number, number]);\r\n return [...sunsetTerminator, ...[...sunrise].reverse()];\r\n}\r\n\r\nexport type GroundStationMapItem = GroundStation | GroundStationEnhanced | SROGroundStation;\r\n\r\nexport interface GroundTrackMapLeafletProps {\r\n allSatellites: SatelliteTrack[];\r\n groundStations: GroundStationMapItem[];\r\n showTerminator?: boolean;\r\n showGrid?: boolean;\r\n showLegend?: boolean;\r\n showEquator?: boolean;\r\n showRecenterButton?: boolean;\r\n defaultCenter?: [number, number];\r\n defaultZoom?: number;\r\n height?: number | string;\r\n width?: number | string;\r\n minHeight?: string;\r\n emptyMessage?: string;\r\n tileUrl?: string;\r\n className?: string;\r\n onSatelliteClick?: (id: string) => void;\r\n onStationClick?: (id: string) => void;\r\n}\r\n\r\nexport function GroundTrackMapLeaflet({\r\n allSatellites,\r\n groundStations,\r\n showTerminator = true,\r\n showGrid = false,\r\n showLegend = true,\r\n showEquator = false,\r\n showRecenterButton = true,\r\n defaultCenter = [20, 0],\r\n defaultZoom = 2,\r\n height = '100%',\r\n width = '100%',\r\n minHeight = '400px',\r\n emptyMessage = 'No orbital data available',\r\n tileUrl = DEFAULT_TILE,\r\n className = '',\r\n onSatelliteClick,\r\n onStationClick,\r\n}: GroundTrackMapLeafletProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const mapRef = useRef<L.Map | null>(null);\r\n const tileLayerRef = useRef<L.TileLayer | null>(null);\r\n const overlayGroupRef = useRef<L.LayerGroup | null>(null);\r\n const controlsRef = useRef<L.Control[]>([]);\r\n const [ready, setReady] = useState(false);\r\n\r\n const clearLayers = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n overlayGroupRef.current?.clearLayers();\r\n controlsRef.current.forEach((ctrl) => map.removeControl(ctrl));\r\n controlsRef.current = [];\r\n }, []);\r\n\r\n const addLayer = useCallback((layer: L.Layer) => {\r\n overlayGroupRef.current?.addLayer(layer);\r\n }, []);\r\n\r\n // Create map once on mount (or when tileUrl changes). Intentionally not depending on\r\n // defaultCenter/defaultZoom so animated updates do not recreate the map (no flicker/zoom reset).\r\n useEffect(() => {\r\n // eslint-disable-next-line react-hooks/exhaustive-deps -- defaultCenter/defaultZoom used only for initial view\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const center: L.LatLngExpression = defaultCenter ?? [20, 0];\r\n const zoom = defaultZoom ?? 2;\r\n\r\n const map = L.map(el, {\r\n center,\r\n zoom,\r\n zoomControl: false,\r\n scrollWheelZoom: true,\r\n doubleClickZoom: true,\r\n touchZoom: true,\r\n boxZoom: true,\r\n keyboard: true,\r\n dragging: true,\r\n attributionControl: true,\r\n maxBounds: [[-90, -540], [90, 540]],\r\n maxBoundsViscosity: 1.0,\r\n });\r\n\r\n L.control.zoom({ position: 'topright' }).addTo(map);\r\n\r\n map.attributionControl.setPrefix('');\r\n\r\n const isCartoTiles = tileUrl.includes('cartocdn');\r\n if (isCartoTiles) {\r\n map.attributionControl.addAttribution(CARTO_ATTRIBUTION);\r\n }\r\n\r\n const tileOptions: L.TileLayerOptions = {\r\n maxZoom: 19,\r\n subdomains: isCartoTiles ? 'abcd' : 'abc',\r\n // crossOrigin avoids tainted-canvas errors when Leaflet tries to read tile pixels\r\n crossOrigin: true,\r\n };\r\n\r\n const tile = L.tileLayer(tileUrl, tileOptions);\r\n tile.addTo(map);\r\n tileLayerRef.current = tile;\r\n\r\n // When primary tiles fail (network, CORS, rate limit), switch to fallback immediately.\r\n // TILE_ERROR_THRESHOLD = 1 so the first error triggers the switch — no visible black period.\r\n let hasSwitchedToFallback = false;\r\n const onTileError = () => {\r\n if (hasSwitchedToFallback) return;\r\n hasSwitchedToFallback = true;\r\n tile.off('tileerror', onTileError);\r\n tile.remove();\r\n if (isCartoTiles) {\r\n map.attributionControl.removeAttribution(CARTO_ATTRIBUTION);\r\n map.attributionControl.addAttribution(OSM_ATTRIBUTION);\r\n }\r\n const fallback = L.tileLayer(FALLBACK_TILE, {\r\n maxZoom: 19,\r\n subdomains: 'abc',\r\n crossOrigin: true,\r\n });\r\n fallback.addTo(map);\r\n tileLayerRef.current = fallback;\r\n };\r\n tile.on('tileerror', onTileError);\r\n\r\n const overlayGroup = L.layerGroup();\r\n overlayGroup.addTo(map);\r\n overlayGroupRef.current = overlayGroup;\r\n\r\n mapRef.current = map;\r\n setReady(true);\r\n\r\n const onSize = () => {\r\n map.invalidateSize({ animate: false });\r\n };\r\n // Immediate RAF + two deferred calls cover: first paint, CSS transitions, flex layout settle\r\n const raf = requestAnimationFrame(onSize);\r\n const t1 = setTimeout(onSize, 150);\r\n const t2 = setTimeout(onSize, 500);\r\n\r\n return () => {\r\n clearTimeout(t1);\r\n clearTimeout(t2);\r\n cancelAnimationFrame(raf);\r\n clearLayers();\r\n tileLayerRef.current = null;\r\n map.remove();\r\n mapRef.current = null;\r\n overlayGroupRef.current = null;\r\n };\r\n }, [tileUrl, clearLayers]);\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const map = mapRef.current;\r\n clearLayers();\r\n\r\n if (showTerminator) {\r\n const sunsetTerminator = calculateTerminatorContinuous(new Date());\r\n const nightPoly = buildNightPolygon(sunsetTerminator);\r\n\r\n if (nightPoly.length > 2) {\r\n [0, 360, -360].forEach((offset) => {\r\n const shifted = nightPoly.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polygon(shifted, {\r\n color: 'transparent',\r\n fillColor: '#00060f',\r\n fillOpacity: 0.35,\r\n interactive: false,\r\n }));\r\n });\r\n }\r\n\r\n // Terminator boundary lines (sunset + sunrise, subtle)\r\n const sunriseTerminator = sunsetTerminator.map(([lat, lon]) => [lat, lon - 180] as [number, number]);\r\n [sunsetTerminator, sunriseTerminator].forEach((line) => {\r\n [0, 360, -360].forEach((offset) => {\r\n const shifted = line.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polyline(shifted, {\r\n color: 'rgba(88, 166, 255, 0.22)',\r\n weight: 1,\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n }\r\n\r\n if (showGrid) {\r\n const gridStyle = { color: 'rgba(157, 112, 255, 0.12)', weight: 0.5, interactive: false };\r\n [-360, 0, 360].forEach((offset) => {\r\n for (let lon = -180; lon <= 180; lon += 30) {\r\n const l = lon + offset;\r\n addLayer(L.polyline([[90, l], [-90, l]], gridStyle));\r\n }\r\n for (let lat = -90; lat <= 90; lat += 30) {\r\n addLayer(L.polyline([[lat, -180 + offset], [lat, 180 + offset]], gridStyle));\r\n }\r\n });\r\n }\r\n\r\n if (showEquator) {\r\n [-360, 0, 360].forEach((offset) => {\r\n addLayer(L.polyline([[0, -180 + offset], [0, 180 + offset]], {\r\n color: 'rgba(88, 166, 255, 0.25)',\r\n weight: 1,\r\n dashArray: '6, 4',\r\n interactive: false,\r\n }));\r\n });\r\n }\r\n\r\n groundStations.forEach((gs) => {\r\n const status = ('status' in gs ? gs.status : undefined) ?? 'standby';\r\n const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;\r\n const stationType = ('type' in gs ? (gs as { type?: string }).type : undefined) ?? 'dish';\r\n const radius = 'coverageRadius' in gs ? gs.coverageRadius : undefined;\r\n const showCoverage = 'showCoverage' in gs ? gs.showCoverage : false;\r\n if (radius != null && radius > 0 && showCoverage) {\r\n const ring = geodesicCirclePoints(gs.latitude, gs.longitude, radius, 64);\r\n const rings = splitRingAtAntimeridian(ring);\r\n [0, 360, -360].forEach((offset) => {\r\n rings.forEach((r) => {\r\n const shifted = r.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polygon(shifted, {\r\n color: `${statusColor}55`,\r\n fillColor: statusColor,\r\n fillOpacity: 0.1,\r\n weight: 1,\r\n dashArray: '4, 3',\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n }\r\n\r\n const gsIcon = createStationDivIcon(statusColor, stationType, status);\r\n const marker = L.marker([gs.latitude, gs.longitude], { icon: gsIcon });\r\n addLayer(marker);\r\n const statusLabel = status !== 'standby' ? ` · ${status}` : '';\r\n marker.bindTooltip(\r\n `<strong>${gs.name}</strong>${statusLabel}${'network' in gs && gs.network ? `<br/><span style=\"opacity:0.7\">${gs.network}</span>` : ''}`,\r\n { permanent: false, direction: 'top', className: 'zendir-leaflet-tooltip' }\r\n );\r\n marker.on('click', () => {\r\n onStationClick?.('id' in gs ? String(gs.id) : gs.name);\r\n });\r\n });\r\n\r\n allSatellites.forEach((sat, satIdx) => {\r\n const track = sat.groundTrack;\r\n if (!track || track.length === 0) return;\r\n\r\n const color = sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];\r\n const futureIdx = sat.futureTrackIndex ?? track.length;\r\n\r\n const pastLatLngs = track\r\n .slice(0, futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(pastLatLngs)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color, weight: 2.5, opacity: 1 }));\r\n }\r\n });\r\n\r\n if (futureIdx < track.length) {\r\n const futureLatLngs = track\r\n .slice(futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(futureLatLngs)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color, weight: 1.5, opacity: 0.5, dashArray: '6, 4' }));\r\n }\r\n });\r\n }\r\n\r\n // Access mask segments (contact passes) — solid green overlay on track\r\n if (sat.accessMask && sat.accessMask.some(Boolean)) {\r\n let segment: [number, number][] = [];\r\n for (let i = 0; i < track.length; i++) {\r\n if (sat.accessMask[i]) {\r\n segment.push([track[i].latitude, track[i].longitude]);\r\n } else if (segment.length >= 2) {\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color: STATUS_COLOR_MAP.normal, weight: 4, opacity: 0.9 }));\r\n }\r\n });\r\n segment = [];\r\n } else {\r\n segment = [];\r\n }\r\n }\r\n if (segment.length >= 2) {\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color: STATUS_COLOR_MAP.normal, weight: 4, opacity: 0.9 }));\r\n }\r\n });\r\n }\r\n }\r\n\r\n if (sat.passMarkers && sat.passMarkers.length > 0) {\r\n sat.passMarkers.forEach((pm) => {\r\n const isAos = pm.type === 'aos';\r\n const pmColor = isAos ? '#56f000' : '#ff3838';\r\n const pmMarker = L.circleMarker([pm.latitude, pm.longitude], {\r\n radius: 5,\r\n fillColor: pmColor,\r\n color: `${pmColor}cc`,\r\n weight: 1.5,\r\n fillOpacity: 0.9,\r\n });\r\n addLayer(pmMarker);\r\n pmMarker.bindTooltip(`${pm.type.toUpperCase()}${pm.label ? ` – ${pm.label}` : ''}`, {\r\n permanent: false,\r\n direction: 'top',\r\n className: 'zendir-leaflet-tooltip',\r\n });\r\n });\r\n }\r\n\r\n if (sat.showFootprint && sat.footprintRadius) {\r\n const lastIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;\r\n const last = track[lastIdx];\r\n const ring = geodesicCirclePoints(last.latitude, last.longitude, sat.footprintRadius, 64);\r\n const rings = splitRingAtAntimeridian(ring);\r\n [0, 360, -360].forEach((offset) => {\r\n rings.forEach((r) => {\r\n const shifted = r.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polygon(shifted, {\r\n color: `${color}70`,\r\n fillColor: color,\r\n fillOpacity: 0.1,\r\n weight: 1,\r\n dashArray: '3, 3',\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n }\r\n\r\n const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;\r\n const current = track[currentIdx];\r\n const statusColor = STATUS_COLOR_MAP[sat.status || 'normal'] || STATUS_COLOR_MAP.normal;\r\n\r\n const satIcon = createSatDivIcon(statusColor, color);\r\n const satMarker = L.marker([current.latitude, current.longitude], { icon: satIcon });\r\n addLayer(satMarker);\r\n satMarker.bindTooltip(\r\n `<strong style=\"color:${color}\">${sat.name}</strong><br/>` +\r\n `Lat ${current.latitude.toFixed(3)}° Lon ${current.longitude.toFixed(3)}°` +\r\n (current.altitude != null ? `<br/>Alt ${current.altitude.toFixed(1)} km` : '') +\r\n (sat.status ? `<br/><span style=\"color:${statusColor}\">${sat.status.toUpperCase()}</span>` : ''),\r\n { permanent: false, direction: 'top', className: 'zendir-leaflet-tooltip' }\r\n );\r\n satMarker.on('click', () => onSatelliteClick?.(sat.id));\r\n });\r\n\r\n if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {\r\n const Legend = L.Control.extend({\r\n onAdd() {\r\n const div = L.DomUtil.create('div', 'leaflet-legend-zendir');\r\n div.style.cssText = `\r\n padding: 8px 12px; background: rgba(15,21,32,0.9); border: 1px solid rgba(157,112,255,0.2);\r\n border-radius: 8px; color: #e4e0f0; font-size: 11px; font-family: Roboto, sans-serif;\r\n `;\r\n allSatellites.forEach((s, i) => {\r\n const c = s.color || DEFAULT_TRACK_COLORS[i % DEFAULT_TRACK_COLORS.length];\r\n const row = document.createElement('div');\r\n row.style.cssText = 'display: flex; align-items: center; gap: 6px; margin: 2px 0;';\r\n const swatch = document.createElement('span');\r\n swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${c};`;\r\n const label = document.createElement('span');\r\n label.textContent = s.name;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n });\r\n if (groundStations.length > 0) {\r\n const row = document.createElement('div');\r\n row.style.cssText = 'display: flex; align-items: center; gap: 6px; margin: 2px 0; margin-top: 6px;';\r\n const swatch = document.createElement('span');\r\n swatch.style.cssText = 'width:8px;height:8px;border-radius:50%;background:#2dccff;';\r\n const label = document.createElement('span');\r\n label.textContent = `${groundStations.length} Ground Station${groundStations.length > 1 ? 's' : ''}`;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n }\r\n return div;\r\n },\r\n });\r\n const legend = new Legend({ position: 'topleft' });\r\n legend.addTo(map);\r\n controlsRef.current.push(legend);\r\n }\r\n\r\n }, [\r\n ready,\r\n allSatellites,\r\n groundStations,\r\n showTerminator,\r\n showGrid,\r\n showEquator,\r\n showLegend,\r\n tokens.colors.text.secondary,\r\n addLayer,\r\n clearLayers,\r\n onSatelliteClick,\r\n onStationClick,\r\n ]);\r\n\r\n const handleRecenter = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n const points: L.LatLngExpression[] = [];\r\n allSatellites.forEach((s) => {\r\n s.groundTrack.forEach((p) => points.push([p.latitude, p.longitude]));\r\n });\r\n groundStations.forEach((gs) => points.push([gs.latitude, gs.longitude]));\r\n if (points.length > 0) {\r\n const bounds = L.latLngBounds(points);\r\n map.fitBounds(bounds, { padding: [40, 40], maxZoom: 8 });\r\n } else {\r\n map.setView(defaultCenter, defaultZoom);\r\n }\r\n }, [allSatellites, groundStations, defaultCenter, defaultZoom]);\r\n\r\n const resolvedMinHeight = minHeight || (typeof height === 'number' ? `${height}px` : '400px');\r\n\r\n const isEmpty = allSatellites.length === 0 && groundStations.length === 0;\r\n\r\n return (\r\n <div\r\n className={`zendir-ground-track-map zendir-ground-track-map-leaflet ${className}`}\r\n data-map-backend=\"leaflet\"\r\n style={{\r\n width,\r\n height,\r\n minHeight: resolvedMinHeight,\r\n backgroundColor: tokens.colors.background.base,\r\n borderRadius: 8,\r\n overflow: 'hidden',\r\n position: 'relative',\r\n }}\r\n >\r\n <div\r\n ref={containerRef}\r\n style={{ width: '100%', height: '100%', minHeight: resolvedMinHeight }}\r\n />\r\n {isEmpty && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n left: '50%',\r\n top: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n color: tokens.colors.text.secondary,\r\n fontSize: 14,\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {emptyMessage}\r\n </div>\r\n )}\r\n {showRecenterButton && (\r\n <button\r\n type=\"button\"\r\n onClick={handleRecenter}\r\n title=\"Recenter map\"\r\n aria-label=\"Recenter map\"\r\n style={{\r\n position: 'absolute',\r\n top: 8,\r\n right: 50,\r\n zIndex: 1000,\r\n background: 'rgba(24, 29, 46, 0.9)',\r\n border: '1px solid rgba(157, 112, 255, 0.25)',\r\n borderRadius: 6,\r\n color: '#e4e0f0',\r\n cursor: 'pointer',\r\n padding: '6px 10px',\r\n fontSize: 12,\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 4,\r\n }}\r\n >\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\r\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\r\n <path d=\"M12 2v4M12 18v4M2 12h4M18 12h4\" />\r\n </svg>\r\n Recenter\r\n </button>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default GroundTrackMapLeaflet;\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GroundTrackMapLeaflet.js","sources":["../../../src/react/charts/GroundTrackMapLeaflet.tsx"],"sourcesContent":["/**\r\n * @zendir/ui - GroundTrackMap Leaflet Provider\r\n *\r\n * Leaflet-based 2D world map for ground tracks, stations, and terminator.\r\n * Uses Zendir hybrid theme; all interactions enabled (zoom, scroll, drag, etc.).\r\n * Import leaflet-zendir.css when using this component.\r\n */\r\n\r\nimport React, { useRef, useEffect, useCallback, useMemo, useState } from 'react';\r\nimport { useTheme } from '../theme';\r\nimport type { GroundStation } from '../types';\r\nimport type { SatelliteTrack, GroundStationEnhanced, SROGroundStation, MapPin } from './GroundTrackMap';\r\n\r\n// Optional Leaflet — consumer must install leaflet when using mapProvider=\"leaflet\"\r\nimport L from 'leaflet';\r\nimport 'leaflet/dist/leaflet.css';\r\nimport './leaflet-zendir.css';\r\nimport {\r\n splitPolylineAtAntimeridian,\r\n segmentsWithWorldCopies,\r\n geodesicCirclePoints,\r\n splitRingAtAntimeridian,\r\n} from './groundTrackMapLeafletUtils';\r\nimport {\r\n DEFAULT_TILE,\r\n FALLBACK_TILE,\r\n TILE_ERROR_THRESHOLD,\r\n CARTO_ATTRIBUTION,\r\n OSM_ATTRIBUTION,\r\n} from './groundTrackMapLeafletTiles';\r\n\r\nconst STATUS_COLOR_MAP: Record<string, string> = {\r\n normal: '#56f000',\r\n standby: '#2dccff',\r\n caution: '#fce83a',\r\n serious: '#ffb302',\r\n critical: '#ff3838',\r\n off: '#a4abb6',\r\n};\r\n\r\nconst DEFAULT_TRACK_COLORS = [\r\n '#2dccff', '#3E3CFF', '#9D70FF', '#56f000', '#fce83a', '#ff7849', '#2dd4bf', '#ff3838',\r\n];\r\n\r\n// =============================================================================\r\n// Icon helpers — produce L.DivIcon with inline SVG (no external assets needed)\r\n// =============================================================================\r\n\r\n/** Satellite spacecraft icon with solar panels, matching Astro UXDS style. */\r\nfunction createSatDivIcon(statusColor: string, trackColor: string): L.DivIcon {\r\n // Status badge shape (Astro UXDS spec)\r\n const badge = statusColor === '#56f000'\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"3\" fill=\"${statusColor}\"/>` // normal: filled dot\r\n : statusColor === '#2dccff'\r\n ? `<circle cx=\"7\" cy=\"7\" r=\"2.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\"/>` // standby: ring\r\n : statusColor === '#fce83a'\r\n ? `<rect x=\"4.5\" y=\"4.5\" width=\"5\" height=\"5\" fill=\"${statusColor}\"/>` // caution: square\r\n : statusColor === '#ffb302'\r\n ? `<polygon points=\"7,3.5 10.5,7 7,10.5 3.5,7\" fill=\"${statusColor}\"/>` // serious: diamond (Astro UXDS)\r\n : statusColor === '#ff3838'\r\n ? `<polygon points=\"7,10 4,4 10,4\" fill=\"${statusColor}\"/>` // critical: triangle down\r\n : `<circle cx=\"7\" cy=\"7\" r=\"2\" fill=\"${statusColor}\"/>`; // off: small dot\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"34\" height=\"34\" viewBox=\"0 0 34 34\">\r\n <!-- Glow ring -->\r\n <circle cx=\"17\" cy=\"17\" r=\"15\" fill=\"${statusColor}\" fill-opacity=\"0.12\"/>\r\n <!-- Main body circle -->\r\n <circle cx=\"17\" cy=\"17\" r=\"11\" fill=\"#0d1323\" stroke=\"${trackColor}\" stroke-width=\"1.5\"/>\r\n <!-- Left solar panel -->\r\n <rect x=\"2\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"5.5\" y1=\"15\" x2=\"5.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Connector left -->\r\n <line x1=\"11\" y1=\"17\" x2=\"13\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Satellite bus body -->\r\n <rect x=\"13\" y=\"14.5\" width=\"8\" height=\"5\" rx=\"1\" fill=\"${statusColor}\"/>\r\n <!-- Connector right -->\r\n <line x1=\"21\" y1=\"17\" x2=\"23\" y2=\"17\" stroke=\"${trackColor}\" stroke-width=\"1.2\"/>\r\n <!-- Right solar panel -->\r\n <rect x=\"23\" y=\"15\" width=\"9\" height=\"4\" rx=\"0.5\" fill=\"${trackColor}\" fill-opacity=\"0.75\"/>\r\n <line x1=\"26.5\" y1=\"15\" x2=\"26.5\" y2=\"19\" stroke=\"${trackColor}\" stroke-width=\"0.5\" stroke-opacity=\"0.45\"/>\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"7\" cy=\"7\" r=\"5.5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: 'zendir-sat-icon',\r\n iconSize: [34, 34] as unknown as L.PointExpression,\r\n iconAnchor: [17, 17] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -18] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/** Ground station icon — dish/antenna shape, matching Astro UXDS style. */\r\nfunction createStationDivIcon(statusColor: string, type: string, status: string): L.DivIcon {\r\n // Status badge\r\n const badge = status === 'normal'\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2.5\" fill=\"${statusColor}\"/>`\r\n : status === 'standby'\r\n ? `<circle cx=\"5\" cy=\"5\" r=\"2\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`\r\n : status === 'caution'\r\n ? `<rect x=\"3\" y=\"3\" width=\"4\" height=\"4\" fill=\"${statusColor}\"/>`\r\n : status === 'serious'\r\n ? `<polygon points=\"5,2.5 7.5,5 5,7.5 2.5,5\" fill=\"${statusColor}\"/>`\r\n : status === 'critical'\r\n ? `<polygon points=\"5,7.5 2.5,2.5 7.5,2.5\" fill=\"${statusColor}\"/>`\r\n : `<circle cx=\"5\" cy=\"5\" r=\"1.5\" fill=\"${statusColor}\"/>`;\r\n\r\n let iconInner = '';\r\n if (type === 'phased-array') {\r\n // Grid pattern (phased array)\r\n iconInner = `\r\n <rect x=\"9\" y=\"7\" width=\"10\" height=\"8\" rx=\"0.5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"12\" y1=\"7\" x2=\"12\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"15\" y1=\"7\" x2=\"15\" y2=\"15\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"10\" x2=\"19\" y2=\"10\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"9\" y1=\"13\" x2=\"19\" y2=\"13\" stroke=\"${statusColor}\" stroke-width=\"0.7\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"20\" x2=\"17\" y2=\"20\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n } else if (type === 'relay') {\r\n // Relay dish with signal arcs\r\n iconInner = `\r\n <path d=\"M 8 16 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 8 16 m 2 -2 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <line x1=\"14\" y1=\"16\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <path d=\"M 16 8 a 3 3 0 0 1 0 4\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-linecap=\"round\"/>\r\n <path d=\"M 17.5 6.5 a 5 5 0 0 1 0 7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1\" stroke-linecap=\"round\"/>`;\r\n } else {\r\n // Default: dish antenna with concentric arcs (Astro UXDS antenna pattern)\r\n iconInner = `\r\n <path d=\"M 14 14 m -7 0 a 7 7 0 0 1 7 -7\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -5 0 a 5 5 0 0 1 5 -5\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <path d=\"M 14 14 m -3 0 a 3 3 0 0 1 3 -3\" fill=\"none\" stroke=\"${statusColor}\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\r\n <circle cx=\"14\" cy=\"14\" r=\"1.5\" fill=\"${statusColor}\"/>\r\n <line x1=\"14\" y1=\"15\" x2=\"14\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>\r\n <line x1=\"11\" y1=\"21\" x2=\"17\" y2=\"21\" stroke=\"${statusColor}\" stroke-width=\"1.2\"/>`;\r\n }\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 28 28\">\r\n <!-- Glow ring -->\r\n <circle cx=\"14\" cy=\"14\" r=\"13\" fill=\"${statusColor}\" fill-opacity=\"0.1\"/>\r\n <!-- Background -->\r\n <circle cx=\"14\" cy=\"14\" r=\"11\" fill=\"#0d1323\" stroke=\"${statusColor}\" stroke-width=\"1.2\" stroke-opacity=\"0.7\"/>\r\n ${iconInner}\r\n <!-- Status badge (top-left) -->\r\n <circle cx=\"5\" cy=\"5\" r=\"5\" fill=\"#0d1323\"/>\r\n ${badge}\r\n </svg>`;\r\n\r\n return L.divIcon({\r\n html: svg,\r\n className: 'zendir-station-icon',\r\n iconSize: [28, 28] as unknown as L.PointExpression,\r\n iconAnchor: [14, 14] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -14] as unknown as L.PointExpression,\r\n });\r\n}\r\n\r\n/**\r\n * Compute a terminator line with CONTINUOUS (unwrapped) longitudes.\r\n *\r\n * `depressionDeg` offsets the solar zenith angle to produce twilight\r\n * boundaries instead of the geometric sunset line:\r\n * - 0° = geometric sunset/sunrise (day/night boundary)\r\n * - 6° = civil twilight (horizon still visible, outdoor activities possible)\r\n * - 12° = nautical twilight (horizon barely visible, stars appearing)\r\n * - 18° = astronomical twilight (sky fully dark for observation)\r\n *\r\n * These thresholds follow IAU/USNO standard definitions used in celestial\r\n * navigation, Astro UX space ops dashboards, and STK/GMAT mission tools.\r\n */\r\nfunction calculateTerminatorContinuous(\r\n date: Date,\r\n depressionDeg: number = 0,\r\n numPoints: number = 72,\r\n): Array<[number, number]> {\r\n const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000);\r\n const declination = -23.44 * Math.cos((2 * Math.PI / 365) * (dayOfYear + 10));\r\n const decRad = (declination * Math.PI) / 180;\r\n const hourAngle = ((date.getUTCHours() + date.getUTCMinutes() / 60) / 24) * 360 - 180;\r\n const depRad = (depressionDeg * Math.PI) / 180;\r\n\r\n const points: Array<[number, number]> = [];\r\n let prevLon: number | null = null;\r\n for (let i = 0; i <= numPoints; i++) {\r\n const latRad = ((i / numPoints) * 180 - 90) * (Math.PI / 180);\r\n const cosH = -(Math.sin(depRad) + Math.sin(latRad) * Math.sin(decRad))\r\n / (Math.cos(latRad) * Math.cos(decRad));\r\n let lon: number;\r\n if (cosH < -1) lon = hourAngle + 180;\r\n else if (cosH > 1) lon = hourAngle;\r\n else lon = hourAngle + (Math.acos(cosH) * 180) / Math.PI;\r\n\r\n if (prevLon != null) {\r\n while (lon - prevLon > 180) lon -= 360;\r\n while (lon - prevLon < -180) lon += 360;\r\n }\r\n prevLon = lon;\r\n points.push([(i / numPoints) * 180 - 90, lon]);\r\n }\r\n return points;\r\n}\r\n\r\n/**\r\n * Build a night-side polygon between two terminator lines.\r\n * The outer line is the \"darker\" boundary, the inner is the \"lighter\" one.\r\n * Result traces: outer (lat −90→+90) → inner reversed (lat +90→−90).\r\n */\r\nfunction buildBandPolygon(\r\n outerSunset: Array<[number, number]>,\r\n innerSunset: Array<[number, number]>,\r\n): [number, number][] {\r\n if (!outerSunset.length || !innerSunset.length) return [];\r\n const outerSunrise = outerSunset.map(([lat, lon]) => [lat, lon - 180] as [number, number]);\r\n const innerSunrise = innerSunset.map(([lat, lon]) => [lat, lon - 180] as [number, number]);\r\n // Sunset side: outer → inner reversed\r\n const sunsetBand = [...outerSunset, ...[...innerSunset].reverse()];\r\n // Sunrise side: inner → outer reversed\r\n const sunriseBand = [...innerSunrise, ...[...outerSunrise].reverse()];\r\n return [...sunsetBand, ...sunriseBand];\r\n}\r\n\r\n/**\r\n * Build the core night polygon (inside the astronomical twilight boundary).\r\n */\r\nfunction buildNightPolygon(sunsetTerminator: Array<[number, number]>): [number, number][] {\r\n if (!sunsetTerminator.length) return [];\r\n const sunrise = sunsetTerminator.map(([lat, lon]) => [lat, lon - 180] as [number, number]);\r\n return [...sunsetTerminator, ...[...sunrise].reverse()];\r\n}\r\n\r\nexport type GroundStationMapItem = GroundStation | GroundStationEnhanced | SROGroundStation;\r\n\r\nexport interface GroundTrackMapLeafletProps {\r\n allSatellites: SatelliteTrack[];\r\n groundStations: GroundStationMapItem[];\r\n showTerminator?: boolean;\r\n /** Override the time used for the terminator calculation (defaults to wall-clock now). */\r\n terminatorTime?: Date;\r\n showGrid?: boolean;\r\n showLegend?: boolean;\r\n showEquator?: boolean;\r\n showRecenterButton?: boolean;\r\n defaultCenter?: [number, number];\r\n defaultZoom?: number;\r\n height?: number | string;\r\n width?: number | string;\r\n minHeight?: string;\r\n emptyMessage?: string;\r\n tileUrl?: string;\r\n className?: string;\r\n onSatelliteClick?: (id: string) => void;\r\n onStationClick?: (id: string) => void;\r\n /** Points-of-interest pins */\r\n pins?: MapPin[];\r\n /** Allow adding/editing pins via map clicks */\r\n pinsEditable?: boolean;\r\n onPinAdd?: (pin: Omit<MapPin, 'id'>) => void;\r\n onPinUpdate?: (pin: MapPin) => void;\r\n onPinRemove?: (pinId: string) => void;\r\n}\r\n\r\nexport function GroundTrackMapLeaflet({\r\n allSatellites,\r\n groundStations,\r\n showTerminator = true,\r\n terminatorTime,\r\n showGrid = false,\r\n showLegend = true,\r\n showEquator = false,\r\n showRecenterButton = true,\r\n defaultCenter = [20, 0],\r\n defaultZoom = 2,\r\n height = '100%',\r\n width = '100%',\r\n minHeight = '400px',\r\n emptyMessage = 'No orbital data available',\r\n tileUrl = DEFAULT_TILE,\r\n className = '',\r\n onSatelliteClick,\r\n onStationClick,\r\n pins,\r\n pinsEditable = false,\r\n onPinAdd,\r\n onPinUpdate,\r\n onPinRemove,\r\n}: GroundTrackMapLeafletProps): React.ReactElement {\r\n const { tokens } = useTheme();\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const mapRef = useRef<L.Map | null>(null);\r\n const tileLayerRef = useRef<L.TileLayer | null>(null);\r\n const overlayGroupRef = useRef<L.LayerGroup | null>(null);\r\n const controlsRef = useRef<L.Control[]>([]);\r\n /** Separate layer group so pin updates don't clear satellite/station layers */\r\n const pinsGroupRef = useRef<L.LayerGroup | null>(null);\r\n const [ready, setReady] = useState(false);\r\n\r\n const clearLayers = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n overlayGroupRef.current?.clearLayers();\r\n controlsRef.current.forEach((ctrl) => map.removeControl(ctrl));\r\n controlsRef.current = [];\r\n }, []);\r\n\r\n const addLayer = useCallback((layer: L.Layer) => {\r\n overlayGroupRef.current?.addLayer(layer);\r\n }, []);\r\n\r\n // Create map once on mount (or when tileUrl changes). Intentionally not depending on\r\n // defaultCenter/defaultZoom so animated updates do not recreate the map (no flicker/zoom reset).\r\n useEffect(() => {\r\n // eslint-disable-next-line react-hooks/exhaustive-deps -- defaultCenter/defaultZoom used only for initial view\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const center: L.LatLngExpression = defaultCenter ?? [20, 0];\r\n const zoom = defaultZoom ?? 2;\r\n\r\n const map = L.map(el, {\r\n center,\r\n zoom,\r\n zoomControl: false,\r\n scrollWheelZoom: true,\r\n doubleClickZoom: true,\r\n touchZoom: true,\r\n boxZoom: true,\r\n keyboard: true,\r\n dragging: true,\r\n attributionControl: true,\r\n maxBounds: [[-90, -540], [90, 540]],\r\n maxBoundsViscosity: 1.0,\r\n });\r\n\r\n L.control.zoom({ position: 'topright' }).addTo(map);\r\n\r\n map.attributionControl.setPrefix('');\r\n\r\n const isCartoTiles = tileUrl.includes('cartocdn');\r\n if (isCartoTiles) {\r\n map.attributionControl.addAttribution(CARTO_ATTRIBUTION);\r\n }\r\n\r\n const tileOptions: L.TileLayerOptions = {\r\n maxZoom: 19,\r\n subdomains: isCartoTiles ? 'abcd' : 'abc',\r\n // crossOrigin avoids tainted-canvas errors when Leaflet tries to read tile pixels\r\n crossOrigin: true,\r\n };\r\n\r\n const tile = L.tileLayer(tileUrl, tileOptions);\r\n tile.addTo(map);\r\n tileLayerRef.current = tile;\r\n\r\n // When primary tiles fail (network, CORS, rate limit), switch to fallback immediately.\r\n // TILE_ERROR_THRESHOLD = 1 so the first error triggers the switch — no visible black period.\r\n let hasSwitchedToFallback = false;\r\n const onTileError = () => {\r\n if (hasSwitchedToFallback) return;\r\n hasSwitchedToFallback = true;\r\n tile.off('tileerror', onTileError);\r\n tile.remove();\r\n if (isCartoTiles) {\r\n map.attributionControl.removeAttribution(CARTO_ATTRIBUTION);\r\n map.attributionControl.addAttribution(OSM_ATTRIBUTION);\r\n }\r\n const fallback = L.tileLayer(FALLBACK_TILE, {\r\n maxZoom: 19,\r\n subdomains: 'abc',\r\n crossOrigin: true,\r\n });\r\n fallback.addTo(map);\r\n tileLayerRef.current = fallback;\r\n };\r\n tile.on('tileerror', onTileError);\r\n\r\n const overlayGroup = L.layerGroup();\r\n overlayGroup.addTo(map);\r\n overlayGroupRef.current = overlayGroup;\r\n\r\n const pinsGroup = L.layerGroup();\r\n pinsGroup.addTo(map);\r\n pinsGroupRef.current = pinsGroup;\r\n\r\n mapRef.current = map;\r\n setReady(true);\r\n\r\n const onSize = () => {\r\n map.invalidateSize({ animate: false });\r\n };\r\n // Immediate RAF + two deferred calls cover: first paint, CSS transitions, flex layout settle\r\n const raf = requestAnimationFrame(onSize);\r\n const t1 = setTimeout(onSize, 150);\r\n const t2 = setTimeout(onSize, 500);\r\n\r\n return () => {\r\n clearTimeout(t1);\r\n clearTimeout(t2);\r\n cancelAnimationFrame(raf);\r\n clearLayers();\r\n pinsGroupRef.current?.clearLayers();\r\n pinsGroupRef.current = null;\r\n tileLayerRef.current = null;\r\n map.remove();\r\n mapRef.current = null;\r\n overlayGroupRef.current = null;\r\n };\r\n }, [tileUrl, clearLayers]);\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const map = mapRef.current;\r\n clearLayers();\r\n\r\n if (showTerminator) {\r\n const now = terminatorTime ?? new Date();\r\n // Compute terminator lines at each twilight boundary (IAU/USNO definitions)\r\n const geometric = calculateTerminatorContinuous(now, 0); // day/night boundary\r\n const civil = calculateTerminatorContinuous(now, 6); // civil twilight (6°)\r\n const nautical = calculateTerminatorContinuous(now, 12); // nautical twilight (12°)\r\n const astro = calculateTerminatorContinuous(now, 18); // astronomical twilight (18°)\r\n\r\n // Graduated twilight bands: each zone gets progressively darker\r\n const twilightBands: Array<{ poly: [number, number][]; opacity: number }> = [\r\n // Civil twilight zone (geometric → 6°): faintest tint\r\n { poly: buildBandPolygon(civil, geometric), opacity: 0.08 },\r\n // Nautical twilight zone (6° → 12°): moderate\r\n { poly: buildBandPolygon(nautical, civil), opacity: 0.10 },\r\n // Astronomical twilight zone (12° → 18°): darker\r\n { poly: buildBandPolygon(astro, nautical), opacity: 0.12 },\r\n // Full night (inside 18° boundary): deepest\r\n { poly: buildNightPolygon(astro), opacity: 0.22 },\r\n ];\r\n\r\n twilightBands.forEach(({ poly, opacity }) => {\r\n if (poly.length < 3) return;\r\n [0, 360, -360].forEach((offset) => {\r\n const shifted = poly.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polygon(shifted, {\r\n color: 'transparent',\r\n fillColor: '#000a28',\r\n fillOpacity: opacity,\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n\r\n // Subtle boundary line at the geometric terminator only (day/night edge)\r\n const sunriseGeometric = geometric.map(([lat, lon]) => [lat, lon - 180] as [number, number]);\r\n [geometric, sunriseGeometric].forEach((line) => {\r\n [0, 360, -360].forEach((offset) => {\r\n const shifted = line.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polyline(shifted, {\r\n color: 'rgba(88, 166, 255, 0.18)',\r\n weight: 1,\r\n dashArray: '4 6',\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n }\r\n\r\n if (showGrid) {\r\n const gridStyle = { color: 'rgba(157, 112, 255, 0.12)', weight: 0.5, interactive: false };\r\n [-360, 0, 360].forEach((offset) => {\r\n for (let lon = -180; lon <= 180; lon += 30) {\r\n const l = lon + offset;\r\n addLayer(L.polyline([[90, l], [-90, l]], gridStyle));\r\n }\r\n for (let lat = -90; lat <= 90; lat += 30) {\r\n addLayer(L.polyline([[lat, -180 + offset], [lat, 180 + offset]], gridStyle));\r\n }\r\n });\r\n }\r\n\r\n if (showEquator) {\r\n [-360, 0, 360].forEach((offset) => {\r\n addLayer(L.polyline([[0, -180 + offset], [0, 180 + offset]], {\r\n color: 'rgba(88, 166, 255, 0.25)',\r\n weight: 1,\r\n dashArray: '6, 4',\r\n interactive: false,\r\n }));\r\n });\r\n }\r\n\r\n groundStations.forEach((gs) => {\r\n const status = ('status' in gs ? gs.status : undefined) ?? 'standby';\r\n const statusColor = STATUS_COLOR_MAP[status] || STATUS_COLOR_MAP.standby;\r\n const stationType = ('type' in gs ? (gs as { type?: string }).type : undefined) ?? 'dish';\r\n const radius = 'coverageRadius' in gs ? gs.coverageRadius : undefined;\r\n const showCoverage = 'showCoverage' in gs ? gs.showCoverage : false;\r\n if (radius != null && radius > 0 && showCoverage) {\r\n const ring = geodesicCirclePoints(gs.latitude, gs.longitude, radius, 64);\r\n const rings = splitRingAtAntimeridian(ring);\r\n [0, 360, -360].forEach((offset) => {\r\n rings.forEach((r) => {\r\n const shifted = r.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polygon(shifted, {\r\n color: `${statusColor}55`,\r\n fillColor: statusColor,\r\n fillOpacity: 0.1,\r\n weight: 1,\r\n dashArray: '4, 3',\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n }\r\n\r\n const gsIcon = createStationDivIcon(statusColor, stationType, status);\r\n const marker = L.marker([gs.latitude, gs.longitude], { icon: gsIcon });\r\n addLayer(marker);\r\n const statusLabel = status !== 'standby' ? ` · ${status}` : '';\r\n marker.bindTooltip(\r\n `<strong>${gs.name}</strong>${statusLabel}${'network' in gs && gs.network ? `<br/><span style=\"opacity:0.7\">${gs.network}</span>` : ''}`,\r\n { permanent: false, direction: 'top', className: 'zendir-leaflet-tooltip' }\r\n );\r\n marker.on('click', () => {\r\n onStationClick?.('id' in gs ? String(gs.id) : gs.name);\r\n });\r\n });\r\n\r\n allSatellites.forEach((sat, satIdx) => {\r\n const track = sat.groundTrack;\r\n if (!track || track.length === 0) return;\r\n\r\n const color = sat.color || DEFAULT_TRACK_COLORS[satIdx % DEFAULT_TRACK_COLORS.length];\r\n const futureIdx = sat.futureTrackIndex ?? track.length;\r\n\r\n const pastLatLngs = track\r\n .slice(0, futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(pastLatLngs)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color, weight: 2.5, opacity: 1 }));\r\n }\r\n });\r\n\r\n if (futureIdx < track.length) {\r\n const futureLatLngs = track\r\n .slice(futureIdx)\r\n .map((p) => [p.latitude, p.longitude] as [number, number]);\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(futureLatLngs)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color, weight: 1.5, opacity: 0.5, dashArray: '6, 4' }));\r\n }\r\n });\r\n }\r\n\r\n // Access mask segments (contact passes) — solid green overlay on track\r\n if (sat.accessMask && sat.accessMask.some(Boolean)) {\r\n let segment: [number, number][] = [];\r\n for (let i = 0; i < track.length; i++) {\r\n if (sat.accessMask[i]) {\r\n segment.push([track[i].latitude, track[i].longitude]);\r\n } else if (segment.length >= 2) {\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color: STATUS_COLOR_MAP.normal, weight: 4, opacity: 0.9 }));\r\n }\r\n });\r\n segment = [];\r\n } else {\r\n segment = [];\r\n }\r\n }\r\n if (segment.length >= 2) {\r\n segmentsWithWorldCopies(splitPolylineAtAntimeridian(segment)).forEach((seg) => {\r\n if (seg.length >= 2) {\r\n addLayer(L.polyline(seg, { color: STATUS_COLOR_MAP.normal, weight: 4, opacity: 0.9 }));\r\n }\r\n });\r\n }\r\n }\r\n\r\n if (sat.passMarkers && sat.passMarkers.length > 0) {\r\n sat.passMarkers.forEach((pm) => {\r\n const isAos = pm.type === 'aos';\r\n const pmColor = isAos ? '#56f000' : '#ff3838';\r\n const pmMarker = L.circleMarker([pm.latitude, pm.longitude], {\r\n radius: 5,\r\n fillColor: pmColor,\r\n color: `${pmColor}cc`,\r\n weight: 1.5,\r\n fillOpacity: 0.9,\r\n });\r\n addLayer(pmMarker);\r\n pmMarker.bindTooltip(`${pm.type.toUpperCase()}${pm.label ? ` – ${pm.label}` : ''}`, {\r\n permanent: false,\r\n direction: 'top',\r\n className: 'zendir-leaflet-tooltip',\r\n });\r\n });\r\n }\r\n\r\n if (sat.showFootprint && sat.footprintRadius) {\r\n const lastIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;\r\n const last = track[lastIdx];\r\n const ring = geodesicCirclePoints(last.latitude, last.longitude, sat.footprintRadius, 64);\r\n const rings = splitRingAtAntimeridian(ring);\r\n [0, 360, -360].forEach((offset) => {\r\n rings.forEach((r) => {\r\n const shifted = r.map(([lat, lon]) => [lat, lon + offset] as [number, number]);\r\n addLayer(L.polygon(shifted, {\r\n color: `${color}70`,\r\n fillColor: color,\r\n fillOpacity: 0.1,\r\n weight: 1,\r\n dashArray: '3, 3',\r\n interactive: false,\r\n }));\r\n });\r\n });\r\n }\r\n\r\n const currentIdx = futureIdx > 0 ? Math.min(futureIdx - 1, track.length - 1) : track.length - 1;\r\n const current = track[currentIdx];\r\n const statusColor = STATUS_COLOR_MAP[sat.status || 'normal'] || STATUS_COLOR_MAP.normal;\r\n\r\n const satIcon = createSatDivIcon(statusColor, color);\r\n const satMarker = L.marker([current.latitude, current.longitude], { icon: satIcon });\r\n addLayer(satMarker);\r\n satMarker.bindTooltip(\r\n `<strong style=\"color:${color}\">${sat.name}</strong><br/>` +\r\n `Lat ${current.latitude.toFixed(3)}° Lon ${current.longitude.toFixed(3)}°` +\r\n (current.altitude != null ? `<br/>Alt ${current.altitude.toFixed(1)} km` : '') +\r\n (sat.status ? `<br/><span style=\"color:${statusColor}\">${sat.status.toUpperCase()}</span>` : ''),\r\n { permanent: false, direction: 'top', className: 'zendir-leaflet-tooltip' }\r\n );\r\n satMarker.on('click', () => onSatelliteClick?.(sat.id));\r\n });\r\n\r\n if (showLegend && (allSatellites.length > 0 || groundStations.length > 0)) {\r\n const Legend = L.Control.extend({\r\n onAdd() {\r\n const div = L.DomUtil.create('div', 'leaflet-legend-zendir');\r\n div.style.cssText = `\r\n padding: 8px 12px; background: rgba(15,21,32,0.9); border: 1px solid rgba(157,112,255,0.2);\r\n border-radius: 8px; color: #e4e0f0; font-size: 11px; font-family: Roboto, sans-serif;\r\n `;\r\n allSatellites.forEach((s, i) => {\r\n const c = s.color || DEFAULT_TRACK_COLORS[i % DEFAULT_TRACK_COLORS.length];\r\n const row = document.createElement('div');\r\n row.style.cssText = 'display: flex; align-items: center; gap: 6px; margin: 2px 0;';\r\n const swatch = document.createElement('span');\r\n swatch.style.cssText = `width:8px;height:8px;border-radius:50%;background:${c};`;\r\n const label = document.createElement('span');\r\n label.textContent = s.name;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n });\r\n if (groundStations.length > 0) {\r\n const row = document.createElement('div');\r\n row.style.cssText = 'display: flex; align-items: center; gap: 6px; margin: 2px 0; margin-top: 6px;';\r\n const swatch = document.createElement('span');\r\n swatch.style.cssText = 'width:8px;height:8px;border-radius:50%;background:#2dccff;';\r\n const label = document.createElement('span');\r\n label.textContent = `${groundStations.length} Ground Station${groundStations.length > 1 ? 's' : ''}`;\r\n row.appendChild(swatch);\r\n row.appendChild(label);\r\n div.appendChild(row);\r\n }\r\n return div;\r\n },\r\n });\r\n const legend = new Legend({ position: 'topleft' });\r\n legend.addTo(map);\r\n controlsRef.current.push(legend);\r\n }\r\n\r\n }, [\r\n ready,\r\n allSatellites,\r\n groundStations,\r\n showTerminator,\r\n terminatorTime,\r\n showGrid,\r\n showEquator,\r\n showLegend,\r\n tokens.colors.text.secondary,\r\n addLayer,\r\n clearLayers,\r\n onSatelliteClick,\r\n onStationClick,\r\n ]);\r\n\r\n // ==========================================================================\r\n // Pins layer — separate from overlayGroup so pin updates don't rebuild tracks\r\n // ==========================================================================\r\n\r\n useEffect(() => {\r\n if (!ready || !mapRef.current) return;\r\n const group = pinsGroupRef.current;\r\n if (!group) return;\r\n group.clearLayers();\r\n\r\n if (!pins || pins.length === 0) return;\r\n const accentColor = tokens.colors.accent.primary;\r\n\r\n pins.forEach((pin) => {\r\n const pinColor = pin.color || accentColor;\r\n\r\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"36\" viewBox=\"0 0 24 36\">\r\n <defs>\r\n <filter id=\"pin-shadow-${pin.id}\" x=\"-30%\" y=\"-10%\" width=\"160%\" height=\"140%\">\r\n <feDropShadow dx=\"0\" dy=\"2\" stdDeviation=\"2\" flood-color=\"#000\" flood-opacity=\"0.35\"/>\r\n </filter>\r\n </defs>\r\n <g filter=\"url(#pin-shadow-${pin.id})\">\r\n <path d=\"M12 2 C6.5 2 2 6.5 2 12 C2 19 12 34 12 34 C12 34 22 19 22 12 C22 6.5 17.5 2 12 2Z\"\r\n fill=\"${pinColor}\" stroke=\"rgba(255,255,255,0.5)\" stroke-width=\"1\"/>\r\n <circle cx=\"12\" cy=\"12\" r=\"4\" fill=\"#0d1323\" stroke=\"rgba(255,255,255,0.7)\" stroke-width=\"0.8\"/>\r\n </g>\r\n </svg>`;\r\n\r\n const icon = L.divIcon({\r\n html: svg,\r\n className: 'zendir-pin-icon',\r\n iconSize: [24, 36] as unknown as L.PointExpression,\r\n iconAnchor: [12, 36] as unknown as L.PointExpression,\r\n tooltipAnchor: [0, -36] as unknown as L.PointExpression,\r\n });\r\n\r\n const marker = L.marker([pin.latitude, pin.longitude], {\r\n icon,\r\n draggable: pinsEditable,\r\n title: pin.label ?? '',\r\n });\r\n\r\n // Rich tooltip\r\n const tooltipLines = [\r\n pin.label ? `<strong>${pin.label}</strong>` : '',\r\n pin.description ? `<div style=\"opacity:0.7;font-size:11px\">${pin.description}</div>` : '',\r\n `<div style=\"font-size:10px;opacity:0.5;font-family:monospace\">${pin.latitude.toFixed(4)}°, ${pin.longitude.toFixed(4)}°</div>`,\r\n pin.createdBy ? `<div style=\"font-size:10px;opacity:0.4;margin-top:2px\">by ${pin.createdBy}</div>` : '',\r\n ].filter(Boolean).join('');\r\n marker.bindTooltip(tooltipLines, {\r\n className: 'zendir-pin-tooltip',\r\n direction: 'top',\r\n offset: [0, -4],\r\n });\r\n\r\n if (pinsEditable) {\r\n // Drag to reposition\r\n marker.on('dragend', () => {\r\n const latlng = marker.getLatLng();\r\n onPinUpdate?.({ ...pin, latitude: latlng.lat, longitude: latlng.lng });\r\n });\r\n\r\n // Right-click to delete\r\n marker.on('contextmenu', (e: L.LeafletEvent) => {\r\n L.DomEvent.stopPropagation(e as unknown as Event);\r\n onPinRemove?.(pin.id);\r\n });\r\n }\r\n\r\n group.addLayer(marker);\r\n });\r\n }, [ready, pins, pinsEditable, onPinUpdate, onPinRemove, tokens.colors.accent.primary]);\r\n\r\n // Click-to-add: listen for clicks on the map background when pinsEditable is true\r\n useEffect(() => {\r\n if (!ready || !mapRef.current || !pinsEditable || !onPinAdd) return;\r\n const map = mapRef.current;\r\n\r\n const handleClick = (e: L.LeafletMouseEvent) => {\r\n // Only fire on map background clicks — ignore clicks that bubbled from markers\r\n if ((e.originalEvent?.target as HTMLElement)?.closest?.('.leaflet-marker-icon')) return;\r\n onPinAdd({\r\n latitude: e.latlng.lat,\r\n longitude: e.latlng.lng,\r\n label: '',\r\n color: tokens.colors.accent.primary,\r\n });\r\n };\r\n\r\n map.on('click', handleClick);\r\n return () => { map.off('click', handleClick); };\r\n }, [ready, pinsEditable, onPinAdd, tokens.colors.accent.primary]);\r\n\r\n const handleRecenter = useCallback(() => {\r\n const map = mapRef.current;\r\n if (!map) return;\r\n const points: L.LatLngExpression[] = [];\r\n allSatellites.forEach((s) => {\r\n s.groundTrack.forEach((p) => points.push([p.latitude, p.longitude]));\r\n });\r\n groundStations.forEach((gs) => points.push([gs.latitude, gs.longitude]));\r\n if (points.length > 0) {\r\n const bounds = L.latLngBounds(points);\r\n map.fitBounds(bounds, { padding: [40, 40], maxZoom: 8 });\r\n } else {\r\n map.setView(defaultCenter, defaultZoom);\r\n }\r\n }, [allSatellites, groundStations, defaultCenter, defaultZoom]);\r\n\r\n const resolvedMinHeight = minHeight || (typeof height === 'number' ? `${height}px` : '400px');\r\n\r\n const isEmpty = allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0);\r\n\r\n return (\r\n <div\r\n className={`zendir-ground-track-map zendir-ground-track-map-leaflet${pinsEditable ? ' zendir-pins-editable' : ''} ${className}`}\r\n data-map-backend=\"leaflet\"\r\n style={{\r\n width,\r\n height,\r\n minHeight: resolvedMinHeight,\r\n backgroundColor: tokens.colors.background.base,\r\n borderRadius: 8,\r\n overflow: 'hidden',\r\n position: 'relative',\r\n }}\r\n >\r\n <div\r\n ref={containerRef}\r\n style={{ width: '100%', height: '100%', minHeight: resolvedMinHeight }}\r\n />\r\n {isEmpty && (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n left: '50%',\r\n top: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n color: tokens.colors.text.secondary,\r\n fontSize: 14,\r\n pointerEvents: 'none',\r\n }}\r\n >\r\n {emptyMessage}\r\n </div>\r\n )}\r\n {showRecenterButton && (\r\n <button\r\n type=\"button\"\r\n onClick={handleRecenter}\r\n title=\"Recenter map\"\r\n aria-label=\"Recenter map\"\r\n style={{\r\n position: 'absolute',\r\n top: 8,\r\n right: 50,\r\n zIndex: 1000,\r\n background: 'rgba(24, 29, 46, 0.9)',\r\n border: '1px solid rgba(157, 112, 255, 0.25)',\r\n borderRadius: 6,\r\n color: '#e4e0f0',\r\n cursor: 'pointer',\r\n padding: '6px 10px',\r\n fontSize: 12,\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: 4,\r\n }}\r\n >\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\r\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\r\n <path d=\"M12 2v4M12 18v4M2 12h4M18 12h4\" />\r\n </svg>\r\n Recenter\r\n </button>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default GroundTrackMapLeaflet;\r\n"],"names":[],"mappings":"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"}
|
|
@@ -7,4 +7,4 @@ export { AstroChart, LineChart, AreaChart, BarChart, ScatterChart, PieChart, Don
|
|
|
7
7
|
export type { AstroChartHandle, AstroChartProps, ChartType, ChartTheme, DataPoint, TimeSeriesPoint, SeriesData, LineStyleOptions, AreaStyleOptions, GradientOptions, SymbolOptions, AxisOptions, ZoomOptions, TooltipOptions, LegendOptions, BrushOptions, MarkLineOptions, MarkAreaOptions, AnnotationOptions, RealTimeOptions, ExportOptions, ChartEventParams, ZoomEventParams, BrushEventParams, PowerChartData, AttitudeChartData, ThermalChartData, LinkBudgetChartData, TelemetryStreamPoint, ContactWindowData, SpectrumDataPoint, ManeuverBudgetData, EclipseTimelineData, BubbleDataPoint, RoseDiagramData, HeliocentricOrbitData, ExportFileNameContext, Data3DPoint, Scatter3DData, Bar3DData, Surface3DData, Lines3DData, LineChartProps, AreaChartProps, BarChartProps, ScatterChartProps, PieChartProps, GaugeChartProps, RadarChartProps, HeatmapChartProps, TimeSeriesChartProps, DualAxisChartProps, BubbleChartProps, HistogramChartProps, SankeyChartProps, TreemapChartProps, TreemapDataNode, SunburstChartProps, SunburstDataNode, ParallelChartProps, ParallelAxisDef, CandlestickChartProps, CandlestickDataPoint, GraphChartProps, GraphNode, GraphLink, GraphCategory, // Domain Props Types
|
|
8
8
|
PowerChartProps, AttitudeChartProps, ThermalHeatmapChartProps, LinkBudgetChartProps, SubsystemGaugeProps, OrbitChartProps, ContactWindowChartProps, SpectrumChartProps, ManeuverBudgetChartProps, EclipseTimelineChartProps, RoseDiagramProps, HeliocentricOrbitProps, Scatter3DChartProps, Bar3DChartProps, Surface3DChartProps, Lines3DChartProps, SphericalRadar3DChartProps, SphericalRadar3DData, TransferOrbit3DChartProps, TransferOrbitSegment, TransferOrbitWaypoint, AttitudeHistory3DChartProps, AttitudeHistoryPoint, ConjunctionAssessment3DChartProps, ConjunctionEvent, LaunchCorridor3DChartProps, LaunchCorridorData, AntennaPattern3DChartProps, AntennaPattern3DData, WaterfallChartProps, DopplerTrackChartProps, LinkMarginChartProps, ConstellationCoverageChartProps, ConjunctionChartProps, WaterfallDataPoint, WaterfallSignalMarker, DopplerTrackPoint, SatellitePassInfo, LinkMarginPoint, CoverageGridData, PowerGeneratorConfig, AttitudeGeneratorConfig, DopplerGeneratorConfig, WaterfallGeneratorConfig, ContactGeneratorConfig, HeliocentricGeneratorConfig, SpectrumGeneratorConfig, EclipseGeneratorConfig, CoverageGeneratorConfig, ConjunctionGeneratorConfig, PowerOverviewChartProps, PowerTimelinePoint, PowerEvent, PowerState, OperationalMode, EventType, PowerEventType, StreamOptions, StreamController, WebSocketStreamOptions, WebSocketStreamController, UseChartSyncOptions, EChartsTheme, } from './unified';
|
|
9
9
|
export { GroundTrackMap } from './GroundTrackMap';
|
|
10
|
-
export type { GroundTrackMapProps, SatelliteTrack, GroundStationEnhanced, TeamPath, SROGroundStation } from './GroundTrackMap';
|
|
10
|
+
export type { GroundTrackMapProps, SatelliteTrack, GroundStationEnhanced, TeamPath, SROGroundStation, MapPin } from './GroundTrackMap';
|
|
@@ -76,22 +76,22 @@ export declare const ASTRO_DATA_VIZ_COLORS: {
|
|
|
76
76
|
*
|
|
77
77
|
* NOTE: Status colors (green, yellow, red) reserved for status indication only
|
|
78
78
|
*/
|
|
79
|
-
mixed: ("#3E3CFF" | "#9D70FF" | "#
|
|
79
|
+
mixed: ("#3E3CFF" | "#9D70FF" | "#6B69FF" | "#B48DFF" | "#3548C0" | "#9997FF" | "#C9A8FF" | "#1B2DA0" | "#3230CC" | "#7E5ACC" | "#4A5FD6")[];
|
|
80
80
|
/**
|
|
81
81
|
* Zendir Brand palette - Full brand spectrum
|
|
82
82
|
* Complete set of Zendir brand colors for maximum brand expression
|
|
83
83
|
*/
|
|
84
|
-
zendir: ("#3E3CFF" | "#9D70FF" | "#
|
|
84
|
+
zendir: ("#3E3CFF" | "#9D70FF" | "#6B69FF" | "#B48DFF" | "#3548C0" | "#9997FF" | "#C9A8FF" | "#1B2DA0")[];
|
|
85
85
|
/**
|
|
86
86
|
* Extended Zendir palette - All shades for complex visualizations
|
|
87
87
|
* Includes dark and light variants for maximum differentiation
|
|
88
88
|
*/
|
|
89
|
-
zendirExtended: ("#3E3CFF" | "#9D70FF" | "#
|
|
89
|
+
zendirExtended: ("#3E3CFF" | "#9D70FF" | "#6B69FF" | "#B48DFF" | "#3548C0" | "#9997FF" | "#C9A8FF" | "#1B2DA0" | "#3230CC" | "#7E5ACC" | "#142380")[];
|
|
90
90
|
/**
|
|
91
91
|
* Cool palette - Blues and purples
|
|
92
92
|
* Professional Zendir-branded cool tones
|
|
93
93
|
*/
|
|
94
|
-
cool: ("#3E3CFF" | "#9D70FF" | "#
|
|
94
|
+
cool: ("#3E3CFF" | "#9D70FF" | "#6B69FF" | "#B48DFF" | "#3548C0" | "#9997FF" | "#1B2DA0" | "#4A5FD6")[];
|
|
95
95
|
/**
|
|
96
96
|
* Warm accent palette - For charts needing warm tones
|
|
97
97
|
* Use sparingly - not part of Zendir brand
|
|
@@ -106,12 +106,12 @@ export declare const ASTRO_DATA_VIZ_COLORS: {
|
|
|
106
106
|
* Prussian-Electric gradient palette
|
|
107
107
|
* Deep to vibrant brand progression
|
|
108
108
|
*/
|
|
109
|
-
prussianElectric: ("#3E3CFF" | "#
|
|
109
|
+
prussianElectric: ("#3E3CFF" | "#6B69FF" | "#3548C0" | "#1B2DA0" | "#3230CC" | "#142380")[];
|
|
110
110
|
/**
|
|
111
111
|
* Full Zendir spectrum gradient
|
|
112
112
|
* Prussian Blue → Electric → Purple
|
|
113
113
|
*/
|
|
114
|
-
zendirSpectrum: ("#3E3CFF" | "#9D70FF" | "#
|
|
114
|
+
zendirSpectrum: ("#3E3CFF" | "#9D70FF" | "#6B69FF" | "#B48DFF" | "#3548C0" | "#1B2DA0")[];
|
|
115
115
|
/**
|
|
116
116
|
* Sequential palettes for heatmaps and gradual data
|
|
117
117
|
* Zendir brand-forward gradients
|
|
@@ -384,7 +384,7 @@ export declare function getStatusColor(value: number, thresholds: {
|
|
|
384
384
|
* const brandColor = getSeriesColor(0, ASTRO_DATA_VIZ_COLORS.zendir);
|
|
385
385
|
* ```
|
|
386
386
|
*/
|
|
387
|
-
export declare function getSeriesColor(index: number, palette?: ("#3E3CFF" | "#9D70FF" | "#
|
|
387
|
+
export declare function getSeriesColor(index: number, palette?: ("#3E3CFF" | "#9D70FF" | "#6B69FF" | "#B48DFF" | "#3548C0" | "#9997FF" | "#C9A8FF" | "#1B2DA0" | "#3230CC" | "#7E5ACC" | "#4A5FD6")[]): string;
|
|
388
388
|
/**
|
|
389
389
|
* Get Zendir brand color by role
|
|
390
390
|
* Use for consistent branding across the application
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { default as React, ReactNode } from 'react';
|
|
2
|
+
import { CategoryPalette } from '../utils/categoryPalette';
|
|
3
|
+
import { CategoryDef } from '../types';
|
|
4
|
+
|
|
5
|
+
export interface CategoryContextValue {
|
|
6
|
+
/** Palette instance built from the provided categories. */
|
|
7
|
+
palette: CategoryPalette;
|
|
8
|
+
/** All registered category definitions. */
|
|
9
|
+
categories: CategoryDef[];
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable label for the grouping dimension.
|
|
12
|
+
* Defaults to `"Categories"`. Components that support a label override
|
|
13
|
+
* (e.g. Timeline's `teamLabel`) should fall back to this when no
|
|
14
|
+
* explicit prop is provided.
|
|
15
|
+
*/
|
|
16
|
+
categoryLabel: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CategoryProviderProps {
|
|
19
|
+
/** Category definitions — each must have `id`, `label`, and `color`. */
|
|
20
|
+
categories: CategoryDef[];
|
|
21
|
+
/**
|
|
22
|
+
* Display label for the grouping dimension, e.g. `"Teams"`, `"Squads"`,
|
|
23
|
+
* `"Subsystems"`. Defaults to `"Categories"`.
|
|
24
|
+
*/
|
|
25
|
+
categoryLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional custom fallback palette for auto-assigned unknown ids.
|
|
28
|
+
* Defaults to the Astro data-viz palette built into `CategoryPalette`.
|
|
29
|
+
*/
|
|
30
|
+
fallbackPalette?: string[];
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Provides a shared `CategoryPalette` to descendant components.
|
|
35
|
+
*
|
|
36
|
+
* Wrap a dashboard section (not the entire app) so sibling components
|
|
37
|
+
* like Timeline + Map + Charts resolve the same colors for the same ids.
|
|
38
|
+
*/
|
|
39
|
+
export declare function CategoryProvider({ categories, categoryLabel, fallbackPalette, children, }: CategoryProviderProps): React.ReactElement;
|
|
40
|
+
/**
|
|
41
|
+
* Access the nearest `CategoryProvider`'s palette and label.
|
|
42
|
+
*
|
|
43
|
+
* Returns `null` when no provider is present — components should treat
|
|
44
|
+
* this as "standalone mode" and fall back to explicit props / defaults.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useCategoryPalette(): CategoryContextValue | null;
|
|
47
|
+
/**
|
|
48
|
+
* Like `useCategoryPalette()` but throws if no provider is found.
|
|
49
|
+
* Use only when you know a provider is always present.
|
|
50
|
+
*/
|
|
51
|
+
export declare function useCategoryPaletteRequired(): CategoryContextValue;
|