Skip to content

Commit e42c3ad

Browse files
committed
Fix astronomy times calculations
1 parent 6070a95 commit e42c3ad

File tree

3 files changed

+180
-77
lines changed

3 files changed

+180
-77
lines changed

app/src/main/java/com/kylecorry/trailsensecore/domain/astronomy/Astro.kt

Lines changed: 145 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,10 @@ internal object Astro {
6464
n: Double,
6565
y1: Double,
6666
y2: Double,
67-
y3: Double,
68-
normalize: Boolean = false
67+
y3: Double
6968
): Double {
7069
var a = y2 - y1
7170
var b = y3 - y2
72-
73-
if (normalize) {
74-
if (a < 0) {
75-
a += 360
76-
}
77-
78-
if (b < 0) {
79-
b += 360
80-
}
81-
}
82-
8371
val c = b - a
8472

8573
return y2 + (n / 2.0) * (a + b + n * c)
@@ -129,8 +117,7 @@ internal object Astro {
129117
fun julianDay(date: LocalDateTime): Double {
130118
var Y = date.year.toDouble()
131119
var M = date.month.value.toDouble()
132-
val D =
133-
date.dayOfMonth.toDouble() + timeToAngle(date.hour, date.minute, date.second) / 360.0
120+
val D = date.dayOfMonth.toDouble() + timeToAngle(date.hour, date.minute, date.second) / 360.0
134121

135122
if (M <= 2) {
136123
Y--
@@ -143,6 +130,14 @@ internal object Astro {
143130
return floor(365.25 * (Y + 4716)) + floor(30.6001 * (M + 1)) + D + B - 1524.5
144131
}
145132

133+
fun julianCenturies(julianDay: Double): Double {
134+
return (julianDay - 2451545.0) / 36525.0
135+
}
136+
137+
fun julianDateFromCenturies(julianCenturies: Double): Double {
138+
return julianCenturies * 36525.0 + 2451545.0
139+
}
140+
146141
/**
147142
* The time difference between TT and UT (TT - UT) in seconds
148143
*/
@@ -237,6 +232,38 @@ internal object Astro {
237232
)
238233
}
239234

235+
fun equationOfTime(julianDay: Double): Double {
236+
val obliquity = obliquityCorrection(julianDay)
237+
val L = sunGeometricLongitude(julianDay)
238+
val e = eccentricity(julianDay)
239+
val M = sunMeanAnomaly(julianDay)
240+
val y = square(tanDegrees(obliquity / 2.0))
241+
242+
val radTime = y * sinDegrees(2.0 * L) - 2.0 * e * sinDegrees(M) +
243+
4.0 * e * y * sinDegrees(M) * cosDegrees(2.0 * L) -
244+
0.5 * square(y) * sinDegrees(4.0 * L) -
245+
1.25 * square(e) * sinDegrees(2.0 * M)
246+
return radTime.toDegrees() * 4.0
247+
}
248+
249+
fun refraction(elevation: Double): Double {
250+
if (elevation > 85.0){
251+
return 0.0
252+
}
253+
254+
val tanElev = tanDegrees(elevation)
255+
256+
if (elevation > 5.0){
257+
return 58.1 / tanElev - 0.07 / cube(tanElev) + 0.000086 / power(tanElev, 5) / 3600.0
258+
}
259+
260+
if (elevation > -0.575){
261+
return polynomial(elevation, 1735.0, -518.2, 103.4, -12.79, 0.711) / 3600.0
262+
}
263+
264+
return -20.774 / tanElev / 3600.0
265+
}
266+
240267
fun riseSetTransitTimes(
241268
latitude: Double,
242269
longitude: Double,
@@ -277,40 +304,39 @@ internal object Astro {
277304
val n1 = m1 + deltaT / 86400
278305
val n2 = m2 + deltaT / 86400
279306

307+
val normalizedRas = normalizeRightAscensions(rightAscensions)
308+
280309
val ra0 =
281-
interpolate(
310+
reduceAngleDegrees(interpolate(
282311
n0,
283-
rightAscensions.first,
284-
rightAscensions.second,
285-
rightAscensions.third,
286-
true
287-
)
312+
normalizedRas.first,
313+
normalizedRas.second,
314+
normalizedRas.third
315+
))
288316
val ra1 =
289-
interpolate(
317+
reduceAngleDegrees(interpolate(
290318
n1,
291-
rightAscensions.first,
292-
rightAscensions.second,
293-
rightAscensions.third,
294-
true
295-
)
296-
val ra2 = interpolate(
319+
normalizedRas.first,
320+
normalizedRas.second,
321+
normalizedRas.third
322+
))
323+
val ra2 = reduceAngleDegrees(interpolate(
297324
n2,
298-
rightAscensions.first,
299-
rightAscensions.second,
300-
rightAscensions.third,
301-
true
302-
)
325+
normalizedRas.first,
326+
normalizedRas.second,
327+
normalizedRas.third
328+
))
303329
val declination1 =
304330
interpolate(n1, declinations.first, declinations.second, declinations.third)
305331
val declination2 =
306332
interpolate(n2, declinations.first, declinations.second, declinations.third)
307333

308-
val hourAngle0 = wrap(hourAngle(sidereal0, longitude, ra0), -180.0, 180.0)
309-
val hourAngle1 = wrap(hourAngle(sidereal1, longitude, ra1), -180.0, 180.0)
310-
val hourAngle2 = wrap(hourAngle(sidereal2, longitude, ra2), -180.0, 180.0)
334+
val hourAngle0 = reduceAngleDegrees(hourAngle(sidereal0, longitude, ra0))
335+
val hourAngle1 = reduceAngleDegrees(hourAngle(sidereal1, longitude, ra1))
336+
val hourAngle2 = reduceAngleDegrees(hourAngle(sidereal2, longitude, ra2))
311337

312-
val altitude1 = wrap(altitude(hourAngle1, latitude, declination1), -180.0, 180.0)
313-
val altitude2 = wrap(altitude(hourAngle2, latitude, declination2), -180.0, 180.0)
338+
val altitude1 = altitude(hourAngle1, latitude, declination1)
339+
val altitude2 = altitude(hourAngle2, latitude, declination2)
314340

315341
val dm0 = -hourAngle0 / 360
316342
val dm1 = (altitude1 - standardAltitude) / (360 * cosDegrees(declination1) * cosDegrees(
@@ -433,17 +459,18 @@ internal object Astro {
433459
}
434460

435461
fun sunMeanAnomaly(julianDay: Double): Double {
436-
val T = (julianDay - 2451545.0) / 36525
437-
return reduceAngleDegrees(polynomial(T, 357.52772, 35999.050340, -0.0001603, -1 / 300000.0))
462+
val T = julianCenturies(julianDay)
463+
// TODO: Maybe don't reduce here?
464+
return reduceAngleDegrees(polynomial(T, 357.52911, 35999.05029, -0.0001537))
438465
}
439466

440467
fun sunGeometricLongitude(julianDay: Double): Double {
441-
val T = (julianDay - 2451545.0) / 36525
442-
return polynomial(T, 280.46646, 36000.76983, 0.0003032)
468+
val T = julianCenturies(julianDay)
469+
return reduceAngleDegrees(polynomial(T, 280.46646, 36000.76983, 0.0003032))
443470
}
444471

445472
fun moonMeanAnomaly(julianDay: Double): Double {
446-
val T = (julianDay - 2451545.0) / 36525
473+
val T = julianCenturies(julianDay)
447474
return reduceAngleDegrees(
448475
polynomial(
449476
T,
@@ -499,18 +526,27 @@ internal object Astro {
499526
}
500527

501528
fun meanObliquityOfEcliptic(julianDay: Double): Double {
502-
val T = (julianDay - 2451545.0) / 36525
503-
return polynomial(T, 23.43929, -0.01300417, -1.638889e-7, 5.036111e-7)
529+
val T = julianCenturies(julianDay)
530+
val seconds = polynomial(T, 21.448, -46.815, -0.00059, 0.001813)
531+
return 23.0 + (26.0 + seconds / 60.0) / 60.0
532+
// return polynomial(T, 23.43929, -0.01300417, -1.638889e-7, 5.036111e-7)
533+
}
534+
535+
fun obliquityCorrection(julianDay: Double): Double {
536+
val T = julianCenturies(julianDay)
537+
val e = meanObliquityOfEcliptic(julianDay)
538+
val omega = polynomial(T, 125.04, -1934.136)
539+
return e + 0.00256 * cosDegrees(omega)
504540
}
505541

506542
fun trueObliquityOfEcliptic(julianDay: Double): Double {
507543
return meanObliquityOfEcliptic(julianDay) + nutationInObliquity(julianDay)
508544
}
509545

510-
// fun eccentricity(julianDay: Double): Double {
511-
// val T = (julianDay - 2451545.0) / 36525
512-
// return polynomial(T, 0.016708634, -0.000042037, -0.0000001267)
513-
// }
546+
fun eccentricity(julianDay: Double): Double {
547+
val T = julianCenturies(julianDay)
548+
return polynomial(T, 0.016708634, -0.000042037, -0.0000001267)
549+
}
514550

515551
fun lunarCoordinates(julianDay: Double): AstroCoordinates {
516552
val T = (julianDay - 2451545.0) / 36525
@@ -616,25 +652,56 @@ internal object Astro {
616652
).toDegrees()
617653
}
618654

619-
fun solarCoordinates(julianDay: Double): AstroCoordinates {
620-
val T = (julianDay - 2451545.0) / 36525
655+
private fun sunCenter(julianDay: Double): Double {
656+
val T = julianCenturies(julianDay)
621657
val M = sunMeanAnomaly(julianDay)
622-
val L = sunGeometricLongitude(julianDay)
623-
val C = polynomial(T, 1.914602, -0.004817, -0.000014) * sinDegrees(M) +
658+
// TODO: Maybe restrict to between 0 and 360
659+
return polynomial(T, 1.914602, -0.004817, -0.000014) * sinDegrees(M) +
624660
polynomial(T, 0.019993, -0.000101) * sinDegrees(2 * M) +
625661
0.000289 * sinDegrees(3 * M)
626-
val trueLng = reduceAngleDegrees(L + C)
627-
val sunAscendingNodeLongitude = 125.04 - 1934.136 * T
628-
val apparentLongitude = trueLng - 0.00569 - 0.00478 * sinDegrees(sunAscendingNodeLongitude)
629-
val obliquity = meanObliquityOfEcliptic(julianDay)
630-
val correctedObliquity = obliquity + 0.00256 * cosDegrees(sunAscendingNodeLongitude)
631-
val rightAscension = atan2(
632-
cosDegrees(correctedObliquity) * sinDegrees(apparentLongitude),
633-
cosDegrees(apparentLongitude)
634-
).toDegrees()
635-
val declination =
636-
asin(sinDegrees(correctedObliquity) * sinDegrees(apparentLongitude)).toDegrees()
662+
}
637663

664+
fun sunTrueLongitude(julianDay: Double): Double {
665+
val L = sunGeometricLongitude(julianDay)
666+
val C = sunCenter(julianDay)
667+
// TODO: Maybe reduce this
668+
return L + C
669+
}
670+
671+
fun sunTrueAnomaly(julianDay: Double): Double {
672+
val M = sunMeanAnomaly(julianDay)
673+
val C = sunCenter(julianDay)
674+
// TODO: Reduce degrees?
675+
return M + C
676+
}
677+
678+
fun sunRadiusVector(julianDay: Double): Double {
679+
val v = sunTrueAnomaly(julianDay)
680+
val e = eccentricity(julianDay)
681+
return (1.000001018 * (1 - e * e)) / (1 + e * cosDegrees(v))
682+
}
683+
684+
fun sunApparentLongitude(julianDay: Double): Double {
685+
val T = julianCenturies(julianDay)
686+
val trueLng = sunTrueLongitude(julianDay)
687+
val omega = polynomial(T, 125.04, -1934.136)
688+
return trueLng - 0.00569 - 0.00478 * sinDegrees(omega)
689+
}
690+
691+
fun solarCoordinates(julianDay: Double): AstroCoordinates {
692+
val apparentLongitude = sunApparentLongitude(julianDay)
693+
val correctedObliquity = obliquityCorrection(julianDay)
694+
val rightAscension = reduceAngleDegrees(
695+
atan2(
696+
cosDegrees(correctedObliquity) * sinDegrees(apparentLongitude),
697+
cosDegrees(apparentLongitude)
698+
).toDegrees()
699+
)
700+
val declination = wrap(
701+
asin(sinDegrees(correctedObliquity) * sinDegrees(apparentLongitude)).toDegrees(),
702+
-90.0, 90.0
703+
)
704+
// TODO: Reduce angle or not?
638705
return AstroCoordinates(declination, rightAscension)
639706
}
640707

@@ -706,6 +773,20 @@ internal object Astro {
706773
return ((1 + cosDegrees(phaseAngle - 180)) / 2) * 100
707774
}
708775

776+
private fun normalizeRightAscensions(rightAscensions: Triple<Double, Double, Double>): Triple<Double, Double, Double> {
777+
val ra1 = rightAscensions.first
778+
val ra2 = if (rightAscensions.second < ra1){
779+
rightAscensions.second + 360
780+
} else {
781+
rightAscensions.second
782+
}
783+
val ra3 = if (rightAscensions.third < ra2){
784+
rightAscensions.third + 360
785+
} else {
786+
rightAscensions.third
787+
}
788+
return Triple(ra1, ra2, ra3)
789+
}
709790

710791
private fun wrap(value: Double, min: Double, max: Double): Double {
711792
val range = max - min

app/src/test/java/com/kylecorry/trailsensecore/domain/astronomy/AstroTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ class AstroTest {
240240
fun solarCoordinates() {
241241
val coords = Astro.solarCoordinates(2448908.5)
242242
assertEquals(-7.78507, coords.declination, 0.0001)
243-
assertEquals(-161.61917, coords.rightAscension, 0.0001)
243+
assertEquals(360-161.61917, coords.rightAscension, 0.0001)
244244
}
245245

246246
@Test

app/src/test/java/com/kylecorry/trailsensecore/domain/astronomy/AstronomyServiceTest.kt

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,39 @@ class AstronomyServiceTest {
2222
RiseSetTransetTestInput(
2323
LocalDate.of(2020, Month.SEPTEMBER, 12),
2424
LocalTime.of(6, 34),
25-
LocalTime.of(12, 52),
25+
LocalTime.of(12, 52, 5),
2626
LocalTime.of(19, 9)
2727
),
28-
// TODO: Figure out why this didn't work - something with equinoxes
29-
// RiseSetTransetTestInput(
30-
// LocalDate.of(2020, Month.SEPTEMBER, 22),
31-
// LocalTime.of(6, 33),
32-
// LocalTime.of(12, 38),
33-
// LocalTime.of(18, 41)
34-
// ),
28+
RiseSetTransetTestInput(
29+
LocalDate.of(2020, Month.SEPTEMBER, 22),
30+
LocalTime.of(6, 44),
31+
LocalTime.of(12, 48, 32),
32+
LocalTime.of(18, 52)
33+
),
34+
RiseSetTransetTestInput(
35+
LocalDate.of(2020, Month.MARCH, 21),
36+
LocalTime.of(6, 57),
37+
LocalTime.of(13, 2, 58),
38+
LocalTime.of(19, 10)
39+
),
40+
RiseSetTransetTestInput(
41+
LocalDate.of(2020, Month.DECEMBER, 21),
42+
LocalTime.of(7, 17),
43+
LocalTime.of(11, 54, 26),
44+
LocalTime.of(16, 32)
45+
),
46+
RiseSetTransetTestInput(
47+
LocalDate.of(2020, Month.DECEMBER, 21),
48+
LocalTime.of(7, 17),
49+
LocalTime.of(11, 54, 26),
50+
LocalTime.of(16, 32)
51+
),
52+
RiseSetTransetTestInput(
53+
LocalDate.of(2020, Month.JUNE, 21),
54+
LocalTime.of(5, 25),
55+
LocalTime.of(12, 57, 59),
56+
LocalTime.of(20, 31)
57+
),
3558
RiseSetTransetTestInput( // UP ALL DAY
3659
LocalDate.of(2020, Month.JUNE, 4),
3760
null,
@@ -84,10 +107,9 @@ class AstronomyServiceTest {
84107
val ny = Coordinate(40.7128, -74.0060)
85108
parametrized(
86109
listOf(
87-
Pair(LocalDateTime.of(2020, Month.SEPTEMBER, 13, 9, 8), 28f),
110+
Pair(LocalDateTime.of(2020, Month.SEPTEMBER, 13, 9, 8), 27.63f),
88111
Pair(LocalDateTime.of(2020, Month.SEPTEMBER, 13, 21, 58), -31f),
89-
// TODO: Figure out why this didn't work - something with equinoxes
90-
// Pair(LocalDateTime.of(2020, Month.SEPTEMBER, 22, 6, 51), 3f),
112+
Pair(LocalDateTime.of(2020, Month.SEPTEMBER, 22, 6, 51), 0.9f),
91113
)
92114
) {
93115
val altitude = service.getSunAltitude(
@@ -537,7 +559,7 @@ class AstronomyServiceTest {
537559
val rise: LocalTime?,
538560
val transit: LocalTime?,
539561
val set: LocalTime?,
540-
val location: Coordinate = Coordinate(40.7128, -74.0060),
562+
val location: Coordinate = Coordinate(40.7128, -74.0060),
541563
val zone: String = "America/New_York"
542564
)
543565
}

0 commit comments

Comments
 (0)