Spacey-Backend / physics.js
abcd118q's picture
Upload 5 files
3bfb0ad verified
const satellite = require('satellite.js');
const fs = require('fs');
const path = require('path');
const TLE_CACHE_PATH = path.join(__dirname, 'TLE_cache.json');
const TLE_URL = 'https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle';
const TLE_REFRESH_INTERVAL = 60 * 60 * 1000;
let tleCache = {};
let lastFetchTime = 0;
function deg2rad(deg) {
return deg * (Math.PI / 180);
}
function rad2deg(rad) {
return rad * (180 / Math.PI);
}
function loadTleCache() {
try {
if (fs.existsSync(TLE_CACHE_PATH)) {
const data = fs.readFileSync(TLE_CACHE_PATH, 'utf-8');
tleCache = JSON.parse(data);
}
} catch (e) {
console.error('Failed to load TLE cache:', e.message);
tleCache = {};
}
}
function saveTleCache() {
try {
fs.writeFileSync(TLE_CACHE_PATH, JSON.stringify(tleCache, null, 2));
} catch (e) {
console.error('Failed to save TLE cache:', e.message);
}
}
loadTleCache();
async function fetchTleData() {
try {
const response = await fetch(TLE_URL);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const text = await response.text();
const lines = text.split('\n').filter(l => l.trim());
const newCache = {};
for (let i = 0; i + 2 < lines.length; i += 3) {
const name = lines[i].trim().replace(/^0 /, '');
const line1 = lines[i + 1].trim();
const line2 = lines[i + 2].trim();
if (line1.startsWith('1 ') && line2.startsWith('2 ')) {
newCache[name] = { name, line1, line2 };
}
}
tleCache = newCache;
lastFetchTime = Date.now();
saveTleCache();
console.log(`Fetched ${Object.keys(newCache).length} TLE records`);
return newCache;
} catch (e) {
console.error('TLE fetch failed:', e.message);
return tleCache;
}
}
async function ensureTleFresh() {
if (Date.now() - lastFetchTime > TLE_REFRESH_INTERVAL) {
await fetchTleData();
}
}
function propagateSatellite(tleRecord, date) {
try {
const satrec = satellite.twoline2satrec(tleRecord.line1, tleRecord.line2);
const positionAndVelocity = satellite.propagate(satrec, date);
if (!positionAndVelocity.position) return null;
const positionEci = positionAndVelocity.position;
const gmst = satellite.gstime(date);
const geodetic = satellite.eciToGeodetic(positionEci, gmst);
const lat = rad2deg(geodetic.latitude);
const lng = rad2deg(geodetic.longitude);
const alt = geodetic.height;
const earthRadius = 6371;
const footprintRadius = earthRadius * Math.acos(earthRadius / (earthRadius + alt));
return { lat, lng, alt: alt * 1000, footprintRadius: footprintRadius * 1000 };
} catch (e) {
return null;
}
}
function getActiveSatellitesState(date) {
const names = Object.keys(tleCache);
const state = {};
for (const name of names) {
const result = propagateSatellite(tleCache[name], date);
if (result) {
state[name] = result;
}
}
return state;
}
function getRandomSatelliteState(date) {
const names = Object.keys(tleCache);
if (names.length === 0) return null;
const name = names[Math.floor(Math.random() * names.length)];
const result = propagateSatellite(tleCache[name], date);
if (!result) return null;
return { name, ...result };
}
function getSatelliteState(name, date) {
if (!tleCache[name]) return null;
const result = propagateSatellite(tleCache[name], date);
if (!result) return null;
return { name, ...result };
}
function haversineDistance(lat1, lng1, lat2, lng2) {
const R = 6371000;
const dLat = deg2rad(lat2 - lat1);
const dLng = deg2rad(lng2 - lng1);
const a = Math.sin(dLat / 2) ** 2 +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLng / 2) ** 2;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
function scoreFromDistance(distanceMeters) {
if (distanceMeters < 1000) return 10000;
if (distanceMeters < 10000) return Math.round(10000 - (distanceMeters / 10000) * 2000);
if (distanceMeters < 100000) return Math.round(8000 - (distanceMeters / 100000) * 3000);
if (distanceMeters < 500000) return Math.round(5000 - (distanceMeters / 500000) * 3000);
return Math.max(0, Math.round(2000 - (distanceMeters / 20000000) * 2000));
}
module.exports = {
fetchTleData,
ensureTleFresh,
propagateSatellite,
getActiveSatellitesState,
getRandomSatelliteState,
getSatelliteState,
haversineDistance,
scoreFromDistance,
tleCache,
};