From 5f29957704e8cb5c5b92bd44cfc2ce418dcb224a Mon Sep 17 00:00:00 2001 From: accius Date: Sun, 1 Feb 2026 23:52:46 -0500 Subject: [PATCH] sat updates --- server.js | 71 ++++++++++++++++++++++----- src/components/WorldMap.jsx | 18 ++++--- src/hooks/useSatellites.js | 97 ++++++++++++++++--------------------- 3 files changed, 114 insertions(+), 72 deletions(-) diff --git a/server.js b/server.js index 26bd7a5..f151355 100644 --- a/server.js +++ b/server.js @@ -1509,18 +1509,67 @@ app.get('/api/myspots/:callsign', async (req, res) => { // SATELLITE TRACKING API // ============================================ -// Ham radio satellites - NORAD IDs +// Comprehensive ham radio satellites - NORAD IDs +// Updated list of active amateur radio satellites const HAM_SATELLITES = { - 'ISS': { norad: 25544, name: 'ISS (ZARYA)', color: '#00ffff', priority: 1 }, - 'AO-91': { norad: 43017, name: 'AO-91 (Fox-1B)', color: '#ff6600', priority: 2 }, - 'AO-92': { norad: 43137, name: 'AO-92 (Fox-1D)', color: '#ff9900', priority: 2 }, - 'SO-50': { norad: 27607, name: 'SO-50 (SaudiSat)', color: '#00ff00', priority: 2 }, - 'RS-44': { norad: 44909, name: 'RS-44 (DOSAAF)', color: '#ff0066', priority: 2 }, - 'IO-117': { norad: 53106, name: 'IO-117 (GreenCube)', color: '#00ff99', priority: 3 }, - 'CAS-4A': { norad: 42761, name: 'CAS-4A (ZHUHAI-1 01)', color: '#9966ff', priority: 3 }, - 'CAS-4B': { norad: 42759, name: 'CAS-4B (ZHUHAI-1 02)', color: '#9933ff', priority: 3 }, - 'PO-101': { norad: 43678, name: 'PO-101 (Diwata-2)', color: '#ff3399', priority: 3 }, - 'TEVEL': { norad: 50988, name: 'TEVEL-1', color: '#66ccff', priority: 4 } + // High Priority - Popular FM satellites + 'ISS': { norad: 25544, name: 'ISS (ZARYA)', color: '#00ffff', priority: 1, mode: 'FM/APRS/SSTV' }, + 'SO-50': { norad: 27607, name: 'SO-50', color: '#00ff00', priority: 1, mode: 'FM' }, + 'AO-91': { norad: 43017, name: 'AO-91 (Fox-1B)', color: '#ff6600', priority: 1, mode: 'FM' }, + 'AO-92': { norad: 43137, name: 'AO-92 (Fox-1D)', color: '#ff9900', priority: 1, mode: 'FM/L-band' }, + 'PO-101': { norad: 43678, name: 'PO-101 (Diwata-2)', color: '#ff3399', priority: 1, mode: 'FM' }, + + // Linear Transponder Satellites + 'RS-44': { norad: 44909, name: 'RS-44 (DOSAAF)', color: '#ff0066', priority: 1, mode: 'Linear' }, + 'AO-7': { norad: 7530, name: 'AO-7', color: '#ffcc00', priority: 2, mode: 'Linear (daylight)' }, + 'FO-29': { norad: 24278, name: 'FO-29 (JAS-2)', color: '#ff6699', priority: 2, mode: 'Linear' }, + 'FO-99': { norad: 43937, name: 'FO-99 (NEXUS)', color: '#ff99cc', priority: 2, mode: 'Linear' }, + 'JO-97': { norad: 43803, name: 'JO-97 (JY1Sat)', color: '#cc99ff', priority: 2, mode: 'Linear/FM' }, + 'XW-2A': { norad: 40903, name: 'XW-2A (CAS-3A)', color: '#66ff99', priority: 2, mode: 'Linear' }, + 'XW-2B': { norad: 40911, name: 'XW-2B (CAS-3B)', color: '#66ffcc', priority: 2, mode: 'Linear' }, + 'XW-2C': { norad: 40906, name: 'XW-2C (CAS-3C)', color: '#99ffcc', priority: 2, mode: 'Linear' }, + 'XW-2D': { norad: 40907, name: 'XW-2D (CAS-3D)', color: '#99ff99', priority: 2, mode: 'Linear' }, + 'XW-2E': { norad: 40909, name: 'XW-2E (CAS-3E)', color: '#ccff99', priority: 2, mode: 'Linear' }, + 'XW-2F': { norad: 40910, name: 'XW-2F (CAS-3F)', color: '#ccffcc', priority: 2, mode: 'Linear' }, + + // CAS (Chinese Amateur Satellites) + 'CAS-4A': { norad: 42761, name: 'CAS-4A', color: '#9966ff', priority: 2, mode: 'Linear' }, + 'CAS-4B': { norad: 42759, name: 'CAS-4B', color: '#9933ff', priority: 2, mode: 'Linear' }, + 'CAS-6': { norad: 44881, name: 'CAS-6 (TO-108)', color: '#cc66ff', priority: 2, mode: 'Linear' }, + + // GreenCube / IO satellites + 'IO-117': { norad: 53106, name: 'IO-117 (GreenCube)', color: '#00ff99', priority: 2, mode: 'Digipeater' }, + + // TEVEL constellation + 'TEVEL-1': { norad: 50988, name: 'TEVEL-1', color: '#66ccff', priority: 3, mode: 'FM' }, + 'TEVEL-2': { norad: 50989, name: 'TEVEL-2', color: '#66ddff', priority: 3, mode: 'FM' }, + 'TEVEL-3': { norad: 50994, name: 'TEVEL-3', color: '#66eeff', priority: 3, mode: 'FM' }, + 'TEVEL-4': { norad: 50998, name: 'TEVEL-4', color: '#77ccff', priority: 3, mode: 'FM' }, + 'TEVEL-5': { norad: 51062, name: 'TEVEL-5', color: '#77ddff', priority: 3, mode: 'FM' }, + 'TEVEL-6': { norad: 51063, name: 'TEVEL-6', color: '#77eeff', priority: 3, mode: 'FM' }, + 'TEVEL-7': { norad: 51069, name: 'TEVEL-7', color: '#88ccff', priority: 3, mode: 'FM' }, + 'TEVEL-8': { norad: 51084, name: 'TEVEL-8', color: '#88ddff', priority: 3, mode: 'FM' }, + + // OSCAR satellites + 'AO-27': { norad: 22825, name: 'AO-27', color: '#ff9966', priority: 3, mode: 'FM' }, + 'AO-73': { norad: 39444, name: 'AO-73 (FUNcube-1)', color: '#ffcc66', priority: 3, mode: 'Linear/Telemetry' }, + 'EO-88': { norad: 42017, name: 'EO-88 (Nayif-1)', color: '#ffaa66', priority: 3, mode: 'Linear/Telemetry' }, + + // Russian satellites + 'RS-15': { norad: 23439, name: 'RS-15', color: '#ff6666', priority: 3, mode: 'Linear' }, + + // QO-100 (Geostationary - special) + 'QO-100': { norad: 43700, name: 'QO-100 (Es\'hail-2)', color: '#ffff00', priority: 1, mode: 'Linear (GEO)' }, + + // APRS Digipeaters + 'ARISS': { norad: 25544, name: 'ARISS (ISS)', color: '#00ffff', priority: 1, mode: 'APRS' }, + + // Cubesats with amateur payloads + 'UVSQ-SAT': { norad: 47438, name: 'UVSQ-SAT', color: '#ff66ff', priority: 4, mode: 'Telemetry' }, + 'MEZNSAT': { norad: 46489, name: 'MeznSat', color: '#66ff66', priority: 4, mode: 'Telemetry' }, + + // SSTV/Slow Scan + 'SSTV-ISS': { norad: 25544, name: 'ISS SSTV', color: '#00ffff', priority: 2, mode: 'SSTV' } }; // Cache for TLE data (refresh every 6 hours) diff --git a/src/components/WorldMap.jsx b/src/components/WorldMap.jsx index 8ffc8a2..6ad138d 100644 --- a/src/components/WorldMap.jsx +++ b/src/components/WorldMap.jsx @@ -342,6 +342,9 @@ export const WorldMap = ({ if (showSatellites && satellites && satellites.length > 0) { satellites.forEach(sat => { + const satColor = sat.color || '#00ffff'; + const satColorDark = sat.visible ? satColor : '#446666'; + // Draw orbit track if available if (sat.track && sat.track.length > 1) { // Split track into segments to handle date line crossing @@ -364,7 +367,7 @@ export const WorldMap = ({ segments.forEach(segment => { if (segment.length > 1) { const trackLine = L.polyline(segment, { - color: sat.visible ? '#00ffff' : '#006688', + color: sat.visible ? satColor : satColorDark, weight: 2, opacity: sat.visible ? 0.8 : 0.4, dashArray: sat.visible ? null : '5, 5' @@ -374,14 +377,14 @@ export const WorldMap = ({ }); } - // Draw footprint circle if available - if (sat.footprintRadius && sat.lat && sat.lon) { + // Draw footprint circle if available and satellite is visible + if (sat.footprintRadius && sat.lat && sat.lon && sat.visible) { const footprint = L.circle([sat.lat, sat.lon], { radius: sat.footprintRadius * 1000, // Convert km to meters - color: '#00ffff', + color: satColor, weight: 1, opacity: 0.5, - fillColor: '#00ffff', + fillColor: satColor, fillOpacity: 0.1 }).addTo(map); satTracksRef.current.push(footprint); @@ -390,7 +393,7 @@ export const WorldMap = ({ // Add satellite marker icon const icon = L.divIcon({ className: '', - html: `🛰 ${sat.name}`, + html: `🛰 ${sat.name}`, iconSize: null, iconAnchor: [0, 0] }); @@ -399,11 +402,12 @@ export const WorldMap = ({ .bindPopup(` 🛰 ${sat.name}
+ - +
Mode:${sat.mode || 'Unknown'}
Alt:${sat.alt} km
Az:${sat.azimuth}°
El:${sat.elevation}°
Range:${sat.range} km
Status:${sat.visible ? 'Visible' : 'Below horizon'}
Status:${sat.visible ? '✓ Visible' : 'Below horizon'}
`) .addTo(map); diff --git a/src/hooks/useSatellites.js b/src/hooks/useSatellites.js index d3d7499..22bdcc1 100644 --- a/src/hooks/useSatellites.js +++ b/src/hooks/useSatellites.js @@ -6,20 +6,6 @@ import { useState, useEffect, useCallback } from 'react'; import * as satellite from 'satellite.js'; -// List of popular amateur radio satellites -const AMATEUR_SATS = [ - 'ISS (ZARYA)', - 'SO-50', - 'AO-91', - 'AO-92', - 'CAS-4A', - 'CAS-4B', - 'XW-2A', - 'XW-2B', - 'JO-97', - 'RS-44' -]; - export const useSatellites = (observerLocation) => { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); @@ -92,54 +78,57 @@ export const useSatellites = (observerLocation) => { const elevation = satellite.radiansToDegrees(lookAngles.elevation); const rangeSat = lookAngles.rangeSat; - // Only include if above horizon or popular sat - const isPopular = AMATEUR_SATS.some(s => name.includes(s)); - if (elevation > -5 || isPopular) { - // Calculate orbit track (past 45 min and future 45 min = 90 min total) - const track = []; - const trackMinutes = 90; - const stepMinutes = 1; + // Include all satellites we get TLE for (they're all ham sats) + // Calculate orbit track (past 45 min and future 45 min = 90 min total) + const track = []; + const trackMinutes = 90; + const stepMinutes = 1; + + for (let m = -trackMinutes/2; m <= trackMinutes/2; m += stepMinutes) { + const trackTime = new Date(now.getTime() + m * 60 * 1000); + const trackPV = satellite.propagate(satrec, trackTime); - for (let m = -trackMinutes/2; m <= trackMinutes/2; m += stepMinutes) { - const trackTime = new Date(now.getTime() + m * 60 * 1000); - const trackPV = satellite.propagate(satrec, trackTime); - - if (trackPV.position) { - const trackGmst = satellite.gstime(trackTime); - const trackGd = satellite.eciToGeodetic(trackPV.position, trackGmst); - const trackLat = satellite.degreesLat(trackGd.latitude); - const trackLon = satellite.degreesLong(trackGd.longitude); - track.push([trackLat, trackLon]); - } + if (trackPV.position) { + const trackGmst = satellite.gstime(trackTime); + const trackGd = satellite.eciToGeodetic(trackPV.position, trackGmst); + const trackLat = satellite.degreesLat(trackGd.latitude); + const trackLon = satellite.degreesLong(trackGd.longitude); + track.push([trackLat, trackLon]); } - - // Calculate footprint radius (visibility circle) - // Formula: radius = Earth_radius * arccos(Earth_radius / (Earth_radius + altitude)) - const earthRadius = 6371; // km - const footprintRadius = earthRadius * Math.acos(earthRadius / (earthRadius + alt)); - - positions.push({ - name, - lat, - lon, - alt: Math.round(alt), - azimuth: Math.round(azimuth), - elevation: Math.round(elevation), - range: Math.round(rangeSat), - visible: elevation > 0, - isPopular, - track, - footprintRadius: Math.round(footprintRadius) - }); } + + // Calculate footprint radius (visibility circle) + // Formula: radius = Earth_radius * arccos(Earth_radius / (Earth_radius + altitude)) + const earthRadius = 6371; // km + const footprintRadius = earthRadius * Math.acos(earthRadius / (earthRadius + alt)); + + positions.push({ + name: tle.name || name, + lat, + lon, + alt: Math.round(alt), + azimuth: Math.round(azimuth), + elevation: Math.round(elevation), + range: Math.round(rangeSat), + visible: elevation > 0, + isPopular: tle.priority <= 2, + track, + footprintRadius: Math.round(footprintRadius), + mode: tle.mode || 'Unknown', + color: tle.color || '#00ffff' + }); } catch (e) { // Skip satellites with invalid TLE } }); - // Sort by elevation (highest first) and limit - positions.sort((a, b) => b.elevation - a.elevation); - setData(positions.slice(0, 15)); + // Sort by visibility first (visible on top), then by elevation + positions.sort((a, b) => { + if (a.visible !== b.visible) return b.visible - a.visible; + return b.elevation - a.elevation; + }); + // Show all satellites (no limit for ham sats) + setData(positions); setLoading(false); } catch (err) { console.error('Satellite calculation error:', err);