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