Skip to content

Commit d81fea3

Browse files
committed
patch for VMG / ETA calcs
1 parent 947ce7b commit d81fea3

File tree

4 files changed

+40
-16
lines changed

4 files changed

+40
-16
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# CHANGELOG: @signalk/course-provider
22

3+
## v1.2.2
4+
5+
- **Fixed**: VMC calculation to use COG instead of heading.
6+
- **Fixed**: VMG calculation.
7+
38
## v1.2.1
49

510
- **Fixed**: Added `@signalk/server-api` dependencies.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@signalk/course-provider",
3-
"version": "1.2.1",
3+
"version": "1.2.2",
44
"description": "Course data provider plugin for SignalK Server.",
55
"main": "plugin/index.js",
66
"keywords": [

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ const SRC_PATHS = [
8282
'navigation.position',
8383
'navigation.magneticVariation',
8484
'navigation.headingTrue',
85+
'navigation.courseOverGroundTrue',
8586
'navigation.speedOverGround',
87+
'environment.wind.angleTrueGround',
8688
'navigation.datetime',
8789
'navigation.course.arrivalCircle',
8890
'navigation.course.startTime',

src/worker/course.ts

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,17 @@ function calcs(src: SKPaths): CourseData {
7070

7171
const xte = vesselPosition?.crossTrackDistanceTo(startPoint, destination)
7272
const magVar = src['navigation.magneticVariation'] ?? 0.0
73+
const vmgValue = vmg(src)
7374

7475
// GreatCircle
7576
const bearingTrackTrue = toRadians(startPoint?.initialBearingTo(destination))
7677
const bearingTrue = toRadians(vesselPosition?.initialBearingTo(destination))
7778
const bearingTrackMagnetic = compassAngle(bearingTrackTrue + magVar)
7879
const bearingMagnetic = compassAngle(bearingTrue + magVar)
7980
const gcDistance = vesselPosition?.distanceTo(destination)
80-
const gcVmg = vmg(src, bearingTrue, 'true') // prefer 'true' values
81-
const gcTime = timeCalcs(src, gcDistance, gcVmg as number, false)
81+
const gcVmg = vmgValue
82+
const gcVmc = vmc(src, bearingTrue, 'true') // for ETA, TTG - prefer 'true' values
83+
const gcTime = timeCalcs(src, gcDistance, gcVmc as number, false)
8284

8385
res.gc = {
8486
calcMethod: 'GreatCircle',
@@ -108,8 +110,9 @@ function calcs(src: SKPaths): CourseData {
108110
const rlBearingTrackMagnetic = compassAngle(rlBearingTrackTrue + magVar)
109111
const rlBearingMagnetic = compassAngle(rlBearingTrue + magVar)
110112
const rlDistance = vesselPosition?.rhumbDistanceTo(destination)
111-
const rlVmg = vmg(src, rlBearingTrue, 'true') // prefer 'true' values
112-
const rlTime = timeCalcs(src, rlDistance, rlVmg as number, true)
113+
const rlVmg = vmgValue
114+
const rlVmc = vmc(src, rlBearingTrue, 'true') // for ETA, TTG - prefer 'true' values
115+
const rlTime = timeCalcs(src, rlDistance, rlVmc as number, true)
113116

114117
res.rl = {
115118
calcMethod: 'Rhumbline',
@@ -143,24 +146,38 @@ function calcs(src: SKPaths): CourseData {
143146
return res
144147
}
145148

146-
// Velocity Made Good to Course
147-
function vmg(
149+
// Velocity Made Good
150+
function vmg(src: SKPaths): number | null {
151+
if (
152+
typeof src['environment.wind.angleTrueGround'] !== 'number' ||
153+
typeof src['navigation.speedOverGround'] !== 'number'
154+
) {
155+
return null
156+
}
157+
return (
158+
Math.cos(src['environment.wind.angleTrueGround']) *
159+
src['navigation.speedOverGround']
160+
)
161+
}
162+
163+
// Velocity Made Good to Course (used for ETA / TTG calcs)
164+
function vmc(
148165
src: SKPaths,
149166
bearing: number,
150167
bearingType: 'true' | 'magnetic' = 'true'
151168
): number | null {
152-
const hdg =
169+
const cog =
153170
bearingType === 'true'
154-
? src['navigation.headingTrue']
155-
: src['navigation.headingMagnetic']
171+
? src['navigation.courseOverGroundTrue']
172+
: src['navigation.courseOverGroundMagnetic']
156173
if (
157-
typeof hdg !== 'number' ||
174+
typeof cog !== 'number' ||
158175
typeof src['navigation.speedOverGround'] !== 'number'
159176
) {
160177
return null
161178
}
162179

163-
return Math.cos(bearing - hdg) * src['navigation.speedOverGround']
180+
return Math.cos(bearing - cog) * src['navigation.speedOverGround']
164181
}
165182

166183
interface CourseTimes {
@@ -178,7 +195,7 @@ interface CourseTimes {
178195
function timeCalcs(
179196
src: SKPaths,
180197
distance: number,
181-
vmg: number,
198+
vmc: number,
182199
rhumbLine: boolean
183200
): CourseTimes {
184201
const isRoute =
@@ -190,7 +207,7 @@ function timeCalcs(
190207
route: { ttg: null, eta: null, dtg: null }
191208
}
192209

193-
if (typeof distance !== 'number' || !vmg) {
210+
if (typeof distance !== 'number' || !vmc) {
194211
return result
195212
}
196213

@@ -200,14 +217,14 @@ function timeCalcs(
200217

201218
const dateMsec = date.getTime()
202219

203-
const nextTtgMsec = Math.floor((distance / vmg) * 1000)
220+
const nextTtgMsec = Math.floor((distance / vmc) * 1000)
204221
const nextEtaMsec = dateMsec + nextTtgMsec
205222
result.nextPoint.ttg = nextTtgMsec / 1000
206223
result.nextPoint.eta = new Date(nextEtaMsec).toISOString()
207224

208225
if (isRoute) {
209226
const rteDistance = distance + routeRemaining(src, rhumbLine)
210-
const routeTtgMsec = Math.floor((rteDistance / vmg) * 1000)
227+
const routeTtgMsec = Math.floor((rteDistance / vmc) * 1000)
211228
const routeEtaMsec = dateMsec + routeTtgMsec
212229
result.route.ttg = routeTtgMsec / 1000
213230
result.route.eta = new Date(routeEtaMsec).toISOString()

0 commit comments

Comments
 (0)