diff --git a/static/css/satellite_dashboard.css b/static/css/satellite_dashboard.css index d7c1460..0dd5851 100644 --- a/static/css/satellite_dashboard.css +++ b/static/css/satellite_dashboard.css @@ -269,11 +269,35 @@ body { position: relative; z-index: 10; display: grid; - grid-template-columns: 1fr 1fr 340px; - grid-template-rows: 1fr auto; - gap: 0; - height: calc(100vh - 100px); - min-height: 500px; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr) auto auto; + gap: 12px; + height: auto; + min-height: 720px; + padding: 12px; +} + +.primary-layout { + display: grid; + grid-template-columns: minmax(0, 1.8fr) 360px; + gap: 12px; + min-height: 520px; + min-width: 0; +} + +.command-rail { + display: grid; + grid-template-rows: auto auto auto minmax(220px, 1fr); + gap: 12px; + min-width: 0; +} + +.data-grid { + display: grid; + grid-template-columns: minmax(320px, 1.2fr) minmax(280px, 0.9fr) minmax(320px, 1.1fr); + grid-template-rows: minmax(220px, 1fr) minmax(220px, 1fr); + gap: 12px; + min-height: 460px; } /* Panels */ @@ -332,44 +356,146 @@ body { .panel-content { padding: 12px; height: calc(100% - 40px); + min-height: 0; overflow-y: auto; } /* Polar plot */ .polar-container { - grid-column: 1; - grid-row: 1; + min-height: 250px; } #polarPlot { width: 100%; height: 100%; - min-height: 300px; + min-height: 220px; } /* Ground track map */ .map-container { - grid-column: 2; - grid-row: 1; + min-width: 0; + min-height: 520px; +} + +.map-panel-content { + position: relative; + padding: 0 !important; + overflow: hidden; } #groundMap { width: 100%; height: 100%; - min-height: 300px; + min-height: 520px; } -/* Right sidebar */ -.sidebar { - grid-column: 3; - grid-row: 1; +.map-header-tools { display: flex; - flex-direction: column; - border-left: 1px solid rgba(0, 212, 255, 0.2); - overflow-y: auto; - overflow-x: hidden; + align-items: center; + gap: 6px; + flex-wrap: wrap; } +.map-mode-btn, +.map-action-btn { + padding: 4px 8px; + border: 1px solid rgba(74, 163, 255, 0.28); + border-radius: 4px; + background: rgba(5, 18, 30, 0.85); + color: var(--text-secondary); + font-family: var(--font-mono); + font-size: 9px; + letter-spacing: 0.08em; + cursor: pointer; + transition: all 0.2s ease; +} + +.map-mode-btn:hover, +.map-action-btn:hover { + border-color: rgba(74, 163, 255, 0.65); + color: var(--accent-cyan); +} + +.map-mode-btn.active, +.map-action-btn.active { + color: var(--bg-dark); + background: linear-gradient(135deg, rgba(74, 163, 255, 0.95), rgba(79, 236, 182, 0.95)); + border-color: transparent; + box-shadow: 0 0 18px rgba(74, 163, 255, 0.22); +} + +.map-overlay-card { + position: absolute; + left: 14px; + bottom: 14px; + z-index: 500; + width: min(360px, calc(100% - 28px)); + padding: 10px 12px; + border: 1px solid rgba(74, 163, 255, 0.22); + border-radius: 8px; + background: + linear-gradient(135deg, rgba(5, 18, 30, 0.95), rgba(10, 27, 43, 0.9)); + box-shadow: 0 10px 35px rgba(0, 0, 0, 0.28); + backdrop-filter: blur(6px); +} + +.map-overlay-kicker { + margin-bottom: 4px; + color: var(--text-secondary); + font-family: var(--font-mono); + font-size: 9px; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.map-overlay-primary { + color: var(--text-primary); + font-family: var(--font-sans); + font-size: 13px; + font-weight: 600; + line-height: 1.25; +} + +.map-overlay-secondary { + margin-top: 4px; + color: var(--text-secondary); + font-size: 11px; + line-height: 1.3; +} + +.map-overlay-legend { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} + +.map-legend-chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 8px; + border-radius: 999px; + font-family: var(--font-mono); + font-size: 9px; + letter-spacing: 0.08em; + color: var(--text-primary); + background: rgba(255, 255, 255, 0.05); +} + +.map-legend-chip::before { + content: ''; + width: 7px; + height: 7px; + border-radius: 50%; + background: currentColor; + box-shadow: 0 0 8px currentColor; +} + +.map-legend-chip.pass { color: var(--accent-cyan); } +.map-legend-chip.live { color: var(--accent-green); } +.map-legend-chip.current { color: var(--accent-orange); } + /* Satellite selector at top of sidebar */ .satellite-selector { display: flex; @@ -514,24 +640,24 @@ body { /* Telemetry panel */ .telemetry-panel { - flex-shrink: 0; + min-height: 188px; } .transmitters-panel, .packets-panel { - flex: 0 0 auto; display: flex; flex-direction: column; + min-height: 0; } .transmitters-panel { - min-height: 190px; - max-height: 190px; + grid-column: 2; + grid-row: 1; } .packets-panel { - min-height: 170px; - max-height: 170px; + grid-column: 2; + grid-row: 2; } .transmitters-panel .panel-content, @@ -569,19 +695,19 @@ body { /* Pass list */ .pass-list { - flex: 0 0 auto; - min-height: 220px; - max-height: 220px; display: flex; flex-direction: column; + grid-column: 1; + grid-row: 1 / span 2; + min-height: 0; } .gs-panel { - flex: 0 0 auto; - min-height: 300px; - max-height: 300px; display: flex; flex-direction: column; + grid-column: 3; + grid-row: 1 / span 2; + min-height: 0; } .gs-panel .panel-content { @@ -667,8 +793,6 @@ body { /* Bottom controls bar */ .controls-bar { - grid-column: 1 / -1; - grid-row: 2; display: flex; align-items: center; gap: 20px; @@ -745,6 +869,38 @@ body { background: var(--bg-dark) !important; } +.leaflet-tooltip.map-track-tooltip { + padding: 4px 7px; + border: 1px solid rgba(74, 163, 255, 0.3); + border-radius: 999px; + background: rgba(4, 14, 23, 0.92); + color: var(--text-primary); + box-shadow: 0 0 18px rgba(0, 0, 0, 0.28); + font-family: var(--font-mono); + font-size: 9px; + letter-spacing: 0.08em; +} + +.leaflet-tooltip.map-track-tooltip::before { + display: none; +} + +.leaflet-tooltip.map-track-tooltip.aos { + color: var(--accent-green); +} + +.leaflet-tooltip.map-track-tooltip.tca { + color: var(--accent-cyan); +} + +.leaflet-tooltip.map-track-tooltip.los { + color: var(--accent-orange); +} + +.leaflet-tooltip.map-track-tooltip.now { + color: var(--accent-orange); +} + /* Using actual dark tiles now - no filter needed */ .leaflet-control-zoom a { @@ -774,67 +930,99 @@ body { } /* Responsive */ -@media (max-width: 1280px) { - .dashboard { +@media (max-width: 1480px) { + .primary-layout { + grid-template-columns: minmax(0, 1.55fr) 330px; + } + + .data-grid { grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr auto auto; + grid-template-rows: auto auto auto; } - .polar-container { + .pass-list { grid-column: 1; - grid-row: 1; + grid-row: 1 / span 2; } - .map-container { + .transmitters-panel { grid-column: 2; grid-row: 1; } - .sidebar { - grid-column: 1 / 3; + .packets-panel { + grid-column: 2; grid-row: 2; - flex-direction: row; - border-left: none; - border-top: 1px solid rgba(0, 212, 255, 0.2); - max-height: 250px; } - .sidebar>* { - flex: 1; - min-width: 200px; - } - - .controls-bar { + .gs-panel { + grid-column: 1 / -1; grid-row: 3; - flex-wrap: wrap; + min-height: 320px; } } -@media (max-width: 768px) { +@media (max-width: 1180px) { .dashboard { - display: flex; - flex-direction: column; + grid-template-rows: auto auto auto; height: auto; min-height: calc(100vh - 100px); } - .polar-container, - .map-container { - min-height: 250px; - flex: 1; + .primary-layout { + grid-template-columns: 1fr; } - .sidebar { - flex-direction: column; - max-height: none; - border-left: none; - border-top: 1px solid rgba(0, 212, 255, 0.2); + .command-rail { + grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-rows: auto auto; + } + + .satellite-selector { + grid-column: 1 / -1; + } + + .data-grid { + grid-template-columns: 1fr; + grid-template-rows: auto; + } + + .pass-list, + .transmitters-panel, + .packets-panel, + .gs-panel { + grid-column: auto; + grid-row: auto; + min-height: 240px; } + .controls-bar { + flex-wrap: wrap; + } + + .map-overlay-card { + width: calc(100% - 28px); + } +} + +@media (max-width: 768px) { .controls-bar { flex-wrap: wrap; padding: 8px 12px; } + + .command-rail { + grid-template-columns: 1fr; + } + + .countdown-grid, + .telemetry-rows { + grid-template-columns: repeat(2, 1fr); + } + + .map-header-tools { + justify-content: flex-end; + } } /* Embedded Mode Styles */ diff --git a/templates/satellite_dashboard.html b/templates/satellite_dashboard.html index 8872b8e..de77e99 100644 --- a/templates/satellite_dashboard.html +++ b/templates/satellite_dashboard.html @@ -71,107 +71,127 @@ {% endif %}
- -
-
- SKY VIEW // POLAR PLOT -
+
+ +
+
+ GROUND TRACK // WORLD VIEW +
+ + + + + +
+
+
+
+
+
TRACK VIEW
+
Loading active orbit and pass corridor...
+
Use PASS, LIVE, or BOTH to switch the overlay view.
+
+ PASS CORRIDOR + LIVE ORBIT + CURRENT SUBPOINT +
+
+
-
- -
-
- -
-
- GROUND TRACK // WORLD VIEW -
-
-
-
-
-
- - - -
+
@@ -441,7 +461,7 @@ /* Transmitters panel */ .transmitters-panel, .packets-panel { - margin-top: 10px; + margin-top: 0; } .tx-item { display: flex; @@ -482,7 +502,7 @@ } /* Ground Station panel */ - .gs-panel { margin-top: 10px; } + .gs-panel { margin-top: 0; } .gs-status-row { display: flex; justify-content: space-between; @@ -596,6 +616,9 @@ let trackLine = null; let observerMarker = null; let orbitTrack = null; + let latestLivePosition = null; + let mapViewMode = 'both'; + let mapFollowMode = false; let selectedSatellite = 25544; let currentLocationSource = 'local'; let agents = []; @@ -691,6 +714,7 @@ selectedPass = null; passes = []; + latestLivePosition = null; if (groundMap) { if (trackLine) { groundMap.removeLayer(trackLine); trackLine = null; } @@ -699,6 +723,7 @@ } clearTelemetry(); + updateMapTrackSummary(); loadTransmitters(selectedSatellite); calculatePasses(); fetchCurrentTelemetry(); @@ -776,6 +801,8 @@ return; } + latestLivePosition = pos; + // Update telemetry panel const telLat = document.getElementById('telLat'); const telLon = document.getElementById('telLon'); @@ -789,33 +816,15 @@ if (telEl) telEl.textContent = (pos.elevation ?? 0).toFixed(1) + '°'; if (telAz) telAz.textContent = (pos.azimuth ?? 0).toFixed(1) + '°'; if (telDist) telDist.textContent = (pos.distance ?? 0).toFixed(0) + ' km'; - - // Update live marker on map - if (groundMap && pos.lat != null && pos.lon != null) { - const satColor = satellites[selectedSatellite]?.color || '#00d4ff'; - if (satMarker) groundMap.removeLayer(satMarker); - const satIcon = L.divIcon({ - className: 'sat-marker-live', - html: `
`, - iconSize: [20, 20], iconAnchor: [10, 10] - }); - satMarker = L.marker([pos.lat, pos.lon], { icon: satIcon }).addTo(groundMap); - } - - // Update orbit track from groundTrack if available - if (groundMap && pos.groundTrack && pos.groundTrack.length > 1) { - if (orbitTrack) { groundMap.removeLayer(orbitTrack); orbitTrack = null; } - const satColor = satellites[selectedSatellite]?.color || '#00d4ff'; - const segments = splitAtAntimeridian(pos.groundTrack); - orbitTrack = L.layerGroup(); - segments.forEach(seg => { - const past = seg.filter(p => p.past); - const future = seg.filter(p => !p.past); - if (past.length > 1) L.polyline(past.map(p => [p.lat, p.lon]), { color: satColor, weight: 2, opacity: 0.4 }).addTo(orbitTrack); - if (future.length > 1) L.polyline(future.map(p => [p.lat, p.lon]), { color: satColor, weight: 2, opacity: 0.7, dashArray: '5, 5' }).addTo(orbitTrack); - }); - orbitTrack.addTo(groundMap); + if (selectedPass == null && pos.azimuth != null && pos.elevation != null) { + drawPolarPlotWithPosition( + pos.azimuth, + pos.elevation, + satellites[selectedSatellite]?.color || '#00d4ff' + ); } + renderMapTrackOverlays(); + updateMapTrackSummary(); } function findSelectedPosition(positions) { @@ -870,7 +879,10 @@ const data = await response.json(); if (data.status !== 'success' || !Array.isArray(data.positions)) return; if (!findSelectedPosition(data.positions)) { + latestLivePosition = null; clearTelemetry(); + renderMapTrackOverlays(); + updateMapTrackSummary(); return; } handleLivePositions(data.positions); @@ -900,6 +912,233 @@ return segments; } + function getSelectedPass() { + return Number.isInteger(selectedPass) && passes[selectedPass] ? passes[selectedPass] : null; + } + + function setMapViewMode(mode) { + mapViewMode = mode; + updateMapModeButtons(); + renderMapTrackOverlays(); + updateMapTrackSummary(); + } + + function toggleMapFollow() { + mapFollowMode = !mapFollowMode; + const btn = document.getElementById('mapFollowBtn'); + if (btn) btn.classList.toggle('active', mapFollowMode); + if (mapFollowMode) { + fitMapToActiveTrack(); + } + } + + function updateMapModeButtons() { + const ids = { + both: 'mapModeBothBtn', + pass: 'mapModePassBtn', + live: 'mapModeLiveBtn' + }; + Object.entries(ids).forEach(([mode, id]) => { + const el = document.getElementById(id); + if (el) el.classList.toggle('active', mapViewMode === mode); + }); + } + + function createTrackWaypoint(layer, latlng, label, color, tooltipClass) { + return L.circleMarker(latlng, { + radius: 5, + color: '#f4fbff', + weight: 2, + fillColor: color, + fillOpacity: 1, + opacity: 0.95 + }) + .bindTooltip(label, { + permanent: true, + direction: 'top', + className: `map-track-tooltip ${tooltipClass}` + }) + .addTo(layer); + } + + function addSegmentSeries(layer, segment, styles) { + if (!Array.isArray(segment) || segment.length < 2) return; + styles.forEach(style => { + L.polyline(segment, style).addTo(layer); + }); + } + + function renderPassTrackLayer(pass) { + const track = Array.isArray(pass?.groundTrack) ? pass.groundTrack : []; + if (!track.length) return null; + + const color = pass.color || satellites[selectedSatellite]?.color || '#00d4ff'; + const layer = L.layerGroup(); + const segments = splitAtAntimeridian(track); + const bounds = []; + + segments.forEach(segObj => { + const seg = segObj.map(p => [p.lat, p.lon]); + if (seg.length < 2) return; + addSegmentSeries(layer, seg, [ + { color, weight: 16, opacity: 0.06, lineCap: 'round' }, + { color, weight: 8, opacity: 0.18, lineCap: 'round' }, + { color, weight: 3.5, opacity: 0.95, lineCap: 'round' } + ]); + bounds.push(...seg); + }); + + const first = track[0]; + const mid = track[Math.floor(track.length / 2)]; + const last = track[track.length - 1]; + if (first) createTrackWaypoint(layer, [first.lat, first.lon], 'AOS', '#38c180', 'aos'); + if (mid) createTrackWaypoint(layer, [mid.lat, mid.lon], 'TCA', color, 'tca'); + if (last) createTrackWaypoint(layer, [last.lat, last.lon], 'LOS', '#d6a85e', 'los'); + + return { layer, bounds }; + } + + function renderLiveOrbitLayer(position) { + const track = Array.isArray(position?.groundTrack) ? position.groundTrack : []; + if (!track.length) return null; + + const color = '#38c180'; + const layer = L.layerGroup(); + const bounds = []; + const segments = splitAtAntimeridian(track); + + segments.forEach(segObj => { + const past = segObj.filter(p => p.past).map(p => [p.lat, p.lon]); + const future = segObj.filter(p => !p.past).map(p => [p.lat, p.lon]); + if (past.length > 1) { + addSegmentSeries(layer, past, [ + { color, weight: 10, opacity: 0.05, lineCap: 'round' }, + { color, weight: 2.5, opacity: 0.3, lineCap: 'round' } + ]); + bounds.push(...past); + } + if (future.length > 1) { + addSegmentSeries(layer, future, [ + { color, weight: 12, opacity: 0.08, lineCap: 'round' }, + { color, weight: 3, opacity: 0.85, dashArray: '10 8', lineCap: 'round' } + ]); + bounds.push(...future); + } + }); + + if (position.lat != null && position.lon != null) { + createTrackWaypoint(layer, [position.lat, position.lon], 'NOW', '#d6a85e', 'now'); + } + + return { layer, bounds }; + } + + function updateMapTrackSummary() { + const primary = document.getElementById('mapTrackPrimary'); + const secondary = document.getElementById('mapTrackSecondary'); + if (!primary || !secondary) return; + + const pass = getSelectedPass(); + const live = latestLivePosition; + const nextTime = pass?.aosTime ? new Date(pass.aosTime).toISOString().substring(11, 16) + ' UTC' : null; + + if (mapViewMode === 'both' && pass && live) { + primary.textContent = `${pass.satellite} pass corridor plus live orbit context`; + secondary.textContent = `Next rise ${nextTime || '--:-- UTC'} · peak ${pass.maxEl ?? '--'}° · current elevation ${(live.elevation ?? 0).toFixed(1)}°`; + return; + } + if (mapViewMode === 'pass' && pass) { + primary.textContent = `${pass.satellite} pass corridor`; + secondary.textContent = `AOS ${nextTime || '--:-- UTC'} · peak ${pass.maxEl ?? '--'}° · duration ${pass.duration ?? '--'} min`; + return; + } + if (mapViewMode === 'live' && live) { + primary.textContent = `${satellites[selectedSatellite]?.name || 'Selected satellite'} live orbit track`; + secondary.textContent = `Subpoint ${(live.lat ?? 0).toFixed(2)}°, ${(live.lon ?? 0).toFixed(2)}° · elevation ${(live.elevation ?? 0).toFixed(1)}°`; + return; + } + if (pass) { + primary.textContent = `${pass.satellite} pass corridor ready`; + secondary.textContent = `Select FIT to frame the path, or switch to LIVE for the current orbit track.`; + return; + } + if (live) { + primary.textContent = `${satellites[selectedSatellite]?.name || 'Selected satellite'} live subpoint available`; + secondary.textContent = 'Awaiting pass prediction data. You can still inspect the current orbit track.'; + return; + } + primary.textContent = 'Awaiting orbit and pass geometry'; + secondary.textContent = 'Choose a satellite and calculate passes to populate the corridor view.'; + } + + function renderMapTrackOverlays(options = {}) { + if (!groundMap) return; + const { fit = false } = options; + + if (trackLine) { groundMap.removeLayer(trackLine); trackLine = null; } + if (orbitTrack) { groundMap.removeLayer(orbitTrack); orbitTrack = null; } + if (satMarker) { groundMap.removeLayer(satMarker); satMarker = null; } + + const bounds = []; + const pass = getSelectedPass(); + const showPass = mapViewMode !== 'live'; + const showLive = mapViewMode !== 'pass'; + + if (showPass && pass) { + const renderedPass = renderPassTrackLayer(pass); + if (renderedPass) { + trackLine = renderedPass.layer; + trackLine.addTo(groundMap); + bounds.push(...renderedPass.bounds); + } + } + + if (showLive && latestLivePosition) { + const renderedLive = renderLiveOrbitLayer(latestLivePosition); + if (renderedLive) { + orbitTrack = renderedLive.layer; + orbitTrack.addTo(groundMap); + bounds.push(...renderedLive.bounds); + } + } + + const satColor = satellites[selectedSatellite]?.color || '#00d4ff'; + const currentPos = latestLivePosition?.lat != null && latestLivePosition?.lon != null + ? { lat: latestLivePosition.lat, lon: latestLivePosition.lon } + : (pass?.currentPos?.lat != null && pass?.currentPos?.lon != null + ? { lat: pass.currentPos.lat, lon: pass.currentPos.lon } + : null); + + if (currentPos) { + const satIcon = L.divIcon({ + className: 'sat-marker-live', + html: `
`, + iconSize: [20, 20], + iconAnchor: [10, 10] + }); + satMarker = L.marker([currentPos.lat, currentPos.lon], { icon: satIcon }) + .addTo(groundMap) + .bindTooltip('CURRENT SUBPOINT', { + permanent: false, + direction: 'top', + className: 'map-track-tooltip now' + }); + } + + if (fit && bounds.length) { + groundMap.fitBounds(L.latLngBounds(bounds), { + padding: [40, 40], + maxZoom: 5 + }); + } else if (mapFollowMode && currentPos) { + groundMap.panTo([currentPos.lat, currentPos.lon], { animate: true, duration: 0.6 }); + } + } + + function fitMapToActiveTrack() { + renderMapTrackOverlays({ fit: true }); + } + // Listen for visibility messages from parent page (embedded mode) window.addEventListener('message', (event) => { if (event.data && event.data.type === 'satellite-visibility') { @@ -1108,6 +1347,8 @@ if (!Number.isNaN(lat) && !Number.isNaN(lon)) { groundMap.setView([lat, lon], 3); } + updateMapModeButtons(); + updateMapTrackSummary(); } function getLocation() { @@ -1190,7 +1431,15 @@ if (passes.length > 0) { selectPass(0); } else { - clearTelemetry(); + renderMapTrackOverlays(); + updateMapTrackSummary(); + if (latestLivePosition?.azimuth != null && latestLivePosition?.elevation != null) { + drawPolarPlotWithPosition( + latestLivePosition.azimuth, + latestLivePosition.elevation, + satellites[selectedSatellite]?.color || '#00d4ff' + ); + } } updateObserverMarker(lat, lon); @@ -1247,6 +1496,7 @@ if (passes.length === 0) { container.innerHTML = '
No passes found
'; if (countEl) countEl.textContent = ''; + updateMapTrackSummary(); return; } @@ -1315,6 +1565,7 @@ drawPolarPlot(pass); updateGroundTrack(pass); updateTelemetry(pass); + updateMapTrackSummary(); } function drawPolarPlot(pass) { @@ -1428,58 +1679,7 @@ function updateGroundTrack(pass) { if (!groundMap) return; - - if (trackLine) { groundMap.removeLayer(trackLine); trackLine = null; } - if (satMarker) { groundMap.removeLayer(satMarker); satMarker = null; } - if (orbitTrack) { groundMap.removeLayer(orbitTrack); orbitTrack = null; } - - if (pass && pass.groundTrack) { - const segments = []; - let currentSegment = []; - - for (let i = 0; i < pass.groundTrack.length; i++) { - const p = pass.groundTrack[i]; - if (currentSegment.length > 0) { - const prevLon = currentSegment[currentSegment.length - 1][1]; - const crossesAntimeridian = (prevLon > 90 && p.lon < -90) || (prevLon < -90 && p.lon > 90); - if (crossesAntimeridian) { - if (currentSegment.length >= 1) segments.push(currentSegment); - currentSegment = []; - } - } - currentSegment.push([p.lat, p.lon]); - } - if (currentSegment.length >= 1) segments.push(currentSegment); - - trackLine = L.layerGroup(); - const allCoords = []; - segments.forEach(seg => { - L.polyline(seg, { - color: pass.color || '#00d4ff', - weight: 4, - opacity: 1.0 - }).addTo(trackLine); - allCoords.push(...seg); - }); - trackLine.addTo(groundMap); - - if (pass.currentPos) { - const satIcon = L.divIcon({ - className: 'sat-marker', - html: `
`, - iconSize: [16, 16], - iconAnchor: [8, 8] - }); - - satMarker = L.marker([pass.currentPos.lat, pass.currentPos.lon], { icon: satIcon }) - .addTo(groundMap) - .bindPopup(`${pass.name}
Alt: ${pass.currentPos.alt?.toFixed(0)} km`); - } - - if (allCoords.length > 0) { - groundMap.fitBounds(L.latLngBounds(allCoords), { padding: [30, 30] }); - } - } + renderMapTrackOverlays({ fit: true }); } function updateObserverMarker(lat, lon) {