@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.
- package/.eslintrc.js +22 -0
- package/.github/workflows/test.yml +15 -0
- package/.prettierrc +4 -0
- package/Gruntfile.js +87 -0
- package/LICENSE +21 -0
- package/README.md +199 -0
- package/babel.config.js +9 -0
- package/dist/tide-predictor.js +1013 -0
- package/examples/browser/index.html +51 -0
- package/jest.config.js +14 -0
- package/lib/astronomy/coefficients.js +31 -0
- package/lib/astronomy/constants.js +10 -0
- package/lib/astronomy/index.js +199 -0
- package/lib/constituents/compound-constituent.js +67 -0
- package/lib/constituents/constituent.js +74 -0
- package/lib/constituents/index.js +140 -0
- package/lib/harmonics/index.js +113 -0
- package/lib/harmonics/prediction.js +195 -0
- package/lib/index.es6.js +1005 -0
- package/lib/index.js +53 -0
- package/lib/node-corrections/index.js +147 -0
- package/package.json +45 -0
- package/rollup.config.js +21 -0
- package/src/__mocks__/constituents.js +335 -0
- package/src/__mocks__/secondary-station.js +11 -0
- package/src/__tests__/index.js +81 -0
- package/src/__tests__/noaa.js +92 -0
- package/src/astronomy/__tests__/coefficients.js +12 -0
- package/src/astronomy/__tests__/index.js +96 -0
- package/src/astronomy/coefficients.js +72 -0
- package/src/astronomy/constants.js +4 -0
- package/src/astronomy/index.js +201 -0
- package/src/constituents/__tests__/compound-constituent.js +44 -0
- package/src/constituents/__tests__/constituent.js +65 -0
- package/src/constituents/__tests__/index.js +34 -0
- package/src/constituents/compound-constituent.js +55 -0
- package/src/constituents/constituent.js +74 -0
- package/src/constituents/index.js +119 -0
- package/src/harmonics/__mocks__/water-levels.js +0 -0
- package/src/harmonics/__tests__/index.js +123 -0
- package/src/harmonics/__tests__/prediction.js +148 -0
- package/src/harmonics/index.js +87 -0
- package/src/harmonics/prediction.js +175 -0
- package/src/index.js +45 -0
- package/src/node-corrections/__tests__/index.js +114 -0
- package/src/node-corrections/index.js +208 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import mockConstituents from '../__mocks__/constituents'
|
|
2
|
+
import tidePrediction from '../index.js'
|
|
3
|
+
|
|
4
|
+
const startDate = new Date()
|
|
5
|
+
startDate.setFullYear(2019)
|
|
6
|
+
startDate.setMonth(8)
|
|
7
|
+
startDate.setDate(1)
|
|
8
|
+
startDate.setHours(0)
|
|
9
|
+
startDate.setMinutes(0)
|
|
10
|
+
startDate.setSeconds(0)
|
|
11
|
+
startDate.setMilliseconds(0)
|
|
12
|
+
|
|
13
|
+
const endDate = new Date()
|
|
14
|
+
endDate.setFullYear(2019)
|
|
15
|
+
endDate.setMonth(8)
|
|
16
|
+
endDate.setDate(1)
|
|
17
|
+
endDate.setHours(6)
|
|
18
|
+
endDate.setMinutes(0)
|
|
19
|
+
endDate.setSeconds(0)
|
|
20
|
+
endDate.setMilliseconds(0)
|
|
21
|
+
|
|
22
|
+
describe('Tidal station', () => {
|
|
23
|
+
test('it is created correctly', () => {
|
|
24
|
+
let stationCreated = true
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
tidePrediction(mockConstituents)
|
|
28
|
+
} catch (e) {
|
|
29
|
+
stationCreated = false
|
|
30
|
+
}
|
|
31
|
+
expect(stationCreated).toBeTruthy()
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
tidePrediction(mockConstituents)
|
|
35
|
+
} catch (e) {
|
|
36
|
+
stationCreated = false
|
|
37
|
+
}
|
|
38
|
+
expect(stationCreated).toBeTruthy()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('it predicts the tides in a timeline', () => {
|
|
42
|
+
const results = tidePrediction(mockConstituents).getTimelinePrediction({
|
|
43
|
+
start: startDate,
|
|
44
|
+
end: endDate,
|
|
45
|
+
})
|
|
46
|
+
expect(results[0].level).toBeCloseTo(-1.34712509, 3)
|
|
47
|
+
expect(results.pop().level).toBeCloseTo(2.85263589, 3)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('it predicts the tidal extremes', () => {
|
|
51
|
+
const results = tidePrediction(mockConstituents).getExtremesPrediction({
|
|
52
|
+
start: startDate,
|
|
53
|
+
end: endDate,
|
|
54
|
+
})
|
|
55
|
+
expect(results[0].level).toBeCloseTo(-1.565033, 4)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('it predicts the tidal extremes with high fidelity', () => {
|
|
59
|
+
const results = tidePrediction(mockConstituents).getExtremesPrediction({
|
|
60
|
+
start: startDate,
|
|
61
|
+
end: endDate,
|
|
62
|
+
timeFidelity: 60,
|
|
63
|
+
})
|
|
64
|
+
expect(results[0].level).toBeCloseTo(-1.565389, 4)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('it fetches a single water level', () => {
|
|
68
|
+
const result = tidePrediction(mockConstituents).getWaterLevelAtTime({
|
|
69
|
+
time: startDate,
|
|
70
|
+
})
|
|
71
|
+
expect(result.level).toBeCloseTo(-1.34712509, 4)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('it adds offset phases', () => {
|
|
75
|
+
const results = tidePrediction(mockConstituents, {
|
|
76
|
+
offset: 3,
|
|
77
|
+
}).getExtremesPrediction({ start: startDate, end: endDate })
|
|
78
|
+
|
|
79
|
+
expect(results[0].level).toBeCloseTo(1.43496678, 4)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import fetch from 'node-fetch'
|
|
3
|
+
import tidePrediction from '..'
|
|
4
|
+
|
|
5
|
+
// Create a directory for test cache
|
|
6
|
+
if (!fs.existsSync('./.test-cache')) {
|
|
7
|
+
fs.mkdirSync('./.test-cache')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const stations = ['9413450', '9411340', '2695535', '8761724', '8410140']
|
|
11
|
+
|
|
12
|
+
const getStation = (station, callback) => {
|
|
13
|
+
const filePath = `./.test-cache/${station}.json`
|
|
14
|
+
if (fs.existsSync(filePath)) {
|
|
15
|
+
fs.readFile(filePath, (err, data) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
throw new Error('Cannot access test cache')
|
|
18
|
+
}
|
|
19
|
+
callback(JSON.parse(data))
|
|
20
|
+
})
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
const stationData = {}
|
|
24
|
+
fetch(
|
|
25
|
+
`https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations/${station}/harcon.json?units=metric`
|
|
26
|
+
)
|
|
27
|
+
.then((response) => {
|
|
28
|
+
return response.json()
|
|
29
|
+
})
|
|
30
|
+
.then((harmonics) => {
|
|
31
|
+
stationData.harmonics = harmonics
|
|
32
|
+
return fetch(
|
|
33
|
+
`https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?date=recent&station=${station}&product=predictions&datum=MTL&time_zone=gmt&units=metric&format=json`
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
.then((response) => {
|
|
37
|
+
return response.json()
|
|
38
|
+
})
|
|
39
|
+
.then((levels) => {
|
|
40
|
+
stationData.levels = levels
|
|
41
|
+
return fetch(
|
|
42
|
+
`https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/stations/${station}/datums.json?units=metric`
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
.then((response) => {
|
|
46
|
+
return response.json()
|
|
47
|
+
})
|
|
48
|
+
.then((info) => {
|
|
49
|
+
stationData.info = info
|
|
50
|
+
fs.writeFile(filePath, JSON.stringify(stationData), (error) => {
|
|
51
|
+
if (error) {
|
|
52
|
+
throw new Error('Cannot write to test cache')
|
|
53
|
+
}
|
|
54
|
+
callback(stationData)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe('Results compare to NOAA', () => {
|
|
60
|
+
stations.forEach((station) => {
|
|
61
|
+
test(`it compares with station ${station}`, (done) => {
|
|
62
|
+
getStation(station, ({ harmonics, levels, info }) => {
|
|
63
|
+
let mtl = 0
|
|
64
|
+
let mllw = 0
|
|
65
|
+
info.datums.forEach((datum) => {
|
|
66
|
+
if (datum.name === 'MTL') {
|
|
67
|
+
mtl = datum.value
|
|
68
|
+
}
|
|
69
|
+
if (datum.name === 'MLLW') {
|
|
70
|
+
mllw = datum.value
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
const tideStation = tidePrediction(
|
|
74
|
+
harmonics.HarmonicConstituents,
|
|
75
|
+
mtl - mllw
|
|
76
|
+
)
|
|
77
|
+
levels.predictions.forEach((prediction) => {
|
|
78
|
+
const neapsPrediction = tideStation.getWaterLevelAtTime({
|
|
79
|
+
time: new Date(prediction.t),
|
|
80
|
+
})
|
|
81
|
+
expect(parseFloat(prediction.v)).toBeGreaterThanOrEqual(
|
|
82
|
+
neapsPrediction.level - 0.5
|
|
83
|
+
)
|
|
84
|
+
expect(parseFloat(prediction.v)).toBeLessThanOrEqual(
|
|
85
|
+
neapsPrediction.level + 0.5
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
done()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import coefficients, { sexagesimalToDecimal } from '../coefficients'
|
|
2
|
+
|
|
3
|
+
describe('astronomy coefficients', () => {
|
|
4
|
+
test('converts a sexagesimal angle into decimal degrees', () => {
|
|
5
|
+
expect(sexagesimalToDecimal(10, 10, 10, 10, 10)).toBe(10.169447225)
|
|
6
|
+
expect(sexagesimalToDecimal(10)).toBe(10)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
test('calculates terrestrial oliquity coefficients rewritten to T', () => {
|
|
10
|
+
expect(coefficients.terrestrialObliquity[1]).toBe(-0.013002583333333335)
|
|
11
|
+
})
|
|
12
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import astro, {
|
|
2
|
+
polynomial,
|
|
3
|
+
derivativePolynomial,
|
|
4
|
+
JD,
|
|
5
|
+
T,
|
|
6
|
+
_I,
|
|
7
|
+
_xi,
|
|
8
|
+
_nu,
|
|
9
|
+
_nup,
|
|
10
|
+
_nupp
|
|
11
|
+
} from '../index'
|
|
12
|
+
|
|
13
|
+
const sampleTime = new Date()
|
|
14
|
+
sampleTime.setFullYear(2019)
|
|
15
|
+
sampleTime.setMonth(9)
|
|
16
|
+
sampleTime.setDate(4)
|
|
17
|
+
sampleTime.setHours(10)
|
|
18
|
+
sampleTime.setMinutes(15)
|
|
19
|
+
sampleTime.setSeconds(40)
|
|
20
|
+
sampleTime.setMilliseconds(10)
|
|
21
|
+
|
|
22
|
+
describe('astronomy', () => {
|
|
23
|
+
test('complete astronomic calculation', () => {
|
|
24
|
+
const result = astro(sampleTime)
|
|
25
|
+
expect(result.s.value).toBeCloseTo(258.23871057233191, 4)
|
|
26
|
+
expect(result.s.speed).toBeCloseTo(0.54901651929993922, 4)
|
|
27
|
+
|
|
28
|
+
expect(result.pp.value).toBeCloseTo(283.27697979858613, 4)
|
|
29
|
+
expect(result.pp.speed).toBeCloseTo(1.9612154426341654e-6, 4)
|
|
30
|
+
|
|
31
|
+
expect(result.h.value).toBeCloseTo(192.82639897760328, 4)
|
|
32
|
+
expect(result.h.speed).toBeCloseTo(0.041068640143510367, 4)
|
|
33
|
+
|
|
34
|
+
expect(result.xi.value).toBeCloseTo(11.989946298635664, 4)
|
|
35
|
+
expect(result.xi.speed).toBeNull()
|
|
36
|
+
|
|
37
|
+
expect(result.I.value).toBeCloseTo(22.811296275568843, 4)
|
|
38
|
+
expect(result.I.speed).toBeNull()
|
|
39
|
+
|
|
40
|
+
expect(result.P.value).toBeCloseTo(155.24265065565865, 4)
|
|
41
|
+
expect(result.P.speed).toBeNull()
|
|
42
|
+
|
|
43
|
+
expect(result.nupp.value).toBeCloseTo(8.8162480626605451, 4)
|
|
44
|
+
expect(result.nupp.speed).toBeNull()
|
|
45
|
+
|
|
46
|
+
expect(result.nu.value).toBeCloseTo(13.028571777192044, 4)
|
|
47
|
+
expect(result.nu.speed).toBeNull()
|
|
48
|
+
|
|
49
|
+
expect(result['T+h-s'].value).toBeCloseTo(268.50435506200392, 4)
|
|
50
|
+
expect(result['T+h-s'].speed).toBeCloseTo(14.492052120843571, 4)
|
|
51
|
+
|
|
52
|
+
expect(result.omega.value).toBeCloseTo(23.436722306067253, 4)
|
|
53
|
+
expect(result.omega.speed).toBeCloseTo(-1.4832917321024327e-8, 4)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('evaluates a polynomial', () => {
|
|
57
|
+
expect(polynomial([1, 2, 3], 3)).toBe(34)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('evaluates derivative polynomials', () => {
|
|
61
|
+
expect(derivativePolynomial([1, 2, 3], 3)).toBe(20)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('evaluates Meeus formula 7.1 (JD) correctly', () => {
|
|
65
|
+
sampleTime.setMonth(9)
|
|
66
|
+
expect(JD(sampleTime)).toBeCloseTo(2458760.92755, 2)
|
|
67
|
+
// Months of less than 2 go back a year
|
|
68
|
+
sampleTime.setMonth(0)
|
|
69
|
+
expect(JD(sampleTime)).toBeCloseTo(2458487.92755, 2)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('evaluates Meeus formula 11.1 (T) correctly', () => {
|
|
73
|
+
sampleTime.setMonth(9)
|
|
74
|
+
expect(T(sampleTime)).toBeCloseTo(0.19756132, 2)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('evaluates value for _I correctly', () => {
|
|
78
|
+
expect(_I(4, 10, 5)).toBeCloseTo(14.9918364991, 4)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('evaluates value for _xi correctly', () => {
|
|
82
|
+
expect(_xi(4, 3, 10)).toBeCloseTo(0.911946348144, 4)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test('evaluates value for _nu correctly', () => {
|
|
86
|
+
expect(_nu(10, 4, 5)).toBeCloseTo(4.45767377718, 4)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('evaluates value for _nup correctly', () => {
|
|
90
|
+
expect(_nup(10, 4, 5)).toBeCloseTo(2.13580480226, 4)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('evaluates value for _nupp correctly', () => {
|
|
94
|
+
expect(_nupp(10, 4, 5)).toBeCloseTo(1.1146589591, 4)
|
|
95
|
+
})
|
|
96
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Convert a sexagesimal angle into decimal degrees
|
|
2
|
+
const sexagesimalToDecimal = (degrees, arcmins, arcsecs, mas, muas) => {
|
|
3
|
+
arcmins = typeof arcmins !== 'undefined' ? arcmins : 0
|
|
4
|
+
arcsecs = typeof arcsecs !== 'undefined' ? arcsecs : 0
|
|
5
|
+
mas = typeof mas !== 'undefined' ? mas : 0
|
|
6
|
+
muas = typeof muas !== 'undefined' ? muas : 0
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
degrees +
|
|
10
|
+
arcmins / 60.0 +
|
|
11
|
+
arcsecs / (60.0 * 60.0) +
|
|
12
|
+
mas / (60.0 * 60.0 * 1e3) +
|
|
13
|
+
muas / (60.0 * 60.0 * 1e6)
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const coefficients = {
|
|
18
|
+
// Meeus formula 21.3
|
|
19
|
+
terrestrialObliquity: [
|
|
20
|
+
sexagesimalToDecimal(23, 26, 21.448),
|
|
21
|
+
-sexagesimalToDecimal(0, 0, 4680.93),
|
|
22
|
+
-sexagesimalToDecimal(0, 0, 1.55),
|
|
23
|
+
sexagesimalToDecimal(0, 0, 1999.25),
|
|
24
|
+
-sexagesimalToDecimal(0, 0, 51.38),
|
|
25
|
+
-sexagesimalToDecimal(0, 0, 249.67),
|
|
26
|
+
-sexagesimalToDecimal(0, 0, 39.05),
|
|
27
|
+
sexagesimalToDecimal(0, 0, 7.12),
|
|
28
|
+
sexagesimalToDecimal(0, 0, 27.87),
|
|
29
|
+
sexagesimalToDecimal(0, 0, 5.79),
|
|
30
|
+
sexagesimalToDecimal(0, 0, 2.45),
|
|
31
|
+
].map((number, index) => {
|
|
32
|
+
return number * Math.pow(1e-2, index)
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
solarPerigee: [
|
|
36
|
+
280.46645 - 357.5291,
|
|
37
|
+
36000.76932 - 35999.0503,
|
|
38
|
+
0.0003032 + 0.0001559,
|
|
39
|
+
0.00000048,
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
solarLongitude: [280.46645, 36000.76983, 0.0003032],
|
|
43
|
+
|
|
44
|
+
lunarInclination: [5.145],
|
|
45
|
+
|
|
46
|
+
lunarLongitude: [
|
|
47
|
+
218.3164591,
|
|
48
|
+
481267.88134236,
|
|
49
|
+
-0.0013268,
|
|
50
|
+
1 / 538841.0 - 1 / 65194000.0,
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
lunarNode: [
|
|
54
|
+
125.044555,
|
|
55
|
+
-1934.1361849,
|
|
56
|
+
0.0020762,
|
|
57
|
+
1 / 467410.0,
|
|
58
|
+
-1 / 60616000.0,
|
|
59
|
+
],
|
|
60
|
+
|
|
61
|
+
lunarPerigee: [
|
|
62
|
+
83.353243,
|
|
63
|
+
4069.0137111,
|
|
64
|
+
-0.0103238,
|
|
65
|
+
-1 / 80053.0,
|
|
66
|
+
1 / 18999000.0,
|
|
67
|
+
],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default coefficients
|
|
71
|
+
|
|
72
|
+
export { sexagesimalToDecimal }
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { d2r, r2d } from './constants'
|
|
2
|
+
import coefficients from './coefficients'
|
|
3
|
+
|
|
4
|
+
// Evaluates a polynomial at argument
|
|
5
|
+
const polynomial = (coefficients, argument) => {
|
|
6
|
+
const result = []
|
|
7
|
+
coefficients.forEach((coefficient, index) => {
|
|
8
|
+
result.push(coefficient * Math.pow(argument, index))
|
|
9
|
+
})
|
|
10
|
+
return result.reduce((a, b) => {
|
|
11
|
+
return a + b
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Evaluates a derivative polynomial at argument
|
|
16
|
+
const derivativePolynomial = (coefficients, argument) => {
|
|
17
|
+
const result = []
|
|
18
|
+
coefficients.forEach((coefficient, index) => {
|
|
19
|
+
result.push(coefficient * index * Math.pow(argument, index - 1))
|
|
20
|
+
})
|
|
21
|
+
return result.reduce((a, b) => {
|
|
22
|
+
return a + b
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Meeus formula 11.1
|
|
27
|
+
const T = (t) => {
|
|
28
|
+
return (JD(t) - 2451545.0) / 36525
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Meeus formula 7.1
|
|
32
|
+
const JD = (t) => {
|
|
33
|
+
let Y = t.getFullYear()
|
|
34
|
+
let M = t.getMonth() + 1
|
|
35
|
+
const D =
|
|
36
|
+
t.getDate() +
|
|
37
|
+
t.getHours() / 24.0 +
|
|
38
|
+
t.getMinutes() / (24.0 * 60.0) +
|
|
39
|
+
t.getSeconds() / (24.0 * 60.0 * 60.0) +
|
|
40
|
+
t.getMilliseconds() / (24.0 * 60.0 * 60.0 * 1e6)
|
|
41
|
+
if (M <= 2) {
|
|
42
|
+
Y = Y - 1
|
|
43
|
+
M = M + 12
|
|
44
|
+
}
|
|
45
|
+
const A = Math.floor(Y / 100.0)
|
|
46
|
+
const B = 2 - A + Math.floor(A / 4.0)
|
|
47
|
+
return (
|
|
48
|
+
Math.floor(365.25 * (Y + 4716)) +
|
|
49
|
+
Math.floor(30.6001 * (M + 1)) +
|
|
50
|
+
D +
|
|
51
|
+
B -
|
|
52
|
+
1524.5
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @todo - What's with the array returned from the arccos?
|
|
58
|
+
* @param {*} N
|
|
59
|
+
* @param {*} i
|
|
60
|
+
* @param {*} omega
|
|
61
|
+
*/
|
|
62
|
+
const _I = (N, i, omega) => {
|
|
63
|
+
N = d2r * N
|
|
64
|
+
i = d2r * i
|
|
65
|
+
omega = d2r * omega
|
|
66
|
+
const cosI =
|
|
67
|
+
Math.cos(i) * Math.cos(omega) - Math.sin(i) * Math.sin(omega) * Math.cos(N)
|
|
68
|
+
return r2d * Math.acos(cosI)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const _xi = (N, i, omega) => {
|
|
72
|
+
N = d2r * N
|
|
73
|
+
i = d2r * i
|
|
74
|
+
omega = d2r * omega
|
|
75
|
+
let e1 =
|
|
76
|
+
(Math.cos(0.5 * (omega - i)) / Math.cos(0.5 * (omega + i))) *
|
|
77
|
+
Math.tan(0.5 * N)
|
|
78
|
+
let e2 =
|
|
79
|
+
(Math.sin(0.5 * (omega - i)) / Math.sin(0.5 * (omega + i))) *
|
|
80
|
+
Math.tan(0.5 * N)
|
|
81
|
+
e1 = Math.atan(e1)
|
|
82
|
+
e2 = Math.atan(e2)
|
|
83
|
+
e1 = e1 - 0.5 * N
|
|
84
|
+
e2 = e2 - 0.5 * N
|
|
85
|
+
return -(e1 + e2) * r2d
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const _nu = (N, i, omega) => {
|
|
89
|
+
N = d2r * N
|
|
90
|
+
i = d2r * i
|
|
91
|
+
omega = d2r * omega
|
|
92
|
+
let e1 =
|
|
93
|
+
(Math.cos(0.5 * (omega - i)) / Math.cos(0.5 * (omega + i))) *
|
|
94
|
+
Math.tan(0.5 * N)
|
|
95
|
+
let e2 =
|
|
96
|
+
(Math.sin(0.5 * (omega - i)) / Math.sin(0.5 * (omega + i))) *
|
|
97
|
+
Math.tan(0.5 * N)
|
|
98
|
+
e1 = Math.atan(e1)
|
|
99
|
+
e2 = Math.atan(e2)
|
|
100
|
+
e1 = e1 - 0.5 * N
|
|
101
|
+
e2 = e2 - 0.5 * N
|
|
102
|
+
return (e1 - e2) * r2d
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Schureman equation 224
|
|
106
|
+
const _nup = (N, i, omega) => {
|
|
107
|
+
const I = d2r * _I(N, i, omega)
|
|
108
|
+
const nu = d2r * _nu(N, i, omega)
|
|
109
|
+
return (
|
|
110
|
+
r2d *
|
|
111
|
+
Math.atan(
|
|
112
|
+
(Math.sin(2 * I) * Math.sin(nu)) /
|
|
113
|
+
(Math.sin(2 * I) * Math.cos(nu) + 0.3347)
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Schureman equation 232
|
|
119
|
+
const _nupp = (N, i, omega) => {
|
|
120
|
+
const I = d2r * _I(N, i, omega)
|
|
121
|
+
const nu = d2r * _nu(N, i, omega)
|
|
122
|
+
const tan2nupp =
|
|
123
|
+
(Math.sin(I) ** 2 * Math.sin(2 * nu)) /
|
|
124
|
+
(Math.sin(I) ** 2 * Math.cos(2 * nu) + 0.0727)
|
|
125
|
+
return r2d * 0.5 * Math.atan(tan2nupp)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const modulus = (a, b) => {
|
|
129
|
+
return ((a % b) + b) % b
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const astro = (time) => {
|
|
133
|
+
const result = {}
|
|
134
|
+
const polynomials = {
|
|
135
|
+
s: coefficients.lunarLongitude,
|
|
136
|
+
h: coefficients.solarLongitude,
|
|
137
|
+
p: coefficients.lunarPerigee,
|
|
138
|
+
N: coefficients.lunarNode,
|
|
139
|
+
pp: coefficients.solarPerigee,
|
|
140
|
+
90: [90.0],
|
|
141
|
+
omega: coefficients.terrestrialObliquity,
|
|
142
|
+
i: coefficients.lunarInclination,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Polynomials are in T, that is Julian Centuries; we want our speeds to be
|
|
146
|
+
// in the more convenient unit of degrees per hour.
|
|
147
|
+
const dTdHour = 1 / (24 * 365.25 * 100)
|
|
148
|
+
Object.keys(polynomials).forEach((name) => {
|
|
149
|
+
result[name] = {
|
|
150
|
+
value: modulus(polynomial(polynomials[name], T(time)), 360.0),
|
|
151
|
+
speed: derivativePolynomial(polynomials[name], T(time)) * dTdHour,
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
// Some other parameters defined by Schureman which are dependent on the
|
|
156
|
+
// parameters N, i, omega for use in node factor calculations. We don't need
|
|
157
|
+
// their speeds.
|
|
158
|
+
const functions = {
|
|
159
|
+
I: _I,
|
|
160
|
+
xi: _xi,
|
|
161
|
+
nu: _nu,
|
|
162
|
+
nup: _nup,
|
|
163
|
+
nupp: _nupp,
|
|
164
|
+
}
|
|
165
|
+
Object.keys(functions).forEach((name) => {
|
|
166
|
+
const functionCall = functions[name]
|
|
167
|
+
result[name] = {
|
|
168
|
+
value: modulus(
|
|
169
|
+
functionCall(result.N.value, result.i.value, result.omega.value),
|
|
170
|
+
360.0
|
|
171
|
+
),
|
|
172
|
+
speed: null,
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// We don't work directly with the T (hours) parameter, instead our spanning
|
|
177
|
+
// set for equilibrium arguments #is given by T+h-s, s, h, p, N, pp, 90.
|
|
178
|
+
// This is in line with convention.
|
|
179
|
+
const hour = {
|
|
180
|
+
value: (JD(time) - Math.floor(JD(time))) * 360.0,
|
|
181
|
+
speed: 15.0,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
result['T+h-s'] = {
|
|
185
|
+
value: hour.value + result.h.value - result.s.value,
|
|
186
|
+
speed: hour.speed + result.h.speed - result.s.speed,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// It is convenient to calculate Schureman's P here since several node
|
|
190
|
+
// factors need it, although it could be argued that these
|
|
191
|
+
// (along with I, xi, nu etc) belong somewhere else.
|
|
192
|
+
result.P = {
|
|
193
|
+
value: result.p.value - (result.xi.value % 360.0),
|
|
194
|
+
speed: null,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return result
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export default astro
|
|
201
|
+
export { polynomial, derivativePolynomial, T, JD, _I, _xi, _nu, _nup, _nupp }
|