| |
| |
| |
| |
|
|
|
|
| export class ISSTelemetry {
|
| constructor(scene, options = {}) {
|
| this.scene = scene;
|
| this.voyagerUrl = options.voyagerUrl || 'http://localhost:4299';
|
| this.issMesh = null;
|
| this.trailPoints = [];
|
| this.trailGeometry = null;
|
| this.trailMesh = null;
|
| this.hudElement = options.hudElement || null;
|
| this.isLive = false;
|
| this.lastUpdate = null;
|
| }
|
|
|
| |
| |
| |
|
|
| latLonToBloch(lat, lon, alt) {
|
|
|
| const latRad = (lat * Math.PI) / 180;
|
| const lonRad = (lon * Math.PI) / 180;
|
|
|
|
|
| const altNorm = Math.min(alt / 500, 1.0) * 0.3;
|
|
|
|
|
| const radius = 1.0 + altNorm;
|
| const x = radius * Math.cos(latRad) * Math.cos(lonRad);
|
| const y = radius * Math.sin(latRad);
|
| const z = radius * Math.cos(latRad) * Math.sin(lonRad);
|
|
|
| return { x, y, z };
|
| }
|
|
|
| |
| |
|
|
| createISSTelemetryMarker() {
|
| if (this.issMesh) {
|
| this.scene.remove(this.issMesh);
|
| }
|
|
|
|
|
| const geo = new THREE.SphereGeometry(0.08, 16, 16);
|
| const mat = new THREE.MeshPhongMaterial({
|
| color: 0xff00ff,
|
| emissive: 0xff00ff,
|
| emissiveIntensity: 0.8,
|
| transparent: true,
|
| opacity: 0.9,
|
| });
|
| this.issMesh = new THREE.Mesh(geo, mat);
|
| this.scene.add(this.issMesh);
|
|
|
|
|
| const glowGeo = new THREE.SphereGeometry(0.12, 16, 16);
|
| const glowMat = new THREE.MeshBasicMaterial({
|
| color: 0xff00ff,
|
| transparent: true,
|
| opacity: 0.3,
|
| });
|
| const glowMesh = new THREE.Mesh(glowGeo, glowMat);
|
| this.issMesh.add(glowMesh);
|
| }
|
|
|
| |
| |
|
|
| initializeTrail() {
|
| if (this.trailMesh) {
|
| this.scene.remove(this.trailMesh);
|
| }
|
|
|
| this.trailPoints = [];
|
| this.trailGeometry = new THREE.BufferGeometry();
|
| const trailMat = new THREE.LineBasicMaterial({
|
| color: 0xff00ff,
|
| transparent: true,
|
| opacity: 0.4,
|
| linewidth: 1,
|
| });
|
| this.trailMesh = new THREE.Line(this.trailGeometry, trailMat);
|
| this.scene.add(this.trailMesh);
|
| }
|
|
|
| |
| |
|
|
| addToTrail(pos) {
|
| this.trailPoints.push(new THREE.Vector3(pos.x, pos.y, pos.z));
|
|
|
|
|
| if (this.trailPoints.length > 200) {
|
| this.trailPoints.shift();
|
| }
|
|
|
| if (this.trailGeometry) {
|
| this.trailGeometry.setFromPoints(this.trailPoints);
|
| }
|
| }
|
|
|
| |
| |
|
|
| async fetchISSTelemetry() {
|
| try {
|
| const res = await fetch(`${this.voyagerUrl}/api/telemetry`);
|
| const data = await res.json();
|
|
|
| if (!data.ok || !data.telemetry) {
|
| console.error('[ISS] Invalid telemetry response');
|
| return null;
|
| }
|
|
|
| return {
|
| lat: data.telemetry.latitude,
|
| lon: data.telemetry.longitude,
|
| alt: data.telemetry.altitude,
|
| vel: data.telemetry.velocity,
|
| timestamp: data.telemetry.fetched_at,
|
| worm: data.worm_head,
|
| };
|
| } catch (e) {
|
| console.error('[ISS] Fetch failed:', e.message);
|
| return null;
|
| }
|
| }
|
|
|
| |
| |
|
|
| async update() {
|
| const telemetry = await this.fetchISSTelemetry();
|
| if (!telemetry) return;
|
|
|
| if (!this.issMesh) {
|
| this.createISSTelemetryMarker();
|
| this.initializeTrail();
|
| }
|
|
|
|
|
| const pos = this.latLonToBloch(telemetry.lat, telemetry.lon, telemetry.alt);
|
| this.issMesh.position.set(pos.x, pos.y, pos.z);
|
|
|
|
|
| if (this.issMesh.children[0]) {
|
| this.issMesh.children[0].material.opacity = 0.3 + 0.2 * Math.sin(Date.now() * 0.005);
|
| }
|
|
|
|
|
| this.addToTrail(pos);
|
|
|
|
|
| if (this.hudElement) {
|
| this.hudElement.innerHTML = `
|
| <div><span class="label">ISS:</span> <span class="value">${telemetry.lat.toFixed(2)}° ${telemetry.lon.toFixed(2)}°</span></div>
|
| <div><span class="label">ALT:</span> <span class="value">${telemetry.alt.toFixed(0)}km VEL ${telemetry.vel.toFixed(2)}km/s</span></div>
|
| <div><span class="label">WORM:</span> <span class="value">${telemetry.worm}</span></div>
|
| `;
|
| }
|
|
|
| this.lastUpdate = telemetry;
|
| this.isLive = true;
|
| }
|
|
|
| |
| |
|
|
| startPolling(intervalMs = 4500) {
|
| console.log('[ISS] Starting telemetry polling at', intervalMs, 'ms interval');
|
| this.pollingInterval = setInterval(() => this.update(), intervalMs);
|
|
|
| this.update();
|
| }
|
|
|
| |
| |
|
|
| stopPolling() {
|
| if (this.pollingInterval) {
|
| clearInterval(this.pollingInterval);
|
| }
|
| }
|
|
|
| |
| |
|
|
| getLastTelemetry() {
|
| return this.lastUpdate;
|
| }
|
| }
|
|
|