UNPKG

@signalk/course-provider

Version:
241 lines (240 loc) 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const worker_threads_1 = require("worker_threads"); const latlon_spherical_js_1 = require("../lib/geodesy/latlon-spherical.js"); let activeDest = false; // process message from main thread worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.on('message', (message) => { if (parseSKPaths(message)) { worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.postMessage(calcs(message)); activeDest = true; } else { if (activeDest) { worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.postMessage({ gc: {}, rl: {} }); activeDest = false; } } }); function parseSKPaths(src) { var _a, _b; return src['navigation.position'] && ((_a = src['navigation.course.nextPoint']) === null || _a === void 0 ? void 0 : _a.position) && ((_b = src['navigation.course.previousPoint']) === null || _b === void 0 ? void 0 : _b.position) ? true : false; } function toRadians(value) { return (value * Math.PI) / 180; } /** Normalises angle to return a value between 0 & (2*PI | 360) * @param angle: angle (in radians or degrees) * @param units: unit of the angle supplied => 'rad' (radians) or 'deg' (degrees) * @returns value between 0 - 2*PI */ function compassAngle(angle, units = 'rad') { const maxAngle = units === 'rad' ? Math.PI * 2 : 360; return angle < 0 ? angle + maxAngle : angle >= maxAngle ? angle - maxAngle : angle; } // course calculations function calcs(src) { var _a; const vesselPosition = src['navigation.position'] ? new latlon_spherical_js_1.LatLonSpherical(src['navigation.position'].latitude, src['navigation.position'].longitude) : null; const destination = src['navigation.course.nextPoint'] ? new latlon_spherical_js_1.LatLonSpherical(src['navigation.course.nextPoint'].position.latitude, src['navigation.course.nextPoint'].position.longitude) : null; const startPoint = src['navigation.course.previousPoint'].position ? new latlon_spherical_js_1.LatLonSpherical(src['navigation.course.previousPoint'].position.latitude, src['navigation.course.previousPoint'].position.longitude) : null; const res = { gc: {}, rl: {}, passedPerpendicular: false }; if (!vesselPosition || !destination || !startPoint) { return res; } const xte = vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.crossTrackDistanceTo(startPoint, destination); const magVar = (_a = src['navigation.magneticVariation']) !== null && _a !== void 0 ? _a : 0.0; // GreatCircle const bearingTrackTrue = toRadians(startPoint === null || startPoint === void 0 ? void 0 : startPoint.initialBearingTo(destination)); const bearingTrue = toRadians(vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.initialBearingTo(destination)); const bearingTrackMagnetic = compassAngle(bearingTrackTrue + magVar); const bearingMagnetic = compassAngle(bearingTrue + magVar); const gcDistance = vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.distanceTo(destination); const gcVmg = vmg(src, bearingTrue, 'true'); // prefer 'true' values const gcTime = timeCalcs(src, gcDistance, gcVmg); res.gc = { calcMethod: 'GreatCircle', bearingTrackTrue: bearingTrackTrue, bearingTrackMagnetic: bearingTrackMagnetic, crossTrackError: xte, distance: gcDistance, bearingTrue: bearingTrue, bearingMagnetic: bearingMagnetic, velocityMadeGood: gcVmg, timeToGo: gcTime.ttg, estimatedTimeOfArrival: gcTime.eta, previousPoint: { distance: vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.distanceTo(startPoint) }, targetSpeed: targetSpeed(src, gcDistance) }; // Rhumbline const rlBearingTrackTrue = toRadians(startPoint === null || startPoint === void 0 ? void 0 : startPoint.rhumbBearingTo(destination)); const rlBearingTrue = toRadians(vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.rhumbBearingTo(destination)); const rlBearingTrackMagnetic = compassAngle(rlBearingTrackTrue + magVar); const rlBearingMagnetic = compassAngle(rlBearingTrue + magVar); const rlDistance = vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.rhumbDistanceTo(destination); const rlVmg = vmg(src, rlBearingTrue, 'true'); // prefer 'true' values const rlTime = timeCalcs(src, rlDistance, rlVmg); res.rl = { calcMethod: 'Rhumbline', bearingTrackTrue: rlBearingTrackTrue, bearingTrackMagnetic: rlBearingTrackMagnetic, crossTrackError: xte, distance: rlDistance, bearingTrue: rlBearingTrue, bearingMagnetic: rlBearingMagnetic, velocityMadeGood: rlVmg, timeToGo: rlTime.ttg, estimatedTimeOfArrival: rlTime.eta, previousPoint: { distance: vesselPosition === null || vesselPosition === void 0 ? void 0 : vesselPosition.rhumbDistanceTo(startPoint) }, targetSpeed: targetSpeed(src, rlDistance, true) }; // passed destination perpendicular res.passedPerpendicular = passedPerpendicular(vesselPosition, destination, startPoint); return res; } // Velocity Made Good to Course function vmg(src, bearing, bearingType = 'true') { const hdg = bearingType === 'true' ? src['navigation.headingTrue'] : src['navigation.headingMagnetic']; if (typeof hdg !== 'number' || typeof src['navigation.speedOverGround'] !== 'number') { return null; } return Math.cos(bearing - hdg) * src['navigation.speedOverGround']; } // Time to Go & Estimated time of arrival at the nextPoint function timeCalcs(src, distance, vmg) { if (typeof distance !== 'number' || !vmg) { return { ttg: null, eta: null }; } const date = src['navigation.datetime'] ? new Date(src['navigation.datetime']) : new Date(); const dateMsec = date.getTime(); const ttgMsec = Math.floor((distance / vmg) * 1000); const etaMsec = dateMsec + ttgMsec; return { ttg: ttgMsec / 1000, eta: new Date(etaMsec).toISOString() }; } // Avg speed required to arrive at destination at targetArrivalTime function targetSpeed(src, distance, rhumbLine) { if (typeof distance !== 'number' || !src['navigation.course.targetArrivalTime']) { return null; } // if route totalDistance = distance plus + length of remaining route segments if (src['navigation.course.activeRoute.waypoints']) { distance += routeRemaining(src, rhumbLine); } const date = src['navigation.datetime'] ? new Date(src['navigation.datetime']) : new Date(); const dateMsec = date.getTime(); const tat = new Date(src['navigation.course.targetArrivalTime']); const tatMsec = tat.getTime(); if (tatMsec <= dateMsec) { // current time is after targetArrivalTime return null; } const tDiffSec = (tatMsec - dateMsec) / 1000; return distance / tDiffSec; } // total distance in meters of remaining route segments function routeRemaining(src, rhumbLine) { if (src['navigation.course.activeRoute.pointIndex'] === null || !src['navigation.course.activeRoute.waypoints'] || !Array.isArray(src['navigation.course.activeRoute.waypoints'])) { return 0; } if (src['navigation.course.activeRoute.waypoints'].length < 2) { return 0; } let reverse = src['navigation.course.activeRoute.reverse']; let ptIndex = src['navigation.course.activeRoute.pointIndex']; let lastIndex = src['navigation.course.activeRoute.waypoints'].length - 1; // determine segments to sum let fromIndex; let toIndex; if (reverse) { fromIndex = 0; toIndex = lastIndex - ptIndex; if (toIndex === fromIndex) { return 0; } } else { if (ptIndex === lastIndex) { return 0; } fromIndex = ptIndex; toIndex = lastIndex; } // sum segment lengths let wpts = src['navigation.course.activeRoute.waypoints']; let rteLen = 0; for (let idx = fromIndex; idx < lastIndex; idx++) { let pt = new latlon_spherical_js_1.LatLonSpherical(wpts[idx].position.latitude, wpts[idx].position.longitude); if (rhumbLine) { rteLen += pt.rhumbDistanceTo(new latlon_spherical_js_1.LatLonSpherical(wpts[idx + 1].position.latitude, wpts[idx + 1].position.longitude)); } else { rteLen += pt.distanceTo(new latlon_spherical_js_1.LatLonSpherical(wpts[idx + 1].position.latitude, wpts[idx + 1].position.longitude)); } } return rteLen; } // return true if vessel is past perpendicular of destination function passedPerpendicular(vesselPosition, destination, startPoint) { const va = toVector(destination, vesselPosition); const vb = toVector(destination, startPoint); const rad = Math.acos((va.x * vb.x + va.y * vb.y) / (va.length * vb.length)); const deg = (180 / Math.PI) * rad; return deg > 90 ? true : false; } function toVector(origin, end) { // calc longitudinal difference (inc dateline transition) function xDiff(a, b) { let bx; if (a > 170 && b < 0) { // E->W transition bx = a + (180 - a) + (180 + b); } else if (a < -170 && b > 0) { // W->E transition bx = a - (180 + a) - (180 - b); } else { bx = b; } return bx - a; } const x = xDiff(origin.longitude, end.longitude); const y = end.latitude - origin.latitude; const v = { x: x, y: y, length: Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) }; return v; }