@@ -311,11 +311,13 @@ private static double getEquationOfTime(double julianCenturies) {
311311 double y = Math .tan (Math .toRadians (epsilon ) / 2.0 );
312312 y *= y ;
313313
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 );
319321
320322 double equationOfTime = y * sin2l0 - 2.0 * eccentricityEarthOrbit * sinm + 4.0 * eccentricityEarthOrbit * y
321323 * sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * eccentricityEarthOrbit * eccentricityEarthOrbit * sin2m ;
@@ -337,34 +339,12 @@ private static double getEquationOfTime(double julianCenturies) {
337339 private static double getSunHourAngleAtSunrise (double lat , double solarDec , double zenith ) {
338340 double latRad = Math .toRadians (lat );
339341 double sdRad = Math .toRadians (solarDec );
342+ double zRad = Math .toRadians (zenith );
340343
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 )
342345 * Math .tan (sdRad ))); // in radians
343346 }
344347
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-
368348 /**
369349 * Return the <a href="https://en.wikipedia.org/wiki/Celestial_coordinate_system">Solar Elevation</a> for the
370350 * 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) {
447427 * @return the time in minutes from zero UTC
448428 */
449429 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 );
479431 }
480432
481433 /**
@@ -557,35 +509,184 @@ private static double getSolarNoonUTC(double julianCenturies, double longitude)
557509 * @return the time in minutes from zero Universal Coordinated Time (UTC)
558510 */
559511 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 ) {
560532 double julianCenturies = getJulianCenturiesFromJulianDay (julianDay );
561533
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+
562543 // Find the time of solar noon at the location, and use that declination. This is better than start of the
563544 // Julian day
564545
565546 double noonmin = getSolarNoonUTC (julianCenturies , longitude );
566547 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-
574548 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
577552
578553 // Second pass includes fractional Julian Day in gamma calc
579554
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 ;
585561
562+ double newt = getJulianCenturiesFromJulianDay (getJulianDayFromJulianCenturies (julianCenturies ) + timeUTC
563+ / 1440.0 );
586564 delta = longitude - Math .toDegrees (hourAngle );
587565 timeDiff = 4 * delta ;
566+ eqTime = getEquationOfTime (newt );
588567 timeUTC = 720 + timeDiff - eqTime ; // in minutes
589568 return timeUTC ;
590569 }
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+ }
591692}
0 commit comments