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:
@@ -450,3 +450,105 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo
|
||||
max-height: 50vh !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
|
||||
/* === Wetter Dot === */
|
||||
.dot-weather { background: #44bbff; }
|
||||
.dot-crosshairs { background: #ffffff; }
|
||||
|
||||
/* === Search Bar === */
|
||||
.header-search { position: relative; }
|
||||
.globe-search {
|
||||
width: 180px;
|
||||
padding: 4px 10px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
outline: none;
|
||||
transition: width 0.3s, border-color 0.2s;
|
||||
}
|
||||
.globe-search:focus { width: 260px; border-color: var(--accent); }
|
||||
.globe-search::placeholder { color: var(--text-dim); }
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0 0 6px 6px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 200;
|
||||
}
|
||||
.search-item {
|
||||
padding: 6px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
color: var(--text);
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.search-item:hover { background: rgba(0,255,136,0.08); }
|
||||
.search-empty { padding: 8px 10px; font-size: 11px; color: var(--text-dim); }
|
||||
|
||||
/* === City Quick-Links === */
|
||||
.city-links {
|
||||
position: fixed;
|
||||
top: 44px;
|
||||
left: 220px;
|
||||
right: 290px;
|
||||
z-index: 90;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.city-links::-webkit-scrollbar { display: none; }
|
||||
.city-btn {
|
||||
padding: 2px 8px;
|
||||
background: rgba(255,255,255,0.04);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 10px;
|
||||
color: var(--text-dim);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.city-btn:hover { border-color: var(--accent); color: var(--accent); background: rgba(0,255,136,0.06); }
|
||||
|
||||
/* === Crosshairs Overlay === */
|
||||
.crosshairs-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 60;
|
||||
pointer-events: none;
|
||||
}
|
||||
.ch-h, .ch-v {
|
||||
position: absolute;
|
||||
background: rgba(0, 255, 136, 0.15);
|
||||
}
|
||||
.ch-h { left: 0; right: 0; top: 50%; height: 1px; }
|
||||
.ch-v { top: 0; bottom: 0; left: 50%; width: 1px; }
|
||||
.ch-center {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
width: 8px; height: 8px;
|
||||
transform: translate(-50%, -50%);
|
||||
border: 1px solid rgba(0, 255, 136, 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.ch-ring {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
border: 1px solid rgba(0, 255, 136, 0.08);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.ch-ring-1 { width: 200px; height: 200px; transform: translate(-50%, -50%); }
|
||||
.ch-ring-2 { width: 400px; height: 400px; transform: translate(-50%, -50%); }
|
||||
.ch-ring-3 { width: 600px; height: 600px; transform: translate(-50%, -50%); }
|
||||
|
||||
@@ -34,7 +34,11 @@
|
||||
</svg>
|
||||
<span class="header-title">AegisSight Globe</span>
|
||||
</div>
|
||||
<div class="header-lage"><select id="lage-select" class="lage-select" onchange="Globe._onLageChange(this.value)"><option value="">-- Lage waehlen --</option></select></div><div class="header-stats" id="header-stats"></div>
|
||||
<div class="header-lage"><select id="lage-select" class="lage-select" onchange="Globe._onLageChange(this.value)"><option value="">-- Lage waehlen --</option></select></div><div class="header-search">
|
||||
<input type="text" id="globe-search" class="globe-search" placeholder="Ort suchen..." autocomplete="off">
|
||||
<div id="search-results" class="search-results" style="display:none"></div>
|
||||
</div>
|
||||
<div class="header-stats" id="header-stats"></div>
|
||||
<div class="header-actions">
|
||||
<button class="header-btn" id="btn-fullscreen" title="Vollbild">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>
|
||||
@@ -88,6 +92,12 @@
|
||||
</label>
|
||||
<div class="layer-loading" id="loading-disasters"></div>
|
||||
<div class="layer-status" id="status-disasters"></div>
|
||||
<label class="layer-toggle">
|
||||
<input type="checkbox" id="layer-weather" title="Regenradar weltweit (RainViewer, 5min Refresh)">
|
||||
<span class="layer-dot dot-weather"></span>
|
||||
<span class="layer-name" title="Niederschlagsradar als Overlay auf dem Globus">Regenradar</span>
|
||||
</label>
|
||||
<div class="layer-status" id="status-weather"></div>
|
||||
</div>
|
||||
<div class="panel-divider"></div>
|
||||
<div class="panel-section">
|
||||
@@ -96,6 +106,11 @@
|
||||
<span class="layer-dot dot-daynight"></span>
|
||||
<span class="layer-name" title="Sonnenlicht-Simulation zeigt aktuelle Tages-/Nachtseite">Tag/Nacht</span>
|
||||
</label>
|
||||
<label class="layer-toggle">
|
||||
<input type="checkbox" id="layer-crosshairs" title="Fadenkreuz und Entfernungsringe">
|
||||
<span class="layer-dot dot-crosshairs"></span>
|
||||
<span class="layer-name">Fadenkreuz</span>
|
||||
</label>
|
||||
<label class="layer-toggle">
|
||||
<input type="checkbox" id="layer-labels" title="Laendergrenzen, Staedte und Gewaessernamen (Esri World Boundaries)" checked>
|
||||
<span class="layer-dot dot-labels"></span>
|
||||
@@ -120,6 +135,9 @@
|
||||
</aside>
|
||||
|
||||
|
||||
<!-- City Quick-Links -->
|
||||
<div id="city-links" class="city-links"></div>
|
||||
|
||||
<!-- Rechte Sidebar: Datenpunkt-Uebersicht -->
|
||||
<aside id="sidebar-right" class="sidebar-right">
|
||||
<button id="sidebar-toggle" class="sidebar-toggle" title="Seitenleiste ein-/ausblenden">
|
||||
@@ -137,6 +155,13 @@
|
||||
|
||||
<!-- CesiumJS Container -->
|
||||
<div id="cesiumContainer"></div>
|
||||
<div id="crosshairs-overlay" class="crosshairs-overlay" style="display:none">
|
||||
<div class="ch-h"></div><div class="ch-v"></div>
|
||||
<div class="ch-ring ch-ring-1"></div>
|
||||
<div class="ch-ring ch-ring-2"></div>
|
||||
<div class="ch-ring ch-ring-3"></div>
|
||||
<div class="ch-center"></div>
|
||||
</div>
|
||||
<div id="visual-overlay" class="visual-overlay" style="display:none"></div>
|
||||
|
||||
<!-- Bottom Bar -->
|
||||
@@ -156,6 +181,9 @@
|
||||
<script src="/static/js/layers/disasters.js"></script>
|
||||
<script src="/static/js/ui/sidebar.js"></script>
|
||||
<script src="/static/js/layers/monitor.js"></script>
|
||||
<script src="/static/js/layers/weather.js"></script>
|
||||
<script src="/static/js/ui/search.js"></script>
|
||||
<script src="/static/js/ui/crosshairs.js"></script>
|
||||
<script src="/static/js/layers/visualmodes.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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
56
static/js/layers/weather.js
Normale Datei
@@ -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
12
static/js/ui/crosshairs.js
Normale Datei
@@ -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
81
static/js/ui/search.js
Normale Datei
@@ -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 = '';
|
||||
},
|
||||
};
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren