001/*
002 * Zmanim Java API
003 * Copyright (C) 2004-2011 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: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
015 */
016package net.sourceforge.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. TODO: consider methods that would
023 * allow atmospheric modeling. This can currently be adjusted by {@link #setRefraction(double) setting the refraction}.
024 * 
025 * @author © Eliyahu Hershfeld 2004 - 2011
026 * @version 1.1
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        // private double refraction = 34.478885263888294 / 60d;
037
038        /**
039         * The commonly used average solar radius in minutes of a degree.
040         * 
041         * @see #getSolarRadius()
042         */
043        private double solarRadius = 16 / 60d;
044
045        /**
046         * The commonly used average earth radius in KM. At this time, this only affects elevation adjustment and not the
047         * sunrise and sunset calculations. The value currently defaults to 6356.9 KM.
048         * 
049         * @see #getEarthRadius()
050         * @see #setEarthRadius(double)
051         */
052        private double earthRadius = 6356.9; // in KM
053
054        /**
055         * A method that returns the earth radius in KM. The value currently defaults to 6356.9 KM if not set.
056         * 
057         * @return the earthRadius the earth radius in KM.
058         */
059        public double getEarthRadius() {
060                return earthRadius;
061        }
062
063        /**
064         * A method that allows setting the earth's radius.
065         * 
066         * @param earthRadius
067         *            the earthRadius to set in KM
068         */
069        public void setEarthRadius(double earthRadius) {
070                this.earthRadius = earthRadius;
071        }
072
073        /**
074         * The zenith of astronomical sunrise and sunset. The sun is 90° from the vertical 0°
075         */
076        private static final double GEOMETRIC_ZENITH = 90;
077
078        /**
079         * getDefault method returns the default sun times calculation engine.
080         * 
081         * @return AstronomicalCalculator the default class for calculating sunrise and sunset. In the current
082         *         implementation the default calculator returned is the {@link SunTimesCalculator}.
083         */
084        public static AstronomicalCalculator getDefault() {
085                return new SunTimesCalculator();
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 net.sourceforge.zmanim.AstronomicalCalendar#getBeginNauticalTwilight()} that passes
115         *            {@link net.sourceforge.zmanim.AstronomicalCalendar#NAUTICAL_ZENITH} to this method.
116         * @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
117         *         the calculation (expected behavior for some locations such as near the poles,
118         *         {@link java.lang.Double.NaN} will be returned.
119         * @see #getElevationAdjustment(double)
120         */
121        public abstract double getUTCSunrise(Calendar calendar, GeoLocation geoLocation, double zenith,
122                        boolean adjustForElevation);
123
124        /**
125         * A method that calculates UTC sunset as well as any time based on an angle above or below sunset. This abstract
126         * method is implemented by the classes that extend this class.
127         * 
128         * @param calendar
129         *            Used to calculate day of year.
130         * @param geoLocation
131         *            The location information used for astronomical calculating sun times.
132         * @param zenith
133         *            the azimuth below the vertical zenith of 90°. For sunset typically the {@link #adjustZenith
134         *            zenith} used for the calculation uses geometric zenith of 90° and {@link #adjustZenith adjusts}
135         *            this slightly to account for solar refraction and the sun's radius. Another example would be
136         *            {@link net.sourceforge.zmanim.AstronomicalCalendar#getEndNauticalTwilight()} that passes
137         *            {@link net.sourceforge.zmanim.AstronomicalCalendar#NAUTICAL_ZENITH} to this method.
138         * @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
139         *         the calculation (expected behavior for some locations such as near the poles,
140         *         {@link java.lang.Double.NaN} will be returned.
141         * @see #getElevationAdjustment(double)
142         */
143        public abstract double getUTCSunset(Calendar calendar, GeoLocation geoLocation, double zenith,
144                        boolean adjustForElevation);
145
146        /**
147         * Method to return the adjustment to the zenith required to account for the elevation. Since a person at a higher
148         * elevation can see farther below the horizon, the calculation for sunrise / sunset is calculated below the horizon
149         * used at sea level. This is only used for sunrise and sunset and not times before or after it such as
150         * {@link net.sourceforge.zmanim.AstronomicalCalendar#getBeginNauticalTwilight() nautical twilight} since those
151         * calculations are based on the level of available light at the given dip below the horizon, something that is not
152         * affected by elevation, the adjustment should only made if the zenith == 90° {@link #adjustZenith adjusted}
153         * for refraction and solar radius.<br />
154         * The algorithm used is:
155         * 
156         * <pre>
157         * elevationAdjustment = Math.toDegrees(Math.acos(earthRadiusInMeters / (earthRadiusInMeters + elevationMeters)));
158         * </pre>
159         * 
160         * The source of this algorthitm is <a href="http://www.calendarists.com">Calendrical Calculations</a> by Edward M.
161         * Reingold and Nachum Dershowitz. An alternate algorithm that produces an almost identical (but not accurate)
162         * result found in Ma'aglay Tzedek by Moishe Kosower and other sources is:
163         * 
164         * <pre>
165         * elevationAdjustment = 0.0347 * Math.sqrt(elevationMeters);
166         * </pre>
167         * 
168         * @param elevation
169         *            elevation in Meters.
170         * @return the adjusted zenith
171         */
172        double getElevationAdjustment(double elevation) {
173                // double elevationAdjustment = 0.0347 * Math.sqrt(elevation);
174                double elevationAdjustment = Math.toDegrees(Math.acos(earthRadius / (earthRadius + (elevation / 1000))));
175                return elevationAdjustment;
176
177        }
178
179        /**
180         * Adjusts the zenith of astronomical sunrise and sunset to account for solar refraction, solar radius and
181         * elevation. The value for Sun's zenith and true rise/set Zenith (used in this class and subclasses) is the angle
182         * that the center of the Sun makes to a line perpendicular to the Earth's surface. If the Sun were a point and the
183         * Earth were without an atmosphere, true sunset and sunrise would correspond to a 90&deg; zenith. Because the Sun
184         * is not a point, and because the atmosphere refracts light, this 90&deg; zenith does not, in fact, correspond to
185         * true sunset or sunrise, instead the centre of the Sun's disk must lie just below the horizon for the upper edge
186         * to be obscured. This means that a zenith of just above 90&deg; must be used. The Sun subtends an angle of 16
187         * minutes of arc (this can be changed via the {@link #setSolarRadius(double)} method , and atmospheric refraction
188         * accounts for 34 minutes or so (this can be changed via the {@link #setRefraction(double)} method), giving a total
189         * of 50 arcminutes. The total value for ZENITH is 90+(5/6) or 90.8333333&deg; for true sunrise/sunset. Since a
190         * person at an elevation can see blow the horizon of a person at sea level, this will also adjust the zenith to
191         * account for elevation if available.
192         * 
193         * @return The zenith adjusted to include the {@link #getSolarRadius sun's radius}, {@link #getRefraction
194         *         refraction} and {@link #getElevationAdjustment elevation} adjustment. This will only be adjusted for
195         *         sunrise and sunset (if the zenith == 90&deg;)
196         * @see #getElevationAdjustment(double)
197         */
198        double adjustZenith(double zenith, double elevation) {
199                double adjustedZenith = zenith;
200                if (zenith == GEOMETRIC_ZENITH) { // only adjust if it is exactly sunrise or sunset
201                        adjustedZenith = zenith + (getSolarRadius() + getRefraction() + getElevationAdjustment(elevation));
202                }
203                return adjustedZenith;
204        }
205
206        /**
207         * Method to get the refraction value to be used when calculating sunrise and sunset. The default value is 34 arc
208         * minutes. The <a href="http://emr.cs.iit.edu/home/reingold/calendar-book/second-edition/errata.pdf">Errata and
209         * Notes for Calendrical Calculations: The Millenium Eddition</a> by Edward M. Reingold and Nachum Dershowitz lists
210         * the actual average refraction value as 34.478885263888294 or approximately 34' 29". The refraction value as well
211         * as the solarRadius and elevation adjustment are added to the zenith used to calculate sunrise and sunset.
212         * 
213         * @return The refraction in arc minutes.
214         */
215        double getRefraction() {
216                return this.refraction;
217        }
218
219        /**
220         * A method to allow overriding the default refraction of the calculator. TODO: At some point in the future, an
221         * AtmosphericModel or Refraction object that models the atmosphere of different locations might be used for
222         * increased accuracy.
223         * 
224         * @param refraction
225         *            The refraction in arc minutes.
226         * @see #getRefraction()
227         */
228        public void setRefraction(double refraction) {
229                this.refraction = refraction;
230        }
231
232        /**
233         * Method to get the sun's radius. The default value is 16 arc minutes. The sun's radius as it appears from earth is
234         * almost universally given as 16 arc minutes but in fact it differs by the time of the year. At the <a
235         * href="http://en.wikipedia.org/wiki/Perihelion">perihelion</a> it has an apparent radius of 16.293, while at the
236         * <a href="http://en.wikipedia.org/wiki/Aphelion">aphelion</a> it has an apparent radius of 15.755. There is little
237         * affect for most location, but at high and low latitudes the difference becomes more apparent. My Calculations for
238         * the difference at the location of the <a href="http://www.rog.nmm.ac.uk">Royal Observatory, Greenwich </a> show
239         * only a 4.494 second difference between the perihelion and aphelion radii, but moving into the arctic circle the
240         * difference becomes more noticeable. Tests for Tromso, Norway (latitude 69.672312, longitude 19.049787) show that
241         * on May 17, the rise of the midnight sun, a 2 minute 23 second difference is observed between the perihelion and
242         * aphelion radii using the USNO algorithm, but only 1 minute and 6 seconds difference using the NOAA algorithm.
243         * Areas farther north show an even greater difference. Note that these test are not real valid test cases because
244         * they show the extreme difference on days that are not the perihelion or aphelion, but are shown for illustrative
245         * purposes only.
246         * 
247         * @return The sun's radius in arc minutes.
248         */
249        double getSolarRadius() {
250                return this.solarRadius;
251        }
252
253        /**
254         * Method to set the sun's radius.
255         * 
256         * @param solarRadius
257         *            The sun's radius in arc minutes.
258         * @see #getSolarRadius()
259         */
260        public void setSolarRadius(double solarRadius) {
261                this.solarRadius = solarRadius;
262        }
263
264        /**
265         * @see java.lang.Object#clone()
266         * @since 1.1
267         */
268        public Object clone() {
269                AstronomicalCalculator clone = null;
270                try {
271                        clone = (AstronomicalCalculator) super.clone();
272                } catch (CloneNotSupportedException cnse) {
273                        System.out.print("Required by the compiler. Should never be reached since we implement clone()");
274                }
275                return clone;
276        }
277}