import { FrcsPlotShot } from './FrcsPlotShot'
import { FrcsPlotFile } from './FrcsPlotFile'
import { Length, UnitizedNumber, Unitize } from '@speleotica/unitized'
import { Segment, SegmentParseError } from 'parse-segment'
/**
* Parses data from a calculated survey file. These look like so:
123.182259
AE20 1 1 0 0 0 153 -257 -51 85 0 20 1
AE19 1 2 653 402 -548 1046 -587 -174 97 0 200 1
AE18 2 3 669 449 -3002 995 94 -597 -56 250 0 1
AE17 3 4 539 1217 -2770 497 47 -298 -28 0 0 1
AE16 4 5 544 1230 -3441 411 -284 -246 170 60 10 1
AE15 5 6 1679 1663 -3833 0 0 -282 283 20 10 1
AE14 6 7 2026 2617 -3730 446 225 -446 -225 0 30 1
AE13 7 8 391 3152 -5788 -111 691 0 0 200 50 1
AE12 8 9 -1019 2175 -4630 -369 336 221 -201 40 40 1
AE11 9 10 -1516 1289 -3919 -348 195 610 -342 50 10 1
*/
export default async function parseFrcsPlotFile(
file: string,
lines: AsyncIterable
): Promise {
let totalLength: UnitizedNumber = Unitize.feet(NaN)
const shots: Array = []
const errors: Array = []
let lineNumber = 0
const error = (
message: string,
line: string,
startColumn: number,
endColumn: number
): void => {
errors.push(
new SegmentParseError(
message,
new Segment({
value: line,
source: file,
startLine: lineNumber - 1,
startCol: 0,
}).substring(startColumn, endColumn)
)
)
}
function parseUint(
line: string,
startColumn: number,
endColumn: number,
fieldName: string
): number | null {
const str = line.substring(startColumn, endColumn)
if (!/\S/.test(str)) {
error(`Missing ${fieldName}`, line, startColumn, endColumn)
return null
}
if (!/^\s*\d+\s*$/.test(str)) {
error(`Invalid ${fieldName}`, line, startColumn, endColumn)
return null
}
return parseInt(str)
}
function parseOptionalUint(
line: string,
startColumn: number,
endColumn: number,
fieldName: string
): number | null {
const str = line.substring(startColumn, endColumn)
if (!/\S/.test(str)) {
return null
}
if (!/^\s*\d+\s*$/.test(str)) {
error(`Invalid ${fieldName}`, line, startColumn, endColumn)
return null
}
return parseInt(str)
}
function parseLength(
line: string,
startColumn: number,
endColumn: number,
fieldName: string,
divisor: number
): UnitizedNumber | null {
const str = line.substring(startColumn, endColumn)
if (!/^\s*-?\d+\s*$/.test(str)) {
error(`Invalid ${fieldName}`, line, startColumn, endColumn)
return null
}
const value = parseFloat(str)
if (isNaN(value)) {
error(`Missing ${fieldName}`, line, startColumn, endColumn)
return null
}
return Unitize.feet(value / divisor)
}
for await (const line of lines) {
lineNumber++
if (lineNumber === 1) {
totalLength = Unitize.feet(parseFloat(line))
continue
}
if (!/\S/.test(line)) continue
const toName = line.substring(0, 6).trim()
if (!toName) {
error('Invalid to station name', line, 0, 6)
continue
}
if (/[^ S]/.test(line[6])) {
error('Invalid flag', line, 6, 7)
continue
}
const isSurface = line[6] === 'S'
const fromNumber = parseUint(line, 7, 12, 'from station number')
if (fromNumber == null) continue
const toNumber = parseUint(line, 12, 17, 'to station number')
if (toNumber == null) continue
const easting = parseLength(line, 17, 25, 'easting', 100)
if (!easting) continue
const northing = parseLength(line, 25, 33, 'northing', 100)
if (!northing) continue
const elevation = parseLength(line, 33, 40, 'elevation', 100)
if (!elevation) continue
const leftEasting = parseLength(line, 40, 46, 'left wall easting', 100)
if (!leftEasting) continue
const leftNorthing = parseLength(line, 46, 52, 'left wall northing', 100)
if (!leftNorthing) continue
const rightEasting = parseLength(line, 52, 58, 'right wall easting', 100)
if (!rightEasting) continue
const rightNorthing = parseLength(line, 58, 64, 'right wall northing', 100)
if (!rightNorthing) continue
const up = parseLength(line, 64, 68, 'up', 10)
if (!up) continue
const down = parseLength(line, 68, 72, 'down', 10)
if (!down) continue
const tripNumber = parseOptionalUint(line, 72, 78, 'trip number')
shots.push({
toName,
isSurface,
fromNumber,
toNumber,
easting,
northing,
elevation,
leftEasting,
leftNorthing,
rightEasting,
rightNorthing,
up,
down,
tripNumber,
})
}
return {
totalLength,
shots,
errors,
}
}