2020-08-08 23:59:13 +02:00
< template >
2022-03-29 17:41:28 +02:00
< div >
2025-07-27 11:03:26 +02:00
< v-overlay :value = "error" >
< v-alert type = "error" > { { error } } < / v-alert >
< / v-overlay >
2022-03-29 17:41:28 +02:00
< div id = "map" >
< / div >
< div > <!-- This div holds the map overlays -- >
< div class = "map-overlay top left expand-on-hover" >
< div class = "map-overlay-icon" >
< v-icon > mdi - menu < / v-icon >
< / div >
< div class = "map-overlay-inner" >
< h3 > Layers < / h3 >
< form >
< input id = "lyr-osm" type = "checkbox" value = "osm" v-model = "layerSelection" />
< label for = "lyr-osm" > OpenStreetMap < / label >
< input id = "lyr-sea" type = "checkbox" value = "sea" v-model = "layerSelection" />
< label for = "lyr-sea" > OpenSeaMap < / label >
< input id = "lyr-nau" type = "checkbox" value = "nau" v-model = "layerSelection" />
< label for = "lyr-nau" title = "Scan of Norway's nautical charts" > Nautical charts ( NO ) < / label >
2025-07-28 10:11:23 +02:00
< / form >
< hr class = "my-2" / >
< div class = "lines-points" >
< 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 >
< label > <!-- No heatmap available -- > < / label >
< span > Sail lines < / span >
< label title = "Show points" > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "pslp" 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 = "psll" v-model = "layerSelection" /> < / label >
< label > <!-- No heatmap available -- > < / label >
< span > Preplots < / span >
< label title = "Show points" > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "pplp" v-model = "layerSelection" /> < / label >
< label title = "Show lines" > < v-icon small left class = "mx-0" > mdi - vector - line < / v-icon > < input type = "checkbox" value = "ppll" v-model = "layerSelection" /> < / label >
< label > <!-- No heatmap available -- > < / label >
< span > Plan < / span >
< label title = "Show points" > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "planp" v-model = "layerSelection" /> < / label >
< label title = "Show lines" > < v-icon small left class = "mx-0" > mdi - vector - line < / v-icon > < input type = "checkbox" value = "planl" v-model = "layerSelection" /> < / label >
< label > <!-- No heatmap available -- > < / label >
< span > Raw data < / span >
< label title = "Show points" > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "seqrp" v-model = "layerSelection" /> < / label >
< label title = "Show lines" > < v-icon small left class = "mx-0" > mdi - vector - line < / v-icon > < input type = "checkbox" value = "seqrl" v-model = "layerSelection" /> < / label >
< label title = "Show position error" > < v-icon small left class = "mx-0" > mdi - dots - grid < / v-icon > < input type = "checkbox" value = "seqrh" v-model = "layerSelection" /> < / label >
< span > Final data < / span >
< label title = "Show points" > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "seqfp" v-model = "layerSelection" /> < / label >
< label title = "Show lines" > < v-icon small left class = "mx-0" > mdi - vector - line < / v-icon > < input type = "checkbox" value = "seqfl" v-model = "layerSelection" /> < / label >
< label title = "Show position error" > < v-icon small left class = "mx-0" > mdi - dots - grid < / v-icon > < input type = "checkbox" value = "seqfh" v-model = "layerSelection" /> < / label >
2022-03-29 17:41:28 +02:00
2025-07-28 10:11:23 +02:00
< span > Events < / span >
< label title = "Show points" > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "log" v-model = "layerSelection" /> < / label >
< label > <!-- No lines available -- > < / label >
< label > <!-- No heatmap available -- > < / label >
2022-03-29 17:41:28 +02:00
2025-07-28 10:11:23 +02:00
< / div >
<!--
2022-03-29 17:41:28 +02:00
< input id = "lyr-psl" type = "checkbox" value = "psl" v-model = "layerSelection" />
< label for = "lyr-psl" title = "Vessel preplots" > Sail lines < / label >
< input id = "lyr-ppl" type = "checkbox" value = "ppl" v-model = "layerSelection" />
< label for = "lyr-ppl" title = "Source preplots" > Preplot lines < / label >
< input id = "lyr-plan" type = "checkbox" value = "plan" v-model = "layerSelection" />
< label for = "lyr-plan" title = "Sequences in the planner" > Planned lines < / label >
< input id = "lyr-preplots" type = "checkbox" value = "preplots" v-model = "layerSelection" />
< label for = "lyr-preplots" title = "Shotpoint deviation from preplot position" > Preplot error < / label >
2025-07-28 10:11:23 +02:00
< input id = "lyr-peh" type = "checkbox" value = "peh" v-model = "layerSelection" />
< label for = "lyr-peh" title = "Shotpoint deviation from preplot position" > Preplot error ( heatmap ) < / label >
2022-03-29 17:41:28 +02:00
< input id = "lyr-seq" type = "checkbox" value = "seq" v-model = "layerSelection" />
< label for = "lyr-seq" title = "Raw and/or final shot positions" > Sequence data < / label >
< input id = "lyr-log" type = "checkbox" value = "log" v-model = "layerSelection" />
< label for = "lyr-log" title = "Event positions" > Event log data < / label >
< input id = "lyr-nav" type = "checkbox" value = "nav" v-model = "layerSelection" />
< label for = "lyr-nav" title = "Vessel track" > Navigation trail < / label >
2025-07-28 10:11:23 +02:00
-- >
< form >
2022-03-29 17:41:28 +02:00
< template v-if = "crosshairsPosition.length" >
< input id = "lyr-crosshairs" type = "checkbox" value = "crosshairs" v-model = "layerSelection" />
< label for = "lyr-crosshairs" title = "Show or hide the crosshairs position marker" > Crosshairs marker < / label >
< / template >
< / form >
2025-07-28 10:11:23 +02:00
<!--
2022-03-29 17:41:28 +02:00
< h3 class = "mt-3" title = "At least one of raw or final will always be selected" > Sequence data < / h3 >
< form >
< input id = "sd-raw" type = "checkbox" value = "R" v-model = "sequenceDataTypes" />
< label for = "sd-raw" > Show raw < / label >
< input id = "sd-final" type = "checkbox" value = "F" v-model = "sequenceDataTypes" />
< label for = "sd-final" > Show final < / label >
< / form >
2025-07-28 10:11:23 +02:00
-- >
2022-03-29 17:41:28 +02:00
<!-- QC data : This section is meant to show ( some ) QC results in a graphical way ,
as 3 D columns with lengths proportional to the QC values . Not implemented
at this time . -- >
<!-- BEGIN QC data
< h3
class = "mt-3"
title = "The selected attribute will be displayed as different length ‘ columns’ in 3D view. Use Ctrl or Shift + Left Click to tilt the map."
> QC data aspect < / h3 >
< v-select
class = "mt-1"
style = "max-width: 20ex;"
dense
hide - details
clearable
: items = "[ 'Depth', 'Pressure', 'Delta' ]"
>
< / v-select >
END QC data -- >
< / div >
< / div >
< div class = "map-overlay top right" >
<!-- TODO Implement search -- >
<!--
< div >
< input v-show = "searchVisible"
type = "text"
class = "mr-2"
placeholder = "Search for a point or event"
v - model = "searchText"
/ >
< v-icon
class = "my-1"
title = "Find element"
@ click = "searchVisible = !searchVisible"
> mdi - crosshairs < / v-icon >
< / div >
-- >
<!-- TODO Implement filtering -- >
<!--
< div >
< input v-show = "filterVisible"
type = "text"
class = "mr-2"
placeholder = "Filter by sequence or line"
v - model = "filterText"
/ >
< v-icon
class = "my-1"
title = "Filter items"
@ click = "filterVisible = !filterVisible"
> mdi - filter - outline < / v-icon >
< / div >
-- >
< div >
< v-icon
class = "my-1 mt-4"
title = "Fit view"
@ click = "zoomReset"
> mdi - magnify - scan < / v-icon >
< / div >
< div >
< v-icon
class = "my-1"
title = "Zoom in"
@ click = "zoomIn"
> mdi - magnify - plus - outline < / v-icon >
< / div >
< div >
< v-icon
class = "my-1"
title = "Zoom out"
@ click = "zoomOut"
> mdi - magnify - minus - outline < / v-icon >
< / div >
< / div >
< div class = "map-overlay bottom right" >
< div >
< v-progress-circular v-if = "loading || loadingProgress !== null"
size = "42"
color = "primary"
title = "Loading data…"
: indeterminate = "loadingProgress === null"
: value = "loadingProgress"
> { { Math . round ( loadingProgress ) } } % < / v-progress-circular >
2025-07-28 12:05:10 +02:00
< div v-else >
< v-icon
class = "my-1"
title = "Manual refresh"
@ click = "refresh"
> mdi - cloud - refresh - variant - outline < / v-icon >
< / div >
< / div >
< / div >
< div class = "map-overlay bottom left" >
< div >
2022-03-29 17:41:28 +02:00
< / div >
< / div >
< / div >
< / div >
2020-08-08 23:59:13 +02:00
< / template >
2022-03-29 17:41:28 +02:00
< style scoped >
2020-08-08 23:59:13 +02:00
# map {
height : 100 % ;
background - color : # dae4f7 ;
}
. theme -- dark # map {
background - color : # 111 ;
}
2020-09-25 18:33:55 +02:00
2022-03-29 17:41:28 +02:00
. theme -- dark . map - overlay - inner , . theme -- dark . map - overlay - icon {
background - color : # 222 ;
2020-09-25 18:33:55 +02:00
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. map - overlay {
font : 12 px / 20 px 'Helvetica Neue' , Arial , Helvetica , sans - serif ;
position : absolute ;
z - index : 1 ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. top {
top : 8 px ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. bottom {
bottom : 8 px ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. left {
left : 8 px ;
max - width : 20 % ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. right {
right : 8 px ;
max - width : 20 % ;
text - align : right ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. map - overlay - icon {
background - color : white ;
border : 1 px solid black ;
border - radius : 3 px ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. map - overlay - inner {
padding : 10 px ;
background - color : white ;
border : 1 px solid black ;
border - radius : 3 px ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. map - overlay - inner form {
display : grid ;
grid - template - columns : 36 px 1 fr ;
}
2020-08-08 23:59:13 +02:00
2025-07-28 10:11:23 +02:00
. map - overlay - inner . lines - points {
display : grid ;
grid - template - columns : minmax ( auto , 1 fr ) 36 px 36 px 36 px ;
grid - column - gap : 4 px ;
}
. map - overlay - inner . lines - points : nth - child ( 4 n + 1 ) {
margin - right : 6 px ;
}
2022-03-29 17:41:28 +02:00
. map - overlay input [ type = text ] {
background - color : white ;
border : 1 px solid black ;
border - radius : 4 px ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
. expand - on - hover : hover . map - overlay - icon {
display : none ;
}
2020-10-09 13:59:59 +02:00
2022-03-29 17:41:28 +02:00
. expand - on - hover : not ( : hover ) . map - overlay - inner {
display : none ;
}
2021-05-28 20:30:29 +02:00
2022-03-29 17:41:28 +02:00
. expand - on - hover : hover . map - overlay - inner {
display : block ;
}
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
/ * B E G I N
* This is meant to be a possible solution for keeping
* the layers menu open while selecting from a v - select
* in the context of displaying QC values . There may be
* better solutions .
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
. expand - on - hover : focus - within . map - overlay - icon {
display : none ;
}
2020-09-01 11:01:13 +02:00
2022-03-29 17:41:28 +02:00
. expand - on - hover : focus - within . map - overlay - inner {
display : block ;
2020-09-01 11:01:13 +02:00
}
2022-03-29 17:41:28 +02:00
* END
* /
< / style >
< script >
import { mapActions , mapGetters , mapState } from 'vuex' ;
import ftstamp from '@/lib/FormatTimestamp'
import zoomFitIcon from '@/assets/zoom-fit-best.svg'
import { markdown , markdownInline } from '@/lib/markdown' ;
import { Deck , WebMercatorViewport , FlyToInterpolator , CompositeLayer } from '@deck.gl/core' ;
import { GeoJsonLayer , LineLayer , BitmapLayer , ScatterplotLayer , IconLayer } from '@deck.gl/layers' ;
2025-07-28 10:11:23 +02:00
import { HeatmapLayer } from '@deck.gl/aggregation-layers' ;
import { TileLayer , MVTLayer } from '@deck.gl/geo-layers' ;
import * as d3a from 'd3-array' ;
2022-03-29 17:41:28 +02:00
//import { json } from 'd3-fetch';
import { SequenceDataLayer } from '@/lib/deck.gl' ;
2025-07-28 10:11:23 +02:00
import { unbundle , unpack , toJSON , isLittleEndian } from '@/lib/binary' ;
2022-03-29 17:41:28 +02:00
// Important info about performance:
// https://deck.gl/docs/developer-guide/performance#supply-attributes-directly
const endianness = isLittleEndian ( ) ;
let deck ;
/ * D e c o d e d S V G :
* < svg xmlns = "http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24" >
< path style = "fill:#ff0000;fill-opacity:1;stroke:none" d = "M 7 3 L 7 4.03125 A 4.5 4.5 0 0 0 3.0332031 8 L 2 8 L 2 9 L 3.03125 9 A 4.5 4.5 0 0 0 7 12.966797 L 7 14 L 8 14 L 8 12.96875 A 4.5 4.5 0 0 0 11.966797 9 L 13 9 L 13 8 L 11.96875 8 A 4.5 4.5 0 0 0 8 4.0332031 L 8 3 L 7 3 z M 7 5.0390625 L 7 8 L 4.0410156 8 A 3.5 3.5 0 0 1 7 5.0390625 z M 8 5.0410156 A 3.5 3.5 0 0 1 10.960938 8 L 8 8 L 8 5.0410156 z M 4.0390625 9 L 7 9 L 7 11.958984 A 3.5 3.5 0 0 1 4.0390625 9 z M 8 9 L 10.958984 9 A 3.5 3.5 0 0 1 8 11.960938 L 8 9 z " / >
< / svg >
* /
const hashMarkerIconURL = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij4KPHBhdGggc3R5bGU9ImZpbGw6I2ZmMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZSIgZD0iTSA3IDMgTCA3IDQuMDMxMjUgQSA0LjUgNC41IDAgMCAwIDMuMDMzMjAzMSA4IEwgMiA4IEwgMiA5IEwgMy4wMzEyNSA5IEEgNC41IDQuNSAwIDAgMCA3IDEyLjk2Njc5NyBMIDcgMTQgTCA4IDE0IEwgOCAxMi45Njg3NSBBIDQuNSA0LjUgMCAwIDAgMTEuOTY2Nzk3IDkgTCAxMyA5IEwgMTMgOCBMIDExLjk2ODc1IDggQSA0LjUgNC41IDAgMCAwIDggNC4wMzMyMDMxIEwgOCAzIEwgNyAzIHogTSA3IDUuMDM5MDYyNSBMIDcgOCBMIDQuMDQxMDE1NiA4IEEgMy41IDMuNSAwIDAgMSA3IDUuMDM5MDYyNSB6IE0gOCA1LjA0MTAxNTYgQSAzLjUgMy41IDAgMCAxIDEwLjk2MDkzOCA4IEwgOCA4IEwgOCA1LjA0MTAxNTYgeiBNIDQuMDM5MDYyNSA5IEwgNyA5IEwgNyAxMS45NTg5ODQgQSAzLjUgMy41IDAgMCAxIDQuMDM5MDYyNSA5IHogTSA4IDkgTCAxMC45NTg5ODQgOSBBIDMuNSAzLjUgMCAwIDEgOCAxMS45NjA5MzggTCA4IDkgeiAiIC8+Cjwvc3ZnPgo=" ;
2020-09-25 18:33:55 +02:00
2020-08-08 23:59:13 +02:00
export default {
name : "Map" ,
2022-03-29 17:41:28 +02:00
components : {
} ,
2020-08-08 23:59:13 +02:00
data ( ) {
return {
2022-03-29 17:41:28 +02:00
layerSelection : [ ] ,
layersAvailable : { } ,
sequenceDataTypes : [ "P" , "F" ] ,
sequenceDataElements : [ ] ,
sequenceDataTStamp : null ,
loadingProgress : null ,
viewState : { } ,
viewStateDefaults : {
//maxZoom: 18,
//maxPitch: 85
} ,
crosshairsPosition : [ ] ,
searchText : "" ,
searchVisible : false ,
filterText : "" ,
2025-07-27 11:03:26 +02:00
filterVisible : false ,
2025-07-28 10:11:23 +02:00
error : null ,
/ *
layerDefinitions : [
{
id : "osm" ,
name : "OpenStreetMap"
} ,
{
id : "sea" ,
name : "OpenSeaMap"
} ,
{
id : "nau" ,
name : "Nautical charts (NO)" ,
title : "Scan of Norway's nautical charts"
disabled : true
} ,
{
]
* /
2020-08-08 23:59:13 +02:00
} ;
} ,
2020-08-11 20:57:29 +02:00
computed : {
2022-03-29 17:41:28 +02:00
sequenceDataPreplots ( ) {
return {
tstamp : this . sequenceDataTStamp ,
sequences : this . sequenceDataElements . filter ( i => i . type == "P" )
} ;
} ,
2025-07-28 10:11:23 +02:00
sequenceDataRaw ( ) {
return {
tstamp : this . sequenceDataTStamp ,
sequences : this . sequenceDataElements . filter ( i => i . type == "R" )
} ;
} ,
sequenceDataFinal ( ) {
return {
tstamp : this . sequenceDataTStamp ,
sequences : this . sequenceDataElements . filter ( i => i . type == "F" )
} ;
} ,
2022-03-29 17:41:28 +02:00
sequenceDataPostplots ( ) {
const fn = i => i . type != "P" && this . sequenceDataTypes . includes ( i . type ) ;
return {
tstamp : this . sequenceDataTStamp ,
sequences : this . sequenceDataElements . filter ( fn )
} ;
} ,
2025-07-28 10:11:23 +02:00
fullProspectRaw ( ) {
// Get raw data, so type = "R"
const sequences = this . sequenceDataElements . filter ( i => i . type == "R" ) ;
return [ ... sequences . map ( i => toJSON ( i . data ) ) ] . flat ( ) ;
} ,
fullProspectFinal ( ) {
// Get raw data, so type = "R"
const sequences = this . sequenceDataElements . filter ( i => i . type == "F" ) ;
return [ ... sequences . map ( i => toJSON ( i . data ) ) ] . flat ( ) ;
} ,
2021-05-09 15:29:17 +02:00
... mapGetters ( [ 'user' , 'loading' , 'serverEvent' , 'lineName' , 'serverEvent' ] ) ,
2020-09-25 18:33:55 +02:00
... mapState ( { projectSchema : state => state . project . projectSchema } )
2020-08-11 20:57:29 +02:00
} ,
2020-08-11 17:16:54 +02:00
watch : {
2022-03-29 17:41:28 +02:00
layerSelection ( newVal , oldVal ) {
this . render ( ) ;
} ,
viewState : {
handler ( ) {
this . setViewState ( ) ;
} ,
deep : true
} ,
sequenceDataTypes ( val , old ) {
// Ensure that at least one of raw or final is always selected
if ( ! val . includes ( "R" ) && ! val . includes ( "F" ) ) {
if ( old . includes ( "F" ) ) {
this . sequenceDataTypes . push ( "R" ) ;
2020-08-26 17:48:55 +02:00
} else {
2022-03-29 17:41:28 +02:00
this . sequenceDataTypes . push ( "F" ) ;
2020-08-26 17:48:55 +02:00
}
2020-08-11 17:16:54 +02:00
}
2020-09-03 16:21:29 +02:00
} ,
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
sequenceDataElements ( ) {
//console.log("seq data changed");
this . sequenceDataTStamp = Date . now ( ) ;
//this.render();
2021-05-09 15:29:17 +02:00
} ,
2020-09-03 16:21:29 +02:00
2022-03-29 17:41:28 +02:00
sequenceDataPreplots ( ) {
this . render ( ) ;
} ,
sequenceDataPostplots ( ) {
this . render ( ) ;
2021-05-17 17:01:44 +02:00
} ,
2022-04-29 14:48:21 +02:00
2021-05-17 17:01:44 +02:00
$route ( to , from ) {
if ( to . name == "map" ) {
2022-03-29 17:41:28 +02:00
this . decodeURLHash ( ) ;
2021-05-17 17:01:44 +02:00
}
2022-03-29 17:41:28 +02:00
} ,
2020-08-11 17:16:54 +02:00
} ,
2020-08-08 23:59:13 +02:00
methods : {
2022-03-29 17:41:28 +02:00
initView ( ) {
} ,
render ( ) {
if ( deck ) {
const layers = [ ] ;
for ( const name in this . layersAvailable ) {
//console.log("Visible", name, this.layerSelection.includes(name));
const fn = this . layersAvailable [ name ] ;
layers . push ( fn ( {
visible : this . layerSelection . includes ( name )
} ) ) ;
}
//console.log("setProps", this.sequenceDataTStamp, deck.layerManager && deck.layerManager.getLayers().length);
//deck.setProps({layers, initialViewState: this.viewState});
deck . setProps ( { layers } ) ;
this . updateURL ( ) ;
}
} ,
zoomReset ( ) {
2025-07-28 10:11:23 +02:00
if ( deck ) {
2022-03-29 17:41:28 +02:00
const bounds = this . sequencesBBox ( ) ;
2025-07-28 10:11:23 +02:00
if ( bounds ) {
const layer = deck . layerManager . layers [ 0 ] ; // Get the first layer we come across
const initialViewState = { ... this . viewStateDefaults , ... layer . context . viewport . fitBounds ( bounds ) } ;
initialViewState . transitionDuration = 500 ;
deck . setProps ( { initialViewState } ) ;
}
} else {
console . warn ( "Unable to calculate bounding box" ) ;
2022-03-29 17:41:28 +02:00
}
} ,
zoomIn ( ) {
if ( deck ) {
const viewState = deck . getViewports ( ) [ 0 ] ;
const initialViewState = { ... this . viewStateDefaults , ... viewState } ;
initialViewState . zoom += 1 ;
initialViewState . transitionDuration = 300 ;
deck . setProps ( { initialViewState } ) ;
}
} ,
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
zoomOut ( ) {
if ( deck ) {
const viewState = deck . getViewports ( ) [ 0 ] ;
const initialViewState = { ... this . viewStateDefaults , ... viewState } ;
initialViewState . zoom -= 1 ;
initialViewState . transitionDuration = 300 ;
deck . setProps ( { initialViewState } ) ;
}
2020-08-08 23:59:13 +02:00
} ,
2022-04-29 14:48:21 +02:00
2025-07-28 12:05:10 +02:00
async refresh ( ) {
const cache = await caches . open ( "dougal" ) ;
for ( const key of await cache . keys ( ) ) {
cache . delete ( key ) ;
}
await this . $root . sleep ( 300 ) ;
this . getSequenceData ( ) ;
} ,
2022-03-29 17:41:28 +02:00
sequencesBBox ( margin = 0.1 ) {
let [ λ0 , φ0 , λ1 , φ1 ] = [ + Infinity , + Infinity , - Infinity , - Infinity ] ;
2022-04-29 14:48:21 +02:00
2025-07-28 10:11:23 +02:00
// FIXME Temporary fix to get this working again, but this.fullProspectRaw /
// this.fullProspectFinal are not intended to remain as JSON data.
const data = this . fullProspectRaw ? ? this . fullProspectFinal ;
if ( data ) {
for ( const i of data ) {
const λ = i . longitude ;
const φ = i . latitude ;
λ0 = Math . min ( λ0 , λ ) ;
φ0 = Math . min ( φ0 , φ ) ;
λ1 = Math . max ( λ1 , λ ) ;
φ1 = Math . max ( φ1 , φ ) ;
2020-09-25 18:33:55 +02:00
}
2025-07-28 10:11:23 +02:00
const δλ = λ1 - λ0 ;
const δφ = φ1 - φ0 ;
const mλ = δλ * margin ;
const mφ = δφ * margin ;
return [ [ λ0 - mλ , φ0 - mφ ] , [ λ1 + mλ , φ1 + mφ ] ] ;
2020-09-25 18:33:55 +02:00
}
} ,
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
async getSequenceData ( ) {
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
const types = [ "P" , "R" , "F" ] ;
const sequenceList = await this . api ( [ ` /project/ ${ this . $route . params . project } /sequence ` ] ) ;
const sequenceNumbers = sequenceList . map ( i => i . sequence ) ;
const sequenceCount = sequenceNumbers . length * types . length ;
let count = 0 ;
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
const self = this ;
const iterator = async function * ( ) {
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
const download = ( url ) => {
2022-04-29 14:48:21 +02:00
2020-10-09 13:59:59 +02:00
const init = {
2022-03-29 17:41:28 +02:00
format : "arrayBuffer" ,
2020-10-09 13:59:59 +02:00
headers : {
2022-03-29 17:41:28 +02:00
Accept : "application/dougal-map-sequence+octet-stream"
2020-10-09 13:59:59 +02:00
}
} ;
2022-04-29 14:48:21 +02:00
2025-07-28 11:09:55 +02:00
return new Promise ( async ( resolve , reject ) => {
2022-03-29 17:41:28 +02:00
const cb = ( err , res ) => {
if ( res && ! err ) {
const etag = res . headers . get ( "ETag" ) ;
res . arrayBuffer ( ) . then ( data => {
resolve ( { etag , data } ) ;
} )
} else {
reject ( err ) ;
2020-09-25 18:33:55 +02:00
}
2022-03-29 17:41:28 +02:00
} ;
2022-04-29 14:48:21 +02:00
2025-07-28 11:09:55 +02:00
// Technically we do not need to await but this is
// to slow down the cient and avoid firing too many
// requests at once.
await self . api ( [ url , init , cb , { cache : true } ] ) ;
2020-09-26 01:18:12 +02:00
} ) ;
2020-08-08 23:59:13 +02:00
2020-08-29 10:30:30 +02:00
}
2022-03-29 17:41:28 +02:00
for ( const sequence of sequenceNumbers ) {
for ( const type of types ) {
2025-07-27 11:16:47 +02:00
try {
const url = ` /project/ ${ self . $route . params . project } /sequence/ ${ sequence } ?geometry= ${ type } ${ endianness ? "" : "&endianness=big" } ` ;
const sequenceData = await download ( url ) ;
if ( sequenceData ) {
yield {
id : ` ${ sequence } - ${ sequenceData . etag } ` ,
sequence : sequence ,
type ,
data : sequenceData . data
} ;
}
} catch ( err ) {
console . error ( ` Error downloading sequence ${ sequence } ` ) ;
console . error ( err ) ;
}
2020-08-29 10:30:30 +02:00
}
}
2022-03-29 17:41:28 +02:00
} ;
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
for await ( const value of iterator ( ) ) {
this . loadingProgress = ++ count / sequenceCount * 100 ;
//console.log("PRG", count, sequenceCount, this.loadingProgress, );
this . sequenceDataElements . push ( Object . freeze ( value ) ) ;
2020-08-29 10:30:30 +02:00
}
2022-03-29 17:41:28 +02:00
this . loadingProgress = null ;
console . log ( "passed for await" , deck ) ;
2020-08-29 10:30:30 +02:00
} ,
2022-03-29 17:41:28 +02:00
async initLayers ( gl ) {
console . log ( "SHOULD BE INITIALISING LAYERS HERE" , gl ) ;
this . decodeURL ( ) ;
this . decodeURLHash ( ) ;
deck . onViewStateChange = this . updateURL ;
} ,
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
setViewState ( ) {
const viewport = deck . getViewports ( ) [ 0 ] ;
if ( deck && viewport && this . viewState ) {
const initialViewState = {
... this . initialViewState ,
... viewport ,
... this . viewState
} ;
deck . setProps ( { initialViewState } ) ;
2020-08-29 10:30:30 +02:00
}
} ,
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
updateURL ( { viewState } = { } ) {
if ( ! viewState && deck ? . viewManager ) {
viewState = deck . getViewports ( ) [ 0 ] ;
2021-05-09 15:29:17 +02:00
}
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
if ( viewState ) {
const key = { x : "longitude" , y : "latitude" , z : "zoom" , p : "pitch" , b : "bearing" } ;
const view = Object . keys ( key ) . map ( k => [ k , viewState [ key [ k ] ] ] ) . filter ( i => i [ 1 ] ) . map ( i => i . join ( "" ) ) . join ( "" ) ;
const layers = this . layerSelection . join ( ";" )
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
const value = [ view , layers ] . join ( ":" ) ;
2021-05-09 15:29:17 +02:00
2022-03-29 17:41:28 +02:00
if ( view . length && layers . length ) {
localStorage . setItem ( ` dougal/prefs/ ${ this . user ? . name } / ${ this . $route . params . project } / ${ this . $options . name } /view/v1 ` , value ) ;
}
2021-05-09 15:29:17 +02:00
}
} ,
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
decodeURL ( ) {
const value = localStorage . getItem ( ` dougal/prefs/ ${ this . user ? . name } / ${ this . $route . params . project } / ${ this . $options . name } /view/v1 ` ) ;
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
if ( value ) {
this . decodeURLHash ( value ) ;
2021-05-17 17:01:44 +02:00
}
2022-03-29 17:41:28 +02:00
} ,
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
decodeURLHash ( hash ) {
if ( ! hash ) {
hash = document . location . hash . substring ( 1 ) ;
2021-05-17 17:01:44 +02:00
}
2020-08-29 10:30:30 +02:00
2022-03-29 17:41:28 +02:00
if ( hash ) {
const [ view , layers , crosshairs ] = hash . split ( ":" ) ;
2022-05-07 12:07:03 +02:00
2022-03-29 17:41:28 +02:00
if ( view ) {
const rx0 = /[xyzpb][+-]?[0-9]+\.?[0-9]*/g ;
const rx1 = /([xyzpb])([+-]?[0-9]+\.?[0-9]*)/ ;
2022-05-07 12:07:03 +02:00
2022-03-29 17:41:28 +02:00
const key = { x : "longitude" , y : "latitude" , z : "zoom" , p : "pitch" , b : "bearing" } ;
2023-09-12 11:20:50 +02:00
2022-03-29 17:41:28 +02:00
this . viewState = Object . fromEntries ( view . match ( rx0 ) . map ( i => i . match ( rx1 ) . slice ( 1 , 3 ) ) . map ( i => { i [ 0 ] = key [ i [ 0 ] ] ; i [ 1 ] = Number ( i [ 1 ] ) ; return i } ) ) ;
2023-09-12 11:20:50 +02:00
}
2022-03-29 17:41:28 +02:00
if ( layers ) {
const l = layers . split ( ";" ) . filter ( i => this . layersAvailable . hasOwnProperty ( i ) ) ;
if ( l . length ) {
this . layerSelection = l ;
2023-09-12 11:20:50 +02:00
}
}
2022-03-29 17:41:28 +02:00
if ( crosshairs ) {
const [ x , y ] = crosshairs . split ( "," ) . map ( i => Number ( i ) ) ;
2023-09-13 12:58:44 +02:00
2022-03-29 17:41:28 +02:00
if ( ! isNaN ( x ) && ! isNaN ( y ) ) {
this . crosshairsPosition = [ x , y ] ;
if ( ! this . layerSelection . includes ( "crosshairs" ) ) {
this . layerSelection . push ( "crosshairs" ) ;
2023-09-13 12:58:44 +02:00
}
}
2022-03-29 17:41:28 +02:00
}
2023-09-12 11:20:50 +02:00
}
} ,
2025-07-27 11:06:12 +02:00
checkWebGLSupport ( ) {
const canvas = document . createElement ( 'canvas' ) ;
const gl = canvas . getContext ( 'webgl2' ) || canvas . getContext ( 'webgl' ) ;
if ( ! gl ) {
//this.error = 'WebGL is not supported in this browser or device.';
//this.loading = false;
console . error ( 'WebGL not supported' ) ;
return false ;
}
const isWebGL2 = gl instanceof WebGL2RenderingContext ;
console . log ( 'WebGL Support:' , isWebGL2 ? 'WebGL2' : 'WebGL1' ) ;
return true ;
} ,
toBase64 ( binary ) {
return btoa ( String . fromCharCode ( ... new Uint8Array ( binary ) ) ) ;
} ,
fromBase64 ( text ) {
const decoded = atob ( text ) ;
const arr = new Uint8Array ( decoded . length ) ;
for ( let i = 0 ; i < decoded .length ; i + + ) {
arr [ i ] = decoded . charCodeAt ( i ) ;
}
return arr . buffer ;
} ,
2022-03-29 17:41:28 +02:00
... mapActions ( [ "api" ] )
2020-08-08 23:59:13 +02:00
} ,
2022-03-29 17:41:28 +02:00
async mounted ( ) {
2025-07-27 11:06:12 +02:00
if ( ! this . checkWebGLSupport ( ) ) {
for ( const countdown = 5 ; countdown > 0 ; countdown -- ) {
if ( countdown ) {
this . error = ` This browser does not support WebGL. Switching to legacy map view in ${ countdown } seconds. ` ;
await new Promise ( ( resolve ) => setTimeout ( ( ) => resolve , 1000 ) ) ;
}
}
this . error = null ;
console . log ( "TODO: Should switch to legacy map view" ) ;
}
2022-03-29 17:41:28 +02:00
const COLOR _SCALE = [
// negative
[ 65 , 182 , 196 ] ,
[ 127 , 205 , 187 ] ,
[ 199 , 233 , 180 ] ,
[ 237 , 248 , 177 ] ,
// positive
[ 255 , 255 , 204 ] ,
[ 255 , 237 , 160 ] ,
[ 254 , 217 , 118 ] ,
[ 254 , 178 , 76 ] ,
[ 253 , 141 , 60 ] ,
[ 252 , 78 , 42 ] ,
[ 227 , 26 , 28 ] ,
[ 189 , 0 , 38 ] ,
[ 128 , 0 , 38 ]
] ;
this . layersAvailable . osm = ( options = { } ) => {
return new TileLayer ( {
id : "osm" ,
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers
data : 'https://c.tile.openstreetmap.org/{z}/{x}/{y}.png' ,
minZoom : 0 ,
maxZoom : 19 ,
tileSize : 256 ,
renderSubLayers : props => {
const {
bbox : { west , south , east , north }
} = props . tile ;
return new BitmapLayer ( props , {
data : null ,
image : props . data ,
bounds : [ west , south , east , north ]
} ) ;
} ,
... options
} )
}
2022-04-29 14:48:21 +02:00
2025-07-28 10:11:23 +02:00
/ *
// OSM tiles layer. Handy to make water transparent
// but super reliable yet
this . layersAvailable . osm = ( options = { } ) => {
return new MVTLayer ( {
id : 'osm' ,
data : 'https://vector.openstreetmap.org/shortbread_v1/{z}/{x}/{y}.mvt' ,
minZoom : 0 ,
maxZoom : 14 ,
getFillColor : feature => {
const layer = feature . properties . layerName ;
//console.log("layer =", layer, feature.properties.kind);
switch ( layer ) {
case "ocean" :
return [ 0 , 0 , 0 , 0 ] ;
case "land" :
return [ 0x54 , 0x6E , 0x7A , 255 ] ;
default :
return [ 240 , 240 , 240 , 255 ] ;
}
} ,
getLineColor : feature => {
if ( feature . properties . layer === 'water' ) {
return [ 0 , 0 , 0 , 0 ] ; // No outline for water
}
return [ 192 , 192 , 192 , 255 ] ; // Default line color for roads, etc.
} ,
getLineWidth : feature => {
if ( feature . properties . highway ) {
return feature . properties . highway === 'motorway' ? 6 : 3 ; // Example road widths
}
return 1 ;
} ,
stroked : true ,
filled : true ,
pickable : true
} ) ;
}
* /
2025-07-27 11:07:08 +02:00
/ *
2022-03-29 17:41:28 +02:00
this . layersAvailable . nau = ( options = { } ) => {
return new TileLayer ( {
id : "nau" ,
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Tile_servers
data : 'https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=sjokartraster&zoom={z}&x={x}&y={y}' ,
minZoom : 0 ,
maxZoom : 19 ,
tileSize : 256 ,
renderSubLayers : props => {
const {
bbox : { west , south , east , north }
} = props . tile ;
return new BitmapLayer ( props , {
data : null ,
image : props . data ,
bounds : [ west , south , east , north ]
} ) ;
2020-09-25 18:33:55 +02:00
} ,
2022-03-29 17:41:28 +02:00
... options
} )
}
2025-07-27 11:07:08 +02:00
* /
2022-03-29 17:41:28 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . navp = ( options = { } ) => {
return new ScatterplotLayer ( {
id : 'navp' ,
2025-07-28 11:00:54 +02:00
data : ` /api/navdata?limit=10000 ` ,
2025-07-28 10:11:23 +02:00
getPosition : ( d ) => ( [ d . longitude , d . latitude ] ) ,
getRadius : d => ( d . speed ) ,
radiusScale : 1 ,
2022-03-29 17:41:28 +02:00
lineWidthMinPixels : 2 ,
2025-07-28 10:11:23 +02:00
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
2022-03-29 17:41:28 +02:00
getLineColor : [ 127 , 65 , 90 ] ,
2025-07-28 10:11:23 +02:00
getColor : [ 255 , 0 , 0 ] ,
getPointRadius : 12 ,
radiusUnits : "pixels" ,
pointRadiusMinPixels : 4 ,
stroked : false ,
filled : true ,
pickable : true ,
... options
} )
} ;
this . layersAvailable . navl = ( options = { } ) => {
return new LineLayer ( {
id : 'navl' ,
data : ` /api/navdata ` ,
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 ,
pickable : true ,
2022-03-29 17:41:28 +02:00
... options
} )
} ;
this . layersAvailable . sea = ( options = { } ) => {
return new TileLayer ( {
id : "sea" ,
data : 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png' ,
minZoom : 0 ,
maxZoom : 19 ,
tileSize : 256 ,
renderSubLayers : props => {
const {
bbox : { west , south , east , north }
} = props . tile ;
return new BitmapLayer ( props , {
data : null ,
image : props . data ,
bounds : [ west , south , east , north ]
2020-09-25 18:33:55 +02:00
} ) ;
} ,
2022-03-29 17:41:28 +02:00
... options
} )
}
this . layersAvailable . log = ( options = { } ) => {
return new GeoJsonLayer ( {
id : 'log' ,
data : ` /api/project/ ${ this . $route . params . project } /event?mime=application/geo%2Bjson ` ,
lineWidthMinPixels : 2 ,
getLineColor : [ 127 , 65 , 90 ] ,
getPointRadius : 2 ,
radiusUnits : "pixels" ,
pointRadiusMinPixels : 2 ,
pickable : true ,
... options
} )
2020-09-25 18:33:55 +02:00
} ;
2022-04-29 14:48:21 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . psll = ( options = { } ) => {
2022-03-29 17:41:28 +02:00
return new GeoJsonLayer ( {
2025-07-28 10:11:23 +02:00
id : 'psll' ,
2022-03-29 17:41:28 +02:00
data : ` /api/project/ ${ this . $route . params . project } /gis/preplot/line?class=V ` ,
lineWidthMinPixels : 1 ,
getLineColor : ( d ) => d . properties . ntba ? [ 240 , 248 , 255 , 200 ] : [ 85 , 170 , 255 , 200 ] ,
getLineWidth : 1 ,
getPointRadius : 2 ,
radiusUnits : "pixels" ,
pointRadiusMinPixels : 2 ,
pickable : true ,
... options
} )
} ;
2022-05-07 12:07:03 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . ppll = ( options = { } ) => {
2022-03-29 17:41:28 +02:00
return new GeoJsonLayer ( {
2025-07-28 10:11:23 +02:00
id : 'ppll' ,
2022-03-29 17:41:28 +02:00
data : ` /api/project/ ${ this . $route . params . project } /gis/preplot/line ` ,
lineWidthMinPixels : 1 ,
getLineColor : ( d ) => d . properties . ntba ? [ 240 , 248 , 255 , 200 ] : [ 85 , 170 , 255 , 200 ] ,
getLineWidth : 1 ,
getPointRadius : 2 ,
radiusUnits : "pixels" ,
pointRadiusMinPixels : 2 ,
pickable : true ,
... options
} )
} ;
2020-08-08 23:59:13 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . planl = ( options = { } ) => {
2022-03-29 17:41:28 +02:00
return new LineLayer ( {
id : 'plan' ,
data : ` /api/project/ ${ this . $route . params . project } /plan ` ,
widthMinPixels : 2 ,
widthUnits : "pixels" ,
getSourcePosition : d => d . geometry . coordinates [ 0 ] ,
getTargetPosition : d => d . geometry . coordinates [ 1 ] ,
getLineWidth : 8 ,
getColor : ( d ) => {
const k = d . azimuth / 360 * 255 ;
console . log ( "COLOUR" , k ) ;
return [ k , 128 , k , 200 ] ;
} ,
pickable : true ,
... options
} )
} ;
2020-09-25 22:42:33 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . seqrl = ( options = { } ) => {
return new GeoJsonLayer ( {
id : 'seqrl' ,
data : ` /api/project/ ${ this . $route . params . project } /gis/raw/line ` ,
lineWidthMinPixels : 1 ,
getLineColor : ( d ) => d . properties . ntbp ? [ 0xe6 , 0x51 , 0x00 , 200 ] : [ 0xff , 0x98 , 0x00 , 200 ] ,
getLineWidth : 1 ,
getPointRadius : 2 ,
radiusUnits : "pixels" ,
pointRadiusMinPixels : 2 ,
pickable : true ,
... options
} )
} ;
this . layersAvailable . seqfl = ( options = { } ) => {
return new GeoJsonLayer ( {
id : 'seqfl' ,
data : ` /api/project/ ${ this . $route . params . project } /gis/final/line ` ,
lineWidthMinPixels : 1 ,
getLineColor : ( d ) => d . properties . pending ? [ 0xa7 , 0xff , 0xab , 200 ] : [ 0x00 , 0x96 , 0x88 , 200 ] ,
getLineWidth : 1 ,
getPointRadius : 2 ,
radiusUnits : "pixels" ,
pointRadiusMinPixels : 2 ,
pickable : true ,
... options
} )
} ;
this . layersAvailable . pplp = ( options = { } ) => {
2022-03-29 17:41:28 +02:00
return new SequenceDataLayer ( {
2025-07-28 10:11:23 +02:00
id : 'pplp' ,
2022-03-29 17:41:28 +02:00
data : this . sequenceDataPreplots ,
pickable : true ,
... options
} ) ;
} ;
2020-09-25 22:42:33 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . seqrp = ( options = { } ) => {
return new SequenceDataLayer ( {
id : 'seqrp' ,
data : this . sequenceDataRaw ,
pickable : true ,
... options
} ) ;
} ;
this . layersAvailable . seqfp = ( options = { } ) => {
2022-03-29 17:41:28 +02:00
return new SequenceDataLayer ( {
2025-07-28 10:11:23 +02:00
id : 'seqfp' ,
data : this . sequenceDataFinal ,
2022-03-29 17:41:28 +02:00
pickable : true ,
... options
} ) ;
} ;
2020-09-25 22:42:33 +02:00
2025-07-28 10:11:23 +02:00
this . layersAvailable . seqrh = ( options = { } ) => {
return new HeatmapLayer ( {
id : 'seqrh' ,
data : this . fullProspectRaw ,
radiusPixels : 15 ,
getPosition : d => [ d . longitude , d . latitude ] ,
getWeight : d => Math . sqrt ( d . Δi * * 2 + d . Δj * * 2 ) ,
colorDomain : [ 2 , 20 ] ,
aggregation : "MEAN" ,
pickable : false ,
... options
} ) ;
} ;
this . layersAvailable . seqfh = ( options = { } ) => {
return new HeatmapLayer ( {
id : 'seqfh' ,
data : this . fullProspectFinal ,
radiusPixels : 15 ,
getPosition : d => [ d . longitude , d . latitude ] ,
getWeight : d => Math . sqrt ( d . Δi * * 2 + d . Δj * * 2 ) ,
colorDomain : [ 2 , 20 ] ,
aggregation : "MEAN" ,
pickable : false ,
... options
} ) ;
} ;
2022-03-29 17:41:28 +02:00
this . layersAvailable . crosshairs = ( options = { } ) => {
return new IconLayer ( {
id : 'crosshairs' ,
data : [ { position : [ ... this . crosshairsPosition ] } ] ,
getSize : 24 ,
getColor : [ 255 , 0 , 0 ] ,
getIcon : ( d ) => {
return {
url : hashMarkerIconURL ,
width : 24 ,
height : 24
} ;
} ,
... options
} ) ;
} ;
2020-09-25 22:42:33 +02:00
2022-03-29 17:41:28 +02:00
deck = new Deck ( {
parent : document . getElementById ( "map" ) ,
mapStyle : 'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json' ,
initialViewState : {
... this . viewStateDefaults ,
latitude : 61.5 ,
longitude : 2.5 ,
zoom : 7 ,
pitch : 0 ,
} ,
controller : { inertia : true } ,
layers : [ ] ,
getTooltip ,
pickingRadius : 24 ,
onWebGLInitialized : this . initLayers
2020-09-25 22:42:33 +02:00
} ) ;
2025-07-28 10:11:23 +02:00
2022-03-29 17:41:28 +02:00
function getTooltip ( args ) {
//console.log("tooltip", args?.layer?.constructor?.layerName, args);
if ( args ? . layer ? . constructor ? . tooltip ) {
2022-04-29 14:48:21 +02:00
2022-03-29 17:41:28 +02:00
return args . layer . constructor . tooltip ( args ) ;
2020-08-29 10:30:30 +02:00
2022-03-29 17:41:28 +02:00
} else if ( args ? . layer ? . id == "log" ) {
2020-08-29 10:30:30 +02:00
2022-03-29 17:41:28 +02:00
const p = args ? . object ? . properties ;
if ( p ) {
let html = "" ;
if ( p . sequence && p . point ) {
html += ` S ${ p . sequence . toString ( ) . padStart ( 3 , "0" ) } ${ p . point } </br> \ n ` ;
}
html += ` <small> ${ p . tstamp } </small><br/> \ n ` ;
html += ` <span> ${ p . remarks } </span> ` ;
if ( p . labels ? . length ) {
html += ` </br> \ n<small><i> ${ p . labels . join ( ", " ) } </i></small> ` ;
}
2020-08-29 10:30:30 +02:00
2022-03-29 17:41:28 +02:00
const style = { "max-width" : "50ex" } ;
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
return { html , style } ;
}
2020-08-29 10:30:30 +02:00
2025-07-28 10:11:23 +02:00
} else if ( args ? . layer ? . id == "pplp" || args ? . layer ? . id == "pslp" ) {
2023-09-12 11:20:50 +02:00
2022-03-29 17:41:28 +02:00
const p = args ? . object ? . properties ;
const isSailline = args ? . layer ? . id == "psl" ;
if ( p ) {
let html = ` ${ isSailline ? "Sail" : "Preplot" } line ${ p . line } ( ${ p . incr ? "+" : "-" } ) ` ;
if ( p . ntba ) {
html += ` <b title="Not to be acquired">NTBA</a> ` ;
}
if ( p . remarks ) {
html += ` <br/> \ n ${ p . remarks } ` ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
const style = { "max-width" : "50ex" } ;
2023-09-12 11:20:50 +02:00
2022-03-29 17:41:28 +02:00
return { html , style } ;
2021-05-09 15:29:17 +02:00
}
2022-03-29 17:41:28 +02:00
} else if ( args ? . layer ? . id == "plan" ) {
const p = args ? . object ;
if ( p ) {
const duration = ` ${ ( p . duration ? . hours ? ? 0 ) . toString ( ) . padStart ( 2 , "0" ) } : ${ ( p . duration ? . minutes ? ? 0 ) . toString ( ) . padStart ( 2 , "0" ) } ` ;
const Δt = ( ( new Date ( p . ts1 ) ) . valueOf ( ) - ( new Date ( p . ts0 ) ) . valueOf ( ) ) / 1000 ; // seconds
const speed = ( p . length / Δt ) * 3.6 / 1.852 ; // knots
let html = ` Planned sequence <b> ${ p . sequence } </b></br> \ n ` +
` Line <b> ${ p . line } </b> – ${ p . name } </br> \ n ` +
` ${ p . num _points } Points</br> \ n ` +
` ${ p . length } m ${ p . azimuth . toFixed ( 2 ) } °</br> \ n ` +
` ${ duration } @ ${ speed . toFixed ( 1 ) } kt</br> \ n ` +
2025-07-28 12:04:27 +02:00
` <b> ${ p . fsp } </b> @ ${ p . ts0 . toISOString ( ) } </br> \ n ` +
` <b> ${ p . lsp } </b> @ ${ p . ts1 . toISOString ( ) } ` ;
2022-03-29 17:41:28 +02:00
if ( p . remarks ) {
html += ` </br> \ n<hr/> ${ p . remarks } ` ;
}
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
const style = { "max-width" : "50ex" } ;
2020-08-08 23:59:13 +02:00
2022-03-29 17:41:28 +02:00
return { html , style } ;
2020-08-08 23:59:13 +02:00
}
2025-07-28 10:11:23 +02:00
} else if ( args ? . layer ? . id == "seqrl" || args ? . layer ? . id == "seqfl" ) {
let type ;
switch ( args . layer . id ) {
case "seqrl" :
type = "Raw" ;
break ;
case "seqfl" :
type = "Final" ;
break ;
}
const p = args ? . object ? . properties ;
if ( p ) {
let html = ` Sequence ${ p . sequence } ( ${ type } )<br/> \ n ` ;
html += ` Line <b> ${ p . line } </b><br/> \ n ` ;
html += ` ${ p . num _points } points ( ${ p . missing _shots ? ( p . missing _shots + " missing" ) : "None missing" } )<br/> \ n ` ;
html += ` ${ ( p . length ? ? 0 ) . toFixed ( 0 ) } m ${ ( p . azimuth ? ? 0 ) . toFixed ( 1 ) } °<br/> \ n ` ;
html += ` ${ p . duration } <br/> \ n ` ;
2025-07-28 11:01:38 +02:00
html += ` <b> ${ p . fsp } </b> @ ${ p . ts0 } <br/> \ n ` ;
html += ` <b> ${ p . lsp } </b> @ ${ p . ts1 } <br/> \ n ` ;
2025-07-28 10:11:23 +02:00
if ( p . ntbp ) {
html += "<b>Not to be processed</b><br/>\n" ;
} else if ( p . pending ) {
html += "<b>Pending</b><br/>\n" ;
}
html += "<hr/><br/>\n" ;
html += markdown ( p . remarks ) ;
const style = { "max-width" : "50ex" } ;
return { html , style } ;
}
} else if ( args ? . layer ? . id == "navp" ) {
const p = args . object ;
if ( p ) {
let html = ` ${ p . vesselName } <br/> \ n `
+ ` ${ p . tstamp } <br/> \ n `
+ ` BSP ${ ( p . speed ? ? 0 ) . toFixed ( 1 ) } kt CMG ${ ( p . cmg ? ? 0 ) . toFixed ( 1 ) . padStart ( 5 , "0" ) } ° HDG ${ ( p . bearing ? ? 0 ) . toFixed ( 1 ) . padStart ( 5 , "0" ) } ° DPT ${ ( p . waterDepth ? ? 0 ) . toFixed ( 1 ) } m<br/>\ n `
+ ` ${ p . lineStatus } <br/> \ n ` ;
if ( p . guns ) {
console . log ( p ) ;
const pressure = p . guns . map ( i => i [ 11 ] ) ; // 11 is gun pressure
const μpress = d3a . mean ( pressure ) ;
const σ press = d3a . deviation ( pressure ) ;
if ( p . lineStatus && p . lineStatus != "offline" ) {
html += ` ${ p . lineName } <br/> \ n `
+ ` S: ${ p . _sequence } L: ${ p . _line } P: ${ p . shot } <br/> `
+ ` Source ${ p . src _number } `
+ ( ( p . trg _mode && p . trg _mode != "external" ) ? ` <b> ${ p . trg _mode . toUpperCase ( ) } TRIGGER</b> ` : "" )
+ ` <small>FSID ${ p . fsid } </small> <small>mask ${ p . mask } </small><br/> \ n `
+ ` Δ ${ ( p . avg _delta ? ? 0 ) . toFixed ( 3 ) } ms ±${ ( p . std _delta ? ? 0 ) . toFixed ( 3 ) } ms<br/>\ n `
+ ` ${ ( μpress ? ? 0 ) . toFixed ( 0 ) } psi ±${ ( σ press? ? 0 ) . toFixed ( 0 ) } psi / ${ ( p . volume ? ? 0 ) . toFixed ( 0 ) } in³<br/>\ n `
+ ` along ${ ( p . inline ? ? 0 ) . toFixed ( 1 ) } m / across ${ ( p . crossline ? ? 0 ) . toFixed ( 1 ) } m<br/>\ n ` ;
} else {
// Soft start?
html +=
` Source ${ p . src _number } `
+ ( ( p . trg _mode && p . trg _mode != "external" ) ? ` <b> ${ p . trg _mode . toUpperCase ( ) } TRIGGER</b> ` : "" )
+ ` <small>mask ${ p . mask } </small><br/> \ n `
+ ` Δ ${ ( p . avg _delta ? ? 0 ) . toFixed ( 3 ) } ms ±${ ( p . std _delta ? ? 0 ) . toFixed ( 3 ) } ms<br/>\ n `
+ ` ${ ( p . manifold ? ? 0 ) . toFixed ( 0 ) } psi / ${ ( p . volume ? ? 0 ) . toFixed ( 0 ) } in³<br/>\ n `
+ ` along ${ ( p . inline ? ? 0 ) . toFixed ( 1 ) } m / across ${ ( p . crossline ? ? 0 ) . toFixed ( 1 ) } m<br/>\ n ` ;
}
}
const style = { "max-width" : "50ex" } ;
return { html , style } ;
}
2022-03-29 17:41:28 +02:00
}
2020-08-08 23:59:13 +02:00
}
2025-07-28 10:11:23 +02:00
//this.layerSelection = [ "seq" ];
2022-03-29 17:41:28 +02:00
this . getSequenceData ( ) ;
2022-04-29 14:48:21 +02:00
2020-08-08 23:59:13 +02:00
}
2020-08-11 17:16:54 +02:00
2020-08-08 23:59:13 +02:00
}
< / script >