Sonnenterminatur + Zeitzonen-Overlay
SONNENTERMINATUR: - Echtzeit Tag/Nacht-Grenze als orange Linie auf dem Globus - Nachtseite als halbtransparenter schwarzer Schatten - Basiert auf Sonnendeklination + UTC-Zeit - Aktualisiert jede Minute ZEITZONEN: - 24 vertikale Linien (alle 15 Grad Laengengrad) - Jede Zone zeigt aktuelle Uhrzeit (z.B. UTC+2 14:30) - Labels bei Zoom (<8000km) sichtbar - Aktualisiert jede Minute Alle Features der ersten Tabelle nun implementiert.
Dieser Commit ist enthalten in:
@@ -574,3 +574,6 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo
|
|||||||
.dot-cables { background: #00ccff; }
|
.dot-cables { background: #00ccff; }
|
||||||
.dot-infra { background: #ffdd00; }
|
.dot-infra { background: #ffdd00; }
|
||||||
.dot-iss { background: #ff4444; box-shadow: 0 0 6px rgba(255,68,68,0.6); }
|
.dot-iss { background: #ff4444; box-shadow: 0 0 6px rgba(255,68,68,0.6); }
|
||||||
|
|
||||||
|
.dot-terminator { background: #ff8800; }
|
||||||
|
.dot-timezones { background: #8888ff; }
|
||||||
|
|||||||
@@ -132,6 +132,16 @@
|
|||||||
<span class="layer-dot dot-daynight"></span>
|
<span class="layer-dot dot-daynight"></span>
|
||||||
<span class="layer-name" title="Sonnenlicht-Simulation zeigt aktuelle Tages-/Nachtseite">Tag/Nacht</span>
|
<span class="layer-name" title="Sonnenlicht-Simulation zeigt aktuelle Tages-/Nachtseite">Tag/Nacht</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="layer-toggle">
|
||||||
|
<input type="checkbox" id="layer-terminator" title="Echtzeit Tag/Nacht-Grenze als orange Linie + Nachtschatten">
|
||||||
|
<span class="layer-dot dot-terminator"></span>
|
||||||
|
<span class="layer-name" title="Sonnenterminatur: Grenze zwischen Tag und Nacht">Terminatur</span>
|
||||||
|
</label>
|
||||||
|
<label class="layer-toggle">
|
||||||
|
<input type="checkbox" id="layer-timezones" title="24 Zeitzonen-Linien mit aktueller Uhrzeit">
|
||||||
|
<span class="layer-dot dot-timezones"></span>
|
||||||
|
<span class="layer-name" title="UTC-Zonen als vertikale Linien mit Uhrzeiten">Zeitzonen</span>
|
||||||
|
</label>
|
||||||
<label class="layer-toggle">
|
<label class="layer-toggle">
|
||||||
<input type="checkbox" id="layer-crosshairs" title="Fadenkreuz und Entfernungsringe">
|
<input type="checkbox" id="layer-crosshairs" title="Fadenkreuz und Entfernungsringe">
|
||||||
<span class="layer-dot dot-crosshairs"></span>
|
<span class="layer-dot dot-crosshairs"></span>
|
||||||
@@ -228,6 +238,8 @@
|
|||||||
<script src="/static/js/layers/cables.js"></script>
|
<script src="/static/js/layers/cables.js"></script>
|
||||||
<script src="/static/js/layers/infra.js"></script>
|
<script src="/static/js/layers/infra.js"></script>
|
||||||
<script src="/static/js/layers/iss.js"></script>
|
<script src="/static/js/layers/iss.js"></script>
|
||||||
|
<script src="/static/js/layers/terminator.js"></script>
|
||||||
|
<script src="/static/js/layers/timezones.js"></script>
|
||||||
<script src="/static/js/layers/weather.js"></script>
|
<script src="/static/js/layers/weather.js"></script>
|
||||||
<script src="/static/js/ui/imagery.js"></script>
|
<script src="/static/js/ui/imagery.js"></script>
|
||||||
<script src="/static/js/ui/search.js"></script>
|
<script src="/static/js/ui/search.js"></script>
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ const Globe = {
|
|||||||
'layer-disasters': function(on) { on ? DisastersLayer.start(Globe.viewer) : DisastersLayer.stop(); },
|
'layer-disasters': function(on) { on ? DisastersLayer.start(Globe.viewer) : DisastersLayer.stop(); },
|
||||||
'layer-weather': function(on) { on ? WeatherLayer.start(Globe.viewer) : WeatherLayer.stop(); },
|
'layer-weather': function(on) { on ? WeatherLayer.start(Globe.viewer) : WeatherLayer.stop(); },
|
||||||
'layer-daynight': function(on) { Globe.viewer.scene.globe.enableLighting = on; },
|
'layer-daynight': function(on) { Globe.viewer.scene.globe.enableLighting = on; },
|
||||||
|
'layer-terminator': function(on) { on ? TerminatorLayer.start(Globe.viewer) : TerminatorLayer.stop(); },
|
||||||
|
'layer-timezones': function(on) { on ? TimezonesLayer.start(Globe.viewer) : TimezonesLayer.stop(); },
|
||||||
'layer-crosshairs': function(on) { CrosshairsUI.toggle(on); },
|
'layer-crosshairs': function(on) { CrosshairsUI.toggle(on); },
|
||||||
'layer-labels': function(on) { Globe._toggleLabels(on); },
|
'layer-labels': function(on) { Globe._toggleLabels(on); },
|
||||||
};
|
};
|
||||||
|
|||||||
91
static/js/layers/terminator.js
Normale Datei
91
static/js/layers/terminator.js
Normale Datei
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Sonnenterminatur: Echtzeit Tag/Nacht-Grenze als Linie + Nachtflaeche.
|
||||||
|
*/
|
||||||
|
const TerminatorLayer = {
|
||||||
|
_viewer: null,
|
||||||
|
_entity: null,
|
||||||
|
_line: null,
|
||||||
|
_interval: null,
|
||||||
|
_count: 0,
|
||||||
|
|
||||||
|
start(viewer) {
|
||||||
|
if (this._entity) return;
|
||||||
|
this._viewer = viewer;
|
||||||
|
this._render();
|
||||||
|
this._count = 1;
|
||||||
|
var self = this;
|
||||||
|
this._interval = setInterval(function() { self._render(); }, 60000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this._interval) { clearInterval(this._interval); this._interval = null; }
|
||||||
|
if (this._entity && this._viewer) { this._viewer.entities.remove(this._entity); this._entity = null; }
|
||||||
|
if (this._line && this._viewer) { this._viewer.entities.remove(this._line); this._line = null; }
|
||||||
|
this._count = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_calcTerminator() {
|
||||||
|
// Sonnenposition berechnen
|
||||||
|
var now = new Date();
|
||||||
|
var dayOfYear = Math.floor((now - new Date(now.getFullYear(), 0, 0)) / 86400000);
|
||||||
|
var declination = -23.45 * Math.cos((360 / 365) * (dayOfYear + 10) * Math.PI / 180);
|
||||||
|
var decRad = declination * Math.PI / 180;
|
||||||
|
|
||||||
|
// Stunden seit Mitternacht UTC
|
||||||
|
var utcHours = now.getUTCHours() + now.getUTCMinutes() / 60 + now.getUTCSeconds() / 3600;
|
||||||
|
var sunLon = -(utcHours - 12) * 15; // Sonnen-Laengengrad
|
||||||
|
|
||||||
|
// Terminatur-Punkte berechnen (Polygon um die Nachtseite)
|
||||||
|
var points = [];
|
||||||
|
var linePoints = [];
|
||||||
|
for (var i = 0; i <= 360; i += 2) {
|
||||||
|
var lon = i - 180;
|
||||||
|
var lonRad = (lon - sunLon) * Math.PI / 180;
|
||||||
|
var lat = Math.atan(-Math.cos(lonRad) / Math.tan(decRad)) * 180 / Math.PI;
|
||||||
|
linePoints.push(lon, lat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nacht-Polygon: Terminatur-Linie + Pole
|
||||||
|
var nightPolygon = [];
|
||||||
|
// Oberer Rand (Nordpol oder Suedpol je nach Jahreszeit)
|
||||||
|
var topLat = declination > 0 ? -90 : 90;
|
||||||
|
nightPolygon.push(-180, topLat);
|
||||||
|
for (var j = 0; j < linePoints.length; j += 2) {
|
||||||
|
nightPolygon.push(linePoints[j], linePoints[j + 1]);
|
||||||
|
}
|
||||||
|
nightPolygon.push(180, topLat);
|
||||||
|
|
||||||
|
return { line: linePoints, night: nightPolygon };
|
||||||
|
},
|
||||||
|
|
||||||
|
_render() {
|
||||||
|
if (!this._viewer) return;
|
||||||
|
var data = this._calcTerminator();
|
||||||
|
|
||||||
|
// Alte Entities entfernen
|
||||||
|
if (this._entity) this._viewer.entities.remove(this._entity);
|
||||||
|
if (this._line) this._viewer.entities.remove(this._line);
|
||||||
|
|
||||||
|
// Nachtflaeche (dunkel, halbtransparent)
|
||||||
|
var nightPositions = Cesium.Cartesian3.fromDegreesArray(data.night);
|
||||||
|
this._entity = this._viewer.entities.add({
|
||||||
|
polygon: {
|
||||||
|
hierarchy: new Cesium.PolygonHierarchy(nightPositions),
|
||||||
|
material: Cesium.Color.BLACK.withAlpha(0.35),
|
||||||
|
height: 0,
|
||||||
|
outline: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Terminatur-Linie (leuchtend)
|
||||||
|
var linePositions = Cesium.Cartesian3.fromDegreesArray(data.line);
|
||||||
|
this._line = this._viewer.entities.add({
|
||||||
|
polyline: {
|
||||||
|
positions: linePositions,
|
||||||
|
width: 2,
|
||||||
|
material: Cesium.Color.fromCssColorString('#ff8800').withAlpha(0.6),
|
||||||
|
clampToGround: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
75
static/js/layers/timezones.js
Normale Datei
75
static/js/layers/timezones.js
Normale Datei
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Zeitzonen-Overlay: Vertikale Linien alle 15 Grad + aktuelle Uhrzeit.
|
||||||
|
*/
|
||||||
|
const TimezonesLayer = {
|
||||||
|
_viewer: null,
|
||||||
|
_primitives: null,
|
||||||
|
_labels: null,
|
||||||
|
_interval: null,
|
||||||
|
_count: 0,
|
||||||
|
|
||||||
|
start(viewer) {
|
||||||
|
if (this._primitives) return;
|
||||||
|
this._viewer = viewer;
|
||||||
|
this._primitives = viewer.scene.primitives.add(new Cesium.PolylineCollection());
|
||||||
|
this._labels = viewer.scene.primitives.add(new Cesium.LabelCollection());
|
||||||
|
this._render();
|
||||||
|
this._count = 24;
|
||||||
|
var self = this;
|
||||||
|
this._interval = setInterval(function() { self._updateLabels(); }, 60000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if (this._interval) { clearInterval(this._interval); this._interval = null; }
|
||||||
|
if (this._primitives && this._viewer) { this._viewer.scene.primitives.remove(this._primitives); this._primitives = null; }
|
||||||
|
if (this._labels && this._viewer) { this._viewer.scene.primitives.remove(this._labels); this._labels = null; }
|
||||||
|
this._count = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
_render() {
|
||||||
|
if (!this._primitives) return;
|
||||||
|
var lineColor = Cesium.Color.fromCssColorString('#ffffff').withAlpha(0.08);
|
||||||
|
// 24 Zeitzonen-Linien (alle 15 Grad)
|
||||||
|
for (var tz = -12; tz <= 12; tz++) {
|
||||||
|
var lon = tz * 15;
|
||||||
|
var positions = [];
|
||||||
|
for (var lat = -80; lat <= 80; lat += 10) {
|
||||||
|
positions.push(Cesium.Cartesian3.fromDegrees(lon, lat, 0));
|
||||||
|
}
|
||||||
|
this._primitives.add({
|
||||||
|
positions: positions,
|
||||||
|
width: 1,
|
||||||
|
material: Cesium.Material.fromType('Color', { color: lineColor }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._updateLabels();
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateLabels() {
|
||||||
|
if (!this._labels) return;
|
||||||
|
this._labels.removeAll();
|
||||||
|
var now = new Date();
|
||||||
|
var utcH = now.getUTCHours();
|
||||||
|
var utcM = now.getUTCMinutes();
|
||||||
|
|
||||||
|
for (var tz = -12; tz <= 12; tz++) {
|
||||||
|
var lon = tz * 15;
|
||||||
|
var h = (utcH + tz + 24) % 24;
|
||||||
|
var timeStr = ('0' + h).slice(-2) + ':' + ('0' + utcM).slice(-2);
|
||||||
|
var label = 'UTC' + (tz >= 0 ? '+' : '') + tz + ' ' + timeStr;
|
||||||
|
|
||||||
|
this._labels.add({
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(lon, 0, 0),
|
||||||
|
text: label,
|
||||||
|
font: '10px monospace',
|
||||||
|
fillColor: Cesium.Color.WHITE.withAlpha(0.4),
|
||||||
|
outlineColor: Cesium.Color.BLACK,
|
||||||
|
outlineWidth: 2,
|
||||||
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
||||||
|
scale: 0.7,
|
||||||
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
|
||||||
|
distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 8000000),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
In neuem Issue referenzieren
Einen Benutzer sperren