Add vessel track layer to map.

Track length may be changed by clicking on the appropriate icon.
This commit is contained in:
D. Berge
2025-08-10 21:45:13 +02:00
parent acdf118a67
commit 40d0038d80
3 changed files with 210 additions and 15 deletions

View File

@@ -31,7 +31,47 @@
<span>Vessel track</span>
<label title="Show points"><v-icon small left class="mx-0">mdi-vector-point</v-icon> <input type="checkbox" value="navp" v-model="layerSelection"/></label>
<!--
<label title="Show lines" disabled><v-icon small left class="mx-0">mdi-vector-line</v-icon> <input type="checkbox" value="navl" v-model="layerSelection"/></label>
-->
<div>
<v-menu bottom offset-y class="pb-1">
<template v-slot:activator="{ on, attrs }">
<v-icon style="margin-right: 3px;" small v-bind="attrs" v-on="on" :title="`Show lines.\nCurrently selected period: ${vesselTrackConfig.periodSettings[vesselTrackConfig.period].title}. Click to change`">mdi-vector-line</v-icon>
</template>
<v-list nav dense>
<v-list-item @click="$set(vesselTrackConfig, 'period', 'hour')">
<v-list-item-content>
<v-list-item-title>Last hour</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item @click="$set(vesselTrackConfig, 'period', 'hour6')">
<v-list-item-content>
<v-list-item-title>Last 6 hours</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item @click="$set(vesselTrackConfig, 'period', 'hour12')">
<v-list-item-content>
<v-list-item-title>Last 12 hours</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item @click="$set(vesselTrackConfig, 'period', 'day')">
<v-list-item-content>
<v-list-item-title>Last 24 hours</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item @click="$set(vesselTrackConfig, 'period', 'week')">
<v-list-item-content>
<v-list-item-title>Last week</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
<input type="checkbox" value="navl" v-model="layerSelection"/>
</div>
<label><!-- No heatmap available --></label>
<span>Sail lines</span>
@@ -620,6 +660,68 @@ export default {
maxPitch: 89
},
vesselTrackConfig: {
lastRefresh: 0,
refreshInterval: 12, // seconds
intervalID: null,
period: "hour",
periodSettings: {
hour: {
title: "1 hour",
offset: 3600 * 1000,
decimation: 1,
refreshInterval: 18,
},
hour6: {
title: "6 hours",
offset: 6 * 3600 * 1000,
decimation: 1,
refreshInterval: 18,
},
hour12: {
title: "12 hours",
offset: 12 * 3600 * 1000,
decimation: 1,
refreshInterval: 18,
},
day: {
title: "24 hours",
offset: 24 * 3600 * 1000,
decimation: 12,
refreshInterval: 18,
},
week: {
title: "7 days",
offset: 7 * 24 * 3600 * 1000,
decimation: 60,
refreshInterval: 60,
},
week2: {
title: "14 days",
offset: 14 * 24 * 3600 * 1000,
decimation: 60,
refreshInterval: 90,
},
month: {
title: "30 days",
offset: 30 * 24 * 3600 * 1000,
decimation: 90,
refreshInterval: 120,
},
quarter: {
title: "90 days",
offset: 90 * 24 * 3600 * 1000,
decimation: 180,
refreshInterval: 300,
},
year: {
title: "1 year",
offset: 365 * 24 * 3600 * 1000,
decimation: 1200,
refreshInterval: 1800,
},
},
},
heatmapValue: "total_error",
isFullscreen: false,
crosshairsPositions: [],
@@ -761,6 +863,24 @@ export default {
deep: true
},
vesselTrackConfig: {
handler (cur, prev) {
if (cur.period != prev.period) {
console.log("period changed");
this.vesselTrackConfig = {
...this.vesselTrackConfig,
lastRefresh: this.currentSecond()
}
this.updateVesselIntervalTimer();
}
if (cur.lastRefresh != prev.lastRefresh) {
console.log("refresh changed");
this.render();
}
},
deep: true
},
lines () {
// Refresh map on change of preplot data
this.render();
@@ -1062,6 +1182,12 @@ export default {
return [[λ0 - , φ0 - ], [λ1 + , φ1 + ]];
},
// Returns the current second, as an integer.
// Used for triggering Deck.gl URL refreshes
currentSecond () {
return Math.floor(Date.now()/1000);
},
async getSequenceData (sequenceNumbers, types = [2, 3]) {
//const types = [2, 3]; // Bundle types: 2 → raw/gun data; 3 final data. See bundles.js
@@ -1407,6 +1533,22 @@ export default {
return arr.buffer;
},
updateVesselIntervalTimer (refreshInterval) {
this.vesselTrackConfig.refreshInterval = refreshInterval ??
this.vesselTrackConfig.periodSettings[this.vesselTrackConfig.period]?.refreshInterval ?? 0;
this.vesselTrackConfig.intervalID = clearInterval(this.vesselTrackConfig.intervalID);
if (this.vesselTrackConfig.refreshInterval) {
this.vesselTrackConfig.lastRefresh = this.currentSecond();
this.vesselTrackConfig.intervalID = setInterval( () => {
this.vesselTrackConfig = {
...this.vesselTrackConfig,
lastRefresh: this.currentSecond()
}
}, this.vesselTrackConfig.refreshInterval * 1000);
}
},
async handleSequences (context, {payload}) {
if (payload.pid != this.$route.params.project) {
console.warn(`${this.$route.params.project} ignoring notification for ${payload.pid}`);
@@ -1473,6 +1615,8 @@ export default {
console.log("TODO: Should switch to legacy map view");
}
this.updateVesselIntervalTimer();
this.layersAvailable.osm = this.osmLayer;
this.layersAvailable.sea = this.openSeaMapLayer;
@@ -1572,6 +1716,7 @@ export default {
beforeDestroy () {
this.unregisterNotificationHandlers();
this.vesselTrackConfig.intervalID = this.clearInterval(this.vesselTrackConfig.intervalID);
}
}

View File

@@ -5,8 +5,7 @@
import { Deck, WebMercatorViewport, FlyToInterpolator, CompositeLayer } from '@deck.gl/core';
import { GeoJsonLayer, LineLayer, PathLayer, BitmapLayer, ScatterplotLayer, ColumnLayer, IconLayer } from '@deck.gl/layers';
import {HeatmapLayer} from '@deck.gl/aggregation-layers';
import { TileLayer, MVTLayer } from '@deck.gl/geo-layers';
import { TileLayer, MVTLayer, TripsLayer } from '@deck.gl/geo-layers';
//import { json } from 'd3-fetch';
import * as d3a from 'd3-array';
@@ -18,7 +17,6 @@ import DougalBinaryLoader from '@/lib/deck.gl/DougalBinaryLoader';
import { colors } from 'vuetify/lib'
function hexToArray (hex, defaultValue = [ 0xc0, 0xc0, 0xc0, 0xff ]) {
if (typeof hex != "string" || hex.length < 6) {
@@ -282,21 +280,58 @@ export default {
},
vesselTrackLinesLayer (options = {}) {
return new LineLayer({ // TODO Change to TrackLayer
const cfg = this.vesselTrackConfig.periodSettings[this.vesselTrackConfig.period];
let ts1 = new Date(this.vesselTrackConfig.lastRefresh*1000);
let ts0 = new Date(ts1.valueOf() - cfg.offset);
let di = cfg.decimation;
let l = 10000;
const breakLimit = (di ? di*20 : 5 * 60) * 1000;
let trailLength = (ts1 - ts0) / 1000;
return new TripsLayer({
id: 'navl',
data: `/api/navdata?v=${Date.now()}`, // NOTE Not too sure about this
...this.loadOptions(),
lineWidthMinPixels: 2,
getLineColor: (d) => d.properties.ntba ? [240, 248, 255, 200] : [85, 170, 255, 200],
getSourcePosition: (obj, i) => i.index < i.data?.length ? [i.data[i.index]?.longitude, i.data[i.index]?.latitude] : null,
getTargetPosition: (obj, i) => i.index < i.data?.length ? [i.data[i.index+1]?.longitude, i.data[i.index+1]?.latitude] : null,
getLineWidth: 3,
getPointRadius: 2,
radiusUnits: "pixels",
pointRadiusMinPixels: 2,
data: `/api/vessel/track/?di=${di}&l=${l}&project=&ts0=${ts0.toISOString()}&ts1=${ts1.toISOString()}`,
dataTransform: (data) => {
if (data.length >= l) {
console.warn(`Vessel track data may be truncated! Limit: ${l}`);
}
const paths = [];
let prevTstamp;
paths.push({path: [], timestamps: [], num: 0});
for (const el of data) {
const tstamp = new Date(el.tstamp).valueOf();
const curPath = () => paths[paths.length-1];
if (prevTstamp && Math.abs(tstamp - prevTstamp) > breakLimit) {
// Start a new path
console.log(`Breaking path on interval ${Math.abs(tstamp - prevTstamp)} > ${breakLimit}`);
paths.push({path: [], timestamps: [], num: paths.length});
}
curPath().path.push([el.x, el.y]);
curPath().timestamps.push(tstamp/1000);
prevTstamp = tstamp;
}
return paths;
},
getPath: d => d.path,
getTimestamps: d => d.timestamps,
currentTime: ts1.valueOf() / 1000,
trailLength,
widthUnits: "meters",
widthMinPixels: 4,
getWidth: 10,
getColor: [ 174, 1, 126, 200 ],
stroked: true,
pickable: true,
...options
})
});
},
eventsLogLayer (options = {}) {

View File

@@ -28,6 +28,8 @@ export default {
return this.sequenceLinesTooltip(args);
} else if (args?.layer?.id == "navp") {
return this.vesselTrackPointsTooltip(args);
} else if (args?.layer?.id == "navl") {
return this.vesselTrackLinesTooltip(args);
}
},
@@ -235,7 +237,20 @@ export default {
}
},
vesselTrackLinesTooltip (args) {
const p = args.object;
console.log("track lines tooltip", p);
if (p) {
const ts1 = new Date(p.timestamps[0] * 1000);
const ts0 = new Date(p.timestamps[p.timestamps.length-1] * 1000);
let html = `${ts0.toISOString()}<br/>\n${ts1.toISOString()}<br/>\n`
return {html, style: this.tooltipDefaultStyle};
}
},
}