001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas/ 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see https://www.gnu.org/licenses/. 027 */ 028package edu.wisc.ssec.mcidasv.util; 029 030// J2SE dependencies 031import java.awt.geom.Point2D; 032import java.text.DateFormat; 033import java.text.ParseException; 034import java.util.Date; 035import java.util.TimeZone; 036import visad.Data; 037import visad.FlatField; 038 039 040/** 041 * Calcule la position du soleil relativement à la position de l'observateur. 042 * Cette classe reçoit en entrés les coordonnées spatio-temporelles de 043 * l'observateur, soit: 044 * 045 * <TABLE border='0'><TR><TD valign="top"> 046 * <BR> 047 * <UL> 048 * <LI>La longitude (en degrées) de l'observateur;</LI> 049 * <LI>La latitude (en degrées) de l'observateur;</LI> 050 * <LI>La date et heure en heure universelle (GMT).</LI> 051 * </UL> 052 * 053 * La position du soleil calculée en sortie comprend les valeurs suivantes: 054 * 055 * <UL> 056 * <LI>L'azimuth du soleil (en degrés dans le sens des aiguilles d'une montre depuis le nord);</LI> 057 * <LI>L'élévation du soleil (en degrés par rapport a l'horizon).</LI> 058 * </UL> 059 * </TD> 060 * 061 * <TD><img src="doc-files/CelestialSphere.png"></TD> 062 * </TR></TABLE> 063 * 064 * Les algorithmes utilisés dans cette classe sont des adaptations des algorithmes 065 * en javascript écrit par le "National Oceanic and Atmospheric Administration, 066 * Surface Radiation Research Branch". L'application original est le 067 * 068 * <a href="http://www.srrb.noaa.gov/highlights/sunrise/azel.html">Solar Position Calculator</a>. 069 * 070 * <p> 071 * The approximations used in these programs are very good for years between 072 * 1800 and 2100. Results should still be sufficiently accurate for the range 073 * from -1000 to 3000. Outside of this range, results will be given, but the 074 * potential for error is higher. 075 * 076 * @since 2.1 077 * 078 * 079 * @source $URL$ 080 * @version $Id$ 081 * @author Remi Eve 082 * @author Martin Desruisseaux (IRD) 083 */ 084public class SunRelativePosition { 085 /** 086 * Number of milliseconds in a day. 087 */ 088 private static final int DAY_MILLIS = 24*60*60*1000; 089 090 /** 091 * Valeur affectée lorsque un resultat n'est pas calculable du 092 * fait de la nuit. Cette valeur concerne les valeurs de sorties 093 * {@link #elevation} et {@link #azimuth}. 094 */ 095 private static final double DARK = Double.NaN; 096 097 /** 098 * {@linkplain #getElevation Elevation angle} of astronomical twilight, in degrees. 099 * Astronomical twilight is the time of morning or evening when the sun is 18° below 100 * the horizon (solar elevation angle of -18°). 101 */ 102 public static final double ASTRONOMICAL_TWILIGHT = -18; 103 104 /** 105 * {@linkplain #getElevation Elevation angle} of nautical twilight, in degrees. 106 * Nautical twilight is the time of morning or evening when the sun is 12° below 107 * the horizon (solar elevation angle of -12°). 108 */ 109 public static final double NAUTICAL_TWILIGHT = -12; 110 111 /** 112 * {@linkplain #getElevation Elevation angle} of civil twilight, in degrees. Civil 113 * twilight is the time of morning or evening when the sun is 6° below the horizon 114 * (solar elevation angle of -6°). 115 */ 116 public static final double CIVIL_TWILIGHT = -6; 117 118 /** 119 * Sun's {@linkplain #getElevation elevation angle} at twilight, in degrees. 120 * Common values are defined for the 121 * {@linkplain #ASTRONOMICAL_TWILIGHT astronomical twilight} (-18°), 122 * {@linkplain #NAUTICAL_TWILIGHT nautical twilight} (-12°) and 123 * {@linkplain #CIVIL_TWILIGHT civil twilight} (-6°). 124 * If no twilight are defined, then this value is {@linkplain Double#NaN NaN}. 125 * The {@linkplain #getElevation elevation} and {@linkplain #getAzimuth azimuth} are 126 * set to {@linkplain Double#NaN NaN} when the sun elevation is below the twilight 127 * value (i.e. during night). The default value is {@link #CIVIL_TWILIGHT}. 128 */ 129 private double twilight = CIVIL_TWILIGHT; 130 131 /** 132 * Heure à laquelle le soleil est au plus haut dans la journée en millisecondes 133 * écoulées depuis le 1er janvier 1970. 134 */ 135 private long noonTime; 136 137 /** 138 * Azimuth du soleil, en degrés dans le sens des 139 * aiguilles d'une montre depuis le nord. 140 */ 141 private double azimuth; 142 143 /** 144 * Elévation du soleil, en degrés par rapport a l'horizon. 145 */ 146 private double elevation; 147 148 /** 149 * Geographic coordinate where current elevation and azimuth were computed. 150 * Value are in degrees of longitude or latitude. 151 */ 152 private double latitude, longitude; 153 154 /** 155 * Date and time when the current elevation and azimuth were computed. 156 * Value is in milliseconds ellapsed since midnight UTC, January 1st, 1970. 157 */ 158 private long time = System.currentTimeMillis(); 159 160 private double julianDay = Double.NaN; 161 162 private double jddate; 163 164 double solarDec; 165 double eqTime; 166 167 private boolean timeUpdated = false; 168 169 /** 170 * {@code true} is the elevation and azimuth are computed, or {@code false} 171 * if they need to be computed. This flag is set to {@code false} when the date 172 * and/or the coordinate change. 173 */ 174 private boolean updated; 175 176 /** 177 * Calculate the equation of center for the sun. This value is a correction 178 * to add to the geometric mean longitude in order to get the "true" longitude 179 * of the sun. 180 * 181 * @param t number of Julian centuries since J2000. 182 * @return Equation of center in degrees. 183 */ 184 private static double sunEquationOfCenter(final double t) { 185 final double m = Math.toRadians(sunGeometricMeanAnomaly(t)); 186 return Math.sin(1*m) * (1.914602 - t*(0.004817 + 0.000014*t)) + 187 Math.sin(2*m) * (0.019993 - t*(0.000101 )) + 188 Math.sin(3*m) * (0.000289); 189 } 190 191 /** 192 * Calculate the Geometric Mean Longitude of the Sun. 193 * This value is close to 0° at the spring equinox, 194 * 90° at the summer solstice, 180° at the automne equinox 195 * and 270° at the winter solstice. 196 * 197 * @param t number of Julian centuries since J2000. 198 * @return Geometric Mean Longitude of the Sun in degrees, 199 * in the range 0° (inclusive) to 360° (exclusive). 200 */ 201 private static double sunGeometricMeanLongitude(final double t) { 202 double L0 = 280.46646 + t*(36000.76983 + 0.0003032*t); 203 L0 = L0 - 360*Math.floor(L0/360); 204 return L0; 205 } 206 207 /** 208 * Calculate the true longitude of the sun. This the geometric mean 209 * longitude plus a correction factor ("equation of center" for the 210 * sun). 211 * 212 * @param t number of Julian centuries since J2000. 213 * @return Sun's true longitude in degrees. 214 */ 215 private static double sunTrueLongitude(final double t) { 216 return sunGeometricMeanLongitude(t) + sunEquationOfCenter(t); 217 } 218 219 /** 220 * Calculate the apparent longitude of the sun. 221 * 222 * @param t number of Julian centuries since J2000. 223 * @return Sun's apparent longitude in degrees. 224 */ 225 private static double sunApparentLongitude(final double t) { 226 final double omega = Math.toRadians(125.04 - 1934.136 * t); 227 return sunTrueLongitude(t) - 0.00569 - 0.00478 * Math.sin(omega); 228 } 229 230 /** 231 * Calculate the Geometric Mean Anomaly of the Sun. 232 * 233 * @param t number of Julian centuries since J2000. 234 * @return Geometric Mean Anomaly of the Sun in degrees. 235 */ 236 private static double sunGeometricMeanAnomaly(final double t) { 237 return 357.52911 + t * (35999.05029 - 0.0001537*t); 238 } 239 240 /** 241 * Calculate the true anamoly of the sun. 242 * 243 * @param t number of Julian centuries since J2000. 244 * @return Sun's true anamoly in degrees. 245 */ 246 private static double sunTrueAnomaly(final double t) { 247 return sunGeometricMeanAnomaly(t) + sunEquationOfCenter(t); 248 } 249 250 /** 251 * Calculate the eccentricity of earth's orbit. This is the ratio 252 * {@code (a-b)/a} where <var>a</var> is the semi-major axis 253 * length and <var>b</var> is the semi-minor axis length. Value 254 * is 0 for a circular orbit. 255 * 256 * @param t number of Julian centuries since J2000. 257 * @return The unitless eccentricity. 258 */ 259 private static double eccentricityEarthOrbit(final double t) { 260 return 0.016708634 - t*(0.000042037 + 0.0000001267*t); 261 } 262 263 /** 264 * Calculate the distance to the sun in Astronomical Units (AU). 265 * 266 * @param t number of Julian centuries since J2000. 267 * @return Sun radius vector in AUs. 268 */ 269 private static double sunRadiusVector(final double t) { 270 final double v = Math.toRadians(sunTrueAnomaly(t)); 271 final double e = eccentricityEarthOrbit(t); 272 return (1.000001018 * (1 - e*e)) / (1 + e*Math.cos(v)); 273 } 274 275 /** 276 * Calculate the mean obliquity of the ecliptic. 277 * 278 * @param t number of Julian centuries since J2000. 279 * @return Mean obliquity in degrees. 280 */ 281 private static double meanObliquityOfEcliptic(final double t) { 282 final double seconds = 21.448 - t*(46.8150 + t*(0.00059 - t*(0.001813))); 283 return 23.0 + (26.0 + (seconds/60.0))/60.0; 284 } 285 286 287 /** 288 * Calculate the corrected obliquity of the ecliptic. 289 * 290 * @param t number of Julian centuries since J2000. 291 * @return Corrected obliquity in degrees. 292 */ 293 private static double obliquityCorrected(final double t) { 294 final double e0 = meanObliquityOfEcliptic(t); 295 final double omega = Math.toRadians(125.04 - 1934.136*t); 296 return e0 + 0.00256 * Math.cos(omega); 297 } 298 299 /** 300 * Calculate the right ascension of the sun. Similar to the angular system 301 * used to define latitude and longitude on Earth's surface, right ascension 302 * is roughly analogous to longitude, and defines an angular offset from the 303 * meridian of the vernal equinox. 304 * 305 * <P align="center"><img src="doc-files/CelestialSphere.png"></P> 306 * 307 * @param t number of Julian centuries since J2000. 308 * @return Sun's right ascension in degrees. 309 */ 310 private static double sunRightAscension(final double t) { 311 final double e = Math.toRadians(obliquityCorrected(t)); 312 final double b = Math.toRadians(sunApparentLongitude(t)); 313 final double y = Math.sin(b) * Math.cos(e); 314 final double x = Math.cos(b); 315 final double alpha = Math.atan2(y, x); 316 return Math.toDegrees(alpha); 317 } 318 319 /** 320 * Calculate the declination of the sun. Declination is analogous to latitude 321 * on Earth's surface, and measures an angular displacement north or south 322 * from the projection of Earth's equator on the celestial sphere to the 323 * location of a celestial body. 324 * 325 * @param t number of Julian centuries since J2000. 326 * @return Sun's declination in degrees. 327 */ 328 private static double sunDeclination(final double t) { 329 final double e = Math.toRadians(obliquityCorrected(t)); 330 final double b = Math.toRadians(sunApparentLongitude(t)); 331 final double sint = Math.sin(e) * Math.sin(b); 332 final double theta = Math.asin(sint); 333 return Math.toDegrees(theta); 334 } 335 336 /** 337 * Calculate the Universal Coordinated Time (UTC) of solar noon for the given day 338 * at the given location on earth. 339 * 340 * @param lon longitude of observer in degrees. 341 * @param eqTime Equation of time. 342 * @return Time in minutes from beginnning of day in UTC. 343 */ 344 private static double solarNoonTime(final double lon, final double eqTime) { 345 return 720.0 + (lon * 4.0) - eqTime; 346 } 347 348 /** 349 * Calculate the difference between true solar time and mean. The "equation 350 * of time" is a term accounting for changes in the time of solar noon for 351 * a given location over the course of a year. Earth's elliptical orbit and 352 * Kepler's law of equal areas in equal times are the culprits behind this 353 * phenomenon. See the 354 * <A HREF="http://www.analemma.com/Pages/framesPage.html">Analemma page</A>. 355 * Below is a plot of the equation of time versus the day of the year. 356 * 357 * <P align="center"><img src="doc-files/EquationOfTime.png"></P> 358 * 359 * @param t number of Julian centuries since J2000. 360 * @return Equation of time in minutes of time. 361 */ 362 private static double equationOfTime(final double t) { 363 double eps = Math.toRadians(obliquityCorrected(t)); 364 double l0 = Math.toRadians(sunGeometricMeanLongitude(t)); 365 double m = Math.toRadians(sunGeometricMeanAnomaly(t)); 366 double e = eccentricityEarthOrbit(t); 367 double y = Math.tan(eps/2); 368 y *= y; 369 370 double sin2l0 = Math.sin(2 * l0); 371 double cos2l0 = Math.cos(2 * l0); 372 double sin4l0 = Math.sin(4 * l0); 373 double sin1m = Math.sin(m); 374 double sin2m = Math.sin(2 * m); 375 376 double etime = y*sin2l0 - 2*e*sin1m + 4*e*y*sin1m*cos2l0 377 - 0.5*y*y*sin4l0 - 1.25*e*e*sin2m; 378 379 return Math.toDegrees(etime)*4.0; 380 } 381 382 /** 383 * Computes the refraction correction angle. 384 * The effects of the atmosphere vary with atmospheric pressure, humidity 385 * and other variables. Therefore the calculation is approximate. Errors 386 * can be expected to increase the further away you are from the equator, 387 * because the sun rises and sets at a very shallow angle. Small variations 388 * in the atmosphere can have a larger effect. 389 * 390 * @param zenith The sun zenith angle in degrees. 391 * @return The refraction correction in degrees. 392 */ 393 private static double refractionCorrection(final double zenith) { 394 final double exoatmElevation = 90 - zenith; 395 if (exoatmElevation > 85) { 396 return 0; 397 } 398 final double refractionCorrection; // In minute of degrees 399 final double te = Math.tan(Math.toRadians(exoatmElevation)); 400 if (exoatmElevation > 5.0) { 401 refractionCorrection = 58.1/te - 0.07/(te*te*te) + 0.000086/(te*te*te*te*te); 402 } else { 403 if (exoatmElevation > -0.575) { 404 refractionCorrection = 1735.0 + exoatmElevation * 405 (-518.2 + exoatmElevation * 406 ( 103.4 + exoatmElevation * 407 (-12.79 + exoatmElevation * 408 0.711))); 409 } else { 410 refractionCorrection = -20.774 / te; 411 } 412 } 413 return refractionCorrection / 3600; 414 } 415 416 /** 417 * Constructs a sun relative position calculator. 418 */ 419 public SunRelativePosition() { 420 } 421 422 /** 423 * Constructs a sun relative position calculator with the specified value 424 * for the {@linkplain #setTwilight sun elevation at twilight}. 425 * 426 * @param twilight The new sun elevation at twilight, or {@link Double#NaN} 427 * if no twilight value should be taken in account. 428 * @throws IllegalArgumentException if the twilight value is illegal. 429 */ 430 public SunRelativePosition(final double twilight) throws IllegalArgumentException { 431 setTwilight(twilight); 432 } 433 434 /** 435 * Calculates solar position for the current date, time and location. 436 * Results are reported in azimuth and elevation in degrees. 437 */ 438 private void compute() { 439 double latitude = this.latitude; 440 double longitude = this.longitude; 441 442 // NOAA convention use positive longitude west, and negative east. 443 // Inverse the sign, in order to be closer to OpenGIS convention. 444 longitude = -longitude; 445 446 // Compute: 1) Julian day (days ellapsed since January 1, 4723 BC at 12:00 GMT). 447 // 2) Time as the centuries ellapsed since January 1, 2000 at 12:00 GMT. 448 449 if (!timeUpdated) { 450 julianDay = Calendar.julianDay(this.time); 451 final double time = (julianDay-2451545)/36525; 452 453 solarDec = sunDeclination(time); 454 eqTime = equationOfTime(time); 455 timeUpdated = true; 456 } 457 458 459 this.noonTime = Math.round(solarNoonTime(longitude, eqTime) * (60*1000)) + 460 (this.time/DAY_MILLIS)*DAY_MILLIS; 461 462 // Formula below use longitude in degrees. Steps are: 463 // 1) Extract the time part of the date, in minutes. 464 // 2) Apply a correction for longitude and equation of time. 465 // 3) Clamp in a 24 hours range (24 hours == 1440 minutes). 466 double trueSolarTime = ((julianDay+0.5) - Math.floor(julianDay+0.5)) * 1440; 467 trueSolarTime += (eqTime - 4.0*longitude); // Correction in minutes. 468 trueSolarTime -= 1440*Math.floor(trueSolarTime/1440); 469 470 // Convert all angles to radians. From this point until 471 // the end of this method, local variables are always in 472 // radians. Output variables ('azimuth' and 'elevation') 473 // will still computed in degrees. 474 longitude = Math.toRadians(longitude); 475 latitude = Math.toRadians(latitude ); 476 solarDec = Math.toRadians(solarDec ); 477 478 double csz = Math.sin(latitude) * 479 Math.sin(solarDec) + 480 Math.cos(latitude) * 481 Math.cos(solarDec) * 482 Math.cos(Math.toRadians(trueSolarTime/4 - 180)); 483 if (csz > +1) csz = +1; 484 if (csz < -1) csz = -1; 485 486 final double zenith = Math.acos(csz); 487 final double azDenom = Math.cos(latitude) * Math.sin(zenith); 488 489 ////////////////////////////////////////// 490 //// Compute azimuth in degrees //// 491 ////////////////////////////////////////// 492 if (Math.abs(azDenom) > 0.001) { 493 double azRad = ((Math.sin(latitude)*Math.cos(zenith)) - Math.sin(solarDec)) / azDenom; 494 if (azRad > +1) azRad = +1; 495 if (azRad < -1) azRad = -1; 496 497 azimuth = 180 - Math.toDegrees(Math.acos(azRad)); 498 if (trueSolarTime > 720) { // 720 minutes == 12 hours 499 azimuth = -azimuth; 500 } 501 } else { 502 azimuth = (latitude>0) ? 180 : 0; 503 } 504 azimuth -= 360*Math.floor(azimuth/360); 505 506 //////////////////////////////////////////// 507 //// Compute elevation in degrees //// 508 //////////////////////////////////////////// 509 final double refractionCorrection = refractionCorrection(Math.toDegrees(zenith)); 510 final double solarZen = Math.toDegrees(zenith) - refractionCorrection; 511 512 elevation = 90 - solarZen; 513 if (elevation < twilight) { 514 // do not report azimuth & elevation after twilight 515 azimuth = DARK; 516 elevation = DARK; 517 } 518 updated = true; 519 } 520 521 /** 522 * Set the geographic coordinate where to compute the {@linkplain #getElevation elevation} 523 * and {@linkplain #getAzimuth azimuth}. 524 * 525 * @param longitude The longitude in degrees. Positive values are East; negative values are West. 526 * @param latitude The latitude in degrees. Positive values are North, negative values are South. 527 */ 528 public void setCoordinate(double longitude, double latitude) { 529 if (latitude > +89.8) latitude = +89.8; 530 if (latitude < -89.8) latitude = -89.8; 531 if (latitude != this.latitude || longitude != this.longitude) { 532 this.latitude = latitude; 533 this.longitude = longitude; 534 this.updated = false; 535 } 536 } 537 538 /** 539 * Set the geographic coordinate where to compute the {@linkplain #getElevation elevation} 540 * and {@linkplain #getAzimuth azimuth}. 541 * 542 * @param point The geographic coordinates in degrees of longitude and latitude. 543 */ 544 public void setCoordinate(final Point2D point) { 545 setCoordinate(point.getX(), point.getY()); 546 } 547 548 /** 549 * Returns the coordinate used for {@linkplain #getElevation elevation} and 550 * {@linkplain #getAzimuth azimuth} computation. This is the coordinate 551 * specified during the last call to a {@link #setCoordinate(double,double) 552 * setCoordinate(...)} method. 553 */ 554 public Point2D getCoordinate() { 555 return new Point2D.Double(longitude, latitude); 556 } 557 558 /** 559 * Set the date and time when to compute the {@linkplain #getElevation elevation} 560 * and {@linkplain #getAzimuth azimuth}. 561 * 562 * @param date The date and time. 563 */ 564 public void setDate(final Date date) { 565 final long time = date.getTime(); 566 if (time != this.time) { 567 this.time = time; 568 this.updated = false; 569 this.timeUpdated = false; 570 } 571 } 572 573 /** 574 * Returns the date used for {@linkplain #getElevation elevation} and 575 * {@linkplain #getAzimuth azimuth} computation. This is the date specified 576 * during the last call to {@link #setDate}. 577 */ 578 public Date getDate() { 579 return new Date(time); 580 } 581 582 /** 583 * Set the sun's {@linkplain #getElevation elevation angle} at twilight, in degrees. 584 * Common values are defined for the 585 * {@linkplain #ASTRONOMICAL_TWILIGHT astronomical twilight} (-18°), 586 * {@linkplain #NAUTICAL_TWILIGHT nautical twilight} (-12°) and 587 * {@linkplain #CIVIL_TWILIGHT civil twilight} (-6°). 588 * The {@linkplain #getElevation elevation} and {@linkplain #getAzimuth azimuth} are 589 * set to {@linkplain Double#NaN NaN} when the sun elevation is below the twilight 590 * value (i.e. during night). The default value is {@link #CIVIL_TWILIGHT}. 591 * 592 * @param twilight The new sun elevation at twilight, or {@link Double#NaN} 593 * if no twilight value should be taken in account. 594 * @throws IllegalArgumentException if the twilight value is illegal. 595 */ 596 public void setTwilight(final double twilight) throws IllegalArgumentException { 597 if (twilight<-90 || twilight>-90) { 598 // TODO: provides a better (localized) message. 599 throw new IllegalArgumentException(String.valueOf(twilight)); 600 } 601 this.twilight = twilight; 602 this.updated = false; 603 } 604 605 /** 606 * Returns the sun's {@linkplain #getElevation elevation angle} at twilight, in degrees. 607 * This is the value set during the last call to {@link #setTwilight}. 608 */ 609 public double getTwilight() { 610 return twilight; 611 } 612 613 /** 614 * Retourne l'azimuth en degrés. 615 * 616 * @return L'azimuth en degrés. 617 */ 618 public double getAzimuth() { 619 if (!updated) { 620 compute(); 621 } 622 return azimuth; 623 } 624 625 /** 626 * Retourne l'élévation en degrés. 627 * 628 * @return L'élévation en degrés. 629 */ 630 public double getElevation() { 631 if (!updated) { 632 compute(); 633 } 634 return elevation; 635 } 636 637 /** 638 * 639 * @return solar zenith angle in degrees 640 */ 641 public double getSolarZenith() { 642 if (!updated) { 643 compute(); 644 } 645 return 90.0 - elevation; 646 } 647 648 /** 649 * Retourne l'heure à laquelle le soleil est au plus haut. L'heure est 650 * retournée en nombre de millisecondes écoulées depuis le debut de la 651 * journée (minuit) en heure UTC. 652 */ 653 public long getNoonTime() { 654 if (!updated) { 655 compute(); 656 } 657 return noonTime % DAY_MILLIS; 658 } 659 660 /** 661 * Retourne la date à laquelle le soleil est au plus haut dans la journée. 662 * Cette méthode est équivalente à {@link #getNoonTime} mais inclue le jour 663 * de la date qui avait été spécifiée à la méthode {@link #compute}. 664 */ 665 public Date getNoonDate() { 666 if (!updated) { 667 compute(); 668 } 669 return new Date(noonTime); 670 } 671 672 /** 673 * @param longitudes float[] of degrees 674 * @param latitudes float[] of degrees 675 * @param dateTime java.util.Date 676 * @param output FlatField of which computed angles (degrees) will be written into the range 677 * @throws Exception 678 */ 679 public static void getCosSolarZenith(float[] longitudes, float[] latitudes, Date dateTime, FlatField output) throws Exception { 680 float[][] values = output.getFloats(false); 681 getSolarZenith(longitudes, latitudes, dateTime, values[0]); 682 for (int i=0; i<values[0].length; i++) { 683 values[0][i] = (float) Math.cos(values[0][i]*Data.DEGREES_TO_RADIANS); 684 } 685 } 686 687 /** 688 * @param longitudes float[] of degrees 689 * @param latitudes float[] of degrees 690 * @param dateTime java.util.Date 691 * @param output FlatField of which computed angles (degrees) will be written into the range 692 * @throws Exception 693 */ 694 public static void getSolarZenith(float[] longitudes, float[] latitudes, Date dateTime, FlatField output) throws Exception { 695 float[][] values = output.getFloats(false); 696 getSolarZenith(longitudes, latitudes, dateTime, values[0]); 697 } 698 699 /** 700 * @param longitudes float[] of degrees 701 * @param latitudes float[] of degrees 702 * @param dateTime java.util.Date 703 * @param solzen float[] of computed solar zenith angle (degrees). If null, array will be allocated. 704 * @return the compute values 705 * @throws Exception 706 */ 707 public static float[] getSolarZenith(float[] longitudes, float[] latitudes, Date dateTime, float[] solzen) throws Exception { 708 SunRelativePosition calculator = new SunRelativePosition(); 709 710 int numPts = longitudes.length; 711 if (numPts != latitudes.length) { 712 throw new Exception("number of longitudes and latitudes must match"); 713 } 714 715 if (solzen == null) { 716 solzen = new float[numPts]; 717 } 718 719 calculator.setDate(dateTime); 720 for (int k=0; k<numPts; k++) { 721 calculator.setCoordinate(longitudes[k], latitudes[k]); 722 solzen[k] = (float) calculator.getSolarZenith(); 723 } 724 725 return solzen; 726 } 727 728 /** 729 * Affiche la position du soleil à la date et coordonnées spécifiée. 730 * Cette application peut être lancée avec la syntaxe suivante: 731 * 732 * <pre>SunRelativePosition <var>[longitude]</var> <var>[latitude]</var> <var>[date]</var></pre> 733 * 734 * où <var>date</var> est un argument optionel spécifiant la date et l'heure. 735 * Si cet argument est omis, la date et heure actuelles seront utilisées. 736 */ 737 public static void main(final String[] args) throws ParseException { 738 final DateFormat format=DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); 739 format.setTimeZone(TimeZone.getTimeZone("UTC")); 740 double longitude = 0; 741 double latitude = 0; 742 Date time = new Date(); 743 switch (args.length) { 744 case 3: time = format.parse (args[2]); // fall through 745 case 2: latitude = Double.parseDouble(args[1]); // fall through 746 case 1: longitude = Double.parseDouble(args[0]); // fall through 747 } 748 final SunRelativePosition calculator = new SunRelativePosition(); 749 calculator.setDate(time); 750 calculator.setCoordinate(longitude, latitude); 751 System.out.print("Date (UTC): "); System.out.println(format.format(time)); 752 System.out.print("Longitude: "); System.out.println(longitude); 753 System.out.print("Latitude: "); System.out.println(latitude); 754 System.out.print("Elevation: "); System.out.println(calculator.getElevation()); 755 System.out.print("Azimuth: "); System.out.println(calculator.getAzimuth()); 756 System.out.print("Noon date: "); System.out.println(format.format(calculator.getNoonDate())); 757 } 758}