Add planner endpoints

This commit is contained in:
D. Berge
2020-10-08 16:36:15 +02:00
parent 2a19caf219
commit 63254a6bf7
17 changed files with 393 additions and 0 deletions

View File

@@ -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 ],

View File

@@ -1,5 +1,6 @@
module.exports = {
event: require('./event'),
plan: require('./plan'),
line: require('./line'),
project: require('./project'),
sequence: require('./sequence'),

View File

@@ -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);
}
};

View File

@@ -0,0 +1,8 @@
module.exports = {
list: require('./list'),
get: require('./get'),
post: require('./post'),
put: require('./put'),
patch: require('./patch'),
delete: require('./delete')
};

View File

@@ -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);
}
};

View File

@@ -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);
}
};

View File

@@ -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);
}
};

View File

@@ -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);
}
};

View File

@@ -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'),

View File

@@ -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;

View File

View File

@@ -0,0 +1,9 @@
module.exports = {
list: require('./list'),
get: require('./get'),
post: require('./post'),
put: require('./put'),
patch: require('./patch'),
delete: require('./delete')
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;