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° zenith. Because the Sun 184 * is not a point, and because the atmosphere refracts light, this 90° 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° 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° 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°) 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}