Add vessel position to map.

Updates via websocket using the `realtime` channel notification
message.
This commit is contained in:
D. Berge
2025-08-10 21:52:02 +02:00
parent 40d0038d80
commit 595c20f504
5 changed files with 407053 additions and 32 deletions

View File

@@ -9,10 +9,12 @@
"dependencies": {
"@deck.gl/aggregation-layers": "^9.1.13",
"@deck.gl/geo-layers": "^9.1.13",
"@deck.gl/mesh-layers": "^9.1.14",
"@dougal/binary": "file:../../../modules/@dougal/binary",
"@dougal/concurrency": "file:../../../modules/@dougal/concurrency",
"@dougal/organisations": "file:../../../modules/@dougal/organisations",
"@dougal/user": "file:../../../modules/@dougal/user",
"@loaders.gl/obj": "^4.3.4",
"@mdi/font": "^7.2.96",
"buffer": "^6.0.3",
"core-js": "^3.6.5",

File diff suppressed because it is too large Load Diff

View File

@@ -660,6 +660,7 @@ export default {
maxPitch: 89
},
vesselPosition: null,
vesselTrackConfig: {
lastRefresh: 0,
refreshInterval: 12, // seconds
@@ -1580,18 +1581,46 @@ export default {
}
},
handleVesselPosition (context, {payload}) {
if (payload.new?.geometry?.coordinates) {
const now = Date.now();
const lastRefresh = this.vesselPosition?._lastRefresh;
// Limits refreshes to once every five seconds max
if (lastRefresh && (now-lastRefresh) < 5000) return;
this.vesselPosition = {
...payload.new.meta,
tstamp: payload.new.tstamp,
_lastRefresh: now
};
if (this.vesselPosition.lineStatus == "offline") {
this.vesselPosition.x = this.vesselPosition.longitude ?? payload.new.geometry.coordinates[0];
this.vesselPosition.y = this.vesselPosition.latitude ?? payload.new.geometry.coordinates[1];
} else {
this.vesselPosition.x = this.vesselPosition.longitudeMaster
?? payload.new.geometry.coordinates[0];
this.vesselPosition.y = this.vesselPosition.latitudeMaster
?? payload.new.geometry.coordinates[1];
}
this.render();
}
},
registerNotificationHandlers (action = "registerHandler") {
["raw_lines", "raw_shots", "final_lines", "final_shots"].forEach( table => {
this.$store.dispatch(action, {
table,
handler: (context, message) => {
this.handleSequences(context, message);
}
handler: this.handleSequences
})
});
this.$store.dispatch(action, {
table: 'realtime',
handler: this.handleVesselPosition
});
},
unregisterNotificationHandlers () {

View File

@@ -6,6 +6,8 @@ import { Deck, WebMercatorViewport, FlyToInterpolator, CompositeLayer } from '@d
import { GeoJsonLayer, LineLayer, PathLayer, BitmapLayer, ScatterplotLayer, ColumnLayer, IconLayer } from '@deck.gl/layers';
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
import { TileLayer, MVTLayer, TripsLayer } from '@deck.gl/geo-layers';
import { SimpleMeshLayer } from '@deck.gl/mesh-layers';
import { OBJLoader } from '@loaders.gl/obj';
//import { json } from 'd3-fetch';
import * as d3a from 'd3-array';
@@ -254,29 +256,22 @@ export default {
},
vesselTrackPointsLayer (options = {}) {
return new ScatterplotLayer({
if (!this.vesselPosition) return;
return new SimpleMeshLayer({
id: 'navp',
data: `/api/navdata?limit=100`,
...this.loadOptions(),
getPosition: (d) => ([d.longitude, d.latitude]),
getRadius: d => (d.speed),
radiusScale: 1,
lineWidthMinPixels: 2,
getFillColor: d => d.guns
? d.lineStatus == "online"
? [0xaa, 0x00, 0xff] // Online
: [0xd5, 0x00, 0xf9] // Soft start or guns otherwise active
: [0xea, 0x80, 0xfc], // Offline, guns inactive
getLineColor: [127, 65, 90],
getColor: [ 255, 0, 0 ],
getPointRadius: 12,
radiusUnits: "pixels",
pointRadiusMinPixels: 4,
stroked: false,
filled: true,
data: [ this.vesselPosition ],
getColor: [ 255, 48, 0 ],
getOrientation: d => [0, (270 - (d.heading ?? d.cmg ?? d.bearing ?? d.lineBearing ?? 0)) % 360 , 0],
getPosition: d => [ d.x, d.y ],
mesh: `/assets/boat0.obj`,
sizeScale: 0.1,
loaders: [OBJLoader],
pickable: true,
...options
})
});
},
vesselTrackLinesLayer (options = {}) {

29
package-lock.json generated
View File

@@ -39,10 +39,12 @@
"dependencies": {
"@deck.gl/aggregation-layers": "^9.1.13",
"@deck.gl/geo-layers": "^9.1.13",
"@deck.gl/mesh-layers": "^9.1.14",
"@dougal/binary": "file:../../../modules/@dougal/binary",
"@dougal/concurrency": "file:../../../modules/@dougal/concurrency",
"@dougal/organisations": "file:../../../modules/@dougal/organisations",
"@dougal/user": "file:../../../modules/@dougal/user",
"@loaders.gl/obj": "^4.3.4",
"@mdi/font": "^7.2.96",
"buffer": "^6.0.3",
"core-js": "^3.6.5",
@@ -13493,19 +13495,18 @@
}
},
"node_modules/@deck.gl/mesh-layers": {
"version": "9.1.13",
"resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.1.13.tgz",
"integrity": "sha512-ujhe9FtB4qRRCXH/hY5p+IQ5VO/AC+/dtern6CTzYzjGnUnAvsbIgBZ3jxSlb1B/D3wlVE778W2cmv7MIToJJg==",
"peer": true,
"version": "9.1.14",
"resolved": "https://registry.npmjs.org/@deck.gl/mesh-layers/-/mesh-layers-9.1.14.tgz",
"integrity": "sha512-NVUw0yG4stJfrklWCGP9j8bNlf9YQc4PccMeNNIHNrU/Je6/Va6dJZg0RGtVkeaTY1Lk3A7wRzq8/M5Urfvuiw==",
"dependencies": {
"@loaders.gl/gltf": "^4.2.0",
"@luma.gl/gltf": "^9.1.5",
"@luma.gl/shadertools": "^9.1.5"
"@luma.gl/gltf": "~9.1.9",
"@luma.gl/shadertools": "~9.1.9"
},
"peerDependencies": {
"@deck.gl/core": "^9.1.0",
"@luma.gl/core": "^9.1.5",
"@luma.gl/engine": "^9.1.5"
"@luma.gl/core": "~9.1.9",
"@luma.gl/engine": "~9.1.9"
}
},
"node_modules/@dougal/binary": {
@@ -13700,6 +13701,18 @@
"@loaders.gl/core": "^4.3.0"
}
},
"node_modules/@loaders.gl/obj": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@loaders.gl/obj/-/obj-4.3.4.tgz",
"integrity": "sha512-Rdn+NHjLI0jKYrKNicJuQJohnHh7QAv4szCji8eafYYMrVtSIonNozBXUfe/c4V7HL/FVvvHCkfC66rvLvayaQ==",
"dependencies": {
"@loaders.gl/loader-utils": "4.3.4",
"@loaders.gl/schema": "4.3.4"
},
"peerDependencies": {
"@loaders.gl/core": "^4.3.0"
}
},
"node_modules/@loaders.gl/schema": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@loaders.gl/schema/-/schema-4.3.4.tgz",