mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 13:37:07 +00:00
Clean up whitespace.
Commands used: find . -type f -name '*.js'| while read FILE; do if echo $FILE |grep -qv node_modules; then sed -ri 's/^\s+$//' "$FILE"; fi; done find . -type f -name '*.vue'| while read FILE; do if echo $FILE |grep -qv node_modules; then sed -ri 's/^\s+$//' "$FILE"; fi; done find . -type f -name '*.py'| while read FILE; do if echo $FILE |grep -qv node_modules; then sed -ri 's/^\s+$//' "$FILE"; fi; done
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
<style lang="stylus">
|
||||
@import '../node_modules/typeface-roboto/index.css'
|
||||
@import '../node_modules/@mdi/font/css/materialdesignicons.css'
|
||||
|
||||
|
||||
.markdown.v-textarea textarea
|
||||
font-family monospace
|
||||
line-height 1.1 !important
|
||||
@@ -66,7 +66,7 @@ export default {
|
||||
snackText (newVal) {
|
||||
this.snack = !!newVal;
|
||||
},
|
||||
|
||||
|
||||
snack (newVal) {
|
||||
// When the snack is hidden (one way or another), clear
|
||||
// the text so that if we receive the same message again
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<v-icon v-if="serverConnected" class="mr-6" small title="Connected to server">mdi-lan-connect</v-icon>
|
||||
<v-icon v-else class="mr-6" small color="red" title="Server connection lost (we'll reconnect automatically when the server comes back)">mdi-lan-disconnect</v-icon>
|
||||
|
||||
|
||||
<dougal-notifications-control class="mr-6"></dougal-notifications-control>
|
||||
|
||||
<div title="Night mode">
|
||||
@@ -31,7 +31,7 @@
|
||||
font-family: "Bank Gothic Medium";
|
||||
src: local("Bank Gothic Medium"), url("/fonts/bank-gothic-medium.woff");
|
||||
}
|
||||
|
||||
|
||||
.brand {
|
||||
font-family: "Bank Gothic Medium";
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
const date = new Date();
|
||||
return date.getUTCFullYear();
|
||||
},
|
||||
|
||||
|
||||
...mapState({serverConnected: state => state.notify.serverConnected})
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ import unpack from '@/lib/unpack.js';
|
||||
|
||||
export default {
|
||||
name: 'DougalGraphArraysIJScatter',
|
||||
|
||||
|
||||
props: [ "data", "settings" ],
|
||||
|
||||
data () {
|
||||
@@ -62,15 +62,15 @@ export default {
|
||||
histogram: false
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
|
||||
|
||||
//...mapGetters(['apiUrl'])
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
data (newVal, oldVal) {
|
||||
if (newVal === null) {
|
||||
this.busy = true;
|
||||
@@ -79,46 +79,46 @@ export default {
|
||||
this.plot();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
settings () {
|
||||
for (const key in this.settings) {
|
||||
this[key] = this.settings[key];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
histogram () {
|
||||
this.plot();
|
||||
this.$emit("update:settings", {[`${this.$options.name}.histogram`]: this.histogram});
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
scatterplot () {
|
||||
this.plot();
|
||||
this.$emit("update:settings", {[`${this.$options.name}.scatterplot`]: this.scatterplot});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
plot () {
|
||||
|
||||
|
||||
this.plotSeries();
|
||||
|
||||
|
||||
if (this.histogram) {
|
||||
this.plotHistogram();
|
||||
}
|
||||
|
||||
|
||||
if (this.scatterplot) {
|
||||
this.plotScatter();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
plotSeries () {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function transform (d, idx=0, otherParams={}) {
|
||||
const errortype = d.errorfinal ? "errorfinal" : "errorraw";
|
||||
const coords = unpack(unpack(d, errortype), "coordinates");
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
const data = [
|
||||
transform(this.data.items, 1, {
|
||||
xaxis: 'x',
|
||||
@@ -155,7 +155,7 @@ export default {
|
||||
})
|
||||
];
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
title: {text: "Inline / crossline error – sequence %{meta.sequence}"},
|
||||
@@ -177,25 +177,25 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph[0] = Plotly.newPlot(this.$refs.graph0, data, layout, config);
|
||||
},
|
||||
|
||||
|
||||
plotScatter () {
|
||||
|
||||
|
||||
console.log("plot");
|
||||
|
||||
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
console.log("Will plot sequence", this.data.meta.project, this.data.meta.sequence);
|
||||
|
||||
|
||||
function transform (d) {
|
||||
const errortype = d.errorfinal ? "errorfinal" : "errorraw";
|
||||
const coords = unpack(unpack(d, errortype), "coordinates");
|
||||
@@ -217,10 +217,10 @@ export default {
|
||||
}];
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
const data = transform(this.data.items);
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
//title: {text: "Inline / crossline error – sequence %{meta.sequence}"},
|
||||
@@ -235,22 +235,22 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph[1] = Plotly.newPlot(this.$refs.graph1, data, layout, config);
|
||||
},
|
||||
|
||||
|
||||
plotHistogram () {
|
||||
|
||||
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
function transform (d, idx=0, otherParams={}) {
|
||||
const errortype = d.errorfinal ? "errorfinal" : "errorraw";
|
||||
const coords = unpack(unpack(d, errortype), "coordinates");
|
||||
@@ -271,7 +271,7 @@ export default {
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
const data = [
|
||||
transform(this.data.items, 0, {
|
||||
xaxis: 'x',
|
||||
@@ -284,7 +284,7 @@ export default {
|
||||
name: 'Inline'
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
//title: {text: "Inline / crossline error – sequence %{meta.sequence}"},
|
||||
@@ -308,7 +308,7 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
@@ -319,12 +319,12 @@ export default {
|
||||
|
||||
this.graph[2] = Plotly.newPlot(this.$refs.graph2, data, layout, config);
|
||||
},
|
||||
|
||||
|
||||
replot () {
|
||||
if (!this.graph.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log("Replotting");
|
||||
this.graph.forEach( (graph, idx) => {
|
||||
const ref = this.$refs["graph"+idx];
|
||||
@@ -334,23 +334,23 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
async mounted () {
|
||||
|
||||
|
||||
if (this.data) {
|
||||
this.plot();
|
||||
} else {
|
||||
this.busy = true;
|
||||
}
|
||||
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graph0);
|
||||
this.resizeObserver.observe(this.$refs.graph1);
|
||||
this.resizeObserver.observe(this.$refs.graph2);
|
||||
},
|
||||
|
||||
|
||||
beforeDestroy () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(this.$refs.graph2);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<v-switch v-model="shotpoint" label="Shotpoint"></v-switch>
|
||||
<v-switch class="ml-4" v-model="violinplot" label="Violin plot"></v-switch>
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-container fluid fill-height>
|
||||
<v-row>
|
||||
<v-col>
|
||||
@@ -49,7 +49,7 @@ import * as aes from '@/lib/graphs/aesthetics.js';
|
||||
|
||||
export default {
|
||||
name: 'DougalGraphGunsDepth',
|
||||
|
||||
|
||||
props: [ "data", "settings" ],
|
||||
|
||||
data () {
|
||||
@@ -62,16 +62,16 @@ export default {
|
||||
violinplot: false
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
//...mapGetters(['apiUrl'])
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
data (newVal, oldVal) {
|
||||
console.log("data changed");
|
||||
|
||||
|
||||
if (newVal === null) {
|
||||
this.busy = true;
|
||||
} else {
|
||||
@@ -79,42 +79,42 @@ export default {
|
||||
this.plot();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
settings () {
|
||||
for (const key in this.settings) {
|
||||
this[key] = this.settings[key];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
shotpoint () {
|
||||
if (this.shotpoint) {
|
||||
this.replot();
|
||||
}
|
||||
this.$emit("update:settings", {[`${this.$options.name}.shotpoint`]: this.shotpoint});
|
||||
},
|
||||
|
||||
|
||||
violinplot () {
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
this.$emit("update:settings", {[`${this.$options.name}.violinplot`]: this.violinplot});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
plot () {
|
||||
this.plotSeries();
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async plotSeries () {
|
||||
|
||||
|
||||
function transformSeries (d, src_number, otherParams={}) {
|
||||
|
||||
|
||||
const meta = src_number
|
||||
? unpack(d, "meta").filter( s => s.src_number == src_number )
|
||||
: unpack(d, "meta");
|
||||
@@ -122,11 +122,11 @@ export default {
|
||||
const gunDepths = guns.map(s => s.map(g => g[10]));
|
||||
const gunDepthsSorted = gunDepths.map(s => d3a.sort(s));
|
||||
const gunsAvgDepth = gunDepths.map( (s, sidx) => d3a.mean(s) );
|
||||
|
||||
|
||||
const x = src_number
|
||||
? unpack(d.filter(s => s.meta.src_number == src_number), "point")
|
||||
: unpack(d, "point");
|
||||
|
||||
|
||||
const tracesGunDepths = [{
|
||||
type: "scatter",
|
||||
mode: "lines",
|
||||
@@ -150,7 +150,7 @@ export default {
|
||||
y: gunDepthsSorted.map(s => d3a.quantileSorted(s, 0.75)),
|
||||
...aes.gunArrays[src_number || 1].max
|
||||
}];
|
||||
|
||||
|
||||
const tracesGunsDepthsIndividual = {
|
||||
//name: `Array ${src_number} outliers`,
|
||||
type: "scatter",
|
||||
@@ -166,22 +166,22 @@ export default {
|
||||
).flat(),
|
||||
...aes.gunArrays[src_number || 1].out
|
||||
};
|
||||
|
||||
|
||||
const data = [ ...tracesGunDepths, tracesGunsDepthsIndividual ]
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const sources = [ ...new Set(unpack(this.data.items, "meta").map( s => s.src_number ))];
|
||||
const data = sources.map( src_number => transformSeries(this.data.items, src_number) ).flat();
|
||||
console.log("Sources", sources);
|
||||
console.log(data);
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
title: {text: "Gun depths – sequence %{meta.sequence}"},
|
||||
@@ -198,12 +198,12 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphSeries, data, layout, config);
|
||||
this.$refs.graphSeries.on('plotly_hover', (d) => {
|
||||
const point = d.points[0].x;
|
||||
@@ -220,7 +220,7 @@ export default {
|
||||
groups: unpack(guns, 0)
|
||||
}],
|
||||
}];
|
||||
|
||||
|
||||
const layout = {
|
||||
title: {text: "Gun depths – shot %{meta.point}"},
|
||||
height: 300,
|
||||
@@ -236,19 +236,19 @@ export default {
|
||||
point
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const config = { displaylogo: false };
|
||||
|
||||
|
||||
Plotly.react(this.$refs.graphBar, data, layout, config);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
async plotViolin () {
|
||||
|
||||
|
||||
function transformViolin (d, opts = {}) {
|
||||
|
||||
|
||||
const styles = [];
|
||||
|
||||
|
||||
unpack(unpack(d, "meta"), "guns").flat().forEach(i => {
|
||||
const gunId = i[1];
|
||||
const arrayId = i[2];
|
||||
@@ -256,7 +256,7 @@ export default {
|
||||
styles[gunId] = Object.assign({target: gunId}, aes.gunArrayViolins[arrayId]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'violin',
|
||||
x: unpack(unpack(unpack(d, "meta"), "guns").flat(), 1), // Gun number
|
||||
@@ -277,21 +277,21 @@ export default {
|
||||
styles: styles.filter(i => !!i)
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
console.log("plot violin");
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
console.log("Will plot sequence", this.data.meta.project, this.data.meta.sequence);
|
||||
|
||||
|
||||
const data = [ transformViolin(this.data.items) ];
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
showlegend: false,
|
||||
@@ -307,21 +307,21 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphViolin, data, layout, config);
|
||||
},
|
||||
|
||||
|
||||
|
||||
replot () {
|
||||
if (!this.graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log("Replotting");
|
||||
Object.values(this.$refs).forEach( ref => {
|
||||
if (ref.data) {
|
||||
@@ -333,25 +333,25 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
...mapActions(["api"])
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
mounted () {
|
||||
|
||||
|
||||
if (this.data) {
|
||||
this.plot();
|
||||
} else {
|
||||
this.busy = true;
|
||||
}
|
||||
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graphSeries);
|
||||
this.resizeObserver.observe(this.$refs.graphViolin);
|
||||
this.resizeObserver.observe(this.$refs.graphBar);
|
||||
},
|
||||
|
||||
|
||||
beforeDestroy () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(this.$refs.graphBar);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-card-title class="headline">
|
||||
Gun details
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-container fluid fill-height>
|
||||
<v-row>
|
||||
<v-col>
|
||||
@@ -37,7 +37,7 @@ import * as aes from '@/lib/graphs/aesthetics.js';
|
||||
|
||||
export default {
|
||||
name: 'DougalGraphGunsDepth',
|
||||
|
||||
|
||||
props: [ "data" ],
|
||||
|
||||
data () {
|
||||
@@ -54,16 +54,16 @@ export default {
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
//...mapGetters(['apiUrl'])
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
data (newVal, oldVal) {
|
||||
console.log("data changed");
|
||||
|
||||
|
||||
if (newVal === null) {
|
||||
this.busy = true;
|
||||
} else {
|
||||
@@ -71,31 +71,31 @@ export default {
|
||||
this.plot();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
violinplot () {
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
plot () {
|
||||
this.plotHeat();
|
||||
},
|
||||
|
||||
|
||||
async plotHeat () {
|
||||
|
||||
|
||||
|
||||
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
function transform (data, aspects=["Depth", "Pressure"]) {
|
||||
|
||||
|
||||
const facets = [
|
||||
// Mode
|
||||
{
|
||||
@@ -103,9 +103,9 @@ export default {
|
||||
name: "Mode",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{text}",
|
||||
},
|
||||
|
||||
|
||||
text: [ "Off", "Auto", "Manual", "Disabled" ],
|
||||
|
||||
|
||||
conversion: (gun, shot) => {
|
||||
switch (gun[3]) {
|
||||
case "A":
|
||||
@@ -119,16 +119,16 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Detect
|
||||
{
|
||||
params: {
|
||||
name: "Detect",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{text}",
|
||||
},
|
||||
|
||||
|
||||
text: [ "Zero", "Peak", "Level" ],
|
||||
|
||||
|
||||
conversion: (gun, shot) => {
|
||||
switch (gun[4]) {
|
||||
case "P":
|
||||
@@ -140,41 +140,41 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Autofire
|
||||
{
|
||||
params: {
|
||||
name: "Autofire",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{text}",
|
||||
},
|
||||
|
||||
|
||||
text: [ "False", "True" ],
|
||||
|
||||
|
||||
conversion: (gun, shot) => {
|
||||
return gun[5] ? 1 : 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Aimpoint
|
||||
{
|
||||
params: {
|
||||
name: "Aimpoint",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[7]
|
||||
},
|
||||
|
||||
|
||||
// Firetime
|
||||
{
|
||||
params: {
|
||||
name: "Firetime",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[2] == shot.meta.src_number ? gun[8] : null
|
||||
},
|
||||
|
||||
|
||||
// Delta
|
||||
{
|
||||
params: {
|
||||
@@ -187,7 +187,7 @@ export default {
|
||||
zmin: -2,
|
||||
zmax: 2
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[2] == shot.meta.src_number ? gun[7]-gun[8] : null
|
||||
},
|
||||
|
||||
@@ -197,7 +197,7 @@ export default {
|
||||
name: "Delay",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[9]
|
||||
},
|
||||
|
||||
@@ -207,7 +207,7 @@ export default {
|
||||
name: "Depth",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} m"
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[10]
|
||||
},
|
||||
|
||||
@@ -217,7 +217,7 @@ export default {
|
||||
name: "Pressure",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} psi"
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[11]
|
||||
},
|
||||
|
||||
@@ -227,7 +227,7 @@ export default {
|
||||
name: "Volume",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} in³"
|
||||
},
|
||||
|
||||
|
||||
conversion: (gun, shot) => gun[12]
|
||||
},
|
||||
|
||||
@@ -237,14 +237,14 @@ export default {
|
||||
name: "Filltime",
|
||||
hovertemplate: "SP%{x}<br>%{y}<br>%{z} ms"
|
||||
},
|
||||
|
||||
|
||||
// NOTE that filltime is applicable to the *non* firing guns
|
||||
conversion: (gun, shot) => gun[2] == shot.meta.src_number ? null : gun[13]
|
||||
}
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
|
||||
// Get gun numbers
|
||||
const guns = [...new Set(data.map( s => s.meta.guns.map( g => g[1] ) ).flat())];
|
||||
|
||||
@@ -256,13 +256,13 @@ export default {
|
||||
// ]
|
||||
// }
|
||||
const z = {};
|
||||
|
||||
|
||||
// x is an array of shotpoints
|
||||
const x = [];
|
||||
|
||||
|
||||
// y is an array of gun numbers
|
||||
const y = guns.map( gun => `G${gun}` );
|
||||
|
||||
|
||||
// Build array of guns (i.e., populate z)
|
||||
// We prefer to do this outside the shot-to-shot loop
|
||||
// for efficiency
|
||||
@@ -273,15 +273,15 @@ export default {
|
||||
z[label][i] = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Populate array of guns with shotpoint data
|
||||
for (let shot of data) {
|
||||
x.push(shot.point);
|
||||
|
||||
|
||||
for (const facet of facets) {
|
||||
const label = facet.params.name;
|
||||
const facetGunsArray = z[label];
|
||||
|
||||
|
||||
for (const gun of shot.meta.guns) {
|
||||
const gunIndex = gun[1]-1;
|
||||
const facetGun = facetGunsArray[gunIndex];
|
||||
@@ -289,10 +289,10 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return aspects.map( (aspect, idx) => {
|
||||
const facet = facets.find(el => el.params.name == aspect) || {};
|
||||
|
||||
|
||||
const defaultParams = {
|
||||
name: aspect,
|
||||
type: "heatmap",
|
||||
@@ -304,15 +304,15 @@ export default {
|
||||
xaxis: "x",
|
||||
yaxis: "y" + (idx > 0 ? idx+1 : "")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return Object.assign({}, defaultParams, facet.params);
|
||||
});
|
||||
}
|
||||
|
||||
const data = transform(this.data.items, this.aspects);
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
title: {text: "Gun details – sequence %{meta.sequence}"},
|
||||
height: 200*this.aspects.length,
|
||||
@@ -327,15 +327,15 @@ export default {
|
||||
*/
|
||||
//autosize: true,
|
||||
// colorscale: "sequential",
|
||||
|
||||
|
||||
xaxis: {
|
||||
title: "Shotpoint",
|
||||
showspikes: true
|
||||
},
|
||||
|
||||
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
this.aspects.forEach ( (aspect, idx) => {
|
||||
const num = idx+1;
|
||||
const key = "yaxis" + num;
|
||||
@@ -352,21 +352,21 @@ export default {
|
||||
domain
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const config = {
|
||||
//editable: true,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphHeat, data, layout, config);
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
replot () {
|
||||
if (!this.graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log("Replotting");
|
||||
Object.values(this.$refs).forEach( ref => {
|
||||
if (ref.data) {
|
||||
@@ -378,23 +378,23 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
...mapActions(["api"])
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
mounted () {
|
||||
|
||||
|
||||
if (this.data) {
|
||||
this.plot();
|
||||
} else {
|
||||
this.busy = true;
|
||||
}
|
||||
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graphHeat);
|
||||
},
|
||||
|
||||
|
||||
beforeDestroy () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(this.$refs.graphHeat);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<v-switch v-model="shotpoint" label="Shotpoint"></v-switch>
|
||||
<v-switch class="ml-4" v-model="violinplot" label="Violin plot"></v-switch>
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-container fluid fill-height>
|
||||
<v-row>
|
||||
<v-col>
|
||||
@@ -49,7 +49,7 @@ import * as aes from '@/lib/graphs/aesthetics.js';
|
||||
|
||||
export default {
|
||||
name: 'DougalGraphGunsPressure',
|
||||
|
||||
|
||||
props: [ "data", "settings" ],
|
||||
|
||||
data () {
|
||||
@@ -62,16 +62,16 @@ export default {
|
||||
violinplot: false
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
//...mapGetters(['apiUrl'])
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
data (newVal, oldVal) {
|
||||
console.log("data changed");
|
||||
|
||||
|
||||
if (newVal === null) {
|
||||
this.busy = true;
|
||||
} else {
|
||||
@@ -79,42 +79,42 @@ export default {
|
||||
this.plot();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
settings () {
|
||||
for (const key in this.settings) {
|
||||
this[key] = this.settings[key];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
shotpoint () {
|
||||
if (this.shotpoint) {
|
||||
this.replot();
|
||||
}
|
||||
this.$emit("update:settings", {[`${this.$options.name}.shotpoint`]: this.shotpoint});
|
||||
},
|
||||
|
||||
|
||||
violinplot () {
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
this.$emit("update:settings", {[`${this.$options.name}.violinplot`]: this.violinplot});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
plot () {
|
||||
this.plotSeries();
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async plotSeries () {
|
||||
|
||||
|
||||
function transformSeries (d, src_number, otherParams={}) {
|
||||
|
||||
|
||||
const meta = src_number
|
||||
? unpack(d, "meta").filter( s => s.src_number == src_number )
|
||||
: unpack(d, "meta");
|
||||
@@ -126,12 +126,12 @@ export default {
|
||||
const gunsWeightedAvgPressure = gunPressures.map( (s, sidx) =>
|
||||
d3a.sum(s.map( (pressure, gidx) => pressure * gunPressureWeights[sidx][gidx] )) / d3a.sum(gunPressureWeights[sidx])
|
||||
);
|
||||
|
||||
|
||||
const manifold = unpack(meta, "manifold");
|
||||
const x = src_number
|
||||
? unpack(d.filter(s => s.meta.src_number == src_number), "point")
|
||||
: unpack(d, "point");
|
||||
|
||||
|
||||
const traceManifold = {
|
||||
name: "Manifold",
|
||||
type: "scatter",
|
||||
@@ -140,7 +140,7 @@ export default {
|
||||
x,
|
||||
y: manifold,
|
||||
};
|
||||
|
||||
|
||||
const tracesGunPressures = [{
|
||||
type: "scatter",
|
||||
mode: "lines",
|
||||
@@ -164,7 +164,7 @@ export default {
|
||||
y: gunPressuresSorted.map(s => d3a.quantileSorted(s, 0.75)),
|
||||
...aes.gunArrays[src_number || 1].max
|
||||
}];
|
||||
|
||||
|
||||
const tracesGunsPressuresIndividual = {
|
||||
//name: `Array ${src_number} outliers`,
|
||||
type: "scatter",
|
||||
@@ -180,22 +180,22 @@ export default {
|
||||
).flat(),
|
||||
...aes.gunArrays[src_number || 1].out
|
||||
};
|
||||
|
||||
|
||||
const data = [ traceManifold, ...tracesGunPressures, tracesGunsPressuresIndividual ]
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const sources = [ ...new Set(unpack(this.data.items, "meta").map( s => s.src_number ))];
|
||||
const data = sources.map( src_number => transformSeries(this.data.items, src_number) ).flat();
|
||||
console.log("Sources", sources);
|
||||
console.log(data);
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
title: {text: "Gun pressures – sequence %{meta.sequence}"},
|
||||
@@ -212,12 +212,12 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphSeries, data, layout, config);
|
||||
this.$refs.graphSeries.on('plotly_hover', (d) => {
|
||||
const point = d.points[0].x;
|
||||
@@ -237,7 +237,7 @@ export default {
|
||||
groups: unpack(guns, 0)
|
||||
}],
|
||||
}];
|
||||
|
||||
|
||||
const layout = {
|
||||
title: {text: "Gun pressures – shot %{meta.point}"},
|
||||
height: 300,
|
||||
@@ -253,19 +253,19 @@ export default {
|
||||
point
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const config = { displaylogo: false };
|
||||
|
||||
|
||||
Plotly.react(this.$refs.graphBar, data, layout, config);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
async plotViolin () {
|
||||
|
||||
|
||||
function transformViolin (d, opts = {}) {
|
||||
|
||||
|
||||
const styles = [];
|
||||
|
||||
|
||||
unpack(unpack(d, "meta"), "guns").flat().forEach(i => {
|
||||
const gunId = i[1];
|
||||
const arrayId = i[2];
|
||||
@@ -273,7 +273,7 @@ export default {
|
||||
styles[gunId] = Object.assign({target: gunId}, aes.gunArrayViolins[arrayId]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'violin',
|
||||
x: unpack(unpack(unpack(d, "meta"), "guns").flat(), 1), // Gun number
|
||||
@@ -294,21 +294,21 @@ export default {
|
||||
styles: styles.filter(i => !!i)
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
console.log("plot violin");
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
console.log("Will plot sequence", this.data.meta.project, this.data.meta.sequence);
|
||||
|
||||
|
||||
const data = [ transformViolin(this.data.items) ];
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
showlegend: false,
|
||||
@@ -324,21 +324,21 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphViolin, data, layout, config);
|
||||
},
|
||||
|
||||
|
||||
|
||||
replot () {
|
||||
if (!this.graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log("Replotting");
|
||||
Object.values(this.$refs).forEach( ref => {
|
||||
if (ref.data) {
|
||||
@@ -350,25 +350,25 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
...mapActions(["api"])
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
mounted () {
|
||||
|
||||
|
||||
if (this.data) {
|
||||
this.plot();
|
||||
} else {
|
||||
this.busy = true;
|
||||
}
|
||||
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graphSeries);
|
||||
this.resizeObserver.observe(this.$refs.graphViolin);
|
||||
this.resizeObserver.observe(this.$refs.graphBar);
|
||||
},
|
||||
|
||||
|
||||
beforeDestroy () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(this.$refs.graphBar);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<v-switch v-model="shotpoint" label="Shotpoint"></v-switch>
|
||||
<v-switch class="ml-4" v-model="violinplot" label="Violin plot"></v-switch>
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-container fluid fill-height>
|
||||
<v-row>
|
||||
<v-col>
|
||||
@@ -49,7 +49,7 @@ import * as aes from '@/lib/graphs/aesthetics.js';
|
||||
|
||||
export default {
|
||||
name: 'DougalGraphGunsTiming',
|
||||
|
||||
|
||||
props: [ "data", "settings" ],
|
||||
|
||||
data () {
|
||||
@@ -62,16 +62,16 @@ export default {
|
||||
violinplot: false
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
//...mapGetters(['apiUrl'])
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
data (newVal, oldVal) {
|
||||
console.log("data changed");
|
||||
|
||||
|
||||
if (newVal === null) {
|
||||
this.busy = true;
|
||||
} else {
|
||||
@@ -79,42 +79,42 @@ export default {
|
||||
this.plot();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
settings () {
|
||||
for (const key in this.settings) {
|
||||
this[key] = this.settings[key];
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
shotpoint () {
|
||||
if (this.shotpoint) {
|
||||
this.replot();
|
||||
}
|
||||
this.$emit("update:settings", {[`${this.$options.name}.shotpoint`]: this.shotpoint});
|
||||
},
|
||||
|
||||
|
||||
violinplot () {
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
this.$emit("update:settings", {[`${this.$options.name}.violinplot`]: this.violinplot});
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
plot () {
|
||||
this.plotSeries();
|
||||
if (this.violinplot) {
|
||||
this.plotViolin();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async plotSeries () {
|
||||
|
||||
|
||||
function transformSeries (d, src_number, otherParams={}) {
|
||||
|
||||
|
||||
const meta = src_number
|
||||
? unpack(d, "meta").filter( s => s.src_number == src_number )
|
||||
: unpack(d, "meta");
|
||||
@@ -122,11 +122,11 @@ export default {
|
||||
const gunTimings = guns.map(s => s.map(g => g[9]));
|
||||
const gunTimingsSorted = gunTimings.map(s => d3a.sort(s));
|
||||
const gunsAvgTiming = gunTimings.map( (s, sidx) => d3a.mean(s) );
|
||||
|
||||
|
||||
const x = src_number
|
||||
? unpack(d.filter(s => s.meta.src_number == src_number), "point")
|
||||
: unpack(d, "point");
|
||||
|
||||
|
||||
const tracesGunTimings = [{
|
||||
type: "scatter",
|
||||
mode: "lines",
|
||||
@@ -150,7 +150,7 @@ export default {
|
||||
y: gunTimingsSorted.map(s => d3a.quantileSorted(s, 0.75)),
|
||||
...aes.gunArrays[src_number || 1].max
|
||||
}];
|
||||
|
||||
|
||||
const tracesGunsTimingsIndividual = {
|
||||
//name: `Array ${src_number} outliers`,
|
||||
type: "scatter",
|
||||
@@ -166,22 +166,22 @@ export default {
|
||||
).flat(),
|
||||
...aes.gunArrays[src_number || 1].out
|
||||
};
|
||||
|
||||
|
||||
const data = [ ...tracesGunTimings, tracesGunsTimingsIndividual ]
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const sources = [ ...new Set(unpack(this.data.items, "meta").map( s => s.src_number ))];
|
||||
const data = sources.map( src_number => transformSeries(this.data.items, src_number) ).flat();
|
||||
console.log("Sources", sources);
|
||||
console.log(data);
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
title: {text: "Gun timings – sequence %{meta.sequence}"},
|
||||
@@ -198,12 +198,12 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphSeries, data, layout, config);
|
||||
this.$refs.graphSeries.on('plotly_hover', (d) => {
|
||||
const point = d.points[0].x;
|
||||
@@ -220,7 +220,7 @@ export default {
|
||||
groups: unpack(guns, 0)
|
||||
}],
|
||||
}];
|
||||
|
||||
|
||||
const layout = {
|
||||
title: {text: "Gun timings – shot %{meta.point}"},
|
||||
height: 300,
|
||||
@@ -236,19 +236,19 @@ export default {
|
||||
point
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const config = { displaylogo: false };
|
||||
|
||||
|
||||
Plotly.react(this.$refs.graphBar, data, layout, config);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
async plotViolin () {
|
||||
|
||||
|
||||
function transformViolin (d, opts = {}) {
|
||||
|
||||
|
||||
const styles = [];
|
||||
|
||||
|
||||
unpack(unpack(d, "meta"), "guns").flat().forEach(i => {
|
||||
const gunId = i[1];
|
||||
const arrayId = i[2];
|
||||
@@ -256,7 +256,7 @@ export default {
|
||||
styles[gunId] = Object.assign({target: gunId}, aes.gunArrayViolins[arrayId]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const data = {
|
||||
type: 'violin',
|
||||
x: unpack(unpack(unpack(d, "meta"), "guns").flat(), 1), // Gun number
|
||||
@@ -277,21 +277,21 @@ export default {
|
||||
styles: styles.filter(i => !!i)
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
console.log("plot violin");
|
||||
if (!this.data) {
|
||||
console.log("missing data");
|
||||
return;
|
||||
}
|
||||
console.log("Will plot sequence", this.data.meta.project, this.data.meta.sequence);
|
||||
|
||||
|
||||
const data = [ transformViolin(this.data.items) ];
|
||||
this.busy = false;
|
||||
|
||||
|
||||
const layout = {
|
||||
//autosize: true,
|
||||
showlegend: false,
|
||||
@@ -307,21 +307,21 @@ export default {
|
||||
},
|
||||
meta: this.data.meta
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
editable: false,
|
||||
displaylogo: false
|
||||
};
|
||||
|
||||
|
||||
this.graph = Plotly.newPlot(this.$refs.graphViolin, data, layout, config);
|
||||
},
|
||||
|
||||
|
||||
|
||||
replot () {
|
||||
if (!this.graph) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log("Replotting");
|
||||
Object.values(this.$refs).forEach( ref => {
|
||||
if (ref.data) {
|
||||
@@ -333,25 +333,25 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
...mapActions(["api"])
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
mounted () {
|
||||
|
||||
|
||||
if (this.data) {
|
||||
this.plot();
|
||||
} else {
|
||||
this.busy = true;
|
||||
}
|
||||
|
||||
|
||||
this.resizeObserver = new ResizeObserver(this.replot)
|
||||
this.resizeObserver.observe(this.$refs.graphSeries);
|
||||
this.resizeObserver.observe(this.$refs.graphViolin);
|
||||
this.resizeObserver.observe(this.$refs.graphBar);
|
||||
},
|
||||
|
||||
|
||||
beforeDestroy () {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(this.$refs.graphBar);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<template>
|
||||
|
||||
<v-dialog v-model="open">
|
||||
|
||||
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn icon v-bind="attrs" v-on="on" title="Configure visible aspects">
|
||||
<v-icon small>mdi-wrench-outline</v-icon>
|
||||
</v-btn>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<v-card>
|
||||
<v-list nav subheader>
|
||||
|
||||
|
||||
<v-subheader>Visualisations</v-subheader>
|
||||
|
||||
|
||||
<v-list-item-group v-model="aspectsVisible" multiple>
|
||||
|
||||
|
||||
<v-list-item value="DougalGraphGunsPressure">
|
||||
<template v-slot:default="{ active }">
|
||||
<v-list-item-action>
|
||||
@@ -28,7 +28,7 @@
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
<v-list-item value="DougalGraphGunsTiming">
|
||||
<template v-slot:default="{ active }">
|
||||
<v-list-item-action>
|
||||
@@ -41,7 +41,7 @@
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
<v-list-item value="DougalGraphGunsDepth">
|
||||
<template v-slot:default="{ active }">
|
||||
<v-list-item-action>
|
||||
@@ -54,7 +54,7 @@
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
<v-list-item value="DougalGraphGunsHeatmap">
|
||||
<template v-slot:default="{ active }">
|
||||
<v-list-item-action>
|
||||
@@ -67,7 +67,7 @@
|
||||
</v-list-item-content>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
<v-list-item value="DougalGraphArraysIJScatter">
|
||||
<template v-slot:default="{ active }">
|
||||
<v-list-item-action>
|
||||
@@ -83,14 +83,14 @@
|
||||
|
||||
</v-list-item-group>
|
||||
</v-list>
|
||||
|
||||
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions>
|
||||
<v-btn v-if="user" color="warning" text @click="save" :title="'Save as preference for user '+user.name+' on this computer (other users may have other defaults).'">Save as default</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" text @click="open=false">Close</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
@@ -102,20 +102,20 @@ import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "DougalGraphSettingsSequence",
|
||||
|
||||
|
||||
props: [
|
||||
"aspects"
|
||||
],
|
||||
|
||||
|
||||
data () {
|
||||
return {
|
||||
open: false,
|
||||
aspectsVisible: this.aspects || []
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
aspects () {
|
||||
// Update the aspects selection list iff the list
|
||||
// is not currently open.
|
||||
@@ -123,19 +123,19 @@ export default {
|
||||
this.aspectsVisible = this.aspects;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
computed: {
|
||||
...mapGetters(['user', 'writeaccess', 'loading', 'serverEvent'])
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
save () {
|
||||
this.open = false;
|
||||
this.$nextTick( () => this.$emit("update:aspects", {aspects: [...this.aspectsVisible]}) );
|
||||
},
|
||||
|
||||
|
||||
reset () {
|
||||
this.aspectsVisible = this.aspects || [];
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@
|
||||
min-height 16px
|
||||
background-color #d3d3d314
|
||||
border-radius 4px
|
||||
|
||||
|
||||
.sequence
|
||||
flex 1 1 auto
|
||||
opacity 0.5
|
||||
border-radius 4px
|
||||
|
||||
|
||||
&.ntbp
|
||||
background-color red
|
||||
&.raw
|
||||
@@ -54,13 +54,13 @@
|
||||
|
||||
export default {
|
||||
name: 'DougalLineStatus',
|
||||
|
||||
|
||||
props: {
|
||||
preplot: Object,
|
||||
sequences: Array,
|
||||
"sequence-href": Function
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
style (s) {
|
||||
const values = {};
|
||||
@@ -69,28 +69,28 @@ export default {
|
||||
: s.status == "ntbp"
|
||||
? (s.fsp_final || s.fsp)
|
||||
: s.fsp; /* status == "raw" */
|
||||
|
||||
|
||||
const lsp = s.status == "final"
|
||||
? s.lsp_final
|
||||
: s.status == "ntbp"
|
||||
? (s.lsp_final || s.lsp)
|
||||
: s.lsp; /* status == "raw" */
|
||||
|
||||
|
||||
const pp0 = Math.min(this.preplot.fsp, this.preplot.lsp);
|
||||
const pp1 = Math.max(this.preplot.fsp, this.preplot.lsp);
|
||||
const len = pp1-pp0;
|
||||
const sp0 = Math.max(Math.min(fsp, lsp), pp0);
|
||||
const sp1 = Math.min(Math.max(fsp, lsp), pp1);
|
||||
|
||||
|
||||
const left = (sp0-pp0)/len;
|
||||
const right = 1-((sp1-pp0)/len);
|
||||
|
||||
|
||||
values["margin-left"] = left*100 + "%";
|
||||
values["margin-right"] = right*100 + "%";
|
||||
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
|
||||
title (s) {
|
||||
const status = s.status == "final"
|
||||
? "Final"
|
||||
@@ -101,13 +101,13 @@ export default {
|
||||
: s.status == "planned"
|
||||
? "Planned"
|
||||
: s.status;
|
||||
|
||||
|
||||
const remarks = "\n"+[s.remarks, s.remarks_final].join("\n").trim()
|
||||
|
||||
|
||||
return `Sequence ${s.sequence} – ${status} (${s.fsp_final || s.fsp}−${s.lsp_final || s.lsp})${remarks}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<v-toolbar-title class="mx-2" @click="$router.push('/')" style="cursor: pointer;">Dougal</v-toolbar-title>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
|
||||
<v-menu bottom offset-y>
|
||||
<template v-slot:activator="{on, attrs}">
|
||||
<v-hover v-slot="{hover}">
|
||||
@@ -29,17 +29,17 @@
|
||||
</v-btn>
|
||||
</v-hover>
|
||||
</template>
|
||||
|
||||
|
||||
<v-list dense>
|
||||
<v-list-item :href="`/settings/equipment`">
|
||||
<v-list-item-title>Equipment list</v-list-item-title>
|
||||
<v-list-item-action><v-icon small>mdi-view-list</v-icon></v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
|
||||
</v-menu>
|
||||
|
||||
|
||||
|
||||
<v-breadcrumbs :items="path"></v-breadcrumbs>
|
||||
|
||||
<template v-if="$route.name != 'Login'">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
|
||||
|
||||
export default function FormatTimestamp (str) {
|
||||
const d = new Date(str);
|
||||
if (isNaN(d)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
export default function unpack(rows, key) {
|
||||
return rows && rows.map( row => row[key] );
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
|
||||
function withParentProps(item, parent, childrenKey, prop, currentValue) {
|
||||
if (!Array.isArray(parent)) {
|
||||
@@ -29,43 +29,43 @@ function withParentProps(item, parent, childrenKey, prop, currentValue) {
|
||||
function dms (lat, lon) {
|
||||
const λh = lat < 0 ? "S" : "N";
|
||||
const φh = lon < 0 ? "W" : "E";
|
||||
|
||||
|
||||
const λn = Math.abs(lat);
|
||||
const φn = Math.abs(lon);
|
||||
|
||||
|
||||
const λi = Math.trunc(λn);
|
||||
const φi = Math.trunc(φn);
|
||||
|
||||
|
||||
const λf = λn - λi;
|
||||
const φf = φn - φi;
|
||||
|
||||
|
||||
const λs = ((λf*3600)%60).toFixed(1);
|
||||
const φs = ((φf*3600)%60).toFixed(1);
|
||||
|
||||
|
||||
const λm = Math.trunc(λf*60);
|
||||
const φm = Math.trunc(φf*60);
|
||||
|
||||
|
||||
const λ =
|
||||
String(λi).padStart(2, "0") + "°" +
|
||||
String(λm).padStart(2, "0") + "'" +
|
||||
String(λs).padStart(4, "0") + '" ' +
|
||||
λh;
|
||||
|
||||
|
||||
const φ =
|
||||
String(φi).padStart(3, "0") + "°" +
|
||||
String(φm).padStart(2, "0") + "'" +
|
||||
String(φs).padStart(4, "0") + '" ' +
|
||||
φh;
|
||||
|
||||
|
||||
return λ+" "+φ;
|
||||
}
|
||||
|
||||
function geometryAsString (item, opts = {}) {
|
||||
const key = "key" in opts ? opts.key : "geometry";
|
||||
const formatDMS = opts.dms;
|
||||
|
||||
|
||||
let str = "";
|
||||
|
||||
|
||||
if (key in item) {
|
||||
const geometry = item[key];
|
||||
if (geometry && "coordinates" in geometry) {
|
||||
@@ -76,7 +76,7 @@ function geometryAsString (item, opts = {}) {
|
||||
str = `${geometry.coordinates[1].toFixed(6)}, ${geometry.coordinates[0].toFixed(6)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (str) {
|
||||
if (opts.url) {
|
||||
if (typeof opts.url === 'string') {
|
||||
@@ -88,7 +88,7 @@ function geometryAsString (item, opts = {}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -117,10 +117,10 @@ function geometryAsString (item, opts = {}) {
|
||||
* not exist or is not searched for.
|
||||
*/
|
||||
function preferencesλ (preferences) {
|
||||
|
||||
|
||||
return function (key, defaults={}) {
|
||||
const keys = Object.keys(preferences).filter(str => str.startsWith(key+".") || str == key);
|
||||
|
||||
|
||||
const settings = {...defaults};
|
||||
for (const str of keys) {
|
||||
const k = str == key ? str : str.substring(key.length+1);
|
||||
@@ -130,7 +130,7 @@ function preferencesλ (preferences) {
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
function setProjectId (state, pid) {
|
||||
state.projectId = pid;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
function showSnack({commit}, [text, colour]) {
|
||||
commit('setSnackColour', colour || 'primary');
|
||||
commit('setSnackText', text);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
function setSnackText (state, text) {
|
||||
state.snackText = text;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ async function logout ({commit, dispatch}) {
|
||||
commit('setUser', null);
|
||||
// Should delete JWT cookie
|
||||
await dispatch('api', ["/logout"]);
|
||||
|
||||
|
||||
// Clear preferences
|
||||
commit('setPreferences', {});
|
||||
}
|
||||
@@ -61,16 +61,16 @@ function setCredentials ({state, commit, getters, dispatch}, force = false) {
|
||||
*/
|
||||
function saveUserPreference ({state, commit}, [key, value]) {
|
||||
const k = `${state.user?.name}.${state.user?.role}.${key}`;
|
||||
|
||||
|
||||
if (value !== undefined) {
|
||||
localStorage.setItem(k, JSON.stringify(value));
|
||||
|
||||
|
||||
const preferences = state.preferences;
|
||||
preferences[key] = value;
|
||||
commit('setPreferences', preferences);
|
||||
} else {
|
||||
localStorage.removeItem(k);
|
||||
|
||||
|
||||
const preferences = state.preferences;
|
||||
delete preferences[key];
|
||||
commit('setPreferences', preferences);
|
||||
@@ -81,7 +81,7 @@ async function loadUserPreferences ({state, commit}) {
|
||||
// Get all keys which are of interest to us
|
||||
const prefix = `${state.user?.name}.${state.user?.role}`;
|
||||
const keys = Object.keys(localStorage).filter( k => k.startsWith(prefix) );
|
||||
|
||||
|
||||
// Build the preferences object
|
||||
const preferences = {};
|
||||
keys.map(str => {
|
||||
@@ -89,7 +89,7 @@ async function loadUserPreferences ({state, commit}) {
|
||||
const key = str.split(".").slice(2).join(".");
|
||||
preferences[key] = value;
|
||||
});
|
||||
|
||||
|
||||
// Commit it
|
||||
commit('setPreferences', preferences);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
function setCookie (state, cookie) {
|
||||
state.cookie = cookie;
|
||||
}
|
||||
|
||||
@@ -35,14 +35,14 @@ import { mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "FeedViewer",
|
||||
|
||||
|
||||
data () {
|
||||
return {
|
||||
timer: null,
|
||||
feed: {}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
parse (text) {
|
||||
const data = {items:[]};
|
||||
@@ -50,13 +50,13 @@ export default {
|
||||
const xml = parser.parseFromString(text, "application/xml");
|
||||
const feed = xml.getElementsByTagNameNS("http://www.w3.org/2005/Atom", "feed")[0];
|
||||
const entries = feed.getElementsByTagName("entry");
|
||||
|
||||
|
||||
data.title = feed.getElementsByTagName("title")[0].childNodes[0].textContent;
|
||||
data.updated = feed.getElementsByTagName("updated")[0].childNodes[0].textContent;
|
||||
data.link = [...feed.getElementsByTagName("link")].filter(i =>
|
||||
i.getAttribute("type") == "text/html"
|
||||
).pop().getAttribute("href");
|
||||
|
||||
|
||||
data.items = [...entries].map(entry => {
|
||||
const item = {};
|
||||
const link = entry.getElementsByTagName("link")[0];
|
||||
@@ -70,18 +70,18 @@ export default {
|
||||
}
|
||||
const summaries = entry.getElementsByTagName("summary");
|
||||
const summary = [...summaries].find(i => i.getAttribute("type") == "xhtml") || summaries[0];
|
||||
|
||||
|
||||
item.summary = summary.innerHTML;
|
||||
item.id = entry.getElementsByTagName("id")[0].childNodes[0].textContent;
|
||||
item.title = entry.getElementsByTagName("title")[0].childNodes[0].textContent;
|
||||
item.updated = entry.getElementsByTagName("updated")[0].childNodes[0].textContent;
|
||||
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
|
||||
/** Try to fix idiosyncrasies and XML bugs in the source.
|
||||
*/
|
||||
fixText (text) {
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
// element in the source.
|
||||
return text.replace(/(<hr( [^>]*)?>)/g, "$1</hr>")
|
||||
},
|
||||
|
||||
|
||||
async refresh () {
|
||||
const text = await this.api([`/rss/?remote=${atob(this.$route.params.source)}`, {text:true}]);
|
||||
try {
|
||||
@@ -100,15 +100,15 @@ export default {
|
||||
this.feed = this.parse(this.fixText(text));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
...mapActions(["api"])
|
||||
},
|
||||
|
||||
|
||||
async mounted () {
|
||||
await this.refresh();
|
||||
this.timer = setInterval(this.refresh, 300000);
|
||||
},
|
||||
|
||||
|
||||
unmounted () {
|
||||
cancelInterval(this.timer);
|
||||
this.timer = null;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</v-toolbar>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
|
||||
|
||||
<v-menu v-if="writeaccess"
|
||||
v-model="contextMenuShow"
|
||||
:position-x="contextMenuX"
|
||||
@@ -63,7 +63,7 @@
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
|
||||
<v-dialog
|
||||
v-model="colourPickerShow"
|
||||
max-width="300"
|
||||
@@ -118,7 +118,7 @@
|
||||
@click:row="setActiveItem"
|
||||
@contextmenu:row="contextMenu"
|
||||
>
|
||||
|
||||
|
||||
<template v-slot:item.status="{item}">
|
||||
<dougal-line-status
|
||||
:preplot="item"
|
||||
@@ -143,7 +143,7 @@
|
||||
<template v-slot:item.azimuth="props">
|
||||
<span>{{ props.value.toFixed(2) }} °</span>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-slot:item.remarks="{item}">
|
||||
<v-text-field v-if="edit && edit.line == item.line && edit.key == 'remarks'"
|
||||
type="text"
|
||||
@@ -169,9 +169,9 @@
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
</v-data-table>
|
||||
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
@@ -192,7 +192,7 @@ import DougalLineStatus from '@/components/line-status';
|
||||
|
||||
export default {
|
||||
name: "LineList",
|
||||
|
||||
|
||||
components: {
|
||||
DougalLineStatus
|
||||
},
|
||||
@@ -258,13 +258,13 @@ export default {
|
||||
edit: null, // {line, key, value}
|
||||
queuedReload: false,
|
||||
itemsPerPage: 25,
|
||||
|
||||
|
||||
// Context menu stuff
|
||||
contextMenuShow: false,
|
||||
contextMenuX: 0,
|
||||
contextMenuY: 0,
|
||||
contextMenuItem: null,
|
||||
|
||||
|
||||
// Colour picker stuff
|
||||
colourPickerShow: false,
|
||||
selectedColour: null,
|
||||
@@ -275,17 +275,17 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['user', 'writeaccess', 'loading', 'serverEvent'])
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
|
||||
async edit (newVal, oldVal) {
|
||||
if (newVal === null && oldVal !== null) {
|
||||
const item = this.items.find(i => i.line == oldVal.line);
|
||||
|
||||
|
||||
// Get around this Vuetify ‘feature’
|
||||
// https://github.com/vuetifyjs/vuetify/issues/4144
|
||||
if (oldVal.value === null) oldVal.value = "";
|
||||
|
||||
|
||||
if (item && item[oldVal.key] != oldVal.value) {
|
||||
if (await this.saveItem(oldVal)) {
|
||||
item[oldVal.key] = oldVal.value;
|
||||
@@ -317,51 +317,51 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
queuedReload (newVal, oldVal) {
|
||||
if (newVal && !oldVal && !this.loading) {
|
||||
this.getLines();
|
||||
this.getSequences();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
loading (newVal, oldVal) {
|
||||
if (!newVal && oldVal && this.queuedReload) {
|
||||
this.getLines();
|
||||
this.getSequences();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
itemsPerPage (newVal, oldVal) {
|
||||
localStorage.setItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`, newVal);
|
||||
},
|
||||
|
||||
|
||||
user (newVal, oldVal) {
|
||||
this.itemsPerPage = Number(localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`)) || 25;
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
itemClass (item) {
|
||||
|
||||
|
||||
const colourClass = item.meta.colour ? "bg-clr-"+item.meta.colour.slice(1) : null;
|
||||
if (colourClass && ![...this.styles.cssRules].some(i => i.selectorText == "."+colourClass)) {
|
||||
const rule = `.${colourClass} { background-color: ${item.meta.colour}; }`;
|
||||
this.styles.insertRule(rule);
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
item.meta.colour ? colourClass : "",
|
||||
(this.activeItem == item && !this.edit) ? 'blue accent-1 elevation-3' : ''
|
||||
];
|
||||
},
|
||||
|
||||
|
||||
isPlanned(item) {
|
||||
return this.sequences.find(i => i.line == item.line && i.status == 'planned');
|
||||
},
|
||||
|
||||
|
||||
contextMenu (e, {item}) {
|
||||
e.preventDefault();
|
||||
this.contextMenuShow = false;
|
||||
@@ -370,7 +370,7 @@ export default {
|
||||
this.contextMenuItem = item;
|
||||
this.$nextTick( () => this.contextMenuShow = true );
|
||||
},
|
||||
|
||||
|
||||
setNTBA () {
|
||||
this.removeFromPlan();
|
||||
this.saveItem({
|
||||
@@ -379,7 +379,7 @@ export default {
|
||||
value: !this.contextMenuItem.ntba
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
setComplete () {
|
||||
this.saveItem({
|
||||
line: this.contextMenuItem.line,
|
||||
@@ -414,21 +414,21 @@ export default {
|
||||
await this.api([url, init]);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
showLineColourDialog () {
|
||||
this.selectedColour = this.contextMenuItem.meta.colour
|
||||
? {hexa: this.contextMenuItem.meta.colour}
|
||||
: null;
|
||||
this.colourPickerShow = true;
|
||||
},
|
||||
|
||||
|
||||
setLineColour () {
|
||||
const items = this.selectOn ? this.selectedRows : [ this.contextMenuItem ];
|
||||
const colour = this.selectedColour ? this.selectedColour.hex+"80" : null;
|
||||
|
||||
|
||||
this.selectedRows = [];
|
||||
this.selectOn = false;
|
||||
|
||||
|
||||
for (const item of items) {
|
||||
if (colour) {
|
||||
item.meta.colour = colour;
|
||||
@@ -439,7 +439,7 @@ export default {
|
||||
this.colourPickerShow = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
editItem (item, key) {
|
||||
this.edit = {
|
||||
line: item.line,
|
||||
@@ -447,10 +447,10 @@ export default {
|
||||
value: item[key]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async saveItem (edit) {
|
||||
if (!edit) return;
|
||||
|
||||
|
||||
try {
|
||||
const url = `/project/${this.$route.params.project}/line/${edit.line}`;
|
||||
const init = {
|
||||
@@ -459,7 +459,7 @@ export default {
|
||||
[edit.key]: edit.value
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let res;
|
||||
await this.api([url, init, (e, r) => res = r]);
|
||||
return res && res.ok;
|
||||
@@ -481,11 +481,11 @@ export default {
|
||||
this.items = await this.api([url]) || [];
|
||||
|
||||
},
|
||||
|
||||
|
||||
async getSequences () {
|
||||
const urlS = `/project/${this.$route.params.project}/sequence`;
|
||||
this.sequences = await this.api([urlS]) || [];
|
||||
|
||||
|
||||
const urlP = `/project/${this.$route.params.project}/plan`;
|
||||
const planned = await this.api([urlP]) || [];
|
||||
planned.forEach(i => i.status = "planned");
|
||||
@@ -505,7 +505,7 @@ export default {
|
||||
this.getLines();
|
||||
this.getNumLines();
|
||||
this.getSequences();
|
||||
|
||||
|
||||
// Initialise stylesheet
|
||||
const el = document.createElement("style");
|
||||
document.head.appendChild(el);
|
||||
|
||||
@@ -165,15 +165,15 @@ const layers = {
|
||||
onEachFeature (feature, layer) {
|
||||
const p = feature.properties;
|
||||
if (feature.geometry) {
|
||||
|
||||
|
||||
const ntbp = p.ntbp
|
||||
? " <b>(NTBP)</b>"
|
||||
: "";
|
||||
|
||||
|
||||
const remarks = p.remarks
|
||||
? "<hr/>"+markdown(p.remarks)
|
||||
: "";
|
||||
|
||||
|
||||
const popup = feature.geometry.type == "Point"
|
||||
? `Raw sequence ${feature.properties.sequence}${ntbp}<br/>Point <b>${feature.properties.line} / ${feature.properties.point}</b><br/>${feature.properties.objref}<br/>${feature.properties.tstamp}`
|
||||
: `Raw sequence ${p.sequence}${ntbp}<br/>
|
||||
@@ -183,7 +183,7 @@ const layers = {
|
||||
${p.duration}<br/>
|
||||
<table><tr><td><b>${p.fsp}</b></td><td>@ ${ftstamp(p.ts0)}</td></tr><tr><td><b>${p.lsp}</b></td><td>@ ${ftstamp(p.ts1)}</td></tr></table>${remarks}`;
|
||||
layer.bindTooltip(popup, {sticky: true});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -204,7 +204,7 @@ const layers = {
|
||||
},
|
||||
onEachFeature (feature, layer) {
|
||||
const p = feature.properties;
|
||||
|
||||
|
||||
const remarks = p.remarks
|
||||
? "<hr/>"+markdown(p.remarks)
|
||||
: "";
|
||||
@@ -221,9 +221,9 @@ const layers = {
|
||||
layer.bindTooltip(popup, {sticky: true});
|
||||
}
|
||||
}),
|
||||
|
||||
|
||||
"Events (QC)": L.geoJSON(null),
|
||||
|
||||
|
||||
"Events (Other)": L.geoJSON(null),
|
||||
|
||||
"Real-time": L.realtime({
|
||||
@@ -394,7 +394,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
user (newVal, oldVal) {
|
||||
if (newVal && (!oldVal || newVal.name != oldVal.name)) {
|
||||
this.initView();
|
||||
@@ -416,7 +416,7 @@ export default {
|
||||
//console.log("EVENT", event);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
$route (to, from) {
|
||||
if (to.name == "map") {
|
||||
this.setHashMarker();
|
||||
@@ -431,13 +431,13 @@ export default {
|
||||
const bbox = new L.GeoJSON(res);
|
||||
map.fitBounds(bbox.getBounds());
|
||||
},
|
||||
|
||||
|
||||
getEvents (ffn = i => true) {
|
||||
return async (success, error) => {
|
||||
const url = `/project/${this.$route.params.project}/event`;
|
||||
const data = await this.api([url, {headers: {"Accept": "application/geo+json"}}]);
|
||||
if (data) {
|
||||
|
||||
|
||||
function colour(feature) {
|
||||
if (feature && feature.properties && feature.properties.type) {
|
||||
if (feature.properties.type == "qc") {
|
||||
@@ -452,7 +452,7 @@ export default {
|
||||
}
|
||||
return "brown";
|
||||
}
|
||||
|
||||
|
||||
const features = data.filter(ffn).map(feature => {
|
||||
feature.properties.colour = colour(feature);
|
||||
return feature;
|
||||
@@ -480,15 +480,15 @@ export default {
|
||||
|
||||
for (const l of this.layerRefreshConfig.filter(i => !layerset || layerset.includes(i.layer))) {
|
||||
if (map.hasLayer(l.layer)) {
|
||||
|
||||
|
||||
const url = l.url(query);
|
||||
// Skip unnecessary requests
|
||||
if (url == l.layer.lastRequestURL) continue;
|
||||
|
||||
|
||||
if (l.layer.abort && l.layer.abort instanceof AbortController) {
|
||||
l.layer.abort.abort();
|
||||
}
|
||||
|
||||
|
||||
l.layer.abort = new AbortController();
|
||||
const signal = l.layer.abort.signal;
|
||||
const init = {
|
||||
@@ -497,7 +497,7 @@ export default {
|
||||
Accept: "application/geo+json"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Firing all refresh events asynchronously, which is OK provided
|
||||
// we don't have hundreds of layers to be refreshed.
|
||||
this.api([url, init])
|
||||
@@ -505,11 +505,11 @@ export default {
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (typeof l.transform == 'function') {
|
||||
layer = l.transform(layer);
|
||||
}
|
||||
|
||||
|
||||
l.layer.clearLayers();
|
||||
if (layer instanceof L.Layer || (layer.features && layer.features.length < limit) || ("length" in layer && layer.length < limit)) {
|
||||
if (l.layer.addData) {
|
||||
@@ -517,7 +517,7 @@ export default {
|
||||
} else if (l.layer.addLayer) {
|
||||
l.layer.addLayer(layer);
|
||||
}
|
||||
|
||||
|
||||
l.layer.lastRequestURL = url;
|
||||
} else {
|
||||
console.warn("Too much data from", url);
|
||||
@@ -551,7 +551,7 @@ export default {
|
||||
} else {
|
||||
value = `${zoom}/${lat}/${lng}`;
|
||||
}
|
||||
|
||||
|
||||
if (value) {
|
||||
localStorage.setItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/view`, value);
|
||||
}
|
||||
@@ -559,11 +559,11 @@ export default {
|
||||
|
||||
decodeURL () {
|
||||
const value = localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/view`);
|
||||
|
||||
|
||||
if (!value) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
const parts = value.split(":");
|
||||
const activeOverlays = parts.length > 1 && parts[1].split(";");
|
||||
const activeLayers = parts.length > 2 && parts[2].split(";");
|
||||
@@ -574,19 +574,19 @@ export default {
|
||||
|
||||
return {position, activeOverlays, activeLayers};
|
||||
},
|
||||
|
||||
|
||||
initView () {
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
map.off('overlayadd', this.updateURL);
|
||||
map.off('overlayremove', this.updateURL);
|
||||
map.off('layeradd', this.updateURL);
|
||||
map.off('layerremove', this.updateURL);
|
||||
|
||||
|
||||
const init = this.decodeURL();
|
||||
|
||||
|
||||
if (init.activeOverlays) {
|
||||
Object.keys(tileMaps).forEach(k => {
|
||||
const l = tileMaps[k];
|
||||
@@ -621,16 +621,16 @@ export default {
|
||||
if (init.position) {
|
||||
map.setView(init.position.slice(1), init.position[0]);
|
||||
}
|
||||
|
||||
|
||||
map.on('overlayadd', this.updateURL);
|
||||
map.on('overlayremove', this.updateURL);
|
||||
map.on('layeradd', this.updateURL);
|
||||
map.on('layerremove', this.updateURL);
|
||||
|
||||
},
|
||||
|
||||
|
||||
setHashMarker () {
|
||||
|
||||
|
||||
const crosshairsMarkerIcon = L.divIcon({
|
||||
iconSize: [20, 20],
|
||||
iconAnchor: [10, 10],
|
||||
@@ -643,7 +643,7 @@ export default {
|
||||
</svg>
|
||||
`
|
||||
});
|
||||
|
||||
|
||||
const updateMarker = (latlng) => {
|
||||
if (this.hashMarker) {
|
||||
if (latlng) {
|
||||
@@ -657,7 +657,7 @@ export default {
|
||||
this.hashMarker.addTo(map).getElement().style.fill = "fuchsia";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const parts = document.location.hash.substring(1).split(":")[0].split("/").map(p => decodeURIComponent(p));
|
||||
if (parts.length == 3) {
|
||||
setTimeout(() => map.setView(parts.slice(1).reverse(), parts[0]), 500);
|
||||
@@ -677,7 +677,7 @@ export default {
|
||||
|
||||
mounted () {
|
||||
map = L.map('map', {maxZoom: 22});
|
||||
|
||||
|
||||
const eventsOptions = () => {
|
||||
return {
|
||||
start: false,
|
||||
@@ -703,7 +703,7 @@ export default {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
layers["Events (QC)"] = L.realtime(this.getEvents(i => i.properties.type == "qc"), eventsOptions());
|
||||
layers["Events (Other)"] = L.realtime(this.getEvents(i => i.properties.type != "qc"), eventsOptions());
|
||||
|
||||
@@ -729,7 +729,7 @@ export default {
|
||||
//console.log("Events (Other) remove event", e);
|
||||
});
|
||||
|
||||
|
||||
|
||||
const init = this.decodeURL();
|
||||
|
||||
if (init.activeOverlays) {
|
||||
@@ -828,7 +828,7 @@ export default {
|
||||
});
|
||||
|
||||
(new LoadingControl({position: "bottomright"})).addTo(map);
|
||||
|
||||
|
||||
// Decode a position if one given in the hash
|
||||
this.setHashMarker();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<v-card-title>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>Plan</v-toolbar-title>
|
||||
|
||||
|
||||
<v-menu v-if="items">
|
||||
<template v-slot:activator="{on, attrs}">
|
||||
<v-btn class="ml-5" small v-on="on" v-bind="attrs">
|
||||
@@ -12,7 +12,7 @@
|
||||
<v-icon right small>mdi-cloud-download</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
|
||||
<v-list>
|
||||
<v-list-item
|
||||
:href="`/api/project/${$route.params.project}/plan/?mime=text%2Fcsv&download`"
|
||||
@@ -36,7 +36,7 @@
|
||||
>PDF</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="filter"
|
||||
@@ -48,7 +48,7 @@
|
||||
</v-toolbar>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
|
||||
|
||||
<v-menu v-if="writeaccess"
|
||||
v-model="contextMenuShow"
|
||||
:position-x="contextMenuX"
|
||||
@@ -63,7 +63,7 @@
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
|
||||
<v-card class="mb-5" flat>
|
||||
<v-card-title class="text-overline">
|
||||
Comments
|
||||
@@ -77,7 +77,7 @@
|
||||
>
|
||||
<v-icon small>mdi-square-edit-outline</v-icon>
|
||||
</v-btn>
|
||||
|
||||
|
||||
<v-btn v-else
|
||||
class="ml-3"
|
||||
small
|
||||
@@ -89,7 +89,7 @@
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-card-text v-if="editRemarks">
|
||||
<v-textarea
|
||||
v-model="remarks"
|
||||
@@ -100,9 +100,9 @@
|
||||
rows="1"
|
||||
></v-textarea>
|
||||
</v-card-text>
|
||||
|
||||
|
||||
<v-card-text v-else v-html="$options.filters.markdown(remarks || '*(nil)*')"></v-card-text>
|
||||
|
||||
|
||||
</v-card>
|
||||
|
||||
<v-data-table
|
||||
@@ -121,7 +121,7 @@
|
||||
<template v-slot:item.srss="{item}">
|
||||
<v-icon small :title="srssInfo(item)">{{srssIcon(item)}}</v-icon>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-slot:item.sequence="{item, value}">
|
||||
<v-edit-dialog v-if="writeaccess"
|
||||
large
|
||||
@@ -253,7 +253,7 @@
|
||||
<template v-slot:item.azimuth="props">
|
||||
<span style="white-space:nowrap;">{{ props.value.toFixed(2) }} °</span>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-slot:item.remarks="{item}">
|
||||
<v-text-field v-if="writeaccess && edit && edit.sequence == item.sequence && edit.key == 'remarks'"
|
||||
type="text"
|
||||
@@ -322,9 +322,9 @@
|
||||
</v-edit-dialog>
|
||||
<span v-else>{{ Math.round(lagAfter(item) / (60*1000)) }} min</span>
|
||||
</template>
|
||||
|
||||
|
||||
</v-data-table>
|
||||
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
@@ -339,7 +339,7 @@ import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: "Plan",
|
||||
|
||||
|
||||
components: {
|
||||
},
|
||||
|
||||
@@ -421,7 +421,7 @@ export default {
|
||||
|
||||
plannerConfig: null,
|
||||
shiftAll: false, // Shift all sequences checkbox
|
||||
|
||||
|
||||
// Context menu stuff
|
||||
contextMenuShow: false,
|
||||
contextMenuX: 0,
|
||||
@@ -433,17 +433,17 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['user', 'writeaccess', 'loading', 'serverEvent'])
|
||||
},
|
||||
|
||||
|
||||
watch: {
|
||||
|
||||
async edit (newVal, oldVal) {
|
||||
if (newVal === null && oldVal !== null) {
|
||||
const item = this.items.find(i => i.sequence == oldVal.sequence);
|
||||
|
||||
|
||||
// Get around this Vuetify ‘feature’
|
||||
// https://github.com/vuetifyjs/vuetify/issues/4144
|
||||
if (oldVal.value === null) oldVal.value = "";
|
||||
|
||||
|
||||
if (item) {
|
||||
if (item[oldVal.key] != oldVal.value) {
|
||||
if (oldVal.key == "lagAfter") {
|
||||
@@ -453,29 +453,29 @@ export default {
|
||||
// Convert knots to metres per second
|
||||
oldVal.value = oldVal.value*(1.852/3.6);
|
||||
}
|
||||
|
||||
|
||||
if (await this.saveItem(oldVal)) {
|
||||
item[oldVal.key] = oldVal.value;
|
||||
} else {
|
||||
this.edit = oldVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
async serverEvent (event) {
|
||||
if (event.channel == "planned_lines" && event.payload.pid == this.$route.params.project) {
|
||||
|
||||
|
||||
// Ignore non-ops
|
||||
/*
|
||||
if (event.payload.old === null && event.payload.new === null) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
if (!this.loading && !this.queuedReload) {
|
||||
// Do not force a non-cached response if refreshing as a result
|
||||
// of an event notification. We will assume that the server has
|
||||
@@ -491,34 +491,34 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
queuedReload (newVal, oldVal) {
|
||||
if (newVal && !oldVal && !this.loading) {
|
||||
this.getPlannedLines();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
loading (newVal, oldVal) {
|
||||
if (!newVal && oldVal && this.queuedReload) {
|
||||
this.getPlannedLines();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
itemsPerPage (newVal, oldVal) {
|
||||
localStorage.setItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`, newVal);
|
||||
},
|
||||
|
||||
|
||||
user (newVal, oldVal) {
|
||||
this.itemsPerPage = Number(localStorage.getItem(`dougal/prefs/${this.user?.name}/${this.$route.params.project}/${this.$options.name}/items-per-page`)) || 25;
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
|
||||
suntimes (line) {
|
||||
const oneday = 86400000;
|
||||
|
||||
|
||||
function isDay (srss, ts, lat, lng) {
|
||||
if (isNaN(srss.sunriseEnd) || isNaN(srss.sunsetStart)) {
|
||||
// Between March and September
|
||||
@@ -541,31 +541,31 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let {ts0, ts1} = line;
|
||||
const [ lng0, lat0 ] = line.geometry.coordinates[0];
|
||||
const [ lng1, lat1 ] = line.geometry.coordinates[1];
|
||||
|
||||
|
||||
if (ts1-ts0 > oneday) {
|
||||
console.warn("Cannot provide reliable sunrise / sunset times for lines over 24 hr in this version");
|
||||
//return null;
|
||||
}
|
||||
|
||||
|
||||
const srss0 = suncalc.getTimes(ts0, lat0, lng0);
|
||||
const srss1 = suncalc.getTimes(ts1, lat1, lng1);
|
||||
|
||||
srss0.prevDay = suncalc.getTimes(new Date(ts0.valueOf()-oneday), lat0, lng0);
|
||||
srss1.nextDay = suncalc.getTimes(new Date(ts1.valueOf()+oneday), lat1, lng1);
|
||||
|
||||
|
||||
srss0.isDay = isDay(srss0, ts0, lat0, lng0);
|
||||
srss1.isDay = isDay(srss1, ts1, lat1, lng1);
|
||||
|
||||
|
||||
return {
|
||||
ts0: srss0,
|
||||
ts1: srss1
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
srssIcon (line) {
|
||||
const srss = this.suntimes(line);
|
||||
const moon = suncalc.getMoonIllumination(line.ts0);
|
||||
@@ -585,7 +585,7 @@ export default {
|
||||
: 'mdi-moon-waning-crescent'
|
||||
: 'mdi-theme-light-dark';
|
||||
},
|
||||
|
||||
|
||||
srssMoonPhase (line) {
|
||||
const ts = new Date((Number(line.ts0)+Number(line.ts1))/2);
|
||||
const moon = suncalc.getMoonIllumination(ts);
|
||||
@@ -601,11 +601,11 @@ export default {
|
||||
? 'Waning gibbous moon'
|
||||
: 'Waning crescent moon';
|
||||
},
|
||||
|
||||
|
||||
srssInfo (line) {
|
||||
const srss = this.suntimes(line);
|
||||
const text = [];
|
||||
|
||||
|
||||
try {
|
||||
text.push(`Sunset at\t${srss.ts0.prevDay.sunset.toISOString().substr(0, 16)}Z (FSP)`);
|
||||
text.push(`Sunrise at\t${srss.ts0.sunrise.toISOString().substr(0, 16)}Z (FSP)`);
|
||||
@@ -622,11 +622,11 @@ export default {
|
||||
console.log("ERROR", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!srss.ts0.isDay || !srss.ts1.isDay) {
|
||||
text.push(this.srssMoonPhase(line));
|
||||
}
|
||||
|
||||
|
||||
return text.join("\n");
|
||||
},
|
||||
|
||||
@@ -647,7 +647,7 @@ export default {
|
||||
const v = item.length / ((item.ts1-item.ts0)/1000); // m/s
|
||||
return v*3.6/1.852;
|
||||
},
|
||||
|
||||
|
||||
contextMenu (e, {item}) {
|
||||
e.preventDefault();
|
||||
this.contextMenuShow = false;
|
||||
@@ -656,7 +656,7 @@ export default {
|
||||
this.contextMenuItem = item;
|
||||
this.$nextTick( () => this.contextMenuShow = true );
|
||||
},
|
||||
|
||||
|
||||
async deletePlannedSequence () {
|
||||
console.log("Delete sequence", this.contextMenuItem.sequence);
|
||||
const url = `/project/${this.$route.params.project}/plan/${this.contextMenuItem.sequence}`;
|
||||
@@ -664,7 +664,7 @@ export default {
|
||||
await this.api([url, init]);
|
||||
await this.getPlannedLines();
|
||||
},
|
||||
|
||||
|
||||
editItem (item, key, value) {
|
||||
this.edit = {
|
||||
sequence: item.sequence,
|
||||
@@ -672,10 +672,10 @@ export default {
|
||||
value: value === undefined ? item[key] : value
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async saveItem (edit) {
|
||||
if (!edit) return;
|
||||
|
||||
|
||||
try {
|
||||
const url = `/project/${this.$route.params.project}/plan/${edit.sequence}`;
|
||||
const init = {
|
||||
@@ -684,7 +684,7 @@ export default {
|
||||
[edit.key]: edit.value
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let res;
|
||||
await this.api([url, init, (e, r) => res = r]);
|
||||
return res && res.ok;
|
||||
@@ -692,7 +692,7 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async saveRemarks () {
|
||||
const url = `/project/${this.$route.params.project}/info/plan/remarks`;
|
||||
let res;
|
||||
@@ -735,12 +735,12 @@ export default {
|
||||
"defaultLineChangeDuration": 36
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async getPlannerRemarks () {
|
||||
const url = `/project/${this.$route.params.project}/info/plan/remarks`;
|
||||
this.remarks = await this.api([url]) || "";
|
||||
},
|
||||
|
||||
|
||||
async getSequences () {
|
||||
const url = `/project/${this.$route.params.project}/sequence`;
|
||||
this.sequences = await this.api([url]) || [];
|
||||
|
||||
@@ -13,9 +13,9 @@ module.exports = async function (req, res, next) {
|
||||
"text/html": html,
|
||||
"application/pdf": pdf
|
||||
};
|
||||
|
||||
|
||||
const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers));
|
||||
|
||||
|
||||
if (mimetype) {
|
||||
res.set("Content-Type", mimetype);
|
||||
await handlers[mimetype](req, res, next);
|
||||
|
||||
@@ -4,13 +4,13 @@ const { plan } = require('../../../../lib/db');
|
||||
const json = async function (req, res, next) {
|
||||
try {
|
||||
const response = await plan.list(req.params.project, req.query);
|
||||
|
||||
|
||||
if ("download" in req.query || "d" in req.query) {
|
||||
const extension = "html";
|
||||
const filename = `${req.params.project.toUpperCase()}-Plan.${extension}`;
|
||||
res.set("Content-Disposition", `attachment; filename="${filename}"`);
|
||||
}
|
||||
|
||||
|
||||
const transforms = (i) => {
|
||||
i.lon0 = Number(((i?.geometry?.coordinates||[])[0]||[])[0]).toFixed(6)*1;
|
||||
i.lat0 = Number(((i?.geometry?.coordinates||[])[0]||[])[1]).toFixed(6)*1;
|
||||
@@ -22,14 +22,14 @@ const json = async function (req, res, next) {
|
||||
delete i.meta;
|
||||
return i;
|
||||
};
|
||||
|
||||
|
||||
const csv = new AsyncParser({transforms}, {objectMode: true});
|
||||
csv.processor.on('error', (err) => { throw err; });
|
||||
csv.processor.on('end', () => {
|
||||
res.end();
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
res.status(200);
|
||||
csv.processor.pipe(res);
|
||||
response.forEach(row => csv.input.push(row));
|
||||
|
||||
@@ -20,10 +20,10 @@ const html = async function (req, res, next) {
|
||||
delete feature.properties.geometry;
|
||||
return feature;
|
||||
});
|
||||
|
||||
|
||||
// const template = (await configuration.get(req.params.project, "sse/templates/0/template")) || defaultTemplatePath;
|
||||
const template = defaultTemplatePath;
|
||||
|
||||
|
||||
const mapConfig = {
|
||||
size: { width: 500, height: 500 },
|
||||
layers: [
|
||||
@@ -52,18 +52,18 @@ const html = async function (req, res, next) {
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
const map = leafletMap(mapConfig);
|
||||
|
||||
|
||||
const data = {
|
||||
projectId: req.params.project,
|
||||
info: planInfo,
|
||||
lines,
|
||||
map: await map.getImageData()
|
||||
}
|
||||
|
||||
|
||||
const response = await render(data, template);
|
||||
|
||||
|
||||
if ("download" in req.query || "d" in req.query) {
|
||||
const extension = "html";
|
||||
const filename = `${req.params.project.toUpperCase()}-Plan.${extension}`;
|
||||
|
||||
@@ -13,9 +13,9 @@ module.exports = async function (req, res, next) {
|
||||
"text/html": html,
|
||||
"application/pdf": pdf
|
||||
};
|
||||
|
||||
|
||||
const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers));
|
||||
|
||||
|
||||
if (mimetype) {
|
||||
res.set("Content-Type", mimetype);
|
||||
await handlers[mimetype](req, res, next);
|
||||
|
||||
@@ -31,8 +31,8 @@ const pdf = async function (req, res, next) {
|
||||
});
|
||||
// const template = (await configuration.get(req.params.project, "sse/templates/0/template")) || defaultTemplatePath;
|
||||
const template = defaultTemplatePath;
|
||||
|
||||
|
||||
|
||||
|
||||
const mapConfig = {
|
||||
size: { width: 500, height: 500 },
|
||||
layers: [
|
||||
@@ -61,21 +61,21 @@ const pdf = async function (req, res, next) {
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
const map = leafletMap(mapConfig);
|
||||
|
||||
|
||||
const data = {
|
||||
projectId: req.params.project,
|
||||
info: planInfo,
|
||||
lines,
|
||||
map: await map.getImageData()
|
||||
}
|
||||
|
||||
|
||||
const html = await render(data, template);
|
||||
|
||||
|
||||
await fs.writeFile(fname, html);
|
||||
const pdf = Buffer.from(await url2pdf("file://"+fname), "base64");
|
||||
|
||||
|
||||
if ("download" in req.query || "d" in req.query) {
|
||||
const extension = "pdf";
|
||||
const filename = `${req.params.project.toUpperCase()}-Plan.${extension}`;
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = async function (req, res, next) {
|
||||
if (req.query.remote) {
|
||||
// We're being asked to fetch a remote feed
|
||||
// NOTE: No, we don't limit what feeds the user can fetch
|
||||
|
||||
|
||||
const r = await fetch(req.query.remote);
|
||||
if (r && r.ok) {
|
||||
res.set("Content-Type", "application/xml");
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = async function (req, res, next) {
|
||||
try {
|
||||
const json = await sequence.get(req.params.project, req.params.sequence, req.query);
|
||||
const geometry = req.query.geometry || "geometrypreplot";
|
||||
|
||||
|
||||
const geojson = {
|
||||
type: "FeatureCollection",
|
||||
features: json.map(feature => {
|
||||
@@ -17,7 +17,7 @@ module.exports = async function (req, res, next) {
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
res.status(200).send(geojson);
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
@@ -7,9 +7,9 @@ module.exports = async function (req, res, next) {
|
||||
"application/json": json,
|
||||
"application/geo+json": geojson,
|
||||
};
|
||||
|
||||
|
||||
const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers));
|
||||
|
||||
|
||||
if (mimetype) {
|
||||
res.set("Content-Type", mimetype);
|
||||
await handlers[mimetype](req, res, next);
|
||||
|
||||
@@ -7,9 +7,9 @@ module.exports = async function (req, res, next) {
|
||||
"application/json": json,
|
||||
"application/geo+json": geojson,
|
||||
};
|
||||
|
||||
|
||||
const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers));
|
||||
|
||||
|
||||
if (mimetype) {
|
||||
res.set("Content-Type", mimetype);
|
||||
await handlers[mimetype](req, res, next);
|
||||
|
||||
@@ -22,37 +22,37 @@ class DetectSOLEOL {
|
||||
* first element to a falsy value, thus bootstrapping the process.
|
||||
*/
|
||||
static MAX_QUEUE_SIZE = 125000;
|
||||
|
||||
|
||||
queue = [];
|
||||
|
||||
|
||||
async processQueue () {
|
||||
while (this.queue.length > 1) {
|
||||
if (this.queue[0].isPending) {
|
||||
setImmediate(() => this.processQueue());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const prev = this.queue.shift();
|
||||
const cur = this.queue[0];
|
||||
|
||||
|
||||
const sequence = Number(cur._sequence);
|
||||
|
||||
|
||||
try {
|
||||
|
||||
|
||||
if (prev.lineName == cur.lineName && prev._sequence == cur._sequence &&
|
||||
prev.lineStatus != "online" && cur.lineStatus == "online" && sequence) {
|
||||
// console.log("TRANSITION TO ONLINE", prev, cur);
|
||||
|
||||
|
||||
// Check if there are already FSP, FGSP events for this sequence
|
||||
const projectId = await schema2pid(cur._schema);
|
||||
const sequenceEvents = await event.list(projectId, {sequence});
|
||||
|
||||
|
||||
const labels = ["FSP", "FGSP"].filter(l => !sequenceEvents.find(i => i.labels.includes(l)));
|
||||
|
||||
|
||||
if (labels.includes("FSP")) {
|
||||
// At this point labels contains either FSP only or FSP + FGSP,
|
||||
// depending on whether a FGSP event has already been entered.
|
||||
|
||||
|
||||
const remarks = `SEQ ${cur._sequence}, SOL ${cur.lineName}, BSP: ${(cur.speed*3.6/1.852).toFixed(1)} kt, Water depth: ${Number(cur.waterDepth).toFixed(0)} m.`;
|
||||
const payload = {
|
||||
type: "sequence",
|
||||
@@ -61,7 +61,7 @@ class DetectSOLEOL {
|
||||
remarks,
|
||||
labels
|
||||
}
|
||||
|
||||
|
||||
// console.log(projectId, payload);
|
||||
await event.post(projectId, payload);
|
||||
} else {
|
||||
@@ -70,17 +70,17 @@ class DetectSOLEOL {
|
||||
}
|
||||
} else if (prev.lineStatus == "online" && cur.lineStatus != "online") {
|
||||
// console.log("TRANSITION TO OFFLINE", prev, cur);
|
||||
|
||||
|
||||
// Check if there are already LSP, LGSP events for this sequence
|
||||
const projectId = await schema2pid(prev._schema);
|
||||
const sequenceEvents = await event.list(projectId, {sequence});
|
||||
|
||||
|
||||
const labels = ["LSP", "LGSP"].filter(l => !sequenceEvents.find(i => i.labels.includes(l)));
|
||||
|
||||
|
||||
if (labels.includes("LSP")) {
|
||||
// At this point labels contains either LSP only or LSP + LGSP,
|
||||
// depending on whether a LGSP event has already been entered.
|
||||
|
||||
|
||||
const remarks = `SEQ ${prev._sequence}, EOL ${prev.lineName}, BSP: ${(prev.speed*3.6/1.852).toFixed(1)} kt, Water depth: ${Number(prev.waterDepth).toFixed(0)} m.`;
|
||||
const payload = {
|
||||
type: "sequence",
|
||||
@@ -89,7 +89,7 @@ class DetectSOLEOL {
|
||||
remarks,
|
||||
labels
|
||||
}
|
||||
|
||||
|
||||
// console.log(projectId, payload);
|
||||
await event.post(projectId, payload);
|
||||
} else {
|
||||
@@ -107,7 +107,7 @@ class DetectSOLEOL {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async run (data) {
|
||||
if (!data || data.channel !== "realtime") {
|
||||
return;
|
||||
@@ -116,11 +116,11 @@ class DetectSOLEOL {
|
||||
if (!(data.payload && data.payload.new && data.payload.new.meta)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const meta = data.payload.new.meta;
|
||||
|
||||
|
||||
if (this.queue.length < DetectSOLEOL.MAX_QUEUE_SIZE) {
|
||||
|
||||
|
||||
this.queue.push({
|
||||
isPending: this.queue.length,
|
||||
_schema: meta._schema,
|
||||
@@ -133,12 +133,12 @@ class DetectSOLEOL {
|
||||
speed: meta.speed,
|
||||
waterDepth: meta.waterDepth
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
// FIXME Change to alert
|
||||
console.error("DetectSOLEOL queue full at", this.queue.length);
|
||||
}
|
||||
|
||||
|
||||
this.processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ function start () {
|
||||
await handler.run(data);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
console.log("Events manager started.", handlers.length, "active handlers");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
|
||||
const { pool } = require('../../connection');
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
|
||||
const { setSurvey } = require('../../connection');
|
||||
|
||||
|
||||
@@ -11,17 +11,17 @@ async function patch (projectId, line, payload, opts = {}) {
|
||||
// NOTE on the "complete" query: if complete is true it sets *only* virgin points to NTBA=true,
|
||||
// but if complete is false it sets all points on the line to NTBA=false.
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
transaction.begin(client);
|
||||
for (const key in payload) {
|
||||
const text = patchables[key];
|
||||
const values = [ line, payload[key] ];
|
||||
|
||||
|
||||
if (!text) {
|
||||
throw {status: 400, message: "Invalid patch" };
|
||||
}
|
||||
|
||||
|
||||
await client.query(text, values);
|
||||
}
|
||||
transaction.commit(client);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// FIXME This code is in painful need of refactoring
|
||||
|
||||
|
||||
const { setSurvey, transaction, pool } = require('../connection');
|
||||
|
||||
let last_tstamp = 0;
|
||||
@@ -67,7 +67,7 @@ async function getNearestPreplot (candidates) {
|
||||
}
|
||||
|
||||
async function getNearestOfflinePreplot (candidates) {
|
||||
|
||||
|
||||
const queries = candidates.map( c=> {
|
||||
let text, values;
|
||||
if ("latitude" in candidates[0] && "longitude" in candidates[0]) {
|
||||
@@ -288,7 +288,7 @@ async function save (navData, opts = {}) {
|
||||
const now = Date.now();
|
||||
const do_save = !opts.offline_survey_detect_interval ||
|
||||
(now - last_tstamp) >= opts.offline_survey_detect_interval;
|
||||
|
||||
|
||||
if (do_save) {
|
||||
const configs = await getAllProjectConfigs();
|
||||
const candidates = configs.map(c => Object.assign({}, navData, {_schema: c.schema}));
|
||||
|
||||
@@ -3,9 +3,9 @@ const { getLineName } = require('./lib');
|
||||
|
||||
async function patch (projectId, sequence, payload, opts = {}) {
|
||||
const client = await setSurvey(projectId);
|
||||
|
||||
|
||||
sequence = Number(sequence);
|
||||
|
||||
|
||||
/*
|
||||
* Takes a Date object and returns the epoch
|
||||
* in seconds
|
||||
@@ -13,7 +13,7 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
function epoch (ts) {
|
||||
return Number(ts)/1000;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Shift sequence ts0, ts1 by dt0, dt1 respectively
|
||||
* for only one sequence
|
||||
@@ -24,10 +24,10 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
SET ts0 = ts0 + make_interval(secs => $2), ts1 = ts1 + make_interval(secs => $3)
|
||||
WHERE sequence = $1
|
||||
`;
|
||||
|
||||
|
||||
return await client.query(text, [sequence, dt0, dt1]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Shift sequence ts0, ts1 by dt0, dt1 respectively
|
||||
* for all sequences >= sequence
|
||||
@@ -38,15 +38,15 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
SET ts0 = ts0 + make_interval(secs => $2), ts1 = ts1 + make_interval(secs => $3)
|
||||
WHERE sequence >= $1
|
||||
`;
|
||||
|
||||
|
||||
return await client.query(text, [sequence, dt0, dt1]);
|
||||
}
|
||||
|
||||
try {
|
||||
transaction.begin(client);
|
||||
|
||||
|
||||
let deltatime;
|
||||
|
||||
|
||||
const r0 = await client.query("SELECT * FROM planned_lines_summary WHERE sequence >= $1 ORDER BY sequence ASC LIMIT 2;", [sequence]);
|
||||
const seq = (r0?.rows || [])[0];
|
||||
if (!seq || seq?.sequence != sequence) {
|
||||
@@ -54,38 +54,38 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
}
|
||||
const seq1 = r0.rows[1];
|
||||
const speed = seq.length/(epoch(seq.ts1)-epoch(seq.ts0)); // m/s
|
||||
|
||||
|
||||
if ("ts0" in payload || "ts1" in payload) {
|
||||
/*
|
||||
* Change in start or end times
|
||||
*/
|
||||
|
||||
|
||||
deltatime = "ts0" in payload
|
||||
? (epoch(new Date(payload.ts0)) - epoch(seq.ts0))
|
||||
: (epoch(new Date(payload.ts1)) - epoch(seq.ts1));
|
||||
// Now shift all sequences >= this one by deltatime
|
||||
await shiftSequences(sequence, deltatime, deltatime);
|
||||
|
||||
|
||||
} else if ("speed" in payload) {
|
||||
/*
|
||||
* Change in acquisition speed (m/s)
|
||||
*/
|
||||
|
||||
|
||||
// Check that speed is sensible
|
||||
if (payload.speed < 0.1) {
|
||||
throw {status: 400, message: "Speed must be at least 0.1 m/s"};
|
||||
}
|
||||
|
||||
|
||||
deltatime = epoch(seq.ts0) + (seq.length/payload.speed) - epoch(seq.ts1);
|
||||
// Fix seq.ts0, shift set.ts1 += deltatime, plus all sequences > this one
|
||||
await shiftSequence(sequence, 0, deltatime);
|
||||
await shiftSequences(sequence+1, deltatime, deltatime);
|
||||
|
||||
|
||||
} else if ("fsp" in payload) {
|
||||
/*
|
||||
* Change of FSP
|
||||
*/
|
||||
|
||||
|
||||
// Keep ts1, adjust fsp and ts0 according to speed
|
||||
// ts0' = (shot_distance * delta_shots / speed) + ts0
|
||||
const sign = Math.sign(seq.lsp-seq.fsp);
|
||||
@@ -96,16 +96,16 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
WHERE sequence = $1;
|
||||
`;
|
||||
await client.query(text, [sequence, payload.fsp, new Date(ts0*1000)]);
|
||||
|
||||
|
||||
} else if ("lsp" in payload) {
|
||||
/*
|
||||
* Change of LSP
|
||||
*/
|
||||
|
||||
|
||||
// Keep ts0, adjust lsp and ts1 according to speed
|
||||
// Calculate deltatime from ts1'-ts1
|
||||
// Shift all sequences > this one by deltatime
|
||||
|
||||
|
||||
// deltatime = (shot_distance * delta_shots / speed)
|
||||
// ts1' = deltatime + ts1
|
||||
const sign = Math.sign(seq.lsp-seq.fsp);
|
||||
@@ -118,12 +118,12 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
`;
|
||||
await client.query(text, [sequence, payload.lsp, new Date(ts1*1000)]);
|
||||
shiftSequences(sequence+1, deltatime, deltatime);
|
||||
|
||||
|
||||
} else if ("lagAfter" in payload && seq1) {
|
||||
/*
|
||||
* Change of line change time
|
||||
*/
|
||||
|
||||
|
||||
// Check that the value is sensible
|
||||
if (payload.lagAfter < 0) {
|
||||
throw {status: 400, message: "Line change time cannot be negative"};
|
||||
@@ -134,15 +134,15 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
const ts0 = epoch(seq.ts1) + payload.lagAfter; // lagAfter is in seconds
|
||||
deltatime = ts0 - epoch(seq1.ts0);
|
||||
shiftSequences(sequence+1, deltatime, deltatime);
|
||||
|
||||
|
||||
} else if ("sequence" in payload) {
|
||||
/*
|
||||
* Renumbering / reshuffling of sequences
|
||||
*/
|
||||
|
||||
|
||||
// NOTE: This does not enforce consecutive sequences, because sometimes
|
||||
// there is a need for those (don't ask).
|
||||
|
||||
|
||||
// Renumber or reorder sequences
|
||||
const r1 = await client.query("SELECT sequence FROM planned_lines ORDER BY sequence;");
|
||||
const sequences = (r1?.rows||[]).map(i => i.sequence);
|
||||
@@ -156,14 +156,14 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
`;
|
||||
await client.query("SET CONSTRAINTS planned_lines_pkey DEFERRED;");
|
||||
await client.query(text, [payload.sequence]);
|
||||
|
||||
|
||||
// And now we need to rename all affected lines
|
||||
const r2 = await client.query("SELECT * FROM planned_lines WHERE sequence > $1 ORDER BY sequence;", [payload.sequence]);
|
||||
for (let row in r2.rows) {
|
||||
const name = await getLineName(client, projectId, row);
|
||||
await client.query("UPDATE planned_lines SET name = $2 WHERE sequence = $1", [row.sequence, name]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// Now update just this sequence
|
||||
const text = `
|
||||
@@ -172,26 +172,26 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
WHERE sequence = $1;
|
||||
`;
|
||||
await client.query(text, [sequence, payload.sequence]);
|
||||
|
||||
|
||||
// And rename
|
||||
const r3 = await client.query("SELECT * FROM planned_lines WHERE sequence = $1 ORDER BY sequence;", [payload.sequence]);
|
||||
const name = await getLineName(client, projectId, r3.rows[0]);
|
||||
await client.query("UPDATE planned_lines SET name = $2 WHERE sequence = $1", [payload.sequence, name]);
|
||||
|
||||
|
||||
|
||||
} else if (["name", "remarks", "meta"].some(i => i in payload)) {
|
||||
/*
|
||||
* Change in various other attributes that do not affect
|
||||
* other sequences
|
||||
*/
|
||||
|
||||
|
||||
// NOTE Magic! If name is empty, we generate one.
|
||||
// Can be used for going back to a default name after it's been
|
||||
// changed manually.
|
||||
if (payload.name === "") {
|
||||
payload.name = await getLineName(client, projectId, r0.rows[0]);
|
||||
}
|
||||
|
||||
|
||||
// Change the relevant attribute
|
||||
const text = `
|
||||
UPDATE planned_lines
|
||||
@@ -202,7 +202,7 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
WHERE sequence = $1;
|
||||
`;
|
||||
await client.query(text, [sequence, payload.name, payload.remarks, payload.meta]);
|
||||
|
||||
|
||||
} else {
|
||||
throw { status: 400, message: "Bad request"};
|
||||
}
|
||||
@@ -210,7 +210,7 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
transaction.commit(client);
|
||||
} catch (err) {
|
||||
transaction.rollback(client);
|
||||
|
||||
|
||||
if (err.code == 23503) {
|
||||
if (err.constraint == "planned_lines_line_fsp_class_fkey" || err.constraint == "planned_lines_line_lsp_class_fkey") {
|
||||
throw {status: 400, message: "Attempt to shoot a non-existent shotpoint"};
|
||||
|
||||
@@ -9,17 +9,17 @@ async function patch (projectId, sequence, payload, opts = {}) {
|
||||
"meta": "UPDATE raw_lines SET meta = $2 WHERE sequence = $1;",
|
||||
"meta_final": "UPDATE final_lines SET meta = $2 WHERE sequence = $1;"
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
transaction.begin(client);
|
||||
for (const key in payload) {
|
||||
const text = patchables[key];
|
||||
const values = [ sequence, payload[key] ];
|
||||
|
||||
|
||||
if (!text) {
|
||||
throw {status: 400, message: "Invalid patch" };
|
||||
}
|
||||
|
||||
|
||||
await client.query(text, values);
|
||||
}
|
||||
transaction.commit(client);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
class NavHeaderError extends Error {
|
||||
constructor (message, payload) {
|
||||
super (message);
|
||||
|
||||
@@ -9,11 +9,11 @@ function leafletMap (cfg) {
|
||||
const bbox = cfg.bbox || L.geoJSON(cfg.layers.map(i => i.features)).getBounds();
|
||||
map.fitBounds(bbox);
|
||||
map.setSize(cfg.size?.width || 500, cfg.size?.height || 500);
|
||||
|
||||
|
||||
for (let layer of cfg.layers) {
|
||||
L.geoJSON(layer.features, layer.options).addTo(map);
|
||||
}
|
||||
|
||||
|
||||
map.fitBounds(bbox); // again
|
||||
|
||||
return map;
|
||||
|
||||
@@ -53,7 +53,7 @@ function njkTimestamp (arg, precision = "seconds") {
|
||||
if (!isNaN(ts)) {
|
||||
str = ts.toISOString();
|
||||
}
|
||||
|
||||
|
||||
if (str) {
|
||||
str = str.replace("T", " ");
|
||||
if (precision.toLowerCase().startsWith("s")) {
|
||||
@@ -80,7 +80,7 @@ function njkMarkdownInline (str) {
|
||||
}
|
||||
|
||||
async function render (data, template) {
|
||||
|
||||
|
||||
const nenv = nunjucks.configure(Path.dirname(template), {autoescape: false, lstripBlocks: false, trimBlocks: false});
|
||||
nenv.addFilter('find', njkFind);
|
||||
nenv.addFilter('unique', njkUnique);
|
||||
@@ -90,7 +90,7 @@ async function render (data, template) {
|
||||
nenv.addFilter('timestamp', njkTimestamp);
|
||||
nenv.addFilter('markdown', njkMarkdown);
|
||||
nenv.addFilter('markdownInline', njkMarkdownInline);
|
||||
|
||||
|
||||
const view = nenv.render(Path.basename(template), data);
|
||||
|
||||
return view;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
|
||||
|
||||
|
||||
function transform (events, sequences, opts = {}) {
|
||||
|
||||
|
||||
const dgl = !!opts.projectId;
|
||||
const exportQC = opts.exportQC !== false;
|
||||
const exportGeneral = opts.exportGeneral !== false;
|
||||
const exportMissing = opts.exportMissing !== false;
|
||||
const missingAsEvent = opts.missingAsEvent;
|
||||
|
||||
|
||||
const output = {
|
||||
DglProjectId: opts.projectId,
|
||||
Sequences: [],
|
||||
DglCreatedOn: dgl && new Date()
|
||||
};
|
||||
|
||||
|
||||
// NOTE: Events come in descending chronological
|
||||
// order from the server.
|
||||
for (const event of events.reverse()) {
|
||||
@@ -23,7 +23,7 @@ function transform (events, sequences, opts = {}) {
|
||||
if (!sequence) {
|
||||
throw Error(`Sequence ${SequenceNumber} not found in sequence list`);
|
||||
}
|
||||
|
||||
|
||||
let SequenceObject = output.Sequences.find(s => s.SequenceNumber == SequenceNumber);
|
||||
if (!SequenceObject) {
|
||||
SequenceObject = {
|
||||
@@ -47,10 +47,10 @@ function transform (events, sequences, opts = {}) {
|
||||
}
|
||||
SequenceObject.DglSequenceComments.push(i);
|
||||
});
|
||||
|
||||
|
||||
output.Sequences.push(SequenceObject);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("FSP")) {
|
||||
const entry = {
|
||||
"EntryType": "Start of line recording",
|
||||
@@ -65,10 +65,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("FGSP")) {
|
||||
const entry = {
|
||||
"EntryType": "Start good",
|
||||
@@ -77,10 +77,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("FCSP")) {
|
||||
const entry = {
|
||||
"EntryType": "Start charged",
|
||||
@@ -89,10 +89,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("LGFFSP")) {
|
||||
const entry = {
|
||||
"EntryType": "Last good Full Fold",
|
||||
@@ -101,10 +101,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("LCFFSP")) {
|
||||
const entry = {
|
||||
"EntryType": "Last charged Full Fold",
|
||||
@@ -113,10 +113,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("FDSP")) {
|
||||
const entry = {
|
||||
"EntryType": "Midnight",
|
||||
@@ -125,10 +125,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("LCSP")) {
|
||||
const entry = {
|
||||
"EntryType": "End charged",
|
||||
@@ -137,10 +137,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("LGSP")) {
|
||||
const entry = {
|
||||
"EntryType": "End good",
|
||||
@@ -149,10 +149,10 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
if (event.labels.includes("LSP")) {
|
||||
const entry = {
|
||||
"EntryType": "End of line recording",
|
||||
@@ -165,12 +165,12 @@ function transform (events, sequences, opts = {}) {
|
||||
"ShotPointId": event.point,
|
||||
"Time": event.tstamp
|
||||
}
|
||||
|
||||
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
|
||||
|
||||
// Dougal QC data
|
||||
|
||||
|
||||
if (exportQC) {
|
||||
if (event.labels.includes("QC")) {
|
||||
if (!event.labels.includes("QCAccepted")) {
|
||||
@@ -184,7 +184,7 @@ function transform (events, sequences, opts = {}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (exportGeneral) {
|
||||
// These are labels that we have already (potentially) exported
|
||||
const excluded = [
|
||||
@@ -203,13 +203,13 @@ function transform (events, sequences, opts = {}) {
|
||||
SequenceObject.Entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const SequenceObject of output.Sequences) {
|
||||
const sequence = sequences.find(s => s.sequence == SequenceObject.SequenceNumber);
|
||||
|
||||
|
||||
// If no explicit FSP but there is a FGSP, clone it as FSP.
|
||||
if (!SequenceObject.Entries.find(i => i.EntryTypeId == 3000)) {
|
||||
const fgspIndex = SequenceObject.Entries.findIndex(i => i.EntryTypeId == 3001);
|
||||
@@ -247,7 +247,7 @@ function transform (events, sequences, opts = {}) {
|
||||
SequenceObject.Entries.unshift(fsp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no explicit LSP but there is a LGSP, clone it as LSP.
|
||||
if (!SequenceObject.Entries.find(i => i.EntryTypeId == 3007)) {
|
||||
const lgspIndex = SequenceObject.Entries.findIndex(i => i.EntryTypeId == 3006);
|
||||
@@ -277,17 +277,17 @@ function transform (events, sequences, opts = {}) {
|
||||
SequenceObject.Entries.push(lsp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the missing shots object if not inhibited by the user.
|
||||
if (exportMissing) {
|
||||
// The user also needs to request missing shots from the sequences
|
||||
// endpoint; these are not returned by default.
|
||||
if ("missing_final" in sequence) {
|
||||
SequenceObject.MissingShots = sequence.missing_final.map(s => s.point).sort();
|
||||
|
||||
|
||||
// Add (pseudo-)events for missing shots
|
||||
if (missingAsEvent) {
|
||||
|
||||
|
||||
// Create the ‘dummy’ missing shot events
|
||||
const pseudoevents = SequenceObject.MissingShots.map( point => {
|
||||
return {
|
||||
@@ -295,28 +295,28 @@ function transform (events, sequences, opts = {}) {
|
||||
EntryType: "Missing shot"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const isAscending = SequenceObject.Entries[0].ShotPointId <= SequenceObject.Entries[SequenceObject.Entries.length-1].ShotPointId;
|
||||
|
||||
|
||||
pseudoevents.forEach( pseudoevent => {
|
||||
for (const index in SequenceObject.Entries) {
|
||||
const ShotPointId = SequenceObject.Entries[index].ShotPointId;
|
||||
const slotFound = isAscending
|
||||
? ShotPointId > pseudoevent.ShotPointId
|
||||
: ShotPointId < pseudoevent.ShotPointId;
|
||||
|
||||
|
||||
if (slotFound) {
|
||||
SequenceObject.Entries.splice(index, 0, pseudoevent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
|
||||
|
||||
function dms (lat, lon) {
|
||||
const λh = lat < 0 ? "S" : "N";
|
||||
const φh = lon < 0 ? "W" : "E";
|
||||
|
||||
|
||||
const λn = Math.abs(lat);
|
||||
const φn = Math.abs(lon);
|
||||
|
||||
|
||||
const λi = Math.trunc(λn);
|
||||
const φi = Math.trunc(φn);
|
||||
|
||||
|
||||
const λf = λn - λi;
|
||||
const φf = φn - φi;
|
||||
|
||||
|
||||
const λs = ((λf*3600)%60).toFixed(1);
|
||||
const φs = ((φf*3600)%60).toFixed(1);
|
||||
|
||||
|
||||
const λm = Math.trunc(λf*60);
|
||||
const φm = Math.trunc(φf*60);
|
||||
|
||||
|
||||
const λ =
|
||||
String(λi).padStart(2, "0") + "°" +
|
||||
String(λm).padStart(2, "0") + "'" +
|
||||
String(λs).padStart(4, "0") + '" ' +
|
||||
λh;
|
||||
|
||||
|
||||
const φ =
|
||||
String(φi).padStart(3, "0") + "°" +
|
||||
String(φm).padStart(2, "0") + "'" +
|
||||
String(φs).padStart(4, "0") + '" ' +
|
||||
φh;
|
||||
|
||||
|
||||
return λ+" "+φ;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ const dms = require('./dms');
|
||||
function geometryAsString (item, opts = {}) {
|
||||
const key = "key" in opts ? opts.key : "geometry";
|
||||
const formatDMS = opts.dms;
|
||||
|
||||
|
||||
let str = "";
|
||||
|
||||
|
||||
if (key in item) {
|
||||
const geometry = item[key];
|
||||
if (geometry && "coordinates" in geometry) {
|
||||
@@ -17,7 +17,7 @@ function geometryAsString (item, opts = {}) {
|
||||
str = `${geometry.coordinates[1].toFixed(6)}, ${geometry.coordinates[0].toFixed(6)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (str) {
|
||||
if (opts.url) {
|
||||
if (typeof opts.url === 'string') {
|
||||
@@ -29,7 +29,7 @@ function geometryAsString (item, opts = {}) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const db = require('./db');
|
||||
const channels = require('../lib/db/channels');
|
||||
|
||||
function start (server, pingInterval=30000) {
|
||||
|
||||
|
||||
const wsServer = new ws.Server({ noServer: true });
|
||||
wsServer.on('connection', socket => {
|
||||
socket.alive = true;
|
||||
|
||||
Reference in New Issue
Block a user