diff --git a/static/css/globe.css b/static/css/globe.css index a79d767..98d49b4 100644 --- a/static/css/globe.css +++ b/static/css/globe.css @@ -33,7 +33,7 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo } .header-brand { display: flex; align-items: center; gap: 8px; } .header-title { font-size: 13px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; color: var(--accent); } -.header-stats { flex: 1; text-align: center; font-size: 11px; color: var(--text-dim); letter-spacing: 1px; } +.header-stats { text-align: center; font-size: 11px; color: var(--text-dim); letter-spacing: 1px; } .header-actions { display: flex; gap: 8px; } .header-btn { background: none; border: 1px solid var(--border); border-radius: 4px; @@ -415,7 +415,7 @@ html, body { height: 100%; overflow: hidden; background: var(--bg-primary); colo /* === Lage-Selector === */ -.header-lage { display: flex; align-items: center; } +.header-lage { display: flex; align-items: center; flex: 1; justify-content: center; } .lage-select { background: rgba(255,255,255,0.05); border: 1px solid var(--border); diff --git a/static/js/layers/disasters.js b/static/js/layers/disasters.js index 84ae17f..1a7651c 100644 --- a/static/js/layers/disasters.js +++ b/static/js/layers/disasters.js @@ -35,75 +35,36 @@ const DisastersLayer = { .catch(function() {}); }, - _findMonitorInfo(lat, lon) { - if (!this._monitorData) return null; - var selectedId = (typeof Globe !== 'undefined') ? Globe._currentLageId : null; - if (!selectedId) return null; - - // Naechsten Monitor-Standort zur Klickposition finden + _buildMonitorHtml(lat, lon) { + if (!this._monitorData) return ''; var features = this._monitorData.features || []; var best = null, bestDist = 999; for (var i = 0; i < features.length; i++) { - var f = features[i]; - var c = f.geometry.coordinates; + var c = features[i].geometry.coordinates; var d = Math.abs(c[1] - lat) + Math.abs(c[0] - lon); - if (d < bestDist) { bestDist = d; best = f; } + if (d < bestDist) { bestDist = d; best = features[i]; } } + if (!best || bestDist > 3) return ''; - // Lage-Infos - var incidents = this._monitorData.incidents || []; - var incident = null; - for (var j = 0; j < incidents.length; j++) { - if (incidents[j].id === selectedId) { incident = incidents[j]; break; } - } - - if (!best || bestDist > 5) return { incident: incident, feature: null }; - return { incident: incident, feature: best }; - }, - - _buildMonitorHtml(lat, lon) { - var info = this._findMonitorInfo(lat, lon); - if (!info || !info.incident) return ''; + var p = best.properties; + var articles = p.articles || []; + if (!articles.length) return ''; var html = '
'; - html += '
MONITOR: ' + - (info.incident.title || 'LAGE').toUpperCase() + '
'; - - // Ortsspezifische Headlines wenn passender Standort gefunden - if (info.feature) { - var p = info.feature.properties; - var headlines = p.headlines || []; - if (headlines.length && headlines[0]) { - html += '
' + (p.name || '') + - ' (' + (p.article_count || 0) + ' Artikel)
'; - html += '
'; - for (var h = 0; h < Math.min(headlines.length, 3); h++) { - if (headlines[h] && headlines[h].trim()) { - html += '
• ' + - headlines[h].trim().substring(0, 120) + '
'; - } - } - html += '
'; - } - } else { - // Kein passender Standort in der Naehe — globale Summary - if (info.incident.summary) { - var summary = info.incident.summary - .replace(/##?\s*/g, '').replace(/\*\*/g, '').replace(/\[(\d+)\]/g, '') - .substring(0, 400); - html += '
' + summary + '
'; - } - } - - if (info.incident.updated_at) { - html += '
Stand: ' + - new Date(info.incident.updated_at).toLocaleString('de-DE') + '
'; - } + html += '
MONITOR: ' + + (p.name || '').toUpperCase() + ' (' + (p.article_count || 0) + ' Artikel)
'; + articles.forEach(function(a) { + html += '
'; + html += '
' + (a.headline || '') + '
'; + if (a.summary) html += '
' + a.summary.substring(0, 200) + '
'; + if (a.source) html += '
' + a.source + '
'; + html += '
'; + }); html += '
'; return html; }, - _fetch() { + _fetch() { var self = this; var loadEl = document.getElementById('loading-disasters'); var statusEl = document.getElementById('status-disasters'); diff --git a/static/js/layers/monitor.js b/static/js/layers/monitor.js index 1a75c7f..de43317 100644 --- a/static/js/layers/monitor.js +++ b/static/js/layers/monitor.js @@ -1,6 +1,5 @@ /** - * Monitor-Layer: OSINT-Daten aus dem AegisSight Monitor. - * Zeigt geoparsete Standorte mit Zusammenfassungen auf dem Globus. + * Monitor-Layer: OSINT-Daten mit ortsspezifischen Artikeln. */ const MonitorLayer = { _viewer: null, @@ -17,15 +16,9 @@ const MonitorLayer = { this._viewer = viewer; this._points = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection()); this._labels = viewer.scene.primitives.add(new Cesium.LabelCollection()); - this._fetch(); var self = this; - this._interval = setInterval(function() { self._fetch(); }, 120000); - - // Klick-Handler this._handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); - this._handler.setInputAction(function(click) { - self._onClick(click.position); - }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + this._handler.setInputAction(function(click) { self._onClick(click.position); }, Cesium.ScreenSpaceEventType.LEFT_CLICK); }, stop() { @@ -36,90 +29,6 @@ const MonitorLayer = { this._count = 0; this._data = []; this._incidents = []; }, - _onClick(position) { - if (!this._viewer || !this._data.length) return; - var cartesian = this._viewer.scene.pickPosition(position); - if (!cartesian) { - var ray = this._viewer.scene.camera.getPickRay(position); - cartesian = this._viewer.scene.globe.pick(ray, this._viewer.scene); - } - if (!cartesian) return; - var carto = Cesium.Cartographic.fromCartesian(cartesian); - var clickLat = Cesium.Math.toDegrees(carto.latitude); - var clickLon = Cesium.Math.toDegrees(carto.longitude); - - // Naechsten Monitor-Punkt finden - var best = null, bestDist = 999; - for (var i = 0; i < this._data.length; i++) { - var f = this._data[i]; - var c = f.geometry.coordinates; - var d = Math.abs(c[1] - clickLat) + Math.abs(c[0] - clickLon); - if (d < bestDist) { bestDist = d; best = f; } - } - if (!best || bestDist > 2) return; - - var p = best.properties; - var incident = null; - for (var j = 0; j < this._incidents.length; j++) { - if (this._incidents[j].id === p.incident_id) { incident = this._incidents[j]; break; } - } - - // Info-Panel bauen - var catColors = { primary: '#ff2222', secondary: '#ff8800', tertiary: '#4499ff', mentioned: '#888888' }; - var catLabels = { primary: 'Hauptgeschehen', secondary: 'Reaktionen', tertiary: 'Beteiligte', mentioned: 'Erwaehnt' }; - var catColor = catColors[p.category] || '#888'; - var catLabel = catLabels[p.category] || p.category; - - var html = '
'; - - // Ort + Kategorie - html += '
'; - html += '' + (p.name || '?') + ''; - if (p.country) html += ' (' + p.country + ')'; - html += '
' + catLabel.toUpperCase() + ''; - html += ' · ' + (p.article_count || 0) + ' Artikel'; - html += '
'; - - // Headlines - var headlines = p.headlines || []; - if (headlines.length && headlines[0]) { - html += '
'; - for (var h = 0; h < Math.min(headlines.length, 3); h++) { - if (headlines[h] && headlines[h].trim()) { - html += '
• ' + headlines[h].trim().substring(0, 120) + '
'; - } - } - html += '
'; - } - - // Lage-Zusammenfassung - if (incident && incident.summary) { - html += '
'; - html += '
' + (incident.title || 'LAGE').toUpperCase() + '
'; - // Summary kuerzen und Markdown-Formatierung entfernen - var summary = incident.summary - .replace(/##?\s*/g, '') - .replace(/\*\*/g, '') - .replace(/\[(\d+)\]/g, '') - .substring(0, 500); - html += '
' + summary; - if (incident.summary.length > 500) html += '...'; - html += '
'; - if (incident.updated_at) { - html += '
Stand: ' + new Date(incident.updated_at).toLocaleString('de-DE') + '
'; - } - html += '
'; - } - - html += '
'; - - this._viewer.trackedEntity = undefined; - this._viewer.selectedEntity = new Cesium.Entity({ - name: (p.name || '?') + ' — ' + (incident ? incident.title : 'Monitor'), - description: html, - }); - }, - _fetchForLage(lageId) { var self = this; var loadEl = document.getElementById('loading-monitor'); @@ -141,68 +50,96 @@ const MonitorLayer = { }, _fetch() { - var self = this; - var loadEl = document.getElementById('loading-monitor'); - var statusEl = document.getElementById('status-monitor'); - if (loadEl) loadEl.classList.add('active'); - if (statusEl) { statusEl.textContent = 'Lade Monitor-Daten...'; statusEl.classList.add('active'); } var lageId = (typeof Globe !== 'undefined' && Globe._currentLageId) ? Globe._currentLageId : ''; - fetch('/api/monitor-feed' + (lageId ? '?incident_id=' + lageId : '')) - .then(function(r) { return r.json(); }) - .then(function(data) { - self._data = data.features || []; - self._incidents = data.incidents || []; - self._count = self._data.length; - self._render(); - if (statusEl) { - var incCount = self._incidents.length; - statusEl.textContent = self._count + ' Orte aus ' + incCount + ' Lagen'; - } - }) - .catch(function(e) { console.warn('Monitor error:', e); if (statusEl) statusEl.textContent = 'Fehler'; }) - .finally(function() { if (loadEl) loadEl.classList.remove('active'); setTimeout(function() { if (statusEl) statusEl.classList.remove('active'); }, 8000); }); + if (lageId) this._fetchForLage(lageId); + }, + + _onClick(position) { + if (!this._viewer || !this._data.length) return; + var cartesian = this._viewer.scene.pickPosition(position); + if (!cartesian) { + var ray = this._viewer.scene.camera.getPickRay(position); + cartesian = this._viewer.scene.globe.pick(ray, this._viewer.scene); + } + if (!cartesian) return; + var carto = Cesium.Cartographic.fromCartesian(cartesian); + var clickLat = Cesium.Math.toDegrees(carto.latitude); + var clickLon = Cesium.Math.toDegrees(carto.longitude); + + var best = null, bestDist = 999; + for (var i = 0; i < this._data.length; i++) { + var f = this._data[i]; + var c = f.geometry.coordinates; + var d = Math.abs(c[1] - clickLat) + Math.abs(c[0] - clickLon); + if (d < bestDist) { bestDist = d; best = f; } + } + if (!best || bestDist > 2) return; + + var p = best.properties; + var catColors = { primary: '#ff2222', secondary: '#ff8800', tertiary: '#4499ff', mentioned: '#888888' }; + var color = catColors[p.category] || '#888'; + + var html = '
'; + html += '' + (p.name || '?') + ''; + if (p.country) html += ' (' + p.country + ')'; + html += '
' + (p.article_count || 0) + ' Artikel'; + + // Ortsspezifische Artikel + var articles = p.articles || []; + if (articles.length) { + html += '
'; + articles.forEach(function(a) { + html += '
'; + html += '
' + (a.headline || '?') + '
'; + if (a.summary) { + html += '
' + a.summary + '
'; + } + html += '
'; + if (a.source) html += a.source; + if (a.date) html += ' | ' + new Date(a.date).toLocaleDateString('de-DE'); + html += '
'; + }); + html += '
'; + } + html += '
'; + + this._viewer.trackedEntity = undefined; + this._viewer.selectedEntity = new Cesium.Entity({ + name: (p.name || '?') + ' — ' + (p.incident_title || ''), + description: html, + }); }, _render() { if (!this._points) return; this._points.removeAll(); this._labels.removeAll(); - var colors = { 'primary': Cesium.Color.fromCssColorString('#ff2222'), 'secondary': Cesium.Color.fromCssColorString('#ff8800'), 'tertiary': Cesium.Color.fromCssColorString('#4499ff'), 'mentioned': Cesium.Color.fromCssColorString('#888888'), }; - for (var i = 0; i < this._data.length; i++) { var f = this._data[i]; var coords = f.geometry.coordinates; var p = f.properties; - var cat = p.category || 'mentioned'; - var color = colors[cat] || colors.mentioned; + var color = colors[p.category] || colors.mentioned; var count = p.article_count || 1; var size = Math.min(Math.max(Math.sqrt(count) * 2.5, 4), 14); - this._points.add({ position: Cesium.Cartesian3.fromDegrees(coords[0], coords[1], 1000), - pixelSize: size, - color: color, - outlineColor: Cesium.Color.WHITE.withAlpha(0.6), - outlineWidth: 1, + pixelSize: size, color: color, + outlineColor: Cesium.Color.WHITE.withAlpha(0.6), outlineWidth: 1, }); - if (count >= 2) { this._labels.add({ position: Cesium.Cartesian3.fromDegrees(coords[0], coords[1], 1000), text: (p.name || '?') + ' (' + count + ')', - font: '11px sans-serif', - fillColor: color, - outlineColor: Cesium.Color.BLACK, - outlineWidth: 3, + font: '11px sans-serif', fillColor: color, + outlineColor: Cesium.Color.BLACK, outlineWidth: 3, style: Cesium.LabelStyle.FILL_AND_OUTLINE, - pixelOffset: new Cesium.Cartesian2(0, -size - 6), - scale: 0.8, + pixelOffset: new Cesium.Cartesian2(0, -size - 6), scale: 0.8, horizontalOrigin: Cesium.HorizontalOrigin.CENTER, distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 3000000), });