@hebcal/noaa 0.8.3 → 0.8.5

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.
@@ -0,0 +1,1075 @@
1
+ import { Temporal } from 'temporal-polyfill';
2
+ /**
3
+ * java.lang.Math.toRadians
4
+ * @private
5
+ * @param degrees
6
+ */
7
+ function degreesToRadians(degrees) {
8
+ return (degrees * Math.PI) / 180;
9
+ }
10
+ /**
11
+ * java.lang.Math.toDegrees
12
+ * @private
13
+ * @param radians
14
+ */
15
+ function radiansToDegrees(radians) {
16
+ return (radians * 180) / Math.PI;
17
+ }
18
+ const Long_MIN_VALUE = NaN;
19
+ /**
20
+ * A class that contains location information such as latitude and longitude required for astronomical calculations. The
21
+ * elevation field may not be used by some calculation engines and would be ignored if set. Check the documentation for
22
+ * specific implementations of the {@link AstronomicalCalculator} to see if elevation is calculated as part of the
23
+ * algorithm.
24
+ *
25
+ * @author © Eliyahu Hershfeld 2004 - 2016
26
+ * @version 1.1
27
+ */
28
+ export class GeoLocation {
29
+ /**
30
+ * GeoLocation constructor with parameters for all required fields.
31
+ *
32
+ * @param {string} name
33
+ * The location name for display use such as "Lakewood, NJ"
34
+ * @param {number} latitude
35
+ * the latitude in a double format such as 40.095965 for Lakewood, NJ.
36
+ * <b>Note: </b> For latitudes south of the equator, a negative value should be used.
37
+ * @param {number} longitude
38
+ * double the longitude in a double format such as -74.222130 for Lakewood, NJ.
39
+ * <b>Note: </b> For longitudes east of the <a href="http://en.wikipedia.org/wiki/Prime_Meridian">Prime
40
+ * Meridian </a> (Greenwich), a negative value should be used.
41
+ * @param {number} elevation
42
+ * the elevation above sea level in Meters. Elevation is not used in most algorithms used for calculating
43
+ * sunrise and set.
44
+ * @param {string} timeZoneId
45
+ * the <code>TimeZone</code> for the location.
46
+ */
47
+ constructor(name, latitude, longitude, elevationOrTimeZoneId, timeZoneId) {
48
+ /**
49
+ * @private
50
+ * @see #getLocationName()
51
+ * @see #setLocationName(String)
52
+ */
53
+ this.locationName = null;
54
+ let elevation = 0;
55
+ if (timeZoneId) {
56
+ elevation = elevationOrTimeZoneId;
57
+ }
58
+ else {
59
+ timeZoneId = elevationOrTimeZoneId;
60
+ }
61
+ this.setLocationName(name);
62
+ this.setLatitude(latitude);
63
+ this.setLongitude(longitude);
64
+ this.setElevation(elevation);
65
+ this.setTimeZone(timeZoneId);
66
+ }
67
+ /**
68
+ * Method to get the elevation in Meters.
69
+ *
70
+ * @return {number} Returns the elevation in Meters.
71
+ */
72
+ getElevation() {
73
+ return this.elevation;
74
+ }
75
+ /**
76
+ * Method to set the elevation in Meters <b>above </b> sea level.
77
+ *
78
+ * @param {number} elevation
79
+ * The elevation to set in Meters. An IllegalArgumentException will be thrown if the value is a negative.
80
+ */
81
+ setElevation(elevation) {
82
+ this.elevation = elevation;
83
+ }
84
+ setLatitude(latitude) {
85
+ this.latitude = latitude;
86
+ }
87
+ /**
88
+ * @return {number} Returns the latitude.
89
+ */
90
+ getLatitude() {
91
+ return this.latitude;
92
+ }
93
+ setLongitude(longitude) {
94
+ this.longitude = longitude;
95
+ }
96
+ /**
97
+ * @return {number} Returns the longitude.
98
+ */
99
+ getLongitude() {
100
+ return this.longitude;
101
+ }
102
+ /**
103
+ * @return {string|null} Returns the location name.
104
+ */
105
+ getLocationName() {
106
+ return this.locationName;
107
+ }
108
+ /**
109
+ * @param {string|null} name
110
+ * The setter method for the display name.
111
+ */
112
+ setLocationName(name) {
113
+ this.locationName = name;
114
+ }
115
+ /**
116
+ * @return {string} Returns the timeZone.
117
+ */
118
+ getTimeZone() {
119
+ return this.timeZoneId;
120
+ }
121
+ /**
122
+ * Method to set the TimeZone. If this is ever set after the GeoLocation is set in the
123
+ * {@link AstronomicalCalendar}, it is critical that
124
+ * {@link AstronomicalCalendar#getCalendar()}.
125
+ * {@link java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} be called in order for the
126
+ * AstronomicalCalendar to output times in the expected offset. This situation will arise if the
127
+ * AstronomicalCalendar is ever {@link AstronomicalCalendar#clone() cloned}.
128
+ *
129
+ * @param {string} timeZone
130
+ * The timeZone to set.
131
+ */
132
+ setTimeZone(timeZoneId) {
133
+ this.timeZoneId = timeZoneId;
134
+ }
135
+ }
136
+ /**
137
+ * The commonly used average solar refraction. Calendrical Calculations lists a more accurate global average of
138
+ * 34.478885263888294
139
+ * @private
140
+ */
141
+ const refraction = 34 / 60;
142
+ // private double refraction = 34.478885263888294 / 60d;
143
+ /**
144
+ * The commonly used average solar radius in minutes of a degree.
145
+ * @private
146
+ */
147
+ const solarRadius = 16 / 60;
148
+ /**
149
+ * The commonly used average earth radius in KM. At this time, this only affects elevation adjustment and not the
150
+ * sunrise and sunset calculations. The value currently defaults to 6356.9 KM.
151
+ * @private
152
+ */
153
+ const earthRadius = 6356.9; // in KM
154
+ /**
155
+ * Implementation of sunrise and sunset methods to calculate astronomical times based on the <a
156
+ * href="http://noaa.gov">NOAA</a> algorithm. This calculator uses the Java algorithm based on the implementation by <a
157
+ * href="http://noaa.gov">NOAA - National Oceanic and Atmospheric Administration</a>'s <a href =
158
+ * "http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html">Surface Radiation Research Branch</a>. NOAA's <a
159
+ * href="http://www.srrb.noaa.gov/highlights/sunrise/solareqns.PDF">implementation</a> is based on equations from <a
160
+ * href="http://www.willbell.com/math/mc1.htm">Astronomical Algorithms</a> by <a
161
+ * href="http://en.wikipedia.org/wiki/Jean_Meeus">Jean Meeus</a>. Added to the algorithm is an adjustment of the zenith
162
+ * to account for elevation. The algorithm can be found in the <a
163
+ * href="http://en.wikipedia.org/wiki/Sunrise_equation">Wikipedia Sunrise Equation</a> article.
164
+ *
165
+ * @author &copy; Eliyahu Hershfeld 2011 - 2019
166
+ */
167
+ export class NOAACalculator {
168
+ /**
169
+ * A constructor that takes in <a href="http://en.wikipedia.org/wiki/Geolocation">geolocation</a> information as a
170
+ * parameter. The default {@link AstronomicalCalculator#getDefault() AstronomicalCalculator} used for solar
171
+ * calculations is the the {@link NOAACalculator}.
172
+ *
173
+ * @param {GeoLocation} geoLocation
174
+ * The location information used for calculating astronomical sun times.
175
+ * @param {Temporal.PlainDate} date
176
+ *
177
+ * @see #setAstronomicalCalculator(AstronomicalCalculator) for changing the calculator class.
178
+ */
179
+ constructor(geoLocation, date) {
180
+ this.date = date;
181
+ this.geoLocation = geoLocation;
182
+ }
183
+ /**
184
+ * The getSunrise method Returns a `Date` representing the
185
+ * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjusted} sunrise time. The zenith used
186
+ * for the calculation uses {@link #GEOMETRIC_ZENITH geometric zenith} of 90&deg; plus
187
+ * {@link AstronomicalCalculator#getElevationAdjustment(double)}. This is adjusted by the
188
+ * {@link AstronomicalCalculator} to add approximately 50/60 of a degree to account for 34 archminutes of refraction
189
+ * and 16 archminutes for the sun's radius for a total of {@link AstronomicalCalculator#adjustZenith 90.83333&deg;}.
190
+ * See documentation for the specific implementation of the {@link AstronomicalCalculator} that you are using.
191
+ *
192
+ * @return {Temporal.ZonedDateTime | null} the `Date` representing the exact sunrise time. If the calculation can't be computed such as
193
+ * in the Arctic Circle where there is at least one day a year where the sun does not rise, and one where it
194
+ * does not set, a null will be returned. See detailed explanation on top of the page.
195
+ * @see AstronomicalCalculator#adjustZenith
196
+ * @see #getSeaLevelSunrise()
197
+ * @see AstronomicalCalendar#getUTCSunrise
198
+ */
199
+ getSunrise() {
200
+ const sunrise = this.getUTCSunrise0(NOAACalculator.GEOMETRIC_ZENITH);
201
+ if (Number.isNaN(sunrise))
202
+ return null;
203
+ return this.getDateFromTime(sunrise, true);
204
+ }
205
+ /**
206
+ * A method that returns the sunrise without {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
207
+ * adjustment}. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light,
208
+ * something that is not affected by elevation. This method returns sunrise calculated at sea level. This forms the
209
+ * base for dawn calculations that are calculated as a dip below the horizon before sunrise.
210
+ *
211
+ * @return {Temporal.ZonedDateTime | null} the `Date` representing the exact sea-level sunrise time. If the calculation can't be computed
212
+ * such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and one
213
+ * where it does not set, a null will be returned. See detailed explanation on top of the page.
214
+ * @see AstronomicalCalendar#getSunrise
215
+ * @see AstronomicalCalendar#getUTCSeaLevelSunrise
216
+ * @see #getSeaLevelSunset()
217
+ */
218
+ getSeaLevelSunrise() {
219
+ const sunrise = this.getUTCSeaLevelSunrise(NOAACalculator.GEOMETRIC_ZENITH);
220
+ if (Number.isNaN(sunrise))
221
+ return null;
222
+ return this.getDateFromTime(sunrise, true);
223
+ }
224
+ /**
225
+ * A method that returns the beginning of civil twilight (dawn) using a zenith of {@link #CIVIL_ZENITH 96&deg;}.
226
+ *
227
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the beginning of civil twilight using a zenith of 96&deg;. If the calculation
228
+ * can't be computed, null will be returned. See detailed explanation on top of the page.
229
+ * @see #CIVIL_ZENITH
230
+ */
231
+ getBeginCivilTwilight() {
232
+ return this.getSunriseOffsetByDegrees(NOAACalculator.CIVIL_ZENITH);
233
+ }
234
+ /**
235
+ * A method that returns the beginning of nautical twilight using a zenith of {@link #NAUTICAL_ZENITH 102&deg;}.
236
+ *
237
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the beginning of nautical twilight using a zenith of 102&deg;. If the
238
+ * calculation can't be computed null will be returned. See detailed explanation on top of the page.
239
+ * @see #NAUTICAL_ZENITH
240
+ */
241
+ getBeginNauticalTwilight() {
242
+ return this.getSunriseOffsetByDegrees(NOAACalculator.NAUTICAL_ZENITH);
243
+ }
244
+ /**
245
+ * A method that returns the beginning of astronomical twilight using a zenith of {@link #ASTRONOMICAL_ZENITH
246
+ * 108&deg;}.
247
+ *
248
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the beginning of astronomical twilight using a zenith of 108&deg;. If the
249
+ * calculation can't be computed, null will be returned. See detailed explanation on top of the page.
250
+ * @see #ASTRONOMICAL_ZENITH
251
+ */
252
+ getBeginAstronomicalTwilight() {
253
+ return this.getSunriseOffsetByDegrees(NOAACalculator.ASTRONOMICAL_ZENITH);
254
+ }
255
+ /**
256
+ * The getSunset method Returns a `Date` representing the
257
+ * {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjusted} sunset time. The zenith used for
258
+ * the calculation uses {@link #GEOMETRIC_ZENITH geometric zenith} of 90&deg; plus
259
+ * {@link AstronomicalCalculator#getElevationAdjustment(double)}. This is adjusted by the
260
+ * {@link AstronomicalCalculator} to add approximately 50/60 of a degree to account for 34 archminutes of refraction
261
+ * and 16 archminutes for the sun's radius for a total of {@link AstronomicalCalculator#adjustZenith 90.83333&deg;}.
262
+ * See documentation for the specific implementation of the {@link AstronomicalCalculator} that you are using. Note:
263
+ * In certain cases the calculates sunset will occur before sunrise. This will typically happen when a timezone
264
+ * other than the local timezone is used (calculating Los Angeles sunset using a GMT timezone for example). In this
265
+ * case the sunset date will be incremented to the following date.
266
+ *
267
+ * @return {Temporal.ZonedDateTime | null} The `Date` representing the exact sunset time. If the calculation can't be computed such as in
268
+ * the Arctic Circle where there is at least one day a year where the sun does not rise, and one where it
269
+ * does not set, a null will be returned. See detailed explanation on top of the page.
270
+ * @see AstronomicalCalculator#adjustZenith
271
+ * @see #getSeaLevelSunset()
272
+ * @see AstronomicalCalendar#getUTCSunset
273
+ */
274
+ getSunset() {
275
+ const sunset = this.getUTCSunset0(NOAACalculator.GEOMETRIC_ZENITH);
276
+ if (Number.isNaN(sunset))
277
+ return null;
278
+ return this.getDateFromTime(sunset, false);
279
+ }
280
+ /**
281
+ * A method that returns the sunset without {@link AstronomicalCalculator#getElevationAdjustment(double) elevation
282
+ * adjustment}. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible light,
283
+ * something that is not affected by elevation. This method returns sunset calculated at sea level. This forms the
284
+ * base for dusk calculations that are calculated as a dip below the horizon after sunset.
285
+ *
286
+ * @return {Temporal.ZonedDateTime | null} The `Date` representing the exact sea-level sunset time. If the calculation can't be computed
287
+ * such as in the Arctic Circle where there is at least one day a year where the sun does not rise, and one
288
+ * where it does not set, a null will be returned. See detailed explanation on top of the page.
289
+ * @see AstronomicalCalendar#getSunset
290
+ * @see AstronomicalCalendar#getUTCSeaLevelSunset 2see {@link #getSunset()}
291
+ */
292
+ getSeaLevelSunset() {
293
+ const sunset = this.getUTCSeaLevelSunset(NOAACalculator.GEOMETRIC_ZENITH);
294
+ if (Number.isNaN(sunset))
295
+ return null;
296
+ return this.getDateFromTime(sunset, false);
297
+ }
298
+ /**
299
+ * A method that returns the end of civil twilight using a zenith of {@link #CIVIL_ZENITH 96&deg;}.
300
+ *
301
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the end of civil twilight using a zenith of {@link #CIVIL_ZENITH 96&deg;}. If
302
+ * the calculation can't be computed, null will be returned. See detailed explanation on top of the page.
303
+ * @see #CIVIL_ZENITH
304
+ */
305
+ getEndCivilTwilight() {
306
+ return this.getSunsetOffsetByDegrees(NOAACalculator.CIVIL_ZENITH);
307
+ }
308
+ /**
309
+ * A method that returns the end of nautical twilight using a zenith of {@link #NAUTICAL_ZENITH 102&deg;}.
310
+ *
311
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the end of nautical twilight using a zenith of {@link #NAUTICAL_ZENITH 102&deg;}
312
+ * . If the calculation can't be computed, null will be returned. See detailed explanation on top of the
313
+ * page.
314
+ * @see #NAUTICAL_ZENITH
315
+ */
316
+ getEndNauticalTwilight() {
317
+ return this.getSunsetOffsetByDegrees(NOAACalculator.NAUTICAL_ZENITH);
318
+ }
319
+ /**
320
+ * A method that returns the end of astronomical twilight using a zenith of {@link #ASTRONOMICAL_ZENITH 108&deg;}.
321
+ *
322
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the end of astronomical twilight using a zenith of {@link #ASTRONOMICAL_ZENITH
323
+ * 108&deg;}. If the calculation can't be computed, null will be returned. See detailed explanation on top
324
+ * of the page.
325
+ * @see #ASTRONOMICAL_ZENITH
326
+ */
327
+ getEndAstronomicalTwilight() {
328
+ return this.getSunsetOffsetByDegrees(NOAACalculator.ASTRONOMICAL_ZENITH);
329
+ }
330
+ /**
331
+ * A utility method that returns a date offset by the offset time passed in. Please note that the level of light
332
+ * during twilight is not affected by elevation, so if this is being used to calculate an offset before sunrise or
333
+ * after sunset with the intent of getting a rough "level of light" calculation, the sunrise or sunset time passed
334
+ * to this method should be sea level sunrise and sunset.
335
+ *
336
+ * @param {Temporal.ZonedDateTime | null} time
337
+ * the start time
338
+ * @param {number} offset
339
+ * the offset in milliseconds to add to the time.
340
+ * @return {Temporal.ZonedDateTime | null} the `Date` with the offset in milliseconds added to it
341
+ */
342
+ static getTimeOffset(time, offset) {
343
+ if (time === null || offset === Long_MIN_VALUE || Number.isNaN(offset)) {
344
+ return null;
345
+ }
346
+ return time.add({ milliseconds: offset });
347
+ }
348
+ /**
349
+ * A utility method that returns the time of an offset by degrees below or above the horizon of
350
+ * {@link #getSunrise() sunrise}. Note that the degree offset is from the vertical, so for a calculation of 14&deg;
351
+ * before sunrise, an offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
352
+ *
353
+ * @param {number} offsetZenith
354
+ * the degrees before {@link #getSunrise()} to use in the calculation. For time after sunrise use
355
+ * negative numbers. Note that the degree offset is from the vertical, so for a calculation of 14&deg;
356
+ * before sunrise, an offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a
357
+ * parameter.
358
+ * @return {Temporal.ZonedDateTime | null} The `Date` of the offset after (or before) {@link #getSunrise()}. If the calculation
359
+ * can't be computed such as in the Arctic Circle where there is at least one day a year where the sun does
360
+ * not rise, and one where it does not set, a null will be returned. See detailed explanation on top of the
361
+ * page.
362
+ */
363
+ getSunriseOffsetByDegrees(offsetZenith) {
364
+ const dawn = this.getUTCSunrise0(offsetZenith);
365
+ if (Number.isNaN(dawn))
366
+ return null;
367
+ return this.getDateFromTime(dawn, true);
368
+ }
369
+ /**
370
+ * A utility method that returns the time of an offset by degrees below or above the horizon of {@link #getSunset()
371
+ * sunset}. Note that the degree offset is from the vertical, so for a calculation of 14&deg; after sunset, an
372
+ * offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
373
+ *
374
+ * @param {number} offsetZenith
375
+ * the degrees after {@link #getSunset()} to use in the calculation. For time before sunset use negative
376
+ * numbers. Note that the degree offset is from the vertical, so for a calculation of 14&deg; after
377
+ * sunset, an offset of 14 + {@link #GEOMETRIC_ZENITH} = 104 would have to be passed as a parameter.
378
+ * @return {Temporal.ZonedDateTime | null} The `Date`of the offset after (or before) {@link #getSunset()}. If the calculation can't
379
+ * be computed such as in the Arctic Circle where there is at least one day a year where the sun does not
380
+ * rise, and one where it does not set, a null will be returned. See detailed explanation on top of the
381
+ * page.
382
+ */
383
+ getSunsetOffsetByDegrees(offsetZenith) {
384
+ const sunset = this.getUTCSunset0(offsetZenith);
385
+ if (Number.isNaN(sunset))
386
+ return null;
387
+ return this.getDateFromTime(sunset, false);
388
+ }
389
+ /**
390
+ * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using
391
+ * daylight savings time.
392
+ *
393
+ * @param {number} zenith
394
+ * the degrees below the horizon. For time after sunrise use negative numbers.
395
+ * @return {number} The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
396
+ * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
397
+ * not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
398
+ */
399
+ getUTCSunrise0(zenith) {
400
+ return this.getUTCSunrise(this.getAdjustedDate(), this.geoLocation, zenith, true);
401
+ }
402
+ /**
403
+ * A method that returns the sunrise in UTC time without correction for time zone offset from GMT and without using
404
+ * daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the amount of visible
405
+ * light, something that is not affected by elevation. This method returns UTC sunrise calculated at sea level. This
406
+ * forms the base for dawn calculations that are calculated as a dip below the horizon before sunrise.
407
+ *
408
+ * @param {number} zenith
409
+ * the degrees below the horizon. For time after sunrise use negative numbers.
410
+ * @return {number} The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
411
+ * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
412
+ * not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
413
+ * @see AstronomicalCalendar#getUTCSunrise
414
+ * @see AstronomicalCalendar#getUTCSeaLevelSunset
415
+ */
416
+ getUTCSeaLevelSunrise(zenith) {
417
+ return this.getUTCSunrise(this.getAdjustedDate(), this.geoLocation, zenith, false);
418
+ }
419
+ /**
420
+ * A method that returns the sunset in UTC time without correction for time zone offset from GMT and without using
421
+ * daylight savings time.
422
+ *
423
+ * @param {number} zenith
424
+ * the degrees below the horizon. For time after sunset use negative numbers.
425
+ * @return {number} The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
426
+ * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
427
+ * not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
428
+ * @see AstronomicalCalendar#getUTCSeaLevelSunset
429
+ */
430
+ getUTCSunset0(zenith) {
431
+ return this.getUTCSunset(this.getAdjustedDate(), this.geoLocation, zenith, true);
432
+ }
433
+ /**
434
+ * A method that returns the sunset in UTC time without correction for elevation, time zone offset from GMT and
435
+ * without using daylight savings time. Non-sunrise and sunset calculations such as dawn and dusk, depend on the
436
+ * amount of visible light, something that is not affected by elevation. This method returns UTC sunset calculated
437
+ * at sea level. This forms the base for dusk calculations that are calculated as a dip below the horizon after
438
+ * sunset.
439
+ *
440
+ * @param {number} zenith
441
+ * the degrees below the horizon. For time before sunset use negative numbers.
442
+ * @return {number} The time in the format: 18.75 for 18:45:00 UTC/GMT. If the calculation can't be computed such as in the
443
+ * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
444
+ * not set, {@link Double#NaN} will be returned. See detailed explanation on top of the page.
445
+ * @see AstronomicalCalendar#getUTCSunset
446
+ * @see AstronomicalCalendar#getUTCSeaLevelSunrise
447
+ */
448
+ getUTCSeaLevelSunset(zenith) {
449
+ return this.getUTCSunset(this.getAdjustedDate(), this.geoLocation, zenith, false);
450
+ }
451
+ /**
452
+ * Adjusts the <code>Calendar</code> to deal with edge cases where the location crosses the antimeridian.
453
+ * @private
454
+ * @see GeoLocation#getAntimeridianAdjustment()
455
+ * @return the adjusted Calendar
456
+ */
457
+ getAdjustedDate() {
458
+ return this.date;
459
+ }
460
+ /**
461
+ * Method to return the adjustment to the zenith required to account for the elevation. Since a person at a higher
462
+ * elevation can see farther below the horizon, the calculation for sunrise / sunset is calculated below the horizon
463
+ * used at sea level. This is only used for sunrise and sunset and not times before or after it such as
464
+ * {@link AstronomicalCalendar#getBeginNauticalTwilight() nautical twilight} since those
465
+ * calculations are based on the level of available light at the given dip below the horizon, something that is not
466
+ * affected by elevation, the adjustment should only made if the zenith == 90&deg; {@link #adjustZenith adjusted}
467
+ * for refraction and solar radius. The algorithm used is
468
+ *
469
+ * <pre>
470
+ * elevationAdjustment = Math.toDegrees(Math.acos(earthRadiusInMeters / (earthRadiusInMeters + elevationMeters)));
471
+ * </pre>
472
+ *
473
+ * The source of this algorithm is <a href="http://www.calendarists.com">Calendrical Calculations</a> by Edward M.
474
+ * Reingold and Nachum Dershowitz. An alternate algorithm that produces an almost identical (but not accurate)
475
+ * result found in Ma'aglay Tzedek by Moishe Kosower and other sources is:
476
+ *
477
+ * <pre>
478
+ * elevationAdjustment = 0.0347 * Math.sqrt(elevationMeters);
479
+ * </pre>
480
+ *
481
+ * @param {number} elevation
482
+ * elevation in Meters.
483
+ * @return {number} the adjusted zenith
484
+ */
485
+ getElevationAdjustment(elevation) {
486
+ // double elevationAdjustment = 0.0347 * Math.sqrt(elevation);
487
+ const elevationAdjustment = radiansToDegrees(Math.acos(earthRadius / (earthRadius + elevation / 1000)));
488
+ return elevationAdjustment;
489
+ }
490
+ /**
491
+ * Adjusts the zenith of astronomical sunrise and sunset to account for solar refraction, solar radius and
492
+ * elevation. The value for Sun's zenith and true rise/set Zenith (used in this class and subclasses) is the angle
493
+ * that the center of the Sun makes to a line perpendicular to the Earth's surface. If the Sun were a point and the
494
+ * Earth were without an atmosphere, true sunset and sunrise would correspond to a 90&deg; zenith. Because the Sun
495
+ * is not a point, and because the atmosphere refracts light, this 90&deg; zenith does not, in fact, correspond to
496
+ * true sunset or sunrise, instead the centre of the Sun's disk must lie just below the horizon for the upper edge
497
+ * to be obscured. This means that a zenith of just above 90&deg; must be used. The Sun subtends an angle of 16
498
+ * minutes of arc (this can be changed via the {@link #setSolarRadius(double)} method , and atmospheric refraction
499
+ * accounts for 34 minutes or so (this can be changed via the {@link #setRefraction(double)} method), giving a total
500
+ * of 50 arcminutes. The total value for ZENITH is 90+(5/6) or 90.8333333&deg; for true sunrise/sunset. Since a
501
+ * person at an elevation can see blow the horizon of a person at sea level, this will also adjust the zenith to
502
+ * account for elevation if available. Note that this will only adjust the value if the zenith is exactly 90 degrees.
503
+ * For values below and above this no correction is done. As an example, astronomical twilight is when the sun is
504
+ * 18&deg; below the horizon or {@link AstronomicalCalendar#ASTRONOMICAL_ZENITH 108&deg;
505
+ * below the zenith}. This is traditionally calculated with none of the above mentioned adjustments. The same goes
506
+ * for various <em>tzais</em> and <em>alos</em> times such as the
507
+ * {@link ZmanimCalendar#ZENITH_16_POINT_1 16.1&deg;} dip used in
508
+ * {@link ComplexZmanimCalendar#getAlos16Point1Degrees()}.
509
+ *
510
+ * @param {number} zenith
511
+ * the azimuth below the vertical zenith of 90&deg;. For sunset typically the {@link #adjustZenith
512
+ * zenith} used for the calculation uses geometric zenith of 90&deg; and {@link #adjustZenith adjusts}
513
+ * this slightly to account for solar refraction and the sun's radius. Another example would be
514
+ * {@link AstronomicalCalendar#getEndNauticalTwilight()} that passes
515
+ * {@link AstronomicalCalendar#NAUTICAL_ZENITH} to this method.
516
+ * @param {number} elevation
517
+ * elevation in Meters.
518
+ * @return {number} The zenith adjusted to include the {@link #getSolarRadius sun's radius}, {@link #getRefraction
519
+ * refraction} and {@link #getElevationAdjustment elevation} adjustment. This will only be adjusted for
520
+ * sunrise and sunset (if the zenith == 90&deg;)
521
+ * @see #getElevationAdjustment(double)
522
+ */
523
+ adjustZenith(zenith, elevation) {
524
+ let adjustedZenith = zenith;
525
+ if (zenith === NOAACalculator.GEOMETRIC_ZENITH) {
526
+ // only adjust if it is exactly sunrise or sunset
527
+ adjustedZenith =
528
+ zenith +
529
+ (solarRadius + refraction + this.getElevationAdjustment(elevation));
530
+ }
531
+ return adjustedZenith;
532
+ }
533
+ /**
534
+ * @see AstronomicalCalculator#getUTCSunrise(Calendar, GeoLocation, double, boolean)
535
+ */
536
+ getUTCSunrise(date, geoLocation, zenith, adjustForElevation) {
537
+ const elevation = adjustForElevation
538
+ ? geoLocation.getElevation()
539
+ : 0;
540
+ const adjustedZenith = this.adjustZenith(zenith, elevation);
541
+ let sunrise = NOAACalculator.getSunriseUTC(NOAACalculator.getJulianDay(date), geoLocation.getLatitude(), -geoLocation.getLongitude(), adjustedZenith);
542
+ sunrise = sunrise / 60;
543
+ // ensure that the time is >= 0 and < 24
544
+ while (sunrise < 0) {
545
+ sunrise += 24;
546
+ }
547
+ while (sunrise >= 24) {
548
+ sunrise -= 24;
549
+ }
550
+ return sunrise;
551
+ }
552
+ /**
553
+ * @see AstronomicalCalculator#getUTCSunset(Calendar, GeoLocation, double, boolean)
554
+ */
555
+ getUTCSunset(date, geoLocation, zenith, adjustForElevation) {
556
+ const elevation = adjustForElevation
557
+ ? geoLocation.getElevation()
558
+ : 0;
559
+ const adjustedZenith = this.adjustZenith(zenith, elevation);
560
+ let sunset = NOAACalculator.getSunsetUTC(NOAACalculator.getJulianDay(date), geoLocation.getLatitude(), -geoLocation.getLongitude(), adjustedZenith);
561
+ sunset = sunset / 60;
562
+ // ensure that the time is >= 0 and < 24
563
+ while (sunset < 0) {
564
+ sunset += 24;
565
+ }
566
+ while (sunset >= 24) {
567
+ sunset -= 24;
568
+ }
569
+ return sunset;
570
+ }
571
+ /**
572
+ * A utility method that will allow the calculation of a temporal (solar) hour based on the sunrise and sunset
573
+ * passed as parameters to this method. An example of the use of this method would be the calculation of a
574
+ * non-elevation adjusted temporal hour by passing in {@link #getSeaLevelSunrise() sea level sunrise} and
575
+ * {@link #getSeaLevelSunset() sea level sunset} as parameters.
576
+ *
577
+ * @param {Temporal.ZonedDateTime | null} startOfDay
578
+ * The start of the day.
579
+ * @param {Temporal.ZonedDateTime | null} endOfDay
580
+ * The end of the day.
581
+ *
582
+ * @return {number} the <code>long</code> millisecond length of the temporal hour. If the calculation can't be computed a
583
+ * {@link Long#MIN_VALUE} will be returned. See detailed explanation on top of the page.
584
+ *
585
+ * @see #getTemporalHour()
586
+ */
587
+ getTemporalHour(startOfDay = this.getSeaLevelSunrise(), endOfDay = this.getSeaLevelSunset()) {
588
+ if (startOfDay === null || endOfDay === null) {
589
+ return Long_MIN_VALUE;
590
+ }
591
+ const delta = endOfDay.epochMilliseconds - startOfDay.epochMilliseconds;
592
+ return Math.floor(delta / 12);
593
+ }
594
+ /**
595
+ * A method that returns sundial or solar noon. It occurs when the Sun is <a href
596
+ * ="http://en.wikipedia.org/wiki/Transit_%28astronomy%29">transiting</a> the <a
597
+ * href="http://en.wikipedia.org/wiki/Meridian_%28astronomy%29">celestial meridian</a>. In this class it is
598
+ * calculated as halfway between the sunrise and sunset passed to this method. This time can be slightly off the
599
+ * real transit time due to changes in declination (the lengthening or shortening day).
600
+ *
601
+ * @param {Temporal.ZonedDateTime | null} startOfDay
602
+ * the start of day for calculating the sun's transit. This can be sea level sunrise, visual sunrise (or
603
+ * any arbitrary start of day) passed to this method.
604
+ * @param {Temporal.ZonedDateTime | null} endOfDay
605
+ * the end of day for calculating the sun's transit. This can be sea level sunset, visual sunset (or any
606
+ * arbitrary end of day) passed to this method.
607
+ *
608
+ * @return {Temporal.ZonedDateTime | null} The `Date` representing Sun's transit. If the calculation can't be computed such as in the
609
+ * Arctic Circle where there is at least one day a year where the sun does not rise, and one where it does
610
+ * not set, null will be returned. See detailed explanation on top of the page.
611
+ */
612
+ getSunTransit(startOfDay = this.getSeaLevelSunrise(), endOfDay = this.getSeaLevelSunset()) {
613
+ const temporalHour = this.getTemporalHour(startOfDay, endOfDay);
614
+ return NOAACalculator.getTimeOffset(startOfDay, temporalHour * 6);
615
+ }
616
+ /**
617
+ * A method that returns a `Date` from the time passed in as a parameter.
618
+ * @protected
619
+ * @param {number} time
620
+ * The time to be set as the time for the `Date`. The time expected is in the format: 18.75
621
+ * for 6:45:00 PM.
622
+ * @param {boolean} isSunrise true if the time is sunrise, and false if it is sunset
623
+ * @return {Temporal.ZonedDateTime | null} The Date.
624
+ */
625
+ getDateFromTime(time, isSunrise) {
626
+ if (Number.isNaN(time)) {
627
+ return null;
628
+ }
629
+ let calculatedTime = time;
630
+ let cal = this.getAdjustedDate();
631
+ // let cal = new Temporal.PlainDate(adj.year, adj.month, adj.day);
632
+ const hours = Math.trunc(calculatedTime); // retain only the hours
633
+ calculatedTime -= hours;
634
+ const minutes = Math.trunc((calculatedTime *= 60)); // retain only the minutes
635
+ calculatedTime -= minutes;
636
+ const seconds = Math.trunc((calculatedTime *= 60)); // retain only the seconds
637
+ calculatedTime -= seconds; // remaining milliseconds
638
+ // Check if a date transition has occurred, or is about to occur - this indicates the date of the event is
639
+ // actually not the target date, but the day prior or after
640
+ const localTimeHours = Math.trunc(this.geoLocation.getLongitude() / 15);
641
+ if (isSunrise && localTimeHours + hours > 18) {
642
+ cal = cal.add({ days: -1 });
643
+ // cal = cal.minus({days: 1});
644
+ }
645
+ else if (!isSunrise && localTimeHours + hours < 6) {
646
+ cal = cal.add({ days: 1 });
647
+ }
648
+ return cal
649
+ .toZonedDateTime({
650
+ timeZone: 'UTC',
651
+ plainTime: new Temporal.PlainTime(hours, minutes, seconds, Math.trunc(calculatedTime * 1000)),
652
+ })
653
+ .withTimeZone(this.geoLocation.getTimeZone());
654
+ }
655
+ /**
656
+ * Return the <a href="http://en.wikipedia.org/wiki/Julian_day">Julian day</a> from a Java Calendar
657
+ * @private
658
+ * @param {Temporal.ZonedDateTime} date
659
+ * The Java Calendar
660
+ * @return the Julian day corresponding to the date Note: Number is returned for start of day. Fractional days
661
+ * should be added later.
662
+ */
663
+ static getJulianDay(date) {
664
+ let { year, month } = date;
665
+ const { day } = date;
666
+ if (month <= 2) {
667
+ year -= 1;
668
+ month += 12;
669
+ }
670
+ const a = Math.trunc(year / 100);
671
+ const b = Math.trunc(2 - a + a / 4);
672
+ return (Math.floor(365.25 * (year + 4716)) +
673
+ Math.floor(30.6001 * (month + 1)) +
674
+ day +
675
+ b -
676
+ 1524.5);
677
+ }
678
+ /**
679
+ * Convert <a href="http://en.wikipedia.org/wiki/Julian_day">Julian day</a> to centuries since J2000.0.
680
+ * @private
681
+ * @param julianDay
682
+ * the Julian Day to convert
683
+ * @return the centuries since 2000 Julian corresponding to the Julian Day
684
+ */
685
+ static getJulianCenturiesFromJulianDay(julianDay) {
686
+ return ((julianDay - NOAACalculator.JULIAN_DAY_JAN_1_2000) /
687
+ NOAACalculator.JULIAN_DAYS_PER_CENTURY);
688
+ }
689
+ /**
690
+ * Convert centuries since J2000.0 to <a href="http://en.wikipedia.org/wiki/Julian_day">Julian day</a>.
691
+ * @private
692
+ * @param julianCenturies
693
+ * the number of Julian centuries since J2000.0
694
+ * @return the Julian Day corresponding to the Julian centuries passed in
695
+ */
696
+ static getJulianDayFromJulianCenturies(julianCenturies) {
697
+ return (julianCenturies * NOAACalculator.JULIAN_DAYS_PER_CENTURY +
698
+ NOAACalculator.JULIAN_DAY_JAN_1_2000);
699
+ }
700
+ /**
701
+ * Returns the Geometric <a href="http://en.wikipedia.org/wiki/Mean_longitude">Mean Longitude</a> of the Sun.
702
+ * @private
703
+ * @param julianCenturies
704
+ * the number of Julian centuries since J2000.0
705
+ * @return the Geometric Mean Longitude of the Sun in degrees
706
+ */
707
+ static getSunGeometricMeanLongitude(julianCenturies) {
708
+ let longitude = 280.46646 + julianCenturies * (36000.76983 + 0.0003032 * julianCenturies);
709
+ while (longitude > 360) {
710
+ longitude -= 360;
711
+ }
712
+ while (longitude < 0) {
713
+ longitude += 360;
714
+ }
715
+ return longitude; // in degrees
716
+ }
717
+ /**
718
+ * Returns the Geometric <a href="http://en.wikipedia.org/wiki/Mean_anomaly">Mean Anomaly</a> of the Sun.
719
+ * @private
720
+ * @param julianCenturies
721
+ * the number of Julian centuries since J2000.0
722
+ * @return the Geometric Mean Anomaly of the Sun in degrees
723
+ */
724
+ static getSunGeometricMeanAnomaly(julianCenturies) {
725
+ return (357.52911 + julianCenturies * (35999.05029 - 0.0001537 * julianCenturies)); // in degrees
726
+ }
727
+ /**
728
+ * Return the <a href="http://en.wikipedia.org/wiki/Eccentricity_%28orbit%29">eccentricity of earth's orbit</a>.
729
+ * @private
730
+ * @param julianCenturies
731
+ * the number of Julian centuries since J2000.0
732
+ * @return the unitless eccentricity
733
+ */
734
+ static getEarthOrbitEccentricity(julianCenturies) {
735
+ return (0.016708634 -
736
+ julianCenturies * (0.000042037 + 0.0000001267 * julianCenturies)); // unitless
737
+ }
738
+ /**
739
+ * Returns the <a href="http://en.wikipedia.org/wiki/Equation_of_the_center">equation of center</a> for the sun.
740
+ * @private
741
+ * @param julianCenturies
742
+ * the number of Julian centuries since J2000.0
743
+ * @return the equation of center for the sun in degrees
744
+ */
745
+ static getSunEquationOfCenter(julianCenturies) {
746
+ const m = NOAACalculator.getSunGeometricMeanAnomaly(julianCenturies);
747
+ const mrad = degreesToRadians(m);
748
+ const sinm = Math.sin(mrad);
749
+ const sin2m = Math.sin(mrad + mrad);
750
+ const sin3m = Math.sin(mrad + mrad + mrad);
751
+ return (sinm *
752
+ (1.914602 - julianCenturies * (0.004817 + 0.000014 * julianCenturies)) +
753
+ sin2m * (0.019993 - 0.000101 * julianCenturies) +
754
+ sin3m * 0.000289); // in degrees
755
+ }
756
+ /**
757
+ * Return the true longitude of the sun
758
+ * @private
759
+ * @param julianCenturies
760
+ * the number of Julian centuries since J2000.0
761
+ * @return the sun's true longitude in degrees
762
+ */
763
+ static getSunTrueLongitude(julianCenturies) {
764
+ const sunLongitude = NOAACalculator.getSunGeometricMeanLongitude(julianCenturies);
765
+ const center = NOAACalculator.getSunEquationOfCenter(julianCenturies);
766
+ return sunLongitude + center; // in degrees
767
+ }
768
+ /**
769
+ * Return the apparent longitude of the sun
770
+ * @private
771
+ * @param julianCenturies
772
+ * the number of Julian centuries since J2000.0
773
+ * @return sun's apparent longitude in degrees
774
+ */
775
+ static getSunApparentLongitude(julianCenturies) {
776
+ const sunTrueLongitude = NOAACalculator.getSunTrueLongitude(julianCenturies);
777
+ const omega = 125.04 - 1934.136 * julianCenturies;
778
+ const lambda = sunTrueLongitude - 0.00569 - 0.00478 * Math.sin(degreesToRadians(omega));
779
+ return lambda; // in degrees
780
+ }
781
+ /**
782
+ * Returns the mean <a href="http://en.wikipedia.org/wiki/Axial_tilt">obliquity of the ecliptic</a> (Axial tilt).
783
+ * @private
784
+ * @param julianCenturies
785
+ * the number of Julian centuries since J2000.0
786
+ * @return the mean obliquity in degrees
787
+ */
788
+ static getMeanObliquityOfEcliptic(julianCenturies) {
789
+ const seconds = 21.448 -
790
+ julianCenturies *
791
+ (46.815 + julianCenturies * (0.00059 - julianCenturies * 0.001813));
792
+ return 23 + (26 + seconds / 60) / 60; // in degrees
793
+ }
794
+ /**
795
+ * Returns the corrected <a href="http://en.wikipedia.org/wiki/Axial_tilt">obliquity of the ecliptic</a> (Axial
796
+ * tilt).
797
+ * @private
798
+ * @param julianCenturies
799
+ * the number of Julian centuries since J2000.0
800
+ * @return the corrected obliquity in degrees
801
+ */
802
+ static getObliquityCorrection(julianCenturies) {
803
+ const obliquityOfEcliptic = NOAACalculator.getMeanObliquityOfEcliptic(julianCenturies);
804
+ const omega = 125.04 - 1934.136 * julianCenturies;
805
+ return obliquityOfEcliptic + 0.00256 * Math.cos(degreesToRadians(omega)); // in degrees
806
+ }
807
+ /**
808
+ * Return the <a href="http://en.wikipedia.org/wiki/Declination">declination</a> of the sun.
809
+ * @private
810
+ * @param julianCenturies
811
+ * the number of Julian centuries since J2000.0
812
+ * @return
813
+ * the sun's declination in degrees
814
+ */
815
+ static getSunDeclination(julianCenturies) {
816
+ const obliquityCorrection = NOAACalculator.getObliquityCorrection(julianCenturies);
817
+ const lambda = NOAACalculator.getSunApparentLongitude(julianCenturies);
818
+ const sint = Math.sin(degreesToRadians(obliquityCorrection)) *
819
+ Math.sin(degreesToRadians(lambda));
820
+ const theta = radiansToDegrees(Math.asin(sint));
821
+ return theta; // in degrees
822
+ }
823
+ /**
824
+ * Return the <a href="http://en.wikipedia.org/wiki/Equation_of_time">Equation of Time</a> - the difference between
825
+ * true solar time and mean solar time
826
+ * @private
827
+ * @param julianCenturies
828
+ * the number of Julian centuries since J2000.0
829
+ * @return equation of time in minutes of time
830
+ */
831
+ static getEquationOfTime(julianCenturies) {
832
+ const epsilon = NOAACalculator.getObliquityCorrection(julianCenturies);
833
+ const geomMeanLongSun = NOAACalculator.getSunGeometricMeanLongitude(julianCenturies);
834
+ const eccentricityEarthOrbit = NOAACalculator.getEarthOrbitEccentricity(julianCenturies);
835
+ const geomMeanAnomalySun = NOAACalculator.getSunGeometricMeanAnomaly(julianCenturies);
836
+ let y = Math.tan(degreesToRadians(epsilon) / 2);
837
+ y *= y;
838
+ const sin2l0 = Math.sin(2 * degreesToRadians(geomMeanLongSun));
839
+ const sinm = Math.sin(degreesToRadians(geomMeanAnomalySun));
840
+ const cos2l0 = Math.cos(2 * degreesToRadians(geomMeanLongSun));
841
+ const sin4l0 = Math.sin(4 * degreesToRadians(geomMeanLongSun));
842
+ const sin2m = Math.sin(2 * degreesToRadians(geomMeanAnomalySun));
843
+ const equationOfTime = y * sin2l0 -
844
+ 2 * eccentricityEarthOrbit * sinm +
845
+ 4 * eccentricityEarthOrbit * y * sinm * cos2l0 -
846
+ 0.5 * y * y * sin4l0 -
847
+ 1.25 * eccentricityEarthOrbit * eccentricityEarthOrbit * sin2m;
848
+ return radiansToDegrees(equationOfTime) * 4; // in minutes of time
849
+ }
850
+ /**
851
+ * Return the <a href="http://en.wikipedia.org/wiki/Hour_angle">hour angle</a> of the sun at sunrise for the
852
+ * latitude.
853
+ * @private
854
+ * @param {number} lat
855
+ * , the latitude of observer in degrees
856
+ * @param solarDec
857
+ * the declination angle of sun in degrees
858
+ * @param {number} zenith
859
+ * the zenith
860
+ * @return hour angle of sunrise in radians
861
+ */
862
+ static getSunHourAngleAtSunrise(lat, solarDec, zenith) {
863
+ const latRad = degreesToRadians(lat);
864
+ const sdRad = degreesToRadians(solarDec);
865
+ return Math.acos(Math.cos(degreesToRadians(zenith)) /
866
+ (Math.cos(latRad) * Math.cos(sdRad)) -
867
+ Math.tan(latRad) * Math.tan(sdRad)); // in radians
868
+ }
869
+ /**
870
+ * Returns the <a href="http://en.wikipedia.org/wiki/Hour_angle">hour angle</a> of the sun at sunset for the
871
+ * latitude. TODO: use - {@link #getSunHourAngleAtSunrise(double, double, double)} implementation to avoid
872
+ * duplication of code.
873
+ * @private
874
+ * @param {number} lat
875
+ * the latitude of observer in degrees
876
+ * @param solarDec
877
+ * the declination angle of sun in degrees
878
+ * @param {number} zenith
879
+ * the zenith
880
+ * @return the hour angle of sunset in radians
881
+ */
882
+ static getSunHourAngleAtSunset(lat, solarDec, zenith) {
883
+ const latRad = degreesToRadians(lat);
884
+ const sdRad = degreesToRadians(solarDec);
885
+ const hourAngle = Math.acos(Math.cos(degreesToRadians(zenith)) /
886
+ (Math.cos(latRad) * Math.cos(sdRad)) -
887
+ Math.tan(latRad) * Math.tan(sdRad));
888
+ return -hourAngle; // in radians
889
+ }
890
+ /**
891
+ * Return the <a href="http://en.wikipedia.org/wiki/Celestial_coordinate_system">Solar Elevation</a> for the
892
+ * horizontal coordinate system at the given location at the given time. Can be negative if the sun is below the
893
+ * horizon. Not corrected for altitude.
894
+ *
895
+ * @param {Temporal.ZonedDateTime} date
896
+ * time of calculation
897
+ * @param {number} lat
898
+ * latitude of location for calculation
899
+ * @param {number} lon
900
+ * longitude of location for calculation
901
+ * @return {number} solar elevation in degrees - horizon is 0 degrees, civil twilight is -6 degrees
902
+ */
903
+ static getSolarElevation(date, lat, lon) {
904
+ const julianDay = NOAACalculator.getJulianDay(date.toPlainDate());
905
+ const julianCenturies = NOAACalculator.getJulianCenturiesFromJulianDay(julianDay);
906
+ const equationOfTime = NOAACalculator.getEquationOfTime(julianCenturies);
907
+ let longitude = date.hour + 12 + (date.minute + equationOfTime + date.second / 60) / 60;
908
+ longitude = -((longitude * 360) / 24) % 360;
909
+ const hourAngleRad = degreesToRadians(lon - longitude);
910
+ const declination = NOAACalculator.getSunDeclination(julianCenturies);
911
+ const decRad = degreesToRadians(declination);
912
+ const latRad = degreesToRadians(lat);
913
+ return radiansToDegrees(Math.asin(Math.sin(latRad) * Math.sin(decRad) +
914
+ Math.cos(latRad) * Math.cos(decRad) * Math.cos(hourAngleRad)));
915
+ }
916
+ /**
917
+ * Return the <a href="http://en.wikipedia.org/wiki/Celestial_coordinate_system">Solar Azimuth</a> for the
918
+ * horizontal coordinate system at the given location at the given time. Not corrected for altitude. True south is 0
919
+ * degrees.
920
+ *
921
+ * @param {Temporal.ZonedDateTime} date
922
+ * time of calculation
923
+ * @param {number} latitude
924
+ * latitude of location for calculation
925
+ * @param {number} lon
926
+ * longitude of location for calculation
927
+ * @return {number}
928
+ */
929
+ static getSolarAzimuth(date, latitude, lon) {
930
+ const julianDay = NOAACalculator.getJulianDay(date.toPlainDate());
931
+ const julianCenturies = NOAACalculator.getJulianCenturiesFromJulianDay(julianDay);
932
+ const equationOfTime = NOAACalculator.getEquationOfTime(julianCenturies);
933
+ let longitude = date.hour + 12 + (date.minute + equationOfTime + date.second / 60) / 60;
934
+ longitude = -((longitude * 360) / 24) % 360;
935
+ const hourAngleRad = degreesToRadians(lon - longitude);
936
+ const declination = NOAACalculator.getSunDeclination(julianCenturies);
937
+ const decRad = degreesToRadians(declination);
938
+ const latRad = degreesToRadians(latitude);
939
+ return (radiansToDegrees(Math.atan(Math.sin(hourAngleRad) /
940
+ (Math.cos(hourAngleRad) * Math.sin(latRad) -
941
+ Math.tan(decRad) * Math.cos(latRad)))) + 180);
942
+ }
943
+ /**
944
+ * Return the <a href="http://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
945
+ * of sunrise for the given day at the given location on earth
946
+ * @private
947
+ * @param julianDay
948
+ * the Julian day
949
+ * @param {number} latitude
950
+ * the latitude of observer in degrees
951
+ * @param {number} longitude
952
+ * the longitude of observer in degrees
953
+ * @param {number} zenith
954
+ * the zenith
955
+ * @return the time in minutes from zero UTC
956
+ */
957
+ static getSunriseUTC(julianDay, latitude, longitude, zenith) {
958
+ const julianCenturies = NOAACalculator.getJulianCenturiesFromJulianDay(julianDay);
959
+ // Find the time of solar noon at the location, and use that declination. This is better than start of the
960
+ // Julian day
961
+ const noonmin = NOAACalculator.getSolarNoonUTC(julianCenturies, longitude);
962
+ const tnoon = NOAACalculator.getJulianCenturiesFromJulianDay(julianDay + noonmin / 1440);
963
+ // First pass to approximate sunrise (using solar noon)
964
+ let eqTime = NOAACalculator.getEquationOfTime(tnoon);
965
+ let solarDec = NOAACalculator.getSunDeclination(tnoon);
966
+ let hourAngle = NOAACalculator.getSunHourAngleAtSunrise(latitude, solarDec, zenith);
967
+ let delta = longitude - radiansToDegrees(hourAngle);
968
+ let timeDiff = 4 * delta; // in minutes of time
969
+ let timeUTC = 720 + timeDiff - eqTime; // in minutes
970
+ // Second pass includes fractional Julian Day in gamma calc
971
+ const newt = NOAACalculator.getJulianCenturiesFromJulianDay(NOAACalculator.getJulianDayFromJulianCenturies(julianCenturies) +
972
+ timeUTC / 1440);
973
+ eqTime = NOAACalculator.getEquationOfTime(newt);
974
+ solarDec = NOAACalculator.getSunDeclination(newt);
975
+ hourAngle = NOAACalculator.getSunHourAngleAtSunrise(latitude, solarDec, zenith);
976
+ delta = longitude - radiansToDegrees(hourAngle);
977
+ timeDiff = 4 * delta;
978
+ timeUTC = 720 + timeDiff - eqTime; // in minutes
979
+ return timeUTC;
980
+ }
981
+ /**
982
+ * Return the <a href="http://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
983
+ * of <a href="http://en.wikipedia.org/wiki/Noon#Solar_noon">solar noon</a> for the given day at the given location
984
+ * on earth.
985
+ * @private
986
+ * @param julianCenturies
987
+ * the number of Julian centuries since J2000.0
988
+ * @param {number} longitude
989
+ * the longitude of observer in degrees
990
+ * @return the time in minutes from zero UTC
991
+ */
992
+ static getSolarNoonUTC(julianCenturies, longitude) {
993
+ // First pass uses approximate solar noon to calculate eqtime
994
+ const tnoon = NOAACalculator.getJulianCenturiesFromJulianDay(NOAACalculator.getJulianDayFromJulianCenturies(julianCenturies) +
995
+ longitude / 360);
996
+ let eqTime = NOAACalculator.getEquationOfTime(tnoon);
997
+ const solNoonUTC = 720 + longitude * 4 - eqTime; // min
998
+ const newt = NOAACalculator.getJulianCenturiesFromJulianDay(NOAACalculator.getJulianDayFromJulianCenturies(julianCenturies) -
999
+ 0.5 +
1000
+ solNoonUTC / 1440);
1001
+ eqTime = NOAACalculator.getEquationOfTime(newt);
1002
+ return 720 + longitude * 4 - eqTime; // min
1003
+ }
1004
+ /**
1005
+ * Return the <a href="http://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
1006
+ * of sunset for the given day at the given location on earth
1007
+ * @private
1008
+ * @param julianDay
1009
+ * the Julian day
1010
+ * @param {number} latitude
1011
+ * the latitude of observer in degrees
1012
+ * @param {number} longitude
1013
+ * : longitude of observer in degrees
1014
+ * @param {number} zenith
1015
+ * the zenith
1016
+ * @return the time in minutes from zero Universal Coordinated Time (UTC)
1017
+ */
1018
+ static getSunsetUTC(julianDay, latitude, longitude, zenith) {
1019
+ const julianCenturies = NOAACalculator.getJulianCenturiesFromJulianDay(julianDay);
1020
+ // Find the time of solar noon at the location, and use that declination. This is better than start of the
1021
+ // Julian day
1022
+ const noonmin = NOAACalculator.getSolarNoonUTC(julianCenturies, longitude);
1023
+ const tnoon = NOAACalculator.getJulianCenturiesFromJulianDay(julianDay + noonmin / 1440);
1024
+ // First calculates sunrise and approx length of day
1025
+ let eqTime = NOAACalculator.getEquationOfTime(tnoon);
1026
+ let solarDec = NOAACalculator.getSunDeclination(tnoon);
1027
+ let hourAngle = NOAACalculator.getSunHourAngleAtSunset(latitude, solarDec, zenith);
1028
+ let delta = longitude - radiansToDegrees(hourAngle);
1029
+ let timeDiff = 4 * delta;
1030
+ let timeUTC = 720 + timeDiff - eqTime;
1031
+ // Second pass includes fractional Julian Day in gamma calc
1032
+ const newt = NOAACalculator.getJulianCenturiesFromJulianDay(NOAACalculator.getJulianDayFromJulianCenturies(julianCenturies) +
1033
+ timeUTC / 1440);
1034
+ eqTime = NOAACalculator.getEquationOfTime(newt);
1035
+ solarDec = NOAACalculator.getSunDeclination(newt);
1036
+ hourAngle = NOAACalculator.getSunHourAngleAtSunset(latitude, solarDec, zenith);
1037
+ delta = longitude - radiansToDegrees(hourAngle);
1038
+ timeDiff = 4 * delta;
1039
+ timeUTC = 720 + timeDiff - eqTime; // in minutes
1040
+ return timeUTC;
1041
+ }
1042
+ }
1043
+ /**
1044
+ * The zenith of astronomical sunrise and sunset. The sun is 90&deg; from the vertical 0&deg;
1045
+ * @private
1046
+ */
1047
+ NOAACalculator.GEOMETRIC_ZENITH = 90;
1048
+ /**
1049
+ * Default value for Sun's zenith and true rise/set Zenith (used in this class and subclasses) is the angle that the
1050
+ * center of the Sun makes to a line perpendicular to the Earth's surface. If the Sun were a point and the Earth
1051
+ * were without an atmosphere, true sunset and sunrise would correspond to a 90&deg; zenith. Because the Sun is not
1052
+ * a point, and because the atmosphere refracts light, this 90&deg; zenith does not, in fact, correspond to true
1053
+ * sunset or sunrise, instead the center of the Sun's disk must lie just below the horizon for the upper edge to be
1054
+ * obscured. This means that a zenith of just above 90&deg; must be used. The Sun subtends an angle of 16 minutes of
1055
+ * arc (this can be changed via the {@link #setSunRadius(double)} method , and atmospheric refraction accounts for
1056
+ * 34 minutes or so (this can be changed via the {@link #setRefraction(double)} method), giving a total of 50
1057
+ * arcminutes. The total value for ZENITH is 90+(5/6) or 90.8333333&deg; for true sunrise/sunset.
1058
+ */
1059
+ // const ZENITH: number = GEOMETRIC_ZENITH + 5.0 / 6.0;
1060
+ /** Sun's zenith at civil twilight (96&deg;). */
1061
+ NOAACalculator.CIVIL_ZENITH = 96;
1062
+ /** Sun's zenith at nautical twilight (102&deg;). */
1063
+ NOAACalculator.NAUTICAL_ZENITH = 102;
1064
+ /** Sun's zenith at astronomical twilight (108&deg;). */
1065
+ NOAACalculator.ASTRONOMICAL_ZENITH = 108;
1066
+ /**
1067
+ * The <a href="http://en.wikipedia.org/wiki/Julian_day">Julian day</a> of January 1, 2000
1068
+ * @private
1069
+ */
1070
+ NOAACalculator.JULIAN_DAY_JAN_1_2000 = 2451545;
1071
+ /**
1072
+ * Julian days per century
1073
+ * @private
1074
+ */
1075
+ NOAACalculator.JULIAN_DAYS_PER_CENTURY = 36525;