@neaps/tide-predictor 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +27 -50
  2. package/dist/index.cjs +931 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +150 -0
  5. package/dist/index.d.ts +150 -0
  6. package/dist/index.js +930 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +16 -23
  9. package/.eslintrc.cjs +0 -18
  10. package/.github/workflows/test.yml +0 -21
  11. package/.prettierrc +0 -5
  12. package/LICENSE +0 -21
  13. package/dist/commonjs/index.js +0 -1005
  14. package/dist/commonjs/package.json +0 -1
  15. package/dist/web/tide-predictor.js +0 -1011
  16. package/dist/web/tide-predictor.min.js +0 -1
  17. package/examples/browser/index.html +0 -51
  18. package/jest.config.js +0 -14
  19. package/rollup.config.js +0 -21
  20. package/src/astronomy/coefficients.js +0 -72
  21. package/src/astronomy/constants.js +0 -4
  22. package/src/astronomy/index.js +0 -201
  23. package/src/constituents/compound-constituent.js +0 -54
  24. package/src/constituents/constituent.js +0 -73
  25. package/src/constituents/index.js +0 -119
  26. package/src/harmonics/index.js +0 -87
  27. package/src/harmonics/prediction.js +0 -175
  28. package/src/index.js +0 -45
  29. package/src/node-corrections/index.js +0 -208
  30. package/test/_mocks/constituents.js +0 -335
  31. package/test/_mocks/secondary-station.js +0 -11
  32. package/test/_mocks/water-levels.js +0 -0
  33. package/test/astronomy/coefficients.js +0 -15
  34. package/test/astronomy/index.js +0 -98
  35. package/test/constituents/compound-constituent.js +0 -46
  36. package/test/constituents/constituent.js +0 -67
  37. package/test/constituents/index.js +0 -35
  38. package/test/harmonics/index.js +0 -125
  39. package/test/harmonics/prediction.js +0 -154
  40. package/test/index.js +0 -83
  41. package/test/lib/close-to.js +0 -7
  42. package/test/noaa.js +0 -110
  43. package/test/node-corrections/index.js +0 -116
@@ -1,1005 +0,0 @@
1
- 'use strict';
2
-
3
- const d2r = Math.PI / 180.0;
4
- const r2d = 180.0 / Math.PI;
5
-
6
- // Convert a sexagesimal angle into decimal degrees
7
- const sexagesimalToDecimal = (degrees, arcmins, arcsecs, mas, muas) => {
8
- arcmins = typeof arcmins !== 'undefined' ? arcmins : 0;
9
- arcsecs = typeof arcsecs !== 'undefined' ? arcsecs : 0;
10
- mas = typeof mas !== 'undefined' ? mas : 0;
11
- muas = typeof muas !== 'undefined' ? muas : 0;
12
-
13
- return (
14
- degrees +
15
- arcmins / 60.0 +
16
- arcsecs / (60.0 * 60.0) +
17
- mas / (60.0 * 60.0 * 1e3) +
18
- muas / (60.0 * 60.0 * 1e6)
19
- )
20
- };
21
-
22
- const coefficients = {
23
- // Meeus formula 21.3
24
- terrestrialObliquity: [
25
- sexagesimalToDecimal(23, 26, 21.448),
26
- -sexagesimalToDecimal(0, 0, 4680.93),
27
- -sexagesimalToDecimal(0, 0, 1.55),
28
- sexagesimalToDecimal(0, 0, 1999.25),
29
- -sexagesimalToDecimal(0, 0, 51.38),
30
- -sexagesimalToDecimal(0, 0, 249.67),
31
- -sexagesimalToDecimal(0, 0, 39.05),
32
- sexagesimalToDecimal(0, 0, 7.12),
33
- sexagesimalToDecimal(0, 0, 27.87),
34
- sexagesimalToDecimal(0, 0, 5.79),
35
- sexagesimalToDecimal(0, 0, 2.45),
36
- ].map((number, index) => {
37
- return number * Math.pow(1e-2, index)
38
- }),
39
-
40
- solarPerigee: [
41
- 280.46645 - 357.5291,
42
- 36000.76932 - 35999.0503,
43
- 0.0003032 + 0.0001559,
44
- 0.00000048,
45
- ],
46
-
47
- solarLongitude: [280.46645, 36000.76983, 0.0003032],
48
-
49
- lunarInclination: [5.145],
50
-
51
- lunarLongitude: [
52
- 218.3164591,
53
- 481267.88134236,
54
- -0.0013268,
55
- 1 / 538841.0 - 1 / 65194000.0,
56
- ],
57
-
58
- lunarNode: [
59
- 125.044555,
60
- -1934.1361849,
61
- 0.0020762,
62
- 1 / 467410.0,
63
- -1 / 60616000.0,
64
- ],
65
-
66
- lunarPerigee: [
67
- 83.353243,
68
- 4069.0137111,
69
- -0.0103238,
70
- -1 / 80053.0,
71
- 1 / 18999000.0,
72
- ],
73
- };
74
-
75
- // Evaluates a polynomial at argument
76
- const polynomial = (coefficients, argument) => {
77
- const result = [];
78
- coefficients.forEach((coefficient, index) => {
79
- result.push(coefficient * Math.pow(argument, index));
80
- });
81
- return result.reduce((a, b) => {
82
- return a + b
83
- })
84
- };
85
-
86
- // Evaluates a derivative polynomial at argument
87
- const derivativePolynomial = (coefficients, argument) => {
88
- const result = [];
89
- coefficients.forEach((coefficient, index) => {
90
- result.push(coefficient * index * Math.pow(argument, index - 1));
91
- });
92
- return result.reduce((a, b) => {
93
- return a + b
94
- })
95
- };
96
-
97
- // Meeus formula 11.1
98
- const T = (t) => {
99
- return (JD(t) - 2451545.0) / 36525
100
- };
101
-
102
- // Meeus formula 7.1
103
- const JD = (t) => {
104
- let Y = t.getFullYear();
105
- let M = t.getMonth() + 1;
106
- const D =
107
- t.getDate() +
108
- t.getHours() / 24.0 +
109
- t.getMinutes() / (24.0 * 60.0) +
110
- t.getSeconds() / (24.0 * 60.0 * 60.0) +
111
- t.getMilliseconds() / (24.0 * 60.0 * 60.0 * 1e6);
112
- if (M <= 2) {
113
- Y = Y - 1;
114
- M = M + 12;
115
- }
116
- const A = Math.floor(Y / 100.0);
117
- const B = 2 - A + Math.floor(A / 4.0);
118
- return (
119
- Math.floor(365.25 * (Y + 4716)) +
120
- Math.floor(30.6001 * (M + 1)) +
121
- D +
122
- B -
123
- 1524.5
124
- )
125
- };
126
-
127
- /**
128
- * @todo - What's with the array returned from the arccos?
129
- * @param {*} N
130
- * @param {*} i
131
- * @param {*} omega
132
- */
133
- const _I = (N, i, omega) => {
134
- N = d2r * N;
135
- i = d2r * i;
136
- omega = d2r * omega;
137
- const cosI =
138
- Math.cos(i) * Math.cos(omega) - Math.sin(i) * Math.sin(omega) * Math.cos(N);
139
- return r2d * Math.acos(cosI)
140
- };
141
-
142
- const _xi = (N, i, omega) => {
143
- N = d2r * N;
144
- i = d2r * i;
145
- omega = d2r * omega;
146
- let e1 =
147
- (Math.cos(0.5 * (omega - i)) / Math.cos(0.5 * (omega + i))) *
148
- Math.tan(0.5 * N);
149
- let e2 =
150
- (Math.sin(0.5 * (omega - i)) / Math.sin(0.5 * (omega + i))) *
151
- Math.tan(0.5 * N);
152
- e1 = Math.atan(e1);
153
- e2 = Math.atan(e2);
154
- e1 = e1 - 0.5 * N;
155
- e2 = e2 - 0.5 * N;
156
- return -(e1 + e2) * r2d
157
- };
158
-
159
- const _nu = (N, i, omega) => {
160
- N = d2r * N;
161
- i = d2r * i;
162
- omega = d2r * omega;
163
- let e1 =
164
- (Math.cos(0.5 * (omega - i)) / Math.cos(0.5 * (omega + i))) *
165
- Math.tan(0.5 * N);
166
- let e2 =
167
- (Math.sin(0.5 * (omega - i)) / Math.sin(0.5 * (omega + i))) *
168
- Math.tan(0.5 * N);
169
- e1 = Math.atan(e1);
170
- e2 = Math.atan(e2);
171
- e1 = e1 - 0.5 * N;
172
- e2 = e2 - 0.5 * N;
173
- return (e1 - e2) * r2d
174
- };
175
-
176
- // Schureman equation 224
177
- const _nup = (N, i, omega) => {
178
- const I = d2r * _I(N, i, omega);
179
- const nu = d2r * _nu(N, i, omega);
180
- return (
181
- r2d *
182
- Math.atan(
183
- (Math.sin(2 * I) * Math.sin(nu)) /
184
- (Math.sin(2 * I) * Math.cos(nu) + 0.3347)
185
- )
186
- )
187
- };
188
-
189
- // Schureman equation 232
190
- const _nupp = (N, i, omega) => {
191
- const I = d2r * _I(N, i, omega);
192
- const nu = d2r * _nu(N, i, omega);
193
- const tan2nupp =
194
- (Math.sin(I) ** 2 * Math.sin(2 * nu)) /
195
- (Math.sin(I) ** 2 * Math.cos(2 * nu) + 0.0727);
196
- return r2d * 0.5 * Math.atan(tan2nupp)
197
- };
198
-
199
- const modulus$1 = (a, b) => {
200
- return ((a % b) + b) % b
201
- };
202
-
203
- const astro = (time) => {
204
- const result = {};
205
- const polynomials = {
206
- s: coefficients.lunarLongitude,
207
- h: coefficients.solarLongitude,
208
- p: coefficients.lunarPerigee,
209
- N: coefficients.lunarNode,
210
- pp: coefficients.solarPerigee,
211
- 90: [90.0],
212
- omega: coefficients.terrestrialObliquity,
213
- i: coefficients.lunarInclination
214
- };
215
-
216
- // Polynomials are in T, that is Julian Centuries; we want our speeds to be
217
- // in the more convenient unit of degrees per hour.
218
- const dTdHour = 1 / (24 * 365.25 * 100);
219
- Object.keys(polynomials).forEach((name) => {
220
- result[name] = {
221
- value: modulus$1(polynomial(polynomials[name], T(time)), 360.0),
222
- speed: derivativePolynomial(polynomials[name], T(time)) * dTdHour
223
- };
224
- });
225
-
226
- // Some other parameters defined by Schureman which are dependent on the
227
- // parameters N, i, omega for use in node factor calculations. We don't need
228
- // their speeds.
229
- const functions = {
230
- I: _I,
231
- xi: _xi,
232
- nu: _nu,
233
- nup: _nup,
234
- nupp: _nupp
235
- };
236
- Object.keys(functions).forEach((name) => {
237
- const functionCall = functions[name];
238
- result[name] = {
239
- value: modulus$1(
240
- functionCall(result.N.value, result.i.value, result.omega.value),
241
- 360.0
242
- ),
243
- speed: null
244
- };
245
- });
246
-
247
- // We don't work directly with the T (hours) parameter, instead our spanning
248
- // set for equilibrium arguments #is given by T+h-s, s, h, p, N, pp, 90.
249
- // This is in line with convention.
250
- const hour = {
251
- value: (JD(time) - Math.floor(JD(time))) * 360.0,
252
- speed: 15.0
253
- };
254
-
255
- result['T+h-s'] = {
256
- value: hour.value + result.h.value - result.s.value,
257
- speed: hour.speed + result.h.speed - result.s.speed
258
- };
259
-
260
- // It is convenient to calculate Schureman's P here since several node
261
- // factors need it, although it could be argued that these
262
- // (along with I, xi, nu etc) belong somewhere else.
263
- result.P = {
264
- value: result.p.value - (result.xi.value % 360.0),
265
- speed: null
266
- };
267
-
268
- return result
269
- };
270
-
271
- const modulus = (a, b) => {
272
- return ((a % b) + b) % b
273
- };
274
-
275
- const addExtremesOffsets = (extreme, offsets) => {
276
- if (typeof offsets === 'undefined' || !offsets) {
277
- return extreme
278
- }
279
- if (extreme.high && offsets.height_offset && offsets.height_offset.high) {
280
- extreme.level *= offsets.height_offset.high;
281
- }
282
- if (extreme.low && offsets.height_offset && offsets.height_offset.low) {
283
- extreme.level *= offsets.height_offset.low;
284
- }
285
- if (extreme.high && offsets.time_offset && offsets.time_offset.high) {
286
- extreme.time = new Date(
287
- extreme.time.getTime() + offsets.time_offset.high * 60 * 1000
288
- );
289
- }
290
- if (extreme.low && offsets.time_offset && offsets.time_offset.low) {
291
- extreme.time = new Date(
292
- extreme.time.getTime() + offsets.time_offset.low * 60 * 1000
293
- );
294
- }
295
- return extreme
296
- };
297
-
298
- const getExtremeLabel = (label, highLowLabels) => {
299
- if (
300
- typeof highLowLabels !== 'undefined' &&
301
- typeof highLowLabels[label] !== 'undefined'
302
- ) {
303
- return highLowLabels[label]
304
- }
305
- const labels = {
306
- high: 'High',
307
- low: 'Low'
308
- };
309
- return labels[label]
310
- };
311
-
312
- const predictionFactory = ({ timeline, constituents, start }) => {
313
- const getLevel = (hour, modelBaseSpeed, modelU, modelF, modelBaseValue) => {
314
- const amplitudes = [];
315
- let result = 0;
316
-
317
- constituents.forEach((constituent) => {
318
- const amplitude = constituent.amplitude;
319
- const phase = constituent._phase;
320
- const f = modelF[constituent.name];
321
- const speed = modelBaseSpeed[constituent.name];
322
- const u = modelU[constituent.name];
323
- const V0 = modelBaseValue[constituent.name];
324
- amplitudes.push(amplitude * f * Math.cos(speed * hour + (V0 + u) - phase));
325
- });
326
- // sum up each row
327
- amplitudes.forEach((item) => {
328
- result += item;
329
- });
330
- return result
331
- };
332
-
333
- const prediction = {};
334
-
335
- prediction.getExtremesPrediction = (options) => {
336
- const { labels, offsets } = typeof options !== 'undefined' ? options : {};
337
- const results = [];
338
- const { baseSpeed, u, f, baseValue } = prepare();
339
- let goingUp = false;
340
- let goingDown = false;
341
- let lastLevel = getLevel(0, baseSpeed, u[0], f[0], baseValue);
342
- timeline.items.forEach((time, index) => {
343
- const hour = timeline.hours[index];
344
- const level = getLevel(hour, baseSpeed, u[index], f[index], baseValue);
345
- // Compare this level to the last one, if we
346
- // are changing angle, then the last one was high or low
347
- if (level > lastLevel && goingDown) {
348
- results.push(
349
- addExtremesOffsets(
350
- {
351
- time: timeline.items[index - 1],
352
- level: lastLevel,
353
- high: false,
354
- low: true,
355
- label: getExtremeLabel('low', labels)
356
- },
357
- offsets
358
- )
359
- );
360
- }
361
- if (level < lastLevel && goingUp) {
362
- results.push(
363
- addExtremesOffsets(
364
- {
365
- time: timeline.items[index - 1],
366
- level: lastLevel,
367
- high: true,
368
- low: false,
369
- label: getExtremeLabel('high', labels)
370
- },
371
- offsets
372
- )
373
- );
374
- }
375
- if (level > lastLevel) {
376
- goingUp = true;
377
- goingDown = false;
378
- }
379
- if (level < lastLevel) {
380
- goingUp = false;
381
- goingDown = true;
382
- }
383
- lastLevel = level;
384
- });
385
- return results
386
- };
387
-
388
- prediction.getTimelinePrediction = () => {
389
- const results = [];
390
- const { baseSpeed, u, f, baseValue } = prepare();
391
- timeline.items.forEach((time, index) => {
392
- const hour = timeline.hours[index];
393
- const prediction = {
394
- time,
395
- hour,
396
- level: getLevel(hour, baseSpeed, u[index], f[index], baseValue)
397
- };
398
-
399
- results.push(prediction);
400
- });
401
- return results
402
- };
403
-
404
- const prepare = () => {
405
- const baseAstro = astro(start);
406
-
407
- const baseValue = {};
408
- const baseSpeed = {};
409
- const u = [];
410
- const f = [];
411
- constituents.forEach((constituent) => {
412
- const value = constituent._model.value(baseAstro);
413
- const speed = constituent._model.speed(baseAstro);
414
- baseValue[constituent.name] = d2r * value;
415
- baseSpeed[constituent.name] = d2r * speed;
416
- });
417
- timeline.items.forEach((time) => {
418
- const uItem = {};
419
- const fItem = {};
420
- const itemAstro = astro(time);
421
- constituents.forEach((constituent) => {
422
- const constituentU = modulus(constituent._model.u(itemAstro), 360);
423
-
424
- uItem[constituent.name] = d2r * constituentU;
425
- fItem[constituent.name] = modulus(constituent._model.f(itemAstro), 360);
426
- });
427
- u.push(uItem);
428
- f.push(fItem);
429
- });
430
-
431
- return {
432
- baseValue,
433
- baseSpeed,
434
- u,
435
- f
436
- }
437
- };
438
-
439
- return Object.freeze(prediction)
440
- };
441
-
442
- const corrections = {
443
- fUnity() {
444
- return 1
445
- },
446
-
447
- // Schureman equations 73, 65
448
- fMm(a) {
449
- const omega = d2r * a.omega.value;
450
- const i = d2r * a.i.value;
451
- const I = d2r * a.I.value;
452
- const mean =
453
- (2 / 3.0 - Math.pow(Math.sin(omega), 2)) *
454
- (1 - (3 / 2.0) * Math.pow(Math.sin(i), 2));
455
- return (2 / 3.0 - Math.pow(Math.sin(I), 2)) / mean
456
- },
457
-
458
- // Schureman equations 74, 66
459
- fMf(a) {
460
- const omega = d2r * a.omega.value;
461
- const i = d2r * a.i.value;
462
- const I = d2r * a.I.value;
463
- const mean = Math.pow(Math.sin(omega), 2) * Math.pow(Math.cos(0.5 * i), 4);
464
- return Math.pow(Math.sin(I), 2) / mean
465
- },
466
-
467
- // Schureman equations 75, 67
468
- fO1(a) {
469
- const omega = d2r * a.omega.value;
470
- const i = d2r * a.i.value;
471
- const I = d2r * a.I.value;
472
- const mean =
473
- Math.sin(omega) *
474
- Math.pow(Math.cos(0.5 * omega), 2) *
475
- Math.pow(Math.cos(0.5 * i), 4);
476
- return (Math.sin(I) * Math.pow(Math.cos(0.5 * I), 2)) / mean
477
- },
478
-
479
- // Schureman equations 76, 68
480
- fJ1(a) {
481
- const omega = d2r * a.omega.value;
482
- const i = d2r * a.i.value;
483
- const I = d2r * a.I.value;
484
- const mean =
485
- Math.sin(2 * omega) * (1 - (3 / 2.0) * Math.pow(Math.sin(i), 2));
486
- return Math.sin(2 * I) / mean
487
- },
488
-
489
- // Schureman equations 77, 69
490
- fOO1(a) {
491
- const omega = d2r * a.omega.value;
492
- const i = d2r * a.i.value;
493
- const I = d2r * a.I.value;
494
- const mean =
495
- Math.sin(omega) *
496
- Math.pow(Math.sin(0.5 * omega), 2) *
497
- Math.pow(Math.cos(0.5 * i), 4);
498
- return (Math.sin(I) * Math.pow(Math.sin(0.5 * I), 2)) / mean
499
- },
500
-
501
- // Schureman equations 78, 70
502
- fM2(a) {
503
- const omega = d2r * a.omega.value;
504
- const i = d2r * a.i.value;
505
- const I = d2r * a.I.value;
506
- const mean =
507
- Math.pow(Math.cos(0.5 * omega), 4) * Math.pow(Math.cos(0.5 * i), 4);
508
- return Math.pow(Math.cos(0.5 * I), 4) / mean
509
- },
510
-
511
- // Schureman equations 227, 226, 68
512
- // Should probably eventually include the derivations of the magic numbers (0.5023 etc).
513
- fK1(a) {
514
- const omega = d2r * a.omega.value;
515
- const i = d2r * a.i.value;
516
- const I = d2r * a.I.value;
517
- const nu = d2r * a.nu.value;
518
- const sin2IcosnuMean =
519
- Math.sin(2 * omega) * (1 - (3 / 2.0) * Math.pow(Math.sin(i), 2));
520
- const mean = 0.5023 * sin2IcosnuMean + 0.1681;
521
- return (
522
- Math.pow(
523
- 0.2523 * Math.pow(Math.sin(2 * I), 2) +
524
- 0.1689 * Math.sin(2 * I) * Math.cos(nu) +
525
- 0.0283,
526
- 0.5
527
- ) / mean
528
- )
529
- },
530
-
531
- // Schureman equations 215, 213, 204
532
- // It can be (and has been) confirmed that the exponent for R_a reads 1/2 via Schureman Table 7
533
- fL2(a) {
534
- const P = d2r * a.P.value;
535
- const I = d2r * a.I.value;
536
- const rAInv = Math.pow(
537
- 1 -
538
- 12 * Math.pow(Math.tan(0.5 * I), 2) * Math.cos(2 * P) +
539
- 36 * Math.pow(Math.tan(0.5 * I), 4),
540
- 0.5
541
- );
542
- return corrections.fM2(a) * rAInv
543
- },
544
-
545
- // Schureman equations 235, 234, 71
546
- // Again, magic numbers
547
- fK2(a) {
548
- const omega = d2r * a.omega.value;
549
- const i = d2r * a.i.value;
550
- const I = d2r * a.I.value;
551
- const nu = d2r * a.nu.value;
552
- const sinsqIcos2nuMean =
553
- Math.sin(omega) ** 2 * (1 - (3 / 2.0) * Math.sin(i) ** 2);
554
- const mean = 0.5023 * sinsqIcos2nuMean + 0.0365;
555
- return (
556
- Math.pow(
557
- 0.2523 * Math.pow(Math.sin(I), 4) +
558
- 0.0367 * Math.pow(Math.sin(I), 2) * Math.cos(2 * nu) +
559
- 0.0013,
560
- 0.5
561
- ) / mean
562
- )
563
- },
564
- // Schureman equations 206, 207, 195
565
- fM1(a) {
566
- const P = d2r * a.P.value;
567
- const I = d2r * a.I.value;
568
- const qAInv = Math.pow(
569
- 0.25 +
570
- 1.5 *
571
- Math.cos(I) *
572
- Math.cos(2 * P) *
573
- Math.pow(Math.cos(0.5 * I), -0.5) +
574
- 2.25 * Math.pow(Math.cos(I), 2) * Math.pow(Math.cos(0.5 * I), -4),
575
- 0.5
576
- );
577
- return corrections.fO1(a) * qAInv
578
- },
579
-
580
- // See e.g. Schureman equation 149
581
- fModd(a, n) {
582
- return Math.pow(corrections.fM2(a), n / 2.0)
583
- },
584
-
585
- // Node factors u, see Table 2 of Schureman.
586
-
587
- uZero() {
588
- return 0.0
589
- },
590
-
591
- uMf(a) {
592
- return -2.0 * a.xi.value
593
- },
594
-
595
- uO1(a) {
596
- return 2.0 * a.xi.value - a.nu.value
597
- },
598
-
599
- uJ1(a) {
600
- return -a.nu.value
601
- },
602
-
603
- uOO1(a) {
604
- return -2.0 * a.xi.value - a.nu.value
605
- },
606
-
607
- uM2(a) {
608
- return 2.0 * a.xi.value - 2.0 * a.nu.value
609
- },
610
-
611
- uK1(a) {
612
- return -a.nup.value
613
- },
614
-
615
- // Schureman 214
616
- uL2(a) {
617
- const I = d2r * a.I.value;
618
- const P = d2r * a.P.value;
619
- const R =
620
- r2d *
621
- Math.atan(
622
- Math.sin(2 * P) /
623
- ((1 / 6.0) * Math.pow(Math.tan(0.5 * I), -2) - Math.cos(2 * P))
624
- );
625
- return 2.0 * a.xi.value - 2.0 * a.nu.value - R
626
- },
627
-
628
- uK2(a) {
629
- return -2.0 * a.nupp.value
630
- },
631
-
632
- // Schureman 202
633
- uM1(a) {
634
- const I = d2r * a.I.value;
635
- const P = d2r * a.P.value;
636
- const Q =
637
- r2d *
638
- Math.atan(((5 * Math.cos(I) - 1) / (7 * Math.cos(I) + 1)) * Math.tan(P));
639
- return a.xi.value - a.nu.value + Q
640
- },
641
-
642
- uModd(a, n) {
643
- return (n / 2.0) * corrections.uM2(a)
644
- }
645
- };
646
-
647
- /**
648
- * Computes the dot notation of two arrays
649
- * @param {*} a
650
- * @param {*} b
651
- */
652
- const dotArray = (a, b) => {
653
- const results = [];
654
- a.forEach((value, index) => {
655
- results.push(value * b[index]);
656
- });
657
- return results.reduce((total, value) => {
658
- return total + value
659
- })
660
- };
661
-
662
- const astronimicDoodsonNumber = (astro) => {
663
- return [
664
- astro['T+h-s'],
665
- astro.s,
666
- astro.h,
667
- astro.p,
668
- astro.N,
669
- astro.pp,
670
- astro['90']
671
- ]
672
- };
673
-
674
- const astronomicSpeed = (astro) => {
675
- const results = [];
676
- astronimicDoodsonNumber(astro).forEach((number) => {
677
- results.push(number.speed);
678
- });
679
- return results
680
- };
681
-
682
- const astronomicValues = (astro) => {
683
- const results = [];
684
- astronimicDoodsonNumber(astro).forEach((number) => {
685
- results.push(number.value);
686
- });
687
- return results
688
- };
689
-
690
- const constituentFactory = (name, coefficients, u, f) => {
691
- if (!coefficients) {
692
- throw new Error('Coefficient must be defined for a constituent')
693
- }
694
-
695
- const constituent = {
696
- name,
697
- coefficients,
698
-
699
- value: (astro) => {
700
- return dotArray(coefficients, astronomicValues(astro))
701
- },
702
-
703
- speed(astro) {
704
- return dotArray(coefficients, astronomicSpeed(astro))
705
- },
706
-
707
- u: typeof u !== 'undefined' ? u : corrections.uZero,
708
-
709
- f: typeof f !== 'undefined' ? f : corrections.fUnity
710
- };
711
-
712
- return Object.freeze(constituent)
713
- };
714
-
715
- const compoundConstituentFactory = (name, members) => {
716
- const coefficients = [];
717
- members.forEach(({ constituent, factor }) => {
718
- constituent.coefficients.forEach((coefficient, index) => {
719
- if (typeof coefficients[index] === 'undefined') {
720
- coefficients[index] = 0;
721
- }
722
- coefficients[index] += coefficient * factor;
723
- });
724
- });
725
-
726
- const compoundConstituent = {
727
- name,
728
- coefficients,
729
-
730
- speed: (astro) => {
731
- let speed = 0;
732
- members.forEach(({ constituent, factor }) => {
733
- speed += constituent.speed(astro) * factor;
734
- });
735
- return speed
736
- },
737
-
738
- value: (astro) => {
739
- let value = 0;
740
- members.forEach(({ constituent, factor }) => {
741
- value += constituent.value(astro) * factor;
742
- });
743
- return value
744
- },
745
-
746
- u: (astro) => {
747
- let u = 0;
748
- members.forEach(({ constituent, factor }) => {
749
- u += constituent.u(astro) * factor;
750
- });
751
- return u
752
- },
753
-
754
- f: (astro) => {
755
- const f = [];
756
- members.forEach(({ constituent, factor }) => {
757
- f.push(Math.pow(constituent.f(astro), Math.abs(factor)));
758
- });
759
- return f.reduce((previous, value) => {
760
- return previous * value
761
- })
762
- }
763
- };
764
-
765
- return Object.freeze(compoundConstituent)
766
- };
767
-
768
- const constituents = {};
769
- // Long Term
770
- constituents.Z0 = constituentFactory('Z0', [0, 0, 0, 0, 0, 0, 0], corrections.uZero, corrections.fUnity);
771
- constituents.SA = constituentFactory('Sa', [0, 0, 1, 0, 0, 0, 0], corrections.uZero, corrections.fUnity);
772
- constituents.SSA = constituentFactory(
773
- 'Ssa',
774
- [0, 0, 2, 0, 0, 0, 0],
775
- corrections.uZero,
776
- corrections.fUnity
777
- );
778
- constituents.MM = constituentFactory('MM', [0, 1, 0, -1, 0, 0, 0], corrections.uZero, corrections.fMm);
779
- constituents.MF = constituentFactory('MF', [0, 2, 0, 0, 0, 0, 0], corrections.uMf, corrections.fMf);
780
- // Diurnals
781
- constituents.Q1 = constituentFactory('Q1', [1, -2, 0, 1, 0, 0, 1], corrections.uO1, corrections.fO1);
782
- constituents.O1 = constituentFactory('O1', [1, -1, 0, 0, 0, 0, 1], corrections.uO1, corrections.fO1);
783
- constituents.K1 = constituentFactory('K1', [1, 1, 0, 0, 0, 0, -1], corrections.uK1, corrections.fK1);
784
- constituents.J1 = constituentFactory('J1', [1, 2, 0, -1, 0, 0, -1], corrections.uJ1, corrections.fJ1);
785
- constituents.M1 = constituentFactory('M1', [1, 0, 0, 0, 0, 0, 1], corrections.uM1, corrections.fM1);
786
- constituents.P1 = constituentFactory('P1', [1, 1, -2, 0, 0, 0, 1], corrections.uZero, corrections.fUnity);
787
- constituents.S1 = constituentFactory('S1', [1, 1, -1, 0, 0, 0, 0], corrections.uZero, corrections.fUnity);
788
- constituents.OO1 = constituentFactory('OO1', [1, 3, 0, 0, 0, 0, -1], corrections.uOO1, corrections.fOO1);
789
- // Semi diurnals
790
- constituents['2N2'] = constituentFactory('2N2', [2, -2, 0, 2, 0, 0, 0], corrections.uM2, corrections.fM2);
791
- constituents.N2 = constituentFactory('N2', [2, -1, 0, 1, 0, 0, 0], corrections.uM2, corrections.fM2);
792
- constituents.NU2 = constituentFactory('NU2', [2, -1, 2, -1, 0, 0, 0], corrections.uM2, corrections.fM2);
793
- constituents.M2 = constituentFactory('M2', [2, 0, 0, 0, 0, 0, 0], corrections.uM2, corrections.fM2);
794
- constituents.LAM2 = constituentFactory('LAM2', [2, 1, -2, 1, 0, 0, 2], corrections.uM2, corrections.fM2);
795
- constituents.L2 = constituentFactory('L2', [2, 1, 0, -1, 0, 0, 2], corrections.uL2, corrections.fL2);
796
- constituents.T2 = constituentFactory('T2', [2, 2, -3, 0, 0, 1, 0], corrections.uZero, corrections.fUnity);
797
- constituents.S2 = constituentFactory('S2', [2, 2, -2, 0, 0, 0, 0], corrections.uZero, corrections.fUnity);
798
- constituents.R2 = constituentFactory(
799
- 'R2',
800
- [2, 2, -1, 0, 0, -1, 2],
801
- corrections.uZero,
802
- corrections.fUnity
803
- );
804
- constituents.K2 = constituentFactory('K2', [2, 2, 0, 0, 0, 0, 0], corrections.uK2, corrections.fK2);
805
- // Third diurnal
806
- constituents.M3 = constituentFactory(
807
- 'M3',
808
- [3, 0, 0, 0, 0, 0, 0],
809
- (a) => {
810
- return corrections.uModd(a, 3)
811
- },
812
- (a) => {
813
- return corrections.fModd(a, 3)
814
- }
815
- );
816
- // Compound
817
- constituents.MSF = compoundConstituentFactory('MSF', [
818
- { constituent: constituents.S2, factor: 1 },
819
- { constituent: constituents.M2, factor: -1 }
820
- ]);
821
-
822
- // Diurnal
823
- constituents['2Q1'] = compoundConstituentFactory('2Q1', [
824
- { constituent: constituents.N2, factor: 1 },
825
- { constituent: constituents.J1, factor: -1 }
826
- ]);
827
- constituents.RHO = compoundConstituentFactory('RHO', [
828
- { constituent: constituents.NU2, factor: 1 },
829
- { constituent: constituents.K1, factor: -1 }
830
- ]);
831
-
832
- // Semi-Diurnal
833
-
834
- constituents.MU2 = compoundConstituentFactory('MU2', [
835
- { constituent: constituents.M2, factor: 2 },
836
- { constituent: constituents.S2, factor: -1 }
837
- ]);
838
- constituents['2SM2'] = compoundConstituentFactory('2SM2', [
839
- { constituent: constituents.S2, factor: 2 },
840
- { constituent: constituents.M2, factor: -1 }
841
- ]);
842
-
843
- // Third-Diurnal
844
- constituents['2MK3'] = compoundConstituentFactory('2MK3', [
845
- { constituent: constituents.M2, factor: 1 },
846
- { constituent: constituents.O1, factor: 1 }
847
- ]);
848
- constituents.MK3 = compoundConstituentFactory('MK3', [
849
- { constituent: constituents.M2, factor: 1 },
850
- { constituent: constituents.K1, factor: 1 }
851
- ]);
852
-
853
- // Quarter-Diurnal
854
- constituents.MN4 = compoundConstituentFactory('MN4', [
855
- { constituent: constituents.M2, factor: 1 },
856
- { constituent: constituents.N2, factor: 1 }
857
- ]);
858
- constituents.M4 = compoundConstituentFactory('M4', [
859
- { constituent: constituents.M2, factor: 2 }
860
- ]);
861
- constituents.MS4 = compoundConstituentFactory('MS4', [
862
- { constituent: constituents.M2, factor: 1 },
863
- { constituent: constituents.S2, factor: 1 }
864
- ]);
865
- constituents.S4 = compoundConstituentFactory('S4', [
866
- { constituent: constituents.S2, factor: 2 }
867
- ]);
868
-
869
- // Sixth-Diurnal
870
- constituents.M6 = compoundConstituentFactory('M6', [
871
- { constituent: constituents.M2, factor: 3 }
872
- ]);
873
- constituents.S6 = compoundConstituentFactory('S6', [
874
- { constituent: constituents.S2, factor: 3 }
875
- ]);
876
-
877
- // Eighth-Diurnals
878
- constituents.M8 = compoundConstituentFactory('M8', [
879
- { constituent: constituents.M2, factor: 4 }
880
- ]);
881
-
882
- const getDate = (time) => {
883
- if (time instanceof Date) {
884
- return time
885
- }
886
- if (typeof time === 'number') {
887
- return new Date(time * 1000)
888
- }
889
- throw new Error('Invalid date format, should be a Date object, or timestamp')
890
- };
891
-
892
- const getTimeline = (start, end, seconds) => {
893
- seconds = typeof seconds !== 'undefined' ? seconds : 10 * 60;
894
- const items = [];
895
- const endTime = end.getTime() / 1000;
896
- let lastTime = start.getTime() / 1000;
897
- const startTime = lastTime;
898
- const hours = [];
899
- while (lastTime <= endTime) {
900
- items.push(new Date(lastTime * 1000));
901
- hours.push((lastTime - startTime) / (60 * 60));
902
- lastTime += seconds;
903
- }
904
-
905
- return {
906
- items,
907
- hours
908
- }
909
- };
910
-
911
- const harmonicsFactory = ({ harmonicConstituents, phaseKey, offset }) => {
912
- if (!Array.isArray(harmonicConstituents)) {
913
- throw new Error('Harmonic constituents are not an array')
914
- }
915
- const constituents$1 = [];
916
- harmonicConstituents.forEach((constituent) => {
917
- if (typeof constituent.name === 'undefined') {
918
- throw new Error('Harmonic constituents must have a name property')
919
- }
920
- if (typeof constituents[constituent.name] !== 'undefined') {
921
- constituent._model = constituents[constituent.name];
922
- constituent._phase = d2r * constituent[phaseKey];
923
- constituents$1.push(constituent);
924
- }
925
- });
926
-
927
- if (offset !== false) {
928
- constituents$1.push({
929
- name: 'Z0',
930
- _model: constituents.Z0,
931
- _phase: 0,
932
- amplitude: offset
933
- });
934
- }
935
-
936
- let start = new Date();
937
- let end = new Date();
938
-
939
- const harmonics = {};
940
-
941
- harmonics.setTimeSpan = (startTime, endTime) => {
942
- start = getDate(startTime);
943
- end = getDate(endTime);
944
- if (start.getTime() >= end.getTime()) {
945
- throw new Error('Start time must be before end time')
946
- }
947
- return harmonics
948
- };
949
-
950
- harmonics.prediction = (options) => {
951
- options =
952
- typeof options !== 'undefined' ? options : { timeFidelity: 10 * 60 };
953
- return predictionFactory({
954
- timeline: getTimeline(start, end, options.timeFidelity),
955
- constituents: constituents$1,
956
- start
957
- })
958
- };
959
-
960
- return Object.freeze(harmonics)
961
- };
962
-
963
- const tidePredictionFactory = (constituents, options) => {
964
- const harmonicsOptions = {
965
- harmonicConstituents: constituents,
966
- phaseKey: 'phase_GMT',
967
- offset: false
968
- };
969
-
970
- if (typeof options !== 'undefined') {
971
- Object.keys(harmonicsOptions).forEach((key) => {
972
- if (typeof options[key] !== 'undefined') {
973
- harmonicsOptions[key] = options[key];
974
- }
975
- });
976
- }
977
-
978
- const tidePrediction = {
979
- getTimelinePrediction: ({ start, end }) => {
980
- return harmonicsFactory(harmonicsOptions)
981
- .setTimeSpan(start, end)
982
- .prediction()
983
- .getTimelinePrediction()
984
- },
985
-
986
- getExtremesPrediction: ({ start, end, labels, offsets, timeFidelity }) => {
987
- return harmonicsFactory(harmonicsOptions)
988
- .setTimeSpan(start, end)
989
- .prediction({ timeFidelity })
990
- .getExtremesPrediction(labels, offsets)
991
- },
992
-
993
- getWaterLevelAtTime: ({ time }) => {
994
- const endDate = new Date(time.getTime() + 10 * 60 * 1000);
995
- return harmonicsFactory(harmonicsOptions)
996
- .setTimeSpan(time, endDate)
997
- .prediction()
998
- .getTimelinePrediction()[0]
999
- }
1000
- };
1001
-
1002
- return tidePrediction
1003
- };
1004
-
1005
- module.exports = tidePredictionFactory;