mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 10:47:07 +00:00
Implement the SequenceSummary component as a shotlog
This commit is contained in:
@@ -1,13 +1,570 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<v-container fluid>
|
||||||
SequenceSummary
|
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
|
<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-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';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SequenceSummary"
|
name: "SequenceSummary",
|
||||||
|
|
||||||
|
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