Apply global map theme updates and UI improvements

This commit is contained in:
Smittix
2026-02-20 00:32:58 +00:00
parent 963bcdf9fa
commit 1466fc2d30
22 changed files with 1365 additions and 351 deletions

View File

@@ -36,6 +36,7 @@ const BtLocate = (function() {
let autoFollowEnabled = true;
let smoothingEnabled = true;
let lastRenderedDetectionKey = null;
let pendingHeatSync = false;
const MAX_HEAT_POINTS = 1200;
const MAX_TRAIL_POINTS = 1200;
@@ -63,6 +64,23 @@ const BtLocate = (function() {
},
};
function getMapContainer() {
if (!map || typeof map.getContainer !== 'function') return null;
return map.getContainer();
}
function isMapContainerVisible() {
const container = getMapContainer();
if (!container) return false;
if (container.offsetWidth <= 0 || container.offsetHeight <= 0) return false;
if (container.style && container.style.display === 'none') return false;
if (typeof window.getComputedStyle === 'function') {
const style = window.getComputedStyle(container);
if (style.display === 'none' || style.visibility === 'hidden') return false;
}
return true;
}
function init() {
loadOverlayPreferences();
syncOverlayControls();
@@ -71,20 +89,21 @@ const BtLocate = (function() {
// Re-invalidate map on re-entry and ensure tiles are present
if (map) {
setTimeout(() => {
map.invalidateSize();
// Re-apply user's tile layer if tiles were lost
let hasTiles = false;
map.eachLayer(layer => {
if (layer instanceof L.TileLayer) hasTiles = true;
});
if (!hasTiles && typeof Settings !== 'undefined' && Settings.createTileLayer) {
Settings.createTileLayer().addTo(map);
}
}, 150);
}
checkStatus();
return;
}
safeInvalidateMap();
// Re-apply user's tile layer if tiles were lost
let hasTiles = false;
map.eachLayer(layer => {
if (layer instanceof L.TileLayer) hasTiles = true;
});
if (!hasTiles && typeof Settings !== 'undefined' && Settings.createTileLayer) {
Settings.createTileLayer().addTo(map);
}
flushPendingHeatSync();
}, 150);
}
checkStatus();
return;
}
// Init map
const mapEl = document.getElementById('btLocateMap');
@@ -107,7 +126,13 @@ const BtLocate = (function() {
ensureHeatLayer();
syncMovementLayer();
syncHeatLayer();
setTimeout(() => map.invalidateSize(), 100);
map.on('resize moveend zoomend', () => {
flushPendingHeatSync();
});
setTimeout(() => {
safeInvalidateMap();
flushPendingHeatSync();
}, 100);
}
// Init RSSI chart canvas
@@ -432,7 +457,12 @@ const BtLocate = (function() {
// Map marker
let mapPointAdded = false;
if (d.lat != null && d.lon != null) {
mapPointAdded = addMapMarker(d, { suppressFollow: options.suppressFollow === true });
try {
mapPointAdded = addMapMarker(d, { suppressFollow: options.suppressFollow === true });
} catch (error) {
console.warn('[BtLocate] Map update skipped:', error);
mapPointAdded = false;
}
}
// Update stats
@@ -535,7 +565,11 @@ const BtLocate = (function() {
}
syncHeatLayer();
if (autoFollowEnabled && !options.suppressFollow) {
if (!isMapRenderable()) {
safeInvalidateMap();
}
const canFollowMap = isMapRenderable();
if (autoFollowEnabled && !options.suppressFollow && canFollowMap) {
if (!gpsLocked) {
gpsLocked = true;
map.setView([lat, lon], Math.max(map.getZoom(), 16));
@@ -631,7 +665,11 @@ const BtLocate = (function() {
const latestGps = trailPoints[trailPoints.length - 1];
gpsLocked = true;
const targetZoom = Math.max(map.getZoom(), 15);
map.setView([latestGps.lat, latestGps.lon], targetZoom);
if (isMapRenderable()) {
map.setView([latestGps.lat, latestGps.lon], targetZoom);
} else {
pendingHeatSync = true;
}
}
syncMovementLayer();
syncStrongestMarker();
@@ -667,7 +705,15 @@ const BtLocate = (function() {
confidenceCircle = null;
}
if (heatLayer) {
heatLayer.setLatLngs([]);
try {
if (isMapRenderable()) {
heatLayer.setLatLngs([]);
} else {
pendingHeatSync = true;
}
} catch (error) {
pendingHeatSync = true;
}
}
updateStrongestInfo(null);
updateConfidenceInfo(null);
@@ -817,14 +863,54 @@ const BtLocate = (function() {
if (!map) return;
ensureHeatLayer();
if (!heatLayer) return;
heatLayer.setLatLngs(heatPoints);
if (heatmapEnabled) {
if (!map.hasLayer(heatLayer)) {
heatLayer.addTo(map);
}
} else if (map.hasLayer(heatLayer)) {
map.removeLayer(heatLayer);
if (!isMapContainerVisible()) {
pendingHeatSync = true;
return;
}
if (!isMapRenderable()) {
safeInvalidateMap();
if (!isMapRenderable()) {
pendingHeatSync = true;
return;
}
}
try {
heatLayer.setLatLngs(heatPoints);
if (heatmapEnabled) {
if (!map.hasLayer(heatLayer)) {
heatLayer.addTo(map);
}
} else if (map.hasLayer(heatLayer)) {
map.removeLayer(heatLayer);
}
pendingHeatSync = false;
} catch (error) {
pendingHeatSync = true;
if (map.hasLayer(heatLayer)) {
map.removeLayer(heatLayer);
}
console.warn('[BtLocate] Heatmap redraw deferred:', error);
}
}
function isMapRenderable() {
if (!map || !isMapContainerVisible()) return false;
if (typeof map.getSize === 'function') {
const size = map.getSize();
if (!size || size.x <= 0 || size.y <= 0) return false;
}
return true;
}
function safeInvalidateMap() {
if (!map || !isMapContainerVisible()) return false;
map.invalidateSize({ pan: false, animate: false });
return true;
}
function flushPendingHeatSync() {
if (!pendingHeatSync) return;
syncHeatLayer();
}
function syncMovementLayer() {
@@ -1473,9 +1559,14 @@ const BtLocate = (function() {
.catch(err => console.error('[BtLocate] Clear trail error:', err));
}
function invalidateMap() {
if (map) map.invalidateSize();
}
function invalidateMap() {
if (safeInvalidateMap()) {
flushPendingHeatSync();
syncMovementLayer();
syncStrongestMarker();
updateConfidenceLayer();
}
}
return {
init,

View File

@@ -12,11 +12,12 @@ const SSTV = (function() {
let progress = 0;
let issMap = null;
let issMarker = null;
let issTrackLine = null;
let issPosition = null;
let issUpdateInterval = null;
let countdownInterval = null;
let nextPassData = null;
let issTrackLine = null;
let issPosition = null;
let issUpdateInterval = null;
let countdownInterval = null;
let nextPassData = null;
let pendingMapInvalidate = false;
// ISS frequency
const ISS_FREQ = 145.800;
@@ -37,15 +38,31 @@ const SSTV = (function() {
/**
* Initialize the SSTV mode
*/
function init() {
checkStatus();
loadImages();
loadLocationInputs();
loadIssSchedule();
initMap();
startIssTracking();
startCountdown();
}
function init() {
checkStatus();
loadImages();
loadLocationInputs();
loadIssSchedule();
initMap();
startIssTracking();
startCountdown();
// Ensure Leaflet recomputes dimensions after the SSTV pane becomes visible.
setTimeout(() => invalidateMap(), 80);
setTimeout(() => invalidateMap(), 260);
}
function isMapContainerVisible() {
if (!issMap || typeof issMap.getContainer !== 'function') return false;
const container = issMap.getContainer();
if (!container) return false;
if (container.offsetWidth <= 0 || container.offsetHeight <= 0) return false;
if (container.style && container.style.display === 'none') return false;
if (typeof window.getComputedStyle === 'function') {
const style = window.getComputedStyle(container);
if (style.display === 'none' || style.visibility === 'hidden') return false;
}
return true;
}
/**
* Load location into input fields
@@ -172,9 +189,9 @@ const SSTV = (function() {
/**
* Initialize Leaflet map for ISS tracking
*/
async function initMap() {
const mapContainer = document.getElementById('sstvIssMap');
if (!mapContainer || issMap) return;
async function initMap() {
const mapContainer = document.getElementById('sstvIssMap');
if (!mapContainer || issMap) return;
// Create map
issMap = L.map('sstvIssMap', {
@@ -214,13 +231,21 @@ const SSTV = (function() {
issMarker = L.marker([0, 0], { icon: issIcon }).addTo(issMap);
// Create ground track line
issTrackLine = L.polyline([], {
color: '#00d4ff',
weight: 2,
opacity: 0.6,
dashArray: '5, 5'
}).addTo(issMap);
}
issTrackLine = L.polyline([], {
color: '#00d4ff',
weight: 2,
opacity: 0.6,
dashArray: '5, 5'
}).addTo(issMap);
issMap.on('resize moveend zoomend', () => {
if (pendingMapInvalidate) invalidateMap();
});
// Initial layout passes for first-time mode load.
setTimeout(() => invalidateMap(), 40);
setTimeout(() => invalidateMap(), 180);
}
/**
* Start ISS position tracking
@@ -429,8 +454,9 @@ const SSTV = (function() {
/**
* Update map with ISS position
*/
function updateMap() {
if (!issMap || !issPosition) return;
function updateMap() {
if (!issMap || !issPosition) return;
if (pendingMapInvalidate) invalidateMap();
const lat = issPosition.lat;
const lon = issPosition.lon;
@@ -490,9 +516,13 @@ const SSTV = (function() {
issTrackLine.setLatLngs(segments.length > 0 ? segments : []);
}
// Pan map to follow ISS
issMap.panTo([lat, lon], { animate: true, duration: 0.5 });
}
// Pan map to follow ISS only when the map pane is currently renderable.
if (isMapContainerVisible()) {
issMap.panTo([lat, lon], { animate: true, duration: 0.5 });
} else {
pendingMapInvalidate = true;
}
}
/**
* Check current decoder status
@@ -1305,13 +1335,27 @@ const SSTV = (function() {
/**
* Show status message
*/
function showStatusMessage(message, type) {
if (typeof showNotification === 'function') {
showNotification('SSTV', message);
} else {
console.log(`[SSTV ${type}] ${message}`);
}
}
function showStatusMessage(message, type) {
if (typeof showNotification === 'function') {
showNotification('SSTV', message);
} else {
console.log(`[SSTV ${type}] ${message}`);
}
}
/**
* Invalidate ISS map size after pane/layout changes.
*/
function invalidateMap() {
if (!issMap) return false;
if (!isMapContainerVisible()) {
pendingMapInvalidate = true;
return false;
}
issMap.invalidateSize({ pan: false, animate: false });
pendingMapInvalidate = false;
return true;
}
// Public API
return {
@@ -1326,11 +1370,12 @@ const SSTV = (function() {
deleteAllImages,
downloadImage,
useGPS,
updateTLE,
stopIssTracking,
stopCountdown
};
})();
updateTLE,
stopIssTracking,
stopCountdown,
invalidateMap
};
})();
// Initialize when DOM is ready (will be called by selectMode)
document.addEventListener('DOMContentLoaded', function() {

View File

@@ -1,8 +1,8 @@
/**
* Weather Satellite Mode
/**
* Weather Satellite Mode
* NOAA APT and Meteor LRPT decoder interface with auto-scheduler,
* polar plot, mercator map, countdown, and timeline.
*/
* polar plot, styled real-world map, countdown, and timeline.
*/
const WeatherSat = (function() {
// State
@@ -11,39 +11,73 @@ const WeatherSat = (function() {
let images = [];
let passes = [];
let selectedPassIndex = -1;
let currentSatellite = null;
let countdownInterval = null;
let currentSatellite = null;
let countdownInterval = null;
let schedulerEnabled = false;
let groundMap = null;
let groundTrackLayer = null;
let groundOverlayLayer = null;
let groundGridLayer = null;
let satCrosshairMarker = null;
let observerMarker = null;
let consoleEntries = [];
let consoleEntries = [];
let consoleCollapsed = false;
let currentPhase = 'idle';
let consoleAutoHideTimer = null;
let currentModalFilename = null;
let locationListenersAttached = false;
let consoleAutoHideTimer = null;
let currentModalFilename = null;
let locationListenersAttached = false;
/**
* Initialize the Weather Satellite mode
*/
function init() {
checkStatus();
loadImages();
loadLocationInputs();
loadPasses();
function init() {
checkStatus();
loadImages();
loadLocationInputs();
loadPasses();
startCountdownTimer();
checkSchedulerStatus();
initGroundMap();
}
/**
* Load observer location into input fields
*/
initGroundMap();
}
/**
* Get observer coordinates from shared location or local storage.
*/
function getObserverCoords() {
let lat;
let lon;
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
const shared = ObserverLocation.getShared();
lat = Number(shared?.lat);
lon = Number(shared?.lon);
} else {
lat = Number(localStorage.getItem('observerLat'));
lon = Number(localStorage.getItem('observerLon'));
}
if (!isFinite(lat) || !isFinite(lon)) return null;
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) return null;
return { lat, lon };
}
/**
* Center the ground map on current observer coordinates when available.
*/
function centerGroundMapOnObserver(zoom = 1) {
if (!groundMap) return;
const observer = getObserverCoords();
if (!observer) return;
const lat = Math.max(-85, Math.min(85, observer.lat));
const lon = normalizeLon(observer.lon);
groundMap.setView([lat, lon], zoom, { animate: false });
}
/**
* Load observer location into input fields
*/
function loadLocationInputs() {
const latInput = document.getElementById('wxsatObsLat');
const latInput = document.getElementById('wxsatObsLat');
const lonInput = document.getElementById('wxsatObsLon');
let storedLat = localStorage.getItem('observerLat');
@@ -80,13 +114,14 @@ const WeatherSat = (function() {
!isNaN(lon) && lon >= -180 && lon <= 180) {
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
ObserverLocation.setShared({ lat, lon });
} else {
localStorage.setItem('observerLat', lat.toString());
localStorage.setItem('observerLon', lon.toString());
}
loadPasses();
}
}
} else {
localStorage.setItem('observerLat', lat.toString());
localStorage.setItem('observerLon', lon.toString());
}
loadPasses();
centerGroundMapOnObserver(1);
}
}
/**
* Use GPS for location
@@ -119,11 +154,12 @@ const WeatherSat = (function() {
localStorage.setItem('observerLon', lon);
}
btn.innerHTML = originalText;
btn.disabled = false;
showNotification('Weather Sat', 'Location updated');
loadPasses();
},
btn.innerHTML = originalText;
btn.disabled = false;
showNotification('Weather Sat', 'Location updated');
loadPasses();
centerGroundMapOnObserver(1);
},
(err) => {
btn.innerHTML = originalText;
btn.disabled = false;
@@ -749,118 +785,140 @@ const WeatherSat = (function() {
ctx.fillText(Math.round(maxEl) + '\u00b0', cx + r * maxR * Math.cos(maxAz), cy + r * maxR * Math.sin(maxAz) - 8);
}
// ========================
// Ground Track Map
// ========================
/**
* Initialize Leaflet ground track map
*/
function initGroundMap() {
// ========================
// Ground Track Map
// ========================
/**
* Initialize styled real-world map panel.
*/
async function initGroundMap() {
const container = document.getElementById('wxsatGroundMap');
if (!container || groundMap) return;
if (!container) return;
if (typeof L === 'undefined') return;
const observer = getObserverCoords();
const defaultCenter = observer
? [Math.max(-85, Math.min(85, observer.lat)), normalizeLon(observer.lon)]
: [12, 0];
const defaultZoom = 1;
groundMap = L.map(container, {
center: [20, 0],
zoom: 2,
zoomControl: false,
attributionControl: false,
crs: L.CRS.EPSG3857, // Web Mercator projection
});
// Check tile provider from settings
let tileUrl = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png';
try {
const provider = localStorage.getItem('tileProvider');
if (provider === 'osm') {
tileUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
}
} catch (e) {}
L.tileLayer(tileUrl, { maxZoom: 10 }).addTo(groundMap);
if (!groundMap) {
groundMap = L.map(container, {
center: defaultCenter,
zoom: defaultZoom,
minZoom: 1,
maxZoom: 7,
zoomControl: false,
attributionControl: false,
worldCopyJump: true,
preferCanvas: true,
});
groundTrackLayer = L.layerGroup().addTo(groundMap);
groundOverlayLayer = L.layerGroup().addTo(groundMap);
if (typeof Settings !== 'undefined' && Settings.createTileLayer) {
await Settings.init();
Settings.createTileLayer().addTo(groundMap);
Settings.registerMap(groundMap);
} else {
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
subdomains: 'abcd',
maxZoom: 18,
noWrap: false,
crossOrigin: true,
className: 'tile-layer-cyan',
}).addTo(groundMap);
}
const selected = getSelectedPass();
if (selected) {
updateGroundTrack(selected);
} else {
updateSatelliteCrosshair(null);
groundGridLayer = L.layerGroup().addTo(groundMap);
addStyledGridOverlay(groundGridLayer);
groundTrackLayer = L.layerGroup().addTo(groundMap);
groundOverlayLayer = L.layerGroup().addTo(groundMap);
}
// Delayed invalidation to fix sizing
setTimeout(() => { if (groundMap) groundMap.invalidateSize(); }, 200);
}
/**
* Update ground track on the map
*/
function updateGroundTrack(pass) {
if (!groundMap || !groundTrackLayer) return;
groundTrackLayer.clearLayers();
if (!pass) {
updateSatelliteCrosshair(null);
return;
}
const track = pass.groundTrack;
if (!track || track.length === 0) {
updateSatelliteCrosshair(null);
return;
}
const color = pass.mode === 'LRPT' ? '#00ff88' : '#00d4ff';
// Draw polyline
const latlngs = track.map(p => [p.lat, p.lon]);
L.polyline(latlngs, { color, weight: 2, opacity: 0.8 }).addTo(groundTrackLayer);
// Start marker
L.circleMarker(latlngs[0], {
radius: 5, color: '#00ff88', fillColor: '#00ff88', fillOpacity: 1, weight: 0,
}).addTo(groundTrackLayer);
// End marker
L.circleMarker(latlngs[latlngs.length - 1], {
radius: 5, color: '#ff4444', fillColor: '#ff4444', fillOpacity: 1, weight: 0,
}).addTo(groundTrackLayer);
// Observer marker
let obsLat, obsLon;
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
const shared = ObserverLocation.getShared();
obsLat = shared?.lat;
obsLon = shared?.lon;
} else {
obsLat = parseFloat(localStorage.getItem('observerLat'));
obsLon = parseFloat(localStorage.getItem('observerLon'));
}
const lat = obsLat;
const lon = obsLon;
if (!isNaN(lat) && !isNaN(lon)) {
L.circleMarker([lat, lon], {
radius: 6, color: '#ffbb00', fillColor: '#ffbb00', fillOpacity: 0.8, weight: 1,
}).addTo(groundTrackLayer);
}
// Fit bounds
try {
const bounds = L.latLngBounds(latlngs);
if (!isNaN(lat) && !isNaN(lon)) bounds.extend([lat, lon]);
groundMap.fitBounds(bounds, { padding: [20, 20] });
} catch (e) {}
updateSatelliteCrosshair(pass);
setTimeout(() => {
if (!groundMap) return;
groundMap.invalidateSize(false);
groundMap.setView(defaultCenter, defaultZoom, { animate: false });
updateGroundTrack(getSelectedPass());
}, 140);
}
function updateMercatorInfo(text) {
const infoEl = document.getElementById('wxsatMercatorInfo');
/**
* Update map panel subtitle.
*/
function updateProjectionInfo(text) {
const infoEl = document.getElementById('wxsatMapInfo');
if (infoEl) infoEl.textContent = text || '--';
}
/**
* Normalize longitude to [-180, 180).
*/
function normalizeLon(value) {
const lon = Number(value);
if (!isFinite(lon)) return 0;
return ((((lon + 180) % 360) + 360) % 360) - 180;
}
/**
* Build track segments that do not cross the date line.
*/
function buildTrackSegments(track) {
const segments = [];
let currentSegment = [];
track.forEach((point) => {
const lat = Number(point?.lat);
const lon = normalizeLon(point?.lon);
if (!isFinite(lat) || !isFinite(lon)) return;
if (currentSegment.length > 0) {
const prevLon = currentSegment[currentSegment.length - 1][1];
if (Math.abs(lon - prevLon) > 180) {
if (currentSegment.length > 1) segments.push(currentSegment);
currentSegment = [];
}
}
currentSegment.push([lat, lon]);
});
if (currentSegment.length > 1) segments.push(currentSegment);
return segments;
}
/**
* Draw a subtle graticule over the base map for a cyber/wireframe look.
*/
function addStyledGridOverlay(layer) {
if (!layer || typeof L === 'undefined') return;
layer.clearLayers();
for (let lon = -180; lon <= 180; lon += 30) {
const line = [];
for (let lat = -85; lat <= 85; lat += 5) line.push([lat, lon]);
L.polyline(line, {
color: '#4ed2ff',
weight: lon % 60 === 0 ? 1.1 : 0.8,
opacity: lon % 60 === 0 ? 0.2 : 0.12,
interactive: false,
lineCap: 'round',
}).addTo(layer);
}
for (let lat = -75; lat <= 75; lat += 15) {
const line = [];
for (let lon = -180; lon <= 180; lon += 5) line.push([lat, lon]);
L.polyline(line, {
color: '#5be7ff',
weight: lat % 30 === 0 ? 1.1 : 0.8,
opacity: lat % 30 === 0 ? 0.2 : 0.12,
interactive: false,
lineCap: 'round',
}).addTo(layer);
}
}
function clearSatelliteCrosshair() {
if (!groundOverlayLayer || !satCrosshairMarker) return;
groundOverlayLayer.removeLayer(satCrosshairMarker);
@@ -870,8 +928,8 @@ const WeatherSat = (function() {
function createSatelliteCrosshairIcon() {
return L.divIcon({
className: 'wxsat-crosshair-icon',
iconSize: [26, 26],
iconAnchor: [13, 13],
iconSize: [30, 30],
iconAnchor: [15, 15],
html: `
<div class="wxsat-crosshair-marker">
<span class="wxsat-crosshair-h"></span>
@@ -883,6 +941,92 @@ const WeatherSat = (function() {
});
}
/**
* Update selected ground track and redraw map overlays.
*/
function updateGroundTrack(pass) {
if (!groundMap || !groundTrackLayer) return;
groundTrackLayer.clearLayers();
observerMarker = null;
if (!pass) {
clearSatelliteCrosshair();
updateProjectionInfo('--');
return;
}
const track = pass?.groundTrack;
if (!Array.isArray(track) || track.length === 0) {
clearSatelliteCrosshair();
updateProjectionInfo(`${pass.name || pass.satellite || '--'} --`);
return;
}
const color = pass.mode === 'LRPT' ? '#27ffc6' : '#58ddff';
const glowClass = pass.mode === 'LRPT' ? 'wxsat-pass-track lrpt' : 'wxsat-pass-track apt';
const segments = buildTrackSegments(track);
const validPoints = track
.map((point) => [Number(point?.lat), normalizeLon(point?.lon)])
.filter((point) => isFinite(point[0]) && isFinite(point[1]));
segments.forEach((segment) => {
L.polyline(segment, {
color,
weight: 2.3,
opacity: 0.9,
className: glowClass,
interactive: false,
lineJoin: 'round',
}).addTo(groundTrackLayer);
});
if (validPoints.length > 0) {
L.circleMarker(validPoints[0], {
radius: 4.5,
color: '#00ffa2',
fillColor: '#00ffa2',
fillOpacity: 0.95,
weight: 0,
interactive: false,
}).addTo(groundTrackLayer);
L.circleMarker(validPoints[validPoints.length - 1], {
radius: 4.5,
color: '#ff5e5e',
fillColor: '#ff5e5e',
fillOpacity: 0.95,
weight: 0,
interactive: false,
}).addTo(groundTrackLayer);
}
let obsLat;
let obsLon;
if (window.ObserverLocation && ObserverLocation.isSharedEnabled()) {
const shared = ObserverLocation.getShared();
obsLat = shared?.lat;
obsLon = shared?.lon;
} else {
obsLat = parseFloat(localStorage.getItem('observerLat'));
obsLon = parseFloat(localStorage.getItem('observerLon'));
}
if (isFinite(obsLat) && isFinite(obsLon)) {
observerMarker = L.circleMarker([obsLat, obsLon], {
radius: 5.5,
color: '#ffd45b',
fillColor: '#ffd45b',
fillOpacity: 0.8,
weight: 1,
className: 'wxsat-observer-marker',
interactive: false,
}).addTo(groundTrackLayer);
}
updateSatelliteCrosshair(pass);
}
function getSelectedPass() {
if (selectedPassIndex < 0 || selectedPassIndex >= passes.length) return null;
return passes[selectedPassIndex];
@@ -938,41 +1082,44 @@ const WeatherSat = (function() {
if (!pass) {
clearSatelliteCrosshair();
updateMercatorInfo('--');
updateProjectionInfo('--');
return;
}
const position = getSatellitePositionForPass(pass);
if (!position) {
clearSatelliteCrosshair();
updateMercatorInfo(`${pass.name || pass.satellite || '--'} --`);
updateProjectionInfo(`${pass.name || pass.satellite || '--'} --`);
return;
}
const latlng = [position.lat, position.lon];
const latlng = [position.lat, normalizeLon(position.lon)];
if (!satCrosshairMarker) {
satCrosshairMarker = L.marker(latlng, {
icon: createSatelliteCrosshairIcon(),
interactive: false,
keyboard: false,
zIndexOffset: 800,
zIndexOffset: 900,
}).addTo(groundOverlayLayer);
} else {
satCrosshairMarker.setLatLng(latlng);
}
const tooltipText = `${pass.name || pass.satellite || 'Satellite'} ${position.lat.toFixed(2)}°, ${position.lon.toFixed(2)}°`;
const infoText =
`${pass.name || pass.satellite || 'Satellite'} ` +
`${position.lat.toFixed(2)}°, ${normalizeLon(position.lon).toFixed(2)}°`;
updateProjectionInfo(infoText);
if (!satCrosshairMarker.getTooltip()) {
satCrosshairMarker.bindTooltip(tooltipText, {
satCrosshairMarker.bindTooltip(infoText, {
direction: 'top',
offset: [0, -10],
opacity: 0.9,
offset: [0, -12],
opacity: 0.92,
className: 'wxsat-map-tooltip',
});
} else {
satCrosshairMarker.setTooltipContent(tooltipText);
satCrosshairMarker.setTooltipContent(infoText);
}
updateMercatorInfo(tooltipText);
}
// ========================
@@ -1502,14 +1649,19 @@ const WeatherSat = (function() {
return div.innerHTML;
}
/**
* Invalidate ground map size (call after container becomes visible)
*/
function invalidateMap() {
if (groundMap) {
setTimeout(() => groundMap.invalidateSize(), 100);
}
}
/**
* Invalidate ground map size (call after container becomes visible)
*/
function invalidateMap() {
setTimeout(() => {
if (!groundMap) {
initGroundMap();
return;
}
groundMap.invalidateSize(false);
updateGroundTrack(getSelectedPass());
}, 100);
}
// ========================
// Decoder Console

View File

@@ -27,7 +27,7 @@ const KIWI_SAMPLE_RATE = 12000;
// ============== INITIALIZATION ==============
function initWebSDR() {
async function initWebSDR() {
if (websdrInitialized) {
if (websdrMap) {
setTimeout(() => websdrMap.invalidateSize(), 100);
@@ -51,11 +51,18 @@ function initWebSDR() {
maxBoundsViscosity: 1.0,
});
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; OpenStreetMap contributors &copy; CARTO',
subdomains: 'abcd',
maxZoom: 19,
}).addTo(websdrMap);
if (typeof Settings !== 'undefined' && Settings.createTileLayer) {
await Settings.init();
Settings.createTileLayer().addTo(websdrMap);
Settings.registerMap(websdrMap);
} else {
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; OpenStreetMap contributors &copy; CARTO',
subdomains: 'abcd',
maxZoom: 19,
className: 'tile-layer-cyan',
}).addTo(websdrMap);
}
// Match background to tile ocean color so any remaining edge is seamless
mapEl.style.background = '#1a1d29';