2020-08-08 23:59:13 +02:00
< template >
2025-08-03 11:57:59 +02:00
< div id = "map-container" >
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" >
2025-07-28 12:09:02 +02:00
< h3 > Background < / h3 >
2022-03-29 17:41:28 +02:00
< 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 >
2025-08-01 17:18:16 +02:00
< input id = "lyr-nau" type = "checkbox" value = "nau" v-model = "layerSelection" disabled / >
< label for = "lyr-nau" title = "Scan of Norway's nautical charts – CURRENTLY UNAVAILABLE" disabled > Nautical charts ( NO ) < / label >
2025-07-28 10:11:23 +02:00
< / form >
2025-07-28 12:09:02 +02:00
< hr class = "my-2" / >
< h3 > Layers < / h3 >
2025-07-28 10:11:23 +02:00
2025-07-28 12:09:02 +02:00
< div class = "lines-points" >
2025-07-28 10:11:23 +02:00
< 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 >
2025-08-01 17:18:16 +02:00
< label title = "Show points" disabled > < v-icon small left class = "mx-0" > mdi - vector - point < / v-icon > < input type = "checkbox" value = "pslp" v-model = "layerSelection" /> < / label >
2025-07-28 10:11:23 +02:00
< 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 >
2025-08-03 11:57:12 +02:00
< div >
< v-menu bottom offset -y class = "mr-1 pb-1" >
< template v -slot : activator = "{ on, attrs }" >
2025-08-03 13:47:48 +02:00
< v-icon small left class = "mx-0" v-bind = "attrs" v-on="on" :title="`Show deviations.\nCurrently selected mode: ${heatmapTitle}. Click to change`" > mdi -dots -grid < / v-icon >
2025-08-03 11:57:12 +02:00
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('total_error')" >
< v-list-item-content >
< v-list-item-title > Δ < span style = "text-decoration:overline;" > ij < / span > Total error < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('delta_i')" >
< v-list-item-content >
< v-list-item-title > Δi Inline error < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('delta_j')" >
< v-list-item-content >
< v-list-item-title > Δj Crossline error < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Gun data < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Pressure < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('press_μ')" >
< v-list-item-content >
< v-list-item-title > Mean ( μ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('press_σ ')" >
< v-list-item-content >
< v-list-item-title > Standard deviation ( σ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('press_R')" >
< v-list-item-content >
< v-list-item-title > Range ( R ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Depths < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('depth_μ')" >
< v-list-item-content >
< v-list-item-title > Mean ( μ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('depth_σ ')" >
< v-list-item-content >
< v-list-item-title > Standard deviation ( σ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('depth_R')" >
< v-list-item-content >
< v-list-item-title > Range ( R ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Deltas < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('delta_μ')" >
< v-list-item-content >
< v-list-item-title > Mean ( μ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('delta_σ ')" >
< v-list-item-content >
< v-list-item-title > Standard deviation ( σ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('delta_R')" >
< v-list-item-content >
< v-list-item-title > Range ( R ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Delay < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('delay_μ')" >
< v-list-item-content >
< v-list-item-title > Mean ( μ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('delay_σ ')" >
< v-list-item-content >
< v-list-item-title > Standard deviation ( σ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('delay_R')" >
< v-list-item-content >
< v-list-item-title > Range ( R ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Fill time < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('fill_μ')" >
< v-list-item-content >
< v-list-item-title > Mean ( μ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('fill_σ ')" >
< v-list-item-content >
< v-list-item-title > Standard deviation ( σ ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('fill_R')" >
< v-list-item-content >
< v-list-item-title > Range ( R ) < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
2025-08-03 13:47:07 +02:00
< v-list-item >
< v-list-item-content >
< v-menu bottom offset -y >
< template v -slot : activator = "{ on, attrs }" >
< v-list-item-title v-bind = "attrs" v-on="on" >
Misfires < v-icon small right > mdi - chevron - right < / v-icon >
< / v-list-item-title >
< / template >
< v-list nav dense >
< v-list-item @click ="setHeatmapValue('no_fire')" >
< v-list-item-content >
< v-list-item-title > No fire < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('autofire')" >
< v-list-item-content >
< v-list-item-title > Autofire < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< v-list-item @click ="setHeatmapValue('misfire')" >
< v-list-item-content >
< v-list-item-title > No fire / autofire < / v-list-item-title >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
2025-08-03 11:57:12 +02:00
< / v-list >
< / v-menu >
< / v-list-item-content >
< / v-list-item >
< / v-list >
< / v-menu >
< input type = "checkbox" value = "seqrh" v-model = "layerSelection" />
< / div >
<!--
2025-07-28 10:11:23 +02:00
< 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 >
2025-08-03 11:57:12 +02:00
-- >
2025-07-28 10:11:23 +02:00
< 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 12:09:02 +02:00
< / div >
2025-07-28 10:11:23 +02:00
<!--
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 >
<!-- 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 -- >
2025-07-28 12:06:56 +02:00
< hr class = "my-2" / >
< div title = "Not yet implemented" >
< v-btn
small
text
disabled
>
< v-icon small left > mdi - cog - outline < / v-icon >
Map settings
< / v-btn >
< / div >
2022-03-29 17:41:28 +02:00
< / 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 >
2025-08-03 11:57:59 +02:00
< div >
< v-icon
class = "my-1"
: title = "isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'"
@ click = "toggleFullscreen"
> { { isFullscreen ? 'mdi-fullscreen-exit' : 'mdi-fullscreen' } } < / v-icon >
< / div >
2022-03-29 17:41:28 +02:00
< / 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 .
2025-08-03 13:48:04 +02:00
* /
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
}
2025-08-03 13:48:04 +02:00
/ *
2022-03-29 17:41:28 +02:00
* END
* /
< / style >
< script >
2025-08-01 17:18:16 +02:00
import MapLayersMixin from './MapLayersMixin' ;
import MapTooltipsMixin from './MapTooltipsMixin' ;
2022-03-29 17:41:28 +02:00
import { mapActions , mapGetters , mapState } from 'vuex' ;
import zoomFitIcon from '@/assets/zoom-fit-best.svg'
2025-08-01 17:18:16 +02:00
import { Deck , FlyToInterpolator } from '@deck.gl/core' ;
import { IconLayer } from '@deck.gl/layers' ;
import { DougalBinaryBundle , DougalBinaryChunkSequential , DougalBinaryChunkInterleaved } from '@dougal/binary' ;
2022-03-29 17:41:28 +02:00
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" ,
2025-08-01 17:18:16 +02:00
mixins : [
MapLayersMixin ,
MapTooltipsMixin
] ,
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 : { } ,
sequenceDataElements : [ ] ,
sequenceDataTStamp : null ,
loadingProgress : null ,
viewState : { } ,
viewStateDefaults : {
//maxZoom: 18,
2025-08-02 16:00:54 +02:00
maxPitch : 89
2022-03-29 17:41:28 +02:00
} ,
2025-08-03 11:57:12 +02:00
heatmapValue : "total_error" ,
2025-08-03 11:57:59 +02:00
isFullscreen : false ,
2022-03-29 17:41:28 +02:00
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 ,
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
2025-08-01 17:18:16 +02:00
sequenceData ( ) {
return this . sequenceDataElements ? . map ( el => el . data ) ;
} ,
2025-08-02 16:00:54 +02:00
sequenceBinaryData ( ) {
const sequences = this . sequenceDataElements ? . sort ( ( a , b ) => a . sequence - b . sequence ) || [ ] ;
if ( ! sequences . length ) {
console . warn ( 'No sequence data available' ) ;
return { positions : new Float32Array ( 0 ) , values : [ ] , udv : 2 } ;
}
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
// Validate first sequence to get array sizes
let firstBundle ;
try {
firstBundle = DougalBinaryBundle . clone ( sequences [ 0 ] . data ) ;
if ( ! firstBundle . chunks || typeof firstBundle . chunks !== 'function' ) {
throw new Error ( 'Invalid DougalBinaryBundle: chunks method missing' ) ;
}
} catch ( e ) {
console . error ( 'Failed to process first sequence:' , e ) ;
return { positions : new Float32Array ( 0 ) , values : [ ] , udv : 2 } ;
}
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
const totalCount = sequences . reduce ( ( acc , { data } ) => {
try {
const bundle = DougalBinaryBundle . clone ( data ) ;
return acc + bundle . chunks ( ) . reduce ( ( sum , chunk ) => sum + chunk . jCount , 0 ) ;
} catch ( e ) {
console . warn ( 'Skipping invalid sequence data:' , e ) ;
return acc ;
}
} , 0 ) ;
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
if ( totalCount === 0 ) {
console . warn ( 'No valid points found in sequences' ) ;
return { positions : new Float32Array ( 0 ) , values : [ ] , udv : 2 } ;
}
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
const ΔelemCount = firstBundle . chunks ( ) [ 0 ] . ΔelemCount ;
const elemCount = firstBundle . chunks ( ) [ 0 ] . elemCount ;
const positions = new Float32Array ( totalCount * 2 ) ;
const values = new Array ( ΔelemCount + elemCount + 2 ) ;
for ( let k = 0 ; k < values . length ; k ++ ) {
values [ k ] = new ( k === 0 ? Uint16Array : k === 1 ? Uint32Array : k === 2 ? BigUint64Array : Float32Array ) ( totalCount ) ;
}
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
let offset = 0 ;
let udv = 2 ;
sequences . forEach ( ( { data , sequence } ) => {
try {
const bundle = DougalBinaryBundle . clone ( data ) ;
const chunks = bundle . chunks ( ) ;
if ( ! chunks . length ) {
console . warn ( ` No chunks in sequence ${ sequence } ` ) ;
return ;
2025-08-01 17:18:16 +02:00
}
2025-08-02 16:00:54 +02:00
udv = chunks [ 0 ] . udv ;
let chunkOffset = offset ;
for ( const chunk of chunks ) {
const λarray = chunk . elem ( 0 ) ;
const φarray = chunk . elem ( 1 ) ;
for ( let i = 0 ; i < λarray . length ; i ++ ) {
positions [ chunkOffset * 2 + i * 2 ] = λarray [ i ] ;
positions [ chunkOffset * 2 + i * 2 + 1 ] = φarray [ i ] ;
}
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
values [ 0 ] . set ( new Uint16Array ( chunk . jCount ) . fill ( chunk . i ) , chunkOffset ) ;
values [ 1 ] . set ( Uint32Array . from ( { length : chunk . jCount } , ( _ , i ) => chunk . j0 + i * chunk . Δj ) , chunkOffset ) ;
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
for ( let j = 0 ; j < chunk . ΔelemCount ; j ++ ) {
values [ 2 + j ] . set ( chunk . Δelem ( j ) , chunkOffset ) ;
}
for ( let j = 2 ; j < chunk . elemCount ; j ++ ) {
values [ 2 + chunk . ΔelemCount + j - 2 ] . set ( chunk . elem ( j ) , chunkOffset ) ;
}
2025-08-01 17:18:16 +02:00
2025-08-02 16:00:54 +02:00
chunkOffset += chunk . jCount ;
2025-08-01 17:18:16 +02:00
}
2025-08-02 16:00:54 +02:00
offset += chunkOffset - offset ;
} catch ( e ) {
console . warn ( ` Error processing sequence ${ sequence } : ` , e ) ;
2025-08-01 17:18:16 +02:00
}
} ) ;
2025-08-02 16:00:54 +02:00
if ( offset !== totalCount ) {
console . warn ( ` Offset mismatch: ${ offset } ≠ ${ totalCount } ` ) ;
}
2025-07-28 10:11:23 +02:00
2025-08-02 16:00:54 +02:00
console . log ( ` Concatenated ${ totalCount } points, ${ values . length } value arrays ` ) ;
return { positions , values , udv } ;
2025-07-28 10:11:23 +02:00
} ,
2025-08-03 13:47:48 +02:00
heatmapTitle ( ) {
let title = this . heatmapValue ;
switch ( this . heatmapValue ) {
case "total_error" :
title = "Total position error (raw data)" ;
break ;
case "delta_j" :
title = "Total crossline position error (raw data)" ;
break ;
case "delta_i" :
title = "Total inline position error (raw data)" ;
break ;
case "delta_μ" :
title = "Guns mean delta error" ;
break ;
case "delta_σ " :
title = "Guns delta standard deviation (1⋅σ )" ;
break ;
case "delta_R" :
title = "Guns delta range" ;
break ;
case "press_μ" :
title = "Fired array guns mean pressure" ;
break ;
case "press_σ " :
title = "Fired array pressure standard deviation (1⋅σ )" ;
break ;
case "press_R" :
title = "Fired array pressure range" ;
break ;
case "depth_μ" :
title = "Guns mean depth" ;
break ;
case "depth_σ " :
title = "Gun depth standard deviation (1⋅σ )" ;
break ;
case "depth_R" :
title = "Gun depth range" ;
break ;
case "fill_μ" :
title = "Guns mean fill time" ;
break ;
case "fill_σ " :
title = "Guns fill time standard deviation (1⋅σ )" ;
break ;
case "fill_R" :
title = "Guns fill time range" ;
break ;
case "delay_μ" :
title = "Guns mean firing delay" ;
break ;
case "delay_σ " :
title = "Guns firing delay standard deviation (1⋅σ )" ;
break ;
case "delay_R" :
title = "Guns firing delay range" ;
break ;
default :
}
return title ;
} ,
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
} ,
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
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 ] ;
2025-08-02 16:00:54 +02:00
if ( typeof fn != 'function' ) {
throw new Error ( ` Layer ${ name } : expected a function, got ${ typeof fn } ` ) ;
}
2022-03-29 17:41:28 +02:00
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 ( ) ;
}
} ,
2025-08-01 17:18:16 +02:00
zoomReset ( ) {
2025-07-28 10:11:23 +02:00
if ( deck ) {
2025-08-01 17:18:16 +02:00
const bounds = this . getBBox ( deck ) ;
2025-07-28 10:11:23 +02:00
if ( bounds ) {
2025-08-01 17:18:16 +02:00
const viewport = deck . getViewports ( ) [ 0 ] ;
const newViewState = {
... this . viewStateDefaults ,
... viewport . fitBounds ( bounds )
} ;
newViewState . transitionDuration = 500 ;
newViewState . transitionInterpolator = new FlyToInterpolator ( ) ;
deck . setProps ( { initialViewState : newViewState } ) ;
} else {
console . warn ( 'Unable to calculate bounding box' ) ;
2025-07-28 10:11:23 +02:00
}
} else {
2025-08-01 17:18:16 +02:00
console . warn ( 'Deck instance not available' ) ;
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-08-03 11:57:59 +02:00
toggleFullscreen ( ) {
const mapElement = document . getElementById ( 'map-container' ) ;
if ( ! this . isFullscreen ) {
if ( mapElement . requestFullscreen ) {
mapElement . requestFullscreen ( ) ;
} else if ( mapElement . webkitRequestFullscreen ) { // Safari
mapElement . webkitRequestFullscreen ( ) ;
} else if ( mapElement . msRequestFullscreen ) { // IE11
mapElement . msRequestFullscreen ( ) ;
}
this . isFullscreen = true ;
} else {
if ( document . exitFullscreen ) {
document . exitFullscreen ( ) ;
} else if ( document . webkitExitFullscreen ) {
document . webkitExitFullscreen ( ) ;
} else if ( document . msExitFullscreen ) {
document . msExitFullscreen ( ) ;
}
this . isFullscreen = false ;
}
} ,
2025-07-28 12:05:10 +02:00
async refresh ( ) {
const cache = await caches . open ( "dougal" ) ;
for ( const key of await cache . keys ( ) ) {
2025-08-03 13:48:51 +02:00
// Match only resolved requests
if ( key instanceof Request ) {
if ( key . url ? . match ( ` /project/ ${ this . $route . params . project } ` ) ) {
console . log ( "removing" , key . method , key . url ) ;
cache . delete ( key ) ;
}
}
2025-07-28 12:05:10 +02:00
}
await this . $root . sleep ( 300 ) ;
this . getSequenceData ( ) ;
} ,
2025-08-01 17:18:16 +02:00
getBBox ( deck , margin = 0.1 ) {
if ( ! deck || ! deck . layerManager ) {
console . warn ( 'Invalid deck instance or layer manager' ) ;
return null ;
}
let λ0 = + Infinity , φ0 = + Infinity , λ1 = - Infinity , φ1 = - Infinity ;
2022-04-29 14:48:21 +02:00
2025-08-01 17:18:16 +02:00
const nonTileLayerTypes = [ 'ScatterplotLayer' , 'GeoJsonLayer' , 'LineLayer' , 'IconLayer' , 'DougalSequenceLayer' ] ;
2025-07-28 10:11:23 +02:00
2025-08-01 17:18:16 +02:00
deck . layerManager . layers . forEach ( layer => {
// Skip tile-based layers
if ( [ 'TileLayer' , 'MVTLayer' ] . includes ( layer . constructor . layerName ) ) {
return ;
}
2025-07-28 10:11:23 +02:00
2025-08-01 17:18:16 +02:00
// Only process visible layers
if ( ! layer . props . visible || ! nonTileLayerTypes . includes ( layer . constructor . layerName ) ) {
return ;
2020-09-25 18:33:55 +02:00
}
2025-07-28 10:11:23 +02:00
2025-08-01 17:18:16 +02:00
let data = layer . props . data ;
2025-08-02 16:00:54 +02:00
// Handle DougalSequenceLayer
2025-08-01 17:18:16 +02:00
if ( layer . constructor . layerName === 'DougalSequenceLayer' ) {
2025-08-02 16:00:54 +02:00
const { positions } = data ;
if ( positions && positions . length ) {
2025-08-01 17:18:16 +02:00
for ( let i = 0 ; i < positions . length ; i += 2 ) {
const lon = positions [ i ] ;
const lat = positions [ i + 1 ] ;
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
2025-08-02 16:00:54 +02:00
}
}
2025-08-01 17:18:16 +02:00
} else if ( layer . constructor . layerName === 'ScatterplotLayer' ) {
// Direct point data (e.g., navp)
if ( Array . isArray ( data ) ) {
data . forEach ( d => {
const [ lon , lat ] = layer . props . getPosition ( d ) ;
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
} ) ;
} else if ( data && data . attributes && data . attributes . getPosition ) {
2025-08-02 16:00:54 +02:00
// Binary data
2025-08-01 17:18:16 +02:00
const positions = data . attributes . getPosition . value ;
for ( let i = 0 ; i < data . length * 2 ; i += 2 ) {
const lon = positions [ i ] ;
const lat = positions [ i + 1 ] ;
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
}
}
} else if ( layer . constructor . layerName === 'GeoJsonLayer' ) {
// Extract points from GeoJSON features
2025-08-02 16:00:54 +02:00
let featureCollections = Array . isArray ( data ) ? data : [ data ] ;
2025-08-01 17:18:16 +02:00
featureCollections . forEach ( item => {
let features = item ;
if ( item ? . type === 'FeatureCollection' ) {
features = item . features || [ ] ;
}
if ( Array . isArray ( features ) ) {
features . forEach ( feature => {
if ( feature . geometry ? . type === 'Point' ) {
const [ lon , lat ] = feature . geometry . coordinates ;
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
} else if ( feature . geometry ? . type === 'LineString' ) {
feature . geometry . coordinates . forEach ( ( [ lon , lat ] ) => {
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
} ) ;
} else if ( feature . geometry ? . type === 'Polygon' ) {
feature . geometry . coordinates [ 0 ] . forEach ( ( [ lon , lat ] ) => {
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
} ) ;
}
} ) ;
}
} ) ;
} else if ( layer . constructor . layerName === 'LineLayer' ) {
// Extract source and target positions
if ( Array . isArray ( data ) ) {
data . forEach ( d => {
const source = layer . props . getSourcePosition ( d ) ;
const target = layer . props . getTargetPosition ( d ) ;
if ( source && source [ 0 ] != null && source [ 1 ] != null && isFinite ( source [ 0 ] ) && isFinite ( source [ 1 ] ) ) {
λ0 = Math . min ( λ0 , source [ 0 ] ) ;
φ0 = Math . min ( φ0 , source [ 1 ] ) ;
λ1 = Math . max ( λ1 , source [ 0 ] ) ;
φ1 = Math . max ( φ1 , source [ 1 ] ) ;
}
if ( target && target [ 0 ] != null && target [ 1 ] != null && isFinite ( target [ 0 ] ) && isFinite ( target [ 1 ] ) ) {
λ0 = Math . min ( λ0 , target [ 0 ] ) ;
φ0 = Math . min ( φ0 , target [ 1 ] ) ;
λ1 = Math . max ( λ1 , target [ 0 ] ) ;
φ1 = Math . max ( φ1 , target [ 1 ] ) ;
}
} ) ;
}
} else if ( layer . constructor . layerName === 'IconLayer' ) {
// Single point (e.g., crosshairs)
if ( Array . isArray ( data ) ) {
data . forEach ( d => {
const [ lon , lat ] = layer . props . getPosition ( d ) ;
if ( lon != null && lat != null && isFinite ( lon ) && isFinite ( lat ) ) {
λ0 = Math . min ( λ0 , lon ) ;
φ0 = Math . min ( φ0 , lat ) ;
λ1 = Math . max ( λ1 , lon ) ;
φ1 = Math . max ( φ1 , lat ) ;
}
} ) ;
}
}
} ) ;
2025-07-28 10:11:23 +02:00
2025-08-01 17:18:16 +02:00
// Check if any valid points were found
if ( ! isFinite ( λ0 ) || ! isFinite ( φ0 ) || ! isFinite ( λ1 ) || ! isFinite ( φ1 ) ) {
console . warn ( 'No valid coordinates found for non-tile layers' ) ;
return null ;
2020-09-25 18:33:55 +02:00
}
2025-08-01 17:18:16 +02:00
// Apply margin
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
2025-08-01 17:18:16 +02:00
const types = [ "p" , "R" , "F" ] ;
2022-03-29 17:41:28 +02:00
const sequenceList = await this . api ( [ ` /project/ ${ this . $route . params . project } /sequence ` ] ) ;
2025-08-01 17:18:16 +02:00
// FIXME if sequenceList is undefined the API call failed. It should error and maybe retry
2022-03-29 17:41:28 +02:00
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 : {
2025-08-01 17:18:16 +02:00
//Accept: "application/vnd.aaltronav.dougal+octet-stream; format=1c"
Accept : "application/vnd.aaltronav.dougal+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.
2025-08-01 17:18:16 +02:00
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 ) {
2025-08-01 17:18:16 +02:00
try {
const url = ` /project/ ${ self . $route . params . project } /sequence/ ${ sequence } ?type=2 ` ;
const sequenceData = await download ( url ) ;
if ( sequenceData ) {
yield {
id : ` seq- ${ sequence } ` ,
sequence : sequence ,
data : sequenceData . data
} ;
2025-07-27 11:16:47 +02:00
}
2025-08-01 17:18:16 +02:00
} 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 ) {
2025-08-01 17:18:16 +02:00
//console.log("SHOULD BE INITIALISING LAYERS HERE", gl);
2022-03-29 17:41:28 +02:00
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-08-03 11:57:12 +02:00
setHeatmapValue ( v ) {
this . heatmapValue = v ;
console . log ( "Switched heatmap to" , v ) ;
this . render ( ) ;
} ,
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-08-01 17:18:16 +02:00
while ( ! Object . keys ( this . $store . state . label . labels ? ? { } ) . length ) {
console . log ( "Waiting for labels…" ) ;
await this . $root . sleep ( 1000 ) ;
console . log ( "LABELS" , this . $store . state . label . labels ) ;
}
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" ) ;
}
2025-08-01 17:18:16 +02:00
this . layersAvailable . osm = this . osmLayer ;
2022-04-29 14:48:21 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . sea = this . openSeaMapLayer ;
2022-03-29 17:41:28 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . navp = this . vesselTrackPointsLayer ;
2022-03-29 17:41:28 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . navl = this . vesselTrackLinesLayer ;
2022-03-29 17:41:28 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . log = this . eventsLogLayer ;
2022-04-29 14:48:21 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . psll = this . preplotSaillinesLinesLayer ;
2022-05-07 12:07:03 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . ppll = this . preplotLinesLayer ;
2020-08-08 23:59:13 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . planl = this . plannedLinesLinesLayer ;
2020-09-25 22:42:33 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . seqrl = this . rawSequencesLinesLayer ;
2025-07-28 10:11:23 +02:00
2025-08-01 17:18:16 +02:00
this . layersAvailable . seqfl = this . finalSequencesLinesLayer ;
2025-07-28 10:11:23 +02:00
2025-08-03 11:56:05 +02:00
this . layersAvailable . pslp = this . preplotSaillinesPointLayer ;
this . layersAvailable . pplp = this . preplotPointsLayer ;
2025-08-01 17:18:16 +02:00
this . layersAvailable . seqrp = this . rawSequencesPointsLayer ;
2020-09-25 22:42:33 +02:00
2025-08-02 16:00:54 +02:00
//this.layersAvailable.seqfp = this.rawSequencesPointsGunDataLayer;
2025-08-03 11:57:12 +02:00
this . layersAvailable . seqrh = this . heatmapLayer ;
2025-07-28 10:11:23 +02:00
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 : [ ] ,
2025-08-01 17:18:16 +02:00
getTooltip : this . getTooltip ,
2022-03-29 17:41:28 +02:00
pickingRadius : 24 ,
onWebGLInitialized : this . initLayers
2020-09-25 22:42:33 +02:00
} ) ;
2025-08-03 11:57:59 +02:00
// Get fullscreen state
document . addEventListener ( 'fullscreenchange' , ( ) => {
this . isFullscreen = ! ! document . fullscreenElement ;
} ) ;
document . addEventListener ( 'webkitfullscreenchange' , ( ) => {
this . isFullscreen = ! ! document . fullscreenElement ;
} ) ;
document . addEventListener ( 'msfullscreenchange' , ( ) => {
this . isFullscreen = ! ! document . fullscreenElement ;
} ) ;
2025-07-28 10:11:23 +02:00
//this.layerSelection = [ "seq" ];
2022-03-29 17:41:28 +02:00
this . getSequenceData ( ) ;
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 >