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 }