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&deg; zenith. Because the Sun is not a point, and
159             * because the atmosphere refracts light, this 90&deg; 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&deg; 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&deg; 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    }