mission-control/scripts/update-space.ts
GeorgeWebberley 3a9b6704cd
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
retrying docker communication to net
2026-01-28 21:18:25 +01:00

196 lines
5.9 KiB
TypeScript

import db from '../lib/db';
import * as satellite from 'satellite.js';
import path from 'path';
import fs from 'fs';
import { CosmicEvent } from '@/types/space';
const MY_LAT = 55.6683;
const MY_LON = 12.5333;
const MY_ALT = 0;
async function updateISSData() {
console.log("🛰️ Fetching TLE data...");
const response = await fetch('https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=tle');
const data = await response.text();
const lines = data.split('\n').filter(line => line.trim().length > 0);
const line1 = lines[1];
const line2 = lines[2];
const satrec = satellite.twoline2satrec(line1, line2);
const observerGd = {
longitude: satellite.degreesToRadians(MY_LON),
latitude: satellite.degreesToRadians(MY_LAT),
height: MY_ALT
};
const passesFound: { start: Date, end: Date }[] = [];
const now = new Date();
const searchTime = new Date(now.getTime());
const maxSearchMinutes = 2880;
let minutesSearched = 0;
while (passesFound.length < 2 && minutesSearched < maxSearchMinutes) {
const checkTime = new Date(searchTime.getTime() + (minutesSearched * 60000));
const positionAndVelocity = satellite.propagate(satrec, checkTime);
const gmst = satellite.gstime(checkTime);
if (typeof positionAndVelocity?.position !== 'boolean') {
const positionEcf = satellite.eciToEcf(positionAndVelocity!.position, gmst);
const lookAngles = satellite.ecfToLookAngles(observerGd, positionEcf);
const elevation = satellite.radiansToDegrees(lookAngles.elevation);
if (elevation > 0) {
const aos = checkTime;
let los = new Date(aos.getTime() + 5 * 60000);
for (let j = 1; j < 20; j++) {
const exitTime = new Date(aos.getTime() + j * 60000);
const pos = satellite.propagate(satrec, exitTime);
const gmstExit = satellite.gstime(exitTime);
if (typeof pos?.position !== 'boolean') {
const lookExit = satellite.ecfToLookAngles(observerGd, satellite.eciToEcf(pos!.position, gmstExit));
if (satellite.radiansToDegrees(lookExit.elevation) < 0) {
los = exitTime;
break;
}
}
}
passesFound.push({ start: aos, end: los });
minutesSearched += 90;
continue;
}
}
minutesSearched++;
}
db.prepare('DELETE FROM iss_passes').run();
const insert = db.prepare('INSERT INTO iss_passes (pass_time, end_time) VALUES (?, ?)');
for (const pass of passesFound) {
insert.run(pass.start.toISOString(), pass.end.toISOString());
}
console.log(`✅ Stored ${passesFound.length} future passes with start and end times.`);
}
function updateMoonPhase() {
const LUNAR_CYCLE = 29.53059;
const KNOWN_NEW_MOON = new Date('2024-01-11T11:57:00Z');
const now = new Date();
const daysSince = (now.getTime() - KNOWN_NEW_MOON.getTime()) / 86400000;
const progress = daysSince % LUNAR_CYCLE;
const daysToFull = (LUNAR_CYCLE / 2) - progress;
const daysToNew = LUNAR_CYCLE - progress;
const moon = daysToFull > 0
? { title: "Next Full Moon", date: new Date(now.getTime() + daysToFull * 86400000) }
: { title: "Next New Moon", date: new Date(now.getTime() + daysToNew * 86400000) };
db.prepare(`
INSERT INTO global_events (id, title, event_time) VALUES ('moon_phase', ?, ?)
ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
`).run(moon.title, moon.date.toISOString());
console.log(`🌙 Moon: ${moon.title}`);
}
function updateCosmicEvents() {
const configPath = path.resolve(process.cwd(), 'config/events.json');
if (!fs.existsSync(configPath)) return;
const events: CosmicEvent[] = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
const now = new Date();
const next = events
.filter((e) => new Date(e.event_time) > now)
.sort((a, b) => new Date(a.event_time).getTime() - new Date(b.event_time).getTime())[0];
if (next) {
db.prepare(`
INSERT INTO global_events (id, title, event_time) VALUES ('next_cosmic_event', ?, ?)
ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
`).run(next.title, next.event_time);
console.log(`✨ Cosmic: ${next.title}`);
}
}
async function updateRocketLaunches() {
try {
const nowISO = new Date().toISOString();
const url = `https://lldev.thespacedevs.com/2.2.0/launch/upcoming/?limit=1&mode=list&net__gt=${nowISO}`;
const response = await fetch(url);
const data = await response.json();
if (data.results && data.results.length > 0) {
const launch = data.results[0];
db.prepare(`
INSERT INTO global_events (id, title, event_time)
VALUES ('next_launch', ?, ?)
ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
`).run(
`Launch: ${launch.name}`,
launch.net
);
console.log(`🚀 Rocket Sync: ${launch.name}`);
}
} catch (error) {
console.error("❌ Rocket Fetch Error:", error);
}
}
async function updateAllData() {
console.log("📡 Starting Ground Station sync...");
try {
await updateISSData();
} catch (e) {
console.error("⚠️ ISS sync failed (Timeout/Network)", e);
}
try {
updateMoonPhase();
} catch (e) {
console.error("⚠️ Moon sync failed", e);
}
try {
updateCosmicEvents();
} catch (e) {
console.error("⚠️ Cosmic sync failed", e);
}
try {
await updateRocketLaunches();
} catch (e) {
console.error("⚠️ Rocket sync failed", e);
}
}
async function startWorker() {
console.log("🛰️ Ground Station Worker started...");
while (true) {
try {
await updateAllData();
console.log("✅ Sync complete. Sleeping for 1 hour...");
} catch (err) {
console.error("❌ Worker loop error:", err);
}
// Sleep for 3600000ms (1 hour)
await new Promise(resolve => setTimeout(resolve, 3600000));
}
}
startWorker();