Make planned line names configurable.

Line names are made up based on:

* Certain properties defined by the system
* Values assigned to those properties either by the system
  or by the user (line number, sequence, attempt, etc.)
* A line format specification configured by the user for each
  project (`online.line.lineNameBuilder.fields`)

Closes #129.
This commit is contained in:
D. Berge
2025-07-09 16:30:26 +02:00
parent 0ef2e60d15
commit 8bbe3aee70
7 changed files with 224 additions and 4 deletions

View File

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

View File

@@ -0,0 +1,4 @@
module.exports = {
properties: require('./properties'),
post: require('./post'),
};

View File

@@ -0,0 +1,39 @@
const { setSurvey, transaction } = require('../connection');
const lib = require('../plan/lib');
async function post (projectId, payload, opts = {}) {
const client = await setSurvey(projectId);
try {
if (!payload.sequence) {
payload.sequence = await lib.getSequence(client);
}
// if (!payload.ts0 || !payload.ts1) {
// const ts = await lib.getTimestamps(client, projectId, payload);
// if (!payload.ts0) {
// payload.ts0 = ts.ts0;
// }
// if (!payload.ts1) {
// payload.ts1 = ts.ts1;
// }
// }
const name = await lib.getLineName(client, projectId, payload);
return name;
} 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,15 @@
const lib = require('../../plan/lib');
async function get (projectId, payload, opts = {}) {
try {
return await lib.getLineNameProperties();
} catch (err) {
throw err;
}
}
module.exports = get;

View File

@@ -0,0 +1,3 @@
module.exports = {
get: require('./get'),
};

View File

@@ -1,6 +1,12 @@
const YAML = require('yaml');
const fs = require('fs').promises;
const path = require('path');
const alert = require("../../../alerts");
const configuration = require('../../configuration');
let lineNameProperties;
async function getDistance (client, payload) {
const text = `
SELECT ST_Distance(pp0.geometry, pp1.geometry) distance
@@ -88,8 +94,6 @@ async function getPlanned (client) {
async function getLineName (client, projectId, payload) {
// FIXME TODO Get line name script from configuration
// Ref.: https://gitlab.com/wgp/dougal/software/-/issues/129
// This is to monitor #165
// https://gitlab.com/wgp/dougal/software/-/issues/incident/165
@@ -97,6 +101,36 @@ async function getLineName (client, projectId, payload) {
alert({function: "getLineName", client, projectId, payload});
}
const lineNameBuilder = await configuration.get(projectId, "online/line/lineNameBuilder");
const fields = lineNameBuilder?.fields;
if (fields) {
const properties = await getLineNameProperties();
const values = await getLineNameValues(client, projectId, payload, lineNameBuilder?.values);
return buildLineName(properties, fields, values, payload?.name);
} else {
// TODO send a user notification via WS to let them know
// they haven't configured the line name parameters
}
// return undefined
}
/** Get line properties that go into making a line name.
*
* The properties are defined in a separate YAML file for
* convenience.
*/
async function getLineNameProperties () {
if (!lineNameProperties) {
const buffer = await fs.readFile(path.join(__dirname, 'linename-properties.yaml'));
lineNameProperties = YAML.parse(buffer.toString());
}
return lineNameProperties;
}
async function getLineNameValues (client, projectId, payload, otherValues = {}) {
const planned = await getPlanned(client);
const previous = await getSequencesForLine(client, payload.line);
const attempt = planned.filter(r => r.line == payload.line).concat(previous).length;
@@ -104,9 +138,79 @@ async function getLineName (client, projectId, payload) {
const incr = p.lsp > p.fsp;
const sequence = p.sequence || 1;
const line = p.line;
return `${incr?"1":"2"}0${line}${attempt}${sequence.toString().padStart(3, "0")}S00000`;
return {
...structuredClone(otherValues),
line_number: payload.line,
sequence_number: payload.sequence || 1,
original_sequence: payload.meta?.original_sequence,
pass_number: attempt,
is_prime: attempt == 0,
is_reshoot: payload.meta?.is_reshoot ?? (!payload.meta?.is_infill && attempt > 0),
is_infill: payload.meta?.is_infill ?? false,
direction: null, // TODO
is_incrementing: incr
};
}
/** Compute the string representation of a line name field
*/
function fieldValue (properties, field, values) {
let value;
if (field.item == "text") {
value = field.value;
} else if (properties[field.item]?.type == "boolean") {
if (values[field.item] === field.when) {
value = field.value;
}
} else {
value = values[field.item];
}
if (value != null) {
if (properties[field.item]?.type == "number") {
if (field.scale_multiplier != null) {
value *= field.scale_multiplier;
}
if (field.scale_offset != null) {
value += field.scale_offset;
}
if (field.format == "integer") {
value = Math.round(value);
}
}
value = String(value);
if (field.pad_side == "left") {
value = value.padStart(field.length, field.pad_string ?? " ");
} else if (field.pad_side == "right") {
value = value.padEnd(field.length, field.pad_string ?? " ");
}
return value;
}
}
/** Build a line name out of its component properties, fields and values.
*
* NOTE: This is the same function as available client-side on
* `fixed-string-encoder.vue`. Consider merging them.
*/
function buildLineName (properties, fields, values, str = "") {
const length = fields.reduce( (acc, cur) => (cur.offset + cur.length) > acc ? (cur.offset + cur.length) : acc, str.length )
str = str.padEnd(length);
for (const field of fields) {
const value = fieldValue(properties, field, values);
if (value != null) {
str = str.slice(0, field.offset) + value + str.slice(field.offset + field.length);
}
}
return str;
}
module.exports = {
getDistance,
@@ -114,5 +218,8 @@ module.exports = {
getTimestamps,
getSequencesForLine,
getPlanned,
getLineName
getLineNameProperties,
getLineNameValues,
getLineName,
buildLineName
};

View File

@@ -0,0 +1,51 @@
#
# These are the properties that can be used to build
# line names.
#
line_number:
summary: Line number
description: The sailline number that is to be acquired
type: number
format: integer
sequence_number:
summary: Sequence
description: The sequence number that will be assigned to this line
type: number
format: integer
original_sequence:
summary: Original sequence
description: The original sequence number of the line that is being reshot
type: number
format: integer
pass_number:
summary: Pass number
description: The number of times this line, or section of line, has been shot
type: number
format: integer
is_prime:
summary: Prime line
description: Whether this is the first time this line is being acquired
type: boolean
is_reshoot:
summary: Reshoot
description: Whether this is a reshoot (mutually exclusive with `is_prime` and `is_infill`)
type: boolean
is_infill:
summary: Infill line
description: Whether this is an infill line (mutually exclusive with `is_prime` and `is_reshoot`)
type: boolean
direction:
summary: Line azimuth
direction: The line azimuth in the Incrementing shotpoints direction
type: number
format: float
is_incrementing:
summary: Incrementing
description: Whether the line is being shot low to high point numbers or vice versa
type: boolean
text:
summary: Fixed text
description: Arbitrary user-entered text (line prefix, suffix, etc.)
type: text