@neaps/tide-predictor 0.0.4

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 (46) hide show
  1. package/.eslintrc.js +22 -0
  2. package/.github/workflows/test.yml +15 -0
  3. package/.prettierrc +4 -0
  4. package/Gruntfile.js +87 -0
  5. package/LICENSE +21 -0
  6. package/README.md +199 -0
  7. package/babel.config.js +9 -0
  8. package/dist/tide-predictor.js +1013 -0
  9. package/examples/browser/index.html +51 -0
  10. package/jest.config.js +14 -0
  11. package/lib/astronomy/coefficients.js +31 -0
  12. package/lib/astronomy/constants.js +10 -0
  13. package/lib/astronomy/index.js +199 -0
  14. package/lib/constituents/compound-constituent.js +67 -0
  15. package/lib/constituents/constituent.js +74 -0
  16. package/lib/constituents/index.js +140 -0
  17. package/lib/harmonics/index.js +113 -0
  18. package/lib/harmonics/prediction.js +195 -0
  19. package/lib/index.es6.js +1005 -0
  20. package/lib/index.js +53 -0
  21. package/lib/node-corrections/index.js +147 -0
  22. package/package.json +45 -0
  23. package/rollup.config.js +21 -0
  24. package/src/__mocks__/constituents.js +335 -0
  25. package/src/__mocks__/secondary-station.js +11 -0
  26. package/src/__tests__/index.js +81 -0
  27. package/src/__tests__/noaa.js +92 -0
  28. package/src/astronomy/__tests__/coefficients.js +12 -0
  29. package/src/astronomy/__tests__/index.js +96 -0
  30. package/src/astronomy/coefficients.js +72 -0
  31. package/src/astronomy/constants.js +4 -0
  32. package/src/astronomy/index.js +201 -0
  33. package/src/constituents/__tests__/compound-constituent.js +44 -0
  34. package/src/constituents/__tests__/constituent.js +65 -0
  35. package/src/constituents/__tests__/index.js +34 -0
  36. package/src/constituents/compound-constituent.js +55 -0
  37. package/src/constituents/constituent.js +74 -0
  38. package/src/constituents/index.js +119 -0
  39. package/src/harmonics/__mocks__/water-levels.js +0 -0
  40. package/src/harmonics/__tests__/index.js +123 -0
  41. package/src/harmonics/__tests__/prediction.js +148 -0
  42. package/src/harmonics/index.js +87 -0
  43. package/src/harmonics/prediction.js +175 -0
  44. package/src/index.js +45 -0
  45. package/src/node-corrections/__tests__/index.js +114 -0
  46. package/src/node-corrections/index.js +208 -0
@@ -0,0 +1,44 @@
1
+ import compoundConstituent from '../compound-constituent'
2
+ import Constituent from '../constituent'
3
+ import astro from '../../astronomy'
4
+
5
+ const sampleTime = new Date()
6
+ sampleTime.setFullYear(2019)
7
+ sampleTime.setMonth(9)
8
+ sampleTime.setDate(4)
9
+ sampleTime.setHours(10)
10
+ sampleTime.setMinutes(15)
11
+ sampleTime.setSeconds(40)
12
+ sampleTime.setMilliseconds(10)
13
+
14
+ const testAstro = astro(sampleTime)
15
+
16
+ // This is a made-up doodson number for a test coefficient
17
+ const testConstituentA = new Constituent('testa', [1, 1, -1, 0, 0, 0, 1])
18
+ const testConstituentB = new Constituent('testb', [0, 1, -1, 0, 0, 0, 1])
19
+
20
+ const compoundTest = compoundConstituent('test compound', [
21
+ { constituent: testConstituentA, factor: 1 },
22
+ { constituent: testConstituentB, factor: -1 }
23
+ ])
24
+ describe('compund constituent', () => {
25
+ test('it calculates compound coefficients', () => {
26
+ expect(compoundTest.coefficients).toEqual([1, 0, 0, 0, 0, 0, 0])
27
+ })
28
+
29
+ test('it calculates speed', () => {
30
+ expect(compoundTest.speed(testAstro)).toBeCloseTo(14.4920521208, 4)
31
+ })
32
+
33
+ test('it calculates value', () => {
34
+ expect(compoundTest.value(testAstro)).toBeCloseTo(268.504355062, 4)
35
+ })
36
+
37
+ test('it returns u correctly', () => {
38
+ expect(compoundTest.u(testAstro)).toBe(0)
39
+ })
40
+
41
+ test('it returns f correctly', () => {
42
+ expect(compoundTest.f(testAstro)).toBe(1)
43
+ })
44
+ })
@@ -0,0 +1,65 @@
1
+ import constituent, {
2
+ astronimicDoodsonNumber,
3
+ astronomicSpeed,
4
+ astronomicValues
5
+ } from '../constituent'
6
+ import astro from '../../astronomy'
7
+
8
+ const sampleTime = new Date()
9
+ sampleTime.setFullYear(2019)
10
+ sampleTime.setMonth(9)
11
+ sampleTime.setDate(4)
12
+ sampleTime.setHours(10)
13
+ sampleTime.setMinutes(15)
14
+ sampleTime.setSeconds(40)
15
+ sampleTime.setMilliseconds(10)
16
+
17
+ const testAstro = astro(sampleTime)
18
+
19
+ // This is a made-up doodson number for a test coefficient
20
+ const testConstituent = constituent('test', [1, 1, -1, 0, 0, 0, 1])
21
+
22
+ describe('constituent', () => {
23
+ test('it throws error if missing coefficients', () => {
24
+ let errorMessage = false
25
+ try {
26
+ const a = constituent('fail') // eslint-disable-line
27
+ } catch (error) {
28
+ errorMessage = error
29
+ }
30
+ expect(errorMessage.message).toBe(
31
+ 'Coefficient must be defined for a constituent'
32
+ )
33
+ })
34
+
35
+ test('it fetches astronimic Doodson Number values', () => {
36
+ const values = astronimicDoodsonNumber(testAstro)
37
+ expect(values[0].value).toBe(testAstro['T+h-s'].value)
38
+ })
39
+
40
+ test('it fetches astronimic speed', () => {
41
+ const values = astronomicSpeed(testAstro)
42
+ expect(values[0]).toBe(testAstro['T+h-s'].speed)
43
+ })
44
+
45
+ test('it fetches astronimic values', () => {
46
+ const values = astronomicValues(testAstro)
47
+ expect(values[0]).toBe(testAstro['T+h-s'].value)
48
+ })
49
+
50
+ test('it computes constituent value', () => {
51
+ expect(testConstituent.value(testAstro)).toBeCloseTo(423.916666657, 4)
52
+ })
53
+
54
+ test('it computes constituent speed', () => {
55
+ expect(testConstituent.speed(testAstro)).toBe(15)
56
+ })
57
+
58
+ test('it returns u correctly', () => {
59
+ expect(testConstituent.u(testAstro)).toBe(0)
60
+ })
61
+
62
+ test('it returns f correctly', () => {
63
+ expect(testConstituent.f(testAstro)).toBe(1)
64
+ })
65
+ })
@@ -0,0 +1,34 @@
1
+ import constituents from '../index'
2
+ import astro from '../../astronomy'
3
+
4
+ const sampleTime = new Date()
5
+ sampleTime.setFullYear(2019)
6
+ sampleTime.setMonth(9)
7
+ sampleTime.setDate(4)
8
+ sampleTime.setHours(10)
9
+ sampleTime.setMinutes(15)
10
+ sampleTime.setSeconds(40)
11
+ sampleTime.setMilliseconds(10)
12
+
13
+ const testAstro = astro(sampleTime)
14
+
15
+ describe('Base constituent definitions', () => {
16
+ test('it prepared constituent SA', () => {
17
+ expect(constituents.SA.value(testAstro)).toBeCloseTo(192.826398978, 4)
18
+ })
19
+
20
+ test('it prepared constituent SSA', () => {
21
+ expect(constituents.SSA.value(testAstro)).toBeCloseTo(385.652797955, 4)
22
+ })
23
+
24
+ test('it prepared constituent M2', () => {
25
+ expect(constituents.M2.value(testAstro)).toBeCloseTo(537.008710124, 4)
26
+ expect(constituents.M2.u(testAstro)).toBeCloseTo(-2.07725095711, 4)
27
+ expect(constituents.M2.f(testAstro)).toBeCloseTo(1.00853563237, 4)
28
+ })
29
+
30
+ test('has a correct lambda for M3', () => {
31
+ expect(constituents.M3.u(testAstro)).toBeCloseTo(-3.11587643567, 4)
32
+ expect(constituents.M3.f(testAstro)).toBeCloseTo(1.01283073119, 4)
33
+ })
34
+ })
@@ -0,0 +1,55 @@
1
+ const compoundConstituentFactory = (name, members) => {
2
+ const coefficients = []
3
+ members.forEach(({ constituent, factor }) => {
4
+ constituent.coefficients.forEach((coefficient, index) => {
5
+ if (typeof coefficients[index] === 'undefined') {
6
+ coefficients[index] = 0
7
+ }
8
+ coefficients[index] += coefficient * factor
9
+ })
10
+ })
11
+
12
+ const compoundConstituent = {
13
+ name: name,
14
+
15
+ coefficients: coefficients,
16
+
17
+ speed: (astro) => {
18
+ let speed = 0
19
+ members.forEach(({ constituent, factor }) => {
20
+ speed += constituent.speed(astro) * factor
21
+ })
22
+ return speed
23
+ },
24
+
25
+ value: (astro) => {
26
+ let value = 0
27
+ members.forEach(({ constituent, factor }) => {
28
+ value += constituent.value(astro) * factor
29
+ })
30
+ return value
31
+ },
32
+
33
+ u: (astro) => {
34
+ let u = 0
35
+ members.forEach(({ constituent, factor }) => {
36
+ u += constituent.u(astro) * factor
37
+ })
38
+ return u
39
+ },
40
+
41
+ f: (astro) => {
42
+ const f = []
43
+ members.forEach(({ constituent, factor }) => {
44
+ f.push(Math.pow(constituent.f(astro), Math.abs(factor)))
45
+ })
46
+ return f.reduce((previous, value) => {
47
+ return previous * value
48
+ })
49
+ },
50
+ }
51
+
52
+ return Object.freeze(compoundConstituent)
53
+ }
54
+
55
+ export default compoundConstituentFactory
@@ -0,0 +1,74 @@
1
+ import nodeCorrections from '../node-corrections/index'
2
+
3
+ /**
4
+ * Computes the dot notation of two arrays
5
+ * @param {*} a
6
+ * @param {*} b
7
+ */
8
+ const dotArray = (a, b) => {
9
+ const results = []
10
+ a.forEach((value, index) => {
11
+ results.push(value * b[index])
12
+ })
13
+ return results.reduce((total, value) => {
14
+ return total + value
15
+ })
16
+ }
17
+
18
+ const astronimicDoodsonNumber = (astro) => {
19
+ return [
20
+ astro['T+h-s'],
21
+ astro.s,
22
+ astro.h,
23
+ astro.p,
24
+ astro.N,
25
+ astro.pp,
26
+ astro['90'],
27
+ ]
28
+ }
29
+
30
+ const astronomicSpeed = (astro) => {
31
+ const results = []
32
+ astronimicDoodsonNumber(astro).forEach((number) => {
33
+ results.push(number.speed)
34
+ })
35
+ return results
36
+ }
37
+
38
+ const astronomicValues = (astro) => {
39
+ const results = []
40
+ astronimicDoodsonNumber(astro).forEach((number) => {
41
+ results.push(number.value)
42
+ })
43
+ return results
44
+ }
45
+
46
+ const constituentFactory = (name, coefficients, u, f) => {
47
+ if (!coefficients) {
48
+ throw new Error('Coefficient must be defined for a constituent')
49
+ }
50
+
51
+ const constituent = {
52
+ name: name,
53
+
54
+ coefficients: coefficients,
55
+
56
+ value: (astro) => {
57
+ return dotArray(coefficients, astronomicValues(astro))
58
+ },
59
+
60
+ speed(astro) {
61
+ return dotArray(coefficients, astronomicSpeed(astro))
62
+ },
63
+
64
+ u: typeof u !== 'undefined' ? u : nodeCorrections.uZero,
65
+
66
+ f: typeof f !== 'undefined' ? f : nodeCorrections.fUnity,
67
+ }
68
+
69
+ return Object.freeze(constituent)
70
+ }
71
+
72
+ export default constituentFactory
73
+
74
+ export { astronimicDoodsonNumber, astronomicSpeed, astronomicValues }
@@ -0,0 +1,119 @@
1
+ import constituent from './constituent'
2
+ import compoundConstituent from './compound-constituent'
3
+ import nc from '../node-corrections/index'
4
+
5
+ const constituents = {}
6
+ // Long Term
7
+ constituents.Z0 = constituent('Z0', [0, 0, 0, 0, 0, 0, 0], nc.uZero, nc.fUnity)
8
+ constituents.SA = constituent('Sa', [0, 0, 1, 0, 0, 0, 0], nc.uZero, nc.fUnity)
9
+ constituents.SSA = constituent(
10
+ 'Ssa',
11
+ [0, 0, 2, 0, 0, 0, 0],
12
+ nc.uZero,
13
+ nc.fUnity
14
+ )
15
+ constituents.MM = constituent('MM', [0, 1, 0, -1, 0, 0, 0], nc.uZero, nc.fMm)
16
+ constituents.MF = constituent('MF', [0, 2, 0, 0, 0, 0, 0], nc.uMf, nc.fMf)
17
+ // Diurnals
18
+ constituents.Q1 = constituent('Q1', [1, -2, 0, 1, 0, 0, 1], nc.uO1, nc.fO1)
19
+ constituents.O1 = constituent('O1', [1, -1, 0, 0, 0, 0, 1], nc.uO1, nc.fO1)
20
+ constituents.K1 = constituent('K1', [1, 1, 0, 0, 0, 0, -1], nc.uK1, nc.fK1)
21
+ constituents.J1 = constituent('J1', [1, 2, 0, -1, 0, 0, -1], nc.uJ1, nc.fJ1)
22
+ constituents.M1 = constituent('M1', [1, 0, 0, 0, 0, 0, 1], nc.uM1, nc.fM1)
23
+ constituents.P1 = constituent('P1', [1, 1, -2, 0, 0, 0, 1], nc.uZero, nc.fUnity)
24
+ constituents.S1 = constituent('S1', [1, 1, -1, 0, 0, 0, 0], nc.uZero, nc.fUnity)
25
+ constituents.OO1 = constituent('OO1', [1, 3, 0, 0, 0, 0, -1], nc.uOO1, nc.fOO1)
26
+ // Semi diurnals
27
+ constituents['2N2'] = constituent('2N2', [2, -2, 0, 2, 0, 0, 0], nc.uM2, nc.fM2)
28
+ constituents.N2 = constituent('N2', [2, -1, 0, 1, 0, 0, 0], nc.uM2, nc.fM2)
29
+ constituents.NU2 = constituent('NU2', [2, -1, 2, -1, 0, 0, 0], nc.uM2, nc.fM2)
30
+ constituents.M2 = constituent('M2', [2, 0, 0, 0, 0, 0, 0], nc.uM2, nc.fM2)
31
+ constituents.LAM2 = constituent('LAM2', [2, 1, -2, 1, 0, 0, 2], nc.uM2, nc.fM2)
32
+ constituents.L2 = constituent('L2', [2, 1, 0, -1, 0, 0, 2], nc.uL2, nc.fL2)
33
+ constituents.T2 = constituent('T2', [2, 2, -3, 0, 0, 1, 0], nc.uZero, nc.fUnity)
34
+ constituents.S2 = constituent('S2', [2, 2, -2, 0, 0, 0, 0], nc.uZero, nc.fUnity)
35
+ constituents.R2 = constituent(
36
+ 'R2',
37
+ [2, 2, -1, 0, 0, -1, 2],
38
+ nc.uZero,
39
+ nc.fUnity
40
+ )
41
+ constituents.K2 = constituent('K2', [2, 2, 0, 0, 0, 0, 0], nc.uK2, nc.fK2)
42
+ // Third diurnal
43
+ constituents.M3 = constituent(
44
+ 'M3',
45
+ [3, 0, 0, 0, 0, 0, 0],
46
+ (a) => {
47
+ return nc.uModd(a, 3)
48
+ },
49
+ (a) => {
50
+ return nc.fModd(a, 3)
51
+ }
52
+ )
53
+ // Compound
54
+ constituents.MSF = compoundConstituent('MSF', [
55
+ { constituent: constituents.S2, factor: 1 },
56
+ { constituent: constituents.M2, factor: -1 },
57
+ ])
58
+
59
+ // Diurnal
60
+ constituents['2Q1'] = compoundConstituent('2Q1', [
61
+ { constituent: constituents.N2, factor: 1 },
62
+ { constituent: constituents.J1, factor: -1 },
63
+ ])
64
+ constituents.RHO = compoundConstituent('RHO', [
65
+ { constituent: constituents.NU2, factor: 1 },
66
+ { constituent: constituents.K1, factor: -1 },
67
+ ])
68
+
69
+ // Semi-Diurnal
70
+
71
+ constituents.MU2 = compoundConstituent('MU2', [
72
+ { constituent: constituents.M2, factor: 2 },
73
+ { constituent: constituents.S2, factor: -1 },
74
+ ])
75
+ constituents['2SM2'] = compoundConstituent('2SM2', [
76
+ { constituent: constituents.S2, factor: 2 },
77
+ { constituent: constituents.M2, factor: -1 },
78
+ ])
79
+
80
+ // Third-Diurnal
81
+ constituents['2MK3'] = compoundConstituent('2MK3', [
82
+ { constituent: constituents.M2, factor: 1 },
83
+ { constituent: constituents.O1, factor: 1 },
84
+ ])
85
+ constituents.MK3 = compoundConstituent('MK3', [
86
+ { constituent: constituents.M2, factor: 1 },
87
+ { constituent: constituents.K1, factor: 1 },
88
+ ])
89
+
90
+ // Quarter-Diurnal
91
+ constituents.MN4 = compoundConstituent('MN4', [
92
+ { constituent: constituents.M2, factor: 1 },
93
+ { constituent: constituents.N2, factor: 1 },
94
+ ])
95
+ constituents.M4 = compoundConstituent('M4', [
96
+ { constituent: constituents.M2, factor: 2 },
97
+ ])
98
+ constituents.MS4 = compoundConstituent('MS4', [
99
+ { constituent: constituents.M2, factor: 1 },
100
+ { constituent: constituents.S2, factor: 1 },
101
+ ])
102
+ constituents.S4 = compoundConstituent('S4', [
103
+ { constituent: constituents.S2, factor: 2 },
104
+ ])
105
+
106
+ // Sixth-Diurnal
107
+ constituents.M6 = compoundConstituent('M6', [
108
+ { constituent: constituents.M2, factor: 3 },
109
+ ])
110
+ constituents.S6 = compoundConstituent('S6', [
111
+ { constituent: constituents.S2, factor: 3 },
112
+ ])
113
+
114
+ // Eighth-Diurnals
115
+ constituents.M8 = compoundConstituent('M8', [
116
+ { constituent: constituents.M2, factor: 4 },
117
+ ])
118
+
119
+ export default constituents
File without changes
@@ -0,0 +1,123 @@
1
+ import harmonics, { getDate, getTimeline } from '../index'
2
+ import mockHarmonicConstituents from '../../__mocks__/constituents'
3
+
4
+ const startDate = new Date(1567346400 * 1000) // 2019-09-01
5
+ const endDate = new Date(1569966078 * 1000) // 2019-10-01
6
+
7
+ describe('harmonics', () => {
8
+ test('it checks constituents', () => {
9
+ let errorMessage = false
10
+
11
+ try {
12
+ harmonics({ harmonicConstituents: 'not array' })
13
+ } catch (error) {
14
+ errorMessage = error
15
+ }
16
+ expect(errorMessage.message).toBe('Harmonic constituents are not an array')
17
+
18
+ errorMessage = false
19
+
20
+ try {
21
+ harmonics({
22
+ harmonicConstituents: [
23
+ {
24
+ name: 'M2',
25
+ description: 'Principal lunar semidiurnal constituent',
26
+ amplitude: 1.61,
27
+ phase_GMT: 181.3,
28
+ phase_local: 309.4,
29
+ speed: 28.984104,
30
+ },
31
+ {
32
+ description: 'Principal solar semidiurnal constituent',
33
+ amplitude: 0.43,
34
+ phase_GMT: 180.1,
35
+ phase_local: 309.4,
36
+ },
37
+ ],
38
+ })
39
+ } catch (error) {
40
+ errorMessage = error
41
+ }
42
+ expect(errorMessage.message).toBe(
43
+ 'Harmonic constituents must have a name property'
44
+ )
45
+
46
+ errorMessage = false
47
+
48
+ try {
49
+ harmonics({
50
+ harmonicConstituents: [
51
+ {
52
+ name: 'not a name',
53
+ description: 'Principal lunar semidiurnal constituent',
54
+ amplitude: 1.61,
55
+ phase_GMT: 181.3,
56
+ phase_local: 309.4,
57
+ speed: 28.984104,
58
+ },
59
+ {
60
+ name: 'M2',
61
+ description: 'Principal solar semidiurnal constituent',
62
+ amplitude: 0.43,
63
+ phase_GMT: 180.1,
64
+ phase_local: 309.4,
65
+ },
66
+ ],
67
+ })
68
+ } catch (error) {
69
+ errorMessage = error
70
+ }
71
+ expect(errorMessage.message).toBeFalsy()
72
+ })
73
+
74
+ test('it checks start and end times', () => {
75
+ const testHarmonics = harmonics({
76
+ harmonicConstituents: mockHarmonicConstituents,
77
+ })
78
+ let timeErrorMessage = false
79
+ try {
80
+ testHarmonics.setTimeSpan('lkjsdlf', 'sdfklj')
81
+ } catch (error) {
82
+ timeErrorMessage = error
83
+ }
84
+ expect(timeErrorMessage.message).toBe(
85
+ 'Invalid date format, should be a Date object, or timestamp'
86
+ )
87
+
88
+ timeErrorMessage = false
89
+ try {
90
+ testHarmonics.setTimeSpan(startDate, startDate)
91
+ } catch (error) {
92
+ timeErrorMessage = error
93
+ }
94
+ expect(timeErrorMessage.message).toBe('Start time must be before end time')
95
+
96
+ timeErrorMessage = false
97
+ try {
98
+ testHarmonics.setTimeSpan(startDate, endDate)
99
+ } catch (error) {
100
+ timeErrorMessage = error
101
+ }
102
+ expect(timeErrorMessage.message).toBeFalsy()
103
+ })
104
+
105
+ test('it parses dates correctly', () => {
106
+ const parsedDate = getDate(startDate)
107
+ expect(parsedDate.getTime()).toBe(startDate.getTime())
108
+
109
+ const parsedUnixDate = getDate(startDate.getTime() / 1000)
110
+ expect(parsedUnixDate.getTime()).toBe(startDate.getTime())
111
+ })
112
+
113
+ test('it creates timeline correctly', () => {
114
+ const seconds = 20 * 60
115
+ const difference =
116
+ Math.round(
117
+ (endDate.getTime() / 1000 - startDate.getTime() / 1000) / seconds
118
+ ) + 1
119
+ const { items, hours } = getTimeline(startDate, endDate, seconds)
120
+ expect(items.length).toBe(difference)
121
+ expect(hours.length).toBe(difference)
122
+ })
123
+ })