mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:37:08 +00:00
Implement the SequenceSummary component as a shotlog
This commit is contained in:
@@ -1,13 +1,570 @@
|
||||
<template>
|
||||
<div>
|
||||
SequenceSummary
|
||||
</div>
|
||||
<v-container fluid>
|
||||
|
||||
<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>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user