@@ -284,11 +284,13 @@ private static double getEquationOfTime(double julianCenturies) {
284284 double geomMeanAnomalySun = getSunGeometricMeanAnomaly (julianCenturies );
285285 double y = Math .tan (Math .toRadians (epsilon ) / 2.0 );
286286 y *= y ;
287- double sin2l0 = Math .sin (2.0 * Math .toRadians (geomMeanLongSun ));
288- double sinm = Math .sin (Math .toRadians (geomMeanAnomalySun ));
289- double cos2l0 = Math .cos (2.0 * Math .toRadians (geomMeanLongSun ));
290- double sin4l0 = Math .sin (4.0 * Math .toRadians (geomMeanLongSun ));
291- double sin2m = Math .sin (2.0 * Math .toRadians (geomMeanAnomalySun ));
287+ double geomMeanLongSunRad = Math .toRadians (geomMeanLongSun );
288+ double geomMeanAnomalySunRad = Math .toRadians (geomMeanAnomalySun );
289+ double sin2l0 = Math .sin (2.0 * geomMeanLongSunRad );
290+ double sinm = Math .sin (geomMeanAnomalySunRad );
291+ double cos2l0 = Math .cos (2.0 * geomMeanLongSunRad );
292+ double sin4l0 = Math .sin (4.0 * geomMeanLongSunRad );
293+ double sin2m = Math .sin (2.0 * geomMeanAnomalySunRad );
292294 double equationOfTime = y * sin2l0 - 2.0 * eccentricityEarthOrbit * sinm + 4.0 * eccentricityEarthOrbit * y
293295 * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * eccentricityEarthOrbit * eccentricityEarthOrbit * sin2m ;
294296 return Math .toDegrees (equationOfTime ) * 4.0 ;
@@ -311,7 +313,8 @@ private static double getEquationOfTime(double julianCenturies) {
311313 private static double getSunHourAngle (double latitude , double solarDeclination , double zenith , SolarEvent solarEvent ) {
312314 double latRad = Math .toRadians (latitude );
313315 double sdRad = Math .toRadians (solarDeclination );
314- double hourAngle = (Math .acos (Math .cos (Math .toRadians (zenith )) / (Math .cos (latRad ) * Math .cos (sdRad ))
316+ double zRad = Math .toRadians (zenith );
317+ double hourAngle = (Math .acos (Math .cos (zRad ) / (Math .cos (latRad ) * Math .cos (sdRad ))
315318 - Math .tan (latRad ) * Math .tan (sdRad )));
316319
317320 if (solarEvent == SolarEvent .SUNSET ) {
@@ -493,19 +496,166 @@ private static double getSunRiseSetUTC(Calendar calendar, double latitude, doubl
493496 double equationOfTime = getEquationOfTime (tnoon );
494497 double solarDeclination = getSunDeclination (tnoon );
495498 double hourAngle = getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
499+ if (Double .isNaN (hourAngle )) {
500+ hourAngle = interpolateHourAngle1 (julianDay , latitude , longitude , zenith , solarEvent );
501+ if (Double .isNaN (hourAngle )) {
502+ return Double .NaN ;
503+ }
504+ }
496505 double delta = longitude - Math .toDegrees (hourAngle );
497506 double timeDiff = 4 * delta ;
498507 double timeUTC = 720 + timeDiff - equationOfTime ;
499508
500509 // Second pass includes fractional Julian Day in gamma calc
501510 double newt = getJulianCenturiesFromJulianDay (julianDay + timeUTC / 1440.0 );
502511 equationOfTime = getEquationOfTime (newt );
503-
512+
504513 solarDeclination = getSunDeclination (newt );
505514 hourAngle = getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
515+ if (Double .isNaN (hourAngle )) {
516+ hourAngle = interpolateHourAngle2 (julianDay , latitude , longitude , zenith , solarEvent );
517+ if (Double .isNaN (hourAngle )) {
518+ return Double .NaN ;
519+ }
520+ }
506521 delta = longitude - Math .toDegrees (hourAngle );
507522 timeDiff = 4 * delta ;
508523 timeUTC = 720 + timeDiff - equationOfTime ;
509524 return timeUTC ;
510525 }
526+
527+ private static double getSunHourAngle1 (double julianDay , double latitude , double longitude , double zenith , SolarEvent solarEvent ) {
528+ double noonmin = getSolarNoonMidnightUTC (julianDay , longitude , SolarEvent .NOON );
529+ double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
530+ double solarDeclination = getSunDeclination (tnoon );
531+ return getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
532+ }
533+
534+ private static double getSunHourAngle2 (double julianDay , double latitude , double longitude , double zenith , SolarEvent solarEvent ) {
535+ double noonmin = getSolarNoonMidnightUTC (julianDay , longitude , SolarEvent .NOON );
536+ double tnoon = getJulianCenturiesFromJulianDay (julianDay + noonmin / 1440.0 );
537+
538+ // First calculates sunrise and approximate length of day
539+ double equationOfTime = getEquationOfTime (tnoon );
540+ double solarDeclination = getSunDeclination (tnoon );
541+ double hourAngle = getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
542+ if (Double .isNaN (hourAngle )) {
543+ return Double .NaN ;
544+ }
545+ double delta = longitude - Math .toDegrees (hourAngle );
546+ double timeDiff = 4 * delta ;
547+ double timeUTC = 720 + timeDiff - equationOfTime ;
548+
549+ // Second pass includes fractional Julian Day in gamma calc
550+ double newt = getJulianCenturiesFromJulianDay (julianDay + timeUTC / 1440.0 );
551+
552+ solarDeclination = getSunDeclination (newt );
553+ return getSunHourAngle (latitude , solarDeclination , zenith , solarEvent );
554+ }
555+
556+ /**
557+ * Use linear interpolation to calculate a missing angle.
558+ */
559+ private static double interpolateHourAngle1 (double julianDay , double latitude , double longitude , double zenith , SolarEvent solarEvent ) {
560+ double hourAngle ;
561+ double x1 = 0 ;
562+ double y1 = 0 ;
563+ double x2 = 0 ;
564+ double y2 = 0 ;
565+
566+ double d = julianDay - 1 ;
567+ final double dayFirst = Math .max (julianDay - 366 , 1 );
568+ while (d >= dayFirst ) {
569+ hourAngle = getSunHourAngle1 (d , latitude , longitude , zenith , solarEvent );
570+
571+ if (!Double .isNaN (hourAngle )) {
572+ x1 = d ;
573+ y1 = hourAngle ;
574+ break ;
575+ }
576+ d --;
577+ }
578+
579+ d = julianDay + 1 ;
580+ final double dayLast = julianDay + 366 ;
581+ while (d <= dayLast ) {
582+ hourAngle = getSunHourAngle1 (d , latitude , longitude , zenith , solarEvent );
583+
584+ if (!Double .isNaN (hourAngle )) {
585+ if (x1 == 0 ) {
586+ x1 = d ;
587+ y1 = hourAngle ;
588+ d ++;
589+ continue ;
590+ }
591+ x2 = d ;
592+ y2 = hourAngle ;
593+ break ;
594+ }
595+ d ++;
596+ }
597+
598+ if ((x1 == 0 ) || (x2 == 0 )) {
599+ return Double .NaN ;
600+ }
601+ double dx = x2 - x1 ;
602+ if (dx == 0 ) {
603+ return Double .NaN ;
604+ }
605+ double dy = y2 - y1 ;
606+ return y1 + ((julianDay - x1 ) * dy / dx );
607+ }
608+
609+ /**
610+ * Use linear interpolation to calculate a missing angle.
611+ */
612+ private static double interpolateHourAngle2 (double julianDay , double latitude , double longitude , double zenith , SolarEvent solarEvent ) {
613+ double hourAngle ;
614+ double x1 = 0 ;
615+ double y1 = 0 ;
616+ double x2 = 0 ;
617+ double y2 = 0 ;
618+
619+ double d = julianDay - 1 ;
620+ final double dayFirst = Math .max (julianDay - 366 , 1 );
621+ while (d >= dayFirst ) {
622+ hourAngle = getSunHourAngle2 (d , latitude , longitude , zenith , solarEvent );
623+
624+ if (!Double .isNaN (hourAngle )) {
625+ x1 = d ;
626+ y1 = hourAngle ;
627+ break ;
628+ }
629+ d --;
630+ }
631+
632+ d = julianDay + 1 ;
633+ final double dayLast = julianDay + 366 ;
634+ while (d <= dayLast ) {
635+ hourAngle = getSunHourAngle2 (d , latitude , longitude , zenith , solarEvent );
636+
637+ if (!Double .isNaN (hourAngle )) {
638+ if (x1 == 0 ) {
639+ x1 = d ;
640+ y1 = hourAngle ;
641+ d ++;
642+ continue ;
643+ }
644+ x2 = d ;
645+ y2 = hourAngle ;
646+ break ;
647+ }
648+ d ++;
649+ }
650+
651+ if ((x1 == 0 ) || (x2 == 0 )) {
652+ return Double .NaN ;
653+ }
654+ double dx = x2 - x1 ;
655+ if (dx == 0 ) {
656+ return Double .NaN ;
657+ }
658+ double dy = y2 - y1 ;
659+ return y1 + ((julianDay - x1 ) * dy / dx );
660+ }
511661}
0 commit comments