@zendir/ui 0.2.1 → 0.2.3
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 +118 -58
- package/dist/react/astro/UnifiedTimeline.js +25 -2
- package/dist/react/astro/UnifiedTimeline.js.map +1 -1
- package/dist/react/charts/GroundTrackMap.d.ts +45 -1
- package/dist/react/charts/GroundTrackMap.js +86 -1
- package/dist/react/charts/GroundTrackMap.js.map +1 -1
- package/dist/react/charts/GroundTrackMapLeaflet.d.ts +15 -2
- package/dist/react/charts/GroundTrackMapLeaflet.js +226 -57
- package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
- package/dist/react/charts/groundTrackMapLeafletTiles.d.ts +10 -8
- package/dist/react/charts/groundTrackMapLeafletTiles.js +10 -3
- package/dist/react/charts/groundTrackMapLeafletTiles.js.map +1 -1
- package/dist/react/charts/index.d.ts +2 -1
- package/dist/react/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import L from "leaflet";
|
|
|
4
4
|
import "leaflet/dist/leaflet.css";
|
|
5
5
|
/* empty css */
|
|
6
6
|
import { geodesicCirclePoints, splitRingAtAntimeridian, segmentsWithWorldCopies, splitPolylineAtAntimeridian } from "./groundTrackMapLeafletUtils.js";
|
|
7
|
-
import {
|
|
7
|
+
import { CARTO_ATTRIBUTION, OSM_ATTRIBUTION, TILE_PRESETS, FALLBACK_TILE } from "./groundTrackMapLeafletTiles.js";
|
|
8
8
|
import { useTheme } from "../theme/ThemeProvider.js";
|
|
9
9
|
const STATUS_COLOR_MAP = {
|
|
10
10
|
normal: "#56f000",
|
|
@@ -143,13 +143,16 @@ function GroundTrackMapLeaflet({
|
|
|
143
143
|
showLegend = true,
|
|
144
144
|
showEquator = false,
|
|
145
145
|
showRecenterButton = true,
|
|
146
|
+
showMapStyleToggle = true,
|
|
146
147
|
defaultCenter = [20, 0],
|
|
147
148
|
defaultZoom = 2,
|
|
148
149
|
height = "100%",
|
|
149
150
|
width = "100%",
|
|
150
151
|
minHeight = "400px",
|
|
151
152
|
emptyMessage = "No orbital data available",
|
|
152
|
-
tileUrl
|
|
153
|
+
tileUrl,
|
|
154
|
+
nightTileUrl,
|
|
155
|
+
lightSources,
|
|
153
156
|
className = "",
|
|
154
157
|
onSatelliteClick,
|
|
155
158
|
onStationClick,
|
|
@@ -167,6 +170,9 @@ function GroundTrackMapLeaflet({
|
|
|
167
170
|
const controlsRef = useRef([]);
|
|
168
171
|
const pinsGroupRef = useRef(null);
|
|
169
172
|
const [ready, setReady] = useState(false);
|
|
173
|
+
const [tileStyle, setTileStyle] = useState("dark");
|
|
174
|
+
const isExplicitTileUrl = tileUrl !== void 0;
|
|
175
|
+
const effectiveTileUrl = isExplicitTileUrl ? tileUrl : TILE_PRESETS[tileStyle];
|
|
170
176
|
const clearLayers = useCallback(() => {
|
|
171
177
|
var _a;
|
|
172
178
|
const map = mapRef.current;
|
|
@@ -200,38 +206,6 @@ function GroundTrackMapLeaflet({
|
|
|
200
206
|
});
|
|
201
207
|
L.control.zoom({ position: "topright" }).addTo(map);
|
|
202
208
|
map.attributionControl.setPrefix("");
|
|
203
|
-
const isCartoTiles = tileUrl.includes("cartocdn");
|
|
204
|
-
if (isCartoTiles) {
|
|
205
|
-
map.attributionControl.addAttribution(CARTO_ATTRIBUTION);
|
|
206
|
-
}
|
|
207
|
-
const tileOptions = {
|
|
208
|
-
maxZoom: 19,
|
|
209
|
-
subdomains: isCartoTiles ? "abcd" : "abc",
|
|
210
|
-
// crossOrigin avoids tainted-canvas errors when Leaflet tries to read tile pixels
|
|
211
|
-
crossOrigin: true
|
|
212
|
-
};
|
|
213
|
-
const tile = L.tileLayer(tileUrl, tileOptions);
|
|
214
|
-
tile.addTo(map);
|
|
215
|
-
tileLayerRef.current = tile;
|
|
216
|
-
let hasSwitchedToFallback = false;
|
|
217
|
-
const onTileError = () => {
|
|
218
|
-
if (hasSwitchedToFallback) return;
|
|
219
|
-
hasSwitchedToFallback = true;
|
|
220
|
-
tile.off("tileerror", onTileError);
|
|
221
|
-
tile.remove();
|
|
222
|
-
if (isCartoTiles) {
|
|
223
|
-
map.attributionControl.removeAttribution(CARTO_ATTRIBUTION);
|
|
224
|
-
map.attributionControl.addAttribution(OSM_ATTRIBUTION);
|
|
225
|
-
}
|
|
226
|
-
const fallback = L.tileLayer(FALLBACK_TILE, {
|
|
227
|
-
maxZoom: 19,
|
|
228
|
-
subdomains: "abc",
|
|
229
|
-
crossOrigin: true
|
|
230
|
-
});
|
|
231
|
-
fallback.addTo(map);
|
|
232
|
-
tileLayerRef.current = fallback;
|
|
233
|
-
};
|
|
234
|
-
tile.on("tileerror", onTileError);
|
|
235
209
|
const overlayGroup = L.layerGroup();
|
|
236
210
|
overlayGroup.addTo(map);
|
|
237
211
|
overlayGroupRef.current = overlayGroup;
|
|
@@ -259,11 +233,61 @@ function GroundTrackMapLeaflet({
|
|
|
259
233
|
mapRef.current = null;
|
|
260
234
|
overlayGroupRef.current = null;
|
|
261
235
|
};
|
|
262
|
-
}, [
|
|
236
|
+
}, [clearLayers]);
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
const map = mapRef.current;
|
|
239
|
+
if (!map || !ready) return;
|
|
240
|
+
if (tileLayerRef.current) {
|
|
241
|
+
tileLayerRef.current.remove();
|
|
242
|
+
tileLayerRef.current = null;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
map.attributionControl.removeAttribution(CARTO_ATTRIBUTION);
|
|
246
|
+
map.attributionControl.removeAttribution(OSM_ATTRIBUTION);
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
const isCartoTiles = effectiveTileUrl.includes("cartocdn");
|
|
250
|
+
if (isCartoTiles) {
|
|
251
|
+
map.attributionControl.addAttribution(CARTO_ATTRIBUTION);
|
|
252
|
+
}
|
|
253
|
+
const tile = L.tileLayer(effectiveTileUrl, {
|
|
254
|
+
maxZoom: 19,
|
|
255
|
+
subdomains: isCartoTiles ? "abcd" : "abc",
|
|
256
|
+
crossOrigin: true
|
|
257
|
+
});
|
|
258
|
+
tile.addTo(map);
|
|
259
|
+
tile.bringToBack();
|
|
260
|
+
tileLayerRef.current = tile;
|
|
261
|
+
let hasSwitchedToFallback = false;
|
|
262
|
+
const onTileError = () => {
|
|
263
|
+
if (hasSwitchedToFallback) return;
|
|
264
|
+
hasSwitchedToFallback = true;
|
|
265
|
+
tile.off("tileerror", onTileError);
|
|
266
|
+
tile.remove();
|
|
267
|
+
const fallback = L.tileLayer(FALLBACK_TILE, {
|
|
268
|
+
maxZoom: 19,
|
|
269
|
+
subdomains: "abc",
|
|
270
|
+
crossOrigin: true
|
|
271
|
+
});
|
|
272
|
+
fallback.addTo(map);
|
|
273
|
+
fallback.bringToBack();
|
|
274
|
+
tileLayerRef.current = fallback;
|
|
275
|
+
};
|
|
276
|
+
tile.on("tileerror", onTileError);
|
|
277
|
+
}, [effectiveTileUrl, ready]);
|
|
263
278
|
useEffect(() => {
|
|
264
279
|
if (!ready || !mapRef.current) return;
|
|
265
280
|
const map = mapRef.current;
|
|
266
281
|
clearLayers();
|
|
282
|
+
if (nightTileUrl && showTerminator) {
|
|
283
|
+
const nightTile = L.tileLayer(nightTileUrl, {
|
|
284
|
+
maxZoom: 19,
|
|
285
|
+
crossOrigin: true,
|
|
286
|
+
opacity: 0.7,
|
|
287
|
+
className: "zendir-night-tiles"
|
|
288
|
+
});
|
|
289
|
+
addLayer(nightTile);
|
|
290
|
+
}
|
|
267
291
|
if (showTerminator) {
|
|
268
292
|
const now = terminatorTime ?? /* @__PURE__ */ new Date();
|
|
269
293
|
const BAND_STEP = 2;
|
|
@@ -297,6 +321,75 @@ function GroundTrackMapLeaflet({
|
|
|
297
321
|
});
|
|
298
322
|
prevOpacity = b.opacity;
|
|
299
323
|
}
|
|
324
|
+
const terminatorEdge = calculateTerminatorContinuous(now, 0);
|
|
325
|
+
if (terminatorEdge.sunset.length > 2) {
|
|
326
|
+
const sunsetLine = terminatorEdge.sunset.map(([lat, lon]) => [lat, lon]);
|
|
327
|
+
const sunriseLine = terminatorEdge.sunrise.map(([lat, lon]) => [lat, lon]);
|
|
328
|
+
[0, 360, -360].forEach((offset) => {
|
|
329
|
+
addLayer(L.polyline(
|
|
330
|
+
sunsetLine.map(([lat, lon]) => [lat, lon + offset]),
|
|
331
|
+
{ color: "#5a8ec8", weight: 4, opacity: 0.12, interactive: false, smoothFactor: 1.5 }
|
|
332
|
+
));
|
|
333
|
+
addLayer(L.polyline(
|
|
334
|
+
sunriseLine.map(([lat, lon]) => [lat, lon + offset]),
|
|
335
|
+
{ color: "#5a8ec8", weight: 4, opacity: 0.12, interactive: false, smoothFactor: 1.5 }
|
|
336
|
+
));
|
|
337
|
+
addLayer(L.polyline(
|
|
338
|
+
sunsetLine.map(([lat, lon]) => [lat, lon + offset]),
|
|
339
|
+
{ color: "#7aa4d4", weight: 1, opacity: 0.5, interactive: false, smoothFactor: 1.5 }
|
|
340
|
+
));
|
|
341
|
+
addLayer(L.polyline(
|
|
342
|
+
sunriseLine.map(([lat, lon]) => [lat, lon + offset]),
|
|
343
|
+
{ color: "#7aa4d4", weight: 1, opacity: 0.5, interactive: false, smoothFactor: 1.5 }
|
|
344
|
+
));
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (lightSources && lightSources.length > 0 && showTerminator) {
|
|
349
|
+
const now = terminatorTime ?? /* @__PURE__ */ new Date();
|
|
350
|
+
const dayOfYear = Math.floor((now.getTime() - new Date(now.getFullYear(), 0, 0).getTime()) / 864e5);
|
|
351
|
+
const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
|
|
352
|
+
const decRad = declination * Math.PI / 180;
|
|
353
|
+
const hourAngle = (now.getUTCHours() + now.getUTCMinutes() / 60) / 24 * 360 - 180;
|
|
354
|
+
for (const light of lightSources) {
|
|
355
|
+
const latRad = light.latitude * Math.PI / 180;
|
|
356
|
+
const sinAlt = Math.sin(latRad) * Math.sin(decRad) + Math.cos(latRad) * Math.cos(decRad) * Math.cos((light.longitude - hourAngle) * Math.PI / 180);
|
|
357
|
+
const solarAltDeg = Math.asin(sinAlt) * 180 / Math.PI;
|
|
358
|
+
let nightFactor;
|
|
359
|
+
if (solarAltDeg >= 0) nightFactor = 0;
|
|
360
|
+
else if (solarAltDeg <= -18) nightFactor = 1;
|
|
361
|
+
else nightFactor = Math.min(1, -solarAltDeg / 18);
|
|
362
|
+
if (nightFactor < 0.01) continue;
|
|
363
|
+
const r = light.radius ?? 4;
|
|
364
|
+
const intensity = light.intensity ?? 0.8;
|
|
365
|
+
const color = light.color ?? "#ffeedd";
|
|
366
|
+
const alpha = intensity * nightFactor;
|
|
367
|
+
const glowMarker = L.circleMarker([light.latitude, light.longitude], {
|
|
368
|
+
radius: r * 2,
|
|
369
|
+
fillColor: color,
|
|
370
|
+
fillOpacity: alpha * 0.4,
|
|
371
|
+
color,
|
|
372
|
+
weight: 0,
|
|
373
|
+
interactive: !!light.label
|
|
374
|
+
});
|
|
375
|
+
addLayer(glowMarker);
|
|
376
|
+
const coreMarker = L.circleMarker([light.latitude, light.longitude], {
|
|
377
|
+
radius: Math.max(1, r * 0.5),
|
|
378
|
+
fillColor: color,
|
|
379
|
+
fillOpacity: Math.min(1, alpha * 1.2),
|
|
380
|
+
color: "transparent",
|
|
381
|
+
weight: 0,
|
|
382
|
+
interactive: false
|
|
383
|
+
});
|
|
384
|
+
addLayer(coreMarker);
|
|
385
|
+
if (light.label) {
|
|
386
|
+
glowMarker.bindTooltip(light.label, {
|
|
387
|
+
permanent: false,
|
|
388
|
+
direction: "top",
|
|
389
|
+
className: "zendir-leaflet-tooltip"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
300
393
|
}
|
|
301
394
|
if (showGrid) {
|
|
302
395
|
const gridStyle = { color: "rgba(157, 112, 255, 0.12)", weight: 0.5, interactive: false };
|
|
@@ -492,6 +585,8 @@ function GroundTrackMapLeaflet({
|
|
|
492
585
|
groundStations,
|
|
493
586
|
showTerminator,
|
|
494
587
|
terminatorTime,
|
|
588
|
+
nightTileUrl,
|
|
589
|
+
lightSources,
|
|
495
590
|
showGrid,
|
|
496
591
|
showEquator,
|
|
497
592
|
showLegend,
|
|
@@ -630,35 +725,109 @@ function GroundTrackMapLeaflet({
|
|
|
630
725
|
children: emptyMessage
|
|
631
726
|
}
|
|
632
727
|
),
|
|
633
|
-
showRecenterButton && /* @__PURE__ */ jsxs(
|
|
634
|
-
"
|
|
728
|
+
(showRecenterButton || showMapStyleToggle && !isExplicitTileUrl) && /* @__PURE__ */ jsxs(
|
|
729
|
+
"div",
|
|
635
730
|
{
|
|
636
|
-
|
|
637
|
-
onClick: handleRecenter,
|
|
638
|
-
title: "Recenter map",
|
|
639
|
-
"aria-label": "Recenter map",
|
|
731
|
+
className: "zendir-map-controls",
|
|
640
732
|
style: {
|
|
641
733
|
position: "absolute",
|
|
642
|
-
|
|
643
|
-
|
|
734
|
+
bottom: 28,
|
|
735
|
+
left: 10,
|
|
644
736
|
zIndex: 1e3,
|
|
645
|
-
background: "rgba(24, 29, 46, 0.9)",
|
|
646
|
-
border: "1px solid rgba(157, 112, 255, 0.25)",
|
|
647
|
-
borderRadius: 6,
|
|
648
|
-
color: "#e4e0f0",
|
|
649
|
-
cursor: "pointer",
|
|
650
|
-
padding: "6px 10px",
|
|
651
|
-
fontSize: 12,
|
|
652
737
|
display: "flex",
|
|
653
|
-
|
|
654
|
-
gap:
|
|
738
|
+
flexDirection: "row",
|
|
739
|
+
gap: 6,
|
|
740
|
+
alignItems: "flex-end"
|
|
655
741
|
},
|
|
656
742
|
children: [
|
|
657
|
-
/* @__PURE__ */ jsxs(
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
743
|
+
showRecenterButton && /* @__PURE__ */ jsxs(
|
|
744
|
+
"button",
|
|
745
|
+
{
|
|
746
|
+
type: "button",
|
|
747
|
+
onClick: handleRecenter,
|
|
748
|
+
title: "Recenter map to fit all assets and ground stations",
|
|
749
|
+
"aria-label": "Recenter map",
|
|
750
|
+
style: {
|
|
751
|
+
background: "rgba(20, 24, 38, 0.92)",
|
|
752
|
+
backdropFilter: "blur(8px)",
|
|
753
|
+
WebkitBackdropFilter: "blur(8px)",
|
|
754
|
+
border: "1px solid rgba(120, 100, 180, 0.2)",
|
|
755
|
+
borderRadius: 4,
|
|
756
|
+
color: "#c8c0d8",
|
|
757
|
+
cursor: "pointer",
|
|
758
|
+
padding: "6px 10px",
|
|
759
|
+
fontSize: 11,
|
|
760
|
+
fontWeight: 500,
|
|
761
|
+
display: "flex",
|
|
762
|
+
alignItems: "center",
|
|
763
|
+
gap: 5,
|
|
764
|
+
transition: "background 0.15s, border-color 0.15s",
|
|
765
|
+
letterSpacing: "0.02em"
|
|
766
|
+
},
|
|
767
|
+
onMouseEnter: (e) => {
|
|
768
|
+
e.currentTarget.style.background = "rgba(30, 34, 52, 0.95)";
|
|
769
|
+
e.currentTarget.style.borderColor = "rgba(157, 112, 255, 0.4)";
|
|
770
|
+
},
|
|
771
|
+
onMouseLeave: (e) => {
|
|
772
|
+
e.currentTarget.style.background = "rgba(20, 24, 38, 0.92)";
|
|
773
|
+
e.currentTarget.style.borderColor = "rgba(120, 100, 180, 0.2)";
|
|
774
|
+
},
|
|
775
|
+
children: [
|
|
776
|
+
/* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
777
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
|
|
778
|
+
/* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })
|
|
779
|
+
] }),
|
|
780
|
+
"Recenter"
|
|
781
|
+
]
|
|
782
|
+
}
|
|
783
|
+
),
|
|
784
|
+
showMapStyleToggle && !isExplicitTileUrl && /* @__PURE__ */ jsx(
|
|
785
|
+
"div",
|
|
786
|
+
{
|
|
787
|
+
style: {
|
|
788
|
+
background: "rgba(20, 24, 38, 0.92)",
|
|
789
|
+
backdropFilter: "blur(8px)",
|
|
790
|
+
WebkitBackdropFilter: "blur(8px)",
|
|
791
|
+
border: "1px solid rgba(120, 100, 180, 0.2)",
|
|
792
|
+
borderRadius: 4,
|
|
793
|
+
display: "flex",
|
|
794
|
+
overflow: "hidden"
|
|
795
|
+
},
|
|
796
|
+
children: ["dark", "satellite"].map((style) => /* @__PURE__ */ jsxs(
|
|
797
|
+
"button",
|
|
798
|
+
{
|
|
799
|
+
type: "button",
|
|
800
|
+
onClick: () => setTileStyle(style),
|
|
801
|
+
title: style === "dark" ? "Dark ops map" : "Satellite imagery",
|
|
802
|
+
"aria-label": style === "dark" ? "Dark map style" : "Satellite imagery",
|
|
803
|
+
"aria-pressed": tileStyle === style,
|
|
804
|
+
style: {
|
|
805
|
+
background: tileStyle === style ? "rgba(157, 112, 255, 0.22)" : "transparent",
|
|
806
|
+
border: "none",
|
|
807
|
+
borderRight: style === "dark" ? "1px solid rgba(120, 100, 180, 0.15)" : "none",
|
|
808
|
+
color: tileStyle === style ? "#d0c4ee" : "#7a748e",
|
|
809
|
+
cursor: "pointer",
|
|
810
|
+
padding: "5px 8px",
|
|
811
|
+
fontSize: 10,
|
|
812
|
+
fontWeight: tileStyle === style ? 600 : 400,
|
|
813
|
+
display: "flex",
|
|
814
|
+
alignItems: "center",
|
|
815
|
+
gap: 4,
|
|
816
|
+
transition: "all 0.15s ease",
|
|
817
|
+
letterSpacing: "0.02em"
|
|
818
|
+
},
|
|
819
|
+
children: [
|
|
820
|
+
style === "dark" ? /* @__PURE__ */ jsx("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" }) }) : /* @__PURE__ */ jsxs("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
821
|
+
/* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
|
|
822
|
+
/* @__PURE__ */ jsx("path", { d: "M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" })
|
|
823
|
+
] }),
|
|
824
|
+
style === "dark" ? "Dark" : "Satellite"
|
|
825
|
+
]
|
|
826
|
+
},
|
|
827
|
+
style
|
|
828
|
+
))
|
|
829
|
+
}
|
|
830
|
+
)
|
|
662
831
|
]
|
|
663
832
|
}
|
|
664
833
|
)
|