/** * Satelliten-Layer: Orbitale Positionen berechnet aus TLE-Daten. * Vereinfachte Kreisbahn-Berechnung (kein SGP4, reicht fuer Visualisierung). */ const SatellitesLayer = { _viewer: null, _points: null, _orbits: null, _interval: null, _count: 0, _data: [], _animFrame: null, start(viewer) { if (this._points) return; this._viewer = viewer; this._points = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection()); this._orbits = viewer.scene.primitives.add(new Cesium.PolylineCollection()); this._fetch(); var self = this; // Positionen alle 2s aktualisieren (Satelliten bewegen sich schnell) this._interval = setInterval(function() { self._updatePositions(); }, 2000); }, stop() { if (this._interval) { clearInterval(this._interval); this._interval = null; } if (this._points && this._viewer) { this._viewer.scene.primitives.remove(this._points); this._points = null; } if (this._orbits && this._viewer) { this._viewer.scene.primitives.remove(this._orbits); this._orbits = null; } this._count = 0; this._data = []; }, _fetch() { var self = this; var loadEl = document.getElementById('loading-satellites'); var statusEl = document.getElementById('status-satellites'); if (loadEl) loadEl.classList.add('active'); if (statusEl) { statusEl.textContent = 'Lade Orbitdaten...'; statusEl.classList.add('active'); } fetch('/api/satellites') .then(function(r) { return r.json(); }) .then(function(data) { self._data = data.satellites || []; self._count = self._data.length; self._updatePositions(); self._drawOrbits(); if (statusEl) statusEl.textContent = self._count + ' Satelliten'; }) .catch(function(e) { console.warn('Satellites error:', e); if (statusEl) statusEl.textContent = 'Fehler'; }) .finally(function() { if (loadEl) loadEl.classList.remove('active'); setTimeout(function() { if (statusEl) statusEl.classList.remove('active'); }, 5000); }); }, _calcPosition(sat, time) { // Vereinfachte Kreisbahn aus Kepler-Elementen var n = sat.meanMotion; // Umlaeufe pro Tag if (!n || n <= 0) return null; var period = 86400 / n; // Umlaufzeit in Sekunden var a = Math.pow(398600.4418 * Math.pow(period / (2 * Math.PI), 2), 1/3); // Semi-major axis in km var altitude = a - 6371; // Hoehe ueber Erdoberflaeche in km if (altitude < 100 || altitude > 50000) return null; var epochMs = new Date(sat.epoch).getTime(); var elapsed = (time - epochMs) / 1000; // Sekunden seit Epoch var M = (sat.meanAnomaly || 0) + (360 * n / 86400) * elapsed; // Aktuelle Mean Anomaly M = ((M % 360) + 360) % 360; // Normalisieren var inc = (sat.inclination || 0) * Math.PI / 180; var raan = (sat.raOfAscNode || 0) * Math.PI / 180; var argP = (sat.argOfPericenter || 0) * Math.PI / 180; var nu = M * Math.PI / 180; // True anomaly ≈ Mean anomaly fuer kleine Exzentrizitaet // Position im Orbital-Frame var u = argP + nu; var r = a; // Kreisbahn-Naeherung // ECI -> ECEF (vereinfacht: Erdrotation ignoriert fuer Visualisierung) var x = r * (Math.cos(raan) * Math.cos(u) - Math.sin(raan) * Math.sin(u) * Math.cos(inc)); var y = r * (Math.sin(raan) * Math.cos(u) + Math.cos(raan) * Math.sin(u) * Math.cos(inc)); var z = r * Math.sin(u) * Math.sin(inc); // Erdrotation beruecksichtigen (grob) var gmst = elapsed * 7.2921159e-5; // Sternzeit-Winkel var xr = x * Math.cos(gmst) + y * Math.sin(gmst); var yr = -x * Math.sin(gmst) + y * Math.cos(gmst); var lon = Math.atan2(yr, xr) * 180 / Math.PI; var lat = Math.asin(z / r) * 180 / Math.PI; return { lat: lat, lon: lon, alt: altitude * 1000 }; // alt in Metern }, _updatePositions() { if (!this._points || !this._data.length) return; this._points.removeAll(); var now = Date.now(); var colors = { 'stations': Cesium.Color.fromCssColorString('#ff4444'), 'gps-ops': Cesium.Color.fromCssColorString('#ffaa00'), 'galileo': Cesium.Color.fromCssColorString('#44aaff'), 'weather': Cesium.Color.fromCssColorString('#aa44ff'), 'resource': Cesium.Color.fromCssColorString('#44ffaa'), 'starlink': Cesium.Color.fromCssColorString('#888888'), }; for (var i = 0; i < this._data.length; i++) { var pos = this._calcPosition(this._data[i], now); if (!pos) continue; var color = colors[this._data[i].group] || Cesium.Color.WHITE; this._points.add({ position: Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, pos.alt), pixelSize: this._data[i].group === 'starlink' ? 1.5 : 3, color: color, }); } }, _drawOrbits() { if (!this._orbits || !this._data.length) return; this._orbits.removeAll(); var now = Date.now(); var colors = { 'stations': Cesium.Color.fromCssColorString('#ff4444').withAlpha(0.4), 'gps-ops': Cesium.Color.fromCssColorString('#ffaa00').withAlpha(0.15), 'galileo': Cesium.Color.fromCssColorString('#44aaff').withAlpha(0.15), }; // Nur Orbits fuer Stationen, GPS, Galileo zeichnen (nicht Starlink — zu viele) for (var i = 0; i < this._data.length; i++) { var sat = this._data[i]; if (!colors[sat.group]) continue; var positions = []; var period = 86400000 / (sat.meanMotion || 14); // Umlaufzeit in ms for (var t = 0; t <= period; t += period / 60) { var pos = this._calcPosition(sat, now + t); if (pos) positions.push(Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, pos.alt)); } if (positions.length > 10) { this._orbits.add({ positions: positions, width: sat.group === 'stations' ? 1.5 : 0.5, material: Cesium.Material.fromType('Color', { color: colors[sat.group] }), }); } } }, };