mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 14:11:54 -07:00
feat: Replace SSTV map with Leaflet for accurate ISS tracking
- Use real Leaflet map with proper tile layers (same as satellite section) - ISS marker with pulsing glow animation - Ground track orbit line showing ISS path - Map auto-pans to follow ISS position - Simplified overlay showing position and next pass info - Responsive layout that adapts to screen size - Removed custom canvas rendering and continent data The Leaflet map uses the same tile provider as other sections, ensuring the ISS position is accurately displayed on a real map. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+102
-91
@@ -449,108 +449,126 @@
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ISS ROW (Globe + Pass Info)
|
||||
ISS MAP ROW
|
||||
============================================ */
|
||||
.sstv-iss-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
.sstv-map-row {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sstv-globe-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.sstv-map-container {
|
||||
position: relative;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
min-width: 320px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#sstvGlobe {
|
||||
border-radius: 4px;
|
||||
.sstv-iss-map {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: #0a1628;
|
||||
box-shadow: 0 0 10px rgba(0, 212, 255, 0.2);
|
||||
}
|
||||
|
||||
.sstv-globe-info {
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
.sstv-map-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sstv-globe-label {
|
||||
.sstv-map-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.sstv-map-label {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
color: #ffcc00;
|
||||
background: rgba(255, 204, 0, 0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.sstv-map-coords {
|
||||
font-size: 11px;
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
.sstv-map-alt {
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.sstv-pass-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
|
||||
.sstv-pass-label {
|
||||
font-size: 9px;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.sstv-globe-coords {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px;
|
||||
color: var(--accent-cyan);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.sstv-globe-alt {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sstv-pass-info-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.sstv-pass-value {
|
||||
font-size: 11px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
ISS PASS INFO
|
||||
ISS MAP MARKER
|
||||
============================================ */
|
||||
.sstv-iss-info {
|
||||
.sstv-iss-marker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 10px 14px;
|
||||
background: rgba(0, 212, 255, 0.05);
|
||||
border: 1px solid rgba(0, 212, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sstv-iss-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: var(--accent-cyan);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sstv-iss-details {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
.sstv-iss-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: #ffcc00;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 15px rgba(255, 204, 0, 0.8), 0 0 30px rgba(255, 204, 0, 0.4);
|
||||
animation: iss-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.sstv-iss-label {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 9px;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.sstv-iss-value {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.sstv-iss-note {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10px;
|
||||
color: var(--accent-orange);
|
||||
font-weight: bold;
|
||||
color: #ffcc00;
|
||||
text-shadow: 0 0 3px rgba(0, 0, 0, 0.8), 0 0 6px rgba(0, 0, 0, 0.5);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@keyframes iss-pulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 15px rgba(255, 204, 0, 0.8), 0 0 30px rgba(255, 204, 0, 0.4);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 25px rgba(255, 204, 0, 1), 0 0 50px rgba(255, 204, 0, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Override Leaflet default marker styles */
|
||||
.leaflet-marker-icon.sstv-iss-marker {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
@@ -618,21 +636,14 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.sstv-iss-row {
|
||||
.sstv-iss-map {
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
.sstv-map-overlay {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sstv-globe-container {
|
||||
flex-direction: row;
|
||||
min-width: auto;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sstv-globe-info {
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +651,7 @@
|
||||
.sstv-stats-strip {
|
||||
padding: 8px 12px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sstv-strip-divider {
|
||||
@@ -660,17 +672,16 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.sstv-iss-info {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
.sstv-iss-map {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.sstv-globe-container {
|
||||
flex-direction: column;
|
||||
.sstv-map-info {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sstv-globe-info {
|
||||
text-align: center;
|
||||
.sstv-map-overlay {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+102
-370
@@ -10,7 +10,9 @@ const SSTV = (function() {
|
||||
let images = [];
|
||||
let currentMode = null;
|
||||
let progress = 0;
|
||||
let globeAnimationId = null;
|
||||
let issMap = null;
|
||||
let issMarker = null;
|
||||
let issTrackLine = null;
|
||||
let issPosition = null;
|
||||
let issUpdateInterval = null;
|
||||
|
||||
@@ -25,7 +27,7 @@ const SSTV = (function() {
|
||||
loadImages();
|
||||
loadLocationInputs();
|
||||
loadIssSchedule();
|
||||
initGlobe();
|
||||
initMap();
|
||||
startIssTracking();
|
||||
}
|
||||
|
||||
@@ -139,13 +141,51 @@ const SSTV = (function() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize 3D globe
|
||||
* Initialize Leaflet map for ISS tracking
|
||||
*/
|
||||
function initGlobe() {
|
||||
const canvas = document.getElementById('sstvGlobe');
|
||||
if (!canvas) return;
|
||||
function initMap() {
|
||||
const mapContainer = document.getElementById('sstvIssMap');
|
||||
if (!mapContainer || issMap) return;
|
||||
|
||||
renderGlobe();
|
||||
// Create map
|
||||
issMap = L.map('sstvIssMap', {
|
||||
center: [0, 0],
|
||||
zoom: 1,
|
||||
minZoom: 1,
|
||||
maxZoom: 6,
|
||||
zoomControl: true,
|
||||
attributionControl: false,
|
||||
worldCopyJump: true
|
||||
});
|
||||
|
||||
// Add tile layer using settings manager if available
|
||||
if (typeof Settings !== 'undefined' && Settings.createTileLayer) {
|
||||
Settings.createTileLayer().addTo(issMap);
|
||||
} else {
|
||||
// Fallback to dark theme tiles
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||||
maxZoom: 19
|
||||
}).addTo(issMap);
|
||||
}
|
||||
|
||||
// Create ISS icon
|
||||
const issIcon = L.divIcon({
|
||||
className: 'sstv-iss-marker',
|
||||
html: `<div class="sstv-iss-dot"></div><div class="sstv-iss-label">ISS</div>`,
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [20, 20]
|
||||
});
|
||||
|
||||
// Create ISS marker (will be positioned when we get data)
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,10 +206,6 @@ const SSTV = (function() {
|
||||
clearInterval(issUpdateInterval);
|
||||
issUpdateInterval = null;
|
||||
}
|
||||
if (globeAnimationId) {
|
||||
cancelAnimationFrame(globeAnimationId);
|
||||
globeAnimationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +223,7 @@ const SSTV = (function() {
|
||||
if (data.status === 'ok') {
|
||||
issPosition = data;
|
||||
updateIssDisplay();
|
||||
renderGlobe();
|
||||
updateMap();
|
||||
console.log('ISS position updated:', data.lat.toFixed(1), data.lon.toFixed(1));
|
||||
} else {
|
||||
console.warn('ISS position error:', data.message);
|
||||
@@ -212,345 +248,72 @@ const SSTV = (function() {
|
||||
if (altEl) altEl.textContent = Math.round(issPosition.altitude);
|
||||
}
|
||||
|
||||
// Accurate world map continent outlines (lon, lat pairs)
|
||||
const continents = {
|
||||
// North America mainland
|
||||
northAmerica: [
|
||||
[-168, 66], [-166, 62], [-164, 60], [-160, 59], [-152, 60], [-146, 61],
|
||||
[-141, 60], [-139, 60], [-137, 59], [-135, 56], [-133, 55], [-130, 54],
|
||||
[-127, 51], [-124, 48], [-124, 42], [-120, 39], [-117, 33], [-114, 31],
|
||||
[-110, 31], [-108, 31], [-105, 29], [-101, 26], [-97, 26], [-97, 28],
|
||||
[-94, 29], [-90, 29], [-89, 30], [-85, 29], [-83, 29], [-81, 25],
|
||||
[-80, 25], [-81, 28], [-81, 31], [-77, 35], [-75, 36], [-75, 38],
|
||||
[-73, 41], [-70, 42], [-70, 44], [-67, 45], [-66, 44], [-64, 46],
|
||||
[-61, 47], [-64, 52], [-59, 48], [-55, 52], [-57, 58], [-62, 58],
|
||||
[-68, 60], [-73, 62], [-77, 64], [-78, 69], [-85, 70], [-95, 70],
|
||||
[-102, 72], [-115, 72], [-125, 72], [-135, 70], [-145, 70], [-155, 71],
|
||||
[-165, 68], [-168, 66]
|
||||
],
|
||||
// Greenland
|
||||
greenland: [
|
||||
[-45, 60], [-43, 60], [-40, 62], [-38, 65], [-30, 68], [-25, 72],
|
||||
[-20, 76], [-22, 80], [-35, 83], [-50, 82], [-60, 78], [-68, 76],
|
||||
[-72, 73], [-60, 70], [-52, 66], [-48, 62], [-45, 60]
|
||||
],
|
||||
// Central America
|
||||
centralAmerica: [
|
||||
[-97, 26], [-97, 22], [-95, 19], [-92, 18], [-90, 16], [-88, 16],
|
||||
[-86, 14], [-84, 11], [-82, 9], [-79, 8], [-77, 9], [-80, 9],
|
||||
[-83, 10], [-86, 12], [-88, 14], [-90, 15], [-92, 16], [-95, 17],
|
||||
[-97, 20], [-97, 26]
|
||||
],
|
||||
// South America
|
||||
southAmerica: [
|
||||
[-79, 8], [-77, 9], [-73, 11], [-72, 12], [-67, 11], [-63, 10],
|
||||
[-60, 8], [-57, 6], [-52, 5], [-50, 2], [-50, 0], [-48, -2],
|
||||
[-44, -3], [-39, -4], [-35, -7], [-35, -10], [-37, -13], [-39, -18],
|
||||
[-41, -22], [-44, -23], [-47, -24], [-49, -29], [-52, -33], [-54, -34],
|
||||
[-57, -38], [-62, -39], [-65, -41], [-66, -45], [-66, -52], [-68, -55],
|
||||
[-72, -53], [-74, -50], [-75, -47], [-75, -41], [-73, -37], [-71, -33],
|
||||
[-71, -29], [-70, -24], [-70, -18], [-75, -15], [-76, -12], [-81, -6],
|
||||
[-81, -2], [-80, 1], [-79, 8]
|
||||
],
|
||||
// UK and Ireland
|
||||
ukIreland: [
|
||||
[-10, 51], [-9, 52], [-10, 54], [-8, 55], [-6, 55], [-6, 58],
|
||||
[-3, 59], [0, 58], [2, 53], [1, 51], [-2, 50], [-5, 50], [-6, 52],
|
||||
[-10, 51]
|
||||
],
|
||||
// Iceland
|
||||
iceland: [
|
||||
[-24, 64], [-22, 66], [-18, 66], [-14, 65], [-14, 64], [-18, 63],
|
||||
[-22, 64], [-24, 64]
|
||||
],
|
||||
// Europe mainland
|
||||
europe: [
|
||||
[-10, 36], [-9, 38], [-9, 43], [-2, 44], [3, 43], [4, 44], [1, 46],
|
||||
[-2, 47], [-5, 48], [-3, 49], [2, 51], [4, 52], [7, 54], [8, 55],
|
||||
[12, 55], [14, 54], [19, 55], [23, 55], [28, 56], [28, 60], [24, 60],
|
||||
[23, 64], [26, 66], [25, 70], [21, 70], [18, 69], [15, 69], [11, 64],
|
||||
[12, 58], [10, 58], [8, 58], [6, 58], [5, 62], [7, 65], [15, 69],
|
||||
[25, 71], [30, 70], [28, 66], [31, 65], [29, 60], [32, 55], [40, 55],
|
||||
[50, 55], [60, 55], [68, 56], [70, 66], [60, 70], [50, 68], [40, 67],
|
||||
[32, 70], [28, 70], [25, 71], [21, 70], [17, 68], [10, 64], [12, 56],
|
||||
[8, 54], [5, 54], [4, 52], [2, 51], [-3, 49], [-5, 48], [-2, 47],
|
||||
[1, 46], [4, 44], [3, 43], [-2, 44], [-9, 43], [-9, 41], [-8, 40],
|
||||
[-9, 38], [-7, 37], [-6, 37], [-5, 36], [-2, 36], [0, 38], [3, 42],
|
||||
[6, 43], [8, 44], [13, 44], [14, 42], [16, 41], [14, 38], [12, 38],
|
||||
[15, 37], [18, 40], [20, 40], [24, 38], [26, 39], [28, 41], [26, 42],
|
||||
[29, 45], [22, 45], [20, 42], [16, 42], [14, 44], [10, 46], [7, 46],
|
||||
[7, 48], [10, 48], [15, 47], [17, 49], [15, 51], [14, 53], [10, 54],
|
||||
[7, 54], [4, 52]
|
||||
],
|
||||
// Scandinavia (simplified)
|
||||
scandinavia: [
|
||||
[5, 58], [6, 62], [8, 64], [14, 66], [18, 68], [20, 70], [28, 71],
|
||||
[31, 70], [30, 67], [27, 65], [24, 60], [18, 60], [16, 57], [11, 56],
|
||||
[8, 56], [5, 58]
|
||||
],
|
||||
// Africa
|
||||
africa: [
|
||||
[-17, 21], [-17, 15], [-16, 13], [-15, 11], [-8, 5], [-5, 5],
|
||||
[0, 5], [2, 6], [10, 4], [10, 1], [9, -1], [12, -5], [14, -5],
|
||||
[17, -12], [23, -18], [26, -23], [28, -28], [28, -33], [23, -35],
|
||||
[18, -34], [16, -29], [14, -22], [12, -17], [14, -10], [20, -3],
|
||||
[30, 5], [35, 5], [42, 11], [44, 11], [49, 12], [51, 11], [43, 5],
|
||||
[41, -2], [40, -10], [36, -20], [33, -26], [28, -33], [23, -35],
|
||||
[18, -34], [16, -29], [13, -25], [10, -18], [9, -6], [5, 4],
|
||||
[-5, 5], [-10, 8], [-17, 15], [-17, 21], [-13, 24], [-8, 28],
|
||||
[-2, 35], [3, 37], [10, 37], [11, 34], [9, 31], [10, 28], [17, 32],
|
||||
[25, 32], [32, 31], [35, 32], [36, 30], [33, 27], [35, 22], [43, 13],
|
||||
[42, 11], [35, 5], [33, 10], [31, 10], [30, 5], [20, -3], [14, -10],
|
||||
[12, -17], [17, -12], [14, -5], [12, -5], [9, -1], [10, 1], [10, 4],
|
||||
[2, 6], [0, 5], [-5, 5], [-8, 5], [-15, 11], [-16, 13], [-17, 15],
|
||||
[-17, 21]
|
||||
],
|
||||
// Madagascar
|
||||
madagascar: [
|
||||
[50, -12], [50, -16], [47, -24], [44, -25], [44, -20], [47, -15],
|
||||
[49, -12], [50, -12]
|
||||
],
|
||||
// Middle East / Arabian Peninsula
|
||||
middleEast: [
|
||||
[35, 32], [36, 30], [40, 29], [48, 30], [52, 26], [56, 25], [57, 21],
|
||||
[55, 17], [52, 13], [44, 13], [43, 13], [35, 22], [33, 27], [35, 32]
|
||||
],
|
||||
// Asia mainland
|
||||
asia: [
|
||||
[60, 55], [70, 55], [80, 55], [90, 55], [100, 55], [110, 55], [120, 53],
|
||||
[130, 48], [135, 45], [135, 42], [130, 43], [123, 40], [120, 35],
|
||||
[117, 30], [118, 25], [118, 22], [110, 20], [108, 22], [107, 17],
|
||||
[103, 10], [100, 14], [99, 7], [104, 2], [104, -2], [117, -8],
|
||||
[120, -10], [115, -8], [107, -6], [105, -6], [106, -2], [103, 1],
|
||||
[99, 7], [100, 14], [103, 10], [105, 12], [107, 17], [108, 22],
|
||||
[105, 22], [102, 22], [98, 24], [90, 22], [89, 26], [92, 28],
|
||||
[88, 28], [84, 28], [80, 30], [77, 35], [72, 37], [68, 37],
|
||||
[60, 40], [52, 42], [50, 46], [55, 50], [60, 55]
|
||||
],
|
||||
// India
|
||||
india: [
|
||||
[68, 24], [70, 22], [72, 21], [73, 17], [75, 12], [77, 8], [80, 10],
|
||||
[80, 14], [83, 15], [86, 20], [90, 22], [89, 26], [88, 28], [84, 28],
|
||||
[80, 30], [77, 30], [75, 25], [72, 25], [68, 24]
|
||||
],
|
||||
// Southeast Asia
|
||||
southeastAsia: [
|
||||
[100, 14], [103, 10], [105, 12], [107, 17], [108, 22], [105, 22],
|
||||
[102, 22], [98, 24], [98, 19], [100, 14]
|
||||
],
|
||||
// Japan
|
||||
japan: [
|
||||
[130, 32], [131, 34], [135, 35], [137, 37], [140, 38], [141, 41],
|
||||
[141, 43], [145, 44], [145, 42], [142, 39], [140, 36], [137, 35],
|
||||
[135, 34], [130, 32]
|
||||
],
|
||||
// Korea
|
||||
korea: [
|
||||
[126, 34], [126, 38], [129, 38], [130, 43], [128, 42], [124, 40],
|
||||
[125, 37], [126, 34]
|
||||
],
|
||||
// Philippines
|
||||
philippines: [
|
||||
[117, 7], [120, 10], [122, 13], [124, 17], [122, 19], [120, 16],
|
||||
[118, 12], [117, 7]
|
||||
],
|
||||
// Indonesia (simplified)
|
||||
indonesia: [
|
||||
[95, 6], [98, 4], [103, 1], [106, -2], [106, -6], [110, -7],
|
||||
[115, -8], [120, -10], [127, -8], [131, -2], [136, -2], [141, -5],
|
||||
[141, -9], [131, -8], [120, -10], [115, -8], [110, -7], [106, -6],
|
||||
[106, -2], [103, 1], [98, 4], [95, 6]
|
||||
],
|
||||
// Australia
|
||||
australia: [
|
||||
[114, -22], [114, -26], [115, -32], [117, -35], [122, -34], [129, -32],
|
||||
[132, -32], [134, -33], [137, -35], [140, -38], [144, -38], [147, -38],
|
||||
[150, -37], [153, -29], [153, -25], [149, -21], [145, -15], [142, -11],
|
||||
[136, -12], [130, -15], [129, -17], [123, -17], [119, -20], [114, -22]
|
||||
],
|
||||
// New Zealand
|
||||
newZealand: [
|
||||
[166, -46], [168, -45], [171, -41], [175, -37], [178, -37], [178, -42],
|
||||
[174, -41], [170, -43], [167, -44], [166, -46]
|
||||
],
|
||||
// Taiwan
|
||||
taiwan: [
|
||||
[120, 22], [121, 23], [122, 25], [121, 25], [120, 24], [120, 22]
|
||||
],
|
||||
// Sri Lanka
|
||||
sriLanka: [
|
||||
[80, 6], [80, 8], [82, 10], [82, 7], [80, 6]
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert lat/lon to canvas x/y (equirectangular projection)
|
||||
* This is a simple, accurate 1:1 mapping
|
||||
* Update map with ISS position
|
||||
*/
|
||||
function latLonToXY(lat, lon, width, height, padding) {
|
||||
// Simple linear mapping - guaranteed accurate
|
||||
// Longitude: -180 to 180 maps to padding to width-padding
|
||||
// Latitude: 90 to -90 maps to padding to height-padding
|
||||
const mapWidth = width - 2 * padding;
|
||||
const mapHeight = height - 2 * padding;
|
||||
function updateMap() {
|
||||
if (!issMap || !issPosition) return;
|
||||
|
||||
const x = padding + ((lon + 180) / 360) * mapWidth;
|
||||
const y = padding + ((90 - lat) / 180) * mapHeight;
|
||||
const lat = issPosition.lat;
|
||||
const lon = issPosition.lon;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Render 2D world map with ISS position
|
||||
*/
|
||||
function renderGlobe() {
|
||||
const canvas = document.getElementById('sstvGlobe');
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
const padding = 5;
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// Draw ocean background
|
||||
ctx.fillStyle = '#0a1628';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// Inner map area
|
||||
ctx.fillStyle = '#0d2847';
|
||||
ctx.fillRect(padding, padding, width - 2 * padding, height - 2 * padding);
|
||||
|
||||
// Draw grid lines
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.15)';
|
||||
ctx.lineWidth = 0.5;
|
||||
|
||||
// Latitude lines every 30 degrees
|
||||
for (let lat = -60; lat <= 60; lat += 30) {
|
||||
const p = latLonToXY(lat, -180, width, height, padding);
|
||||
const p2 = latLonToXY(lat, 180, width, height, padding);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p.x, p.y);
|
||||
ctx.lineTo(p2.x, p2.y);
|
||||
ctx.stroke();
|
||||
// Update marker position
|
||||
if (issMarker) {
|
||||
issMarker.setLatLng([lat, lon]);
|
||||
}
|
||||
|
||||
// Longitude lines every 30 degrees
|
||||
for (let lon = -180; lon <= 180; lon += 30) {
|
||||
const p = latLonToXY(90, lon, width, height, padding);
|
||||
const p2 = latLonToXY(-90, lon, width, height, padding);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(p.x, p.y);
|
||||
ctx.lineTo(p2.x, p2.y);
|
||||
ctx.stroke();
|
||||
}
|
||||
// Calculate and draw ground track
|
||||
if (issTrackLine) {
|
||||
const trackPoints = [];
|
||||
const inclination = 51.6; // ISS orbital inclination in degrees
|
||||
|
||||
// Draw equator slightly brighter
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.3)';
|
||||
const eq1 = latLonToXY(0, -180, width, height, padding);
|
||||
const eq2 = latLonToXY(0, 180, width, height, padding);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(eq1.x, eq1.y);
|
||||
ctx.lineTo(eq2.x, eq2.y);
|
||||
ctx.stroke();
|
||||
// Generate orbit track points
|
||||
for (let offset = -180; offset <= 180; offset += 3) {
|
||||
let trackLon = lon + offset;
|
||||
|
||||
// Draw continents
|
||||
ctx.fillStyle = 'rgba(34, 139, 87, 0.6)';
|
||||
ctx.strokeStyle = 'rgba(50, 180, 120, 0.8)';
|
||||
ctx.lineWidth = 1;
|
||||
// Normalize longitude
|
||||
while (trackLon > 180) trackLon -= 360;
|
||||
while (trackLon < -180) trackLon += 360;
|
||||
|
||||
for (const [name, coords] of Object.entries(continents)) {
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < coords.length; i++) {
|
||||
const [lon, lat] = coords[i];
|
||||
const p = latLonToXY(lat, lon, width, height, padding);
|
||||
if (i === 0) {
|
||||
ctx.moveTo(p.x, p.y);
|
||||
} else {
|
||||
ctx.lineTo(p.x, p.y);
|
||||
}
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Draw ISS ground track (orbit path)
|
||||
if (issPosition) {
|
||||
// Draw approximate orbit path (ISS completes orbit in ~92 minutes)
|
||||
// Orbit is inclined at 51.6 degrees
|
||||
ctx.strokeStyle = 'rgba(255, 200, 0, 0.3)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([3, 3]);
|
||||
ctx.beginPath();
|
||||
|
||||
let lastX = null;
|
||||
for (let offset = -180; offset <= 180; offset += 2) {
|
||||
// ISS moves ~360° longitude per 92 minutes, latitude oscillates ±51.6°
|
||||
const orbitLon = issPosition.lon + offset;
|
||||
// Normalize longitude to -180 to 180
|
||||
let normLon = orbitLon;
|
||||
while (normLon > 180) normLon -= 360;
|
||||
while (normLon < -180) normLon += 360;
|
||||
|
||||
// Calculate latitude based on orbit (sinusoidal pattern)
|
||||
// Calculate latitude based on orbital inclination
|
||||
const phase = (offset / 360) * 2 * Math.PI;
|
||||
const orbitLat = 51.6 * Math.sin(phase + Math.asin(issPosition.lat / 51.6));
|
||||
const currentPhase = Math.asin(Math.max(-1, Math.min(1, lat / inclination)));
|
||||
let trackLat = inclination * Math.sin(phase + currentPhase);
|
||||
|
||||
// Clamp latitude to valid range
|
||||
const clampedLat = Math.max(-90, Math.min(90, orbitLat));
|
||||
// Clamp to valid range
|
||||
trackLat = Math.max(-inclination, Math.min(inclination, trackLat));
|
||||
|
||||
const p = latLonToXY(clampedLat, normLon, width, height, padding);
|
||||
|
||||
// Handle wrap-around (don't draw line across the map)
|
||||
if (lastX !== null && Math.abs(p.x - lastX) > width / 2) {
|
||||
ctx.moveTo(p.x, p.y);
|
||||
} else if (offset === -180) {
|
||||
ctx.moveTo(p.x, p.y);
|
||||
} else {
|
||||
ctx.lineTo(p.x, p.y);
|
||||
}
|
||||
lastX = p.x;
|
||||
trackPoints.push([trackLat, trackLon]);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Draw ISS position marker
|
||||
const issP = latLonToXY(issPosition.lat, issPosition.lon, width, height, padding);
|
||||
// Split track at antimeridian to avoid line across map
|
||||
const segments = [];
|
||||
let currentSegment = [];
|
||||
|
||||
// ISS glow
|
||||
const issGradient = ctx.createRadialGradient(issP.x, issP.y, 0, issP.x, issP.y, 15);
|
||||
issGradient.addColorStop(0, 'rgba(255, 200, 0, 0.9)');
|
||||
issGradient.addColorStop(0.4, 'rgba(255, 150, 0, 0.4)');
|
||||
issGradient.addColorStop(1, 'rgba(255, 100, 0, 0)');
|
||||
for (let i = 0; i < trackPoints.length; i++) {
|
||||
if (i > 0) {
|
||||
const prevLon = trackPoints[i - 1][1];
|
||||
const currLon = trackPoints[i][1];
|
||||
if (Math.abs(currLon - prevLon) > 180) {
|
||||
// Crossed antimeridian
|
||||
if (currentSegment.length > 0) {
|
||||
segments.push(currentSegment);
|
||||
}
|
||||
currentSegment = [];
|
||||
}
|
||||
}
|
||||
currentSegment.push(trackPoints[i]);
|
||||
}
|
||||
if (currentSegment.length > 0) {
|
||||
segments.push(currentSegment);
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(issP.x, issP.y, 15, 0, Math.PI * 2);
|
||||
ctx.fillStyle = issGradient;
|
||||
ctx.fill();
|
||||
|
||||
// ISS dot
|
||||
ctx.beginPath();
|
||||
ctx.arc(issP.x, issP.y, 4, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#ffcc00';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.stroke();
|
||||
|
||||
// ISS label
|
||||
ctx.fillStyle = '#ffcc00';
|
||||
ctx.font = 'bold 9px JetBrains Mono, monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('ISS', issP.x, issP.y - 10);
|
||||
// Use only the longest segment or combine if needed
|
||||
issTrackLine.setLatLngs(segments.length > 0 ? segments : []);
|
||||
}
|
||||
|
||||
// Draw border
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.5)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(padding, padding, width - 2 * padding, height - 2 * padding);
|
||||
// Pan map to follow ISS
|
||||
issMap.panTo([lat, lon], { animate: true, duration: 0.5 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -870,48 +633,17 @@ const SSTV = (function() {
|
||||
* Render ISS pass info
|
||||
*/
|
||||
function renderIssInfo(nextPass, hasLocation = true) {
|
||||
const container = document.getElementById('sstvIssInfo');
|
||||
if (!container) return;
|
||||
const passEl = document.getElementById('sstvNextPass');
|
||||
if (!passEl) return;
|
||||
|
||||
if (!nextPass) {
|
||||
const locationMsg = hasLocation
|
||||
? 'No passes in next 48 hours'
|
||||
: 'Set location in Settings > Location tab';
|
||||
const noteMsg = hasLocation
|
||||
? 'Check ARISS.org for SSTV event schedules'
|
||||
: 'Click the gear icon to open Settings';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="sstv-iss-info">
|
||||
<svg class="sstv-iss-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 7L9 3 5 7l4 4"/>
|
||||
<path d="m17 11 4 4-4 4-4-4"/>
|
||||
<path d="m8 12 4 4 6-6-4-4-6 6"/>
|
||||
</svg>
|
||||
<div class="sstv-iss-details">
|
||||
<div class="sstv-iss-label">Next ISS Pass</div>
|
||||
<div class="sstv-iss-value">${locationMsg}</div>
|
||||
<div class="sstv-iss-note">${noteMsg}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
passEl.textContent = hasLocation
|
||||
? 'No passes in 48h'
|
||||
: 'Set location above';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="sstv-iss-info">
|
||||
<svg class="sstv-iss-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 7L9 3 5 7l4 4"/>
|
||||
<path d="m17 11 4 4-4 4-4-4"/>
|
||||
<path d="m8 12 4 4 6-6-4-4-6 6"/>
|
||||
</svg>
|
||||
<div class="sstv-iss-details">
|
||||
<div class="sstv-iss-label">Next ISS Pass</div>
|
||||
<div class="sstv-iss-value">${nextPass.startTime} (${nextPass.maxEl}° max elevation)</div>
|
||||
<div class="sstv-iss-note">Duration: ${nextPass.duration} min | Check ARISS.org for SSTV events</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
passEl.textContent = `${nextPass.startTime} (${nextPass.maxEl}° el, ${nextPass.duration}min)`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-24
@@ -1772,31 +1772,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ISS Info Row (Globe + Pass Info) -->
|
||||
<div class="sstv-iss-row">
|
||||
<!-- World Map -->
|
||||
<div class="sstv-globe-container">
|
||||
<canvas id="sstvGlobe" width="300" height="150"></canvas>
|
||||
<div class="sstv-globe-info">
|
||||
<div class="sstv-globe-label">ISS POSITION</div>
|
||||
<div class="sstv-globe-coords">
|
||||
<span id="sstvIssLat">--.-°</span>, <span id="sstvIssLon">--.-°</span>
|
||||
<!-- ISS Tracking Map -->
|
||||
<div class="sstv-map-row">
|
||||
<div class="sstv-map-container">
|
||||
<div id="sstvIssMap" class="sstv-iss-map"></div>
|
||||
<div class="sstv-map-overlay">
|
||||
<div class="sstv-map-info">
|
||||
<span class="sstv-map-label">ISS</span>
|
||||
<span class="sstv-map-coords"><span id="sstvIssLat">--.-</span>°, <span id="sstvIssLon">--.-</span>°</span>
|
||||
<span class="sstv-map-alt">Alt: <span id="sstvIssAlt">---</span> km</span>
|
||||
</div>
|
||||
<div class="sstv-globe-alt">Alt: <span id="sstvIssAlt">---</span> km</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pass Info -->
|
||||
<div id="sstvIssInfo" class="sstv-pass-info-container">
|
||||
<div class="sstv-iss-info">
|
||||
<svg class="sstv-iss-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 7L9 3 5 7l4 4"/>
|
||||
<path d="m17 11 4 4-4 4-4-4"/>
|
||||
<path d="m8 12 4 4 6-6-4-4-6 6"/>
|
||||
</svg>
|
||||
<div class="sstv-iss-details">
|
||||
<div class="sstv-iss-label">Next ISS Pass</div>
|
||||
<div class="sstv-iss-value">Loading...</div>
|
||||
<div class="sstv-iss-note">Check ARISS.org for SSTV event schedules</div>
|
||||
<div class="sstv-pass-info">
|
||||
<span class="sstv-pass-label">Next Pass:</span>
|
||||
<span class="sstv-pass-value" id="sstvNextPass">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user