@@ -311,11 +311,13 @@ private static double getEquationOfTime(double julianCenturies) {
311
311
double y = Math .tan (Math .toRadians (epsilon ) / 2.0 );
312
312
y *= y ;
313
313
314
- double sin2l0 = Math .sin (2.0 * Math .toRadians (geomMeanLongSun ));
315
- double sinm = Math .sin (Math .toRadians (geomMeanAnomalySun ));
316
- double cos2l0 = Math .cos (2.0 * Math .toRadians (geomMeanLongSun ));
317
- double sin4l0 = Math .sin (4.0 * Math .toRadians (geomMeanLongSun ));
318
- double sin2m = Math .sin (2.0 * Math .toRadians (geomMeanAnomalySun ));
314
+ double geomMeanLongSunRad = Math .toRadians (geomMeanLongSun );
315
+ double geomMeanAnomalySunRad = Math .toRadians (geomMeanAnomalySun );
316
+ double sin2l0 = Math .sin (2.0 * geomMeanLongSunRad );
317
+ double sinm = Math .sin (geomMeanAnomalySunRad );
318
+ double cos2l0 = Math .cos (2.0 * geomMeanLongSunRad );
319
+ double sin4l0 = Math .sin (4.0 * geomMeanLongSunRad );
320
+ double sin2m = Math .sin (2.0 * geomMeanAnomalySunRad );
319
321
320
322
double equationOfTime = y * sin2l0 - 2.0 * eccentricityEarthOrbit * sinm + 4.0 * eccentricityEarthOrbit * y
321
323
* sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * eccentricityEarthOrbit * eccentricityEarthOrbit * sin2m ;
@@ -337,34 +339,12 @@ private static double getEquationOfTime(double julianCenturies) {
337
339
private static double getSunHourAngleAtSunrise (double lat , double solarDec , double zenith ) {
338
340
double latRad = Math .toRadians (lat );
339
341
double sdRad = Math .toRadians (solarDec );
342
+ double zRad = Math .toRadians (zenith );
340
343
341
- return (Math .acos (Math .cos (Math . toRadians ( zenith ) ) / (Math .cos (latRad ) * Math .cos (sdRad )) - Math .tan (latRad )
344
+ return (Math .acos (Math .cos (zRad ) / (Math .cos (latRad ) * Math .cos (sdRad )) - Math .tan (latRad )
342
345
* Math .tan (sdRad ))); // in radians
343
346
}
344
347
345
- /**
346
- * Returns the <a href="https://en.wikipedia.org/wiki/Hour_angle">hour angle</a> of the sun in <a href=
347
- * "https://en.wikipedia.org/wiki/Radian">radians</a>at sunset for the latitude.
348
- * @todo use - {@link #getSunHourAngleAtSunrise(double, double, double)} implementation to avoid
349
- * duplication of code.
350
- *
351
- * @param lat
352
- * the latitude of observer in degrees
353
- * @param solarDec
354
- * the declination angle of sun in degrees
355
- * @param zenith
356
- * the zenith
357
- * @return the hour angle of sunset in <a href="https://en.wikipedia.org/wiki/Radian">radians</a>
358
- */
359
- private static double getSunHourAngleAtSunset (double lat , double solarDec , double zenith ) {
360
- double latRad = Math .toRadians (lat );
361
- double sdRad = Math .toRadians (solarDec );
362
-
363
- double hourAngle = (Math .acos (Math .cos (Math .toRadians (zenith )) / (Math .cos (latRad ) * Math .cos (sdRad ))
364
- - Math .tan (latRad ) * Math .tan (sdRad )));
365
- return -hourAngle ; // in radians
366
- }
367
-
368
348
/**
369
349
* Return the <a href="https://en.wikipedia.org/wiki/Celestial_coordinate_system">Solar Elevation</a> for the
370
350
* horizontal coordinate system at the given location at the given time. Can be negative if the sun is below the
@@ -447,35 +427,7 @@ public static double getSolarAzimuth(Calendar cal, double lat, double lon) {
447
427
* @return the time in minutes from zero UTC
448
428
*/
449
429
private static double getSunriseUTC (double julianDay , double latitude , double longitude , double zenith ) {
450
- double julianCenturies = getJulianCenturiesFromJulianDay (julianDay );
451
-
452
- // Find the time of solar noon at the location, and use that declination. This is better than start of the
453
- // Julian day
454
-
455
- double noonmin = getSolarNoonUTC (julianCenturies , longitude );
456
- double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
457
-
458
- // First pass to approximate sunrise (using solar noon)
459
-
460
- double eqTime = getEquationOfTime (tnoon );
461
- double solarDec = getSunDeclination (tnoon );
462
- double hourAngle = getSunHourAngleAtSunrise (latitude , solarDec , zenith );
463
-
464
- double delta = longitude - Math .toDegrees (hourAngle );
465
- double timeDiff = 4 * delta ; // in minutes of time
466
- double timeUTC = 720 + timeDiff - eqTime ; // in minutes
467
-
468
- // Second pass includes fractional Julian Day in gamma calc
469
-
470
- double newt = getJulianCenturiesFromJulianDay (getJulianDayFromJulianCenturies (julianCenturies ) + timeUTC
471
- / 1440.0 );
472
- eqTime = getEquationOfTime (newt );
473
- solarDec = getSunDeclination (newt );
474
- hourAngle = getSunHourAngleAtSunrise (latitude , solarDec , zenith );
475
- delta = longitude - Math .toDegrees (hourAngle );
476
- timeDiff = 4 * delta ;
477
- timeUTC = 720 + timeDiff - eqTime ; // in minutes
478
- return timeUTC ;
430
+ return getTimeUTC (julianDay , latitude , longitude , zenith , false );
479
431
}
480
432
481
433
/**
@@ -557,35 +509,184 @@ private static double getSolarNoonUTC(double julianCenturies, double longitude)
557
509
* @return the time in minutes from zero Universal Coordinated Time (UTC)
558
510
*/
559
511
private static double getSunsetUTC (double julianDay , double latitude , double longitude , double zenith ) {
512
+ return getTimeUTC (julianDay , latitude , longitude , zenith , true );
513
+ }
514
+
515
+ /**
516
+ * Return the <a href="http://en.wikipedia.org/wiki/Universal_Coordinated_Time">Universal Coordinated Time</a> (UTC)
517
+ * of sunrise/sunset for the given day at the given location on earth
518
+ *
519
+ * @param julianDay
520
+ * the Julian day
521
+ * @param latitude
522
+ * the latitude of observer in degrees
523
+ * @param longitude
524
+ * the longitude of observer in degrees
525
+ * @param zenith
526
+ * the zenith
527
+ * @param isSunset
528
+ * True for sunset and false for sunrise.
529
+ * @return the time in minutes from zero UTC
530
+ */
531
+ private static double getTimeUTC (double julianDay , double latitude , double longitude , double zenith , boolean isSunset ) {
560
532
double julianCenturies = getJulianCenturiesFromJulianDay (julianDay );
561
533
534
+ // First pass to approximate sunrise (using solar noon)
535
+
536
+ double hourAngle = getHourAngleSunrise (julianDay , latitude , longitude , zenith );
537
+ if (Double .isNaN (hourAngle )) {
538
+ hourAngle = interpolateHourAngleSunrise (julianDay , latitude , longitude , zenith );
539
+ if (Double .isNaN (hourAngle )) return Double .NaN ;
540
+ }
541
+ if (isSunset ) hourAngle = -hourAngle ;
542
+
562
543
// Find the time of solar noon at the location, and use that declination. This is better than start of the
563
544
// Julian day
564
545
565
546
double noonmin = getSolarNoonUTC (julianCenturies , longitude );
566
547
double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
567
-
568
- // First calculates sunrise and approx length of day
569
-
570
- double eqTime = getEquationOfTime (tnoon );
571
- double solarDec = getSunDeclination (tnoon );
572
- double hourAngle = getSunHourAngleAtSunset (latitude , solarDec , zenith );
573
-
574
548
double delta = longitude - Math .toDegrees (hourAngle );
575
- double timeDiff = 4 * delta ;
576
- double timeUTC = 720 + timeDiff - eqTime ;
549
+ double timeDiff = 4 * delta ; // in minutes
550
+ double eqTime = getEquationOfTime (tnoon );
551
+ double timeUTC = 720 + timeDiff - eqTime ; // in minutes
577
552
578
553
// Second pass includes fractional Julian Day in gamma calc
579
554
580
- double newt = getJulianCenturiesFromJulianDay (getJulianDayFromJulianCenturies (julianCenturies ) + timeUTC
581
- / 1440.0 );
582
- eqTime = getEquationOfTime (newt );
583
- solarDec = getSunDeclination (newt );
584
- hourAngle = getSunHourAngleAtSunset (latitude , solarDec , zenith );
555
+ hourAngle = getHourAngleSunset (julianDay , latitude , zenith , timeUTC );
556
+ if (Double .isNaN (hourAngle )) {
557
+ hourAngle = interpolateHourAngleSunset (julianDay , latitude , zenith , timeUTC );
558
+ if (Double .isNaN (hourAngle )) return Double .NaN ;
559
+ }
560
+ if (isSunset ) hourAngle = -hourAngle ;
585
561
562
+ double newt = getJulianCenturiesFromJulianDay (getJulianDayFromJulianCenturies (julianCenturies ) + timeUTC
563
+ / 1440.0 );
586
564
delta = longitude - Math .toDegrees (hourAngle );
587
565
timeDiff = 4 * delta ;
566
+ eqTime = getEquationOfTime (newt );
588
567
timeUTC = 720 + timeDiff - eqTime ; // in minutes
589
568
return timeUTC ;
590
569
}
570
+
571
+ private static double getHourAngleSunrise (double julianDay , double latitude , double longitude , double zenith ) {
572
+ double julianCenturies = getJulianCenturiesFromJulianDay (julianDay );
573
+ double noonmin = getSolarNoonUTC (julianCenturies , longitude );
574
+ double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
575
+ double solarDec = getSunDeclination (tnoon );
576
+ return getSunHourAngleAtSunrise (latitude , solarDec , zenith );
577
+ }
578
+
579
+ private static double getHourAngleSunset (double julianDay , double latitude , double zenith , double timeUTC ) {
580
+ double julianCenturies = getJulianCenturiesFromJulianDay (julianDay );
581
+ double newt = getJulianCenturiesFromJulianDay (getJulianDayFromJulianCenturies (julianCenturies ) + timeUTC
582
+ / 1440.0 );
583
+ double solarDec = getSunDeclination (newt );
584
+ return getSunHourAngleAtSunrise (latitude , solarDec , zenith );
585
+ }
586
+
587
+ /**
588
+ * Use linear interpolation to calculate a missing angle.
589
+ */
590
+ private static double interpolateHourAngleSunrise (double julianDay , double latitude , double longitude , double zenith ) {
591
+ double hourAngle ;
592
+ double x1 = 0 ;
593
+ double y1 = 0 ;
594
+ double x2 = 0 ;
595
+ double y2 = 0 ;
596
+
597
+ double d = julianDay - 1 ;
598
+ final double dayFirst = Math .max (julianDay - 366 , 1 );
599
+ while (d >= dayFirst ) {
600
+ hourAngle = getHourAngleSunrise (d , latitude , longitude , zenith );
601
+
602
+ if (!Double .isNaN (hourAngle )) {
603
+ x1 = d ;
604
+ y1 = hourAngle ;
605
+ break ;
606
+ }
607
+ d --;
608
+ }
609
+
610
+ d = julianDay + 1 ;
611
+ final double dayLast = julianDay + 366 ;
612
+ while (d <= dayLast ) {
613
+ hourAngle = getHourAngleSunrise (d , latitude , longitude , zenith );
614
+
615
+ if (!Double .isNaN (hourAngle )) {
616
+ if (x1 == 0 ) {
617
+ x1 = d ;
618
+ y1 = hourAngle ;
619
+ d ++;
620
+ continue ;
621
+ }
622
+ x2 = d ;
623
+ y2 = hourAngle ;
624
+ break ;
625
+ }
626
+ d ++;
627
+ }
628
+
629
+ if ((x1 == 0 ) || (x2 == 0 )) {
630
+ return Double .NaN ;
631
+ }
632
+ double dx = x2 - x1 ;
633
+ if (dx == 0 ) {
634
+ return Double .NaN ;
635
+ }
636
+ double dy = y2 - y1 ;
637
+ return y1 + ((julianDay - x1 ) * dy / dx );
638
+ }
639
+
640
+ /**
641
+ * Use linear interpolation to calculate a missing angle.
642
+ */
643
+ private static double interpolateHourAngleSunset (double julianDay , double latitude , double zenith , double timeUTC ) {
644
+ double hourAngle ;
645
+ double x1 = 0 ;
646
+ double y1 = 0 ;
647
+ double x2 = 0 ;
648
+ double y2 = 0 ;
649
+
650
+ double d = julianDay - 1 ;
651
+ final double dayFirst = Math .max (julianDay - 366 , 1 );
652
+ while (d >= dayFirst ) {
653
+ hourAngle = getHourAngleSunset (d , latitude , zenith , timeUTC );
654
+
655
+ if (!Double .isNaN (hourAngle )) {
656
+ x1 = d ;
657
+ y1 = hourAngle ;
658
+ break ;
659
+ }
660
+ d --;
661
+ }
662
+
663
+ d = julianDay + 1 ;
664
+ final double dayLast = julianDay + 366 ;
665
+ while (d <= dayLast ) {
666
+ hourAngle = getHourAngleSunset (d , latitude , zenith , timeUTC );
667
+
668
+ if (!Double .isNaN (hourAngle )) {
669
+ if (x1 == 0 ) {
670
+ x1 = d ;
671
+ y1 = hourAngle ;
672
+ d ++;
673
+ continue ;
674
+ }
675
+ x2 = d ;
676
+ y2 = hourAngle ;
677
+ break ;
678
+ }
679
+ d ++;
680
+ }
681
+
682
+ if ((x1 == 0 ) || (x2 == 0 )) {
683
+ return Double .NaN ;
684
+ }
685
+ double dx = x2 - x1 ;
686
+ if (dx == 0 ) {
687
+ return Double .NaN ;
688
+ }
689
+ double dy = y2 - y1 ;
690
+ return y1 + ((julianDay - x1 ) * dy / dx );
691
+ }
591
692
}
0 commit comments