diff --git a/lib/www/server/api/index.js b/lib/www/server/api/index.js index be4efba..8cdd00a 100644 --- a/lib/www/server/api/index.js +++ b/lib/www/server/api/index.js @@ -155,10 +155,13 @@ app.map({ '/project/:project/line/': { get: [ mw.auth.access.read, mw.line.list ], }, - '/project/:project/line/:line': { -// get: [ mw.auth.access.read, mw.line.get ], + '/project/:project/line/:line(\\d+)': { + get: [ mw.auth.access.read, mw.line.get ], patch: [ mw.auth.access.write, mw.line.patch ], }, + '/project/:project/line/:class(sail|source)': { + get: [ mw.auth.access.read, mw.line.get ], + }, /* * Sequence endpoints diff --git a/lib/www/server/api/middleware/line/get.js b/lib/www/server/api/middleware/line/get.js deleted file mode 100644 index e69de29..0000000 diff --git a/lib/www/server/api/middleware/line/get/binary.js b/lib/www/server/api/middleware/line/get/binary.js new file mode 100644 index 0000000..bad0b91 --- /dev/null +++ b/lib/www/server/api/middleware/line/get/binary.js @@ -0,0 +1,17 @@ +const { bundle } = require('../../../../lib/binary'); +const { line } = require('../../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const json = await line.get(req.params.project, req.params.class, req.params.line, {wgs84: true, ...req.query}); + const data = bundle(json, {type: req.params.class == "source" ? 1 : 0}); + console.log("bundle", data); + + res.status(200).send(Buffer.from(data)); + next(); + } catch (err) { + next(err); + } + +}; diff --git a/lib/www/server/api/middleware/line/get/geojson.js b/lib/www/server/api/middleware/line/get/geojson.js new file mode 100644 index 0000000..82cd950 --- /dev/null +++ b/lib/www/server/api/middleware/line/get/geojson.js @@ -0,0 +1,27 @@ + +const { line } = require('../../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const json = await line.get(req.params.project, req.params.class, req.params.line, {wgs84: true, ...req.query}); + + const geojson = { + type: "FeatureCollection", + features: json.map(feature => { + return { + type: "Feature", + geometry: feature["geometry"], + properties: {...feature} + } + }) + }; + + res.status(200).send(geojson); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/line/get/index.js b/lib/www/server/api/middleware/line/get/index.js new file mode 100644 index 0000000..bb3ae72 --- /dev/null +++ b/lib/www/server/api/middleware/line/get/index.js @@ -0,0 +1,26 @@ +const json = require('./json'); +const geojson = require('./geojson'); +const binary = require('./binary'); + +module.exports = async function (req, res, next) { + try { + const handlers = { + "application/json": json, + "application/geo+json": geojson, + "application/vnd.aaltronav.dougal+octet-stream": binary, + "application/vnd.aaltronav.dougal+octet-stream; format=0x1c": binary, + }; + + 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); + } else { + res.status(406).send(); + next(); + } + } catch (err) { + next(err); + } +} diff --git a/lib/www/server/api/middleware/line/get/json.js b/lib/www/server/api/middleware/line/get/json.js new file mode 100644 index 0000000..6408459 --- /dev/null +++ b/lib/www/server/api/middleware/line/get/json.js @@ -0,0 +1,14 @@ + +const { line } = require('../../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + res.status(200).send(await line.get(req.params.project, req.params.class, req.params.line, req.query)); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/line/list/binary.js b/lib/www/server/api/middleware/line/list/binary.js new file mode 100644 index 0000000..d80230a --- /dev/null +++ b/lib/www/server/api/middleware/line/list/binary.js @@ -0,0 +1,17 @@ +const { bundle } = require('../../../../lib/binary'); +const { line } = require('../../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const json = await line.get(req.params.project, req.params.class, req.params.line, {wgs84: true, ...req.query}); + const data = bundle(json, {type: req.query.type ?? 0}); + console.log("bundle", data); + + res.status(200).send(Buffer.from(data)); + next(); + } catch (err) { + next(err); + } + +}; diff --git a/lib/www/server/api/middleware/line/list/geojson.js b/lib/www/server/api/middleware/line/list/geojson.js new file mode 100644 index 0000000..82cd950 --- /dev/null +++ b/lib/www/server/api/middleware/line/list/geojson.js @@ -0,0 +1,27 @@ + +const { line } = require('../../../../lib/db'); + +module.exports = async function (req, res, next) { + + try { + const json = await line.get(req.params.project, req.params.class, req.params.line, {wgs84: true, ...req.query}); + + const geojson = { + type: "FeatureCollection", + features: json.map(feature => { + return { + type: "Feature", + geometry: feature["geometry"], + properties: {...feature} + } + }) + }; + + res.status(200).send(geojson); + next(); + } catch (err) { + next(err); + } + + +}; diff --git a/lib/www/server/api/middleware/line/list/index.js b/lib/www/server/api/middleware/line/list/index.js new file mode 100644 index 0000000..bb3ae72 --- /dev/null +++ b/lib/www/server/api/middleware/line/list/index.js @@ -0,0 +1,26 @@ +const json = require('./json'); +const geojson = require('./geojson'); +const binary = require('./binary'); + +module.exports = async function (req, res, next) { + try { + const handlers = { + "application/json": json, + "application/geo+json": geojson, + "application/vnd.aaltronav.dougal+octet-stream": binary, + "application/vnd.aaltronav.dougal+octet-stream; format=0x1c": binary, + }; + + 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); + } else { + res.status(406).send(); + next(); + } + } catch (err) { + next(err); + } +} diff --git a/lib/www/server/api/middleware/line/list.js b/lib/www/server/api/middleware/line/list/json.js similarity index 78% rename from lib/www/server/api/middleware/line/list.js rename to lib/www/server/api/middleware/line/list/json.js index f8125d1..9076ddb 100644 --- a/lib/www/server/api/middleware/line/list.js +++ b/lib/www/server/api/middleware/line/list/json.js @@ -1,5 +1,5 @@ -const { line } = require('../../../lib/db'); +const { line } = require('../../../../lib/db'); module.exports = async function (req, res, next) { @@ -10,4 +10,5 @@ module.exports = async function (req, res, next) { next(err); } + }; diff --git a/lib/www/server/api/middleware/sequence/get/binary.js b/lib/www/server/api/middleware/sequence/get/binary.js index 1b934d0..f095b77 100644 --- a/lib/www/server/api/middleware/sequence/get/binary.js +++ b/lib/www/server/api/middleware/sequence/get/binary.js @@ -5,7 +5,7 @@ module.exports = async function (req, res, next) { try { const json = await sequence.get(req.params.project, req.params.sequence, req.query); - const data = bundle(json, {type: req.query.type}); + const data = bundle(json, {type: req.query.type ?? 2}); console.log("bundle", data); res.status(200).send(Buffer.from(data)); diff --git a/lib/www/server/api/middleware/sequence/get/index.js b/lib/www/server/api/middleware/sequence/get/index.js index ed052e5..bb3ae72 100644 --- a/lib/www/server/api/middleware/sequence/get/index.js +++ b/lib/www/server/api/middleware/sequence/get/index.js @@ -7,8 +7,8 @@ module.exports = async function (req, res, next) { const handlers = { "application/json": json, "application/geo+json": geojson, - // application/vnd.aaltronav.dougal+octet-stream; format=0x1c - "application/vnd.aaltronav.dougal+octet-stream": binary + "application/vnd.aaltronav.dougal+octet-stream": binary, + "application/vnd.aaltronav.dougal+octet-stream; format=0x1c": binary, }; const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers)); diff --git a/lib/www/server/api/middleware/sequence/list/binary.js b/lib/www/server/api/middleware/sequence/list/binary.js index d8e1a83..c5a848c 100644 --- a/lib/www/server/api/middleware/sequence/list/binary.js +++ b/lib/www/server/api/middleware/sequence/list/binary.js @@ -5,7 +5,7 @@ module.exports = async function (req, res, next) { try { const json = await sequence.get(req.params.project, null, req.query); - const data = bundle(json, {type: req.query.type}); + const data = bundle(json, {type: req.query.type ?? 2}); console.log("bundle", data); res.status(200).send(Buffer.from(data)); diff --git a/lib/www/server/api/middleware/sequence/list/index.js b/lib/www/server/api/middleware/sequence/list/index.js index 189e68a..bb3ae72 100644 --- a/lib/www/server/api/middleware/sequence/list/index.js +++ b/lib/www/server/api/middleware/sequence/list/index.js @@ -7,7 +7,8 @@ module.exports = async function (req, res, next) { const handlers = { "application/json": json, "application/geo+json": geojson, - "application/vnd.aaltronav.dougal+octet-stream": binary + "application/vnd.aaltronav.dougal+octet-stream": binary, + "application/vnd.aaltronav.dougal+octet-stream; format=0x1c": binary, }; const mimetype = (handlers[req.query.mime] && req.query.mime) || req.accepts(Object.keys(handlers)); diff --git a/lib/www/server/lib/binary/bundle.js b/lib/www/server/lib/binary/bundle.js index bfec08c..d60c482 100644 --- a/lib/www/server/lib/binary/bundle.js +++ b/lib/www/server/lib/binary/bundle.js @@ -11,19 +11,85 @@ function bundle (json, opts = {}) { // console.log("JSON LENGTH", json.length); // console.log("OPTS", geometries, payload); - /* Gun information: - * - * - Δelem 0: BigUint64Array + Int16Array – timestamps - * - elem 0‒1: Float32Array, Float32Array – Raw positions (x, y) - * - elem 2‒3: Int16Array, Int16Array – (×10) Raw position errors (i, j) - * - elem 4‒6: Int8Array, Int8Array, Uint8Array – Gun deltas (μ, σ, R) - * - elem 7‒9: Uint16Array, Uint8Array, Uint16Array – Gun pressures (μ, σ, R) - * - elem 10‒12: Gun depths (μ, σ, R) - * - elem 13‒15: Gun fill times (μ, σ, R) - * - elem 16‒18: Gun delay (μ, σ, R) - * - elem 19: No fire / autofire (in a single byte) - */ - if (type == 2) { + if (type == 0) { + /* Preplot information – sail line points + * + * elem 0: Float32Array Longitude + * elem 1: Float32Array Latitude + * elem 2: Uint8Array – Flags: 0x01 point ntba, 0x02 sailline ntba + */ + + // Add preplot positions + values.push({ + // longitude + key: el => el.geometry.coordinates?.[0], + type: Float32Array + }); + + values.push({ + // latitude + key: el => el.geometry?.coordinates?.[1], + type: Float32Array + }); + + values.push({ + // flags + key: el => (el.sailline_ntba ? 0x02 : 0) | (el.ntba ? 0x01 : 0), + type: Uint8Array + }); + + return encode.sequential(json, el => el.sailline, el => el.point, deltas, values, type) + + } if (type == 1) { + /* Preplot information – source line points + * + * elem 0: Float32Array Longitude + * elem 1: Float32Array Latitude + * elem 2: Uint8Array – Flags: 0x01 point ntba, 0x02 sailline ntba + * elem 3: Uint16Array Sailline + */ + + // Add preplot positions + values.push({ + // longitude + key: el => el.geometry.coordinates?.[0], + type: Float32Array + }); + + values.push({ + // latitude + key: el => el.geometry?.coordinates?.[1], + type: Float32Array + }); + + values.push({ + // flags + key: el => (el.sailline_ntba ? 0x02 : 0) | (el.ntba ? 0x01 : 0), + type: Uint8Array + }); + + values.push({ + // sailline + key: el => el.sailline, + type: Uint16Array + }); + + console.log("JSON", json[0]); + return encode.sequential(json, el => el.line, el => el.point, deltas, values, type) + + } else if (type == 2) { + /* Gun information: + * + * - Δelem 0: BigUint64Array + Int16Array – timestamps + * - elem 0‒1: Float32Array, Float32Array – Raw positions (x, y) + * - elem 2‒3: Int16Array, Int16Array – (×10) Raw position errors (i, j) + * - elem 4‒6: Int8Array, Int8Array, Uint8Array – Gun deltas (μ, σ, R) + * - elem 7‒9: Uint16Array, Uint8Array, Uint16Array – Gun pressures (μ, σ, R) + * - elem 10‒12: Gun depths (μ, σ, R) + * - elem 13‒15: Gun fill times (μ, σ, R) + * - elem 16‒18: Gun delay (μ, σ, R) + * - elem 19: No fire / autofire (in a single byte) + */ // Add timestamps deltas.push({ @@ -155,12 +221,12 @@ function bundle (json, opts = {}) { key: el => (el.meta.raw?.smsrc?.no_fire ?? 0) << 4 | (el.meta.raw?.smsrc?.num_auto ?? 0), type: Uint8Array }); + + console.log("DELTAS", deltas); + console.log("VALUES", values); + + return encode.sequential(json, el => el.sequence, el => el.point, deltas, values, type) } - - console.log("DELTAS", deltas); - console.log("VALUES", values); - - return encode.sequential(json, el => el.sequence, el => el.point, deltas, values, type) } module.exports = { bundle }; diff --git a/lib/www/server/lib/db/line/get.js b/lib/www/server/lib/db/line/get.js index e69de29..142e62b 100644 --- a/lib/www/server/lib/db/line/get.js +++ b/lib/www/server/lib/db/line/get.js @@ -0,0 +1,108 @@ +const { setSurvey } = require('../connection'); + +async function getSummary (projectId, line, opts = {}) { + const client = await setSurvey(projectId); + + const text = ` + SELECT pls.line, pls.fsp, pls.lsp, pls.num_points, pls.length, pls.azimuth, pls.incr, + pl.ntba, pl.geometry, pl.remarks, pl.meta + FROM preplot_lines_summary pls + INNER JOIN preplot_lines pl + ON psl.line = pl.line and pl.class = 'V' + WHERE ($1:integer IS NULL) OR (line = $1); + `; + + const res = await client.query(text, [line]); + client.release(); + + return res.rows[0]; +} + + +async function get (projectId, type = "sail", line = null, opts = {}) { + if (opts.summary) { + return await getSummary(projectId, line, opts); + } + + const client = await setSurvey(projectId); + + const sortFields = [ + "sailline", "line", "point", "ntba", "sailline_ntba" + ]; + const sortKey = opts.sortBy && sortFields.includes(opts.sortBy) && opts.sortBy || "line"; + const sortDir = opts.sortDesc == "false" ? "ASC" : "DESC"; + const offset = Math.abs(opts.offset) || Math.abs((opts.page-1)*opts.itemsPerPage) || 0; + const limit = Math.abs(opts.limit) || Math.abs(Number(opts.itemsPerPage)) || null; + + let res; + + if (type == "source") { + + const geometry = (opts.wgs84 || opts.lonlat || opts.latlon) + ? "ST_Transform(geometry, 4326)::json geometry" + : "geometry::json"; + + const restriction = line + ? "(sailline = $3 OR line = $3)" + : "(TRUE OR $3)"; + + const text = ` + SELECT + sailline, sailline_ntba, line, point, ntba, + ${geometry}, + meta + FROM preplot_saillines_points + WHERE ${restriction} + ORDER BY ${sortKey} ${sortDir} + OFFSET $1 + LIMIT $2; + `; + + const values = [offset, limit, line]; + res = await client.query(text, values); + + } else if (type == "sail") { + + const geometry = (opts.wgs84 || opts.lonlat || opts.latlon) + ? "ST_Transform(pp.geometry, 4326)::json geometry" + : "pp.geometry::json"; + + const restriction = line + ? "(sailline = $3 OR psl.line = $3)" + : "(TRUE OR $3)"; + + const text = ` + WITH psl AS ( + SELECT DISTINCT sailline, ntba + FROM preplot_saillines + ) + SELECT + psl.sailline, pp.point, pp.ntba, psl.ntba sailline_ntba, pp.meta, ${geometry} + FROM preplot_points pp + INNER JOIN psl ON psl.sailline = pp.line + WHERE pp.class = 'V' + AND ${restriction} + ORDER BY ${sortKey} ${sortDir} + OFFSET $1 + LIMIT $2; + `; + + const values = [offset, limit, line]; + res = await client.query(text, values); + + } + + client.release(); + + if (opts.project) { + const tokens = opts.project.split(/\s*[,;:\s]\s*/).filter(e => e.length); + const project = tokens.map(i => i.replace(/^([^.]+)\..*$/, "$1")); + return res.rows.map( r => + Object.fromEntries(Object.entries(r).filter(entry => project.includes(entry[0]))) + ); + } else { + return res.rows; + } +} + +module.exports = get;