001/* 002 * Zmanim Java API 003 * Copyright (C) 2004-2023 Eliyahu Hershfeld 004 * 005 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General 006 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 007 * any later version. 008 * 009 * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied 010 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 011 * details. 012 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to 013 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, 014 * or connect to: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 015 */ 016package com.kosherjava.zmanim.util; 017 018import java.util.Calendar; 019 020/** 021 * An abstract class that all sun time calculating classes extend. This allows the algorithm used to be changed at 022 * runtime, easily allowing comparison the results of using different algorithms. 023 * @todo Consider methods that would allow atmospheric modeling. This can currently be adjusted by {@link 024 * #setRefraction(double) setting the refraction}. 025 * 026 * @author © Eliyahu Hershfeld 2004 - 2023 027 */ 028public abstract class AstronomicalCalculator implements Cloneable { 029 /** 030 * The commonly used average solar refraction. Calendrical Calculations lists a more accurate global average of 031 * 34.478885263888294 032 * 033 * @see #getRefraction() 034 */ 035 private double refraction = 34 / 60d; 036 037 /** 038 * The commonly used average solar radius in minutes of a degree. 039 * 040 * @see #getSolarRadius() 041 */ 042 private double solarRadius = 16 / 60d; 043 044 /** 045 * The commonly used average earth radius in KM. At this time, this only affects elevation adjustment and not the 046 * sunrise and sunset calculations. The value currently defaults to 6356.9 KM. 047 * 048 * @see #getEarthRadius() 049 * @see #setEarthRadius(double) 050 */ 051 private double earthRadius = 6356.9; // in KM 052 053 /** 054 * A method that returns the earth radius in KM. The value currently defaults to 6356.9 KM if not set. 055 * 056 * @return the earthRadius the earth radius in KM. 057 */ 058 public double getEarthRadius() { 059 return earthRadius; 060 } 061 062 /** 063 * A method that allows setting the earth's radius. 064 * 065 * @param earthRadius 066 * the earthRadius to set in KM 067 */ 068 public void setEarthRadius(double earthRadius) { 069 this.earthRadius = earthRadius; 070 } 071 072 /** 073 * The zenith of astronomical sunrise and sunset. The sun is 90° from the vertical 0° 074 */ 075 private static final double GEOMETRIC_ZENITH = 90; 076 077 /** 078 * Returns the default class for calculating sunrise and sunset. This is currently the {@link NOAACalculator}, 079 * but this may change. 080 * 081 * @return AstronomicalCalculator the default class for calculating sunrise and sunset. In the current 082 * implementation the default calculator returned is the {@link NOAACalculator}. 083 */ 084 public static AstronomicalCalculator getDefault() { 085 return new NOAACalculator(); 086 } 087 088 /** 089 * Returns the name of the algorithm. 090 * 091 * @return the descriptive name of the algorithm. 092 */ 093 public abstract String getCalculatorName(); 094 095 /** 096 * Setter method for the descriptive name of the calculator. This will typically not have to be set 097 * 098 * @param calculatorName 099 * descriptive name of the algorithm. 100 */ 101 102 /** 103 * A method that calculates UTC sunrise as well as any time based on an angle above or below sunrise. This abstract 104 * method is implemented by the classes that extend this class. 105 * 106 * @param calendar 107 * Used to calculate day of year. 108 * @param geoLocation 109 * The location information used for astronomical calculating sun times. 110 * @param zenith 111 * the azimuth below the vertical zenith of 90 degrees. for sunrise typically the {@link #adjustZenith 112 * zenith} used for the calculation uses geometric zenith of 90° and {@link #adjustZenith adjusts} 113 * this slightly to account for solar refraction and the sun's radius. Another example would be 114 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getBeginNauticalTwilight()} that passes 115 * {@link com.kosherjava.zmanim.AstronomicalCalendar#NAUTICAL_ZENITH} to this method. 116 * @param adjustForElevation 117 * Should the time be adjusted for elevation 118 * @return The UTC time of sunrise in 24 hour format. 5:45:00 AM will return 5.75.0. If an error was encountered in 119 * the calculation (expected behavior for some locations such as near the poles, 120 * {@link java.lang.Double#NaN} will be returned. 121 * @see #getElevationAdjustment(double) 122 */ 123 public abstract double getUTCSunrise(Calendar calendar, GeoLocation geoLocation, double zenith, 124 boolean adjustForElevation); 125 126 /** 127 * A method that calculates UTC sunset as well as any time based on an angle above or below sunset. This abstract 128 * method is implemented by the classes that extend this class. 129 * 130 * @param calendar 131 * Used to calculate day of year. 132 * @param geoLocation 133 * The location information used for astronomical calculating sun times. 134 * @param zenith 135 * the azimuth below the vertical zenith of 90°. For sunset typically the {@link #adjustZenith 136 * zenith} used for the calculation uses geometric zenith of 90° and {@link #adjustZenith adjusts} 137 * this slightly to account for solar refraction and the sun's radius. Another example would be 138 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getEndNauticalTwilight()} that passes 139 * {@link com.kosherjava.zmanim.AstronomicalCalendar#NAUTICAL_ZENITH} to this method. 140 * @param adjustForElevation 141 * Should the time be adjusted for elevation 142 * @return The UTC time of sunset in 24 hour format. 5:45:00 AM will return 5.75.0. If an error was encountered in 143 * the calculation (expected behavior for some locations such as near the poles, 144 * {@link java.lang.Double#NaN} will be returned. 145 * @see #getElevationAdjustment(double) 146 */ 147 public abstract double getUTCSunset(Calendar calendar, GeoLocation geoLocation, double zenith, 148 boolean adjustForElevation); 149 150 151 /** 152 * Return <a href="https://en.wikipedia.org/wiki/Noon#Solar_noon">solar noon</a> (UTC) for the given day at the 153 * given location on earth. The the {@link com.kosherjava.zmanim.util.NOAACalculator} implementation calculates 154 * true solar noon, while the {@link com.kosherjava.zmanim.util.SunTimesCalculator} approximates it, calculating 155 * the time as halfway between sunrise and sunset. 156 * 157 * @param calendar 158 * Used to calculate day of year. 159 * @param geoLocation 160 * The location information used for astronomical calculating sun times. 161 * 162 * @return the time in minutes from zero UTC 163 */ 164 public abstract double getUTCNoon(Calendar calendar, GeoLocation geoLocation); 165 166 /** 167 * Method to return the adjustment to the zenith required to account for the elevation. Since a person at a higher 168 * elevation can see farther below the horizon, the calculation for sunrise / sunset is calculated below the horizon 169 * used at sea level. This is only used for sunrise and sunset and not times before or after it such as 170 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getBeginNauticalTwilight() nautical twilight} since those 171 * calculations are based on the level of available light at the given dip below the horizon, something that is not 172 * affected by elevation, the adjustment should only made if the zenith == 90° {@link #adjustZenith adjusted} 173 * for refraction and solar radius. The algorithm used is 174 * 175 * <pre> 176 * elevationAdjustment = Math.toDegrees(Math.acos(earthRadiusInMeters / (earthRadiusInMeters + elevationMeters))); 177 * </pre> 178 * 179 * The source of this algorithm is <a href="http://www.calendarists.com">Calendrical Calculations</a> by Edward M. 180 * Reingold and Nachum Dershowitz. An alternate algorithm that produces an almost identical (but not accurate) 181 * result found in Ma'aglay Tzedek by Moishe Kosower and other sources is: 182 * 183 * <pre> 184 * elevationAdjustment = 0.0347 * Math.sqrt(elevationMeters); 185 * </pre> 186 * 187 * @param elevation 188 * elevation in Meters. 189 * @return the adjusted zenith 190 */ 191 double getElevationAdjustment(double elevation) { 192 double elevationAdjustment = Math.toDegrees(Math.acos(earthRadius / (earthRadius + (elevation / 1000)))); 193 return elevationAdjustment; 194 } 195 196 /** 197 * Adjusts the zenith of astronomical sunrise and sunset to account for solar refraction, solar radius and 198 * elevation. The value for Sun's zenith and true rise/set Zenith (used in this class and subclasses) is the angle 199 * that the center of the Sun makes to a line perpendicular to the Earth's surface. If the Sun were a point and the 200 * Earth were without an atmosphere, true sunset and sunrise would correspond to a 90° zenith. Because the Sun 201 * is not a point, and because the atmosphere refracts light, this 90° zenith does not, in fact, correspond to 202 * true sunset or sunrise, instead the center of the Sun's disk must lie just below the horizon for the upper edge 203 * to be obscured. This means that a zenith of just above 90° must be used. The Sun subtends an angle of 16 204 * minutes of arc (this can be changed via the {@link #setSolarRadius(double)} method , and atmospheric refraction 205 * accounts for 34 minutes or so (this can be changed via the {@link #setRefraction(double)} method), giving a total 206 * of 50 arcminutes. The total value for ZENITH is 90+(5/6) or 90.8333333° for true sunrise/sunset. Since a 207 * person at an elevation can see blow the horizon of a person at sea level, this will also adjust the zenith to 208 * account for elevation if available. Note that this will only adjust the value if the zenith is exactly 90 degrees. 209 * For values below and above this no correction is done. As an example, astronomical twilight is when the sun is 210 * 18° below the horizon or {@link com.kosherjava.zmanim.AstronomicalCalendar#ASTRONOMICAL_ZENITH 108° 211 * below the zenith}. This is traditionally calculated with none of the above mentioned adjustments. The same goes 212 * for various <em>tzais</em> and <em>alos</em> times such as the 213 * {@link com.kosherjava.zmanim.ZmanimCalendar#ZENITH_16_POINT_1 16.1°} dip used in 214 * {@link com.kosherjava.zmanim.ComplexZmanimCalendar#getAlos16Point1Degrees()}. 215 * 216 * @param zenith 217 * the azimuth below the vertical zenith of 90°. For sunset typically the {@link #adjustZenith 218 * zenith} used for the calculation uses geometric zenith of 90° and {@link #adjustZenith adjusts} 219 * this slightly to account for solar refraction and the sun's radius. Another example would be 220 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getEndNauticalTwilight()} that passes 221 * {@link com.kosherjava.zmanim.AstronomicalCalendar#NAUTICAL_ZENITH} to this method. 222 * @param elevation 223 * elevation in Meters. 224 * @return The zenith adjusted to include the {@link #getSolarRadius sun's radius}, {@link #getRefraction 225 * refraction} and {@link #getElevationAdjustment elevation} adjustment. This will only be adjusted for 226 * sunrise and sunset (if the zenith == 90°) 227 * @see #getElevationAdjustment(double) 228 */ 229 double adjustZenith(double zenith, double elevation) { 230 double adjustedZenith = zenith; 231 if (zenith == GEOMETRIC_ZENITH) { // only adjust if it is exactly sunrise or sunset 232 adjustedZenith = zenith + (getSolarRadius() + getRefraction() + getElevationAdjustment(elevation)); 233 } 234 return adjustedZenith; 235 } 236 237 /** 238 * Method to get the refraction value to be used when calculating sunrise and sunset. The default value is 34 arc 239 * minutes. The <a href= 240 * "https://web.archive.org/web/20150915094635/http://emr.cs.iit.edu/home/reingold/calendar-book/second-edition/errata.pdf" 241 * >Errata and Notes for Calendrical Calculations: The Millennium Edition</a> by Edward M. Reingold and Nachum Dershowitz 242 * lists the actual average refraction value as 34.478885263888294 or approximately 34' 29". The refraction value as well 243 * as the solarRadius and elevation adjustment are added to the zenith used to calculate sunrise and sunset. 244 * 245 * @return The refraction in arc minutes. 246 */ 247 public double getRefraction() { 248 return this.refraction; 249 } 250 251 /** 252 * A method to allow overriding the default refraction of the calculator. 253 * @todo At some point in the future, an AtmosphericModel or Refraction object that models the atmosphere of different 254 * locations might be used for increased accuracy. 255 * 256 * @param refraction 257 * The refraction in arc minutes. 258 * @see #getRefraction() 259 */ 260 public void setRefraction(double refraction) { 261 this.refraction = refraction; 262 } 263 264 /** 265 * Method to get the sun's radius. The default value is 16 arc minutes. The sun's radius as it appears from earth is 266 * almost universally given as 16 arc minutes but in fact it differs by the time of the year. At the <a 267 * href="https://en.wikipedia.org/wiki/Perihelion">perihelion</a> it has an apparent radius of 16.293, while at the 268 * <a href="https://en.wikipedia.org/wiki/Aphelion">aphelion</a> it has an apparent radius of 15.755. There is little 269 * affect for most location, but at high and low latitudes the difference becomes more apparent. My Calculations for 270 * the difference at the location of the <a href="https://www.rmg.co.uk/royal-observatory">Royal Observatory, Greenwich</a> 271 * shows only a 4.494 second difference between the perihelion and aphelion radii, but moving into the arctic circle the 272 * difference becomes more noticeable. Tests for Tromso, Norway (latitude 69.672312, longitude 19.049787) show that 273 * on May 17, the rise of the midnight sun, a 2 minute 23 second difference is observed between the perihelion and 274 * aphelion radii using the USNO algorithm, but only 1 minute and 6 seconds difference using the NOAA algorithm. 275 * Areas farther north show an even greater difference. Note that these test are not real valid test cases because 276 * they show the extreme difference on days that are not the perihelion or aphelion, but are shown for illustrative 277 * purposes only. 278 * 279 * @return The sun's radius in arc minutes. 280 */ 281 public double getSolarRadius() { 282 return this.solarRadius; 283 } 284 285 /** 286 * Method to set the sun's radius. 287 * 288 * @param solarRadius 289 * The sun's radius in arc minutes. 290 * @see #getSolarRadius() 291 */ 292 public void setSolarRadius(double solarRadius) { 293 this.solarRadius = solarRadius; 294 } 295 296 /** 297 * @see java.lang.Object#clone() 298 * @since 1.1 299 */ 300 public Object clone() { 301 AstronomicalCalculator clone = null; 302 try { 303 clone = (AstronomicalCalculator) super.clone(); 304 } catch (CloneNotSupportedException cnse) { 305 System.out.print("Required by the compiler. Should never be reached since we implement clone()"); 306 } 307 return clone; 308 } 309}