Skip to content

Commit df36932

Browse files
committed
Interpolate missing dates
1 parent 62c71af commit df36932

File tree

4 files changed

+264
-94
lines changed

4 files changed

+264
-94
lines changed

src/main/java/com/kosherjava/zmanim/AstronomicalCalendar.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public class AstronomicalCalendar implements Cloneable {
106106
*/
107107
private AstronomicalCalculator astronomicalCalculator;
108108

109+
private final TimeZone timeZoneUTC = TimeZone.getTimeZone("UTC");
110+
109111
/**
110112
* The getSunrise method returns a <code>Date</code> representing the
111113
* {@link AstronomicalCalculator#getElevationAdjustment(double) elevation adjusted} sunrise time. The zenith used
@@ -616,7 +618,7 @@ protected Date getDateFromTime(double time, SolarEvent solarEvent) {
616618
double calculatedTime = time;
617619

618620
Calendar adjustedCalendar = getAdjustedCalendar();
619-
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
621+
Calendar cal = Calendar.getInstance(timeZoneUTC);
620622
cal.clear();// clear all fields
621623
cal.set(Calendar.YEAR, adjustedCalendar.get(Calendar.YEAR));
622624
cal.set(Calendar.MONTH, adjustedCalendar.get(Calendar.MONTH));

src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java

+173-72
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)