1 2===================== 3 Almanac Computation 4===================== 5 6The highest-level routines in Skyfield let you search back and forward 7through time for the exact moments when the Earth, Sun, and Moon are in 8special configurations. 9 10They all require you to start by loading up a timescale object and also 11an ephemeris file that provides positions from the planets: 12 13.. testcode:: 14 15 from skyfield import api 16 17 ts = api.load.timescale() 18 eph = api.load('de421.bsp') 19 20Then, load the “almanac” module. 21 22.. testcode:: 23 24 from skyfield import almanac 25 26Note that almanac computation can be slow and expensive. To determine 27the moment of sunrise, for example, Skyfield has to search back and 28forth through time asking for the altitude of the Sun over and over 29until it finally works out the moment at which it crests the horizon. 30 31Rounding time to the nearest minute 32=================================== 33 34If you compare almanac results to official sources like the `United 35States Naval Observatory <http://aa.usno.navy.mil/data/index.php>`_, the 36printed time will often differ because the Naval Observatory results are 37rounded to the nearest minute — any time with ``:30`` or more seconds at 38the end gets named as the next minute. 39 40If you try to display a date that needs to be rounded to the nearest 41minute by simply stopping at ``%M`` and leaving off the ``%S`` seconds, 42the output will be one minute too early. For example, the Naval 43Observatory would round ``14:59`` up to ``:15`` in the following date. 44 45.. testcode:: 46 47 t = ts.utc(2018, 9, 10, 5, 14, 59) 48 dt = t.utc_datetime() 49 print(dt.strftime('%Y-%m-%d %H:%M')) 50 51.. testoutput:: 52 53 2018-09-10 05:14 54 55To do the same rounding yourself, simply add 30 seconds to the time 56before truncating the seconds. 57 58.. testcode:: 59 60 from datetime import timedelta 61 62 def nearest_minute(dt): 63 return (dt + timedelta(seconds=30)).replace(second=0, microsecond=0) 64 65 dt = nearest_minute(t.utc_datetime()) 66 print(dt.strftime('%Y-%m-%d %H:%M')) 67 68.. testoutput:: 69 70 2018-09-10 05:15 71 72The results should then agree with the tables produced by the USNO. 73 74The Seasons 75=========== 76 77Create a start time and an end time to ask for all of the equinoxes and 78solstices that fall in between. 79 80.. testcode:: 81 82 t0 = ts.utc(2018, 1, 1) 83 t1 = ts.utc(2018, 12, 31) 84 t, y = almanac.find_discrete(t0, t1, almanac.seasons(eph)) 85 86 for yi, ti in zip(y, t): 87 print(yi, almanac.SEASON_EVENTS[yi], ti.utc_iso(' ')) 88 89.. testoutput:: 90 91 0 Vernal Equinox 2018-03-20 16:15:27Z 92 1 Summer Solstice 2018-06-21 10:07:18Z 93 2 Autumnal Equinox 2018-09-23 01:54:06Z 94 3 Winter Solstice 2018-12-21 22:22:44Z 95 96The result ``t`` will be an array of times, and ``y`` will be ``0`` 97through ``3`` for the Vernal Equinox through the Winter Solstice. 98 99If you or some of your users live in the Southern Hemisphere, 100you can use the ``SEASON_EVENTS_NEUTRAL`` array. 101Instead of naming specific seasons, 102it names the equinoxes and solstices by the month in which they occur — 103so the ``March Equinox``, for example, is followed by the ``June Solstice``. 104 105Phases of the Moon 106================== 107 108The phases of the Moon are the same for everyone on Earth, so you don’t 109need to specify the longitude and latitude of your location. Simply ask 110for the current phase of the Moon as an angle, where 0° is New Moon and 111180° is Full: 112 113.. testcode:: 114 115 t = ts.utc(2020, 11, 19) 116 phase = almanac.moon_phase(eph, t) 117 print('Moon phase: {:.1f} degrees'.format(phase.degrees)) 118 119.. testoutput:: 120 121 Moon phase: 51.3 degrees 122 123Or you can have Skyfield search over a range of dates for the moments 124when the Moon reaches First Quarter, Full, Last Quarter, and New: 125 126.. testcode:: 127 128 t0 = ts.utc(2018, 9, 1) 129 t1 = ts.utc(2018, 9, 10) 130 t, y = almanac.find_discrete(t0, t1, almanac.moon_phases(eph)) 131 132 print(t.utc_iso()) 133 print(y) 134 print([almanac.MOON_PHASES[yi] for yi in y]) 135 136.. testoutput:: 137 138 ['2018-09-03T02:37:24Z', '2018-09-09T18:01:28Z'] 139 [3 0] 140 ['Last Quarter', 'New Moon'] 141 142The result ``t`` will be an array of times, and ``y`` will be a 143corresponding array of Moon phases with 0 for New Moon and 3 for Last 144Quarter. You can use the array ``MOON_PHASES`` to retrieve names for 145each phase. 146 147.. _lunar-nodes: 148 149Lunar Nodes 150=========== 151 152The Moon’s ascending node and descending node are the moments each lunar 153month when the Moon crosses the plane of Earth’s orbit and eclipses are 154possible. 155 156.. testcode:: 157 158 t0 = ts.utc(2020, 4, 22) 159 t1 = ts.utc(2020, 5, 22) 160 t, y = almanac.find_discrete(t0, t1, almanac.moon_nodes(eph)) 161 162 print(t.utc_iso()) 163 print(y) 164 print([almanac.MOON_NODES[yi] for yi in y]) 165 166.. testoutput:: 167 168 ['2020-04-27T17:54:17Z', '2020-05-10T09:01:42Z'] 169 [1 0] 170 ['ascending', 'descending'] 171 172.. _oppositions-conjunctions: 173 174Opposition and Conjunction 175========================== 176 177The moment at which a planet is in opposition with the Sun or in 178conjunction with the Sun is when their ecliptic longitudes are at 0° or 179180° difference. 180 181.. testcode:: 182 183 t0 = ts.utc(2019, 1, 1) 184 t1 = ts.utc(2021, 1, 1) 185 f = almanac.oppositions_conjunctions(eph, eph['mars']) 186 t, y = almanac.find_discrete(t0, t1, f) 187 188 print(t.utc_iso()) 189 print(y) 190 191.. testoutput:: 192 193 ['2019-09-02T10:42:14Z', '2020-10-13T23:25:47Z'] 194 [0 1] 195 196The result ``t`` will be an array of times, and ``y`` will be an array 197of integers indicating which half of the sky the body has just entered: 1980 means the half of the sky west of the Sun along the ecliptic, and 1 199means the half of the sky east of the Sun. This means different things 200for different bodies: 201 202* For the outer planets Mars, Jupiter, Saturn, Uranus, and all other 203 bodies out beyond our orbit, 0 means the moment of conjunction with 204 the Sun and 1 means the moment of opposition. 205 206* Because the Moon moves eastward across our sky relative to the Sun, 207 not westward, the output is reversed compared to the outer planets: 0 208 means the moment of opposition or Full Moon, while 1 means the moment 209 of conjunction or New Moon. 210 211* The inner planets Mercury and Venus only ever experience conjunctions 212 with the Sun from our point of view, never oppositions, with 0 213 indicating an inferior conjunction and 1 a superior conjunction. 214 215.. _transits: 216 217Meridian Transits 218================= 219 220Every day the Earth’s rotation 221swings the sky through nearly 360°, 222leaving the celestial poles stationary 223while bringing each star and planet in turn 224across your *meridian* — 225the “line of longitude” in the sky above you 226that runs from the South Pole to the North Pole 227through the zenith point directly above your location on Earth. 228You can ask Skyfield for the times at which a body 229crosses your meridian, 230and then the antimeridian on the opposite side of the celestial globe: 231 232.. testcode:: 233 234 bluffton = api.wgs84.latlon(+40.8939, -83.8917) 235 236 t0 = ts.utc(2020, 11, 6) 237 t1 = ts.utc(2020, 11, 7) 238 f = almanac.meridian_transits(eph, eph['Mars'], bluffton) 239 t, y = almanac.find_discrete(t0, t1, f) 240 241 print(t.utc_strftime('%Y-%m-%d %H:%M')) 242 print(y) 243 print([almanac.MERIDIAN_TRANSITS[yi] for yi in y]) 244 245.. testoutput:: 246 247 ['2020-11-06 03:32', '2020-11-06 15:30'] 248 [1 0] 249 ['Meridian transit', 'Antimeridian transit'] 250 251Some astronomers call these moments 252“upper culmination” and “lower culmination” instead. 253 254Observers often think of transit as the moment 255when an object is highest in the sky, 256which is roughly true. 257But at very high precision, 258if the body has any north or south velocity 259then its moment of highest altitude will be slightly earlier or later. 260 261Bodies near the poles are exceptions to the general rule 262that a body is visible at transit but below the horizon at antitransit. 263For a body that’s circumpolar from your location, 264transit and antitransit are both moments of visibility, 265when it stands above and below the pole; 266and objects close to the opposite pole will always be below the horizon, 267even as they invisibly transit your line of longitude 268down below your horizon. 269 270Sunrise and Sunset 271================== 272 273Because sunrise and sunset differ depending on your location on the 274Earth’s surface, you will need to specify a latitude and longitude. 275 276Then you can create a start time and an end time and ask for all of the 277sunrises and sunsets in between. 278Skyfield uses the 279`official definition of sunrise and sunset 280<http://aa.usno.navy.mil/faq/docs/RST_defs.php>`_ 281from the United States Naval Observatory, 282which defines them as the moment when the center — not the limb — 283of the sun is 0.8333 degrees below the horizon, 284to account for both the average radius of the Sun itself 285and for the average refraction of the atmosphere at the horizon. 286 287.. testcode:: 288 289 t0 = ts.utc(2018, 9, 12, 4) 290 t1 = ts.utc(2018, 9, 13, 4) 291 t, y = almanac.find_discrete(t0, t1, almanac.sunrise_sunset(eph, bluffton)) 292 293 print(t.utc_iso()) 294 print(y) 295 296.. testoutput:: 297 298 ['2018-09-12T11:13:13Z', '2018-09-12T23:49:38Z'] 299 [1 0] 300 301The result ``t`` will be an array of times, and ``y`` will be ``1`` if 302the sun rises at the corresponding time and ``0`` if it sets. 303 304If you need to provide your own custom value for refraction, adjust the 305estimate of the Sun’s radius, or account for a vantage point above the 306Earth’s surface, see :ref:`risings-and-settings` to learn about the more 307versatile :func:`~skyfield.almanac.risings_and_settings()` routine. 308 309Note that a location near one of the poles during polar summer or polar 310winter will not experience sunrise and sunset. To learn whether the sun 311is up or down, call the sunrise-sunset function at the time that 312interests you, and the return value will indicate whether the sun is up. 313 314.. testcode:: 315 316 far_north = api.wgs84.latlon(89, -80) 317 f = almanac.sunrise_sunset(eph, far_north) 318 t, y = almanac.find_discrete(t0, t1, f) 319 320 print(t.utc_iso()) # Empty list: no sunrise or sunset 321 print(f(t0)) # But we can ask if the sun is up 322 323 print('polar day' if f(t0) else 'polar night') 324 325.. testoutput:: 326 327 [] 328 True 329 polar day 330 331Twilight 332======== 333 334An expanded version of the sunrise-sunset routine 335named :func:`~skyfield.almanac.dark_twilight_day()` 336returns a separate code for each of the phases of twilight: 337 3380. Dark of night. 3391. Astronomical twilight. 3402. Nautical twilight. 3413. Civil twilight. 3424. Daytime. 343 344You can find a full example of its use 345at the :ref:`dark_twilight_day() example`. 346 347.. _risings-and-settings: 348 349Risings and Settings 350==================== 351 352Skyfield can compute when a given body rises and sets. 353The routine is designed for bodies at the Moon’s distance or farther, 354that tend to rise and set about once a day. 355But it might be caught off guard 356if you pass it an Earth satellite 357that rises several times a day; 358for that case, see :ref:`satellite-rising-and-setting`. 359 360Rising and setting predictions can be generated 361using the :func:`~skyfield.almanac.risings_and_settings()` routine: 362 363.. testcode:: 364 365 t0 = ts.utc(2020, 2, 1) 366 t1 = ts.utc(2020, 2, 2) 367 f = almanac.risings_and_settings(eph, eph['Mars'], bluffton) 368 t, y = almanac.find_discrete(t0, t1, f) 369 370 for ti, yi in zip(t, y): 371 print(ti.utc_iso(), 'Rise' if yi else 'Set') 372 373.. testoutput:: 374 375 2020-02-01T09:29:16Z Rise 376 2020-02-01T18:42:57Z Set 377 378As with sunrise and sunset above, 379``1`` means the moment of rising and ``0`` means the moment of setting. 380 381The routine also offers some optional parameters, 382whose several uses are covered in the following sections. 383 384Computing your own refraction angle 385----------------------------------- 386 387Instead of accepting the standard estimate of 34 arcminutes 388for the angle by which refraction will raise the image 389of a body at the horizon, 390you can compute atmospheric refraction yourself 391and supply the resulting angle to ``horizon_degrees``. 392Note that the value passed should be a small negative angle. 393In this example it makes a 3 second difference 394in both the rising and setting time: 395 396.. testcode:: 397 398 from skyfield.earthlib import refraction 399 400 r = refraction(0.0, temperature_C=15.0, pressure_mbar=1030.0) 401 print('Arcminutes refraction for body seen at horizon: %.2f\n' % (r * 60.0)) 402 403 f = almanac.risings_and_settings(eph, eph['Mars'], bluffton, horizon_degrees=-r) 404 t, y = almanac.find_discrete(t0, t1, f) 405 406 for ti, yi in zip(t, y): 407 print(ti.utc_iso(), 'Rise' if yi else 'Set') 408 409.. testoutput:: 410 411 Arcminutes refraction for body seen at horizon: 34.53 412 413 2020-02-01T09:29:13Z Rise 414 2020-02-01T18:43:00Z Set 415 416Adjusting for apparent radius 417----------------------------- 418 419Planets and especially the Sun and Moon have an appreciable radius, 420and we usually consider the moment of sunrise 421to be the moment when its bright limb crests the horizon — 422not the later moment when its center finally rises into view. 423Set the parameter ``radius_degrees`` to the body’s apparent radius 424to generate an earlier rising and later setting; 425the value ``0.25``, for example, 426would be a rough estimate for the Sun or Moon. 427 428The difference in rising time can be a minute or more: 429 430.. testcode:: 431 432 f = almanac.risings_and_settings(eph, eph['Sun'], bluffton, radius_degrees=0.25) 433 t, y = almanac.find_discrete(t0, t1, f) 434 print(t[0].utc_iso(' '), 'Limb of the Sun crests the horizon') 435 436 f = almanac.risings_and_settings(eph, eph['Sun'], bluffton) 437 t, y = almanac.find_discrete(t0, t1, f) 438 print(t[0].utc_iso(' '), 'Center of the Sun reaches the horizon') 439 440.. testoutput:: 441 442 2020-02-01 12:46:27Z Limb of the Sun crests the horizon 443 2020-02-01 12:47:53Z Center of the Sun reaches the horizon 444 445Elevated vantage points 446----------------------- 447 448Rising and setting predictions usually assume a flat local horizon 449that does not vary with elevation. 450Yes, Denver is the Mile High City, 451but it sees the sun rise against a local horizon that’s also a mile high. 452Since the city’s high altitude 453is matched by the high altitude of the terrain around it, 454the horizon winds up in the same place it would be for a city at sea level. 455 456But sometimes you need to account not only for local elevation, 457but for *altitude* above the surrounding terrain. 458Some observatories, for example, are located on mountaintops 459that are much higher than the elevation of the terrain 460that forms their horizon. 461And Earth satellites can be hundreds of kilometers 462above the surface of the Earth that produces their sunrises and sunsets. 463 464You can account for high altitude above the horizon’s terrain 465by setting an artificially negative value for ``horizon_degrees``. 466If we consider the Earth to be approximately a sphere, 467then we can use a bit of trigonometry 468to estimate the position of the horizon for an observer at altitude: 469 470.. testcode:: 471 472 from numpy import arccos 473 from skyfield.units import Angle 474 475 # When does the Sun rise in the ionosphere’s F-layer, 300km up? 476 altitude_m = 300e3 477 478 earth_radius_m = 6378136.6 479 side_over_hypotenuse = earth_radius_m / (earth_radius_m + altitude_m) 480 h = Angle(radians = -arccos(side_over_hypotenuse)) 481 print('The horizon from 300km up is at %.2f degrees' % h.degrees) 482 483 f = almanac.risings_and_settings( 484 eph, eph['Sun'], bluffton, horizon_degrees=h.degrees, 485 radius_degrees=0.25, 486 ) 487 t, y = almanac.find_discrete(t0, t1, f) 488 print(t[0].utc_iso(' '), 'Limb of the Sun crests the horizon') 489 490.. testoutput:: 491 492 The horizon from 300km up is at -17.24 degrees 493 2020-02-01 00:22:42Z Limb of the Sun crests the horizon 494 495When writing code for this situation, 496we need to be very careful to keep straight 497the two different meanings of *altitude*. 498 4991. The *altitude above sea level* is a linear distance measured in meters 500 between the ground and the location at which 501 we want to compute rises and settings. 502 5032. The *altitude of the horizon* names a quite different measure. 504 It’s an angle measured in degrees 505 that is one of the two angles 506 of the altitude-azimuth (“altazimuth”) system 507 oriented around an observer on a planet’s surface. 508 While azimuth measures horizontally around the horizon 509 from north through east, south, and west, 510 the altitude angle measures up towards the zenith (positive) 511 and down towards the nadir (negative). 512 The altitude is zero all along the great circle between zenith and nadir. 513 514The problem of an elevated observer 515unfortunately involves both kinds of altitude at the same time: 516for each extra meter of “altitude” above the ground, 517there is a slight additional depression in the angular “altitude” 518of the horizon on the altazimuth globe. 519 520When a right ascension and declination rises and sets 521----------------------------------------------------- 522 523If you are interested in finding the times 524when a fixed point in the sky rises and sets, 525simply create a star object with the coordinates 526of the position you are interested in 527(see :doc:`stars`). 528Here, for example, are rising and setting times for the Galactic Center: 529 530.. testcode:: 531 532 galactic_center = api.Star(ra_hours=(17, 45, 40.04), 533 dec_degrees=(-29, 0, 28.1)) 534 535 f = almanac.risings_and_settings(eph, galactic_center, bluffton) 536 t, y = almanac.find_discrete(t0, t1, f) 537 538 for ti, yi in zip(t, y): 539 verb = 'rises above' if yi else 'sets below' 540 print(ti.utc_iso(' '), '- Galactic Center', verb, 'the horizon') 541 542.. testoutput:: 543 544 2020-02-01 10:29:00Z - Galactic Center rises above the horizon 545 2020-02-01 18:45:46Z - Galactic Center sets below the horizon 546 547Solar terms 548=========== 549 550The solar terms are widely used in East Asian calendars. 551 552.. testcode:: 553 554 from skyfield import almanac_east_asia as almanac_ea 555 556 t0 = ts.utc(2019, 12, 1) 557 t1 = ts.utc(2019, 12, 31) 558 t, tm = almanac.find_discrete(t0, t1, almanac_ea.solar_terms(eph)) 559 560 for tmi, ti in zip(tm, t): 561 print(tmi, almanac_ea.SOLAR_TERMS_ZHS[tmi], ti.utc_iso(' ')) 562 563.. testoutput:: 564 565 17 大雪 2019-12-07 10:18:28Z 566 18 冬至 2019-12-22 04:19:26Z 567 568The result ``t`` will be an array of times, and ``y`` will be integers 569in the range 0–23 which are each the index of a solar term. Localized 570names for the solar terms in different East Asia languages are provided 571as ``SOLAR_TERMS_JP`` for Japanese, ``SOLAR_TERMS_VN`` for Vietnamese, 572``SOLAR_TERMS_ZHT`` for Traditional Chinese, and (as shown above) 573``SOLAR_TERMS_ZHS`` for Simplified Chinese. 574 575.. _lunar-eclipses: 576 577Lunar eclipses 578============== 579 580Skyfield can find the dates of lunar eclipses. 581 582.. testcode:: 583 584 from skyfield import eclipselib 585 586 t0 = ts.utc(2019, 1, 1) 587 t1 = ts.utc(2020, 1, 1) 588 t, y, details = eclipselib.lunar_eclipses(t0, t1, eph) 589 590 for ti, yi in zip(t, y): 591 print(ti.utc_strftime('%Y-%m-%d %H:%M'), 592 'y={}'.format(yi), 593 eclipselib.LUNAR_ECLIPSES[yi]) 594 595.. testoutput:: 596 597 2019-01-21 05:12 y=2 Total 598 2019-07-16 21:31 y=1 Partial 599 600Note that any eclipse forecast 601is forced to make arbitrary distinctions 602when eclipses fall very close to the boundary 603between the categories “partial”, “penumbral”, and “total”. 604Skyfield searches for lunar eclipses using the techniques described 605in the *Explanatory Supplement to the Astronomical Almanac.* 606 607* Some disagreement is inevitable — 608 for example, 609 because Skyfield uses a modern ephemeris 610 while the *Supplement* used the old VSOP87 theory. 611 612* However, 613 Skyfield does currently find every one of the 7,238 lunar eclipses 614 between the years 1 and 2999 615 that are listed in NASA’s 616 `Five Millennium Canon of Lunar Eclipses 617 <https://eclipse.gsfc.nasa.gov/SEpubs/5MCLE.html>`_ 618 by Espenak and Meeus. 619 While tweaks might be made to Skyfield’s routine in the future, 620 it is expected to always at least return every eclipse 621 listed in the *Canon*. 622 623* Skyfield tends to return eclipse times 624 that are a few seconds earlier than those given by the *Canon*. 625 For decades near the present the disagreement 626 rarely exceeds 2 seconds, 627 but for eclipses 2,000 years ago the difference 628 can be as large as 20 seconds. 629 630* Skyfield also finds 71 barely partial eclipses 631 beyond those listed in the *Canon*. 632 633* Skyfield agrees with the *Canon*\ ’s category 634 (partial, penumbral, total) for more than 99.6% of the eclipses. 635 636To help you study each eclipse in greater detail, 637Skyfield returns a ``details`` dictionary of extra arrays 638that provide the dimensions of the Moon and Earth’s shadow 639at the height of the eclipse. 640The dictionary currently offers the following arrays, 641whose meanings are hopefully self-explanatory: 642 643* ``closest_approach_radians`` 644* ``moon_radius_radians`` 645* ``penumbra_radius_radians`` 646* ``umbra_radius_radians`` 647 648By combining these dimensions 649with the position of the Moon at the height of the eclipse 650(which you can generate using Skyfield’s usual approach 651to computing a position), 652you should be able to produce a detailed diagram of each eclipse. 653 654For a review of the parameters that differ between eclipse forecasts, 655see NASA’s 656`Enlargement of Earth's shadows 657<https://eclipse.gsfc.nasa.gov/LEcat5/shadow.html>`_ 658page on their Five Millennium Canon site. 659If you need lunar eclipse forecasts 660generated by a very specific set of parameters, 661try cutting and pasting Skyfield’s ``lunar_eclipses()`` function 662into your own code 663and making your adjustments there — 664you will have complete control of the outcome, 665and your application will be immune 666to any tweaking that takes place in Skyfield in the future 667if it’s found that Skyfield’s eclipse accuracy can become even better. 668