Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ public class GeoLocation implements Cloneable {
/** constant for milliseconds in an hour (3,600,000) */
private static final long HOUR_MILLIS = MINUTE_MILLIS * 60;

private static final double PI2 = Math.PI * 2;
private static final double PI_4 = Math.PI / 4;

/**
* Method to return the elevation in Meters <b>above</b> sea level.
*
Expand Down Expand Up @@ -452,7 +455,7 @@ private double vincentyInverseFormula(GeoLocation location, int formula) {
double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);

double lambda = L;
double lambdaP = 2 * Math.PI;
double lambdaP = PI2;
double iterLimit = 20;
double sinLambda = 0;
double cosLambda = 0;
Expand Down Expand Up @@ -521,10 +524,10 @@ private double vincentyInverseFormula(GeoLocation location, int formula) {
*/
public double getRhumbLineBearing(GeoLocation location) {
double dLon = Math.toRadians(location.getLongitude() - getLongitude());
double dPhi = Math.log(Math.tan(Math.toRadians(location.getLatitude()) / 2 + Math.PI / 4)
/ Math.tan(Math.toRadians(getLatitude()) / 2 + Math.PI / 4));
double dPhi = Math.log(Math.tan(Math.toRadians(location.getLatitude()) / 2 + PI_4)
/ Math.tan(Math.toRadians(getLatitude()) / 2 + PI_4));
if (Math.abs(dLon) > Math.PI)
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon);
dLon = dLon > 0 ? -(PI2 - dLon) : (PI2 + dLon);
return Math.toDegrees(Math.atan2(dLon, dPhi));
}

Expand All @@ -540,16 +543,16 @@ public double getRhumbLineDistance(GeoLocation location) {
double earthRadius = 6378137; // Earth's radius in meters (WGS-84)
double dLat = Math.toRadians(location.getLatitude()) - Math.toRadians(getLatitude());
double dLon = Math.abs(Math.toRadians(location.getLongitude()) - Math.toRadians(getLongitude()));
double dPhi = Math.log(Math.tan(Math.toRadians(location.getLatitude()) / 2 + Math.PI / 4)
/ Math.tan(Math.toRadians(getLatitude()) / 2 + Math.PI / 4));
double dPhi = Math.log(Math.tan(Math.toRadians(location.getLatitude()) / 2 + PI_4)
/ Math.tan(Math.toRadians(getLatitude()) / 2 + PI_4));
double q = dLat / dPhi;

if (!(Math.abs(q) <= Double.MAX_VALUE)) {
q = Math.cos(Math.toRadians(getLatitude()));
}
// if dLon over 180° take shorter rhumb across 180° meridian:
if (dLon > Math.PI) {
dLon = 2 * Math.PI - dLon;
dLon = PI2 - dLon;
}
double d = Math.sqrt(dLat * dLat + q * q * dLon * dLon);
return d * earthRadius;
Expand Down
112 changes: 95 additions & 17 deletions src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ public double getUTCSunrise(LocalDate dt, GeoLocation geoLocation, double zenith
public double getUTCSunset(LocalDate dt, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
return getUTCSunRiseSet(dt, geoLocation, zenith, adjustForElevation,SolarEvent.SUNSET);
}

/**
* A method that calculates UTC sunrise or sunset as well as any time based on an angle above or below sunset and
* returns it as a <code>double</code> in 24-hour format. 5:45:00 AM will return 5.75.
*
*
* @param localDate
* Used to calculate day of year.
* @param geoLocation
Expand All @@ -98,7 +98,7 @@ public double getUTCSunset(LocalDate dt, GeoLocation geoLocation, double zenith,
* calculation (expected behavior for some locations such as near the poles, {@link Double#NaN} will be returned.
* @see #getElevationAdjustment(double)
*/
private double getUTCSunRiseSet(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation,
private double getUTCSunRiseSet(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation,
SolarEvent solarEvent) {
double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
double adjustedZenith = adjustZenith(zenith, elevation);
Expand Down Expand Up @@ -287,11 +287,13 @@ private static double getEquationOfTime(double julianCenturies) {
double geomMeanAnomalySun = getSunGeometricMeanAnomaly(julianCenturies);
double y = Math.tan(Math.toRadians(epsilon) / 2.0);
y *= y;
double sin2l0 = Math.sin(2.0 * Math.toRadians(geomMeanLongSun));
double sinm = Math.sin(Math.toRadians(geomMeanAnomalySun));
double cos2l0 = Math.cos(2.0 * Math.toRadians(geomMeanLongSun));
double sin4l0 = Math.sin(4.0 * Math.toRadians(geomMeanLongSun));
double sin2m = Math.sin(2.0 * Math.toRadians(geomMeanAnomalySun));
double geomMeanLongSunRad = Math.toRadians(geomMeanLongSun);
double geomMeanAnomalySunRad = Math.toRadians(geomMeanAnomalySun);
double sin2l0 = Math.sin(2.0 * geomMeanLongSunRad);
double sinm = Math.sin(geomMeanAnomalySunRad);
double cos2l0 = Math.cos(2.0 * geomMeanLongSunRad);
double sin4l0 = Math.sin(4.0 * geomMeanLongSunRad);
double sin2m = Math.sin(2.0 * geomMeanAnomalySunRad);
double equationOfTime = y * sin2l0 - 2.0 * eccentricityEarthOrbit * sinm + 4.0 * eccentricityEarthOrbit * y
* sinm * cos2l0 - 0.5 * y * y * sin4l0 - 1.25 * eccentricityEarthOrbit * eccentricityEarthOrbit * sin2m;
return Math.toDegrees(equationOfTime) * 4.0;
Expand All @@ -314,7 +316,8 @@ private static double getEquationOfTime(double julianCenturies) {
private static double getSunHourAngle(double latitude, double solarDeclination, double zenith, SolarEvent solarEvent) {
double latRad = Math.toRadians(latitude);
double sdRad = Math.toRadians(solarDeclination);
double hourAngle = (Math.acos(Math.cos(Math.toRadians(zenith)) / (Math.cos(latRad) * Math.cos(sdRad))
double zRad = Math.toRadians(zenith);
double hourAngle = (Math.acos(Math.cos(zRad) / (Math.cos(latRad) * Math.cos(sdRad))
- Math.tan(latRad) * Math.tan(sdRad)));

if (solarEvent == SolarEvent.SUNSET) {
Expand Down Expand Up @@ -346,7 +349,7 @@ public double getSolarAzimuth(ZonedDateTime zonedDateTime, GeoLocation geoLocati
* @param isAzimuth
* true for azimuth, false for elevation
* @return solar elevation or azimuth in degrees.
*
*
* @see #getSolarElevation(ZonedDateTime, GeoLocation)
* @see #getSolarAzimuth(ZonedDateTime, GeoLocation)
*/
Expand All @@ -363,7 +366,7 @@ private double getSolarElevationAzimuth(ZonedDateTime zonedDateTime, GeoLocation
double trueSolarTime = ((fractionalDay + eot / 1440.0 + lon / 360.0) + 2) % 1;
double hourAngle = trueSolarTime * 2 * Math.PI - Math.PI;
double cosZenith = Math.sin(lat) * Math.sin(decl) + Math.cos(lat) * Math.cos(decl) * Math.cos(hourAngle);
double zenith = Math.acos(Math.max(-1, Math.min(1, cosZenith)));
double zenith = Math.acos(Math.max(-1, Math.min(1, cosZenith)));
double zenithDeg = Math.toDegrees(zenith);
double elevation = 90.0 - zenithDeg;
elevation = 90.0 - (zenithDeg - adjustElevationForRefraction(elevation));
Expand All @@ -378,9 +381,9 @@ private double getSolarElevationAzimuth(ZonedDateTime zonedDateTime, GeoLocation
}
return isAzimuth ? (azimuth + 360) % 360 : elevation;
}

/**
* Apply refraction adjustment to solar elevation.
* Apply refraction adjustment to solar elevation.
* @param elevation the elevation to adjust.
* @return the adjusted elevation.
*/
Expand All @@ -401,7 +404,7 @@ private double adjustElevationForRefraction(double elevation) {
}
return correction / 3600.0;
}

/**
* {@inheritDoc}
* @see #getSolarNoonMidnightUTC(double, double, SolarEvent)
Expand Down Expand Up @@ -477,6 +480,11 @@ private static double getSolarNoonMidnightUTC(double julianDay, double longitude
*/
private static double getSunRiseSetUTC(LocalDate localDate, double latitude, double longitude, double zenith,
SolarEvent solarEvent) {
return getSunRiseSetUTC(localDate, latitude, longitude, zenith, solarEvent, true);
}

private static double getSunRiseSetUTC(LocalDate localDate, double latitude, double longitude, double zenith,
SolarEvent solarEvent, boolean interpolate) {
double julianDay = getJulianDay(localDate);

// Find the time of solar noon at the location, and use that declination.
Expand All @@ -485,29 +493,99 @@ private static double getSunRiseSetUTC(LocalDate localDate, double latitude, dou
// efficient but would likely cause a very minor discrepancy in the calculated times (likely not reducing
// accuracy, just slightly different, thus potentially breaking test cases). Regardless, it would be within
// milliseconds.
double noonmin = getSolarNoonMidnightUTC(julianDay, longitude, SolarEvent.NOON);
double noonmin = getSolarNoonMidnightUTC(julianDay, longitude, SolarEvent.NOON);
double tnoon = getJulianCenturiesFromJulianDay(julianDay + noonmin / 1440.0);

// First calculates sunrise and approximate length of day
double equationOfTime = getEquationOfTime(tnoon);
double solarDeclination = getSunDeclination(tnoon);
double hourAngle = getSunHourAngle(latitude, solarDeclination, zenith, solarEvent);
if (Double.isNaN(hourAngle)) {
if (interpolate) {
return interpolateSunRiseSetUTC(localDate, latitude, longitude, zenith, solarEvent);
}
return Double.NaN;
}
double delta = longitude - Math.toDegrees(hourAngle);
double timeDiff = 4 * delta;
double timeUTC = 720 + timeDiff - equationOfTime;

// Second pass includes fractional Julian Day in gamma calc
double newt = getJulianCenturiesFromJulianDay(julianDay + timeUTC / 1440.0);
equationOfTime = getEquationOfTime(newt);

solarDeclination = getSunDeclination(newt);
hourAngle = getSunHourAngle(latitude, solarDeclination, zenith, solarEvent);
if (Double.isNaN(hourAngle)) {
if (interpolate) {
return interpolateSunRiseSetUTC(localDate, latitude, longitude, zenith, solarEvent);
}
return Double.NaN;
}
delta = longitude - Math.toDegrees(hourAngle);
timeDiff = 4 * delta;
timeUTC = 720 + timeDiff - equationOfTime;
return timeUTC;
}


// Use linear interpolation to calculate a missing time.
private static double interpolateSunRiseSetUTC(LocalDate localDate, double latitude, double longitude, double zenith, SolarEvent solarEvent) {
final int dayOfYear = localDate.getDayOfYear();
double x1 = 0;
double y1 = 0;
double x2 = 0;
double y2 = 0;
double dd1 = Double.MAX_VALUE;
double dd2 = Double.MAX_VALUE;

final int d1 = Math.max(dayOfYear - 180, 1);
final int d2 = Math.min(dayOfYear + 180, 365);
for (int d = d1; d < dayOfYear; d++) {
double time = getSunRiseSetUTC(localDate.withDayOfYear(d), latitude, longitude, zenith, solarEvent, false);
if (!Double.isNaN(time)) {
int dd = Math.abs(dayOfYear - d);
if (dd < dd1) {
x2 = x1;
y2 = y1;
dd2 = dd1;

x1 = d;
y1 = time;
dd1 = dd;
}
}
}
for (int d = dayOfYear + 1; d <= d2; d++) {
double time = getSunRiseSetUTC(localDate.withDayOfYear(d), latitude, longitude, zenith, solarEvent, false);
if (!Double.isNaN(time)) {
if ((x2 > 0) && (x2 < dayOfYear)) {
x2 = 0;
y2 = 0;
dd2 = Integer.MAX_VALUE;
}
int dd = Math.abs(dayOfYear - d);
if (dd <= dd2) {
x2 = d;
y2 = time;
dd2 = dd;
}
}
}

if ((x1 == 0) || (x2 == 0)) {
return Double.NaN;
}
double dx = x2 - x1;
if (dx == 0) {
return Double.NaN;
}
double dy = y2 - y1;
return y1 + (((dayOfYear - x1) * dy) / dx);
}

/**
* {@inheritDoc}
* @todo This is very much a work in progress. It works in some but not all cases.
* @todo This is very much a work in progress. It works in some but not all cases.
* There will be edge cases where the azimuth will occur more than once a day when based on the equation of time,
* the day is shorter than 24 hours. In that case, the time for the first one will be returned.
* <br>FIXME:
Expand Down
Loading
Loading