@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
|
@@ -83,6 +83,29 @@ export interface MapPin {
|
|
|
83
83
|
/** Who placed the pin (display-only; not enforced) */
|
|
84
84
|
createdBy?: string;
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A point light source rendered on the night side of the map.
|
|
88
|
+
* Use this to display city lights, base locations, RF emitters,
|
|
89
|
+
* or any glow that should only be visible after sunset.
|
|
90
|
+
*
|
|
91
|
+
* Light sources are masked by the terminator — they fade in
|
|
92
|
+
* through the twilight gradient and reach full brightness in
|
|
93
|
+
* deep night, matching real-world light-pollution behavior.
|
|
94
|
+
*/
|
|
95
|
+
export interface LightSource {
|
|
96
|
+
/** Latitude in degrees (−90 … 90) */
|
|
97
|
+
latitude: number;
|
|
98
|
+
/** Longitude in degrees (−180 … 180) */
|
|
99
|
+
longitude: number;
|
|
100
|
+
/** Glow radius in pixels (default 4). Larger values create wider halos. */
|
|
101
|
+
radius?: number;
|
|
102
|
+
/** Glow intensity 0–1 (default 0.8). Controls the peak alpha of the light dot. */
|
|
103
|
+
intensity?: number;
|
|
104
|
+
/** Light color — any CSS color string (default '#ffeedd', warm white). */
|
|
105
|
+
color?: string;
|
|
106
|
+
/** Optional label shown on hover (Leaflet) or in tooltip (Canvas). */
|
|
107
|
+
label?: string;
|
|
108
|
+
}
|
|
86
109
|
export interface GroundTrackMapProps {
|
|
87
110
|
/** Single satellite ground track (legacy API) */
|
|
88
111
|
groundTrack?: GroundTrackPoint[];
|
|
@@ -108,6 +131,8 @@ export interface GroundTrackMapProps {
|
|
|
108
131
|
showEquator?: boolean;
|
|
109
132
|
/** Show recenter button (SRO compat) */
|
|
110
133
|
showRecenterButton?: boolean;
|
|
134
|
+
/** Show a toggle button to switch between Dark and Satellite tile styles. Default true for Leaflet. */
|
|
135
|
+
showMapStyleToggle?: boolean;
|
|
111
136
|
/** Whether data is still loading (SRO compat) */
|
|
112
137
|
isLoading?: boolean;
|
|
113
138
|
/** Message when there is no data (SRO compat) */
|
|
@@ -132,6 +157,25 @@ export interface GroundTrackMapProps {
|
|
|
132
157
|
mapProvider?: 'leaflet' | 'canvas';
|
|
133
158
|
/** Tile URL for Leaflet (default: CartoDB dark). Ignored when mapProvider is 'canvas'. */
|
|
134
159
|
tileUrl?: string;
|
|
160
|
+
/**
|
|
161
|
+
* URL template for a "night" tile layer (e.g. NASA Black Marble, VIIRS city lights).
|
|
162
|
+
* When provided, this layer is rendered beneath the terminator overlay so that it
|
|
163
|
+
* appears only on the night side of the map — creating a realistic day/night transition.
|
|
164
|
+
* The day tiles remain visible on the sunlit side.
|
|
165
|
+
*
|
|
166
|
+
* Requires `showTerminator` to be enabled. Leaflet backend only.
|
|
167
|
+
*
|
|
168
|
+
* Example: `'https://map1.vis.earthdata.nasa.gov/wmts-webmerc/VIIRS_CityLights_2012/...'`
|
|
169
|
+
*/
|
|
170
|
+
nightTileUrl?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Array of point light sources rendered on the night side of the map.
|
|
173
|
+
* Each light appears as a soft glow dot that is masked by the terminator —
|
|
174
|
+
* invisible in daylight, fading in through twilight, full brightness at night.
|
|
175
|
+
*
|
|
176
|
+
* Use cases: city lights, base indicators, RF emitters, targets, population centers.
|
|
177
|
+
*/
|
|
178
|
+
lightSources?: LightSource[];
|
|
135
179
|
/** Array of user-placed pins displayed on the map */
|
|
136
180
|
pins?: MapPin[];
|
|
137
181
|
/**
|
|
@@ -147,5 +191,5 @@ export interface GroundTrackMapProps {
|
|
|
147
191
|
/** Called when the user removes a pin */
|
|
148
192
|
onPinRemove?: (pinId: string) => void;
|
|
149
193
|
}
|
|
150
|
-
export declare function GroundTrackMap({ groundTrack, satellites, groundStations, accessMask, teamPaths, showTerminator, terminatorTime, showGrid, showLegend, showEquator, showRecenterButton, isLoading, emptyMessage, width, height, minHeight, defaultCenter, defaultZoom, className, onSatelliteClick, onStationClick, mapProvider, tileUrl, pins, pinsEditable, onPinAdd, onPinUpdate, onPinRemove, }: GroundTrackMapProps): React.ReactElement;
|
|
194
|
+
export declare function GroundTrackMap({ groundTrack, satellites, groundStations, accessMask, teamPaths, showTerminator, terminatorTime, showGrid, showLegend, showEquator, showRecenterButton, showMapStyleToggle, isLoading, emptyMessage, width, height, minHeight, defaultCenter, defaultZoom, className, onSatelliteClick, onStationClick, mapProvider, tileUrl, nightTileUrl, lightSources, pins, pinsEditable, onPinAdd, onPinUpdate, onPinRemove, }: GroundTrackMapProps): React.ReactElement;
|
|
151
195
|
export default GroundTrackMap;
|
|
@@ -68,6 +68,16 @@ function drawCoverageEllipse(ctx, centerLat, centerLon, radiusDeg, W, H, lonToX,
|
|
|
68
68
|
}
|
|
69
69
|
ctx.closePath();
|
|
70
70
|
}
|
|
71
|
+
function hexToRgb(hex) {
|
|
72
|
+
const h = hex.replace("#", "");
|
|
73
|
+
if (h.length === 3) {
|
|
74
|
+
return [parseInt(h[0] + h[0], 16), parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16)];
|
|
75
|
+
}
|
|
76
|
+
if (h.length >= 6) {
|
|
77
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
78
|
+
}
|
|
79
|
+
return [255, 238, 221];
|
|
80
|
+
}
|
|
71
81
|
function calculateTerminatorContinuous(date, depressionDeg = 0, numPoints = 360) {
|
|
72
82
|
const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 864e5);
|
|
73
83
|
const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
|
|
@@ -112,6 +122,7 @@ function GroundTrackMap({
|
|
|
112
122
|
showLegend = true,
|
|
113
123
|
showEquator = true,
|
|
114
124
|
showRecenterButton = false,
|
|
125
|
+
showMapStyleToggle = true,
|
|
115
126
|
isLoading = false,
|
|
116
127
|
emptyMessage = "No orbital data available",
|
|
117
128
|
width = "100%",
|
|
@@ -124,6 +135,8 @@ function GroundTrackMap({
|
|
|
124
135
|
onStationClick,
|
|
125
136
|
mapProvider = "leaflet",
|
|
126
137
|
tileUrl,
|
|
138
|
+
nightTileUrl,
|
|
139
|
+
lightSources,
|
|
127
140
|
pins,
|
|
128
141
|
pinsEditable = false,
|
|
129
142
|
onPinAdd,
|
|
@@ -261,6 +274,39 @@ function GroundTrackMap({
|
|
|
261
274
|
}
|
|
262
275
|
prevAlpha = zone.alpha;
|
|
263
276
|
}
|
|
277
|
+
const terminatorEdge = calculateTerminatorContinuous(now, 0);
|
|
278
|
+
if (terminatorEdge.sunset.length > 2) {
|
|
279
|
+
ctx.save();
|
|
280
|
+
ctx.strokeStyle = "rgba(90, 142, 200, 0.12)";
|
|
281
|
+
ctx.lineWidth = 4;
|
|
282
|
+
for (const curve of [terminatorEdge.sunset, terminatorEdge.sunrise]) {
|
|
283
|
+
for (const offset of [-360, 0, 360]) {
|
|
284
|
+
ctx.beginPath();
|
|
285
|
+
for (let i = 0; i < curve.length; i++) {
|
|
286
|
+
const x = lonToX(curve[i][1] + offset, W);
|
|
287
|
+
const y = latToY(curve[i][0], H);
|
|
288
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
289
|
+
else ctx.lineTo(x, y);
|
|
290
|
+
}
|
|
291
|
+
ctx.stroke();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
ctx.strokeStyle = "rgba(122, 164, 212, 0.5)";
|
|
295
|
+
ctx.lineWidth = 1;
|
|
296
|
+
for (const curve of [terminatorEdge.sunset, terminatorEdge.sunrise]) {
|
|
297
|
+
for (const offset of [-360, 0, 360]) {
|
|
298
|
+
ctx.beginPath();
|
|
299
|
+
for (let i = 0; i < curve.length; i++) {
|
|
300
|
+
const x = lonToX(curve[i][1] + offset, W);
|
|
301
|
+
const y = latToY(curve[i][0], H);
|
|
302
|
+
if (i === 0) ctx.moveTo(x, y);
|
|
303
|
+
else ctx.lineTo(x, y);
|
|
304
|
+
}
|
|
305
|
+
ctx.stroke();
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
ctx.restore();
|
|
309
|
+
}
|
|
264
310
|
}
|
|
265
311
|
ctx.fillStyle = COLORS.land;
|
|
266
312
|
ctx.strokeStyle = "rgba(100, 120, 160, 0.25)";
|
|
@@ -483,6 +529,42 @@ function GroundTrackMap({
|
|
|
483
529
|
ctx.textAlign = "center";
|
|
484
530
|
ctx.fillText(gs.name.length > 14 ? gs.name.slice(0, 14) + "…" : gs.name, x, y + halfIcon + 12);
|
|
485
531
|
}
|
|
532
|
+
if (lightSources && lightSources.length > 0 && showTerminator) {
|
|
533
|
+
const now = terminatorTime ?? /* @__PURE__ */ new Date();
|
|
534
|
+
for (const light of lightSources) {
|
|
535
|
+
const lx = lonToX(light.longitude, W);
|
|
536
|
+
const ly = latToY(light.latitude, H);
|
|
537
|
+
const r = light.radius ?? 4;
|
|
538
|
+
const intensity = light.intensity ?? 0.8;
|
|
539
|
+
const color = light.color ?? "#ffeedd";
|
|
540
|
+
const dayOfYear = Math.floor((now.getTime() - new Date(now.getFullYear(), 0, 0).getTime()) / 864e5);
|
|
541
|
+
const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
|
|
542
|
+
const decRad = declination * Math.PI / 180;
|
|
543
|
+
const hourAngle = (now.getUTCHours() + now.getUTCMinutes() / 60) / 24 * 360 - 180;
|
|
544
|
+
const latRad = light.latitude * Math.PI / 180;
|
|
545
|
+
const sinAlt = Math.sin(latRad) * Math.sin(decRad) + Math.cos(latRad) * Math.cos(decRad) * Math.cos((light.longitude - hourAngle) * Math.PI / 180);
|
|
546
|
+
const solarAltDeg = Math.asin(sinAlt) * 180 / Math.PI;
|
|
547
|
+
let nightFactor;
|
|
548
|
+
if (solarAltDeg >= 0) nightFactor = 0;
|
|
549
|
+
else if (solarAltDeg <= -18) nightFactor = 1;
|
|
550
|
+
else nightFactor = Math.min(1, -solarAltDeg / 18);
|
|
551
|
+
if (nightFactor < 0.01) continue;
|
|
552
|
+
const alpha = intensity * nightFactor;
|
|
553
|
+
const [cr, cg, cb] = hexToRgb(color);
|
|
554
|
+
const grad = ctx.createRadialGradient(lx, ly, 0, lx, ly, r * 2);
|
|
555
|
+
grad.addColorStop(0, `rgba(${cr}, ${cg}, ${cb}, ${alpha})`);
|
|
556
|
+
grad.addColorStop(0.4, `rgba(${cr}, ${cg}, ${cb}, ${alpha * 0.6})`);
|
|
557
|
+
grad.addColorStop(1, `rgba(${cr}, ${cg}, ${cb}, 0)`);
|
|
558
|
+
ctx.fillStyle = grad;
|
|
559
|
+
ctx.beginPath();
|
|
560
|
+
ctx.arc(lx, ly, r * 2, 0, Math.PI * 2);
|
|
561
|
+
ctx.fill();
|
|
562
|
+
ctx.fillStyle = `rgba(${cr}, ${cg}, ${cb}, ${Math.min(1, alpha * 1.3)})`;
|
|
563
|
+
ctx.beginPath();
|
|
564
|
+
ctx.arc(lx, ly, Math.max(1, r * 0.4), 0, Math.PI * 2);
|
|
565
|
+
ctx.fill();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
486
568
|
allSatellites.forEach((sat, satIdx) => {
|
|
487
569
|
const track = sat.groundTrack;
|
|
488
570
|
if (!track || track.length === 0) return;
|
|
@@ -791,7 +873,7 @@ function GroundTrackMap({
|
|
|
791
873
|
ctx.textAlign = "center";
|
|
792
874
|
ctx.fillText(emptyMessage, W / 2, H / 2);
|
|
793
875
|
}
|
|
794
|
-
}, [allSatellites, groundStations, height, showTerminator, terminatorTime, showGrid, showLegend, showEquator, lonToX, latToY, COLORS, emptyMessage, pins, tokens.colors.accent.primary]);
|
|
876
|
+
}, [allSatellites, groundStations, height, showTerminator, terminatorTime, showGrid, showLegend, showEquator, lonToX, latToY, COLORS, emptyMessage, pins, lightSources, tokens.colors.accent.primary]);
|
|
795
877
|
const handleMouseMove = useCallback((e) => {
|
|
796
878
|
const canvas = canvasRef.current;
|
|
797
879
|
if (!canvas) return;
|
|
@@ -912,6 +994,7 @@ function GroundTrackMap({
|
|
|
912
994
|
showLegend,
|
|
913
995
|
showEquator,
|
|
914
996
|
showRecenterButton,
|
|
997
|
+
showMapStyleToggle,
|
|
915
998
|
defaultCenter: leafletDefaultCenter,
|
|
916
999
|
defaultZoom: leafletDefaultZoom,
|
|
917
1000
|
height,
|
|
@@ -919,6 +1002,8 @@ function GroundTrackMap({
|
|
|
919
1002
|
minHeight,
|
|
920
1003
|
emptyMessage,
|
|
921
1004
|
tileUrl,
|
|
1005
|
+
nightTileUrl,
|
|
1006
|
+
lightSources,
|
|
922
1007
|
className,
|
|
923
1008
|
onSatelliteClick,
|
|
924
1009
|
onStationClick,
|