diff --git a/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java b/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
index b894f1ab..22d56420 100644
--- a/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
+++ b/src/main/java/com/kosherjava/zmanim/util/GeoLocation.java
@@ -64,6 +64,26 @@ public class GeoLocation implements Cloneable {
* @see #setElevation(double)
*/
private double elevation;
+
+ /**
+ * The elevation in Meters above sea level of the horizon at the point where the sun rises that day.
+ * This point is not simply due east and shifts through the year as the sunrise azimuth changes with the season.
+ * Defaults to 0.
+ * @see #getHorizonElevationAtSunrise()
+ * @see #setHorizonElevationAtSunrise(double)
+ * @see #getSunriseElevation()
+ */
+ private double horizonElevationAtSunrise = 0;
+
+ /**
+ * The elevation in Meters above sea level of the horizon at the point where the sun sets that day.
+ * This point is not simply due west and shifts through the year as the sunset azimuth changes with the season.
+ * Defaults to 0.
+ * @see #getHorizonElevationAtSunset()
+ * @see #setHorizonElevationAtSunset(double)
+ * @see #getSunsetElevation()
+ */
+ private double horizonElevationAtSunset = 0;
/**
* Constant for a distance type calculation.
@@ -118,6 +138,87 @@ public void setElevation(double elevation) {
this.elevation = elevation;
}
+ /**
+ * Method to return the elevation in Meters above sea level of the horizon at the point where the sun rises that day.
+ *
+ * @return the horizon elevation at sunrise in Meters.
+ * @see #setHorizonElevationAtSunrise(double)
+ */
+ public double getHorizonElevationAtSunrise() {
+ return horizonElevationAtSunrise;
+ }
+
+ /**
+ * Method to set the elevation in Meters above sea level of the horizon at the point where the sun rises that day.
+ * This point is not simply due east and shifts through the year as the sunrise azimuth changes with the season.
+ * Sunrise is calculated with the observer's height above this horizon instead of above sea level.
+ * @param horizonElevationAtSunrise the horizon elevation at sunrise in Meters. An IllegalArgumentException will be thrown
+ * if the value is a negative, {@link java.lang.Double#isNaN(double)} or {@link java.lang.Double#isInfinite(double)}.
+ * @see #getSunriseElevation()
+ */
+ public void setHorizonElevationAtSunrise(double horizonElevationAtSunrise) {
+ validateHorizonElevation(horizonElevationAtSunrise);
+ this.horizonElevationAtSunrise = horizonElevationAtSunrise;
+ }
+
+ /**
+ * Method to return the elevation in Meters above sea level of the horizon at the point where the sun sets that day.
+ *
+ * @return the horizon elevation at sunset in Meters.
+ * @see #setHorizonElevationAtSunset(double)
+ */
+ public double getHorizonElevationAtSunset() {
+ return horizonElevationAtSunset;
+ }
+
+ /**
+ * Method to set the elevation in Meters above sea level of the horizon at the point where the sun sets that day.
+ * This point is not simply due west and shifts through the year as the sunset azimuth changes with the season.
+ * Sunset is calculated with the observer's height above this horizon instead of above sea level.
+ * @param horizonElevationAtSunset the horizon elevation at sunset in Meters. An IllegalArgumentException will be thrown
+ * if the value is a negative, {@link java.lang.Double#isNaN(double)} or {@link java.lang.Double#isInfinite(double)}.
+ * @see #getSunsetElevation()
+ */
+ public void setHorizonElevationAtSunset(double horizonElevationAtSunset) {
+ validateHorizonElevation(horizonElevationAtSunset);
+ this.horizonElevationAtSunset = horizonElevationAtSunset;
+ }
+
+ /**
+ * Throws if a horizon elevation is not a usable height in Meters above sea level.
+ * @param horizonElevation the value to check.
+ */
+ private static void validateHorizonElevation(double horizonElevation) {
+ if (horizonElevation < 0) {
+ throw new IllegalArgumentException("Horizon elevation cannot be negative");
+ }
+ if (Double.isNaN(horizonElevation) || Double.isInfinite(horizonElevation)) {
+ throw new IllegalArgumentException("Horizon elevation cannot be NaN or infinite");
+ }
+ }
+
+ /**
+ * The height in Meters of the observer above the {@link #getHorizonElevationAtSunrise() sunrise horizon}.
+ * A higher horizon returns 0, since halachically land blocking the sun does not delay sunrise.
+ *
+ * @return the observer's height above the sunrise horizon in Meters, never negative. With the default sea-level horizon
+ * this is the same as {@link #getElevation()}.
+ */
+ public double getSunriseElevation() {
+ return Math.max(elevation - horizonElevationAtSunrise, 0);
+ }
+
+ /**
+ * The height in Meters of the observer above the {@link #getHorizonElevationAtSunset() sunset horizon}.
+ * A higher horizon returns 0, since halachically land blocking the sun does not make sunset earlier.
+ *
+ * @return the observer's height above the sunset horizon in Meters, never negative. With the default sea-level horizon
+ * this is the same as {@link #getElevation()}.
+ */
+ public double getSunsetElevation() {
+ return Math.max(elevation - horizonElevationAtSunset, 0);
+ }
+
/**
* GeoLocation constructor with parameters for all required fields.
*
@@ -153,6 +254,30 @@ public GeoLocation(String name, double latitude, double longitude, double elevat
setZoneId(zoneId);
}
+ /**
+ * GeoLocation constructor for a location whose horizon at sunrise or sunset is not at sea level.
+ *
+ * @param name The location name for display, for example "Lakewood, NJ".
+ * @param latitude the latitude as a {@code double}, for example 40.096 for Lakewood, NJ. Note: For latitudes south of
+ * the equator, a negative value should be used.
+ * @param longitude the longitude as a {@code double}, for example -74.222 for Lakewood, NJ. Note: For longitudes west
+ * of the Prime Meridian (Greenwich), a negative value should
+ * be used.
+ * @param elevation the elevation above sea level in Meters.
+ * @param horizonElevationAtSunrise the elevation above sea level in Meters of the horizon at the point where the sun rises
+ * that day.
+ * @param horizonElevationAtSunset the elevation above sea level in Meters of the horizon at the point where the sun sets
+ * that day.
+ * @param zoneId the {@code ZoneId} for the location.
+ * @see #setHorizonElevationAtSunrise(double)
+ */
+ public GeoLocation(String name, double latitude, double longitude, double elevation, double horizonElevationAtSunrise,
+ double horizonElevationAtSunset, ZoneId zoneId) {
+ this(name, latitude, longitude, elevation, zoneId);
+ setHorizonElevationAtSunrise(horizonElevationAtSunrise);
+ setHorizonElevationAtSunset(horizonElevationAtSunset);
+ }
+
/**
* Default GeoLocation constructor will set location to the Prime Meridian at Greenwich, England and a {@code ZoneId}
* of GMT. The longitude will be set to 0 and the latitude will be 51.4772 to match the location of the " + getLatitude() + "\n" +
"\t" + getLongitude() + "\n" +
"\t" + getElevation() + " Meters" + "\n" +
+ "\t" + getHorizonElevationAtSunrise() + " Meters" + "\n" +
+ "\t" + getHorizonElevationAtSunset() + " Meters" + "\n" +
"\t" + getZoneId().getId() + "\n" +
"\t" + getZoneId().getDisplayName(TextStyle.FULL, Locale.ENGLISH) + "\n" +
"";
@@ -509,6 +636,8 @@ public boolean equals(Object object) {
return Double.compare(this.latitude, geo.latitude) == 0
&& Double.compare(this.longitude, geo.longitude) == 0
&& Double.compare(this.elevation, geo.elevation) == 0
+ && Double.compare(this.horizonElevationAtSunrise, geo.horizonElevationAtSunrise) == 0
+ && Double.compare(this.horizonElevationAtSunset, geo.horizonElevationAtSunset) == 0
&& Objects.equals(this.locationName, geo.locationName)
&& Objects.equals(this.zoneId, geo.zoneId);
}
@@ -518,7 +647,8 @@ public boolean equals(Object object) {
*/
@Override
public int hashCode() {
- return Objects.hash(getClass(), this.latitude, this.longitude, this.elevation, this.locationName, this.zoneId);
+ return Objects.hash(getClass(), this.latitude, this.longitude, this.elevation, this.horizonElevationAtSunrise,
+ this.horizonElevationAtSunset, this.locationName, this.zoneId);
}
/**
@@ -530,6 +660,8 @@ public String toString() {
"\nLatitude:\t\t\t" + getLatitude() + "\u00B0" +
"\nLongitude:\t\t\t" + getLongitude() + "\u00B0" +
"\nElevation:\t\t\t" + getElevation() + " Meters" +
+ "\nHorizon Elevation At Sunrise:\t" + getHorizonElevationAtSunrise() + " Meters" +
+ "\nHorizon Elevation At Sunset:\t" + getHorizonElevationAtSunset() + " Meters" +
"\nTimezone ID:\t\t\t" + getZoneId().getId() +
"\nTimezone Display Name:\t\t" + getZoneId().getDisplayName(TextStyle.FULL, Locale.ENGLISH);
}
diff --git a/src/main/java/com/kosherjava/zmanim/util/MeeusCalculator.java b/src/main/java/com/kosherjava/zmanim/util/MeeusCalculator.java
index 046afaed..e4187a63 100644
--- a/src/main/java/com/kosherjava/zmanim/util/MeeusCalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/MeeusCalculator.java
@@ -150,7 +150,14 @@ public double getUTCSunset(LocalDate dt, GeoLocation geoLocation, double zenith,
*/
private double getUTCSunRiseSet(LocalDate localDate, GeoLocation geoLocation, double zenith,
boolean adjustForElevation, SolarEvent solarEvent) {
- double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
+ double elevation = 0;
+ if (adjustForElevation) {
+ if (solarEvent == SolarEvent.SUNRISE) {
+ elevation = geoLocation.getSunriseElevation();
+ } else {
+ elevation = geoLocation.getSunsetElevation();
+ }
+ }
double adjustedZenith = adjustZenith(zenith, elevation, localDate);
double riseSet = getSunRiseSetUTC(localDate, geoLocation.getLatitude(), -geoLocation.getLongitude(),
adjustedZenith, solarEvent);
diff --git a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
index 633ae38b..416e2f74 100644
--- a/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/NOAACalculator.java
@@ -97,7 +97,14 @@ public double getUTCSunset(LocalDate dt, GeoLocation geoLocation, double zenith,
*/
private double getUTCSunRiseSet(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation,
SolarEvent solarEvent) {
- double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
+ double elevation = 0;
+ if (adjustForElevation) {
+ if (solarEvent == SolarEvent.SUNRISE) {
+ elevation = geoLocation.getSunriseElevation();
+ } else {
+ elevation = geoLocation.getSunsetElevation();
+ }
+ }
double adjustedZenith = adjustZenith(zenith, elevation, localDate);
double riseSet = getSunRiseSetUTC(localDate, geoLocation.getLatitude(), -geoLocation.getLongitude(),
adjustedZenith, solarEvent);
diff --git a/src/main/java/com/kosherjava/zmanim/util/SPACalculator.java b/src/main/java/com/kosherjava/zmanim/util/SPACalculator.java
index 0717069b..100457ea 100644
--- a/src/main/java/com/kosherjava/zmanim/util/SPACalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/SPACalculator.java
@@ -207,7 +207,14 @@ public double getUTCSunset(LocalDate dt, GeoLocation geoLocation, double zenith,
*/
private double getUTCSunRiseSet(LocalDate localDate, GeoLocation geoLocation, double zenith,
boolean adjustForElevation, SolarEvent solarEvent) {
- double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
+ double elevation = 0;
+ if (adjustForElevation) {
+ if (solarEvent == SolarEvent.SUNRISE) {
+ elevation = geoLocation.getSunriseElevation();
+ } else {
+ elevation = geoLocation.getSunsetElevation();
+ }
+ }
double adjustedZenith = adjustZenith(zenith, elevation, localDate);
double riseSet = solveRiseSet(localDate, geoLocation, adjustedZenith, solarEvent);
if (Double.isNaN(riseSet)) {
diff --git a/src/main/java/com/kosherjava/zmanim/util/SunTimesCalculator.java b/src/main/java/com/kosherjava/zmanim/util/SunTimesCalculator.java
index 4b36ae54..5c341e46 100644
--- a/src/main/java/com/kosherjava/zmanim/util/SunTimesCalculator.java
+++ b/src/main/java/com/kosherjava/zmanim/util/SunTimesCalculator.java
@@ -66,14 +66,14 @@ public String getCalculatorName() {
@Override
public double getUTCSunrise(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
- double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
+ double elevation = adjustForElevation ? geoLocation.getSunriseElevation() : 0;
double adjustedZenith = adjustZenith(zenith, elevation, localDate);
return getTimeUTC(localDate, geoLocation, adjustedZenith, true);
}
@Override
public double getUTCSunset(LocalDate localDate, GeoLocation geoLocation, double zenith, boolean adjustForElevation) {
- double elevation = adjustForElevation ? geoLocation.getElevation() : 0;
+ double elevation = adjustForElevation ? geoLocation.getSunsetElevation() : 0;
double adjustedZenith = adjustZenith(zenith, elevation, localDate);
return getTimeUTC(localDate, geoLocation, adjustedZenith, false);
}