From d3c1de9d5d639d39be07bd14c0bfc94439cb2fa0 Mon Sep 17 00:00:00 2001 From: Claude Dev Date: Tue, 24 Mar 2026 12:33:11 +0100 Subject: [PATCH] Flights+Ships: PointPrimitiveCollection statt Entity API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entity API (6.500+ Objekte mit Point+Label+Description) war zu schwer. PointPrimitiveCollection ist WebGL/GPU-beschleunigt im CLIENT-Browser und kann hunderttausende Punkte fluessig darstellen. Klick-Details ueber ScreenSpaceEventHandler mit naechstem-Punkt-Suche. Kein Server-GPU noetig — alles rendert im Browser des Users. --- static/js/layers/flights.js | 108 ++++++++++++++++++++---------------- static/js/layers/ships.js | 105 +++++++++++++++++++---------------- 2 files changed, 117 insertions(+), 96 deletions(-) diff --git a/static/js/layers/flights.js b/static/js/layers/flights.js index b578969..56ef5e3 100644 --- a/static/js/layers/flights.js +++ b/static/js/layers/flights.js @@ -1,32 +1,67 @@ /** - * Flugverkehr-Layer: Grüne Punkte auf dem 3D-Globus. + * Flugverkehr-Layer: GPU-beschleunigte Punkt-Primitive. */ const FlightsLayer = { _viewer: null, - _dataSource: null, + _points: null, _interval: null, _count: 0, + _data: [], start(viewer) { - if (this._dataSource) return; + if (this._points) return; this._viewer = viewer; - this._dataSource = new Cesium.CustomDataSource('flights'); - this._dataSource.clustering.enabled = true; - this._dataSource.clustering.pixelRange = 30; - this._dataSource.clustering.minimumClusterSize = 5; - viewer.dataSources.add(this._dataSource); + this._points = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection()); this._fetch(); var self = this; this._interval = setInterval(function() { self._fetch(); }, 30000); + + // Klick-Handler fuer Flugzeug-Details + this._handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); + this._handler.setInputAction(function(click) { + self._onClick(click.position); + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); }, stop() { if (this._interval) { clearInterval(this._interval); this._interval = null; } - if (this._dataSource && this._viewer) { - this._viewer.dataSources.remove(this._dataSource); - this._dataSource = null; + if (this._points && this._viewer) { + this._viewer.scene.primitives.remove(this._points); + this._points = null; } + if (this._handler) { this._handler.destroy(); this._handler = null; } this._count = 0; + this._data = []; + }, + + _onClick(position) { + if (!this._viewer || !this._data.length) return; + var picked = this._viewer.scene.pick(position); + if (!picked || !picked.primitive || picked.primitive.constructor.name !== 'PointPrimitive') return; + // Finde naechsten Flug zur Klickposition + var cartesian = this._viewer.scene.pickPosition(position); + 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 a = this._data[i]; + var d = Math.abs(a.lat - clickLat) + Math.abs(a.lon - clickLon); + if (d < bestDist) { bestDist = d; best = a; } + } + if (best && bestDist < 2) { + var cs = (best.flight || best.hex || '?').trim(); + var html = '' + cs + '
' + + 'ALT: ' + (best.alt_baro || '?') + ' ft
' + + 'SPD: ' + (best.gs || '?') + ' kts
' + + 'HDG: ' + Math.round(best.track || 0) + '°' + + (best.origin ? '
FROM: ' + best.origin : ''); + this._viewer.selectedEntity = new Cesium.Entity({ + name: cs, + description: '
' + html + '
', + }); + } }, _fetch() { @@ -38,48 +73,25 @@ const FlightsLayer = { fetch('/api/flights') .then(function(r) { return r.json(); }) .then(function(data) { - if (!self._dataSource) return; - self._dataSource.entities.removeAll(); + if (!self._points) return; + self._points.removeAll(); var ac = data.ac || []; + self._data = ac; self._count = ac.length; - ac.forEach(function(a) { - if (!a.lat || !a.lon) return; - // alt_baro ist in ft vom Backend, konvertiere zu m fuer Cesium - var altFt = a.alt_baro || 30000; - var altM = altFt * 0.3048; - var cs = (a.flight || a.hex || '').trim() || '?'; - self._dataSource.entities.add({ + var green = Cesium.Color.fromCssColorString('#00ff88'); + for (var i = 0; i < ac.length; i++) { + var a = ac[i]; + if (!a.lat || !a.lon) continue; + var altM = (a.alt_baro || 10000) * 0.3048; + self._points.add({ position: Cesium.Cartesian3.fromDegrees(a.lon, a.lat, altM), - point: { - pixelSize: 3, - color: Cesium.Color.fromCssColorString('#00ff88'), - outlineColor: Cesium.Color.fromCssColorString('#003311'), - outlineWidth: 1, - heightReference: Cesium.HeightReference.NONE, - }, - label: { - text: cs, - font: '10px monospace', - fillColor: Cesium.Color.fromCssColorString('#00ff88').withAlpha(0.7), - outlineColor: Cesium.Color.BLACK, - outlineWidth: 2, - style: Cesium.LabelStyle.FILL_AND_OUTLINE, - pixelOffset: new Cesium.Cartesian2(6, -3), - scale: 0.7, - distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 800000), - }, - description: '
' + - '' + cs + '
' + - 'ALT: ' + altFt + ' ft
' + - 'SPD: ' + (a.gs || '?') + ' kts
' + - 'HDG: ' + Math.round(a.track || 0) + '°' + - (a.origin ? '
FROM: ' + a.origin : '') + - '
', + pixelSize: 3.0, + color: green, }); - }); - if (statusEl) { statusEl.textContent = ac.length.toLocaleString('de-DE') + ' Flugzeuge'; } + } + if (statusEl) statusEl.textContent = ac.length.toLocaleString('de-DE') + ' Flugzeuge'; }) - .catch(function(e) { console.warn('Flights fetch error:', e); if (statusEl) { statusEl.textContent = 'Fehler beim Laden'; } }) + .catch(function(e) { console.warn('Flights error:', e); if (statusEl) statusEl.textContent = 'Fehler'; }) .finally(function() { if (loadEl) loadEl.classList.remove('active'); setTimeout(function() { if (statusEl) statusEl.classList.remove('active'); }, 5000); }); }, }; diff --git a/static/js/layers/ships.js b/static/js/layers/ships.js index 7d8b552..a357444 100644 --- a/static/js/layers/ships.js +++ b/static/js/layers/ships.js @@ -1,32 +1,64 @@ /** - * Schiffsverkehr-Layer: Blaue Punkte auf Meereshöhe. + * Schiffsverkehr-Layer: GPU-beschleunigte Punkt-Primitive. */ const ShipsLayer = { _viewer: null, - _dataSource: null, + _points: null, _interval: null, _count: 0, + _data: [], start(viewer) { - if (this._dataSource) return; + if (this._points) return; this._viewer = viewer; - this._dataSource = new Cesium.CustomDataSource('ships'); - this._dataSource.clustering.enabled = true; - this._dataSource.clustering.pixelRange = 25; - this._dataSource.clustering.minimumClusterSize = 5; - viewer.dataSources.add(this._dataSource); + this._points = viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection()); this._fetch(); var self = this; this._interval = setInterval(function() { self._fetch(); }, 60000); + + this._handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); + this._handler.setInputAction(function(click) { + self._onClick(click.position); + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); }, stop() { if (this._interval) { clearInterval(this._interval); this._interval = null; } - if (this._dataSource && this._viewer) { - this._viewer.dataSources.remove(this._dataSource); - this._dataSource = null; + if (this._points && this._viewer) { + this._viewer.scene.primitives.remove(this._points); + this._points = null; } + if (this._handler) { this._handler.destroy(); this._handler = null; } this._count = 0; + this._data = []; + }, + + _onClick(position) { + if (!this._viewer || !this._data.length) return; + var picked = this._viewer.scene.pick(position); + if (!picked || !picked.primitive || picked.primitive.constructor.name !== 'PointPrimitive') return; + var cartesian = this._viewer.scene.pickPosition(position); + 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 s = this._data[i]; + var d = Math.abs(s.lat - clickLat) + Math.abs(s.lon - clickLon); + if (d < bestDist) { bestDist = d; best = s; } + } + if (best && bestDist < 1) { + var name = best.name || ('MMSI ' + (best.mmsi || '?')); + var html = '' + name + '
' + + 'MMSI: ' + (best.mmsi || '?') + '
' + + 'SOG: ' + (best.sog || 0).toFixed(1) + ' kn
' + + 'COG: ' + Math.round(best.cog || 0) + '°'; + this._viewer.selectedEntity = new Cesium.Entity({ + name: name, + description: '
' + html + '
', + }); + } }, _fetch() { @@ -38,48 +70,25 @@ const ShipsLayer = { fetch('/api/ships') .then(function(r) { return r.json(); }) .then(function(data) { - if (!self._dataSource) return; - self._dataSource.entities.removeAll(); + if (!self._points) return; + self._points.removeAll(); var ships = data.ships || []; + self._data = ships; self._count = ships.length; - ships.forEach(function(s) { - if (!s.lat || !s.lon) return; - var name = s.name || ('MMSI ' + (s.mmsi || '?')); - var sog = s.sog || 0; - var color = sog > 0.5 - ? Cesium.Color.fromCssColorString('#4499ff') - : Cesium.Color.fromCssColorString('#445577'); - self._dataSource.entities.add({ + var blue = Cesium.Color.fromCssColorString('#4499ff'); + var gray = Cesium.Color.fromCssColorString('#445577'); + for (var i = 0; i < ships.length; i++) { + var s = ships[i]; + if (!s.lat || !s.lon) continue; + self._points.add({ position: Cesium.Cartesian3.fromDegrees(s.lon, s.lat, 0), - point: { - pixelSize: 3, - color: color, - outlineColor: Cesium.Color.fromCssColorString('#112233'), - outlineWidth: 0.5, - heightReference: Cesium.HeightReference.NONE, - }, - label: { - text: name, - font: '9px monospace', - fillColor: Cesium.Color.fromCssColorString('#4499ff').withAlpha(0.6), - outlineColor: Cesium.Color.BLACK, - outlineWidth: 2, - style: Cesium.LabelStyle.FILL_AND_OUTLINE, - pixelOffset: new Cesium.Cartesian2(5, -2), - scale: 0.6, - distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 300000), - }, - description: '
' + - '' + name + '
' + - 'MMSI: ' + (s.mmsi || '?') + '
' + - 'SOG: ' + sog.toFixed(1) + ' kn
' + - 'COG: ' + Math.round(s.cog || 0) + '°' + - '
', + pixelSize: 2.5, + color: (s.sog || 0) > 0.5 ? blue : gray, }); - }); - if (statusEl) { statusEl.textContent = ships.length.toLocaleString('de-DE') + ' Schiffe'; } + } + if (statusEl) statusEl.textContent = ships.length.toLocaleString('de-DE') + ' Schiffe'; }) - .catch(function(e) { console.warn('Ships fetch error:', e); if (statusEl) { statusEl.textContent = 'Fehler beim Laden'; } }) + .catch(function(e) { console.warn('Ships error:', e); if (statusEl) statusEl.textContent = 'Fehler'; }) .finally(function() { if (loadEl) loadEl.classList.remove('active'); setTimeout(function() { if (statusEl) statusEl.classList.remove('active'); }, 5000); }); }, };