krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Packages/MoonriseKit/Sources/CAstronomy/astronomy.c

13058 lines · 481221 bytes

    1/*
    2    Astronomy Engine for C/C++.
    3    https://github.com/cosinekitty/astronomy
    4
    5    MIT License
    6
    7    Copyright (c) 2019-2023 Don Cross <cosinekitty@gmail.com>
    8
    9    Permission is hereby granted, free of charge, to any person obtaining a copy
   10    of this software and associated documentation files (the "Software"), to deal
   11    in the Software without restriction, including without limitation the rights
   12    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   13    copies of the Software, and to permit persons to whom the Software is
   14    furnished to do so, subject to the following conditions:
   15
   16    The above copyright notice and this permission notice shall be included in all
   17    copies or substantial portions of the Software.
   18
   19    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   20    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   21    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   22    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   23    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   24    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   25    SOFTWARE.
   26*/
   27
   28#include <math.h>
   29#include <stdint.h>
   30#include <stdio.h>
   31#include <stdlib.h>
   32#include <string.h>
   33
   34#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME)
   35#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
   36#include <sys/time.h>
   37#elif defined(_WIN32)
   38#define WIN32_LEAN_AND_MEAN
   39#include <windows.h>
   40#else
   41#include <time.h>
   42#endif
   43#endif
   44
   45#include "astronomy.h"
   46
   47#ifdef __FAST_MATH__
   48#error Astronomy Engine does not support "fast math" optimization because it causes incorrect behavior. See: https://github.com/cosinekitty/astronomy/issues/245
   49#endif
   50
   51#ifdef __cplusplus
   52extern "C" {
   53#endif
   54
   55/** @cond DOXYGEN_SKIP */
   56#define PI      3.14159265358979323846
   57
   58#define PLUTO_NUM_STATES  51
   59#define PLUTO_TIME_STEP   29200
   60#define PLUTO_DT          146
   61
   62#define PLUTO_NSTEPS      201
   63
   64
   65
   66typedef enum
   67{
   68    FROM_2000,
   69    INTO_2000
   70}
   71precess_dir_t;
   72
   73typedef struct
   74{
   75    double x;
   76    double y;
   77    double z;
   78}
   79terse_vector_t;
   80
   81typedef struct
   82{
   83    double          tt;   /* J2000 terrestrial time [days] */
   84    terse_vector_t  r;    /* position [au] */
   85    terse_vector_t  v;    /* velocity [au/day] */
   86    terse_vector_t  a;    /* acceleration [au/day^2] */
   87}
   88body_grav_calc_t;
   89
   90typedef struct
   91{
   92    body_grav_calc_t   step[PLUTO_NSTEPS];
   93}
   94body_segment_t;
   95
   96typedef struct
   97{
   98    double          tt;  /* Terrestrial Time in J2000 days */
   99    terse_vector_t  r;   /* position [au] */
  100    terse_vector_t  v;   /* velocity [au/day] */
  101}
  102body_state_t;
  103
  104typedef struct
  105{
  106    body_state_t Sun;
  107    body_state_t Jupiter;
  108    body_state_t Saturn;
  109    body_state_t Uranus;
  110    body_state_t Neptune;
  111}
  112major_bodies_t;
  113
  114typedef struct
  115{
  116    astro_time_t      time;
  117    body_state_t      gravitators[1 + BODY_SUN];
  118    body_grav_calc_t *bodies;
  119}
  120gravsim_endpoint_t;
  121
  122struct astro_grav_sim_s
  123{
  124    astro_body_t        originBody;
  125    int                 numBodies;
  126    gravsim_endpoint_t  endpoint[2];
  127    gravsim_endpoint_t *prev;
  128    gravsim_endpoint_t *curr;
  129};
  130
  131typedef struct
  132{
  133    double ra;
  134    double dec;
  135    double dist;
  136}
  137stardef_t;
  138
  139/* Mean obliquity of the J2000 ecliptic in radians. */
  140#define OBLIQ_2000       0.40909260059599012
  141#define COS_OBLIQ_2000   0.9174821430670688
  142#define SIN_OBLIQ_2000   0.3977769691083922
  143
  144/** @endcond */
  145
  146#define NSTARS 8
  147static stardef_t StarTable[NSTARS];
  148
  149#define GetStarPointer(body)    (((body) >= BODY_STAR1) && ((body) <= BODY_STAR8) ? &StarTable[(body) - BODY_STAR1] : NULL)
  150
  151static stardef_t *UserDefinedStar(astro_body_t body)
  152{
  153    stardef_t *star = GetStarPointer(body);
  154    if (star != NULL && star->dist > 0.0)
  155        return star;
  156    return NULL;
  157}
  158
  159
  160/**
  161 * @brief Assign equatorial coordinates to a user-defined star.
  162 *
  163 * Some Astronomy Engine functions allow their `body` parameter to
  164 * be a user-defined fixed point in the sky, loosely called a "star".
  165 * This function assigns a right ascension, declination, and distance
  166 * to one of the eight user-defined stars `BODY_STAR1` .. `BODY_STAR8`.
  167 *
  168 * Stars are not valid until defined. Once defined, they retain their
  169 * definition until re-defined by another call to `Astronomy_DefineStar`.
  170 *
  171 * @param body
  172 *      One of the eight user-defined star identifiers: `BODY_STAR1` .. `BODY_STAR8`.
  173 *
  174 * @param ra
  175 *      The right ascension to be assigned to the star, expressed in J2000 equatorial coordinates (EQJ).
  176 *      The value is in units of sidereal hours, and must be within the half-open range [0, 24).
  177 *
  178 * @param dec
  179 *      The declination to be assigned to the star, expressed in J2000 equatorial coordinates (EQJ).
  180 *      The value is in units of degrees north (positive) or south (negative) of the J2000 equator,
  181 *      and must be within the closed range [-90, +90].
  182 *
  183 * @param distanceLightYears
  184 *      The distance between the star and the Sun, expressed in light-years.
  185 *      This value is used to calculate the tiny parallax shift as seen by an observer on Earth.
  186 *      If you don't know the distance to the star, using a large value like 1000 will generally work well.
  187 *      The minimum allowed distance is 1 light-year, which is required to provide certain internal optimizations.
  188 *
  189 * @return
  190 *      `ASTRO_SUCCESS` indicates the star has been defined. Any other value indicates an error,
  191 *      in which case no change has taken place to any of the star definitions.
  192 */
  193astro_status_t Astronomy_DefineStar(astro_body_t body, double ra, double dec, double distanceLightYears)
  194{
  195    stardef_t *star = GetStarPointer(body);
  196
  197    if (star == NULL)
  198        return ASTRO_INVALID_BODY;
  199
  200    if (!isfinite(ra) || ra < 0.0 || ra >= 24.0)
  201        return ASTRO_INVALID_PARAMETER;
  202
  203    if (!isfinite(dec) || dec < -90.0 || dec > +90.0)
  204        return ASTRO_INVALID_PARAMETER;
  205
  206    if (!isfinite(distanceLightYears) || distanceLightYears < 1.0)
  207        return ASTRO_INVALID_PARAMETER;
  208
  209    star->ra   = ra;
  210    star->dec  = dec;
  211    star->dist = distanceLightYears * AU_PER_LY;
  212
  213    return ASTRO_SUCCESS;
  214}
  215
  216static const terse_vector_t VecZero = { 0.0, 0.0, 0.0 };
  217
  218static terse_vector_t VecAdd(terse_vector_t a, terse_vector_t b)
  219{
  220    terse_vector_t c;
  221    c.x = a.x + b.x;
  222    c.y = a.y + b.y;
  223    c.z = a.z + b.z;
  224    return c;
  225}
  226
  227static void VecIncr(terse_vector_t *target, terse_vector_t source)
  228{
  229    target->x += source.x;
  230    target->y += source.y;
  231    target->z += source.z;
  232}
  233
  234static void VecDecr(terse_vector_t *target, terse_vector_t source)
  235{
  236    target->x -= source.x;
  237    target->y -= source.y;
  238    target->z -= source.z;
  239}
  240
  241static terse_vector_t VecMul(double s, terse_vector_t v)
  242{
  243    terse_vector_t p;
  244    p.x = s * v.x;
  245    p.y = s * v.y;
  246    p.z = s * v.z;
  247    return p;
  248}
  249
  250static void VecScale(terse_vector_t *target, double scalar)
  251{
  252    target->x *= scalar;
  253    target->y *= scalar;
  254    target->z *= scalar;
  255}
  256
  257static terse_vector_t VecRamp(terse_vector_t a, terse_vector_t b, double ramp)
  258{
  259    terse_vector_t c;
  260    c.x = (1-ramp)*a.x + ramp*b.x;
  261    c.y = (1-ramp)*a.y + ramp*b.y;
  262    c.z = (1-ramp)*a.z + ramp*b.z;
  263    return c;
  264}
  265
  266static terse_vector_t VecMean(terse_vector_t a, terse_vector_t b)
  267{
  268    terse_vector_t c;
  269    c.x = (a.x + b.x) / 2;
  270    c.y = (a.y + b.y) / 2;
  271    c.z = (a.z + b.z) / 2;
  272    return c;
  273}
  274
  275static astro_state_vector_t ExportState(body_state_t terse, astro_time_t time)
  276{
  277    astro_state_vector_t state;
  278
  279    state.status = ASTRO_SUCCESS;
  280    state.x  = terse.r.x;
  281    state.y  = terse.r.y;
  282    state.z  = terse.r.z;
  283    state.vx = terse.v.x;
  284    state.vy = terse.v.y;
  285    state.vz = terse.v.z;
  286    state.t  = time;
  287
  288    return state;
  289}
  290
  291static astro_state_vector_t ExportGravCalc(body_grav_calc_t calc, astro_time_t time)
  292{
  293    astro_state_vector_t state;
  294
  295    state.status = ASTRO_SUCCESS;
  296    state.x  = calc.r.x;
  297    state.y  = calc.r.y;
  298    state.z  = calc.r.z;
  299    state.vx = calc.v.x;
  300    state.vy = calc.v.y;
  301    state.vz = calc.v.z;
  302    state.t  = time;
  303
  304    return state;
  305}
  306
  307static const double DAYS_PER_TROPICAL_YEAR = 365.24217;
  308static const double ASEC360 = 1296000.0;
  309static const double ASEC2RAD = 4.848136811095359935899141e-6;
  310static const double PI2 = 2.0 * PI;
  311static const double ARC = 3600.0 * 180.0 / PI;          /* arcseconds per radian */
  312static const double SECONDS_PER_DAY = 24.0 * 3600.0;
  313static const double SOLAR_DAYS_PER_SIDEREAL_DAY = 0.9972695717592592;
  314static const double MEAN_SYNODIC_MONTH = 29.530588;     /* average number of days for Moon to return to the same phase */
  315static const double EARTH_ORBITAL_PERIOD = 365.256;
  316static const double NEPTUNE_ORBITAL_PERIOD = 60189.0;
  317
  318/*
  319    Degrees of refractive "lift" seen for objects near horizon.
  320    More precisely, the angle below the horizon a point has to be, at sea level,
  321    to appear to be exactly on the horizon.
  322    If the ground plane is higher than sea level, this angle
  323    needs to be corrected for decreased atmospheric density.
  324*/
  325static const double REFRACTION_NEAR_HORIZON = 34.0 / 60.0;
  326
  327
  328#define             SUN_RADIUS_AU  (SUN_RADIUS_KM / KM_PER_AU)
  329
  330#define EARTH_MEAN_RADIUS_KM        6371.0            /* mean radius of the Earth's geoid, without atmosphere */
  331#define EARTH_ATMOSPHERE_KM           88.0            /* effective atmosphere thickness for lunar eclipses. see: https://eclipse.gsfc.nasa.gov/LEcat5/shadow.html */
  332#define EARTH_ECLIPSE_RADIUS_KM     (EARTH_MEAN_RADIUS_KM + EARTH_ATMOSPHERE_KM)
  333#define EARTH_EQUATORIAL_RADIUS_AU  (EARTH_EQUATORIAL_RADIUS_KM / KM_PER_AU)
  334
  335#define MOON_MEAN_RADIUS_KM         1737.4
  336#define MOON_EQUATORIAL_RADIUS_AU   (MOON_EQUATORIAL_RADIUS_KM / KM_PER_AU)
  337#define MOON_POLAR_RADIUS_AU        (MOON_POLAR_RADIUS_KM / KM_PER_AU)
  338
  339/* The inclination of the moon's rotation axis to the ecliptic plane, in radians. */
  340#define MOON_AXIS_INCLINATION_RADIANS    (DEG2RAD * 1.543)
  341
  342static const double ASEC180 = 180.0 * 60.0 * 60.0;      /* arcseconds per 180 degrees (or pi radians) */
  343static const double EARTH_MOON_MASS_RATIO = 81.30056;
  344
  345/*
  346    Masses of the Sun and planets, used for:
  347    (1) Calculating the Solar System Barycenter
  348    (2) Integrating the movement of Pluto
  349
  350    https://web.archive.org/web/20120220062549/http://iau-comm4.jpl.nasa.gov/de405iom/de405iom.pdf
  351
  352    Page 10 in the above document describes the constants used in the DE405 ephemeris.
  353    The following are GM values (gravity constant * mass) in [au^3 / day^2].
  354    This side-steps issues of not knowing the exact values of G and masses M[i];
  355    the products GM[i] are known extremely accurately.
  356*/
  357static const double SUN_GM     = 0.2959122082855911e-03;
  358static const double MERCURY_GM = 0.4912547451450812e-10;
  359static const double VENUS_GM   = 0.7243452486162703e-09;
  360static const double EARTH_GM   = 0.8887692390113509e-09;
  361static const double MARS_GM    = 0.9549535105779258e-10;
  362static const double JUPITER_GM = 0.2825345909524226e-06;
  363static const double SATURN_GM  = 0.8459715185680659e-07;
  364static const double URANUS_GM  = 0.1292024916781969e-07;
  365static const double NEPTUNE_GM = 0.1524358900784276e-07;
  366static const double PLUTO_GM   = 0.2188699765425970e-11;
  367
  368#define MOON_GM   (EARTH_GM / EARTH_MOON_MASS_RATIO)
  369
  370/** @cond DOXYGEN_SKIP */
  371#define ASTRO_ARRAYSIZE(x)    (sizeof(x) / sizeof(x[0]))
  372#define AU_PER_PARSEC   (ASEC180 / PI)             /* exact definition of how many AU = one parsec */
  373#define Y2000_IN_MJD    (T0 - MJD_BASIS)
  374/** @endcond */
  375
  376static astro_ecliptic_t RotateEquatorialToEcliptic(const double pos[3], double obliq_radians, astro_time_t time);
  377static int QuadInterp(
  378    double tm, double dt, double fa, double fm, double fb,
  379    double *t, double *df_dt);
  380
  381static double LongitudeOffset(double diff)
  382{
  383    double offset = diff;
  384
  385    while (offset <= -180.0)
  386        offset += 360.0;
  387
  388    while (offset > 180.0)
  389        offset -= 360.0;
  390
  391    return offset;
  392}
  393
  394static double NormalizeLongitude(double lon)
  395{
  396    while (lon < 0.0)
  397        lon += 360.0;
  398
  399    while (lon >= 360.0)
  400        lon -= 360.0;
  401
  402    return lon;
  403}
  404
  405/**
  406 * @brief Calculates the length of the given vector.
  407 *
  408 * Calculates the non-negative length of the given vector.
  409 * The length is expressed in the same units as the vector's components,
  410 * usually astronomical units (AU).
  411 *
  412 * @param vector The vector whose length is to be calculated.
  413 * @return The length of the vector.
  414 */
  415double Astronomy_VectorLength(astro_vector_t vector)
  416{
  417    return sqrt(vector.x*vector.x + vector.y*vector.y + vector.z*vector.z);
  418}
  419
  420/**
  421 * @brief Finds the name of a celestial body.
  422 * @param body The celestial body whose name is to be found.
  423 * @return The English-language name of the celestial body, or "" if the body is not valid.
  424 */
  425const char *Astronomy_BodyName(astro_body_t body)
  426{
  427    switch (body)
  428    {
  429    case BODY_MERCURY:  return "Mercury";
  430    case BODY_VENUS:    return "Venus";
  431    case BODY_EARTH:    return "Earth";
  432    case BODY_MARS:     return "Mars";
  433    case BODY_JUPITER:  return "Jupiter";
  434    case BODY_SATURN:   return "Saturn";
  435    case BODY_URANUS:   return "Uranus";
  436    case BODY_NEPTUNE:  return "Neptune";
  437    case BODY_PLUTO:    return "Pluto";
  438    case BODY_SUN:      return "Sun";
  439    case BODY_MOON:     return "Moon";
  440    case BODY_EMB:      return "EMB";
  441    case BODY_SSB:      return "SSB";
  442    default:            return "";
  443    }
  444}
  445
  446/**
  447 * @brief Returns the #astro_body_t value corresponding to the given English name.
  448 * @param name One of the following strings: Sun, Moon, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, EMB, SSB.
  449 * @return If `name` is one of the listed strings (case-sensitive), the returned value is the corresponding #astro_body_t value, otherwise it is `BODY_INVALID`.
  450 */
  451astro_body_t Astronomy_BodyCode(const char *name)
  452{
  453    if (name != NULL)
  454    {
  455        if (!strcmp(name, "Mercury"))   return BODY_MERCURY;
  456        if (!strcmp(name, "Venus"))     return BODY_VENUS;
  457        if (!strcmp(name, "Earth"))     return BODY_EARTH;
  458        if (!strcmp(name, "Mars"))      return BODY_MARS;
  459        if (!strcmp(name, "Jupiter"))   return BODY_JUPITER;
  460        if (!strcmp(name, "Saturn"))    return BODY_SATURN;
  461        if (!strcmp(name, "Uranus"))    return BODY_URANUS;
  462        if (!strcmp(name, "Neptune"))   return BODY_NEPTUNE;
  463        if (!strcmp(name, "Pluto"))     return BODY_PLUTO;
  464        if (!strcmp(name, "Sun"))       return BODY_SUN;
  465        if (!strcmp(name, "Moon"))      return BODY_MOON;
  466        if (!strcmp(name, "EMB"))       return BODY_EMB;
  467        if (!strcmp(name, "SSB"))       return BODY_SSB;
  468    }
  469    return BODY_INVALID;
  470}
  471
  472/**
  473 * @brief Returns 1 for planets that are farther from the Sun than the Earth is, 0 otherwise.
  474 */
  475static int IsSuperiorPlanet(astro_body_t body)
  476{
  477    switch (body)
  478    {
  479    case BODY_MARS:
  480    case BODY_JUPITER:
  481    case BODY_SATURN:
  482    case BODY_URANUS:
  483    case BODY_NEPTUNE:
  484    case BODY_PLUTO:
  485        return 1;
  486
  487    default:
  488        return 0;
  489    }
  490}
  491
  492/**
  493 * @brief Returns the average number of days it takes for a planet to orbit the Sun.
  494 * @param body One of the planets: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, or Pluto.
  495 * @return The mean orbital period of the body, or 0.0 if the `body` parameter is not valid.
  496 */
  497double Astronomy_PlanetOrbitalPeriod(astro_body_t body)
  498{
  499    switch (body)
  500    {
  501    case BODY_MERCURY:  return     87.969;
  502    case BODY_VENUS:    return    224.701;
  503    case BODY_EARTH:    return    EARTH_ORBITAL_PERIOD;
  504    case BODY_MARS:     return    686.980;
  505    case BODY_JUPITER:  return   4332.589;
  506    case BODY_SATURN:   return  10759.22;
  507    case BODY_URANUS:   return  30685.4;
  508    case BODY_NEPTUNE:  return  NEPTUNE_ORBITAL_PERIOD;
  509    case BODY_PLUTO:    return  90560.0;
  510    default:            return  0.0;        /* invalid body */
  511    }
  512}
  513
  514static astro_vector_t VecError(astro_status_t status, astro_time_t time)
  515{
  516    astro_vector_t vec;
  517    vec.x = vec.y = vec.z = NAN;
  518    vec.t = time;
  519    vec.status = status;
  520    return vec;
  521}
  522
  523static astro_state_vector_t StateVecError(astro_status_t status, astro_time_t time)
  524{
  525    astro_state_vector_t vec;
  526    vec.x = vec.y = vec.z = NAN;
  527    vec.vx = vec.vy = vec.vz = NAN;
  528    vec.t = time;
  529    vec.status = status;
  530    return vec;
  531}
  532
  533static astro_spherical_t SphereError(astro_status_t status)
  534{
  535    astro_spherical_t sphere;
  536    sphere.status = status;
  537    sphere.dist = sphere.lat = sphere.lon = NAN;
  538    return sphere;
  539}
  540
  541static astro_time_t TimeError(void)
  542{
  543    astro_time_t time;
  544    time.tt = time.ut = time.eps = time.psi = time.st = NAN;
  545    return time;
  546}
  547
  548static astro_equatorial_t EquError(astro_status_t status)
  549{
  550    astro_equatorial_t equ;
  551    equ.vec = VecError(status, TimeError());
  552    equ.ra = equ.dec = equ.dist = NAN;
  553    equ.status = status;
  554    return equ;
  555}
  556
  557static astro_ecliptic_t EclError(astro_status_t status)
  558{
  559    astro_ecliptic_t ecl;
  560    ecl.status = status;
  561    ecl.elon = ecl.elat = NAN;
  562    ecl.vec = VecError(status, TimeError());
  563    return ecl;
  564}
  565
  566static astro_angle_result_t AngleError(astro_status_t status)
  567{
  568    astro_angle_result_t result;
  569    result.status = status;
  570    result.angle = NAN;
  571    return result;
  572}
  573
  574static astro_func_result_t FuncError(astro_status_t status)
  575{
  576    astro_func_result_t result;
  577    result.status = status;
  578    result.value = NAN;
  579    return result;
  580}
  581
  582static astro_rotation_t RotationErr(astro_status_t status)
  583{
  584    astro_rotation_t rotation;
  585    int i, j;
  586
  587    rotation.status = status;
  588    for (i=0; i<3; ++i)
  589        for (j=0; j<3; ++j)
  590            rotation.rot[i][j] = NAN;
  591
  592    return rotation;
  593}
  594
  595static astro_moon_quarter_t MoonQuarterError(astro_status_t status)
  596{
  597    astro_moon_quarter_t result;
  598    result.status = status;
  599    result.quarter = -1;
  600    result.time = TimeError();
  601    return result;
  602}
  603
  604static astro_elongation_t ElongError(astro_status_t status)
  605{
  606    astro_elongation_t result;
  607
  608    result.status = status;
  609    result.elongation = NAN;
  610    result.ecliptic_separation = NAN;
  611    result.time = TimeError();
  612    result.visibility = (astro_visibility_t)(-1);
  613
  614    return result;
  615}
  616
  617static astro_hour_angle_t HourAngleError(astro_status_t status)
  618{
  619    astro_hour_angle_t result;
  620
  621    result.status = status;
  622    result.time = TimeError();
  623    result.hor.altitude = result.hor.azimuth = result.hor.dec = result.hor.ra = NAN;
  624
  625    return result;
  626}
  627
  628static astro_illum_t IllumError(astro_status_t status)
  629{
  630    astro_illum_t result;
  631
  632    result.status = status;
  633    result.time = TimeError();
  634    result.mag = NAN;
  635    result.phase_angle = NAN;
  636    result.phase_fraction = NAN;
  637    result.helio_dist = NAN;
  638    result.ring_tilt = NAN;
  639
  640    return result;
  641}
  642
  643static astro_apsis_t ApsisError(astro_status_t status)
  644{
  645    astro_apsis_t result;
  646
  647    result.status = status;
  648    result.time = TimeError();
  649    result.kind = APSIS_INVALID;
  650    result.dist_km = result.dist_au = NAN;
  651
  652    return result;
  653}
  654
  655static astro_search_result_t SearchError(astro_status_t status)
  656{
  657    astro_search_result_t result;
  658    result.time = TimeError();
  659    result.status = status;
  660    return result;
  661}
  662
  663static astro_constellation_t ConstelErr(astro_status_t status)
  664{
  665    astro_constellation_t constel;
  666    constel.status = status;
  667    constel.symbol = constel.name = NULL;
  668    constel.ra_1875 = constel.dec_1875 = NAN;
  669    return constel;
  670}
  671
  672static astro_transit_t TransitErr(astro_status_t status)
  673{
  674    astro_transit_t transit;
  675    transit.status = status;
  676    transit.start = transit.peak = transit.finish = TimeError();
  677    transit.separation = NAN;
  678    return transit;
  679}
  680
  681static astro_axis_t AxisErr(astro_status_t status, astro_time_t time)
  682{
  683    astro_axis_t axis;
  684    axis.status = status;
  685    axis.ra = axis.dec = axis.spin = NAN;
  686    axis.north = VecError(status, time);
  687    return axis;
  688}
  689
  690static astro_func_result_t SynodicPeriod(astro_body_t body)
  691{
  692    double Tp;                         /* planet's orbital period in days */
  693    astro_func_result_t result;
  694
  695    /* The Earth does not have a synodic period as seen from itself. */
  696    if (body == BODY_EARTH)
  697        return FuncError(ASTRO_EARTH_NOT_ALLOWED);
  698
  699    if (body == BODY_MOON)
  700    {
  701        result.status = ASTRO_SUCCESS;
  702        result.value = MEAN_SYNODIC_MONTH;
  703        return result;
  704    }
  705
  706    Tp = Astronomy_PlanetOrbitalPeriod(body);
  707    if (Tp <= 0.0)
  708        return FuncError(ASTRO_INVALID_BODY);
  709
  710    result.status = ASTRO_SUCCESS;
  711    result.value = fabs(EARTH_ORBITAL_PERIOD / (EARTH_ORBITAL_PERIOD/Tp - 1.0));
  712    return result;
  713}
  714
  715
  716/**
  717 * @brief Calculates the angle between two vectors.
  718 *
  719 * Given a pair of vectors, this function returns the angle in degrees
  720 * between the two vectors in 3D space.
  721 * The angle is measured in the plane that contains both vectors.
  722 *
  723 * @param a
  724 *      The first vector.
  725 *
  726 * @param b
  727 *      The second vector.
  728 *
  729 * @returns
  730 *      On success, the `status` field holds `ASTRO_SUCCESS` and `angle` holds
  731 *      a number of degrees in the range [0, 180].
  732 *      If either vector has a zero magnitude or contains NAN (not a number)
  733 *      components, the `status` will hold the error code `ASTRO_BAD_VECTOR`.
  734 */
  735astro_angle_result_t Astronomy_AngleBetween(astro_vector_t a, astro_vector_t b)
  736{
  737    double r, dot;
  738    astro_angle_result_t result;
  739
  740    r = Astronomy_VectorLength(a) * Astronomy_VectorLength(b);
  741    if (r < 1.0e-8 || !isfinite(r))
  742        return AngleError(ASTRO_BAD_VECTOR);
  743
  744    dot = (a.x*b.x + a.y*b.y + a.z*b.z) / r;
  745
  746    if (dot <= -1.0)
  747        result.angle = 180.0;
  748    else if (dot >= +1.0)
  749        result.angle = 0.0;
  750    else
  751        result.angle = RAD2DEG * acos(dot);
  752
  753    result.status = ASTRO_SUCCESS;
  754    return result;
  755}
  756
  757/**
  758 * @brief The default Delta T function used by Astronomy Engine.
  759 *
  760 * Espenak and Meeus use a series of piecewise polynomials to
  761 * approximate DeltaT of the Earth in their "Five Millennium Canon of Solar Eclipses".
  762 * See: https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html
  763 * This is the default Delta T function used by Astronomy Engine.
  764 *
  765 * @param ut
  766 *      The floating point number of days since noon UTC on January 1, 2000.
  767 *
  768 * @returns
  769 *      The estimated difference TT-UT on the given date, expressed in seconds.
  770 */
  771double Astronomy_DeltaT_EspenakMeeus(double ut)
  772{
  773    double y, u, u2, u3, u4, u5, u6, u7;
  774
  775    /*
  776        Fred Espenak writes about Delta-T generically here:
  777        https://eclipse.gsfc.nasa.gov/SEhelp/deltaT.html
  778        https://eclipse.gsfc.nasa.gov/SEhelp/deltat2004.html
  779
  780        He provides polynomial approximations for distant years here:
  781        https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html
  782
  783        They start with a year value 'y' such that y=2000 corresponds
  784        to the UTC Date 15-January-2000. Convert difference in days
  785        to mean tropical years.
  786    */
  787
  788    y = 2000 + ((ut - 14) / DAYS_PER_TROPICAL_YEAR);
  789
  790    if (y < -500)
  791    {
  792        u = (y - 1820) / 100;
  793        return -20 + (32 * u*u);
  794    }
  795    if (y < 500)
  796    {
  797        u = y / 100;
  798        u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; u6 = u3*u3;
  799        return 10583.6 - 1014.41*u + 33.78311*u2 - 5.952053*u3 - 0.1798452*u4 + 0.022174192*u5 + 0.0090316521*u6;
  800    }
  801    if (y < 1600)
  802    {
  803        u = (y - 1000) / 100;
  804        u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; u6 = u3*u3;
  805        return 1574.2 - 556.01*u + 71.23472*u2 + 0.319781*u3 - 0.8503463*u4 - 0.005050998*u5 + 0.0083572073*u6;
  806    }
  807    if (y < 1700)
  808    {
  809        u = y - 1600;
  810        u2 = u*u; u3 = u*u2;
  811        return 120 - 0.9808*u - 0.01532*u2 + u3/7129.0;
  812    }
  813    if (y < 1800)
  814    {
  815        u = y - 1700;
  816        u2 = u*u; u3 = u*u2; u4 = u2*u2;
  817        return 8.83 + 0.1603*u - 0.0059285*u2 + 0.00013336*u3 - u4/1174000;
  818    }
  819    if (y < 1860)
  820    {
  821        u = y - 1800;
  822        u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; u6 = u3*u3; u7 = u3*u4;
  823        return 13.72 - 0.332447*u + 0.0068612*u2 + 0.0041116*u3 - 0.00037436*u4 + 0.0000121272*u5 - 0.0000001699*u6 + 0.000000000875*u7;
  824    }
  825    if (y < 1900)
  826    {
  827        u = y - 1860;
  828        u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3;
  829        return 7.62 + 0.5737*u - 0.251754*u2 + 0.01680668*u3 - 0.0004473624*u4 + u5/233174;
  830    }
  831    if (y < 1920)
  832    {
  833        u = y - 1900;
  834        u2 = u*u; u3 = u*u2; u4 = u2*u2;
  835        return -2.79 + 1.494119*u - 0.0598939*u2 + 0.0061966*u3 - 0.000197*u4;
  836    }
  837    if (y < 1941)
  838    {
  839        u = y - 1920;
  840        u2 = u*u; u3 = u*u2;
  841        return 21.20 + 0.84493*u - 0.076100*u2 + 0.0020936*u3;
  842    }
  843    if (y < 1961)
  844    {
  845        u = y - 1950;
  846        u2 = u*u; u3 = u*u2;
  847        return 29.07 + 0.407*u - u2/233 + u3/2547;
  848    }
  849    if (y < 1986)
  850    {
  851        u = y - 1975;
  852        u2 = u*u; u3 = u*u2;
  853        return 45.45 + 1.067*u - u2/260 - u3/718;
  854    }
  855    if (y < 2005)
  856    {
  857        u = y - 2000;
  858        u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3;
  859        return 63.86 + 0.3345*u - 0.060374*u2 + 0.0017275*u3 + 0.000651814*u4 + 0.00002373599*u5;
  860    }
  861    if (y < 2050)
  862    {
  863        u = y - 2000;
  864        return 62.92 + 0.32217*u + 0.005589*u*u;
  865    }
  866    if (y < 2150)
  867    {
  868        u = (y-1820)/100;
  869        return -20 + 32*u*u - 0.5628*(2150 - y);
  870    }
  871
  872    /* all years after 2150 */
  873    u = (y - 1820) / 100;
  874    return -20 + (32 * u*u);
  875}
  876
  877/**
  878 * @brief A Delta T function that approximates the one used by the JPL Horizons tool.
  879 *
  880 * In order to support unit tests based on data generated by the JPL Horizons online
  881 * tool, I had to reverse engineer their Delta T function by generating a table that
  882 * contained it. The main difference between their tool and the Espenak/Meeus function
  883 * is that they stop extrapolating the Earth's deceleration after the year 2017.
  884 *
  885 * @param ut
  886 *      The floating point number of days since noon UTC on January 1, 2000.
  887 *
  888 * @returns
  889 *      The estimated difference TT-UT on the given date, expressed in seconds.
  890 */
  891double Astronomy_DeltaT_JplHorizons(double ut)
  892{
  893    if (ut > 17.0 * DAYS_PER_TROPICAL_YEAR)
  894        ut = 17.0 * DAYS_PER_TROPICAL_YEAR;
  895
  896    return Astronomy_DeltaT_EspenakMeeus(ut);
  897}
  898
  899static astro_deltat_func DeltaTFunc = Astronomy_DeltaT_EspenakMeeus;
  900
  901/**
  902 * @brief Changes the function Astronomy Engine uses to calculate Delta T.
  903 *
  904 * Most programs should not call this function. It is for advanced use cases only.
  905 * By default, Astronomy Engine uses the function #Astronomy_DeltaT_EspenakMeeus
  906 * to estimate changes in the Earth's rotation rate over time.
  907 * However, for the sake of unit tests that compare calculations against
  908 * external data sources that use alternative models for Delta T,
  909 * it is sometimes useful to replace the Delta T model to match.
  910 * This function allows replacing the Delta T model with any other
  911 * desired model.
  912 *
  913 * @param func
  914 *      A pointer to a function to convert UT values to DeltaT values.
  915 */
  916void Astronomy_SetDeltaTFunction(astro_deltat_func func)
  917{
  918    DeltaTFunc = func;
  919}
  920
  921static double TerrestrialTime(double ut)
  922{
  923    return ut + DeltaTFunc(ut)/86400.0;
  924}
  925
  926/**
  927 * @brief Converts a J2000 day value to an #astro_time_t value.
  928 *
  929 * This function can be useful for reproducing an #astro_time_t structure
  930 * from its `ut` field only.
  931 *
  932 * @param ut
  933 *      The floating point number of days since noon UTC on January 1, 2000.
  934 *      This time is based on UTC/UT1 civil time.
  935 *      See #Astronomy_TerrestrialTime if you instead want to create
  936 *      a time value based on atomic Terrestrial Time (TT).
  937 *
  938 * @returns
  939 *      An #astro_time_t value for the given `ut` value.
  940 */
  941astro_time_t Astronomy_TimeFromDays(double ut)
  942{
  943    astro_time_t  time;
  944    time.ut = ut;
  945    time.tt = TerrestrialTime(ut);
  946    time.psi = time.eps = time.st = NAN;
  947    return time;
  948}
  949
  950
  951/**
  952 * @brief Converts a terrestrial time value into an #astro_time_t value.
  953 *
  954 * This function can be used in rare cases where a time must be based
  955 * on Terrestrial Time (TT) rather than Universal Time (UT).
  956 * Most developers will want to call #Astronomy_TimeFromDays instead of
  957 * this function, because usually time is based on civil time adjusted
  958 * by leap seconds to match the Earth's rotation, rather than the uniformly
  959 * flowing TT used to calculate solar system dynamics. In rare cases
  960 * where the caller already knows TT, this function is provided to create
  961 * an #astro_time_t value that can be passed to Astronomy Engine functions.
  962 *
  963 * @param tt
  964 *      The floating point number of days of uniformly flowing
  965 *      Terrestrial Time since the J2000 epoch.
  966 *
  967 * @returns
  968 *      An #astro_time_t value for the given `tt` value.
  969 */
  970astro_time_t Astronomy_TerrestrialTime(double tt)
  971{
  972    /* Iterate to solve to find the correct ut for a given tt, and create an astro_time_t for that time. */
  973    astro_time_t time = Astronomy_TimeFromDays(tt);
  974    for(;;)
  975    {
  976        double err = tt - time.tt;
  977        if (fabs(err) < 1.0e-12)
  978            return time;
  979        time = Astronomy_AddDays(time, err);
  980    }
  981}
  982
  983#if !defined(ASTRONOMY_ENGINE_NO_CURRENT_TIME)
  984/**
  985 * @brief Returns the computer's current date and time in the form of an #astro_time_t.
  986 *
  987 * Uses the computer's system clock to find the current UTC date and time.
  988 * Converts that date and time to an #astro_time_t value and returns the result.
  989 * Callers can pass this value to other Astronomy Engine functions to calculate
  990 * current observational conditions.
  991 *
  992 * On supported platforms (Linux/Unix, Mac, Windows), the time is measured with
  993 * microsecond resolution.
  994 *
  995 * On unsupported platforms, a compiler error will occur due to lack of
  996 * microsecond resolution support. However, if whole second resolution is good
  997 * enough for your application, you can define the preprocessor symbol
  998 * `ASTRONOMY_ENGINE_WHOLE_SECOND` to use the portable function `time(NULL)`.
  999 * Alternatively, if you do not need to use `Astronomy_CurrentTime`, you can
 1000 * define the preprocessor symbol `ASTRONOMY_ENGINE_NO_CURRENT_TIME` to
 1001 * exclude this function from your code.
 1002 */
 1003astro_time_t Astronomy_CurrentTime(void)
 1004{
 1005    astro_time_t t;
 1006    double sec;         /* Seconds since midnight January 1, 1970. */
 1007
 1008#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
 1009    struct timeval tv;
 1010    gettimeofday(&tv, NULL);
 1011    sec = (double)tv.tv_sec + tv.tv_usec/1.0e+6;
 1012#elif defined(_WIN32)
 1013    FILETIME ft;
 1014    ULARGE_INTEGER large;
 1015    /* Get time in 100-nanosecond units from January 1, 1601. */
 1016    GetSystemTimePreciseAsFileTime(&ft);
 1017    large.u.LowPart  = ft.dwLowDateTime;
 1018    large.u.HighPart = ft.dwHighDateTime;
 1019    sec = (large.QuadPart - 116444736000000000ULL) / 1.0e+7;
 1020#elif defined(ASTRONOMY_ENGINE_WHOLE_SECOND)
 1021    sec = time(NULL);
 1022#else
 1023    #error Microsecond time resolution is not supported on this platform. Define ASTRONOMY_ENGINE_WHOLE_SECOND to use second resolution instead.
 1024#endif
 1025
 1026    /* Convert seconds to days, then subtract to get days since noon on January 1, 2000. */
 1027    t.ut = (sec / SECONDS_PER_DAY) - 10957.5;
 1028    t.tt = TerrestrialTime(t.ut);
 1029    t.psi = t.eps = t.st = NAN;
 1030    return t;
 1031}
 1032#endif
 1033
 1034/**
 1035 * @brief Creates an #astro_time_t value from a given calendar date and time.
 1036 *
 1037 * Given a UTC calendar date and time, calculates an #astro_time_t value that can
 1038 * be passed to other Astronomy Engine functions for performing various calculations
 1039 * relating to that date and time.
 1040 *
 1041 * It is the caller's responsibility to ensure that the parameter values are correct.
 1042 * The parameters are not checked for validity,
 1043 * and this function never returns any indication of an error.
 1044 * Invalid values, for example passing in February 31, may cause unexpected return values.
 1045 *
 1046 * @param year      The UTC calendar year, e.g. 2019.
 1047 * @param month     The UTC calendar month in the range 1..12.
 1048 * @param day       The UTC calendar day in the range 1..31.
 1049 * @param hour      The UTC hour of the day in the range 0..23.
 1050 * @param minute    The UTC minute in the range 0..59.
 1051 * @param second    The UTC floating-point second in the range [0, 60).
 1052 *
 1053 * @return  An #astro_time_t value that represents the given calendar date and time.
 1054 */
 1055astro_time_t Astronomy_MakeTime(int year, int month, int day, int hour, int minute, double second)
 1056{
 1057    astro_time_t time;
 1058    int64_t y = (int64_t)year;
 1059    int64_t m = (int64_t)month;
 1060    int64_t d = (int64_t)day;
 1061    int64_t f = (14 - m) / 12;
 1062
 1063    /*
 1064        This formula is adapted from NOVAS C 3.1 function julian_date(),
 1065        which in turn comes from Henry F. Fliegel & Thomas C. Van Flendern:
 1066        Communications of the ACM, Vol 11, No 10, October 1968, p. 657.
 1067        See: https://dl.acm.org/doi/pdf/10.1145/364096.364097
 1068
 1069        [Don Cross - 2023-02-25] I modified the formula so that it will
 1070        work correctly with years as far back as -999999.
 1071    */
 1072    int64_t y2000 = (
 1073        (d - 365972956)
 1074        + (1461*(y + 1000000 - f))/4
 1075        + (367*(m - 2 + 12*f))/12
 1076        - (3*((y + 1000100 - f) / 100))/4
 1077    );
 1078
 1079    time.ut = (y2000 - 0.5) + (hour / 24.0) + (minute / 1440.0) + (second / 86400.0);
 1080    time.tt = TerrestrialTime(time.ut);
 1081    time.psi = time.eps = time.st = NAN;
 1082
 1083    return time;
 1084}
 1085
 1086/**
 1087 * @brief   Calculates the sum or difference of an #astro_time_t with a specified floating point number of days.
 1088 *
 1089 * Sometimes we need to adjust a given #astro_time_t value by a certain amount of time.
 1090 * This function adds the given real number of days in `days` to the date and time in `time`.
 1091 *
 1092 * More precisely, the result's Universal Time field `ut` is exactly adjusted by `days` and
 1093 * the Terrestrial Time field `tt` is adjusted correctly for the resulting UTC date and time,
 1094 * according to the historical and predictive Delta-T model provided by the
 1095 * [United States Naval Observatory](http://maia.usno.navy.mil/ser7/).
 1096 *
 1097 * The value stored in `time` will not be modified; it is passed by value.
 1098 *
 1099 * @param time  A date and time for which to calculate an adjusted date and time.
 1100 * @param days  A floating point number of days by which to adjust `time`. May be negative, 0, or positive.
 1101 * @return  A date and time that is conceptually equal to `time + days`.
 1102 */
 1103astro_time_t Astronomy_AddDays(astro_time_t time, double days)
 1104{
 1105    /*
 1106        This is slightly wrong, but the error is tiny.
 1107        We really should be adding to TT, not to UT.
 1108        But using TT would require creating an inverse function for DeltaT,
 1109        which would be quite a bit of extra calculation.
 1110        I estimate the error is in practice on the order of 10^(-7)
 1111        times the value of 'days'.
 1112        This is based on a typical drift of 1 second per year between UT and TT.
 1113    */
 1114
 1115    astro_time_t sum;
 1116
 1117    sum.ut = time.ut + days;
 1118    sum.tt = TerrestrialTime(sum.ut);
 1119    sum.eps = sum.psi = sum.st = NAN;
 1120
 1121    return sum;
 1122}
 1123
 1124/**
 1125 * @brief   Creates an #astro_time_t value from a given calendar date and time.
 1126 *
 1127 * This function is similar to #Astronomy_MakeTime, only it receives a
 1128 * UTC calendar date and time in the form of an #astro_utc_t structure instead of
 1129 * as separate numeric parameters.  Astronomy_TimeFromUtc is the inverse of
 1130 * #Astronomy_UtcFromTime.
 1131 *
 1132 * @param utc   The UTC calendar date and time to be converted to #astro_time_t.
 1133 * @return  A value that can be used for astronomical calculations for the given date and time.
 1134 */
 1135astro_time_t Astronomy_TimeFromUtc(astro_utc_t utc)
 1136{
 1137    return Astronomy_MakeTime(utc.year, utc.month, utc.day, utc.hour, utc.minute, utc.second);
 1138}
 1139
 1140/**
 1141 * @brief Determines the calendar year, month, day, and time from an #astro_time_t value.
 1142 *
 1143 * After calculating the date and time of an astronomical event in the form of
 1144 * an #astro_time_t value, it is often useful to display the result in a human-readable
 1145 * form. This function converts the linear time scales in the `ut` field of #astro_time_t
 1146 * into a calendar date and time: year, month, day, hours, minutes, and seconds, expressed
 1147 * in UTC.
 1148 *
 1149 * @param time  The astronomical time value to be converted to calendar date and time.
 1150 * @return  A date and time broken out into conventional year, month, day, hour, minute, and second.
 1151 */
 1152astro_utc_t Astronomy_UtcFromTime(astro_time_t time)
 1153{
 1154    /* Adapted from the NOVAS C 3.1 function cal_date() */
 1155    astro_utc_t utc;
 1156    int64_t jd, k, m, n;
 1157    double djd, x;
 1158    const int64_t c = 2500;
 1159
 1160    djd = time.ut + 2451545.5;
 1161    jd = (int64_t)floor(djd);
 1162
 1163    x = 24.0 * fmod(djd, 1.0);
 1164    if (x < 0.0)
 1165        x += 24.0;
 1166    utc.hour = (int)x;
 1167    x = 60.0 * fmod(x, 1.0);
 1168    utc.minute = (int)x;
 1169    utc.second = 60.0 * fmod(x, 1.0);
 1170
 1171    /*
 1172        This is my own adjustment to the NOVAS cal_date logic
 1173        so that it can handle dates much farther back in the past.
 1174        I add c*400 years worth of days at the front,
 1175        then subtract c*400 years at the back,
 1176        which avoids negative values in the formulas that mess up
 1177        the calendar date calculations.
 1178        Any multiple of 400 years has the same number of days,
 1179        because it eliminates all the special cases for leap years.
 1180    */
 1181    k = jd + (68569 + c*146097);
 1182    n = (4 * k) / 146097;
 1183    k = k - (146097 * n + 3)/4;
 1184    m = (4000 * (k+1)) / 1461001;
 1185    k = k - (1461 * m)/4 + 31;
 1186
 1187    utc.month = (int) ((80 * k) / 2447);
 1188    utc.day = (int) (k - (2447*utc.month)/80);
 1189    k = utc.month / 11;
 1190
 1191    utc.month = (int) (utc.month + 2 - 12*k);
 1192    utc.year = (int) (100 * (n - 49) + m + k - 400*c);
 1193
 1194    return utc;
 1195}
 1196
 1197
 1198/**
 1199 * @brief Formats an #astro_time_t value as an ISO 8601 string.
 1200 *
 1201 * Given an #astro_time_t value `time`, formats it as an ISO 8601
 1202 * string to the resolution specified by the `format` parameter.
 1203 * The result is stored in the `text` buffer whose capacity in bytes
 1204 * is specified by `size`.
 1205 *
 1206 * @param time
 1207 *      The date and time whose civil time `time.ut` is to be formatted as an ISO 8601 string.
 1208 *      If the civil time is outside the year range -999999 to +999999, the function fails
 1209 *      and returns `ASTRO_BAD_TIME`. Years prior to 1583 are treated as if they are
 1210 *      using the modern Gregorian calendar, even when the Julian calendar was actually in effect.
 1211 *      The year before 1 AD, commonly known as 1 BC, is represented by the value 0.
 1212 *      The year 2 BC is represented by -1, etc.
 1213 *
 1214 * @param format
 1215 *      Specifies the resolution to which the date and time should be formatted,
 1216 *      as explained at #astro_time_format_t.
 1217 *      If the value of `format` is not recognized, the function fails and
 1218 *      returns `ASTRO_INVALID_PARAMETER`.
 1219 *
 1220 * @param text
 1221 *      A pointer to a text buffer to receive the output.
 1222 *      If `text` is `NULL`, this function returns `ASTRO_INVALID_PARAMETER`.
 1223 *      If the function fails for any reason, and `text` is not `NULL`,
 1224 *      and `size` is greater than 0, the `text` buffer is set to an empty string.
 1225 *
 1226 * @param size
 1227 *      The size in bytes of the buffer pointed to by `text`. The buffer must
 1228 *      be large enough to accomodate the output format selected by the
 1229 *      `format` parameter, as specified at #astro_time_format_t.
 1230 *      If `size` is too small to hold the string as specified by `format`,
 1231 *      the `text` buffer is set to `""` (if possible)
 1232 *      and the function returns `ASTRO_BUFFER_TOO_SMALL`.
 1233 *      A buffer that is `TIME_TEXT_BYTES` (28) bytes or larger is always large enough for this function.
 1234 *
 1235 * @return `ASTRO_SUCCESS` on success; otherwise an error as described in the parameter notes.
 1236 */
 1237astro_status_t Astronomy_FormatTime(
 1238    astro_time_t time,
 1239    astro_time_format_t format,
 1240    char *text,
 1241    size_t size)
 1242{
 1243    int nprinted;
 1244    double rounding;
 1245    size_t min_size;
 1246    astro_utc_t utc;
 1247    char ytext[20];   /* worst case: "+999999" = 8 characters including terminal '\0'. But gcc 12.2 still complains! */
 1248
 1249    if (text == NULL)
 1250        return ASTRO_INVALID_PARAMETER;
 1251
 1252    if (size == 0)
 1253        return ASTRO_BUFFER_TOO_SMALL;
 1254
 1255    text[0] = '\0';     /* initialize to empty string, in case an error occurs */
 1256
 1257    /* Validate 'size' parameter and perform date/time rounding. */
 1258    switch (format)
 1259    {
 1260    case TIME_FORMAT_DAY:
 1261        min_size = 11;                          /* "2020-12-31" */
 1262        rounding = 0.0;                         /* no rounding */
 1263        break;
 1264
 1265    case TIME_FORMAT_MINUTE:
 1266        min_size = 18;                          /* "2020-12-31T15:47Z" */
 1267        rounding = 0.5 / (24.0 * 60.0);         /* round to nearest minute */
 1268        break;
 1269
 1270    case TIME_FORMAT_SECOND:
 1271        min_size = 21;                          /* "2020-12-31T15:47:59Z" */
 1272        rounding = 0.5 / (24.0 * 3600.0);       /* round to nearest second */
 1273        break;
 1274
 1275    case TIME_FORMAT_MILLI:
 1276        min_size = 25;                          /* "2020-12-31T15:47:59.123Z" */
 1277        rounding = 0.5 / (24.0 * 3600000.0);    /* round to nearest millisecond */
 1278        break;
 1279
 1280    default:
 1281        return ASTRO_INVALID_PARAMETER;
 1282    }
 1283
 1284    /* Perform rounding. */
 1285    time.ut += rounding;
 1286
 1287    /* Convert linear J2000 days to Gregorian UTC date/time. */
 1288    utc = Astronomy_UtcFromTime(time);
 1289    if (utc.year < -999999 || utc.year > +999999)
 1290        return ASTRO_BAD_TIME;
 1291
 1292    if (utc.year < 0)
 1293    {
 1294        snprintf(ytext, sizeof(ytext), "-%06d", -utc.year);
 1295        min_size += 3;  /* '-' prefix and two extra year digits. */
 1296    }
 1297    else if (utc.year <= 9999)
 1298    {
 1299        snprintf(ytext, sizeof(ytext), "%04d", utc.year);
 1300    }
 1301    else
 1302    {
 1303        snprintf(ytext, sizeof(ytext), "+%06d", utc.year);
 1304        min_size += 3;  /* '+' prefix and two extra year digits. */
 1305    }
 1306
 1307    /* Check for insufficient buffer size. */
 1308    if (size < min_size)
 1309        return ASTRO_BUFFER_TOO_SMALL;
 1310
 1311    /* Format the string. */
 1312    switch (format)
 1313    {
 1314    case TIME_FORMAT_DAY:
 1315        nprinted = snprintf(text, size, "%s-%02d-%02d",
 1316            ytext, utc.month, utc.day);
 1317        break;
 1318
 1319    case TIME_FORMAT_MINUTE:
 1320        nprinted = snprintf(text, size, "%s-%02d-%02dT%02d:%02dZ",
 1321            ytext, utc.month, utc.day,
 1322            utc.hour, utc.minute);
 1323        break;
 1324
 1325    case TIME_FORMAT_SECOND:
 1326        nprinted = snprintf(text, size, "%s-%02d-%02dT%02d:%02d:%02.0lfZ",
 1327            ytext, utc.month, utc.day,
 1328            utc.hour, utc.minute, floor(utc.second));
 1329        break;
 1330
 1331    case TIME_FORMAT_MILLI:
 1332        nprinted = snprintf(text, size, "%s-%02d-%02dT%02d:%02d:%06.3lfZ",
 1333            ytext, utc.month, utc.day,
 1334            utc.hour, utc.minute, floor(1000.0 * utc.second) / 1000.0);
 1335        break;
 1336
 1337    default:
 1338        /* We should have already failed for any unknown 'format' value. */
 1339        return ASTRO_INTERNAL_ERROR;
 1340    }
 1341
 1342    if (nprinted < 0)
 1343        return ASTRO_INTERNAL_ERROR;    /* should not be possible for snprintf to return a negative number */
 1344
 1345    if ((size_t)(1+nprinted) != min_size)
 1346        return ASTRO_INTERNAL_ERROR;    /* there must be a bug calculating min_size or formatting the string */
 1347
 1348    return ASTRO_SUCCESS;
 1349}
 1350
 1351
 1352/**
 1353 * @brief   Creates an observer object that represents a location on or near the surface of the Earth.
 1354 *
 1355 * Some Astronomy Engine functions calculate values pertaining to an observer on the Earth.
 1356 * These functions require a value of type #astro_observer_t that represents the location
 1357 * of such an observer.
 1358 *
 1359 * @param latitude      The geographic latitude of the observer in degrees north (positive) or south (negative) of the equator.
 1360 * @param longitude     The geographic longitude of the observer in degrees east (positive) or west (negative) of the prime meridian at Greenwich, England.
 1361 * @param height        The height of the observer in meters above mean sea level.
 1362 * @return An observer object that can be passed to astronomy functions that require a geographic location.
 1363 */
 1364astro_observer_t Astronomy_MakeObserver(double latitude, double longitude, double height)
 1365{
 1366    astro_observer_t observer;
 1367
 1368    observer.latitude = latitude;
 1369    observer.longitude = longitude;
 1370    observer.height = height;
 1371
 1372    return observer;
 1373}
 1374
 1375static void iau2000b(astro_time_t *time)
 1376{
 1377    /* Truncated and hand-optimized nutation model. */
 1378
 1379    if ((time != NULL) && isnan(time->psi))
 1380    {
 1381        double t, elp, f, d, om, arg, dp, de, sarg, carg;
 1382
 1383        t = time->tt / 36525.0;
 1384        elp = fmod(1287104.79305 + t * 129596581.0481,  ASEC360) * ASEC2RAD;
 1385        f   = fmod(335779.526232 + t * 1739527262.8478, ASEC360) * ASEC2RAD;
 1386        d   = fmod(1072260.70369 + t * 1602961601.2090, ASEC360) * ASEC2RAD;
 1387        om  = fmod(450160.398036 - t * 6962890.5431,    ASEC360) * ASEC2RAD;
 1388
 1389        sarg = sin(om);
 1390        carg = cos(om);
 1391        dp = (-172064161.0 - 174666.0*t)*sarg + 33386.0*carg;
 1392        de = (92052331.0 + 9086.0*t)*carg + 15377.0*sarg;
 1393
 1394        arg = 2.0*(f - d + om);
 1395        sarg = sin(arg);
 1396        carg = cos(arg);
 1397        dp += (-13170906.0 - 1675.0*t)*sarg - 13696.0*carg;
 1398        de += (5730336.0 - 3015.0*t)*carg - 4587.0*sarg;
 1399
 1400        arg = 2.0*(f + om);
 1401        sarg = sin(arg);
 1402        carg = cos(arg);
 1403        dp += (-2276413.0 - 234.0*t)*sarg + 2796.0*carg;
 1404        de += (978459.0 - 485.0*t)*carg + 1374.0*sarg;
 1405
 1406        arg = 2.0*om;
 1407        sarg = sin(arg);
 1408        carg = cos(arg);
 1409        dp += (2074554.0 + 207.0*t)*sarg - 698.0*carg;
 1410        de += (-897492.0 + 470.0*t)*carg - 291.0*sarg;
 1411
 1412        sarg = sin(elp);
 1413        carg = cos(elp);
 1414        dp += (1475877.0 - 3633.0*t)*sarg + 11817.0*carg;
 1415        de += (73871.0 - 184.0*t)*carg - 1924.0*sarg;
 1416
 1417        time->psi = -0.000135 + (dp * 1.0e-7);
 1418        time->eps = +0.000388 + (de * 1.0e-7);
 1419    }
 1420}
 1421
 1422static double mean_obliq(double tt)
 1423{
 1424    double t = tt / 36525.0;
 1425    double asec =
 1426        (((( -  0.0000000434   * t
 1427             -  0.000000576  ) * t
 1428             +  0.00200340   ) * t
 1429             -  0.0001831    ) * t
 1430             - 46.836769     ) * t + 84381.406;
 1431
 1432    return asec / 3600.0;
 1433}
 1434
 1435/** @cond DOXYGEN_SKIP */
 1436typedef struct
 1437{
 1438    double tt;
 1439    double dpsi;
 1440    double deps;
 1441    double ee;
 1442    double mobl;
 1443    double tobl;
 1444}
 1445earth_tilt_t;
 1446/** @endcond */
 1447
 1448static earth_tilt_t e_tilt(astro_time_t *time)
 1449{
 1450    earth_tilt_t et;
 1451
 1452    /* There is no good answer for what to do if time==NULL. Callers must prevent! */
 1453
 1454    iau2000b(time);
 1455    et.dpsi = time->psi;
 1456    et.deps = time->eps;
 1457    et.mobl = mean_obliq(time->tt);
 1458    et.tobl = et.mobl + (et.deps / 3600.0);
 1459    et.tt = time->tt;
 1460    et.ee = et.dpsi * cos(et.mobl * DEG2RAD) / 15.0;
 1461
 1462    return et;
 1463}
 1464
 1465static void obl_ecl2equ_vec(double obl, astro_time_t time, const double ecl[3], double equ[3])
 1466{
 1467    double obl_rad = obl * DEG2RAD;
 1468    double cos_obl = cos(obl_rad);
 1469    double sin_obl = sin(obl_rad);
 1470
 1471    equ[0] = ecl[0];
 1472    equ[1] = ecl[1]*cos_obl - ecl[2]*sin_obl;
 1473    equ[2] = ecl[1]*sin_obl + ecl[2]*cos_obl;
 1474}
 1475
 1476static void ecl2equ_vec(astro_time_t time, const double ecl[3], double equ[3])
 1477{
 1478    double obl = mean_obliq(time.tt);
 1479    obl_ecl2equ_vec(obl, time, ecl, equ);
 1480}
 1481
 1482static astro_rotation_t precession_rot(astro_time_t time, precess_dir_t dir)
 1483{
 1484    /*
 1485        dir==INTO_2000: converts mean equator of date (EQM) to J2000 mean equator (EQJ).
 1486        dir==FROM_2000: converts J2000 mean equator (EQJ) to mean equator of date (EQM).
 1487    */
 1488
 1489    astro_rotation_t rotation;
 1490    double xx, yx, zx, xy, yy, zy, xz, yz, zz;
 1491    double t, psia, omegaa, chia, sa, ca, sb, cb, sc, cc, sd, cd;
 1492    double eps0 = 84381.406;
 1493
 1494    t = time.tt / 36525;
 1495
 1496    psia   = (((((-    0.0000000951  * t
 1497                 +    0.000132851 ) * t
 1498                 -    0.00114045  ) * t
 1499                 -    1.0790069   ) * t
 1500                 + 5038.481507    ) * t);
 1501
 1502    omegaa = (((((+    0.0000003337  * t
 1503                 -    0.000000467 ) * t
 1504                 -    0.00772503  ) * t
 1505                 +    0.0512623   ) * t
 1506                 -    0.025754    ) * t + eps0);
 1507
 1508    chia   = (((((-    0.0000000560  * t
 1509                 +    0.000170663 ) * t
 1510                 -    0.00121197  ) * t
 1511                 -    2.3814292   ) * t
 1512                 +   10.556403    ) * t);
 1513
 1514    eps0 = eps0 * ASEC2RAD;
 1515    psia = psia * ASEC2RAD;
 1516    omegaa = omegaa * ASEC2RAD;
 1517    chia = chia * ASEC2RAD;
 1518
 1519    sa = sin(eps0);
 1520    ca = cos(eps0);
 1521    sb = sin(-psia);
 1522    cb = cos(-psia);
 1523    sc = sin(-omegaa);
 1524    cc = cos(-omegaa);
 1525    sd = sin(chia);
 1526    cd = cos(chia);
 1527
 1528    xx =  cd * cb - sb * sd * cc;
 1529    yx =  cd * sb * ca + sd * cc * cb * ca - sa * sd * sc;
 1530    zx =  cd * sb * sa + sd * cc * cb * sa + ca * sd * sc;
 1531    xy = -sd * cb - sb * cd * cc;
 1532    yy = -sd * sb * ca + cd * cc * cb * ca - sa * cd * sc;
 1533    zy = -sd * sb * sa + cd * cc * cb * sa + ca * cd * sc;
 1534    xz =  sb * sc;
 1535    yz = -sc * cb * ca - sa * cc;
 1536    zz = -sc * cb * sa + cc * ca;
 1537
 1538    if (dir == INTO_2000)
 1539    {
 1540        /* Perform rotation from other epoch to J2000.0. */
 1541        rotation.rot[0][0] = xx;
 1542        rotation.rot[0][1] = yx;
 1543        rotation.rot[0][2] = zx;
 1544        rotation.rot[1][0] = xy;
 1545        rotation.rot[1][1] = yy;
 1546        rotation.rot[1][2] = zy;
 1547        rotation.rot[2][0] = xz;
 1548        rotation.rot[2][1] = yz;
 1549        rotation.rot[2][2] = zz;
 1550    }
 1551    else
 1552    {
 1553        /* Perform rotation from J2000.0 to other epoch. */
 1554        rotation.rot[0][0] = xx;
 1555        rotation.rot[0][1] = xy;
 1556        rotation.rot[0][2] = xz;
 1557        rotation.rot[1][0] = yx;
 1558        rotation.rot[1][1] = yy;
 1559        rotation.rot[1][2] = yz;
 1560        rotation.rot[2][0] = zx;
 1561        rotation.rot[2][1] = zy;
 1562        rotation.rot[2][2] = zz;
 1563    }
 1564
 1565    rotation.status = ASTRO_SUCCESS;
 1566    return rotation;
 1567}
 1568
 1569
 1570static void rotate(const double invec[3], const double rot[3][3], double outvec[3])
 1571{
 1572    outvec[0] = rot[0][0]*invec[0] + rot[1][0]*invec[1] + rot[2][0]*invec[2];
 1573    outvec[1] = rot[0][1]*invec[0] + rot[1][1]*invec[1] + rot[2][1]*invec[2];
 1574    outvec[2] = rot[0][2]*invec[0] + rot[1][2]*invec[1] + rot[2][2]*invec[2];
 1575}
 1576
 1577static void precession(
 1578    const double pos1[3],
 1579    astro_time_t time,
 1580    precess_dir_t dir,
 1581    double pos2[3])
 1582{
 1583    astro_rotation_t r = precession_rot(time, dir);
 1584    rotate(pos1, r.rot, pos2);
 1585}
 1586
 1587static void precession_posvel(
 1588    const double pos1[3],
 1589    const double vel1[3],
 1590    astro_time_t time,
 1591    precess_dir_t dir,
 1592    double pos2[3],
 1593    double vel2[3])
 1594{
 1595    astro_rotation_t r = precession_rot(time, dir);
 1596    rotate(pos1, r.rot, pos2);
 1597    rotate(vel1, r.rot, vel2);
 1598}
 1599
 1600
 1601static astro_equatorial_t vector2radec(const double pos[3], astro_time_t time)
 1602{
 1603    astro_equatorial_t equ;
 1604    double xyproj;
 1605
 1606    /* Copy the cartesian coordinates from the input into the returned structure. */
 1607    equ.vec.status = ASTRO_SUCCESS;
 1608    equ.vec.t = time;
 1609    equ.vec.x = pos[0];
 1610    equ.vec.y = pos[1];
 1611    equ.vec.z = pos[2];
 1612
 1613    /* Calculate spherical coordinates: RA, DEC, distance. */
 1614    xyproj = pos[0]*pos[0] + pos[1]*pos[1];
 1615    equ.dist = sqrt(xyproj + pos[2]*pos[2]);
 1616    equ.status = ASTRO_SUCCESS;
 1617    if (xyproj == 0.0)
 1618    {
 1619        if (pos[2] == 0.0)
 1620        {
 1621            /* Indeterminate coordinates; pos vector has zero length. */
 1622            equ = EquError(ASTRO_BAD_VECTOR);
 1623        }
 1624        else if (pos[2] < 0)
 1625        {
 1626            equ.ra = 0.0;
 1627            equ.dec = -90.0;
 1628        }
 1629        else
 1630        {
 1631            equ.ra = 0.0;
 1632            equ.dec = +90.0;
 1633        }
 1634    }
 1635    else
 1636    {
 1637        equ.ra = RAD2HOUR * atan2(pos[1], pos[0]);
 1638        if (equ.ra < 0)
 1639            equ.ra += 24.0;
 1640
 1641        equ.dec = RAD2DEG * atan2(pos[2], sqrt(xyproj));
 1642    }
 1643
 1644    return equ;
 1645}
 1646
 1647
 1648static astro_rotation_t nutation_rot(astro_time_t *time, precess_dir_t dir)
 1649{
 1650    /*
 1651        Creates a rotation matrix that adds/removes nutation from
 1652        an equatorial vector of date:
 1653
 1654        The `dir` parameter is a little misleading, but reflects the
 1655        common task of working together with precession_rot() to
 1656        convert mean equator of 2000 to/from true equator of date.
 1657        Here is the actual result of `dir`:
 1658
 1659        dir==INTO_2000: Subtract nutation from true equator of date (EQD)
 1660        to produce mean equator of date (EQM).
 1661
 1662        dir==FROM_2000: Add nutation from mean equator of date (EQM) to
 1663        produce true equator of date (EQD).
 1664    */
 1665
 1666    astro_rotation_t rotation;
 1667    earth_tilt_t tilt;
 1668    double oblm, oblt, psi, cobm, sobm, cobt, sobt, cpsi, spsi;
 1669    double xx, yx, zx, xy, yy, zy, xz, yz, zz;
 1670
 1671    if (time == NULL)
 1672        return RotationErr(ASTRO_INVALID_PARAMETER);
 1673
 1674    tilt = e_tilt(time);
 1675    oblm = tilt.mobl * DEG2RAD;
 1676    oblt = tilt.tobl * DEG2RAD;
 1677    psi = tilt.dpsi * ASEC2RAD;
 1678    cobm = cos(oblm);
 1679    sobm = sin(oblm);
 1680    cobt = cos(oblt);
 1681    sobt = sin(oblt);
 1682    cpsi = cos(psi);
 1683    spsi = sin(psi);
 1684
 1685    xx = cpsi;
 1686    yx = -spsi * cobm;
 1687    zx = -spsi * sobm;
 1688    xy = spsi * cobt;
 1689    yy = cpsi * cobm * cobt + sobm * sobt;
 1690    zy = cpsi * sobm * cobt - cobm * sobt;
 1691    xz = spsi * sobt;
 1692    yz = cpsi * cobm * sobt - sobm * cobt;
 1693    zz = cpsi * sobm * sobt + cobm * cobt;
 1694
 1695    if (dir == FROM_2000)
 1696    {
 1697        /* convert J2000 to of-date */
 1698        rotation.rot[0][0] = xx;
 1699        rotation.rot[0][1] = xy;
 1700        rotation.rot[0][2] = xz;
 1701        rotation.rot[1][0] = yx;
 1702        rotation.rot[1][1] = yy;
 1703        rotation.rot[1][2] = yz;
 1704        rotation.rot[2][0] = zx;
 1705        rotation.rot[2][1] = zy;
 1706        rotation.rot[2][2] = zz;
 1707    }
 1708    else
 1709    {
 1710        /* convert of-date to J2000 */
 1711        rotation.rot[0][0] = xx;
 1712        rotation.rot[0][1] = yx;
 1713        rotation.rot[0][2] = zx;
 1714        rotation.rot[1][0] = xy;
 1715        rotation.rot[1][1] = yy;
 1716        rotation.rot[1][2] = zy;
 1717        rotation.rot[2][0] = xz;
 1718        rotation.rot[2][1] = yz;
 1719        rotation.rot[2][2] = zz;
 1720    }
 1721
 1722    rotation.status = ASTRO_SUCCESS;
 1723    return rotation;
 1724}
 1725
 1726static void nutation(
 1727    const double inpos[3],
 1728    astro_time_t *time,
 1729    precess_dir_t dir,
 1730    double outpos[3])
 1731{
 1732    astro_rotation_t r = nutation_rot(time, dir);
 1733    rotate(inpos, r.rot, outpos);
 1734}
 1735
 1736static void nutation_posvel(
 1737    const double inpos[3],
 1738    const double invel[3],
 1739    astro_time_t *time,
 1740    precess_dir_t dir,
 1741    double outpos[3],
 1742    double outvel[3])
 1743{
 1744    astro_rotation_t r = nutation_rot(time, dir);
 1745    rotate(inpos, r.rot, outpos);
 1746    rotate(invel, r.rot, outvel);
 1747}
 1748
 1749static double era(double ut)        /* Earth Rotation Angle */
 1750{
 1751    double thet1 = 0.7790572732640 + 0.00273781191135448 * ut;
 1752    double thet3 = fmod(ut, 1.0);
 1753    double theta = 360.0 * fmod(thet1 + thet3, 1.0);
 1754    if (theta < 0.0)
 1755        theta += 360.0;
 1756
 1757    return theta;
 1758}
 1759
 1760/**
 1761 * @brief Calculates Greenwich Apparent Sidereal Time (GAST).
 1762 *
 1763 * Given a date and time, this function calculates the rotation of the
 1764 * Earth, represented by the equatorial angle of the Greenwich prime meridian
 1765 * with respect to distant stars (not the Sun, which moves relative to background
 1766 * stars by almost one degree per day).
 1767 * This angle is called Greenwich Apparent Sidereal Time (GAST).
 1768 * GAST is measured in sidereal hours in the half-open range [0, 24).
 1769 * When GAST = 0, it means the prime meridian is aligned with the of-date equinox,
 1770 * corrected at that time for precession and nutation of the Earth's axis.
 1771 * In this context, the "equinox" is the direction in space where the Earth's
 1772 * orbital plane (the ecliptic) intersects with the plane of the Earth's equator,
 1773 * at the location on the Earth's orbit of the (seasonal) March equinox.
 1774 * As the Earth rotates, GAST increases from 0 up to 24 sidereal hours,
 1775 * then starts over at 0.
 1776 * To convert to degrees, multiply the return value by 15.
 1777 *
 1778 * @param time
 1779 *      The date and time for which to find GAST.
 1780 *      The parameter is passed by address because it can be modified by the call:
 1781 *      As an optimization, this function caches the sidereal time value in `time`,
 1782 *      unless it has already been cached, in which case the cached value is reused.
 1783 *      If the `time` pointer is NULL, this function returns a NAN value.
 1784 *
 1785 * @returns {number}
 1786 */
 1787double Astronomy_SiderealTime(astro_time_t *time)
 1788{
 1789    if (time == NULL)
 1790        return NAN;
 1791
 1792    if (isnan(time->st))
 1793    {
 1794        double t = time->tt / 36525.0;
 1795        double eqeq = 15.0 * e_tilt(time).ee;    /* Replace with eqeq=0 to get GMST instead of GAST (if we ever need it) */
 1796        double theta = era(time->ut);
 1797        double st = (eqeq + 0.014506 +
 1798            (((( -    0.0000000368   * t
 1799                -    0.000029956  ) * t
 1800                -    0.00000044   ) * t
 1801                +    1.3915817    ) * t
 1802                + 4612.156534     ) * t);
 1803
 1804        double gst = fmod(st/3600.0 + theta, 360.0) / 15.0;
 1805        if (gst < 0.0)
 1806            gst += 24.0;
 1807
 1808        time->st = gst;
 1809    }
 1810
 1811    return time->st;     /* return sidereal hours in the half-open range [0, 24). */
 1812}
 1813
 1814static astro_observer_t inverse_terra(const double ovec[3], double st)
 1815{
 1816    double x, y, z, p, F, W, D, c, s, c2, s2;
 1817    double lon_deg, lat_deg, lat, radicand, factor, denom, adjust;
 1818    double height_km, stlocl;
 1819    astro_observer_t observer;
 1820    int count;
 1821
 1822    /* Convert from AU to kilometers. */
 1823    x = ovec[0] * KM_PER_AU;
 1824    y = ovec[1] * KM_PER_AU;
 1825    z = ovec[2] * KM_PER_AU;
 1826    p = hypot(x, y);
 1827    if (p < 1.0e-6)
 1828    {
 1829        /* Special case: within 1 millimeter of a pole! */
 1830        /* Use arbitrary longitude, and latitude determined by polarity of z. */
 1831        lon_deg = 0.0;
 1832        lat_deg = (z > 0.0) ? +90.0 : -90.0;
 1833        /* Elevation is calculated directly from z */
 1834        height_km = fabs(z) - EARTH_POLAR_RADIUS_KM;
 1835    }
 1836    else
 1837    {
 1838        stlocl = atan2(y, x);
 1839        /* Calculate exact longitude. */
 1840        lon_deg = RAD2DEG*stlocl - (15.0 * st);
 1841        /* Normalize longitude to the range (-180, +180]. */
 1842        while (lon_deg <= -180.0)
 1843            lon_deg += 360.0;
 1844        while (lon_deg > +180.0)
 1845            lon_deg -= 360.0;
 1846        /* Numerically solve for exact latitude, using Newton's Method. */
 1847        F = EARTH_FLATTENING * EARTH_FLATTENING;
 1848        /* Start with initial latitude estimate, based on a spherical Earth. */
 1849        lat = atan2(z, p);
 1850        for (count = 0; ; ++count)
 1851        {
 1852            if (count > 10)
 1853            {
 1854                fprintf(stderr, "\nFATAL(inverse_terra): did not converge!\n");
 1855                exit(1);
 1856            }
 1857
 1858            /* Calculate the error function W(lat). */
 1859            /* We try to find the root of W, meaning where the error is 0. */
 1860            c = cos(lat);
 1861            s = sin(lat);
 1862            factor = (F-1)*EARTH_EQUATORIAL_RADIUS_KM;
 1863            c2 = c*c;
 1864            s2 = s*s;
 1865            radicand = c2 + F*s2;
 1866            denom = sqrt(radicand);
 1867            W = (factor*s*c)/denom - z*c + p*s;
 1868            if (fabs(W) < 1.0e-8)
 1869                break;  /* The error is now negligible. */
 1870            /* Error is still too large. Find the next estimate. */
 1871            /* Calculate D = the derivative of W with respect to lat. */
 1872            D = factor*((c2 - s2)/denom - s2*c2*(F-1)/(factor*radicand)) + z*s + p*c;
 1873            lat -= W/D;
 1874        }
 1875        /* We now have a solution for the latitude in radians. */
 1876        lat_deg = lat * RAD2DEG;
 1877        /* Solve for exact height in meters. */
 1878        /* There are two formulas I can use. Use whichever has the less risky denominator. */
 1879        adjust = EARTH_EQUATORIAL_RADIUS_KM / denom;
 1880        if (fabs(s) > fabs(c))
 1881            height_km = z/s - F*adjust;
 1882        else
 1883            height_km = p/c - adjust;
 1884    }
 1885
 1886    observer.latitude = lat_deg;
 1887    observer.longitude = lon_deg;
 1888    observer.height = 1000.0 * height_km;
 1889    return observer;
 1890}
 1891
 1892static void terra(astro_observer_t observer, double st, double pos[3], double vel[3])
 1893{
 1894    static const double ANGVEL = 7.2921150e-5;
 1895
 1896    double phi = observer.latitude * DEG2RAD;
 1897    double sinphi = sin(phi);
 1898    double cosphi = cos(phi);
 1899    double c = 1.0 / hypot(cosphi, sinphi*EARTH_FLATTENING);
 1900    double s = c * (EARTH_FLATTENING * EARTH_FLATTENING);
 1901    double ht_km = observer.height / 1000.0;
 1902    double ach = EARTH_EQUATORIAL_RADIUS_KM*c + ht_km;
 1903    double ash = EARTH_EQUATORIAL_RADIUS_KM*s + ht_km;
 1904    double stlocl = (15.0*st + observer.longitude) * DEG2RAD;
 1905    double sinst = sin(stlocl);
 1906    double cosst = cos(stlocl);
 1907
 1908    if (pos != NULL)
 1909    {
 1910        pos[0] = ach * cosphi * cosst / KM_PER_AU;
 1911        pos[1] = ach * cosphi * sinst / KM_PER_AU;
 1912        pos[2] = ash * sinphi / KM_PER_AU;
 1913    }
 1914
 1915    if (vel != NULL)
 1916    {
 1917        vel[0] = -(ANGVEL * 86400.0 / KM_PER_AU) * ach * cosphi * sinst;
 1918        vel[1] = +(ANGVEL * 86400.0 / KM_PER_AU) * ach * cosphi * cosst;
 1919        vel[2] = 0.0;
 1920    }
 1921}
 1922
 1923static void geo_pos(astro_time_t *time, astro_observer_t observer, double pos[3])
 1924{
 1925    double gast;
 1926    double pos1[3], pos2[3];
 1927
 1928    if (time == NULL)
 1929    {
 1930        pos[0] = pos[1] = pos[2] = NAN;
 1931    }
 1932    else
 1933    {
 1934        gast = Astronomy_SiderealTime(time);
 1935        terra(observer, gast, pos1, NULL);
 1936        nutation(pos1, time, INTO_2000, pos2);
 1937        precession(pos2, *time, INTO_2000, pos);
 1938    }
 1939}
 1940
 1941static void spin(double angle, const double pos1[3], double vec2[3])
 1942{
 1943    double angr = angle * DEG2RAD;
 1944    double cosang = cos(angr);
 1945    double sinang = sin(angr);
 1946    vec2[0] = +cosang*pos1[0] + sinang*pos1[1];
 1947    vec2[1] = -sinang*pos1[0] + cosang*pos1[1];
 1948    vec2[2] = pos1[2];
 1949}
 1950
 1951/*------------------ CalcMoon ------------------*/
 1952
 1953/** @cond DOXYGEN_SKIP */
 1954
 1955#define DECLARE_PASCAL_ARRAY_1(elemtype,name,xmin,xmax) \
 1956    elemtype name[(xmax)-(xmin)+1]
 1957
 1958#define DECLARE_PASCAL_ARRAY_2(elemtype,name,xmin,xmax,ymin,ymax) \
 1959    elemtype name[(xmax)-(xmin)+1][(ymax)-(ymin)+1]
 1960
 1961#define ACCESS_PASCAL_ARRAY_1(name,xmin,x) \
 1962    ((name)[(x)-(xmin)])
 1963
 1964#define ACCESS_PASCAL_ARRAY_2(name,xmin,ymin,x,y) \
 1965    ((name)[(x)-(xmin)][(y)-(ymin)])
 1966
 1967typedef struct
 1968{
 1969    double t;
 1970    double dgam;
 1971    double dlam, n, gam1c, sinpi;
 1972    double l0, l, ls, f, d, s;
 1973    double dl0, dl, dls, df, dd, ds;
 1974    DECLARE_PASCAL_ARRAY_2(double,co,-6,6,1,4);   /* ARRAY[-6..6,1..4] OF REAL */
 1975    DECLARE_PASCAL_ARRAY_2(double,si,-6,6,1,4);   /* ARRAY[-6..6,1..4] OF REAL */
 1976}
 1977MoonContext;
 1978
 1979#define T           (ctx->t)
 1980#define DGAM        (ctx->dgam)
 1981#define DLAM        (ctx->dlam)
 1982#define N           (ctx->n)
 1983#define GAM1C       (ctx->gam1c)
 1984#define SINPI       (ctx->sinpi)
 1985#define L0          (ctx->l0)
 1986#define L           (ctx->l)
 1987#define LS          (ctx->ls)
 1988#define F           (ctx->f)
 1989#define D           (ctx->d)
 1990#define S           (ctx->s)
 1991#define DL0         (ctx->dl0)
 1992#define DL          (ctx->dl)
 1993#define DLS         (ctx->dls)
 1994#define DF          (ctx->df)
 1995#define DD          (ctx->dd)
 1996#define DS          (ctx->ds)
 1997#define CO(x,y)     ACCESS_PASCAL_ARRAY_2(ctx->co,-6,1,x,y)
 1998#define SI(x,y)     ACCESS_PASCAL_ARRAY_2(ctx->si,-6,1,x,y)
 1999
 2000static double Frac(double x)
 2001{
 2002    return x - floor(x);
 2003}
 2004
 2005static void AddThe(
 2006    double c1, double s1, double c2, double s2,
 2007    double *c, double *s)
 2008{
 2009    *c = c1*c2 - s1*s2;
 2010    *s = s1*c2 + c1*s2;
 2011}
 2012
 2013static double Sine(double phi)
 2014{
 2015    /* sine, of phi in revolutions, not radians */
 2016    return sin(PI2 * phi);
 2017}
 2018
 2019static void LongPeriodic(MoonContext *ctx)
 2020{
 2021    double S1 = Sine(0.19833+0.05611*T);
 2022    double S2 = Sine(0.27869+0.04508*T);
 2023    double S3 = Sine(0.16827-0.36903*T);
 2024    double S4 = Sine(0.34734-5.37261*T);
 2025    double S5 = Sine(0.10498-5.37899*T);
 2026    double S6 = Sine(0.42681-0.41855*T);
 2027    double S7 = Sine(0.14943-5.37511*T);
 2028
 2029    DL0 = 0.84*S1+0.31*S2+14.27*S3+ 7.26*S4+ 0.28*S5+0.24*S6;
 2030    DL  = 2.94*S1+0.31*S2+14.27*S3+ 9.34*S4+ 1.12*S5+0.83*S6;
 2031    DLS =-6.40*S1                                   -1.89*S6;
 2032    DF  = 0.21*S1+0.31*S2+14.27*S3-88.70*S4-15.30*S5+0.24*S6-1.86*S7;
 2033    DD  = DL0-DLS;
 2034    DGAM  = -3332E-9 * Sine(0.59734-5.37261*T)
 2035             -539E-9 * Sine(0.35498-5.37899*T)
 2036              -64E-9 * Sine(0.39943-5.37511*T);
 2037}
 2038
 2039static void Init(MoonContext *ctx)
 2040{
 2041    int I, J, MAX;
 2042    double T2, ARG, FAC;
 2043
 2044    T2 = T*T;
 2045    DLAM = 0;
 2046    DS = 0;
 2047    GAM1C = 0;
 2048    SINPI = 3422.7000;
 2049    LongPeriodic(ctx);
 2050    L0 = PI2*Frac(0.60643382+1336.85522467*T-0.00000313*T2) + DL0/ARC;
 2051    L  = PI2*Frac(0.37489701+1325.55240982*T+0.00002565*T2) + DL /ARC;
 2052    LS = PI2*Frac(0.99312619+  99.99735956*T-0.00000044*T2) + DLS/ARC;
 2053    F  = PI2*Frac(0.25909118+1342.22782980*T-0.00000892*T2) + DF /ARC;
 2054    D  = PI2*Frac(0.82736186+1236.85308708*T-0.00000397*T2) + DD /ARC;
 2055    for (I=1; I<=4; ++I)
 2056    {
 2057        switch(I)
 2058        {
 2059            case 1:  ARG=L;  MAX=4; FAC=1.000002208;               break;
 2060            case 2:  ARG=LS; MAX=3; FAC=0.997504612-0.002495388*T; break;
 2061            case 3:  ARG=F;  MAX=4; FAC=1.000002708+139.978*DGAM;  break;
 2062            default: ARG=D;  MAX=6; FAC=1.0;                       break;
 2063        }
 2064        CO(0,I) = 1.0;
 2065        CO(1,I) = cos(ARG)*FAC;
 2066        SI(0,I) = 0.0;
 2067        SI(1,I) = sin(ARG)*FAC;
 2068        for (J=2; J<=MAX; ++J)
 2069            AddThe(CO(J-1,I), SI(J-1,I), CO(1,I), SI(1,I), &CO(J,I), &SI(J,I));
 2070
 2071        for (J=1; J<=MAX; ++J)
 2072        {
 2073            CO(-J,I) =  CO(J,I);
 2074            SI(-J,I) = -SI(J,I);
 2075        }
 2076    }
 2077}
 2078
 2079static void Term(const MoonContext *ctx, int p, int q, int r, int s, double *x, double *y)
 2080{
 2081    int k;
 2082    DECLARE_PASCAL_ARRAY_1(int, i, 1, 4);
 2083    #define I(n) ACCESS_PASCAL_ARRAY_1(i,1,n)
 2084
 2085    I(1) = p;
 2086    I(2) = q;
 2087    I(3) = r;
 2088    I(4) = s;
 2089    *x = 1.0;
 2090    *y = 0.0;
 2091
 2092    for (k=1; k<=4; ++k)
 2093        if (I(k) != 0.0)
 2094            AddThe(*x, *y, CO(I(k), k), SI(I(k), k), x, y);
 2095
 2096    #undef I
 2097}
 2098
 2099static void AddSol(
 2100    MoonContext *ctx,
 2101    double coeffl,
 2102    double coeffs,
 2103    double coeffg,
 2104    double coeffp,
 2105    int p,
 2106    int q,
 2107    int r,
 2108    int s)
 2109{
 2110    double x, y;
 2111    Term(ctx, p, q, r, s, &x, &y);
 2112    DLAM += coeffl*y;
 2113    DS += coeffs*y;
 2114    GAM1C += coeffg*x;
 2115    SINPI += coeffp*x;
 2116}
 2117
 2118#define ADDN(coeffn,p,q,r,s)    ( Term(ctx, (p),(q),(r),(s),&x,&y), (N += (coeffn)*y) )
 2119
 2120static void SolarN(MoonContext *ctx)
 2121{
 2122    double x, y;
 2123
 2124    N = 0.0;
 2125    ADDN(-526.069, 0, 0,1,-2);
 2126    ADDN(  -3.352, 0, 0,1,-4);
 2127    ADDN( +44.297,+1, 0,1,-2);
 2128    ADDN(  -6.000,+1, 0,1,-4);
 2129    ADDN( +20.599,-1, 0,1, 0);
 2130    ADDN( -30.598,-1, 0,1,-2);
 2131    ADDN( -24.649,-2, 0,1, 0);
 2132    ADDN(  -2.000,-2, 0,1,-2);
 2133    ADDN( -22.571, 0,+1,1,-2);
 2134    ADDN( +10.985, 0,-1,1,-2);
 2135}
 2136
 2137static void Planetary(MoonContext *ctx)
 2138{
 2139    DLAM +=
 2140        +0.82*Sine(0.7736  -62.5512*T)+0.31*Sine(0.0466 -125.1025*T)
 2141        +0.35*Sine(0.5785  -25.1042*T)+0.66*Sine(0.4591+1335.8075*T)
 2142        +0.64*Sine(0.3130  -91.5680*T)+1.14*Sine(0.1480+1331.2898*T)
 2143        +0.21*Sine(0.5918+1056.5859*T)+0.44*Sine(0.5784+1322.8595*T)
 2144        +0.24*Sine(0.2275   -5.7374*T)+0.28*Sine(0.2965   +2.6929*T)
 2145        +0.33*Sine(0.3132   +6.3368*T);
 2146}
 2147
 2148int _CalcMoonCount;     /* Undocumented global for performance tuning. */
 2149
 2150static void CalcMoon(
 2151    double centuries_since_j2000,
 2152    double *geo_eclip_lon,      /* (LAMBDA) equinox of date */
 2153    double *geo_eclip_lat,      /* (BETA)   equinox of date */
 2154    double *distance_au)        /* (R) */
 2155{
 2156    double lat_seconds;
 2157    MoonContext context;
 2158    MoonContext *ctx = &context;    /* goofy, but makes macros work inside this function */
 2159
 2160    context.t = centuries_since_j2000;
 2161    Init(ctx);
 2162
 2163    AddSol(ctx,    13.9020,    14.0600,    -0.0010,     0.2607, 0, 0, 0, 4);
 2164    AddSol(ctx,     0.4030,    -4.0100,     0.3940,     0.0023, 0, 0, 0, 3);
 2165    AddSol(ctx,  2369.9120,  2373.3600,     0.6010,    28.2333, 0, 0, 0, 2);
 2166    AddSol(ctx,  -125.1540,  -112.7900,    -0.7250,    -0.9781, 0, 0, 0, 1);
 2167    AddSol(ctx,     1.9790,     6.9800,    -0.4450,     0.0433, 1, 0, 0, 4);
 2168    AddSol(ctx,   191.9530,   192.7200,     0.0290,     3.0861, 1, 0, 0, 2);
 2169    AddSol(ctx,    -8.4660,   -13.5100,     0.4550,    -0.1093, 1, 0, 0, 1);
 2170    AddSol(ctx, 22639.5000, 22609.0700,     0.0790,   186.5398, 1, 0, 0, 0);
 2171    AddSol(ctx,    18.6090,     3.5900,    -0.0940,     0.0118, 1, 0, 0,-1);
 2172    AddSol(ctx, -4586.4650, -4578.1300,    -0.0770,    34.3117, 1, 0, 0,-2);
 2173    AddSol(ctx,     3.2150,     5.4400,     0.1920,    -0.0386, 1, 0, 0,-3);
 2174    AddSol(ctx,   -38.4280,   -38.6400,     0.0010,     0.6008, 1, 0, 0,-4);
 2175    AddSol(ctx,    -0.3930,    -1.4300,    -0.0920,     0.0086, 1, 0, 0,-6);
 2176    AddSol(ctx,    -0.2890,    -1.5900,     0.1230,    -0.0053, 0, 1, 0, 4);
 2177    AddSol(ctx,   -24.4200,   -25.1000,     0.0400,    -0.3000, 0, 1, 0, 2);
 2178    AddSol(ctx,    18.0230,    17.9300,     0.0070,     0.1494, 0, 1, 0, 1);
 2179    AddSol(ctx,  -668.1460,  -126.9800,    -1.3020,    -0.3997, 0, 1, 0, 0);
 2180    AddSol(ctx,     0.5600,     0.3200,    -0.0010,    -0.0037, 0, 1, 0,-1);
 2181    AddSol(ctx,  -165.1450,  -165.0600,     0.0540,     1.9178, 0, 1, 0,-2);
 2182    AddSol(ctx,    -1.8770,    -6.4600,    -0.4160,     0.0339, 0, 1, 0,-4);
 2183    AddSol(ctx,     0.2130,     1.0200,    -0.0740,     0.0054, 2, 0, 0, 4);
 2184    AddSol(ctx,    14.3870,    14.7800,    -0.0170,     0.2833, 2, 0, 0, 2);
 2185    AddSol(ctx,    -0.5860,    -1.2000,     0.0540,    -0.0100, 2, 0, 0, 1);
 2186    AddSol(ctx,   769.0160,   767.9600,     0.1070,    10.1657, 2, 0, 0, 0);
 2187    AddSol(ctx,     1.7500,     2.0100,    -0.0180,     0.0155, 2, 0, 0,-1);
 2188    AddSol(ctx,  -211.6560,  -152.5300,     5.6790,    -0.3039, 2, 0, 0,-2);
 2189    AddSol(ctx,     1.2250,     0.9100,    -0.0300,    -0.0088, 2, 0, 0,-3);
 2190    AddSol(ctx,   -30.7730,   -34.0700,    -0.3080,     0.3722, 2, 0, 0,-4);
 2191    AddSol(ctx,    -0.5700,    -1.4000,    -0.0740,     0.0109, 2, 0, 0,-6);
 2192    AddSol(ctx,    -2.9210,   -11.7500,     0.7870,    -0.0484, 1, 1, 0, 2);
 2193    AddSol(ctx,     1.2670,     1.5200,    -0.0220,     0.0164, 1, 1, 0, 1);
 2194    AddSol(ctx,  -109.6730,  -115.1800,     0.4610,    -0.9490, 1, 1, 0, 0);
 2195    AddSol(ctx,  -205.9620,  -182.3600,     2.0560,     1.4437, 1, 1, 0,-2);
 2196    AddSol(ctx,     0.2330,     0.3600,     0.0120,    -0.0025, 1, 1, 0,-3);
 2197    AddSol(ctx,    -4.3910,    -9.6600,    -0.4710,     0.0673, 1, 1, 0,-4);
 2198    AddSol(ctx,     0.2830,     1.5300,    -0.1110,     0.0060, 1,-1, 0, 4);
 2199    AddSol(ctx,    14.5770,    31.7000,    -1.5400,     0.2302, 1,-1, 0, 2);
 2200    AddSol(ctx,   147.6870,   138.7600,     0.6790,     1.1528, 1,-1, 0, 0);
 2201    AddSol(ctx,    -1.0890,     0.5500,     0.0210,     0.0000, 1,-1, 0,-1);
 2202    AddSol(ctx,    28.4750,    23.5900,    -0.4430,    -0.2257, 1,-1, 0,-2);
 2203    AddSol(ctx,    -0.2760,    -0.3800,    -0.0060,    -0.0036, 1,-1, 0,-3);
 2204    AddSol(ctx,     0.6360,     2.2700,     0.1460,    -0.0102, 1,-1, 0,-4);
 2205    AddSol(ctx,    -0.1890,    -1.6800,     0.1310,    -0.0028, 0, 2, 0, 2);
 2206    AddSol(ctx,    -7.4860,    -0.6600,    -0.0370,    -0.0086, 0, 2, 0, 0);
 2207    AddSol(ctx,    -8.0960,   -16.3500,    -0.7400,     0.0918, 0, 2, 0,-2);
 2208    AddSol(ctx,    -5.7410,    -0.0400,     0.0000,    -0.0009, 0, 0, 2, 2);
 2209    AddSol(ctx,     0.2550,     0.0000,     0.0000,     0.0000, 0, 0, 2, 1);
 2210    AddSol(ctx,  -411.6080,    -0.2000,     0.0000,    -0.0124, 0, 0, 2, 0);
 2211    AddSol(ctx,     0.5840,     0.8400,     0.0000,     0.0071, 0, 0, 2,-1);
 2212    AddSol(ctx,   -55.1730,   -52.1400,     0.0000,    -0.1052, 0, 0, 2,-2);
 2213    AddSol(ctx,     0.2540,     0.2500,     0.0000,    -0.0017, 0, 0, 2,-3);
 2214    AddSol(ctx,     0.0250,    -1.6700,     0.0000,     0.0031, 0, 0, 2,-4);
 2215    AddSol(ctx,     1.0600,     2.9600,    -0.1660,     0.0243, 3, 0, 0, 2);
 2216    AddSol(ctx,    36.1240,    50.6400,    -1.3000,     0.6215, 3, 0, 0, 0);
 2217    AddSol(ctx,   -13.1930,   -16.4000,     0.2580,    -0.1187, 3, 0, 0,-2);
 2218    AddSol(ctx,    -1.1870,    -0.7400,     0.0420,     0.0074, 3, 0, 0,-4);
 2219    AddSol(ctx,    -0.2930,    -0.3100,    -0.0020,     0.0046, 3, 0, 0,-6);
 2220    AddSol(ctx,    -0.2900,    -1.4500,     0.1160,    -0.0051, 2, 1, 0, 2);
 2221    AddSol(ctx,    -7.6490,   -10.5600,     0.2590,    -0.1038, 2, 1, 0, 0);
 2222    AddSol(ctx,    -8.6270,    -7.5900,     0.0780,    -0.0192, 2, 1, 0,-2);
 2223    AddSol(ctx,    -2.7400,    -2.5400,     0.0220,     0.0324, 2, 1, 0,-4);
 2224    AddSol(ctx,     1.1810,     3.3200,    -0.2120,     0.0213, 2,-1, 0, 2);
 2225    AddSol(ctx,     9.7030,    11.6700,    -0.1510,     0.1268, 2,-1, 0, 0);
 2226    AddSol(ctx,    -0.3520,    -0.3700,     0.0010,    -0.0028, 2,-1, 0,-1);
 2227    AddSol(ctx,    -2.4940,    -1.1700,    -0.0030,    -0.0017, 2,-1, 0,-2);
 2228    AddSol(ctx,     0.3600,     0.2000,    -0.0120,    -0.0043, 2,-1, 0,-4);
 2229    AddSol(ctx,    -1.1670,    -1.2500,     0.0080,    -0.0106, 1, 2, 0, 0);
 2230    AddSol(ctx,    -7.4120,    -6.1200,     0.1170,     0.0484, 1, 2, 0,-2);
 2231    AddSol(ctx,    -0.3110,    -0.6500,    -0.0320,     0.0044, 1, 2, 0,-4);
 2232    AddSol(ctx,     0.7570,     1.8200,    -0.1050,     0.0112, 1,-2, 0, 2);
 2233    AddSol(ctx,     2.5800,     2.3200,     0.0270,     0.0196, 1,-2, 0, 0);
 2234    AddSol(ctx,     2.5330,     2.4000,    -0.0140,    -0.0212, 1,-2, 0,-2);
 2235    AddSol(ctx,    -0.3440,    -0.5700,    -0.0250,     0.0036, 0, 3, 0,-2);
 2236    AddSol(ctx,    -0.9920,    -0.0200,     0.0000,     0.0000, 1, 0, 2, 2);
 2237    AddSol(ctx,   -45.0990,    -0.0200,     0.0000,    -0.0010, 1, 0, 2, 0);
 2238    AddSol(ctx,    -0.1790,    -9.5200,     0.0000,    -0.0833, 1, 0, 2,-2);
 2239    AddSol(ctx,    -0.3010,    -0.3300,     0.0000,     0.0014, 1, 0, 2,-4);
 2240    AddSol(ctx,    -6.3820,    -3.3700,     0.0000,    -0.0481, 1, 0,-2, 2);
 2241    AddSol(ctx,    39.5280,    85.1300,     0.0000,    -0.7136, 1, 0,-2, 0);
 2242    AddSol(ctx,     9.3660,     0.7100,     0.0000,    -0.0112, 1, 0,-2,-2);
 2243    AddSol(ctx,     0.2020,     0.0200,     0.0000,     0.0000, 1, 0,-2,-4);
 2244    AddSol(ctx,     0.4150,     0.1000,     0.0000,     0.0013, 0, 1, 2, 0);
 2245    AddSol(ctx,    -2.1520,    -2.2600,     0.0000,    -0.0066, 0, 1, 2,-2);
 2246    AddSol(ctx,    -1.4400,    -1.3000,     0.0000,     0.0014, 0, 1,-2, 2);
 2247    AddSol(ctx,     0.3840,    -0.0400,     0.0000,     0.0000, 0, 1,-2,-2);
 2248    AddSol(ctx,     1.9380,     3.6000,    -0.1450,     0.0401, 4, 0, 0, 0);
 2249    AddSol(ctx,    -0.9520,    -1.5800,     0.0520,    -0.0130, 4, 0, 0,-2);
 2250    AddSol(ctx,    -0.5510,    -0.9400,     0.0320,    -0.0097, 3, 1, 0, 0);
 2251    AddSol(ctx,    -0.4820,    -0.5700,     0.0050,    -0.0045, 3, 1, 0,-2);
 2252    AddSol(ctx,     0.6810,     0.9600,    -0.0260,     0.0115, 3,-1, 0, 0);
 2253    AddSol(ctx,    -0.2970,    -0.2700,     0.0020,    -0.0009, 2, 2, 0,-2);
 2254    AddSol(ctx,     0.2540,     0.2100,    -0.0030,     0.0000, 2,-2, 0,-2);
 2255    AddSol(ctx,    -0.2500,    -0.2200,     0.0040,     0.0014, 1, 3, 0,-2);
 2256    AddSol(ctx,    -3.9960,     0.0000,     0.0000,     0.0004, 2, 0, 2, 0);
 2257    AddSol(ctx,     0.5570,    -0.7500,     0.0000,    -0.0090, 2, 0, 2,-2);
 2258    AddSol(ctx,    -0.4590,    -0.3800,     0.0000,    -0.0053, 2, 0,-2, 2);
 2259    AddSol(ctx,    -1.2980,     0.7400,     0.0000,     0.0004, 2, 0,-2, 0);
 2260    AddSol(ctx,     0.5380,     1.1400,     0.0000,    -0.0141, 2, 0,-2,-2);
 2261    AddSol(ctx,     0.2630,     0.0200,     0.0000,     0.0000, 1, 1, 2, 0);
 2262    AddSol(ctx,     0.4260,     0.0700,     0.0000,    -0.0006, 1, 1,-2,-2);
 2263    AddSol(ctx,    -0.3040,     0.0300,     0.0000,     0.0003, 1,-1, 2, 0);
 2264    AddSol(ctx,    -0.3720,    -0.1900,     0.0000,    -0.0027, 1,-1,-2, 2);
 2265    AddSol(ctx,     0.4180,     0.0000,     0.0000,     0.0000, 0, 0, 4, 0);
 2266    AddSol(ctx,    -0.3300,    -0.0400,     0.0000,     0.0000, 3, 0, 2, 0);
 2267
 2268    SolarN(ctx);
 2269    Planetary(ctx);
 2270    S = F + DS/ARC;
 2271
 2272    lat_seconds = (1.000002708 + 139.978*DGAM)*(18518.511+1.189+GAM1C)*sin(S)-6.24*sin(3*S) + N;
 2273
 2274    *geo_eclip_lon = PI2 * Frac((L0+DLAM/ARC) / PI2);
 2275    *geo_eclip_lat = lat_seconds * (DEG2RAD / 3600.0);
 2276    *distance_au = (ARC * EARTH_EQUATORIAL_RADIUS_AU) / (0.999953253 * SINPI);
 2277    ++_CalcMoonCount;
 2278}
 2279
 2280#undef T
 2281#undef DGAM
 2282#undef DLAM
 2283#undef N
 2284#undef GAM1C
 2285#undef SINPI
 2286#undef L0
 2287#undef L
 2288#undef LS
 2289#undef F
 2290#undef D
 2291#undef S
 2292#undef DL0
 2293#undef DL
 2294#undef DLS
 2295#undef DF
 2296#undef DD
 2297#undef DS
 2298#undef CO
 2299#undef SI
 2300
 2301/** @endcond */
 2302
 2303/**
 2304 * @brief Calculates equatorial geocentric position of the Moon at a given time.
 2305 *
 2306 * Given a time of observation, calculates the Moon's position as a vector.
 2307 * The vector gives the location of the Moon's center relative to the Earth's center
 2308 * with x-, y-, and z-components measured in astronomical units.
 2309 * The coordinates are oriented with respect to the Earth's equator at the J2000 epoch.
 2310 * In Astronomy Engine, this orientation is called EQJ.
 2311 *
 2312 * This algorithm is based on the Nautical Almanac Office's *Improved Lunar Ephemeris* of 1954,
 2313 * which in turn derives from E. W. Brown's lunar theories from the early twentieth century.
 2314 * It is adapted from Turbo Pascal code from the book
 2315 * [Astronomy on the Personal Computer](https://www.springer.com/us/book/9783540672210)
 2316 * by Montenbruck and Pfleger.
 2317 *
 2318 * To calculate ecliptic spherical coordinates instead, see #Astronomy_EclipticGeoMoon.
 2319 *
 2320 * @param time  The date and time for which to calculate the Moon's position.
 2321 * @return The Moon's position as a vector in J2000 Cartesian equatorial (EQJ) coordinates.
 2322 */
 2323astro_vector_t Astronomy_GeoMoon(astro_time_t time)
 2324{
 2325    double geo_eclip_lon, geo_eclip_lat, distance_au;
 2326    double dist_cos_lat;
 2327    astro_vector_t vector;
 2328    double gepos[3];
 2329    double mpos1[3];
 2330    double mpos2[3];
 2331
 2332    CalcMoon(time.tt / 36525.0, &geo_eclip_lon, &geo_eclip_lat, &distance_au);
 2333
 2334    /* Convert geocentric ecliptic spherical coordinates to Cartesian coordinates. */
 2335    dist_cos_lat = distance_au * cos(geo_eclip_lat);
 2336    gepos[0] = dist_cos_lat * cos(geo_eclip_lon);
 2337    gepos[1] = dist_cos_lat * sin(geo_eclip_lon);
 2338    gepos[2] = distance_au * sin(geo_eclip_lat);
 2339
 2340    /* Convert ecliptic coordinates to equatorial coordinates, both in mean equinox of date. */
 2341    ecl2equ_vec(time, gepos, mpos1);
 2342
 2343    /* Convert equatorial coordinates from mean equinox of date to J2000 mean equinox. */
 2344    precession(mpos1, time, INTO_2000, mpos2);
 2345
 2346    vector.status = ASTRO_SUCCESS;
 2347    vector.x = mpos2[0];
 2348    vector.y = mpos2[1];
 2349    vector.z = mpos2[2];
 2350    vector.t = time;
 2351    return vector;
 2352}
 2353
 2354
 2355/**
 2356 * @brief Calculates spherical ecliptic geocentric position of the Moon.
 2357 *
 2358 * Given a time of observation, calculates the Moon's geocentric position
 2359 * in ecliptic spherical coordinates. Provides the ecliptic latitude and
 2360 * longitude in degrees, and the geocentric distance in astronomical units (AU).
 2361 *
 2362 * The ecliptic angles are measured in "ECT": relative to the true ecliptic plane and
 2363 * equatorial plane at the specified time. This means the Earth's equator
 2364 * is corrected for precession and nutation, and the plane of the Earth's
 2365 * orbit is corrected for gradual obliquity drift.
 2366 *
 2367 * This algorithm is based on the Nautical Almanac Office's *Improved Lunar Ephemeris* of 1954,
 2368 * which in turn derives from E. W. Brown's lunar theories from the early twentieth century.
 2369 * It is adapted from Turbo Pascal code from the book
 2370 * [Astronomy on the Personal Computer](https://www.springer.com/us/book/9783540672210)
 2371 * by Montenbruck and Pfleger.
 2372 *
 2373 * To calculate a J2000 mean equator vector instead, use #Astronomy_GeoMoon.
 2374 *
 2375 * @param time  The date and time for which to calculate the Moon's position.
 2376 * @return The Moon's position expressed in ecliptic coordinates using the true equinox of date (ECT).
 2377 */
 2378astro_spherical_t Astronomy_EclipticGeoMoon(astro_time_t time)
 2379{
 2380    astro_spherical_t sphere;
 2381    astro_ecliptic_t eclip;
 2382    earth_tilt_t et;
 2383    double dist_cos_lat, ecm[3], eqm[3], eqd[3];
 2384
 2385    /* CalcMoon produces ecliptic coordinates in mean equinox of date (ECM). */
 2386    CalcMoon(time.tt / 36525.0, &sphere.lon, &sphere.lat, &sphere.dist);
 2387
 2388    /* Calculate vector in ecliptic coordinates (ECM). */
 2389    dist_cos_lat = sphere.dist * cos(sphere.lat);
 2390    ecm[0] = dist_cos_lat * cos(sphere.lon);
 2391    ecm[1] = dist_cos_lat * sin(sphere.lon);
 2392    ecm[2] = sphere.dist * sin(sphere.lat);
 2393
 2394    /* Obtain true and mean obliquity angles for the given time. */
 2395    /* This serves to pre-calculate the nutation also, and cache it in `time`. */
 2396    et = e_tilt(&time);
 2397
 2398    /* Convert ecliptic coordinates to equatorial coordinates, both in mean equinox of date. */
 2399    /* In other words, convert ECM to EQM. */
 2400    obl_ecl2equ_vec(et.mobl, time, ecm, eqm);
 2401
 2402    /* Add nutation to convert ECM to true equatorial coordinates of date (EQD). */
 2403    nutation(eqm, &time, FROM_2000, eqd);
 2404
 2405    /* Convert back to ecliptic, this time in true equinox of date (ECT). */
 2406    eclip = RotateEquatorialToEcliptic(eqd, DEG2RAD * et.tobl, time);
 2407
 2408    /* Package the return value. */
 2409    /* CalcMoon() already set sphere.dist to the correct value. */
 2410    sphere.status = eclip.status;
 2411    sphere.lat = eclip.elat;
 2412    sphere.lon = eclip.elon;
 2413    return sphere;
 2414}
 2415
 2416
 2417/**
 2418 * @brief Calculates equatorial geocentric position and velocity of the Moon at a given time.
 2419 *
 2420 * Given a time of observation, calculates the Moon's position and velocity vectors.
 2421 * The position and velocity are of the Moon's center relative to the Earth's center.
 2422 * The position (x, y, z) components are expressed in AU (astronomical units).
 2423 * The velocity (vx, vy, vz) components are expressed in AU/day.
 2424 * The coordinates are oriented with respect to the Earth's equator at the J2000 epoch.
 2425 * In Astronomy Engine, this orientation is called EQJ.
 2426 *
 2427 * If you need the Moon's position only, and not its velocity,
 2428 * it is much more efficient to use #Astronomy_GeoMoon instead.
 2429 *
 2430 * @param time  The date and time for which to calculate the Moon's position and velocity.
 2431 * @return The Moon's position and velocity vectors in J2000 equatorial coordinates (EQJ).
 2432 */
 2433astro_state_vector_t Astronomy_GeoMoonState(astro_time_t time)
 2434{
 2435    /*
 2436        This is a hack, because trying to figure out how to derive a time
 2437        derivative for CalcMoon() would be extremely painful!
 2438        Calculate just before and just after the given time.
 2439        Average to find position, subtract to find velocity.
 2440    */
 2441    const double dt = 1.0e-5;   /* 0.864 seconds */
 2442    astro_vector_t r1, r2;
 2443    astro_time_t t1, t2;
 2444    astro_state_vector_t s;
 2445
 2446    t1 = Astronomy_AddDays(time, -dt);
 2447    t2 = Astronomy_AddDays(time, +dt);
 2448
 2449    r1 = Astronomy_GeoMoon(t1);
 2450    r2 = Astronomy_GeoMoon(t2);
 2451
 2452    /* The desired position is the average of the two calculated positions. */
 2453    s.x = (r1.x + r2.x) / 2;
 2454    s.y = (r1.y + r2.y) / 2;
 2455    s.z = (r1.z + r2.z) / 2;
 2456
 2457    /* The difference of the position vectors divided by the time span gives the velocity vector. */
 2458    s.vx = (r2.x - r1.x) / (2 * dt);
 2459    s.vy = (r2.y - r1.y) / (2 * dt);
 2460    s.vz = (r2.z - r1.z) / (2 * dt);
 2461    s.t = time;
 2462    s.status = ASTRO_SUCCESS;
 2463
 2464    return s;
 2465}
 2466
 2467
 2468/**
 2469 * @brief Calculates the geocentric position and velocity of the Earth/Moon barycenter.
 2470 *
 2471 * Given a time of observation, calculates the geocentric position and velocity vectors
 2472 * of the Earth/Moon barycenter (EMB).
 2473 * The position (x, y, z) components are expressed in AU (astronomical units).
 2474 * The velocity (vx, vy, vz) components are expressed in AU/day.
 2475 * The coordinates are oriented with respect to the Earth's equator at the J2000 epoch.
 2476 * In Astronomy Engine, this orientation is called EQJ.
 2477 *
 2478 * @param time  The date and time for which to calculate the EMB vectors.
 2479 * @return The EMB's position and velocity vectors in geocentric J2000 equatorial coordinates.
 2480 */
 2481astro_state_vector_t Astronomy_GeoEmbState(astro_time_t time)
 2482{
 2483    astro_state_vector_t s = Astronomy_GeoMoonState(time);
 2484    const double d = 1.0 + EARTH_MOON_MASS_RATIO;
 2485    s.x /= d;
 2486    s.y /= d;
 2487    s.z /= d;
 2488    s.vx /= d;
 2489    s.vy /= d;
 2490    s.vz /= d;
 2491    return s;
 2492}
 2493
 2494
 2495/**
 2496 * @brief Calculates the Moon's libration angles at a given moment in time.
 2497 *
 2498 * Libration is an observed back-and-forth wobble of the portion of the
 2499 * Moon visible from the Earth. It is caused by the imperfect tidal locking
 2500 * of the Moon's fixed rotation rate, compared to its variable angular speed
 2501 * of orbit around the Earth.
 2502 *
 2503 * This function calculates a pair of perpendicular libration angles,
 2504 * one representing rotation of the Moon in ecliptic longitude `elon`, the other
 2505 * in ecliptic latitude `elat`, both relative to the Moon's mean Earth-facing position.
 2506 *
 2507 * This function also returns the geocentric position of the Moon
 2508 * expressed in ecliptic longitude `mlon`, ecliptic latitude `mlat`, the
 2509 * distance `dist_km` between the centers of the Earth and Moon expressed in kilometers,
 2510 * and the apparent angular diameter of the Moon `diam_deg`.
 2511 *
 2512 * @param time  The date and time for which to calculate libration angles.
 2513 * @return The Moon's ecliptic position and libration angles as seen from the Earth.
 2514 */
 2515astro_libration_t Astronomy_Libration(astro_time_t time)
 2516{
 2517    astro_libration_t lib;
 2518    double t, t2, t3, t4;
 2519    double f, omega, w, a, ldash, ldash2, bdash, bdash2;
 2520    double k1, k2, m, mdash, d, e, rho, sigma, tau;
 2521    const double sin_I = sin(MOON_AXIS_INCLINATION_RADIANS);
 2522    const double cos_I = cos(MOON_AXIS_INCLINATION_RADIANS);
 2523
 2524    t = time.tt / 36525.0;
 2525    t2 = t * t;
 2526    t3 = t2 * t;
 2527    t4 = t2 * t2;
 2528
 2529    double mlon;    /* Moon's ecliptic longitude in radians. */
 2530    double mlat;    /* Moon's ecliptic latitude in radians. */
 2531    CalcMoon(t, &mlon, &mlat, &lib.dist_km);
 2532    lib.mlon = RAD2DEG * mlon;
 2533    lib.mlat = RAD2DEG * mlat;
 2534    lib.dist_km *= KM_PER_AU;
 2535    lib.diam_deg = (2.0 * RAD2DEG) * atan(MOON_MEAN_RADIUS_KM / sqrt(lib.dist_km*lib.dist_km - MOON_MEAN_RADIUS_KM*MOON_MEAN_RADIUS_KM));
 2536
 2537    /* Moon's argument of latitude in radians. */
 2538    f = DEG2RAD * NormalizeLongitude(93.2720950 + 483202.0175233*t - 0.0036539*t2 - t3/3526000 + t4/863310000);
 2539
 2540    /* Moon's ascending node's mean longitude in radians. */
 2541    omega = DEG2RAD * NormalizeLongitude(125.0445479 - 1934.1362891*t + 0.0020754*t2 + t3/467441 - t4/60616000);
 2542
 2543    /* Sun's mean anomaly. */
 2544    m = DEG2RAD * NormalizeLongitude(357.5291092 + 35999.0502909*t - 0.0001536*t2 + t3/24490000);
 2545
 2546    /* Moon's mean anomaly. */
 2547    mdash = DEG2RAD * NormalizeLongitude(134.9633964 + 477198.8675055*t + 0.0087414*t2 + t3/69699 - t4/14712000);
 2548
 2549    /* Moon's mean elongation. */
 2550    d = DEG2RAD * NormalizeLongitude(297.8501921 + 445267.1114034*t - 0.0018819*t2 + t3/545868 - t4/113065000);
 2551
 2552    /* Eccentricity of the Earth's orbit. */
 2553    e = 1.0 - 0.002516*t - 0.0000074*t2;
 2554
 2555    /* Optical librations */
 2556    w = mlon - omega;
 2557    a = atan2(sin(w)*cos(mlat)*cos_I - sin(mlat)*sin_I, cos(w)*cos(mlat));
 2558    ldash = LongitudeOffset(RAD2DEG * (a - f));
 2559    bdash = asin(-sin(w)*cos(mlat)*sin_I - sin(mlat)*cos_I);
 2560
 2561    /* Physical librations */
 2562    k1 = DEG2RAD*(119.75 + 131.849*t);
 2563    k2 = DEG2RAD*(72.56 + 20.186*t);
 2564
 2565    rho = (
 2566        -0.02752*cos(mdash) +
 2567        -0.02245*sin(f) +
 2568        +0.00684*cos(mdash - 2*f) +
 2569        -0.00293*cos(2*f) +
 2570        -0.00085*cos(2*f - 2*d) +
 2571        -0.00054*cos(mdash - 2*d) +
 2572        -0.00020*sin(mdash + f) +
 2573        -0.00020*cos(mdash + 2*f) +
 2574        -0.00020*cos(mdash - f) +
 2575        +0.00014*cos(mdash + 2*f - 2*d)
 2576    );
 2577
 2578    sigma = (
 2579        -0.02816*sin(mdash) +
 2580        +0.02244*cos(f) +
 2581        -0.00682*sin(mdash - 2*f) +
 2582        -0.00279*sin(2*f) +
 2583        -0.00083*sin(2*f - 2*d) +
 2584        +0.00069*sin(mdash - 2*d) +
 2585        +0.00040*cos(mdash + f) +
 2586        -0.00025*sin(2*mdash) +
 2587        -0.00023*sin(mdash + 2*f) +
 2588        +0.00020*cos(mdash - f) +
 2589        +0.00019*sin(mdash - f) +
 2590        +0.00013*sin(mdash + 2*f - 2*d) +
 2591        -0.00010*cos(mdash - 3*f)
 2592    );
 2593
 2594    tau = (
 2595        +0.02520*e*sin(m) +
 2596        +0.00473*sin(2*mdash - 2*f) +
 2597        -0.00467*sin(mdash) +
 2598        +0.00396*sin(k1) +
 2599        +0.00276*sin(2*mdash - 2*d) +
 2600        +0.00196*sin(omega) +
 2601        -0.00183*cos(mdash - f) +
 2602        +0.00115*sin(mdash - 2*d) +
 2603        -0.00096*sin(mdash - d) +
 2604        +0.00046*sin(2*f - 2*d) +
 2605        -0.00039*sin(mdash - f) +
 2606        -0.00032*sin(mdash - m - d) +
 2607        +0.00027*sin(2*mdash - m - 2*d) +
 2608        +0.00023*sin(k2) +
 2609        -0.00014*sin(2*d) +
 2610        +0.00014*cos(2*mdash - 2*f) +
 2611        -0.00012*sin(mdash - 2*f) +
 2612        -0.00012*sin(2*mdash) +
 2613        +0.00011*sin(2*mdash - 2*m - 2*d)
 2614    );
 2615
 2616    ldash2 = -tau + (rho*cos(a) + sigma*sin(a))*tan(bdash);
 2617    bdash *= RAD2DEG;
 2618    bdash2 = sigma*cos(a) - rho*sin(a);
 2619
 2620    lib.elon = ldash + ldash2;
 2621    lib.elat = bdash + bdash2;
 2622
 2623    return lib;
 2624}
 2625
 2626
 2627/*------------------ VSOP ------------------*/
 2628
 2629/** @cond DOXYGEN_SKIP */
 2630typedef struct
 2631{
 2632    double amplitude;
 2633    double phase;
 2634    double frequency;
 2635}
 2636vsop_term_t;
 2637
 2638typedef struct
 2639{
 2640    int nterms;
 2641    const vsop_term_t *term;
 2642}
 2643vsop_series_t;
 2644
 2645typedef struct
 2646{
 2647    int nseries;
 2648    const vsop_series_t *series;
 2649}
 2650vsop_formula_t;
 2651
 2652typedef struct
 2653{
 2654    const vsop_formula_t formula[3];
 2655}
 2656vsop_model_t;
 2657
 2658typedef struct
 2659{
 2660    double mu;
 2661    double al[2];
 2662    vsop_series_t a;
 2663    vsop_series_t l;
 2664    vsop_series_t z;
 2665    vsop_series_t zeta;
 2666}
 2667jupiter_moon_t;
 2668/** @endcond */
 2669
 2670static const vsop_term_t vsop_lon_Mercury_0[] =
 2671{
 2672    { 4.40250710144, 0.00000000000, 0.00000000000 },
 2673    { 0.40989414977, 1.48302034195, 26087.90314157420 },
 2674    { 0.05046294200, 4.47785489551, 52175.80628314840 },
 2675    { 0.00855346844, 1.16520322459, 78263.70942472259 },
 2676    { 0.00165590362, 4.11969163423, 104351.61256629678 },
 2677    { 0.00034561897, 0.77930768443, 130439.51570787099 },
 2678    { 0.00007583476, 3.71348404924, 156527.41884944518 }
 2679};
 2680
 2681static const vsop_term_t vsop_lon_Mercury_1[] =
 2682{
 2683    { 26087.90313685529, 0.00000000000, 0.00000000000 },
 2684    { 0.01131199811, 6.21874197797, 26087.90314157420 },
 2685    { 0.00292242298, 3.04449355541, 52175.80628314840 },
 2686    { 0.00075775081, 6.08568821653, 78263.70942472259 },
 2687    { 0.00019676525, 2.80965111777, 104351.61256629678 }
 2688};
 2689
 2690static const vsop_series_t vsop_lon_Mercury[] =
 2691{
 2692    { 7, vsop_lon_Mercury_0 },
 2693    { 5, vsop_lon_Mercury_1 }
 2694};
 2695
 2696static const vsop_term_t vsop_lat_Mercury_0[] =
 2697{
 2698    { 0.11737528961, 1.98357498767, 26087.90314157420 },
 2699    { 0.02388076996, 5.03738959686, 52175.80628314840 },
 2700    { 0.01222839532, 3.14159265359, 0.00000000000 },
 2701    { 0.00543251810, 1.79644363964, 78263.70942472259 },
 2702    { 0.00129778770, 4.83232503958, 104351.61256629678 },
 2703    { 0.00031866927, 1.58088495658, 130439.51570787099 },
 2704    { 0.00007963301, 4.60972126127, 156527.41884944518 }
 2705};
 2706
 2707static const vsop_term_t vsop_lat_Mercury_1[] =
 2708{
 2709    { 0.00274646065, 3.95008450011, 26087.90314157420 },
 2710    { 0.00099737713, 3.14159265359, 0.00000000000 }
 2711};
 2712
 2713static const vsop_series_t vsop_lat_Mercury[] =
 2714{
 2715    { 7, vsop_lat_Mercury_0 },
 2716    { 2, vsop_lat_Mercury_1 }
 2717};
 2718
 2719static const vsop_term_t vsop_rad_Mercury_0[] =
 2720{
 2721    { 0.39528271651, 0.00000000000, 0.00000000000 },
 2722    { 0.07834131818, 6.19233722598, 26087.90314157420 },
 2723    { 0.00795525558, 2.95989690104, 52175.80628314840 },
 2724    { 0.00121281764, 6.01064153797, 78263.70942472259 },
 2725    { 0.00021921969, 2.77820093972, 104351.61256629678 },
 2726    { 0.00004354065, 5.82894543774, 130439.51570787099 }
 2727};
 2728
 2729static const vsop_term_t vsop_rad_Mercury_1[] =
 2730{
 2731    { 0.00217347740, 4.65617158665, 26087.90314157420 },
 2732    { 0.00044141826, 1.42385544001, 52175.80628314840 }
 2733};
 2734
 2735static const vsop_series_t vsop_rad_Mercury[] =
 2736{
 2737    { 6, vsop_rad_Mercury_0 },
 2738    { 2, vsop_rad_Mercury_1 }
 2739};
 2740
 2741;
 2742static const vsop_term_t vsop_lon_Venus_0[] =
 2743{
 2744    { 3.17614666774, 0.00000000000, 0.00000000000 },
 2745    { 0.01353968419, 5.59313319619, 10213.28554621100 },
 2746    { 0.00089891645, 5.30650047764, 20426.57109242200 },
 2747    { 0.00005477194, 4.41630661466, 7860.41939243920 },
 2748    { 0.00003455741, 2.69964447820, 11790.62908865880 },
 2749    { 0.00002372061, 2.99377542079, 3930.20969621960 },
 2750    { 0.00001317168, 5.18668228402, 26.29831979980 },
 2751    { 0.00001664146, 4.25018630147, 1577.34354244780 },
 2752    { 0.00001438387, 4.15745084182, 9683.59458111640 },
 2753    { 0.00001200521, 6.15357116043, 30639.85663863300 }
 2754};
 2755
 2756static const vsop_term_t vsop_lon_Venus_1[] =
 2757{
 2758    { 10213.28554621638, 0.00000000000, 0.00000000000 },
 2759    { 0.00095617813, 2.46406511110, 10213.28554621100 },
 2760    { 0.00007787201, 0.62478482220, 20426.57109242200 }
 2761};
 2762
 2763static const vsop_series_t vsop_lon_Venus[] =
 2764{
 2765    { 10, vsop_lon_Venus_0 },
 2766    { 3, vsop_lon_Venus_1 }
 2767};
 2768
 2769static const vsop_term_t vsop_lat_Venus_0[] =
 2770{
 2771    { 0.05923638472, 0.26702775812, 10213.28554621100 },
 2772    { 0.00040107978, 1.14737178112, 20426.57109242200 },
 2773    { 0.00032814918, 3.14159265359, 0.00000000000 }
 2774};
 2775
 2776static const vsop_term_t vsop_lat_Venus_1[] =
 2777{
 2778    { 0.00287821243, 1.88964962838, 10213.28554621100 }
 2779};
 2780
 2781static const vsop_series_t vsop_lat_Venus[] =
 2782{
 2783    { 3, vsop_lat_Venus_0 },
 2784    { 1, vsop_lat_Venus_1 }
 2785};
 2786
 2787static const vsop_term_t vsop_rad_Venus_0[] =
 2788{
 2789    { 0.72334820891, 0.00000000000, 0.00000000000 },
 2790    { 0.00489824182, 4.02151831717, 10213.28554621100 },
 2791    { 0.00001658058, 4.90206728031, 20426.57109242200 },
 2792    { 0.00001378043, 1.12846591367, 11790.62908865880 },
 2793    { 0.00001632096, 2.84548795207, 7860.41939243920 },
 2794    { 0.00000498395, 2.58682193892, 9683.59458111640 },
 2795    { 0.00000221985, 2.01346696541, 19367.18916223280 },
 2796    { 0.00000237454, 2.55136053886, 15720.83878487840 }
 2797};
 2798
 2799static const vsop_term_t vsop_rad_Venus_1[] =
 2800{
 2801    { 0.00034551041, 0.89198706276, 10213.28554621100 }
 2802};
 2803
 2804static const vsop_series_t vsop_rad_Venus[] =
 2805{
 2806    { 8, vsop_rad_Venus_0 },
 2807    { 1, vsop_rad_Venus_1 }
 2808};
 2809
 2810;
 2811static const vsop_term_t vsop_lon_Earth_0[] =
 2812{
 2813    { 1.75347045673, 0.00000000000, 0.00000000000 },
 2814    { 0.03341656453, 4.66925680415, 6283.07584999140 },
 2815    { 0.00034894275, 4.62610242189, 12566.15169998280 },
 2816    { 0.00003417572, 2.82886579754, 3.52311834900 },
 2817    { 0.00003497056, 2.74411783405, 5753.38488489680 },
 2818    { 0.00003135899, 3.62767041756, 77713.77146812050 },
 2819    { 0.00002676218, 4.41808345438, 7860.41939243920 },
 2820    { 0.00002342691, 6.13516214446, 3930.20969621960 },
 2821    { 0.00001273165, 2.03709657878, 529.69096509460 },
 2822    { 0.00001324294, 0.74246341673, 11506.76976979360 },
 2823    { 0.00000901854, 2.04505446477, 26.29831979980 },
 2824    { 0.00001199167, 1.10962946234, 1577.34354244780 },
 2825    { 0.00000857223, 3.50849152283, 398.14900340820 },
 2826    { 0.00000779786, 1.17882681962, 5223.69391980220 },
 2827    { 0.00000990250, 5.23268072088, 5884.92684658320 },
 2828    { 0.00000753141, 2.53339052847, 5507.55323866740 },
 2829    { 0.00000505267, 4.58292599973, 18849.22754997420 },
 2830    { 0.00000492392, 4.20505711826, 775.52261132400 },
 2831    { 0.00000356672, 2.91954114478, 0.06731030280 },
 2832    { 0.00000284125, 1.89869240932, 796.29800681640 },
 2833    { 0.00000242879, 0.34481445893, 5486.77784317500 },
 2834    { 0.00000317087, 5.84901948512, 11790.62908865880 },
 2835    { 0.00000271112, 0.31486255375, 10977.07880469900 },
 2836    { 0.00000206217, 4.80646631478, 2544.31441988340 },
 2837    { 0.00000205478, 1.86953770281, 5573.14280143310 },
 2838    { 0.00000202318, 2.45767790232, 6069.77675455340 },
 2839    { 0.00000126225, 1.08295459501, 20.77539549240 },
 2840    { 0.00000155516, 0.83306084617, 213.29909543800 }
 2841};
 2842
 2843static const vsop_term_t vsop_lon_Earth_1[] =
 2844{
 2845    { 6283.07584999140, 0.00000000000, 0.00000000000 },
 2846    { 0.00206058863, 2.67823455808, 6283.07584999140 },
 2847    { 0.00004303419, 2.63512233481, 12566.15169998280 }
 2848};
 2849
 2850static const vsop_term_t vsop_lon_Earth_2[] =
 2851{
 2852    { 0.00008721859, 1.07253635559, 6283.07584999140 }
 2853};
 2854
 2855static const vsop_series_t vsop_lon_Earth[] =
 2856{
 2857    { 28, vsop_lon_Earth_0 },
 2858    { 3, vsop_lon_Earth_1 },
 2859    { 1, vsop_lon_Earth_2 }
 2860};
 2861
 2862static const vsop_term_t vsop_lat_Earth_1[] =
 2863{
 2864    { 0.00227777722, 3.41376620530, 6283.07584999140 },
 2865    { 0.00003805678, 3.37063423795, 12566.15169998280 }
 2866};
 2867
 2868static const vsop_series_t vsop_lat_Earth[] =
 2869{
 2870    { 0, NULL },
 2871    { 2, vsop_lat_Earth_1 }
 2872};
 2873
 2874static const vsop_term_t vsop_rad_Earth_0[] =
 2875{
 2876    { 1.00013988784, 0.00000000000, 0.00000000000 },
 2877    { 0.01670699632, 3.09846350258, 6283.07584999140 },
 2878    { 0.00013956024, 3.05524609456, 12566.15169998280 },
 2879    { 0.00003083720, 5.19846674381, 77713.77146812050 },
 2880    { 0.00001628463, 1.17387558054, 5753.38488489680 },
 2881    { 0.00001575572, 2.84685214877, 7860.41939243920 },
 2882    { 0.00000924799, 5.45292236722, 11506.76976979360 },
 2883    { 0.00000542439, 4.56409151453, 3930.20969621960 },
 2884    { 0.00000472110, 3.66100022149, 5884.92684658320 },
 2885    { 0.00000085831, 1.27079125277, 161000.68573767410 },
 2886    { 0.00000057056, 2.01374292245, 83996.84731811189 },
 2887    { 0.00000055736, 5.24159799170, 71430.69561812909 },
 2888    { 0.00000174844, 3.01193636733, 18849.22754997420 },
 2889    { 0.00000243181, 4.27349530790, 11790.62908865880 }
 2890};
 2891
 2892static const vsop_term_t vsop_rad_Earth_1[] =
 2893{
 2894    { 0.00103018607, 1.10748968172, 6283.07584999140 },
 2895    { 0.00001721238, 1.06442300386, 12566.15169998280 }
 2896};
 2897
 2898static const vsop_term_t vsop_rad_Earth_2[] =
 2899{
 2900    { 0.00004359385, 5.78455133808, 6283.07584999140 }
 2901};
 2902
 2903static const vsop_series_t vsop_rad_Earth[] =
 2904{
 2905    { 14, vsop_rad_Earth_0 },
 2906    { 2, vsop_rad_Earth_1 },
 2907    { 1, vsop_rad_Earth_2 }
 2908};
 2909
 2910;
 2911static const vsop_term_t vsop_lon_Mars_0[] =
 2912{
 2913    { 6.20347711581, 0.00000000000, 0.00000000000 },
 2914    { 0.18656368093, 5.05037100270, 3340.61242669980 },
 2915    { 0.01108216816, 5.40099836344, 6681.22485339960 },
 2916    { 0.00091798406, 5.75478744667, 10021.83728009940 },
 2917    { 0.00027744987, 5.97049513147, 3.52311834900 },
 2918    { 0.00010610235, 2.93958560338, 2281.23049651060 },
 2919    { 0.00012315897, 0.84956094002, 2810.92146160520 },
 2920    { 0.00008926784, 4.15697846427, 0.01725365220 },
 2921    { 0.00008715691, 6.11005153139, 13362.44970679920 },
 2922    { 0.00006797556, 0.36462229657, 398.14900340820 },
 2923    { 0.00007774872, 3.33968761376, 5621.84292321040 },
 2924    { 0.00003575078, 1.66186505710, 2544.31441988340 },
 2925    { 0.00004161108, 0.22814971327, 2942.46342329160 },
 2926    { 0.00003075252, 0.85696614132, 191.44826611160 },
 2927    { 0.00002628117, 0.64806124465, 3337.08930835080 },
 2928    { 0.00002937546, 6.07893711402, 0.06731030280 },
 2929    { 0.00002389414, 5.03896442664, 796.29800681640 },
 2930    { 0.00002579844, 0.02996736156, 3344.13554504880 },
 2931    { 0.00001528141, 1.14979301996, 6151.53388830500 },
 2932    { 0.00001798806, 0.65634057445, 529.69096509460 },
 2933    { 0.00001264357, 3.62275122593, 5092.15195811580 },
 2934    { 0.00001286228, 3.06796065034, 2146.16541647520 },
 2935    { 0.00001546404, 2.91579701718, 1751.53953141600 },
 2936    { 0.00001024902, 3.69334099279, 8962.45534991020 },
 2937    { 0.00000891566, 0.18293837498, 16703.06213349900 },
 2938    { 0.00000858759, 2.40093811940, 2914.01423582380 },
 2939    { 0.00000832715, 2.46418619474, 3340.59517304760 },
 2940    { 0.00000832720, 4.49495782139, 3340.62968035200 },
 2941    { 0.00000712902, 3.66335473479, 1059.38193018920 },
 2942    { 0.00000748723, 3.82248614017, 155.42039943420 },
 2943    { 0.00000723861, 0.67497311481, 3738.76143010800 },
 2944    { 0.00000635548, 2.92182225127, 8432.76438481560 },
 2945    { 0.00000655162, 0.48864064125, 3127.31333126180 },
 2946    { 0.00000550474, 3.81001042328, 0.98032106820 },
 2947    { 0.00000552750, 4.47479317037, 1748.01641306700 },
 2948    { 0.00000425966, 0.55364317304, 6283.07584999140 },
 2949    { 0.00000415131, 0.49662285038, 213.29909543800 },
 2950    { 0.00000472167, 3.62547124025, 1194.44701022460 },
 2951    { 0.00000306551, 0.38052848348, 6684.74797174860 },
 2952    { 0.00000312141, 0.99853944405, 6677.70173505060 },
 2953    { 0.00000293198, 4.22131299634, 20.77539549240 },
 2954    { 0.00000302375, 4.48618007156, 3532.06069281140 },
 2955    { 0.00000274027, 0.54222167059, 3340.54511639700 },
 2956    { 0.00000281079, 5.88163521788, 1349.86740965880 },
 2957    { 0.00000231183, 1.28242156993, 3870.30339179440 },
 2958    { 0.00000283602, 5.76885434940, 3149.16416058820 },
 2959    { 0.00000236117, 5.75503217933, 3333.49887969900 },
 2960    { 0.00000274033, 0.13372524985, 3340.67973700260 },
 2961    { 0.00000299395, 2.78323740866, 6254.62666252360 }
 2962};
 2963
 2964static const vsop_term_t vsop_lon_Mars_1[] =
 2965{
 2966    { 3340.61242700512, 0.00000000000, 0.00000000000 },
 2967    { 0.01457554523, 3.60433733236, 3340.61242669980 },
 2968    { 0.00168414711, 3.92318567804, 6681.22485339960 },
 2969    { 0.00020622975, 4.26108844583, 10021.83728009940 },
 2970    { 0.00003452392, 4.73210393190, 3.52311834900 },
 2971    { 0.00002586332, 4.60670058555, 13362.44970679920 },
 2972    { 0.00000841535, 4.45864030426, 2281.23049651060 }
 2973};
 2974
 2975static const vsop_term_t vsop_lon_Mars_2[] =
 2976{
 2977    { 0.00058152577, 2.04961712429, 3340.61242669980 },
 2978    { 0.00013459579, 2.45738706163, 6681.22485339960 }
 2979};
 2980
 2981static const vsop_series_t vsop_lon_Mars[] =
 2982{
 2983    { 49, vsop_lon_Mars_0 },
 2984    { 7, vsop_lon_Mars_1 },
 2985    { 2, vsop_lon_Mars_2 }
 2986};
 2987
 2988static const vsop_term_t vsop_lat_Mars_0[] =
 2989{
 2990    { 0.03197134986, 3.76832042431, 3340.61242669980 },
 2991    { 0.00298033234, 4.10616996305, 6681.22485339960 },
 2992    { 0.00289104742, 0.00000000000, 0.00000000000 },
 2993    { 0.00031365539, 4.44651053090, 10021.83728009940 },
 2994    { 0.00003484100, 4.78812549260, 13362.44970679920 }
 2995};
 2996
 2997static const vsop_term_t vsop_lat_Mars_1[] =
 2998{
 2999    { 0.00217310991, 6.04472194776, 3340.61242669980 },
 3000    { 0.00020976948, 3.14159265359, 0.00000000000 },
 3001    { 0.00012834709, 1.60810667915, 6681.22485339960 }
 3002};
 3003
 3004static const vsop_series_t vsop_lat_Mars[] =
 3005{
 3006    { 5, vsop_lat_Mars_0 },
 3007    { 3, vsop_lat_Mars_1 }
 3008};
 3009
 3010static const vsop_term_t vsop_rad_Mars_0[] =
 3011{
 3012    { 1.53033488271, 0.00000000000, 0.00000000000 },
 3013    { 0.14184953160, 3.47971283528, 3340.61242669980 },
 3014    { 0.00660776362, 3.81783443019, 6681.22485339960 },
 3015    { 0.00046179117, 4.15595316782, 10021.83728009940 },
 3016    { 0.00008109733, 5.55958416318, 2810.92146160520 },
 3017    { 0.00007485318, 1.77239078402, 5621.84292321040 },
 3018    { 0.00005523191, 1.36436303770, 2281.23049651060 },
 3019    { 0.00003825160, 4.49407183687, 13362.44970679920 },
 3020    { 0.00002306537, 0.09081579001, 2544.31441988340 },
 3021    { 0.00001999396, 5.36059617709, 3337.08930835080 },
 3022    { 0.00002484394, 4.92545639920, 2942.46342329160 },
 3023    { 0.00001960195, 4.74249437639, 3344.13554504880 },
 3024    { 0.00001167119, 2.11260868341, 5092.15195811580 },
 3025    { 0.00001102816, 5.00908403998, 398.14900340820 },
 3026    { 0.00000899066, 4.40791133207, 529.69096509460 },
 3027    { 0.00000992252, 5.83861961952, 6151.53388830500 },
 3028    { 0.00000807354, 2.10217065501, 1059.38193018920 },
 3029    { 0.00000797915, 3.44839203899, 796.29800681640 },
 3030    { 0.00000740975, 1.49906336885, 2146.16541647520 }
 3031};
 3032
 3033static const vsop_term_t vsop_rad_Mars_1[] =
 3034{
 3035    { 0.01107433345, 2.03250524857, 3340.61242669980 },
 3036    { 0.00103175887, 2.37071847807, 6681.22485339960 },
 3037    { 0.00012877200, 0.00000000000, 0.00000000000 },
 3038    { 0.00010815880, 2.70888095665, 10021.83728009940 }
 3039};
 3040
 3041static const vsop_term_t vsop_rad_Mars_2[] =
 3042{
 3043    { 0.00044242249, 0.47930604954, 3340.61242669980 },
 3044    { 0.00008138042, 0.86998389204, 6681.22485339960 }
 3045};
 3046
 3047static const vsop_series_t vsop_rad_Mars[] =
 3048{
 3049    { 19, vsop_rad_Mars_0 },
 3050    { 4, vsop_rad_Mars_1 },
 3051    { 2, vsop_rad_Mars_2 }
 3052};
 3053
 3054;
 3055static const vsop_term_t vsop_lon_Jupiter_0[] =
 3056{
 3057    { 0.59954691494, 0.00000000000, 0.00000000000 },
 3058    { 0.09695898719, 5.06191793158, 529.69096509460 },
 3059    { 0.00573610142, 1.44406205629, 7.11354700080 },
 3060    { 0.00306389205, 5.41734730184, 1059.38193018920 },
 3061    { 0.00097178296, 4.14264726552, 632.78373931320 },
 3062    { 0.00072903078, 3.64042916389, 522.57741809380 },
 3063    { 0.00064263975, 3.41145165351, 103.09277421860 },
 3064    { 0.00039806064, 2.29376740788, 419.48464387520 },
 3065    { 0.00038857767, 1.27231755835, 316.39186965660 },
 3066    { 0.00027964629, 1.78454591820, 536.80451209540 },
 3067    { 0.00013589730, 5.77481040790, 1589.07289528380 },
 3068    { 0.00008246349, 3.58227925840, 206.18554843720 },
 3069    { 0.00008768704, 3.63000308199, 949.17560896980 },
 3070    { 0.00007368042, 5.08101194270, 735.87651353180 },
 3071    { 0.00006263150, 0.02497628807, 213.29909543800 },
 3072    { 0.00006114062, 4.51319998626, 1162.47470440780 },
 3073    { 0.00004905396, 1.32084470588, 110.20632121940 },
 3074    { 0.00005305285, 1.30671216791, 14.22709400160 },
 3075    { 0.00005305441, 4.18625634012, 1052.26838318840 },
 3076    { 0.00004647248, 4.69958103684, 3.93215326310 },
 3077    { 0.00003045023, 4.31676431084, 426.59819087600 },
 3078    { 0.00002609999, 1.56667394063, 846.08283475120 },
 3079    { 0.00002028191, 1.06376530715, 3.18139373770 },
 3080    { 0.00001764763, 2.14148655117, 1066.49547719000 },
 3081    { 0.00001722972, 3.88036268267, 1265.56747862640 },
 3082    { 0.00001920945, 0.97168196472, 639.89728631400 },
 3083    { 0.00001633223, 3.58201833555, 515.46387109300 },
 3084    { 0.00001431999, 4.29685556046, 625.67019231240 },
 3085    { 0.00000973272, 4.09764549134, 95.97922721780 }
 3086};
 3087
 3088static const vsop_term_t vsop_lon_Jupiter_1[] =
 3089{
 3090    { 529.69096508814, 0.00000000000, 0.00000000000 },
 3091    { 0.00489503243, 4.22082939470, 529.69096509460 },
 3092    { 0.00228917222, 6.02646855621, 7.11354700080 },
 3093    { 0.00030099479, 4.54540782858, 1059.38193018920 },
 3094    { 0.00020720920, 5.45943156902, 522.57741809380 },
 3095    { 0.00012103653, 0.16994816098, 536.80451209540 },
 3096    { 0.00006067987, 4.42422292017, 103.09277421860 },
 3097    { 0.00005433968, 3.98480737746, 419.48464387520 },
 3098    { 0.00004237744, 5.89008707199, 14.22709400160 }
 3099};
 3100
 3101static const vsop_term_t vsop_lon_Jupiter_2[] =
 3102{
 3103    { 0.00047233601, 4.32148536482, 7.11354700080 },
 3104    { 0.00030649436, 2.92977788700, 529.69096509460 },
 3105    { 0.00014837605, 3.14159265359, 0.00000000000 }
 3106};
 3107
 3108static const vsop_series_t vsop_lon_Jupiter[] =
 3109{
 3110    { 29, vsop_lon_Jupiter_0 },
 3111    { 9, vsop_lon_Jupiter_1 },
 3112    { 3, vsop_lon_Jupiter_2 }
 3113};
 3114
 3115static const vsop_term_t vsop_lat_Jupiter_0[] =
 3116{
 3117    { 0.02268615702, 3.55852606721, 529.69096509460 },
 3118    { 0.00109971634, 3.90809347197, 1059.38193018920 },
 3119    { 0.00110090358, 0.00000000000, 0.00000000000 },
 3120    { 0.00008101428, 3.60509572885, 522.57741809380 },
 3121    { 0.00006043996, 4.25883108339, 1589.07289528380 },
 3122    { 0.00006437782, 0.30627119215, 536.80451209540 }
 3123};
 3124
 3125static const vsop_term_t vsop_lat_Jupiter_1[] =
 3126{
 3127    { 0.00078203446, 1.52377859742, 529.69096509460 }
 3128};
 3129
 3130static const vsop_series_t vsop_lat_Jupiter[] =
 3131{
 3132    { 6, vsop_lat_Jupiter_0 },
 3133    { 1, vsop_lat_Jupiter_1 }
 3134};
 3135
 3136static const vsop_term_t vsop_rad_Jupiter_0[] =
 3137{
 3138    { 5.20887429326, 0.00000000000, 0.00000000000 },
 3139    { 0.25209327119, 3.49108639871, 529.69096509460 },
 3140    { 0.00610599976, 3.84115365948, 1059.38193018920 },
 3141    { 0.00282029458, 2.57419881293, 632.78373931320 },
 3142    { 0.00187647346, 2.07590383214, 522.57741809380 },
 3143    { 0.00086792905, 0.71001145545, 419.48464387520 },
 3144    { 0.00072062974, 0.21465724607, 536.80451209540 },
 3145    { 0.00065517248, 5.97995884790, 316.39186965660 },
 3146    { 0.00029134542, 1.67759379655, 103.09277421860 },
 3147    { 0.00030135335, 2.16132003734, 949.17560896980 },
 3148    { 0.00023453271, 3.54023522184, 735.87651353180 },
 3149    { 0.00022283743, 4.19362594399, 1589.07289528380 },
 3150    { 0.00023947298, 0.27458037480, 7.11354700080 },
 3151    { 0.00013032614, 2.96042965363, 1162.47470440780 },
 3152    { 0.00009703360, 1.90669633585, 206.18554843720 },
 3153    { 0.00012749023, 2.71550286592, 1052.26838318840 },
 3154    { 0.00007057931, 2.18184839926, 1265.56747862640 },
 3155    { 0.00006137703, 6.26418240033, 846.08283475120 },
 3156    { 0.00002616976, 2.00994012876, 1581.95934828300 }
 3157};
 3158
 3159static const vsop_term_t vsop_rad_Jupiter_1[] =
 3160{
 3161    { 0.01271801520, 2.64937512894, 529.69096509460 },
 3162    { 0.00061661816, 3.00076460387, 1059.38193018920 },
 3163    { 0.00053443713, 3.89717383175, 522.57741809380 },
 3164    { 0.00031185171, 4.88276958012, 536.80451209540 },
 3165    { 0.00041390269, 0.00000000000, 0.00000000000 }
 3166};
 3167
 3168static const vsop_series_t vsop_rad_Jupiter[] =
 3169{
 3170    { 19, vsop_rad_Jupiter_0 },
 3171    { 5, vsop_rad_Jupiter_1 }
 3172};
 3173
 3174;
 3175static const vsop_term_t vsop_lon_Saturn_0[] =
 3176{
 3177    { 0.87401354025, 0.00000000000, 0.00000000000 },
 3178    { 0.11107659762, 3.96205090159, 213.29909543800 },
 3179    { 0.01414150957, 4.58581516874, 7.11354700080 },
 3180    { 0.00398379389, 0.52112032699, 206.18554843720 },
 3181    { 0.00350769243, 3.30329907896, 426.59819087600 },
 3182    { 0.00206816305, 0.24658372002, 103.09277421860 },
 3183    { 0.00079271300, 3.84007056878, 220.41264243880 },
 3184    { 0.00023990355, 4.66976924553, 110.20632121940 },
 3185    { 0.00016573588, 0.43719228296, 419.48464387520 },
 3186    { 0.00014906995, 5.76903183869, 316.39186965660 },
 3187    { 0.00015820290, 0.93809155235, 632.78373931320 },
 3188    { 0.00014609559, 1.56518472000, 3.93215326310 },
 3189    { 0.00013160301, 4.44891291899, 14.22709400160 },
 3190    { 0.00015053543, 2.71669915667, 639.89728631400 },
 3191    { 0.00013005299, 5.98119023644, 11.04570026390 },
 3192    { 0.00010725067, 3.12939523827, 202.25339517410 },
 3193    { 0.00005863206, 0.23656938524, 529.69096509460 },
 3194    { 0.00005227757, 4.20783365759, 3.18139373770 },
 3195    { 0.00006126317, 1.76328667907, 277.03499374140 },
 3196    { 0.00005019687, 3.17787728405, 433.71173787680 },
 3197    { 0.00004592550, 0.61977744975, 199.07200143640 },
 3198    { 0.00004005867, 2.24479718502, 63.73589830340 },
 3199    { 0.00002953796, 0.98280366998, 95.97922721780 },
 3200    { 0.00003873670, 3.22283226966, 138.51749687070 },
 3201    { 0.00002461186, 2.03163875071, 735.87651353180 },
 3202    { 0.00003269484, 0.77492638211, 949.17560896980 },
 3203    { 0.00001758145, 3.26580109940, 522.57741809380 },
 3204    { 0.00001640172, 5.50504453050, 846.08283475120 },
 3205    { 0.00001391327, 4.02333150505, 323.50541665740 },
 3206    { 0.00001580648, 4.37265307169, 309.27832265580 },
 3207    { 0.00001123498, 2.83726798446, 415.55249061210 },
 3208    { 0.00001017275, 3.71700135395, 227.52618943960 },
 3209    { 0.00000848642, 3.19150170830, 209.36694217490 }
 3210};
 3211
 3212static const vsop_term_t vsop_lon_Saturn_1[] =
 3213{
 3214    { 213.29909521690, 0.00000000000, 0.00000000000 },
 3215    { 0.01297370862, 1.82834923978, 213.29909543800 },
 3216    { 0.00564345393, 2.88499717272, 7.11354700080 },
 3217    { 0.00093734369, 1.06311793502, 426.59819087600 },
 3218    { 0.00107674962, 2.27769131009, 206.18554843720 },
 3219    { 0.00040244455, 2.04108104671, 220.41264243880 },
 3220    { 0.00019941774, 1.27954390470, 103.09277421860 },
 3221    { 0.00010511678, 2.74880342130, 14.22709400160 },
 3222    { 0.00006416106, 0.38238295041, 639.89728631400 },
 3223    { 0.00004848994, 2.43037610229, 419.48464387520 },
 3224    { 0.00004056892, 2.92133209468, 110.20632121940 },
 3225    { 0.00003768635, 3.64965330780, 3.93215326310 }
 3226};
 3227
 3228static const vsop_term_t vsop_lon_Saturn_2[] =
 3229{
 3230    { 0.00116441330, 1.17988132879, 7.11354700080 },
 3231    { 0.00091841837, 0.07325195840, 213.29909543800 },
 3232    { 0.00036661728, 0.00000000000, 0.00000000000 },
 3233    { 0.00015274496, 4.06493179167, 206.18554843720 }
 3234};
 3235
 3236static const vsop_series_t vsop_lon_Saturn[] =
 3237{
 3238    { 33, vsop_lon_Saturn_0 },
 3239    { 12, vsop_lon_Saturn_1 },
 3240    { 4, vsop_lon_Saturn_2 }
 3241};
 3242
 3243static const vsop_term_t vsop_lat_Saturn_0[] =
 3244{
 3245    { 0.04330678039, 3.60284428399, 213.29909543800 },
 3246    { 0.00240348302, 2.85238489373, 426.59819087600 },
 3247    { 0.00084745939, 0.00000000000, 0.00000000000 },
 3248    { 0.00030863357, 3.48441504555, 220.41264243880 },
 3249    { 0.00034116062, 0.57297307557, 206.18554843720 },
 3250    { 0.00014734070, 2.11846596715, 639.89728631400 },
 3251    { 0.00009916667, 5.79003188904, 419.48464387520 },
 3252    { 0.00006993564, 4.73604689720, 7.11354700080 },
 3253    { 0.00004807588, 5.43305312061, 316.39186965660 }
 3254};
 3255
 3256static const vsop_term_t vsop_lat_Saturn_1[] =
 3257{
 3258    { 0.00198927992, 4.93901017903, 213.29909543800 },
 3259    { 0.00036947916, 3.14159265359, 0.00000000000 },
 3260    { 0.00017966989, 0.51979431110, 426.59819087600 }
 3261};
 3262
 3263static const vsop_series_t vsop_lat_Saturn[] =
 3264{
 3265    { 9, vsop_lat_Saturn_0 },
 3266    { 3, vsop_lat_Saturn_1 }
 3267};
 3268
 3269static const vsop_term_t vsop_rad_Saturn_0[] =
 3270{
 3271    { 9.55758135486, 0.00000000000, 0.00000000000 },
 3272    { 0.52921382865, 2.39226219573, 213.29909543800 },
 3273    { 0.01873679867, 5.23549604660, 206.18554843720 },
 3274    { 0.01464663929, 1.64763042902, 426.59819087600 },
 3275    { 0.00821891141, 5.93520042303, 316.39186965660 },
 3276    { 0.00547506923, 5.01532618980, 103.09277421860 },
 3277    { 0.00371684650, 2.27114821115, 220.41264243880 },
 3278    { 0.00361778765, 3.13904301847, 7.11354700080 },
 3279    { 0.00140617506, 5.70406606781, 632.78373931320 },
 3280    { 0.00108974848, 3.29313390175, 110.20632121940 },
 3281    { 0.00069006962, 5.94099540992, 419.48464387520 },
 3282    { 0.00061053367, 0.94037691801, 639.89728631400 },
 3283    { 0.00048913294, 1.55733638681, 202.25339517410 },
 3284    { 0.00034143772, 0.19519102597, 277.03499374140 },
 3285    { 0.00032401773, 5.47084567016, 949.17560896980 },
 3286    { 0.00020936596, 0.46349251129, 735.87651353180 },
 3287    { 0.00009796004, 5.20477537945, 1265.56747862640 },
 3288    { 0.00011993338, 5.98050967385, 846.08283475120 },
 3289    { 0.00020839300, 1.52102476129, 433.71173787680 },
 3290    { 0.00015298404, 3.05943814940, 529.69096509460 },
 3291    { 0.00006465823, 0.17732249942, 1052.26838318840 },
 3292    { 0.00011380257, 1.73105427040, 522.57741809380 },
 3293    { 0.00003419618, 4.94550542171, 1581.95934828300 }
 3294};
 3295
 3296static const vsop_term_t vsop_rad_Saturn_1[] =
 3297{
 3298    { 0.06182981340, 0.25843511480, 213.29909543800 },
 3299    { 0.00506577242, 0.71114625261, 206.18554843720 },
 3300    { 0.00341394029, 5.79635741658, 426.59819087600 },
 3301    { 0.00188491195, 0.47215589652, 220.41264243880 },
 3302    { 0.00186261486, 3.14159265359, 0.00000000000 },
 3303    { 0.00143891146, 1.40744822888, 7.11354700080 }
 3304};
 3305
 3306static const vsop_term_t vsop_rad_Saturn_2[] =
 3307{
 3308    { 0.00436902572, 4.78671677509, 213.29909543800 }
 3309};
 3310
 3311static const vsop_series_t vsop_rad_Saturn[] =
 3312{
 3313    { 23, vsop_rad_Saturn_0 },
 3314    { 6, vsop_rad_Saturn_1 },
 3315    { 1, vsop_rad_Saturn_2 }
 3316};
 3317
 3318;
 3319static const vsop_term_t vsop_lon_Uranus_0[] =
 3320{
 3321    { 5.48129294297, 0.00000000000, 0.00000000000 },
 3322    { 0.09260408234, 0.89106421507, 74.78159856730 },
 3323    { 0.01504247898, 3.62719260920, 1.48447270830 },
 3324    { 0.00365981674, 1.89962179044, 73.29712585900 },
 3325    { 0.00272328168, 3.35823706307, 149.56319713460 },
 3326    { 0.00070328461, 5.39254450063, 63.73589830340 },
 3327    { 0.00068892678, 6.09292483287, 76.26607127560 },
 3328    { 0.00061998615, 2.26952066061, 2.96894541660 },
 3329    { 0.00061950719, 2.85098872691, 11.04570026390 },
 3330    { 0.00026468770, 3.14152083966, 71.81265315070 },
 3331    { 0.00025710476, 6.11379840493, 454.90936652730 },
 3332    { 0.00021078850, 4.36059339067, 148.07872442630 },
 3333    { 0.00017818647, 1.74436930289, 36.64856292950 },
 3334    { 0.00014613507, 4.73732166022, 3.93215326310 },
 3335    { 0.00011162509, 5.82681796350, 224.34479570190 },
 3336    { 0.00010997910, 0.48865004018, 138.51749687070 },
 3337    { 0.00009527478, 2.95516862826, 35.16409022120 },
 3338    { 0.00007545601, 5.23626582400, 109.94568878850 },
 3339    { 0.00004220241, 3.23328220918, 70.84944530420 },
 3340    { 0.00004051900, 2.27755017300, 151.04766984290 },
 3341    { 0.00003354596, 1.06549007380, 4.45341812490 },
 3342    { 0.00002926718, 4.62903718891, 9.56122755560 },
 3343    { 0.00003490340, 5.48306144511, 146.59425171800 },
 3344    { 0.00003144069, 4.75199570434, 77.75054398390 },
 3345    { 0.00002922333, 5.35235361027, 85.82729883120 },
 3346    { 0.00002272788, 4.36600400036, 70.32818044240 },
 3347    { 0.00002051219, 1.51773566586, 0.11187458460 },
 3348    { 0.00002148602, 0.60745949945, 38.13303563780 },
 3349    { 0.00001991643, 4.92437588682, 277.03499374140 },
 3350    { 0.00001376226, 2.04283539351, 65.22037101170 },
 3351    { 0.00001666902, 3.62744066769, 380.12776796000 },
 3352    { 0.00001284107, 3.11347961505, 202.25339517410 },
 3353    { 0.00001150429, 0.93343589092, 3.18139373770 },
 3354    { 0.00001533221, 2.58594681212, 52.69019803950 },
 3355    { 0.00001281604, 0.54271272721, 222.86032299360 },
 3356    { 0.00001372139, 4.19641530878, 111.43016149680 },
 3357    { 0.00001221029, 0.19900650030, 108.46121608020 },
 3358    { 0.00000946181, 1.19253165736, 127.47179660680 },
 3359    { 0.00001150989, 4.17898916639, 33.67961751290 }
 3360};
 3361
 3362static const vsop_term_t vsop_lon_Uranus_1[] =
 3363{
 3364    { 74.78159860910, 0.00000000000, 0.00000000000 },
 3365    { 0.00154332863, 5.24158770553, 74.78159856730 },
 3366    { 0.00024456474, 1.71260334156, 1.48447270830 },
 3367    { 0.00009258442, 0.42829732350, 11.04570026390 },
 3368    { 0.00008265977, 1.50218091379, 63.73589830340 },
 3369    { 0.00009150160, 1.41213765216, 149.56319713460 }
 3370};
 3371
 3372static const vsop_series_t vsop_lon_Uranus[] =
 3373{
 3374    { 39, vsop_lon_Uranus_0 },
 3375    { 6, vsop_lon_Uranus_1 }
 3376};
 3377
 3378static const vsop_term_t vsop_lat_Uranus_0[] =
 3379{
 3380    { 0.01346277648, 2.61877810547, 74.78159856730 },
 3381    { 0.00062341400, 5.08111189648, 149.56319713460 },
 3382    { 0.00061601196, 3.14159265359, 0.00000000000 },
 3383    { 0.00009963722, 1.61603805646, 76.26607127560 },
 3384    { 0.00009926160, 0.57630380333, 73.29712585900 }
 3385};
 3386
 3387static const vsop_term_t vsop_lat_Uranus_1[] =
 3388{
 3389    { 0.00034101978, 0.01321929936, 74.78159856730 }
 3390};
 3391
 3392static const vsop_series_t vsop_lat_Uranus[] =
 3393{
 3394    { 5, vsop_lat_Uranus_0 },
 3395    { 1, vsop_lat_Uranus_1 }
 3396};
 3397
 3398static const vsop_term_t vsop_rad_Uranus_0[] =
 3399{
 3400    { 19.21264847206, 0.00000000000, 0.00000000000 },
 3401    { 0.88784984413, 5.60377527014, 74.78159856730 },
 3402    { 0.03440836062, 0.32836099706, 73.29712585900 },
 3403    { 0.02055653860, 1.78295159330, 149.56319713460 },
 3404    { 0.00649322410, 4.52247285911, 76.26607127560 },
 3405    { 0.00602247865, 3.86003823674, 63.73589830340 },
 3406    { 0.00496404167, 1.40139935333, 454.90936652730 },
 3407    { 0.00338525369, 1.58002770318, 138.51749687070 },
 3408    { 0.00243509114, 1.57086606044, 71.81265315070 },
 3409    { 0.00190522303, 1.99809394714, 1.48447270830 },
 3410    { 0.00161858838, 2.79137786799, 148.07872442630 },
 3411    { 0.00143706183, 1.38368544947, 11.04570026390 },
 3412    { 0.00093192405, 0.17437220467, 36.64856292950 },
 3413    { 0.00071424548, 4.24509236074, 224.34479570190 },
 3414    { 0.00089806014, 3.66105364565, 109.94568878850 },
 3415    { 0.00039009723, 1.66971401684, 70.84944530420 },
 3416    { 0.00046677296, 1.39976401694, 35.16409022120 },
 3417    { 0.00039025624, 3.36234773834, 277.03499374140 },
 3418    { 0.00036755274, 3.88649278513, 146.59425171800 },
 3419    { 0.00030348723, 0.70100838798, 151.04766984290 },
 3420    { 0.00029156413, 3.18056336700, 77.75054398390 },
 3421    { 0.00022637073, 0.72518687029, 529.69096509460 },
 3422    { 0.00011959076, 1.75043392140, 984.60033162190 },
 3423    { 0.00025620756, 5.25656086672, 380.12776796000 }
 3424};
 3425
 3426static const vsop_term_t vsop_rad_Uranus_1[] =
 3427{
 3428    { 0.01479896629, 3.67205697578, 74.78159856730 }
 3429};
 3430
 3431static const vsop_series_t vsop_rad_Uranus[] =
 3432{
 3433    { 24, vsop_rad_Uranus_0 },
 3434    { 1, vsop_rad_Uranus_1 }
 3435};
 3436
 3437;
 3438static const vsop_term_t vsop_lon_Neptune_0[] =
 3439{
 3440    { 5.31188633046, 0.00000000000, 0.00000000000 },
 3441    { 0.01798475530, 2.90101273890, 38.13303563780 },
 3442    { 0.01019727652, 0.48580922867, 1.48447270830 },
 3443    { 0.00124531845, 4.83008090676, 36.64856292950 },
 3444    { 0.00042064466, 5.41054993053, 2.96894541660 },
 3445    { 0.00037714584, 6.09221808686, 35.16409022120 },
 3446    { 0.00033784738, 1.24488874087, 76.26607127560 },
 3447    { 0.00016482741, 0.00007727998, 491.55792945680 },
 3448    { 0.00009198584, 4.93747051954, 39.61750834610 },
 3449    { 0.00008994250, 0.27462171806, 175.16605980020 }
 3450};
 3451
 3452static const vsop_term_t vsop_lon_Neptune_1[] =
 3453{
 3454    { 38.13303563957, 0.00000000000, 0.00000000000 },
 3455    { 0.00016604172, 4.86323329249, 1.48447270830 },
 3456    { 0.00015744045, 2.27887427527, 38.13303563780 }
 3457};
 3458
 3459static const vsop_series_t vsop_lon_Neptune[] =
 3460{
 3461    { 10, vsop_lon_Neptune_0 },
 3462    { 3, vsop_lon_Neptune_1 }
 3463};
 3464
 3465static const vsop_term_t vsop_lat_Neptune_0[] =
 3466{
 3467    { 0.03088622933, 1.44104372644, 38.13303563780 },
 3468    { 0.00027780087, 5.91271884599, 76.26607127560 },
 3469    { 0.00027623609, 0.00000000000, 0.00000000000 },
 3470    { 0.00015355489, 2.52123799551, 36.64856292950 },
 3471    { 0.00015448133, 3.50877079215, 39.61750834610 }
 3472};
 3473
 3474static const vsop_series_t vsop_lat_Neptune[] =
 3475{
 3476    { 5, vsop_lat_Neptune_0 }
 3477};
 3478
 3479static const vsop_term_t vsop_rad_Neptune_0[] =
 3480{
 3481    { 30.07013205828, 0.00000000000, 0.00000000000 },
 3482    { 0.27062259632, 1.32999459377, 38.13303563780 },
 3483    { 0.01691764014, 3.25186135653, 36.64856292950 },
 3484    { 0.00807830553, 5.18592878704, 1.48447270830 },
 3485    { 0.00537760510, 4.52113935896, 35.16409022120 },
 3486    { 0.00495725141, 1.57105641650, 491.55792945680 },
 3487    { 0.00274571975, 1.84552258866, 175.16605980020 },
 3488    { 0.00012012320, 1.92059384991, 1021.24889455140 },
 3489    { 0.00121801746, 5.79754470298, 76.26607127560 },
 3490    { 0.00100896068, 0.37702724930, 73.29712585900 },
 3491    { 0.00135134092, 3.37220609835, 39.61750834610 },
 3492    { 0.00007571796, 1.07149207335, 388.46515523820 }
 3493};
 3494
 3495static const vsop_series_t vsop_rad_Neptune[] =
 3496{
 3497    { 12, vsop_rad_Neptune_0 }
 3498};
 3499
 3500;
 3501
 3502/** @cond DOXYGEN_SKIP */
 3503#define VSOPFORMULA(x)    { ASTRO_ARRAYSIZE(x), x }
 3504/** @endcond */
 3505
 3506static const vsop_model_t vsop[] =
 3507{
 3508    { { VSOPFORMULA(vsop_lon_Mercury),  VSOPFORMULA(vsop_lat_Mercury),  VSOPFORMULA(vsop_rad_Mercury) } },
 3509    { { VSOPFORMULA(vsop_lon_Venus),    VSOPFORMULA(vsop_lat_Venus),    VSOPFORMULA(vsop_rad_Venus)   } },
 3510    { { VSOPFORMULA(vsop_lon_Earth),    VSOPFORMULA(vsop_lat_Earth),    VSOPFORMULA(vsop_rad_Earth)   } },
 3511    { { VSOPFORMULA(vsop_lon_Mars),     VSOPFORMULA(vsop_lat_Mars),     VSOPFORMULA(vsop_rad_Mars)    } },
 3512    { { VSOPFORMULA(vsop_lon_Jupiter),  VSOPFORMULA(vsop_lat_Jupiter),  VSOPFORMULA(vsop_rad_Jupiter) } },
 3513    { { VSOPFORMULA(vsop_lon_Saturn),   VSOPFORMULA(vsop_lat_Saturn),   VSOPFORMULA(vsop_rad_Saturn)  } },
 3514    { { VSOPFORMULA(vsop_lon_Uranus),   VSOPFORMULA(vsop_lat_Uranus),   VSOPFORMULA(vsop_rad_Uranus)  } },
 3515    { { VSOPFORMULA(vsop_lon_Neptune),  VSOPFORMULA(vsop_lat_Neptune),  VSOPFORMULA(vsop_rad_Neptune) } }
 3516};
 3517
 3518/** @cond DOXYGEN_SKIP */
 3519#define CalcEarth(time)     CalcVsop(&vsop[BODY_EARTH], (time))
 3520#define LON_INDEX 0
 3521#define LAT_INDEX 1
 3522#define RAD_INDEX 2
 3523/** @endcond */
 3524
 3525static void VsopCoords(const vsop_model_t *model, double t, double sphere[3])
 3526{
 3527    int k, s, i;
 3528    double incr;
 3529
 3530    for (k=0; k < 3; ++k)
 3531    {
 3532        double tpower = 1.0;
 3533        const vsop_formula_t *formula = &model->formula[k];
 3534        sphere[k] = 0.0;
 3535        for (s=0; s < formula->nseries; ++s)
 3536        {
 3537            double sum = 0.0;
 3538            const vsop_series_t *series = &formula->series[s];
 3539            for (i=0; i < series->nterms; ++i)
 3540            {
 3541                const vsop_term_t *term = &series->term[i];
 3542                sum  += term->amplitude * cos(term->phase + (t * term->frequency));
 3543            }
 3544            incr = tpower * sum;
 3545            if (k == LON_INDEX)
 3546                incr = fmod(incr, PI2);     /* improve precision for longitudes, which can be hundreds of radians */
 3547            sphere[k] += incr;
 3548            tpower *= t;
 3549        }
 3550    }
 3551}
 3552
 3553
 3554static terse_vector_t VsopRotate(const double ecl[3])
 3555{
 3556    terse_vector_t equ;
 3557
 3558    /*
 3559        X        +1.000000000000  +0.000000440360  -0.000000190919   X
 3560        Y     =  -0.000000479966  +0.917482137087  -0.397776982902   Y
 3561        Z FK5     0.000000000000  +0.397776982902  +0.917482137087   Z VSOP87A
 3562    */
 3563
 3564    equ.x = ecl[0] + 0.000000440360*ecl[1] - 0.000000190919*ecl[2];
 3565    equ.y = -0.000000479966*ecl[0] + 0.917482137087*ecl[1] - 0.397776982902*ecl[2];
 3566    equ.z = 0.397776982902*ecl[1] + 0.917482137087*ecl[2];
 3567
 3568    return equ;
 3569}
 3570
 3571
 3572static void VsopSphereToRect(double lon, double lat, double radius, double pos[3])
 3573{
 3574    double r_coslat = radius * cos(lat);
 3575    double coslon = cos(lon);
 3576    double sinlon = sin(lon);
 3577    pos[0] = r_coslat * coslon;
 3578    pos[1] = r_coslat * sinlon;
 3579    pos[2] = radius * sin(lat);
 3580}
 3581
 3582static const double DAYS_PER_MILLENNIUM = 365250.0;
 3583
 3584
 3585static astro_vector_t CalcVsop(const vsop_model_t *model, astro_time_t time)
 3586{
 3587    double t = time.tt / DAYS_PER_MILLENNIUM;
 3588    double sphere[3];       /* lon, lat, rad */
 3589    double eclip[3];
 3590    astro_vector_t vector;
 3591    terse_vector_t pos;
 3592
 3593    /* Calculate the VSOP "B" trigonometric series to obtain ecliptic spherical coordinates. */
 3594    VsopCoords(model, t, sphere);
 3595
 3596    /* Convert ecliptic spherical coordinates to ecliptic Cartesian coordinates. */
 3597    VsopSphereToRect(sphere[LON_INDEX], sphere[LAT_INDEX], sphere[RAD_INDEX], eclip);
 3598
 3599    /* Convert ecliptic Cartesian coordinates to equatorial Cartesian coordinates. */
 3600    pos = VsopRotate(eclip);
 3601
 3602    /* Package the position as astro_vector_t. */
 3603    vector.status = ASTRO_SUCCESS;
 3604    vector.t = time;
 3605    vector.x = pos.x;
 3606    vector.y = pos.y;
 3607    vector.z = pos.z;
 3608
 3609    return vector;
 3610}
 3611
 3612
 3613static void VsopDeriv(const vsop_model_t *model, double t, double deriv[3])
 3614{
 3615    int k, s, i;
 3616
 3617    for (k=0; k < 3; ++k)
 3618    {
 3619        double tpower = 1.0;        /* t^s */
 3620        double dpower = 0.0;        /* t^(s-1) */
 3621        const vsop_formula_t *formula = &model->formula[k];
 3622        deriv[k] = 0.0;
 3623        for (s=0; s < formula->nseries; ++s)
 3624        {
 3625            double sin_sum = 0.0;
 3626            double cos_sum = 0.0;
 3627            const vsop_series_t *series = &formula->series[s];
 3628            for (i=0; i < series->nterms; ++i)
 3629            {
 3630                const vsop_term_t *term = &series->term[i];
 3631                double angle = term->phase + (t * term->frequency);
 3632                sin_sum += term->amplitude * term->frequency * sin(angle);
 3633                if (s > 0)
 3634                    cos_sum += term->amplitude * cos(angle);
 3635            }
 3636            deriv[k] += (s * dpower * cos_sum) - (tpower * sin_sum);
 3637            dpower = tpower;
 3638            tpower *= t;
 3639        }
 3640    }
 3641}
 3642
 3643
 3644static body_state_t CalcVsopPosVel(const vsop_model_t *model, double tt)
 3645{
 3646    body_state_t state;
 3647    double t = tt / DAYS_PER_MILLENNIUM;
 3648    double sphere[3];   /* lon, lat, r */
 3649    double deriv[3];    /* d(lon)/dt, d(lat)/dt, dr/dt */
 3650    double eclip[3];
 3651    double dr_dt, dlat_dt, dlon_dt;
 3652    double r, coslat, coslon, sinlat, sinlon;
 3653
 3654    state.tt = tt;
 3655    VsopCoords(model, t, sphere);
 3656    VsopSphereToRect(sphere[LON_INDEX], sphere[LAT_INDEX], sphere[RAD_INDEX], eclip);
 3657    state.r = VsopRotate(eclip);
 3658
 3659    VsopDeriv(model, t, deriv);
 3660
 3661    /* Use spherical coords and spherical derivatives to calculate */
 3662    /* the velocity vector in rectangular coordinates. */
 3663
 3664    /* Calculate mnemonic variables to help keep the math straight. */
 3665    coslon = cos(sphere[LON_INDEX]);
 3666    sinlon = sin(sphere[LON_INDEX]);
 3667    coslat = cos(sphere[LAT_INDEX]);
 3668    sinlat = sin(sphere[LAT_INDEX]);
 3669    r = sphere[RAD_INDEX];
 3670    dlon_dt = deriv[LON_INDEX];
 3671    dlat_dt = deriv[LAT_INDEX];
 3672    dr_dt   = deriv[RAD_INDEX];
 3673
 3674    /* vx = dx/dt */
 3675    eclip[0] = (dr_dt * coslat * coslon) - (r * sinlat * coslon * dlat_dt) - (r * coslat * sinlon * dlon_dt);
 3676
 3677    /* vy = dy/dt */
 3678    eclip[1] = (dr_dt * coslat * sinlon) - (r * sinlat * sinlon * dlat_dt) + (r * coslat * coslon * dlon_dt);
 3679
 3680    /* vz = dz/dt */
 3681    eclip[2] = (dr_dt * sinlat) + (r * coslat * dlat_dt);
 3682
 3683    /* Rotate the velocity vector from ecliptic to equatorial coordinates. */
 3684    state.v = VsopRotate(eclip);
 3685
 3686    /* Convert speed units from [AU/millennium] to [AU/day]. */
 3687    VecScale(&state.v, 1 / DAYS_PER_MILLENNIUM);
 3688
 3689    return state;
 3690}
 3691
 3692
 3693static double VsopHelioDistance(const vsop_model_t *model, astro_time_t time)
 3694{
 3695    int s, i;
 3696    double t = time.tt / DAYS_PER_MILLENNIUM;
 3697    double distance = 0.0;
 3698    double tpower = 1.0;
 3699    const vsop_formula_t *formula = &model->formula[2];     /* [2] is the distance part of the formula */
 3700
 3701    /*
 3702        The caller only wants to know the distance between the planet and the Sun.
 3703        So we only need to calculate the radial component of the spherical coordinates.
 3704    */
 3705
 3706    for (s=0; s < formula->nseries; ++s)
 3707    {
 3708        double sum = 0.0;
 3709        const vsop_series_t *series = &formula->series[s];
 3710        for (i=0; i < series->nterms; ++i)
 3711        {
 3712            const vsop_term_t *term = &series->term[i];
 3713            sum += term->amplitude * cos(term->phase + (t * term->frequency));
 3714        }
 3715        distance += tpower * sum;
 3716        tpower *= t;
 3717    }
 3718
 3719    return distance;
 3720}
 3721
 3722
 3723static void AdjustBarycenter(astro_vector_t *ssb, astro_time_t time, astro_body_t body, double planet_gm)
 3724{
 3725    astro_vector_t planet;
 3726    double shift;
 3727
 3728    shift = planet_gm / (planet_gm + SUN_GM);
 3729    planet = CalcVsop(&vsop[body], time);
 3730    ssb->x += shift * planet.x;
 3731    ssb->y += shift * planet.y;
 3732    ssb->z += shift * planet.z;
 3733}
 3734
 3735
 3736static astro_vector_t CalcSolarSystemBarycenter(astro_time_t time)
 3737{
 3738    astro_vector_t ssb;
 3739
 3740    ssb.status = ASTRO_SUCCESS;
 3741    ssb.t = time;
 3742    ssb.x = ssb.y = ssb.z = 0.0;
 3743
 3744    AdjustBarycenter(&ssb, time, BODY_JUPITER, JUPITER_GM);
 3745    AdjustBarycenter(&ssb, time, BODY_SATURN,  SATURN_GM);
 3746    AdjustBarycenter(&ssb, time, BODY_URANUS,  URANUS_GM);
 3747    AdjustBarycenter(&ssb, time, BODY_NEPTUNE, NEPTUNE_GM);
 3748
 3749    return ssb;
 3750}
 3751
 3752/*------------------ begin general gravity simulator ------------------*/
 3753
 3754static terse_vector_t UpdatePosition(double dt, terse_vector_t r, terse_vector_t v, terse_vector_t a)
 3755{
 3756    r.x += (v.x + a.x*dt/2) * dt;
 3757    r.y += (v.y + a.y*dt/2) * dt;
 3758    r.z += (v.z + a.z*dt/2) * dt;
 3759    return r;
 3760}
 3761
 3762
 3763static terse_vector_t UpdateVelocity(double dt, terse_vector_t v, terse_vector_t a)
 3764{
 3765    v.x += dt * a.x;
 3766    v.y += dt * a.y;
 3767    v.z += dt * a.z;
 3768    return v;
 3769}
 3770
 3771
 3772static body_state_t AdjustBarycenterPosVel(body_state_t *ssb, double tt, astro_body_t body, double planet_gm)
 3773{
 3774    body_state_t planet;
 3775    double shift;
 3776
 3777    /*
 3778        This function does 2 important things:
 3779        1. Adjusts 'ssb' by the effect of one major body on the Solar System Barycenter.
 3780        2, Returns the heliocentric position of that major body.
 3781    */
 3782
 3783    shift = planet_gm / (planet_gm + SUN_GM);
 3784    planet = CalcVsopPosVel(&vsop[body], tt);
 3785    VecIncr(&ssb->r, VecMul(shift, planet.r));
 3786    VecIncr(&ssb->v, VecMul(shift, planet.v));
 3787
 3788    return planet;
 3789}
 3790
 3791
 3792static void MajorBodyBary(major_bodies_t *bary, double tt)
 3793{
 3794    /* Initialize the Sun's position as a zero vector, then adjust it from pulls from the planets. */
 3795    bary->Sun.tt = tt;
 3796    bary->Sun.r = VecZero;
 3797    bary->Sun.v = VecZero;
 3798
 3799    /* Calculate heliocentric planet positions and SSB. */
 3800    bary->Jupiter = AdjustBarycenterPosVel(&bary->Sun, tt, BODY_JUPITER, JUPITER_GM);
 3801    bary->Saturn  = AdjustBarycenterPosVel(&bary->Sun, tt, BODY_SATURN,  SATURN_GM);
 3802    bary->Uranus  = AdjustBarycenterPosVel(&bary->Sun, tt, BODY_URANUS,  URANUS_GM);
 3803    bary->Neptune = AdjustBarycenterPosVel(&bary->Sun, tt, BODY_NEPTUNE, NEPTUNE_GM);
 3804
 3805    /* Convert planet [pos, vel] from heliocentric to barycentric. */
 3806    VecDecr(&bary->Jupiter.r, bary->Sun.r);  VecDecr(&bary->Jupiter.v, bary->Sun.v);
 3807    VecDecr(&bary->Saturn.r,  bary->Sun.r);  VecDecr(&bary->Saturn.v,  bary->Sun.v);
 3808    VecDecr(&bary->Uranus.r,  bary->Sun.r);  VecDecr(&bary->Uranus.v,  bary->Sun.v);
 3809    VecDecr(&bary->Neptune.r, bary->Sun.r);  VecDecr(&bary->Neptune.v, bary->Sun.v);
 3810
 3811    /* Convert heliocentric SSB to barycentric Sun. */
 3812    VecScale(&bary->Sun.r, -1.0);
 3813    VecScale(&bary->Sun.v, -1.0);
 3814}
 3815
 3816
 3817static void AddAcceleration(terse_vector_t *acc, terse_vector_t small_pos, double gm, terse_vector_t major_pos)
 3818{
 3819    double dx, dy, dz, r2, pull;
 3820
 3821    dx = major_pos.x - small_pos.x;
 3822    dy = major_pos.y - small_pos.y;
 3823    dz = major_pos.z - small_pos.z;
 3824
 3825    r2 = dx*dx + dy*dy + dz*dz;
 3826    pull = gm / (r2 * sqrt(r2));
 3827
 3828    acc->x += dx * pull;
 3829    acc->y += dy * pull;
 3830    acc->z += dz * pull;
 3831}
 3832
 3833
 3834static terse_vector_t SmallBodyAcceleration(terse_vector_t small_pos, const major_bodies_t *bary)
 3835{
 3836    terse_vector_t acc = VecZero;
 3837
 3838    /* Use barycentric coordinates of the Sun and major planets to calculate gravitational accelerations. */
 3839    AddAcceleration(&acc, small_pos, SUN_GM,     bary->Sun.r);
 3840    AddAcceleration(&acc, small_pos, JUPITER_GM, bary->Jupiter.r);
 3841    AddAcceleration(&acc, small_pos, SATURN_GM,  bary->Saturn.r);
 3842    AddAcceleration(&acc, small_pos, URANUS_GM,  bary->Uranus.r);
 3843    AddAcceleration(&acc, small_pos, NEPTUNE_GM, bary->Neptune.r);
 3844
 3845    return acc;
 3846}
 3847
 3848
 3849body_grav_calc_t GravSim(           /* out: [pos, vel, acc] of the simulated body at time tt2 */
 3850    major_bodies_t *bary2,          /* temp: work area for major body barycentric state */
 3851    double tt2,                     /* in:  a target time to be calculated (either before or after tt1) */
 3852    const body_grav_calc_t *calc1)  /* in:  [pos, vel, acc] of the simulated body at time tt1 */
 3853{
 3854    body_grav_calc_t calc2;
 3855    terse_vector_t approx_pos;
 3856    terse_vector_t acc;
 3857    const double dt = tt2 - calc1->tt;
 3858
 3859    /* Calculate where the major bodies (Sun, Jupiter...Neptune) will be at the next time step. */
 3860    MajorBodyBary(bary2, tt2);
 3861
 3862    /* Estimate position of small body as if current acceleration applies across the whole time interval. */
 3863    /* approx_pos = pos1 + vel1*dt + (1/2)acc*dt^2 */
 3864    approx_pos = UpdatePosition(dt, calc1->r, calc1->v, calc1->a);
 3865
 3866    /* Calculate acceleration experienced by small body at approximate next location. */
 3867    acc = SmallBodyAcceleration(approx_pos, bary2);
 3868
 3869    /* Calculate the average acceleration of the endpoints. */
 3870    /* This becomes our estimate of the mean effective acceleration over the whole interval. */
 3871    acc = VecMean(acc, calc1->a);
 3872
 3873    /* Refine the estimates of [pos, vel, acc] at tt2 using the mean acceleration. */
 3874    calc2.r = UpdatePosition(dt, calc1->r, calc1->v, acc);
 3875    calc2.v = UpdateVelocity(dt, calc1->v, acc);
 3876    calc2.a = SmallBodyAcceleration(calc2.r, bary2);
 3877    calc2.tt = tt2;
 3878    return calc2;
 3879}
 3880
 3881
 3882static body_grav_calc_t GravFromState(major_bodies_t *bary, const body_state_t *state)
 3883{
 3884    body_grav_calc_t calc;
 3885
 3886    MajorBodyBary(bary, state->tt);
 3887
 3888    calc.tt = state->tt;
 3889    calc.r  = VecAdd(state->r, bary->Sun.r);      /* convert heliocentric to barycentric */
 3890    calc.v  = VecAdd(state->v, bary->Sun.v);      /* convert heliocentric to barycentric */
 3891    calc.a  = SmallBodyAcceleration(calc.r, bary);
 3892
 3893    return calc;
 3894}
 3895
 3896
 3897static void CalcSolarSystem(astro_grav_sim_t *sim)
 3898{
 3899    int body;
 3900    double tt = sim->curr->time.tt;
 3901    body_state_t *grav = sim->curr->gravitators;
 3902    body_state_t *sun = &grav[BODY_SUN];
 3903
 3904    /* Initialize the Sun's position/velocity as zero vectors, then adjust from pulls from the planets. */
 3905    sun->tt = tt;
 3906    sun->r  = VecZero;
 3907    sun->v  = VecZero;
 3908
 3909    /* Calculate the position of each planet, and adjust the SSB position accordingly. */
 3910    grav[BODY_MERCURY] = AdjustBarycenterPosVel(sun, tt, BODY_MERCURY, MERCURY_GM);
 3911    grav[BODY_VENUS  ] = AdjustBarycenterPosVel(sun, tt, BODY_VENUS,   VENUS_GM);
 3912    grav[BODY_EARTH  ] = AdjustBarycenterPosVel(sun, tt, BODY_EARTH,   EARTH_GM + MOON_GM);
 3913    grav[BODY_MARS   ] = AdjustBarycenterPosVel(sun, tt, BODY_MARS,    MARS_GM);
 3914    grav[BODY_JUPITER] = AdjustBarycenterPosVel(sun, tt, BODY_JUPITER, JUPITER_GM);
 3915    grav[BODY_SATURN ] = AdjustBarycenterPosVel(sun, tt, BODY_SATURN,  SATURN_GM);
 3916    grav[BODY_URANUS ] = AdjustBarycenterPosVel(sun, tt, BODY_URANUS,  URANUS_GM);
 3917    grav[BODY_NEPTUNE] = AdjustBarycenterPosVel(sun, tt, BODY_NEPTUNE, NEPTUNE_GM);
 3918
 3919    /* Convert planet state vectors from heliocentric to barycentric. */
 3920    for (body = BODY_MERCURY; body <= BODY_NEPTUNE; ++body)
 3921    {
 3922        VecDecr(&grav[body].r, sun->r);
 3923        VecDecr(&grav[body].v, sun->v);
 3924    }
 3925
 3926    /* Convert heliocentric SSB to barycentric Sun. */
 3927    VecScale(&sun->r, -1.0);
 3928    VecScale(&sun->v, -1.0);
 3929}
 3930
 3931
 3932static void CalcBodyAccelerations(astro_grav_sim_t *sim)
 3933{
 3934    int i;
 3935    const body_state_t *grav = sim->curr->gravitators;
 3936
 3937    /* Calculate the gravitational acceleration experienced by the simulated bodies. */
 3938    for (i = 0; i < sim->numBodies; ++i)
 3939    {
 3940        body_grav_calc_t *calc = &sim->curr->bodies[i];
 3941
 3942        calc->a = VecZero;
 3943
 3944        AddAcceleration(&calc->a, calc->r, SUN_GM,              grav[BODY_SUN    ].r);
 3945        AddAcceleration(&calc->a, calc->r, MERCURY_GM,          grav[BODY_MERCURY].r);
 3946        AddAcceleration(&calc->a, calc->r, VENUS_GM,            grav[BODY_VENUS  ].r);
 3947        AddAcceleration(&calc->a, calc->r, EARTH_GM + MOON_GM,  grav[BODY_EARTH  ].r);
 3948        AddAcceleration(&calc->a, calc->r, MARS_GM,             grav[BODY_MARS   ].r);
 3949        AddAcceleration(&calc->a, calc->r, JUPITER_GM,          grav[BODY_JUPITER].r);
 3950        AddAcceleration(&calc->a, calc->r, SATURN_GM,           grav[BODY_SATURN ].r);
 3951        AddAcceleration(&calc->a, calc->r, URANUS_GM,           grav[BODY_URANUS ].r);
 3952        AddAcceleration(&calc->a, calc->r, NEPTUNE_GM,          grav[BODY_NEPTUNE].r);
 3953    }
 3954}
 3955
 3956
 3957static body_state_t *GravSimBodyStatePtr(astro_grav_sim_t *sim, astro_body_t body)
 3958{
 3959    /*
 3960        Return a pointer to the place where we cache the barycentric
 3961        state of the given body, or NULL if this body is not one
 3962        that we use for calculating gravitational interactions.
 3963    */
 3964
 3965    if ((body == BODY_SUN) || (body >= BODY_MERCURY && body <= BODY_NEPTUNE))
 3966        return &sim->curr->gravitators[body];
 3967
 3968    return NULL;
 3969}
 3970
 3971
 3972static astro_state_vector_t GravSimOriginState(astro_grav_sim_t *sim)
 3973{
 3974    body_state_t *optr;
 3975    astro_time_t time = sim->curr->time;
 3976
 3977    if (sim->originBody == BODY_SSB)
 3978    {
 3979        /* The barycentric state of the SSB is zero, by definition. */
 3980        astro_state_vector_t state;
 3981        state.status = ASTRO_SUCCESS;
 3982        state.t  = time;
 3983        state.x  = 0.0;
 3984        state.y  = 0.0;
 3985        state.z  = 0.0;
 3986        state.vx = 0.0;
 3987        state.vy = 0.0;
 3988        state.vz = 0.0;
 3989        return state;
 3990    }
 3991
 3992    optr = GravSimBodyStatePtr(sim, sim->originBody);
 3993    if (optr != NULL)
 3994        return ExportState(*optr, time);
 3995
 3996    /* We only support the VSOP bodies, for efficiency. */
 3997    return StateVecError(ASTRO_INVALID_BODY, time);
 3998}
 3999
 4000
 4001static void GravSimDuplicate(astro_grav_sim_t *sim)
 4002{
 4003    /* Copy the current state into the previous state, so that both become the same moment in time. */
 4004    sim->prev->time = sim->curr->time;
 4005    memcpy(sim->prev->gravitators, sim->curr->gravitators, sizeof(sim->prev->gravitators));
 4006    memcpy(sim->prev->bodies, sim->curr->bodies, ((size_t)sim->numBodies) * sizeof(sim->prev->bodies[0]));
 4007}
 4008
 4009
 4010/**
 4011 * @brief Allocate and initialize a gravity step simulator.
 4012 *
 4013 * Prepares to simulate a series of incremental time steps,
 4014 * simulating the movement of zero or more small bodies through the Solar System
 4015 * acting under gravitational attraction from the Sun and planets.
 4016 *
 4017 * After calling this function, you can call #Astronomy_GravSimUpdate
 4018 * as many times as desired to advance the simulation by small time steps.
 4019 *
 4020 * If this function succeeds (returns `ASTRO_SUCCESS`), `sim`
 4021 * will be set to a dynamically allocated object. The caller is
 4022 * then responsible for eventually calling #Astronomy_GravSimFree
 4023 * to release the memory.
 4024 *
 4025 * @param simOut
 4026 *      The address of a pointer to store the newly allocated simulation object.
 4027 *      The type #astro_grav_sim_t is an opaque type, so its internal structure is not documented.
 4028 *
 4029 * @param originBody
 4030 *      Specifies the origin of the reference frame.
 4031 *      All position vectors and velocity vectors will use `originBody`
 4032 *      as the origin of the coordinate system.
 4033 *      This origin applies to all the input vectors provided in the
 4034 *      `bodyStateArray` parameter of this function, along with all
 4035 *      output vectors returned by #Astronomy_GravSimUpdate.
 4036 *      Most callers will want to provide one of the following:
 4037 *      `BODY_SUN` for heliocentric coordinates,
 4038 *      `BODY_SSB` for solar system barycentric coordinates,
 4039 *      or `BODY_EARTH` for geocentric coordinates. Note that the
 4040 *      gravity simulator does not correct for light travel time;
 4041 *      all state vectors are tied to a Newtonian "instantaneous" time.
 4042 *
 4043 * @param time
 4044 *      The initial time at which to start the simulation.
 4045 *
 4046 * @param numBodies
 4047 *      The number of small bodies to be simulated. This may be any non-negative integer.
 4048 *
 4049 * @param bodyStateArray
 4050 *      An array of initial state vectors (positions and velocities) of the small bodies to be simulated.
 4051 *      The caller must know the positions and velocities of the small bodies at an initial moment in time.
 4052 *      Their positions and velocities are expressed with respect to `originBody`, using J2000 mean equator orientation (EQJ).
 4053 *      Positions are expressed in astronomical units (AU). Velocities are expressed in AU/day.
 4054 *      All the times embedded within the state vectors must be exactly equal to `time`,
 4055 *      or this function will fail with the error `ASTRO_INCONSISTENT_TIMES`.
 4056 *
 4057 * @return
 4058 *      `ASTRO_SUCCESS` on success, with `*sim` set to a non-NULL value. Otherwise an error code with `*sim` set to NULL.
 4059 */
 4060astro_status_t Astronomy_GravSimInit(
 4061    astro_grav_sim_t **simOut,
 4062    astro_body_t originBody,
 4063    astro_time_t time,
 4064    int numBodies,
 4065    const astro_state_vector_t *bodyStateArray)
 4066{
 4067    astro_grav_sim_t *sim;
 4068    astro_status_t status;
 4069    body_grav_calc_t *array;
 4070    int i;
 4071
 4072    /* Validate parameters before attempting to allocate memory. */
 4073
 4074    if (simOut == NULL)
 4075        return ASTRO_INVALID_PARAMETER;
 4076
 4077    *simOut = NULL;
 4078
 4079    if (numBodies < 0)
 4080        return ASTRO_INVALID_PARAMETER;
 4081
 4082    if (numBodies > 0 && bodyStateArray == NULL)
 4083        return ASTRO_INVALID_PARAMETER;
 4084
 4085    if (originBody < BODY_MERCURY || originBody > BODY_SSB)
 4086        return ASTRO_INVALID_BODY;
 4087
 4088    /* Verify that all the state vectors are valid and have matching times. */
 4089    for (i = 0; i < numBodies; ++i)
 4090    {
 4091        if (bodyStateArray[i].status != ASTRO_SUCCESS)
 4092            return ASTRO_INVALID_PARAMETER;
 4093
 4094        if (bodyStateArray[i].t.tt != time.tt)
 4095            return ASTRO_INCONSISTENT_TIMES;
 4096    }
 4097
 4098    *simOut = sim = (astro_grav_sim_t *) calloc(1, sizeof(astro_grav_sim_t));
 4099    if (sim == NULL)
 4100        return ASTRO_OUT_OF_MEMORY;
 4101
 4102    sim->originBody = originBody;
 4103    sim->numBodies = numBodies;
 4104    sim->prev = &(sim->endpoint[0]);
 4105    sim->curr = &(sim->endpoint[1]);
 4106    sim->curr->time = time;
 4107
 4108    if (numBodies > 0)
 4109    {
 4110        sim->prev->bodies = (body_grav_calc_t *) calloc(numBodies, sizeof(body_grav_calc_t));
 4111        sim->curr->bodies = (body_grav_calc_t *) calloc(numBodies, sizeof(body_grav_calc_t));
 4112        if (sim->prev->bodies == NULL || sim->curr->bodies == NULL)
 4113        {
 4114            status = ASTRO_OUT_OF_MEMORY;
 4115            goto fail;
 4116        }
 4117    }
 4118
 4119    /* Remember the initial states of all the bodies as "current". */
 4120    /* Convert from the public type astro_state_t to our internal type body_grav_calc_t. */
 4121    array = sim->curr->bodies;
 4122    for (i = 0; i < numBodies; ++i)
 4123    {
 4124        array[i].tt  = bodyStateArray[i].t.tt;
 4125        array[i].r.x = bodyStateArray[i].x;
 4126        array[i].r.y = bodyStateArray[i].y;
 4127        array[i].r.z = bodyStateArray[i].z;
 4128        array[i].v.x = bodyStateArray[i].vx;
 4129        array[i].v.y = bodyStateArray[i].vy;
 4130        array[i].v.z = bodyStateArray[i].vz;
 4131    }
 4132
 4133    /* Calculate the state of the Sun and planets. */
 4134    CalcSolarSystem(sim);
 4135
 4136    /*
 4137        We need to do all the physics calculations in barycentric coordinates.
 4138        But the caller can provide the input vectors with respect to any body.
 4139        Correct the input body state vectors for the specified coordinate origin.
 4140    */
 4141    if (originBody != BODY_SSB)
 4142    {
 4143        /* Determine the barycentric state of the origin body. */
 4144        astro_state_vector_t originState = GravSimOriginState(sim);
 4145        if (originState.status != ASTRO_SUCCESS)
 4146        {
 4147            status = originState.status;
 4148            goto fail;
 4149        }
 4150
 4151        /* Add barycentric origin to origin-centric body to obtain barycentric body. */
 4152        for (i = 0; i < numBodies; ++i)
 4153        {
 4154            array[i].r.x += originState.x;
 4155            array[i].r.y += originState.y;
 4156            array[i].r.z += originState.z;
 4157            array[i].v.x += originState.vx;
 4158            array[i].v.y += originState.vy;
 4159            array[i].v.z += originState.vz;
 4160        }
 4161    }
 4162
 4163    /* Calculate the net acceleration experienced by the small bodies. */
 4164    CalcBodyAccelerations(sim);
 4165
 4166    /* To prepare for a possible swap operation, duplicate the current state into the previous state. */
 4167    GravSimDuplicate(sim);
 4168
 4169    return ASTRO_SUCCESS;
 4170
 4171fail:
 4172    Astronomy_GravSimFree(sim);
 4173    *simOut = NULL;
 4174    return status;
 4175}
 4176
 4177
 4178/**
 4179 * @brief Advances a gravity simulation by a small time step.
 4180 *
 4181 * @param sim
 4182 *      A simulation object that was created by a prior call to #Astronomy_GravSimInit.
 4183 *
 4184 * @param time
 4185 *      A time that is a small increment away from the current simulation time.
 4186 *      It is up to the developer to figure out an appropriate time increment.
 4187 *      Depending on the trajectories, a smaller or larger increment
 4188 *      may be needed for the desired accuracy. Some experimentation may be needed.
 4189 *      Generally, bodies that stay in the outer Solar System and move slowly can
 4190 *      use larger time steps.  Bodies that pass into the inner Solar System and
 4191 *      move faster will need a smaller time step to maintain accuracy.
 4192 *      The `time` value may be after or before the current simulation time
 4193 *      to move forward or backward in time.
 4194 *
 4195 * @param numBodies
 4196 *      The number of bodies whose state vectors are to be updated.
 4197 *      This is the number of elements in the `bodyStateArray`.
 4198 *      This parameter is passed as a sanity check, and must be equal
 4199 *      to the value passed to #Astronomy_GravSimInit when `sim` was created.
 4200 *
 4201 * @param bodyStateArray
 4202 *      An array big enough to hold `numBodies` state vectors, to receive
 4203 *      the updated positions and velocities of the simulated small bodies.
 4204 *      Alternatively, `bodyStateArray` may be NULL if the output of this
 4205 *      simulation step is not needed. This makes the call slightly faster.
 4206 *
 4207 * @return
 4208 *      `ASTRO_SUCCESS` if the calculation was successful.
 4209 *      Otherwise, an error code if something went wrong, in which case
 4210 *      the simulation should be considered "broken". This means there
 4211 *      is no reliable output in `bodyStateArray` and that no more calculations
 4212 *      can be performed with `sim`.
 4213 */
 4214astro_status_t Astronomy_GravSimUpdate(
 4215    astro_grav_sim_t *sim,
 4216    astro_time_t time,
 4217    int numBodies,
 4218    astro_state_vector_t *bodyStateArray)
 4219{
 4220    terse_vector_t acc;
 4221    double dt;      /* terrestrial time increment */
 4222    int i;
 4223
 4224    /*
 4225        The caller's understanding of the number of bodies must match the actual
 4226        array size in `sim`, or we risk corrupting/accessing invalid memory.
 4227    */
 4228    if (numBodies != sim->numBodies)
 4229        return ASTRO_INVALID_PARAMETER;
 4230
 4231    dt = time.tt - sim->curr->time.tt;
 4232
 4233    if (dt == 0.0)
 4234    {
 4235        /*
 4236            Special case: the time has not changed, so skip the usual physics calculations.
 4237            This allows a way for the caller to query the current state if desired.
 4238            It is also necessary to avoid dividing by `dt` if `dt` is zero.
 4239            To prepare for a possible swap operation, duplicate the current state into the previous state.
 4240        */
 4241        GravSimDuplicate(sim);
 4242    }
 4243    else
 4244    {
 4245        /* Swap the current state and the previous state. Then calculate the new current state. */
 4246        Astronomy_GravSimSwap(sim);
 4247
 4248        /* Update the current time. This is the only place we have a full (tt,ut) pair. */
 4249        /* All of the Newtonian dynamics are calculated using tt only. */
 4250        sim->curr->time = time;
 4251
 4252        /* Now that sim->time is set, it is safe to call `CalcSolarSystem`. */
 4253        CalcSolarSystem(sim);
 4254
 4255        for (i = 0; i < numBodies; ++i)
 4256        {
 4257            /*
 4258                Estimate the positions of the small bodies as if their
 4259                current accelerations apply across the whole time interval.
 4260                approx_pos = pos1 + vel1*dt + (1/2)acc*dt^2
 4261            */
 4262            const body_grav_calc_t *prev = &sim->prev->bodies[i];
 4263            sim->curr->bodies[i].r = UpdatePosition(dt, prev->r, prev->v, prev->a);
 4264        }
 4265
 4266        /*
 4267            Calculate the acceleration experienced by the small bodies
 4268            at their respective approximate next locations.
 4269        */
 4270        CalcBodyAccelerations(sim);
 4271
 4272        for (i = 0; i < numBodies; ++i)
 4273        {
 4274            const body_grav_calc_t *prev = &sim->prev->bodies[i];
 4275            body_grav_calc_t *curr = &sim->curr->bodies[i];
 4276
 4277            /*
 4278                Calculate the average of the acceleration vectors
 4279                experienced by the previous body positions and
 4280                their estimated next positions.
 4281                These become estimates of the mean effective accelerations over the whole interval.
 4282            */
 4283            acc = VecMean(prev->a, curr->a);
 4284
 4285            /*
 4286                Refine the estimates of position and velocity at the next time step,
 4287                using the mean acceleration as a better approximation of the
 4288                continuously changing acceleration acting on each body.
 4289            */
 4290            curr->tt = time.tt;
 4291            curr->r = UpdatePosition(dt, prev->r, prev->v, acc);
 4292            curr->v = UpdateVelocity(dt, prev->v, acc);
 4293        }
 4294
 4295        /*
 4296            Re-calculate accelerations experienced by each body.
 4297            These will be needed for the next simulation step (if any).
 4298            Also, they will be potentially useful if some day we add
 4299            a function to query the acceleration vectors for the bodies.
 4300        */
 4301        CalcBodyAccelerations(sim);
 4302    }
 4303
 4304    /*
 4305        Translate our internal calculations of body positions
 4306        and velocities into state vectors that the caller can understand.
 4307        But if the output buffer `bodyStateArray` is NULL, it means
 4308        the caller wanted us to update the simulation state without
 4309        returning any output.
 4310    */
 4311    if (bodyStateArray != NULL)
 4312    {
 4313        for (i = 0; i < numBodies; ++i)
 4314            bodyStateArray[i] = ExportGravCalc(sim->curr->bodies[i], time);
 4315
 4316        if (sim->originBody != BODY_SSB)
 4317        {
 4318            /* Determine the barycentric state of the origin body. */
 4319            astro_state_vector_t originState = GravSimOriginState(sim);
 4320            if (originState.status != ASTRO_SUCCESS)
 4321                return originState.status;
 4322
 4323            /* Subtract vectors to convert barycentric states to origin-centric states. */
 4324            for (i = 0; i < numBodies; ++i)
 4325            {
 4326                bodyStateArray[i].x  -= originState.x;
 4327                bodyStateArray[i].y  -= originState.y;
 4328                bodyStateArray[i].z  -= originState.z;
 4329                bodyStateArray[i].vx -= originState.vx;
 4330                bodyStateArray[i].vy -= originState.vy;
 4331                bodyStateArray[i].vz -= originState.vz;
 4332            }
 4333        }
 4334    }
 4335
 4336    return ASTRO_SUCCESS;
 4337}
 4338
 4339
 4340/**
 4341 * @brief Get the position and velocity of a Solar System body included in the simulation.
 4342 *
 4343 * In order to simulate the movement of small bodies through the Solar System,
 4344 * the simulator needs to calculate the state vectors for the Sun and planets.
 4345 *
 4346 * If an application wants to know the positions of one or more of the planets
 4347 * in addition to the small bodies, this function provides a way to obtain
 4348 * their state vectors. This is provided for the sake of efficiency, to avoid
 4349 * redundant calculations.
 4350 *
 4351 * @param sim
 4352 *      A gravity simulator object created by a successful call to #Astronomy_GravSimInit.
 4353 *
 4354 * @param body
 4355 *      The Sun, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, or Neptune.
 4356 *
 4357 * @return
 4358 *      If the given body is part of the set of calculated bodies
 4359 *      (Sun and planets), returns the current time step's state
 4360 *      vector for that body, expressed in the coordinate system
 4361 *      that was specified by the `originBody` parameter to #Astronomy_GravSimInit.
 4362 *      Success is indicated by the returned structure's `status` field holding
 4363 *      `ASTRO_SUCCESS`. Any other `status` value indicates an error, meaning
 4364 *      the returned state vector is invalid.
 4365 */
 4366astro_state_vector_t Astronomy_GravSimBodyState(
 4367    astro_grav_sim_t *sim,
 4368    astro_body_t body)
 4369{
 4370    astro_state_vector_t state;
 4371    body_state_t *bptr;
 4372
 4373    bptr = GravSimBodyStatePtr(sim, body);
 4374    if (bptr == NULL)
 4375        return StateVecError(ASTRO_INVALID_BODY, sim->curr->time);
 4376
 4377    state = GravSimOriginState(sim);
 4378    if (state.status != ASTRO_SUCCESS)
 4379        return state;
 4380
 4381    /*
 4382        Subtract the origin's state from the body's barycentric
 4383        state to get the body in the desired reference frame.
 4384        state.t and state.status are already initialized correctly.
 4385    */
 4386
 4387    state.x  = bptr->r.x - state.x;
 4388    state.y  = bptr->r.y - state.y;
 4389    state.z  = bptr->r.z - state.z;
 4390    state.vx = bptr->v.x - state.vx;
 4391    state.vy = bptr->v.y - state.vy;
 4392    state.vz = bptr->v.z - state.vz;
 4393
 4394    return state;
 4395}
 4396
 4397
 4398/**
 4399 * @brief Exchange the current time step with the previous time step.
 4400 *
 4401 * Sometimes it is helpful to "explore" various times near a given
 4402 * simulation time step, while repeatedly returning to the original
 4403 * time step. For example, when backdating a position for light travel
 4404 * time, the caller may wish to repeatedly try different amounts of
 4405 * backdating. When the backdating solver has converged, the caller
 4406 * wants to leave the simulation in its original state.
 4407 *
 4408 * This function allows a single "undo" of a simulation, and does so
 4409 * very efficiently.
 4410 *
 4411 * Usually this function will be called immediately after a matching
 4412 * call to #Astronomy_GravSimUpdate. It has the effect of rolling
 4413 * back the most recent update. If called twice in a row, it reverts
 4414 * the swap and thus has no net effect.
 4415 *
 4416 * #Astronomy_GravSimInit initializes the current state and previous
 4417 * state to be identical. Both states represent the `time` parameter that was
 4418 * passed into the initializer. Therefore, `Astronomy_GravSimSwap` will
 4419 * have no effect from the caller's point of view when passed a simulator
 4420 * that has not yet been updated by a call to #Astronomy_GravSimUpdate.
 4421 *
 4422 * @param sim
 4423 *      A gravity simulator object that was created by a prior call to #Astronomy_GravSimInit.
 4424 */
 4425void Astronomy_GravSimSwap(astro_grav_sim_t *sim)
 4426{
 4427    gravsim_endpoint_t *swap = sim->prev;
 4428    sim->prev = sim->curr;
 4429    sim->curr = swap;
 4430}
 4431
 4432
 4433/**
 4434 * @brief Returns the time of the current simulation step.
 4435 *
 4436 * @param sim
 4437 *      A gravity simulator object that was created by a prior call to #Astronomy_GravSimInit.
 4438 */
 4439astro_time_t Astronomy_GravSimTime(const astro_grav_sim_t *sim)
 4440{
 4441    return sim->curr->time;
 4442}
 4443
 4444
 4445/**
 4446 * @brief Returns the number of small bodies represented in this simulation.
 4447 *
 4448 * When a simulation is created by a call to #Astronomy_GravSimInit,
 4449 * the caller specifies the number of small bodies.
 4450 * This function returns that same number, which may be convenient for a caller,
 4451 * so that it does not need to track the body count separately.
 4452 *
 4453 * @param sim
 4454 *      A gravity simulator object that was created by a prior call to #Astronomy_GravSimInit.
 4455 */
 4456int Astronomy_GravSimNumBodies(const astro_grav_sim_t *sim)
 4457{
 4458    return sim->numBodies;
 4459}
 4460
 4461
 4462/**
 4463 * @brief Returns the body whose center is the coordinate origin that small bodies are referenced to.
 4464 *
 4465 * When a simulation is created by a call to #Astronomy_GravSimInit,
 4466 * the caller specifies an `originBody` to indicate the coordinate origin
 4467 * used to represent the small bodies being simulated.
 4468 * This function returns that same #astro_body_t value.
 4469 *
 4470 * @param sim
 4471 *      A gravity simulator object that was created by a prior call to #Astronomy_GravSimInit.
 4472 */
 4473astro_body_t Astronomy_GravSimOrigin(const astro_grav_sim_t *sim)
 4474{
 4475    return sim->originBody;
 4476}
 4477
 4478
 4479/**
 4480 * @brief Releases memory allocated to a gravity simulator object.
 4481 *
 4482 * To avoid memory leaks, any successful call to #Astronomy_GravSimInit
 4483 * must be paired with a matching call to `Astronomy_GravSimFree`.
 4484 *
 4485 * @param sim
 4486 *      A gravity simulator object that was created by a prior call to #Astronomy_GravSimInit.
 4487 */
 4488void Astronomy_GravSimFree(astro_grav_sim_t *sim)
 4489{
 4490    if (sim != NULL)
 4491    {
 4492        free(sim->endpoint[0].bodies);
 4493        free(sim->endpoint[1].bodies);
 4494        free(sim);
 4495    }
 4496}
 4497
 4498
 4499/*------------------ begin Pluto integrator ------------------*/
 4500
 4501static const body_state_t PlutoStateTable[] =
 4502{
 4503    {  -730000.0, {-26.118207232108, -14.376168177825,   3.384402515299}, { 1.6339372163656e-03, -2.7861699588508e-03, -1.3585880229445e-03} }
 4504,   {  -700800.0, { 41.974905202127,  -0.448502952929, -12.770351505989}, { 7.3458569351457e-04,  2.2785014891658e-03,  4.8619778602049e-04} }
 4505,   {  -671600.0, { 14.706930780744,  44.269110540027,   9.353698474772}, {-2.1000147999800e-03,  2.2295915939915e-04,  7.0143443551414e-04} }
 4506,   {  -642400.0, {-29.441003929957,  -6.430161530570,   6.858481011305}, { 8.4495803960544e-04, -3.0783914758711e-03, -1.2106305981192e-03} }
 4507,   {  -613200.0, { 39.444396946234,  -6.557989760571, -13.913760296463}, { 1.1480029005873e-03,  2.2400006880665e-03,  3.5168075922288e-04} }
 4508,   {  -584000.0, { 20.230380950700,  43.266966657189,   7.382966091923}, {-1.9754081700585e-03,  5.3457141292226e-04,  7.5929169129793e-04} }
 4509,   {  -554800.0, {-30.658325364620,   2.093818874552,   9.880531138071}, { 6.1010603013347e-05, -3.1326500935382e-03, -9.9346125151067e-04} }
 4510,   {  -525600.0, { 35.737703251673, -12.587706024764, -14.677847247563}, { 1.5802939375649e-03,  2.1347678412429e-03,  1.9074436384343e-04} }
 4511,   {  -496400.0, { 25.466295188546,  41.367478338417,   5.216476873382}, {-1.8054401046468e-03,  8.3283083599510e-04,  8.0260156912107e-04} }
 4512,   {  -467200.0, {-29.847174904071,  10.636426313081,  12.297904180106}, {-6.3257063052907e-04, -2.9969577578221e-03, -7.4476074151596e-04} }
 4513,   {  -438000.0, { 30.774692107687, -18.236637015304, -14.945535879896}, { 2.0113162005465e-03,  1.9353827024189e-03, -2.0937793168297e-06} }
 4514,   {  -408800.0, { 30.243153324028,  38.656267888503,   2.938501750218}, {-1.6052508674468e-03,  1.1183495337525e-03,  8.3333973416824e-04} }
 4515,   {  -379600.0, {-27.288984772533,  18.643162147874,  14.023633623329}, {-1.1856388898191e-03, -2.7170609282181e-03, -4.9015526126399e-04} }
 4516,   {  -350400.0, { 24.519605196774, -23.245756064727, -14.626862367368}, { 2.4322321483154e-03,  1.6062008146048e-03, -2.3369181613312e-04} }
 4517,   {  -321200.0, { 34.505274805875,  35.125338586954,   0.557361475637}, {-1.3824391637782e-03,  1.3833397561817e-03,  8.4823598806262e-04} }
 4518,   {  -292000.0, {-23.275363915119,  25.818514298769,  15.055381588598}, {-1.6062295460975e-03, -2.3395961498533e-03, -2.4377362639479e-04} }
 4519,   {  -262800.0, { 17.050384798092, -27.180376290126, -13.608963321694}, { 2.8175521080578e-03,  1.1358749093955e-03, -4.9548725258825e-04} }
 4520,   {  -233600.0, { 38.093671910285,  30.880588383337,  -1.843688067413}, {-1.1317697153459e-03,  1.6128814698472e-03,  8.4177586176055e-04} }
 4521,   {  -204400.0, {-18.197852930878,  31.932869934309,  15.438294826279}, {-1.9117272501813e-03, -1.9146495909842e-03, -1.9657304369835e-05} }
 4522,   {  -175200.0, {  8.528924039997, -29.618422200048, -11.805400994258}, { 3.1034370787005e-03,  5.1393633292430e-04, -7.7293066202546e-04} }
 4523,   {  -146000.0, { 40.946857258640,  25.904973592021,  -4.256336240499}, {-8.3652705194051e-04,  1.8129497136404e-03,  8.1564228273060e-04} }
 4524,   {  -116800.0, {-12.326958895325,  36.881883446292,  15.217158258711}, {-2.1166103705038e-03, -1.4814420035990e-03,  1.7401209844705e-04} }
 4525,   {   -87600.0, { -0.633258375909, -30.018759794709,  -9.171932874950}, { 3.2016994581737e-03, -2.5279858672148e-04, -1.0411088271861e-03} }
 4526,   {   -58400.0, { 42.936048423883,  20.344685584452,  -6.588027007912}, {-5.0525450073192e-04,  1.9910074335507e-03,  7.7440196540269e-04} }
 4527,   {   -29200.0, { -5.975910552974,  40.611809958460,  14.470131723673}, {-2.2184202156107e-03, -1.0562361130164e-03,  3.3652250216211e-04} }
 4528,   {        0.0, { -9.875369580774, -27.978926224737,  -5.753711824704}, { 3.0287533248818e-03, -1.1276087003636e-03, -1.2651326732361e-03} }
 4529,   {    29200.0, { 43.958831986165,  14.214147973292,  -8.808306227163}, {-1.4717608981871e-04,  2.1404187242141e-03,  7.1486567806614e-04} }
 4530,   {    58400.0, {  0.678136763520,  43.094461639362,  13.243238780721}, {-2.2358226110718e-03, -6.3233636090933e-04,  4.7664798895648e-04} }
 4531,   {    87600.0, {-18.282602096834, -23.305039586660,  -1.766620508028}, { 2.5567245263557e-03, -1.9902940754171e-03, -1.3943491701082e-03} }
 4532,   {   116800.0, { 43.873338744526,   7.700705617215, -10.814273666425}, { 2.3174803055677e-04,  2.2402163127924e-03,  6.2988756452032e-04} }
 4533,   {   146000.0, {  7.392949027906,  44.382678951534,  11.629500214854}, {-2.1932815453830e-03, -2.1751799585364e-04,  5.9556516201114e-04} }
 4534,   {   175200.0, {-24.981690229261, -16.204012851426,   2.466457544298}, { 1.8193989149580e-03, -2.6765419531201e-03, -1.3848283502247e-03} }
 4535,   {   204400.0, { 42.530187039511,   0.845935508021, -12.554907527683}, { 6.5059779150669e-04,  2.2725657282262e-03,  5.1133743202822e-04} }
 4536,   {   233600.0, { 13.999526486822,  44.462363044894,   9.669418486465}, {-2.1079296569252e-03,  1.7533423831993e-04,  6.9128485798076e-04} }
 4537,   {   262800.0, {-29.184024803031,  -7.371243995762,   6.493275957928}, { 9.3581363109681e-04, -3.0610357109184e-03, -1.2364201089345e-03} }
 4538,   {   292000.0, { 39.831980671753,  -6.078405766765, -13.909815358656}, { 1.1117769689167e-03,  2.2362097830152e-03,  3.6230548231153e-04} }
 4539,   {   321200.0, { 20.294955108476,  43.417190420251,   7.450091985932}, {-1.9742157451535e-03,  5.3102050468554e-04,  7.5938408813008e-04} }
 4540,   {   350400.0, {-30.669992302160,   2.318743558955,   9.973480913858}, { 4.5605107450676e-05, -3.1308219926928e-03, -9.9066533301924e-04} }
 4541,   {   379600.0, { 35.626122155983, -12.897647509224, -14.777586508444}, { 1.6015684949743e-03,  2.1171931182284e-03,  1.8002516202204e-04} }
 4542,   {   408800.0, { 26.133186148561,  41.232139187599,   5.006401326220}, {-1.7857704419579e-03,  8.6046232702817e-04,  8.0614690298954e-04} }
 4543,   {   438000.0, {-29.576740229230,  11.863535943587,  12.631323039872}, {-7.2292830060955e-04, -2.9587820140709e-03, -7.0824296450300e-04} }
 4544,   {   467200.0, { 29.910805787391, -19.159019294000, -15.013363865194}, { 2.0871080437997e-03,  1.8848372554514e-03, -3.8528655083926e-05} }
 4545,   {   496400.0, { 31.375957451819,  38.050372720763,   2.433138343754}, {-1.5546055556611e-03,  1.1699815465629e-03,  8.3565439266001e-04} }
 4546,   {   525600.0, {-26.360071336928,  20.662505904952,  14.414696258958}, {-1.3142373118349e-03, -2.6236647854842e-03, -4.2542017598193e-04} }
 4547,   {   554800.0, { 22.599441488648, -24.508879898306, -14.484045731468}, { 2.5454108304806e-03,  1.4917058755191e-03, -3.0243665086079e-04} }
 4548,   {   584000.0, { 35.877864013014,  33.894226366071,  -0.224524636277}, {-1.2941245730845e-03,  1.4560427668319e-03,  8.4762160640137e-04} }
 4549,   {   613200.0, {-21.538149762417,  28.204068269761,  15.321973799534}, {-1.7312117409010e-03, -2.1939631314577e-03, -1.6316913275180e-04} }
 4550,   {   642400.0, { 13.971521374415, -28.339941764789, -13.083792871886}, { 2.9334630526035e-03,  9.1860931752944e-04, -5.9939422488627e-04} }
 4551,   {   671600.0, { 39.526942044143,  28.939897360110,  -2.872799527539}, {-1.0068481658095e-03,  1.7021132888090e-03,  8.3578230511981e-04} }
 4552,   {   700800.0, {-15.576200701394,  34.399412961275,  15.466033737854}, {-2.0098814612884e-03, -1.7191109825989e-03,  7.0414782780416e-05} }
 4553,   {   730000.0, {  4.243252837090, -30.118201690825, -10.707441231349}, { 3.1725847067411e-03,  1.6098461202270e-04, -9.0672150593868e-04} }
 4554};
 4555
 4556/* FIXFIXFIX - Using a global is not thread-safe. Either add thread-locks or change API to accept a cache pointer. */
 4557static body_segment_t *pluto_cache[PLUTO_NUM_STATES-1];
 4558
 4559
 4560static int ClampIndex(double frac, int nsteps)
 4561{
 4562    int index = (int) floor(frac);
 4563    if (index < 0)
 4564        return 0;
 4565    if (index >= nsteps)
 4566        return nsteps-1;
 4567    return index;
 4568}
 4569
 4570
 4571static astro_status_t GetSegment(int *seg_index, body_segment_t *cache[], double tt)
 4572{
 4573    int i;
 4574    body_segment_t reverse;
 4575    body_segment_t *seg;
 4576    major_bodies_t bary;
 4577    double step_tt, ramp;
 4578
 4579    if (tt < PlutoStateTable[0].tt || tt > PlutoStateTable[PLUTO_NUM_STATES-1].tt)
 4580    {
 4581        /* We don't bother calculating a segment. Let the caller crawl backward/forward to this time. */
 4582        *seg_index = -1;
 4583        return ASTRO_SUCCESS;
 4584    }
 4585
 4586    /* See if we have a segment that straddles the requested time. */
 4587    /* If so, return it. Otherwise, calculate it and return it. */
 4588
 4589    *seg_index = ClampIndex((tt - PlutoStateTable[0].tt) / PLUTO_TIME_STEP, PLUTO_NUM_STATES-1);
 4590    if (cache[*seg_index] == NULL)
 4591    {
 4592        /* Allocate memory for the segment (about 11K each). */
 4593        seg = cache[*seg_index] = (body_segment_t *) calloc(1, sizeof(body_segment_t));
 4594        if (seg == NULL)
 4595            return ASTRO_OUT_OF_MEMORY;
 4596
 4597        /* Calculate the segment. */
 4598        /* Pick the pair of bracketing body states to fill the segment. */
 4599
 4600        /* Each endpoint is exact. */
 4601        seg->step[0] = GravFromState(&bary, &PlutoStateTable[*seg_index]);
 4602        seg->step[PLUTO_NSTEPS-1] = GravFromState(&bary, &PlutoStateTable[*seg_index + 1]);
 4603
 4604        /* Simulate forwards from the lower time bound. */
 4605        step_tt = seg->step[0].tt;
 4606        for (i=1; i < PLUTO_NSTEPS-1; ++i)
 4607            seg->step[i] = GravSim(&bary, step_tt += PLUTO_DT, &seg->step[i-1]);
 4608
 4609        /* Simulate backwards from the upper time bound. */
 4610        step_tt = seg->step[PLUTO_NSTEPS-1].tt;
 4611        reverse.step[PLUTO_NSTEPS-1] = seg->step[PLUTO_NSTEPS-1];
 4612        for (i=PLUTO_NSTEPS-2; i > 0; --i)
 4613            reverse.step[i] = GravSim(&bary, step_tt -= PLUTO_DT, &reverse.step[i+1]);
 4614
 4615        /* Fade-mix the two series so that there are no discontinuities. */
 4616        for (i=PLUTO_NSTEPS-2; i > 0; --i)
 4617        {
 4618            ramp = (double)i / (PLUTO_NSTEPS-1);
 4619            seg->step[i].r = VecRamp(seg->step[i].r, reverse.step[i].r, ramp);
 4620            seg->step[i].v = VecRamp(seg->step[i].v, reverse.step[i].v, ramp);
 4621            seg->step[i].a = VecRamp(seg->step[i].a, reverse.step[i].a, ramp);
 4622        }
 4623    }
 4624
 4625    return ASTRO_SUCCESS;
 4626}
 4627
 4628
 4629static body_grav_calc_t CalcPlutoOneWay(major_bodies_t *bary, const body_state_t *init_state, double target_tt, double dt)
 4630{
 4631    body_grav_calc_t calc;
 4632    int i, n;
 4633
 4634    calc = GravFromState(bary, init_state);
 4635    n = (int) ceil((target_tt - calc.tt) / dt);
 4636    for (i=0; i < n; ++i)
 4637        calc = GravSim(bary, (i+1 == n) ? target_tt : (calc.tt + dt), &calc);
 4638
 4639    return calc;
 4640}
 4641
 4642
 4643static astro_status_t CalcPluto(body_state_t *bstate, astro_time_t time, int helio)
 4644{
 4645    terse_vector_t acc, ra, rb, va, vb;
 4646    major_bodies_t bary;
 4647    const body_segment_t *seg;
 4648    int seg_index, left;
 4649    const body_grav_calc_t *s1;
 4650    const body_grav_calc_t *s2;
 4651    body_grav_calc_t calc;
 4652    astro_status_t status;
 4653    double ramp;
 4654
 4655    memset(bstate, 0, sizeof(body_state_t));
 4656    bstate->tt = time.tt;
 4657
 4658    status = GetSegment(&seg_index, pluto_cache, time.tt);
 4659    if (status != ASTRO_SUCCESS)
 4660        return status;
 4661
 4662    if (seg_index < 0)
 4663    {
 4664        /* The target time is outside the year range 0000..4000. */
 4665        /* Calculate it by crawling backward from 0000 or forward from 4000. */
 4666        /* FIXFIXFIX - This is super slow. Could optimize this with extra caching if needed. */
 4667        if (time.tt < PlutoStateTable[0].tt)
 4668            calc = CalcPlutoOneWay(&bary, &PlutoStateTable[0], time.tt, -PLUTO_DT);
 4669        else
 4670            calc = CalcPlutoOneWay(&bary, &PlutoStateTable[PLUTO_NUM_STATES-1], time.tt, +PLUTO_DT);
 4671
 4672        bstate->r  = calc.r;
 4673        bstate->v  = calc.v;
 4674    }
 4675    else
 4676    {
 4677        seg = pluto_cache[seg_index];
 4678        left = ClampIndex((time.tt - seg->step[0].tt) / PLUTO_DT, PLUTO_NSTEPS-1);
 4679        s1 = &seg->step[left];
 4680        s2 = &seg->step[left+1];
 4681
 4682        /* Find mean acceleration vector over the interval. */
 4683        acc = VecMean(s1->a, s2->a);
 4684
 4685        /* Use Newtonian mechanics to extrapolate away from t1 in the positive time direction. */
 4686        ra = UpdatePosition(time.tt - s1->tt, s1->r, s1->v, acc);
 4687        va = UpdateVelocity(time.tt - s1->tt, s1->v, acc);
 4688
 4689        /* Use Newtonian mechanics to extrapolate away from t2 in the negative time direction. */
 4690        rb = UpdatePosition(time.tt - s2->tt, s2->r, s2->v, acc);
 4691        vb = UpdateVelocity(time.tt - s2->tt, s2->v, acc);
 4692
 4693        /* Use fade in/out idea to blend the two position estimates. */
 4694        ramp = (time.tt - s1->tt)/PLUTO_DT;
 4695        bstate->r = VecRamp(ra, rb, ramp);
 4696        bstate->v = VecRamp(va, vb, ramp);
 4697
 4698        if (helio)
 4699            MajorBodyBary(&bary, time.tt);
 4700    }
 4701
 4702    if (helio)
 4703    {
 4704        /* Convert barycentric coordinates back to heliocentric coordinates. */
 4705        VecDecr(&bstate->r, bary.Sun.r);
 4706        VecDecr(&bstate->v, bary.Sun.v);
 4707    }
 4708
 4709    return ASTRO_SUCCESS;
 4710}
 4711
 4712/*------------------ end Pluto integrator ------------------*/
 4713
 4714
 4715/*---------------------- begin Jupiter moons ----------------------*/
 4716
 4717static const astro_rotation_t Rotation_JUP_EQJ =
 4718{
 4719    ASTRO_SUCCESS,
 4720    {
 4721        {  9.99432765338654e-01, -3.36771074697641e-02,  0.00000000000000e+00 },
 4722        {  3.03959428906285e-02,  9.02057912352809e-01,  4.30543388542295e-01 },
 4723        { -1.44994559663353e-02, -4.30299169409101e-01,  9.02569881273754e-01 }
 4724    }
 4725};
 4726
 4727static const vsop_term_t jm_Io_a[] =
 4728{
 4729    {  0.0028210960212903,  0.0000000000000000e+00,  0.0000000000000000e+00 }
 4730};
 4731
 4732static const vsop_term_t jm_Io_l[] =
 4733{
 4734    { -0.0001925258348666,  4.9369589722644998e+00,  1.3584836583050000e-02 },
 4735    { -0.0000970803596076,  4.3188796477322002e+00,  1.3034138432430000e-02 },
 4736    { -0.0000898817416500,  1.9080016428616999e+00,  3.0506486715799999e-03 },
 4737    { -0.0000553101050262,  1.4936156681568999e+00,  1.2938928911549999e-02 }
 4738};
 4739
 4740static const vsop_term_t jm_Io_z[] =
 4741{
 4742    {  0.0041510849668155,  4.0899396355450000e+00, -1.2906864146660001e-02 },
 4743    {  0.0006260521444113,  1.4461888986270000e+00,  3.5515522949801999e+00 },
 4744    {  0.0000352747346169,  2.1256287034577999e+00,  1.2727416566999999e-04 }
 4745};
 4746
 4747static const vsop_term_t jm_Io_zeta[] =
 4748{
 4749    {  0.0003142172466014,  2.7964219722923001e+00, -2.3150960980000000e-03 },
 4750    {  0.0000904169207946,  1.0477061879627001e+00, -5.6920638196000003e-04 }
 4751};
 4752
 4753static const vsop_term_t jm_Europa_a[] =
 4754{
 4755    {  0.0044871037804314,  0.0000000000000000e+00,  0.0000000000000000e+00 },
 4756    {  0.0000004324367498,  1.8196456062910000e+00,  1.7822295777568000e+00 }
 4757};
 4758
 4759static const vsop_term_t jm_Europa_l[] =
 4760{
 4761    {  0.0008576433172936,  4.3188693178264002e+00,  1.3034138308049999e-02 },
 4762    {  0.0004549582875086,  1.4936531751079001e+00,  1.2938928819619999e-02 },
 4763    {  0.0003248939825174,  1.8196494533458001e+00,  1.7822295777568000e+00 },
 4764    { -0.0003074250079334,  4.9377037005910998e+00,  1.3584832867240000e-02 },
 4765    {  0.0001982386144784,  1.9079869054759999e+00,  3.0510121286900001e-03 },
 4766    {  0.0001834063551804,  2.1402853388529000e+00,  1.4500978933800000e-03 },
 4767    { -0.0001434383188452,  5.6222140366630002e+00,  8.9111478887838003e-01 },
 4768    { -0.0000771939140944,  4.3002724372349999e+00,  2.6733443704265998e+00 }
 4769};
 4770
 4771static const vsop_term_t jm_Europa_z[] =
 4772{
 4773    { -0.0093589104136341,  4.0899396509038999e+00, -1.2906864146660001e-02 },
 4774    {  0.0002988994545555,  5.9097265185595003e+00,  1.7693227079461999e+00 },
 4775    {  0.0002139036390350,  2.1256289300016000e+00,  1.2727418406999999e-04 },
 4776    {  0.0001980963564781,  2.7435168292649998e+00,  6.7797343008999997e-04 },
 4777    {  0.0001210388158965,  5.5839943711203004e+00,  3.2056614899999997e-05 },
 4778    {  0.0000837042048393,  1.6094538368039000e+00, -9.0402165808846002e-01 },
 4779    {  0.0000823525166369,  1.4461887708689001e+00,  3.5515522949801999e+00 }
 4780};
 4781
 4782static const vsop_term_t jm_Europa_zeta[] =
 4783{
 4784    {  0.0040404917832303,  1.0477063169425000e+00, -5.6920640539999997e-04 },
 4785    {  0.0002200421034564,  3.3368857864364001e+00, -1.2491307306999999e-04 },
 4786    {  0.0001662544744719,  2.4134862374710999e+00,  0.0000000000000000e+00 },
 4787    {  0.0000590282470983,  5.9719930968366004e+00, -3.0561602250000000e-05 }
 4788};
 4789
 4790static const vsop_term_t jm_Ganymede_a[] =
 4791{
 4792    {  0.0071566594572575,  0.0000000000000000e+00,  0.0000000000000000e+00 },
 4793    {  0.0000013930299110,  1.1586745884981000e+00,  2.6733443704265998e+00 }
 4794};
 4795
 4796static const vsop_term_t jm_Ganymede_l[] =
 4797{
 4798    {  0.0002310797886226,  2.1402987195941998e+00,  1.4500978438400001e-03 },
 4799    { -0.0001828635964118,  4.3188672736968003e+00,  1.3034138282630000e-02 },
 4800    {  0.0001512378778204,  4.9373102372298003e+00,  1.3584834812520000e-02 },
 4801    { -0.0001163720969778,  4.3002659861490002e+00,  2.6733443704265998e+00 },
 4802    { -0.0000955478069846,  1.4936612842567001e+00,  1.2938928798570001e-02 },
 4803    {  0.0000815246854464,  5.6222137132535002e+00,  8.9111478887838003e-01 },
 4804    { -0.0000801219679602,  1.2995922951532000e+00,  1.0034433456728999e+00 },
 4805    { -0.0000607017260182,  6.4978769669238001e-01,  5.0172167043264004e-01 }
 4806};
 4807
 4808static const vsop_term_t jm_Ganymede_z[] =
 4809{
 4810    {  0.0014289811307319,  2.1256295942738999e+00,  1.2727413029000001e-04 },
 4811    {  0.0007710931226760,  5.5836330003496002e+00,  3.2064341100000001e-05 },
 4812    {  0.0005925911780766,  4.0899396636447998e+00, -1.2906864146660001e-02 },
 4813    {  0.0002045597496146,  5.2713683670371996e+00, -1.2523544076106000e-01 },
 4814    {  0.0001785118648258,  2.8743156721063001e-01,  8.7820792442520001e-01 },
 4815    {  0.0001131999784893,  1.4462127277818000e+00,  3.5515522949801999e+00 },
 4816    { -0.0000658778169210,  2.2702423990985001e+00, -1.7951364394536999e+00 },
 4817    {  0.0000497058888328,  5.9096792204858000e+00,  1.7693227129285001e+00 }
 4818};
 4819
 4820static const vsop_term_t jm_Ganymede_zeta[] =
 4821{
 4822    {  0.0015932721570848,  3.3368862796665000e+00, -1.2491307058000000e-04 },
 4823    {  0.0008533093128905,  2.4133881688166001e+00,  0.0000000000000000e+00 },
 4824    {  0.0003513347911037,  5.9720789850126996e+00, -3.0561017709999999e-05 },
 4825    { -0.0001441929255483,  1.0477061764435001e+00, -5.6920632124000004e-04 }
 4826};
 4827
 4828static const vsop_term_t jm_Callisto_a[] =
 4829{
 4830    {  0.0125879701715314,  0.0000000000000000e+00,  0.0000000000000000e+00 },
 4831    {  0.0000035952049470,  6.4965776007116005e-01,  5.0172168165034003e-01 },
 4832    {  0.0000027580210652,  1.8084235781510001e+00,  3.1750660413359002e+00 }
 4833};
 4834
 4835static const vsop_term_t jm_Callisto_l[] =
 4836{
 4837    {  0.0005586040123824,  2.1404207189814999e+00,  1.4500979323100001e-03 },
 4838    { -0.0003805813868176,  2.7358844897852999e+00,  2.9729650620000000e-05 },
 4839    {  0.0002205152863262,  6.4979652596399995e-01,  5.0172167243580001e-01 },
 4840    {  0.0001877895151158,  1.8084787604004999e+00,  3.1750660413359002e+00 },
 4841    {  0.0000766916975242,  6.2720114319754998e+00,  1.3928364636651001e+00 },
 4842    {  0.0000747056855106,  1.2995916202344000e+00,  1.0034433456728999e+00 }
 4843};
 4844
 4845static const vsop_term_t jm_Callisto_z[] =
 4846{
 4847    {  0.0073755808467977,  5.5836071576083999e+00,  3.2065099140000001e-05 },
 4848    {  0.0002065924169942,  5.9209831565786004e+00,  3.7648624194703001e-01 },
 4849    {  0.0001589869764021,  2.8744006242622999e-01,  8.7820792442520001e-01 },
 4850    { -0.0001561131605348,  2.1257397865089001e+00,  1.2727441285000001e-04 },
 4851    {  0.0001486043380971,  1.4462134301023000e+00,  3.5515522949801999e+00 },
 4852    {  0.0000635073108731,  5.9096803285953996e+00,  1.7693227129285001e+00 },
 4853    {  0.0000599351698525,  4.1125517584797997e+00, -2.7985797954588998e+00 },
 4854    {  0.0000540660842731,  5.5390350845569003e+00,  2.8683408228299999e-03 },
 4855    { -0.0000489596900866,  4.6218149483337996e+00, -6.2695712529518999e-01 }
 4856};
 4857
 4858static const vsop_term_t jm_Callisto_zeta[] =
 4859{
 4860    {  0.0038422977898495,  2.4133922085556998e+00,  0.0000000000000000e+00 },
 4861    {  0.0022453891791894,  5.9721736773277003e+00, -3.0561255249999997e-05 },
 4862    { -0.0002604479450559,  3.3368746306408998e+00, -1.2491309972000001e-04 },
 4863    {  0.0000332112143230,  5.5604137742336999e+00,  2.9003768850700000e-03 }
 4864};
 4865
 4866static const jupiter_moon_t JupiterMoonModel[] =
 4867{
 4868    {  2.8248942843381399e-07, {  1.4462132960212239e+00,  3.5515522861824000e+00 }
 4869        , {   1, jm_Io_a    }
 4870        , {   4, jm_Io_l    }
 4871        , {   3, jm_Io_z    }
 4872        , {   2, jm_Io_zeta } },
 4873
 4874    {  2.8248327439289299e-07, { -3.7352634374713622e-01,  1.7693227111234699e+00 }
 4875        , {   2, jm_Europa_a    }
 4876        , {   8, jm_Europa_l    }
 4877        , {   7, jm_Europa_z    }
 4878        , {   4, jm_Europa_zeta } },
 4879
 4880    {  2.8249818418472298e-07, {  2.8740893911433479e-01,  8.7820792358932798e-01 }
 4881        , {   2, jm_Ganymede_a    }
 4882        , {   8, jm_Ganymede_l    }
 4883        , {   8, jm_Ganymede_z    }
 4884        , {   4, jm_Ganymede_zeta } },
 4885
 4886    {  2.8249214488990899e-07, { -3.6203412913757038e-01,  3.7648623343382798e-01 }
 4887        , {   3, jm_Callisto_a    }
 4888        , {   6, jm_Callisto_l    }
 4889        , {   9, jm_Callisto_z    }
 4890        , {   4, jm_Callisto_zeta } }
 4891};
 4892
 4893static astro_state_vector_t JupiterMoon_elem2pv(astro_time_t time, double mu, const double elem[6])
 4894{
 4895    /* Translation of FORTRAN subroutine ELEM2PV from: */
 4896    /* https://ftp.imcce.fr/pub/ephem/satel/galilean/L1/L1.2/ */
 4897    astro_state_vector_t state;
 4898    double EE, DE, CE, SE, DLE, RSAM1, ASR, PHI, PSI, X1, Y1, VX1, VY1, F2, P2, Q2, PQ;
 4899
 4900    const double A  = elem[0];
 4901    const double AL = elem[1];
 4902    const double K  = elem[2];
 4903    const double H  = elem[3];
 4904    const double Q  = elem[4];
 4905    const double P  = elem[5];
 4906
 4907    const double AN = sqrt(mu / (A*A*A));
 4908
 4909    EE = AL + K*sin(AL) - H*cos(AL);
 4910    do
 4911    {
 4912        CE = cos(EE);
 4913        SE = sin(EE);
 4914        DE = (AL - EE + K*SE - H*CE) / (1.0 - K*CE - H*SE);
 4915        EE += DE;
 4916    }
 4917    while (fabs(DE) >= 1.0e-12);
 4918
 4919    CE = cos(EE);
 4920    SE = sin(EE);
 4921    DLE = H*CE - K*SE;
 4922    RSAM1 = -K*CE - H*SE;
 4923    ASR = 1.0/(1.0 + RSAM1);
 4924    PHI = sqrt(1.0 - K*K - H*H);
 4925    PSI = 1.0/(1.0 + PHI);
 4926    X1 = A*(CE - K - PSI*H*DLE);
 4927    Y1 = A*(SE - H + PSI*K*DLE);
 4928    VX1 = AN*ASR*A*(-SE - PSI*H*RSAM1);
 4929    VY1 = AN*ASR*A*(+CE + PSI*K*RSAM1);
 4930    F2 = 2.0*sqrt(1.0 - Q*Q - P*P);
 4931    P2 = 1.0 - 2.0*P*P;
 4932    Q2 = 1.0 - 2.0*Q*Q;
 4933    PQ = 2.0*P*Q;
 4934
 4935    state.x = X1*P2 + Y1*PQ;
 4936    state.y = X1*PQ + Y1*Q2;
 4937    state.z = (Q*Y1 - X1*P)*F2;
 4938
 4939    state.vx = VX1*P2 + VY1*PQ;
 4940    state.vy = VX1*PQ + VY1*Q2;
 4941    state.vz = (Q*VY1 - VX1*P)*F2;
 4942
 4943    state.t = time;
 4944    state.status = ASTRO_SUCCESS;
 4945    return state;
 4946}
 4947
 4948static astro_state_vector_t CalcJupiterMoon(astro_time_t time, int mindex)
 4949{
 4950    /* This is a translation of FORTRAN code by Duriez, Lainey, and Vienne: */
 4951    /* https://ftp.imcce.fr/pub/ephem/satel/galilean/L1/L1.2/ */
 4952
 4953    astro_state_vector_t state;
 4954    int k;
 4955    double arg;
 4956    double elem[6];
 4957    const jupiter_moon_t *m = &JupiterMoonModel[mindex];
 4958    const double t = time.tt + 18262.5;     /* t = time since 1950-01-01T00:00:00Z */
 4959
 4960    /* Calculate 6 orbital elements at the given time t. */
 4961
 4962    elem[0] = 0.0;
 4963    for (k = 0; k < m->a.nterms; ++k)
 4964    {
 4965        arg = m->a.term[k].phase + (t * m->a.term[k].frequency);
 4966        elem[0] += m->a.term[k].amplitude * cos(arg);
 4967    }
 4968
 4969    elem[1] = m->al[0] + (t * m->al[1]);
 4970    for (k = 0; k < m->l.nterms; ++k)
 4971    {
 4972        arg = m->l.term[k].phase + (t * m->l.term[k].frequency);
 4973        elem[1] += m->l.term[k].amplitude * sin(arg);
 4974    }
 4975    elem[1] = fmod(elem[1], PI2);
 4976    if (elem[1] < 0.0)
 4977        elem[1] += PI2;
 4978
 4979    elem[2] = elem[3] = 0.0;
 4980    for (k = 0; k < m->z.nterms; ++k)
 4981    {
 4982        arg = m->z.term[k].phase + (t * m->z.term[k].frequency);
 4983        elem[2] += m->z.term[k].amplitude * cos(arg);
 4984        elem[3] += m->z.term[k].amplitude * sin(arg);
 4985    }
 4986
 4987    elem[4] = elem[5] = 0.0;
 4988    for (k = 0; k < m->zeta.nterms; ++k)
 4989    {
 4990        arg = m->zeta.term[k].phase + (t * m->zeta.term[k].frequency);
 4991        elem[4] += m->zeta.term[k].amplitude * cos(arg);
 4992        elem[5] += m->zeta.term[k].amplitude * sin(arg);
 4993    }
 4994
 4995    /* Convert the oribital elements into position vectors in the Jupiter equatorial system (JUP). */
 4996    state = JupiterMoon_elem2pv(time, m->mu, elem);
 4997
 4998    /* Re-orient position and velocity vectors from Jupiter-equatorial (JUP) to Earth-equatorial in J2000 (EQJ). */
 4999    return Astronomy_RotateState(Rotation_JUP_EQJ, state);
 5000}
 5001
 5002
 5003/**
 5004 * @brief Calculates jovicentric positions and velocities of Jupiter's largest 4 moons.
 5005 *
 5006 * Calculates position and velocity vectors for Jupiter's moons
 5007 * Io, Europa, Ganymede, and Callisto, at the given date and time.
 5008 * The vectors are jovicentric (relative to the center of Jupiter).
 5009 * Their orientation is the Earth's equatorial system at the J2000 epoch (EQJ).
 5010 * The position components are expressed in astronomical units (AU), and the
 5011 * velocity components are in AU/day.
 5012 *
 5013 * To convert to heliocentric position vectors, call #Astronomy_HelioVector
 5014 * with `BODY_JUPITER` to get Jupiter's heliocentric position, then
 5015 * add the jovicentric positions.
 5016 *
 5017 * Likewise, you can call #Astronomy_GeoVector
 5018 * with `BODY_JUPITER` to convert to geocentric positions.
 5019 * However, you will have to manually correct for light travel time
 5020 * from the Jupiter system to Earth to figure out what time to pass
 5021 * to `Astronomy_JupiterMoons` to get an accurate picture
 5022 * of how Jupiter and its moons look from Earth.
 5023 *
 5024 * @param time  The date and time for which to calculate the position vectors.
 5025 * @return Position vectors of Jupiter's largest 4 moons, as described above.
 5026 */
 5027astro_jupiter_moons_t Astronomy_JupiterMoons(astro_time_t time)
 5028{
 5029    astro_jupiter_moons_t jm;
 5030
 5031    jm.io       = CalcJupiterMoon(time, 0);
 5032    jm.europa   = CalcJupiterMoon(time, 1);
 5033    jm.ganymede = CalcJupiterMoon(time, 2);
 5034    jm.callisto = CalcJupiterMoon(time, 3);
 5035
 5036    return jm;
 5037}
 5038
 5039/*---------------------- end Jupiter moons ----------------------*/
 5040
 5041
 5042/**
 5043 * @brief Calculates heliocentric Cartesian coordinates of a body in the J2000 equatorial system.
 5044 *
 5045 * This function calculates the position of the given celestial body as a vector,
 5046 * using the center of the Sun as the origin.  The result is expressed as a Cartesian
 5047 * vector in the J2000 equatorial system: the coordinates are based on the mean equator
 5048 * of the Earth at noon UTC on 1 January 2000.
 5049 *
 5050 * The position is not corrected for light travel time or aberration.
 5051 * This is different from the behavior of #Astronomy_GeoVector.
 5052 *
 5053 * If given an invalid value for `body`, this function will fail. The caller should always check
 5054 * the `status` field inside the returned #astro_vector_t for `ASTRO_SUCCESS` (success)
 5055 * or any other value (failure) before trusting the resulting vector.
 5056 *
 5057 * @param body
 5058 *      A body for which to calculate a heliocentric position: the Sun, Moon, any of the planets,
 5059 *      the Solar System Barycenter (SSB), or the Earth Moon Barycenter (EMB).
 5060 *      Can also be a star defined by #Astronomy_DefineStar.
 5061 * @param time  The date and time for which to calculate the position.
 5062 * @return      A heliocentric position vector of the center of the given body.
 5063 */
 5064astro_vector_t Astronomy_HelioVector(astro_body_t body, astro_time_t time)
 5065{
 5066    astro_vector_t vector, earth;
 5067    body_state_t bstate;
 5068    stardef_t *star;
 5069
 5070    star = UserDefinedStar(body);
 5071    if (star != NULL)
 5072    {
 5073        astro_spherical_t sphere;
 5074        sphere.lat = star->dec;
 5075        sphere.lon = 15.0 * star->ra;
 5076        sphere.dist = star->dist;
 5077        sphere.status = ASTRO_SUCCESS;
 5078        return Astronomy_VectorFromSphere(sphere, time);
 5079    }
 5080
 5081    switch (body)
 5082    {
 5083    case BODY_SUN:
 5084        vector.status = ASTRO_SUCCESS;
 5085        vector.x = 0.0;
 5086        vector.y = 0.0;
 5087        vector.z = 0.0;
 5088        vector.t = time;
 5089        return vector;
 5090
 5091    case BODY_MERCURY:
 5092    case BODY_VENUS:
 5093    case BODY_EARTH:
 5094    case BODY_MARS:
 5095    case BODY_JUPITER:
 5096    case BODY_SATURN:
 5097    case BODY_URANUS:
 5098    case BODY_NEPTUNE:
 5099        return CalcVsop(&vsop[body], time);
 5100
 5101    case BODY_PLUTO:
 5102        vector.t = time;
 5103        vector.status = CalcPluto(&bstate, time, 1);
 5104        if (vector.status != ASTRO_SUCCESS)
 5105        {
 5106            vector.x = vector.y = vector.z = NAN;
 5107        }
 5108        else
 5109        {
 5110            vector.x = bstate.r.x;
 5111            vector.y = bstate.r.y;
 5112            vector.z = bstate.r.z;
 5113        }
 5114        return vector;
 5115
 5116    case BODY_MOON:
 5117        vector = Astronomy_GeoMoon(time);
 5118        earth = CalcEarth(time);
 5119        vector.x += earth.x;
 5120        vector.y += earth.y;
 5121        vector.z += earth.z;
 5122        return vector;
 5123
 5124    case BODY_EMB:
 5125        vector = Astronomy_GeoMoon(time);
 5126        earth = CalcEarth(time);
 5127        vector.x = earth.x + (vector.x / (1.0 + EARTH_MOON_MASS_RATIO));
 5128        vector.y = earth.y + (vector.y / (1.0 + EARTH_MOON_MASS_RATIO));
 5129        vector.z = earth.z + (vector.z / (1.0 + EARTH_MOON_MASS_RATIO));
 5130        return vector;
 5131
 5132    case BODY_SSB:
 5133        return CalcSolarSystemBarycenter(time);
 5134
 5135    default:
 5136        return VecError(ASTRO_INVALID_BODY, time);
 5137    }
 5138}
 5139
 5140/**
 5141 * @brief Calculates the distance from a body to the Sun at a given time.
 5142 *
 5143 * Given a date and time, this function calculates the distance between
 5144 * the center of `body` and the center of the Sun.
 5145 * For the planets Mercury through Neptune, this function is significantly
 5146 * more efficient than calling #Astronomy_HelioVector followed by #Astronomy_VectorLength.
 5147 *
 5148 * @param body
 5149 *      A body for which to calculate a heliocentric distance:
 5150 *      the Sun, Moon, any of the planets, or a user-defined star.
 5151 *
 5152 * @param time
 5153 *      The date and time for which to calculate the heliocentric distance.
 5154 *
 5155 * @return
 5156 *      If successful, an #astro_func_result_t structure whose `status` is `ASTRO_SUCCESS`
 5157 *      and whose `value` holds the heliocentric distance in AU.
 5158 *      Otherwise, `status` reports an error condition.
 5159 */
 5160astro_func_result_t Astronomy_HelioDistance(astro_body_t body, astro_time_t time)
 5161{
 5162    astro_vector_t vector;
 5163    astro_func_result_t result;
 5164    stardef_t *star;
 5165
 5166    star = UserDefinedStar(body);
 5167    if (star != NULL)
 5168    {
 5169        result.status = ASTRO_SUCCESS;
 5170        result.value = star->dist;
 5171        return result;
 5172    }
 5173
 5174    switch (body)
 5175    {
 5176    case BODY_SUN:
 5177        result.status = ASTRO_SUCCESS;
 5178        result.value = 0.0;
 5179        return result;
 5180
 5181    case BODY_MERCURY:
 5182    case BODY_VENUS:
 5183    case BODY_EARTH:
 5184    case BODY_MARS:
 5185    case BODY_JUPITER:
 5186    case BODY_SATURN:
 5187    case BODY_URANUS:
 5188    case BODY_NEPTUNE:
 5189        result.status = ASTRO_SUCCESS;
 5190        result.value = VsopHelioDistance(&vsop[body], time);
 5191        return result;
 5192
 5193    default:
 5194        /* For non-VSOP objects, fall back to taking the length of the heliocentric vector. */
 5195        vector = Astronomy_HelioVector(body, time);
 5196        if (vector.status != ASTRO_SUCCESS)
 5197            return FuncError(vector.status);
 5198        result.status = ASTRO_SUCCESS;
 5199        result.value = Astronomy_VectorLength(vector);
 5200        return result;
 5201    }
 5202}
 5203
 5204
 5205/**
 5206 * @brief Solve for light travel time of a vector function.
 5207 *
 5208 * When observing a distant object, for example Jupiter as seen from Earth,
 5209 * the amount of time it takes for light to travel from the object to the
 5210 * observer can significantly affect the object's apparent position.
 5211 * This function is a generic solver that figures out how long in the
 5212 * past light must have left the observed object to reach the observer
 5213 * at the specified observation time. It uses a context/function pair
 5214 * as a generic interface that expresses an arbitrary position vector
 5215 * as a function of time.
 5216 *
 5217 * This function repeatedly calls `func`, passing `context` and a series of time
 5218 * estimates in the past. Then `func` must return a relative position vector between
 5219 * the observer and the target. `Astronomy_CorrectLightTravel` keeps calling
 5220 * `func` with more and more refined estimates of the time light must have
 5221 * left the target to arrive at the observer.
 5222 *
 5223 * For common use cases, it is simpler to use #Astronomy_BackdatePosition
 5224 * for calculating the light travel time correction of one body observing another body.
 5225 *
 5226 * For geocentric calculations, #Astronomy_GeoVector also backdates the returned
 5227 * position vector for light travel time, only it returns the observation time in
 5228 * the returned vector's `t` field rather than the backdated time.
 5229 *
 5230 * @param context   Holds any parameters needed by `func`.
 5231 * @param func      Pointer to a function that returns a relative position vector as a function of time.
 5232 * @param time      The observation time for which to solve for light travel delay.
 5233 * @return
 5234 *      The position vector returned by `func` at the solved backdated time.
 5235 *      On success, the vector will hold `ASTRO_SUCCESS` in its `status` field,
 5236 *      the backdated time in its `t` field, along with the apparent relative position.
 5237 *      If an error occurs, `status` will hold an error code and the remaining fields
 5238 *      should be ignored.
 5239 */
 5240astro_vector_t Astronomy_CorrectLightTravel(
 5241    void *context,
 5242    astro_position_func_t func,
 5243    astro_time_t time)
 5244{
 5245    int iter;
 5246    astro_time_t ltime, ltime2;
 5247    astro_vector_t pos;
 5248    double distance, dt;
 5249
 5250    ltime = time;
 5251    for (iter = 0; iter < 10; ++iter)
 5252    {
 5253        pos = func(context, ltime);
 5254        if (pos.status != ASTRO_SUCCESS)
 5255            return pos;
 5256
 5257        distance = Astronomy_VectorLength(pos);
 5258
 5259        /*
 5260            This solver does not support more than one light-day of distance,
 5261            because that would cause convergence problems and inaccurate
 5262            values for stellar aberration angles.
 5263        */
 5264        if (distance > C_AUDAY)
 5265            return VecError(ASTRO_INVALID_PARAMETER, time);
 5266
 5267        ltime2 = Astronomy_AddDays(time, -distance/C_AUDAY);
 5268        dt = fabs(ltime2.tt - ltime.tt);
 5269        if (dt < 1.0e-9)        /* 86.4 microseconds */
 5270            return pos;
 5271
 5272        ltime = ltime2;
 5273    }
 5274    return VecError(ASTRO_NO_CONVERGE, time);   /* light travel time solver did not converge */
 5275}
 5276
 5277
 5278/** @cond DOXYGEN_SKIP */
 5279typedef struct
 5280{
 5281    astro_body_t        observerBody;
 5282    astro_body_t        targetBody;
 5283    astro_aberration_t  aberration;
 5284    astro_vector_t      observerPos;          /* used only when aberration == NO_ABERRATION */
 5285}
 5286backdate_context_t;
 5287/** @endcond */
 5288
 5289
 5290static astro_vector_t BodyPosition(void *context, astro_time_t time)
 5291{
 5292    const backdate_context_t *b = (const backdate_context_t *)context;
 5293    astro_vector_t observerPos, pos;
 5294
 5295    if (b->aberration == NO_ABERRATION)
 5296    {
 5297        /* No aberration, so use the pre-calculated initial position of the observer body. */
 5298        observerPos = b->observerPos;
 5299    }
 5300    else
 5301    {
 5302        /*
 5303            The following discussion is worded with the observer body being the Earth,
 5304            which is often the case. However, the same reasoning applies to any observer body
 5305            without loss of generality.
 5306
 5307            To include aberration, make a good first-order approximation
 5308            by backdating the Earth's position also.
 5309            This is confusing, but it works for objects within the Solar System
 5310            because the distance the Earth moves in that small amount of light
 5311            travel time (a few minutes to a few hours) is well approximated
 5312            by a line segment that substends the angle seen from the remote
 5313            body viewing Earth. That angle is pretty close to the aberration
 5314            angle of the moving Earth viewing the remote body.
 5315            In other words, both of the following approximate the aberration angle:
 5316                (transverse distance Earth moves) / (distance to body)
 5317                (transverse speed of Earth) / (speed of light).
 5318        */
 5319        observerPos = Astronomy_HelioVector(b->observerBody, time);
 5320    }
 5321
 5322    if (observerPos.status != ASTRO_SUCCESS)
 5323        return observerPos;
 5324
 5325    pos = Astronomy_HelioVector(b->targetBody, time);
 5326    if (pos.status == ASTRO_SUCCESS)
 5327    {
 5328        /* Convert heliocentric body position to observer-centric position. */
 5329        pos.x -= observerPos.x;
 5330        pos.y -= observerPos.y;
 5331        pos.z -= observerPos.z;
 5332    }
 5333    return pos;
 5334}
 5335
 5336
 5337/**
 5338 * @brief Solve for light travel time correction of apparent position.
 5339 *
 5340 * When observing a distant object, for example Jupiter as seen from Earth,
 5341 * the amount of time it takes for light to travel from the object to the
 5342 * observer can significantly affect the object's apparent position.
 5343 *
 5344 * This function solves the light travel time correction for the apparent
 5345 * relative position vector of a target body as seen by an observer body
 5346 * at a given observation time.
 5347 *
 5348 * For geocentric calculations, #Astronomy_GeoVector also includes light
 5349 * travel time correction, but the time `t` embedded in its returned vector
 5350 * refers to the observation time, not the backdated time that light left
 5351 * the observed body. Thus `Astronomy_BackdatePosition` provides direct
 5352 * access to the light departure time for callers that need it.
 5353 *
 5354 * For a more generalized light travel correction solver, see #Astronomy_CorrectLightTravel.
 5355 *
 5356 * @param time          The time of observation.
 5357 * @param observerBody  The body to be used as the observation location.
 5358 * @param targetBody    The body to be observed.
 5359 * @param aberration    `ABERRATION` to correct for aberration, or `NO_ABERRATION` to leave uncorrected.
 5360 *
 5361 * @return
 5362 *      On success, the position vector at the solved backdated time.
 5363 *      The returned vector will hold `ASTRO_SUCCESS` in its `status` field,
 5364 *      the backdated time in its `t` field, along with the apparent relative position.
 5365 *      If an error occurs, `status` will hold an error code and the remaining fields should be ignored.
 5366 */
 5367astro_vector_t Astronomy_BackdatePosition(
 5368    astro_time_t time,
 5369    astro_body_t observerBody,
 5370    astro_body_t targetBody,
 5371    astro_aberration_t aberration)
 5372{
 5373    if (UserDefinedStar(targetBody))
 5374    {
 5375        /*
 5376            This is a user-defined star, which must be treated as a special case.
 5377            First, we assume its heliocentric position does not change with time.
 5378            Second, we assume its heliocentric position has already been corrected
 5379            for light-travel time, its coordinates given as it appears on Earth at the present.
 5380            Therefore, no backdating is applied.
 5381        */
 5382        astro_vector_t ovec, tvec, vec;
 5383        double rx, ry, rz, s;
 5384        astro_state_vector_t ostate;
 5385
 5386        tvec = Astronomy_HelioVector(targetBody, time);
 5387        if (tvec.status != ASTRO_SUCCESS)
 5388            return tvec;
 5389
 5390        switch (aberration)
 5391        {
 5392        case NO_ABERRATION:
 5393            /* Return the star's position as seen from the observer. */
 5394            ovec = Astronomy_HelioVector(observerBody, time);
 5395            if (ovec.status != ASTRO_SUCCESS)
 5396                return ovec;
 5397            vec.x = tvec.x - ovec.x;
 5398            vec.y = tvec.y - ovec.y;
 5399            vec.z = tvec.z - ovec.z;
 5400            vec.t = time;
 5401            vec.status = ASTRO_SUCCESS;
 5402            return vec;
 5403
 5404        case ABERRATION:
 5405            /*
 5406                (Observer velocity) - (light vector) = (Aberration-corrected direction to target body).
 5407                Note that this is an approximation, because technically the light vector should
 5408                be measured in barycentric coordinates, not heliocentric. The error is very small.
 5409            */
 5410            ostate = Astronomy_HelioState(observerBody, time);
 5411            if (ostate.status != ASTRO_SUCCESS)
 5412                return VecError(ostate.status, time);
 5413
 5414            rx = tvec.x - ostate.x;
 5415            ry = tvec.y - ostate.y;
 5416            rz = tvec.z - ostate.z;
 5417            s = C_AUDAY / sqrt(rx*rx + ry*ry + rz*rz);
 5418
 5419            vec.x = rx + ostate.vx/s;
 5420            vec.y = ry + ostate.vy/s;
 5421            vec.z = rz + ostate.vz/s;
 5422            vec.t = time;
 5423            vec.status = ASTRO_SUCCESS;
 5424            return vec;
 5425
 5426        default:
 5427            return VecError(ASTRO_INVALID_PARAMETER, time);
 5428        }
 5429    }
 5430    else
 5431    {
 5432        backdate_context_t context;
 5433
 5434        context.observerBody = observerBody;
 5435        context.targetBody   = targetBody;
 5436        context.aberration   = aberration;
 5437        switch (aberration)
 5438        {
 5439        case NO_ABERRATION:
 5440            /* Without aberration, we need the observer body position at the observation time only. */
 5441            /* For efficiency, calculate it once and hold onto it, so `BodyPosition` can keep using it. */
 5442            context.observerPos = Astronomy_HelioVector(observerBody, time);
 5443            break;
 5444
 5445        case ABERRATION:
 5446            /* With aberration, `BackdatePosition` will calculate the observer body state at different times. */
 5447            /* Therefore, do not waste time calculating it at the observation time. */
 5448            /* Initialize the memory with an explicitly invalid value. */
 5449            context.observerPos = VecError(ASTRO_NOT_INITIALIZED, time);
 5450            break;
 5451
 5452        default:
 5453            return VecError(ASTRO_INVALID_PARAMETER, time);
 5454        }
 5455
 5456        return Astronomy_CorrectLightTravel(&context, BodyPosition, time);
 5457    }
 5458}
 5459
 5460
 5461/**
 5462 * @brief Calculates geocentric Cartesian coordinates of a body in the J2000 equatorial system.
 5463 *
 5464 * This function calculates the position of the given celestial body as a vector,
 5465 * using the center of the Earth as the origin.  The result is expressed as a Cartesian
 5466 * vector in the J2000 equatorial system: the coordinates are based on the mean equator
 5467 * of the Earth at noon UTC on 1 January 2000.
 5468 *
 5469 * If given an invalid value for `body`, this function will fail. The caller should always check
 5470 * the `status` field inside the returned #astro_vector_t for `ASTRO_SUCCESS` (success)
 5471 * or any other value (failure) before trusting the resulting vector.
 5472 *
 5473 * Unlike #Astronomy_HelioVector, this function corrects for light travel time.
 5474 * This means the position of the body is "back-dated" by the amount of time it takes
 5475 * light to travel from that body to an observer on the Earth.
 5476 *
 5477 * Also, the position can optionally be corrected for
 5478 * [aberration](https://en.wikipedia.org/wiki/Aberration_of_light), an effect
 5479 * causing the apparent direction of the body to be shifted due to transverse
 5480 * movement of the Earth with respect to the rays of light coming from that body.
 5481 *
 5482 * @param body
 5483 *      A body for which to calculate a heliocentric position: the Sun, Moon, or any of the planets.
 5484 *      Can also be a star defined by #Astronomy_DefineStar.
 5485 * @param time          The date and time for which to calculate the position.
 5486 * @param aberration    `ABERRATION` to correct for aberration, or `NO_ABERRATION` to leave uncorrected.
 5487 * @return              A geocentric position vector of the center of the given body.
 5488 */
 5489astro_vector_t Astronomy_GeoVector(astro_body_t body, astro_time_t time, astro_aberration_t aberration)
 5490{
 5491    astro_vector_t vector;
 5492
 5493    switch (body)
 5494    {
 5495    case BODY_EARTH:
 5496        /* The Earth's geocentric coordinates are always (0,0,0). */
 5497        vector.status = ASTRO_SUCCESS;
 5498        vector.x = 0.0;
 5499        vector.y = 0.0;
 5500        vector.z = 0.0;
 5501        break;
 5502
 5503    case BODY_MOON:
 5504        /* The moon is so close, aberration and light travel time don't matter. */
 5505        vector = Astronomy_GeoMoon(time);
 5506        break;
 5507
 5508    default:
 5509        /* For all other bodies, apply light travel time correction. */
 5510        vector = Astronomy_BackdatePosition(time, BODY_EARTH, body, aberration);
 5511        break;
 5512    }
 5513
 5514    vector.t = time;    /* tricky: return the observation time, not the backdated time */
 5515    return vector;
 5516}
 5517
 5518
 5519/**
 5520 * @brief  Calculates barycentric position and velocity vectors for the given body.
 5521 *
 5522 * Given a body and a time, calculates the barycentric position and velocity
 5523 * vectors for the center of that body at that time.
 5524 * The vectors are expressed in J2000 mean equator coordinates (EQJ).
 5525 *
 5526 * @param body
 5527 *      The celestial body whose barycentric state vector is to be calculated.
 5528 *      Supported values are `BODY_SUN`, `BODY_MOON`, `BODY_EMB`, `BODY_SSB`, and all planets:
 5529 *      `BODY_MERCURY`, `BODY_VENUS`, `BODY_EARTH`, `BODY_MARS`, `BODY_JUPITER`,
 5530 *      `BODY_SATURN`, `BODY_URANUS`, `BODY_NEPTUNE`, `BODY_PLUTO`.
 5531 * @param time
 5532 *      The date and time for which to calculate position and velocity.
 5533 * @return
 5534 *      A structure that contains barycentric position and velocity vectors.
 5535 */
 5536astro_state_vector_t Astronomy_BaryState(astro_body_t body, astro_time_t time)
 5537{
 5538    astro_state_vector_t state;
 5539    major_bodies_t bary;
 5540    body_state_t planet, earth;
 5541
 5542    if (body == BODY_SSB)
 5543    {
 5544        /* Trivial case: the solar system barycenter itself. */
 5545        state.status = ASTRO_SUCCESS;
 5546        state.x = state.y = state.z = 0.0;
 5547        state.vx = state.vy = state.vz = 0.0;
 5548        state.t = time;
 5549        return state;
 5550    }
 5551
 5552    if (body == BODY_PLUTO)
 5553    {
 5554        astro_status_t status = CalcPluto(&planet, time, 0);
 5555        if (status != ASTRO_SUCCESS)
 5556            return StateVecError(status, time);
 5557        return ExportState(planet, time);
 5558    }
 5559
 5560    MajorBodyBary(&bary, time.tt);
 5561
 5562    switch (body)
 5563    {
 5564    /* If the caller is asking for one of the major bodies, we can immediately return the answer. */
 5565    case BODY_SUN:      return ExportState(bary.Sun,     time);
 5566    case BODY_JUPITER:  return ExportState(bary.Jupiter, time);
 5567    case BODY_SATURN:   return ExportState(bary.Saturn,  time);
 5568    case BODY_URANUS:   return ExportState(bary.Uranus,  time);
 5569    case BODY_NEPTUNE:  return ExportState(bary.Neptune, time);
 5570
 5571    /* Handle the remaining VSOP bodies: Mercury, Venus, Earth, Mars. */
 5572    case BODY_MERCURY:
 5573    case BODY_VENUS:
 5574    case BODY_EARTH:
 5575    case BODY_MARS:
 5576        planet = CalcVsopPosVel(&vsop[body], time.tt);
 5577        /* BarySun + HelioBody = BaryBody */
 5578        state.x  = bary.Sun.r.x + planet.r.x;
 5579        state.y  = bary.Sun.r.y + planet.r.y;
 5580        state.z  = bary.Sun.r.z + planet.r.z;
 5581        state.vx = bary.Sun.v.x + planet.v.x;
 5582        state.vy = bary.Sun.v.y + planet.v.y;
 5583        state.vz = bary.Sun.v.z + planet.v.z;
 5584        state.t  = time;
 5585        state.status = ASTRO_SUCCESS;
 5586        return state;
 5587
 5588    case BODY_MOON:
 5589    case BODY_EMB:
 5590        earth = CalcVsopPosVel(&vsop[BODY_EARTH], time.tt);
 5591        if (body == BODY_MOON)
 5592            state = Astronomy_GeoMoonState(time);
 5593        else
 5594            state = Astronomy_GeoEmbState(time);
 5595        state.x  += bary.Sun.r.x + earth.r.x;
 5596        state.y  += bary.Sun.r.y + earth.r.y;
 5597        state.z  += bary.Sun.r.z + earth.r.z;
 5598        state.vx += bary.Sun.v.x + earth.v.x;
 5599        state.vy += bary.Sun.v.y + earth.v.y;
 5600        state.vz += bary.Sun.v.z + earth.v.z;
 5601        return state;
 5602
 5603    default:
 5604        return StateVecError(ASTRO_INVALID_BODY, time);
 5605    }
 5606}
 5607
 5608
 5609/**
 5610 * @brief  Calculates heliocentric position and velocity vectors for the given body.
 5611 *
 5612 * Given a body and a time, calculates the position and velocity
 5613 * vectors for the center of that body at that time, relative to the center of the Sun.
 5614 * The vectors are expressed in J2000 mean equator coordinates (EQJ).
 5615 * If you need the position vector only, it is more efficient to call #Astronomy_HelioVector.
 5616 * The Sun's center is a non-inertial frame of reference. In other words, the Sun
 5617 * experiences acceleration due to gravitational forces, mostly from the larger
 5618 * planets (Jupiter, Saturn, Uranus, and Neptune). If you want to calculate momentum,
 5619 * kinetic energy, or other quantities that require a non-accelerating frame
 5620 * of reference, consider using #Astronomy_BaryState instead.
 5621 *
 5622 * @param body
 5623 *      The celestial body whose heliocentric state vector is to be calculated.
 5624 *      Supported values are `BODY_SUN`, `BODY_MOON`, `BODY_EMB`, `BODY_SSB`, and all planets:
 5625 *      `BODY_MERCURY`, `BODY_VENUS`, `BODY_EARTH`, `BODY_MARS`, `BODY_JUPITER`,
 5626 *      `BODY_SATURN`, `BODY_URANUS`, `BODY_NEPTUNE`, `BODY_PLUTO`.
 5627 *      Also allowed to be a user-defined star created by #Astronomy_DefineStar.
 5628 * @param time
 5629 *      The date and time for which to calculate position and velocity.
 5630 * @return
 5631 *      A structure that contains heliocentric position and velocity vectors.
 5632 */
 5633astro_state_vector_t Astronomy_HelioState(astro_body_t body, astro_time_t time)
 5634{
 5635    astro_status_t status;
 5636    astro_state_vector_t state;
 5637    major_bodies_t bary;
 5638    body_state_t planet, earth;
 5639
 5640    if (UserDefinedStar(body))
 5641    {
 5642        astro_vector_t vec = Astronomy_HelioVector(body, time);
 5643        state.x = vec.x;
 5644        state.y = vec.y;
 5645        state.z = vec.z;
 5646        state.vx = state.vy = state.vz = 0.0;
 5647        state.t = time;
 5648        state.status = vec.status;
 5649        return state;
 5650    }
 5651
 5652    switch (body)
 5653    {
 5654    case BODY_SUN:
 5655        /* Trivial case: the Sun is the origin of the heliocentric frame. */
 5656        state.status = ASTRO_SUCCESS;
 5657        state.x = state.y = state.z = 0.0;
 5658        state.vx = state.vy = state.vz = 0.0;
 5659        state.t = time;
 5660        return state;
 5661
 5662    case BODY_SSB:
 5663        /* Calculate the barycentric Sun. Then the negative of that is the heliocentric SSB. */
 5664        MajorBodyBary(&bary, time.tt);
 5665        state.x  = -bary.Sun.r.x;
 5666        state.y  = -bary.Sun.r.y;
 5667        state.z  = -bary.Sun.r.z;
 5668        state.vx = -bary.Sun.v.x;
 5669        state.vy = -bary.Sun.v.y;
 5670        state.vz = -bary.Sun.v.z;
 5671        state.t  = time;
 5672        state.status = ASTRO_SUCCESS;
 5673        return state;
 5674
 5675    case BODY_MERCURY:
 5676    case BODY_VENUS:
 5677    case BODY_EARTH:
 5678    case BODY_MARS:
 5679    case BODY_JUPITER:
 5680    case BODY_SATURN:
 5681    case BODY_URANUS:
 5682    case BODY_NEPTUNE:
 5683        /* Planets included in the VSOP87 model. */
 5684        planet = CalcVsopPosVel(&vsop[body], time.tt);
 5685        return ExportState(planet, time);
 5686
 5687    case BODY_PLUTO:
 5688        status = CalcPluto(&planet, time, 1);
 5689        if (status != ASTRO_SUCCESS)
 5690            return StateVecError(status, time);
 5691        return ExportState(planet, time);
 5692
 5693    case BODY_MOON:
 5694    case BODY_EMB:
 5695        earth = CalcVsopPosVel(&vsop[BODY_EARTH], time.tt);
 5696        if (body == BODY_MOON)
 5697            state = Astronomy_GeoMoonState(time);
 5698        else
 5699            state = Astronomy_GeoEmbState(time);
 5700        state.x  += earth.r.x;
 5701        state.y  += earth.r.y;
 5702        state.z  += earth.r.z;
 5703        state.vx += earth.v.x;
 5704        state.vy += earth.v.y;
 5705        state.vz += earth.v.z;
 5706        return state;
 5707
 5708    default:
 5709        return StateVecError(ASTRO_INVALID_BODY, time);
 5710    }
 5711}
 5712
 5713
 5714/**
 5715 * @brief Returns the product of mass and universal gravitational constant of a Solar System body.
 5716 *
 5717 * For problems involving the gravitational interactions of Solar System bodies,
 5718 * it is helpful to know the product GM, where G = the universal gravitational constant
 5719 * and M = the mass of the body. In practice, GM is known to a higher precision than
 5720 * either G or M alone, and thus using the product results in the most accurate results.
 5721 * This function returns the product GM in the units au^3/day^2, or 0 for invalid bodies.
 5722 * The values come from page 10 of a
 5723 * [JPL memorandum regarding the DE405/LE405 ephemeris](https://web.archive.org/web/20120220062549/http://iau-comm4.jpl.nasa.gov/de405iom/de405iom.pdf).
 5724 *
 5725 * @param body      The body for which to find the GM product.
 5726 * @return          The mass product of the given body in au^3/day^2.
 5727 */
 5728double Astronomy_MassProduct(astro_body_t body)
 5729{
 5730    switch (body)
 5731    {
 5732    case BODY_SUN:      return SUN_GM;
 5733    case BODY_MERCURY:  return MERCURY_GM;
 5734    case BODY_VENUS:    return VENUS_GM;
 5735    case BODY_EARTH:    return EARTH_GM;
 5736    case BODY_MOON:     return MOON_GM;
 5737    case BODY_EMB:      return EARTH_GM + MOON_GM;
 5738    case BODY_MARS:     return MARS_GM;
 5739    case BODY_JUPITER:  return JUPITER_GM;
 5740    case BODY_SATURN:   return SATURN_GM;
 5741    case BODY_URANUS:   return URANUS_GM;
 5742    case BODY_NEPTUNE:  return NEPTUNE_GM;
 5743    case BODY_PLUTO:    return PLUTO_GM;
 5744    default:            return 0.0;         /* invalid body */
 5745    }
 5746}
 5747
 5748
 5749/**
 5750 * @brief Calculates one of the 5 Lagrange points for a pair of co-orbiting bodies.
 5751 *
 5752 * Given a more massive "major" body and a much less massive "minor" body,
 5753 * calculates one of the five Lagrange points in relation to the minor body's
 5754 * orbit around the major body. The parameter `point` is an integer that
 5755 * selects the Lagrange point as follows:
 5756 *
 5757 * 1 = the Lagrange point between the major body and minor body.
 5758 * 2 = the Lagrange point on the far side of the minor body.
 5759 * 3 = the Lagrange point on the far side of the major body.
 5760 * 4 = the Lagrange point 60 degrees ahead of the minor body's orbital position.
 5761 * 5 = the Lagrange point 60 degrees behind the minor body's orbital position.
 5762 *
 5763 * The function returns the state vector for the selected Lagrange point
 5764 * in J2000 mean equator coordinates (EQJ), with respect to the center of the
 5765 * major body.
 5766 *
 5767 * To calculate Sun/Earth Lagrange points, pass in `BODY_SUN` for `major_body`
 5768 * and `BODY_EMB` (Earth/Moon barycenter) for `minor_body`.
 5769 * For Lagrange points of the Sun and any other planet, pass in that planet
 5770 * (e.g. `BODY_JUPITER`) for `minor_body`.
 5771 * To calculate Earth/Moon Lagrange points, pass in `BODY_EARTH` and `BODY_MOON`
 5772 * for the major and minor bodies respectively.
 5773 *
 5774 * In some cases, it may be more efficient to call #Astronomy_LagrangePointFast,
 5775 * especially when the state vectors have already been calculated, or are needed
 5776 * for some other purpose.
 5777 *
 5778 * @param point         A value 1..5 that selects which of the Lagrange points to calculate.
 5779 * @param time          The time at which the Lagrange point is to be calculated.
 5780 * @param major_body    The more massive of the co-orbiting bodies: `BODY_SUN` or `BODY_EARTH`.
 5781 * @param minor_body    The less massive of the co-orbiting bodies. See main remarks.
 5782 * @return              The position and velocity of the selected Lagrange point with respect to the major body's center.
 5783 */
 5784astro_state_vector_t Astronomy_LagrangePoint(
 5785    int point,
 5786    astro_time_t time,
 5787    astro_body_t major_body,
 5788    astro_body_t minor_body)
 5789{
 5790    astro_state_vector_t major_state, minor_state;
 5791    double major_mass, minor_mass;
 5792
 5793    major_mass = Astronomy_MassProduct(major_body);
 5794    if (major_mass <= 0.0)
 5795        return StateVecError(ASTRO_INVALID_BODY, time);
 5796
 5797    minor_mass = Astronomy_MassProduct(minor_body);
 5798    if (minor_mass <= 0.0)
 5799        return StateVecError(ASTRO_INVALID_BODY, time);
 5800
 5801    /* Calculate the state vectors for the major and minor bodies. */
 5802    if (major_body == BODY_EARTH && minor_body == BODY_MOON)
 5803    {
 5804        /* Use geocentric calculations for more precision. */
 5805
 5806        /* The Earth's geocentric state is trivial. */
 5807        major_state.status = ASTRO_SUCCESS;
 5808        major_state.t = time;
 5809        major_state.x = major_state.y = major_state.z = 0.0;
 5810        major_state.vx = major_state.vy = major_state.vz = 0.0;
 5811
 5812        minor_state = Astronomy_GeoMoonState(time);
 5813        if (minor_state.status != ASTRO_SUCCESS)
 5814            return minor_state;
 5815    }
 5816    else
 5817    {
 5818        major_state = Astronomy_HelioState(major_body, time);
 5819        if (major_state.status != ASTRO_SUCCESS)
 5820            return major_state;
 5821
 5822        minor_state = Astronomy_HelioState(minor_body, time);
 5823        if (minor_state.status != ASTRO_SUCCESS)
 5824            return minor_state;
 5825    }
 5826
 5827    return Astronomy_LagrangePointFast(
 5828        point,
 5829        major_state,
 5830        major_mass,
 5831        minor_state,
 5832        minor_mass
 5833    );
 5834}
 5835
 5836
 5837/**
 5838 * @brief Calculates one of the 5 Lagrange points from body masses and state vectors.
 5839 *
 5840 * Given a more massive "major" body and a much less massive "minor" body,
 5841 * calculates one of the five Lagrange points in relation to the minor body's
 5842 * orbit around the major body. The parameter `point` is an integer that
 5843 * selects the Lagrange point as follows:
 5844 *
 5845 * 1 = the Lagrange point between the major body and minor body.
 5846 * 2 = the Lagrange point on the far side of the minor body.
 5847 * 3 = the Lagrange point on the far side of the major body.
 5848 * 4 = the Lagrange point 60 degrees ahead of the minor body's orbital position.
 5849 * 5 = the Lagrange point 60 degrees behind the minor body's orbital position.
 5850 *
 5851 * The caller passes in the state vector and mass for both bodies.
 5852 * The state vectors can be in any orientation and frame of reference.
 5853 * The body masses are expressed as GM products, where G = the universal
 5854 * gravitation constant and M = the body's mass. Thus the units for
 5855 * `major_mass` and `minor_mass` must be au^3/day^2.
 5856 * Use #Astronomy_MassProduct to obtain GM values for various solar system bodies.
 5857 *
 5858 * The function returns the state vector for the selected Lagrange point
 5859 * using the same orientation as the state vector parameters `major_state` and `minor_state`,
 5860 * and the position and velocity components are with respect to the major body's center.
 5861 *
 5862 * Consider calling #Astronomy_LagrangePoint, instead of this function, for simpler usage in most cases.
 5863 *
 5864 * @param point         A value 1..5 that selects which of the Lagrange points to calculate.
 5865 * @param major_state   The state vector of the major (more massive) of the pair of bodies.
 5866 * @param major_mass    The mass product GM of the major body.
 5867 * @param minor_state   The state vector of the minor (less massive) of the pair of bodies.
 5868 * @param minor_mass    The mass product GM of the minor body.
 5869 * @return              The position and velocity of the selected Lagrange point with respect to the major body's center.
 5870 */
 5871astro_state_vector_t Astronomy_LagrangePointFast(
 5872    int point,
 5873    astro_state_vector_t major_state,
 5874    double major_mass,
 5875    astro_state_vector_t minor_state,
 5876    double minor_mass)
 5877{
 5878    const double cos_60 = 0.5;
 5879    const double sin_60 = 0.8660254037844386;   /* sqrt(3) / 2 */
 5880    double scale, dx, dy, dz;
 5881    double vx, vy, vz;
 5882    double R2, R, r1, r2, x, deltax, dr1, dr2, numer1, numer2, omega2, accel, deriv;
 5883    astro_state_vector_t  p;
 5884
 5885    if (point < 1 || point > 5)
 5886        return StateVecError(ASTRO_INVALID_PARAMETER, major_state.t);
 5887
 5888    if (major_state.status != ASTRO_SUCCESS || minor_state.status != ASTRO_SUCCESS)
 5889        return StateVecError(ASTRO_INVALID_PARAMETER, major_state.t);
 5890
 5891    if (!isfinite(major_mass) || major_mass <= 0.0)
 5892        return StateVecError(ASTRO_INVALID_PARAMETER, major_state.t);
 5893
 5894    if (!isfinite(minor_mass) || minor_mass <= 0.0)
 5895        return StateVecError(ASTRO_INVALID_PARAMETER, major_state.t);
 5896
 5897    /* Find the relative position vector <dx, dy, dz>. */
 5898    dx = minor_state.x - major_state.x;
 5899    dy = minor_state.y - major_state.y;
 5900    dz = minor_state.z - major_state.z;
 5901    R2 = (dx*dx + dy*dy + dz*dz);
 5902
 5903    /* R = Total distance between the bodies. */
 5904    R = sqrt(R2);
 5905
 5906    /* Find the velocity vector <vx, vy, vz>. */
 5907    vx = minor_state.vx - major_state.vx;
 5908    vy = minor_state.vy - major_state.vy;
 5909    vz = minor_state.vz - major_state.vz;
 5910
 5911    if (point == 4 || point == 5)
 5912    {
 5913        double nx, ny, nz;
 5914        double ux, uy, uz, U;
 5915        double Dx, Dy, Dz;
 5916        double Ux, Uy, Uz;
 5917        double vert, vrad, vtan;
 5918
 5919        /*
 5920            For L4 and L5, we need to find points 60 degrees away from the
 5921            line connecting the two bodies and in the instantaneous orbital plane.
 5922            Define the instantaneous orbital plane as the unique plane that contains
 5923            both the relative position vector and the relative velocity vector.
 5924        */
 5925
 5926        /* Take the cross product of position and velocity to find a normal vector <nx, ny, nz>. */
 5927        nx = dy*vz - dz*vy;
 5928        ny = dz*vx - dx*vz;
 5929        nz = dx*vy - dy*vx;
 5930
 5931        /* Take the cross product normal*position to get a tangential vector <ux, uy, uz>. */
 5932        ux = ny*dz - nz*dy;
 5933        uy = nz*dx - nx*dz;
 5934        uz = nx*dy - ny*dx;
 5935
 5936        /* Convert the tangential direction vector to a unit vector. */
 5937        U = sqrt(ux*ux + uy*uy + uz*uz);
 5938        ux /= U;
 5939        uy /= U;
 5940        uz /= U;
 5941
 5942        /* Convert the relative position vector into a unit vector. */
 5943        dx /= R;
 5944        dy /= R;
 5945        dz /= R;
 5946
 5947        /* Now we have two perpendicular unit vectors in the orbital plane: 'd' and 'u'. */
 5948
 5949        /* Create new unit vectors rotated (+/-)60 degrees from the radius/tangent directions. */
 5950        vert = (point == 4) ? +sin_60 : -sin_60;
 5951
 5952        /* Rotated radial vector */
 5953        Dx = cos_60*dx + vert*ux;
 5954        Dy = cos_60*dy + vert*uy;
 5955        Dz = cos_60*dz + vert*uz;
 5956
 5957        /* Rotated tangent vector */
 5958        Ux = cos_60*ux - vert*dx;
 5959        Uy = cos_60*uy - vert*dy;
 5960        Uz = cos_60*uz - vert*dz;
 5961
 5962        /* Calculate L4/L5 positions relative to the major body. */
 5963        p.x = R * Dx;
 5964        p.y = R * Dy;
 5965        p.z = R * Dz;
 5966
 5967        /* Use dot products to find radial and tangential components of the relative velocity. */
 5968        vrad = vx*dx + vy*dy + vz*dz;
 5969        vtan = vx*ux + vy*uy + vz*uz;
 5970
 5971        /* Calculate L4/L5 velocities. */
 5972        p.vx = vrad*Dx + vtan*Ux;
 5973        p.vy = vrad*Dy + vtan*Uy;
 5974        p.vz = vrad*Dz + vtan*Uz;
 5975    }
 5976    else
 5977    {
 5978        /*
 5979            Calculate the distances of each body from their mutual barycenter.
 5980            r1 = negative distance of major mass from barycenter (e.g. Sun to the left of barycenter)
 5981            r2 = positive distance of minor mass from barycenter (e.g. Earth to the right of barycenter)
 5982        */
 5983        r1 = -R * (minor_mass / (major_mass + minor_mass));
 5984        r2 = +R * (major_mass / (major_mass + minor_mass));
 5985
 5986        /* Calculate the square of the angular orbital speed in [rad^2 / day^2]. */
 5987        omega2 = (major_mass + minor_mass) / (R2*R);
 5988
 5989        /*
 5990            Use Newton's Method to numerically solve for the location where
 5991            outward centrifugal acceleration in the rotating frame of reference
 5992            is equal to net inward gravitational acceleration.
 5993            First derive a good initial guess based on approximate analysis.
 5994        */
 5995        if (point == 1 || point == 2)
 5996        {
 5997            scale = (major_mass / (major_mass + minor_mass)) * cbrt(minor_mass / (3.0 * major_mass));
 5998            numer1 = -major_mass;    /* The major mass is to the left of L1 and L2 */
 5999            if (point == 1)
 6000            {
 6001                scale = 1.0 - scale;
 6002                numer2 = +minor_mass;    /* The minor mass is to the right of L1. */
 6003            }
 6004            else
 6005            {
 6006                scale = 1.0 + scale;
 6007                numer2 = -minor_mass;    /* The minor mass is to the left of L2. */
 6008            }
 6009        }
 6010        else /* point == 3 */
 6011        {
 6012            scale = ((7.0/12.0)*minor_mass - major_mass) / (minor_mass + major_mass);
 6013            numer1 = +major_mass;    /* major mass is to the right of L3. */
 6014            numer2 = +minor_mass;    /* minor mass is to the right of L3. */
 6015        }
 6016
 6017        /* Iterate Newton's Method until it converges. */
 6018        x = R*scale - r1;
 6019        do
 6020        {
 6021            dr1 = x - r1;
 6022            dr2 = x - r2;
 6023            accel = omega2*x + numer1/(dr1*dr1) + numer2/(dr2*dr2);
 6024            deriv = omega2 - 2*numer1/(dr1*dr1*dr1) - 2*numer2/(dr2*dr2*dr2);
 6025            deltax = accel/deriv;
 6026            x -= deltax;
 6027        }
 6028        while (fabs(deltax/R) > 1.0e-14);
 6029        scale = (x - r1) / R;
 6030
 6031        p.x  = scale * dx;
 6032        p.y  = scale * dy;
 6033        p.z  = scale * dz;
 6034        p.vx = scale * vx;
 6035        p.vy = scale * vy;
 6036        p.vz = scale * vz;
 6037    }
 6038    p.t = major_state.t;
 6039    p.status = ASTRO_SUCCESS;
 6040    return p;
 6041}
 6042
 6043
 6044/**
 6045 * @brief   Calculates equatorial coordinates of a celestial body as seen by an observer on the Earth's surface.
 6046 *
 6047 * Calculates topocentric equatorial coordinates in one of two different systems:
 6048 * J2000 or true-equator-of-date, depending on the value of the `equdate` parameter.
 6049 * Equatorial coordinates include right ascension, declination, and distance in astronomical units.
 6050 *
 6051 * This function corrects for light travel time: it adjusts the apparent location
 6052 * of the observed body based on how long it takes for light to travel from the body to the Earth.
 6053 *
 6054 * This function corrects for *topocentric parallax*, meaning that it adjusts for the
 6055 * angular shift depending on where the observer is located on the Earth. This is most
 6056 * significant for the Moon, because it is so close to the Earth. However, parallax corection
 6057 * has a small effect on the apparent positions of other bodies.
 6058 *
 6059 * Correction for aberration is optional, using the `aberration` parameter.
 6060 *
 6061 * @param body          The celestial body to be observed. Not allowed to be `BODY_EARTH`.
 6062 * @param time          The date and time at which the observation takes place.
 6063 * @param observer      A location on or near the surface of the Earth.
 6064 * @param equdate       Selects the date of the Earth's equator in which to express the equatorial coordinates.
 6065 * @param aberration    Selects whether or not to correct for aberration.
 6066 * @return              Topocentric equatorial coordinates of the celestial body.
 6067 */
 6068astro_equatorial_t Astronomy_Equator(
 6069    astro_body_t body,
 6070    astro_time_t *time,
 6071    astro_observer_t observer,
 6072    astro_equator_date_t equdate,
 6073    astro_aberration_t aberration)
 6074{
 6075    astro_equatorial_t equ;
 6076    astro_vector_t gc;
 6077    double gc_observer[3];
 6078    double j2000[3];
 6079    double temp[3];
 6080    double datevect[3];
 6081
 6082    if (time == NULL)
 6083        return EquError(ASTRO_INVALID_PARAMETER);
 6084
 6085    /* Calculate the geocentric location of the observer. */
 6086    geo_pos(time, observer, gc_observer);
 6087
 6088    /* Calculate the geocentric location of the body. */
 6089    gc = Astronomy_GeoVector(body, *time, aberration);
 6090    if (gc.status != ASTRO_SUCCESS)
 6091        return EquError(gc.status);
 6092
 6093    /* Convert geocentric coordinates to topocentric coordinates. */
 6094    j2000[0] = gc.x - gc_observer[0];
 6095    j2000[1] = gc.y - gc_observer[1];
 6096    j2000[2] = gc.z - gc_observer[2];
 6097
 6098    switch (equdate)
 6099    {
 6100    case EQUATOR_OF_DATE:
 6101        precession(j2000, *time, FROM_2000, temp);
 6102        nutation(temp, time, FROM_2000, datevect);
 6103        equ = vector2radec(datevect, *time);
 6104        return equ;
 6105
 6106    case EQUATOR_J2000:
 6107        equ = vector2radec(j2000, *time);
 6108        return equ;
 6109
 6110    default:
 6111        return EquError(ASTRO_INVALID_PARAMETER);
 6112    }
 6113}
 6114
 6115/**
 6116 * @brief Calculates geocentric equatorial coordinates of an observer on the surface of the Earth.
 6117 *
 6118 * This function calculates a vector from the center of the Earth to
 6119 * a point on or near the surface of the Earth, expressed in equatorial
 6120 * coordinates. It takes into account the rotation of the Earth at the given
 6121 * time, along with the given latitude, longitude, and elevation of the observer.
 6122 *
 6123 * The caller may pass a value in `equdate` to select either `EQUATOR_J2000`
 6124 * for using J2000 coordinates, or `EQUATOR_OF_DATE` for using coordinates relative
 6125 * to the Earth's equator at the specified time.
 6126 *
 6127 * The returned vector has components expressed in astronomical units (AU).
 6128 * To convert to kilometers, multiply the `x`, `y`, and `z` values by
 6129 * the constant value #KM_PER_AU.
 6130 *
 6131 * The inverse of this function is also available: #Astronomy_VectorObserver.
 6132 *
 6133 * @param time
 6134 *      The date and time for which to calculate the observer's position vector.
 6135 *
 6136 * @param observer
 6137 *      The geographic location of a point on or near the surface of the Earth.
 6138 *
 6139 * @param equdate
 6140 *      Selects the date of the Earth's equator in which to express the equatorial coordinates.
 6141 *      The caller may select `EQUATOR_J2000` to use the orientation of the Earth's equator
 6142 *      at noon UTC on January 1, 2000, in which case this function corrects for precession
 6143 *      and nutation of the Earth as it was at the moment specified by the `time` parameter.
 6144 *      Or the caller may select `EQUATOR_OF_DATE` to use the Earth's equator at `time`
 6145 *      as the orientation.
 6146 *
 6147 * @return
 6148 *      If successful, the returned vector holds `ASTRO_SUCCESS` in its `status` field,
 6149 *      and is an equatorial vector from the center of the Earth to the specified location
 6150 *      on (or near) the Earth's surface. Otherwise, `status` holds an error code.
 6151 */
 6152astro_vector_t Astronomy_ObserverVector(
 6153    astro_time_t *time,
 6154    astro_observer_t observer,
 6155    astro_equator_date_t equdate)
 6156{
 6157    astro_vector_t vec;
 6158    double gast, pos[3], temp[3];
 6159
 6160    if (time == NULL)
 6161        return VecError(ASTRO_INVALID_PARAMETER, TimeError());
 6162
 6163    gast = Astronomy_SiderealTime(time);
 6164    terra(observer, gast, pos, NULL);
 6165
 6166    switch (equdate)
 6167    {
 6168    case EQUATOR_OF_DATE:
 6169        /* 'pos' already contains equator-of-date coordinates. */
 6170        break;
 6171
 6172    case EQUATOR_J2000:
 6173        /* Convert 'pos' from equator-of-date to J2000. */
 6174        nutation(pos, time, INTO_2000, temp);
 6175        precession(temp, *time, INTO_2000, pos);
 6176        break;
 6177
 6178    default:
 6179        /* This is not a valid value of the 'equdate' parameter. */
 6180        return VecError(ASTRO_INVALID_PARAMETER, *time);
 6181    }
 6182
 6183    vec.x = pos[0];
 6184    vec.y = pos[1];
 6185    vec.z = pos[2];
 6186    vec.t = *time;
 6187    vec.status = ASTRO_SUCCESS;
 6188    return vec;
 6189}
 6190
 6191
 6192/**
 6193 * @brief Calculates geocentric equatorial position and velocity of an observer on the surface of the Earth.
 6194 *
 6195 * This function calculates position and velocity vectors of an observer
 6196 * on or near the surface of the Earth, expressed in equatorial
 6197 * coordinates. It takes into account the rotation of the Earth at the given
 6198 * time, along with the given latitude, longitude, and elevation of the observer.
 6199 *
 6200 * The caller may pass a value in `equdate` to select either `EQUATOR_J2000`
 6201 * for using J2000 coordinates, or `EQUATOR_OF_DATE` for using coordinates relative
 6202 * to the Earth's equator at the specified time.
 6203 *
 6204 * The returned position vector has components expressed in astronomical units (AU).
 6205 * To convert to kilometers, multiply the `x`, `y`, and `z` values by
 6206 * the constant value #KM_PER_AU.
 6207 *
 6208 * The returned velocity vector is measured in AU/day.
 6209 *
 6210 * If you need the position only, and not the velocity, #Astronomy_ObserverVector
 6211 * is slightly more efficient than this function.
 6212 *
 6213 * @param time
 6214 *      The date and time for which to calculate the observer's geocentric state vector.
 6215 *
 6216 * @param observer
 6217 *      The geographic location of a point on or near the surface of the Earth.
 6218 *
 6219 * @param equdate
 6220 *      Selects the date of the Earth's equator in which to express the equatorial coordinates.
 6221 *      The caller may select `EQUATOR_J2000` to use the orientation of the Earth's equator
 6222 *      at noon UTC on January 1, 2000, in which case this function corrects for precession
 6223 *      and nutation of the Earth as it was at the moment specified by the `time` parameter.
 6224 *      Or the caller may select `EQUATOR_OF_DATE` to use the Earth's equator at `time`
 6225 *      as the orientation.
 6226 *
 6227 * @return
 6228 *      If successful, the returned state vector holds `ASTRO_SUCCESS` in its `status` field,
 6229 *      and the position (x, y, z) and velocity (vx, vy, vz) vectors are valid.
 6230 *      Otherwise, `status` holds an error code.
 6231 */
 6232astro_state_vector_t Astronomy_ObserverState(
 6233    astro_time_t *time,
 6234    astro_observer_t observer,
 6235    astro_equator_date_t equdate)
 6236{
 6237    astro_state_vector_t state;
 6238    double gast, pos[3], vel[3], postemp[3], veltemp[3];
 6239
 6240    if (time == NULL)
 6241        return StateVecError(ASTRO_INVALID_PARAMETER, TimeError());
 6242
 6243    gast = Astronomy_SiderealTime(time);
 6244    terra(observer, gast, pos, vel);
 6245
 6246    switch (equdate)
 6247    {
 6248    case EQUATOR_OF_DATE:
 6249        /* 'pos' and 'vel' already contain equator-of-date coordinates. */
 6250        break;
 6251
 6252    case EQUATOR_J2000:
 6253        /* Convert 'pos' from equator-of-date to J2000. */
 6254        nutation_posvel(pos, vel, time, INTO_2000, postemp, veltemp);
 6255        precession_posvel(postemp, veltemp, *time, INTO_2000, pos, vel);
 6256        break;
 6257
 6258    default:
 6259        /* This is not a valid value of the 'equdate' parameter. */
 6260        return StateVecError(ASTRO_INVALID_PARAMETER, *time);
 6261    }
 6262
 6263    state.x  = pos[0];
 6264    state.y  = pos[1];
 6265    state.z  = pos[2];
 6266    state.vx = vel[0];
 6267    state.vy = vel[1];
 6268    state.vz = vel[2];
 6269    state.t  = *time;
 6270    state.status = ASTRO_SUCCESS;
 6271    return state;
 6272}
 6273
 6274
 6275/**
 6276 * @brief Calculates the geographic location corresponding to an equatorial vector.
 6277 *
 6278 * This is the inverse function of #Astronomy_ObserverVector.
 6279 * Given a geocentric equatorial vector, it returns the geographic
 6280 * latitude, longitude, and elevation for that vector.
 6281 *
 6282 * @param vector
 6283 *      The geocentric equatorial position vector for which to find geographic coordinates.
 6284 *      The components are expressed in Astronomical Units (AU).
 6285 *      You can calculate AU by dividing kilometers by the constant #KM_PER_AU.
 6286 *      The time `vector.t` determines the Earth's rotation. The caller must set `vector.t` to a valid time.
 6287 *      The vector is passed by reference (using a pointer) so that nutation calculations
 6288 *      can be cached inside `vector.t` as an optimization.
 6289 *
 6290 * @param equdate
 6291 *      Selects the date of the Earth's equator in which `vector` is expressed.
 6292 *      The caller may select `EQUATOR_J2000` to use the orientation of the Earth's equator
 6293 *      at noon UTC on January 1, 2000, in which case this function corrects for precession
 6294 *      and nutation of the Earth as it was at the moment specified by `vector.t`.
 6295 *      Or the caller may select `EQUATOR_OF_DATE` to use the Earth's equator at `vector.t`
 6296 *      as the orientation.
 6297 *
 6298 * @return
 6299 *      The geographic latitude, longitude, and elevation above sea level
 6300 *      that corresponds to the given equatorial vector.
 6301 */
 6302astro_observer_t Astronomy_VectorObserver(
 6303    astro_vector_t *vector,
 6304    astro_equator_date_t equdate)
 6305{
 6306    double gast;
 6307    double pos1[3];
 6308    double pos2[3];
 6309
 6310    gast = Astronomy_SiderealTime(&vector->t);
 6311    pos1[0] = vector->x;
 6312    pos1[1] = vector->y;
 6313    pos1[2] = vector->z;
 6314    if (equdate == EQUATOR_J2000)
 6315    {
 6316        precession(pos1, vector->t, FROM_2000, pos2);
 6317        nutation(pos2, &vector->t, FROM_2000, pos1);
 6318    }
 6319    return inverse_terra(pos1, gast);
 6320}
 6321
 6322
 6323/**
 6324 * @brief Calculates the gravitational acceleration experienced by an observer on the Earth.
 6325 *
 6326 * This function implements the WGS 84 Ellipsoidal Gravity Formula.
 6327 * The result is a combination of inward gravitational acceleration
 6328 * with outward centrifugal acceleration, as experienced by an observer
 6329 * in the Earth's rotating frame of reference.
 6330 * The resulting value increases toward the Earth's poles and decreases
 6331 * toward the equator, consistent with changes of the weight measured
 6332 * by a spring scale of a fixed mass moved to different latitudes and heights
 6333 * on the Earth.
 6334 *
 6335 * @param latitude
 6336 *      The latitude of the observer in degrees north or south of the equator.
 6337 *      By formula symmetry, positive latitudes give the same answer as negative
 6338 *      latitudes, so the sign does not matter.
 6339 *
 6340 * @param height
 6341 *      The height above the sea level geoid in meters.
 6342 *      No range checking is done; however, accuracy is only valid in the
 6343 *      range 0 to 100000 meters.
 6344 *
 6345 * @return
 6346 *      The effective gravitational acceleration expressed in meters per second squared [m/s^2].
 6347 */
 6348double Astronomy_ObserverGravity(double latitude, double height)
 6349{
 6350    double s = sin(latitude * DEG2RAD);
 6351    double s2 = s*s;
 6352    double g0 = 9.7803253359 * (1.0 + 0.00193185265241*s2) / sqrt(1.0 - 0.00669437999013*s2);
 6353    return g0 * (1.0 - (3.15704e-07 - 2.10269e-09*s2)*height + 7.37452e-14*height*height);
 6354}
 6355
 6356
 6357/**
 6358 * @brief Calculates the apparent location of a body relative to the local horizon of an observer on the Earth.
 6359 *
 6360 * Given a date and time, the geographic location of an observer on the Earth, and
 6361 * equatorial coordinates (right ascension and declination) of a celestial body,
 6362 * this function returns horizontal coordinates (azimuth and altitude angles) for the body
 6363 * relative to the horizon at the geographic location.
 6364 *
 6365 * The right ascension `ra` and declination `dec` passed in must be *equator of date*
 6366 * coordinates, based on the Earth's true equator at the date and time of the observation.
 6367 * Otherwise the resulting horizontal coordinates will be inaccurate.
 6368 * Equator of date coordinates can be obtained by calling #Astronomy_Equator, passing in
 6369 * `EQUATOR_OF_DATE` as its `equdate` parameter. It is also recommended to enable
 6370 * aberration correction by passing in `ABERRATION` as the `aberration` parameter.
 6371 *
 6372 * This function optionally corrects for atmospheric refraction.
 6373 * For most uses, it is recommended to pass `REFRACTION_NORMAL` in the `refraction` parameter to
 6374 * correct for optical lensing of the Earth's atmosphere that causes objects
 6375 * to appear somewhat higher above the horizon than they actually are.
 6376 * However, callers may choose to avoid this correction by passing in `REFRACTION_NONE`.
 6377 * If refraction correction is enabled, the azimuth, altitude, right ascension, and declination
 6378 * in the #astro_horizon_t structure returned by this function will all be corrected for refraction.
 6379 * If refraction is disabled, none of these four coordinates will be corrected; in that case,
 6380 * the right ascension and declination in the returned structure will be numerically identical
 6381 * to the respective `ra` and `dec` values passed in.
 6382 *
 6383 * @param time
 6384 *      The date and time of the observation.
 6385 *
 6386 * @param observer
 6387 *      The geographic location of the observer.
 6388 *
 6389 * @param ra
 6390 *      The right ascension of the body in sidereal hours.
 6391 *      See function remarks for more details.
 6392 *
 6393 * @param dec
 6394 *      The declination of the body in degrees. See function remarks for more details.
 6395 *
 6396 * @param refraction
 6397 *      Selects whether to correct for atmospheric refraction, and if so, which model to use.
 6398 *      The recommended value for most uses is `REFRACTION_NORMAL`.
 6399 *      See function remarks for more details.
 6400 *
 6401 * @return
 6402 *      The body's apparent horizontal coordinates and equatorial coordinates, both optionally corrected for refraction.
 6403 */
 6404astro_horizon_t Astronomy_Horizon(
 6405    astro_time_t *time,
 6406    astro_observer_t observer,
 6407    double ra,
 6408    double dec,
 6409    astro_refraction_t refraction)
 6410{
 6411    astro_horizon_t hor;
 6412    double latrad, lonrad, decrad, rarad;
 6413    double uze[3], une[3], uwe[3];
 6414    double uz[3], un[3], uw[3];
 6415    double p[3], pz, pn, pw, proj;
 6416    double az, zd;
 6417    double spin_angle;
 6418
 6419    if (time == NULL)
 6420    {
 6421        /* The best we can do is return an invalid state. */
 6422        /* It would break external dependencies to expect them to check for errors. */
 6423        hor.altitude = NAN;
 6424        hor.azimuth = NAN;
 6425        hor.ra = NAN;
 6426        hor.dec = NAN;
 6427        return hor;
 6428    }
 6429
 6430    latrad = observer.latitude * DEG2RAD;
 6431    lonrad = observer.longitude * DEG2RAD;
 6432    decrad = dec * DEG2RAD;
 6433    rarad = ra * HOUR2RAD;
 6434
 6435    double sinlat = sin(latrad);
 6436    double coslat = cos(latrad);
 6437    double sinlon = sin(lonrad);
 6438    double coslon = cos(lonrad);
 6439    double sindc = sin(decrad);
 6440    double cosdc = cos(decrad);
 6441    double sinra = sin(rarad);
 6442    double cosra = cos(rarad);
 6443
 6444    /*
 6445        Calculate three mutually perpendicular unit vectors
 6446        in equatorial coordinates: uze, une, uwe.
 6447
 6448        uze = The direction of the observer's local zenith (straight up).
 6449        une = The direction toward due north on the observer's horizon.
 6450        uwe = The direction toward due west on the observer's horizon.
 6451
 6452        HOWEVER, these are uncorrected for the Earth's rotation due to the time of day.
 6453
 6454        The components of these 3 vectors are as follows:
 6455        [0] = x = direction from center of Earth toward 0 degrees longitude (the prime meridian) on equator.
 6456        [1] = y = direction from center of Earth toward 90 degrees west longitude on equator.
 6457        [2] = z = direction from center of Earth toward the north pole.
 6458    */
 6459
 6460    uze[0] = coslat * coslon;
 6461    uze[1] = coslat * sinlon;
 6462    uze[2] = sinlat;
 6463
 6464    une[0] = -sinlat * coslon;
 6465    une[1] = -sinlat * sinlon;
 6466    une[2] = coslat;
 6467
 6468    uwe[0] = sinlon;
 6469    uwe[1] = -coslon;
 6470    uwe[2] = 0.0;
 6471
 6472    /*
 6473        Correct the vectors uze, une, uwe for the Earth's rotation by calculating
 6474        sidereal time. Call spin() for each uncorrected vector to rotate about
 6475        the Earth's axis to yield corrected unit vectors uz, un, uw.
 6476        Multiply sidereal hours by -15 to convert to degrees and flip eastward
 6477        rotation of the Earth to westward apparent movement of objects with time.
 6478    */
 6479
 6480    spin_angle = -15.0 * Astronomy_SiderealTime(time);
 6481    spin(spin_angle, uze, uz);
 6482    spin(spin_angle, une, un);
 6483    spin(spin_angle, uwe, uw);
 6484
 6485    /*
 6486        Convert angular equatorial coordinates (RA, DEC) to
 6487        cartesian equatorial coordinates in 'p', using the
 6488        same orientation system as uze, une, uwe.
 6489    */
 6490
 6491    p[0] = cosdc * cosra;
 6492    p[1] = cosdc * sinra;
 6493    p[2] = sindc;
 6494
 6495    /*
 6496        Use dot products of p with the zenith, north, and west
 6497        vectors to obtain the cartesian coordinates of the body in
 6498        the observer's horizontal orientation system.
 6499
 6500        pn = north  component [-1, +1]
 6501        pw = west   component [-1, +1]
 6502        pz = zenith component [-1, +1]
 6503    */
 6504
 6505    pn = p[0]*un[0] + p[1]*un[1] + p[2]*un[2];
 6506    pw = p[0]*uw[0] + p[1]*uw[1] + p[2]*uw[2];
 6507    pz = p[0]*uz[0] + p[1]*uz[1] + p[2]*uz[2];
 6508
 6509    /* proj is the "shadow" of the body vector along the observer's flat ground. */
 6510    proj = hypot(pn, pw);
 6511    if (proj > 0.0)
 6512    {
 6513        /* If the body is not exactly straight up/down, it has an azimuth. */
 6514        /* Invert the angle to produce degrees eastward from north. */
 6515        az = -atan2(pw, pn) * RAD2DEG;
 6516        if (az < 0.0)
 6517            az += 360;
 6518    }
 6519    else
 6520    {
 6521        /* The body is straight up/down, so it does not have an azimuth. */
 6522        /* Report an arbitrary but reasonable value. */
 6523        az = 0.0;
 6524    }
 6525
 6526    /* zd = the angle of the body away from the observer's zenith, in degrees. */
 6527    zd = atan2(proj, pz) * RAD2DEG;
 6528    hor.ra = ra;
 6529    hor.dec = dec;
 6530
 6531    if (refraction == REFRACTION_NORMAL || refraction == REFRACTION_JPLHOR)
 6532    {
 6533        double zd0, refr;
 6534
 6535        zd0 = zd;
 6536        refr = Astronomy_Refraction(refraction, 90.0 - zd);
 6537        zd -= refr;
 6538
 6539        if (refr > 0.0 && zd > 3.0e-4)
 6540        {
 6541            int j;
 6542            double sinzd = sin(zd * DEG2RAD);
 6543            double coszd = cos(zd * DEG2RAD);
 6544            double sinzd0 = sin(zd0 * DEG2RAD);
 6545            double coszd0 = cos(zd0 * DEG2RAD);
 6546            double pr[3];
 6547
 6548            for (j=0; j<3; ++j)
 6549                pr[j] = ((p[j] - coszd0 * uz[j]) / sinzd0)*sinzd + uz[j]*coszd;
 6550
 6551            proj = hypot(pr[0], pr[1]);
 6552            if (proj > 0)
 6553            {
 6554                hor.ra = RAD2HOUR * atan2(pr[1], pr[0]);
 6555                if (hor.ra < 0.0)
 6556                    hor.ra += 24.0;
 6557            }
 6558            else
 6559            {
 6560                hor.ra = 0.0;
 6561            }
 6562            hor.dec = RAD2DEG * atan2(pr[2], proj);
 6563        }
 6564    }
 6565
 6566    hor.azimuth = az;
 6567    hor.altitude = 90.0 - zd;
 6568    return hor;
 6569}
 6570
 6571/**
 6572 * @brief Calculates geocentric ecliptic coordinates for the Sun.
 6573 *
 6574 * This function calculates the position of the Sun as seen from the Earth.
 6575 * The returned value includes both Cartesian and spherical coordinates.
 6576 * The x-coordinate and longitude values in the returned structure are based
 6577 * on the *true equinox of date*: one of two points in the sky where the instantaneous
 6578 * plane of the Earth's equator at the given date and time (the *equatorial plane*)
 6579 * intersects with the plane of the Earth's orbit around the Sun (the *ecliptic plane*).
 6580 * By convention, the apparent location of the Sun at the March equinox is chosen
 6581 * as the longitude origin and x-axis direction, instead of the one for September.
 6582 *
 6583 * `Astronomy_SunPosition` corrects for precession and nutation of the Earth's axis
 6584 * in order to obtain the exact equatorial plane at the given time.
 6585 *
 6586 * This function can be used for calculating changes of seasons: equinoxes and solstices.
 6587 * In fact, the function #Astronomy_Seasons does use this function for that purpose.
 6588 *
 6589 * @param time
 6590 *      The date and time for which to calculate the Sun's position.
 6591 *
 6592 * @return
 6593 *      The ecliptic coordinates of the Sun using the Earth's true equator of date.
 6594 */
 6595astro_ecliptic_t Astronomy_SunPosition(astro_time_t time)
 6596{
 6597    astro_time_t adjusted_time;
 6598    astro_vector_t earth2000;
 6599    double sun2000[3];
 6600    double stemp[3];
 6601    double sun_ofdate[3];
 6602    double true_obliq;
 6603
 6604    /* Correct for light travel time from the Sun. */
 6605    /* Otherwise season calculations (equinox, solstice) will all be early by about 8 minutes! */
 6606    adjusted_time = Astronomy_AddDays(time, -1.0 / C_AUDAY);
 6607
 6608    earth2000 = CalcEarth(adjusted_time);
 6609    if (earth2000.status != ASTRO_SUCCESS)
 6610        return EclError(earth2000.status);
 6611
 6612    /* Convert heliocentric location of Earth to geocentric location of Sun. */
 6613    sun2000[0] = -earth2000.x;
 6614    sun2000[1] = -earth2000.y;
 6615    sun2000[2] = -earth2000.z;
 6616
 6617    /* Convert to equatorial Cartesian coordinates of date. */
 6618    precession(sun2000, adjusted_time, FROM_2000, stemp);
 6619    nutation(stemp, &adjusted_time, FROM_2000, sun_ofdate);
 6620
 6621    /* Convert equatorial coordinates to ecliptic coordinates. */
 6622    true_obliq = DEG2RAD * e_tilt(&adjusted_time).tobl;
 6623    return RotateEquatorialToEcliptic(sun_ofdate, true_obliq, time);
 6624}
 6625
 6626/**
 6627 * @brief Converts a J2000 mean equator (EQJ) vector to a true ecliptic of date (ETC) vector and angles.
 6628 *
 6629 * Given coordinates relative to the Earth's equator at J2000 (the instant of noon UTC
 6630 * on 1 January 2000), this function converts those coordinates to true ecliptic coordinates
 6631 * that are relative to the plane of the Earth's orbit around the Sun on that date.
 6632 *
 6633 * @param eqj
 6634 *      Equatorial coordinates in the EQJ frame of reference.
 6635 *      You can call #Astronomy_GeoVector to obtain suitable equatorial coordinates.
 6636 *
 6637 * @return
 6638 *      Spherical and vector coordinates expressed in true ecliptic coordinates of date (ECT).
 6639 */
 6640astro_ecliptic_t Astronomy_Ecliptic(astro_vector_t eqj)
 6641{
 6642    earth_tilt_t et;
 6643    double eqj_pos[3];
 6644    double mean_pos[3];
 6645    double eqd_pos[3];
 6646
 6647    if (eqj.status != ASTRO_SUCCESS)
 6648        return EclError(eqj.status);
 6649
 6650    /* Calculate nutation and obliquity for this time. */
 6651    /* As an optimization, the nutation angles are cached in `time`, */
 6652    /* and reused below when the `nutation` function is called. */
 6653    et = e_tilt(&eqj.t);
 6654
 6655    /* Convert mean J2000 equator (EQJ) to true equator of date (EQD). */
 6656    eqj_pos[0] = eqj.x;
 6657    eqj_pos[1] = eqj.y;
 6658    eqj_pos[2] = eqj.z;
 6659    precession(eqj_pos, eqj.t, FROM_2000, mean_pos);
 6660    nutation(mean_pos, &eqj.t, FROM_2000, eqd_pos);
 6661
 6662    /* Rotate from EQD to true ecliptic of date (ECT). */
 6663    return RotateEquatorialToEcliptic(eqd_pos, et.tobl * DEG2RAD, eqj.t);
 6664}
 6665
 6666/**
 6667 * @brief   Calculates heliocentric ecliptic longitude of a body.
 6668 *
 6669 * This function calculates the angle around the plane of the Earth's orbit
 6670 * of a celestial body, as seen from the center of the Sun.
 6671 * The angle is measured prograde (in the direction of the Earth's orbit around the Sun)
 6672 * in degrees from the true equinox of date. The ecliptic longitude is always in the range [0, 360).
 6673 *
 6674 * @param body
 6675 *      A body other than the Sun.
 6676 *
 6677 * @param time
 6678 *      The date and time at which the body's ecliptic longitude is to be calculated.
 6679 *
 6680 * @return
 6681 *      On success, returns a structure whose `status` is `ASTRO_SUCCESS` and whose
 6682 *      `angle` holds the ecliptic longitude in degrees.
 6683 *      On failure, `status` holds a value other than `ASTRO_SUCCESS`.
 6684 */
 6685astro_angle_result_t Astronomy_EclipticLongitude(astro_body_t body, astro_time_t time)
 6686{
 6687    astro_vector_t hv;
 6688    astro_ecliptic_t eclip;
 6689    astro_angle_result_t result;
 6690
 6691    if (body == BODY_SUN)
 6692        return AngleError(ASTRO_INVALID_BODY);      /* cannot calculate heliocentric longitude of the Sun */
 6693
 6694    hv = Astronomy_HelioVector(body, time);
 6695    eclip = Astronomy_Ecliptic(hv);     /* checks for errors in hv, so we don't have to here */
 6696    if (eclip.status != ASTRO_SUCCESS)
 6697        return AngleError(eclip.status);
 6698
 6699    result.angle = eclip.elon;
 6700    result.status = ASTRO_SUCCESS;
 6701    return result;
 6702}
 6703
 6704static astro_ecliptic_t RotateEquatorialToEcliptic(const double pos[3], double obliq_radians, astro_time_t time)
 6705{
 6706    astro_ecliptic_t ecl;
 6707    double cos_ob, sin_ob;
 6708    double xyproj;
 6709
 6710    cos_ob = cos(obliq_radians);
 6711    sin_ob = sin(obliq_radians);
 6712
 6713    ecl.vec.status = ASTRO_SUCCESS;
 6714    ecl.vec.t = time;
 6715    ecl.vec.x = +pos[0];
 6716    ecl.vec.y = +pos[1]*cos_ob + pos[2]*sin_ob;
 6717    ecl.vec.z = -pos[1]*sin_ob + pos[2]*cos_ob;
 6718
 6719    xyproj = hypot(ecl.vec.x, ecl.vec.y);
 6720    if (xyproj > 0.0)
 6721    {
 6722        ecl.elon = RAD2DEG * atan2(ecl.vec.y, ecl.vec.x);
 6723        if (ecl.elon < 0.0)
 6724            ecl.elon += 360.0;
 6725    }
 6726    else
 6727        ecl.elon = 0.0;
 6728
 6729    ecl.elat = RAD2DEG * atan2(ecl.vec.z, xyproj);
 6730    ecl.status = ASTRO_SUCCESS;
 6731    return ecl;
 6732}
 6733
 6734static astro_func_result_t sun_offset(void *context, astro_time_t time)
 6735{
 6736    astro_func_result_t result;
 6737    double targetLon = *((double *)context);
 6738    astro_ecliptic_t ecl = Astronomy_SunPosition(time);
 6739    if (ecl.status != ASTRO_SUCCESS)
 6740        return FuncError(ecl.status);
 6741    result.value = LongitudeOffset(ecl.elon - targetLon);
 6742    result.status = ASTRO_SUCCESS;
 6743    return result;
 6744}
 6745
 6746/**
 6747 * @brief
 6748 *      Searches for the time when the Sun reaches an apparent ecliptic longitude as seen from the Earth.
 6749 *
 6750 * This function finds the moment in time, if any exists in the given time window,
 6751 * that the center of the Sun reaches a specific ecliptic longitude as seen from the center of the Earth.
 6752 *
 6753 * This function can be used to determine equinoxes and solstices.
 6754 * However, it is usually more convenient and efficient to call #Astronomy_Seasons
 6755 * to calculate all equinoxes and solstices for a given calendar year.
 6756 *
 6757 * The function searches the window of time specified by `startTime` and `startTime+limitDays`.
 6758 * The search will return an error if the Sun never reaches the longitude `targetLon` or
 6759 * if the window is so large that the longitude ranges more than 180 degrees within it.
 6760 * It is recommended to keep the window smaller than 10 days when possible.
 6761 *
 6762 * @param targetLon
 6763 *      The desired ecliptic longitude in degrees, relative to the true equinox of date.
 6764 *      This may be any value in the range [0, 360), although certain values have
 6765 *      conventional meanings:
 6766 *      0 = March equinox, 90 = June solstice, 180 = September equinox, 270 = December solstice.
 6767 *
 6768 * @param startTime
 6769 *      The date and time for starting the search for the desired longitude event.
 6770 *
 6771 * @param limitDays
 6772 *      The real-valued number of days, which when added to `startTime`, limits the
 6773 *      range of time over which the search looks.
 6774 *      It is recommended to keep this value between 1 and 10 days.
 6775 *      See function remarks for more details.
 6776 *
 6777 * @return
 6778 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
 6779 *      and the `time` field will contain the date and time the Sun reaches the target longitude.
 6780 *      Any other value indicates an error.
 6781 *      See remarks in #Astronomy_Search (which this function calls) for more information about possible error codes.
 6782 */
 6783astro_search_result_t Astronomy_SearchSunLongitude(
 6784    double targetLon,
 6785    astro_time_t startTime,
 6786    double limitDays)
 6787{
 6788    astro_time_t t2 = Astronomy_AddDays(startTime, limitDays);
 6789    return Astronomy_Search(sun_offset, &targetLon, startTime, t2, 0.01);
 6790}
 6791
 6792/** @cond DOXYGEN_SKIP */
 6793#define CALLFUNC(f,t)  \
 6794    do { \
 6795        funcres = func(context, (t)); \
 6796        if (funcres.status != ASTRO_SUCCESS) return SearchError(funcres.status); \
 6797        (f) = funcres.value; \
 6798    } while(0)
 6799/** @endcond */
 6800
 6801/**
 6802 * @brief Searches for a time at which a function's value increases through zero.
 6803 *
 6804 * Certain astronomy calculations involve finding a time when an event occurs.
 6805 * Often such events can be defined as the root of a function:
 6806 * the time at which the function's value becomes zero.
 6807 *
 6808 * `Astronomy_Search` finds the *ascending root* of a function: the time at which
 6809 * the function's value becomes zero while having a positive slope. That is, as time increases,
 6810 * the function transitions from a negative value, through zero at a specific moment,
 6811 * to a positive value later. The goal of the search is to find that specific moment.
 6812 *
 6813 * The search function is specified by two parameters: `func` and `context`.
 6814 * The `func` parameter is a pointer to the function itself, which accepts a time
 6815 * and a context containing any other arguments needed to evaluate the function.
 6816 * The `context` parameter supplies that context for the given search.
 6817 * As an example, a caller may wish to find the moment a celestial body reaches a certain
 6818 * ecliptic longitude. In that case, the caller might create a structure that contains
 6819 * an #astro_body_t member to specify the body and a `double` to hold the target longitude.
 6820 * The function would cast the pointer `context` passed in as a pointer to that structure type.
 6821 * It could subtract the target longitude from the actual longitude at a given time;
 6822 * thus the difference would equal zero at the moment in time the planet reaches the
 6823 * desired longitude.
 6824 *
 6825 * The `func` returns an #astro_func_result_t structure every time it is called.
 6826 * If the returned structure has a value of `status` other than `ASTRO_SUCCESS`,
 6827 * the search immediately fails and reports that same error code in the `status`
 6828 * returned by `Astronomy_Search`. Otherwise, `status` is `ASTRO_SUCCESS` and
 6829 * `value` is the value of the function, and the search proceeds until it either
 6830 * finds the ascending root or fails for some reason.
 6831 *
 6832 * The search calls `func` repeatedly to rapidly narrow in on any ascending
 6833 * root within the time window specified by `t1` and `t2`. The search never
 6834 * reports a solution outside this time window.
 6835 *
 6836 * `Astronomy_Search` uses a combination of bisection and quadratic interpolation
 6837 * to minimize the number of function calls. However, it is critical that the
 6838 * supplied time window be small enough that there cannot be more than one root
 6839 * (ascedning or descending) within it; otherwise the search can fail.
 6840 * Beyond that, it helps to make the time window as small as possible, ideally
 6841 * such that the function itself resembles a smooth parabolic curve within that window.
 6842 *
 6843 * If an ascending root is not found, or more than one root
 6844 * (ascending and/or descending) exists within the window `t1`..`t2`,
 6845 * the search will fail with status code `ASTRO_SEARCH_FAILURE`.
 6846 *
 6847 * If the search does not converge within 20 iterations, it will fail
 6848 * with status code `ASTRO_NO_CONVERGE`.
 6849 *
 6850 * @param func
 6851 *      The function for which to find the time of an ascending root.
 6852 *      See function remarks for more details.
 6853 *
 6854 * @param context
 6855 *      Any ancillary data needed by the function `func` to calculate a value.
 6856 *      The data type varies depending on the function passed in.
 6857 *      For example, the function may involve a specific celestial body that
 6858 *      must be specified somehow.
 6859 *
 6860 * @param t1
 6861 *      The lower time bound of the search window.
 6862 *      See function remarks for more details.
 6863 *
 6864 * @param t2
 6865 *      The upper time bound of the search window.
 6866 *      See function remarks for more details.
 6867 *
 6868 * @param dt_tolerance_seconds
 6869 *      Specifies an amount of time in seconds within which a bounded ascending root
 6870 *      is considered accurate enough to stop. A typical value is 1 second.
 6871 *
 6872 * @return
 6873 *      If successful, the returned structure has `status` equal to `ASTRO_SUCCESS`
 6874 *      and `time` set to a value within `dt_tolerance_seconds` of an ascending root.
 6875 *      On success, the `time` value will always be in the inclusive range [`t1`, `t2`].
 6876 *      If the search fails, `status` will be set to a value other than `ASTRO_SUCCESS`.
 6877 *      See function remarks for more details.
 6878 */
 6879astro_search_result_t Astronomy_Search(
 6880    astro_search_func_t func,
 6881    void *context,
 6882    astro_time_t t1,
 6883    astro_time_t t2,
 6884    double dt_tolerance_seconds)
 6885{
 6886    astro_search_result_t result;
 6887    astro_time_t tmid;
 6888    astro_time_t tq;
 6889    astro_func_result_t funcres;
 6890    double f1, f2, fmid=0.0, fq, dt_days, dt, dt_guess;
 6891    double q_ut, q_df_dt;
 6892    const int iter_limit = 20;
 6893    int iter = 0;
 6894    int calc_fmid = 1;
 6895
 6896    dt_days = fabs(dt_tolerance_seconds / SECONDS_PER_DAY);
 6897    CALLFUNC(f1, t1);
 6898    CALLFUNC(f2, t2);
 6899
 6900    for(;;)
 6901    {
 6902        if (++iter > iter_limit)
 6903            return SearchError(ASTRO_NO_CONVERGE);
 6904
 6905        dt = (t2.tt - t1.tt) / 2.0;
 6906        tmid = Astronomy_AddDays(t1, dt);
 6907        if (fabs(dt) < dt_days)
 6908        {
 6909            /* We are close enough to the event to stop the search. */
 6910            result.time = tmid;
 6911            result.status = ASTRO_SUCCESS;
 6912            return result;
 6913        }
 6914
 6915        if (calc_fmid)
 6916            CALLFUNC(fmid, tmid);
 6917        else
 6918            calc_fmid = 1;      /* we already have the correct value of fmid from the previous loop */
 6919
 6920        /* Quadratic interpolation: */
 6921        /* Try to find a parabola that passes through the 3 points we have sampled: */
 6922        /* (t1,f1), (tmid,fmid), (t2,f2) */
 6923
 6924        if (QuadInterp(tmid.ut, t2.ut - tmid.ut, f1, fmid, f2, &q_ut, &q_df_dt))
 6925        {
 6926            tq = Astronomy_TimeFromDays(q_ut);
 6927            CALLFUNC(fq, tq);
 6928            if (q_df_dt != 0.0)
 6929            {
 6930                dt_guess = fabs(fq / q_df_dt);
 6931                if (dt_guess < dt_days)
 6932                {
 6933                    /* The estimated time error is small enough that we can quit now. */
 6934                    result.time = tq;
 6935                    result.status = ASTRO_SUCCESS;
 6936                    return result;
 6937                }
 6938
 6939                /* Try guessing a tighter boundary with the interpolated root at the center. */
 6940                dt_guess *= 1.2;
 6941                if (dt_guess < dt/10.0)
 6942                {
 6943                    astro_time_t tleft = Astronomy_AddDays(tq, -dt_guess);
 6944                    astro_time_t tright = Astronomy_AddDays(tq, +dt_guess);
 6945                    if ((tleft.ut - t1.ut)*(tleft.ut - t2.ut) < 0)
 6946                    {
 6947                        if ((tright.ut - t1.ut)*(tright.ut - t2.ut) < 0)
 6948                        {
 6949                            double fleft, fright;
 6950                            CALLFUNC(fleft, tleft);
 6951                            CALLFUNC(fright, tright);
 6952                            if (fleft<0.0 && fright>=0.0)
 6953                            {
 6954                                f1 = fleft;
 6955                                f2 = fright;
 6956                                t1 = tleft;
 6957                                t2 = tright;
 6958                                fmid = fq;
 6959                                calc_fmid = 0;  /* save a little work -- no need to re-calculate fmid next time around the loop */
 6960                                continue;
 6961                            }
 6962                        }
 6963                    }
 6964                }
 6965            }
 6966        }
 6967
 6968        /* After quadratic interpolation attempt. */
 6969        /* Now just divide the region in two parts and pick whichever one appears to contain a root. */
 6970        if (f1 < 0.0 && fmid >= 0.0)
 6971        {
 6972            t2 = tmid;
 6973            f2 = fmid;
 6974            continue;
 6975        }
 6976
 6977        if (fmid < 0.0 && f2 >= 0.0)
 6978        {
 6979            t1 = tmid;
 6980            f1 = fmid;
 6981            continue;
 6982        }
 6983
 6984        /* Either there is no ascending zero-crossing in this range */
 6985        /* or the search window is too wide (more than one zero-crossing). */
 6986        return SearchError(ASTRO_SEARCH_FAILURE);
 6987    }
 6988}
 6989
 6990static int QuadInterp(
 6991    double tm, double dt, double fa, double fm, double fb,
 6992    double *out_t, double *out_df_dt)
 6993{
 6994    double Q, R, S;
 6995    double x, u, ru, x1, x2;
 6996
 6997    Q = (fb + fa)/2.0 - fm;
 6998    R = (fb - fa)/2.0;
 6999    S = fm;
 7000
 7001    if (Q == 0.0)
 7002    {
 7003        /* This is a line, not a parabola. */
 7004        if (R == 0.0)
 7005            return 0;       /* This is a HORIZONTAL line... can't make progress! */
 7006        x = -S / R;
 7007        if (x < -1.0 || x > +1.0)
 7008            return 0;   /* out of bounds */
 7009    }
 7010    else
 7011    {
 7012        /* This really is a parabola. Find roots x1, x2. */
 7013        u = R*R - 4*Q*S;
 7014        if (u <= 0.0)
 7015            return 0;   /* can't solve if imaginary, or if vertex of parabola is tangent. */
 7016
 7017        ru = sqrt(u);
 7018        x1 = (-R + ru) / (2.0 * Q);
 7019        x2 = (-R - ru) / (2.0 * Q);
 7020        if (-1.0 <= x1 && x1 <= +1.0)
 7021        {
 7022            if (-1.0 <= x2 && x2 <= +1.0)
 7023                return 0;   /* two roots are within bounds; we require a unique zero-crossing. */
 7024            x = x1;
 7025        }
 7026        else if (-1.0 <= x2 && x2 <= +1.0)
 7027            x = x2;
 7028        else
 7029            return 0;   /* neither root is within bounds */
 7030    }
 7031
 7032    *out_t = tm + x*dt;
 7033    *out_df_dt = (2*Q*x + R) / dt;
 7034    return 1;   /* success */
 7035}
 7036
 7037static astro_status_t FindSeasonChange(double targetLon, int year, int month, int day, astro_time_t *time)
 7038{
 7039    astro_time_t startTime;
 7040    astro_search_result_t result;
 7041
 7042    startTime = Astronomy_MakeTime(year, month, day, 0, 0, 0.0);
 7043    result = Astronomy_SearchSunLongitude(targetLon, startTime, 20.0);
 7044    *time = result.time;
 7045    return result.status;
 7046}
 7047
 7048/**
 7049 * @brief Finds both equinoxes and both solstices for a given calendar year.
 7050 *
 7051 * The changes of seasons are defined by solstices and equinoxes.
 7052 * Given a calendar year number, this function calculates the
 7053 * March and September equinoxes and the June and December solstices.
 7054 *
 7055 * The equinoxes are the moments twice each year when the plane of the
 7056 * Earth's equator passes through the center of the Sun. In other words,
 7057 * the Sun's declination is zero at both equinoxes.
 7058 * The March equinox defines the beginning of spring in the northern hemisphere
 7059 * and the beginning of autumn in the southern hemisphere.
 7060 * The September equinox defines the beginning of autumn in the northern hemisphere
 7061 * and the beginning of spring in the southern hemisphere.
 7062 *
 7063 * The solstices are the moments twice each year when one of the Earth's poles
 7064 * is most tilted toward the Sun. More precisely, the Sun's declination reaches
 7065 * its minimum value at the December solstice, which defines the beginning of
 7066 * winter in the northern hemisphere and the beginning of summer in the southern
 7067 * hemisphere. The Sun's declination reaches its maximum value at the June solstice,
 7068 * which defines the beginning of summer in the northern hemisphere and the beginning
 7069 * of winter in the southern hemisphere.
 7070 *
 7071 * @param year
 7072 *      The calendar year number for which to calculate equinoxes and solstices.
 7073 *      The value may be any integer, but only the years 1800 through 2100 have been
 7074 *      validated for accuracy: unit testing against data from the
 7075 *      United States Naval Observatory confirms that all equinoxes and solstices
 7076 *      for that range of years are within 2 minutes of the correct time.
 7077 *
 7078 * @return
 7079 *      The times of the four seasonal changes in the given calendar year.
 7080 *      This function should always succeed. However, to be safe, callers
 7081 *      should check the `status` field of the returned structure to make sure
 7082 *      it contains `ASTRO_SUCCESS`. Any failures indicate a bug in the algorithm
 7083 *      and should be [reported as an issue](https://github.com/cosinekitty/astronomy/issues).
 7084 */
 7085astro_seasons_t Astronomy_Seasons(int year)
 7086{
 7087    astro_seasons_t seasons;
 7088    astro_status_t  status;
 7089
 7090    seasons.status = ASTRO_SUCCESS;
 7091
 7092    /*
 7093        https://github.com/cosinekitty/astronomy/issues/187
 7094        Solstices and equinoxes drift over long spans of time,
 7095        due to precession of the Earth's axis.
 7096        Therefore, we have to search a wider range of time than
 7097        one might expect. It turns out this has very little
 7098        effect on efficiency, thanks to the quick convergence
 7099        of quadratic interpolation inside Astronomy_Search().
 7100    */
 7101
 7102    status = FindSeasonChange(  0, year,  3, 10, &seasons.mar_equinox);
 7103    if (status != ASTRO_SUCCESS) seasons.status = status;
 7104
 7105    status = FindSeasonChange( 90, year,  6, 10, &seasons.jun_solstice);
 7106    if (status != ASTRO_SUCCESS) seasons.status = status;
 7107
 7108    status = FindSeasonChange(180, year,  9, 10, &seasons.sep_equinox);
 7109    if (status != ASTRO_SUCCESS) seasons.status = status;
 7110
 7111    status = FindSeasonChange(270, year, 12, 10, &seasons.dec_solstice);
 7112    if (status != ASTRO_SUCCESS) seasons.status = status;
 7113
 7114    return seasons;
 7115}
 7116
 7117/**
 7118 * @brief   Returns the angle between the given body and the Sun, as seen from the Earth.
 7119 *
 7120 * This function calculates the angular separation between the given body and the Sun,
 7121 * as seen from the center of the Earth. This angle is helpful for determining how
 7122 * easy it is to see the body away from the glare of the Sun.
 7123 *
 7124 * @param body
 7125 *      The celestial body whose angle from the Sun is to be measured.
 7126 *      Not allowed to be `BODY_EARTH`.
 7127 *
 7128 * @param time
 7129 *      The time at which the observation is made.
 7130 *
 7131 * @return
 7132 *      If successful, the returned structure contains `ASTRO_SUCCESS` in the `status` field
 7133 *      and `angle` holds the angle in degrees between the Sun and the specified body as
 7134 *      seen from the center of the Earth.
 7135 *      If an error occurs, the `status` field contains a value other than `ASTRO_SUCCESS`
 7136 *      that indicates the error condition.
 7137 */
 7138astro_angle_result_t Astronomy_AngleFromSun(astro_body_t body, astro_time_t time)
 7139{
 7140    astro_vector_t sv, bv;
 7141
 7142    if (body == BODY_EARTH)
 7143        return AngleError(ASTRO_EARTH_NOT_ALLOWED);
 7144
 7145    sv = Astronomy_GeoVector(BODY_SUN, time, ABERRATION);
 7146    if (sv.status != ASTRO_SUCCESS)
 7147        return AngleError(sv.status);
 7148
 7149    bv = Astronomy_GeoVector(body, time, ABERRATION);
 7150    if (bv.status != ASTRO_SUCCESS)
 7151        return AngleError(bv.status);
 7152
 7153    return Astronomy_AngleBetween(sv, bv);
 7154}
 7155
 7156/**
 7157 * @brief
 7158 *      Determines visibility of a celestial body relative to the Sun, as seen from the Earth.
 7159 *
 7160 * This function returns an #astro_elongation_t structure, which provides the following
 7161 * information about the given celestial body at the given time:
 7162 *
 7163 * - `visibility` is an enumerated type that specifies whether the body is more easily seen
 7164 *    in the morning before sunrise, or in the evening after sunset.
 7165 *
 7166 * - `elongation` is the angle in degrees between two vectors: one from the center of the Earth to the
 7167 *    center of the Sun, the other from the center of the Earth to the center of the specified body.
 7168 *    This angle indicates how far away the body is from the glare of the Sun.
 7169 *    The elongation angle is always in the range [0, 180].
 7170 *
 7171 * - `ecliptic_separation` is the absolute value of the difference between the body's ecliptic longitude
 7172 *   and the Sun's ecliptic longitude, both as seen from the center of the Earth. This angle measures
 7173 *   around the plane of the Earth's orbit, and ignores how far above or below that plane the body is.
 7174 *   The ecliptic separation is measured in degrees and is always in the range [0, 180].
 7175 *
 7176 * @param body
 7177 *      The celestial body whose visibility is to be calculated.
 7178 *
 7179 * @param time
 7180 *      The date and time of the observation.
 7181 *
 7182 * @return
 7183 *      If successful, the `status` field in the returned structure contains `ASTRO_SUCCESS`
 7184 *      and all the other fields in the structure are valid. On failure, `status` contains
 7185 *      some other value as an error code and the other fields contain invalid values.
 7186 */
 7187astro_elongation_t Astronomy_Elongation(astro_body_t body, astro_time_t time)
 7188{
 7189    astro_elongation_t result;
 7190    astro_angle_result_t angres;
 7191
 7192    angres = Astronomy_PairLongitude(body, BODY_SUN, time);
 7193    if (angres.status != ASTRO_SUCCESS)
 7194        return ElongError(angres.status);
 7195
 7196    if (angres.angle > 180.0)
 7197    {
 7198        result.visibility = VISIBLE_MORNING;
 7199        result.ecliptic_separation = 360.0 - angres.angle;
 7200    }
 7201    else
 7202    {
 7203        result.visibility = VISIBLE_EVENING;
 7204        result.ecliptic_separation = angres.angle;
 7205    }
 7206
 7207    angres = Astronomy_AngleFromSun(body, time);
 7208    if (angres.status != ASTRO_SUCCESS)
 7209        return ElongError(angres.status);
 7210
 7211    result.elongation = angres.angle;
 7212    result.time = time;
 7213    result.status = ASTRO_SUCCESS;
 7214
 7215    return result;
 7216}
 7217
 7218static astro_func_result_t neg_elong_slope(void *context, astro_time_t time)
 7219{
 7220    static const double dt = 0.1;
 7221    astro_angle_result_t e1, e2;
 7222    astro_func_result_t result;
 7223    astro_body_t body = *((astro_body_t *)context);
 7224    astro_time_t t1 = Astronomy_AddDays(time, -dt/2.0);
 7225    astro_time_t t2 = Astronomy_AddDays(time, +dt/2.0);
 7226
 7227    e1 = Astronomy_AngleFromSun(body, t1);
 7228    if (e1.status != ASTRO_SUCCESS)
 7229        return FuncError(e1.status);
 7230
 7231    e2 = Astronomy_AngleFromSun(body, t2);
 7232    if (e2.status)
 7233        return FuncError(e2.status);
 7234
 7235    result.value = (e1.angle - e2.angle)/dt;
 7236    result.status = ASTRO_SUCCESS;
 7237    return result;
 7238}
 7239
 7240/**
 7241 * @brief
 7242 *      Finds a date and time when Mercury or Venus reaches its maximum angle from the Sun as seen from the Earth.
 7243 *
 7244 * Mercury and Venus are are often difficult to observe because they are closer to the Sun than the Earth is.
 7245 * Mercury especially is almost always impossible to see because it gets lost in the Sun's glare.
 7246 * The best opportunities for spotting Mercury, and the best opportunities for viewing Venus through
 7247 * a telescope without atmospheric interference, are when these planets reach maximum elongation.
 7248 * These are events where the planets reach the maximum angle from the Sun as seen from the Earth.
 7249 *
 7250 * This function solves for those times, reporting the next maximum elongation event's date and time,
 7251 * the elongation value itself, the relative longitude with the Sun, and whether the planet is best
 7252 * observed in the morning or evening. See #Astronomy_Elongation for more details about the returned structure.
 7253 *
 7254 * @param body
 7255 *      Either `BODY_MERCURY` or `BODY_VENUS`. Any other value will fail with the error `ASTRO_INVALID_BODY`.
 7256 *      To find the best viewing opportunites for planets farther from the Sun than the Earth is (Mars through Pluto)
 7257 *      use #Astronomy_SearchRelativeLongitude to find the next opposition event.
 7258 *
 7259 * @param startTime
 7260 *      The date and time at which to begin the search. The maximum elongation event found will always
 7261 *      be the first one that occurs after this date and time.
 7262 *
 7263 * @return
 7264 *      If successful, the `status` field of the returned structure will be `ASTRO_SUCCESS`
 7265 *      and the other structure fields will be valid. Otherwise, `status` will contain
 7266 *      some other value indicating an error.
 7267 */
 7268astro_elongation_t Astronomy_SearchMaxElongation(astro_body_t body, astro_time_t startTime)
 7269{
 7270    double s1, s2;
 7271    int iter;
 7272    astro_angle_result_t plon, elon;
 7273    astro_time_t t_start;
 7274    double rlon, rlon_lo, rlon_hi, adjust_days;
 7275    astro_func_result_t syn;
 7276    astro_search_result_t search1, search2, searchx;
 7277    astro_time_t t1, t2;
 7278    astro_func_result_t m1, m2;
 7279
 7280    /* Determine the range of relative longitudes within which maximum elongation can occur for this planet. */
 7281    switch (body)
 7282    {
 7283    case BODY_MERCURY:
 7284        s1 = 50.0;
 7285        s2 = 85.0;
 7286        break;
 7287
 7288    case BODY_VENUS:
 7289        s1 = 40.0;
 7290        s2 = 50.0;
 7291        break;
 7292
 7293    default:
 7294        /* SearchMaxElongation works for Mercury and Venus only. */
 7295        return ElongError(ASTRO_INVALID_BODY);
 7296    }
 7297
 7298    syn = SynodicPeriod(body);
 7299    if (syn.status != ASTRO_SUCCESS)
 7300        return ElongError(syn.status);
 7301
 7302    iter = 0;
 7303    while (++iter <= 2)
 7304    {
 7305        plon = Astronomy_EclipticLongitude(body, startTime);
 7306        if (plon.status != ASTRO_SUCCESS)
 7307            return ElongError(plon.status);
 7308
 7309        elon = Astronomy_EclipticLongitude(BODY_EARTH, startTime);
 7310        if (elon.status != ASTRO_SUCCESS)
 7311            return ElongError(elon.status);
 7312
 7313        rlon = LongitudeOffset(plon.angle - elon.angle);    /* clamp to (-180, +180] */
 7314
 7315        /* The slope function is not well-behaved when rlon is near 0 degrees or 180 degrees */
 7316        /* because there is a cusp there that causes a discontinuity in the derivative. */
 7317        /* So we need to guard against searching near such times. */
 7318        if (rlon >= -s1 && rlon < +s1)
 7319        {
 7320            /* Seek to the window [+s1, +s2]. */
 7321            adjust_days = 0.0;
 7322            /* Search forward for the time t1 when rel lon = +s1. */
 7323            rlon_lo = +s1;
 7324            /* Search forward for the time t2 when rel lon = +s2. */
 7325            rlon_hi = +s2;
 7326        }
 7327        else if (rlon > +s2 || rlon < -s2)
 7328        {
 7329            /* Seek to the next search window at [-s2, -s1]. */
 7330            adjust_days = 0.0;
 7331            /* Search forward for the time t1 when rel lon = -s2. */
 7332            rlon_lo = -s2;
 7333            /* Search forward for the time t2 when rel lon = -s1. */
 7334            rlon_hi = -s1;
 7335        }
 7336        else if (rlon >= 0.0)
 7337        {
 7338            /* rlon must be in the middle of the window [+s1, +s2]. */
 7339            /* Search BACKWARD for the time t1 when rel lon = +s1. */
 7340            adjust_days = -syn.value / 4.0;
 7341            rlon_lo = +s1;
 7342            rlon_hi = +s2;
 7343            /* Search forward from t1 to find t2 such that rel lon = +s2. */
 7344        }
 7345        else
 7346        {
 7347            /* rlon must be in the middle of the window [-s2, -s1]. */
 7348            /* Search BACKWARD for the time t1 when rel lon = -s2. */
 7349            adjust_days = -syn.value / 4.0;
 7350            rlon_lo = -s2;
 7351            /* Search forward from t1 to find t2 such that rel lon = -s1. */
 7352            rlon_hi = -s1;
 7353        }
 7354
 7355        t_start = Astronomy_AddDays(startTime, adjust_days);
 7356
 7357        search1 = Astronomy_SearchRelativeLongitude(body, rlon_lo, t_start);
 7358        if (search1.status != ASTRO_SUCCESS)
 7359            return ElongError(search1.status);
 7360        t1 = search1.time;
 7361
 7362        search2 = Astronomy_SearchRelativeLongitude(body, rlon_hi, t1);
 7363        if (search2.status != ASTRO_SUCCESS)
 7364            return ElongError(search2.status);
 7365        t2 = search2.time;
 7366
 7367        /* Now we have a time range [t1,t2] that brackets a maximum elongation event. */
 7368        /* Confirm the bracketing. */
 7369        m1 = neg_elong_slope(&body, t1);
 7370        if (m1.status != ASTRO_SUCCESS)
 7371            return ElongError(m1.status);
 7372
 7373        if (m1.value >= 0)
 7374            return ElongError(ASTRO_INTERNAL_ERROR);    /* there is a bug in the bracketing algorithm! */
 7375
 7376        m2 = neg_elong_slope(&body, t2);
 7377        if (m2.status != ASTRO_SUCCESS)
 7378            return ElongError(m2.status);
 7379
 7380        if (m2.value <= 0)
 7381            return ElongError(ASTRO_INTERNAL_ERROR);    /* there is a bug in the bracketing algorithm! */
 7382
 7383        /* Use the generic search algorithm to home in on where the slope crosses from negative to positive. */
 7384        searchx = Astronomy_Search(neg_elong_slope, &body, t1, t2, 10.0);
 7385        if (searchx.status != ASTRO_SUCCESS)
 7386            return ElongError(searchx.status);
 7387
 7388        if (searchx.time.tt >= startTime.tt)
 7389            return Astronomy_Elongation(body, searchx.time);
 7390
 7391        /* This event is in the past (earlier than startTime). */
 7392        /* We need to search forward from t2 to find the next possible window. */
 7393        /* We never need to search more than twice. */
 7394        startTime = Astronomy_AddDays(t2, 1.0);
 7395    }
 7396
 7397    return ElongError(ASTRO_SEARCH_FAILURE);
 7398}
 7399
 7400
 7401/**
 7402 * @brief Returns one body's ecliptic longitude with respect to another, as seen from the Earth.
 7403 *
 7404 * This function determines where one body appears around the ecliptic plane
 7405 * (the plane of the Earth's orbit around the Sun) as seen from the Earth,
 7406 * relative to the another body's apparent position.
 7407 * The function returns an angle in the half-open range [0, 360) degrees.
 7408 * The value is the ecliptic longitude of `body1` relative to the ecliptic
 7409 * longitude of `body2`.
 7410 *
 7411 * The angle is 0 when the two bodies are at the same ecliptic longitude
 7412 * as seen from the Earth. The angle increases in the prograde direction
 7413 * (the direction that the planets orbit the Sun and the Moon orbits the Earth).
 7414 *
 7415 * When the angle is 180 degrees, it means the two bodies appear on opposite sides
 7416 * of the sky for an Earthly observer.
 7417 *
 7418 * Neither `body1` nor `body2` is allowed to be `BODY_EARTH`.
 7419 * If this happens, the function fails with the error code `ASTRO_EARTH_NOT_ALLOWED`.
 7420 *
 7421 * @param body1
 7422 *      The first body, whose longitude is to be found relative to the second body.
 7423 *
 7424 * @param body2
 7425 *      The second body, relative to which the longitude of the first body is to be found.
 7426 *
 7427 * @param time
 7428 *      The date and time of the observation.
 7429 *
 7430 * @return
 7431 *      On success, the `status` field in the returned structure holds `ASTRO_SUCCESS` and
 7432 *      the `angle` field holds a value in the range [0, 360).
 7433 *      On failure, the `status` field contains some other value indicating an error condition.
 7434 */
 7435astro_angle_result_t Astronomy_PairLongitude(
 7436    astro_body_t body1,
 7437    astro_body_t body2,
 7438    astro_time_t time)
 7439{
 7440    astro_vector_t vector1, vector2;
 7441    astro_ecliptic_t eclip1, eclip2;
 7442    astro_angle_result_t result;
 7443
 7444    if (body1 == BODY_EARTH || body2 == BODY_EARTH)
 7445        return AngleError(ASTRO_EARTH_NOT_ALLOWED);
 7446
 7447    vector1 = Astronomy_GeoVector(body1, time, NO_ABERRATION);
 7448    eclip1 = Astronomy_Ecliptic(vector1);        /* checks for errors in vector1 */
 7449    if (eclip1.status != ASTRO_SUCCESS)
 7450        return AngleError(eclip1.status);
 7451
 7452    vector2 = Astronomy_GeoVector(body2, time, NO_ABERRATION);
 7453    eclip2 = Astronomy_Ecliptic(vector2);        /* checks for errors in vector2 */
 7454    if (eclip2.status != ASTRO_SUCCESS)
 7455        return AngleError(eclip2.status);
 7456
 7457    result.status = ASTRO_SUCCESS;
 7458    result.angle = NormalizeLongitude(eclip1.elon - eclip2.elon);
 7459    return result;
 7460}
 7461
 7462
 7463/**
 7464 * @brief
 7465 *      Returns the Moon's phase as an angle from 0 to 360 degrees.
 7466 *
 7467 * This function determines the phase of the Moon using its apparent
 7468 * ecliptic longitude relative to the Sun, as seen from the center of the Earth.
 7469 * Certain values of the angle have conventional definitions:
 7470 *
 7471 * - 0 = new moon
 7472 * - 90 = first quarter
 7473 * - 180 = full moon
 7474 * - 270 = third quarter
 7475 *
 7476 * @param time
 7477 *      The date and time of the observation.
 7478 *
 7479 * @return
 7480 *      On success, the function returns the angle as described in the function remarks
 7481 *      in the `angle` field and `ASTRO_SUCCESS` in the `status` field.
 7482 *      The function should always succeed, but it is a good idea for callers to check
 7483 *      the `status` field in the returned structure.
 7484 *      Any other value in `status` indicates a failure that should be
 7485 *      [reported as an issue](https://github.com/cosinekitty/astronomy/issues).
 7486 */
 7487astro_angle_result_t Astronomy_MoonPhase(astro_time_t time)
 7488{
 7489    return Astronomy_PairLongitude(BODY_MOON, BODY_SUN, time);
 7490}
 7491
 7492static astro_func_result_t moon_offset(void *context, astro_time_t time)
 7493{
 7494    astro_func_result_t result;
 7495    double targetLon = *((double *)context);
 7496    astro_angle_result_t angres = Astronomy_MoonPhase(time);
 7497    if (angres.status != ASTRO_SUCCESS)
 7498        return FuncError(angres.status);
 7499    result.value = LongitudeOffset(angres.angle - targetLon);
 7500    result.status = ASTRO_SUCCESS;
 7501    return result;
 7502}
 7503
 7504/**
 7505 * @brief
 7506 *      Searches for the time that the Moon reaches a specified phase.
 7507 *
 7508 * Lunar phases are conventionally defined in terms of the Moon's geocentric ecliptic
 7509 * longitude with respect to the Sun's geocentric ecliptic longitude.
 7510 * When the Moon and the Sun have the same longitude, that is defined as a new moon.
 7511 * When their longitudes are 180 degrees apart, that is defined as a full moon.
 7512 *
 7513 * This function searches for any value of the lunar phase expressed as an
 7514 * angle in degrees in the range [0, 360).
 7515 *
 7516 * If you want to iterate through lunar quarters (new moon, first quarter, full moon, third quarter)
 7517 * it is much easier to call the functions #Astronomy_SearchMoonQuarter and #Astronomy_NextMoonQuarter.
 7518 * This function is useful for finding general phase angles outside those four quarters.
 7519 *
 7520 * @param targetLon
 7521 *      The difference in geocentric longitude between the Sun and Moon
 7522 *      that specifies the lunar phase being sought. This can be any value
 7523 *      in the range [0, 360).  Certain values have conventional names:
 7524 *      0 = new moon, 90 = first quarter, 180 = full moon, 270 = third quarter.
 7525 *
 7526 * @param startTime
 7527 *      The beginning of the time window in which to search for the Moon reaching the specified phase.
 7528 *
 7529 * @param limitDays
 7530 *      The number of days away from `startTime` that limits the time window for the search.
 7531 *      If the value is negative, the search is performed into the past from `startTime`.
 7532 *      Otherwise, the search is performed into the future from `startTime`.
 7533 *
 7534 * @return
 7535 *      On success, the `status` field in the returned structure holds `ASTRO_SUCCESS` and
 7536 *      the `time` field holds the date and time when the Moon reaches the target longitude.
 7537 *      On failure, `status` holds some other value as an error code.
 7538 *      One possible error code is `ASTRO_NO_MOON_QUARTER` if `startTime` and `limitDays`
 7539 *      do not enclose the desired event. See remarks in #Astronomy_Search for other possible
 7540 *      error codes.
 7541 */
 7542astro_search_result_t Astronomy_SearchMoonPhase(double targetLon, astro_time_t startTime, double limitDays)
 7543{
 7544    /*
 7545        To avoid discontinuities in the moon_offset function causing problems,
 7546        we need to approximate when that function will next return 0.
 7547        We probe it with the start time and take advantage of the fact
 7548        that every lunar phase repeats roughly every 29.5 days.
 7549        There is a surprising uncertainty in the quarter timing,
 7550        due to the eccentricity of the moon's orbit.
 7551        I have seen more than 0.9 days away from the simple prediction.
 7552        To be safe, we take the predicted time of the event and search
 7553        +/-1.5 days around it (a 3-day wide window).
 7554        Return ASTRO_NO_MOON_QUARTER if the final result goes beyond limitDays after startTime.
 7555    */
 7556    const double uncertainty = 1.5;
 7557    astro_func_result_t funcres;
 7558    double ya, est_dt, dt1, dt2;
 7559    astro_time_t t1, t2;
 7560
 7561    funcres = moon_offset(&targetLon, startTime);
 7562    if (funcres.status != ASTRO_SUCCESS)
 7563        return SearchError(funcres.status);
 7564
 7565    ya = funcres.value;
 7566    if (limitDays < 0.0)
 7567    {
 7568        /* Search backward in time. */
 7569        if (ya < 0.0) ya += 360.0;
 7570        est_dt = -(MEAN_SYNODIC_MONTH * ya) / 360.0;
 7571        dt1 = est_dt - uncertainty;
 7572        dt2 = est_dt + uncertainty;
 7573        if (dt2 < limitDays)
 7574            return SearchError(ASTRO_NO_MOON_QUARTER);    /* not possible for moon phase to occur within specified window (too short) */
 7575        if (dt1 < limitDays)
 7576            dt1 = limitDays;
 7577    }
 7578    else
 7579    {
 7580        /* Search forward in time. */
 7581        if (ya > 0.0) ya -= 360.0;
 7582        est_dt = -(MEAN_SYNODIC_MONTH * ya) / 360.0;
 7583        dt1 = est_dt - uncertainty;
 7584        dt2 = est_dt + uncertainty;
 7585        if (dt1 > limitDays)
 7586            return SearchError(ASTRO_NO_MOON_QUARTER);    /* not possible for moon phase to occur within specified window (too short) */
 7587        if (dt2 > limitDays)
 7588            dt2 = limitDays;
 7589    }
 7590    t1 = Astronomy_AddDays(startTime, dt1);
 7591    t2 = Astronomy_AddDays(startTime, dt2);
 7592    return Astronomy_Search(moon_offset, &targetLon, t1, t2, 0.1);
 7593}
 7594
 7595/**
 7596 * @brief
 7597 *      Finds the first lunar quarter after the specified date and time.
 7598 *
 7599 * A lunar quarter is one of the following four lunar phase events:
 7600 * new moon, first quarter, full moon, third quarter.
 7601 * This function finds the lunar quarter that happens soonest
 7602 * after the specified date and time.
 7603 *
 7604 * To continue iterating through consecutive lunar quarters, call this function once,
 7605 * followed by calls to #Astronomy_NextMoonQuarter as many times as desired.
 7606 *
 7607 * @param startTime
 7608 *      The date and time at which to start the search.
 7609 *
 7610 * @return
 7611 *      This function should always succeed, indicated by the `status` field
 7612 *      in the returned structure holding `ASTRO_SUCCESS`. Any other value indicates
 7613 *      an internal error, which should be [reported as an issue](https://github.com/cosinekitty/astronomy/issues).
 7614 *      To be safe, calling code should always check the `status` field for errors.
 7615 */
 7616astro_moon_quarter_t Astronomy_SearchMoonQuarter(astro_time_t startTime)
 7617{
 7618    astro_moon_quarter_t mq;
 7619    astro_angle_result_t angres;
 7620    astro_search_result_t srchres;
 7621
 7622    /* Determine what the next quarter phase will be. */
 7623    angres = Astronomy_MoonPhase(startTime);
 7624    if (angres.status != ASTRO_SUCCESS)
 7625        return MoonQuarterError(angres.status);
 7626
 7627    mq.quarter = (1 + (int)floor(angres.angle / 90.0)) % 4;
 7628    srchres = Astronomy_SearchMoonPhase(90.0 * mq.quarter, startTime, 10.0);
 7629    if (srchres.status != ASTRO_SUCCESS)
 7630        return MoonQuarterError(srchres.status);
 7631
 7632    mq.status = ASTRO_SUCCESS;
 7633    mq.time = srchres.time;
 7634    return mq;
 7635}
 7636
 7637/**
 7638 * @brief
 7639 *      Continues searching for lunar quarters from a previous search.
 7640 *
 7641 * After calling #Astronomy_SearchMoonQuarter, this function can be called
 7642 * one or more times to continue finding consecutive lunar quarters.
 7643 * This function finds the next consecutive moon quarter event after the one passed in as the parameter `mq`.
 7644 *
 7645 * @param mq
 7646 *      A value returned by a prior call to #Astronomy_SearchMoonQuarter or #Astronomy_NextMoonQuarter.
 7647 *
 7648 * @return
 7649 *      If `mq` is valid, this function should always succeed, indicated by the `status` field
 7650 *      in the returned structure holding `ASTRO_SUCCESS`. Any other value indicates
 7651 *      an internal error, which (after confirming that `mq` is valid) should be
 7652 *      [reported as an issue](https://github.com/cosinekitty/astronomy/issues).
 7653 *      To be safe, calling code should always check the `status` field for errors.
 7654 */
 7655astro_moon_quarter_t Astronomy_NextMoonQuarter(astro_moon_quarter_t mq)
 7656{
 7657    astro_time_t time;
 7658    astro_moon_quarter_t next_mq;
 7659
 7660    if (mq.status != ASTRO_SUCCESS)
 7661        return MoonQuarterError(ASTRO_INVALID_PARAMETER);
 7662
 7663    /* Skip 6 days past the previous found moon quarter to find the next one. */
 7664    /* This is less than the minimum possible increment. */
 7665    /* So far I have seen the interval well contained by the range (6.5, 8.3) days. */
 7666
 7667    time = Astronomy_AddDays(mq.time, 6.0);
 7668    next_mq = Astronomy_SearchMoonQuarter(time);
 7669    if (next_mq.status == ASTRO_SUCCESS)
 7670    {
 7671        /* Verify that we found the expected moon quarter. */
 7672        if (next_mq.quarter != (1 + mq.quarter) % 4)
 7673            return MoonQuarterError(ASTRO_WRONG_MOON_QUARTER);  /* internal error! we found the wrong moon quarter */
 7674    }
 7675    return next_mq;
 7676}
 7677
 7678static astro_func_result_t rlon_offset(astro_body_t body, astro_time_t time, int direction, double targetRelLon)
 7679{
 7680    astro_func_result_t result;
 7681    astro_angle_result_t plon, elon;
 7682    double diff;
 7683
 7684    plon = Astronomy_EclipticLongitude(body, time);
 7685    if (plon.status != ASTRO_SUCCESS)
 7686        return FuncError(plon.status);
 7687
 7688    elon = Astronomy_EclipticLongitude(BODY_EARTH, time);
 7689    if (elon.status != ASTRO_SUCCESS)
 7690        return FuncError(elon.status);
 7691
 7692    diff = direction * (elon.angle - plon.angle);
 7693    result.value = LongitudeOffset(diff - targetRelLon);
 7694    result.status = ASTRO_SUCCESS;
 7695    return result;
 7696}
 7697
 7698/**
 7699 * @brief
 7700 *      Searches for the time when the Earth and another planet are separated by a specified angle
 7701 *      in ecliptic longitude, as seen from the Sun.
 7702 *
 7703 * A relative longitude is the angle between two bodies measured in the plane of the Earth's orbit
 7704 * (the ecliptic plane). The distance of the bodies above or below the ecliptic plane is ignored.
 7705 * If you imagine the shadow of the body cast onto the ecliptic plane, and the angle measured around
 7706 * that plane from one body to the other in the direction the planets orbit the Sun, you will get an
 7707 * angle somewhere between 0 and 360 degrees. This is the relative longitude.
 7708 *
 7709 * Given a planet other than the Earth in `body` and a time to start the search in `startTime`,
 7710 * this function searches for the next time that the relative longitude measured from the planet
 7711 * to the Earth is `targetRelLon`.
 7712 *
 7713 * Certain astronomical events are defined in terms of relative longitude between the Earth and another planet:
 7714 *
 7715 * - When the relative longitude is 0 degrees, it means both planets are in the same direction from the Sun.
 7716 *   For planets that orbit closer to the Sun (Mercury and Venus), this is known as *inferior conjunction*,
 7717 *   a time when the other planet becomes very difficult to see because of being lost in the Sun's glare.
 7718 *   (The only exception is in the rare event of a transit, when we see the silhouette of the planet passing
 7719 *   between the Earth and the Sun.)
 7720 *
 7721 * - When the relative longitude is 0 degrees and the other planet orbits farther from the Sun,
 7722 *   this is known as *opposition*.  Opposition is when the planet is closest to the Earth, and
 7723 *   also when it is visible for most of the night, so it is considered the best time to observe the planet.
 7724 *
 7725 * - When the relative longitude is 180 degrees, it means the other planet is on the opposite side of the Sun
 7726 *   from the Earth. This is called *superior conjunction*. Like inferior conjunction, the planet is
 7727 *   very difficult to see from the Earth. Superior conjunction is possible for any planet other than the Earth.
 7728 *
 7729 * @param body
 7730 *      A planet other than the Earth. If `body` is not a planet other than the Earth, an error occurs.
 7731 *
 7732 * @param targetRelLon
 7733 *      The desired relative longitude, expressed in degrees. Must be in the range [0, 360).
 7734 *
 7735 * @param startTime
 7736 *      The date and time at which to begin the search.
 7737 *
 7738 * @return
 7739 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
 7740 *      and `time` will hold the date and time of the relative longitude event.
 7741 *      Otherwise `status` will hold some other value that indicates an error condition.
 7742 */
 7743astro_search_result_t Astronomy_SearchRelativeLongitude(astro_body_t body, double targetRelLon, astro_time_t startTime)
 7744{
 7745    astro_search_result_t result;
 7746    astro_func_result_t syn;
 7747    astro_func_result_t error_angle;
 7748    double prev_angle;
 7749    astro_time_t time;
 7750    int iter, direction;
 7751
 7752    if (body == BODY_EARTH)
 7753        return SearchError(ASTRO_EARTH_NOT_ALLOWED);
 7754
 7755    if (body == BODY_MOON || body == BODY_SUN)
 7756        return SearchError(ASTRO_INVALID_BODY);
 7757
 7758    syn = SynodicPeriod(body);
 7759    if (syn.status != ASTRO_SUCCESS)
 7760        return SearchError(syn.status);
 7761
 7762    direction = IsSuperiorPlanet(body) ? +1 : -1;
 7763
 7764    /* Iterate until we converge on the desired event. */
 7765    /* Calculate the error angle, which will be a negative number of degrees, */
 7766    /* meaning we are "behind" the target relative longitude. */
 7767
 7768    error_angle = rlon_offset(body, startTime, direction, targetRelLon);
 7769    if (error_angle.status != ASTRO_SUCCESS)
 7770        return SearchError(error_angle.status);
 7771
 7772    if (error_angle.value > 0)
 7773        error_angle.value -= 360;    /* force searching forward in time */
 7774
 7775    time = startTime;
 7776    for (iter = 0; iter < 100; ++iter)
 7777    {
 7778        /* Estimate how many days in the future (positive) or past (negative) */
 7779        /* we have to go to get closer to the target relative longitude. */
 7780        double day_adjust = (-error_angle.value/360.0) * syn.value;
 7781        time = Astronomy_AddDays(time, day_adjust);
 7782        if (fabs(day_adjust) * SECONDS_PER_DAY < 1.0)
 7783        {
 7784            result.time = time;
 7785            result.status = ASTRO_SUCCESS;
 7786            return result;
 7787        }
 7788
 7789        prev_angle = error_angle.value;
 7790        error_angle = rlon_offset(body, time, direction, targetRelLon);
 7791        if (error_angle.status != ASTRO_SUCCESS)
 7792            return SearchError(error_angle.status);
 7793
 7794        if (fabs(prev_angle) < 30.0 && (prev_angle != error_angle.value))
 7795        {
 7796            /* Improve convergence for Mercury/Mars (eccentric orbits) */
 7797            /* by adjusting the synodic period to more closely match the */
 7798            /* variable speed of both planets in this part of their respective orbits. */
 7799            double ratio = prev_angle / (prev_angle - error_angle.value);
 7800            if (ratio > 0.5 && ratio < 2.0)
 7801                syn.value *= ratio;
 7802        }
 7803    }
 7804
 7805    return SearchError(ASTRO_NO_CONVERGE);
 7806}
 7807
 7808/**
 7809 * @brief Searches for the time when the center of a body reaches a specified hour angle as seen by an observer on the Earth.
 7810 *
 7811 * The *hour angle* of a celestial body indicates its position in the sky with respect
 7812 * to the Earth's rotation. The hour angle depends on the location of the observer on the Earth.
 7813 * The hour angle is 0 when the body's center reaches its highest angle above the horizon in a given day.
 7814 * The hour angle increases by 1 unit for every sidereal hour that passes after that point, up
 7815 * to 24 sidereal hours when it reaches the highest point again. So the hour angle indicates
 7816 * the number of hours that have passed since the most recent time that the body has culminated,
 7817 * or reached its highest point.
 7818 *
 7819 * This function searches for the next or previous time a celestial body reaches the given hour angle
 7820 * relative to the date and time specified by `startTime`.
 7821 * To find when a body culminates, pass 0 for `hourAngle`.
 7822 * To find when a body reaches its lowest point in the sky, pass 12 for `hourAngle`.
 7823 *
 7824 * Note that, especially close to the Earth's poles, a body as seen on a given day
 7825 * may always be above the horizon or always below the horizon, so the caller cannot
 7826 * assume that a culminating object is visible nor that an object is below the horizon
 7827 * at its minimum altitude.
 7828 *
 7829 * On success, the function reports the date and time, along with the horizontal coordinates
 7830 * of the body at that time, as seen by the given observer.
 7831 *
 7832 * @param body
 7833 *      The Sun, Moon, any planet other than the Earth,
 7834 *      or a user-defined star that was created by a call to #Astronomy_DefineStar.
 7835 *
 7836 * @param observer
 7837 *      Indicates a location on or near the surface of the Earth where the observer is located.
 7838 *      Call #Astronomy_MakeObserver to create an observer structure.
 7839 *
 7840 * @param hourAngle
 7841 *      An hour angle value in the range [0, 24) indicating the number of sidereal hours after the
 7842 *      body's most recent culmination.
 7843 *
 7844 * @param startTime
 7845 *      The date and time at which to start the search.
 7846 *
 7847 * @param direction
 7848 *      The direction in time to perform the search: a positive value
 7849 *      searches forward in time, a negative value searches backward in time.
 7850 *      The function will fail with `ASTRO_INVALID_PARAMETER` if `direction` is zero.
 7851 *
 7852 * @return
 7853 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`
 7854 *      and the other structure fields are valid. Otherwise, `status` holds some other value
 7855 *      that indicates an error condition.
 7856 */
 7857astro_hour_angle_t Astronomy_SearchHourAngleEx(
 7858    astro_body_t body,
 7859    astro_observer_t observer,
 7860    double hourAngle,
 7861    astro_time_t startTime,
 7862    int direction)
 7863{
 7864    int iter = 0;
 7865    astro_time_t time;
 7866    astro_equatorial_t ofdate;
 7867    astro_hour_angle_t result;
 7868    double delta_sidereal_hours, delta_days, gast;
 7869
 7870    if (body == BODY_EARTH)
 7871        return HourAngleError(ASTRO_EARTH_NOT_ALLOWED);
 7872
 7873    if (hourAngle < 0.0 || hourAngle >= 24.0)
 7874        return HourAngleError(ASTRO_INVALID_PARAMETER);
 7875
 7876    if (direction == 0)
 7877        return HourAngleError(ASTRO_INVALID_PARAMETER);
 7878
 7879    time = startTime;
 7880    for(;;)
 7881    {
 7882        ++iter;
 7883
 7884        /* Calculate Greenwich Apparent Sidereal Time (GAST) at the given time. */
 7885        gast = Astronomy_SiderealTime(&time);
 7886
 7887        /* Obtain equatorial coordinates of date for the body. */
 7888        ofdate = Astronomy_Equator(body, &time, observer, EQUATOR_OF_DATE, ABERRATION);
 7889        if (ofdate.status != ASTRO_SUCCESS)
 7890            return HourAngleError(ofdate.status);
 7891
 7892        /* Calculate the adjustment needed in sidereal time */
 7893        /* to bring the hour angle to the desired value. */
 7894
 7895        delta_sidereal_hours = fmod((hourAngle + ofdate.ra - observer.longitude/15) - gast, 24.0);
 7896        if (iter == 1)
 7897        {
 7898            /* On the first iteration, always search the requested time direction. */
 7899            if (direction > 0)
 7900            {
 7901                /* Search forward in time. */
 7902                if (delta_sidereal_hours < 0.0)
 7903                    delta_sidereal_hours += 24.0;
 7904            }
 7905            else
 7906            {
 7907                /* Search backward in time. */
 7908                if (delta_sidereal_hours > 0.0)
 7909                    delta_sidereal_hours -= 24.0;
 7910            }
 7911        }
 7912        else
 7913        {
 7914            /* On subsequent iterations, we make the smallest possible adjustment, */
 7915            /* either forward or backward in time. */
 7916            if (delta_sidereal_hours < -12.0)
 7917                delta_sidereal_hours += 24.0;
 7918            else if (delta_sidereal_hours > +12.0)
 7919                delta_sidereal_hours -= 24.0;
 7920        }
 7921
 7922        /* If the error is tolerable (less than 0.1 seconds), the search has succeeded. */
 7923        if (fabs(delta_sidereal_hours) * 3600.0 < 0.1)
 7924        {
 7925            result.hor = Astronomy_Horizon(&time, observer, ofdate.ra, ofdate.dec, REFRACTION_NORMAL);
 7926            result.time = time;
 7927            result.status = ASTRO_SUCCESS;
 7928            return result;
 7929        }
 7930
 7931        /* We need to loop another time to get more accuracy. */
 7932        /* Update the terrestrial time (in solar days) adjusting by sidereal time (sidereal hours). */
 7933        delta_days = (delta_sidereal_hours / 24.0) * SOLAR_DAYS_PER_SIDEREAL_DAY;
 7934        time = Astronomy_AddDays(time, delta_days);
 7935    }
 7936}
 7937
 7938
 7939/**
 7940 * @brief Finds the hour angle of a body for a given observer and time.
 7941 *
 7942 * The *hour angle* of a celestial body indicates its position in the sky with respect
 7943 * to the Earth's rotation. The hour angle depends on the location of the observer on the Earth.
 7944 * The hour angle is 0 when the body's center reaches its highest angle above the horizon in a given day.
 7945 * The hour angle increases by 1 unit for every sidereal hour that passes after that point, up
 7946 * to 24 sidereal hours when it reaches the highest point again. So the hour angle indicates
 7947 * the number of hours that have passed since the most recent time that the body has culminated,
 7948 * or reached its highest point.
 7949 *
 7950 * @param body
 7951 *      The body whose observed hour angle is to be found.
 7952 *
 7953 * @param time
 7954 *      The time of the observation.
 7955 *
 7956 * @param observer
 7957 *      The geographic location where the observation takes place.
 7958 *
 7959 * @return astro_func_result_t
 7960 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`
 7961 *      and `value` holds the hour angle in the half-open range [0, 24).
 7962 *      Otherwise, `status` is an error code that indicates failure.
 7963 */
 7964astro_func_result_t Astronomy_HourAngle(astro_body_t body, astro_time_t *time, astro_observer_t observer)
 7965{
 7966    astro_func_result_t result;
 7967    astro_equatorial_t ofdate;
 7968    double gast;
 7969
 7970    if (time == NULL)
 7971        return FuncError(ASTRO_INVALID_PARAMETER);
 7972
 7973    /* Calculate Greenwich Apparent Sidereal Time (GAST) at the given time. */
 7974    gast = Astronomy_SiderealTime(time);
 7975
 7976    /* Obtain equatorial coordinates of date for the body. */
 7977    ofdate = Astronomy_Equator(body, time, observer, EQUATOR_OF_DATE, ABERRATION);
 7978    if (ofdate.status != ASTRO_SUCCESS)
 7979        return FuncError(ofdate.status);
 7980
 7981    result.value = fmod(observer.longitude/15 + gast - ofdate.ra, 24.0);
 7982    if (result.value < 0.0)
 7983        result.value += 24.0;
 7984    result.status = ASTRO_SUCCESS;
 7985
 7986    return result;
 7987}
 7988
 7989
 7990/** @cond DOXYGEN_SKIP */
 7991
 7992typedef struct
 7993{
 7994    astro_body_t        body;
 7995    int                 direction;          // search option: +1 = rise, -1 = set
 7996    astro_observer_t    observer;
 7997    double              body_radius_au;
 7998    double              target_altitude;
 7999}
 8000context_altitude_t;
 8001
 8002static const double RISE_SET_DT = 0.42;    /* 10.08 hours: Nyquist-safe for 22-hour period. */
 8003
 8004typedef struct
 8005{
 8006    astro_status_t  status;
 8007    astro_time_t    tx;
 8008    astro_time_t    ty;
 8009    double          ax;
 8010    double          ay;
 8011}
 8012ascent_t;
 8013
 8014int _AltitudeDiffCallCount;
 8015int _FindAscentMaxRecursionDepth;
 8016
 8017/** @endcond */
 8018
 8019static astro_func_result_t altitude_diff(void *context, astro_time_t time)
 8020{
 8021    astro_func_result_t result;
 8022    astro_equatorial_t ofdate;
 8023    astro_horizon_t hor;
 8024    double altitude;
 8025    const context_altitude_t *p = (const context_altitude_t *)context;
 8026
 8027    ++_AltitudeDiffCallCount;   /* for internal performance testing */
 8028
 8029    ofdate = Astronomy_Equator(p->body, &time, p->observer, EQUATOR_OF_DATE, ABERRATION);
 8030    if (ofdate.status != ASTRO_SUCCESS)
 8031        return FuncError(ofdate.status);
 8032
 8033    hor = Astronomy_Horizon(&time, p->observer, ofdate.ra, ofdate.dec, REFRACTION_NONE);
 8034    altitude = hor.altitude + RAD2DEG*asin(p->body_radius_au / ofdate.dist);
 8035    result.value = p->direction*(altitude - p->target_altitude);
 8036    result.status = ASTRO_SUCCESS;
 8037    return result;
 8038}
 8039
 8040
 8041static ascent_t AscentError(astro_status_t status)
 8042{
 8043    ascent_t ascent;
 8044    ascent.ax = ascent.ay = NAN;
 8045    ascent.tx = ascent.ty = TimeError();
 8046    ascent.status = status;
 8047    return ascent;
 8048}
 8049
 8050
 8051static ascent_t FindAscent(
 8052    int depth,
 8053    context_altitude_t *context,
 8054    double max_deriv_alt,
 8055    astro_time_t t1,
 8056    astro_time_t t2,
 8057    double a1,
 8058    double a2)
 8059{
 8060    ascent_t ascent;
 8061    double da, dt, abs_a1, abs_a2;
 8062    astro_time_t tm;
 8063    astro_func_result_t alt;
 8064
 8065    /* For internal performance testing. */
 8066    if (depth > _FindAscentMaxRecursionDepth)
 8067        _FindAscentMaxRecursionDepth = depth;
 8068
 8069    /* See if we can find any time interval where the altitude-diff function */
 8070    /* rises from non-positive to positive. */
 8071    /* Return ASTRO_SUCCESS if we do, ASTRO_SEARCH_FAILURE if we don't, or some other status for error cases. */
 8072
 8073    if (a1 < 0.0 && a2 >= 0.0)
 8074    {
 8075        /* Trivial success case: the endpoints already rise through zero. */
 8076        ascent.status = ASTRO_SUCCESS;
 8077        ascent.tx = t1;
 8078        ascent.ty = t2;
 8079        ascent.ax = a1;
 8080        ascent.ay = a2;
 8081        return ascent;
 8082    }
 8083
 8084    if (a1 >= 0.0 && a2 < 0.0)
 8085    {
 8086        /* Trivial failure case: Assume Nyquist condition prevents an ascent. */
 8087        return AscentError(ASTRO_SEARCH_FAILURE);
 8088    }
 8089
 8090    if (depth > 17)
 8091    {
 8092        /*
 8093            Safety valve: do not allow unlimited recursion.
 8094            This should never happen if the rest of the logic is working correctly,
 8095            so fail the whole search if it does happen. It's a bug!
 8096        */
 8097        return AscentError(ASTRO_NO_CONVERGE);
 8098    }
 8099
 8100    /*
 8101        Both altitudes are on the same side of zero: both are negative, or both are non-negative.
 8102        There could be a convex "hill" or a concave "valley" that passes through zero.
 8103        In polar regions sometimes there is a rise/set or set/rise pair within minutes of each other.
 8104        For example, the Moon can be below the horizon, then the very top of it becomes
 8105        visible (moonrise) for a few minutes, then it moves sideways and down below
 8106        the horizon again (moonset). We want to catch these cases.
 8107        However, for efficiency and practicality concerns, because the rise/set search itself
 8108        has a 0.1 second threshold, we do not worry about rise/set pairs that are less than
 8109        one second apart. These are marginal cases that are rendered highly uncertain
 8110        anyway, due to unpredictable atmospheric refraction conditions (air temperature and pressure).
 8111    */
 8112    dt = (t2.ut - t1.ut) / 2;
 8113    if (dt * SECONDS_PER_DAY < 1.0)
 8114        return AscentError(ASTRO_SEARCH_FAILURE);
 8115
 8116    /* Is it possible to reach zero from the altitude that is closer to zero? */
 8117    abs_a1 = fabs(a1);
 8118    abs_a2 = fabs(a2);
 8119    da = (abs_a1 < abs_a2) ? abs_a1 : abs_a2;
 8120
 8121    /*
 8122        Without loss of generality, assume |a1| <= |a2|.
 8123        (Reverse the argument in the case |a2| < |a1|.)
 8124        Imagine you have to "drive" from a1 to 0, then back to a2.
 8125        You can't go faster than max_deriv_alt. If you can't reach 0 in half the time,
 8126        you certainly don't have time to reach 0, turn around, and still make your way
 8127        back up to a2 (which is at least as far from 0 than a1 is) in the time interval dt.
 8128        Therefore, the time threshold is half the time interval, or dt/2.
 8129    */
 8130    if (da > max_deriv_alt*(dt / 2))
 8131    {
 8132        /* Prune: the altitude cannot change fast enough to reach zero. */
 8133        return AscentError(ASTRO_SEARCH_FAILURE);
 8134    }
 8135
 8136    /* Bisect the time interval and evaluate the altitude at the midpoint. */
 8137    tm = Astronomy_TimeFromDays((t1.ut + t2.ut)/2);
 8138    alt = altitude_diff(context, tm);
 8139    if (alt.status != ASTRO_SUCCESS)
 8140        return AscentError(ASTRO_SEARCH_FAILURE);
 8141
 8142    /* Recurse to the left interval. */
 8143    ascent = FindAscent(1+depth, context, max_deriv_alt, t1, tm, a1, alt.value);
 8144    if (ascent.status == ASTRO_SEARCH_FAILURE)
 8145    {
 8146        /* Recurse to the right interval. */
 8147        ascent = FindAscent(1+depth, context, max_deriv_alt, tm, t2, alt.value, a2);
 8148    }
 8149
 8150    return ascent;
 8151}
 8152
 8153
 8154static astro_func_result_t MaxAltitudeSlope(astro_body_t body, double latitude)
 8155{
 8156    astro_func_result_t result;
 8157    double deriv_ra, deriv_dec, latrad;
 8158
 8159    if (!isfinite(latitude) || latitude < -90.0 || latitude > +90.0)
 8160    {
 8161        result.value = NAN;
 8162        result.status = ASTRO_INVALID_PARAMETER;
 8163        return result;
 8164    }
 8165
 8166    /*
 8167        Calculate the maximum possible rate that this body's altitude
 8168        could change [degrees/day] as seen by this observer.
 8169        First use experimentally determined extreme bounds for this body
 8170        of how much topocentric RA and DEC can ever change per rate of time.
 8171        We need minimum possible d(RA)/dt, and maximum possible magnitude of d(DEC)/dt.
 8172        Conservatively, we round d(RA)/dt down, d(DEC)/dt up.
 8173        Then calculate the resulting maximum possible altitude change rate.
 8174    */
 8175
 8176    switch (body)
 8177    {
 8178    case BODY_MOON:
 8179        deriv_ra  = +4.5;
 8180        deriv_dec = +8.2;
 8181        break;
 8182
 8183    case BODY_SUN:
 8184        deriv_ra  = +0.8;
 8185        deriv_dec = +0.5;
 8186        break;
 8187
 8188    case BODY_MERCURY:
 8189        deriv_ra  = -1.6;
 8190        deriv_dec = +1.0;
 8191        break;
 8192
 8193    case BODY_VENUS:
 8194        deriv_ra  = -0.8;
 8195        deriv_dec = +0.6;
 8196        break;
 8197
 8198    case BODY_MARS:
 8199        deriv_ra  = -0.5;
 8200        deriv_dec = +0.4;
 8201        break;
 8202
 8203    case BODY_JUPITER:
 8204    case BODY_SATURN:
 8205    case BODY_URANUS:
 8206    case BODY_NEPTUNE:
 8207    case BODY_PLUTO:
 8208        deriv_ra  = -0.2;
 8209        deriv_dec = +0.2;
 8210        break;
 8211
 8212    case BODY_EARTH:
 8213        result.value = NAN;
 8214        result.status = ASTRO_EARTH_NOT_ALLOWED;
 8215        return result;
 8216
 8217    default:
 8218        if (UserDefinedStar(body))
 8219        {
 8220            /*
 8221                The minimum allowed heliocentric distance of a user-defined star
 8222                is one light-year. This can cause a tiny amount of parallax (about 0.001 degrees).
 8223                Also, including stellar aberration (22 arcsec = 0.006 degrees), we provide a
 8224                generous safety buffer of 0.008 degrees.
 8225            */
 8226            deriv_ra  = -0.008;
 8227            deriv_dec = +0.008;
 8228            break;
 8229        }
 8230        result.value = NAN;
 8231        result.status = ASTRO_INVALID_BODY;
 8232        return result;
 8233    }
 8234
 8235    latrad = DEG2RAD * latitude;
 8236    result.value = fabs(((360.0 / SOLAR_DAYS_PER_SIDEREAL_DAY) - deriv_ra)*cos(latrad)) + fabs(deriv_dec*sin(latrad));
 8237    result.status = isfinite(result.value) ? ASTRO_SUCCESS : ASTRO_INTERNAL_ERROR;
 8238    return result;
 8239}
 8240
 8241
 8242static astro_search_result_t InternalSearchAltitude(
 8243    astro_body_t body,
 8244    astro_observer_t observer,
 8245    astro_direction_t direction,
 8246    astro_time_t startTime,
 8247    double limitDays,
 8248    double bodyRadiusAu,
 8249    double targetAltitude)
 8250{
 8251    astro_search_result_t search_result;
 8252    astro_func_result_t func_result;
 8253    context_altitude_t context;
 8254    ascent_t ascent;
 8255    astro_time_t t1, t2;
 8256    double a1, a2, max_deriv_alt;
 8257
 8258    if (!isfinite(targetAltitude) || targetAltitude < -90.0 || targetAltitude > +90.0)
 8259        return SearchError(ASTRO_INVALID_PARAMETER);
 8260
 8261    func_result = MaxAltitudeSlope(body, observer.latitude);
 8262    if (func_result.status != ASTRO_SUCCESS)
 8263        return SearchError(func_result.status);
 8264    max_deriv_alt = func_result.value;
 8265
 8266    context.body = body;
 8267    context.direction = (int)direction;
 8268    context.observer = observer;
 8269    context.body_radius_au = bodyRadiusAu;
 8270    context.target_altitude = targetAltitude;
 8271
 8272    /* We allow searching forward or backward in time. */
 8273    /* But we want to keep t1 < t2, so we need a few if/else statements. */
 8274    t1 = t2 = startTime;
 8275    func_result = altitude_diff(&context, t2);
 8276    if (func_result.status != ASTRO_SUCCESS)
 8277        return SearchError(func_result.status);
 8278    a1 = a2 = func_result.value;
 8279
 8280    for(;;)
 8281    {
 8282        if (limitDays < 0.0)
 8283        {
 8284            t1 = Astronomy_AddDays(t2, -RISE_SET_DT);
 8285            func_result = altitude_diff(&context, t1);
 8286            if (func_result.status != ASTRO_SUCCESS)
 8287                return SearchError(func_result.status);
 8288            a1 = func_result.value;
 8289        }
 8290        else
 8291        {
 8292            t2 = Astronomy_AddDays(t1, +RISE_SET_DT);
 8293            func_result = altitude_diff(&context, t2);
 8294            if (func_result.status != ASTRO_SUCCESS)
 8295                return SearchError(func_result.status);
 8296            a2 = func_result.value;
 8297        }
 8298
 8299        ascent = FindAscent(0, &context, max_deriv_alt, t1, t2, a1, a2);
 8300        if (ascent.status == ASTRO_SUCCESS)
 8301        {
 8302            /* We found a time interval [t1, t2] that contains an alt-diff */
 8303            /* rising from negative a1 to non-negative a2. */
 8304            /* Search for the time where the root occurs. */
 8305            search_result = Astronomy_Search(altitude_diff, &context, ascent.tx, ascent.ty, 0.1);
 8306            if (search_result.status == ASTRO_SUCCESS)
 8307            {
 8308                /* Now that we have a solution, we have to check whether it goes outside the time bounds. */
 8309                if (limitDays < 0.0)
 8310                {
 8311                    if (search_result.time.ut < startTime.ut + limitDays)
 8312                        return SearchError(ASTRO_SEARCH_FAILURE);
 8313                }
 8314                else
 8315                {
 8316                    if (search_result.time.ut > startTime.ut + limitDays)
 8317                        return SearchError(ASTRO_SEARCH_FAILURE);
 8318                }
 8319                return search_result;  /* success! */
 8320            }
 8321
 8322            /* The search should have succeeded. Something is wrong with FindAscent! */
 8323            return SearchError(ASTRO_INTERNAL_ERROR);
 8324        }
 8325        else if (ascent.status == ASTRO_SEARCH_FAILURE)
 8326        {
 8327            /* There is no ascent in this interval, so keep searching. */
 8328        }
 8329        else
 8330        {
 8331            /* An unexpected error occurred. Fail the search. */
 8332            return SearchError(ascent.status);
 8333        }
 8334
 8335        if (limitDays < 0.0)
 8336        {
 8337            if (t1.ut < startTime.ut + limitDays)
 8338                return SearchError(ASTRO_SEARCH_FAILURE);
 8339            t2 = t1;
 8340            a2 = a1;
 8341        }
 8342        else
 8343        {
 8344            if (t2.ut > startTime.ut + limitDays)
 8345                return SearchError(ASTRO_SEARCH_FAILURE);
 8346            t1 = t2;
 8347            a1 = a2;
 8348        }
 8349    }
 8350}
 8351
 8352
 8353/**
 8354 * @brief Calculates U.S. Standard Atmosphere (1976) variables as a function of elevation.
 8355 *
 8356 * This function calculates idealized values of pressure, temperature, and density
 8357 * using the U.S. Standard Atmosphere (1976) model.
 8358 *
 8359 * See:
 8360 * https://hbcp.chemnetbase.com/faces/documents/14_12/14_12_0001.xhtml
 8361 * https://ntrs.nasa.gov/api/citations/19770009539/downloads/19770009539.pdf
 8362 * https://www.ngdc.noaa.gov/stp/space-weather/online-publications/miscellaneous/us-standard-atmosphere-1976/us-standard-atmosphere_st76-1562_noaa.pdf
 8363 *
 8364 * @param elevationMeters
 8365 *      The elevation above sea level at which to calculate atmospheric variables.
 8366 *      The value must be at least -500 to +100000, or the function will
 8367 *      fail with status `ASTRO_INVALID_PARAMETER`.
 8368 *
 8369 * @return astro_atmosphere_tp0
 8370 */
 8371astro_atmosphere_t Astronomy_Atmosphere(double elevationMeters)
 8372{
 8373    astro_atmosphere_t atmos;
 8374    const double P0 = 101325.0;     /* pressure at sea level [pascals] */
 8375    const double T0 = 288.15;       /* temperature at sea level [kelvins] */
 8376    const double T1 = 216.65;       /* temperature between 20 km and 32 km [kelvins] */
 8377
 8378    /*
 8379        Formulas for air temperature and pressure at a height of `h` meters
 8380        were found at:
 8381        https://hbcp.chemnetbase.com/faces/documents/14_12/14_12_0001.xhtml
 8382
 8383        These in turn come from:
 8384        1. COESA, U.S. Standard Atmosphere, 1976, U.S. Government Printing Office, Washington, DC, 1976.
 8385        2. Jursa, A. S., Ed., Handbook of Geophysics and the Space Environment, Air Force Geophysics Laboratory, 1985.
 8386    */
 8387
 8388    if (!isfinite(elevationMeters) || elevationMeters < -500.0 || elevationMeters > 100000.0)
 8389    {
 8390        /* Invalid elevation. */
 8391        atmos.status = ASTRO_INVALID_PARAMETER;
 8392        atmos.pressure = atmos.temperature = atmos.density = NAN;
 8393    }
 8394    else
 8395    {
 8396        if (elevationMeters <= 11000.0)
 8397        {
 8398            atmos.temperature = T0 - 0.0065*elevationMeters;
 8399            atmos.pressure = P0 * pow(T0 / atmos.temperature, -5.25577);
 8400        }
 8401        else if (elevationMeters <= 20000.0)
 8402        {
 8403            atmos.temperature = T1;
 8404            atmos.pressure = 22632.0 * exp(-0.00015768832 * (elevationMeters - 11000.0));
 8405        }
 8406        else
 8407        {
 8408            atmos.temperature = T1 + 0.001*(elevationMeters - 20000.0);
 8409            atmos.pressure = 5474.87 * pow(T1 / atmos.temperature, 34.16319);
 8410        }
 8411        /* The density is calculated relative to the sea level value. */
 8412        /* Using the ideal gas law PV=nRT, we deduce that density is proportional to P/T. */
 8413        atmos.density = (atmos.pressure / atmos.temperature) / (P0 / T0);
 8414        atmos.status = ASTRO_SUCCESS;
 8415    }
 8416
 8417    return atmos;
 8418}
 8419
 8420
 8421static double HorizonDipAngle(
 8422    astro_observer_t observer,
 8423    double metersAboveGround)
 8424{
 8425    double phi, sinphi, cosphi, c, s, ht_km, ach, ash, radius_m;
 8426    double k, dip;
 8427
 8428    /* Calculate the effective radius of the Earth at ground level below the observer. */
 8429    /* Correct for the Earth's oblateness. */
 8430    phi = observer.latitude * DEG2RAD;
 8431    sinphi = sin(phi);
 8432    cosphi = cos(phi);
 8433    c = 1.0 / hypot(cosphi, sinphi*EARTH_FLATTENING);
 8434    s = c * (EARTH_FLATTENING * EARTH_FLATTENING);
 8435    ht_km = (observer.height - metersAboveGround) / 1000.0;     /* height of ground above sea level */
 8436    ach = EARTH_EQUATORIAL_RADIUS_KM*c + ht_km;
 8437    ash = EARTH_EQUATORIAL_RADIUS_KM*s + ht_km;
 8438    radius_m = 1000.0 * hypot(ach*cosphi, ash*sinphi);
 8439
 8440    /*
 8441        Correct refraction of a ray of light traveling tangent to the Earth's surface.
 8442        Based on: https://www.largeformatphotography.info/sunmooncalc/SMCalc.js
 8443        which in turn derives from:
 8444        Sweer, John. 1938.  The Path of a Ray of Light Tangent to the Surface of the Earth.
 8445        Journal of the Optical Society of America 28 (September):327-329.
 8446    */
 8447
 8448    /* k = refraction index */
 8449    k = 0.175 * pow(1.0 - (6.5e-3/283.15)*(observer.height - (2.0/3.0)*metersAboveGround), 3.256);
 8450
 8451    /* Calculate how far below the observer's horizontal plane the observed horizon dips. */
 8452    dip = RAD2DEG * -(sqrt(2*(1 - k)*metersAboveGround / radius_m) / (1 - k));
 8453    return dip;
 8454}
 8455
 8456
 8457
 8458/**
 8459 * @brief Searches for the next time a celestial body rises or sets as seen by an observer on the Earth.
 8460 *
 8461 * This function finds the next rise or set time of the Sun, Moon, or planet other than the Earth.
 8462 * Rise time is when the body first starts to be visible above the horizon.
 8463 * For example, sunrise is the moment that the top of the Sun first appears to peek above the horizon.
 8464 * Set time is the moment when the body appears to vanish below the horizon.
 8465 * Therefore, this function adjusts for the apparent angular radius of the observed body
 8466 * (significant only for the Sun and Moon).
 8467 *
 8468 * This function corrects for a typical value of atmospheric refraction, which causes celestial
 8469 * bodies to appear higher above the horizon than they would if the Earth had no atmosphere.
 8470 * Astronomy Engine uses a correction of 34 arcminutes. Real-world refraction varies based
 8471 * on air temperature, pressure, and humidity; such weather-based conditions are outside
 8472 * the scope of Astronomy Engine.
 8473 *
 8474 * Note that rise or set may not occur in every 24 hour period.
 8475 * For example, near the Earth's poles, there are long periods of time where
 8476 * the Sun stays below the horizon, never rising.
 8477 * Also, it is possible for the Moon to rise just before midnight but not set during the subsequent 24-hour day.
 8478 * This is because the Moon sets nearly an hour later each day due to orbiting the Earth a
 8479 * significant amount during each rotation of the Earth.
 8480 * Therefore callers must not assume that the function will always succeed.
 8481 *
 8482 * @param body
 8483 *      The Sun, Moon, any planet other than the Earth,
 8484 *      or a user-defined star that was created by a call to #Astronomy_DefineStar.
 8485 *
 8486 * @param observer
 8487 *      The location where observation takes place.
 8488 *      You can create an observer structure by calling #Astronomy_MakeObserver.
 8489 *
 8490 * @param direction
 8491 *      Either `DIRECTION_RISE` to find a rise time or `DIRECTION_SET` to find a set time.
 8492 *
 8493 * @param startTime
 8494 *      The date and time at which to start the search.
 8495 *
 8496 * @param limitDays
 8497 *      Limits how many days to search for a rise or set time, and defines
 8498 *      the direction in time to search. When `limitDays` is positive, the
 8499 *      search is performed into the future, after `startTime`.
 8500 *      When negative, the search is performed into the past, before `startTime`.
 8501 *      To limit a rise or set time to the same day, you can use a value of 1 day.
 8502 *      In cases where you want to find the next rise or set time no matter how far
 8503 *      in the future (for example, for an observer near the south pole), you can
 8504 *      pass in a larger value like 365.
 8505 *
 8506 * @param metersAboveGround
 8507 *      Usually the observer is located at ground level. Then this parameter
 8508 *      should be zero. But if the observer is significantly higher than ground
 8509 *      level, for example in an airplane, this parameter should be a positive
 8510 *      number indicating how far above the ground the observer is.
 8511 *      An error occurs if `metersAboveGround` is negative.
 8512 *
 8513 * @return
 8514 *      On success, the `status` field in the returned structure contains `ASTRO_SUCCESS`
 8515 *      and the `time` field contains the date and time of the rise or set time as requested.
 8516 *      If the `status` field contains `ASTRO_SEARCH_FAILURE`, it means the rise or set
 8517 *      event does not occur within `limitDays` days of `startTime`. This is a normal condition,
 8518 *      not an error. Any other value of `status` indicates an error of some kind.
 8519 */
 8520astro_search_result_t Astronomy_SearchRiseSetEx(
 8521    astro_body_t body,
 8522    astro_observer_t observer,
 8523    astro_direction_t direction,
 8524    astro_time_t startTime,
 8525    double limitDays,
 8526    double metersAboveGround)
 8527{
 8528    double altitude, dip;
 8529    double body_radius_au;
 8530    astro_atmosphere_t atmos;
 8531
 8532    if (!isfinite(metersAboveGround) || (metersAboveGround < 0.0))
 8533        return SearchError(ASTRO_INVALID_PARAMETER);
 8534
 8535    switch (body)
 8536    {
 8537    case BODY_SUN:  body_radius_au = SUN_RADIUS_AU;                 break;
 8538    case BODY_MOON: body_radius_au = MOON_EQUATORIAL_RADIUS_AU;     break;
 8539    default:        body_radius_au = 0.0;                           break;
 8540    }
 8541
 8542    /* Calculate atmospheric density at ground level. */
 8543    atmos = Astronomy_Atmosphere(observer.height - metersAboveGround);
 8544    if (atmos.status != ASTRO_SUCCESS)
 8545        return SearchError(atmos.status);
 8546
 8547    /* Calculate the apparent angular dip of the horizon. */
 8548    dip = HorizonDipAngle(observer, metersAboveGround);
 8549
 8550    /* Correct refraction for objects near the horizon, using atmospheric density at the ground. */
 8551    altitude = dip - (REFRACTION_NEAR_HORIZON * atmos.density);
 8552
 8553    /* Search for the top of the body crossing the corrected altitude angle. */
 8554    return InternalSearchAltitude(body, observer, direction, startTime, limitDays, body_radius_au, altitude);
 8555}
 8556
 8557
 8558/**
 8559 * @brief Finds the next time the center of a body passes through a given altitude.
 8560 *
 8561 * Finds when the center of the given body ascends or descends through a given
 8562 * altitude angle, as seen by an observer at the specified location on the Earth.
 8563 * By using the appropriate combination of `direction` and `altitude` parameters,
 8564 * this function can be used to find when civil, nautical, or astronomical twilight
 8565 * begins (dawn) or ends (dusk).
 8566 *
 8567 * Civil dawn begins before sunrise when the Sun ascends through 6 degrees below
 8568 * the horizon. To find civil dawn, pass `DIRECTION_RISE` for `direction` and -6 for `altitude`.
 8569 *
 8570 * Civil dusk ends after sunset when the Sun descends through 6 degrees below the horizon.
 8571 * To find civil dusk, pass `DIRECTION_SET` for `direction` and -6 for `altitude`.
 8572 *
 8573 * Nautical twilight is similar to civil twilight, only the `altitude` value should be -12 degrees.
 8574 *
 8575 * Astronomical twilight uses -18 degrees as the `altitude` value.
 8576 *
 8577 * By convention for twilight time calculations, the altitude is not corrected for
 8578 * atmospheric refraction. This is because the target altitudes are below the horizon,
 8579 * and refraction is not directly observable.
 8580 *
 8581 * `Astronomy_SearchAltitude` is not intended to find rise/set times of a body for two reasons:
 8582 * (1) Rise/set times of the Sun or Moon are defined by their topmost visible portion, not their centers.
 8583 * (2) Rise/set times are affected significantly by atmospheric refraction.
 8584 * Therefore, it is better to use #Astronomy_SearchRiseSetEx to find rise/set times, which
 8585 * corrects for both of these considerations.
 8586 *
 8587 * `Astronomy_SearchAltitude` will not work reliably for altitudes at or near the body's
 8588 * maximum or minimum altitudes. To find the time a body reaches minimum or maximum altitude
 8589 * angles, use #Astronomy_SearchHourAngleEx.
 8590 *
 8591 * @param body
 8592 *      The Sun, Moon, any planet other than the Earth,
 8593 *      or a user-defined star that was created by a call to #Astronomy_DefineStar.
 8594 *
 8595 * @param observer
 8596 *      The location where observation takes place.
 8597 *      You can create an observer structure by calling #Astronomy_MakeObserver.
 8598 *
 8599 * @param direction
 8600 *      Either `DIRECTION_RISE` to find when the body ascends through the altitude,
 8601 *      or `DIRECTION_SET` for when the body descends through the altitude.
 8602 *
 8603 * @param startTime
 8604 *      The date and time at which to start the search.
 8605 *
 8606 * @param limitDays
 8607 *      Limits how many days to search for the body reaching the altitude angle,
 8608 *      and defines the direction in time to search. When `limitDays` is positive, the
 8609 *      search is performed into the future, after `startTime`.
 8610 *      When negative, the search is performed into the past, before `startTime`.
 8611 *      To limit the search to the same day, you can use a value of 1 day.
 8612 *      In cases where you want to find the altitude event no matter how far
 8613 *      in the future (for example, for an observer near the south pole), you can
 8614 *      pass in a larger value like 365.
 8615 *
 8616 * @param altitude
 8617 *      The desired altitude angle of the body's center above (positive)
 8618 *      or below (negative) the observer's local horizon, expressed in degrees.
 8619 *      Must be in the range [-90, +90].
 8620 *
 8621 * @return
 8622 *      On success, the `status` field in the returned structure contains `ASTRO_SUCCESS`
 8623 *      and the `time` field contains the date and time of the requested altitude event.
 8624 *      If the `status` field contains `ASTRO_SEARCH_FAILURE`, it means the altitude
 8625 *      event does not occur within `limitDays` days of `startTime`. This is a normal condition,
 8626 *      not an error. Any other value of `status` indicates an error of some kind.
 8627 */
 8628astro_search_result_t Astronomy_SearchAltitude(
 8629    astro_body_t body,
 8630    astro_observer_t observer,
 8631    astro_direction_t direction,
 8632    astro_time_t startTime,
 8633    double limitDays,
 8634    double altitude)
 8635{
 8636    return InternalSearchAltitude(body, observer, direction, startTime, limitDays, 0.0, altitude);
 8637}
 8638
 8639
 8640static double MoonMagnitude(double phase, double helio_dist, double geo_dist)
 8641{
 8642    /* https://astronomy.stackexchange.com/questions/10246/is-there-a-simple-analytical-formula-for-the-lunar-phase-brightness-curve */
 8643    double rad = phase * DEG2RAD;
 8644    double rad2 = rad * rad;
 8645    double rad4 = rad2 * rad2;
 8646    double mag = -12.717 + 1.49*fabs(rad) + 0.0431*rad4;
 8647    double moon_mean_distance_au = 385000.6 / KM_PER_AU;
 8648    double geo_au = geo_dist / moon_mean_distance_au;
 8649    mag += 5*log10(helio_dist * geo_au);
 8650    return mag;
 8651}
 8652
 8653static astro_status_t SaturnMagnitude(
 8654    double phase,
 8655    double helio_dist,
 8656    double geo_dist,
 8657    astro_vector_t gc,
 8658    astro_time_t time,
 8659    double *mag,
 8660    double *ring_tilt)
 8661{
 8662    astro_ecliptic_t eclip;
 8663    double ir, Nr, lat, lon, tilt, sin_tilt;
 8664
 8665    *mag = *ring_tilt = NAN;
 8666
 8667    /* Based on formulas by Paul Schlyter found here: */
 8668    /* http://www.stjarnhimlen.se/comp/ppcomp.html#15 */
 8669
 8670    /* We must handle Saturn's rings as a major component of its visual magnitude. */
 8671    /* Find geocentric ecliptic coordinates of Saturn. */
 8672    eclip = Astronomy_Ecliptic(gc);
 8673    if (eclip.status != ASTRO_SUCCESS)
 8674        return eclip.status;
 8675
 8676    ir = DEG2RAD * 28.06;   /* tilt of Saturn's rings to the ecliptic, in radians */
 8677    Nr = DEG2RAD * (169.51 + (3.82e-5 * time.tt));    /* ascending node of Saturn's rings, in radians */
 8678
 8679    /* Find tilt of Saturn's rings, as seen from Earth. */
 8680    lat = DEG2RAD * eclip.elat;
 8681    lon = DEG2RAD * eclip.elon;
 8682    tilt = asin(sin(lat)*cos(ir) - cos(lat)*sin(ir)*sin(lon-Nr));
 8683    sin_tilt = sin(fabs(tilt));
 8684
 8685    *mag = -9.0 + 0.044*phase;
 8686    *mag += sin_tilt*(-2.6 + 1.2*sin_tilt);
 8687    *mag += 5.0 * log10(helio_dist * geo_dist);
 8688
 8689    *ring_tilt = RAD2DEG * tilt;
 8690
 8691    return ASTRO_SUCCESS;
 8692}
 8693
 8694static astro_status_t VisualMagnitude(
 8695    astro_body_t body,
 8696    double phase,
 8697    double helio_dist,
 8698    double geo_dist,
 8699    double *mag)
 8700{
 8701    /* For Mercury and Venus, see:  https://iopscience.iop.org/article/10.1086/430212 */
 8702    double c0, c1=0, c2=0, c3=0, x;
 8703    *mag = NAN;
 8704    switch (body)
 8705    {
 8706    case BODY_MERCURY:  c0 = -0.60, c1 = +4.98, c2 = -4.88, c3 = +3.02; break;
 8707    case BODY_VENUS:
 8708        if (phase < 163.6)
 8709            c0 = -4.47, c1 = +1.03, c2 = +0.57, c3 = +0.13;
 8710        else
 8711            c0 = 0.98, c1 = -1.02;
 8712        break;
 8713    case BODY_MARS:        c0 = -1.52, c1 = +1.60;   break;
 8714    case BODY_JUPITER:     c0 = -9.40, c1 = +0.50;   break;
 8715    case BODY_URANUS:      c0 = -7.19, c1 = +0.25;   break;
 8716    case BODY_NEPTUNE:     c0 = -6.87;               break;
 8717    case BODY_PLUTO:       c0 = -1.00, c1 = +4.00;   break;
 8718    default: return ASTRO_INVALID_BODY;
 8719    }
 8720
 8721    x = phase / 100;
 8722    *mag = c0 + x*(c1 + x*(c2 + x*c3));
 8723    *mag += 5.0 * log10(helio_dist * geo_dist);
 8724    return ASTRO_SUCCESS;
 8725}
 8726
 8727/**
 8728 * @brief
 8729 *      Finds visual magnitude, phase angle, and other illumination information about a celestial body.
 8730 *
 8731 * This function calculates information about how bright a celestial body appears from the Earth,
 8732 * reported as visual magnitude, which is a smaller (or even negative) number for brighter objects
 8733 * and a larger number for dimmer objects.
 8734 *
 8735 * For bodies other than the Sun, it reports a phase angle, which is the angle in degrees between
 8736 * the Sun and the Earth, as seen from the center of the body. Phase angle indicates what fraction
 8737 * of the body appears illuminated as seen from the Earth. For example, when the phase angle is
 8738 * near zero, it means the body appears "full" as seen from the Earth.  A phase angle approaching
 8739 * 180 degrees means the body appears as a thin crescent as seen from the Earth.  A phase angle
 8740 * of 90 degrees means the body appears "half full".
 8741 * For the Sun, the phase angle is always reported as 0; the Sun emits light rather than reflecting it,
 8742 * so it doesn't have a phase angle.
 8743 *
 8744 * When the body is Saturn, the returned structure contains a field `ring_tilt` that holds
 8745 * the tilt angle in degrees of Saturn's rings as seen from the Earth. A value of 0 means
 8746 * the rings appear edge-on, and are thus nearly invisible from the Earth. The `ring_tilt` holds
 8747 * 0 for all bodies other than Saturn.
 8748 *
 8749 * @param body
 8750 *      The Sun, Moon, or any planet other than the Earth.
 8751 *
 8752 * @param time
 8753 *      The date and time of the observation.
 8754 *
 8755 * @return
 8756 *      On success, the `status` field of the return structure holds `ASTRO_SUCCESS`
 8757 *      and the other structure fields are valid.
 8758 *      Any other value indicates an error, in which case the remaining structure fields are not valid.
 8759 */
 8760astro_illum_t Astronomy_Illumination(astro_body_t body, astro_time_t time)
 8761{
 8762    astro_vector_t earth;   /* vector from Sun to Earth */
 8763    astro_vector_t hc;      /* vector from Sun to body */
 8764    astro_vector_t gc;      /* vector from Earth to body */
 8765    double mag;             /* visual magnitude */
 8766    astro_angle_result_t phase;     /* phase angle in degrees between Earth and Sun as seen from body */
 8767    double helio_dist;      /* distance from Sun to body */
 8768    double geo_dist;        /* distance from Earth to body */
 8769    double ring_tilt = 0.0; /* Saturn's ring tilt (0 for all other bodies) */
 8770    astro_illum_t illum;
 8771    astro_status_t status;
 8772
 8773    if (body == BODY_EARTH)
 8774        return IllumError(ASTRO_EARTH_NOT_ALLOWED);
 8775
 8776    earth = CalcEarth(time);
 8777    if (earth.status != ASTRO_SUCCESS)
 8778        return IllumError(earth.status);
 8779
 8780    if (body == BODY_SUN)
 8781    {
 8782        gc.status = ASTRO_SUCCESS;
 8783        gc.t = time;
 8784        gc.x = -earth.x;
 8785        gc.y = -earth.y;
 8786        gc.z = -earth.z;
 8787
 8788        hc.status = ASTRO_SUCCESS;
 8789        hc.t = time;
 8790        hc.x = 0.0;
 8791        hc.y = 0.0;
 8792        hc.z = 0.0;
 8793
 8794        /* The Sun emits light instead of reflecting it, */
 8795        /* so we report a placeholder phase angle of 0. */
 8796        phase.status = ASTRO_SUCCESS;
 8797        phase.angle = 0.0;
 8798    }
 8799    else
 8800    {
 8801        if (body == BODY_MOON)
 8802        {
 8803            /* For extra numeric precision, use geocentric Moon formula directly. */
 8804            gc = Astronomy_GeoMoon(time);
 8805            if (gc.status != ASTRO_SUCCESS)
 8806                return IllumError(gc.status);
 8807
 8808            hc.status = ASTRO_SUCCESS;
 8809            hc.t = time;
 8810            hc.x = earth.x + gc.x;
 8811            hc.y = earth.y + gc.y;
 8812            hc.z = earth.z + gc.z;
 8813        }
 8814        else
 8815        {
 8816            /* For planets, the heliocentric vector is more direct to calculate. */
 8817            hc = Astronomy_HelioVector(body, time);
 8818            if (hc.status != ASTRO_SUCCESS)
 8819                return IllumError(hc.status);
 8820
 8821            gc.status = ASTRO_SUCCESS;
 8822            gc.t = time;
 8823            gc.x = hc.x - earth.x;
 8824            gc.y = hc.y - earth.y;
 8825            gc.z = hc.z - earth.z;
 8826        }
 8827
 8828        phase = Astronomy_AngleBetween(gc, hc);
 8829        if (phase.status != ASTRO_SUCCESS)
 8830            return IllumError(phase.status);
 8831    }
 8832
 8833    geo_dist = Astronomy_VectorLength(gc);
 8834    helio_dist = Astronomy_VectorLength(hc);
 8835
 8836    switch (body)
 8837    {
 8838    case BODY_SUN:
 8839        mag = -0.17 + 5.0*log10(geo_dist / AU_PER_PARSEC);
 8840        break;
 8841
 8842    case BODY_MOON:
 8843        mag = MoonMagnitude(phase.angle, helio_dist, geo_dist);
 8844        break;
 8845
 8846    case BODY_SATURN:
 8847        status = SaturnMagnitude(phase.angle, helio_dist, geo_dist, gc, time, &mag, &ring_tilt);
 8848        if (status != ASTRO_SUCCESS)
 8849            return IllumError(status);
 8850        break;
 8851
 8852    default:
 8853        status = VisualMagnitude(body, phase.angle, helio_dist, geo_dist, &mag);
 8854        if (status != ASTRO_SUCCESS)
 8855            return IllumError(status);
 8856        break;
 8857    }
 8858
 8859    illum.status = ASTRO_SUCCESS;
 8860    illum.time = time;
 8861    illum.mag = mag;
 8862    illum.phase_angle = phase.angle;
 8863    illum.phase_fraction = (1.0 + cos(DEG2RAD * phase.angle)) / 2.0;
 8864    illum.helio_dist = helio_dist;
 8865    illum.ring_tilt = ring_tilt;
 8866
 8867    return illum;
 8868}
 8869
 8870static astro_func_result_t mag_slope(void *context, astro_time_t time)
 8871{
 8872    /*
 8873        The Search() function finds a transition from negative to positive values.
 8874        The derivative of magnitude y with respect to time t (dy/dt)
 8875        is negative as an object gets brighter, because the magnitude numbers
 8876        get smaller. At peak magnitude dy/dt = 0, then as the object gets dimmer,
 8877        dy/dt > 0.
 8878    */
 8879    static const double dt = 0.01;
 8880    astro_illum_t y1, y2;
 8881    astro_body_t body = *((astro_body_t *)context);
 8882    astro_time_t t1 = Astronomy_AddDays(time, -dt/2);
 8883    astro_time_t t2 = Astronomy_AddDays(time, +dt/2);
 8884    astro_func_result_t result;
 8885
 8886    y1 = Astronomy_Illumination(body, t1);
 8887    if (y1.status != ASTRO_SUCCESS)
 8888        return FuncError(y1.status);
 8889
 8890    y2 = Astronomy_Illumination(body, t2);
 8891    if (y2.status != ASTRO_SUCCESS)
 8892        return FuncError(y2.status);
 8893
 8894    result.value = (y2.mag - y1.mag) / dt;
 8895    result.status = ASTRO_SUCCESS;
 8896    return result;
 8897}
 8898
 8899/**
 8900 * @brief
 8901 *      Searches for the date and time Venus will next appear brightest as seen from the Earth.
 8902 *
 8903 * This function searches for the date and time Venus appears brightest as seen from the Earth.
 8904 * Currently only Venus is supported for the `body` parameter, though this could change in the future.
 8905 * Mercury's peak magnitude occurs at superior conjunction, when it is virtually impossible to see from the Earth,
 8906 * so peak magnitude events have little practical value for that planet.
 8907 * Planets other than Venus and Mercury reach peak magnitude at opposition, which can
 8908 * be found using #Astronomy_SearchRelativeLongitude.
 8909 * The Moon reaches peak magnitude at full moon, which can be found using
 8910 * #Astronomy_SearchMoonQuarter or #Astronomy_SearchMoonPhase.
 8911 * The Sun reaches peak magnitude at perihelion, which occurs each year in January.
 8912 * However, the difference is minor and has little practical value.
 8913 *
 8914 * @param body
 8915 *      Currently only `BODY_VENUS` is allowed. Any other value results in the error `ASTRO_INVALID_BODY`.
 8916 *      See function remarks for more details.
 8917 *
 8918 * @param startTime
 8919 *      The date and time to start searching for the next peak magnitude event.
 8920 *
 8921 * @return
 8922 *      See documentation about the return value from #Astronomy_Illumination.
 8923 */
 8924astro_illum_t Astronomy_SearchPeakMagnitude(astro_body_t body, astro_time_t startTime)
 8925{
 8926    /* s1 and s2 are relative longitudes within which peak magnitude of Venus can occur. */
 8927    static const double s1 = 10.0;
 8928    static const double s2 = 30.0;
 8929    int iter;
 8930    astro_angle_result_t plon, elon;
 8931    astro_search_result_t t1, t2, tx;
 8932    astro_func_result_t syn, m1, m2;
 8933    astro_time_t t_start;
 8934    double rlon, rlon_lo, rlon_hi, adjust_days;
 8935
 8936    if (body != BODY_VENUS)
 8937        return IllumError(ASTRO_INVALID_BODY);
 8938
 8939    iter = 0;
 8940    while (++iter <= 2)
 8941    {
 8942        /* Find current heliocentric relative longitude between the */
 8943        /* inferior planet and the Earth. */
 8944        plon = Astronomy_EclipticLongitude(body, startTime);
 8945        if (plon.status != ASTRO_SUCCESS)
 8946            return IllumError(plon.status);
 8947
 8948        elon = Astronomy_EclipticLongitude(BODY_EARTH, startTime);
 8949        if (elon.status != ASTRO_SUCCESS)
 8950            return IllumError(elon.status);
 8951
 8952        rlon = LongitudeOffset(plon.angle - elon.angle);    /* clamp to (-180, +180]. */
 8953
 8954        /* The slope function is not well-behaved when rlon is near 0 degrees or 180 degrees */
 8955        /* because there is a cusp there that causes a discontinuity in the derivative. */
 8956        /* So we need to guard against searching near such times. */
 8957
 8958        if (rlon >= -s1 && rlon < +s1)
 8959        {
 8960            /* Seek to the window [+s1, +s2]. */
 8961            adjust_days = 0.0;
 8962            /* Search forward for the time t1 when rel lon = +s1. */
 8963            rlon_lo = +s1;
 8964            /* Search forward for the time t2 when rel lon = +s2. */
 8965            rlon_hi = +s2;
 8966        }
 8967        else if (rlon >= +s2 || rlon < -s2)
 8968        {
 8969            /* Seek to the next search window at [-s2, -s1]. */
 8970            adjust_days = 0.0;
 8971            /* Search forward for the time t1 when rel lon = -s2. */
 8972            rlon_lo = -s2;
 8973            /* Search forward for the time t2 when rel lon = -s1. */
 8974            rlon_hi = -s1;
 8975        }
 8976        else if (rlon >= 0)
 8977        {
 8978            /* rlon must be in the middle of the window [+s1, +s2]. */
 8979            /* Search BACKWARD for the time t1 when rel lon = +s1. */
 8980            syn = SynodicPeriod(body);
 8981            if (syn.status != ASTRO_SUCCESS)
 8982                return IllumError(syn.status);
 8983            adjust_days = -syn.value / 4;
 8984            rlon_lo = +s1;
 8985            /* Search forward from t1 to find t2 such that rel lon = +s2. */
 8986            rlon_hi = +s2;
 8987        }
 8988        else
 8989        {
 8990            /* rlon must be in the middle of the window [-s2, -s1]. */
 8991            /* Search BACKWARD for the time t1 when rel lon = -s2. */
 8992            syn = SynodicPeriod(body);
 8993            if (syn.status != ASTRO_SUCCESS)
 8994                return IllumError(syn.status);
 8995            adjust_days = -syn.value / 4;
 8996            rlon_lo = -s2;
 8997            /* Search forward from t1 to find t2 such that rel lon = -s1. */
 8998            rlon_hi = -s1;
 8999        }
 9000        t_start = Astronomy_AddDays(startTime, adjust_days);
 9001        t1 = Astronomy_SearchRelativeLongitude(body, rlon_lo, t_start);
 9002        if (t1.status != ASTRO_SUCCESS)
 9003            return IllumError(t1.status);
 9004        t2 = Astronomy_SearchRelativeLongitude(body, rlon_hi, t1.time);
 9005        if (t2.status != ASTRO_SUCCESS)
 9006            return IllumError(t2.status);
 9007
 9008        /* Now we have a time range [t1,t2] that brackets a maximum magnitude event. */
 9009        /* Confirm the bracketing. */
 9010        m1 = mag_slope(&body, t1.time);
 9011        if (m1.status != ASTRO_SUCCESS)
 9012            return IllumError(m1.status);
 9013        if (m1.value >= 0.0)
 9014            return IllumError(ASTRO_INTERNAL_ERROR);    /* should never happen! */
 9015
 9016        m2 = mag_slope(&body, t2.time);
 9017        if (m2.status != ASTRO_SUCCESS)
 9018            return IllumError(m2.status);
 9019        if (m2.value <= 0.0)
 9020            return IllumError(ASTRO_INTERNAL_ERROR);    /* should never happen! */
 9021
 9022        /* Use the generic search algorithm to home in on where the slope crosses from negative to positive. */
 9023        tx = Astronomy_Search(mag_slope, &body, t1.time, t2.time, 10.0);
 9024        if (tx.status != ASTRO_SUCCESS)
 9025            return IllumError(tx.status);
 9026
 9027        if (tx.time.tt >= startTime.tt)
 9028            return Astronomy_Illumination(body, tx.time);
 9029
 9030        /* This event is in the past (earlier than startTime). */
 9031        /* We need to search forward from t2 to find the next possible window. */
 9032        /* We never need to search more than twice. */
 9033        startTime = Astronomy_AddDays(t2.time, 1.0);
 9034    }
 9035
 9036    return IllumError(ASTRO_SEARCH_FAILURE);
 9037}
 9038
 9039static double MoonDistance(astro_time_t t)
 9040{
 9041    double lon, lat, dist;
 9042    CalcMoon(t.tt / 36525.0, &lon, &lat, &dist);
 9043    return dist;
 9044}
 9045
 9046static astro_func_result_t moon_distance_slope(void *context, astro_time_t time)
 9047{
 9048    static const double dt = 0.001;
 9049    astro_time_t t1 = Astronomy_AddDays(time, -dt/2.0);
 9050    astro_time_t t2 = Astronomy_AddDays(time, +dt/2.0);
 9051    double dist1, dist2;
 9052    int direction = *((int *)context);
 9053    astro_func_result_t result;
 9054
 9055    dist1 = MoonDistance(t1);
 9056    dist2 = MoonDistance(t2);
 9057    result.value = direction * (dist2 - dist1) / dt;
 9058    result.status = ASTRO_SUCCESS;
 9059    return result;
 9060}
 9061
 9062/**
 9063 * @brief
 9064 *      Finds the date and time of the Moon's closest distance (perigee)
 9065 *      or farthest distance (apogee) with respect to the Earth.
 9066 *
 9067 * Given a date and time to start the search in `startTime`, this function finds the
 9068 * next date and time that the center of the Moon reaches the closest or farthest point
 9069 * in its orbit with respect to the center of the Earth, whichever comes first
 9070 * after `startTime`.
 9071 *
 9072 * The closest point is called *perigee* and the farthest point is called *apogee*.
 9073 * The word *apsis* refers to either event.
 9074 *
 9075 * To iterate through consecutive alternating perigee and apogee events, call `Astronomy_SearchLunarApsis`
 9076 * once, then use the return value to call #Astronomy_NextLunarApsis. After that,
 9077 * keep feeding the previous return value from `Astronomy_NextLunarApsis` into another
 9078 * call of `Astronomy_NextLunarApsis` as many times as desired.
 9079 *
 9080 * @param startTime
 9081 *      The date and time at which to start searching for the next perigee or apogee.
 9082 *
 9083 * @return
 9084 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`,
 9085 *      `time` holds the date and time of the next lunar apsis, `kind` holds either
 9086 *      `APSIS_PERICENTER` for perigee or `APSIS_APOCENTER` for apogee, and the distance
 9087 *      values `dist_au` (astronomical units) and `dist_km` (kilometers) are valid.
 9088 *      If the function fails, `status` holds some value other than `ASTRO_SUCCESS` that
 9089 *      indicates what went wrong, and the other structure fields are invalid.
 9090 */
 9091astro_apsis_t Astronomy_SearchLunarApsis(astro_time_t startTime)
 9092{
 9093    astro_time_t t1, t2;
 9094    astro_search_result_t search;
 9095    astro_func_result_t m1, m2;
 9096    int positive_direction = +1;
 9097    int negative_direction = -1;
 9098    const double increment = 5.0;   /* number of days to skip in each iteration */
 9099    astro_apsis_t result;
 9100    int iter;
 9101
 9102    /*
 9103        Check the rate of change of the distance dr/dt at the start time.
 9104        If it is positive, the Moon is currently getting farther away,
 9105        so start looking for apogee.
 9106        Conversely, if dr/dt < 0, start looking for perigee.
 9107        Either way, the polarity of the slope will change, so the product will be negative.
 9108        Handle the crazy corner case of exactly touching zero by checking for m1*m2 <= 0.
 9109    */
 9110
 9111    t1 = startTime;
 9112    m1 = moon_distance_slope(&positive_direction, t1);
 9113    if (m1.status != ASTRO_SUCCESS)
 9114        return ApsisError(m1.status);
 9115
 9116    for (iter=0; iter * increment < 2.0 * MEAN_SYNODIC_MONTH; ++iter)
 9117    {
 9118        t2 = Astronomy_AddDays(t1, increment);
 9119        m2 = moon_distance_slope(&positive_direction, t2);
 9120        if (m2.status != ASTRO_SUCCESS)
 9121            return ApsisError(m2.status);
 9122
 9123        if (m1.value * m2.value <= 0.0)
 9124        {
 9125            /* There is a change of slope polarity within the time range [t1, t2]. */
 9126            /* Therefore this time range contains an apsis. */
 9127            /* Figure out whether it is perigee or apogee. */
 9128
 9129            if (m1.value < 0.0 || m2.value > 0.0)
 9130            {
 9131                /* We found a minimum-distance event: perigee. */
 9132                /* Search the time range for the time when the slope goes from negative to positive. */
 9133                search = Astronomy_Search(moon_distance_slope, &positive_direction, t1, t2, 1.0);
 9134                result.kind = APSIS_PERICENTER;
 9135            }
 9136            else if (m1.value > 0.0 || m2.value < 0.0)
 9137            {
 9138                /* We found a maximum-distance event: apogee. */
 9139                /* Search the time range for the time when the slope goes from positive to negative. */
 9140                search = Astronomy_Search(moon_distance_slope, &negative_direction, t1, t2, 1.0);
 9141                result.kind = APSIS_APOCENTER;
 9142            }
 9143            else
 9144            {
 9145                /* This should never happen. It should not be possible for both slopes to be zero. */
 9146                return ApsisError(ASTRO_INTERNAL_ERROR);
 9147            }
 9148
 9149            if (search.status != ASTRO_SUCCESS)
 9150                return ApsisError(search.status);
 9151
 9152            result.status = ASTRO_SUCCESS;
 9153            result.time = search.time;
 9154            result.dist_au = MoonDistance(search.time);
 9155            result.dist_km = result.dist_au * KM_PER_AU;
 9156            return result;
 9157        }
 9158
 9159        /* We have not yet found a slope polarity change. Keep searching. */
 9160        t1 = t2;
 9161        m1 = m2;
 9162    }
 9163
 9164    /* It should not be possible to fail to find an apsis within 2 synodic months. */
 9165    return ApsisError(ASTRO_INTERNAL_ERROR);
 9166}
 9167
 9168/**
 9169 * @brief
 9170 *      Finds the next lunar perigee or apogee event in a series.
 9171 *
 9172 * This function requires an #astro_apsis_t value obtained from a call
 9173 * to #Astronomy_SearchLunarApsis or `Astronomy_NextLunarApsis`. Given
 9174 * an apogee event, this function finds the next perigee event, and vice versa.
 9175 *
 9176 * See #Astronomy_SearchLunarApsis for more details.
 9177 *
 9178 * @param apsis
 9179 *      An apsis event obtained from a call to #Astronomy_SearchLunarApsis or `Astronomy_NextLunarApsis`.
 9180 *      See #Astronomy_SearchLunarApsis for more details.
 9181 *
 9182 * @return
 9183 *      Same as the return value for #Astronomy_SearchLunarApsis.
 9184 */
 9185astro_apsis_t Astronomy_NextLunarApsis(astro_apsis_t apsis)
 9186{
 9187    static const double skip = 11.0;    /* number of days to skip to start looking for next apsis event */
 9188    astro_apsis_t next;
 9189    astro_time_t time;
 9190
 9191    if (apsis.status != ASTRO_SUCCESS)
 9192        return ApsisError(ASTRO_INVALID_PARAMETER);
 9193
 9194    if (apsis.kind != APSIS_APOCENTER && apsis.kind != APSIS_PERICENTER)
 9195        return ApsisError(ASTRO_INVALID_PARAMETER);
 9196
 9197    time = Astronomy_AddDays(apsis.time, skip);
 9198    next = Astronomy_SearchLunarApsis(time);
 9199    if (next.status == ASTRO_SUCCESS)
 9200    {
 9201        /* Verify that we found the opposite apsis from the previous one. */
 9202        if (next.kind + apsis.kind != 1)
 9203            return ApsisError(ASTRO_INTERNAL_ERROR);
 9204    }
 9205    return next;
 9206}
 9207
 9208
 9209/** @cond DOXYGEN_SKIP */
 9210typedef struct
 9211{
 9212    int direction;
 9213    astro_body_t body;
 9214}
 9215planet_distance_context_t;
 9216/** @endcond */
 9217
 9218
 9219static astro_func_result_t planet_distance_slope(void *context, astro_time_t time)
 9220{
 9221    static const double dt = 0.001;
 9222    const planet_distance_context_t *pc = (const planet_distance_context_t *) context;
 9223    astro_time_t t1 = Astronomy_AddDays(time, -dt/2.0);
 9224    astro_time_t t2 = Astronomy_AddDays(time, +dt/2.0);
 9225    astro_func_result_t dist1, dist2, result;
 9226
 9227    dist1 = Astronomy_HelioDistance(pc->body, t1);
 9228    if (dist1.status != ASTRO_SUCCESS)
 9229        return dist1;
 9230
 9231    dist2 = Astronomy_HelioDistance(pc->body, t2);
 9232    if (dist2.status != ASTRO_SUCCESS)
 9233        return dist2;
 9234
 9235    result.value = pc->direction * (dist2.value - dist1.value) / dt;
 9236    result.status = ASTRO_SUCCESS;
 9237    return result;
 9238}
 9239
 9240static astro_apsis_t PlanetExtreme(
 9241    astro_body_t body,
 9242    astro_apsis_kind_t kind,
 9243    astro_time_t start_time,
 9244    double dayspan)
 9245{
 9246    astro_apsis_t apsis;
 9247    const double direction = (kind == APSIS_APOCENTER) ? +1.0 : -1.0;
 9248    const int npoints = 10;
 9249    int i, best_i;
 9250    double interval;
 9251    double dist, best_dist;
 9252    astro_time_t time;
 9253    astro_func_result_t result;
 9254
 9255    for(;;)
 9256    {
 9257        interval = dayspan / (npoints - 1);
 9258
 9259        if (interval < 1.0 / 1440.0)    /* iterate until uncertainty is less than one minute */
 9260        {
 9261            apsis.status = ASTRO_SUCCESS;
 9262            apsis.kind = kind;
 9263            apsis.time = Astronomy_AddDays(start_time, interval / 2.0);
 9264            result = Astronomy_HelioDistance(body, apsis.time);
 9265            if (result.status != ASTRO_SUCCESS)
 9266                return ApsisError(result.status);
 9267            apsis.dist_au = result.value;
 9268            apsis.dist_km = apsis.dist_au * KM_PER_AU;
 9269            return apsis;
 9270        }
 9271
 9272        best_i = -1;
 9273        best_dist = 0.0;
 9274        for (i=0; i < npoints; ++i)
 9275        {
 9276            time = Astronomy_AddDays(start_time, i * interval);
 9277            result = Astronomy_HelioDistance(body, time);
 9278            if (result.status != ASTRO_SUCCESS)
 9279                return ApsisError(result.status);
 9280            dist = direction * result.value;
 9281            if (i==0 || dist > best_dist)
 9282            {
 9283                best_i = i;
 9284                best_dist = dist;
 9285            }
 9286        }
 9287
 9288        /* Narrow in on the extreme point. */
 9289        start_time = Astronomy_AddDays(start_time, (best_i - 1) * interval);
 9290        dayspan = 2.0 * interval;
 9291    }
 9292}
 9293
 9294
 9295static astro_apsis_t BruteSearchPlanetApsis(astro_body_t body, astro_time_t startTime)
 9296{
 9297    const int npoints = 100;
 9298    int i;
 9299    astro_time_t t1, t2, time, t_min, t_max;
 9300    double dist, max_dist, min_dist;
 9301    astro_apsis_t perihelion, aphelion;
 9302    double interval;
 9303    double period;
 9304    astro_func_result_t result;
 9305
 9306    /*
 9307        Neptune is a special case for two reasons:
 9308        1. Its orbit is nearly circular (low orbital eccentricity).
 9309        2. It is so distant from the Sun that the orbital period is very long.
 9310        Put together, this causes wobbling of the Sun around the Solar System Barycenter (SSB)
 9311        to be so significant that there are 3 local minima in the distance-vs-time curve
 9312        near each apsis. Therefore, unlike for other planets, we can't use an optimized
 9313        algorithm for finding dr/dt = 0.
 9314        Instead, we use a dumb, brute-force algorithm of sampling and finding min/max
 9315        heliocentric distance.
 9316
 9317        There is a similar problem in the TOP2013 model for Pluto:
 9318        Its position vector has high-frequency oscillations that confuse the
 9319        slope-based determination of apsides.
 9320    */
 9321
 9322    /*
 9323        Rewind approximately 30 degrees in the orbit,
 9324        then search forward for 270 degrees.
 9325        This is a very cautious way to prevent missing an apsis.
 9326        Typically we will find two apsides, and we pick whichever
 9327        apsis is ealier, but after startTime.
 9328        Sample points around this orbital arc and find when the distance
 9329        is greatest and smallest.
 9330    */
 9331    period = Astronomy_PlanetOrbitalPeriod(body);
 9332    t1 = Astronomy_AddDays(startTime, period * ( -30.0 / 360.0));
 9333    t2 = Astronomy_AddDays(startTime, period * (+270.0 / 360.0));
 9334    t_min = t_max = t1;
 9335    min_dist = max_dist = -1.0;     /* prevent warning about uninitialized variables */
 9336    interval = (t2.ut - t1.ut) / (npoints - 1.0);
 9337
 9338    for (i=0; i < npoints; ++i)
 9339    {
 9340        double ut = t1.ut + (i * interval);
 9341        time = Astronomy_TimeFromDays(ut);
 9342        result = Astronomy_HelioDistance(body, time);
 9343        if (result.status != ASTRO_SUCCESS)
 9344            return ApsisError(result.status);
 9345        dist = result.value;
 9346        if (i == 0)
 9347        {
 9348            max_dist = min_dist = dist;
 9349        }
 9350        else
 9351        {
 9352            if (dist > max_dist)
 9353            {
 9354                max_dist = dist;
 9355                t_max = time;
 9356            }
 9357            if (dist < min_dist)
 9358            {
 9359                min_dist = dist;
 9360                t_min = time;
 9361            }
 9362        }
 9363    }
 9364
 9365    t1 = Astronomy_AddDays(t_min, -2 * interval);
 9366    perihelion = PlanetExtreme(body, APSIS_PERICENTER, t1, 4 * interval);
 9367
 9368    t1 = Astronomy_AddDays(t_max, -2 * interval);
 9369    aphelion = PlanetExtreme(body, APSIS_APOCENTER, t1, 4 * interval);
 9370
 9371    if (perihelion.status == ASTRO_SUCCESS && perihelion.time.tt >= startTime.tt)
 9372    {
 9373        if (aphelion.status == ASTRO_SUCCESS && aphelion.time.tt >= startTime.tt)
 9374        {
 9375            /* Perihelion and aphelion are both valid. Pick the one that comes first. */
 9376            if (aphelion.time.tt < perihelion.time.tt)
 9377                return aphelion;
 9378        }
 9379        return perihelion;
 9380    }
 9381
 9382    if (aphelion.status == ASTRO_SUCCESS && aphelion.time.tt >= startTime.tt)
 9383        return aphelion;
 9384
 9385    return ApsisError(ASTRO_FAIL_APSIS);
 9386}
 9387
 9388
 9389/**
 9390 * @brief
 9391 *      Finds the date and time of a planet's perihelion (closest approach to the Sun)
 9392 *      or aphelion (farthest distance from the Sun) after a given time.
 9393 *
 9394 * Given a date and time to start the search in `startTime`, this function finds the
 9395 * next date and time that the center of the specified planet reaches the closest or farthest point
 9396 * in its orbit with respect to the center of the Sun, whichever comes first
 9397 * after `startTime`.
 9398 *
 9399 * The closest point is called *perihelion* and the farthest point is called *aphelion*.
 9400 * The word *apsis* refers to either event.
 9401 *
 9402 * To iterate through consecutive alternating perihelion and aphelion events,
 9403 * call `Astronomy_SearchPlanetApsis` once, then use the return value to call
 9404 * #Astronomy_NextPlanetApsis. After that, keep feeding the previous return value
 9405 * from `Astronomy_NextPlanetApsis` into another call of `Astronomy_NextPlanetApsis`
 9406 * as many times as desired.
 9407 *
 9408 * @param body
 9409 *      The planet for which to find the next perihelion/aphelion event.
 9410 *      Not allowed to be `BODY_SUN` or `BODY_MOON`.
 9411 *
 9412 * @param startTime
 9413 *      The date and time at which to start searching for the next perihelion or aphelion.
 9414 *
 9415 * @return
 9416 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`,
 9417 *      `time` holds the date and time of the next planetary apsis, `kind` holds either
 9418 *      `APSIS_PERICENTER` for perihelion or `APSIS_APOCENTER` for aphelion, and the distance
 9419 *      values `dist_au` (astronomical units) and `dist_km` (kilometers) are valid.
 9420 *      If the function fails, `status` holds some value other than `ASTRO_SUCCESS` that
 9421 *      indicates what went wrong, and the other structure fields are invalid.
 9422 */
 9423astro_apsis_t Astronomy_SearchPlanetApsis(astro_body_t body, astro_time_t startTime)
 9424{
 9425    astro_time_t t1, t2;
 9426    astro_search_result_t search;
 9427    astro_func_result_t m1, m2;
 9428    planet_distance_context_t context;
 9429    astro_apsis_t result;
 9430    int iter;
 9431    double orbit_period_days;
 9432    double increment;   /* number of days to skip in each iteration */
 9433    astro_func_result_t dist;
 9434
 9435    if (body == BODY_NEPTUNE || body == BODY_PLUTO)
 9436        return BruteSearchPlanetApsis(body, startTime);
 9437
 9438    orbit_period_days = Astronomy_PlanetOrbitalPeriod(body);
 9439    if (orbit_period_days == 0.0)
 9440        return ApsisError(ASTRO_INVALID_BODY);      /* The body must be a planet. */
 9441
 9442    increment = orbit_period_days / 6.0;
 9443
 9444    context.body = body;
 9445
 9446    t1 = startTime;
 9447    context.direction = +1;
 9448    m1 = planet_distance_slope(&context, t1);
 9449    if (m1.status != ASTRO_SUCCESS)
 9450        return ApsisError(m1.status);
 9451
 9452    for (iter=0; iter * increment < 2.0 * orbit_period_days; ++iter)
 9453    {
 9454        t2 = Astronomy_AddDays(t1, increment);
 9455        context.direction = +1;
 9456        m2 = planet_distance_slope(&context, t2);
 9457        if (m2.status != ASTRO_SUCCESS)
 9458            return ApsisError(m2.status);
 9459
 9460        if (m1.value * m2.value <= 0.0)
 9461        {
 9462            /* There is a change of slope polarity within the time range [t1, t2]. */
 9463            /* Therefore this time range contains an apsis. */
 9464            /* Figure out whether it is perihelion or aphelion. */
 9465
 9466            if (m1.value < 0.0 || m2.value > 0.0)
 9467            {
 9468                /* We found a minimum-distance event: perihelion. */
 9469                /* Search the time range for the time when the slope goes from negative to positive. */
 9470                context.direction = +1;
 9471                result.kind = APSIS_PERICENTER;
 9472            }
 9473            else if (m1.value > 0.0 || m2.value < 0.0)
 9474            {
 9475                /* We found a maximum-distance event: aphelion. */
 9476                /* Search the time range for the time when the slope goes from positive to negative. */
 9477                context.direction = -1;
 9478                result.kind = APSIS_APOCENTER;
 9479            }
 9480            else
 9481            {
 9482                /* This should never happen. It should not be possible for both slopes to be zero. */
 9483                return ApsisError(ASTRO_INTERNAL_ERROR);
 9484            }
 9485
 9486            search = Astronomy_Search(planet_distance_slope, &context, t1, t2, 1.0);
 9487            if (search.status != ASTRO_SUCCESS)
 9488                return ApsisError(search.status);
 9489
 9490            dist = Astronomy_HelioDistance(body, search.time);
 9491            if (dist.status != ASTRO_SUCCESS)
 9492                return ApsisError(dist.status);
 9493
 9494            result.status = ASTRO_SUCCESS;
 9495            result.time = search.time;
 9496            result.dist_au = dist.value;
 9497            result.dist_km = dist.value * KM_PER_AU;
 9498            return result;
 9499        }
 9500
 9501        /* We have not yet found a slope polarity change. Keep searching. */
 9502        t1 = t2;
 9503        m1 = m2;
 9504    }
 9505
 9506    /* It should not be possible to fail to find an apsis within 2 orbits. */
 9507    return ApsisError(ASTRO_INTERNAL_ERROR);
 9508}
 9509
 9510/**
 9511 * @brief
 9512 *      Finds the next planetary perihelion or aphelion event in a series.
 9513 *
 9514 * This function requires an #astro_apsis_t value obtained from a call
 9515 * to #Astronomy_SearchPlanetApsis or `Astronomy_NextPlanetApsis`.
 9516 * Given an aphelion event, this function finds the next perihelion event, and vice versa.
 9517 *
 9518 * See #Astronomy_SearchPlanetApsis for more details.
 9519 *
 9520 * @param body
 9521 *      The planet for which to find the next perihelion/aphelion event.
 9522 *      Not allowed to be `BODY_SUN` or `BODY_MOON`.
 9523 *      Must match the body passed into the call that produced the `apsis` parameter.
 9524 *
 9525 * @param apsis
 9526 *      An apsis event obtained from a call to #Astronomy_SearchPlanetApsis or `Astronomy_NextPlanetApsis`.
 9527 *
 9528 * @return
 9529 *      Same as the return value for #Astronomy_SearchPlanetApsis.
 9530 */
 9531astro_apsis_t Astronomy_NextPlanetApsis(astro_body_t body, astro_apsis_t apsis)
 9532{
 9533    double skip;    /* number of days to skip to start looking for next apsis event */
 9534    astro_apsis_t next;
 9535    astro_time_t time;
 9536
 9537    if (apsis.status != ASTRO_SUCCESS)
 9538        return ApsisError(ASTRO_INVALID_PARAMETER);
 9539
 9540    if (apsis.kind != APSIS_APOCENTER && apsis.kind != APSIS_PERICENTER)
 9541        return ApsisError(ASTRO_INVALID_PARAMETER);
 9542
 9543    skip = 0.25 * Astronomy_PlanetOrbitalPeriod(body);        /* skip 1/4 of an orbit before starting search again */
 9544    if (skip <= 0.0)
 9545        return ApsisError(ASTRO_INVALID_BODY);      /* body must be a planet */
 9546
 9547    time = Astronomy_AddDays(apsis.time, skip);
 9548    next = Astronomy_SearchPlanetApsis(body, time);
 9549    if (next.status == ASTRO_SUCCESS)
 9550    {
 9551        /* Verify that we found the opposite apsis from the previous one. */
 9552        if (next.kind + apsis.kind != 1)
 9553            return ApsisError(ASTRO_INTERNAL_ERROR);
 9554    }
 9555    return next;
 9556}
 9557
 9558
 9559/**
 9560 * @brief Calculates the inverse of a rotation matrix.
 9561 *
 9562 * Given a rotation matrix that performs some coordinate transform,
 9563 * this function returns the matrix that reverses that transform.
 9564 *
 9565 * @param rotation
 9566 *      The rotation matrix to be inverted.
 9567 *
 9568 * @return
 9569 *      A rotation matrix that performs the opposite transformation.
 9570 */
 9571astro_rotation_t Astronomy_InverseRotation(astro_rotation_t rotation)
 9572{
 9573    astro_rotation_t inverse;
 9574
 9575    if (rotation.status != ASTRO_SUCCESS)
 9576        return RotationErr(ASTRO_INVALID_PARAMETER);
 9577
 9578    inverse.status = ASTRO_SUCCESS;
 9579    inverse.rot[0][0] = rotation.rot[0][0];
 9580    inverse.rot[0][1] = rotation.rot[1][0];
 9581    inverse.rot[0][2] = rotation.rot[2][0];
 9582    inverse.rot[1][0] = rotation.rot[0][1];
 9583    inverse.rot[1][1] = rotation.rot[1][1];
 9584    inverse.rot[1][2] = rotation.rot[2][1];
 9585    inverse.rot[2][0] = rotation.rot[0][2];
 9586    inverse.rot[2][1] = rotation.rot[1][2];
 9587    inverse.rot[2][2] = rotation.rot[2][2];
 9588
 9589    return inverse;
 9590}
 9591
 9592/**
 9593 * @brief Creates a rotation based on applying one rotation followed by another.
 9594 *
 9595 * Given two rotation matrices, returns a combined rotation matrix that is
 9596 * equivalent to rotating based on the first matrix, followed by the second.
 9597 *
 9598 * @param a
 9599 *      The first rotation to apply.
 9600 *
 9601 * @param b
 9602 *      The second rotation to apply.
 9603 *
 9604 * @return
 9605 *      The combined rotation matrix.
 9606 */
 9607astro_rotation_t Astronomy_CombineRotation(astro_rotation_t a, astro_rotation_t b)
 9608{
 9609    astro_rotation_t c;
 9610
 9611    if (a.status != ASTRO_SUCCESS || b.status != ASTRO_SUCCESS)
 9612        return RotationErr(ASTRO_INVALID_PARAMETER);
 9613
 9614    /*
 9615        Use matrix multiplication: c = b*a.
 9616        We put 'b' on the left and 'a' on the right because,
 9617        just like when you use a matrix M to rotate a vector V,
 9618        you put the M on the left in the product M*V.
 9619        We can think of this as 'b' rotating all the 3 column vectors in 'a'.
 9620    */
 9621    c.rot[0][0] = b.rot[0][0]*a.rot[0][0] + b.rot[1][0]*a.rot[0][1] + b.rot[2][0]*a.rot[0][2];
 9622    c.rot[1][0] = b.rot[0][0]*a.rot[1][0] + b.rot[1][0]*a.rot[1][1] + b.rot[2][0]*a.rot[1][2];
 9623    c.rot[2][0] = b.rot[0][0]*a.rot[2][0] + b.rot[1][0]*a.rot[2][1] + b.rot[2][0]*a.rot[2][2];
 9624    c.rot[0][1] = b.rot[0][1]*a.rot[0][0] + b.rot[1][1]*a.rot[0][1] + b.rot[2][1]*a.rot[0][2];
 9625    c.rot[1][1] = b.rot[0][1]*a.rot[1][0] + b.rot[1][1]*a.rot[1][1] + b.rot[2][1]*a.rot[1][2];
 9626    c.rot[2][1] = b.rot[0][1]*a.rot[2][0] + b.rot[1][1]*a.rot[2][1] + b.rot[2][1]*a.rot[2][2];
 9627    c.rot[0][2] = b.rot[0][2]*a.rot[0][0] + b.rot[1][2]*a.rot[0][1] + b.rot[2][2]*a.rot[0][2];
 9628    c.rot[1][2] = b.rot[0][2]*a.rot[1][0] + b.rot[1][2]*a.rot[1][1] + b.rot[2][2]*a.rot[1][2];
 9629    c.rot[2][2] = b.rot[0][2]*a.rot[2][0] + b.rot[1][2]*a.rot[2][1] + b.rot[2][2]*a.rot[2][2];
 9630
 9631    c.status = ASTRO_SUCCESS;
 9632    return c;
 9633}
 9634
 9635/**
 9636 * @brief Creates an identity rotation matrix.
 9637 *
 9638 * Returns a rotation matrix that has no effect on orientation.
 9639 * This matrix can be the starting point for other operations,
 9640 * such as using a series of calls to #Astronomy_Pivot to
 9641 * create a custom rotation matrix.
 9642 *
 9643 * @return
 9644 *      The identity matrix.
 9645 */
 9646astro_rotation_t Astronomy_IdentityMatrix(void)
 9647{
 9648    astro_rotation_t r;
 9649
 9650    r.rot[0][0] = 1.0;  r.rot[1][0] = 0.0;  r.rot[2][0] = 0.0;
 9651    r.rot[0][1] = 0.0;  r.rot[1][1] = 1.0;  r.rot[2][1] = 0.0;
 9652    r.rot[0][2] = 0.0;  r.rot[1][2] = 0.0;  r.rot[2][2] = 1.0;
 9653
 9654    r.status = ASTRO_SUCCESS;
 9655
 9656    return r;
 9657}
 9658
 9659/**
 9660 * @brief Re-orients a rotation matrix by pivoting it by an angle around one of its axes.
 9661 *
 9662 * Given a rotation matrix, a selected coordinate axis, and an angle in degrees,
 9663 * this function pivots the rotation matrix by that angle around that coordinate axis.
 9664 *
 9665 * For example, if you have rotation matrix that converts ecliptic coordinates (ECL)
 9666 * to horizontal coordinates (HOR), but you really want to convert ECL to the orientation
 9667 * of a telescope camera pointed at a given body, you can use `Astronomy_Pivot` twice:
 9668 * (1) pivot around the zenith axis by the body's azimuth, then (2) pivot around the
 9669 * western axis by the body's altitude angle. The resulting rotation matrix will then
 9670 * reorient ECL coordinates to the orientation of your telescope camera.
 9671 *
 9672 * @param rotation
 9673 *      The input rotation matrix.
 9674 *
 9675 * @param axis
 9676 *      An integer that selects which coordinate axis to rotate around:
 9677 *      0 = x, 1 = y, 2 = z. Any other value will fail with the error code
 9678 *      `ASTRO_INVALID_PARAMETER` in the `status` field of the return value.
 9679 *
 9680 * @param angle
 9681 *      An angle in degrees indicating the amount of rotation around the specified axis.
 9682 *      Positive angles indicate rotation counterclockwise as seen from the positive
 9683 *      direction along that axis, looking towards the origin point of the orientation system.
 9684 *      If `angle` is NAN or infinite, the function will fail with the error code
 9685 *      `ASTRO_INVALID_PARAMETER`. Any finite number of degrees is allowed, but best
 9686 *      precision will result from keeping `angle` in the range [-360, +360].
 9687 *
 9688 * @return
 9689 *      If successful, the return value will have `ASTRO_SUCCESS` in the `status`
 9690 *      field, along with a pivoted rotation matrix. Otherwise, `status` holds
 9691 *      an appropriate error code and the rotation matrix is invalid.
 9692 */
 9693astro_rotation_t Astronomy_Pivot(astro_rotation_t rotation, int axis, double angle)
 9694{
 9695    astro_rotation_t p;
 9696    double radians, c, s;
 9697    int i, j, k;
 9698
 9699    /* Check for an invalid input matrix. */
 9700    if (rotation.status != ASTRO_SUCCESS)
 9701        return RotationErr(ASTRO_INVALID_PARAMETER);
 9702
 9703    /* Check for an invalid coordinate axis. */
 9704    if (axis < 0 || axis > 2)
 9705        return RotationErr(ASTRO_INVALID_PARAMETER);
 9706
 9707    /* Check for an invalid angle value. */
 9708    if (!isfinite(angle))
 9709        return RotationErr(ASTRO_INVALID_PARAMETER);
 9710
 9711    radians = angle * DEG2RAD;
 9712    c = cos(radians);
 9713    s = sin(radians);
 9714
 9715    /*
 9716        We need to maintain the "right-hand" rule, no matter which
 9717        axis was selected. That means we pick (i, j, k) axis order
 9718        such that the following vector cross product is satisfied:
 9719        i x j = k
 9720    */
 9721    i = (axis + 1) % 3;
 9722    j = (axis + 2) % 3;
 9723    k = axis;
 9724
 9725    p.rot[i][i] = c*rotation.rot[i][i] - s*rotation.rot[i][j];
 9726    p.rot[i][j] = s*rotation.rot[i][i] + c*rotation.rot[i][j];
 9727    p.rot[i][k] = rotation.rot[i][k];
 9728
 9729    p.rot[j][i] = c*rotation.rot[j][i] - s*rotation.rot[j][j];
 9730    p.rot[j][j] = s*rotation.rot[j][i] + c*rotation.rot[j][j];
 9731    p.rot[j][k] = rotation.rot[j][k];
 9732
 9733    p.rot[k][i] = c*rotation.rot[k][i] - s*rotation.rot[k][j];
 9734    p.rot[k][j] = s*rotation.rot[k][i] + c*rotation.rot[k][j];
 9735    p.rot[k][k] = rotation.rot[k][k];
 9736
 9737    p.status = ASTRO_SUCCESS;
 9738    return p;
 9739}
 9740
 9741/**
 9742 * @brief Converts spherical coordinates to Cartesian coordinates.
 9743 *
 9744 * Given spherical coordinates and a time at which they are valid,
 9745 * returns a vector of Cartesian coordinates. The returned value
 9746 * includes the time, as required by the type #astro_vector_t.
 9747 *
 9748 * @param sphere
 9749 *      Spherical coordinates to be converted.
 9750 *
 9751 * @param time
 9752 *      The time that should be included in the return value.
 9753 *
 9754 * @return
 9755 *      The vector form of the supplied spherical coordinates.
 9756 */
 9757astro_vector_t Astronomy_VectorFromSphere(astro_spherical_t sphere, astro_time_t time)
 9758{
 9759    astro_vector_t vector;
 9760    double radlat, radlon, rcoslat;
 9761
 9762    if (sphere.status != ASTRO_SUCCESS)
 9763        return VecError(ASTRO_INVALID_PARAMETER, time);
 9764
 9765    radlat = sphere.lat * DEG2RAD;
 9766    radlon = sphere.lon * DEG2RAD;
 9767    rcoslat = sphere.dist * cos(radlat);
 9768
 9769    vector.status = ASTRO_SUCCESS;
 9770    vector.t = time;
 9771    vector.x = rcoslat * cos(radlon);
 9772    vector.y = rcoslat * sin(radlon);
 9773    vector.z = sphere.dist * sin(radlat);
 9774
 9775    return vector;
 9776}
 9777
 9778
 9779/**
 9780 * @brief Converts Cartesian coordinates to spherical coordinates.
 9781 *
 9782 * Given a Cartesian vector, returns latitude, longitude, and distance.
 9783 *
 9784 * @param vector
 9785 *      Cartesian vector to be converted to spherical coordinates.
 9786 *
 9787 * @return
 9788 *      Spherical coordinates that are equivalent to the given vector.
 9789 */
 9790astro_spherical_t Astronomy_SphereFromVector(astro_vector_t vector)
 9791{
 9792    double xyproj;
 9793    astro_spherical_t sphere;
 9794
 9795    if (vector.status != ASTRO_SUCCESS)
 9796        return SphereError(vector.status);
 9797
 9798    xyproj = vector.x*vector.x + vector.y*vector.y;
 9799    sphere.dist = sqrt(xyproj + vector.z*vector.z);
 9800    if (xyproj == 0.0)
 9801    {
 9802        if (vector.z == 0.0)
 9803        {
 9804            /* Indeterminate coordinates; pos vector has zero length. */
 9805            return SphereError(ASTRO_INVALID_PARAMETER);
 9806        }
 9807
 9808        sphere.lon = 0.0;
 9809        sphere.lat = (vector.z < 0.0) ? -90.0 : +90.0;
 9810    }
 9811    else
 9812    {
 9813        sphere.lon = RAD2DEG * atan2(vector.y, vector.x);
 9814        if (sphere.lon < 0.0)
 9815            sphere.lon += 360.0;
 9816
 9817        sphere.lat = RAD2DEG * atan2(vector.z, sqrt(xyproj));
 9818    }
 9819
 9820    sphere.status = ASTRO_SUCCESS;
 9821    return sphere;
 9822}
 9823
 9824
 9825/**
 9826 * @brief
 9827 *      Given an equatorial vector, calculates equatorial angular coordinates.
 9828 *
 9829 * @param vector
 9830 *      A vector in an equatorial coordinate system.
 9831 *
 9832 * @return
 9833 *      Angular coordinates expressed in the same equatorial system as `vector`.
 9834 */
 9835astro_equatorial_t Astronomy_EquatorFromVector(astro_vector_t vector)
 9836{
 9837    astro_equatorial_t equ;
 9838    astro_spherical_t sphere;
 9839
 9840    sphere = Astronomy_SphereFromVector(vector);
 9841    if (sphere.status != ASTRO_SUCCESS)
 9842        return EquError(sphere.status);
 9843
 9844    equ.status = ASTRO_SUCCESS;
 9845    equ.dec = sphere.lat;
 9846    equ.ra = sphere.lon / 15.0;     /* convert degrees to sidereal hours */
 9847    equ.dist = sphere.dist;
 9848    equ.vec = vector;
 9849
 9850    return equ;
 9851}
 9852
 9853
 9854static double ToggleAzimuthDirection(double az)
 9855{
 9856    az = 360.0 - az;
 9857    if (az >= 360.0)
 9858        az -= 360.0;
 9859    else if (az < 0.0)
 9860        az += 360.0;
 9861    return az;
 9862}
 9863
 9864/**
 9865 * @brief Converts Cartesian coordinates to horizontal coordinates.
 9866 *
 9867 * Given a horizontal Cartesian vector, returns horizontal azimuth and altitude.
 9868 *
 9869 * *IMPORTANT:* This function differs from #Astronomy_SphereFromVector in two ways:
 9870 * - `Astronomy_SphereFromVector` returns a `lon` value that represents azimuth defined counterclockwise
 9871 *   from north (e.g., west = +90), but this function represents a clockwise rotation
 9872 *   (e.g., east = +90). The difference is because `Astronomy_SphereFromVector` is intended
 9873 *   to preserve the vector "right-hand rule", while this function defines azimuth in a more
 9874 *   traditional way as used in navigation and cartography.
 9875 * - This function optionally corrects for atmospheric refraction, while `Astronomy_SphereFromVector`
 9876 *   does not.
 9877 *
 9878 * The returned structure contains the azimuth in `lon`.
 9879 * It is measured in degrees clockwise from north: east = +90 degrees, west = +270 degrees.
 9880 *
 9881 * The altitude is stored in `lat`.
 9882 *
 9883 * The distance to the observed object is stored in `dist`,
 9884 * and is expressed in astronomical units (AU).
 9885 *
 9886 * @param vector
 9887 *      Cartesian vector to be converted to horizontal coordinates.
 9888 *
 9889 * @param refraction
 9890 *      `REFRACTION_NORMAL`: correct altitude for atmospheric refraction (recommended).
 9891 *      `REFRACTION_NONE`: no atmospheric refraction correction is performed.
 9892 *      `REFRACTION_JPLHOR`: for JPL Horizons compatibility testing only; not recommended for normal use.
 9893 *
 9894 * @return
 9895 *      If successful, `status` holds `ASTRO_SUCCESS` and the other fields are valid as described
 9896 *      in the function remarks.
 9897 *      Otherwise `status` holds an error code and the other fields are undefined.
 9898 */
 9899astro_spherical_t Astronomy_HorizonFromVector(astro_vector_t vector, astro_refraction_t refraction)
 9900{
 9901    astro_spherical_t sphere;
 9902
 9903    sphere = Astronomy_SphereFromVector(vector);
 9904    if (sphere.status == ASTRO_SUCCESS)
 9905    {
 9906        /* Convert azimuth from counterclockwise-from-north to clockwise-from-north. */
 9907        sphere.lon = ToggleAzimuthDirection(sphere.lon);
 9908        sphere.lat += Astronomy_Refraction(refraction, sphere.lat);
 9909    }
 9910
 9911    return sphere;
 9912}
 9913
 9914
 9915/**
 9916 * @brief
 9917 *      Given apparent angular horizontal coordinates in `sphere`, calculate horizontal vector.
 9918 *
 9919 * @param sphere
 9920 *      A structure that contains apparent horizontal coordinates:
 9921 *      `lat` holds the refracted altitude angle,
 9922 *      `lon` holds the azimuth in degrees clockwise from north,
 9923 *      and `dist` holds the distance from the observer to the object in AU.
 9924 *
 9925 * @param time
 9926 *      The date and time of the observation. This is needed because the returned
 9927 *      #astro_vector_t structure requires a valid time value when passed to certain other functions.
 9928 *
 9929 * @param refraction
 9930 *      The refraction option used to model atmospheric lensing. See #Astronomy_Refraction.
 9931 *      This specifies how refraction is to be removed from the altitude stored in `sphere.lat`.
 9932 *
 9933 * @return
 9934 *      A vector in the horizontal system: `x` = north, `y` = west, and `z` = zenith (up).
 9935 */
 9936astro_vector_t Astronomy_VectorFromHorizon(astro_spherical_t sphere, astro_time_t time, astro_refraction_t refraction)
 9937{
 9938    if (sphere.status != ASTRO_SUCCESS)
 9939        return VecError(ASTRO_INVALID_PARAMETER, time);
 9940
 9941    /* Convert azimuth from clockwise-from-north to counterclockwise-from-north. */
 9942    sphere.lon = ToggleAzimuthDirection(sphere.lon);
 9943
 9944    /* Reverse any applied refraction. */
 9945    sphere.lat += Astronomy_InverseRefraction(refraction, sphere.lat);
 9946
 9947    return Astronomy_VectorFromSphere(sphere, time);
 9948}
 9949
 9950
 9951/**
 9952 * @brief
 9953 *      Calculates the amount of "lift" to an altitude angle caused by atmospheric refraction.
 9954 *
 9955 * Given an altitude angle and a refraction option, calculates
 9956 * the amount of "lift" caused by atmospheric refraction.
 9957 * This is the number of degrees higher in the sky an object appears
 9958 * due to the lensing of the Earth's atmosphere.
 9959 * This function works best near sea level.
 9960 * To correct for higher elevations, call #Astronomy_Atmosphere for that
 9961 * elevation and multiply the refraction angle by the resulting relative density.
 9962 *
 9963 * @param refraction
 9964 *      The option selecting which refraction correction to use.
 9965 *      If `REFRACTION_NORMAL`, uses a well-behaved refraction model that works well for
 9966 *      all valid values (-90 to +90) of `altitude`.
 9967 *      If `REFRACTION_JPLHOR`, this function returns a compatible value with the JPL Horizons tool.
 9968 *      If any other value (including `REFRACTION_NONE`), this function returns 0.
 9969 *
 9970 * @param altitude
 9971 *      An altitude angle in a horizontal coordinate system. Must be a value between -90 and +90.
 9972 *
 9973 * @return
 9974 *      The angular adjustment in degrees to be added to the altitude angle to correct for atmospheric lensing.
 9975 */
 9976double Astronomy_Refraction(astro_refraction_t refraction, double altitude)
 9977{
 9978    double refr, hd;
 9979
 9980    if (altitude < -90.0 || altitude > +90.0)
 9981        return 0.0;     /* no attempt to correct an invalid altitude */
 9982
 9983    if (refraction == REFRACTION_NORMAL || refraction == REFRACTION_JPLHOR)
 9984    {
 9985        /*
 9986            http://extras.springer.com/1999/978-1-4471-0555-8/chap4/horizons/horizons.pdf
 9987            JPL Horizons says it uses refraction algorithm from
 9988            Meeus "Astronomical Algorithms", 1991, p. 101-102.
 9989            I found the following Go implementation:
 9990            https://github.com/soniakeys/meeus/blob/master/v3/refraction/refract.go
 9991            This is a translation from the function "Saemundsson" there.
 9992            I found experimentally that JPL Horizons clamps the angle to 1 degree below the horizon.
 9993            This is important because the 'refr' formula below goes crazy near hd = -5.11.
 9994        */
 9995
 9996        hd = altitude;
 9997        if (hd < -1.0)
 9998            hd = -1.0;
 9999
10000        refr = (1.02 / tan((hd+10.3/(hd+5.11))*DEG2RAD)) / 60.0;
10001
10002        if (refraction == REFRACTION_NORMAL && altitude < -1.0)
10003        {
10004            /*
10005                In "normal" mode we gradually reduce refraction toward the nadir
10006                so that we never get an altitude angle less than -90 degrees.
10007                When horizon angle is -1 degrees, the factor is exactly 1.
10008                As altitude approaches -90 (the nadir), the fraction approaches 0 linearly.
10009            */
10010            refr *= (altitude + 90.0) / 89.0;
10011        }
10012    }
10013    else
10014    {
10015        /* No refraction, or the refraction option is invalid. */
10016        refr = 0.0;
10017    }
10018
10019    return refr;
10020}
10021
10022
10023/**
10024 * @brief
10025 *      Calculates the inverse of an atmospheric refraction angle.
10026 *
10027 * Given an observed altitude angle that includes atmospheric refraction,
10028 * calculates the negative angular correction to obtain the unrefracted
10029 * altitude. This is useful for cases where observed horizontal
10030 * coordinates are to be converted to another orientation system,
10031 * but refraction first must be removed from the observed position.
10032 *
10033 * @param refraction
10034 *      The option selecting which refraction correction to use.
10035 *      See #Astronomy_Refraction.
10036 *
10037 * @param bent_altitude
10038 *      The apparent altitude that includes atmospheric refraction.
10039 *
10040 * @return
10041 *      The angular adjustment in degrees to be added to the
10042 *      altitude angle to correct for atmospheric lensing.
10043 *      This will be less than or equal to zero.
10044 */
10045double Astronomy_InverseRefraction(astro_refraction_t refraction, double bent_altitude)
10046{
10047    double altitude, diff;
10048
10049    if (bent_altitude < -90.0 || bent_altitude > +90.0)
10050        return 0.0;     /* no attempt to correct an invalid altitude */
10051
10052    /* Find the pre-adjusted altitude whose refraction correction leads to 'altitude'. */
10053    altitude = bent_altitude - Astronomy_Refraction(refraction, bent_altitude);
10054    for(;;)
10055    {
10056        /* See how close we got. */
10057        diff = (altitude + Astronomy_Refraction(refraction, altitude)) - bent_altitude;
10058        if (fabs(diff) < 1.0e-14)
10059            return altitude - bent_altitude;
10060
10061        altitude -= diff;
10062    }
10063}
10064
10065/**
10066 * @brief Applies a rotation to a vector, yielding a rotated vector.
10067 *
10068 * This function transforms a vector in one orientation to a vector
10069 * in another orientation.
10070 *
10071 * @param rotation
10072 *      A rotation matrix that specifies how the orientation of the vector is to be changed.
10073 *
10074 * @param vector
10075 *      The vector whose orientation is to be changed.
10076 *
10077 * @return
10078 *      A vector in the orientation specified by `rotation`.
10079 */
10080astro_vector_t Astronomy_RotateVector(astro_rotation_t rotation, astro_vector_t vector)
10081{
10082    astro_vector_t target;
10083
10084    if (rotation.status != ASTRO_SUCCESS || vector.status != ASTRO_SUCCESS)
10085        return VecError(ASTRO_INVALID_PARAMETER, vector.t);
10086
10087    target.status = ASTRO_SUCCESS;
10088    target.t = vector.t;
10089    target.x = rotation.rot[0][0]*vector.x + rotation.rot[1][0]*vector.y + rotation.rot[2][0]*vector.z;
10090    target.y = rotation.rot[0][1]*vector.x + rotation.rot[1][1]*vector.y + rotation.rot[2][1]*vector.z;
10091    target.z = rotation.rot[0][2]*vector.x + rotation.rot[1][2]*vector.y + rotation.rot[2][2]*vector.z;
10092
10093    return target;
10094}
10095
10096
10097/**
10098 * @brief Applies a rotation to a state vector, yielding a rotated vector.
10099 *
10100 * This function transforms a state vector in one orientation to a vector
10101 * in another orientation.
10102 *
10103 * @param rotation
10104 *      A rotation matrix that specifies how the orientation of the state vector is to be changed.
10105 *
10106 * @param state
10107 *      The state vector whose orientation is to be changed.
10108 *      Both the position and velocity components are transformed.
10109 *
10110 * @return
10111 *      A state vector in the orientation specified by `rotation`.
10112 */
10113astro_state_vector_t Astronomy_RotateState(astro_rotation_t rotation, astro_state_vector_t state)
10114{
10115    astro_state_vector_t target;
10116
10117    if (rotation.status != ASTRO_SUCCESS || state.status != ASTRO_SUCCESS)
10118        return StateVecError(ASTRO_INVALID_PARAMETER, state.t);
10119
10120    target.status = ASTRO_SUCCESS;
10121    target.t = state.t;
10122
10123    target.x = rotation.rot[0][0]*state.x + rotation.rot[1][0]*state.y + rotation.rot[2][0]*state.z;
10124    target.y = rotation.rot[0][1]*state.x + rotation.rot[1][1]*state.y + rotation.rot[2][1]*state.z;
10125    target.z = rotation.rot[0][2]*state.x + rotation.rot[1][2]*state.y + rotation.rot[2][2]*state.z;
10126
10127    target.vx = rotation.rot[0][0]*state.vx + rotation.rot[1][0]*state.vy + rotation.rot[2][0]*state.vz;
10128    target.vy = rotation.rot[0][1]*state.vx + rotation.rot[1][1]*state.vy + rotation.rot[2][1]*state.vz;
10129    target.vz = rotation.rot[0][2]*state.vx + rotation.rot[1][2]*state.vy + rotation.rot[2][2]*state.vz;
10130
10131    return target;
10132}
10133
10134
10135/**
10136 * @brief
10137 *      Calculates a rotation matrix from J2000 mean equator (EQJ) to J2000 mean ecliptic (ECL).
10138 *
10139 * This is one of the family of functions that returns a rotation matrix
10140 * for converting from one orientation to another.
10141 * Source: EQJ = equatorial system, using equator at J2000 epoch.
10142 * Target: ECL = ecliptic system, using equator at J2000 epoch.
10143 *
10144 * @return
10145 *      A rotation matrix that converts EQJ to ECL.
10146 */
10147astro_rotation_t Astronomy_Rotation_EQJ_ECL(void)
10148{
10149    static const double c = COS_OBLIQ_2000;
10150    static const double s = SIN_OBLIQ_2000;
10151    astro_rotation_t r;
10152
10153    r.status = ASTRO_SUCCESS;
10154    r.rot[0][0] = 1.0;  r.rot[1][0] = 0.0;  r.rot[2][0] = 0.0;
10155    r.rot[0][1] = 0.0;  r.rot[1][1] = +c;   r.rot[2][1] = +s;
10156    r.rot[0][2] = 0.0;  r.rot[1][2] = -s;   r.rot[2][2] = +c;
10157    return r;
10158}
10159
10160/**
10161 * @brief
10162 *      Calculates a rotation matrix from J2000 mean ecliptic (ECL) to J2000 mean equator (EQJ).
10163 *
10164 * This is one of the family of functions that returns a rotation matrix
10165 * for converting from one orientation to another.
10166 * Source: ECL = ecliptic system, using equator at J2000 epoch.
10167 * Target: EQJ = equatorial system, using equator at J2000 epoch.
10168 *
10169 * @return
10170 *      A rotation matrix that converts ECL to EQJ.
10171 */
10172astro_rotation_t Astronomy_Rotation_ECL_EQJ(void)
10173{
10174    static const double c = COS_OBLIQ_2000;
10175    static const double s = SIN_OBLIQ_2000;
10176    astro_rotation_t r;
10177
10178    r.status = ASTRO_SUCCESS;
10179    r.rot[0][0] = 1.0;  r.rot[1][0] = 0.0;  r.rot[2][0] = 0.0;
10180    r.rot[0][1] = 0.0;  r.rot[1][1] = +c;   r.rot[2][1] = -s;
10181    r.rot[0][2] = 0.0;  r.rot[1][2] = +s;   r.rot[2][2] = +c;
10182    return r;
10183}
10184
10185/**
10186 * @brief
10187 *      Calculates a rotation matrix from J2000 mean equator (EQJ) to equatorial of-date (EQD).
10188 *
10189 * This is one of the family of functions that returns a rotation matrix
10190 * for converting from one orientation to another.
10191 * Source: EQJ = equatorial system, using equator at J2000 epoch.
10192 * Target: EQD = equatorial system, using equator of the specified date/time.
10193 *
10194 * @param time
10195 *      The date and time at which the Earth's equator defines the target orientation.
10196 *
10197 * @return
10198 *      A rotation matrix that converts EQJ to EQD at `time`.
10199 */
10200astro_rotation_t Astronomy_Rotation_EQJ_EQD(astro_time_t *time)
10201{
10202    astro_rotation_t prec, nut;
10203
10204    if (time == NULL)
10205        return RotationErr(ASTRO_INVALID_PARAMETER);
10206
10207    prec = precession_rot(*time, FROM_2000);
10208    nut = nutation_rot(time, FROM_2000);
10209    return Astronomy_CombineRotation(prec, nut);
10210}
10211
10212
10213/**
10214 * @brief
10215 *      Calculates a rotation matrix from J2000 mean equator (EQJ) to true ecliptic of date (ECT).
10216 *
10217 * This is one of the family of functions that returns a rotation matrix
10218 * for converting from one orientation to another.
10219 * Source: EQJ = equatorial system, using mean equator at J2000 epoch.
10220 * Target: ECT = ecliptic system, using true equinox of the specified date/time.
10221 *
10222 * @param time
10223 *      The date and time at which the Earth's equator defines the target orientation.
10224 *
10225 * @return
10226 *      A rotation matrix that converts EQJ to ECT at `time`.
10227 */
10228astro_rotation_t Astronomy_Rotation_EQJ_ECT(astro_time_t *time)
10229{
10230    astro_rotation_t rot, step;
10231
10232    if (time == NULL)
10233        return RotationErr(ASTRO_INVALID_PARAMETER);
10234
10235    rot = Astronomy_Rotation_EQJ_EQD(time);
10236    step = Astronomy_Rotation_EQD_ECT(time);
10237    return Astronomy_CombineRotation(rot, step);
10238}
10239
10240
10241/**
10242 * @brief
10243 *      Calculates a rotation matrix from true ecliptic of date (ECT) to J2000 mean equator (EQJ).
10244 *
10245 * This is one of the family of functions that returns a rotation matrix
10246 * for converting from one orientation to another.
10247 * Source: ECT = ecliptic system, using true equinox of the specified date/time.
10248 * Target: EQJ = equatorial system, using mean equator at J2000 epoch.
10249 *
10250 * @param time
10251 *      The date and time at which the Earth's equator defines the target orientation.
10252 *
10253 * @return
10254 *      A rotation matrix that converts ECT to EQJ at `time`.
10255 */
10256astro_rotation_t Astronomy_Rotation_ECT_EQJ(astro_time_t *time)
10257{
10258    astro_rotation_t rot, step;
10259
10260    if (time == NULL)
10261        return RotationErr(ASTRO_INVALID_PARAMETER);
10262
10263    rot = Astronomy_Rotation_ECT_EQD(time);
10264    step = Astronomy_Rotation_EQD_EQJ(time);
10265    return Astronomy_CombineRotation(rot, step);
10266}
10267
10268
10269/**
10270 * @brief
10271 *      Calculates a rotation matrix from equatorial of-date (EQD) to J2000 mean equator (EQJ).
10272 *
10273 * This is one of the family of functions that returns a rotation matrix
10274 * for converting from one orientation to another.
10275 * Source: EQD = equatorial system, using equator of the specified date/time.
10276 * Target: EQJ = equatorial system, using equator at J2000 epoch.
10277 *
10278 * @param time
10279 *      The date and time at which the Earth's equator defines the source orientation.
10280 *
10281 * @return
10282 *      A rotation matrix that converts EQD at `time` to EQJ.
10283 */
10284astro_rotation_t Astronomy_Rotation_EQD_EQJ(astro_time_t *time)
10285{
10286    astro_rotation_t prec, nut;
10287
10288    if (time == NULL)
10289        return RotationErr(ASTRO_INVALID_PARAMETER);
10290
10291    nut = nutation_rot(time, INTO_2000);
10292    prec = precession_rot(*time, INTO_2000);
10293    return Astronomy_CombineRotation(nut, prec);
10294}
10295
10296
10297/**
10298 * @brief
10299 *      Calculates a rotation matrix from equatorial of-date (EQD) to horizontal (HOR).
10300 *
10301 * This is one of the family of functions that returns a rotation matrix
10302 * for converting from one orientation to another.
10303 * Source: EQD = equatorial system, using equator of the specified date/time.
10304 * Target: HOR = horizontal system.
10305 *
10306 * @param time
10307 *      The date and time at which the Earth's equator applies.
10308 *
10309 * @param observer
10310 *      A location near the Earth's mean sea level that defines the observer's horizon.
10311 *
10312 * @return
10313 *      A rotation matrix that converts EQD to HOR at `time` and for `observer`.
10314 *      The components of the horizontal vector are:
10315 *      x = north, y = west, z = zenith (straight up from the observer).
10316 *      These components are chosen so that the "right-hand rule" works for the vector
10317 *      and so that north represents the direction where azimuth = 0.
10318 */
10319astro_rotation_t Astronomy_Rotation_EQD_HOR(astro_time_t *time, astro_observer_t observer)
10320{
10321    astro_rotation_t rot;
10322    double uze[3], une[3], uwe[3];
10323    double uz[3], un[3], uw[3];
10324    double spin_angle;
10325
10326    if (time == NULL)
10327        return RotationErr(ASTRO_INVALID_PARAMETER);
10328
10329    double sinlat = sin(observer.latitude * DEG2RAD);
10330    double coslat = cos(observer.latitude * DEG2RAD);
10331    double sinlon = sin(observer.longitude * DEG2RAD);
10332    double coslon = cos(observer.longitude * DEG2RAD);
10333
10334    uze[0] = coslat * coslon;
10335    uze[1] = coslat * sinlon;
10336    uze[2] = sinlat;
10337
10338    une[0] = -sinlat * coslon;
10339    une[1] = -sinlat * sinlon;
10340    une[2] = coslat;
10341
10342    uwe[0] = sinlon;
10343    uwe[1] = -coslon;
10344    uwe[2] = 0.0;
10345
10346    spin_angle = -15.0 * Astronomy_SiderealTime(time);
10347    spin(spin_angle, uze, uz);
10348    spin(spin_angle, une, un);
10349    spin(spin_angle, uwe, uw);
10350
10351    rot.rot[0][0] = un[0]; rot.rot[1][0] = un[1]; rot.rot[2][0] = un[2];
10352    rot.rot[0][1] = uw[0]; rot.rot[1][1] = uw[1]; rot.rot[2][1] = uw[2];
10353    rot.rot[0][2] = uz[0]; rot.rot[1][2] = uz[1]; rot.rot[2][2] = uz[2];
10354
10355    rot.status = ASTRO_SUCCESS;
10356    return rot;
10357}
10358
10359
10360/**
10361 * @brief
10362 *      Calculates a rotation matrix from horizontal (HOR) to equatorial of-date (EQD).
10363 *
10364 * This is one of the family of functions that returns a rotation matrix
10365 * for converting from one orientation to another.
10366 * Source: HOR = horizontal system (x=North, y=West, z=Zenith).
10367 * Target: EQD = equatorial system, using equator of the specified date/time.
10368 *
10369 * @param time
10370 *      The date and time at which the Earth's equator applies.
10371 *
10372 * @param observer
10373 *      A location near the Earth's mean sea level that defines the observer's horizon.
10374 *
10375 * @return
10376 *      A rotation matrix that converts HOR to EQD at `time` and for `observer`.
10377 */
10378astro_rotation_t Astronomy_Rotation_HOR_EQD(astro_time_t *time, astro_observer_t observer)
10379{
10380    astro_rotation_t rot = Astronomy_Rotation_EQD_HOR(time, observer);
10381    return Astronomy_InverseRotation(rot);
10382}
10383
10384
10385/**
10386 * @brief
10387 *      Calculates a rotation matrix from horizontal (HOR) to J2000 equatorial (EQJ).
10388 *
10389 * This is one of the family of functions that returns a rotation matrix
10390 * for converting from one orientation to another.
10391 * Source: HOR = horizontal system (x=North, y=West, z=Zenith).
10392 * Target: EQJ = equatorial system, using equator at the J2000 epoch.
10393 *
10394 * @param time
10395 *      The date and time of the observation.
10396 *
10397 * @param observer
10398 *      A location near the Earth's mean sea level that defines the observer's horizon.
10399 *
10400 * @return
10401 *      A rotation matrix that converts HOR to EQJ at `time` and for `observer`.
10402 */
10403astro_rotation_t Astronomy_Rotation_HOR_EQJ(astro_time_t *time, astro_observer_t observer)
10404{
10405    astro_rotation_t hor_eqd, eqd_eqj;
10406
10407    if (time == NULL)
10408        return RotationErr(ASTRO_INVALID_PARAMETER);
10409
10410    hor_eqd = Astronomy_Rotation_HOR_EQD(time, observer);
10411    eqd_eqj = Astronomy_Rotation_EQD_EQJ(time);
10412    return Astronomy_CombineRotation(hor_eqd, eqd_eqj);
10413}
10414
10415
10416/**
10417 * @brief
10418 *      Calculates a rotation matrix from J2000 mean equator (EQJ) to horizontal (HOR).
10419 *
10420 * This is one of the family of functions that returns a rotation matrix
10421 * for converting from one orientation to another.
10422 * Source: EQJ = equatorial system, using the equator at the J2000 epoch.
10423 * Target: HOR = horizontal system.
10424 *
10425 * @param time
10426 *      The date and time of the desired horizontal orientation.
10427 *
10428 * @param observer
10429 *      A location near the Earth's mean sea level that defines the observer's horizon.
10430 *
10431 * @return
10432 *      A rotation matrix that converts EQJ to HOR at `time` and for `observer`.
10433 *      The components of the horizontal vector are:
10434 *      x = north, y = west, z = zenith (straight up from the observer).
10435 *      These components are chosen so that the "right-hand rule" works for the vector
10436 *      and so that north represents the direction where azimuth = 0.
10437 */
10438astro_rotation_t Astronomy_Rotation_EQJ_HOR(astro_time_t *time, astro_observer_t observer)
10439{
10440    astro_rotation_t rot = Astronomy_Rotation_HOR_EQJ(time, observer);
10441    return Astronomy_InverseRotation(rot);
10442}
10443
10444
10445/**
10446 * @brief
10447 *      Calculates a rotation matrix from equatorial of-date (EQD) to J2000 mean ecliptic (ECL).
10448 *
10449 * This is one of the family of functions that returns a rotation matrix
10450 * for converting from one orientation to another.
10451 * Source: EQD = equatorial system, using equator of date.
10452 * Target: ECL = ecliptic system, using equator at J2000 epoch.
10453 *
10454 * @param time
10455 *      The date and time of the source equator.
10456 *
10457 * @return
10458 *      A rotation matrix that converts EQD to ECL.
10459 */
10460astro_rotation_t Astronomy_Rotation_EQD_ECL(astro_time_t *time)
10461{
10462    astro_rotation_t eqd_eqj;
10463    astro_rotation_t eqj_ecl;
10464
10465    if (time == NULL)
10466        return RotationErr(ASTRO_INVALID_PARAMETER);
10467
10468    eqd_eqj = Astronomy_Rotation_EQD_EQJ(time);
10469    eqj_ecl = Astronomy_Rotation_EQJ_ECL();
10470    return Astronomy_CombineRotation(eqd_eqj, eqj_ecl);
10471}
10472
10473
10474/**
10475 * @brief
10476 *      Calculates a rotation matrix from J2000 mean ecliptic (ECL) to equatorial of-date (EQD).
10477 *
10478 * This is one of the family of functions that returns a rotation matrix
10479 * for converting from one orientation to another.
10480 * Source: ECL = ecliptic system, using equator at J2000 epoch.
10481 * Target: EQD = equatorial system, using equator of date.
10482 *
10483 * @param time
10484 *      The date and time of the desired equator.
10485 *
10486 * @return
10487 *      A rotation matrix that converts ECL to EQD.
10488 */
10489astro_rotation_t Astronomy_Rotation_ECL_EQD(astro_time_t *time)
10490{
10491    astro_rotation_t rot = Astronomy_Rotation_EQD_ECL(time);
10492    return Astronomy_InverseRotation(rot);
10493}
10494
10495/**
10496 * @brief
10497 *      Calculates a rotation matrix from J2000 mean ecliptic (ECL) to horizontal (HOR).
10498 *
10499 * This is one of the family of functions that returns a rotation matrix
10500 * for converting from one orientation to another.
10501 * Source: ECL = ecliptic system, using equator at J2000 epoch.
10502 * Target: HOR = horizontal system.
10503 *
10504 * @param time
10505 *      The date and time of the desired horizontal orientation.
10506 *
10507 * @param observer
10508 *      A location near the Earth's mean sea level that defines the observer's horizon.
10509 *
10510 * @return
10511 *      A rotation matrix that converts ECL to HOR at `time` and for `observer`.
10512 *      The components of the horizontal vector are:
10513 *      x = north, y = west, z = zenith (straight up from the observer).
10514 *      These components are chosen so that the "right-hand rule" works for the vector
10515 *      and so that north represents the direction where azimuth = 0.
10516 */
10517astro_rotation_t Astronomy_Rotation_ECL_HOR(astro_time_t *time, astro_observer_t observer)
10518{
10519    astro_rotation_t ecl_eqd;
10520    astro_rotation_t eqd_hor;
10521
10522    if (time == NULL)
10523        return RotationErr(ASTRO_INVALID_PARAMETER);
10524
10525    ecl_eqd = Astronomy_Rotation_ECL_EQD(time);
10526    eqd_hor = Astronomy_Rotation_EQD_HOR(time, observer);
10527    return Astronomy_CombineRotation(ecl_eqd, eqd_hor);
10528}
10529
10530/**
10531 * @brief
10532 *      Calculates a rotation matrix from horizontal (HOR) to J2000 mean ecliptic (ECL).
10533 *
10534 * This is one of the family of functions that returns a rotation matrix
10535 * for converting from one orientation to another.
10536 * Source: HOR = horizontal system.
10537 * Target: ECL = ecliptic system, using equator at J2000 epoch.
10538 *
10539 * @param time
10540 *      The date and time of the horizontal observation.
10541 *
10542 * @param observer
10543 *      The location of the horizontal observer.
10544 *
10545 * @return
10546 *      A rotation matrix that converts HOR to ECL.
10547 */
10548astro_rotation_t Astronomy_Rotation_HOR_ECL(astro_time_t *time, astro_observer_t observer)
10549{
10550    astro_rotation_t rot = Astronomy_Rotation_ECL_HOR(time, observer);
10551    return Astronomy_InverseRotation(rot);
10552}
10553
10554/**
10555 * @brief
10556 *      Returns a rotation matrix from J2000 mean ecliptic (EQJ) to galactic (GAL).
10557 *
10558 * This is one of the family of functions that returns a rotation matrix
10559 * for converting from one orientation to another.
10560 * Source: EQJ = equatorial system, using the equator at the J2000 epoch.
10561 * Target: GAL = galactic system (IAU 1958 definition).
10562 *
10563 * @return
10564 *      A rotation matrix that converts EQJ to GAL.
10565 */
10566astro_rotation_t Astronomy_Rotation_EQJ_GAL(void)
10567{
10568    astro_rotation_t rot;
10569
10570    /*
10571        This rotation matrix was calculated by the following script
10572        in this same source code repository:
10573        demo/python/galeqj_matrix.py
10574    */
10575
10576    rot.rot[0][0] = -0.0548624779711344;
10577    rot.rot[0][1] = +0.4941095946388765;
10578    rot.rot[0][2] = -0.8676668813529025;
10579
10580    rot.rot[1][0] = -0.8734572784246782;
10581    rot.rot[1][1] = -0.4447938112296831;
10582    rot.rot[1][2] = -0.1980677870294097;
10583
10584    rot.rot[2][0] = -0.4838000529948520;
10585    rot.rot[2][1] = +0.7470034631630423;
10586    rot.rot[2][2] = +0.4559861124470794;
10587
10588    rot.status = ASTRO_SUCCESS;
10589
10590    return rot;
10591}
10592
10593/**
10594 * @brief
10595 *      Returns a rotation matrix from ecliptic galactic (GAL) to J2000 (EQJ).
10596 *
10597 * This is one of the family of functions that returns a rotation matrix
10598 * for converting from one orientation to another.
10599 * Source: GAL = galactic system (IAU 1958 definition).
10600 * Target: EQJ = equatorial system, using the equator at the J2000 epoch.
10601 *
10602 * @return
10603 *      A rotation matrix that converts GAL to EQJ.
10604 */
10605astro_rotation_t Astronomy_Rotation_GAL_EQJ(void)
10606{
10607    astro_rotation_t rot;
10608
10609    /*
10610        This rotation matrix was calculated by the following script
10611        in this same source code repository:
10612        demo/python/galeqj_matrix.py
10613    */
10614
10615    rot.rot[0][0] = -0.0548624779711344;
10616    rot.rot[0][1] = -0.8734572784246782;
10617    rot.rot[0][2] = -0.4838000529948520;
10618
10619    rot.rot[1][0] = +0.4941095946388765;
10620    rot.rot[1][1] = -0.4447938112296831;
10621    rot.rot[1][2] = +0.7470034631630423;
10622
10623    rot.rot[2][0] = -0.8676668813529025;
10624    rot.rot[2][1] = -0.1980677870294097;
10625    rot.rot[2][2] = +0.4559861124470794;
10626
10627    rot.status = ASTRO_SUCCESS;
10628
10629    return rot;
10630}
10631
10632
10633/**
10634 * @brief
10635 *      Returns a rotation matrix from true ecliptic of date (ECT) to equator of date (EQD).
10636 *
10637 * This is one of the family of functions that returns a rotation matrix
10638 * for converting from one orientation to another.
10639 * Source: ECT = true ecliptic of date.
10640 * Target: EQD = equator of date.
10641 *
10642 * @param time
10643 *      The date and time of the ecliptic/equator conversion.
10644 *
10645 * @return
10646 *      A rotation matrix that converts ECT to EQD.
10647 */
10648astro_rotation_t Astronomy_Rotation_ECT_EQD(astro_time_t *time)
10649{
10650    astro_rotation_t m;
10651    earth_tilt_t et;
10652    double tobl, cos_tobl, sin_tobl;
10653
10654    if (time == NULL)
10655        return RotationErr(ASTRO_INVALID_PARAMETER);
10656
10657    /* Find true ecliptic obliquity for this time. */
10658    et = e_tilt(time);
10659    tobl = et.tobl * DEG2RAD;
10660    cos_tobl = cos(tobl);
10661    sin_tobl = sin(tobl);
10662
10663    /* EQD.x = ECT.x */
10664    m.rot[0][0] = 1.0;
10665    m.rot[1][0] = 0.0;
10666    m.rot[2][0] = 0.0;
10667
10668    /* EQD.y = +cos*ECT.y - sin*ECT.z */
10669    m.rot[0][1] = 0.0;
10670    m.rot[1][1] = +cos_tobl;
10671    m.rot[2][1] = -sin_tobl;
10672
10673    /* EQD.z = +sin*ECT.y + cos*ECT.z */
10674    m.rot[0][2] = 0.0;
10675    m.rot[1][2] = +sin_tobl;
10676    m.rot[2][2] = +cos_tobl;
10677
10678    m.status = ASTRO_SUCCESS;
10679    return m;
10680}
10681
10682
10683/**
10684 * @brief
10685 *      Returns a rotation matrix from equator of date (EQD) to true ecliptic of date (ECT).
10686 *
10687 * This is one of the family of functions that returns a rotation matrix
10688 * for converting from one orientation to another.
10689 * Source: EQD = equator of date.
10690 * Target: ECT = true ecliptic of date.
10691 *
10692 * @param time
10693 *      The date and time of the equator/ecliptic conversion.
10694 *
10695 * @return
10696 *      A rotation matrix that converts EQD to ECT.
10697 */
10698astro_rotation_t Astronomy_Rotation_EQD_ECT(astro_time_t *time)
10699{
10700    astro_rotation_t m;
10701    earth_tilt_t et;
10702    double tobl, cos_tobl, sin_tobl;
10703
10704    if (time == NULL)
10705        return RotationErr(ASTRO_INVALID_PARAMETER);
10706
10707    /* Find true ecliptic obliquity for this time. */
10708    et = e_tilt(time);
10709    tobl = et.tobl * DEG2RAD;
10710    cos_tobl = cos(tobl);
10711    sin_tobl = sin(tobl);
10712
10713    /* ECT.x = EQD.x */
10714    m.rot[0][0] = 1.0;
10715    m.rot[1][0] = 0.0;
10716    m.rot[2][0] = 0.0;
10717
10718    /* ECT.y = +cos*EQJ.y + sin*EQJ.z */
10719    m.rot[0][1] = 0.0;
10720    m.rot[1][1] = +cos_tobl;
10721    m.rot[2][1] = +sin_tobl;
10722
10723    /* ECT.z = -sin*EQJ.y + cos*EQJ.z */
10724    m.rot[0][2] = 0.0;
10725    m.rot[1][2] = -sin_tobl;
10726    m.rot[2][2] = +cos_tobl;
10727
10728    m.status = ASTRO_SUCCESS;
10729    return m;
10730}
10731
10732
10733/** @cond DOXYGEN_SKIP */
10734typedef struct
10735{
10736    const char *symbol;
10737    const char *name;
10738}
10739constel_info_t;
10740
10741
10742typedef struct
10743{
10744    int    index;
10745    double ra_lo;
10746    double ra_hi;
10747    double dec_lo;
10748}
10749constel_boundary_t;
10750/** @endcond */
10751
10752#define NUM_CONSTELLATIONS   88
10753
10754static const constel_info_t ConstelInfo[] = {
10755    /*  0 */ { "And", "Andromeda"            }
10756,   /*  1 */ { "Ant", "Antila"               }
10757,   /*  2 */ { "Aps", "Apus"                 }
10758,   /*  3 */ { "Aql", "Aquila"               }
10759,   /*  4 */ { "Aqr", "Aquarius"             }
10760,   /*  5 */ { "Ara", "Ara"                  }
10761,   /*  6 */ { "Ari", "Aries"                }
10762,   /*  7 */ { "Aur", "Auriga"               }
10763,   /*  8 */ { "Boo", "Bootes"               }
10764,   /*  9 */ { "Cae", "Caelum"               }
10765,   /* 10 */ { "Cam", "Camelopardis"         }
10766,   /* 11 */ { "Cap", "Capricornus"          }
10767,   /* 12 */ { "Car", "Carina"               }
10768,   /* 13 */ { "Cas", "Cassiopeia"           }
10769,   /* 14 */ { "Cen", "Centaurus"            }
10770,   /* 15 */ { "Cep", "Cepheus"              }
10771,   /* 16 */ { "Cet", "Cetus"                }
10772,   /* 17 */ { "Cha", "Chamaeleon"           }
10773,   /* 18 */ { "Cir", "Circinus"             }
10774,   /* 19 */ { "CMa", "Canis Major"          }
10775,   /* 20 */ { "CMi", "Canis Minor"          }
10776,   /* 21 */ { "Cnc", "Cancer"               }
10777,   /* 22 */ { "Col", "Columba"              }
10778,   /* 23 */ { "Com", "Coma Berenices"       }
10779,   /* 24 */ { "CrA", "Corona Australis"     }
10780,   /* 25 */ { "CrB", "Corona Borealis"      }
10781,   /* 26 */ { "Crt", "Crater"               }
10782,   /* 27 */ { "Cru", "Crux"                 }
10783,   /* 28 */ { "Crv", "Corvus"               }
10784,   /* 29 */ { "CVn", "Canes Venatici"       }
10785,   /* 30 */ { "Cyg", "Cygnus"               }
10786,   /* 31 */ { "Del", "Delphinus"            }
10787,   /* 32 */ { "Dor", "Dorado"               }
10788,   /* 33 */ { "Dra", "Draco"                }
10789,   /* 34 */ { "Equ", "Equuleus"             }
10790,   /* 35 */ { "Eri", "Eridanus"             }
10791,   /* 36 */ { "For", "Fornax"               }
10792,   /* 37 */ { "Gem", "Gemini"               }
10793,   /* 38 */ { "Gru", "Grus"                 }
10794,   /* 39 */ { "Her", "Hercules"             }
10795,   /* 40 */ { "Hor", "Horologium"           }
10796,   /* 41 */ { "Hya", "Hydra"                }
10797,   /* 42 */ { "Hyi", "Hydrus"               }
10798,   /* 43 */ { "Ind", "Indus"                }
10799,   /* 44 */ { "Lac", "Lacerta"              }
10800,   /* 45 */ { "Leo", "Leo"                  }
10801,   /* 46 */ { "Lep", "Lepus"                }
10802,   /* 47 */ { "Lib", "Libra"                }
10803,   /* 48 */ { "LMi", "Leo Minor"            }
10804,   /* 49 */ { "Lup", "Lupus"                }
10805,   /* 50 */ { "Lyn", "Lynx"                 }
10806,   /* 51 */ { "Lyr", "Lyra"                 }
10807,   /* 52 */ { "Men", "Mensa"                }
10808,   /* 53 */ { "Mic", "Microscopium"         }
10809,   /* 54 */ { "Mon", "Monoceros"            }
10810,   /* 55 */ { "Mus", "Musca"                }
10811,   /* 56 */ { "Nor", "Norma"                }
10812,   /* 57 */ { "Oct", "Octans"               }
10813,   /* 58 */ { "Oph", "Ophiuchus"            }
10814,   /* 59 */ { "Ori", "Orion"                }
10815,   /* 60 */ { "Pav", "Pavo"                 }
10816,   /* 61 */ { "Peg", "Pegasus"              }
10817,   /* 62 */ { "Per", "Perseus"              }
10818,   /* 63 */ { "Phe", "Phoenix"              }
10819,   /* 64 */ { "Pic", "Pictor"               }
10820,   /* 65 */ { "PsA", "Pisces Austrinus"     }
10821,   /* 66 */ { "Psc", "Pisces"               }
10822,   /* 67 */ { "Pup", "Puppis"               }
10823,   /* 68 */ { "Pyx", "Pyxis"                }
10824,   /* 69 */ { "Ret", "Reticulum"            }
10825,   /* 70 */ { "Scl", "Sculptor"             }
10826,   /* 71 */ { "Sco", "Scorpius"             }
10827,   /* 72 */ { "Sct", "Scutum"               }
10828,   /* 73 */ { "Ser", "Serpens"              }
10829,   /* 74 */ { "Sex", "Sextans"              }
10830,   /* 75 */ { "Sge", "Sagitta"              }
10831,   /* 76 */ { "Sgr", "Sagittarius"          }
10832,   /* 77 */ { "Tau", "Taurus"               }
10833,   /* 78 */ { "Tel", "Telescopium"          }
10834,   /* 79 */ { "TrA", "Triangulum Australe"  }
10835,   /* 80 */ { "Tri", "Triangulum"           }
10836,   /* 81 */ { "Tuc", "Tucana"               }
10837,   /* 82 */ { "UMa", "Ursa Major"           }
10838,   /* 83 */ { "UMi", "Ursa Minor"           }
10839,   /* 84 */ { "Vel", "Vela"                 }
10840,   /* 85 */ { "Vir", "Virgo"                }
10841,   /* 86 */ { "Vol", "Volans"               }
10842,   /* 87 */ { "Vul", "Vulpecula"            }
10843};
10844
10845static const constel_boundary_t ConstelBounds[] = {
10846    { 83,      0,   8640,   2112 }    /* UMi */
10847,   { 83,   2880,   5220,   2076 }    /* UMi */
10848,   { 83,   7560,   8280,   2068 }    /* UMi */
10849,   { 83,   6480,   7560,   2064 }    /* UMi */
10850,   { 15,      0,   2880,   2040 }    /* Cep */
10851,   { 10,   3300,   3840,   1968 }    /* Cam */
10852,   { 15,      0,   1800,   1920 }    /* Cep */
10853,   { 10,   3840,   5220,   1920 }    /* Cam */
10854,   { 83,   6300,   6480,   1920 }    /* UMi */
10855,   { 33,   7260,   7560,   1920 }    /* Dra */
10856,   { 15,      0,   1263,   1848 }    /* Cep */
10857,   { 10,   4140,   4890,   1848 }    /* Cam */
10858,   { 83,   5952,   6300,   1800 }    /* UMi */
10859,   { 15,   7260,   7440,   1800 }    /* Cep */
10860,   { 10,   2868,   3300,   1764 }    /* Cam */
10861,   { 33,   3300,   4080,   1764 }    /* Dra */
10862,   { 83,   4680,   5952,   1680 }    /* UMi */
10863,   { 13,   1116,   1230,   1632 }    /* Cas */
10864,   { 33,   7350,   7440,   1608 }    /* Dra */
10865,   { 33,   4080,   4320,   1596 }    /* Dra */
10866,   { 15,      0,    120,   1584 }    /* Cep */
10867,   { 83,   5040,   5640,   1584 }    /* UMi */
10868,   { 15,   8490,   8640,   1584 }    /* Cep */
10869,   { 33,   4320,   4860,   1536 }    /* Dra */
10870,   { 33,   4860,   5190,   1512 }    /* Dra */
10871,   { 15,   8340,   8490,   1512 }    /* Cep */
10872,   { 10,   2196,   2520,   1488 }    /* Cam */
10873,   { 33,   7200,   7350,   1476 }    /* Dra */
10874,   { 15, 7393.2,   7416,   1462 }    /* Cep */
10875,   { 10,   2520,   2868,   1440 }    /* Cam */
10876,   { 82,   2868,   3030,   1440 }    /* UMa */
10877,   { 33,   7116,   7200,   1428 }    /* Dra */
10878,   { 15,   7200, 7393.2,   1428 }    /* Cep */
10879,   { 15,   8232,   8340,   1418 }    /* Cep */
10880,   { 13,      0,    876,   1404 }    /* Cas */
10881,   { 33,   6990,   7116,   1392 }    /* Dra */
10882,   { 13,    612,    687,   1380 }    /* Cas */
10883,   { 13,    876,   1116,   1368 }    /* Cas */
10884,   { 10,   1116,   1140,   1368 }    /* Cam */
10885,   { 15,   8034,   8232,   1350 }    /* Cep */
10886,   { 10,   1800,   2196,   1344 }    /* Cam */
10887,   { 82,   5052,   5190,   1332 }    /* UMa */
10888,   { 33,   5190,   6990,   1332 }    /* Dra */
10889,   { 10,   1140,   1200,   1320 }    /* Cam */
10890,   { 15,   7968,   8034,   1320 }    /* Cep */
10891,   { 15,   7416,   7908,   1316 }    /* Cep */
10892,   { 13,      0,    612,   1296 }    /* Cas */
10893,   { 50,   2196,   2340,   1296 }    /* Lyn */
10894,   { 82,   4350,   4860,   1272 }    /* UMa */
10895,   { 33,   5490,   5670,   1272 }    /* Dra */
10896,   { 15,   7908,   7968,   1266 }    /* Cep */
10897,   { 10,   1200,   1800,   1260 }    /* Cam */
10898,   { 13,   8232,   8400,   1260 }    /* Cas */
10899,   { 33,   5670,   6120,   1236 }    /* Dra */
10900,   { 62,    735,    906,   1212 }    /* Per */
10901,   { 33,   6120,   6564,   1212 }    /* Dra */
10902,   { 13,      0,    492,   1200 }    /* Cas */
10903,   { 62,    492,    600,   1200 }    /* Per */
10904,   { 50,   2340,   2448,   1200 }    /* Lyn */
10905,   { 13,   8400,   8640,   1200 }    /* Cas */
10906,   { 82,   4860,   5052,   1164 }    /* UMa */
10907,   { 13,      0,    402,   1152 }    /* Cas */
10908,   { 13,   8490,   8640,   1152 }    /* Cas */
10909,   { 39,   6543,   6564,   1140 }    /* Her */
10910,   { 33,   6564,   6870,   1140 }    /* Dra */
10911,   { 30,   6870,   6900,   1140 }    /* Cyg */
10912,   { 62,    600,    735,   1128 }    /* Per */
10913,   { 82,   3030,   3300,   1128 }    /* UMa */
10914,   { 13,     60,    312,   1104 }    /* Cas */
10915,   { 82,   4320,   4350,   1080 }    /* UMa */
10916,   { 50,   2448,   2652,   1068 }    /* Lyn */
10917,   { 30,   7887,   7908,   1056 }    /* Cyg */
10918,   { 30,   7875,   7887,   1050 }    /* Cyg */
10919,   { 30,   6900,   6984,   1044 }    /* Cyg */
10920,   { 82,   3300,   3660,   1008 }    /* UMa */
10921,   { 82,   3660,   3882,    960 }    /* UMa */
10922,   {  8,   5556,   5670,    960 }    /* Boo */
10923,   { 39,   5670,   5880,    960 }    /* Her */
10924,   { 50,   3330,   3450,    954 }    /* Lyn */
10925,   {  0,      0,    906,    882 }    /* And */
10926,   { 62,    906,    924,    882 }    /* Per */
10927,   { 51,   6969,   6984,    876 }    /* Lyr */
10928,   { 62,   1620,   1689,    864 }    /* Per */
10929,   { 30,   7824,   7875,    864 }    /* Cyg */
10930,   { 44,   7875,   7920,    864 }    /* Lac */
10931,   {  7,   2352,   2652,    852 }    /* Aur */
10932,   { 50,   2652,   2790,    852 }    /* Lyn */
10933,   {  0,      0,    720,    840 }    /* And */
10934,   { 44,   7920,   8214,    840 }    /* Lac */
10935,   { 44,   8214,   8232,    828 }    /* Lac */
10936,   {  0,   8232,   8460,    828 }    /* And */
10937,   { 62,    924,    978,    816 }    /* Per */
10938,   { 82,   3882,   3960,    816 }    /* UMa */
10939,   { 29,   4320,   4440,    816 }    /* CVn */
10940,   { 50,   2790,   3330,    804 }    /* Lyn */
10941,   { 48,   3330,   3558,    804 }    /* LMi */
10942,   {  0,    258,    507,    792 }    /* And */
10943,   {  8,   5466,   5556,    792 }    /* Boo */
10944,   {  0,   8460,   8550,    770 }    /* And */
10945,   { 29,   4440,   4770,    768 }    /* CVn */
10946,   {  0,   8550,   8640,    752 }    /* And */
10947,   { 29,   5025,   5052,    738 }    /* CVn */
10948,   { 80,    870,    978,    736 }    /* Tri */
10949,   { 62,    978,   1620,    736 }    /* Per */
10950,   {  7,   1620,   1710,    720 }    /* Aur */
10951,   { 51,   6543,   6969,    720 }    /* Lyr */
10952,   { 82,   3960,   4320,    696 }    /* UMa */
10953,   { 30,   7080,   7530,    696 }    /* Cyg */
10954,   {  7,   1710,   2118,    684 }    /* Aur */
10955,   { 48,   3558,   3780,    684 }    /* LMi */
10956,   { 29,   4770,   5025,    684 }    /* CVn */
10957,   {  0,      0,     24,    672 }    /* And */
10958,   { 80,    507,    600,    672 }    /* Tri */
10959,   {  7,   2118,   2352,    672 }    /* Aur */
10960,   { 37,   2838,   2880,    672 }    /* Gem */
10961,   { 30,   7530,   7824,    672 }    /* Cyg */
10962,   { 30,   6933,   7080,    660 }    /* Cyg */
10963,   { 80,    690,    870,    654 }    /* Tri */
10964,   { 25,   5820,   5880,    648 }    /* CrB */
10965,   {  8,   5430,   5466,    624 }    /* Boo */
10966,   { 25,   5466,   5820,    624 }    /* CrB */
10967,   { 51,   6612,   6792,    624 }    /* Lyr */
10968,   { 48,   3870,   3960,    612 }    /* LMi */
10969,   { 51,   6792,   6933,    612 }    /* Lyr */
10970,   { 80,    600,    690,    600 }    /* Tri */
10971,   { 66,    258,    306,    570 }    /* Psc */
10972,   { 48,   3780,   3870,    564 }    /* LMi */
10973,   { 87,   7650,   7710,    564 }    /* Vul */
10974,   { 77,   2052,   2118,    548 }    /* Tau */
10975,   {  0,     24,     51,    528 }    /* And */
10976,   { 73,   5730,   5772,    528 }    /* Ser */
10977,   { 37,   2118,   2238,    516 }    /* Gem */
10978,   { 87,   7140,   7290,    510 }    /* Vul */
10979,   { 87,   6792,   6930,    506 }    /* Vul */
10980,   {  0,     51,    306,    504 }    /* And */
10981,   { 87,   7290,   7404,    492 }    /* Vul */
10982,   { 37,   2811,   2838,    480 }    /* Gem */
10983,   { 87,   7404,   7650,    468 }    /* Vul */
10984,   { 87,   6930,   7140,    460 }    /* Vul */
10985,   {  6,   1182,   1212,    456 }    /* Ari */
10986,   { 75,   6792,   6840,    444 }    /* Sge */
10987,   { 59,   2052,   2076,    432 }    /* Ori */
10988,   { 37,   2238,   2271,    420 }    /* Gem */
10989,   { 75,   6840,   7140,    388 }    /* Sge */
10990,   { 77,   1788,   1920,    384 }    /* Tau */
10991,   { 39,   5730,   5790,    384 }    /* Her */
10992,   { 75,   7140,   7290,    378 }    /* Sge */
10993,   { 77,   1662,   1788,    372 }    /* Tau */
10994,   { 77,   1920,   2016,    372 }    /* Tau */
10995,   { 23,   4620,   4860,    360 }    /* Com */
10996,   { 39,   6210,   6570,    344 }    /* Her */
10997,   { 23,   4272,   4620,    336 }    /* Com */
10998,   { 37,   2700,   2811,    324 }    /* Gem */
10999,   { 39,   6030,   6210,    308 }    /* Her */
11000,   { 61,      0,     51,    300 }    /* Peg */
11001,   { 77,   2016,   2076,    300 }    /* Tau */
11002,   { 37,   2520,   2700,    300 }    /* Gem */
11003,   { 61,   7602,   7680,    300 }    /* Peg */
11004,   { 37,   2271,   2496,    288 }    /* Gem */
11005,   { 39,   6570,   6792,    288 }    /* Her */
11006,   { 31,   7515,   7578,    284 }    /* Del */
11007,   { 61,   7578,   7602,    284 }    /* Peg */
11008,   { 45,   4146,   4272,    264 }    /* Leo */
11009,   { 59,   2247,   2271,    240 }    /* Ori */
11010,   { 37,   2496,   2520,    240 }    /* Gem */
11011,   { 21,   2811,   2853,    240 }    /* Cnc */
11012,   { 61,   8580,   8640,    240 }    /* Peg */
11013,   {  6,    600,   1182,    238 }    /* Ari */
11014,   { 31,   7251,   7308,    204 }    /* Del */
11015,   {  8,   4860,   5430,    192 }    /* Boo */
11016,   { 61,   8190,   8580,    180 }    /* Peg */
11017,   { 21,   2853,   3330,    168 }    /* Cnc */
11018,   { 45,   3330,   3870,    168 }    /* Leo */
11019,   { 58,   6570, 6718.4,    150 }    /* Oph */
11020,   {  3, 6718.4,   6792,    150 }    /* Aql */
11021,   { 31,   7500,   7515,    144 }    /* Del */
11022,   { 20,   2520,   2526,    132 }    /* CMi */
11023,   { 73,   6570,   6633,    108 }    /* Ser */
11024,   { 39,   5790,   6030,     96 }    /* Her */
11025,   { 58,   6570,   6633,     72 }    /* Oph */
11026,   { 61,   7728,   7800,     66 }    /* Peg */
11027,   { 66,      0,    720,     48 }    /* Psc */
11028,   { 73,   6690,   6792,     48 }    /* Ser */
11029,   { 31,   7308,   7500,     48 }    /* Del */
11030,   { 34,   7500,   7680,     48 }    /* Equ */
11031,   { 61,   7680,   7728,     48 }    /* Peg */
11032,   { 61,   7920,   8190,     48 }    /* Peg */
11033,   { 61,   7800,   7920,     42 }    /* Peg */
11034,   { 20,   2526,   2592,     36 }    /* CMi */
11035,   { 77,   1290,   1662,      0 }    /* Tau */
11036,   { 59,   1662,   1680,      0 }    /* Ori */
11037,   { 20,   2592,   2910,      0 }    /* CMi */
11038,   { 85,   5280,   5430,      0 }    /* Vir */
11039,   { 58,   6420,   6570,      0 }    /* Oph */
11040,   { 16,    954,   1182,    -42 }    /* Cet */
11041,   { 77,   1182,   1290,    -42 }    /* Tau */
11042,   { 73,   5430,   5856,    -78 }    /* Ser */
11043,   { 59,   1680,   1830,    -96 }    /* Ori */
11044,   { 59,   2100,   2247,    -96 }    /* Ori */
11045,   { 73,   6420,   6468,    -96 }    /* Ser */
11046,   { 73,   6570,   6690,    -96 }    /* Ser */
11047,   {  3,   6690,   6792,    -96 }    /* Aql */
11048,   { 66,   8190,   8580,    -96 }    /* Psc */
11049,   { 45,   3870,   4146,   -144 }    /* Leo */
11050,   { 85,   4146,   4260,   -144 }    /* Vir */
11051,   { 66,      0,    120,   -168 }    /* Psc */
11052,   { 66,   8580,   8640,   -168 }    /* Psc */
11053,   { 85,   5130,   5280,   -192 }    /* Vir */
11054,   { 58,   5730,   5856,   -192 }    /* Oph */
11055,   {  3,   7200,   7392,   -216 }    /* Aql */
11056,   {  4,   7680,   7872,   -216 }    /* Aqr */
11057,   { 58,   6180,   6468,   -240 }    /* Oph */
11058,   { 54,   2100,   2910,   -264 }    /* Mon */
11059,   { 35,   1770,   1830,   -264 }    /* Eri */
11060,   { 59,   1830,   2100,   -264 }    /* Ori */
11061,   { 41,   2910,   3012,   -264 }    /* Hya */
11062,   { 74,   3450,   3870,   -264 }    /* Sex */
11063,   { 85,   4260,   4620,   -264 }    /* Vir */
11064,   { 58,   6330,   6360,   -280 }    /* Oph */
11065,   {  3,   6792,   7200, -288.8 }    /* Aql */
11066,   { 35,   1740,   1770,   -348 }    /* Eri */
11067,   {  4,   7392,   7680,   -360 }    /* Aqr */
11068,   { 73,   6180,   6570,   -384 }    /* Ser */
11069,   { 72,   6570,   6792,   -384 }    /* Sct */
11070,   { 41,   3012,   3090,   -408 }    /* Hya */
11071,   { 58,   5856,   5895,   -438 }    /* Oph */
11072,   { 41,   3090,   3270,   -456 }    /* Hya */
11073,   { 26,   3870,   3900,   -456 }    /* Crt */
11074,   { 71,   5856,   5895,   -462 }    /* Sco */
11075,   { 47,   5640,   5730,   -480 }    /* Lib */
11076,   { 28,   4530,   4620,   -528 }    /* Crv */
11077,   { 85,   4620,   5130,   -528 }    /* Vir */
11078,   { 41,   3270,   3510,   -576 }    /* Hya */
11079,   { 16,    600,    954, -585.2 }    /* Cet */
11080,   { 35,    954,   1350, -585.2 }    /* Eri */
11081,   { 26,   3900,   4260,   -588 }    /* Crt */
11082,   { 28,   4260,   4530,   -588 }    /* Crv */
11083,   { 47,   5130,   5370,   -588 }    /* Lib */
11084,   { 58,   5856,   6030,   -590 }    /* Oph */
11085,   { 16,      0,    600,   -612 }    /* Cet */
11086,   { 11,   7680,   7872,   -612 }    /* Cap */
11087,   {  4,   7872,   8580,   -612 }    /* Aqr */
11088,   { 16,   8580,   8640,   -612 }    /* Cet */
11089,   { 41,   3510,   3690,   -636 }    /* Hya */
11090,   { 35,   1692,   1740,   -654 }    /* Eri */
11091,   { 46,   1740,   2202,   -654 }    /* Lep */
11092,   { 11,   7200,   7680,   -672 }    /* Cap */
11093,   { 41,   3690,   3810,   -700 }    /* Hya */
11094,   { 41,   4530,   5370,   -708 }    /* Hya */
11095,   { 47,   5370,   5640,   -708 }    /* Lib */
11096,   { 71,   5640,   5760,   -708 }    /* Sco */
11097,   { 35,   1650,   1692,   -720 }    /* Eri */
11098,   { 58,   6030,   6336,   -720 }    /* Oph */
11099,   { 76,   6336,   6420,   -720 }    /* Sgr */
11100,   { 41,   3810,   3900,   -748 }    /* Hya */
11101,   { 19,   2202,   2652,   -792 }    /* CMa */
11102,   { 41,   4410,   4530,   -792 }    /* Hya */
11103,   { 41,   3900,   4410,   -840 }    /* Hya */
11104,   { 36,   1260,   1350,   -864 }    /* For */
11105,   { 68,   3012,   3372,   -882 }    /* Pyx */
11106,   { 35,   1536,   1650,   -888 }    /* Eri */
11107,   { 76,   6420,   6900,   -888 }    /* Sgr */
11108,   { 65,   7680,   8280,   -888 }    /* PsA */
11109,   { 70,   8280,   8400,   -888 }    /* Scl */
11110,   { 36,   1080,   1260,   -950 }    /* For */
11111,   {  1,   3372,   3960,   -954 }    /* Ant */
11112,   { 70,      0,    600,   -960 }    /* Scl */
11113,   { 36,    600,   1080,   -960 }    /* For */
11114,   { 35,   1392,   1536,   -960 }    /* Eri */
11115,   { 70,   8400,   8640,   -960 }    /* Scl */
11116,   { 14,   5100,   5370,  -1008 }    /* Cen */
11117,   { 49,   5640,   5760,  -1008 }    /* Lup */
11118,   { 71,   5760, 5911.5,  -1008 }    /* Sco */
11119,   {  9,   1740,   1800,  -1032 }    /* Cae */
11120,   { 22,   1800,   2370,  -1032 }    /* Col */
11121,   { 67,   2880,   3012,  -1032 }    /* Pup */
11122,   { 35,   1230,   1392,  -1056 }    /* Eri */
11123,   { 71, 5911.5,   6420,  -1092 }    /* Sco */
11124,   { 24,   6420,   6900,  -1092 }    /* CrA */
11125,   { 76,   6900,   7320,  -1092 }    /* Sgr */
11126,   { 53,   7320,   7680,  -1092 }    /* Mic */
11127,   { 35,   1080,   1230,  -1104 }    /* Eri */
11128,   {  9,   1620,   1740,  -1116 }    /* Cae */
11129,   { 49,   5520,   5640,  -1152 }    /* Lup */
11130,   { 63,      0,    840,  -1156 }    /* Phe */
11131,   { 35,    960,   1080,  -1176 }    /* Eri */
11132,   { 40,   1470,   1536,  -1176 }    /* Hor */
11133,   {  9,   1536,   1620,  -1176 }    /* Cae */
11134,   { 38,   7680,   7920,  -1200 }    /* Gru */
11135,   { 67,   2160,   2880,  -1218 }    /* Pup */
11136,   { 84,   2880,   2940,  -1218 }    /* Vel */
11137,   { 35,    870,    960,  -1224 }    /* Eri */
11138,   { 40,   1380,   1470,  -1224 }    /* Hor */
11139,   { 63,      0,    660,  -1236 }    /* Phe */
11140,   { 12,   2160,   2220,  -1260 }    /* Car */
11141,   { 84,   2940,   3042,  -1272 }    /* Vel */
11142,   { 40,   1260,   1380,  -1276 }    /* Hor */
11143,   { 32,   1380,   1440,  -1276 }    /* Dor */
11144,   { 63,      0,    570,  -1284 }    /* Phe */
11145,   { 35,    780,    870,  -1296 }    /* Eri */
11146,   { 64,   1620,   1800,  -1296 }    /* Pic */
11147,   { 49,   5418,   5520,  -1296 }    /* Lup */
11148,   { 84,   3042,   3180,  -1308 }    /* Vel */
11149,   { 12,   2220,   2340,  -1320 }    /* Car */
11150,   { 14,   4260,   4620,  -1320 }    /* Cen */
11151,   { 49,   5100,   5418,  -1320 }    /* Lup */
11152,   { 56,   5418,   5520,  -1320 }    /* Nor */
11153,   { 32,   1440,   1560,  -1356 }    /* Dor */
11154,   { 84,   3180,   3960,  -1356 }    /* Vel */
11155,   { 14,   3960,   4050,  -1356 }    /* Cen */
11156,   {  5,   6300,   6480,  -1368 }    /* Ara */
11157,   { 78,   6480,   7320,  -1368 }    /* Tel */
11158,   { 38,   7920,   8400,  -1368 }    /* Gru */
11159,   { 40,   1152,   1260,  -1380 }    /* Hor */
11160,   { 64,   1800,   1980,  -1380 }    /* Pic */
11161,   { 12,   2340,   2460,  -1392 }    /* Car */
11162,   { 63,      0,    480,  -1404 }    /* Phe */
11163,   { 35,    480,    780,  -1404 }    /* Eri */
11164,   { 63,   8400,   8640,  -1404 }    /* Phe */
11165,   { 32,   1560,   1650,  -1416 }    /* Dor */
11166,   { 56,   5520, 5911.5,  -1440 }    /* Nor */
11167,   { 43,   7320,   7680,  -1440 }    /* Ind */
11168,   { 64,   1980,   2160,  -1464 }    /* Pic */
11169,   { 18,   5460,   5520,  -1464 }    /* Cir */
11170,   {  5, 5911.5,   5970,  -1464 }    /* Ara */
11171,   { 18,   5370,   5460,  -1526 }    /* Cir */
11172,   {  5,   5970,   6030,  -1526 }    /* Ara */
11173,   { 64,   2160,   2460,  -1536 }    /* Pic */
11174,   { 12,   2460,   3252,  -1536 }    /* Car */
11175,   { 14,   4050,   4260,  -1536 }    /* Cen */
11176,   { 27,   4260,   4620,  -1536 }    /* Cru */
11177,   { 14,   4620,   5232,  -1536 }    /* Cen */
11178,   { 18,   4860,   4920,  -1560 }    /* Cir */
11179,   {  5,   6030,   6060,  -1560 }    /* Ara */
11180,   { 40,    780,   1152,  -1620 }    /* Hor */
11181,   { 69,   1152,   1650,  -1620 }    /* Ret */
11182,   { 18,   5310,   5370,  -1620 }    /* Cir */
11183,   {  5,   6060,   6300,  -1620 }    /* Ara */
11184,   { 60,   6300,   6480,  -1620 }    /* Pav */
11185,   { 81,   7920,   8400,  -1620 }    /* Tuc */
11186,   { 32,   1650,   2370,  -1680 }    /* Dor */
11187,   { 18,   4920,   5310,  -1680 }    /* Cir */
11188,   { 79,   5310,   6120,  -1680 }    /* TrA */
11189,   { 81,      0,    480,  -1800 }    /* Tuc */
11190,   { 42,   1260,   1650,  -1800 }    /* Hyi */
11191,   { 86,   2370,   3252,  -1800 }    /* Vol */
11192,   { 12,   3252,   4050,  -1800 }    /* Car */
11193,   { 55,   4050,   4920,  -1800 }    /* Mus */
11194,   { 60,   6480,   7680,  -1800 }    /* Pav */
11195,   { 43,   7680,   8400,  -1800 }    /* Ind */
11196,   { 81,   8400,   8640,  -1800 }    /* Tuc */
11197,   { 81,    270,    480,  -1824 }    /* Tuc */
11198,   { 42,      0,   1260,  -1980 }    /* Hyi */
11199,   { 17,   2760,   4920,  -1980 }    /* Cha */
11200,   {  2,   4920,   6480,  -1980 }    /* Aps */
11201,   { 52,   1260,   2760,  -2040 }    /* Men */
11202,   { 57,      0,   8640,  -2160 }    /* Oct */
11203};
11204
11205#define NUM_CONSTEL_BOUNDARIES  357
11206
11207
11208
11209/**
11210 * @brief
11211 *      Determines the constellation that contains the given point in the sky.
11212 *
11213 * Given J2000 equatorial (EQJ) coordinates of a point in the sky, determines the
11214 * constellation that contains that point.
11215 *
11216 * @param ra
11217 *      The right ascension (RA) of a point in the sky, using the J2000 equatorial system.
11218 *
11219 * @param dec
11220 *      The declination (DEC) of a point in the sky, using the J2000 equatorial system.
11221 *
11222 * @return
11223 *      If successful, `status` holds `ASTRO_SUCCESS`,
11224 *      `symbol` holds a pointer to a 3-character string like "Ori", and
11225 *      `name` holds a pointer to the full constellation name like "Orion".
11226 */
11227astro_constellation_t Astronomy_Constellation(double ra, double dec)
11228{
11229    static astro_time_t epoch2000;
11230    static astro_rotation_t rot = { ASTRO_NOT_INITIALIZED };
11231    astro_constellation_t constel;
11232    astro_spherical_t s2000;
11233    astro_equatorial_t b1875;
11234    astro_vector_t vec2000, vec1875;
11235    double x_ra, x_dec;
11236    int i, c;
11237
11238    if (dec < -90.0 || dec > +90.0)
11239        return ConstelErr(ASTRO_INVALID_PARAMETER);
11240
11241    /* Allow right ascension to "wrap around". Clamp to [0, 24) sidereal hours. */
11242    ra = fmod(ra, 24.0);
11243    if (ra < 0.0)
11244        ra += 24.0;
11245
11246    /* Lazy-initialize the rotation matrix for converting J2000 to B1875. */
11247    if (rot.status != ASTRO_SUCCESS)
11248    {
11249        /*
11250            Need to calculate the B1875 epoch. Based on this:
11251            https://en.wikipedia.org/wiki/Epoch_(astronomy)#Besselian_years
11252            B = 1900 + (JD - 2415020.31352) / 365.242198781
11253            I'm interested in using TT instead of JD, giving:
11254            B = 1900 + ((TT+2451545) - 2415020.31352) / 365.242198781
11255            B = 1900 + (TT + 36524.68648) / 365.242198781
11256            TT = 365.242198781*(B - 1900) - 36524.68648 = -45655.741449525
11257            But Astronomy_TimeFromDays() wants UT, not TT.
11258            Near that date, I get a historical correction of ut-tt = 3.2 seconds.
11259            That gives UT = -45655.74141261017 for the B1875 epoch,
11260            or 1874-12-31T18:12:21.950Z.
11261        */
11262        astro_time_t time = Astronomy_TimeFromDays(-45655.74141261017);
11263        rot = Astronomy_Rotation_EQJ_EQD(&time);
11264        if (rot.status != ASTRO_SUCCESS)
11265            return ConstelErr(rot.status);
11266
11267        epoch2000 = Astronomy_TimeFromDays(0.0);
11268    }
11269
11270    /* Convert coordinates from J2000 to year 1875. */
11271    s2000.status = ASTRO_SUCCESS;
11272    s2000.lon = ra * 15.0;
11273    s2000.lat = dec;
11274    s2000.dist = 1.0;
11275    vec2000 = Astronomy_VectorFromSphere(s2000, epoch2000);
11276    if (vec2000.status != ASTRO_SUCCESS)
11277        return ConstelErr(vec2000.status);
11278
11279    vec1875 = Astronomy_RotateVector(rot, vec2000);
11280    if (vec1875.status != ASTRO_SUCCESS)
11281        return ConstelErr(vec1875.status);
11282
11283    b1875 = Astronomy_EquatorFromVector(vec1875);
11284    if (b1875.status != ASTRO_SUCCESS)
11285        return ConstelErr(b1875.status);
11286
11287    /* Convert DEC from degrees, and RA from hours, to compact angle units used in the ContelBounds table. */
11288    x_ra = (24.0 * 15.0) * b1875.ra;
11289    x_dec = 24.0 * b1875.dec;
11290
11291    /* Search for the constellation using the B1875 coordinates. */
11292    c = -1;     /* constellation not (yet) found */
11293    for (i=0; i < NUM_CONSTEL_BOUNDARIES; ++i)
11294    {
11295        const constel_boundary_t *b = &ConstelBounds[i];
11296        if ((b->dec_lo <= x_dec) && (b->ra_hi > x_ra) && (b->ra_lo <= x_ra))
11297        {
11298            c = b->index;
11299            break;
11300        }
11301    }
11302
11303    if (c < 0 || c >= NUM_CONSTELLATIONS)
11304        return ConstelErr(ASTRO_INTERNAL_ERROR);    /* should have been able to find the constellation */
11305
11306    constel.status = ASTRO_SUCCESS;
11307    constel.symbol = ConstelInfo[c].symbol;
11308    constel.name = ConstelInfo[c].name;
11309    constel.ra_1875 = b1875.ra;
11310    constel.dec_1875 = b1875.dec;
11311    return constel;
11312}
11313
11314
11315static astro_lunar_eclipse_t LunarEclipseError(astro_status_t status)
11316{
11317    astro_lunar_eclipse_t eclipse;
11318    eclipse.status = status;
11319    eclipse.kind = ECLIPSE_NONE;
11320    eclipse.peak = TimeError();
11321    eclipse.obscuration = eclipse.sd_penum = eclipse.sd_partial = eclipse.sd_total = NAN;
11322    return eclipse;
11323}
11324
11325
11326/** @cond DOXYGEN_SKIP */
11327typedef struct
11328{
11329    astro_status_t status;
11330    astro_time_t time;
11331    double  u;              /* dot product of (heliocentric earth) and (geocentric moon): defines the shadow plane where the Moon is */
11332    double  r;              /* km distance between center of Moon/Earth (shaded body) and the line passing through the centers of the Sun and Earth/Moon (casting body). */
11333    double  k;              /* umbra radius in km, at the shadow plane */
11334    double  p;              /* penumbra radius in km, at the shadow plane */
11335    astro_vector_t target;  /* coordinates of target body relative to shadow-casting body at 'time' */
11336    astro_vector_t dir;     /* heliocentric coordinates of shadow-casting body at 'time' */
11337}
11338shadow_t;               /* Represents alignment of the Moon/Earth with the Earth's/Moon's shadow, for finding eclipses. */
11339
11340typedef struct
11341{
11342    double radius_limit;
11343    double direction;
11344}
11345shadow_context_t;
11346/** @endcond */
11347
11348
11349static double Obscuration(      /* returns area of intersection of the two discs, divided by area of first disc. */
11350    double a,   /* radius of first disc */
11351    double b,   /* radius of second disc */
11352    double c)   /* distance between the centers of the discs */
11353{
11354    double x;   /* Horizontal location of intersection point on both circumferences */
11355    double y;   /* Positive vertical location of intersection point on both circumferences */
11356    double radicand, lens1, lens2, obs;
11357
11358    if (a <= 0.0 || b <= 0.0)
11359        return 0.0;     /* invalid radius */
11360
11361    if (c < 0.0)
11362        return 0.0;     /* invalid distance between centers */
11363
11364    if (c >= a + b)
11365        return 0.0;     /* the discs are too far apart to have any overlapping area */
11366
11367    if (c == 0.0)
11368    {
11369        /* The discs have a common center. Therefore, one disc is inside the other. */
11370        return (a <= b) ? 1.0 : (b*b)/(a*a);
11371    }
11372
11373    x = (a*a - b*b + c*c) / (2 * c);
11374
11375    radicand = a*a - x*x;
11376    if (radicand <= 0.0)
11377    {
11378        /* The circumferences do not intersect, or are tangent. */
11379        /* We already ruled out the case of non-overlapping discs. */
11380        /* Therefore, one disc is inside the other. */
11381        return (a <= b) ? 1.0 : (b*b)/(a*a);
11382    }
11383
11384    /* The discs overlap fractionally in a pair of lens-shaped areas. */
11385
11386    y = sqrt(radicand);
11387
11388    /* Return the overlapping fractional area. */
11389    /* There are two lens-shaped areas, one to the left of x, the other to the right of x. */
11390    /* Each part is calculated by subtracting a triangular area from a sector's area. */
11391    lens1 = a*a*acos(x/a) - x*y;
11392    lens2 = b*b*acos((c-x)/b) - (c-x)*y;
11393
11394    /* Find the fractional area with respect to the first disc. */
11395    obs = (lens1 + lens2) / (PI*a*a);
11396    return obs;
11397}
11398
11399
11400static shadow_t ShadowError(astro_status_t status)
11401{
11402    shadow_t shadow;
11403    memset(&shadow, 0, sizeof(shadow));
11404    shadow.status = status;
11405    return shadow;
11406}
11407
11408
11409static shadow_t CalcShadow(
11410    double body_radius_km,
11411    astro_time_t time,
11412    astro_vector_t target,
11413    astro_vector_t dir)
11414{
11415    double dx, dy, dz;
11416    shadow_t shadow;
11417
11418    shadow.target = target;
11419    shadow.dir = dir;
11420
11421    shadow.u = (dir.x*target.x + dir.y*target.y + dir.z*target.z) / (dir.x*dir.x + dir.y*dir.y + dir.z*dir.z);
11422
11423    dx = (shadow.u * dir.x) - target.x;
11424    dy = (shadow.u * dir.y) - target.y;
11425    dz = (shadow.u * dir.z) - target.z;
11426    shadow.r = KM_PER_AU * sqrt(dx*dx + dy*dy + dz*dz);
11427
11428    shadow.k = +SUN_RADIUS_KM - (1.0 + shadow.u)*(SUN_RADIUS_KM - body_radius_km);
11429    shadow.p = -SUN_RADIUS_KM + (1.0 + shadow.u)*(SUN_RADIUS_KM + body_radius_km);
11430    shadow.status = ASTRO_SUCCESS;
11431    shadow.time = time;
11432
11433    return shadow;
11434}
11435
11436
11437static shadow_t PlanetShadow(astro_body_t body, double planet_radius_km, astro_time_t time)
11438{
11439    astro_vector_t e, p, g;
11440
11441    /* Calculate light-travel-corrected vector from Earth to planet. */
11442    g = Astronomy_GeoVector(body, time, ABERRATION);
11443    if (g.status != ASTRO_SUCCESS)
11444        return ShadowError(g.status);
11445
11446    /* Calculate light-travel-corrected vector from Earth to Sun. */
11447    e = Astronomy_GeoVector(BODY_SUN, time, ABERRATION);
11448    if (e.status != ASTRO_SUCCESS)
11449        return ShadowError(e.status);
11450
11451    /* Deduce light-travel-corrected vector from Sun to planet. */
11452    p.status = ASTRO_SUCCESS;
11453    p.t = time;
11454    p.x = g.x - e.x;
11455    p.y = g.y - e.y;
11456    p.z = g.z - e.z;
11457
11458    /* Calcluate Earth's position from the planet's point of view. */
11459    e.x = -g.x;
11460    e.y = -g.y;
11461    e.z = -g.z;
11462
11463    return CalcShadow(planet_radius_km, time, e, p);
11464}
11465
11466
11467static shadow_t EarthShadow(astro_time_t time)
11468{
11469    /* This function helps find when the Earth's shadow falls upon the Moon. */
11470    astro_vector_t e, m;
11471
11472    /* Find geocentric Sun with aberration correction. */
11473    e = Astronomy_GeoVector(BODY_SUN, time, ABERRATION);
11474    if (e.status != ASTRO_SUCCESS)
11475        return ShadowError(e.status);
11476
11477    /* Convert geocentric Sun to heliocentric Earth. */
11478    /* Thus `e` points in the direction of sunlight heading toward the Earth's center. */
11479    e.x = -e.x;
11480    e.y = -e.y;
11481    e.z = -e.z;
11482
11483    /* Find geocentric Moon. */
11484    m = Astronomy_GeoMoon(time);    /* This function never fails; no need to check return value */
11485
11486    return CalcShadow(EARTH_ECLIPSE_RADIUS_KM, time, m, e);
11487}
11488
11489
11490static shadow_t MoonShadow(astro_time_t time)
11491{
11492    /* This function helps find when the Moon's shadow falls upon the Earth. */
11493
11494    astro_vector_t s, e, m;
11495
11496    /* Calculate geocentric Sun with aberration correction. */
11497    s = Astronomy_GeoVector(BODY_SUN, time, ABERRATION);
11498    if (s.status != ASTRO_SUCCESS)
11499        return ShadowError(s.status);
11500
11501    m = Astronomy_GeoMoon(time);    /* geocentric Moon */
11502
11503    /* Calculate lunacentric Earth. */
11504    e.status = m.status;
11505    e.x = -m.x;
11506    e.y = -m.y;
11507    e.z = -m.z;
11508    e.t = m.t;
11509
11510    /* Convert geocentric moon to heliocentric Moon. */
11511    m.x -= s.x;
11512    m.y -= s.y;
11513    m.z -= s.z;
11514
11515    return CalcShadow(MOON_MEAN_RADIUS_KM, time, e, m);
11516}
11517
11518
11519/** @cond DOXYGEN_SKIP */
11520typedef shadow_t (* shadow_func_t) (astro_time_t time);
11521/** @endcond */
11522
11523
11524static astro_func_result_t shadow_distance_slope(void *context, astro_time_t time)
11525{
11526    const double dt = 1.0 / 86400.0;
11527    astro_time_t t1, t2;
11528    astro_func_result_t result;
11529    shadow_t shadow1, shadow2;
11530    shadow_func_t shadowfunc = (shadow_func_t) context;
11531
11532    t1 = Astronomy_AddDays(time, -dt);
11533    t2 = Astronomy_AddDays(time, +dt);
11534
11535    shadow1 = shadowfunc(t1);
11536    if (shadow1.status != ASTRO_SUCCESS)
11537        return FuncError(shadow1.status);
11538
11539    shadow2 = shadowfunc(t2);
11540    if (shadow2.status != ASTRO_SUCCESS)
11541        return FuncError(shadow2.status);
11542
11543    result.value = (shadow2.r - shadow1.r) / dt;
11544    result.status = ASTRO_SUCCESS;
11545    return result;
11546}
11547
11548
11549static shadow_t PeakEarthShadow(astro_time_t search_center_time)
11550{
11551    /* Search for when the Earth's shadow axis is closest to the center of the Moon. */
11552
11553    astro_time_t t1, t2;
11554    astro_search_result_t result;
11555    const double window = 0.03;        /* days before/after full moon to search for minimum shadow distance */
11556
11557    t1 = Astronomy_AddDays(search_center_time, -window);
11558    t2 = Astronomy_AddDays(search_center_time, +window);
11559
11560    result = Astronomy_Search(shadow_distance_slope, (void *)EarthShadow, t1, t2, 1.0);
11561    if (result.status != ASTRO_SUCCESS)
11562        return ShadowError(result.status);
11563
11564    return EarthShadow(result.time);
11565}
11566
11567
11568static shadow_t PeakMoonShadow(astro_time_t search_center_time)
11569{
11570    /* Search for when the Moon's shadow axis is closest to the center of the Earth. */
11571
11572    astro_time_t t1, t2;
11573    astro_search_result_t result;
11574    const double window = 0.03;     /* days before/after new moon to search for minimum shadow distance */
11575
11576    t1 = Astronomy_AddDays(search_center_time, -window);
11577    t2 = Astronomy_AddDays(search_center_time, +window);
11578
11579    result = Astronomy_Search(shadow_distance_slope, (void *)MoonShadow, t1, t2, 1.0);
11580    if (result.status != ASTRO_SUCCESS)
11581        return ShadowError(result.status);
11582
11583    return MoonShadow(result.time);
11584}
11585
11586
11587/** @cond DOXYGEN_SKIP */
11588typedef struct
11589{
11590    astro_body_t    body;
11591    double          planet_radius_km;
11592    double          direction;          /* used for transit start/finish search only */
11593}
11594planet_shadow_context_t;
11595/** @endcond */
11596
11597
11598static astro_func_result_t planet_shadow_distance_slope(void *context, astro_time_t time)
11599{
11600    const double dt = 1.0 / 86400.0;
11601    astro_time_t t1, t2;
11602    astro_func_result_t result;
11603    shadow_t shadow1, shadow2;
11604    const planet_shadow_context_t *p = (const planet_shadow_context_t *) context;
11605
11606    t1 = Astronomy_AddDays(time, -dt);
11607    t2 = Astronomy_AddDays(time, +dt);
11608
11609    shadow1 = PlanetShadow(p->body, p->planet_radius_km, t1);
11610    if (shadow1.status != ASTRO_SUCCESS)
11611        return FuncError(shadow1.status);
11612
11613    shadow2 = PlanetShadow(p->body, p->planet_radius_km, t2);
11614    if (shadow2.status != ASTRO_SUCCESS)
11615        return FuncError(shadow2.status);
11616
11617    result.value = (shadow2.r - shadow1.r) / dt;
11618    result.status = ASTRO_SUCCESS;
11619    return result;
11620}
11621
11622
11623static shadow_t PeakPlanetShadow(astro_body_t body, double planet_radius_km, astro_time_t search_center_time)
11624{
11625    /* Search for when the body's shadow is closest to the center of the Earth. */
11626
11627    astro_time_t t1, t2;
11628    astro_search_result_t result;
11629    planet_shadow_context_t context;
11630    const double window = 1.0;     /* days before/after inferior conjunction to search for minimum shadow distance */
11631
11632    t1 = Astronomy_AddDays(search_center_time, -window);
11633    t2 = Astronomy_AddDays(search_center_time, +window);
11634
11635    context.body = body;
11636    context.planet_radius_km = planet_radius_km;
11637    context.direction = 0.0;    /* not used in this search */
11638
11639    result = Astronomy_Search(planet_shadow_distance_slope, &context, t1, t2, 1.0);
11640    if (result.status != ASTRO_SUCCESS)
11641        return ShadowError(result.status);
11642
11643    return PlanetShadow(body, planet_radius_km, result.time);
11644}
11645
11646
11647static astro_func_result_t shadow_distance(void *context, astro_time_t time)
11648{
11649    astro_func_result_t result;
11650    const shadow_context_t *p = (const shadow_context_t *) context;
11651    shadow_t shadow = EarthShadow(time);
11652    if (shadow.status != ASTRO_SUCCESS)
11653        return FuncError(shadow.status);
11654
11655    result.value = p->direction * (shadow.r - p->radius_limit);
11656    result.status = ASTRO_SUCCESS;
11657    return result;
11658}
11659
11660
11661static double ShadowSemiDurationMinutes(astro_time_t center_time, double radius_limit, double window_minutes)
11662{
11663    /* Search backwards and forwards from the center time until shadow axis distance crosses radius limit. */
11664    double window = window_minutes / (24.0 * 60.0);
11665    shadow_context_t context;
11666    astro_search_result_t s1, s2;
11667    astro_time_t before, after;
11668
11669    before = Astronomy_AddDays(center_time, -window);
11670    after  = Astronomy_AddDays(center_time, +window);
11671
11672    context.radius_limit = radius_limit;
11673    context.direction = -1.0;
11674    s1 = Astronomy_Search(shadow_distance, &context, before, center_time, 1.0);
11675
11676    context.direction = +1.0;
11677    s2 = Astronomy_Search(shadow_distance, &context, center_time, after, 1.0);
11678
11679    if (s1.status != ASTRO_SUCCESS || s2.status != ASTRO_SUCCESS)
11680        return -1.0;    /* something went wrong! */
11681
11682    return (s2.time.ut - s1.time.ut) * ((24.0 * 60.0) / 2.0);       /* convert days to minutes and average the semi-durations. */
11683}
11684
11685
11686/**
11687 * @brief Searches for a lunar eclipse.
11688 *
11689 * This function finds the first lunar eclipse that occurs after `startTime`.
11690 * A lunar eclipse may be penumbral, partial, or total.
11691 * See #astro_lunar_eclipse_t for more information.
11692 * To find a series of lunar eclipses, call this function once,
11693 * then keep calling #Astronomy_NextLunarEclipse as many times as desired,
11694 * passing in the `peak` value returned from the previous call.
11695 *
11696 * @param startTime
11697 *      The date and time for starting the search for a lunar eclipse.
11698 *
11699 * @return
11700 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
11701 *      and the remaining structure fields will be valid.
11702 *      Any other value indicates an error.
11703 */
11704astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime)
11705{
11706    const double PruneLatitude = 1.8;   /* full Moon's ecliptic latitude above which eclipse is impossible */
11707    astro_time_t fmtime;
11708    astro_lunar_eclipse_t eclipse;
11709    astro_search_result_t fullmoon;
11710    shadow_t shadow;
11711    int fmcount;
11712    double eclip_lat, eclip_lon, distance;
11713
11714    /* Iterate through consecutive full moons until we find any kind of lunar eclipse. */
11715    fmtime = startTime;
11716    for (fmcount=0; fmcount < 12; ++fmcount)
11717    {
11718        /* Search for the next full moon. Any eclipse will be near it. */
11719        fullmoon = Astronomy_SearchMoonPhase(180.0, fmtime, 40.0);
11720        if (fullmoon.status != ASTRO_SUCCESS)
11721            return LunarEclipseError(fullmoon.status);
11722
11723        /* Pruning: if the full Moon's ecliptic latitude is too large, a lunar eclipse is not possible. */
11724        CalcMoon(fullmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance);
11725        if (RAD2DEG * fabs(eclip_lat) < PruneLatitude)
11726        {
11727            /* Search near the full moon for the time when the center of the Moon */
11728            /* is closest to the line passing through the centers of the Sun and Earth. */
11729            shadow = PeakEarthShadow(fullmoon.time);
11730            if (shadow.status != ASTRO_SUCCESS)
11731                return LunarEclipseError(shadow.status);
11732
11733            if (shadow.r < shadow.p + MOON_MEAN_RADIUS_KM)
11734            {
11735                /* This is at least a penumbral eclipse. We will return a result. */
11736                eclipse.status = ASTRO_SUCCESS;
11737                eclipse.kind = ECLIPSE_PENUMBRAL;
11738                eclipse.obscuration = 0.0;
11739                eclipse.peak = shadow.time;
11740                eclipse.sd_total = 0.0;
11741                eclipse.sd_partial = 0.0;
11742                eclipse.sd_penum = ShadowSemiDurationMinutes(shadow.time, shadow.p + MOON_MEAN_RADIUS_KM, 200.0);
11743                if (eclipse.sd_penum <= 0.0)
11744                    return LunarEclipseError(ASTRO_SEARCH_FAILURE);
11745
11746                if (shadow.r < shadow.k + MOON_MEAN_RADIUS_KM)
11747                {
11748                    /* This is at least a partial eclipse. */
11749                    eclipse.kind = ECLIPSE_PARTIAL;
11750                    eclipse.sd_partial = ShadowSemiDurationMinutes(shadow.time, shadow.k + MOON_MEAN_RADIUS_KM, eclipse.sd_penum);
11751                    if (eclipse.sd_partial <= 0.0)
11752                        return LunarEclipseError(ASTRO_SEARCH_FAILURE);
11753
11754                    if (shadow.r + MOON_MEAN_RADIUS_KM < shadow.k)
11755                    {
11756                        /* This is a total eclipse. */
11757                        eclipse.kind = ECLIPSE_TOTAL;
11758                        eclipse.obscuration = 1.0;
11759                        eclipse.sd_total = ShadowSemiDurationMinutes(shadow.time, shadow.k - MOON_MEAN_RADIUS_KM, eclipse.sd_partial);
11760                        if (eclipse.sd_total <= 0.0)
11761                            return LunarEclipseError(ASTRO_SEARCH_FAILURE);
11762                    }
11763                    else
11764                    {
11765                        /* For lunar eclipses, we calculate the fraction of the Moon's disc covered by the Earth's umbra. */
11766                        eclipse.obscuration = Obscuration(MOON_MEAN_RADIUS_KM, shadow.k, shadow.r);
11767                    }
11768                }
11769                return eclipse;
11770            }
11771        }
11772
11773        /* We didn't find an eclipse on this full moon, so search for the next one. */
11774        fmtime = Astronomy_AddDays(fullmoon.time, 10.0);
11775    }
11776
11777    /* Safety valve to prevent infinite loop. */
11778    /* This should never happen, because at least 2 lunar eclipses happen per year. */
11779    return LunarEclipseError(ASTRO_INTERNAL_ERROR);
11780}
11781
11782/**
11783 * @brief Searches for the next lunar eclipse in a series.
11784 *
11785 * After using #Astronomy_SearchLunarEclipse to find the first lunar eclipse
11786 * in a series, you can call this function to find the next consecutive lunar eclipse.
11787 * Pass in the `peak` value from the #astro_lunar_eclipse_t returned by the
11788 * previous call to `Astronomy_SearchLunarEclipse` or `Astronomy_NextLunarEclipse`
11789 * to find the next lunar eclipse.
11790 *
11791 * @param prevEclipseTime
11792 *      A date and time near a full moon. Lunar eclipse search will start at the next full moon.
11793 *
11794 * @return
11795 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
11796 *      and the remaining structure fields will be valid.
11797 *      Any other value indicates an error.
11798 */
11799astro_lunar_eclipse_t Astronomy_NextLunarEclipse(astro_time_t prevEclipseTime)
11800{
11801    astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0);
11802    return Astronomy_SearchLunarEclipse(startTime);
11803}
11804
11805
11806static astro_global_solar_eclipse_t GlobalSolarEclipseError(astro_status_t status)
11807{
11808    astro_global_solar_eclipse_t eclipse;
11809
11810    eclipse.status = status;
11811    eclipse.kind = ECLIPSE_NONE;
11812    eclipse.peak = TimeError();
11813    eclipse.obscuration = eclipse.distance = eclipse.latitude = eclipse.longitude = NAN;
11814
11815    return eclipse;
11816}
11817
11818/* The umbra radius tells us what kind of eclipse the observer sees. */
11819/* If the umbra radius is positive, this is a total eclipse. Otherwise, it's annular. */
11820/* HACK: I added a tiny bias (14 meters) to match Espenak test data. */
11821#define EclipseKindFromUmbra(k)     (((k) > 0.014) ? ECLIPSE_TOTAL : ECLIPSE_ANNULAR)
11822
11823static double SolarEclipseObscuration(
11824    astro_vector_t hm,     /* heliocentric Moon */
11825    astro_vector_t lo)     /* lunacentric observer */
11826{
11827    astro_vector_t ho;     /* heliocentric observer */
11828    astro_angle_result_t sun_moon_separation;
11829    double obscuration, sun_au, sun_radius, moon_radius;
11830
11831    /* Find heliocentric observer. */
11832    ho.status = ASTRO_SUCCESS;
11833    ho.t = lo.t;
11834    ho.x = hm.x + lo.x;
11835    ho.y = hm.y + lo.y;
11836    ho.z = hm.z + lo.z;
11837
11838    /* Find the distance from the Sun's center to the observer. */
11839    sun_au = Astronomy_VectorLength(ho);
11840
11841    /* Calculate the apparent angular radius of the Sun for the observer. */
11842    sun_radius = asin(SUN_RADIUS_AU / sun_au);
11843
11844    /* Calculate the apparent angular radius of the Moon for the observer. */
11845    moon_radius = asin(MOON_POLAR_RADIUS_AU / Astronomy_VectorLength(lo));
11846
11847    /* Calculate the apparent angular separation between the Sun's center and the Moon's center. */
11848    sun_moon_separation = Astronomy_AngleBetween(lo, ho);
11849
11850    if (sun_moon_separation.status != ASTRO_SUCCESS)
11851        return -1.0;    /* internal error! should never happen. */
11852
11853    obscuration = Obscuration(sun_radius, moon_radius, sun_moon_separation.angle * DEG2RAD);
11854
11855    /* HACK: In marginal cases, we need to clamp obscuration to less than 1.0. */
11856    if (obscuration > 0.9999)
11857        obscuration = 0.9999;
11858
11859    return obscuration;
11860}
11861
11862static astro_global_solar_eclipse_t GeoidIntersect(shadow_t shadow)
11863{
11864    astro_global_solar_eclipse_t eclipse;
11865    astro_rotation_t rot, inv;
11866    astro_vector_t v, e, o;
11867    shadow_t surface;
11868    double A, B, C, radic, u, R;
11869    double px, py, pz, proj;
11870    double gast;
11871
11872    eclipse.status = ASTRO_SUCCESS;
11873    eclipse.kind = ECLIPSE_PARTIAL;
11874    eclipse.peak = shadow.time;
11875    eclipse.distance = shadow.r;
11876    eclipse.latitude = eclipse.longitude = NAN;
11877
11878    /*
11879        We want to calculate the intersection of the shadow axis with the Earth's geoid.
11880        First we must convert EQJ (equator of J2000) coordinates to EQD (equator of date)
11881        coordinates that are perfectly aligned with the Earth's equator at this
11882        moment in time.
11883    */
11884    rot = Astronomy_Rotation_EQJ_EQD(&shadow.time);
11885    if (rot.status != ASTRO_SUCCESS)
11886        return GlobalSolarEclipseError(rot.status);
11887
11888    v = Astronomy_RotateVector(rot, shadow.dir);        /* shadow-axis vector in equator-of-date coordinates */
11889    if (v.status != ASTRO_SUCCESS)
11890        return GlobalSolarEclipseError(v.status);
11891
11892    e = Astronomy_RotateVector(rot, shadow.target);     /* lunacentric Earth in equator-of-date coordinates */
11893    if (e.status != ASTRO_SUCCESS)
11894        return GlobalSolarEclipseError(e.status);
11895
11896    /*
11897        Convert all distances from AU to km.
11898        But dilate the z-coordinates so that the Earth becomes a perfect sphere.
11899        Then find the intersection of the vector with the sphere.
11900        See p 184 in Montenbruck & Pfleger's "Astronomy on the Personal Computer", second edition.
11901    */
11902    v.x *= KM_PER_AU;
11903    v.y *= KM_PER_AU;
11904    v.z *= KM_PER_AU / EARTH_FLATTENING;
11905
11906    e.x *= KM_PER_AU;
11907    e.y *= KM_PER_AU;
11908    e.z *= KM_PER_AU / EARTH_FLATTENING;
11909
11910    /*
11911        Solve the quadratic equation that finds whether and where
11912        the shadow axis intersects with the Earth in the dilated coordinate system.
11913    */
11914    R = EARTH_EQUATORIAL_RADIUS_KM;
11915    A = v.x*v.x + v.y*v.y + v.z*v.z;
11916    B = -2.0 * (v.x*e.x + v.y*e.y + v.z*e.z);
11917    C = (e.x*e.x + e.y*e.y + e.z*e.z) - R*R;
11918    radic = B*B - 4*A*C;
11919
11920    if (radic > 0.0)
11921    {
11922        /* Calculate the closer of the two intersection points. */
11923        /* This will be on the day side of the Earth. */
11924        u = (-B - sqrt(radic)) / (2 * A);
11925
11926        /* Convert lunacentric dilated coordinates to geocentric coordinates. */
11927        px = u*v.x - e.x;
11928        py = u*v.y - e.y;
11929        pz = (u*v.z - e.z) * EARTH_FLATTENING;
11930
11931        /* Convert cartesian coordinates into geodetic latitude/longitude. */
11932        proj = hypot(px, py) * (EARTH_FLATTENING * EARTH_FLATTENING);
11933        if (proj == 0.0)
11934            eclipse.latitude = (pz > 0.0) ? +90.0 : -90.0;
11935        else
11936            eclipse.latitude = RAD2DEG * atan(pz / proj);
11937
11938        /* Adjust longitude for Earth's rotation at the given UT. */
11939        gast = Astronomy_SiderealTime(&eclipse.peak);
11940        eclipse.longitude = fmod((RAD2DEG*atan2(py, px)) - (15*gast), 360.0);
11941        if (eclipse.longitude <= -180.0)
11942            eclipse.longitude += 360.0;
11943        else if (eclipse.longitude > +180.0)
11944            eclipse.longitude -= 360.0;
11945
11946        /* We want to determine whether the observer sees a total eclipse or an annular eclipse. */
11947        /* We need to perform a series of vector calculations... */
11948        /* Calculate the inverse rotation matrix, so we can convert EQD to EQJ. */
11949        inv = Astronomy_InverseRotation(rot);
11950        if (inv.status != ASTRO_SUCCESS)
11951            return GlobalSolarEclipseError(inv.status);
11952
11953        /* Put the EQD geocentric coordinates of the observer into the vector 'o'. */
11954        /* Also convert back from kilometers to astronomical units. */
11955        o.status = ASTRO_SUCCESS;
11956        o.t = shadow.time;
11957        o.x = px / KM_PER_AU;
11958        o.y = py / KM_PER_AU;
11959        o.z = pz / KM_PER_AU;
11960
11961        /* Rotate the observer's geocentric EQD back to the EQJ system. */
11962        o = Astronomy_RotateVector(inv, o);
11963
11964        /* Convert geocentric vector to lunacentric vector. */
11965        o.x += shadow.target.x;
11966        o.y += shadow.target.y;
11967        o.z += shadow.target.z;
11968
11969        /* Recalculate the shadow using a vector from the Moon's center toward the observer. */
11970        surface = CalcShadow(MOON_POLAR_RADIUS_KM, shadow.time, o, shadow.dir);
11971
11972        /* If we did everything right, the shadow distance should be very close to zero. */
11973        /* That's because we already determined the observer 'o' is on the shadow axis! */
11974        if (surface.r > 1.0e-9 || surface.r < 0.0)
11975            return GlobalSolarEclipseError(ASTRO_INTERNAL_ERROR);
11976
11977        eclipse.kind = EclipseKindFromUmbra(surface.k);
11978        if (eclipse.kind == ECLIPSE_TOTAL)
11979            eclipse.obscuration = 1.0;
11980        else
11981            eclipse.obscuration = SolarEclipseObscuration(shadow.dir, o);
11982    }
11983    else
11984    {
11985        /* This is a partial solar eclipse. It does not make practical sense to calculate obscuration. */
11986        /* Anyone who wants obscuration should use Astronomy_SearchLocalSolarEclipse for a specific location on the Earth. */
11987        eclipse.obscuration = NAN;
11988    }
11989
11990    return eclipse;
11991}
11992
11993
11994/**
11995 * @brief Searches for a solar eclipse visible anywhere on the Earth's surface.
11996 *
11997 * This function finds the first solar eclipse that occurs after `startTime`.
11998 * A solar eclipse may be partial, annular, or total.
11999 * See #astro_global_solar_eclipse_t for more information.
12000 * To find a series of solar eclipses, call this function once,
12001 * then keep calling #Astronomy_NextGlobalSolarEclipse as many times as desired,
12002 * passing in the `peak` value returned from the previous call.
12003 *
12004 * @param startTime
12005 *      The date and time for starting the search for a solar eclipse.
12006 *
12007 * @return
12008 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
12009 *      and the remaining structure fields are as described in #astro_global_solar_eclipse_t.
12010 *      Any other value indicates an error.
12011 */
12012astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t startTime)
12013{
12014    const double PruneLatitude = 1.8;   /* Moon's ecliptic latitude beyond which eclipse is impossible */
12015    astro_time_t nmtime;
12016    astro_search_result_t newmoon;
12017    shadow_t shadow;
12018    int nmcount;
12019    double eclip_lat, eclip_lon, distance;
12020
12021    /* Iterate through consecutive new moons until we find a solar eclipse visible somewhere on Earth. */
12022    nmtime = startTime;
12023    for (nmcount=0; nmcount < 12; ++nmcount)
12024    {
12025        /* Search for the next new moon. Any eclipse will be near it. */
12026        newmoon = Astronomy_SearchMoonPhase(0.0, nmtime, 40.0);
12027        if (newmoon.status != ASTRO_SUCCESS)
12028            return GlobalSolarEclipseError(newmoon.status);
12029
12030        /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */
12031        CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance);
12032        if (RAD2DEG * fabs(eclip_lat) < PruneLatitude)
12033        {
12034            /* Search near the new moon for the time when the center of the Earth */
12035            /* is closest to the line passing through the centers of the Sun and Moon. */
12036            shadow = PeakMoonShadow(newmoon.time);
12037            if (shadow.status != ASTRO_SUCCESS)
12038                return GlobalSolarEclipseError(shadow.status);
12039
12040            if (shadow.r < shadow.p + EARTH_MEAN_RADIUS_KM)
12041            {
12042                /* This is at least a partial solar eclipse visible somewhere on Earth. */
12043                /* Try to find an intersection between the shadow axis and the Earth's oblate geoid. */
12044                return GeoidIntersect(shadow);
12045            }
12046        }
12047
12048        /* We didn't find an eclipse on this new moon, so search for the next one. */
12049        nmtime = Astronomy_AddDays(newmoon.time, 10.0);
12050    }
12051
12052    /* Safety valve to prevent infinite loop. */
12053    /* This should never happen, because at least 2 solar eclipses happen per year. */
12054    return GlobalSolarEclipseError(ASTRO_INTERNAL_ERROR);
12055}
12056
12057
12058/**
12059 * @brief Searches for the next global solar eclipse in a series.
12060 *
12061 * After using #Astronomy_SearchGlobalSolarEclipse to find the first solar eclipse
12062 * in a series, you can call this function to find the next consecutive solar eclipse.
12063 * Pass in the `peak` value from the #astro_global_solar_eclipse_t returned by the
12064 * previous call to `Astronomy_SearchGlobalSolarEclipse` or `Astronomy_NextGlobalSolarEclipse`
12065 * to find the next solar eclipse.
12066 *
12067 * @param prevEclipseTime
12068 *      A date and time near a new moon. Solar eclipse search will start at the next new moon.
12069 *
12070 * @return
12071 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
12072 *      and the remaining structure fields are as described in #astro_global_solar_eclipse_t.
12073 *      Any other value indicates an error.
12074 */
12075astro_global_solar_eclipse_t Astronomy_NextGlobalSolarEclipse(astro_time_t prevEclipseTime)
12076{
12077    astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0);
12078    return Astronomy_SearchGlobalSolarEclipse(startTime);
12079}
12080
12081
12082static astro_eclipse_event_t EclipseEventError(void)
12083{
12084    astro_eclipse_event_t evt;
12085    evt.time = TimeError();
12086    evt.altitude = NAN;
12087    return evt;
12088}
12089
12090
12091static astro_local_solar_eclipse_t LocalSolarEclipseError(astro_status_t status)
12092{
12093    astro_local_solar_eclipse_t eclipse;
12094
12095    eclipse.status = status;
12096    eclipse.kind = ECLIPSE_NONE;
12097    eclipse.obscuration = NAN;
12098
12099    eclipse.partial_begin = EclipseEventError();
12100    eclipse.total_begin   = EclipseEventError();
12101    eclipse.peak          = EclipseEventError();
12102    eclipse.total_end     = EclipseEventError();
12103    eclipse.partial_end   = EclipseEventError();
12104
12105    return eclipse;
12106}
12107
12108
12109static shadow_t LocalMoonShadow(astro_time_t time, astro_observer_t observer)
12110{
12111    astro_vector_t s, o, m;
12112    double pos[3];
12113
12114    /* Calculate observer's geocentric position. */
12115    /* For efficiency, do this first, to populate the earth rotation parameters in 'time'. */
12116    /* That way they can be recycled instead of recalculated. */
12117    geo_pos(&time, observer, pos);
12118
12119    /* Calculate geocentric Sun with aberration correction. */
12120    s = Astronomy_GeoVector(BODY_SUN, time, ABERRATION);
12121    if (s.status != ASTRO_SUCCESS)
12122        return ShadowError(s.status);
12123
12124    m = Astronomy_GeoMoon(time);    /* geocentric Moon */
12125
12126    /* Calculate lunacentric location of an observer on the Earth's surface. */
12127    o.status = m.status;
12128    o.x = pos[0] - m.x;
12129    o.y = pos[1] - m.y;
12130    o.z = pos[2] - m.z;
12131    o.t = m.t;
12132
12133    /* Convert geocentric moon to heliocentric Moon. */
12134    m.x -= s.x;
12135    m.y -= s.y;
12136    m.z -= s.z;
12137
12138    return CalcShadow(MOON_MEAN_RADIUS_KM, time, o, m);
12139}
12140
12141
12142static astro_func_result_t local_shadow_distance_slope(void *context, astro_time_t time)
12143{
12144    const double dt = 1.0 / 86400.0;
12145    astro_time_t t1, t2;
12146    astro_func_result_t result;
12147    shadow_t shadow1, shadow2;
12148    const astro_observer_t *observer = (const astro_observer_t *) context;
12149
12150    t1 = Astronomy_AddDays(time, -dt);
12151    t2 = Astronomy_AddDays(time, +dt);
12152
12153    shadow1 = LocalMoonShadow(t1, *observer);
12154    if (shadow1.status != ASTRO_SUCCESS)
12155        return FuncError(shadow1.status);
12156
12157    shadow2 = LocalMoonShadow(t2, *observer);
12158    if (shadow2.status != ASTRO_SUCCESS)
12159        return FuncError(shadow2.status);
12160
12161    result.value = (shadow2.r - shadow1.r) / dt;
12162    result.status = ASTRO_SUCCESS;
12163    return result;
12164}
12165
12166
12167static shadow_t PeakLocalMoonShadow(astro_time_t search_center_time, astro_observer_t observer)
12168{
12169    astro_time_t t1, t2;
12170    astro_search_result_t result;
12171    const double window = 0.2;
12172
12173    /*
12174        Search for the time near search_center_time that the Moon's shadow comes
12175        closest to the given observer.
12176    */
12177
12178    t1 = Astronomy_AddDays(search_center_time, -window);
12179    t2 = Astronomy_AddDays(search_center_time, +window);
12180
12181    result = Astronomy_Search(local_shadow_distance_slope, &observer, t1, t2, 1.0);
12182    if (result.status != ASTRO_SUCCESS)
12183        return ShadowError(result.status);
12184
12185    return LocalMoonShadow(result.time, observer);
12186}
12187
12188
12189static double local_partial_distance(const shadow_t *shadow)
12190{
12191    return shadow->p - shadow->r;
12192}
12193
12194static double local_total_distance(const shadow_t *shadow)
12195{
12196    /* Must take the absolute value of the umbra radius 'k' */
12197    /* because it can be negative for an annular eclipse. */
12198    return fabs(shadow->k) - shadow->r;
12199}
12200
12201/** @cond DOXYGEN_SKIP */
12202typedef double (* local_distance_func) (const shadow_t *shadow);
12203
12204typedef struct
12205{
12206    local_distance_func     func;
12207    double                  direction;
12208    astro_observer_t        observer;
12209}
12210eclipse_transition_t;
12211/* @endcond */
12212
12213
12214static astro_func_result_t local_eclipse_func(void *context, astro_time_t time)
12215{
12216    const eclipse_transition_t *trans = (const eclipse_transition_t *) context;
12217    shadow_t shadow;
12218    astro_func_result_t result;
12219
12220    shadow = LocalMoonShadow(time, trans->observer);
12221    if (shadow.status != ASTRO_SUCCESS)
12222        return FuncError(shadow.status);
12223
12224    result.value = trans->direction * trans->func(&shadow);
12225    result.status = ASTRO_SUCCESS;
12226    return result;
12227}
12228
12229
12230astro_func_result_t SunAltitude(
12231    astro_time_t time,
12232    astro_observer_t observer)
12233{
12234    astro_equatorial_t equ;
12235    astro_horizon_t hor;
12236    astro_func_result_t result;
12237
12238    equ = Astronomy_Equator(BODY_SUN, &time, observer, EQUATOR_OF_DATE, ABERRATION);
12239    if (equ.status != ASTRO_SUCCESS)
12240        return FuncError(equ.status);
12241
12242    hor = Astronomy_Horizon(&time, observer, equ.ra, equ.dec, REFRACTION_NORMAL);
12243    result.value = hor.altitude;
12244    result.status = ASTRO_SUCCESS;
12245    return result;
12246}
12247
12248
12249static astro_status_t CalcEvent(
12250    astro_observer_t observer,
12251    astro_time_t time,
12252    astro_eclipse_event_t *evt)
12253{
12254    astro_func_result_t result;
12255
12256    result = SunAltitude(time, observer);
12257    if (result.status != ASTRO_SUCCESS)
12258    {
12259        evt->time = TimeError();
12260        evt->altitude = NAN;
12261        return result.status;
12262    }
12263
12264    evt->time = time;
12265    evt->altitude = result.value;
12266    return ASTRO_SUCCESS;
12267}
12268
12269
12270static astro_status_t LocalEclipseTransition(
12271    astro_observer_t observer,
12272    double direction,
12273    local_distance_func func,
12274    astro_time_t t1,
12275    astro_time_t t2,
12276    astro_eclipse_event_t *evt)
12277{
12278    eclipse_transition_t trans;
12279    astro_search_result_t search;
12280
12281    trans.func = func;
12282    trans.direction = direction;
12283    trans.observer = observer;
12284
12285    search = Astronomy_Search(local_eclipse_func, &trans, t1, t2, 1.0);
12286    if (search.status != ASTRO_SUCCESS)
12287    {
12288        evt->time = TimeError();
12289        evt->altitude = NAN;
12290        return search.status;
12291    }
12292
12293    return CalcEvent(observer, search.time, evt);
12294}
12295
12296
12297static astro_local_solar_eclipse_t LocalEclipse(
12298    shadow_t shadow,
12299    astro_observer_t observer)
12300{
12301    const double PARTIAL_WINDOW = 0.2;
12302    const double TOTAL_WINDOW = 0.01;
12303    astro_local_solar_eclipse_t eclipse;
12304    astro_time_t t1, t2;
12305    astro_status_t status;
12306
12307    status = CalcEvent(observer, shadow.time, &eclipse.peak);
12308    if (status != ASTRO_SUCCESS)
12309        return LocalSolarEclipseError(status);
12310
12311    t1 = Astronomy_AddDays(shadow.time, -PARTIAL_WINDOW);
12312    t2 = Astronomy_AddDays(shadow.time, +PARTIAL_WINDOW);
12313
12314    status = LocalEclipseTransition(observer, +1.0, local_partial_distance, t1, shadow.time, &eclipse.partial_begin);
12315    if (status != ASTRO_SUCCESS)
12316        return LocalSolarEclipseError(status);
12317
12318    status = LocalEclipseTransition(observer, -1.0, local_partial_distance, shadow.time, t2, &eclipse.partial_end);
12319    if (status != ASTRO_SUCCESS)
12320        return LocalSolarEclipseError(status);
12321
12322    if (shadow.r < fabs(shadow.k))      /* take absolute value of 'k' to handle annular eclipses too. */
12323    {
12324        t1 = Astronomy_AddDays(shadow.time, -TOTAL_WINDOW);
12325        t2 = Astronomy_AddDays(shadow.time, +TOTAL_WINDOW);
12326
12327        status = LocalEclipseTransition(observer, +1.0, local_total_distance, t1, shadow.time, &eclipse.total_begin);
12328        if (status != ASTRO_SUCCESS)
12329            return LocalSolarEclipseError(status);
12330
12331        status = LocalEclipseTransition(observer, -1.0, local_total_distance, shadow.time, t2, &eclipse.total_end);
12332        if (status != ASTRO_SUCCESS)
12333            return LocalSolarEclipseError(status);
12334
12335        eclipse.kind = EclipseKindFromUmbra(shadow.k);
12336        if (eclipse.kind == ECLIPSE_TOTAL)
12337            eclipse.obscuration = 1.0;
12338        else
12339            eclipse.obscuration = SolarEclipseObscuration(shadow.dir, shadow.target);
12340    }
12341    else
12342    {
12343        eclipse.total_begin = eclipse.total_end = EclipseEventError();
12344        eclipse.kind = ECLIPSE_PARTIAL;
12345        eclipse.obscuration = SolarEclipseObscuration(shadow.dir, shadow.target);
12346    }
12347
12348    eclipse.status = ASTRO_SUCCESS;
12349    return eclipse;
12350}
12351
12352
12353/**
12354 * @brief Searches for a solar eclipse visible at a specific location on the Earth's surface.
12355 *
12356 * This function finds the first solar eclipse that occurs after `startTime`.
12357 * A solar eclipse may be partial, annular, or total.
12358 * See #astro_local_solar_eclipse_t for more information.
12359 * To find a series of solar eclipses, call this function once,
12360 * then keep calling #Astronomy_NextLocalSolarEclipse as many times as desired,
12361 * passing in the `peak` value returned from the previous call.
12362 *
12363 * IMPORTANT: An eclipse reported by this function might be partly or
12364 * completely invisible to the observer due to the time of day.
12365 *
12366 * @param startTime
12367 *      The date and time for starting the search for a solar eclipse.
12368 *
12369 * @param observer
12370 *      The geographic location of the observer.
12371 *
12372 * @return
12373 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
12374 *      and the remaining structure fields are as described in #astro_local_solar_eclipse_t.
12375 *      Any other value indicates an error.
12376 */
12377astro_local_solar_eclipse_t Astronomy_SearchLocalSolarEclipse(
12378    astro_time_t startTime,
12379    astro_observer_t observer)
12380{
12381    const double PruneLatitude = 1.8;   /* Moon's ecliptic latitude beyond which eclipse is impossible */
12382    astro_time_t nmtime;
12383    astro_search_result_t newmoon;
12384    shadow_t shadow;
12385    double eclip_lat, eclip_lon, distance;
12386    astro_local_solar_eclipse_t eclipse;
12387
12388    /* Iterate through consecutive new moons until we find a solar eclipse visible somewhere on Earth. */
12389    nmtime = startTime;
12390    for(;;)
12391    {
12392        /* Search for the next new moon. Any eclipse will be near it. */
12393        newmoon = Astronomy_SearchMoonPhase(0.0, nmtime, 40.0);
12394        if (newmoon.status != ASTRO_SUCCESS)
12395            return LocalSolarEclipseError(newmoon.status);
12396
12397        /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */
12398        CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance);
12399        if (RAD2DEG * fabs(eclip_lat) < PruneLatitude)
12400        {
12401            /* Search near the new moon for the time when the observer */
12402            /* is closest to the line passing through the centers of the Sun and Moon. */
12403            shadow = PeakLocalMoonShadow(newmoon.time, observer);
12404            if (shadow.status != ASTRO_SUCCESS)
12405                return LocalSolarEclipseError(shadow.status);
12406
12407            if (shadow.r < shadow.p)
12408            {
12409                /* This is at least a partial solar eclipse for the observer. */
12410                eclipse = LocalEclipse(shadow, observer);
12411
12412                /* If any error occurs, something is really wrong and we should bail out. */
12413                if (eclipse.status != ASTRO_SUCCESS)
12414                    return eclipse;
12415
12416                /* Ignore any eclipse that happens completely at night. */
12417                /* More precisely, the center of the Sun must be above the horizon */
12418                /* at the beginning or the end of the eclipse, or we skip the event. */
12419                if (eclipse.partial_begin.altitude > 0.0 || eclipse.partial_end.altitude > 0.0)
12420                    return eclipse;
12421            }
12422        }
12423
12424        /* We didn't find an eclipse on this new moon, so search for the next one. */
12425        nmtime = Astronomy_AddDays(newmoon.time, 10.0);
12426    }
12427}
12428
12429
12430/**
12431 * @brief Searches for the next local solar eclipse in a series.
12432 *
12433 * After using #Astronomy_SearchLocalSolarEclipse to find the first solar eclipse
12434 * in a series, you can call this function to find the next consecutive solar eclipse.
12435 * Pass in the `peak` value from the #astro_local_solar_eclipse_t returned by the
12436 * previous call to `Astronomy_SearchLocalSolarEclipse` or `Astronomy_NextLocalSolarEclipse`
12437 * to find the next solar eclipse.
12438 *
12439 * @param prevEclipseTime
12440 *      A date and time near a new moon. Solar eclipse search will start at the next new moon.
12441 *
12442 * @param observer
12443 *      The geographic location of the observer.
12444 *
12445 * @return
12446 *      If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS`
12447 *      and the remaining structure fields are as described in #astro_local_solar_eclipse_t.
12448 *      Any other value indicates an error.
12449 */
12450astro_local_solar_eclipse_t Astronomy_NextLocalSolarEclipse(
12451    astro_time_t prevEclipseTime,
12452    astro_observer_t observer)
12453{
12454    astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0);
12455    return Astronomy_SearchLocalSolarEclipse(startTime, observer);
12456}
12457
12458
12459static astro_func_result_t planet_transit_bound(void *context, astro_time_t time)
12460{
12461    shadow_t shadow;
12462    astro_func_result_t result;
12463    const planet_shadow_context_t *p = (const planet_shadow_context_t *) context;
12464
12465    shadow = PlanetShadow(p->body, p->planet_radius_km, time);
12466    if (shadow.status != ASTRO_SUCCESS)
12467        return FuncError(shadow.status);
12468
12469    result.status = ASTRO_SUCCESS;
12470    result.value = p->direction * (shadow.r - shadow.p);
12471    return result;
12472}
12473
12474
12475static astro_search_result_t PlanetTransitBoundary(
12476    astro_body_t body,
12477    double planet_radius_km,
12478    astro_time_t t1,
12479    astro_time_t t2,
12480    double direction)
12481{
12482    /* Search for the time the planet's penumbra begins/ends making contact with the center of the Earth. */
12483    planet_shadow_context_t context;
12484
12485    context.body = body;
12486    context.planet_radius_km = planet_radius_km;
12487    context.direction = direction;
12488
12489    return Astronomy_Search(planet_transit_bound, &context, t1, t2, 1.0);
12490}
12491
12492
12493/**
12494 * @brief Searches for the first transit of Mercury or Venus after a given date.
12495 *
12496 * Finds the first transit of Mercury or Venus after a specified date.
12497 * A transit is when an inferior planet passes between the Sun and the Earth
12498 * so that the silhouette of the planet is visible against the Sun in the background.
12499 * To continue the search, pass the `finish` time in the returned structure to
12500 * #Astronomy_NextTransit.
12501 *
12502 * @param body
12503 *      The planet whose transit is to be found. Must be `BODY_MERCURY` or `BODY_VENUS`.
12504 *
12505 * @param startTime
12506 *      The date and time for starting the search for a transit.
12507 *
12508 * @return
12509 *      If successful, the `status` field in the returned structure hold `ASTRO_SUCCESS`
12510 *      and the other fields are as documented in #astro_transit_t.
12511 *      Otherwise, `status` holds an error code and the other structure members are undefined.
12512 */
12513astro_transit_t Astronomy_SearchTransit(astro_body_t body, astro_time_t startTime)
12514{
12515    astro_time_t search_time;
12516    astro_transit_t transit;
12517    astro_search_result_t conj, search;
12518    astro_angle_result_t conj_separation, min_separation;
12519    shadow_t shadow;
12520    double planet_radius_km;
12521    astro_time_t tx;
12522    const double threshold_angle = 0.4;     /* maximum angular separation to attempt transit calculation */
12523    const double dt_days = 1.0;
12524
12525    /* Validate the planet and find its mean radius. */
12526    switch (body)
12527    {
12528    case BODY_MERCURY:  planet_radius_km = 2439.7;  break;
12529    case BODY_VENUS:    planet_radius_km = 6051.8;  break;
12530    default:
12531        return TransitErr(ASTRO_INVALID_BODY);
12532    }
12533
12534    search_time = startTime;
12535    for(;;)
12536    {
12537        /*
12538            Search for the next inferior conjunction of the given planet.
12539            This is the next time the Earth and the other planet have the same
12540            ecliptic longitude as seen from the Sun.
12541        */
12542        conj = Astronomy_SearchRelativeLongitude(body, 0.0, search_time);
12543        if (conj.status != ASTRO_SUCCESS)
12544            return TransitErr(conj.status);
12545
12546        /* Calculate the angular separation between the body and the Sun at this time. */
12547        conj_separation = Astronomy_AngleFromSun(body, conj.time);
12548        if (conj_separation.status != ASTRO_SUCCESS)
12549            return TransitErr(conj_separation.status);
12550
12551        if (conj_separation.angle < threshold_angle)
12552        {
12553            /*
12554                The planet's angular separation from the Sun is small enough
12555                to consider it a transit candidate.
12556                Search for the moment when the line passing through the Sun
12557                and planet are closest to the Earth's center.
12558            */
12559            shadow = PeakPlanetShadow(body, planet_radius_km, conj.time);
12560            if (shadow.status != ASTRO_SUCCESS)
12561                return TransitErr(shadow.status);
12562
12563            if (shadow.r < shadow.p)        /* does the planet's penumbra touch the Earth's center? */
12564            {
12565                /* Find the beginning and end of the penumbral contact. */
12566                tx = Astronomy_AddDays(shadow.time, -dt_days);
12567                search = PlanetTransitBoundary(body, planet_radius_km, tx, shadow.time, -1.0);
12568                if (search.status != ASTRO_SUCCESS)
12569                    return TransitErr(search.status);
12570                transit.start = search.time;
12571
12572                tx = Astronomy_AddDays(shadow.time, +dt_days);
12573                search = PlanetTransitBoundary(body, planet_radius_km, shadow.time, tx, +1.0);
12574                if (search.status != ASTRO_SUCCESS)
12575                    return TransitErr(search.status);
12576                transit.finish = search.time;
12577                transit.status = ASTRO_SUCCESS;
12578                transit.peak = shadow.time;
12579
12580                min_separation = Astronomy_AngleFromSun(body, shadow.time);
12581                if (min_separation.status != ASTRO_SUCCESS)
12582                    return TransitErr(min_separation.status);
12583
12584                transit.separation = 60.0 * min_separation.angle;   /* convert degrees to arcminutes */
12585                return transit;
12586            }
12587        }
12588
12589        /* This inferior conjunction was not a transit. Try the next inferior conjunction. */
12590        search_time = Astronomy_AddDays(conj.time, 10.0);
12591    }
12592}
12593
12594
12595/**
12596 * @brief Searches for another transit of Mercury or Venus.
12597 *
12598 * After calling #Astronomy_SearchTransit to find a transit of Mercury or Venus,
12599 * this function finds the next transit after that.
12600 * Keep calling this function as many times as you want to keep finding more transits.
12601 *
12602 * @param body
12603 *      The planet whose transit is to be found. Must be `BODY_MERCURY` or `BODY_VENUS`.
12604 *
12605 * @param prevTransitTime
12606 *      A date and time near the previous transit.
12607 *
12608 * @return
12609 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`
12610 *      and the other fields are as documented in #astro_transit_t.
12611 *      Otherwise, `status` holds an error code and the other structure members are undefined.
12612 */
12613astro_transit_t Astronomy_NextTransit(astro_body_t body, astro_time_t prevTransitTime)
12614{
12615    astro_time_t startTime;
12616
12617    startTime = Astronomy_AddDays(prevTransitTime, 100.0);
12618    return Astronomy_SearchTransit(body, startTime);
12619}
12620
12621
12622static astro_node_event_t NodeError(astro_status_t status)
12623{
12624    astro_node_event_t node;
12625
12626    node.status = status;
12627    node.time = TimeError();
12628    node.kind = INVALID_NODE;
12629
12630    return node;
12631}
12632
12633static astro_func_result_t MoonNodeSearchFunc(void *context, astro_time_t time)
12634{
12635    astro_func_result_t result;
12636    astro_spherical_t eclip;
12637    astro_node_kind_t kind = *((astro_node_kind_t *)context);
12638
12639    eclip = Astronomy_EclipticGeoMoon(time);
12640
12641    result.value = eclip.lat * (double)kind;
12642    result.status = ASTRO_SUCCESS;
12643    return result;
12644}
12645
12646static const double MOON_NODE_STEP_DAYS = +10.0;    /* a safe number of days to step without missing a Moon node */
12647
12648/**
12649 * @brief Searches for a time when the Moon's center crosses through the ecliptic plane.
12650 *
12651 * Searches for the first ascending or descending node of the Moon after `startTime`.
12652 * An ascending node is when the Moon's center passes through the ecliptic plane
12653 * (the plane of the Earth's orbit around the Sun) from south to north.
12654 * A descending node is when the Moon's center passes through the ecliptic plane
12655 * from north to south. Nodes indicate possible times of solar or lunar eclipses,
12656 * if the Moon also happens to be in the correct phase (new or full, respectively).
12657 *
12658 * Call `Astronomy_SearchMoonNode` to find the first of a series of nodes.
12659 * Then call #Astronomy_NextMoonNode to find as many more consecutive nodes as desired.
12660 *
12661 * @param startTime
12662 *      The date and time for starting the search for an ascending or descending node of the Moon.
12663 *
12664 * @return
12665 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`
12666 *      and the other fields are as documented in #astro_node_event_t.
12667 *      Otherwise, `status` holds an error code and the other structure members are undefined.
12668 */
12669astro_node_event_t Astronomy_SearchMoonNode(astro_time_t startTime)
12670{
12671    astro_node_event_t node;
12672    astro_time_t time1, time2;
12673    astro_spherical_t eclip1, eclip2;
12674    astro_node_kind_t kind;
12675    astro_search_result_t result;
12676
12677    /* Start at the given moment in time and sample the Moon's ecliptic latitude. */
12678    /* Step 10 days at a time, searching for an interval where that latitude crosses zero. */
12679    time1 = startTime;
12680    eclip1 = Astronomy_EclipticGeoMoon(time1);    /* never returns a failure code */
12681
12682    for(;;)
12683    {
12684        time2 = Astronomy_AddDays(time1, MOON_NODE_STEP_DAYS);
12685        eclip2 = Astronomy_EclipticGeoMoon(time2);      /* never returns a failure code */
12686        if (eclip1.lat * eclip2.lat <= 0.0)
12687        {
12688            /* There is a node somewhere inside this closed time interval. */
12689            /* Figure out whether it is an ascending node or a descending node. */
12690            kind = (eclip2.lat > eclip1.lat) ? ASCENDING_NODE : DESCENDING_NODE;
12691            result = Astronomy_Search(MoonNodeSearchFunc, &kind, time1, time2, 1.0);
12692            if (result.status != ASTRO_SUCCESS)
12693                return NodeError(result.status);
12694
12695            node.status = ASTRO_SUCCESS;
12696            node.time = result.time;
12697            node.kind = kind;
12698            return node;
12699        }
12700        time1 = time2;
12701        eclip1 = eclip2;
12702    }
12703}
12704
12705
12706/**
12707 * @brief Searches for the next time when the Moon's center crosses through the ecliptic plane.
12708 *
12709 * Call #Astronomy_SearchMoonNode to find the first of a series of nodes.
12710 * Then call `Astronomy_NextMoonNode` to find as many more consecutive nodes as desired.
12711 *
12712 * @param prevNode
12713 *      The previous node found from calling #Astronomy_SearchMoonNode or `Astronomy_NextMoonNode`.
12714 *
12715 * @return
12716 *      If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`
12717 *      and the other fields are as documented in #astro_node_event_t.
12718 *      Otherwise, `status` holds an error code and the other structure members are undefined.
12719 */
12720astro_node_event_t Astronomy_NextMoonNode(astro_node_event_t prevNode)
12721{
12722    astro_time_t time;
12723    astro_node_event_t node;
12724
12725    if (prevNode.status != ASTRO_SUCCESS)
12726        return NodeError(ASTRO_INVALID_PARAMETER);
12727
12728    if (prevNode.kind != ASCENDING_NODE && prevNode.kind != DESCENDING_NODE)
12729        return NodeError(ASTRO_INVALID_PARAMETER);
12730
12731    time = Astronomy_AddDays(prevNode.time, MOON_NODE_STEP_DAYS);
12732    node = Astronomy_SearchMoonNode(time);
12733    if (node.status == ASTRO_SUCCESS)
12734    {
12735        /* Verify nodes are alternating as expected. */
12736        if (prevNode.kind == ASCENDING_NODE)
12737        {
12738            if (node.kind != DESCENDING_NODE)
12739                return NodeError(ASTRO_INTERNAL_ERROR);
12740        }
12741        else
12742        {
12743            if (node.kind != ASCENDING_NODE)
12744                return NodeError(ASTRO_INTERNAL_ERROR);
12745        }
12746    }
12747    return node;
12748}
12749
12750
12751/**
12752 * @brief Frees up all dynamic memory allocated by Astronomy Engine.
12753 *
12754 * Astronomy Engine uses dynamic memory allocation in only one place:
12755 * it makes calculation of Pluto's orbit more efficient by caching 11 KB
12756 * segments and recycling them. To force purging this cache and
12757 * freeing all the dynamic memory, you can call this function at any time.
12758 * It is always safe to call, although it will slow down the very next
12759 * calculation of Pluto's position for a nearby time value.
12760 * Calling this function before your program exits is optional, but
12761 * it will be helpful for leak-checkers like valgrind.
12762 */
12763void Astronomy_Reset(void)
12764{
12765    int i;
12766    for (i=0; i < PLUTO_NUM_STATES-1; ++i)
12767    {
12768        free(pluto_cache[i]);
12769        pluto_cache[i] = NULL;
12770    }
12771}
12772
12773
12774static astro_axis_t EarthRotationAxis(astro_time_t *time)
12775{
12776    astro_axis_t axis;
12777    double pos1[3];
12778    double pos2[3];
12779    astro_equatorial_t equ;
12780
12781    if (time == NULL)
12782        return AxisErr(ASTRO_INVALID_PARAMETER, TimeError());
12783
12784    /*
12785        Unlike the other planets, we have a model of precession and nutation
12786        for the Earth's axis that provides a north pole vector.
12787        So calculate the vector first, then derive the (RA,DEC) angles from the vector.
12788    */
12789
12790    /* Start with a north pole vector in equator-of-date coordinates: (0,0,1). */
12791    pos1[0] = 0.0;
12792    pos1[1] = 0.0;
12793    pos1[2] = 1.0;
12794
12795    /* Convert the vector into J2000 coordinates. */
12796    nutation(pos1, time, INTO_2000, pos2);
12797    precession(pos2, *time, INTO_2000, pos1);
12798    axis.north.x = pos1[0];
12799    axis.north.y = pos1[1];
12800    axis.north.z = pos1[2];
12801    axis.north.t = *time;
12802    axis.north.status = ASTRO_SUCCESS;
12803
12804    /* Derive angular values: right ascension and declination. */
12805    equ = Astronomy_EquatorFromVector(axis.north);
12806    if (equ.status != ASTRO_SUCCESS)
12807        return AxisErr(equ.status, *time);
12808    axis.ra = equ.ra;
12809    axis.dec = equ.dec;
12810
12811    /* Use a modified version of the era() function that does not trim to 0..360 degrees. */
12812    /* This expression is also corrected to give the correct angle at the J2000 epoch. */
12813    axis.spin = 190.41375788700253 + (360.9856122880876 * time->ut);
12814
12815    axis.status = ASTRO_SUCCESS;
12816
12817    return axis;
12818}
12819
12820
12821/**
12822 * @brief Calculates information about a body's rotation axis at a given time.
12823 *
12824 * Calculates the orientation of a body's rotation axis, along with
12825 * the rotation angle of its prime meridian, at a given moment in time.
12826 *
12827 * This function uses formulas standardized by the IAU Working Group
12828 * on Cartographics and Rotational Elements 2015 report, as described
12829 * in the following document:
12830 *
12831 * https://astropedia.astrogeology.usgs.gov/download/Docs/WGCCRE/WGCCRE2015reprint.pdf
12832 *
12833 * See #astro_axis_t for more detailed information.
12834 *
12835 * @param body
12836 *      The body whose rotation axis is to be found. The supported bodies are:
12837 *      `BODY_SUN`, `BODY_MOON`, `BODY_MERCURY`, `BODY_VENUS`, `BODY_EARTH`, `BODY_MARS`,
12838 *      `BODY_JUPITER`, `BODY_SATURN`, `BODY_URANUS`, `BODY_NEPTUNE`, `BODY_PLUTO`.
12839 *
12840 * @param time
12841 *      The time for which the body's rotation axis is to be found.
12842 *
12843 * @return astro_axis_t
12844 */
12845astro_axis_t Astronomy_RotationAxis(astro_body_t body, astro_time_t *time)
12846{
12847    astro_axis_t axis;
12848    double ra, dec, w;
12849    double radlat, radlon, rcoslat;
12850    double Ja, Jb, Jc, Jd, Je, N;
12851    double E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13;
12852    double d, T;
12853
12854    if (time == NULL)
12855        return AxisErr(ASTRO_INVALID_PARAMETER, TimeError());
12856
12857    d = time->tt;
12858    T = d / 36525.0;
12859
12860    switch (body)
12861    {
12862    case BODY_SUN:
12863        ra = 286.13;
12864        dec = 63.87;
12865        w = 84.176 + (14.1844 * d);
12866        break;
12867
12868    case BODY_MERCURY:
12869        ra = 281.0103 - (0.0328 * T);
12870        dec = 61.4155 - (0.0049 * T);
12871        w = (
12872            329.5988
12873            + (6.1385108 * d)
12874            + (0.01067257 * sin(DEG2RAD*(174.7910857 + 4.092335*d)))
12875            - (0.00112309 * sin(DEG2RAD*(349.5821714 + 8.184670*d)))
12876            - (0.00011040 * sin(DEG2RAD*(164.3732571 + 12.277005*d)))
12877            - (0.00002539 * sin(DEG2RAD*(339.1643429 + 16.369340*d)))
12878            - (0.00000571 * sin(DEG2RAD*(153.9554286 + 20.461675*d)))
12879        );
12880        break;
12881
12882    case BODY_VENUS:
12883        ra = 272.76;
12884        dec = 67.16;
12885        w = 160.20 - (1.4813688 * d);
12886        break;
12887
12888    case BODY_EARTH:
12889        return EarthRotationAxis(time);
12890
12891    case BODY_MOON:
12892        /*
12893            https://astropedia.astrogeology.usgs.gov/alfresco/d/d/workspace/SpacesStore/28fd9e81-1964-44d6-a58b-fbbf61e64e15/WGCCRE2009reprint.pdf
12894            Page 8, Table 2.
12895        */
12896        E1  = DEG2RAD * (125.045 -  0.0529921*d);
12897        E2  = DEG2RAD * (250.089 -  0.1059842*d);
12898        E3  = DEG2RAD * (260.008 + 13.0120009*d);
12899        E4  = DEG2RAD * (176.625 + 13.3407154*d);
12900        E5  = DEG2RAD * (357.529 +  0.9856003*d);
12901        E6  = DEG2RAD * (311.589 + 26.4057084*d);
12902        E7  = DEG2RAD * (134.963 + 13.0649930*d);
12903        E8  = DEG2RAD * (276.617 +  0.3287146*d);
12904        E9  = DEG2RAD * (34.226  +  1.7484877*d);
12905        E10 = DEG2RAD * (15.134  -  0.1589763*d);
12906        E11 = DEG2RAD * (119.743 +  0.0036096*d);
12907        E12 = DEG2RAD * (239.961 +  0.1643573*d);
12908        E13 = DEG2RAD * (25.053  + 12.9590088*d);
12909
12910        ra = (
12911            269.9949 + 0.0031*T
12912            - 3.8787*sin(E1)
12913            - 0.1204*sin(E2)
12914            + 0.0700*sin(E3)
12915            - 0.0172*sin(E4)
12916            + 0.0072*sin(E6)
12917            - 0.0052*sin(E10)
12918            + 0.0043*sin(E13)
12919        );
12920
12921        dec = (
12922            66.5392 + 0.0130*T
12923            + 1.5419*cos(E1)
12924            + 0.0239*cos(E2)
12925            - 0.0278*cos(E3)
12926            + 0.0068*cos(E4)
12927            - 0.0029*cos(E6)
12928            + 0.0009*cos(E7)
12929            + 0.0008*cos(E10)
12930            - 0.0009*cos(E13)
12931        );
12932
12933        w = (
12934            38.3213 + (13.17635815 - 1.4e-12*d)*d
12935            + 3.5610*sin(E1)
12936            + 0.1208*sin(E2)
12937            - 0.0642*sin(E3)
12938            + 0.0158*sin(E4)
12939            + 0.0252*sin(E5)
12940            - 0.0066*sin(E6)
12941            - 0.0047*sin(E7)
12942            - 0.0046*sin(E8)
12943            + 0.0028*sin(E9)
12944            + 0.0052*sin(E10)
12945            + 0.0040*sin(E11)
12946            + 0.0019*sin(E12)
12947            - 0.0044*sin(E13)
12948        );
12949        break;
12950
12951    case BODY_MARS:
12952        ra = (
12953            317.269202 - 0.10927547*T
12954            + 0.000068 * sin(DEG2RAD*(198.991226 + 19139.4819985*T))
12955            + 0.000238 * sin(DEG2RAD*(226.292679 + 38280.8511281*T))
12956            + 0.000052 * sin(DEG2RAD*(249.663391 + 57420.7251593*T))
12957            + 0.000009 * sin(DEG2RAD*(266.183510 + 76560.6367950*T))
12958            + 0.419057 * sin(DEG2RAD*(79.398797 + 0.5042615*T))
12959        );
12960
12961        dec = (
12962            54.432516 - 0.05827105*T
12963            + 0.000051*cos(DEG2RAD*(122.433576 + 19139.9407476*T))
12964            + 0.000141*cos(DEG2RAD*(43.058401 + 38280.8753272*T))
12965            + 0.000031*cos(DEG2RAD*(57.663379 + 57420.7517205*T))
12966            + 0.000005*cos(DEG2RAD*(79.476401 + 76560.6495004*T))
12967            + 1.591274*cos(DEG2RAD*(166.325722 + 0.5042615*T))
12968        );
12969
12970        w = (
12971            176.049863 + 350.891982443297*d
12972            + 0.000145*sin(DEG2RAD*(129.071773 + 19140.0328244*T))
12973            + 0.000157*sin(DEG2RAD*(36.352167 + 38281.0473591*T))
12974            + 0.000040*sin(DEG2RAD*(56.668646 + 57420.9295360*T))
12975            + 0.000001*sin(DEG2RAD*(67.364003 + 76560.2552215*T))
12976            + 0.000001*sin(DEG2RAD*(104.792680 + 95700.4387578*T))
12977            + 0.584542*sin(DEG2RAD*(95.391654 + 0.5042615*T))
12978        );
12979        break;
12980
12981    case BODY_JUPITER:
12982        Ja = DEG2RAD*(99.360714 + 4850.4046*T);
12983        Jb = DEG2RAD*(175.895369 + 1191.9605*T);
12984        Jc = DEG2RAD*(300.323162 + 262.5475*T);
12985        Jd = DEG2RAD*(114.012305 + 6070.2476*T);
12986        Je = DEG2RAD*(49.511251 + 64.3000*T);
12987
12988        ra = (
12989            268.056595 - 0.006499*T
12990            + 0.000117*sin(Ja)
12991            + 0.000938*sin(Jb)
12992            + 0.001432*sin(Jc)
12993            + 0.000030*sin(Jd)
12994            + 0.002150*sin(Je)
12995        );
12996
12997        dec = (
12998            64.495303 + 0.002413*T
12999            + 0.000050*cos(Ja)
13000            + 0.000404*cos(Jb)
13001            + 0.000617*cos(Jc)
13002            - 0.000013*cos(Jd)
13003            + 0.000926*cos(Je)
13004        );
13005
13006        w = 284.95 + 870.536*d;
13007        break;
13008
13009    case BODY_SATURN:
13010        ra = 40.589 - 0.036*T;
13011        dec = 83.537 - 0.004*T;
13012        w = 38.90 + 810.7939024*d;
13013        break;
13014
13015    case BODY_URANUS:
13016        ra = 257.311;
13017        dec = -15.175;
13018        w = 203.81 - 501.1600928*d;
13019        break;
13020
13021    case BODY_NEPTUNE:
13022        N = DEG2RAD*(357.85 + 52.316*T);
13023        ra = 299.36 + 0.70*sin(N);
13024        dec = 43.46 - 0.51*cos(N);
13025        w = 249.978 + 541.1397757*d - 0.48*sin(N);
13026        break;
13027
13028    case BODY_PLUTO:
13029        ra = 132.993;
13030        dec = -6.163;
13031        w = 302.695 + 56.3625225*d;
13032        break;
13033
13034    default:
13035        return AxisErr(ASTRO_INVALID_BODY, *time);
13036    }
13037
13038    axis.ra = ra / 15.0;      /* convert degrees to sidereal hours */
13039    axis.dec = dec;
13040    axis.spin = w;
13041
13042    /* calculate the north pole vector using the given angles. */
13043    radlat = dec * DEG2RAD;
13044    radlon = ra * DEG2RAD;
13045    rcoslat = cos(radlat);
13046    axis.north.x = rcoslat * cos(radlon);
13047    axis.north.y = rcoslat * sin(radlon);
13048    axis.north.z = sin(radlat);
13049    axis.north.t = *time;
13050    axis.north.status = ASTRO_SUCCESS;
13051
13052    axis.status = ASTRO_SUCCESS;
13053    return axis;
13054}
13055
13056#ifdef __cplusplus
13057}
13058#endif