From 63254a6bf7ec7a06be8c4adf3ca7b6a6febcd2f1 Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Thu, 8 Oct 2020 16:36:15 +0200 Subject: [PATCH 01/19] Add planner endpoints --- lib/www/server/api/index.js | 11 ++ lib/www/server/api/middleware/index.js | 1 + lib/www/server/api/middleware/plan/delete.js | 17 +++ lib/www/server/api/middleware/plan/get.js | 0 lib/www/server/api/middleware/plan/index.js | 8 + lib/www/server/api/middleware/plan/list.js | 14 ++ lib/www/server/api/middleware/plan/patch.js | 17 +++ lib/www/server/api/middleware/plan/post.js | 17 +++ lib/www/server/api/middleware/plan/put.js | 17 +++ lib/www/server/lib/db/index.js | 1 + lib/www/server/lib/db/plan/delete.js | 24 +++ lib/www/server/lib/db/plan/get.js | 0 lib/www/server/lib/db/plan/index.js | 9 ++ lib/www/server/lib/db/plan/list.js | 41 ++++++ lib/www/server/lib/db/plan/patch.js | 33 +++++ lib/www/server/lib/db/plan/post.js | 147 +++++++++++++++++++ lib/www/server/lib/db/plan/put.js | 36 +++++ 17 files changed, 393 insertions(+) create mode 100644 lib/www/server/api/middleware/plan/delete.js create mode 100644 lib/www/server/api/middleware/plan/get.js create mode 100644 lib/www/server/api/middleware/plan/index.js create mode 100644 lib/www/server/api/middleware/plan/list.js create mode 100644 lib/www/server/api/middleware/plan/patch.js create mode 100644 lib/www/server/api/middleware/plan/post.js create mode 100644 lib/www/server/api/middleware/plan/put.js create mode 100644 lib/www/server/lib/db/plan/delete.js create mode 100644 lib/www/server/lib/db/plan/get.js create mode 100644 lib/www/server/lib/db/plan/index.js create mode 100644 lib/www/server/lib/db/plan/list.js create mode 100644 lib/www/server/lib/db/plan/patch.js create mode 100644 lib/www/server/lib/db/plan/post.js create mode 100644 lib/www/server/lib/db/plan/put.js diff --git a/lib/www/server/api/index.js b/lib/www/server/api/index.js index 9b83487..947952e 100644 --- a/lib/www/server/api/index.js +++ b/lib/www/server/api/index.js @@ -103,6 +103,17 @@ app.map({ // get: [ mw.sequence.get ], patch: [ mw.sequence.patch ], }, + + '/project/:project/plan/': { + get: [ mw.plan.list ], + put: [ mw.plan.put ], + post: [ mw.plan.post ] + }, + '/project/:project/plan/:sequence': { +// get: [ mw.plan.get ], + patch: [ mw.plan.patch ], + delete: [ mw.plan.delete ] + }, // '/project/:project/event/': { get: [ mw.event.cache.get, mw.event.list, mw.event.cache.save ], diff --git a/lib/www/server/api/middleware/index.js b/lib/www/server/api/middleware/index.js index fef5468..e096595 100644 --- a/lib/www/server/api/middleware/index.js +++ b/lib/www/server/api/middleware/index.js @@ -1,5 +1,6 @@ module.exports = { event: require('./event'), + plan: require('./plan'), line: require('./line'), project: require('./project'), sequence: require('./sequence'), diff --git a/lib/www/server/api/middleware/plan/delete.js b/lib/www/server/api/middleware/plan/delete.js new file mode 100644 index 0000000..a4f78b5 --- /dev/null +++ b/lib/www/server/api/middleware/plan/delete.js @@ -0,0 +1,17 @@ + +const { plan } = require('../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const payload = req.body; + + await plan.delete(req.params.project, req.params.sequence); + res.status(201).send(); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/plan/get.js b/lib/www/server/api/middleware/plan/get.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/www/server/api/middleware/plan/index.js b/lib/www/server/api/middleware/plan/index.js new file mode 100644 index 0000000..70d08c5 --- /dev/null +++ b/lib/www/server/api/middleware/plan/index.js @@ -0,0 +1,8 @@ +module.exports = { + list: require('./list'), + get: require('./get'), + post: require('./post'), + put: require('./put'), + patch: require('./patch'), + delete: require('./delete') +}; diff --git a/lib/www/server/api/middleware/plan/list.js b/lib/www/server/api/middleware/plan/list.js new file mode 100644 index 0000000..c29a7dd --- /dev/null +++ b/lib/www/server/api/middleware/plan/list.js @@ -0,0 +1,14 @@ + +const { plan } = require('../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + res.status(200).send(await plan.list(req.params.project, req.query)); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/plan/patch.js b/lib/www/server/api/middleware/plan/patch.js new file mode 100644 index 0000000..a1ea30c --- /dev/null +++ b/lib/www/server/api/middleware/plan/patch.js @@ -0,0 +1,17 @@ + +const { plan } = require('../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const payload = req.body; + + await plan.patch(req.params.project, req.params.sequence, payload); + res.status(201).send(); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/plan/post.js b/lib/www/server/api/middleware/plan/post.js new file mode 100644 index 0000000..d26e283 --- /dev/null +++ b/lib/www/server/api/middleware/plan/post.js @@ -0,0 +1,17 @@ + +const { plan } = require('../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const payload = req.body; + + await plan.post(req.params.project, payload); + res.status(201).send(); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/plan/put.js b/lib/www/server/api/middleware/plan/put.js new file mode 100644 index 0000000..c14376d --- /dev/null +++ b/lib/www/server/api/middleware/plan/put.js @@ -0,0 +1,17 @@ + +const { plan } = require('../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const payload = req.body; + + await plan.put(req.params.project, payload); + res.status(201).send(); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/lib/db/index.js b/lib/www/server/lib/db/index.js index 8167b10..8412e95 100644 --- a/lib/www/server/lib/db/index.js +++ b/lib/www/server/lib/db/index.js @@ -4,6 +4,7 @@ module.exports = { line: require('./line'), sequence: require('./sequence'), event: require('./event'), + plan: require('./plan'), gis: require('./gis'), label: require('./label'), configuration: require('./configuration'), diff --git a/lib/www/server/lib/db/plan/delete.js b/lib/www/server/lib/db/plan/delete.js new file mode 100644 index 0000000..fa24920 --- /dev/null +++ b/lib/www/server/lib/db/plan/delete.js @@ -0,0 +1,24 @@ +const { setSurvey, transaction } = require('../connection'); + +async function del (projectId, sequence, opts = {}) { + + const client = await setSurvey(projectId); + try { + + const text = ` + DELETE + FROM planned_lines + WHERE sequence = $1; + `; + + await client.query(text, [sequence]); + } catch (err) { + throw err; + } finally { + client.release(); + } + + return; +} + +module.exports = del; diff --git a/lib/www/server/lib/db/plan/get.js b/lib/www/server/lib/db/plan/get.js new file mode 100644 index 0000000..e69de29 diff --git a/lib/www/server/lib/db/plan/index.js b/lib/www/server/lib/db/plan/index.js new file mode 100644 index 0000000..8a0f6fc --- /dev/null +++ b/lib/www/server/lib/db/plan/index.js @@ -0,0 +1,9 @@ + +module.exports = { + list: require('./list'), + get: require('./get'), + post: require('./post'), + put: require('./put'), + patch: require('./patch'), + delete: require('./delete') +} diff --git a/lib/www/server/lib/db/plan/list.js b/lib/www/server/lib/db/plan/list.js new file mode 100644 index 0000000..f446f59 --- /dev/null +++ b/lib/www/server/lib/db/plan/list.js @@ -0,0 +1,41 @@ +const { setSurvey } = require('../connection'); + +async function list (projectId, opts = {}) { + const client = await setSurvey(projectId); + +// const sortFields = [ "line", "length", "azimuth", "fsp", "lsp", "num_points", "incr", "remarks" ]; +// const sortKey = opts.sortBy && sortFields.includes(opts.sortBy) && opts.sortBy || "line"; +// const sortDir = opts.sortDesc == "true" ? "DESC" : "ASC"; +// const offset = Math.abs((opts.page-1)*opts.itemsPerPage) || 0; +// const limit = Math.abs(Number(opts.itemsPerPage)) || null; + + const text = ` + SELECT + pl.*, + (SELECT count(*) + FROM preplot_points pp + WHERE pp.line = pl.line + AND pp.class = pl.class + AND pp.point BETWEEN SYMMETRIC pl.fsp AND pl.lsp + ) num_points, + (ts1-ts0) duration, + ST_Distance(pp0.geometry, pp1.geometry) length, + ST_Azimuth(pp0.geometry, pp1.geometry)*180.0/pi() azimuth, + -- speed? Nah + ST_Transform(ST_MakeLine(pp0.geometry, pp1.geometry), 4326)::jsonb geometry + FROM planned_lines pl + INNER JOIN preplot_points pp0 + ON pl.line = pp0.line AND pl.fsp = pp0.point AND pl.class = pp0.class + INNER JOIN preplot_points pp1 + ON pl.line = pp1.line AND pl.lsp = pp1.point AND pl.class = pp1.class + INNER JOIN preplot_points pp + ON pl.line = pp.line AND pp.point BETWEEN SYMMETRIC pl.fsp AND pl.fsp AND pl.class = pp.class + ORDER BY sequence ASC; + `; + + const res = await client.query(text); + client.release(); + return res.rows; +} + +module.exports = list; diff --git a/lib/www/server/lib/db/plan/patch.js b/lib/www/server/lib/db/plan/patch.js new file mode 100644 index 0000000..23cb5e7 --- /dev/null +++ b/lib/www/server/lib/db/plan/patch.js @@ -0,0 +1,33 @@ +const { setSurvey, transaction } = require('../connection'); + +async function patch (projectId, sequence, payload, opts = {}) { + const client = await setSurvey(projectId); + + try { + + const text = ` + UPDATE planned_lines + SET + sequence = COALESCE($2, sequence), + fsp = COALESCE($3, fsp), + lsp = COALESCE($4, lsp), + ts0 = COALESCE($5, ts0), + ts1 = COALESCE($6, ts1), + remarks = COALESCE($7, remarks), + meta = COALESCE($8, meta) + WHERE sequence = $1; + ` + + const p = payload; // For short + const values = [ sequence, p.sequence, p.fsp, p.lsp, p.lsp, p.ts0, p.ts1, p.remarks, p.meta ]; + + await client.query(text, values); + } catch (err) { + transaction.rollback(err); + throw err; + } finally { + client.release(); + } +} + +module.exports = patch; diff --git a/lib/www/server/lib/db/plan/post.js b/lib/www/server/lib/db/plan/post.js new file mode 100644 index 0000000..2349f08 --- /dev/null +++ b/lib/www/server/lib/db/plan/post.js @@ -0,0 +1,147 @@ +const { setSurvey, transaction } = require('../connection'); +const configuration = require('../configuration'); + +async function getDistance (client, payload) { + const text = ` + SELECT ST_Distance(pp0.geometry, pp1.geometry) distance + FROM preplot_points pp0, + preplot_points pp1 + WHERE + pp0.line = $1 AND pp1.line = $1 + AND pp0.class = $4 AND pp1.class = $4 + AND pp0.point = $2 AND pp1.point = $3; + `; + + const p = payload; + const res = await client.query(text, [p.line, p.fsp, p.lsp, p.class || "V"]); + if (res.rows.length) { + return res.rows[0].distance; + } // else undefined +} + +async function getSequence (client) { + const text = ` + SELECT max(sequence)+1 AS sequence + FROM ( + SELECT sequence + FROM raw_lines + UNION SELECT sequence + FROM planned_lines + ) t; + `; + + const res = await client.query(text); + return res.rows[0] && res.rows[0].sequence; +} + +async function getTimestamps (client, projectId, payload) { + + const defaultLineChangeDuration = (await configuration.get(projectId, "planner/defaultLineChangeDuration") || 30) * 60*1000; // minutes to milliseconds + + const defaultAcquisitionSpeed = (await configuration.get(projectId, "planner/defaultAcquisitionSpeed") || 4.8) * 1.852 / 3.6; // Knots to m/s + + const distance = await getDistance(client, payload); + + const text = ` + SELECT * FROM planned_lines; + `; + + const res = await client.query(text); + + const ts0 = new Date( + (res.rows.length + ? res.rows.map(r => r.ts1).reduce( (a, b) => Math.max(a, b) ) + : Date.now() + ) + defaultLineChangeDuration + ); + + const ts1 = new Date(ts0.valueOf() + (distance / defaultAcquisitionSpeed)*1000); + + return {ts0, ts1}; +} + +async function getSequencesForLine (client, line) { + const text = ` + SELECT * from sequences_summary WHERE line = $1 ORDER BY sequence; + `; + + const res = await client.query(text, [line]); + return res.rows; +} + +async function getPlanned (client) { + const text = ` + SELECT * FROM planned_lines ORDER BY sequence; + `; + + const res = await client.query(text); + return res.rows; +} + +async function getLineName (client, projectId, payload) { + // FIXME TODO Get line name script from configuration + + const planned = await getPlanned(client); + const previous = await getSequencesForLine(client, payload.line); + console.log("Previous", previous); + const attempt = planned.filter(r => r.line == payload.line).concat(previous).length; + const p = payload; + const incr = p.lsp > p.fsp; + const sequence = p.sequence; + const line = p.line; + return `${incr?"1":"2"}0${line}${attempt}${sequence.toString().padStart(3, "0")}S00000`; +} + +async function post (projectId, payload, opts = {}) { + + const client = await setSurvey(projectId); + try { + + if (!payload.sequence) { + payload.sequence = await getSequence(client); + console.log("Sequence", payload.sequence); + } + if (!payload.ts0 || !payload.ts1) { + const ts = await getTimestamps(client, projectId, payload); + console.log("ts", ts); + if (!payload.ts0) { + payload.ts0 = ts.ts0; + } + if (!payload.ts1) { + payload.ts1 = ts.ts1; + } + } + if (!payload.name) { + payload.name = await getLineName(client, projectId, payload); + } + + const p = Object.assign({ + remarks: "", + meta: {} + }, payload); + + const text = ` + INSERT + INTO planned_lines (sequence, line, fsp, lsp, ts0, ts1, name, remarks, meta) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); + `; + const values = [ p.sequence, p.line, p.fsp, p.lsp, p.ts0, p.ts1, p.name, p.remarks, p.meta ]; + + console.log(text, values); + await client.query(text, values); + } catch (err) { + if (err.code && Math.trunc(err.code/1000) == 23) { + // Class 23 — Integrity Constraint Violation + console.error(err); + throw { status: 400, message: "Malformed request" }; + } else { + throw err; + } + } finally { + client.release(); + } + + return; +} + +module.exports = post; diff --git a/lib/www/server/lib/db/plan/put.js b/lib/www/server/lib/db/plan/put.js new file mode 100644 index 0000000..a47d09f --- /dev/null +++ b/lib/www/server/lib/db/plan/put.js @@ -0,0 +1,36 @@ +const { setSurvey, transaction } = require('../connection'); + +async function put (projectId, payload, opts = {}) { + + const client = await setSurvey(projectId); + try { + + const p = Object.assign({ + remarks: "", + meta: {} + }, payload); + + const text = ` + INSERT + INTO planned_lines (sequence, line, fsp, lsp, ts0, ts1, name, remarks, meta) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); + `; + const values = [ p.sequence, p.line, p.fsp, p.lsp, p.ts0, p.ts1, p.name, p.remarks, p.meta ]; + + await client.query(text, values); + } catch (err) { + if (err.code && Math.trunc(err.code/1000) == 23) { + // Class 23 — Integrity Constraint Violation + console.error(err); + throw { status: 400, message: "Malformed request" }; + } else { + throw err; + } + } finally { + client.release(); + } + + return; +} + +module.exports = put; From d86a5a2feb0a0df422f0aa83ebac383ae7e6a49d Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Thu, 8 Oct 2020 16:36:52 +0200 Subject: [PATCH 02/19] Add planner frontend component --- lib/www/client/source/src/views/Plan.vue | 337 +++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 lib/www/client/source/src/views/Plan.vue diff --git a/lib/www/client/source/src/views/Plan.vue b/lib/www/client/source/src/views/Plan.vue new file mode 100644 index 0000000..c12a03e --- /dev/null +++ b/lib/www/client/source/src/views/Plan.vue @@ -0,0 +1,337 @@ + + + + + From a8fa238e683636c94a8ca5d5bd27e92f954286a0 Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Thu, 8 Oct 2020 16:38:37 +0200 Subject: [PATCH 03/19] Add planner component to site --- lib/www/client/source/src/components/navigation.vue | 1 + lib/www/client/source/src/router/index.js | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/lib/www/client/source/src/components/navigation.vue b/lib/www/client/source/src/components/navigation.vue index 4af78b1..a376831 100644 --- a/lib/www/client/source/src/components/navigation.vue +++ b/lib/www/client/source/src/components/navigation.vue @@ -44,6 +44,7 @@ export default { tabs: [ { href: "summary", text: "Summary" }, { href: "lines", text: "Lines" }, + { href: "plan", text: "Plan" }, { href: "sequences", text: "Sequences" }, { href: "calendar", text: "Calendar" }, { href: "log", text: "Log" }, diff --git a/lib/www/client/source/src/router/index.js b/lib/www/client/source/src/router/index.js index 7bbf77a..8d7255a 100644 --- a/lib/www/client/source/src/router/index.js +++ b/lib/www/client/source/src/router/index.js @@ -5,6 +5,7 @@ import Project from '../views/Project.vue' import ProjectList from '../views/ProjectList.vue' import ProjectSummary from '../views/ProjectSummary.vue' import LineList from '../views/LineList.vue' +import Plan from '../views/Plan.vue' import LineSummary from '../views/LineSummary.vue' import SequenceList from '../views/SequenceList.vue' import SequenceSummary from '../views/SequenceSummary.vue' @@ -78,6 +79,11 @@ Vue.use(VueRouter) name: "LineList", component: LineList }, + { + path: "plan/", + name: "Plan", + component: Plan + }, { path: "lines/:line", name: "Line", From c4915e43d7cda87a72a2ccac3e37d4f0ed9fd531 Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Thu, 8 Oct 2020 16:39:04 +0200 Subject: [PATCH 04/19] Add option to append line to planner --- lib/www/client/source/src/views/LineList.vue | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/www/client/source/src/views/LineList.vue b/lib/www/client/source/src/views/LineList.vue index 0e688fe..4316cb1 100644 --- a/lib/www/client/source/src/views/LineList.vue +++ b/lib/www/client/source/src/views/LineList.vue @@ -28,6 +28,9 @@ Unset NTBA Set NTBA + + Add to plan + @@ -244,7 +247,24 @@ export default { value: !this.contextMenuItem.ntba }) }, - + + async addToPlan () { + const payload = { + sequence: null, + line: this.contextMenuItem.line, + fsp: this.contextMenuItem.fsp, + lsp: this.contextMenuItem.lsp + } + console.log("Plan", payload); + const url = `/project/${this.$route.params.project}/plan`; + const init = { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: payload + } + await this.api([url, init]); + }, + editItem (item, key) { this.edit = { line: item.line, From bc54d4ad5921c9d79bf871fa72dda3633f26b01e Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Thu, 8 Oct 2020 16:39:29 +0200 Subject: [PATCH 05/19] Add option to add sequence to planner as reshoot --- .../client/source/src/views/SequenceList.vue | 119 +++++++++++++++--- 1 file changed, 104 insertions(+), 15 deletions(-) diff --git a/lib/www/client/source/src/views/SequenceList.vue b/lib/www/client/source/src/views/SequenceList.vue index 4828a79..6c09590 100644 --- a/lib/www/client/source/src/views/SequenceList.vue +++ b/lib/www/client/source/src/views/SequenceList.vue @@ -19,18 +19,36 @@ + + + + Reshoot + + + Reshoot with overlap + + + + -