mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 11:07:08 +00:00
Merge branch '265-add-shotlog' into 'devel'
Resolve "Add shotlog" Closes #265 See merge request wgp/dougal/software!54
This commit is contained in:
14
lib/www/client/source/package-lock.json
generated
14
lib/www/client/source/package-lock.json
generated
@@ -18,7 +18,7 @@
|
|||||||
"leaflet-realtime": "^2.2.0",
|
"leaflet-realtime": "^2.2.0",
|
||||||
"leaflet.markercluster": "^1.4.1",
|
"leaflet.markercluster": "^1.4.1",
|
||||||
"marked": "^2.0.3",
|
"marked": "^2.0.3",
|
||||||
"plotly.js-dist": "^2.5.0",
|
"plotly.js-dist": "^2.27.0",
|
||||||
"suncalc": "^1.8.0",
|
"suncalc": "^1.8.0",
|
||||||
"typeface-roboto": "0.0.75",
|
"typeface-roboto": "0.0.75",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
@@ -10317,9 +10317,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/plotly.js-dist": {
|
"node_modules/plotly.js-dist": {
|
||||||
"version": "2.11.1",
|
"version": "2.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-2.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-2.27.0.tgz",
|
||||||
"integrity": "sha512-TubG71bBueWRMkQQMGlG8/0q753x5LLXzEHieUt9s0M/nD7WpnjC7sEwH+flhN6dOLqQ2wGlOb8Z4A3ah4IDWg=="
|
"integrity": "sha512-SuuIF6zpJpW+9ssgXghMdcaJ9mp06SATykpxBVSrX9PpX9YZpCg6oo/79WvjYUVHf33QreJ4csAJU3o5Ll32cQ=="
|
||||||
},
|
},
|
||||||
"node_modules/pnp-webpack-plugin": {
|
"node_modules/pnp-webpack-plugin": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
@@ -23254,9 +23254,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plotly.js-dist": {
|
"plotly.js-dist": {
|
||||||
"version": "2.11.1",
|
"version": "2.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-2.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-2.27.0.tgz",
|
||||||
"integrity": "sha512-TubG71bBueWRMkQQMGlG8/0q753x5LLXzEHieUt9s0M/nD7WpnjC7sEwH+flhN6dOLqQ2wGlOb8Z4A3ah4IDWg=="
|
"integrity": "sha512-SuuIF6zpJpW+9ssgXghMdcaJ9mp06SATykpxBVSrX9PpX9YZpCg6oo/79WvjYUVHf33QreJ4csAJU3o5Ll32cQ=="
|
||||||
},
|
},
|
||||||
"pnp-webpack-plugin": {
|
"pnp-webpack-plugin": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"leaflet-realtime": "^2.2.0",
|
"leaflet-realtime": "^2.2.0",
|
||||||
"leaflet.markercluster": "^1.4.1",
|
"leaflet.markercluster": "^1.4.1",
|
||||||
"marked": "^2.0.3",
|
"marked": "^2.0.3",
|
||||||
"plotly.js-dist": "^2.5.0",
|
"plotly.js-dist": "^2.27.0",
|
||||||
"suncalc": "^1.8.0",
|
"suncalc": "^1.8.0",
|
||||||
"typeface-roboto": "0.0.75",
|
"typeface-roboto": "0.0.75",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="graph"
|
||||||
|
class="graph-container"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.graph-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Plotly from 'plotly.js-dist';
|
||||||
|
import unpack from '@/lib/unpack.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DougalGraphProjectSequenceInlineCrossline",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
items: Array,
|
||||||
|
gunDataFormat: { type: String, default: "smsrc" },
|
||||||
|
facet: { type: String, default: "scatter" }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
plotted: false,
|
||||||
|
resizeObserver: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
config () {
|
||||||
|
switch (this.facet) {
|
||||||
|
case "scatter":
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
editable: false,
|
||||||
|
displayLogo: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
layout () {
|
||||||
|
const base = {
|
||||||
|
font: {
|
||||||
|
color: this.$vuetify.theme.isDark ? "#fff" : undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (this.facet) {
|
||||||
|
case "scatter":
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
autocolorscale: true,
|
||||||
|
title: {text: `Preplot deviation <span style="font-size:smaller;">(x̅: %{data[0].meta.avg_x} ±%{data[0].meta.std_x} m; y̅: %{data[0].meta.avg_y} ±%{data[0].meta.std_y} m)</span>`},
|
||||||
|
xaxis: {
|
||||||
|
title: "Crossline (m)"
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: "Inline (m)"
|
||||||
|
},
|
||||||
|
plot_bgcolor:"rgba(0,0,0,0)",
|
||||||
|
paper_bgcolor:"rgba(0,0,0,0)"
|
||||||
|
};
|
||||||
|
|
||||||
|
case "crossline":
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
autocolorscale: true,
|
||||||
|
title: {text: `Crossline deviation <span style="font-size:smaller;">(x̅: %{data[0].meta.avg_x} ±%{data[0].meta.std_x} m)</span>`},
|
||||||
|
xaxis: {
|
||||||
|
title: "Shotpoint"
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: "Crossline (m)"
|
||||||
|
},
|
||||||
|
plot_bgcolor:"rgba(0,0,0,0)",
|
||||||
|
paper_bgcolor:"rgba(0,0,0,0)"
|
||||||
|
};
|
||||||
|
|
||||||
|
case "2dhist":
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
showlegend: true,
|
||||||
|
title: {text: `Preplot deviation <span style="font-size:smaller;">(x̅: %{data[0].meta.avg_x} ±%{data[0].meta.std_x} m; y̅: %{data[0].meta.avg_y} ±%{data[0].meta.std_y} m)</span>`},
|
||||||
|
xaxis: {
|
||||||
|
title: "Crossline (m)",
|
||||||
|
showgrid: true,
|
||||||
|
zeroline: true
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: "Inline (m)",
|
||||||
|
showgrid: true,
|
||||||
|
zeroline: true
|
||||||
|
},
|
||||||
|
plot_bgcolor:"rgba(0,0,0,0)",
|
||||||
|
paper_bgcolor:"rgba(0,0,0,0)"
|
||||||
|
};
|
||||||
|
|
||||||
|
case "c-o":
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
showlegend: true,
|
||||||
|
title: {text: `Final vs raw <span style="font-size:smaller;">(x̅: %{data[0].meta.avg_x} ±%{data[0].meta.std_x} m; y̅: %{data[0].meta.avg_y} ±%{data[0].meta.std_y} m)</span>`},
|
||||||
|
xaxis: {
|
||||||
|
title: "Crossline (m)",
|
||||||
|
showgrid: true,
|
||||||
|
zeroline: true
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: "Inline (m)",
|
||||||
|
showgrid: true,
|
||||||
|
zeroline: true
|
||||||
|
},
|
||||||
|
plot_bgcolor:"rgba(0,0,0,0)",
|
||||||
|
paper_bgcolor:"rgba(0,0,0,0)"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
if (!this.items?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let x, y, avg_x, avg_y, std_x, std_y;
|
||||||
|
|
||||||
|
const items = this.items.sort( (a, b) => a.point - b.point );
|
||||||
|
const meta = unpack(items, "meta");
|
||||||
|
const src_number = unpack(unpack(unpack(meta, "raw"), this.gunDataFormat), "src_number");
|
||||||
|
|
||||||
|
if (this.facet == "c-o") {
|
||||||
|
const _items = items.filter(i => i.errorfinal && i.errorraw);
|
||||||
|
const εf = unpack(unpack(_items, "errorfinal"), "coordinates");
|
||||||
|
const εr = unpack(unpack(_items, "errorraw"), "coordinates");
|
||||||
|
|
||||||
|
x = εf.map( (f, idx) => f[0] - εr[idx][0] )
|
||||||
|
y = εf.map( (f, idx) => f[1] - εr[idx][1] )
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const coords = unpack(unpack(items, ((row) => row?.errorfinal ? row.errorfinal : row.errorraw)), "coordinates");
|
||||||
|
|
||||||
|
x = unpack(coords, 0);
|
||||||
|
y = unpack(coords, 1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// No chance of overflow
|
||||||
|
avg_x = (x.reduce((acc, cur) => acc + cur, 0) / x.length).toFixed(2);
|
||||||
|
avg_y = (y.reduce((acc, cur) => acc + cur, 0) / y.length).toFixed(2);
|
||||||
|
std_x = Math.sqrt(x.reduce((acc, cur) => (cur-avg_x)**2 + acc, 0) / x.length).toFixed(2);
|
||||||
|
std_y = Math.sqrt(y.reduce((acc, cur) => (cur-avg_y)**2 + acc, 0) / y.length).toFixed(2);
|
||||||
|
|
||||||
|
if (this.facet == "scatter") {
|
||||||
|
|
||||||
|
const data = [{
|
||||||
|
type: "scatter",
|
||||||
|
mode: "markers",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
meta: { avg_x, avg_y, std_x, std_y},
|
||||||
|
transforms: [{
|
||||||
|
type: "groupby",
|
||||||
|
groups: src_number,
|
||||||
|
styles: [
|
||||||
|
{target: 1, value: {line: {color: "green"}}},
|
||||||
|
{target: 2, value: {line: {color: "red"}}},
|
||||||
|
{target: 3, value: {line: {color: "blue"}}}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
}];
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} else if (this.facet == "crossline") {
|
||||||
|
|
||||||
|
const s = unpack(items, "point");
|
||||||
|
|
||||||
|
const data = [{
|
||||||
|
type: "scatter",
|
||||||
|
x: s,
|
||||||
|
y: x,
|
||||||
|
meta: { avg_x, avg_y, std_x, std_y},
|
||||||
|
_transforms: [{
|
||||||
|
type: "groupby",
|
||||||
|
groups: src_number,
|
||||||
|
styles: [
|
||||||
|
{target: 1, value: {line: {color: "green"}}},
|
||||||
|
{target: 2, value: {line: {color: "red"}}},
|
||||||
|
{target: 3, value: {line: {color: "blue"}}}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
}];
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
} else if (this.facet == "2dhist" || this.facet == "c-o") {
|
||||||
|
|
||||||
|
const bottomValue = this.$vuetify.theme.isDark
|
||||||
|
? ['0.0', 'rgba(0,0,0,0)']
|
||||||
|
: ['0.0', 'rgb(165,0,38)'];
|
||||||
|
const topValue = this.$vuetify.theme.isDark
|
||||||
|
? ['1.0', 'rgb(49,54,149)']
|
||||||
|
: ['1.0', 'rgba(0,0,0,0)'];
|
||||||
|
|
||||||
|
const colourscale = this.facet == "c-o"
|
||||||
|
? [bottomValue, [0.1, 'rgb(0,0,0)'], [0.9, 'rgb(255,255,255)'], topValue]
|
||||||
|
: [
|
||||||
|
bottomValue,
|
||||||
|
['0.111111111111', 'rgb(215,48,39)'],
|
||||||
|
['0.222222222222', 'rgb(244,109,67)'],
|
||||||
|
['0.333333333333', 'rgb(253,174,97)'],
|
||||||
|
['0.444444444444', 'rgb(254,224,144)'],
|
||||||
|
['0.555555555556', 'rgb(224,243,248)'],
|
||||||
|
['0.666666666667', 'rgb(171,217,233)'],
|
||||||
|
['0.777777777778', 'rgb(116,173,209)'],
|
||||||
|
['0.888888888889', 'rgb(69,117,180)'],
|
||||||
|
topValue
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = [{
|
||||||
|
type: "histogram2dcontour",
|
||||||
|
ncontours: 20,
|
||||||
|
colorscale: colourscale,
|
||||||
|
showscale: false,
|
||||||
|
reversescale: !this.$vuetify.theme.isDark,
|
||||||
|
contours: {
|
||||||
|
coloring: this.facet == "c-o" ? "fill" : "heatmap",
|
||||||
|
},
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
meta: { avg_x, avg_y, std_x, std_y}
|
||||||
|
}];
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
items (cur, prev) {
|
||||||
|
if (cur != prev) {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"$vuetify.theme.isDark" () {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
plot () {
|
||||||
|
Plotly.newPlot(this.$refs.graph, this.data, this.layout, this.config);
|
||||||
|
this.plotted = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
replot () {
|
||||||
|
if (this.plotted) {
|
||||||
|
const ref = this.$refs.graph;
|
||||||
|
Plotly.relayout(ref, {
|
||||||
|
width: ref.clientWidth,
|
||||||
|
height: ref.clientHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.resizeObserver = new ResizeObserver(this.replot)
|
||||||
|
this.resizeObserver.observe(this.$refs.graph);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy () {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.unobserve(this.$refs.graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="graph"
|
||||||
|
class="graph-container"
|
||||||
|
></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.graph-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Plotly from 'plotly.js-dist';
|
||||||
|
import unpack from '@/lib/unpack.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DougalGraphProjectSequenceShotpointTiming",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
items: Array,
|
||||||
|
gunDataFormat: { type: String, default: "smsrc" },
|
||||||
|
facet: { type: String, default: "bars" }
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
plotted: false,
|
||||||
|
resizeObserver: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
config () {
|
||||||
|
return {
|
||||||
|
editable: false,
|
||||||
|
displayLogo: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
layout () {
|
||||||
|
return {
|
||||||
|
font: {
|
||||||
|
color: this.$vuetify.theme.isDark ? "#fff" : undefined
|
||||||
|
},
|
||||||
|
title: {text: "Shotpoint timing %{data[0].meta.subtitle}"},
|
||||||
|
xaxis: {
|
||||||
|
title: "Shotpoint"
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
title: "Time (s)"
|
||||||
|
},
|
||||||
|
plot_bgcolor:"rgba(0,0,0,0)",
|
||||||
|
paper_bgcolor:"rgba(0,0,0,0)"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
|
||||||
|
const items = this.items.map(i => {
|
||||||
|
return {
|
||||||
|
point: i.point,
|
||||||
|
tstamp: new Date(i.tstamp)
|
||||||
|
}
|
||||||
|
}).sort( (a, b) => a.tstamp - b.tstamp );
|
||||||
|
const x = [...unpack(items, "point")];
|
||||||
|
const y = items.map( (i, idx, ary) => (ary[idx+1]?.tstamp - i.tstamp)/1000 );
|
||||||
|
const src_number = unpack(this.items, ["meta", "raw", this.gunDataFormat, "src_number"]);
|
||||||
|
|
||||||
|
// We're dealing with intervals not points
|
||||||
|
x.pop(); y.pop(); src_number.pop();
|
||||||
|
|
||||||
|
const meta = {};
|
||||||
|
|
||||||
|
const stats = this.stats(x, y, src_number);
|
||||||
|
|
||||||
|
// We need to do the subtitle here rather than in layout as layout knows nothing
|
||||||
|
// about the number of arrays
|
||||||
|
|
||||||
|
if (stats.src_ids.length == 1) {
|
||||||
|
meta.subtitle = `<span style="font-size:smaller;">(μ = ${stats.avg.all.toFixed(2)} ±${stats.std.all.toFixed(2)} s)</span>`;
|
||||||
|
} else {
|
||||||
|
meta.subtitle = `<span style="font-size:smaller;">(μ = ${stats.avg.all.toFixed(2)} ±${stats.std.all.toFixed(2)} s)</span>`;
|
||||||
|
const per_source = [];
|
||||||
|
for (const key in stats.avg) {
|
||||||
|
if (key == "all") continue;
|
||||||
|
const s = `μ<sub>${key}</sub> = ${stats.avg[key].toFixed(2)} ±${stats.std[key].toFixed(2)} s`;
|
||||||
|
per_source.push(s);
|
||||||
|
}
|
||||||
|
meta.subtitle += `<br><span style="font-size:smaller;">` + per_source.join("; ") + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const trace0 = {
|
||||||
|
type: "bar",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
transforms: [{
|
||||||
|
type: "groupby",
|
||||||
|
groups: src_number,
|
||||||
|
styles: [
|
||||||
|
{target: 1, value: {line: {color: "green"}}},
|
||||||
|
{target: 2, value: {line: {color: "red"}}},
|
||||||
|
{target: 3, value: {line: {color: "blue"}}}
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
meta
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (this.facet) {
|
||||||
|
case "lines":
|
||||||
|
trace0.type = "scatter";
|
||||||
|
break;
|
||||||
|
case "area":
|
||||||
|
trace0.type = "scatter";
|
||||||
|
trace0.fill = "tozeroy";
|
||||||
|
break;
|
||||||
|
case "bars":
|
||||||
|
default:
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
return [trace0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
items (cur, prev) {
|
||||||
|
if (cur != prev) {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"$vuetify.theme.isDark" () {
|
||||||
|
this.plot();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
plot () {
|
||||||
|
Plotly.newPlot(this.$refs.graph, this.data, this.layout, this.config);
|
||||||
|
this.plotted = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
replot () {
|
||||||
|
if (this.plotted) {
|
||||||
|
const ref = this.$refs.graph;
|
||||||
|
Plotly.relayout(ref, {
|
||||||
|
width: ref.clientWidth,
|
||||||
|
height: ref.clientHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stats (x, y, src_number) {
|
||||||
|
const avg = {};
|
||||||
|
const std = {};
|
||||||
|
|
||||||
|
const avg_all = (y.reduce((acc, cur) => acc + cur, 0) / y.length);
|
||||||
|
const std_all = Math.sqrt(y.reduce((acc, cur) => (cur-avg_all)**2 + acc, 0) / y.length);
|
||||||
|
|
||||||
|
avg.all = avg_all;
|
||||||
|
std.all = std_all;
|
||||||
|
|
||||||
|
const src_ids = new Set(src_number);
|
||||||
|
|
||||||
|
for (const src of src_ids) {
|
||||||
|
const v = y.filter((i, idx) => src_number[idx] == src);
|
||||||
|
const μ = (v.reduce((acc, cur) => acc + cur, 0) / v.length);
|
||||||
|
const σ = Math.sqrt(v.reduce((acc, cur) => (cur-μ)**2 + acc, 0) / v.length);
|
||||||
|
avg[src] = μ;
|
||||||
|
std[src] = σ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { avg, std, src_ids };
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.resizeObserver = new ResizeObserver(this.replot)
|
||||||
|
this.resizeObserver.observe(this.$refs.graph);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy () {
|
||||||
|
if (this.resizeObserver) {
|
||||||
|
this.resizeObserver.unobserve(this.$refs.graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -1,4 +1,33 @@
|
|||||||
|
/** Unpacks attributes from array items.
|
||||||
|
*
|
||||||
|
* At it simplest, given an array of objects,
|
||||||
|
* the call unpack(rows, "x") returns an array
|
||||||
|
* of the "x" attribute of every item in rows.
|
||||||
|
*
|
||||||
|
* `key` may also be:
|
||||||
|
*
|
||||||
|
* - a function with the signature
|
||||||
|
* (Object) => any
|
||||||
|
* the result of applying the function to
|
||||||
|
* the object will be used as the unpacked
|
||||||
|
* value.
|
||||||
|
*
|
||||||
|
* - an array of strings, functions or other
|
||||||
|
* arrays. In this case, it does a recursive
|
||||||
|
* fold operation. NOTE: it mutates `key`.
|
||||||
|
*
|
||||||
|
*/
|
||||||
export default function unpack(rows, key) {
|
export default function unpack(rows, key) {
|
||||||
return rows && rows.map( row => row[key] );
|
if (typeof key === "function") {
|
||||||
|
return rows && rows.map( row => key(row) );
|
||||||
|
} else if (Array.isArray(key)) {
|
||||||
|
const car = key.shift();
|
||||||
|
if (key.length) {
|
||||||
|
return unpack(unpack(rows, car), key);
|
||||||
|
} else {
|
||||||
|
return unpack(rows, car);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return rows && rows.map( row => row?.[key] );
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,6 +33,17 @@
|
|||||||
</span>
|
</span>
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
|
|
||||||
|
<a v-if="$route.params.sequence"
|
||||||
|
class="mr-3"
|
||||||
|
:href="`/projects/${$route.params.project}/sequences/${$route.params.sequence}`"
|
||||||
|
title="View the shotlog for this sequence"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
right
|
||||||
|
color="teal"
|
||||||
|
>mdi-format-list-numbered</v-icon>
|
||||||
|
</a>
|
||||||
|
|
||||||
<dougal-event-edit v-if="writeaccess"
|
<dougal-event-edit v-if="writeaccess"
|
||||||
v-model="eventDialog"
|
v-model="eventDialog"
|
||||||
v-bind="editedEvent"
|
v-bind="editedEvent"
|
||||||
|
|||||||
@@ -332,9 +332,31 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:item.sequence="{value}">
|
<template v-slot:item.sequence="{value}">
|
||||||
|
<div style="white-space:nowrap;">
|
||||||
|
{{value}}
|
||||||
|
|
||||||
<a
|
<a
|
||||||
:href="`/projects/${$route.params.project}/log/sequence/${value}`"
|
:href="`/projects/${$route.params.project}/log/sequence/${value}`"
|
||||||
title="View the event log for this sequence">{{value}}</a>
|
title="View the event log for this sequence"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
small
|
||||||
|
right
|
||||||
|
color="blue"
|
||||||
|
>mdi-format-list-bulleted-type</v-icon>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="`/projects/${$route.params.project}/sequences/${value}`"
|
||||||
|
title="View the shotlog for this sequence"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
small
|
||||||
|
right
|
||||||
|
color="teal"
|
||||||
|
>mdi-format-list-numbered</v-icon>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:item.line="{value}">
|
<template v-slot:item.line="{value}">
|
||||||
|
|||||||
@@ -1,13 +1,631 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<v-container fluid>
|
||||||
SequenceSummary
|
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<v-progress-linear indeterminate v-if="loading"></v-progress-linear>
|
||||||
|
<v-toolbar flat>
|
||||||
|
<v-toolbar-title>
|
||||||
|
Sequence {{sequenceNumber}}
|
||||||
|
<small :class="statusColour" v-if="sequence">({{sequence.status}})</small>
|
||||||
|
</v-toolbar-title>
|
||||||
|
|
||||||
|
<a v-if="$route.params.sequence"
|
||||||
|
:href="`/projects/${$route.params.project}/log/sequence/${$route.params.sequence}`"
|
||||||
|
title="View the event log for this sequence"
|
||||||
|
>
|
||||||
|
<v-icon
|
||||||
|
right
|
||||||
|
color="blue"
|
||||||
|
>mdi-format-list-bulleted-type</v-icon>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-text-field
|
||||||
|
v-model="filter"
|
||||||
|
append-icon="mdi-magnify"
|
||||||
|
label="Filter shots"
|
||||||
|
single-line
|
||||||
|
clearable
|
||||||
|
hint="Filter by point, line, time, etc."
|
||||||
|
></v-text-field>
|
||||||
|
</v-toolbar>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
|
||||||
|
<v-container fluid>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6" class="pa-0">
|
||||||
|
<v-card outlined tile height="100%" class="flex-grow-1">
|
||||||
|
<v-card-subtitle>
|
||||||
|
Acquisition remarks
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text v-html="$options.filters.markdown(remarks)">
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6" class="pa-0">
|
||||||
|
<v-card outlined tile height="100%" class="flex-grow-1">
|
||||||
|
<v-card-subtitle>
|
||||||
|
Processing remarks
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text v-html="$options.filters.markdown(remarks_final)">
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<template v-if="sequence">
|
||||||
|
<v-row>
|
||||||
|
<v-col v-for="(col, idx) in infoColumns" :key="idx" cols="3">
|
||||||
|
<b :title="col.title">{{col.text}}:</b> {{ col.value }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols=12 md=6 lg=4>
|
||||||
|
<div style="height:300px;">
|
||||||
|
<dougal-graph-project-sequence-inline-crossline
|
||||||
|
:items="shots"
|
||||||
|
gun-data-format="smsrc"
|
||||||
|
facet="2dhist"
|
||||||
|
>
|
||||||
|
</dougal-graph-project-sequence-inline-crossline>
|
||||||
</div>
|
</div>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols=12 md=6 lg=4>
|
||||||
|
<div style="height:300px;">
|
||||||
|
<dougal-graph-project-sequence-inline-crossline
|
||||||
|
:items="shots"
|
||||||
|
gun-data-format="smsrc"
|
||||||
|
facet="crossline"
|
||||||
|
>
|
||||||
|
</dougal-graph-project-sequence-inline-crossline>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols=12 md=6 lg=4>
|
||||||
|
<div ref="" style="height:300px;">
|
||||||
|
<dougal-graph-project-sequence-shotpoint-timing
|
||||||
|
:items="shots"
|
||||||
|
facet="area"
|
||||||
|
>
|
||||||
|
</dougal-graph-project-sequence-shotpoint-timing>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols=12 md=6 lg=4 >
|
||||||
|
<div ref="" style="height:300px;">
|
||||||
|
<dougal-graph-project-sequence-inline-crossline
|
||||||
|
:items="shots"
|
||||||
|
facet="c-o"
|
||||||
|
>
|
||||||
|
</dougal-graph-project-sequence-inline-crossline>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
</v-container>
|
||||||
|
|
||||||
|
<v-card outlined tile class="flex-grow-1">
|
||||||
|
<v-card-subtitle>
|
||||||
|
Shot log
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
|
||||||
|
<v-data-table
|
||||||
|
:headers="shotlogHeaders"
|
||||||
|
:items="shots"
|
||||||
|
item-key="point"
|
||||||
|
:search="filter"
|
||||||
|
:custom-sort="customSorter"
|
||||||
|
fixed-header
|
||||||
|
show-expand
|
||||||
|
@click:row="onRowClicked"
|
||||||
|
>
|
||||||
|
|
||||||
|
<template v-slot:item.position="{item}">
|
||||||
|
<a v-if="position(item).latitude"
|
||||||
|
:href="`/projects/${$route.params.project}/map#15/${position(item).longitude}/${position(item).latitude}`"
|
||||||
|
title="View on map"
|
||||||
|
:target="`/projects/${$route.params.project}/map`"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<small>{{ format_position(position(item)) }}</small>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.ε_inline="{item}">
|
||||||
|
{{ ε(item).inline }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.ε_crossline="{item}">
|
||||||
|
{{ ε(item).crossline }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.co_inline="{item}">
|
||||||
|
{{ co(item).inline }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:item.co_crossline="{item}">
|
||||||
|
{{ co(item).crossline }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:expanded-item="{ headers, item }">
|
||||||
|
<td :colspan="headers.length" class="pa-0">
|
||||||
|
<v-card flat>
|
||||||
|
<v-card-subtitle>Gun data</v-card-subtitle>
|
||||||
|
<v-card-text v-if="item.meta.raw.smsrc">
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
Source fired: {{ gun_data(item).src_number }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Subarrays: {{ gun_data(item).num_subarray }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Total guns: {{ gun_data(item).num_guns }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Active guns: {{ gun_data(item).num_active }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Autofires: {{ gun_data(item).num_auto }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
No fires: {{ gun_data(item).num_nofire }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
Average delta: {{ gun_data(item).avg_delta }} ±{{ gun_data(item).std_delta }} ms
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Delta errors: {{ gun_data(item).num_delta }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Source volume: {{ gun_data(item).volume }} in³
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Manifold pressure: {{ gun_data(item).manifold }} psi
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Mask: {{ gun_data(item).mask }}
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
Trigger mode: {{ gun_data(item).trg_mode }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols=12>
|
||||||
|
<v-expansion-panels>
|
||||||
|
<v-expansion-panel>
|
||||||
|
<v-expansion-panel-header>
|
||||||
|
Individual gun data
|
||||||
|
</v-expansion-panel-header>
|
||||||
|
<v-expansion-panel-content>
|
||||||
|
<v-simple-table>
|
||||||
|
<template v-slot:default>
|
||||||
|
<thead>
|
||||||
|
<th>String</th>
|
||||||
|
<th>Gun</th>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Mode</th>
|
||||||
|
<th>Detect</th>
|
||||||
|
<th>Autofire</th>
|
||||||
|
<th>Aimpoint</th>
|
||||||
|
<th>Fire time</th>
|
||||||
|
<th>Delay</th>
|
||||||
|
<th>Depth</th>
|
||||||
|
<th>Pressure</th>
|
||||||
|
<th>Volume</th>
|
||||||
|
<th>Filltime</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(gun_values, idx_gun) in gun_data(item).guns"
|
||||||
|
:key="idx_gun"
|
||||||
|
>
|
||||||
|
<td v-for="(val, idx) in gun_values" v-if="idx != 6"
|
||||||
|
:key="idx"
|
||||||
|
:class="typeof val === 'number' ? 'text-right' : 'text-left'"
|
||||||
|
>{{ gun_value_formatter(val, idx) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</template>
|
||||||
|
</v-simple-table>
|
||||||
|
</v-expansion-panel-content>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-else>
|
||||||
|
No gun data available {{ item }}
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
</v-data-table>
|
||||||
|
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
import DougalGraphProjectSequenceInlineCrossline from '@/components/graphs/project/sequence/inline-crossline';
|
||||||
|
import DougalGraphProjectSequenceShotpointTiming from '@/components/graphs/project/sequence/shotpoint-timing';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SequenceSummary"
|
name: "SequenceSummary",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DougalGraphProjectSequenceInlineCrossline,
|
||||||
|
DougalGraphProjectSequenceShotpointTiming
|
||||||
|
},
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
filter: null,
|
||||||
|
loading: false,
|
||||||
|
shotlogHeaders: [
|
||||||
|
{
|
||||||
|
value: "point",
|
||||||
|
text: "Point",
|
||||||
|
sort: this.sorterFor("point", this.number_sort)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "sailline",
|
||||||
|
text: "Sail line",
|
||||||
|
sort: this.sorterFor("sailline", this.number_sort)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "line",
|
||||||
|
text: "Source line",
|
||||||
|
sort: this.sorterFor("line", this.number_sort)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "tstamp",
|
||||||
|
text: "Timestamp",
|
||||||
|
sort: this.sorterFor("tstamp", this.string_sort)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "position",
|
||||||
|
text: "φ, λ",
|
||||||
|
sort: this.sorterFor(["geometryfinal", "geometryraw", "geometrypreplot"],
|
||||||
|
this.sorterFor("coordinates",
|
||||||
|
this.sequentialSort([0, 1], this.number_sort)))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ε_inline",
|
||||||
|
text: "ε inline",
|
||||||
|
align: "end",
|
||||||
|
sort: this.sorterFor(["errorfinal", "errorraw"],
|
||||||
|
this.sorterFor("coordinates",
|
||||||
|
this.sorterFor(1, this.number_sort)))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "ε_crossline",
|
||||||
|
text: "ε crossline",
|
||||||
|
align: "end",
|
||||||
|
sort: this.sorterFor(["errorfinal", "errorraw"],
|
||||||
|
this.sorterFor("coordinates",
|
||||||
|
this.sorterFor(0, this.number_sort)))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "co_inline",
|
||||||
|
text: "c-o inline",
|
||||||
|
align: "end",
|
||||||
|
sort: (a, b) => this.number_sort(this.co(a).inline, this.co(b).inline)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "co_crossline",
|
||||||
|
text: "c-o crossline",
|
||||||
|
align: "end",
|
||||||
|
sort: (a, b) => this.number_sort(this.co(a).crossline, this.co(b).crossline)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'data-table-expand'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
shots: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sequenceNumber () {
|
||||||
|
return this.$route.params.sequence;
|
||||||
|
},
|
||||||
|
|
||||||
|
sequence () {
|
||||||
|
return this.sequences.find(i => i.sequence == this.sequenceNumber);
|
||||||
|
},
|
||||||
|
|
||||||
|
remarks () {
|
||||||
|
return this.sequence?.remarks || "Nil.";
|
||||||
|
},
|
||||||
|
|
||||||
|
remarks_final () {
|
||||||
|
return this.sequence?.remarks_final || "Nil.";
|
||||||
|
},
|
||||||
|
|
||||||
|
statusColour () {
|
||||||
|
switch (this.sequence?.status) {
|
||||||
|
case "final":
|
||||||
|
return "green--text";
|
||||||
|
case "raw":
|
||||||
|
return "orange--text";
|
||||||
|
case "ntbp":
|
||||||
|
return "red--text";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
infoColumns () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: "FSP",
|
||||||
|
title: "First shotpoint",
|
||||||
|
value: this.sequence.fsp ?? "n/a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "FPSP",
|
||||||
|
title: "First prime shotpoint",
|
||||||
|
value: this.sequence.fsp_final ?? "n/a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "LPSP",
|
||||||
|
title: "Last prime shotpoint",
|
||||||
|
value: this.sequence.lsp_final ?? "n/a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "LSP",
|
||||||
|
title: "Last shotpoint",
|
||||||
|
value: this.sequence.lsp ?? "n/a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Start time",
|
||||||
|
value: this.sequence.ts0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "FPSP time",
|
||||||
|
title: "First prime shotpoint time",
|
||||||
|
value: this.sequence.ts0_final ?? "n/a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "LPSP time",
|
||||||
|
title: "Last prime shotpoint time",
|
||||||
|
value: this.sequence.ts1_final ?? "n/a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "End time",
|
||||||
|
value: this.sequence.ts1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Length",
|
||||||
|
value: this.sequence.length.toFixed(0)+" m"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Azimuth",
|
||||||
|
value: this.sequence.azimuth.toFixed(4).padStart(8, "0")+"°"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Shots acquired",
|
||||||
|
value: this.sequence.num_points
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Shots missed",
|
||||||
|
value: this.sequence.missing_shots
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Total duration",
|
||||||
|
value: this.format_duration(this.sequence.duration)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Prime duration",
|
||||||
|
value: this.format_duration(this.sequence.duration_final) ?? "n/a"
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
...mapGetters(["sequencesLoading", "sequences"])
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
|
||||||
|
sequencesLoading () {
|
||||||
|
this.loading == this.loading || this.sequencesLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
/** Apply fn to a[keys], b[keys]
|
||||||
|
*
|
||||||
|
* If keys is an array, use the first
|
||||||
|
* attribute that exists in each of a
|
||||||
|
* and b (may not be the same attribute)
|
||||||
|
*/
|
||||||
|
sorterFor (keys, fn) {
|
||||||
|
if (Array.isArray(keys)) {
|
||||||
|
return (a, b) => fn(a[keys.find(k => k in a)], b[keys.find(k => k in b)]);
|
||||||
|
} else {
|
||||||
|
return (a, b) => fn(a[keys], b[keys]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Apply fn to each member of keys
|
||||||
|
* until the comparison returns non-zero.
|
||||||
|
*/
|
||||||
|
sequentialSort (keys, fn) {
|
||||||
|
return (a, b) => {
|
||||||
|
for (const key of keys) {
|
||||||
|
const res = fn(a[key], b[key]);
|
||||||
|
if (res != 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
number_sort (a, b) {
|
||||||
|
return a - b;
|
||||||
|
},
|
||||||
|
|
||||||
|
string_sort (a, b) {
|
||||||
|
return a > b
|
||||||
|
? 1
|
||||||
|
: a < b
|
||||||
|
? -1
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
customSorter (items, sortBy, sortDesc) {
|
||||||
|
|
||||||
|
/** Get the sorter for a given column from shotlogHeaders
|
||||||
|
*/
|
||||||
|
const getFieldSorter = (key) => {
|
||||||
|
return this.shotlogHeaders.find(i => i.value == key)?.sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Conditionally invert a comparator
|
||||||
|
*/
|
||||||
|
function inverter (fn, desc) {
|
||||||
|
return desc
|
||||||
|
? (a, b) => fn(a, b) * -1
|
||||||
|
: fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const idx in sortBy) {
|
||||||
|
const sortKey = sortBy[idx];
|
||||||
|
const desc = sortDesc[idx];
|
||||||
|
|
||||||
|
const fn = inverter(getFieldSorter(sortKey), desc);
|
||||||
|
|
||||||
|
items.sort(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
onRowClicked (data, row) {
|
||||||
|
row.expand(!row.isExpanded);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSequence (seq = this.sequence) {
|
||||||
|
|
||||||
|
// NOTE:
|
||||||
|
// We are intentionally not using Vuex here, given that at this time
|
||||||
|
// no other component (except Graphs?) this endpoint's data.
|
||||||
|
// NB: Graphs does use this endpoint but there doesn't seem to be
|
||||||
|
// any point in keeping the data in memory anyway in case the user
|
||||||
|
// decides to navigate from the shotlog to the graphs or vice-versa.
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
const url = `/project/${this.$route.params.project}/sequence/${this.$route.params.sequence}`;
|
||||||
|
this.shots = await this.api([url])
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
format_duration (duration) {
|
||||||
|
if (duration) {
|
||||||
|
let t = ["hours", "minutes", "seconds"].map(k =>
|
||||||
|
(duration[k] ?? 0).toFixed(0).padStart(2, "0")).join(":");
|
||||||
|
|
||||||
|
if (duration.days) {
|
||||||
|
t = duration.days+" d "+t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
format_position (position) {
|
||||||
|
return `${position.latitude.toFixed(6).padStart(9, "0")}, ${position.longitude.toFixed(6).padStart(10, "0")}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
position (item) {
|
||||||
|
const coords = (item.geometryfinal ?? item.geometryraw)?.coordinates;
|
||||||
|
if (coords) {
|
||||||
|
return {
|
||||||
|
longitude: coords[0],
|
||||||
|
latitude: coords[1]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
ε (item) {
|
||||||
|
const coords = (item.errorfinal ?? item.errorraw)?.coordinates;
|
||||||
|
if (coords) {
|
||||||
|
return {
|
||||||
|
crossline: coords[0]?.toFixed(2),
|
||||||
|
inline: coords[1]?.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
co (item) {
|
||||||
|
const raw = item.errorraw?.coordinates;
|
||||||
|
const final = item.errorfinal?.coordinates;
|
||||||
|
if (raw && final) {
|
||||||
|
return {
|
||||||
|
crossline: (final[0] - raw[0]).toFixed(2),
|
||||||
|
inline: (final[1] - raw[1]).toFixed(2),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
crossline: "n/a",
|
||||||
|
inline: "n/a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
gun_data (item) {
|
||||||
|
return item?.meta?.raw?.smsrc ?? {};
|
||||||
|
},
|
||||||
|
|
||||||
|
gun_value_formatter (value, index) {
|
||||||
|
const ms = (v) => v.toFixed(2)+" ms";
|
||||||
|
const transformers = [];
|
||||||
|
transformers[7] = ms;
|
||||||
|
transformers[8] = ms;
|
||||||
|
transformers[9] = ms;
|
||||||
|
transformers[10] = (v) => v.toFixed(1)+" m";
|
||||||
|
transformers[11] = (v) => v+" psi";
|
||||||
|
transformers[12] = (v) => v+" in³";
|
||||||
|
transformers[13] = ms;
|
||||||
|
|
||||||
|
const transformer = transformers[index];
|
||||||
|
return transformer ? transformer(value) : value;
|
||||||
|
},
|
||||||
|
|
||||||
|
...mapActions(["api"])
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
this.getSequence();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user