001 /*
002 * Zmanim Java API
003 * Copyright (C) 2004-2008 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 - 2008
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 // public abstract void setCalculatorName(String calculatorName);
060
061 /**
062 * A method that calculates UTC sunrise as well as any time based on an
063 * angle above or below sunrise. This abstract method is implemented by the
064 * classes that extend this class.
065 *
066 * @param astronomicalCalendar
067 * Used to calculate day of year.
068 * @param zenith
069 * the azimuth below the vertical zenith of 90 degrees. for
070 * sunrise typically the {@link #adjustZenith zenith} used for
071 * the calculation uses geometric zenith of 90° and
072 * {@link #adjustZenith adjusts} this slightly to account for
073 * solar refraction and the sun's radius. Another example would
074 * be {@link AstronomicalCalendar#getBeginNauticalTwilight()}
075 * that passes {@link AstronomicalCalendar#NAUTICAL_ZENITH} to
076 * this method.
077 * @return The UTC time of sunrise in 24 hour format. 5:45:00 AM will return
078 * 5.75.0. If an error was encountered in the calculation (expected
079 * behavior for some locations such as near the poles,
080 * {@link java.lang.Double.NaN} will be returned.
081 */
082 public abstract double getUTCSunrise(
083 AstronomicalCalendar astronomicalCalendar, double zenith,
084 boolean adjustForElevation);
085
086 /**
087 * A method that calculates UTC sunset as well as any time based on an angle
088 * above or below sunset. This abstract method is implemented by the classes
089 * that extend this class.
090 *
091 * @param astronomicalCalendar
092 * Used to calculate day of year.
093 * @param zenith
094 * the azimuth below the vertical zenith of 90°. For sunset
095 * typically the {@link #adjustZenith zenith} used for the
096 * calculation uses geometric zenith of 90° and
097 * {@link #adjustZenith adjusts} this slightly to account for
098 * solar refraction and the sun's radius. Another example would
099 * be {@link AstronomicalCalendar#getEndNauticalTwilight()} that
100 * passes {@link AstronomicalCalendar#NAUTICAL_ZENITH} to this
101 * method.
102 * @return The UTC time of sunset in 24 hour format. 5:45:00 AM will return
103 * 5.75.0. If an error was encountered in the calculation (expected
104 * behavior for some locations such as near the poles,
105 * {@link java.lang.Double.NaN} will be returned.
106 */
107 public abstract double getUTCSunset(
108 AstronomicalCalendar astronomicalCalendar, double zenith,
109 boolean adjustForElevation);
110
111 /**
112 * Method to return the adjustment to the zenith required to account for the
113 * elevation. Since a person at a higher elevation can see farther below the
114 * horizon, the calculation for sunrise / sunset is calculated below the
115 * horizon used at sea level. This is only used for sunrise and sunset and
116 * not times above or below it such as
117 * {@link AstronomicalCalendar#getBeginNauticalTwilight() nautical twilight}
118 * since those calculations are based on the level of available light at the
119 * given dip below the horizon, something that is not affected by elevation,
120 * the adjustment should only made if the zenith == 90°
121 * {@link #adjustZenith adjusted} for refraction and solar radius.<br />
122 * The algorithm used is:
123 *
124 * <pre>
125 * elevationAdjustment = Math.toDegrees(Math.acos(earthRadiusInMeters
126 * / (earthRadiusInMeters + elevationMeters)));
127 * </pre>
128 *
129 * The source of this algorthitm is <a
130 * href="http://www.calendarists.com">Calendrical Calculations</a> by
131 * Edward M. Reingold and Nachum Dershowitz. An alternate algorithm that
132 * produces an almost identical (but not accurate) result found in Ma'aglay
133 * Tzedek by Moishe Kosower and other sources is:
134 *
135 * <pre>
136 * elevationAdjustment = 0.0347 * Math.sqrt(elevationMeters);
137 * </pre>
138 *
139 * @param elevation
140 * elevation in Meters.
141 * @return the adjusted zenith
142 */
143 double getElevationAdjustment(double elevation) {
144 double earthRadius = 6356.9;
145 // double elevationAdjustment = 0.0347 * Math.sqrt(elevation);
146 double elevationAdjustment = Math.toDegrees(Math.acos(earthRadius
147 / (earthRadius + (elevation / 1000))));
148 return elevationAdjustment;
149
150 }
151
152 /**
153 * Adjusts the zenith to account for solar refraction, solar radius and
154 * elevation. The value for Sun's zenith and true rise/set Zenith (used in
155 * this class and subclasses) is the angle that the center of the Sun makes
156 * to a line perpendicular to the Earth's surface. If the Sun were a point
157 * and the Earth were without an atmosphere, true sunset and sunrise would
158 * correspond to a 90° zenith. Because the Sun is not a point, and
159 * because the atmosphere refracts light, this 90° zenith does not, in
160 * fact, correspond to true sunset or sunrise, instead the centre of the
161 * Sun's disk must lie just below the horizon for the upper edge to be
162 * obscured. This means that a zenith of just above 90° must be used.
163 * The Sun subtends an angle of 16 minutes of arc (this can be changed via
164 * the {@link #setSolarRadius(double)} method , and atmospheric refraction
165 * accounts for 34 minutes or so (this can be changed via the
166 * {@link #setRefraction(double)} method), giving a total of 50 arcminutes.
167 * The total value for ZENITH is 90+(5/6) or 90.8333333° for true
168 * sunrise/sunset. Since a person at an elevation can see blow the horizon
169 * of a person at sea level, this will also adjust the zenith to account for
170 * elevation if available.
171 *
172 * @return The zenith adjusted to include the
173 * {@link #getSolarRadius sun's radius},
174 * {@link #getRefraction refraction} and
175 * {@link #getElevationAdjustment elevation} adjustment.
176 */
177 double adjustZenith(double zenith, double elevation) {
178 if (zenith == AstronomicalCalendar.GEOMETRIC_ZENITH) {
179 zenith = zenith
180 + (getSolarRadius() + getRefraction() + getElevationAdjustment(elevation));
181 }
182
183 return zenith;
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 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 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 }