From 8790a797d97a70b3df999cb78f833a0e8c6a3726 Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Sun, 27 Feb 2022 18:27:49 +0100 Subject: [PATCH] Allow restricting by timestamp or position. Closes #181. --- lib/www/server/lib/db/navdata/get.js | 67 +++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/www/server/lib/db/navdata/get.js b/lib/www/server/lib/db/navdata/get.js index b53d4e1..7ef7741 100644 --- a/lib/www/server/lib/db/navdata/get.js +++ b/lib/www/server/lib/db/navdata/get.js @@ -1,4 +1,4 @@ - + const { transaction, pool } = require('../connection'); function fields (obj, keys) { @@ -9,12 +9,76 @@ function fields (obj, keys) { return retval; } +/** Perform a restrict (aka select) operation on the relation. + * + * Recognised queries are: + * + * - tstamp:i; tolerance:j + * - pos:λ,φ; radius: ρ + * + * Defaults: + * + * - tolerance: 1000 (ms) + * - radius: 1 (m) + * + * Queries are ANDed together. + */ +function restrict (q="") { + let sql = []; + + /* This takes in a string of the form: + * "key0=value0;key1=value1;key2=value=2" + * and returns an object like: + * { key0: "value0", key1: "value1", key2: "value=2" } + */ + const constraints = Object.fromEntries(q.split(/\s*;\s*/).map(i => { + const parts = i.split(/(\s*:\s*)/); + return [parts[0], parts.slice(2).join("")] + })); + + // TODO Consider adding an event_tstamp field, or indexing meta->>tstamp + if ("tstamp" in constraints) { + const ts = new Date(constraints.tstamp); + + if (isNaN(ts)) { + throw new Error("Invalid query"); + } + + const tolerance = constraints.tolerance ?? 1000; + const ts0 = new Date(ts.valueOf()-Number(tolerance)); + const ts1 = new Date(ts.valueOf()+Number(tolerance)); + console.log(tolerance, ts, ts0, ts1); + console.log(constraints); + + if (!isNaN(ts0) && !isNaN(ts1)) { + sql.push(`(meta->>'tstamp')::timestamptz BETWEEN '${ts0.toISOString()}' AND '${ts1.toISOString()}'`); + } else { + throw new Error("Invalid query"); + } + } + + if ("pos" in constraints) { + const [ lon, lat ] = constraints.pos.split(",").map(i => Number(i)); + + if (isNaN(lon) || isNaN(lat)) { + throw new Error("Invalid query"); + } + + const radius = constraints.radius ?? 1; + + sql.push(`ST_DistanceSphere(geometry, ST_Point(${lon}, ${lat}, 4326)) <= ${radius}`) + } + + return sql.length ? ("WHERE " + sql.join(" AND ")) : ""; +} + async function get (opts = {}) { const client = await pool.connect(); const text = ` SELECT meta FROM real_time_inputs + ${restrict(opts.q)} ORDER BY tstamp DESC LIMIT $1 OFFSET $2 @@ -24,6 +88,7 @@ async function get (opts = {}) { const res = await client.query(text, values); client.release(); + // TODO Must change this to use utils/project instead. return opts.fields ? res.rows.map(r => fields(r.meta, opts.fields.split(/[;,\s]+/))) : res.rows.map(r => r.meta);