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); });
},
};