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