Neue Features: Regenradar, Ortssuche, City-Links, Fadenkreuz

REGENRADAR (RainViewer):
- Weltweit Niederschlagsradar als Overlay, kostenlos, kein API-Key
- 5min Refresh, 60% Transparenz

ORTSSUCHE (Nominatim/OpenStreetMap):
- Suchfeld im Header, Ergebnisse als Dropdown
- Klick fliegt zur Position
- Deutsche Ergebnisse

CITY QUICK-LINKS:
- 12 strategische Staedte als Schnellnavigation unter dem Header
- Berlin, Washington, Moskau, Peking, Tokio, London, Paris,
  Teheran, Kiew, Jerusalem, Taipei, Hormuz

FADENKREUZ + RANGE RINGS:
- Zuschaltbar im Panel
- Horizontale/vertikale Linie + 3 konzentrische Ringe
- Gruener taktischer Stil
Dieser Commit ist enthalten in:
Claude Dev
2026-03-24 21:48:29 +01:00
Ursprung d3a45f1901
Commit e8a9e49dba
6 geänderte Dateien mit 283 neuen und 1 gelöschten Zeilen

Datei anzeigen

@@ -84,6 +84,7 @@ const Globe = {
this._toggleLabels(true);
if (typeof VisualModes !== 'undefined') VisualModes.init();
if (typeof Sidebar !== 'undefined') Sidebar.init();
if (typeof SearchUI !== 'undefined') SearchUI.init();
this._loadLagen();
document.getElementById('bottom-stats').textContent = 'Globe initialisiert — Lade Daten...';
},
@@ -155,7 +156,9 @@ const Globe = {
'layer-gdelt': function(on) { on ? GdeltLayer.start(Globe.viewer) : GdeltLayer.stop(); },
'layer-satellites': function(on) { on ? SatellitesLayer.start(Globe.viewer) : SatellitesLayer.stop(); },
'layer-disasters': function(on) { on ? DisastersLayer.start(Globe.viewer) : DisastersLayer.stop(); },
'layer-weather': function(on) { on ? WeatherLayer.start(Globe.viewer) : WeatherLayer.stop(); },
'layer-daynight': function(on) { Globe.viewer.scene.globe.enableLighting = on; },
'layer-crosshairs': function(on) { CrosshairsUI.toggle(on); },
'layer-labels': function(on) { Globe._toggleLabels(on); },
};
Object.keys(toggles).forEach(function(id) {

56
static/js/layers/weather.js Normale Datei
Datei anzeigen

@@ -0,0 +1,56 @@
/**
* Wetter-Layer: Regenradar via RainViewer (kostenlos, kein API-Key).
*/
const WeatherLayer = {
_viewer: null,
_layer: null,
_interval: null,
_count: 0,
start(viewer) {
if (this._layer) return;
this._viewer = viewer;
this._fetchAndShow();
var self = this;
this._interval = setInterval(function() { self._fetchAndShow(); }, 300000);
},
stop() {
if (this._interval) { clearInterval(this._interval); this._interval = null; }
if (this._layer && this._viewer) {
this._viewer.imageryLayers.remove(this._layer);
this._layer = null;
}
this._count = 0;
},
_fetchAndShow() {
var self = this;
var statusEl = document.getElementById('status-weather');
if (statusEl) { statusEl.textContent = 'Lade Radar...'; statusEl.classList.add('active'); }
fetch('https://api.rainviewer.com/public/weather-maps.json')
.then(function(r) { return r.json(); })
.then(function(data) {
var frames = (data.radar && data.radar.past) || [];
if (!frames.length) return;
var latest = frames[frames.length - 1];
var tileUrl = 'https://tilecache.rainviewer.com' + latest.path + '/256/{z}/{x}/{y}/2/1_1.png';
if (self._layer && self._viewer) {
self._viewer.imageryLayers.remove(self._layer);
}
self._layer = self._viewer.imageryLayers.addImageryProvider(
new Cesium.UrlTemplateImageryProvider({
url: tileUrl,
maximumLevel: 12,
credit: 'RainViewer',
})
);
self._layer.alpha = 0.6;
self._count = 1;
if (statusEl) statusEl.textContent = 'Regenradar aktiv';
})
.catch(function(e) { console.warn('Weather:', e); if (statusEl) statusEl.textContent = 'Fehler'; })
.finally(function() { setTimeout(function() { if (statusEl) statusEl.classList.remove('active'); }, 5000); });
},
};

12
static/js/ui/crosshairs.js Normale Datei
Datei anzeigen

@@ -0,0 +1,12 @@
/**
* Crosshairs + Range Rings Overlay.
*/
const CrosshairsUI = {
_active: false,
toggle(on) {
this._active = on;
var el = document.getElementById('crosshairs-overlay');
if (el) el.style.display = on ? 'block' : 'none';
},
};

81
static/js/ui/search.js Normale Datei
Datei anzeigen

@@ -0,0 +1,81 @@
/**
* Suche + City Quick-Links: Ortssuche via Nominatim (kostenlos).
*/
const SearchUI = {
_cities: [
{ name: 'Berlin', lat: 52.52, lon: 13.40 },
{ name: 'Washington', lat: 38.90, lon: -77.04 },
{ name: 'Moskau', lat: 55.75, lon: 37.62 },
{ name: 'Peking', lat: 39.91, lon: 116.40 },
{ name: 'Tokio', lat: 35.68, lon: 139.69 },
{ name: 'London', lat: 51.51, lon: -0.13 },
{ name: 'Paris', lat: 48.86, lon: 2.35 },
{ name: 'Teheran', lat: 35.69, lon: 51.39 },
{ name: 'Kiew', lat: 50.45, lon: 30.52 },
{ name: 'Jerusalem', lat: 31.77, lon: 35.23 },
{ name: 'Taipei', lat: 25.03, lon: 121.57 },
{ name: 'Hormuz', lat: 26.56, lon: 56.28 },
],
init() {
var self = this;
var input = document.getElementById('globe-search');
var results = document.getElementById('search-results');
if (!input) return;
// City Quick-Links rendern
var linksEl = document.getElementById('city-links');
if (linksEl) {
linksEl.innerHTML = this._cities.map(function(c) {
return '<button class="city-btn" onclick="SearchUI.flyTo(' + c.lat + ',' + c.lon + ')" title="' + c.name + '">' + c.name + '</button>';
}).join('');
}
// Suche
var timer = null;
input.addEventListener('input', function() {
clearTimeout(timer);
var q = this.value.trim();
if (q.length < 3) { results.style.display = 'none'; return; }
timer = setTimeout(function() { self._search(q); }, 400);
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
var q = this.value.trim();
if (q.length >= 2) self._search(q);
}
});
},
_search(query) {
var results = document.getElementById('search-results');
fetch('https://nominatim.openstreetmap.org/search?q=' + encodeURIComponent(query) + '&format=json&limit=5', {
headers: { 'Accept-Language': 'de' }
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (!data.length) { results.innerHTML = '<div class="search-empty">Keine Ergebnisse</div>'; results.style.display = 'block'; return; }
results.innerHTML = data.map(function(r) {
return '<div class="search-item" onclick="SearchUI.flyTo(' + r.lat + ',' + r.lon + ')">' +
'<span class="search-name">' + r.display_name.split(',').slice(0, 2).join(', ') + '</span></div>';
}).join('');
results.style.display = 'block';
})
.catch(function() { results.style.display = 'none'; });
},
flyTo(lat, lon) {
if (typeof Globe !== 'undefined' && Globe.viewer) {
Globe.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lon, lat, 500000),
duration: 1.5,
});
}
var results = document.getElementById('search-results');
if (results) results.style.display = 'none';
var input = document.getElementById('globe-search');
if (input) input.value = '';
},
};