acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
astronomy.c (345283B)
1 /* 2 Astronomy Engine for C/C++. 3 https://github.com/cosinekitty/astronomy 4 5 MIT License 6 7 Copyright (c) 2019-2020 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 <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <time.h> 32 #include <math.h> 33 #include "astronomy.h" 34 35 #ifdef __cplusplus 36 extern "C" { 37 #endif 38 39 /** @cond DOXYGEN_SKIP */ 40 #define PI 3.14159265358979323846 41 42 typedef struct 43 { 44 double x; 45 double y; 46 double z; 47 } 48 terse_vector_t; 49 50 static const terse_vector_t VecZero; 51 52 static terse_vector_t VecAdd(terse_vector_t a, terse_vector_t b) 53 { 54 terse_vector_t c; 55 c.x = a.x + b.x; 56 c.y = a.y + b.y; 57 c.z = a.z + b.z; 58 return c; 59 } 60 61 static terse_vector_t VecSub(terse_vector_t a, terse_vector_t b) 62 { 63 terse_vector_t c; 64 c.x = a.x - b.x; 65 c.y = a.y - b.y; 66 c.z = a.z - b.z; 67 return c; 68 } 69 70 static void VecIncr(terse_vector_t *target, terse_vector_t source) 71 { 72 target->x += source.x; 73 target->y += source.y; 74 target->z += source.z; 75 } 76 77 static void VecDecr(terse_vector_t *target, terse_vector_t source) 78 { 79 target->x -= source.x; 80 target->y -= source.y; 81 target->z -= source.z; 82 } 83 84 static terse_vector_t VecMul(double s, terse_vector_t v) 85 { 86 terse_vector_t p; 87 p.x = s * v.x; 88 p.y = s * v.y; 89 p.z = s * v.z; 90 return p; 91 } 92 93 static void VecScale(terse_vector_t *target, double scalar) 94 { 95 target->x *= scalar; 96 target->y *= scalar; 97 target->z *= scalar; 98 } 99 100 static terse_vector_t VecRamp(terse_vector_t a, terse_vector_t b, double ramp) 101 { 102 terse_vector_t c; 103 c.x = (1-ramp)*a.x + ramp*b.x; 104 c.y = (1-ramp)*a.y + ramp*b.y; 105 c.z = (1-ramp)*a.z + ramp*b.z; 106 return c; 107 } 108 109 static terse_vector_t VecMean(terse_vector_t a, terse_vector_t b) 110 { 111 terse_vector_t c; 112 c.x = (a.x + b.x) / 2; 113 c.y = (a.y + b.y) / 2; 114 c.z = (a.z + b.z) / 2; 115 return c; 116 } 117 118 static astro_vector_t PublicVec(astro_time_t time, terse_vector_t terse) 119 { 120 astro_vector_t vector; 121 122 vector.status = ASTRO_SUCCESS; 123 vector.t = time; 124 vector.x = terse.x; 125 vector.y = terse.y; 126 vector.z = terse.z; 127 128 return vector; 129 } 130 131 typedef struct 132 { 133 double tt; /* Terrestrial Time in J2000 days */ 134 terse_vector_t r; /* position [au] */ 135 terse_vector_t v; /* velocity [au/day] */ 136 } 137 body_state_t; 138 /** @endcond */ 139 140 static const double DAYS_PER_TROPICAL_YEAR = 365.24217; 141 static const double DEG2RAD = 0.017453292519943296; 142 static const double RAD2DEG = 57.295779513082321; 143 static const double ASEC360 = 1296000.0; 144 static const double ASEC2RAD = 4.848136811095359935899141e-6; 145 static const double PI2 = 2.0 * PI; 146 static const double ARC = 3600.0 * 180.0 / PI; /* arcseconds per radian */ 147 static const double C_AUDAY = 173.1446326846693; /* speed of light in AU/day */ 148 static const double KM_PER_AU = 1.4959787069098932e+8; 149 static const double SECONDS_PER_DAY = 24.0 * 3600.0; 150 static const double SOLAR_DAYS_PER_SIDEREAL_DAY = 0.9972695717592592; 151 static const double MEAN_SYNODIC_MONTH = 29.530588; /* average number of days for Moon to return to the same phase */ 152 static const double EARTH_ORBITAL_PERIOD = 365.256; 153 static const double NEPTUNE_ORBITAL_PERIOD = 60189.0; 154 static const double REFRACTION_NEAR_HORIZON = 34.0 / 60.0; /* degrees of refractive "lift" seen for objects near horizon */ 155 156 static const double SUN_RADIUS_KM = 695700.0; 157 #define SUN_RADIUS_AU (SUN_RADIUS_KM / KM_PER_AU) 158 159 #define EARTH_FLATTENING 0.996647180302104 160 #define EARTH_EQUATORIAL_RADIUS_KM 6378.1366 161 #define EARTH_EQUATORIAL_RADIUS_AU (EARTH_EQUATORIAL_RADIUS_KM / KM_PER_AU) 162 #define EARTH_MEAN_RADIUS_KM 6371.0 /* mean radius of the Earth's geoid, without atmosphere */ 163 #define EARTH_ATMOSPHERE_KM 88.0 /* effective atmosphere thickness for lunar eclipses */ 164 #define EARTH_ECLIPSE_RADIUS_KM (EARTH_MEAN_RADIUS_KM + EARTH_ATMOSPHERE_KM) 165 /* Note: if we ever need Earth's polar radius, it is (EARTH_FLATTENING * EARTH_EQUATORIAL_RADIUS_KM) */ 166 167 #define MOON_EQUATORIAL_RADIUS_KM 1738.1 168 #define MOON_MEAN_RADIUS_KM 1737.4 169 #define MOON_POLAR_RADIUS_KM 1736.0 170 #define MOON_EQUATORIAL_RADIUS_AU (MOON_EQUATORIAL_RADIUS_KM / KM_PER_AU) 171 172 static const double ASEC180 = 180.0 * 60.0 * 60.0; /* arcseconds per 180 degrees (or pi radians) */ 173 static const double EARTH_MOON_MASS_RATIO = 81.30056; 174 175 /* 176 Masses of the Sun and outer planets, used for: 177 (1) Calculating the Solar System Barycenter 178 (2) Integrating the movement of Pluto 179 180 https://web.archive.org/web/20120220062549/http://iau-comm4.jpl.nasa.gov/de405iom/de405iom.pdf 181 182 Page 10 in the above document describes the constants used in the DE405 ephemeris. 183 The following are G*M values (gravity constant * mass) in [au^3 / day^2]. 184 This side-steps issues of not knowing the exact values of G and masses M[i]; 185 the products GM[i] are known extremely accurately. 186 */ 187 static const double SUN_GM = 0.2959122082855911e-03; 188 static const double JUPITER_GM = 0.2825345909524226e-06; 189 static const double SATURN_GM = 0.8459715185680659e-07; 190 static const double URANUS_GM = 0.1292024916781969e-07; 191 static const double NEPTUNE_GM = 0.1524358900784276e-07; 192 193 /** @cond DOXYGEN_SKIP */ 194 #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) 195 #define AU_PER_PARSEC (ASEC180 / PI) /* exact definition of how many AU = one parsec */ 196 #define Y2000_IN_MJD (T0 - MJD_BASIS) 197 /** @endcond */ 198 199 static astro_ecliptic_t RotateEquatorialToEcliptic(const double pos[3], double obliq_radians); 200 static int QuadInterp( 201 double tm, double dt, double fa, double fm, double fb, 202 double *x, double *t, double *df_dt); 203 204 static double LongitudeOffset(double diff) 205 { 206 double offset = diff; 207 208 while (offset <= -180.0) 209 offset += 360.0; 210 211 while (offset > 180.0) 212 offset -= 360.0; 213 214 return offset; 215 } 216 217 static double NormalizeLongitude(double lon) 218 { 219 while (lon < 0.0) 220 lon += 360.0; 221 222 while (lon >= 360.0) 223 lon -= 360.0; 224 225 return lon; 226 } 227 228 /** 229 * @brief Calculates the length of the given vector. 230 * 231 * Calculates the non-negative length of the given vector. 232 * The length is expressed in the same units as the vector's components, 233 * usually astronomical units (AU). 234 * 235 * @param vector The vector whose length is to be calculated. 236 * @return The length of the vector. 237 */ 238 double Astronomy_VectorLength(astro_vector_t vector) 239 { 240 return sqrt(vector.x*vector.x + vector.y*vector.y + vector.z*vector.z); 241 } 242 243 /** 244 * @brief Finds the name of a celestial body. 245 * @param body The celestial body whose name is to be found. 246 * @return The English-language name of the celestial body, or "" if the body is not valid. 247 */ 248 const char *Astronomy_BodyName(astro_body_t body) 249 { 250 switch (body) 251 { 252 case BODY_MERCURY: return "Mercury"; 253 case BODY_VENUS: return "Venus"; 254 case BODY_EARTH: return "Earth"; 255 case BODY_MARS: return "Mars"; 256 case BODY_JUPITER: return "Jupiter"; 257 case BODY_SATURN: return "Saturn"; 258 case BODY_URANUS: return "Uranus"; 259 case BODY_NEPTUNE: return "Neptune"; 260 case BODY_PLUTO: return "Pluto"; 261 case BODY_SUN: return "Sun"; 262 case BODY_MOON: return "Moon"; 263 case BODY_EMB: return "EMB"; 264 case BODY_SSB: return "SSB"; 265 default: return ""; 266 } 267 } 268 269 /** 270 * @brief Returns the #astro_body_t value corresponding to the given English name. 271 * @param name One of the following strings: Sun, Moon, Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, EMB, SSB. 272 * @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`. 273 */ 274 astro_body_t Astronomy_BodyCode(const char *name) 275 { 276 if (name != NULL) 277 { 278 if (!strcmp(name, "Mercury")) return BODY_MERCURY; 279 if (!strcmp(name, "Venus")) return BODY_VENUS; 280 if (!strcmp(name, "Earth")) return BODY_EARTH; 281 if (!strcmp(name, "Mars")) return BODY_MARS; 282 if (!strcmp(name, "Jupiter")) return BODY_JUPITER; 283 if (!strcmp(name, "Saturn")) return BODY_SATURN; 284 if (!strcmp(name, "Uranus")) return BODY_URANUS; 285 if (!strcmp(name, "Neptune")) return BODY_NEPTUNE; 286 if (!strcmp(name, "Pluto")) return BODY_PLUTO; 287 if (!strcmp(name, "Sun")) return BODY_SUN; 288 if (!strcmp(name, "Moon")) return BODY_MOON; 289 if (!strcmp(name, "EMB")) return BODY_EMB; 290 if (!strcmp(name, "SSB")) return BODY_SSB; 291 } 292 return BODY_INVALID; 293 } 294 295 /** 296 * @brief Returns 1 for planets that are farther from the Sun than the Earth is, 0 otherwise. 297 */ 298 static int IsSuperiorPlanet(astro_body_t body) 299 { 300 switch (body) 301 { 302 case BODY_MARS: 303 case BODY_JUPITER: 304 case BODY_SATURN: 305 case BODY_URANUS: 306 case BODY_NEPTUNE: 307 case BODY_PLUTO: 308 return 1; 309 310 default: 311 return 0; 312 } 313 } 314 315 /** 316 * @brief Returns the number of days it takes for a planet to orbit the Sun. 317 */ 318 static double PlanetOrbitalPeriod(astro_body_t body) 319 { 320 switch (body) 321 { 322 case BODY_MERCURY: return 87.969; 323 case BODY_VENUS: return 224.701; 324 case BODY_EARTH: return EARTH_ORBITAL_PERIOD; 325 case BODY_MARS: return 686.980; 326 case BODY_JUPITER: return 4332.589; 327 case BODY_SATURN: return 10759.22; 328 case BODY_URANUS: return 30685.4; 329 case BODY_NEPTUNE: return NEPTUNE_ORBITAL_PERIOD; 330 case BODY_PLUTO: return 90560.0; 331 default: return 0.0; /* invalid body */ 332 } 333 } 334 335 static void FatalError(const char *message) 336 { 337 fprintf(stderr, "FATAL: %s\n", message); 338 exit(1); 339 } 340 341 static astro_vector_t VecError(astro_status_t status, astro_time_t time) 342 { 343 astro_vector_t vec; 344 vec.x = vec.y = vec.z = NAN; 345 vec.t = time; 346 vec.status = status; 347 return vec; 348 } 349 350 static astro_spherical_t SphereError(astro_status_t status) 351 { 352 astro_spherical_t sphere; 353 sphere.status = status; 354 sphere.dist = sphere.lat = sphere.lon = NAN; 355 return sphere; 356 } 357 358 static astro_equatorial_t EquError(astro_status_t status) 359 { 360 astro_equatorial_t equ; 361 equ.ra = equ.dec = equ.dist = NAN; 362 equ.status = status; 363 return equ; 364 } 365 366 static astro_ecliptic_t EclError(astro_status_t status) 367 { 368 astro_ecliptic_t ecl; 369 ecl.status = status; 370 ecl.ex = ecl.ey = ecl.ez = ecl.elat = ecl.elon = NAN; 371 return ecl; 372 } 373 374 static astro_angle_result_t AngleError(astro_status_t status) 375 { 376 astro_angle_result_t result; 377 result.status = status; 378 result.angle = NAN; 379 return result; 380 } 381 382 static astro_func_result_t FuncError(astro_status_t status) 383 { 384 astro_func_result_t result; 385 result.status = status; 386 result.value = NAN; 387 return result; 388 } 389 390 static astro_time_t TimeError(void) 391 { 392 astro_time_t time; 393 time.tt = time.ut = time.eps = time.psi = NAN; 394 return time; 395 } 396 397 static astro_rotation_t RotationErr(astro_status_t status) 398 { 399 astro_rotation_t rotation; 400 int i, j; 401 402 rotation.status = status; 403 for (i=0; i<3; ++i) 404 for (j=0; j<3; ++j) 405 rotation.rot[i][j] = NAN; 406 407 return rotation; 408 } 409 410 static astro_moon_quarter_t MoonQuarterError(astro_status_t status) 411 { 412 astro_moon_quarter_t result; 413 result.status = status; 414 result.quarter = -1; 415 result.time = TimeError(); 416 return result; 417 } 418 419 static astro_elongation_t ElongError(astro_status_t status) 420 { 421 astro_elongation_t result; 422 423 result.status = status; 424 result.elongation = NAN; 425 result.ecliptic_separation = NAN; 426 result.time = TimeError(); 427 result.visibility = (astro_visibility_t)(-1); 428 429 return result; 430 } 431 432 static astro_hour_angle_t HourAngleError(astro_status_t status) 433 { 434 astro_hour_angle_t result; 435 436 result.status = status; 437 result.time = TimeError(); 438 result.hor.altitude = result.hor.azimuth = result.hor.dec = result.hor.ra = NAN; 439 440 return result; 441 } 442 443 static astro_illum_t IllumError(astro_status_t status) 444 { 445 astro_illum_t result; 446 447 result.status = status; 448 result.time = TimeError(); 449 result.mag = NAN; 450 result.phase_angle = NAN; 451 result.helio_dist = NAN; 452 result.ring_tilt = NAN; 453 454 return result; 455 } 456 457 static astro_apsis_t ApsisError(astro_status_t status) 458 { 459 astro_apsis_t result; 460 461 result.status = status; 462 result.time = TimeError(); 463 result.kind = APSIS_INVALID; 464 result.dist_km = result.dist_au = NAN; 465 466 return result; 467 } 468 469 static astro_search_result_t SearchError(astro_status_t status) 470 { 471 astro_search_result_t result; 472 result.time = TimeError(); 473 result.status = status; 474 return result; 475 } 476 477 static astro_constellation_t ConstelErr(astro_status_t status) 478 { 479 astro_constellation_t constel; 480 constel.status = status; 481 constel.symbol = constel.name = NULL; 482 constel.ra_1875 = constel.dec_1875 = NAN; 483 return constel; 484 } 485 486 static astro_transit_t TransitErr(astro_status_t status) 487 { 488 astro_transit_t transit; 489 transit.status = status; 490 transit.start = transit.peak = transit.finish = TimeError(); 491 transit.separation = NAN; 492 return transit; 493 } 494 495 static astro_func_result_t SynodicPeriod(astro_body_t body) 496 { 497 double Tp; /* planet's orbital period in days */ 498 astro_func_result_t result; 499 500 /* The Earth does not have a synodic period as seen from itself. */ 501 if (body == BODY_EARTH) 502 return FuncError(ASTRO_EARTH_NOT_ALLOWED); 503 504 if (body == BODY_MOON) 505 { 506 result.status = ASTRO_SUCCESS; 507 result.value = MEAN_SYNODIC_MONTH; 508 return result; 509 } 510 511 Tp = PlanetOrbitalPeriod(body); 512 if (Tp <= 0.0) 513 return FuncError(ASTRO_INVALID_BODY); 514 515 result.status = ASTRO_SUCCESS; 516 result.value = fabs(EARTH_ORBITAL_PERIOD / (EARTH_ORBITAL_PERIOD/Tp - 1.0)); 517 return result; 518 } 519 520 static astro_angle_result_t AngleBetween(astro_vector_t a, astro_vector_t b) 521 { 522 double r, dot; 523 astro_angle_result_t result; 524 525 r = Astronomy_VectorLength(a) * Astronomy_VectorLength(b); 526 if (r < 1.0e-8) 527 return AngleError(ASTRO_BAD_VECTOR); 528 529 dot = (a.x*b.x + a.y*b.y + a.z*b.z) / r; 530 531 if (dot <= -1.0) 532 result.angle = 180.0; 533 else if (dot >= +1.0) 534 result.angle = 0.0; 535 else 536 result.angle = RAD2DEG * acos(dot); 537 538 result.status = ASTRO_SUCCESS; 539 return result; 540 } 541 542 /** 543 * @brief The default Delta T function used by Astronomy Engine. 544 * 545 * Espenak and Meeus use a series of piecewise polynomials to 546 * approximate DeltaT of the Earth in their "Five Millennium Canon of Solar Eclipses". 547 * See: https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html 548 * This is the default Delta T function used by Astronomy Engine. 549 * 550 * @param ut 551 * The floating point number of days since noon UTC on January 1, 2000. 552 * 553 * @returns 554 * The estimated difference TT-UT on the given date, expressed in seconds. 555 */ 556 double Astronomy_DeltaT_EspenakMeeus(double ut) 557 { 558 double y, u, u2, u3, u4, u5, u6, u7; 559 560 /* 561 Fred Espenak writes about Delta-T generically here: 562 https://eclipse.gsfc.nasa.gov/SEhelp/deltaT.html 563 https://eclipse.gsfc.nasa.gov/SEhelp/deltat2004.html 564 565 He provides polynomial approximations for distant years here: 566 https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html 567 568 They start with a year value 'y' such that y=2000 corresponds 569 to the UTC Date 15-January-2000. Convert difference in days 570 to mean tropical years. 571 */ 572 573 y = 2000 + ((ut - 14) / DAYS_PER_TROPICAL_YEAR); 574 575 if (y < -500) 576 { 577 u = (y - 1820) / 100; 578 return -20 + (32 * u*u); 579 } 580 if (y < 500) 581 { 582 u = y / 100; 583 u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; u6 = u3*u3; 584 return 10583.6 - 1014.41*u + 33.78311*u2 - 5.952053*u3 - 0.1798452*u4 + 0.022174192*u5 + 0.0090316521*u6; 585 } 586 if (y < 1600) 587 { 588 u = (y - 1000) / 100; 589 u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; u6 = u3*u3; 590 return 1574.2 - 556.01*u + 71.23472*u2 + 0.319781*u3 - 0.8503463*u4 - 0.005050998*u5 + 0.0083572073*u6; 591 } 592 if (y < 1700) 593 { 594 u = y - 1600; 595 u2 = u*u; u3 = u*u2; 596 return 120 - 0.9808*u - 0.01532*u2 + u3/7129.0; 597 } 598 if (y < 1800) 599 { 600 u = y - 1700; 601 u2 = u*u; u3 = u*u2; u4 = u2*u2; 602 return 8.83 + 0.1603*u - 0.0059285*u2 + 0.00013336*u3 - u4/1174000; 603 } 604 if (y < 1860) 605 { 606 u = y - 1800; 607 u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; u6 = u3*u3; u7 = u3*u4; 608 return 13.72 - 0.332447*u + 0.0068612*u2 + 0.0041116*u3 - 0.00037436*u4 + 0.0000121272*u5 - 0.0000001699*u6 + 0.000000000875*u7; 609 } 610 if (y < 1900) 611 { 612 u = y - 1860; 613 u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; 614 return 7.62 + 0.5737*u - 0.251754*u2 + 0.01680668*u3 - 0.0004473624*u4 + u5/233174; 615 } 616 if (y < 1920) 617 { 618 u = y - 1900; 619 u2 = u*u; u3 = u*u2; u4 = u2*u2; 620 return -2.79 + 1.494119*u - 0.0598939*u2 + 0.0061966*u3 - 0.000197*u4; 621 } 622 if (y < 1941) 623 { 624 u = y - 1920; 625 u2 = u*u; u3 = u*u2; 626 return 21.20 + 0.84493*u - 0.076100*u2 + 0.0020936*u3; 627 } 628 if (y < 1961) 629 { 630 u = y - 1950; 631 u2 = u*u; u3 = u*u2; 632 return 29.07 + 0.407*u - u2/233 + u3/2547; 633 } 634 if (y < 1986) 635 { 636 u = y - 1975; 637 u2 = u*u; u3 = u*u2; 638 return 45.45 + 1.067*u - u2/260 - u3/718; 639 } 640 if (y < 2005) 641 { 642 u = y - 2000; 643 u2 = u*u; u3 = u*u2; u4 = u2*u2; u5 = u2*u3; 644 return 63.86 + 0.3345*u - 0.060374*u2 + 0.0017275*u3 + 0.000651814*u4 + 0.00002373599*u5; 645 } 646 if (y < 2050) 647 { 648 u = y - 2000; 649 return 62.92 + 0.32217*u + 0.005589*u*u; 650 } 651 if (y < 2150) 652 { 653 u = (y-1820)/100; 654 return -20 + 32*u*u - 0.5628*(2150 - y); 655 } 656 657 /* all years after 2150 */ 658 u = (y - 1820) / 100; 659 return -20 + (32 * u*u); 660 } 661 662 /** 663 * @brief A Delta T function that approximates the one used by the JPL Horizons tool. 664 * 665 * In order to support unit tests based on data generated by the JPL Horizons online 666 * tool, I had to reverse engineer their Delta T function by generating a table that 667 * contained it. The main difference between their tool and the Espenak/Meeus function 668 * is that they stop extrapolating the Earth's deceleration after the year 2017. 669 * 670 * @param ut 671 * The floating point number of days since noon UTC on January 1, 2000. 672 * 673 * @returns 674 * The estimated difference TT-UT on the given date, expressed in seconds. 675 */ 676 double Astronomy_DeltaT_JplHorizons(double ut) 677 { 678 if (ut > 17.0 * DAYS_PER_TROPICAL_YEAR) 679 ut = 17.0 * DAYS_PER_TROPICAL_YEAR; 680 681 return Astronomy_DeltaT_EspenakMeeus(ut); 682 } 683 684 static astro_deltat_func DeltaTFunc = Astronomy_DeltaT_EspenakMeeus; 685 686 /** 687 * @brief Changes the function Astronomy Engine uses to calculate Delta T. 688 * 689 * Most programs should not call this function. It is for advanced use cases only. 690 * By default, Astronomy Engine uses the function #Astronomy_DeltaT_EspenakMeeus 691 * to estimate changes in the Earth's rotation rate over time. 692 * However, for the sake of unit tests that compare calculations against 693 * external data sources that use alternative models for Delta T, 694 * it is sometimes useful to replace the Delta T model to match. 695 * This function allows replacing the Delta T model with any other 696 * desired model. 697 * 698 * @param func 699 * A pointer to a function to convert UT values to DeltaT values. 700 */ 701 void Astronomy_SetDeltaTFunction(astro_deltat_func func) 702 { 703 DeltaTFunc = func; 704 } 705 706 static double TerrestrialTime(double ut) 707 { 708 return ut + DeltaTFunc(ut)/86400.0; 709 } 710 711 /** 712 * @brief 713 * Converts a J2000 day value to an #astro_time_t value. 714 * 715 * This function can be useful for reproducing an #astro_time_t structure 716 * from its `ut` field only. 717 * 718 * @param ut 719 * The floating point number of days since noon UTC on January 1, 2000. 720 * 721 * @returns 722 * An #astro_time_t value for the given `ut` value. 723 */ 724 astro_time_t Astronomy_TimeFromDays(double ut) 725 { 726 astro_time_t time; 727 time.ut = ut; 728 time.tt = TerrestrialTime(ut); 729 time.psi = time.eps = NAN; 730 return time; 731 } 732 733 /** 734 * @brief Returns the computer's current date and time in the form of an #astro_time_t. 735 * 736 * Uses the computer's system clock to find the current UTC date and time with 1-second granularity. 737 * Converts that date and time to an #astro_time_t value and returns the result. 738 * Callers can pass this value to other Astronomy Engine functions to calculate 739 * current observational conditions. 740 */ 741 astro_time_t Astronomy_CurrentTime(void) 742 { 743 astro_time_t t; 744 745 /* Get seconds since midnight January 1, 1970, divide to convert to days, */ 746 /* then subtract to get days since noon on January 1, 2000. */ 747 748 t.ut = (time(NULL) / SECONDS_PER_DAY) - 10957.5; 749 t.tt = TerrestrialTime(t.ut); 750 t.psi = t.eps = NAN; 751 return t; 752 } 753 754 /** 755 * @brief Creates an #astro_time_t value from a given calendar date and time. 756 * 757 * Given a UTC calendar date and time, calculates an #astro_time_t value that can 758 * be passed to other Astronomy Engine functions for performing various calculations 759 * relating to that date and time. 760 * 761 * It is the caller's responsibility to ensure that the parameter values are correct. 762 * The parameters are not checked for validity, 763 * and this function never returns any indication of an error. 764 * Invalid values, for example passing in February 31, may cause unexpected return values. 765 * 766 * @param year The UTC calendar year, e.g. 2019. 767 * @param month The UTC calendar month in the range 1..12. 768 * @param day The UTC calendar day in the range 1..31. 769 * @param hour The UTC hour of the day in the range 0..23. 770 * @param minute The UTC minute in the range 0..59. 771 * @param second The UTC floating-point second in the range [0, 60). 772 * 773 * @return An #astro_time_t value that represents the given calendar date and time. 774 */ 775 astro_time_t Astronomy_MakeTime(int year, int month, int day, int hour, int minute, double second) 776 { 777 astro_time_t time; 778 long int jd12h; 779 long int y2000; 780 781 /* This formula is adapted from NOVAS C 3.1 function julian_date() */ 782 jd12h = (long) day - 32075L + 1461L * ((long) year + 4800L 783 + ((long) month - 14L) / 12L) / 4L 784 + 367L * ((long) month - 2L - ((long) month - 14L) / 12L * 12L) 785 / 12L - 3L * (((long) year + 4900L + ((long) month - 14L) / 12L) 786 / 100L) / 4L; 787 788 y2000 = jd12h - 2451545L; 789 790 time.ut = (double)y2000 - 0.5 + (hour / 24.0) + (minute / (24.0 * 60.0)) + (second / (24.0 * 3600.0)); 791 time.tt = TerrestrialTime(time.ut); 792 time.psi = time.eps = NAN; 793 794 return time; 795 } 796 797 /** 798 * @brief Calculates the sum or difference of an #astro_time_t with a specified floating point number of days. 799 * 800 * Sometimes we need to adjust a given #astro_time_t value by a certain amount of time. 801 * This function adds the given real number of days in `days` to the date and time in `time`. 802 * 803 * More precisely, the result's Universal Time field `ut` is exactly adjusted by `days` and 804 * the Terrestrial Time field `tt` is adjusted correctly for the resulting UTC date and time, 805 * according to the historical and predictive Delta-T model provided by the 806 * [United States Naval Observatory](http://maia.usno.navy.mil/ser7/). 807 * 808 * The value stored in `time` will not be modified; it is passed by value. 809 * 810 * @param time A date and time for which to calculate an adjusted date and time. 811 * @param days A floating point number of days by which to adjust `time`. May be negative, 0, or positive. 812 * @return A date and time that is conceptually equal to `time + days`. 813 */ 814 astro_time_t Astronomy_AddDays(astro_time_t time, double days) 815 { 816 /* 817 This is slightly wrong, but the error is tiny. 818 We really should be adding to TT, not to UT. 819 But using TT would require creating an inverse function for DeltaT, 820 which would be quite a bit of extra calculation. 821 I estimate the error is in practice on the order of 10^(-7) 822 times the value of 'days'. 823 This is based on a typical drift of 1 second per year between UT and TT. 824 */ 825 826 astro_time_t sum; 827 828 sum.ut = time.ut + days; 829 sum.tt = TerrestrialTime(sum.ut); 830 sum.eps = sum.psi = NAN; 831 832 return sum; 833 } 834 835 /** 836 * @brief Creates an #astro_time_t value from a given calendar date and time. 837 * 838 * This function is similar to #Astronomy_MakeTime, only it receives a 839 * UTC calendar date and time in the form of an #astro_utc_t structure instead of 840 * as separate numeric parameters. Astronomy_TimeFromUtc is the inverse of 841 * #Astronomy_UtcFromTime. 842 * 843 * @param utc The UTC calendar date and time to be converted to #astro_time_t. 844 * @return A value that can be used for astronomical calculations for the given date and time. 845 */ 846 astro_time_t Astronomy_TimeFromUtc(astro_utc_t utc) 847 { 848 return Astronomy_MakeTime(utc.year, utc.month, utc.day, utc.hour, utc.minute, utc.second); 849 } 850 851 /** 852 * @brief Determines the calendar year, month, day, and time from an #astro_time_t value. 853 * 854 * After calculating the date and time of an astronomical event in the form of 855 * an #astro_time_t value, it is often useful to display the result in a human-readable 856 * form. This function converts the linear time scales in the `ut` field of #astro_time_t 857 * into a calendar date and time: year, month, day, hours, minutes, and seconds, expressed 858 * in UTC. 859 * 860 * @param time The astronomical time value to be converted to calendar date and time. 861 * @return A date and time broken out into conventional year, month, day, hour, minute, and second. 862 */ 863 astro_utc_t Astronomy_UtcFromTime(astro_time_t time) 864 { 865 /* Adapted from the NOVAS C 3.1 function cal_date() */ 866 astro_utc_t utc; 867 long jd, k, m, n; 868 double djd, x; 869 870 djd = time.ut + 2451545.5; 871 jd = (long)djd; 872 873 x = 24.0 * fmod(djd, 1.0); 874 utc.hour = (int)x; 875 x = 60.0 * fmod(x, 1.0); 876 utc.minute = (int)x; 877 utc.second = 60.0 * fmod(x, 1.0); 878 879 k = jd + 68569L; 880 n = 4L * k / 146097L; 881 k = k - (146097L * n + 3L) / 4L; 882 m = 4000L * (k + 1L) / 1461001L; 883 k = k - 1461L * m / 4L + 31L; 884 885 utc.month = (int) (80L * k / 2447L); 886 utc.day = (int) (k - 2447L * (long)utc.month / 80L); 887 k = (long) utc.month / 11L; 888 889 utc.month = (int) ((long)utc.month + 2L - 12L * k); 890 utc.year = (int) (100L * (n - 49L) + m + k); 891 892 return utc; 893 } 894 895 896 /** 897 * @brief Formats an #astro_time_t value as an ISO 8601 string. 898 * 899 * Given an #astro_time_t value `time`, formats it as an ISO 8601 900 * string to the resolution specified by the `format` parameter. 901 * The result is stored in the `text` buffer whose capacity in bytes 902 * is specified by `size`. 903 * 904 * @param time 905 * The date and time whose civil time `time.ut` is to be formatted as an ISO 8601 string. 906 * If the civil time is outside the year range 0000 to 9999, the function fails 907 * and returns `ASTRO_BAD_TIME`. Years prior to 1583 are treated as if they are 908 * using the modern Gregorian calendar, even when the Julian calendar was actually in effect. 909 * 910 * @param format 911 * Specifies the resolution to which the date and time should be formatted, 912 * as explained at #astro_time_format_t. 913 * If the value of `format` is not recognized, the function fails and 914 * returns `ASTRO_INVALID_PARAMETER`. 915 * 916 * @param text 917 * A pointer to a text buffer to receive the output. 918 * If `text` is `NULL`, this function returns `ASTRO_INVALID_PARAMETER`. 919 * If the function fails for any reason, and `text` is not `NULL`, 920 * and `size` is greater than 0, the `text` buffer is set to an empty string. 921 * 922 * @param size 923 * The size in bytes of the buffer pointed to by `text`. The buffer must 924 * be large enough to accomodate the output format selected by the 925 * `format` parameter, as specified at #astro_time_format_t. 926 * If `size` is too small to hold the string as specified by `format`, 927 * the `text` buffer is set to `""` (if possible) 928 * and the function returns `ASTRO_BUFFER_TOO_SMALL`. 929 * A buffer that is `TIME_TEXT_BYTES` (25) bytes or larger is always large enough for this function. 930 * 931 * @return `ASTRO_SUCCESS` on success; otherwise an error as described in the parameter notes. 932 */ 933 astro_status_t Astronomy_FormatTime( 934 astro_time_t time, 935 astro_time_format_t format, 936 char *text, 937 size_t size) 938 { 939 int nprinted; 940 double rounding; 941 size_t min_size; 942 astro_utc_t utc; 943 944 if (text == NULL) 945 return ASTRO_INVALID_PARAMETER; 946 947 if (size == 0) 948 return ASTRO_BUFFER_TOO_SMALL; 949 950 text[0] = '\0'; /* initialize to empty string, in case an error occurs */ 951 952 /* Validate 'size' parameter and perform date/time rounding. */ 953 switch (format) 954 { 955 case TIME_FORMAT_DAY: 956 min_size = 11; /* "2020-12-31" */ 957 rounding = 0.0; /* no rounding */ 958 break; 959 960 case TIME_FORMAT_MINUTE: 961 min_size = 18; /* "2020-12-31T15:47Z" */ 962 rounding = 0.5 / (24.0 * 60.0); /* round to nearest minute */ 963 break; 964 965 case TIME_FORMAT_SECOND: 966 min_size = 21; /* "2020-12-31T15:47:59Z" */ 967 rounding = 0.5 / (24.0 * 3600.0); /* round to nearest second */ 968 break; 969 970 case TIME_FORMAT_MILLI: 971 min_size = 25; /* "2020-12-31T15:47:59.123Z" */ 972 rounding = 0.5 / (24.0 * 3600000.0); /* round to nearest millisecond */ 973 break; 974 975 default: 976 return ASTRO_INVALID_PARAMETER; 977 } 978 979 /* Check for insufficient buffer size. */ 980 if (size < min_size) 981 return ASTRO_BUFFER_TOO_SMALL; 982 983 /* Perform rounding. */ 984 time.ut += rounding; 985 986 /* Convert linear J2000 days to Gregorian UTC date/time. */ 987 utc = Astronomy_UtcFromTime(time); 988 989 /* We require the year to be formatted as a 4-digit non-negative integer. */ 990 if (utc.year < 0 || utc.year > 9999) 991 return ASTRO_BAD_TIME; 992 993 /* Format the string. */ 994 switch (format) 995 { 996 case TIME_FORMAT_DAY: 997 nprinted = snprintf(text, size, "%04d-%02d-%02d", 998 utc.year, utc.month, utc.day); 999 break; 1000 1001 case TIME_FORMAT_MINUTE: 1002 nprinted = snprintf(text, size, "%04d-%02d-%02dT%02d:%02dZ", 1003 utc.year, utc.month, utc.day, 1004 utc.hour, utc.minute); 1005 break; 1006 1007 case TIME_FORMAT_SECOND: 1008 nprinted = snprintf(text, size, "%04d-%02d-%02dT%02d:%02d:%02.0lfZ", 1009 utc.year, utc.month, utc.day, 1010 utc.hour, utc.minute, floor(utc.second)); 1011 break; 1012 1013 case TIME_FORMAT_MILLI: 1014 nprinted = snprintf(text, size, "%04d-%02d-%02dT%02d:%02d:%06.3lfZ", 1015 utc.year, utc.month, utc.day, 1016 utc.hour, utc.minute, floor(1000.0 * utc.second) / 1000.0); 1017 break; 1018 1019 default: 1020 /* We should have already failed for any unknown 'format' value. */ 1021 return ASTRO_INTERNAL_ERROR; 1022 } 1023 1024 if (nprinted < 0) 1025 return ASTRO_INTERNAL_ERROR; /* should not be possible for snprintf to return a negative number */ 1026 1027 if (1+(int)nprinted != min_size) 1028 return ASTRO_INTERNAL_ERROR; /* there must be a bug calculating min_size or formatting the string */ 1029 1030 return ASTRO_SUCCESS; 1031 } 1032 1033 1034 /** 1035 * @brief Creates an observer object that represents a location on or near the surface of the Earth. 1036 * 1037 * Some Astronomy Engine functions calculate values pertaining to an observer on the Earth. 1038 * These functions require a value of type #astro_observer_t that represents the location 1039 * of such an observer. 1040 * 1041 * @param latitude The geographic latitude of the observer in degrees north (positive) or south (negative) of the equator. 1042 * @param longitude The geographic longitude of the observer in degrees east (positive) or west (negative) of the prime meridian at Greenwich, England. 1043 * @param height The height of the observer in meters above mean sea level. 1044 * @return An observer object that can be passed to astronomy functions that require a geographic location. 1045 */ 1046 astro_observer_t Astronomy_MakeObserver(double latitude, double longitude, double height) 1047 { 1048 astro_observer_t observer; 1049 1050 observer.latitude = latitude; 1051 observer.longitude = longitude; 1052 observer.height = height; 1053 1054 return observer; 1055 } 1056 1057 static void iau2000b(astro_time_t *time) 1058 { 1059 /* Adapted from the NOVAS C 3.1 function of the same name. */ 1060 1061 struct row_t 1062 { 1063 int nals[5]; 1064 double cls[6]; 1065 }; 1066 1067 static const struct row_t row[77] = 1068 { 1069 1070 { { 0, 0, 0, 0, 1 }, { -172064161, -174666, 33386, 92052331, 9086, 15377 } }, 1071 { { 0, 0, 2, -2, 2 }, { -13170906, -1675, -13696, 5730336, -3015, -4587 } }, 1072 { { 0, 0, 2, 0, 2 }, { -2276413, -234, 2796, 978459, -485, 1374 } }, 1073 { { 0, 0, 0, 0, 2 }, { 2074554, 207, -698, -897492, 470, -291 } }, 1074 { { 0, 1, 0, 0, 0 }, { 1475877, -3633, 11817, 73871, -184, -1924 } }, 1075 { { 0, 1, 2, -2, 2 }, { -516821, 1226, -524, 224386, -677, -174 } }, 1076 { { 1, 0, 0, 0, 0 }, { 711159, 73, -872, -6750, 0, 358 } }, 1077 { { 0, 0, 2, 0, 1 }, { -387298, -367, 380, 200728, 18, 318 } }, 1078 { { 1, 0, 2, 0, 2 }, { -301461, -36, 816, 129025, -63, 367 } }, 1079 { { 0, -1, 2, -2, 2 }, { 215829, -494, 111, -95929, 299, 132 } }, 1080 { { 0, 0, 2, -2, 1 }, { 128227, 137, 181, -68982, -9, 39 } }, 1081 { { -1, 0, 2, 0, 2 }, { 123457, 11, 19, -53311, 32, -4 } }, 1082 { { -1, 0, 0, 2, 0 }, { 156994, 10, -168, -1235, 0, 82 } }, 1083 { { 1, 0, 0, 0, 1 }, { 63110, 63, 27, -33228, 0, -9 } }, 1084 { { -1, 0, 0, 0, 1 }, { -57976, -63, -189, 31429, 0, -75 } }, 1085 { { -1, 0, 2, 2, 2 }, { -59641, -11, 149, 25543, -11, 66 } }, 1086 { { 1, 0, 2, 0, 1 }, { -51613, -42, 129, 26366, 0, 78 } }, 1087 { { -2, 0, 2, 0, 1 }, { 45893, 50, 31, -24236, -10, 20 } }, 1088 { { 0, 0, 0, 2, 0 }, { 63384, 11, -150, -1220, 0, 29 } }, 1089 { { 0, 0, 2, 2, 2 }, { -38571, -1, 158, 16452, -11, 68 } }, 1090 { { 0, -2, 2, -2, 2 }, { 32481, 0, 0, -13870, 0, 0 } }, 1091 { { -2, 0, 0, 2, 0 }, { -47722, 0, -18, 477, 0, -25 } }, 1092 { { 2, 0, 2, 0, 2 }, { -31046, -1, 131, 13238, -11, 59 } }, 1093 { { 1, 0, 2, -2, 2 }, { 28593, 0, -1, -12338, 10, -3 } }, 1094 { { -1, 0, 2, 0, 1 }, { 20441, 21, 10, -10758, 0, -3 } }, 1095 { { 2, 0, 0, 0, 0 }, { 29243, 0, -74, -609, 0, 13 } }, 1096 { { 0, 0, 2, 0, 0 }, { 25887, 0, -66, -550, 0, 11 } }, 1097 { { 0, 1, 0, 0, 1 }, { -14053, -25, 79, 8551, -2, -45 } }, 1098 { { -1, 0, 0, 2, 1 }, { 15164, 10, 11, -8001, 0, -1 } }, 1099 { { 0, 2, 2, -2, 2 }, { -15794, 72, -16, 6850, -42, -5 } }, 1100 { { 0, 0, -2, 2, 0 }, { 21783, 0, 13, -167, 0, 13 } }, 1101 { { 1, 0, 0, -2, 1 }, { -12873, -10, -37, 6953, 0, -14 } }, 1102 { { 0, -1, 0, 0, 1 }, { -12654, 11, 63, 6415, 0, 26 } }, 1103 { { -1, 0, 2, 2, 1 }, { -10204, 0, 25, 5222, 0, 15 } }, 1104 { { 0, 2, 0, 0, 0 }, { 16707, -85, -10, 168, -1, 10 } }, 1105 { { 1, 0, 2, 2, 2 }, { -7691, 0, 44, 3268, 0, 19 } }, 1106 { { -2, 0, 2, 0, 0 }, { -11024, 0, -14, 104, 0, 2 } }, 1107 { { 0, 1, 2, 0, 2 }, { 7566, -21, -11, -3250, 0, -5 } }, 1108 { { 0, 0, 2, 2, 1 }, { -6637, -11, 25, 3353, 0, 14 } }, 1109 { { 0, -1, 2, 0, 2 }, { -7141, 21, 8, 3070, 0, 4 } }, 1110 { { 0, 0, 0, 2, 1 }, { -6302, -11, 2, 3272, 0, 4 } }, 1111 { { 1, 0, 2, -2, 1 }, { 5800, 10, 2, -3045, 0, -1 } }, 1112 { { 2, 0, 2, -2, 2 }, { 6443, 0, -7, -2768, 0, -4 } }, 1113 { { -2, 0, 0, 2, 1 }, { -5774, -11, -15, 3041, 0, -5 } }, 1114 { { 2, 0, 2, 0, 1 }, { -5350, 0, 21, 2695, 0, 12 } }, 1115 { { 0, -1, 2, -2, 1 }, { -4752, -11, -3, 2719, 0, -3 } }, 1116 { { 0, 0, 0, -2, 1 }, { -4940, -11, -21, 2720, 0, -9 } }, 1117 { { -1, -1, 0, 2, 0 }, { 7350, 0, -8, -51, 0, 4 } }, 1118 { { 2, 0, 0, -2, 1 }, { 4065, 0, 6, -2206, 0, 1 } }, 1119 { { 1, 0, 0, 2, 0 }, { 6579, 0, -24, -199, 0, 2 } }, 1120 { { 0, 1, 2, -2, 1 }, { 3579, 0, 5, -1900, 0, 1 } }, 1121 { { 1, -1, 0, 0, 0 }, { 4725, 0, -6, -41, 0, 3 } }, 1122 { { -2, 0, 2, 0, 2 }, { -3075, 0, -2, 1313, 0, -1 } }, 1123 { { 3, 0, 2, 0, 2 }, { -2904, 0, 15, 1233, 0, 7 } }, 1124 { { 0, -1, 0, 2, 0 }, { 4348, 0, -10, -81, 0, 2 } }, 1125 { { 1, -1, 2, 0, 2 }, { -2878, 0, 8, 1232, 0, 4 } }, 1126 { { 0, 0, 0, 1, 0 }, { -4230, 0, 5, -20, 0, -2 } }, 1127 { { -1, -1, 2, 2, 2 }, { -2819, 0, 7, 1207, 0, 3 } }, 1128 { { -1, 0, 2, 0, 0 }, { -4056, 0, 5, 40, 0, -2 } }, 1129 { { 0, -1, 2, 2, 2 }, { -2647, 0, 11, 1129, 0, 5 } }, 1130 { { -2, 0, 0, 0, 1 }, { -2294, 0, -10, 1266, 0, -4 } }, 1131 { { 1, 1, 2, 0, 2 }, { 2481, 0, -7, -1062, 0, -3 } }, 1132 { { 2, 0, 0, 0, 1 }, { 2179, 0, -2, -1129, 0, -2 } }, 1133 { { -1, 1, 0, 1, 0 }, { 3276, 0, 1, -9, 0, 0 } }, 1134 { { 1, 1, 0, 0, 0 }, { -3389, 0, 5, 35, 0, -2 } }, 1135 { { 1, 0, 2, 0, 0 }, { 3339, 0, -13, -107, 0, 1 } }, 1136 { { -1, 0, 2, -2, 1 }, { -1987, 0, -6, 1073, 0, -2 } }, 1137 { { 1, 0, 0, 0, 2 }, { -1981, 0, 0, 854, 0, 0 } }, 1138 { { -1, 0, 0, 1, 0 }, { 4026, 0, -353, -553, 0, -139 } }, 1139 { { 0, 0, 2, 1, 2 }, { 1660, 0, -5, -710, 0, -2 } }, 1140 { { -1, 0, 2, 4, 2 }, { -1521, 0, 9, 647, 0, 4 } }, 1141 { { -1, 1, 0, 1, 1 }, { 1314, 0, 0, -700, 0, 0 } }, 1142 { { 0, -2, 2, -2, 1 }, { -1283, 0, 0, 672, 0, 0 } }, 1143 { { 1, 0, 2, 2, 1 }, { -1331, 0, 8, 663, 0, 4 } }, 1144 { { -2, 0, 2, 2, 2 }, { 1383, 0, -2, -594, 0, -2 } }, 1145 { { -1, 0, 0, 0, 2 }, { 1405, 0, 4, -610, 0, 2 } }, 1146 { { 1, 1, 2, -2, 2 }, { 1290, 0, 0, -556, 0, 0 } } 1147 1148 }; 1149 1150 double t, el, elp, f, d, om, arg, dp, de, sarg, carg; 1151 int i; 1152 1153 if (isnan(time->psi)) 1154 { 1155 t = time->tt / 36525; 1156 el = fmod(485868.249036 + t * 1717915923.2178, ASEC360) * ASEC2RAD; 1157 elp = fmod(1287104.79305 + t * 129596581.0481, ASEC360) * ASEC2RAD; 1158 f = fmod(335779.526232 + t * 1739527262.8478, ASEC360) * ASEC2RAD; 1159 d = fmod(1072260.70369 + t * 1602961601.2090, ASEC360) * ASEC2RAD; 1160 om = fmod(450160.398036 - t * 6962890.5431, ASEC360) * ASEC2RAD; 1161 dp = 0; 1162 de = 0; 1163 for (i=76; i >= 0; --i) 1164 { 1165 arg = fmod((row[i].nals[0]*el + row[i].nals[1]*elp + row[i].nals[2]*f + row[i].nals[3]*d + row[i].nals[4]*om), PI2); 1166 sarg = sin(arg); 1167 carg = cos(arg); 1168 dp += (row[i].cls[0] + row[i].cls[1]*t) * sarg + row[i].cls[2]*carg; 1169 de += (row[i].cls[3] + row[i].cls[4]*t) * carg + row[i].cls[5]*sarg; 1170 } 1171 1172 time->psi = -0.000135 + (dp * 1.0e-7); 1173 time->eps = +0.000388 + (de * 1.0e-7); 1174 } 1175 } 1176 1177 static double mean_obliq(double tt) 1178 { 1179 double t = tt / 36525.0; 1180 double asec = 1181 (((( - 0.0000000434 * t 1182 - 0.000000576 ) * t 1183 + 0.00200340 ) * t 1184 - 0.0001831 ) * t 1185 - 46.836769 ) * t + 84381.406; 1186 1187 return asec / 3600.0; 1188 } 1189 1190 /** @cond DOXYGEN_SKIP */ 1191 typedef struct 1192 { 1193 double tt; 1194 double dpsi; 1195 double deps; 1196 double ee; 1197 double mobl; 1198 double tobl; 1199 } 1200 earth_tilt_t; 1201 /** @endcond */ 1202 1203 static earth_tilt_t e_tilt(astro_time_t *time) 1204 { 1205 earth_tilt_t et; 1206 1207 iau2000b(time); 1208 et.dpsi = time->psi; 1209 et.deps = time->eps; 1210 et.mobl = mean_obliq(time->tt); 1211 et.tobl = et.mobl + (et.deps / 3600.0); 1212 et.tt = time->tt; 1213 et.ee = et.dpsi * cos(et.mobl * DEG2RAD) / 15.0; 1214 1215 return et; 1216 } 1217 1218 static void ecl2equ_vec(astro_time_t time, const double ecl[3], double equ[3]) 1219 { 1220 double obl = mean_obliq(time.tt) * DEG2RAD; 1221 double cos_obl = cos(obl); 1222 double sin_obl = sin(obl); 1223 1224 equ[0] = ecl[0]; 1225 equ[1] = ecl[1]*cos_obl - ecl[2]*sin_obl; 1226 equ[2] = ecl[1]*sin_obl + ecl[2]*cos_obl; 1227 } 1228 1229 1230 static astro_rotation_t precession_rot(double tt1, double tt2) 1231 { 1232 astro_rotation_t rotation; 1233 double xx, yx, zx, xy, yy, zy, xz, yz, zz; 1234 double t, psia, omegaa, chia, sa, ca, sb, cb, sc, cc, sd, cd; 1235 double eps0 = 84381.406; 1236 1237 if ((tt1 != 0.0) && (tt2 != 0.0)) 1238 FatalError("precession_rot: one of (tt1, tt2) must be zero."); 1239 1240 t = (tt2 - tt1) / 36525; 1241 if (tt2 == 0) 1242 t = -t; 1243 1244 psia = (((((- 0.0000000951 * t 1245 + 0.000132851 ) * t 1246 - 0.00114045 ) * t 1247 - 1.0790069 ) * t 1248 + 5038.481507 ) * t); 1249 1250 omegaa = (((((+ 0.0000003337 * t 1251 - 0.000000467 ) * t 1252 - 0.00772503 ) * t 1253 + 0.0512623 ) * t 1254 - 0.025754 ) * t + eps0); 1255 1256 chia = (((((- 0.0000000560 * t 1257 + 0.000170663 ) * t 1258 - 0.00121197 ) * t 1259 - 2.3814292 ) * t 1260 + 10.556403 ) * t); 1261 1262 eps0 = eps0 * ASEC2RAD; 1263 psia = psia * ASEC2RAD; 1264 omegaa = omegaa * ASEC2RAD; 1265 chia = chia * ASEC2RAD; 1266 1267 sa = sin(eps0); 1268 ca = cos(eps0); 1269 sb = sin(-psia); 1270 cb = cos(-psia); 1271 sc = sin(-omegaa); 1272 cc = cos(-omegaa); 1273 sd = sin(chia); 1274 cd = cos(chia); 1275 1276 xx = cd * cb - sb * sd * cc; 1277 yx = cd * sb * ca + sd * cc * cb * ca - sa * sd * sc; 1278 zx = cd * sb * sa + sd * cc * cb * sa + ca * sd * sc; 1279 xy = -sd * cb - sb * cd * cc; 1280 yy = -sd * sb * ca + cd * cc * cb * ca - sa * cd * sc; 1281 zy = -sd * sb * sa + cd * cc * cb * sa + ca * cd * sc; 1282 xz = sb * sc; 1283 yz = -sc * cb * ca - sa * cc; 1284 zz = -sc * cb * sa + cc * ca; 1285 1286 if (tt2 == 0.0) 1287 { 1288 /* Perform rotation from other epoch to J2000.0. */ 1289 rotation.rot[0][0] = xx; 1290 rotation.rot[0][1] = yx; 1291 rotation.rot[0][2] = zx; 1292 rotation.rot[1][0] = xy; 1293 rotation.rot[1][1] = yy; 1294 rotation.rot[1][2] = zy; 1295 rotation.rot[2][0] = xz; 1296 rotation.rot[2][1] = yz; 1297 rotation.rot[2][2] = zz; 1298 } 1299 else 1300 { 1301 /* Perform rotation from J2000.0 to other epoch. */ 1302 rotation.rot[0][0] = xx; 1303 rotation.rot[0][1] = xy; 1304 rotation.rot[0][2] = xz; 1305 rotation.rot[1][0] = yx; 1306 rotation.rot[1][1] = yy; 1307 rotation.rot[1][2] = yz; 1308 rotation.rot[2][0] = zx; 1309 rotation.rot[2][1] = zy; 1310 rotation.rot[2][2] = zz; 1311 } 1312 1313 rotation.status = ASTRO_SUCCESS; 1314 return rotation; 1315 } 1316 1317 1318 static void precession(double tt1, const double pos1[3], double tt2, double pos2[3]) 1319 { 1320 astro_rotation_t r = precession_rot(tt1, tt2); 1321 pos2[0] = r.rot[0][0]*pos1[0] + r.rot[1][0]*pos1[1] + r.rot[2][0]*pos1[2]; 1322 pos2[1] = r.rot[0][1]*pos1[0] + r.rot[1][1]*pos1[1] + r.rot[2][1]*pos1[2]; 1323 pos2[2] = r.rot[0][2]*pos1[0] + r.rot[1][2]*pos1[1] + r.rot[2][2]*pos1[2]; 1324 } 1325 1326 1327 static astro_equatorial_t vector2radec(const double pos[3]) 1328 { 1329 astro_equatorial_t equ; 1330 double xyproj; 1331 1332 xyproj = pos[0]*pos[0] + pos[1]*pos[1]; 1333 equ.dist = sqrt(xyproj + pos[2]*pos[2]); 1334 equ.status = ASTRO_SUCCESS; 1335 if (xyproj == 0.0) 1336 { 1337 if (pos[2] == 0.0) 1338 { 1339 /* Indeterminate coordinates; pos vector has zero length. */ 1340 equ = EquError(ASTRO_BAD_VECTOR); 1341 } 1342 else if (pos[2] < 0) 1343 { 1344 equ.ra = 0.0; 1345 equ.dec = -90.0; 1346 } 1347 else 1348 { 1349 equ.ra = 0.0; 1350 equ.dec = +90.0; 1351 } 1352 } 1353 else 1354 { 1355 equ.ra = atan2(pos[1], pos[0]) / (DEG2RAD * 15.0); 1356 if (equ.ra < 0) 1357 equ.ra += 24.0; 1358 1359 equ.dec = RAD2DEG * atan2(pos[2], sqrt(xyproj)); 1360 } 1361 1362 return equ; 1363 } 1364 1365 1366 static astro_rotation_t nutation_rot(astro_time_t *time, int direction) 1367 { 1368 astro_rotation_t rotation; 1369 earth_tilt_t tilt = e_tilt(time); 1370 double oblm = tilt.mobl * DEG2RAD; 1371 double oblt = tilt.tobl * DEG2RAD; 1372 double psi = tilt.dpsi * ASEC2RAD; 1373 double cobm = cos(oblm); 1374 double sobm = sin(oblm); 1375 double cobt = cos(oblt); 1376 double sobt = sin(oblt); 1377 double cpsi = cos(psi); 1378 double spsi = sin(psi); 1379 1380 double xx = cpsi; 1381 double yx = -spsi * cobm; 1382 double zx = -spsi * sobm; 1383 double xy = spsi * cobt; 1384 double yy = cpsi * cobm * cobt + sobm * sobt; 1385 double zy = cpsi * sobm * cobt - cobm * sobt; 1386 double xz = spsi * sobt; 1387 double yz = cpsi * cobm * sobt - sobm * cobt; 1388 double zz = cpsi * sobm * sobt + cobm * cobt; 1389 1390 if (direction == 0) 1391 { 1392 /* forward rotation */ 1393 rotation.rot[0][0] = xx; 1394 rotation.rot[0][1] = xy; 1395 rotation.rot[0][2] = xz; 1396 rotation.rot[1][0] = yx; 1397 rotation.rot[1][1] = yy; 1398 rotation.rot[1][2] = yz; 1399 rotation.rot[2][0] = zx; 1400 rotation.rot[2][1] = zy; 1401 rotation.rot[2][2] = zz; 1402 } 1403 else 1404 { 1405 /* inverse rotation */ 1406 rotation.rot[0][0] = xx; 1407 rotation.rot[0][1] = yx; 1408 rotation.rot[0][2] = zx; 1409 rotation.rot[1][0] = xy; 1410 rotation.rot[1][1] = yy; 1411 rotation.rot[1][2] = zy; 1412 rotation.rot[2][0] = xz; 1413 rotation.rot[2][1] = yz; 1414 rotation.rot[2][2] = zz; 1415 } 1416 1417 rotation.status = ASTRO_SUCCESS; 1418 return rotation; 1419 } 1420 1421 static void nutation(astro_time_t *time, int direction, const double inpos[3], double outpos[3]) 1422 { 1423 astro_rotation_t r = nutation_rot(time, direction); 1424 outpos[0] = r.rot[0][0]*inpos[0] + r.rot[1][0]*inpos[1] + r.rot[2][0]*inpos[2]; 1425 outpos[1] = r.rot[0][1]*inpos[0] + r.rot[1][1]*inpos[1] + r.rot[2][1]*inpos[2]; 1426 outpos[2] = r.rot[0][2]*inpos[0] + r.rot[1][2]*inpos[1] + r.rot[2][2]*inpos[2]; 1427 } 1428 1429 static double era(double ut) /* Earth Rotation Angle */ 1430 { 1431 double thet1 = 0.7790572732640 + 0.00273781191135448 * ut; 1432 double thet3 = fmod(ut, 1.0); 1433 double theta = 360.0 * fmod(thet1 + thet3, 1.0); 1434 if (theta < 0.0) 1435 theta += 360.0; 1436 1437 return theta; 1438 } 1439 1440 static double sidereal_time(astro_time_t *time) 1441 { 1442 double t = time->tt / 36525.0; 1443 double eqeq = 15.0 * e_tilt(time).ee; /* Replace with eqeq=0 to get GMST instead of GAST (if we ever need it) */ 1444 double theta = era(time->ut); 1445 double st = (eqeq + 0.014506 + 1446 (((( - 0.0000000368 * t 1447 - 0.000029956 ) * t 1448 - 0.00000044 ) * t 1449 + 1.3915817 ) * t 1450 + 4612.156534 ) * t); 1451 1452 double gst = fmod(st/3600.0 + theta, 360.0) / 15.0; 1453 if (gst < 0.0) 1454 gst += 24.0; 1455 1456 return gst; 1457 } 1458 1459 static void terra(astro_observer_t observer, double st, double pos[3]) 1460 { 1461 double df2 = EARTH_FLATTENING * EARTH_FLATTENING; 1462 double phi = observer.latitude * DEG2RAD; 1463 double sinphi = sin(phi); 1464 double cosphi = cos(phi); 1465 double c = 1.0 / sqrt(cosphi*cosphi + df2*sinphi*sinphi); 1466 double s = df2 * c; 1467 double ht_km = observer.height / 1000.0; 1468 double ach = EARTH_EQUATORIAL_RADIUS_KM*c + ht_km; 1469 double ash = EARTH_EQUATORIAL_RADIUS_KM*s + ht_km; 1470 double stlocl = (15.0*st + observer.longitude) * DEG2RAD; 1471 double sinst = sin(stlocl); 1472 double cosst = cos(stlocl); 1473 1474 pos[0] = ach * cosphi * cosst / KM_PER_AU; 1475 pos[1] = ach * cosphi * sinst / KM_PER_AU; 1476 pos[2] = ash * sinphi / KM_PER_AU; 1477 1478 #if 0 1479 /* If we ever need to calculate the observer's velocity vector, here is how NOVAS C 3.1 does it... */ 1480 static const double ANGVEL = 7.2921150e-5; 1481 vel[0] = -ANGVEL * ach * cosphi * sinst * 86400.0; 1482 vel[1] = +ANGVEL * ach * cosphi * cosst * 86400.0; 1483 vel[2] = 0.0; 1484 #endif 1485 } 1486 1487 static void geo_pos(astro_time_t *time, astro_observer_t observer, double outpos[3]) 1488 { 1489 double gast, pos1[3], pos2[3]; 1490 1491 gast = sidereal_time(time); 1492 terra(observer, gast, pos1); 1493 nutation(time, -1, pos1, pos2); 1494 precession(time->tt, pos2, 0.0, outpos); 1495 } 1496 1497 static void spin(double angle, const double pos1[3], double vec2[3]) 1498 { 1499 double angr = angle * DEG2RAD; 1500 double cosang = cos(angr); 1501 double sinang = sin(angr); 1502 vec2[0] = +cosang*pos1[0] + sinang*pos1[1]; 1503 vec2[1] = -sinang*pos1[0] + cosang*pos1[1]; 1504 vec2[2] = pos1[2]; 1505 } 1506 1507 /*------------------ CalcMoon ------------------*/ 1508 1509 /** @cond DOXYGEN_SKIP */ 1510 1511 #define DECLARE_PASCAL_ARRAY_1(elemtype,name,xmin,xmax) \ 1512 elemtype name[(xmax)-(xmin)+1] 1513 1514 #define DECLARE_PASCAL_ARRAY_2(elemtype,name,xmin,xmax,ymin,ymax) \ 1515 elemtype name[(xmax)-(xmin)+1][(ymax)-(ymin)+1] 1516 1517 #define ACCESS_PASCAL_ARRAY_1(name,xmin,x) \ 1518 ((name)[(x)-(xmin)]) 1519 1520 #define ACCESS_PASCAL_ARRAY_2(name,xmin,ymin,x,y) \ 1521 ((name)[(x)-(xmin)][(y)-(ymin)]) 1522 1523 typedef struct 1524 { 1525 double t; 1526 double dgam; 1527 double dlam, n, gam1c, sinpi; 1528 double l0, l, ls, f, d, s; 1529 double dl0, dl, dls, df, dd, ds; 1530 DECLARE_PASCAL_ARRAY_2(double,co,-6,6,1,4); /* ARRAY[-6..6,1..4] OF REAL */ 1531 DECLARE_PASCAL_ARRAY_2(double,si,-6,6,1,4); /* ARRAY[-6..6,1..4] OF REAL */ 1532 } 1533 MoonContext; 1534 1535 #define T (ctx->t) 1536 #define DGAM (ctx->dgam) 1537 #define DLAM (ctx->dlam) 1538 #define N (ctx->n) 1539 #define GAM1C (ctx->gam1c) 1540 #define SINPI (ctx->sinpi) 1541 #define L0 (ctx->l0) 1542 #define L (ctx->l) 1543 #define LS (ctx->ls) 1544 #define F (ctx->f) 1545 #define D (ctx->d) 1546 #define S (ctx->s) 1547 #define DL0 (ctx->dl0) 1548 #define DL (ctx->dl) 1549 #define DLS (ctx->dls) 1550 #define DF (ctx->df) 1551 #define DD (ctx->dd) 1552 #define DS (ctx->ds) 1553 #define CO(x,y) ACCESS_PASCAL_ARRAY_2(ctx->co,-6,1,x,y) 1554 #define SI(x,y) ACCESS_PASCAL_ARRAY_2(ctx->si,-6,1,x,y) 1555 1556 static double Frac(double x) 1557 { 1558 return x - floor(x); 1559 } 1560 1561 static void AddThe( 1562 double c1, double s1, double c2, double s2, 1563 double *c, double *s) 1564 { 1565 *c = c1*c2 - s1*s2; 1566 *s = s1*c2 + c1*s2; 1567 } 1568 1569 static double Sine(double phi) 1570 { 1571 /* sine, of phi in revolutions, not radians */ 1572 return sin(PI2 * phi); 1573 } 1574 1575 static void LongPeriodic(MoonContext *ctx) 1576 { 1577 double S1 = Sine(0.19833+0.05611*T); 1578 double S2 = Sine(0.27869+0.04508*T); 1579 double S3 = Sine(0.16827-0.36903*T); 1580 double S4 = Sine(0.34734-5.37261*T); 1581 double S5 = Sine(0.10498-5.37899*T); 1582 double S6 = Sine(0.42681-0.41855*T); 1583 double S7 = Sine(0.14943-5.37511*T); 1584 1585 DL0 = 0.84*S1+0.31*S2+14.27*S3+ 7.26*S4+ 0.28*S5+0.24*S6; 1586 DL = 2.94*S1+0.31*S2+14.27*S3+ 9.34*S4+ 1.12*S5+0.83*S6; 1587 DLS =-6.40*S1 -1.89*S6; 1588 DF = 0.21*S1+0.31*S2+14.27*S3-88.70*S4-15.30*S5+0.24*S6-1.86*S7; 1589 DD = DL0-DLS; 1590 DGAM = -3332E-9 * Sine(0.59734-5.37261*T) 1591 -539E-9 * Sine(0.35498-5.37899*T) 1592 -64E-9 * Sine(0.39943-5.37511*T); 1593 } 1594 1595 static void Init(MoonContext *ctx) 1596 { 1597 int I, J, MAX; 1598 double T2, ARG, FAC; 1599 1600 T2 = T*T; 1601 DLAM = 0; 1602 DS = 0; 1603 GAM1C = 0; 1604 SINPI = 3422.7000; 1605 LongPeriodic(ctx); 1606 L0 = PI2*Frac(0.60643382+1336.85522467*T-0.00000313*T2) + DL0/ARC; 1607 L = PI2*Frac(0.37489701+1325.55240982*T+0.00002565*T2) + DL /ARC; 1608 LS = PI2*Frac(0.99312619+ 99.99735956*T-0.00000044*T2) + DLS/ARC; 1609 F = PI2*Frac(0.25909118+1342.22782980*T-0.00000892*T2) + DF /ARC; 1610 D = PI2*Frac(0.82736186+1236.85308708*T-0.00000397*T2) + DD /ARC; 1611 for (I=1; I<=4; ++I) 1612 { 1613 switch(I) 1614 { 1615 case 1: ARG=L; MAX=4; FAC=1.000002208; break; 1616 case 2: ARG=LS; MAX=3; FAC=0.997504612-0.002495388*T; break; 1617 case 3: ARG=F; MAX=4; FAC=1.000002708+139.978*DGAM; break; 1618 default: ARG=D; MAX=6; FAC=1.0; break; 1619 } 1620 CO(0,I) = 1.0; 1621 CO(1,I) = cos(ARG)*FAC; 1622 SI(0,I) = 0.0; 1623 SI(1,I) = sin(ARG)*FAC; 1624 for (J=2; J<=MAX; ++J) 1625 AddThe(CO(J-1,I), SI(J-1,I), CO(1,I), SI(1,I), &CO(J,I), &SI(J,I)); 1626 1627 for (J=1; J<=MAX; ++J) 1628 { 1629 CO(-J,I) = CO(J,I); 1630 SI(-J,I) = -SI(J,I); 1631 } 1632 } 1633 } 1634 1635 static void Term(MoonContext *ctx, int p, int q, int r, int s, double *x, double *y) 1636 { 1637 int k; 1638 DECLARE_PASCAL_ARRAY_1(int, i, 1, 4); 1639 #define I(n) ACCESS_PASCAL_ARRAY_1(i,1,n) 1640 1641 I(1) = p; 1642 I(2) = q; 1643 I(3) = r; 1644 I(4) = s; 1645 *x = 1.0; 1646 *y = 0.0; 1647 1648 for (k=1; k<=4; ++k) 1649 if (I(k) != 0.0) 1650 AddThe(*x, *y, CO(I(k), k), SI(I(k), k), x, y); 1651 1652 #undef I 1653 } 1654 1655 static void AddSol( 1656 MoonContext *ctx, 1657 double coeffl, 1658 double coeffs, 1659 double coeffg, 1660 double coeffp, 1661 int p, 1662 int q, 1663 int r, 1664 int s) 1665 { 1666 double x, y; 1667 Term(ctx, p, q, r, s, &x, &y); 1668 DLAM += coeffl*y; 1669 DS += coeffs*y; 1670 GAM1C += coeffg*x; 1671 SINPI += coeffp*x; 1672 } 1673 1674 #define ADDN(coeffn,p,q,r,s) ( Term(ctx, (p),(q),(r),(s),&x,&y), (N += (coeffn)*y) ) 1675 1676 static void SolarN(MoonContext *ctx) 1677 { 1678 double x, y; 1679 1680 N = 0.0; 1681 ADDN(-526.069, 0, 0,1,-2); 1682 ADDN( -3.352, 0, 0,1,-4); 1683 ADDN( +44.297,+1, 0,1,-2); 1684 ADDN( -6.000,+1, 0,1,-4); 1685 ADDN( +20.599,-1, 0,1, 0); 1686 ADDN( -30.598,-1, 0,1,-2); 1687 ADDN( -24.649,-2, 0,1, 0); 1688 ADDN( -2.000,-2, 0,1,-2); 1689 ADDN( -22.571, 0,+1,1,-2); 1690 ADDN( +10.985, 0,-1,1,-2); 1691 } 1692 1693 static void Planetary(MoonContext *ctx) 1694 { 1695 DLAM += 1696 +0.82*Sine(0.7736 -62.5512*T)+0.31*Sine(0.0466 -125.1025*T) 1697 +0.35*Sine(0.5785 -25.1042*T)+0.66*Sine(0.4591+1335.8075*T) 1698 +0.64*Sine(0.3130 -91.5680*T)+1.14*Sine(0.1480+1331.2898*T) 1699 +0.21*Sine(0.5918+1056.5859*T)+0.44*Sine(0.5784+1322.8595*T) 1700 +0.24*Sine(0.2275 -5.7374*T)+0.28*Sine(0.2965 +2.6929*T) 1701 +0.33*Sine(0.3132 +6.3368*T); 1702 } 1703 1704 int _CalcMoonCount; /* Undocumented global for performance tuning. */ 1705 1706 static void CalcMoon( 1707 double centuries_since_j2000, 1708 double *geo_eclip_lon, /* (LAMBDA) equinox of date */ 1709 double *geo_eclip_lat, /* (BETA) equinox of date */ 1710 double *distance_au) /* (R) */ 1711 { 1712 double lat_seconds; 1713 MoonContext context; 1714 MoonContext *ctx = &context; /* goofy, but makes macros work inside this function */ 1715 1716 context.t = centuries_since_j2000; 1717 Init(ctx); 1718 1719 AddSol(ctx, 13.9020, 14.0600, -0.0010, 0.2607, 0, 0, 0, 4); 1720 AddSol(ctx, 0.4030, -4.0100, 0.3940, 0.0023, 0, 0, 0, 3); 1721 AddSol(ctx, 2369.9120, 2373.3600, 0.6010, 28.2333, 0, 0, 0, 2); 1722 AddSol(ctx, -125.1540, -112.7900, -0.7250, -0.9781, 0, 0, 0, 1); 1723 AddSol(ctx, 1.9790, 6.9800, -0.4450, 0.0433, 1, 0, 0, 4); 1724 AddSol(ctx, 191.9530, 192.7200, 0.0290, 3.0861, 1, 0, 0, 2); 1725 AddSol(ctx, -8.4660, -13.5100, 0.4550, -0.1093, 1, 0, 0, 1); 1726 AddSol(ctx, 22639.5000, 22609.0700, 0.0790, 186.5398, 1, 0, 0, 0); 1727 AddSol(ctx, 18.6090, 3.5900, -0.0940, 0.0118, 1, 0, 0,-1); 1728 AddSol(ctx, -4586.4650, -4578.1300, -0.0770, 34.3117, 1, 0, 0,-2); 1729 AddSol(ctx, 3.2150, 5.4400, 0.1920, -0.0386, 1, 0, 0,-3); 1730 AddSol(ctx, -38.4280, -38.6400, 0.0010, 0.6008, 1, 0, 0,-4); 1731 AddSol(ctx, -0.3930, -1.4300, -0.0920, 0.0086, 1, 0, 0,-6); 1732 AddSol(ctx, -0.2890, -1.5900, 0.1230, -0.0053, 0, 1, 0, 4); 1733 AddSol(ctx, -24.4200, -25.1000, 0.0400, -0.3000, 0, 1, 0, 2); 1734 AddSol(ctx, 18.0230, 17.9300, 0.0070, 0.1494, 0, 1, 0, 1); 1735 AddSol(ctx, -668.1460, -126.9800, -1.3020, -0.3997, 0, 1, 0, 0); 1736 AddSol(ctx, 0.5600, 0.3200, -0.0010, -0.0037, 0, 1, 0,-1); 1737 AddSol(ctx, -165.1450, -165.0600, 0.0540, 1.9178, 0, 1, 0,-2); 1738 AddSol(ctx, -1.8770, -6.4600, -0.4160, 0.0339, 0, 1, 0,-4); 1739 AddSol(ctx, 0.2130, 1.0200, -0.0740, 0.0054, 2, 0, 0, 4); 1740 AddSol(ctx, 14.3870, 14.7800, -0.0170, 0.2833, 2, 0, 0, 2); 1741 AddSol(ctx, -0.5860, -1.2000, 0.0540, -0.0100, 2, 0, 0, 1); 1742 AddSol(ctx, 769.0160, 767.9600, 0.1070, 10.1657, 2, 0, 0, 0); 1743 AddSol(ctx, 1.7500, 2.0100, -0.0180, 0.0155, 2, 0, 0,-1); 1744 AddSol(ctx, -211.6560, -152.5300, 5.6790, -0.3039, 2, 0, 0,-2); 1745 AddSol(ctx, 1.2250, 0.9100, -0.0300, -0.0088, 2, 0, 0,-3); 1746 AddSol(ctx, -30.7730, -34.0700, -0.3080, 0.3722, 2, 0, 0,-4); 1747 AddSol(ctx, -0.5700, -1.4000, -0.0740, 0.0109, 2, 0, 0,-6); 1748 AddSol(ctx, -2.9210, -11.7500, 0.7870, -0.0484, 1, 1, 0, 2); 1749 AddSol(ctx, 1.2670, 1.5200, -0.0220, 0.0164, 1, 1, 0, 1); 1750 AddSol(ctx, -109.6730, -115.1800, 0.4610, -0.9490, 1, 1, 0, 0); 1751 AddSol(ctx, -205.9620, -182.3600, 2.0560, 1.4437, 1, 1, 0,-2); 1752 AddSol(ctx, 0.2330, 0.3600, 0.0120, -0.0025, 1, 1, 0,-3); 1753 AddSol(ctx, -4.3910, -9.6600, -0.4710, 0.0673, 1, 1, 0,-4); 1754 AddSol(ctx, 0.2830, 1.5300, -0.1110, 0.0060, 1,-1, 0, 4); 1755 AddSol(ctx, 14.5770, 31.7000, -1.5400, 0.2302, 1,-1, 0, 2); 1756 AddSol(ctx, 147.6870, 138.7600, 0.6790, 1.1528, 1,-1, 0, 0); 1757 AddSol(ctx, -1.0890, 0.5500, 0.0210, 0.0000, 1,-1, 0,-1); 1758 AddSol(ctx, 28.4750, 23.5900, -0.4430, -0.2257, 1,-1, 0,-2); 1759 AddSol(ctx, -0.2760, -0.3800, -0.0060, -0.0036, 1,-1, 0,-3); 1760 AddSol(ctx, 0.6360, 2.2700, 0.1460, -0.0102, 1,-1, 0,-4); 1761 AddSol(ctx, -0.1890, -1.6800, 0.1310, -0.0028, 0, 2, 0, 2); 1762 AddSol(ctx, -7.4860, -0.6600, -0.0370, -0.0086, 0, 2, 0, 0); 1763 AddSol(ctx, -8.0960, -16.3500, -0.7400, 0.0918, 0, 2, 0,-2); 1764 AddSol(ctx, -5.7410, -0.0400, 0.0000, -0.0009, 0, 0, 2, 2); 1765 AddSol(ctx, 0.2550, 0.0000, 0.0000, 0.0000, 0, 0, 2, 1); 1766 AddSol(ctx, -411.6080, -0.2000, 0.0000, -0.0124, 0, 0, 2, 0); 1767 AddSol(ctx, 0.5840, 0.8400, 0.0000, 0.0071, 0, 0, 2,-1); 1768 AddSol(ctx, -55.1730, -52.1400, 0.0000, -0.1052, 0, 0, 2,-2); 1769 AddSol(ctx, 0.2540, 0.2500, 0.0000, -0.0017, 0, 0, 2,-3); 1770 AddSol(ctx, 0.0250, -1.6700, 0.0000, 0.0031, 0, 0, 2,-4); 1771 AddSol(ctx, 1.0600, 2.9600, -0.1660, 0.0243, 3, 0, 0, 2); 1772 AddSol(ctx, 36.1240, 50.6400, -1.3000, 0.6215, 3, 0, 0, 0); 1773 AddSol(ctx, -13.1930, -16.4000, 0.2580, -0.1187, 3, 0, 0,-2); 1774 AddSol(ctx, -1.1870, -0.7400, 0.0420, 0.0074, 3, 0, 0,-4); 1775 AddSol(ctx, -0.2930, -0.3100, -0.0020, 0.0046, 3, 0, 0,-6); 1776 AddSol(ctx, -0.2900, -1.4500, 0.1160, -0.0051, 2, 1, 0, 2); 1777 AddSol(ctx, -7.6490, -10.5600, 0.2590, -0.1038, 2, 1, 0, 0); 1778 AddSol(ctx, -8.6270, -7.5900, 0.0780, -0.0192, 2, 1, 0,-2); 1779 AddSol(ctx, -2.7400, -2.5400, 0.0220, 0.0324, 2, 1, 0,-4); 1780 AddSol(ctx, 1.1810, 3.3200, -0.2120, 0.0213, 2,-1, 0, 2); 1781 AddSol(ctx, 9.7030, 11.6700, -0.1510, 0.1268, 2,-1, 0, 0); 1782 AddSol(ctx, -0.3520, -0.3700, 0.0010, -0.0028, 2,-1, 0,-1); 1783 AddSol(ctx, -2.4940, -1.1700, -0.0030, -0.0017, 2,-1, 0,-2); 1784 AddSol(ctx, 0.3600, 0.2000, -0.0120, -0.0043, 2,-1, 0,-4); 1785 AddSol(ctx, -1.1670, -1.2500, 0.0080, -0.0106, 1, 2, 0, 0); 1786 AddSol(ctx, -7.4120, -6.1200, 0.1170, 0.0484, 1, 2, 0,-2); 1787 AddSol(ctx, -0.3110, -0.6500, -0.0320, 0.0044, 1, 2, 0,-4); 1788 AddSol(ctx, 0.7570, 1.8200, -0.1050, 0.0112, 1,-2, 0, 2); 1789 AddSol(ctx, 2.5800, 2.3200, 0.0270, 0.0196, 1,-2, 0, 0); 1790 AddSol(ctx, 2.5330, 2.4000, -0.0140, -0.0212, 1,-2, 0,-2); 1791 AddSol(ctx, -0.3440, -0.5700, -0.0250, 0.0036, 0, 3, 0,-2); 1792 AddSol(ctx, -0.9920, -0.0200, 0.0000, 0.0000, 1, 0, 2, 2); 1793 AddSol(ctx, -45.0990, -0.0200, 0.0000, -0.0010, 1, 0, 2, 0); 1794 AddSol(ctx, -0.1790, -9.5200, 0.0000, -0.0833, 1, 0, 2,-2); 1795 AddSol(ctx, -0.3010, -0.3300, 0.0000, 0.0014, 1, 0, 2,-4); 1796 AddSol(ctx, -6.3820, -3.3700, 0.0000, -0.0481, 1, 0,-2, 2); 1797 AddSol(ctx, 39.5280, 85.1300, 0.0000, -0.7136, 1, 0,-2, 0); 1798 AddSol(ctx, 9.3660, 0.7100, 0.0000, -0.0112, 1, 0,-2,-2); 1799 AddSol(ctx, 0.2020, 0.0200, 0.0000, 0.0000, 1, 0,-2,-4); 1800 AddSol(ctx, 0.4150, 0.1000, 0.0000, 0.0013, 0, 1, 2, 0); 1801 AddSol(ctx, -2.1520, -2.2600, 0.0000, -0.0066, 0, 1, 2,-2); 1802 AddSol(ctx, -1.4400, -1.3000, 0.0000, 0.0014, 0, 1,-2, 2); 1803 AddSol(ctx, 0.3840, -0.0400, 0.0000, 0.0000, 0, 1,-2,-2); 1804 AddSol(ctx, 1.9380, 3.6000, -0.1450, 0.0401, 4, 0, 0, 0); 1805 AddSol(ctx, -0.9520, -1.5800, 0.0520, -0.0130, 4, 0, 0,-2); 1806 AddSol(ctx, -0.5510, -0.9400, 0.0320, -0.0097, 3, 1, 0, 0); 1807 AddSol(ctx, -0.4820, -0.5700, 0.0050, -0.0045, 3, 1, 0,-2); 1808 AddSol(ctx, 0.6810, 0.9600, -0.0260, 0.0115, 3,-1, 0, 0); 1809 AddSol(ctx, -0.2970, -0.2700, 0.0020, -0.0009, 2, 2, 0,-2); 1810 AddSol(ctx, 0.2540, 0.2100, -0.0030, 0.0000, 2,-2, 0,-2); 1811 AddSol(ctx, -0.2500, -0.2200, 0.0040, 0.0014, 1, 3, 0,-2); 1812 AddSol(ctx, -3.9960, 0.0000, 0.0000, 0.0004, 2, 0, 2, 0); 1813 AddSol(ctx, 0.5570, -0.7500, 0.0000, -0.0090, 2, 0, 2,-2); 1814 AddSol(ctx, -0.4590, -0.3800, 0.0000, -0.0053, 2, 0,-2, 2); 1815 AddSol(ctx, -1.2980, 0.7400, 0.0000, 0.0004, 2, 0,-2, 0); 1816 AddSol(ctx, 0.5380, 1.1400, 0.0000, -0.0141, 2, 0,-2,-2); 1817 AddSol(ctx, 0.2630, 0.0200, 0.0000, 0.0000, 1, 1, 2, 0); 1818 AddSol(ctx, 0.4260, 0.0700, 0.0000, -0.0006, 1, 1,-2,-2); 1819 AddSol(ctx, -0.3040, 0.0300, 0.0000, 0.0003, 1,-1, 2, 0); 1820 AddSol(ctx, -0.3720, -0.1900, 0.0000, -0.0027, 1,-1,-2, 2); 1821 AddSol(ctx, 0.4180, 0.0000, 0.0000, 0.0000, 0, 0, 4, 0); 1822 AddSol(ctx, -0.3300, -0.0400, 0.0000, 0.0000, 3, 0, 2, 0); 1823 1824 SolarN(ctx); 1825 Planetary(ctx); 1826 S = F + DS/ARC; 1827 1828 lat_seconds = (1.000002708 + 139.978*DGAM)*(18518.511+1.189+GAM1C)*sin(S)-6.24*sin(3*S) + N; 1829 1830 *geo_eclip_lon = PI2 * Frac((L0+DLAM/ARC) / PI2); 1831 *geo_eclip_lat = lat_seconds * (DEG2RAD / 3600.0); 1832 *distance_au = (ARC * EARTH_EQUATORIAL_RADIUS_AU) / (0.999953253 * SINPI); 1833 ++_CalcMoonCount; 1834 } 1835 1836 #undef T 1837 #undef DGAM 1838 #undef DLAM 1839 #undef N 1840 #undef GAM1C 1841 #undef SINPI 1842 #undef L0 1843 #undef L 1844 #undef LS 1845 #undef F 1846 #undef D 1847 #undef S 1848 #undef DL0 1849 #undef DL 1850 #undef DLS 1851 #undef DF 1852 #undef DD 1853 #undef DS 1854 #undef CO 1855 #undef SI 1856 1857 /** @endcond */ 1858 1859 /** 1860 * @brief Calculates the geocentric position of the Moon at a given time. 1861 * 1862 * Given a time of observation, calculates the Moon's position as a vector. 1863 * The vector gives the location of the Moon's center relative to the Earth's center 1864 * with x-, y-, and z-components measured in astronomical units. 1865 * 1866 * This algorithm is based on Nautical Almanac Office's *Improved Lunar Ephemeris* of 1954, 1867 * which in turn derives from E. W. Brown's lunar theories from the early twentieth century. 1868 * It is adapted from Turbo Pascal code from the book 1869 * [Astronomy on the Personal Computer](https://www.springer.com/us/book/9783540672210) 1870 * by Montenbruck and Pfleger. 1871 * 1872 * @param time The date and time for which to calculate the Moon's position. 1873 * @return The Moon's position as a vector in J2000 Cartesian equatorial coordinates. 1874 */ 1875 astro_vector_t Astronomy_GeoMoon(astro_time_t time) 1876 { 1877 double geo_eclip_lon, geo_eclip_lat, distance_au; 1878 double dist_cos_lat; 1879 astro_vector_t vector; 1880 double gepos[3]; 1881 double mpos1[3]; 1882 double mpos2[3]; 1883 1884 CalcMoon(time.tt / 36525.0, &geo_eclip_lon, &geo_eclip_lat, &distance_au); 1885 1886 /* Convert geocentric ecliptic spherical coordinates to Cartesian coordinates. */ 1887 dist_cos_lat = distance_au * cos(geo_eclip_lat); 1888 gepos[0] = dist_cos_lat * cos(geo_eclip_lon); 1889 gepos[1] = dist_cos_lat * sin(geo_eclip_lon); 1890 gepos[2] = distance_au * sin(geo_eclip_lat); 1891 1892 /* Convert ecliptic coordinates to equatorial coordinates, both in mean equinox of date. */ 1893 ecl2equ_vec(time, gepos, mpos1); 1894 1895 /* Convert from mean equinox of date to J2000. */ 1896 precession(time.tt, mpos1, 0, mpos2); 1897 1898 vector.status = ASTRO_SUCCESS; 1899 vector.x = mpos2[0]; 1900 vector.y = mpos2[1]; 1901 vector.z = mpos2[2]; 1902 vector.t = time; 1903 return vector; 1904 } 1905 1906 /*------------------ VSOP ------------------*/ 1907 1908 /** @cond DOXYGEN_SKIP */ 1909 typedef struct 1910 { 1911 double amplitude; 1912 double phase; 1913 double frequency; 1914 } 1915 vsop_term_t; 1916 1917 typedef struct 1918 { 1919 int nterms; 1920 const vsop_term_t *term; 1921 } 1922 vsop_series_t; 1923 1924 typedef struct 1925 { 1926 int nseries; 1927 const vsop_series_t *series; 1928 } 1929 vsop_formula_t; 1930 1931 typedef struct 1932 { 1933 const vsop_formula_t formula[3]; 1934 } 1935 vsop_model_t; 1936 /** @endcond */ 1937 1938 static const vsop_term_t vsop_lon_Mercury_0[] = 1939 { 1940 { 4.40250710144, 0.00000000000, 0.00000000000 }, 1941 { 0.40989414977, 1.48302034195, 26087.90314157420 }, 1942 { 0.05046294200, 4.47785489551, 52175.80628314840 }, 1943 { 0.00855346844, 1.16520322459, 78263.70942472259 }, 1944 { 0.00165590362, 4.11969163423, 104351.61256629678 }, 1945 { 0.00034561897, 0.77930768443, 130439.51570787099 }, 1946 { 0.00007583476, 3.71348404924, 156527.41884944518 } 1947 }; 1948 1949 static const vsop_term_t vsop_lon_Mercury_1[] = 1950 { 1951 { 26087.90313685529, 0.00000000000, 0.00000000000 }, 1952 { 0.01131199811, 6.21874197797, 26087.90314157420 }, 1953 { 0.00292242298, 3.04449355541, 52175.80628314840 }, 1954 { 0.00075775081, 6.08568821653, 78263.70942472259 }, 1955 { 0.00019676525, 2.80965111777, 104351.61256629678 } 1956 }; 1957 1958 static const vsop_series_t vsop_lon_Mercury[] = 1959 { 1960 { 7, vsop_lon_Mercury_0 }, 1961 { 5, vsop_lon_Mercury_1 } 1962 }; 1963 1964 static const vsop_term_t vsop_lat_Mercury_0[] = 1965 { 1966 { 0.11737528961, 1.98357498767, 26087.90314157420 }, 1967 { 0.02388076996, 5.03738959686, 52175.80628314840 }, 1968 { 0.01222839532, 3.14159265359, 0.00000000000 }, 1969 { 0.00543251810, 1.79644363964, 78263.70942472259 }, 1970 { 0.00129778770, 4.83232503958, 104351.61256629678 }, 1971 { 0.00031866927, 1.58088495658, 130439.51570787099 }, 1972 { 0.00007963301, 4.60972126127, 156527.41884944518 } 1973 }; 1974 1975 static const vsop_term_t vsop_lat_Mercury_1[] = 1976 { 1977 { 0.00274646065, 3.95008450011, 26087.90314157420 }, 1978 { 0.00099737713, 3.14159265359, 0.00000000000 } 1979 }; 1980 1981 static const vsop_series_t vsop_lat_Mercury[] = 1982 { 1983 { 7, vsop_lat_Mercury_0 }, 1984 { 2, vsop_lat_Mercury_1 } 1985 }; 1986 1987 static const vsop_term_t vsop_rad_Mercury_0[] = 1988 { 1989 { 0.39528271651, 0.00000000000, 0.00000000000 }, 1990 { 0.07834131818, 6.19233722598, 26087.90314157420 }, 1991 { 0.00795525558, 2.95989690104, 52175.80628314840 }, 1992 { 0.00121281764, 6.01064153797, 78263.70942472259 }, 1993 { 0.00021921969, 2.77820093972, 104351.61256629678 }, 1994 { 0.00004354065, 5.82894543774, 130439.51570787099 } 1995 }; 1996 1997 static const vsop_term_t vsop_rad_Mercury_1[] = 1998 { 1999 { 0.00217347740, 4.65617158665, 26087.90314157420 }, 2000 { 0.00044141826, 1.42385544001, 52175.80628314840 } 2001 }; 2002 2003 static const vsop_series_t vsop_rad_Mercury[] = 2004 { 2005 { 6, vsop_rad_Mercury_0 }, 2006 { 2, vsop_rad_Mercury_1 } 2007 }; 2008 2009 ; 2010 static const vsop_term_t vsop_lon_Venus_0[] = 2011 { 2012 { 3.17614666774, 0.00000000000, 0.00000000000 }, 2013 { 0.01353968419, 5.59313319619, 10213.28554621100 }, 2014 { 0.00089891645, 5.30650047764, 20426.57109242200 }, 2015 { 0.00005477194, 4.41630661466, 7860.41939243920 }, 2016 { 0.00003455741, 2.69964447820, 11790.62908865880 }, 2017 { 0.00002372061, 2.99377542079, 3930.20969621960 }, 2018 { 0.00001317168, 5.18668228402, 26.29831979980 }, 2019 { 0.00001664146, 4.25018630147, 1577.34354244780 }, 2020 { 0.00001438387, 4.15745084182, 9683.59458111640 }, 2021 { 0.00001200521, 6.15357116043, 30639.85663863300 } 2022 }; 2023 2024 static const vsop_term_t vsop_lon_Venus_1[] = 2025 { 2026 { 10213.28554621638, 0.00000000000, 0.00000000000 }, 2027 { 0.00095617813, 2.46406511110, 10213.28554621100 }, 2028 { 0.00007787201, 0.62478482220, 20426.57109242200 } 2029 }; 2030 2031 static const vsop_series_t vsop_lon_Venus[] = 2032 { 2033 { 10, vsop_lon_Venus_0 }, 2034 { 3, vsop_lon_Venus_1 } 2035 }; 2036 2037 static const vsop_term_t vsop_lat_Venus_0[] = 2038 { 2039 { 0.05923638472, 0.26702775812, 10213.28554621100 }, 2040 { 0.00040107978, 1.14737178112, 20426.57109242200 }, 2041 { 0.00032814918, 3.14159265359, 0.00000000000 } 2042 }; 2043 2044 static const vsop_term_t vsop_lat_Venus_1[] = 2045 { 2046 { 0.00287821243, 1.88964962838, 10213.28554621100 } 2047 }; 2048 2049 static const vsop_series_t vsop_lat_Venus[] = 2050 { 2051 { 3, vsop_lat_Venus_0 }, 2052 { 1, vsop_lat_Venus_1 } 2053 }; 2054 2055 static const vsop_term_t vsop_rad_Venus_0[] = 2056 { 2057 { 0.72334820891, 0.00000000000, 0.00000000000 }, 2058 { 0.00489824182, 4.02151831717, 10213.28554621100 }, 2059 { 0.00001658058, 4.90206728031, 20426.57109242200 }, 2060 { 0.00001378043, 1.12846591367, 11790.62908865880 }, 2061 { 0.00001632096, 2.84548795207, 7860.41939243920 }, 2062 { 0.00000498395, 2.58682193892, 9683.59458111640 }, 2063 { 0.00000221985, 2.01346696541, 19367.18916223280 }, 2064 { 0.00000237454, 2.55136053886, 15720.83878487840 } 2065 }; 2066 2067 static const vsop_term_t vsop_rad_Venus_1[] = 2068 { 2069 { 0.00034551041, 0.89198706276, 10213.28554621100 } 2070 }; 2071 2072 static const vsop_series_t vsop_rad_Venus[] = 2073 { 2074 { 8, vsop_rad_Venus_0 }, 2075 { 1, vsop_rad_Venus_1 } 2076 }; 2077 2078 ; 2079 static const vsop_term_t vsop_lon_Earth_0[] = 2080 { 2081 { 1.75347045673, 0.00000000000, 0.00000000000 }, 2082 { 0.03341656453, 4.66925680415, 6283.07584999140 }, 2083 { 0.00034894275, 4.62610242189, 12566.15169998280 }, 2084 { 0.00003417572, 2.82886579754, 3.52311834900 }, 2085 { 0.00003497056, 2.74411783405, 5753.38488489680 }, 2086 { 0.00003135899, 3.62767041756, 77713.77146812050 }, 2087 { 0.00002676218, 4.41808345438, 7860.41939243920 }, 2088 { 0.00002342691, 6.13516214446, 3930.20969621960 }, 2089 { 0.00001273165, 2.03709657878, 529.69096509460 }, 2090 { 0.00001324294, 0.74246341673, 11506.76976979360 }, 2091 { 0.00000901854, 2.04505446477, 26.29831979980 }, 2092 { 0.00001199167, 1.10962946234, 1577.34354244780 }, 2093 { 0.00000857223, 3.50849152283, 398.14900340820 }, 2094 { 0.00000779786, 1.17882681962, 5223.69391980220 }, 2095 { 0.00000990250, 5.23268072088, 5884.92684658320 }, 2096 { 0.00000753141, 2.53339052847, 5507.55323866740 }, 2097 { 0.00000505267, 4.58292599973, 18849.22754997420 }, 2098 { 0.00000492392, 4.20505711826, 775.52261132400 }, 2099 { 0.00000356672, 2.91954114478, 0.06731030280 }, 2100 { 0.00000284125, 1.89869240932, 796.29800681640 }, 2101 { 0.00000242879, 0.34481445893, 5486.77784317500 }, 2102 { 0.00000317087, 5.84901948512, 11790.62908865880 }, 2103 { 0.00000271112, 0.31486255375, 10977.07880469900 }, 2104 { 0.00000206217, 4.80646631478, 2544.31441988340 }, 2105 { 0.00000205478, 1.86953770281, 5573.14280143310 }, 2106 { 0.00000202318, 2.45767790232, 6069.77675455340 }, 2107 { 0.00000126225, 1.08295459501, 20.77539549240 }, 2108 { 0.00000155516, 0.83306084617, 213.29909543800 } 2109 }; 2110 2111 static const vsop_term_t vsop_lon_Earth_1[] = 2112 { 2113 { 6283.07584999140, 0.00000000000, 0.00000000000 }, 2114 { 0.00206058863, 2.67823455808, 6283.07584999140 }, 2115 { 0.00004303419, 2.63512233481, 12566.15169998280 } 2116 }; 2117 2118 static const vsop_term_t vsop_lon_Earth_2[] = 2119 { 2120 { 0.00008721859, 1.07253635559, 6283.07584999140 } 2121 }; 2122 2123 static const vsop_series_t vsop_lon_Earth[] = 2124 { 2125 { 28, vsop_lon_Earth_0 }, 2126 { 3, vsop_lon_Earth_1 }, 2127 { 1, vsop_lon_Earth_2 } 2128 }; 2129 2130 static const vsop_term_t vsop_lat_Earth_1[] = 2131 { 2132 { 0.00227777722, 3.41376620530, 6283.07584999140 }, 2133 { 0.00003805678, 3.37063423795, 12566.15169998280 } 2134 }; 2135 2136 static const vsop_series_t vsop_lat_Earth[] = 2137 { 2138 { 0, NULL }, 2139 { 2, vsop_lat_Earth_1 } 2140 }; 2141 2142 static const vsop_term_t vsop_rad_Earth_0[] = 2143 { 2144 { 1.00013988784, 0.00000000000, 0.00000000000 }, 2145 { 0.01670699632, 3.09846350258, 6283.07584999140 }, 2146 { 0.00013956024, 3.05524609456, 12566.15169998280 }, 2147 { 0.00003083720, 5.19846674381, 77713.77146812050 }, 2148 { 0.00001628463, 1.17387558054, 5753.38488489680 }, 2149 { 0.00001575572, 2.84685214877, 7860.41939243920 }, 2150 { 0.00000924799, 5.45292236722, 11506.76976979360 }, 2151 { 0.00000542439, 4.56409151453, 3930.20969621960 }, 2152 { 0.00000472110, 3.66100022149, 5884.92684658320 }, 2153 { 0.00000085831, 1.27079125277, 161000.68573767410 }, 2154 { 0.00000057056, 2.01374292245, 83996.84731811189 }, 2155 { 0.00000055736, 5.24159799170, 71430.69561812909 }, 2156 { 0.00000174844, 3.01193636733, 18849.22754997420 }, 2157 { 0.00000243181, 4.27349530790, 11790.62908865880 } 2158 }; 2159 2160 static const vsop_term_t vsop_rad_Earth_1[] = 2161 { 2162 { 0.00103018607, 1.10748968172, 6283.07584999140 }, 2163 { 0.00001721238, 1.06442300386, 12566.15169998280 } 2164 }; 2165 2166 static const vsop_term_t vsop_rad_Earth_2[] = 2167 { 2168 { 0.00004359385, 5.78455133808, 6283.07584999140 } 2169 }; 2170 2171 static const vsop_series_t vsop_rad_Earth[] = 2172 { 2173 { 14, vsop_rad_Earth_0 }, 2174 { 2, vsop_rad_Earth_1 }, 2175 { 1, vsop_rad_Earth_2 } 2176 }; 2177 2178 ; 2179 static const vsop_term_t vsop_lon_Mars_0[] = 2180 { 2181 { 6.20347711581, 0.00000000000, 0.00000000000 }, 2182 { 0.18656368093, 5.05037100270, 3340.61242669980 }, 2183 { 0.01108216816, 5.40099836344, 6681.22485339960 }, 2184 { 0.00091798406, 5.75478744667, 10021.83728009940 }, 2185 { 0.00027744987, 5.97049513147, 3.52311834900 }, 2186 { 0.00010610235, 2.93958560338, 2281.23049651060 }, 2187 { 0.00012315897, 0.84956094002, 2810.92146160520 }, 2188 { 0.00008926784, 4.15697846427, 0.01725365220 }, 2189 { 0.00008715691, 6.11005153139, 13362.44970679920 }, 2190 { 0.00006797556, 0.36462229657, 398.14900340820 }, 2191 { 0.00007774872, 3.33968761376, 5621.84292321040 }, 2192 { 0.00003575078, 1.66186505710, 2544.31441988340 }, 2193 { 0.00004161108, 0.22814971327, 2942.46342329160 }, 2194 { 0.00003075252, 0.85696614132, 191.44826611160 }, 2195 { 0.00002628117, 0.64806124465, 3337.08930835080 }, 2196 { 0.00002937546, 6.07893711402, 0.06731030280 }, 2197 { 0.00002389414, 5.03896442664, 796.29800681640 }, 2198 { 0.00002579844, 0.02996736156, 3344.13554504880 }, 2199 { 0.00001528141, 1.14979301996, 6151.53388830500 }, 2200 { 0.00001798806, 0.65634057445, 529.69096509460 }, 2201 { 0.00001264357, 3.62275122593, 5092.15195811580 }, 2202 { 0.00001286228, 3.06796065034, 2146.16541647520 }, 2203 { 0.00001546404, 2.91579701718, 1751.53953141600 }, 2204 { 0.00001024902, 3.69334099279, 8962.45534991020 }, 2205 { 0.00000891566, 0.18293837498, 16703.06213349900 }, 2206 { 0.00000858759, 2.40093811940, 2914.01423582380 }, 2207 { 0.00000832715, 2.46418619474, 3340.59517304760 }, 2208 { 0.00000832720, 4.49495782139, 3340.62968035200 }, 2209 { 0.00000712902, 3.66335473479, 1059.38193018920 }, 2210 { 0.00000748723, 3.82248614017, 155.42039943420 }, 2211 { 0.00000723861, 0.67497311481, 3738.76143010800 }, 2212 { 0.00000635548, 2.92182225127, 8432.76438481560 }, 2213 { 0.00000655162, 0.48864064125, 3127.31333126180 }, 2214 { 0.00000550474, 3.81001042328, 0.98032106820 }, 2215 { 0.00000552750, 4.47479317037, 1748.01641306700 }, 2216 { 0.00000425966, 0.55364317304, 6283.07584999140 }, 2217 { 0.00000415131, 0.49662285038, 213.29909543800 }, 2218 { 0.00000472167, 3.62547124025, 1194.44701022460 }, 2219 { 0.00000306551, 0.38052848348, 6684.74797174860 }, 2220 { 0.00000312141, 0.99853944405, 6677.70173505060 }, 2221 { 0.00000293198, 4.22131299634, 20.77539549240 }, 2222 { 0.00000302375, 4.48618007156, 3532.06069281140 }, 2223 { 0.00000274027, 0.54222167059, 3340.54511639700 }, 2224 { 0.00000281079, 5.88163521788, 1349.86740965880 }, 2225 { 0.00000231183, 1.28242156993, 3870.30339179440 }, 2226 { 0.00000283602, 5.76885434940, 3149.16416058820 }, 2227 { 0.00000236117, 5.75503217933, 3333.49887969900 }, 2228 { 0.00000274033, 0.13372524985, 3340.67973700260 }, 2229 { 0.00000299395, 2.78323740866, 6254.62666252360 } 2230 }; 2231 2232 static const vsop_term_t vsop_lon_Mars_1[] = 2233 { 2234 { 3340.61242700512, 0.00000000000, 0.00000000000 }, 2235 { 0.01457554523, 3.60433733236, 3340.61242669980 }, 2236 { 0.00168414711, 3.92318567804, 6681.22485339960 }, 2237 { 0.00020622975, 4.26108844583, 10021.83728009940 }, 2238 { 0.00003452392, 4.73210393190, 3.52311834900 }, 2239 { 0.00002586332, 4.60670058555, 13362.44970679920 }, 2240 { 0.00000841535, 4.45864030426, 2281.23049651060 } 2241 }; 2242 2243 static const vsop_term_t vsop_lon_Mars_2[] = 2244 { 2245 { 0.00058152577, 2.04961712429, 3340.61242669980 }, 2246 { 0.00013459579, 2.45738706163, 6681.22485339960 } 2247 }; 2248 2249 static const vsop_series_t vsop_lon_Mars[] = 2250 { 2251 { 49, vsop_lon_Mars_0 }, 2252 { 7, vsop_lon_Mars_1 }, 2253 { 2, vsop_lon_Mars_2 } 2254 }; 2255 2256 static const vsop_term_t vsop_lat_Mars_0[] = 2257 { 2258 { 0.03197134986, 3.76832042431, 3340.61242669980 }, 2259 { 0.00298033234, 4.10616996305, 6681.22485339960 }, 2260 { 0.00289104742, 0.00000000000, 0.00000000000 }, 2261 { 0.00031365539, 4.44651053090, 10021.83728009940 }, 2262 { 0.00003484100, 4.78812549260, 13362.44970679920 } 2263 }; 2264 2265 static const vsop_term_t vsop_lat_Mars_1[] = 2266 { 2267 { 0.00217310991, 6.04472194776, 3340.61242669980 }, 2268 { 0.00020976948, 3.14159265359, 0.00000000000 }, 2269 { 0.00012834709, 1.60810667915, 6681.22485339960 } 2270 }; 2271 2272 static const vsop_series_t vsop_lat_Mars[] = 2273 { 2274 { 5, vsop_lat_Mars_0 }, 2275 { 3, vsop_lat_Mars_1 } 2276 }; 2277 2278 static const vsop_term_t vsop_rad_Mars_0[] = 2279 { 2280 { 1.53033488271, 0.00000000000, 0.00000000000 }, 2281 { 0.14184953160, 3.47971283528, 3340.61242669980 }, 2282 { 0.00660776362, 3.81783443019, 6681.22485339960 }, 2283 { 0.00046179117, 4.15595316782, 10021.83728009940 }, 2284 { 0.00008109733, 5.55958416318, 2810.92146160520 }, 2285 { 0.00007485318, 1.77239078402, 5621.84292321040 }, 2286 { 0.00005523191, 1.36436303770, 2281.23049651060 }, 2287 { 0.00003825160, 4.49407183687, 13362.44970679920 }, 2288 { 0.00002306537, 0.09081579001, 2544.31441988340 }, 2289 { 0.00001999396, 5.36059617709, 3337.08930835080 }, 2290 { 0.00002484394, 4.92545639920, 2942.46342329160 }, 2291 { 0.00001960195, 4.74249437639, 3344.13554504880 }, 2292 { 0.00001167119, 2.11260868341, 5092.15195811580 }, 2293 { 0.00001102816, 5.00908403998, 398.14900340820 }, 2294 { 0.00000899066, 4.40791133207, 529.69096509460 }, 2295 { 0.00000992252, 5.83861961952, 6151.53388830500 }, 2296 { 0.00000807354, 2.10217065501, 1059.38193018920 }, 2297 { 0.00000797915, 3.44839203899, 796.29800681640 }, 2298 { 0.00000740975, 1.49906336885, 2146.16541647520 } 2299 }; 2300 2301 static const vsop_term_t vsop_rad_Mars_1[] = 2302 { 2303 { 0.01107433345, 2.03250524857, 3340.61242669980 }, 2304 { 0.00103175887, 2.37071847807, 6681.22485339960 }, 2305 { 0.00012877200, 0.00000000000, 0.00000000000 }, 2306 { 0.00010815880, 2.70888095665, 10021.83728009940 } 2307 }; 2308 2309 static const vsop_term_t vsop_rad_Mars_2[] = 2310 { 2311 { 0.00044242249, 0.47930604954, 3340.61242669980 }, 2312 { 0.00008138042, 0.86998389204, 6681.22485339960 } 2313 }; 2314 2315 static const vsop_series_t vsop_rad_Mars[] = 2316 { 2317 { 19, vsop_rad_Mars_0 }, 2318 { 4, vsop_rad_Mars_1 }, 2319 { 2, vsop_rad_Mars_2 } 2320 }; 2321 2322 ; 2323 static const vsop_term_t vsop_lon_Jupiter_0[] = 2324 { 2325 { 0.59954691494, 0.00000000000, 0.00000000000 }, 2326 { 0.09695898719, 5.06191793158, 529.69096509460 }, 2327 { 0.00573610142, 1.44406205629, 7.11354700080 }, 2328 { 0.00306389205, 5.41734730184, 1059.38193018920 }, 2329 { 0.00097178296, 4.14264726552, 632.78373931320 }, 2330 { 0.00072903078, 3.64042916389, 522.57741809380 }, 2331 { 0.00064263975, 3.41145165351, 103.09277421860 }, 2332 { 0.00039806064, 2.29376740788, 419.48464387520 }, 2333 { 0.00038857767, 1.27231755835, 316.39186965660 }, 2334 { 0.00027964629, 1.78454591820, 536.80451209540 }, 2335 { 0.00013589730, 5.77481040790, 1589.07289528380 }, 2336 { 0.00008246349, 3.58227925840, 206.18554843720 }, 2337 { 0.00008768704, 3.63000308199, 949.17560896980 }, 2338 { 0.00007368042, 5.08101194270, 735.87651353180 }, 2339 { 0.00006263150, 0.02497628807, 213.29909543800 }, 2340 { 0.00006114062, 4.51319998626, 1162.47470440780 }, 2341 { 0.00004905396, 1.32084470588, 110.20632121940 }, 2342 { 0.00005305285, 1.30671216791, 14.22709400160 }, 2343 { 0.00005305441, 4.18625634012, 1052.26838318840 }, 2344 { 0.00004647248, 4.69958103684, 3.93215326310 }, 2345 { 0.00003045023, 4.31676431084, 426.59819087600 }, 2346 { 0.00002609999, 1.56667394063, 846.08283475120 }, 2347 { 0.00002028191, 1.06376530715, 3.18139373770 }, 2348 { 0.00001764763, 2.14148655117, 1066.49547719000 }, 2349 { 0.00001722972, 3.88036268267, 1265.56747862640 }, 2350 { 0.00001920945, 0.97168196472, 639.89728631400 }, 2351 { 0.00001633223, 3.58201833555, 515.46387109300 }, 2352 { 0.00001431999, 4.29685556046, 625.67019231240 }, 2353 { 0.00000973272, 4.09764549134, 95.97922721780 } 2354 }; 2355 2356 static const vsop_term_t vsop_lon_Jupiter_1[] = 2357 { 2358 { 529.69096508814, 0.00000000000, 0.00000000000 }, 2359 { 0.00489503243, 4.22082939470, 529.69096509460 }, 2360 { 0.00228917222, 6.02646855621, 7.11354700080 }, 2361 { 0.00030099479, 4.54540782858, 1059.38193018920 }, 2362 { 0.00020720920, 5.45943156902, 522.57741809380 }, 2363 { 0.00012103653, 0.16994816098, 536.80451209540 }, 2364 { 0.00006067987, 4.42422292017, 103.09277421860 }, 2365 { 0.00005433968, 3.98480737746, 419.48464387520 }, 2366 { 0.00004237744, 5.89008707199, 14.22709400160 } 2367 }; 2368 2369 static const vsop_term_t vsop_lon_Jupiter_2[] = 2370 { 2371 { 0.00047233601, 4.32148536482, 7.11354700080 }, 2372 { 0.00030649436, 2.92977788700, 529.69096509460 }, 2373 { 0.00014837605, 3.14159265359, 0.00000000000 } 2374 }; 2375 2376 static const vsop_series_t vsop_lon_Jupiter[] = 2377 { 2378 { 29, vsop_lon_Jupiter_0 }, 2379 { 9, vsop_lon_Jupiter_1 }, 2380 { 3, vsop_lon_Jupiter_2 } 2381 }; 2382 2383 static const vsop_term_t vsop_lat_Jupiter_0[] = 2384 { 2385 { 0.02268615702, 3.55852606721, 529.69096509460 }, 2386 { 0.00109971634, 3.90809347197, 1059.38193018920 }, 2387 { 0.00110090358, 0.00000000000, 0.00000000000 }, 2388 { 0.00008101428, 3.60509572885, 522.57741809380 }, 2389 { 0.00006043996, 4.25883108339, 1589.07289528380 }, 2390 { 0.00006437782, 0.30627119215, 536.80451209540 } 2391 }; 2392 2393 static const vsop_term_t vsop_lat_Jupiter_1[] = 2394 { 2395 { 0.00078203446, 1.52377859742, 529.69096509460 } 2396 }; 2397 2398 static const vsop_series_t vsop_lat_Jupiter[] = 2399 { 2400 { 6, vsop_lat_Jupiter_0 }, 2401 { 1, vsop_lat_Jupiter_1 } 2402 }; 2403 2404 static const vsop_term_t vsop_rad_Jupiter_0[] = 2405 { 2406 { 5.20887429326, 0.00000000000, 0.00000000000 }, 2407 { 0.25209327119, 3.49108639871, 529.69096509460 }, 2408 { 0.00610599976, 3.84115365948, 1059.38193018920 }, 2409 { 0.00282029458, 2.57419881293, 632.78373931320 }, 2410 { 0.00187647346, 2.07590383214, 522.57741809380 }, 2411 { 0.00086792905, 0.71001145545, 419.48464387520 }, 2412 { 0.00072062974, 0.21465724607, 536.80451209540 }, 2413 { 0.00065517248, 5.97995884790, 316.39186965660 }, 2414 { 0.00029134542, 1.67759379655, 103.09277421860 }, 2415 { 0.00030135335, 2.16132003734, 949.17560896980 }, 2416 { 0.00023453271, 3.54023522184, 735.87651353180 }, 2417 { 0.00022283743, 4.19362594399, 1589.07289528380 }, 2418 { 0.00023947298, 0.27458037480, 7.11354700080 }, 2419 { 0.00013032614, 2.96042965363, 1162.47470440780 }, 2420 { 0.00009703360, 1.90669633585, 206.18554843720 }, 2421 { 0.00012749023, 2.71550286592, 1052.26838318840 }, 2422 { 0.00007057931, 2.18184839926, 1265.56747862640 }, 2423 { 0.00006137703, 6.26418240033, 846.08283475120 }, 2424 { 0.00002616976, 2.00994012876, 1581.95934828300 } 2425 }; 2426 2427 static const vsop_term_t vsop_rad_Jupiter_1[] = 2428 { 2429 { 0.01271801520, 2.64937512894, 529.69096509460 }, 2430 { 0.00061661816, 3.00076460387, 1059.38193018920 }, 2431 { 0.00053443713, 3.89717383175, 522.57741809380 }, 2432 { 0.00031185171, 4.88276958012, 536.80451209540 }, 2433 { 0.00041390269, 0.00000000000, 0.00000000000 } 2434 }; 2435 2436 static const vsop_series_t vsop_rad_Jupiter[] = 2437 { 2438 { 19, vsop_rad_Jupiter_0 }, 2439 { 5, vsop_rad_Jupiter_1 } 2440 }; 2441 2442 ; 2443 static const vsop_term_t vsop_lon_Saturn_0[] = 2444 { 2445 { 0.87401354025, 0.00000000000, 0.00000000000 }, 2446 { 0.11107659762, 3.96205090159, 213.29909543800 }, 2447 { 0.01414150957, 4.58581516874, 7.11354700080 }, 2448 { 0.00398379389, 0.52112032699, 206.18554843720 }, 2449 { 0.00350769243, 3.30329907896, 426.59819087600 }, 2450 { 0.00206816305, 0.24658372002, 103.09277421860 }, 2451 { 0.00079271300, 3.84007056878, 220.41264243880 }, 2452 { 0.00023990355, 4.66976924553, 110.20632121940 }, 2453 { 0.00016573588, 0.43719228296, 419.48464387520 }, 2454 { 0.00014906995, 5.76903183869, 316.39186965660 }, 2455 { 0.00015820290, 0.93809155235, 632.78373931320 }, 2456 { 0.00014609559, 1.56518472000, 3.93215326310 }, 2457 { 0.00013160301, 4.44891291899, 14.22709400160 }, 2458 { 0.00015053543, 2.71669915667, 639.89728631400 }, 2459 { 0.00013005299, 5.98119023644, 11.04570026390 }, 2460 { 0.00010725067, 3.12939523827, 202.25339517410 }, 2461 { 0.00005863206, 0.23656938524, 529.69096509460 }, 2462 { 0.00005227757, 4.20783365759, 3.18139373770 }, 2463 { 0.00006126317, 1.76328667907, 277.03499374140 }, 2464 { 0.00005019687, 3.17787728405, 433.71173787680 }, 2465 { 0.00004592550, 0.61977744975, 199.07200143640 }, 2466 { 0.00004005867, 2.24479718502, 63.73589830340 }, 2467 { 0.00002953796, 0.98280366998, 95.97922721780 }, 2468 { 0.00003873670, 3.22283226966, 138.51749687070 }, 2469 { 0.00002461186, 2.03163875071, 735.87651353180 }, 2470 { 0.00003269484, 0.77492638211, 949.17560896980 }, 2471 { 0.00001758145, 3.26580109940, 522.57741809380 }, 2472 { 0.00001640172, 5.50504453050, 846.08283475120 }, 2473 { 0.00001391327, 4.02333150505, 323.50541665740 }, 2474 { 0.00001580648, 4.37265307169, 309.27832265580 }, 2475 { 0.00001123498, 2.83726798446, 415.55249061210 }, 2476 { 0.00001017275, 3.71700135395, 227.52618943960 }, 2477 { 0.00000848642, 3.19150170830, 209.36694217490 } 2478 }; 2479 2480 static const vsop_term_t vsop_lon_Saturn_1[] = 2481 { 2482 { 213.29909521690, 0.00000000000, 0.00000000000 }, 2483 { 0.01297370862, 1.82834923978, 213.29909543800 }, 2484 { 0.00564345393, 2.88499717272, 7.11354700080 }, 2485 { 0.00093734369, 1.06311793502, 426.59819087600 }, 2486 { 0.00107674962, 2.27769131009, 206.18554843720 }, 2487 { 0.00040244455, 2.04108104671, 220.41264243880 }, 2488 { 0.00019941774, 1.27954390470, 103.09277421860 }, 2489 { 0.00010511678, 2.74880342130, 14.22709400160 }, 2490 { 0.00006416106, 0.38238295041, 639.89728631400 }, 2491 { 0.00004848994, 2.43037610229, 419.48464387520 }, 2492 { 0.00004056892, 2.92133209468, 110.20632121940 }, 2493 { 0.00003768635, 3.64965330780, 3.93215326310 } 2494 }; 2495 2496 static const vsop_term_t vsop_lon_Saturn_2[] = 2497 { 2498 { 0.00116441330, 1.17988132879, 7.11354700080 }, 2499 { 0.00091841837, 0.07325195840, 213.29909543800 }, 2500 { 0.00036661728, 0.00000000000, 0.00000000000 }, 2501 { 0.00015274496, 4.06493179167, 206.18554843720 } 2502 }; 2503 2504 static const vsop_series_t vsop_lon_Saturn[] = 2505 { 2506 { 33, vsop_lon_Saturn_0 }, 2507 { 12, vsop_lon_Saturn_1 }, 2508 { 4, vsop_lon_Saturn_2 } 2509 }; 2510 2511 static const vsop_term_t vsop_lat_Saturn_0[] = 2512 { 2513 { 0.04330678039, 3.60284428399, 213.29909543800 }, 2514 { 0.00240348302, 2.85238489373, 426.59819087600 }, 2515 { 0.00084745939, 0.00000000000, 0.00000000000 }, 2516 { 0.00030863357, 3.48441504555, 220.41264243880 }, 2517 { 0.00034116062, 0.57297307557, 206.18554843720 }, 2518 { 0.00014734070, 2.11846596715, 639.89728631400 }, 2519 { 0.00009916667, 5.79003188904, 419.48464387520 }, 2520 { 0.00006993564, 4.73604689720, 7.11354700080 }, 2521 { 0.00004807588, 5.43305312061, 316.39186965660 } 2522 }; 2523 2524 static const vsop_term_t vsop_lat_Saturn_1[] = 2525 { 2526 { 0.00198927992, 4.93901017903, 213.29909543800 }, 2527 { 0.00036947916, 3.14159265359, 0.00000000000 }, 2528 { 0.00017966989, 0.51979431110, 426.59819087600 } 2529 }; 2530 2531 static const vsop_series_t vsop_lat_Saturn[] = 2532 { 2533 { 9, vsop_lat_Saturn_0 }, 2534 { 3, vsop_lat_Saturn_1 } 2535 }; 2536 2537 static const vsop_term_t vsop_rad_Saturn_0[] = 2538 { 2539 { 9.55758135486, 0.00000000000, 0.00000000000 }, 2540 { 0.52921382865, 2.39226219573, 213.29909543800 }, 2541 { 0.01873679867, 5.23549604660, 206.18554843720 }, 2542 { 0.01464663929, 1.64763042902, 426.59819087600 }, 2543 { 0.00821891141, 5.93520042303, 316.39186965660 }, 2544 { 0.00547506923, 5.01532618980, 103.09277421860 }, 2545 { 0.00371684650, 2.27114821115, 220.41264243880 }, 2546 { 0.00361778765, 3.13904301847, 7.11354700080 }, 2547 { 0.00140617506, 5.70406606781, 632.78373931320 }, 2548 { 0.00108974848, 3.29313390175, 110.20632121940 }, 2549 { 0.00069006962, 5.94099540992, 419.48464387520 }, 2550 { 0.00061053367, 0.94037691801, 639.89728631400 }, 2551 { 0.00048913294, 1.55733638681, 202.25339517410 }, 2552 { 0.00034143772, 0.19519102597, 277.03499374140 }, 2553 { 0.00032401773, 5.47084567016, 949.17560896980 }, 2554 { 0.00020936596, 0.46349251129, 735.87651353180 }, 2555 { 0.00009796004, 5.20477537945, 1265.56747862640 }, 2556 { 0.00011993338, 5.98050967385, 846.08283475120 }, 2557 { 0.00020839300, 1.52102476129, 433.71173787680 }, 2558 { 0.00015298404, 3.05943814940, 529.69096509460 }, 2559 { 0.00006465823, 0.17732249942, 1052.26838318840 }, 2560 { 0.00011380257, 1.73105427040, 522.57741809380 }, 2561 { 0.00003419618, 4.94550542171, 1581.95934828300 } 2562 }; 2563 2564 static const vsop_term_t vsop_rad_Saturn_1[] = 2565 { 2566 { 0.06182981340, 0.25843511480, 213.29909543800 }, 2567 { 0.00506577242, 0.71114625261, 206.18554843720 }, 2568 { 0.00341394029, 5.79635741658, 426.59819087600 }, 2569 { 0.00188491195, 0.47215589652, 220.41264243880 }, 2570 { 0.00186261486, 3.14159265359, 0.00000000000 }, 2571 { 0.00143891146, 1.40744822888, 7.11354700080 } 2572 }; 2573 2574 static const vsop_term_t vsop_rad_Saturn_2[] = 2575 { 2576 { 0.00436902572, 4.78671677509, 213.29909543800 } 2577 }; 2578 2579 static const vsop_series_t vsop_rad_Saturn[] = 2580 { 2581 { 23, vsop_rad_Saturn_0 }, 2582 { 6, vsop_rad_Saturn_1 }, 2583 { 1, vsop_rad_Saturn_2 } 2584 }; 2585 2586 ; 2587 static const vsop_term_t vsop_lon_Uranus_0[] = 2588 { 2589 { 5.48129294297, 0.00000000000, 0.00000000000 }, 2590 { 0.09260408234, 0.89106421507, 74.78159856730 }, 2591 { 0.01504247898, 3.62719260920, 1.48447270830 }, 2592 { 0.00365981674, 1.89962179044, 73.29712585900 }, 2593 { 0.00272328168, 3.35823706307, 149.56319713460 }, 2594 { 0.00070328461, 5.39254450063, 63.73589830340 }, 2595 { 0.00068892678, 6.09292483287, 76.26607127560 }, 2596 { 0.00061998615, 2.26952066061, 2.96894541660 }, 2597 { 0.00061950719, 2.85098872691, 11.04570026390 }, 2598 { 0.00026468770, 3.14152083966, 71.81265315070 }, 2599 { 0.00025710476, 6.11379840493, 454.90936652730 }, 2600 { 0.00021078850, 4.36059339067, 148.07872442630 }, 2601 { 0.00017818647, 1.74436930289, 36.64856292950 }, 2602 { 0.00014613507, 4.73732166022, 3.93215326310 }, 2603 { 0.00011162509, 5.82681796350, 224.34479570190 }, 2604 { 0.00010997910, 0.48865004018, 138.51749687070 }, 2605 { 0.00009527478, 2.95516862826, 35.16409022120 }, 2606 { 0.00007545601, 5.23626582400, 109.94568878850 }, 2607 { 0.00004220241, 3.23328220918, 70.84944530420 }, 2608 { 0.00004051900, 2.27755017300, 151.04766984290 }, 2609 { 0.00003354596, 1.06549007380, 4.45341812490 }, 2610 { 0.00002926718, 4.62903718891, 9.56122755560 }, 2611 { 0.00003490340, 5.48306144511, 146.59425171800 }, 2612 { 0.00003144069, 4.75199570434, 77.75054398390 }, 2613 { 0.00002922333, 5.35235361027, 85.82729883120 }, 2614 { 0.00002272788, 4.36600400036, 70.32818044240 }, 2615 { 0.00002051219, 1.51773566586, 0.11187458460 }, 2616 { 0.00002148602, 0.60745949945, 38.13303563780 }, 2617 { 0.00001991643, 4.92437588682, 277.03499374140 }, 2618 { 0.00001376226, 2.04283539351, 65.22037101170 }, 2619 { 0.00001666902, 3.62744066769, 380.12776796000 }, 2620 { 0.00001284107, 3.11347961505, 202.25339517410 }, 2621 { 0.00001150429, 0.93343589092, 3.18139373770 }, 2622 { 0.00001533221, 2.58594681212, 52.69019803950 }, 2623 { 0.00001281604, 0.54271272721, 222.86032299360 }, 2624 { 0.00001372139, 4.19641530878, 111.43016149680 }, 2625 { 0.00001221029, 0.19900650030, 108.46121608020 }, 2626 { 0.00000946181, 1.19253165736, 127.47179660680 }, 2627 { 0.00001150989, 4.17898916639, 33.67961751290 } 2628 }; 2629 2630 static const vsop_term_t vsop_lon_Uranus_1[] = 2631 { 2632 { 74.78159860910, 0.00000000000, 0.00000000000 }, 2633 { 0.00154332863, 5.24158770553, 74.78159856730 }, 2634 { 0.00024456474, 1.71260334156, 1.48447270830 }, 2635 { 0.00009258442, 0.42829732350, 11.04570026390 }, 2636 { 0.00008265977, 1.50218091379, 63.73589830340 }, 2637 { 0.00009150160, 1.41213765216, 149.56319713460 } 2638 }; 2639 2640 static const vsop_series_t vsop_lon_Uranus[] = 2641 { 2642 { 39, vsop_lon_Uranus_0 }, 2643 { 6, vsop_lon_Uranus_1 } 2644 }; 2645 2646 static const vsop_term_t vsop_lat_Uranus_0[] = 2647 { 2648 { 0.01346277648, 2.61877810547, 74.78159856730 }, 2649 { 0.00062341400, 5.08111189648, 149.56319713460 }, 2650 { 0.00061601196, 3.14159265359, 0.00000000000 }, 2651 { 0.00009963722, 1.61603805646, 76.26607127560 }, 2652 { 0.00009926160, 0.57630380333, 73.29712585900 } 2653 }; 2654 2655 static const vsop_term_t vsop_lat_Uranus_1[] = 2656 { 2657 { 0.00034101978, 0.01321929936, 74.78159856730 } 2658 }; 2659 2660 static const vsop_series_t vsop_lat_Uranus[] = 2661 { 2662 { 5, vsop_lat_Uranus_0 }, 2663 { 1, vsop_lat_Uranus_1 } 2664 }; 2665 2666 static const vsop_term_t vsop_rad_Uranus_0[] = 2667 { 2668 { 19.21264847206, 0.00000000000, 0.00000000000 }, 2669 { 0.88784984413, 5.60377527014, 74.78159856730 }, 2670 { 0.03440836062, 0.32836099706, 73.29712585900 }, 2671 { 0.02055653860, 1.78295159330, 149.56319713460 }, 2672 { 0.00649322410, 4.52247285911, 76.26607127560 }, 2673 { 0.00602247865, 3.86003823674, 63.73589830340 }, 2674 { 0.00496404167, 1.40139935333, 454.90936652730 }, 2675 { 0.00338525369, 1.58002770318, 138.51749687070 }, 2676 { 0.00243509114, 1.57086606044, 71.81265315070 }, 2677 { 0.00190522303, 1.99809394714, 1.48447270830 }, 2678 { 0.00161858838, 2.79137786799, 148.07872442630 }, 2679 { 0.00143706183, 1.38368544947, 11.04570026390 }, 2680 { 0.00093192405, 0.17437220467, 36.64856292950 }, 2681 { 0.00071424548, 4.24509236074, 224.34479570190 }, 2682 { 0.00089806014, 3.66105364565, 109.94568878850 }, 2683 { 0.00039009723, 1.66971401684, 70.84944530420 }, 2684 { 0.00046677296, 1.39976401694, 35.16409022120 }, 2685 { 0.00039025624, 3.36234773834, 277.03499374140 }, 2686 { 0.00036755274, 3.88649278513, 146.59425171800 }, 2687 { 0.00030348723, 0.70100838798, 151.04766984290 }, 2688 { 0.00029156413, 3.18056336700, 77.75054398390 }, 2689 { 0.00022637073, 0.72518687029, 529.69096509460 }, 2690 { 0.00011959076, 1.75043392140, 984.60033162190 }, 2691 { 0.00025620756, 5.25656086672, 380.12776796000 } 2692 }; 2693 2694 static const vsop_term_t vsop_rad_Uranus_1[] = 2695 { 2696 { 0.01479896629, 3.67205697578, 74.78159856730 } 2697 }; 2698 2699 static const vsop_series_t vsop_rad_Uranus[] = 2700 { 2701 { 24, vsop_rad_Uranus_0 }, 2702 { 1, vsop_rad_Uranus_1 } 2703 }; 2704 2705 ; 2706 static const vsop_term_t vsop_lon_Neptune_0[] = 2707 { 2708 { 5.31188633046, 0.00000000000, 0.00000000000 }, 2709 { 0.01798475530, 2.90101273890, 38.13303563780 }, 2710 { 0.01019727652, 0.48580922867, 1.48447270830 }, 2711 { 0.00124531845, 4.83008090676, 36.64856292950 }, 2712 { 0.00042064466, 5.41054993053, 2.96894541660 }, 2713 { 0.00037714584, 6.09221808686, 35.16409022120 }, 2714 { 0.00033784738, 1.24488874087, 76.26607127560 }, 2715 { 0.00016482741, 0.00007727998, 491.55792945680 }, 2716 { 0.00009198584, 4.93747051954, 39.61750834610 }, 2717 { 0.00008994250, 0.27462171806, 175.16605980020 } 2718 }; 2719 2720 static const vsop_term_t vsop_lon_Neptune_1[] = 2721 { 2722 { 38.13303563957, 0.00000000000, 0.00000000000 }, 2723 { 0.00016604172, 4.86323329249, 1.48447270830 }, 2724 { 0.00015744045, 2.27887427527, 38.13303563780 } 2725 }; 2726 2727 static const vsop_series_t vsop_lon_Neptune[] = 2728 { 2729 { 10, vsop_lon_Neptune_0 }, 2730 { 3, vsop_lon_Neptune_1 } 2731 }; 2732 2733 static const vsop_term_t vsop_lat_Neptune_0[] = 2734 { 2735 { 0.03088622933, 1.44104372644, 38.13303563780 }, 2736 { 0.00027780087, 5.91271884599, 76.26607127560 }, 2737 { 0.00027623609, 0.00000000000, 0.00000000000 }, 2738 { 0.00015355489, 2.52123799551, 36.64856292950 }, 2739 { 0.00015448133, 3.50877079215, 39.61750834610 } 2740 }; 2741 2742 static const vsop_series_t vsop_lat_Neptune[] = 2743 { 2744 { 5, vsop_lat_Neptune_0 } 2745 }; 2746 2747 static const vsop_term_t vsop_rad_Neptune_0[] = 2748 { 2749 { 30.07013205828, 0.00000000000, 0.00000000000 }, 2750 { 0.27062259632, 1.32999459377, 38.13303563780 }, 2751 { 0.01691764014, 3.25186135653, 36.64856292950 }, 2752 { 0.00807830553, 5.18592878704, 1.48447270830 }, 2753 { 0.00537760510, 4.52113935896, 35.16409022120 }, 2754 { 0.00495725141, 1.57105641650, 491.55792945680 }, 2755 { 0.00274571975, 1.84552258866, 175.16605980020 }, 2756 { 0.00012012320, 1.92059384991, 1021.24889455140 }, 2757 { 0.00121801746, 5.79754470298, 76.26607127560 }, 2758 { 0.00100896068, 0.37702724930, 73.29712585900 }, 2759 { 0.00135134092, 3.37220609835, 39.61750834610 }, 2760 { 0.00007571796, 1.07149207335, 388.46515523820 } 2761 }; 2762 2763 static const vsop_series_t vsop_rad_Neptune[] = 2764 { 2765 { 12, vsop_rad_Neptune_0 } 2766 }; 2767 2768 ; 2769 2770 /** @cond DOXYGEN_SKIP */ 2771 #define VSOPFORMULA(x) { ARRAYSIZE(x), x } 2772 /** @endcond */ 2773 2774 static const vsop_model_t vsop[] = 2775 { 2776 { { VSOPFORMULA(vsop_lon_Mercury), VSOPFORMULA(vsop_lat_Mercury), VSOPFORMULA(vsop_rad_Mercury) } }, 2777 { { VSOPFORMULA(vsop_lon_Venus), VSOPFORMULA(vsop_lat_Venus), VSOPFORMULA(vsop_rad_Venus) } }, 2778 { { VSOPFORMULA(vsop_lon_Earth), VSOPFORMULA(vsop_lat_Earth), VSOPFORMULA(vsop_rad_Earth) } }, 2779 { { VSOPFORMULA(vsop_lon_Mars), VSOPFORMULA(vsop_lat_Mars), VSOPFORMULA(vsop_rad_Mars) } }, 2780 { { VSOPFORMULA(vsop_lon_Jupiter), VSOPFORMULA(vsop_lat_Jupiter), VSOPFORMULA(vsop_rad_Jupiter) } }, 2781 { { VSOPFORMULA(vsop_lon_Saturn), VSOPFORMULA(vsop_lat_Saturn), VSOPFORMULA(vsop_rad_Saturn) } }, 2782 { { VSOPFORMULA(vsop_lon_Uranus), VSOPFORMULA(vsop_lat_Uranus), VSOPFORMULA(vsop_rad_Uranus) } }, 2783 { { VSOPFORMULA(vsop_lon_Neptune), VSOPFORMULA(vsop_lat_Neptune), VSOPFORMULA(vsop_rad_Neptune) } } 2784 }; 2785 2786 /** @cond DOXYGEN_SKIP */ 2787 #define CalcEarth(time) CalcVsop(&vsop[BODY_EARTH], (time)) 2788 #define LON_INDEX 0 2789 #define LAT_INDEX 1 2790 #define RAD_INDEX 2 2791 /** @endcond */ 2792 2793 static void VsopCoords(const vsop_model_t *model, double t, double sphere[3]) 2794 { 2795 int k, s, i; 2796 2797 for (k=0; k < 3; ++k) 2798 { 2799 double tpower = 1.0; 2800 const vsop_formula_t *formula = &model->formula[k]; 2801 sphere[k] = 0.0; 2802 for (s=0; s < formula->nseries; ++s) 2803 { 2804 double sum = 0.0; 2805 const vsop_series_t *series = &formula->series[s]; 2806 for (i=0; i < series->nterms; ++i) 2807 { 2808 const vsop_term_t *term = &series->term[i]; 2809 sum += term->amplitude * cos(term->phase + (t * term->frequency)); 2810 } 2811 sphere[k] += tpower * sum; 2812 tpower *= t; 2813 } 2814 } 2815 } 2816 2817 2818 static terse_vector_t VsopRotate(const double ecl[3]) 2819 { 2820 terse_vector_t equ; 2821 2822 /* 2823 X +1.000000000000 +0.000000440360 -0.000000190919 X 2824 Y = -0.000000479966 +0.917482137087 -0.397776982902 Y 2825 Z FK5 0.000000000000 +0.397776982902 +0.917482137087 Z VSOP87A 2826 */ 2827 2828 equ.x = ecl[0] + 0.000000440360*ecl[1] - 0.000000190919*ecl[2]; 2829 equ.y = -0.000000479966*ecl[0] + 0.917482137087*ecl[1] - 0.397776982902*ecl[2]; 2830 equ.z = 0.397776982902*ecl[1] + 0.917482137087*ecl[2]; 2831 2832 return equ; 2833 } 2834 2835 2836 static void VsopSphereToRect(double lon, double lat, double radius, double pos[3]) 2837 { 2838 double r_coslat = radius * cos(lat); 2839 pos[0] = r_coslat * cos(lon); 2840 pos[1] = r_coslat * sin(lon); 2841 pos[2] = radius * sin(lat); 2842 } 2843 2844 static const double DAYS_PER_MILLENNIUM = 365250.0; 2845 2846 2847 static astro_vector_t CalcVsop(const vsop_model_t *model, astro_time_t time) 2848 { 2849 double t = time.tt / DAYS_PER_MILLENNIUM; 2850 double sphere[3]; /* lon, lat, rad */ 2851 double eclip[3]; 2852 astro_vector_t vector; 2853 terse_vector_t pos; 2854 2855 /* Calculate the VSOP "B" trigonometric series to obtain ecliptic spherical coordinates. */ 2856 VsopCoords(model, t, sphere); 2857 2858 /* Convert ecliptic spherical coordinates to ecliptic Cartesian coordinates. */ 2859 VsopSphereToRect(sphere[LON_INDEX], sphere[LAT_INDEX], sphere[RAD_INDEX], eclip); 2860 2861 /* Convert ecliptic Cartesian coordinates to equatorial Cartesian coordinates. */ 2862 pos = VsopRotate(eclip); 2863 2864 /* Package the position as astro_vector_t. */ 2865 vector.status = ASTRO_SUCCESS; 2866 vector.t = time; 2867 vector.x = pos.x; 2868 vector.y = pos.y; 2869 vector.z = pos.z; 2870 2871 return vector; 2872 } 2873 2874 2875 static void VsopDeriv(const vsop_model_t *model, double t, double deriv[3]) 2876 { 2877 int k, s, i; 2878 2879 for (k=0; k < 3; ++k) 2880 { 2881 double tpower = 1.0; /* t^s */ 2882 double dpower = 0.0; /* t^(s-1) */ 2883 const vsop_formula_t *formula = &model->formula[k]; 2884 deriv[k] = 0.0; 2885 for (s=0; s < formula->nseries; ++s) 2886 { 2887 double sin_sum = 0.0; 2888 double cos_sum = 0.0; 2889 const vsop_series_t *series = &formula->series[s]; 2890 for (i=0; i < series->nterms; ++i) 2891 { 2892 const vsop_term_t *term = &series->term[i]; 2893 double angle = term->phase + (t * term->frequency); 2894 sin_sum += term->amplitude * term->frequency * sin(angle); 2895 if (s > 0) 2896 cos_sum += term->amplitude * cos(angle); 2897 } 2898 deriv[k] += (s * dpower * cos_sum) - (tpower * sin_sum); 2899 dpower = tpower; 2900 tpower *= t; 2901 } 2902 } 2903 } 2904 2905 2906 static body_state_t CalcVsopPosVel(const vsop_model_t *model, double tt) 2907 { 2908 body_state_t state; 2909 double t = tt / DAYS_PER_MILLENNIUM; 2910 double sphere[3]; /* lon, lat, r */ 2911 double deriv[3]; /* d(lon)/dt, d(lat)/dt, dr/dt */ 2912 double eclip[3]; 2913 double dr_dt, dlat_dt, dlon_dt; 2914 double r, coslat, coslon, sinlat, sinlon; 2915 2916 state.tt = tt; 2917 VsopCoords(model, t, sphere); 2918 VsopSphereToRect(sphere[LON_INDEX], sphere[LAT_INDEX], sphere[RAD_INDEX], eclip); 2919 state.r = VsopRotate(eclip); 2920 2921 VsopDeriv(model, t, deriv); 2922 2923 /* Use spherical coords and spherical derivatives to calculate */ 2924 /* the velocity vector in rectangular coordinates. */ 2925 2926 /* Calculate mnemonic variables to help keep the math straight. */ 2927 coslon = cos(sphere[LON_INDEX]); 2928 sinlon = sin(sphere[LON_INDEX]); 2929 coslat = cos(sphere[LAT_INDEX]); 2930 sinlat = sin(sphere[LAT_INDEX]); 2931 r = sphere[RAD_INDEX]; 2932 dlon_dt = deriv[LON_INDEX]; 2933 dlat_dt = deriv[LAT_INDEX]; 2934 dr_dt = deriv[RAD_INDEX]; 2935 2936 /* vx = dx/dt */ 2937 eclip[0] = (dr_dt * coslat * coslon) - (r * sinlat * coslon * dlat_dt) - (r * coslat * sinlon * dlon_dt); 2938 2939 /* vy = dy/dt */ 2940 eclip[1] = (dr_dt * coslat * sinlon) - (r * sinlat * sinlon * dlat_dt) + (r * coslat * coslon * dlon_dt); 2941 2942 /* vz = dz/dt */ 2943 eclip[2] = (dr_dt * sinlat) + (r * coslat * dlat_dt); 2944 2945 /* Rotate the velocity vector from ecliptic to equatorial coordinates. */ 2946 state.v = VsopRotate(eclip); 2947 2948 /* Convert speed units from [AU/millennium] to [AU/day]. */ 2949 VecScale(&state.v, 1 / DAYS_PER_MILLENNIUM); 2950 2951 return state; 2952 } 2953 2954 2955 static double VsopHelioDistance(const vsop_model_t *model, astro_time_t time) 2956 { 2957 int s, i; 2958 double t = time.tt / DAYS_PER_MILLENNIUM; 2959 double distance = 0.0; 2960 double tpower = 1.0; 2961 const vsop_formula_t *formula = &model->formula[2]; /* [2] is the distance part of the formula */ 2962 2963 /* 2964 The caller only wants to know the distance between the planet and the Sun. 2965 So we only need to calculate the radial component of the spherical coordinates. 2966 */ 2967 2968 for (s=0; s < formula->nseries; ++s) 2969 { 2970 double sum = 0.0; 2971 const vsop_series_t *series = &formula->series[s]; 2972 for (i=0; i < series->nterms; ++i) 2973 { 2974 const vsop_term_t *term = &series->term[i]; 2975 sum += term->amplitude * cos(term->phase + (t * term->frequency)); 2976 } 2977 distance += tpower * sum; 2978 tpower *= t; 2979 } 2980 2981 return distance; 2982 } 2983 2984 2985 static void AdjustBarycenter(astro_vector_t *ssb, astro_time_t time, astro_body_t body, double planet_gm) 2986 { 2987 astro_vector_t planet; 2988 double shift; 2989 2990 shift = planet_gm / (planet_gm + SUN_GM); 2991 planet = CalcVsop(&vsop[body], time); 2992 ssb->x += shift * planet.x; 2993 ssb->y += shift * planet.y; 2994 ssb->z += shift * planet.z; 2995 } 2996 2997 2998 static astro_vector_t CalcSolarSystemBarycenter(astro_time_t time) 2999 { 3000 astro_vector_t ssb; 3001 3002 ssb.status = ASTRO_SUCCESS; 3003 ssb.t = time; 3004 ssb.x = ssb.y = ssb.z = 0.0; 3005 3006 AdjustBarycenter(&ssb, time, BODY_JUPITER, JUPITER_GM); 3007 AdjustBarycenter(&ssb, time, BODY_SATURN, SATURN_GM); 3008 AdjustBarycenter(&ssb, time, BODY_URANUS, URANUS_GM); 3009 AdjustBarycenter(&ssb, time, BODY_NEPTUNE, NEPTUNE_GM); 3010 3011 return ssb; 3012 } 3013 3014 /*------------------ begin Pluto integrator ------------------*/ 3015 3016 /** @cond DOXYGEN_SKIP */ 3017 typedef struct 3018 { 3019 double tt; /* J2000 terrestrial time [days] */ 3020 terse_vector_t r; /* position [au] */ 3021 terse_vector_t v; /* velocity [au/day] */ 3022 terse_vector_t a; /* acceleration [au/day^2] */ 3023 } body_grav_calc_t; 3024 /** @endcond */ 3025 3026 #define PLUTO_NUM_STATES 41 3027 #define PLUTO_TIME_STEP 36500 3028 3029 static const body_state_t PlutoStateTable[] = 3030 { 3031 { -730000.0, {-26.1182072321076, -14.3761681778250, 3.3844025152995}, { 1.6339372163656e-03, -2.7861699588508e-03, -1.3585880229445e-03} } 3032 , { -693500.0, { 43.6599275018261, 15.7782921408811, -8.2269833881374}, {-2.5043046295860e-04, 2.1163039457238e-03, 7.3466073583102e-04} } 3033 , { -657000.0, {-17.0086014985033, 33.0590743876420, 15.4080189624259}, {-1.9676551946049e-03, -1.8337707766770e-03, 2.0125441459959e-05} } 3034 , { -620500.0, { 26.9005106893171, -21.5285596810214, -14.7987712668075}, { 2.2939261196998e-03, 1.7431871970059e-03, -1.4585639832643e-04} } 3035 , { -584000.0, { 20.2303809506997, 43.2669666571891, 7.3829660919234}, {-1.9754081700585e-03, 5.3457141292226e-04, 7.5929169129793e-04} } 3036 , { -547500.0, {-22.5571440338751, -19.2958112538447, 0.7806423603826}, { 2.1494578646505e-03, -2.4266772630044e-03, -1.4013084013574e-03} } 3037 , { -511000.0, { 43.0236236810360, 19.6179542007347, -6.8406553041565}, {-4.7729923671058e-04, 2.0208979483877e-03, 7.7191815992131e-04} } 3038 , { -474500.0, {-20.4245105862934, 29.5157679318005, 15.3408675727018}, {-1.8003167284198e-03, -2.1025226687937e-03, -1.1262333332859e-04} } 3039 , { -438000.0, { 30.7746921076872, -18.2366370153037, -14.9455358798963}, { 2.0113162005465e-03, 1.9353827024189e-03, -2.0937793168297e-06} } 3040 , { -401500.0, { 16.7235440456361, 44.0505598318603, 8.6886113939440}, {-2.0565226049264e-03, 3.2710694138777e-04, 7.2006155046579e-04} } 3041 , { -365000.0, {-18.4891734360057, -23.1428732331142, -1.6436720878799}, { 2.5524223225832e-03, -2.0035792463879e-03, -1.3910737531294e-03} } 3042 , { -328500.0, { 42.0853950560734, 22.9742531259520, -5.5131410205412}, {-6.7105845193949e-04, 1.9177289500465e-03, 7.9770011059534e-04} } 3043 , { -292000.0, {-23.2753639151193, 25.8185142987694, 15.0553815885983}, {-1.6062295460975e-03, -2.3395961498533e-03, -2.4377362639479e-04} } 3044 , { -255500.0, { 33.9015793210130, -14.9421228983498, -14.8664994855707}, { 1.7455105487563e-03, 2.0655068871494e-03, 1.1695000657630e-04} } 3045 , { -219000.0, { 13.3770189322702, 44.4442211120183, 9.8260227015847}, {-2.1171882923251e-03, 1.3114714542921e-04, 6.7884578840323e-04} } 3046 , { -182500.0, {-14.1723844533379, -26.0054690135836, -3.8387026446526}, { 2.8419751785822e-03, -1.5579441656564e-03, -1.3408416711060e-03} } 3047 , { -146000.0, { 40.9468572586403, 25.9049735920209, -4.2563362404988}, {-8.3652705194051e-04, 1.8129497136404e-03, 8.1564228273060e-04} } 3048 , { -109500.0, {-25.5839689598009, 22.0699164999425, 14.5902026036780}, {-1.3923977856331e-03, -2.5442249745422e-03, -3.7169906721828e-04} } 3049 , { -73000.0, { 36.4035708396756, -11.7473067389593, -14.6304139635223}, { 1.5037714418941e-03, 2.1500325702247e-03, 2.1523781242948e-04} } 3050 , { -36500.0, { 10.2436041239517, 44.5280986402285, 10.8048664487066}, {-2.1615839201823e-03, -5.1418983893534e-05, 6.3687060751430e-04} } 3051 , { 0.0, { -9.8753695807739, -27.9789262247367, -5.7537118247043}, { 3.0287533248818e-03, -1.1276087003636e-03, -1.2651326732361e-03} } 3052 , { 36500.0, { 39.7009143866164, 28.4327664903825, -3.0906026170881}, {-9.7720559866138e-04, 1.7121518344796e-03, 8.2822409843551e-04} } 3053 , { 73000.0, {-27.3620419812795, 18.4265651225706, 13.9975343005914}, {-1.1690934621340e-03, -2.7143131627458e-03, -4.9312695340367e-04} } 3054 , { 109500.0, { 38.3556091850032, -8.7643800131842, -14.2951819118807}, { 1.2922798115839e-03, 2.2032141141126e-03, 2.9606522103424e-04} } 3055 , { 146000.0, { 7.3929490279056, 44.3826789515344, 11.6295002148543}, {-2.1932815453830e-03, -2.1751799585364e-04, 5.9556516201114e-04} } 3056 , { 182500.0, { -5.8649529029432, -29.1987619981354, -7.3502494912123}, { 3.1339384323665e-03, -7.4205968379701e-04, -1.1783357537604e-03} } 3057 , { 219000.0, { 38.4269476345329, 30.5667598351632, -2.0378379641214}, {-1.0958945370084e-03, 1.6194885149659e-03, 8.3705272532546e-04} } 3058 , { 255500.0, {-28.6586488201636, 15.0309000931701, 13.3365724093667}, {-9.4611899595408e-04, -2.8506813871559e-03, -6.0508645822989e-04} } 3059 , { 292000.0, { 39.8319806717528, -6.0784057667647, -13.9098153586562}, { 1.1117769689167e-03, 2.2362097830152e-03, 3.6230548231153e-04} } 3060 , { 328500.0, { 4.8371523764030, 44.0723119541530, 12.3146147867802}, {-2.2164547537724e-03, -3.6790365636785e-04, 5.5542723844616e-04} } 3061 , { 365000.0, { -2.2619763759487, -29.8581508706765, -8.6502366418978}, { 3.1821176368396e-03, -4.0915169873994e-04, -1.0895893040652e-03} } 3062 , { 401500.0, { 37.1576590087419, 32.3528396259588, -1.0950381786229}, {-1.1988412606830e-03, 1.5356290902995e-03, 8.4339118209852e-04} } 3063 , { 438000.0, {-29.5767402292299, 11.8635359435865, 12.6313230398719}, {-7.2292830060955e-04, -2.9587820140709e-03, -7.0824296450300e-04} } 3064 , { 474500.0, { 40.9541099577599, -3.6589805945370, -13.4994699563950}, { 9.5387298337127e-04, 2.2572135462477e-03, 4.1826529781128e-04} } 3065 , { 511000.0, { 2.4859523114116, 43.6181887566155, 12.8914184596699}, {-2.2339745420393e-03, -5.1034757181916e-04, 5.1485330196245e-04} } 3066 , { 547500.0, { 1.0594791441638, -30.1357921778687, -9.7458684762963}, { 3.1921591684898e-03, -1.1305312796150e-04, -9.9954096945965e-04} } 3067 , { 584000.0, { 35.8778640130144, 33.8942263660709, -0.2245246362769}, {-1.2941245730845e-03, 1.4560427668319e-03, 8.4762160640137e-04} } 3068 , { 620500.0, {-30.2026537318923, 8.7794211940578, 11.8609238187578}, {-4.9002221381806e-04, -3.0438768469137e-03, -8.0605935262763e-04} } 3069 , { 657000.0, { 41.8536204011376, -1.3790965838042, -13.0624345337527}, { 8.0674627557124e-04, 2.2702374399791e-03, 4.6832587475465e-04} } 3070 , { 693500.0, { 0.2468843977112, 43.0303960481227, 13.3909343344167}, {-2.2436121787266e-03, -6.5238074250728e-04, 4.7172729553196e-04} } 3071 , { 730000.0, { 4.2432528370899, -30.1182016908248, -10.7074412313491}, { 3.1725847067411e-03, 1.6098461202270e-04, -9.0672150593868e-04} } 3072 }; 3073 3074 3075 static terse_vector_t UpdatePosition(double dt, terse_vector_t r, terse_vector_t v, terse_vector_t a) 3076 { 3077 r.x += (v.x + a.x*dt/2) * dt; 3078 r.y += (v.y + a.y*dt/2) * dt; 3079 r.z += (v.z + a.z*dt/2) * dt; 3080 return r; 3081 } 3082 3083 3084 static body_state_t AdjustBarycenterPosVel(body_state_t *ssb, double tt, astro_body_t body, double planet_gm) 3085 { 3086 body_state_t planet; 3087 double shift; 3088 3089 /* 3090 This function does 2 important things: 3091 1. Adjusts 'ssb' by the effect of one major body on the Solar System Barycenter. 3092 2, Returns the heliocentric position of that major body. 3093 */ 3094 3095 shift = planet_gm / (planet_gm + SUN_GM); 3096 planet = CalcVsopPosVel(&vsop[body], tt); 3097 VecIncr(&ssb->r, VecMul(shift, planet.r)); 3098 VecIncr(&ssb->v, VecMul(shift, planet.v)); 3099 3100 return planet; 3101 } 3102 3103 3104 static void MajorBodyBary(body_state_t bary[5], double tt) 3105 { 3106 int p; 3107 3108 /* bary[0] starts out receiving the Solar System Barycenter. */ 3109 bary[0].tt = tt; 3110 bary[0].r = VecZero; 3111 bary[0].v = VecZero; 3112 3113 /* Calculate heliocentric planet positions and SSB. */ 3114 bary[1] = AdjustBarycenterPosVel(&bary[0], tt, BODY_JUPITER, JUPITER_GM); 3115 bary[2] = AdjustBarycenterPosVel(&bary[0], tt, BODY_SATURN, SATURN_GM); 3116 bary[3] = AdjustBarycenterPosVel(&bary[0], tt, BODY_URANUS, URANUS_GM); 3117 bary[4] = AdjustBarycenterPosVel(&bary[0], tt, BODY_NEPTUNE, NEPTUNE_GM); 3118 3119 for (p=1; p < 5; ++p) 3120 { 3121 /* Convert major body [pos, vel] from heliocentric to barycentric. */ 3122 VecDecr(&bary[p].r, bary[0].r); 3123 VecDecr(&bary[p].v, bary[0].v); 3124 } 3125 3126 /* Convert heliocentric SSB to barycentric Sun. */ 3127 VecScale(&bary[0].r, -1.0); 3128 VecScale(&bary[0].v, -1.0); 3129 } 3130 3131 3132 static void AddAcceleration(terse_vector_t *acc, terse_vector_t small_pos, double gm, terse_vector_t major_pos) 3133 { 3134 double dx, dy, dz, r2, pull; 3135 3136 dx = major_pos.x - small_pos.x; 3137 dy = major_pos.y - small_pos.y; 3138 dz = major_pos.z - small_pos.z; 3139 3140 r2 = dx*dx + dy*dy + dz*dz; 3141 pull = gm / (r2 * sqrt(r2)); 3142 3143 acc->x += dx * pull; 3144 acc->y += dy * pull; 3145 acc->z += dz * pull; 3146 } 3147 3148 3149 static terse_vector_t SmallBodyAcceleration(terse_vector_t small_pos, const body_state_t bary[5]) 3150 { 3151 terse_vector_t acc = VecZero; 3152 3153 /* Use barycentric coordinates of the Sun and major planets to calculate gravitational accelerations. */ 3154 AddAcceleration(&acc, small_pos, SUN_GM, bary[0].r); 3155 AddAcceleration(&acc, small_pos, JUPITER_GM, bary[1].r); 3156 AddAcceleration(&acc, small_pos, SATURN_GM, bary[2].r); 3157 AddAcceleration(&acc, small_pos, URANUS_GM, bary[3].r); 3158 AddAcceleration(&acc, small_pos, NEPTUNE_GM, bary[4].r); 3159 3160 return acc; 3161 } 3162 3163 3164 body_grav_calc_t GravSim( /* out: [pos, vel, acc] of the simulated body at time tt2 */ 3165 body_state_t bary2[5], /* out: major body barycentric positions at tt2 */ 3166 double tt2, /* in: a target time to be calculated (either before or after tt1 */ 3167 const body_grav_calc_t *calc1) /* in: [pos, vel, acc] of the simulated body at time tt1 */ 3168 { 3169 body_grav_calc_t calc2; 3170 terse_vector_t approx_pos; 3171 terse_vector_t acc; 3172 const double dt = tt2 - calc1->tt; 3173 3174 /* Calculate where the major bodies (Sun, Jupiter...Neptune) will be at the next time step. */ 3175 MajorBodyBary(bary2, tt2); 3176 3177 /* Estimate position of small body as if current acceleration applies across the whole time interval. */ 3178 /* approx_pos = pos1 + vel1*dt + (1/2)acc*dt^2 */ 3179 approx_pos = UpdatePosition(dt, calc1->r, calc1->v, calc1->a); 3180 3181 /* Calculate acceleration experienced by small body at approximate next location. */ 3182 acc = SmallBodyAcceleration(approx_pos, bary2); 3183 3184 /* Calculate the average acceleration of the endpoints. */ 3185 /* This becomes our estimate of the mean effective acceleration over the whole interval. */ 3186 acc = VecMean(acc, calc1->a); 3187 3188 /* Refine the estimates of [pos, vel, acc] at tt2 using the mean acceleration. */ 3189 calc2.r = UpdatePosition(dt, calc1->r, calc1->v, acc); 3190 calc2.v = VecAdd(calc1->v, VecMul(dt, acc)); 3191 calc2.a = SmallBodyAcceleration(calc2.r, bary2); 3192 calc2.tt = tt2; 3193 return calc2; 3194 } 3195 3196 3197 #define PLUTO_DT 250 3198 #if PLUTO_TIME_STEP % PLUTO_DT != 0 3199 #error Invalid combination of Pluto time step, time increment. 3200 #endif 3201 3202 #define PLUTO_NSTEPS ((PLUTO_TIME_STEP / PLUTO_DT) + 1) 3203 3204 /** @cond DOXYGEN_SKIP */ 3205 typedef struct 3206 { 3207 body_grav_calc_t step[PLUTO_NSTEPS]; 3208 } 3209 body_segment_t; 3210 /** @endcond */ 3211 3212 3213 /* FIXFIXFIX - Using a global is not thread-safe. Either add thread-locks or change API to accept a cache pointer. */ 3214 static body_segment_t *pluto_cache[PLUTO_NUM_STATES-1]; 3215 3216 3217 static int ClampIndex(double frac, int nsteps) 3218 { 3219 int index = (int) floor(frac); 3220 if (index < 0) 3221 return 0; 3222 if (index >= nsteps) 3223 return nsteps-1; 3224 return index; 3225 } 3226 3227 3228 static body_grav_calc_t GravFromState(body_state_t bary[5], const body_state_t *state) 3229 { 3230 body_grav_calc_t calc; 3231 3232 MajorBodyBary(bary, state->tt); 3233 3234 calc.tt = state->tt; 3235 calc.r = VecAdd(state->r, bary[0].r); /* convert heliocentric to barycentric */ 3236 calc.v = VecAdd(state->v, bary[0].v); /* convert heliocentric to barycentric */ 3237 calc.a = SmallBodyAcceleration(calc.r, bary); 3238 3239 return calc; 3240 } 3241 3242 3243 static astro_status_t GetSegment(int *seg_index, body_segment_t *cache[], double tt) 3244 { 3245 int i; 3246 body_segment_t reverse; 3247 body_segment_t *seg; 3248 body_state_t bary[5]; 3249 double step_tt, ramp; 3250 3251 if (tt < PlutoStateTable[0].tt || tt > PlutoStateTable[PLUTO_NUM_STATES-1].tt) 3252 { 3253 /* We don't bother calculating a segment. Let the caller crawl backward/forward to this time. */ 3254 *seg_index = -1; 3255 return ASTRO_SUCCESS; 3256 } 3257 3258 /* See if we have a segment that straddles the requested time. */ 3259 /* If so, return it. Otherwise, calculate it and return it. */ 3260 3261 *seg_index = ClampIndex((tt - PlutoStateTable[0].tt) / PLUTO_TIME_STEP, PLUTO_NUM_STATES-1); 3262 if (cache[*seg_index] == NULL) 3263 { 3264 /* Allocate memory for the segment (about 11K each). */ 3265 seg = cache[*seg_index] = calloc(1, sizeof(body_segment_t)); 3266 if (seg == NULL) 3267 return ASTRO_OUT_OF_MEMORY; 3268 3269 /* Calculate the segment. */ 3270 /* Pick the pair of bracketing body states to fill the segment. */ 3271 3272 /* Each endpoint is exact. */ 3273 seg->step[0] = GravFromState(bary, &PlutoStateTable[*seg_index]); 3274 seg->step[PLUTO_NSTEPS-1] = GravFromState(bary, &PlutoStateTable[*seg_index + 1]); 3275 3276 /* Simulate forwards from the lower time bound. */ 3277 step_tt = seg->step[0].tt; 3278 for (i=1; i < PLUTO_NSTEPS-1; ++i) 3279 seg->step[i] = GravSim(bary, step_tt += PLUTO_DT, &seg->step[i-1]); 3280 3281 /* Simulate backwards from the upper time bound. */ 3282 step_tt = seg->step[PLUTO_NSTEPS-1].tt; 3283 reverse.step[PLUTO_NSTEPS-1] = seg->step[PLUTO_NSTEPS-1]; 3284 for (i=PLUTO_NSTEPS-2; i > 0; --i) 3285 reverse.step[i] = GravSim(bary, step_tt -= PLUTO_DT, &reverse.step[i+1]); 3286 3287 /* Fade-mix the two series so that there are no discontinuities. */ 3288 for (i=PLUTO_NSTEPS-2; i > 0; --i) 3289 { 3290 ramp = (double)i / (PLUTO_NSTEPS-1); 3291 seg->step[i].r = VecRamp(seg->step[i].r, reverse.step[i].r, ramp); 3292 seg->step[i].v = VecRamp(seg->step[i].v, reverse.step[i].v, ramp); 3293 seg->step[i].a = VecRamp(seg->step[i].a, reverse.step[i].a, ramp); 3294 } 3295 } 3296 3297 return ASTRO_SUCCESS; 3298 } 3299 3300 3301 static terse_vector_t CalcPlutoOneWay(body_state_t bary[5], const body_state_t *init_state, double target_tt, double dt) 3302 { 3303 body_grav_calc_t calc; 3304 int i, n; 3305 3306 calc = GravFromState(bary, init_state); 3307 n = (int) ceil((target_tt - calc.tt) / dt); 3308 for (i=0; i < n; ++i) 3309 calc = GravSim(bary, (i+1 == n) ? target_tt : (calc.tt + dt), &calc); 3310 3311 return calc.r; 3312 } 3313 3314 3315 static astro_vector_t CalcPluto(astro_time_t time) 3316 { 3317 terse_vector_t acc, ra, rb, r; 3318 body_state_t bary[5]; 3319 const body_segment_t *seg; 3320 int seg_index, left; 3321 const body_grav_calc_t *s1; 3322 const body_grav_calc_t *s2; 3323 astro_status_t status; 3324 3325 status = GetSegment(&seg_index, pluto_cache, time.tt); 3326 if (status != ASTRO_SUCCESS) 3327 return VecError(status, time); 3328 3329 if (seg_index < 0) 3330 { 3331 /* The target time is outside the year range 0000..4000. */ 3332 /* Calculate it by crawling backward from 0000 or forward from 4000. */ 3333 /* FIXFIXFIX - This is super slow. Could optimize this with extra caching if needed. */ 3334 if (time.tt < PlutoStateTable[0].tt) 3335 r = CalcPlutoOneWay(bary, &PlutoStateTable[0], time.tt, -PLUTO_DT); 3336 else 3337 r = CalcPlutoOneWay(bary, &PlutoStateTable[PLUTO_NUM_STATES-1], time.tt, +PLUTO_DT); 3338 } 3339 else 3340 { 3341 seg = pluto_cache[seg_index]; 3342 left = ClampIndex((time.tt - seg->step[0].tt) / PLUTO_DT, PLUTO_NSTEPS-1); 3343 s1 = &seg->step[left]; 3344 s2 = &seg->step[left+1]; 3345 3346 /* Find mean acceleration vector over the interval. */ 3347 acc = VecMean(s1->a, s2->a); 3348 3349 /* Use Newtonian mechanics to extrapolate away from t1 in the positive time direction. */ 3350 ra = UpdatePosition(time.tt - s1->tt, s1->r, s1->v, acc); 3351 3352 /* Use Newtonian mechanics to extrapolate away from t2 in the negative time direction. */ 3353 rb = UpdatePosition(time.tt - s2->tt, s2->r, s2->v, acc); 3354 3355 /* Use fade in/out idea to blend the two position estimates. */ 3356 r = VecRamp(ra, rb, (time.tt - s1->tt)/PLUTO_DT); 3357 MajorBodyBary(bary, time.tt); 3358 } 3359 3360 /* Convert barycentric coordinates back to heliocentric coordinates. */ 3361 return PublicVec(time, VecSub(r, bary[0].r)); 3362 } 3363 3364 /*------------------ end Pluto integrator ------------------*/ 3365 3366 3367 /** 3368 * @brief Calculates heliocentric Cartesian coordinates of a body in the J2000 equatorial system. 3369 * 3370 * This function calculates the position of the given celestial body as a vector, 3371 * using the center of the Sun as the origin. The result is expressed as a Cartesian 3372 * vector in the J2000 equatorial system: the coordinates are based on the mean equator 3373 * of the Earth at noon UTC on 1 January 2000. 3374 * 3375 * The position is not corrected for light travel time or aberration. 3376 * This is different from the behavior of #Astronomy_GeoVector. 3377 * 3378 * If given an invalid value for `body`, this function will fail. The caller should always check 3379 * the `status` field inside the returned #astro_vector_t for `ASTRO_SUCCESS` (success) 3380 * or any other value (failure) before trusting the resulting vector. 3381 * 3382 * @param body 3383 * A body for which to calculate a heliocentric position: the Sun, Moon, any of the planets, 3384 * the Solar System Barycenter (SSB), or the Earth Moon Barycenter (EMB). 3385 * @param time The date and time for which to calculate the position. 3386 * @return A heliocentric position vector of the center of the given body. 3387 */ 3388 astro_vector_t Astronomy_HelioVector(astro_body_t body, astro_time_t time) 3389 { 3390 astro_vector_t vector, earth; 3391 3392 switch (body) 3393 { 3394 case BODY_SUN: 3395 vector.status = ASTRO_SUCCESS; 3396 vector.x = 0.0; 3397 vector.y = 0.0; 3398 vector.z = 0.0; 3399 vector.t = time; 3400 return vector; 3401 3402 case BODY_MERCURY: 3403 case BODY_VENUS: 3404 case BODY_EARTH: 3405 case BODY_MARS: 3406 case BODY_JUPITER: 3407 case BODY_SATURN: 3408 case BODY_URANUS: 3409 case BODY_NEPTUNE: 3410 return CalcVsop(&vsop[body], time); 3411 3412 case BODY_PLUTO: 3413 return CalcPluto(time); 3414 3415 case BODY_MOON: 3416 vector = Astronomy_GeoMoon(time); 3417 earth = CalcEarth(time); 3418 vector.x += earth.x; 3419 vector.y += earth.y; 3420 vector.z += earth.z; 3421 return vector; 3422 3423 case BODY_EMB: 3424 vector = Astronomy_GeoMoon(time); 3425 earth = CalcEarth(time); 3426 vector.x = earth.x + (vector.x / (1.0 + EARTH_MOON_MASS_RATIO)); 3427 vector.y = earth.y + (vector.y / (1.0 + EARTH_MOON_MASS_RATIO)); 3428 vector.z = earth.z + (vector.z / (1.0 + EARTH_MOON_MASS_RATIO)); 3429 return vector; 3430 3431 case BODY_SSB: 3432 return CalcSolarSystemBarycenter(time); 3433 3434 default: 3435 return VecError(ASTRO_INVALID_BODY, time); 3436 } 3437 } 3438 3439 /** 3440 * @brief Calculates the distance from a body to the Sun at a given time. 3441 * 3442 * Given a date and time, this function calculates the distance between 3443 * the center of `body` and the center of the Sun. 3444 * For the planets Mercury through Neptune, this function is significantly 3445 * more efficient than calling #Astronomy_HelioVector followed by #Astronomy_VectorLength. 3446 * 3447 * @param body 3448 * A body for which to calculate a heliocentric distance: the Sun, Moon, or any of the planets. 3449 * 3450 * @param time 3451 * The date and time for which to calculate the heliocentric distance. 3452 * 3453 * @return 3454 * If successful, an #astro_func_result_t structure whose `status` is `ASTRO_SUCCESS` 3455 * and whose `value` holds the heliocentric distance in AU. 3456 * Otherwise, `status` reports an error condition. 3457 */ 3458 astro_func_result_t Astronomy_HelioDistance(astro_body_t body, astro_time_t time) 3459 { 3460 astro_vector_t vector; 3461 astro_func_result_t result; 3462 3463 switch (body) 3464 { 3465 case BODY_SUN: 3466 result.status = ASTRO_SUCCESS; 3467 result.value = 0.0; 3468 return result; 3469 3470 case BODY_MERCURY: 3471 case BODY_VENUS: 3472 case BODY_EARTH: 3473 case BODY_MARS: 3474 case BODY_JUPITER: 3475 case BODY_SATURN: 3476 case BODY_URANUS: 3477 case BODY_NEPTUNE: 3478 result.status = ASTRO_SUCCESS; 3479 result.value = VsopHelioDistance(&vsop[body], time); 3480 return result; 3481 3482 default: 3483 /* For non-VSOP objects, fall back to taking the length of the heliocentric vector. */ 3484 vector = Astronomy_HelioVector(body, time); 3485 if (vector.status != ASTRO_SUCCESS) 3486 return FuncError(vector.status); 3487 result.status = ASTRO_SUCCESS; 3488 result.value = Astronomy_VectorLength(vector); 3489 return result; 3490 } 3491 } 3492 3493 3494 /** 3495 * @brief Calculates geocentric Cartesian coordinates of a body in the J2000 equatorial system. 3496 * 3497 * This function calculates the position of the given celestial body as a vector, 3498 * using the center of the Earth as the origin. The result is expressed as a Cartesian 3499 * vector in the J2000 equatorial system: the coordinates are based on the mean equator 3500 * of the Earth at noon UTC on 1 January 2000. 3501 * 3502 * If given an invalid value for `body`, this function will fail. The caller should always check 3503 * the `status` field inside the returned #astro_vector_t for `ASTRO_SUCCESS` (success) 3504 * or any other value (failure) before trusting the resulting vector. 3505 * 3506 * Unlike #Astronomy_HelioVector, this function always corrects for light travel time. 3507 * This means the position of the body is "back-dated" by the amount of time it takes 3508 * light to travel from that body to an observer on the Earth. 3509 * 3510 * Also, the position can optionally be corrected for 3511 * [aberration](https://en.wikipedia.org/wiki/Aberration_of_light), an effect 3512 * causing the apparent direction of the body to be shifted due to transverse 3513 * movement of the Earth with respect to the rays of light coming from that body. 3514 * 3515 * @param body A body for which to calculate a heliocentric position: the Sun, Moon, or any of the planets. 3516 * @param time The date and time for which to calculate the position. 3517 * @param aberration `ABERRATION` to correct for aberration, or `NO_ABERRATION` to leave uncorrected. 3518 * @return A geocentric position vector of the center of the given body. 3519 */ 3520 astro_vector_t Astronomy_GeoVector(astro_body_t body, astro_time_t time, astro_aberration_t aberration) 3521 { 3522 astro_vector_t vector; 3523 astro_vector_t earth; 3524 astro_time_t ltime; 3525 astro_time_t ltime2; 3526 double dt; 3527 int iter; 3528 3529 if (aberration != ABERRATION && aberration != NO_ABERRATION) 3530 return VecError(ASTRO_INVALID_PARAMETER, time); 3531 3532 switch (body) 3533 { 3534 case BODY_EARTH: 3535 /* The Earth's geocentric coordinates are always (0,0,0). */ 3536 vector.status = ASTRO_SUCCESS; 3537 vector.x = 0.0; 3538 vector.y = 0.0; 3539 vector.z = 0.0; 3540 break; 3541 3542 case BODY_MOON: 3543 vector = Astronomy_GeoMoon(time); 3544 break; 3545 3546 default: 3547 /* For all other bodies, apply light travel time correction. */ 3548 3549 if (aberration == NO_ABERRATION) 3550 { 3551 /* No aberration, so calculate Earth's position once, at the time of observation. */ 3552 earth = CalcEarth(time); 3553 if (earth.status != ASTRO_SUCCESS) 3554 return earth; 3555 } 3556 3557 ltime = time; 3558 for (iter=0; iter < 10; ++iter) 3559 { 3560 vector = Astronomy_HelioVector(body, ltime); 3561 if (vector.status != ASTRO_SUCCESS) 3562 return vector; 3563 3564 if (aberration == ABERRATION) 3565 { 3566 /* 3567 Include aberration, so make a good first-order approximation 3568 by backdating the Earth's position also. 3569 This is confusing, but it works for objects within the Solar System 3570 because the distance the Earth moves in that small amount of light 3571 travel time (a few minutes to a few hours) is well approximated 3572 by a line segment that substends the angle seen from the remote 3573 body viewing Earth. That angle is pretty close to the aberration 3574 angle of the moving Earth viewing the remote body. 3575 In other words, both of the following approximate the aberration angle: 3576 (transverse distance Earth moves) / (distance to body) 3577 (transverse speed of Earth) / (speed of light). 3578 */ 3579 earth = CalcEarth(ltime); 3580 if (earth.status != ASTRO_SUCCESS) 3581 return earth; 3582 } 3583 3584 /* Convert heliocentric vector to geocentric vector. */ 3585 vector.x -= earth.x; 3586 vector.y -= earth.y; 3587 vector.z -= earth.z; 3588 3589 ltime2 = Astronomy_AddDays(time, -Astronomy_VectorLength(vector) / C_AUDAY); 3590 dt = fabs(ltime2.tt - ltime.tt); 3591 if (dt < 1.0e-9) 3592 goto finished; /* Ensures we patch 'vector.t' with current time, not ante-dated time. */ 3593 3594 ltime = ltime2; 3595 } 3596 return VecError(ASTRO_NO_CONVERGE, time); /* light travel time solver did not converge */ 3597 } 3598 3599 finished: 3600 vector.t = time; 3601 return vector; 3602 } 3603 3604 /** 3605 * @brief Calculates equatorial coordinates of a celestial body as seen by an observer on the Earth's surface. 3606 * 3607 * Calculates topocentric equatorial coordinates in one of two different systems: 3608 * J2000 or true-equator-of-date, depending on the value of the `equdate` parameter. 3609 * Equatorial coordinates include right ascension, declination, and distance in astronomical units. 3610 * 3611 * This function corrects for light travel time: it adjusts the apparent location 3612 * of the observed body based on how long it takes for light to travel from the body to the Earth. 3613 * 3614 * This function corrects for *topocentric parallax*, meaning that it adjusts for the 3615 * angular shift depending on where the observer is located on the Earth. This is most 3616 * significant for the Moon, because it is so close to the Earth. However, parallax corection 3617 * has a small effect on the apparent positions of other bodies. 3618 * 3619 * Correction for aberration is optional, using the `aberration` parameter. 3620 * 3621 * @param body The celestial body to be observed. Not allowed to be `BODY_EARTH`. 3622 * @param time The date and time at which the observation takes place. 3623 * @param observer A location on or near the surface of the Earth. 3624 * @param equdate Selects the date of the Earth's equator in which to express the equatorial coordinates. 3625 * @param aberration Selects whether or not to correct for aberration. 3626 */ 3627 astro_equatorial_t Astronomy_Equator( 3628 astro_body_t body, 3629 astro_time_t *time, 3630 astro_observer_t observer, 3631 astro_equator_date_t equdate, 3632 astro_aberration_t aberration) 3633 { 3634 astro_equatorial_t equ; 3635 astro_vector_t gc; 3636 double gc_observer[3]; 3637 double j2000[3]; 3638 double temp[3]; 3639 double datevect[3]; 3640 3641 geo_pos(time, observer, gc_observer); 3642 gc = Astronomy_GeoVector(body, *time, aberration); 3643 if (gc.status != ASTRO_SUCCESS) 3644 return EquError(gc.status); 3645 3646 j2000[0] = gc.x - gc_observer[0]; 3647 j2000[1] = gc.y - gc_observer[1]; 3648 j2000[2] = gc.z - gc_observer[2]; 3649 3650 switch (equdate) 3651 { 3652 case EQUATOR_OF_DATE: 3653 precession(0.0, j2000, time->tt, temp); 3654 nutation(time, 0, temp, datevect); 3655 equ = vector2radec(datevect); 3656 return equ; 3657 3658 case EQUATOR_J2000: 3659 equ = vector2radec(j2000); 3660 return equ; 3661 3662 default: 3663 return EquError(ASTRO_INVALID_PARAMETER); 3664 } 3665 } 3666 3667 /** 3668 * @brief Calculates the apparent location of a body relative to the local horizon of an observer on the Earth. 3669 * 3670 * Given a date and time, the geographic location of an observer on the Earth, and 3671 * equatorial coordinates (right ascension and declination) of a celestial body, 3672 * this function returns horizontal coordinates (azimuth and altitude angles) for the body 3673 * relative to the horizon at the geographic location. 3674 * 3675 * The right ascension `ra` and declination `dec` passed in must be *equator of date* 3676 * coordinates, based on the Earth's true equator at the date and time of the observation. 3677 * Otherwise the resulting horizontal coordinates will be inaccurate. 3678 * Equator of date coordinates can be obtained by calling #Astronomy_Equator, passing in 3679 * `EQUATOR_OF_DATE` as its `equdate` parameter. It is also recommended to enable 3680 * aberration correction by passing in `ABERRATION` as the `aberration` parameter. 3681 * 3682 * This function optionally corrects for atmospheric refraction. 3683 * For most uses, it is recommended to pass `REFRACTION_NORMAL` in the `refraction` parameter to 3684 * correct for optical lensing of the Earth's atmosphere that causes objects 3685 * to appear somewhat higher above the horizon than they actually are. 3686 * However, callers may choose to avoid this correction by passing in `REFRACTION_NONE`. 3687 * If refraction correction is enabled, the azimuth, altitude, right ascension, and declination 3688 * in the #astro_horizon_t structure returned by this function will all be corrected for refraction. 3689 * If refraction is disabled, none of these four coordinates will be corrected; in that case, 3690 * the right ascension and declination in the returned structure will be numerically identical 3691 * to the respective `ra` and `dec` values passed in. 3692 * 3693 * @param time 3694 * The date and time of the observation. 3695 * 3696 * @param observer 3697 * The geographic location of the observer. 3698 * 3699 * @param ra 3700 * The right ascension of the body in sidereal hours. 3701 * See function remarks for more details. 3702 * 3703 * @param dec 3704 * The declination of the body in degrees. See function remarks for more details. 3705 * 3706 * @param refraction 3707 * Selects whether to correct for atmospheric refraction, and if so, which model to use. 3708 * The recommended value for most uses is `REFRACTION_NORMAL`. 3709 * See function remarks for more details. 3710 * 3711 * @return 3712 * The body's apparent horizontal coordinates and equatorial coordinates, both optionally corrected for refraction. 3713 */ 3714 astro_horizon_t Astronomy_Horizon( 3715 astro_time_t *time, astro_observer_t observer, double ra, double dec, astro_refraction_t refraction) 3716 { 3717 astro_horizon_t hor; 3718 double uze[3], une[3], uwe[3]; 3719 double uz[3], un[3], uw[3]; 3720 double p[3], pz, pn, pw, proj; 3721 double az, zd; 3722 double spin_angle; 3723 3724 double sinlat = sin(observer.latitude * DEG2RAD); 3725 double coslat = cos(observer.latitude * DEG2RAD); 3726 double sinlon = sin(observer.longitude * DEG2RAD); 3727 double coslon = cos(observer.longitude * DEG2RAD); 3728 double sindc = sin(dec * DEG2RAD); 3729 double cosdc = cos(dec * DEG2RAD); 3730 double sinra = sin(ra * 15 * DEG2RAD); 3731 double cosra = cos(ra * 15 * DEG2RAD); 3732 3733 uze[0] = coslat * coslon; 3734 uze[1] = coslat * sinlon; 3735 uze[2] = sinlat; 3736 3737 une[0] = -sinlat * coslon; 3738 une[1] = -sinlat * sinlon; 3739 une[2] = coslat; 3740 3741 uwe[0] = sinlon; 3742 uwe[1] = -coslon; 3743 uwe[2] = 0.0; 3744 3745 spin_angle = -15.0 * sidereal_time(time); 3746 spin(spin_angle, uze, uz); 3747 spin(spin_angle, une, un); 3748 spin(spin_angle, uwe, uw); 3749 3750 p[0] = cosdc * cosra; 3751 p[1] = cosdc * sinra; 3752 p[2] = sindc; 3753 3754 pz = p[0]*uz[0] + p[1]*uz[1] + p[2]*uz[2]; 3755 pn = p[0]*un[0] + p[1]*un[1] + p[2]*un[2]; 3756 pw = p[0]*uw[0] + p[1]*uw[1] + p[2]*uw[2]; 3757 3758 proj = sqrt(pn*pn + pw*pw); 3759 az = 0.0; 3760 if (proj > 0.0) 3761 { 3762 az = -atan2(pw, pn) * RAD2DEG; 3763 if (az < 0) 3764 az += 360; 3765 else if (az >= 360) 3766 az -= 360; 3767 } 3768 zd = atan2(proj, pz) * RAD2DEG; 3769 hor.ra = ra; 3770 hor.dec = dec; 3771 3772 if (refraction == REFRACTION_NORMAL || refraction == REFRACTION_JPLHOR) 3773 { 3774 double zd0, refr; 3775 3776 zd0 = zd; 3777 refr = Astronomy_Refraction(refraction, 90.0 - zd); 3778 zd -= refr; 3779 3780 if (refr > 0.0 && zd > 3.0e-4) 3781 { 3782 int j; 3783 double sinzd = sin(zd * DEG2RAD); 3784 double coszd = cos(zd * DEG2RAD); 3785 double sinzd0 = sin(zd0 * DEG2RAD); 3786 double coszd0 = cos(zd0 * DEG2RAD); 3787 double pr[3]; 3788 3789 for (j=0; j<3; ++j) 3790 pr[j] = ((p[j] - coszd0 * uz[j]) / sinzd0)*sinzd + uz[j]*coszd; 3791 3792 proj = sqrt(pr[0]*pr[0] + pr[1]*pr[1]); 3793 if (proj > 0) 3794 { 3795 hor.ra = atan2(pr[1], pr[0]) * (RAD2DEG / 15.0); 3796 if (hor.ra < 0.0) 3797 hor.ra += 24.0; 3798 else if (hor.ra >= 24.0) 3799 hor.ra -= 24.0; 3800 } 3801 else 3802 { 3803 hor.ra = 0.0; 3804 } 3805 hor.dec = atan2(pr[2], proj) * RAD2DEG; 3806 } 3807 } 3808 3809 hor.azimuth = az; 3810 hor.altitude = 90.0 - zd; 3811 return hor; 3812 } 3813 3814 /** 3815 * @brief Calculates geocentric ecliptic coordinates for the Sun. 3816 * 3817 * This function calculates the position of the Sun as seen from the Earth. 3818 * The returned value includes both Cartesian and spherical coordinates. 3819 * The x-coordinate and longitude values in the returned structure are based 3820 * on the *true equinox of date*: one of two points in the sky where the instantaneous 3821 * plane of the Earth's equator at the given date and time (the *equatorial plane*) 3822 * intersects with the plane of the Earth's orbit around the Sun (the *ecliptic plane*). 3823 * By convention, the apparent location of the Sun at the March equinox is chosen 3824 * as the longitude origin and x-axis direction, instead of the one for September. 3825 * 3826 * `Astronomy_SunPosition` corrects for precession and nutation of the Earth's axis 3827 * in order to obtain the exact equatorial plane at the given time. 3828 * 3829 * This function can be used for calculating changes of seasons: equinoxes and solstices. 3830 * In fact, the function #Astronomy_Seasons does use this function for that purpose. 3831 * 3832 * @param time 3833 * The date and time for which to calculate the Sun's position. 3834 * 3835 * @return 3836 * The ecliptic coordinates of the Sun using the Earth's true equator of date. 3837 */ 3838 astro_ecliptic_t Astronomy_SunPosition(astro_time_t time) 3839 { 3840 astro_time_t adjusted_time; 3841 astro_vector_t earth2000; 3842 double sun2000[3]; 3843 double stemp[3]; 3844 double sun_ofdate[3]; 3845 double true_obliq; 3846 3847 /* Correct for light travel time from the Sun. */ 3848 /* Otherwise season calculations (equinox, solstice) will all be early by about 8 minutes! */ 3849 adjusted_time = Astronomy_AddDays(time, -1.0 / C_AUDAY); 3850 3851 earth2000 = CalcEarth(adjusted_time); 3852 if (earth2000.status != ASTRO_SUCCESS) 3853 return EclError(earth2000.status); 3854 3855 /* Convert heliocentric location of Earth to geocentric location of Sun. */ 3856 sun2000[0] = -earth2000.x; 3857 sun2000[1] = -earth2000.y; 3858 sun2000[2] = -earth2000.z; 3859 3860 /* Convert to equatorial Cartesian coordinates of date. */ 3861 precession(0.0, sun2000, adjusted_time.tt, stemp); 3862 nutation(&adjusted_time, 0, stemp, sun_ofdate); 3863 3864 /* Convert equatorial coordinates to ecliptic coordinates. */ 3865 true_obliq = DEG2RAD * e_tilt(&adjusted_time).tobl; 3866 return RotateEquatorialToEcliptic(sun_ofdate, true_obliq); 3867 } 3868 3869 /** 3870 * @brief Converts J2000 equatorial Cartesian coordinates to J2000 ecliptic coordinates. 3871 * 3872 * Given coordinates relative to the Earth's equator at J2000 (the instant of noon UTC 3873 * on 1 January 2000), this function converts those coordinates to J2000 ecliptic coordinates, 3874 * which are relative to the plane of the Earth's orbit around the Sun. 3875 * 3876 * @param equ 3877 * Equatorial coordinates in the J2000 frame of reference. 3878 * You can call #Astronomy_GeoVector to obtain suitable equatorial coordinates. 3879 * 3880 * @return 3881 * Ecliptic coordinates in the J2000 frame of reference. 3882 */ 3883 astro_ecliptic_t Astronomy_Ecliptic(astro_vector_t equ) 3884 { 3885 /* Based on NOVAS functions equ2ecl() and equ2ecl_vec(). */ 3886 static const double ob2000 = 0.40909260059599012; /* mean obliquity of the J2000 ecliptic in radians */ 3887 double pos[3]; 3888 3889 if (equ.status != ASTRO_SUCCESS) 3890 return EclError(equ.status); 3891 3892 pos[0] = equ.x; 3893 pos[1] = equ.y; 3894 pos[2] = equ.z; 3895 3896 return RotateEquatorialToEcliptic(pos, ob2000); 3897 } 3898 3899 /** 3900 * @brief Calculates heliocentric ecliptic longitude of a body based on the J2000 equinox. 3901 * 3902 * This function calculates the angle around the plane of the Earth's orbit 3903 * of a celestial body, as seen from the center of the Sun. 3904 * The angle is measured prograde (in the direction of the Earth's orbit around the Sun) 3905 * in degrees from the J2000 equinox. The ecliptic longitude is always in the range [0, 360). 3906 * 3907 * @param body 3908 * A body other than the Sun. 3909 * 3910 * @param time 3911 * The date and time at which the body's ecliptic longitude is to be calculated. 3912 * 3913 * @return 3914 * On success, returns a structure whose `status` is `ASTRO_SUCCESS` and whose 3915 * `angle` holds the ecliptic longitude in degrees. 3916 * On failure, `status` holds a value other than `ASTRO_SUCCESS`. 3917 */ 3918 astro_angle_result_t Astronomy_EclipticLongitude(astro_body_t body, astro_time_t time) 3919 { 3920 astro_vector_t hv; 3921 astro_ecliptic_t eclip; 3922 astro_angle_result_t result; 3923 3924 if (body == BODY_SUN) 3925 return AngleError(ASTRO_INVALID_BODY); /* cannot calculate heliocentric longitude of the Sun */ 3926 3927 hv = Astronomy_HelioVector(body, time); 3928 eclip = Astronomy_Ecliptic(hv); /* checks for errors in hv, so we don't have to here */ 3929 if (eclip.status != ASTRO_SUCCESS) 3930 return AngleError(eclip.status); 3931 3932 result.angle = eclip.elon; 3933 result.status = ASTRO_SUCCESS; 3934 return result; 3935 } 3936 3937 static astro_ecliptic_t RotateEquatorialToEcliptic(const double pos[3], double obliq_radians) 3938 { 3939 astro_ecliptic_t ecl; 3940 double cos_ob, sin_ob; 3941 double xyproj; 3942 3943 cos_ob = cos(obliq_radians); 3944 sin_ob = sin(obliq_radians); 3945 3946 ecl.ex = +pos[0]; 3947 ecl.ey = +pos[1]*cos_ob + pos[2]*sin_ob; 3948 ecl.ez = -pos[1]*sin_ob + pos[2]*cos_ob; 3949 3950 xyproj = sqrt(ecl.ex*ecl.ex + ecl.ey*ecl.ey); 3951 if (xyproj > 0.0) 3952 { 3953 ecl.elon = RAD2DEG * atan2(ecl.ey, ecl.ex); 3954 if (ecl.elon < 0.0) 3955 ecl.elon += 360.0; 3956 } 3957 else 3958 ecl.elon = 0.0; 3959 3960 ecl.elat = RAD2DEG * atan2(ecl.ez, xyproj); 3961 ecl.status = ASTRO_SUCCESS; 3962 return ecl; 3963 } 3964 3965 static astro_func_result_t sun_offset(void *context, astro_time_t time) 3966 { 3967 astro_func_result_t result; 3968 double targetLon = *((double *)context); 3969 astro_ecliptic_t ecl = Astronomy_SunPosition(time); 3970 if (ecl.status != ASTRO_SUCCESS) 3971 return FuncError(ecl.status); 3972 result.value = LongitudeOffset(ecl.elon - targetLon); 3973 result.status = ASTRO_SUCCESS; 3974 return result; 3975 } 3976 3977 /** 3978 * @brief 3979 * Searches for the time when the Sun reaches an apparent ecliptic longitude as seen from the Earth. 3980 * 3981 * This function finds the moment in time, if any exists in the given time window, 3982 * that the center of the Sun reaches a specific ecliptic longitude as seen from the center of the Earth. 3983 * 3984 * This function can be used to determine equinoxes and solstices. 3985 * However, it is usually more convenient and efficient to call #Astronomy_Seasons 3986 * to calculate all equinoxes and solstices for a given calendar year. 3987 * 3988 * The function searches the window of time specified by `startTime` and `startTime+limitDays`. 3989 * The search will return an error if the Sun never reaches the longitude `targetLon` or 3990 * if the window is so large that the longitude ranges more than 180 degrees within it. 3991 * It is recommended to keep the window smaller than 10 days when possible. 3992 * 3993 * @param targetLon 3994 * The desired ecliptic longitude in degrees, relative to the true equinox of date. 3995 * This may be any value in the range [0, 360), although certain values have 3996 * conventional meanings: 3997 * 0 = March equinox, 90 = June solstice, 180 = September equinox, 270 = December solstice. 3998 * 3999 * @param startTime 4000 * The date and time for starting the search for the desired longitude event. 4001 * 4002 * @param limitDays 4003 * The real-valued number of days, which when added to `startTime`, limits the 4004 * range of time over which the search looks. 4005 * It is recommended to keep this value between 1 and 10 days. 4006 * See function remarks for more details. 4007 * 4008 * @return 4009 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 4010 * and the `time` field will contain the date and time the Sun reaches the target longitude. 4011 * Any other value indicates an error. 4012 * See remarks in #Astronomy_Search (which this function calls) for more information about possible error codes. 4013 */ 4014 astro_search_result_t Astronomy_SearchSunLongitude( 4015 double targetLon, 4016 astro_time_t startTime, 4017 double limitDays) 4018 { 4019 astro_time_t t2 = Astronomy_AddDays(startTime, limitDays); 4020 return Astronomy_Search(sun_offset, &targetLon, startTime, t2, 1.0); 4021 } 4022 4023 /** @cond DOXYGEN_SKIP */ 4024 #define CALLFUNC(f,t) \ 4025 do { \ 4026 funcres = func(context, (t)); \ 4027 if (funcres.status != ASTRO_SUCCESS) return SearchError(funcres.status); \ 4028 (f) = funcres.value; \ 4029 } while(0) 4030 /** @endcond */ 4031 4032 /** 4033 * @brief Searches for a time at which a function's value increases through zero. 4034 * 4035 * Certain astronomy calculations involve finding a time when an event occurs. 4036 * Often such events can be defined as the root of a function: 4037 * the time at which the function's value becomes zero. 4038 * 4039 * `Astronomy_Search` finds the *ascending root* of a function: the time at which 4040 * the function's value becomes zero while having a positive slope. That is, as time increases, 4041 * the function transitions from a negative value, through zero at a specific moment, 4042 * to a positive value later. The goal of the search is to find that specific moment. 4043 * 4044 * The search function is specified by two parameters: `func` and `context`. 4045 * The `func` parameter is a pointer to the function itself, which accepts a time 4046 * and a context containing any other arguments needed to evaluate the function. 4047 * The `context` parameter supplies that context for the given search. 4048 * As an example, a caller may wish to find the moment a celestial body reaches a certain 4049 * ecliptic longitude. In that case, the caller might create a structure that contains 4050 * an #astro_body_t member to specify the body and a `double` to hold the target longitude. 4051 * The function would cast the pointer `context` passed in as a pointer to that structure type. 4052 * It could subtract the target longitude from the actual longitude at a given time; 4053 * thus the difference would equal zero at the moment in time the planet reaches the 4054 * desired longitude. 4055 * 4056 * The `func` returns an #astro_func_result_t structure every time it is called. 4057 * If the returned structure has a value of `status` other than `ASTRO_SUCCESS`, 4058 * the search immediately fails and reports that same error code in the `status` 4059 * returned by `Astronomy_Search`. Otherwise, `status` is `ASTRO_SUCCESS` and 4060 * `value` is the value of the function, and the search proceeds until it either 4061 * finds the ascending root or fails for some reason. 4062 * 4063 * The search calls `func` repeatedly to rapidly narrow in on any ascending 4064 * root within the time window specified by `t1` and `t2`. The search never 4065 * reports a solution outside this time window. 4066 * 4067 * `Astronomy_Search` uses a combination of bisection and quadratic interpolation 4068 * to minimize the number of function calls. However, it is critical that the 4069 * supplied time window be small enough that there cannot be more than one root 4070 * (ascedning or descending) within it; otherwise the search can fail. 4071 * Beyond that, it helps to make the time window as small as possible, ideally 4072 * such that the function itself resembles a smooth parabolic curve within that window. 4073 * 4074 * If an ascending root is not found, or more than one root 4075 * (ascending and/or descending) exists within the window `t1`..`t2`, 4076 * the search will fail with status code `ASTRO_SEARCH_FAILURE`. 4077 * 4078 * If the search does not converge within 20 iterations, it will fail 4079 * with status code `ASTRO_NO_CONVERGE`. 4080 * 4081 * @param func 4082 * The function for which to find the time of an ascending root. 4083 * See function remarks for more details. 4084 * 4085 * @param context 4086 * Any ancillary data needed by the function `func` to calculate a value. 4087 * The data type varies depending on the function passed in. 4088 * For example, the function may involve a specific celestial body that 4089 * must be specified somehow. 4090 * 4091 * @param t1 4092 * The lower time bound of the search window. 4093 * See function remarks for more details. 4094 * 4095 * @param t2 4096 * The upper time bound of the search window. 4097 * See function remarks for more details. 4098 * 4099 * @param dt_tolerance_seconds 4100 * Specifies an amount of time in seconds within which a bounded ascending root 4101 * is considered accurate enough to stop. A typical value is 1 second. 4102 * 4103 * @return 4104 * If successful, the returned structure has `status` equal to `ASTRO_SUCCESS` 4105 * and `time` set to a value within `dt_tolerance_seconds` of an ascending root. 4106 * On success, the `time` value will always be in the inclusive range [`t1`, `t2`]. 4107 * If the search fails, `status` will be set to a value other than `ASTRO_SUCCESS`. 4108 * See function remarks for more details. 4109 */ 4110 astro_search_result_t Astronomy_Search( 4111 astro_search_func_t func, 4112 void *context, 4113 astro_time_t t1, 4114 astro_time_t t2, 4115 double dt_tolerance_seconds) 4116 { 4117 astro_search_result_t result; 4118 astro_time_t tmid; 4119 astro_time_t tq; 4120 astro_func_result_t funcres; 4121 double f1, f2, fmid=0.0, fq, dt_days, dt, dt_guess; 4122 double q_x, q_ut, q_df_dt; 4123 const int iter_limit = 20; 4124 int iter = 0; 4125 int calc_fmid = 1; 4126 4127 dt_days = fabs(dt_tolerance_seconds / SECONDS_PER_DAY); 4128 CALLFUNC(f1, t1); 4129 CALLFUNC(f2, t2); 4130 4131 for(;;) 4132 { 4133 if (++iter > iter_limit) 4134 return SearchError(ASTRO_NO_CONVERGE); 4135 4136 dt = (t2.tt - t1.tt) / 2.0; 4137 tmid = Astronomy_AddDays(t1, dt); 4138 if (fabs(dt) < dt_days) 4139 { 4140 /* We are close enough to the event to stop the search. */ 4141 result.time = tmid; 4142 result.status = ASTRO_SUCCESS; 4143 return result; 4144 } 4145 4146 if (calc_fmid) 4147 CALLFUNC(fmid, tmid); 4148 else 4149 calc_fmid = 1; /* we already have the correct value of fmid from the previous loop */ 4150 4151 /* Quadratic interpolation: */ 4152 /* Try to find a parabola that passes through the 3 points we have sampled: */ 4153 /* (t1,f1), (tmid,fmid), (t2,f2) */ 4154 4155 if (QuadInterp(tmid.ut, t2.ut - tmid.ut, f1, fmid, f2, &q_x, &q_ut, &q_df_dt)) 4156 { 4157 tq = Astronomy_TimeFromDays(q_ut); 4158 CALLFUNC(fq, tq); 4159 if (q_df_dt != 0.0) 4160 { 4161 dt_guess = fabs(fq / q_df_dt); 4162 if (dt_guess < dt_days) 4163 { 4164 /* The estimated time error is small enough that we can quit now. */ 4165 result.time = tq; 4166 result.status = ASTRO_SUCCESS; 4167 return result; 4168 } 4169 4170 /* Try guessing a tighter boundary with the interpolated root at the center. */ 4171 dt_guess *= 1.2; 4172 if (dt_guess < dt/10.0) 4173 { 4174 astro_time_t tleft = Astronomy_AddDays(tq, -dt_guess); 4175 astro_time_t tright = Astronomy_AddDays(tq, +dt_guess); 4176 if ((tleft.ut - t1.ut)*(tleft.ut - t2.ut) < 0) 4177 { 4178 if ((tright.ut - t1.ut)*(tright.ut - t2.ut) < 0) 4179 { 4180 double fleft, fright; 4181 CALLFUNC(fleft, tleft); 4182 CALLFUNC(fright, tright); 4183 if (fleft<0.0 && fright>=0.0) 4184 { 4185 f1 = fleft; 4186 f2 = fright; 4187 t1 = tleft; 4188 t2 = tright; 4189 fmid = fq; 4190 calc_fmid = 0; /* save a little work -- no need to re-calculate fmid next time around the loop */ 4191 continue; 4192 } 4193 } 4194 } 4195 } 4196 } 4197 } 4198 4199 /* After quadratic interpolation attempt. */ 4200 /* Now just divide the region in two parts and pick whichever one appears to contain a root. */ 4201 if (f1 < 0.0 && fmid >= 0.0) 4202 { 4203 t2 = tmid; 4204 f2 = fmid; 4205 continue; 4206 } 4207 4208 if (fmid < 0.0 && f2 >= 0.0) 4209 { 4210 t1 = tmid; 4211 f1 = fmid; 4212 continue; 4213 } 4214 4215 /* Either there is no ascending zero-crossing in this range */ 4216 /* or the search window is too wide (more than one zero-crossing). */ 4217 return SearchError(ASTRO_SEARCH_FAILURE); 4218 } 4219 } 4220 4221 static int QuadInterp( 4222 double tm, double dt, double fa, double fm, double fb, 4223 double *out_x, double *out_t, double *out_df_dt) 4224 { 4225 double Q, R, S; 4226 double u, ru, x1, x2; 4227 4228 Q = (fb + fa)/2.0 - fm; 4229 R = (fb - fa)/2.0; 4230 S = fm; 4231 4232 if (Q == 0.0) 4233 { 4234 /* This is a line, not a parabola. */ 4235 if (R == 0.0) 4236 return 0; /* This is a HORIZONTAL line... can't make progress! */ 4237 *out_x = -S / R; 4238 if (*out_x < -1.0 || *out_x > +1.0) 4239 return 0; /* out of bounds */ 4240 } 4241 else 4242 { 4243 /* This really is a parabola. Find roots x1, x2. */ 4244 u = R*R - 4*Q*S; 4245 if (u <= 0.0) 4246 return 0; /* can't solve if imaginary, or if vertex of parabola is tangent. */ 4247 4248 ru = sqrt(u); 4249 x1 = (-R + ru) / (2.0 * Q); 4250 x2 = (-R - ru) / (2.0 * Q); 4251 if (-1.0 <= x1 && x1 <= +1.0) 4252 { 4253 if (-1.0 <= x2 && x2 <= +1.0) 4254 return 0; /* two roots are within bounds; we require a unique zero-crossing. */ 4255 *out_x = x1; 4256 } 4257 else if (-1.0 <= x2 && x2 <= +1.0) 4258 *out_x = x2; 4259 else 4260 return 0; /* neither root is within bounds */ 4261 } 4262 4263 *out_t = tm + (*out_x)*dt; 4264 *out_df_dt = (2*Q*(*out_x) + R) / dt; 4265 return 1; /* success */ 4266 } 4267 4268 static astro_status_t FindSeasonChange(double targetLon, int year, int month, int day, astro_time_t *time) 4269 { 4270 astro_time_t startTime = Astronomy_MakeTime(year, month, day, 0, 0, 0.0); 4271 astro_search_result_t result = Astronomy_SearchSunLongitude(targetLon, startTime, 4.0); 4272 *time = result.time; 4273 return result.status; 4274 } 4275 4276 /** 4277 * @brief Finds both equinoxes and both solstices for a given calendar year. 4278 * 4279 * The changes of seasons are defined by solstices and equinoxes. 4280 * Given a calendar year number, this function calculates the 4281 * March and September equinoxes and the June and December solstices. 4282 * 4283 * The equinoxes are the moments twice each year when the plane of the 4284 * Earth's equator passes through the center of the Sun. In other words, 4285 * the Sun's declination is zero at both equinoxes. 4286 * The March equinox defines the beginning of spring in the northern hemisphere 4287 * and the beginning of autumn in the southern hemisphere. 4288 * The September equinox defines the beginning of autumn in the northern hemisphere 4289 * and the beginning of spring in the southern hemisphere. 4290 * 4291 * The solstices are the moments twice each year when one of the Earth's poles 4292 * is most tilted toward the Sun. More precisely, the Sun's declination reaches 4293 * its minimum value at the December solstice, which defines the beginning of 4294 * winter in the northern hemisphere and the beginning of summer in the southern 4295 * hemisphere. The Sun's declination reaches its maximum value at the June solstice, 4296 * which defines the beginning of summer in the northern hemisphere and the beginning 4297 * of winter in the southern hemisphere. 4298 * 4299 * @param year 4300 * The calendar year number for which to calculate equinoxes and solstices. 4301 * The value may be any integer, but only the years 1800 through 2100 have been 4302 * validated for accuracy: unit testing against data from the 4303 * United States Naval Observatory confirms that all equinoxes and solstices 4304 * for that range of years are within 2 minutes of the correct time. 4305 * 4306 * @return 4307 * The times of the four seasonal changes in the given calendar year. 4308 * This function should always succeed. However, to be safe, callers 4309 * should check the `status` field of the returned structure to make sure 4310 * it contains `ASTRO_SUCCESS`. Any failures indicate a bug in the algorithm 4311 * and should be [reported as an issue](https://github.com/cosinekitty/astronomy/issues). 4312 */ 4313 astro_seasons_t Astronomy_Seasons(int year) 4314 { 4315 astro_seasons_t seasons; 4316 astro_status_t status; 4317 4318 seasons.status = ASTRO_SUCCESS; 4319 4320 status = FindSeasonChange( 0, year, 3, 19, &seasons.mar_equinox); 4321 if (status != ASTRO_SUCCESS) seasons.status = status; 4322 4323 status = FindSeasonChange( 90, year, 6, 19, &seasons.jun_solstice); 4324 if (status != ASTRO_SUCCESS) seasons.status = status; 4325 4326 status = FindSeasonChange(180, year, 9, 21, &seasons.sep_equinox); 4327 if (status != ASTRO_SUCCESS) seasons.status = status; 4328 4329 status = FindSeasonChange(270, year, 12, 20, &seasons.dec_solstice); 4330 if (status != ASTRO_SUCCESS) seasons.status = status; 4331 4332 return seasons; 4333 } 4334 4335 /** 4336 * @brief Returns the angle between the given body and the Sun, as seen from the Earth. 4337 * 4338 * This function calculates the angular separation between the given body and the Sun, 4339 * as seen from the center of the Earth. This angle is helpful for determining how 4340 * easy it is to see the body away from the glare of the Sun. 4341 * 4342 * @param body 4343 * The celestial body whose angle from the Sun is to be measured. 4344 * Not allowed to be `BODY_EARTH`. 4345 * 4346 * @param time 4347 * The time at which the observation is made. 4348 * 4349 * @return 4350 * If successful, the returned structure contains `ASTRO_SUCCESS` in the `status` field 4351 * and `angle` holds the angle in degrees between the Sun and the specified body as 4352 * seen from the center of the Earth. 4353 * If an error occurs, the `status` field contains a value other than `ASTRO_SUCCESS` 4354 * that indicates the error condition. 4355 */ 4356 astro_angle_result_t Astronomy_AngleFromSun(astro_body_t body, astro_time_t time) 4357 { 4358 astro_vector_t sv, bv; 4359 4360 if (body == BODY_EARTH) 4361 return AngleError(ASTRO_EARTH_NOT_ALLOWED); 4362 4363 sv = Astronomy_GeoVector(BODY_SUN, time, ABERRATION); 4364 if (sv.status != ASTRO_SUCCESS) 4365 return AngleError(sv.status); 4366 4367 bv = Astronomy_GeoVector(body, time, ABERRATION); 4368 if (bv.status != ASTRO_SUCCESS) 4369 return AngleError(bv.status); 4370 4371 return AngleBetween(sv, bv); 4372 } 4373 4374 /** 4375 * @brief 4376 * Determines visibility of a celestial body relative to the Sun, as seen from the Earth. 4377 * 4378 * This function returns an #astro_elongation_t structure, which provides the following 4379 * information about the given celestial body at the given time: 4380 * 4381 * - `visibility` is an enumerated type that specifies whether the body is more easily seen 4382 * in the morning before sunrise, or in the evening after sunset. 4383 * 4384 * - `elongation` is the angle in degrees between two vectors: one from the center of the Earth to the 4385 * center of the Sun, the other from the center of the Earth to the center of the specified body. 4386 * This angle indicates how far away the body is from the glare of the Sun. 4387 * The elongation angle is always in the range [0, 180]. 4388 * 4389 * - `ecliptic_separation` is the absolute value of the difference between the body's ecliptic longitude 4390 * and the Sun's ecliptic longitude, both as seen from the center of the Earth. This angle measures 4391 * around the plane of the Earth's orbit, and ignores how far above or below that plane the body is. 4392 * The ecliptic separation is measured in degrees and is always in the range [0, 180]. 4393 * 4394 * @param body 4395 * The celestial body whose visibility is to be calculated. 4396 * 4397 * @param time 4398 * The date and time of the observation. 4399 * 4400 * @return 4401 * If successful, the `status` field in the returned structure contains `ASTRO_SUCCESS` 4402 * and all the other fields in the structure are valid. On failure, `status` contains 4403 * some other value as an error code and the other fields contain invalid values. 4404 */ 4405 astro_elongation_t Astronomy_Elongation(astro_body_t body, astro_time_t time) 4406 { 4407 astro_elongation_t result; 4408 astro_angle_result_t angres; 4409 4410 angres = Astronomy_LongitudeFromSun(body, time); 4411 if (angres.status != ASTRO_SUCCESS) 4412 return ElongError(angres.status); 4413 4414 if (angres.angle > 180.0) 4415 { 4416 result.visibility = VISIBLE_MORNING; 4417 result.ecliptic_separation = 360.0 - angres.angle; 4418 } 4419 else 4420 { 4421 result.visibility = VISIBLE_EVENING; 4422 result.ecliptic_separation = angres.angle; 4423 } 4424 4425 angres = Astronomy_AngleFromSun(body, time); 4426 if (angres.status != ASTRO_SUCCESS) 4427 return ElongError(angres.status); 4428 4429 result.elongation = angres.angle; 4430 result.time = time; 4431 result.status = ASTRO_SUCCESS; 4432 4433 return result; 4434 } 4435 4436 static astro_func_result_t neg_elong_slope(void *context, astro_time_t time) 4437 { 4438 static const double dt = 0.1; 4439 astro_angle_result_t e1, e2; 4440 astro_func_result_t result; 4441 astro_body_t body = *((astro_body_t *)context); 4442 astro_time_t t1 = Astronomy_AddDays(time, -dt/2.0); 4443 astro_time_t t2 = Astronomy_AddDays(time, +dt/2.0); 4444 4445 e1 = Astronomy_AngleFromSun(body, t1); 4446 if (e1.status != ASTRO_SUCCESS) 4447 return FuncError(e1.status); 4448 4449 e2 = Astronomy_AngleFromSun(body, t2); 4450 if (e2.status) 4451 return FuncError(e2.status); 4452 4453 result.value = (e1.angle - e2.angle)/dt; 4454 result.status = ASTRO_SUCCESS; 4455 return result; 4456 } 4457 4458 /** 4459 * @brief 4460 * Finds a date and time when Mercury or Venus reaches its maximum angle from the Sun as seen from the Earth. 4461 * 4462 * Mercury and Venus are are often difficult to observe because they are closer to the Sun than the Earth is. 4463 * Mercury especially is almost always impossible to see because it gets lost in the Sun's glare. 4464 * The best opportunities for spotting Mercury, and the best opportunities for viewing Venus through 4465 * a telescope without atmospheric interference, are when these planets reach maximum elongation. 4466 * These are events where the planets reach the maximum angle from the Sun as seen from the Earth. 4467 * 4468 * This function solves for those times, reporting the next maximum elongation event's date and time, 4469 * the elongation value itself, the relative longitude with the Sun, and whether the planet is best 4470 * observed in the morning or evening. See #Astronomy_Elongation for more details about the returned structure. 4471 * 4472 * @param body 4473 * Either `BODY_MERCURY` or `BODY_VENUS`. Any other value will fail with the error `ASTRO_INVALID_BODY`. 4474 * To find the best viewing opportunites for planets farther from the Sun than the Earth is (Mars through Pluto) 4475 * use #Astronomy_SearchRelativeLongitude to find the next opposition event. 4476 * 4477 * @param startTime 4478 * The date and time at which to begin the search. The maximum elongation event found will always 4479 * be the first one that occurs after this date and time. 4480 * 4481 * @return 4482 * If successful, the `status` field of the returned structure will be `ASTRO_SUCCESS` 4483 * and the other structure fields will be valid. Otherwise, `status` will contain 4484 * some other value indicating an error. 4485 */ 4486 astro_elongation_t Astronomy_SearchMaxElongation(astro_body_t body, astro_time_t startTime) 4487 { 4488 double s1, s2; 4489 int iter; 4490 astro_angle_result_t plon, elon; 4491 astro_time_t t_start; 4492 double rlon, rlon_lo, rlon_hi, adjust_days; 4493 astro_func_result_t syn; 4494 astro_search_result_t search1, search2, searchx; 4495 astro_time_t t1, t2; 4496 astro_func_result_t m1, m2; 4497 4498 /* Determine the range of relative longitudes within which maximum elongation can occur for this planet. */ 4499 switch (body) 4500 { 4501 case BODY_MERCURY: 4502 s1 = 50.0; 4503 s2 = 85.0; 4504 break; 4505 4506 case BODY_VENUS: 4507 s1 = 40.0; 4508 s2 = 50.0; 4509 break; 4510 4511 default: 4512 /* SearchMaxElongation works for Mercury and Venus only. */ 4513 return ElongError(ASTRO_INVALID_BODY); 4514 } 4515 4516 syn = SynodicPeriod(body); 4517 if (syn.status != ASTRO_SUCCESS) 4518 return ElongError(syn.status); 4519 4520 iter = 0; 4521 while (++iter <= 2) 4522 { 4523 plon = Astronomy_EclipticLongitude(body, startTime); 4524 if (plon.status != ASTRO_SUCCESS) 4525 return ElongError(plon.status); 4526 4527 elon = Astronomy_EclipticLongitude(BODY_EARTH, startTime); 4528 if (elon.status != ASTRO_SUCCESS) 4529 return ElongError(elon.status); 4530 4531 rlon = LongitudeOffset(plon.angle - elon.angle); /* clamp to (-180, +180] */ 4532 4533 /* The slope function is not well-behaved when rlon is near 0 degrees or 180 degrees */ 4534 /* because there is a cusp there that causes a discontinuity in the derivative. */ 4535 /* So we need to guard against searching near such times. */ 4536 if (rlon >= -s1 && rlon < +s1) 4537 { 4538 /* Seek to the window [+s1, +s2]. */ 4539 adjust_days = 0.0; 4540 /* Search forward for the time t1 when rel lon = +s1. */ 4541 rlon_lo = +s1; 4542 /* Search forward for the time t2 when rel lon = +s2. */ 4543 rlon_hi = +s2; 4544 } 4545 else if (rlon > +s2 || rlon < -s2) 4546 { 4547 /* Seek to the next search window at [-s2, -s1]. */ 4548 adjust_days = 0.0; 4549 /* Search forward for the time t1 when rel lon = -s2. */ 4550 rlon_lo = -s2; 4551 /* Search forward for the time t2 when rel lon = -s1. */ 4552 rlon_hi = -s1; 4553 } 4554 else if (rlon >= 0.0) 4555 { 4556 /* rlon must be in the middle of the window [+s1, +s2]. */ 4557 /* Search BACKWARD for the time t1 when rel lon = +s1. */ 4558 adjust_days = -syn.value / 4.0; 4559 rlon_lo = +s1; 4560 rlon_hi = +s2; 4561 /* Search forward from t1 to find t2 such that rel lon = +s2. */ 4562 } 4563 else 4564 { 4565 /* rlon must be in the middle of the window [-s2, -s1]. */ 4566 /* Search BACKWARD for the time t1 when rel lon = -s2. */ 4567 adjust_days = -syn.value / 4.0; 4568 rlon_lo = -s2; 4569 /* Search forward from t1 to find t2 such that rel lon = -s1. */ 4570 rlon_hi = -s1; 4571 } 4572 4573 t_start = Astronomy_AddDays(startTime, adjust_days); 4574 4575 search1 = Astronomy_SearchRelativeLongitude(body, rlon_lo, t_start); 4576 if (search1.status != ASTRO_SUCCESS) 4577 return ElongError(search1.status); 4578 t1 = search1.time; 4579 4580 search2 = Astronomy_SearchRelativeLongitude(body, rlon_hi, t1); 4581 if (search2.status != ASTRO_SUCCESS) 4582 return ElongError(search2.status); 4583 t2 = search2.time; 4584 4585 /* Now we have a time range [t1,t2] that brackets a maximum elongation event. */ 4586 /* Confirm the bracketing. */ 4587 m1 = neg_elong_slope(&body, t1); 4588 if (m1.status != ASTRO_SUCCESS) 4589 return ElongError(m1.status); 4590 4591 if (m1.value >= 0) 4592 return ElongError(ASTRO_INTERNAL_ERROR); /* there is a bug in the bracketing algorithm! */ 4593 4594 m2 = neg_elong_slope(&body, t2); 4595 if (m2.status != ASTRO_SUCCESS) 4596 return ElongError(m2.status); 4597 4598 if (m2.value <= 0) 4599 return ElongError(ASTRO_INTERNAL_ERROR); /* there is a bug in the bracketing algorithm! */ 4600 4601 /* Use the generic search algorithm to home in on where the slope crosses from negative to positive. */ 4602 searchx = Astronomy_Search(neg_elong_slope, &body, t1, t2, 10.0); 4603 if (searchx.status != ASTRO_SUCCESS) 4604 return ElongError(searchx.status); 4605 4606 if (searchx.time.tt >= startTime.tt) 4607 return Astronomy_Elongation(body, searchx.time); 4608 4609 /* This event is in the past (earlier than startTime). */ 4610 /* We need to search forward from t2 to find the next possible window. */ 4611 /* We never need to search more than twice. */ 4612 startTime = Astronomy_AddDays(t2, 1.0); 4613 } 4614 4615 return ElongError(ASTRO_SEARCH_FAILURE); 4616 } 4617 4618 /** 4619 * @brief 4620 * Returns a body's ecliptic longitude with respect to the Sun, as seen from the Earth. 4621 * 4622 * This function can be used to determine where a planet appears around the ecliptic plane 4623 * (the plane of the Earth's orbit around the Sun) as seen from the Earth, 4624 * relative to the Sun's apparent position. 4625 * 4626 * The angle starts at 0 when the body and the Sun are at the same ecliptic longitude 4627 * as seen from the Earth. The angle increases in the prograde direction 4628 * (the direction that the planets orbit the Sun and the Moon orbits the Earth). 4629 * 4630 * When the angle is 180 degrees, it means the Sun and the body appear on opposite sides 4631 * of the sky for an Earthly observer. When `body` is a planet whose orbit around the 4632 * Sun is farther than the Earth's, 180 degrees indicates opposition. For the Moon, 4633 * it indicates a full moon. 4634 * 4635 * The angle keeps increasing up to 360 degrees as the body's apparent prograde 4636 * motion continues relative to the Sun. When the angle reaches 360 degrees, it starts 4637 * over at 0 degrees. 4638 * 4639 * Values between 0 and 180 degrees indicate that the body is visible in the evening sky 4640 * after sunset. Values between 180 degrees and 360 degrees indicate that the body 4641 * is visible in the morning sky before sunrise. 4642 * 4643 * @param body 4644 * The celestial body for which to find longitude from the Sun. 4645 * 4646 * @param time 4647 * The date and time of the observation. 4648 * 4649 * @return 4650 * On success, the `status` field in the returned structure holds `ASTRO_SUCCESS` and 4651 * the `angle` field holds a value in the range [0, 360). 4652 * On failure, the `status` field contains some other value indicating an error condition. 4653 */ 4654 astro_angle_result_t Astronomy_LongitudeFromSun(astro_body_t body, astro_time_t time) 4655 { 4656 astro_vector_t sv, bv; 4657 astro_ecliptic_t se, be; 4658 astro_angle_result_t result; 4659 4660 if (body == BODY_EARTH) 4661 return AngleError(ASTRO_EARTH_NOT_ALLOWED); 4662 4663 sv = Astronomy_GeoVector(BODY_SUN, time, NO_ABERRATION); 4664 se = Astronomy_Ecliptic(sv); /* checks for errors in sv */ 4665 if (se.status != ASTRO_SUCCESS) 4666 return AngleError(se.status); 4667 4668 bv = Astronomy_GeoVector(body, time, NO_ABERRATION); 4669 be = Astronomy_Ecliptic(bv); /* checks for errors in bv */ 4670 if (be.status != ASTRO_SUCCESS) 4671 return AngleError(be.status); 4672 4673 result.status = ASTRO_SUCCESS; 4674 result.angle = NormalizeLongitude(be.elon - se.elon); 4675 return result; 4676 } 4677 4678 /** 4679 * @brief 4680 * Returns the Moon's phase as an angle from 0 to 360 degrees. 4681 * 4682 * This function determines the phase of the Moon using its apparent 4683 * ecliptic longitude relative to the Sun, as seen from the center of the Earth. 4684 * Certain values of the angle have conventional definitions: 4685 * 4686 * - 0 = new moon 4687 * - 90 = first quarter 4688 * - 180 = full moon 4689 * - 270 = third quarter 4690 * 4691 * @param time 4692 * The date and time of the observation. 4693 * 4694 * @return 4695 * On success, the function returns the angle as described in the function remarks 4696 * in the `angle` field and `ASTRO_SUCCESS` in the `status` field. 4697 * The function should always succeed, but it is a good idea for callers to check 4698 * the `status` field in the returned structure. 4699 * Any other value in `status` indicates a failure that should be 4700 * [reported as an issue](https://github.com/cosinekitty/astronomy/issues). 4701 */ 4702 astro_angle_result_t Astronomy_MoonPhase(astro_time_t time) 4703 { 4704 return Astronomy_LongitudeFromSun(BODY_MOON, time); 4705 } 4706 4707 static astro_func_result_t moon_offset(void *context, astro_time_t time) 4708 { 4709 astro_func_result_t result; 4710 double targetLon = *((double *)context); 4711 astro_angle_result_t angres = Astronomy_MoonPhase(time); 4712 if (angres.status != ASTRO_SUCCESS) 4713 return FuncError(angres.status); 4714 result.value = LongitudeOffset(angres.angle - targetLon); 4715 result.status = ASTRO_SUCCESS; 4716 return result; 4717 } 4718 4719 /** 4720 * @brief 4721 * Searches for the time that the Moon reaches a specified phase. 4722 * 4723 * Lunar phases are conventionally defined in terms of the Moon's geocentric ecliptic 4724 * longitude with respect to the Sun's geocentric ecliptic longitude. 4725 * When the Moon and the Sun have the same longitude, that is defined as a new moon. 4726 * When their longitudes are 180 degrees apart, that is defined as a full moon. 4727 * 4728 * This function searches for any value of the lunar phase expressed as an 4729 * angle in degrees in the range [0, 360). 4730 * 4731 * If you want to iterate through lunar quarters (new moon, first quarter, full moon, third quarter) 4732 * it is much easier to call the functions #Astronomy_SearchMoonQuarter and #Astronomy_NextMoonQuarter. 4733 * This function is useful for finding general phase angles outside those four quarters. 4734 * 4735 * @param targetLon 4736 * The difference in geocentric longitude between the Sun and Moon 4737 * that specifies the lunar phase being sought. This can be any value 4738 * in the range [0, 360). Certain values have conventional names: 4739 * 0 = new moon, 90 = first quarter, 180 = full moon, 270 = third quarter. 4740 * 4741 * @param startTime 4742 * The beginning of the time window in which to search for the Moon reaching the specified phase. 4743 * 4744 * @param limitDays 4745 * The number of days after `startTime` that limits the time window for the search. 4746 * 4747 * @return 4748 * On success, the `status` field in the returned structure holds `ASTRO_SUCCESS` and 4749 * the `time` field holds the date and time when the Moon reaches the target longitude. 4750 * On failure, `status` holds some other value as an error code. 4751 * One possible error code is `ASTRO_NO_MOON_QUARTER` if `startTime` and `limitDays` 4752 * do not enclose the desired event. See remarks in #Astronomy_Search for other possible 4753 * error codes. 4754 */ 4755 astro_search_result_t Astronomy_SearchMoonPhase(double targetLon, astro_time_t startTime, double limitDays) 4756 { 4757 /* 4758 To avoid discontinuities in the moon_offset function causing problems, 4759 we need to approximate when that function will next return 0. 4760 We probe it with the start time and take advantage of the fact 4761 that every lunar phase repeats roughly every 29.5 days. 4762 There is a surprising uncertainty in the quarter timing, 4763 due to the eccentricity of the moon's orbit. 4764 I have seen up to 0.826 days away from the simple prediction. 4765 To be safe, we take the predicted time of the event and search 4766 +/-0.9 days around it (a 1.8-day wide window). 4767 Return ASTRO_NO_MOON_QUARTER if the final result goes beyond limitDays after startTime. 4768 */ 4769 const double uncertainty = 0.9; 4770 astro_func_result_t funcres; 4771 double ya, est_dt, dt1, dt2; 4772 astro_time_t t1, t2; 4773 4774 funcres = moon_offset(&targetLon, startTime); 4775 if (funcres.status != ASTRO_SUCCESS) 4776 return SearchError(funcres.status); 4777 4778 ya = funcres.value; 4779 if (ya > 0.0) ya -= 360.0; /* force searching forward in time, not backward */ 4780 est_dt = -(MEAN_SYNODIC_MONTH * ya) / 360.0; 4781 dt1 = est_dt - uncertainty; 4782 if (dt1 > limitDays) 4783 return SearchError(ASTRO_NO_MOON_QUARTER); /* not possible for moon phase to occur within specified window (too short) */ 4784 dt2 = est_dt + uncertainty; 4785 if (limitDays < dt2) 4786 dt2 = limitDays; 4787 t1 = Astronomy_AddDays(startTime, dt1); 4788 t2 = Astronomy_AddDays(startTime, dt2); 4789 return Astronomy_Search(moon_offset, &targetLon, t1, t2, 1.0); 4790 } 4791 4792 /** 4793 * @brief 4794 * Finds the first lunar quarter after the specified date and time. 4795 * 4796 * A lunar quarter is one of the following four lunar phase events: 4797 * new moon, first quarter, full moon, third quarter. 4798 * This function finds the lunar quarter that happens soonest 4799 * after the specified date and time. 4800 * 4801 * To continue iterating through consecutive lunar quarters, call this function once, 4802 * followed by calls to #Astronomy_NextMoonQuarter as many times as desired. 4803 * 4804 * @param startTime 4805 * The date and time at which to start the search. 4806 * 4807 * @return 4808 * This function should always succeed, indicated by the `status` field 4809 * in the returned structure holding `ASTRO_SUCCESS`. Any other value indicates 4810 * an internal error, which should be [reported as an issue](https://github.com/cosinekitty/astronomy/issues). 4811 * To be safe, calling code should always check the `status` field for errors. 4812 */ 4813 astro_moon_quarter_t Astronomy_SearchMoonQuarter(astro_time_t startTime) 4814 { 4815 astro_moon_quarter_t mq; 4816 astro_angle_result_t angres; 4817 astro_search_result_t srchres; 4818 4819 /* Determine what the next quarter phase will be. */ 4820 angres = Astronomy_MoonPhase(startTime); 4821 if (angres.status != ASTRO_SUCCESS) 4822 return MoonQuarterError(angres.status); 4823 4824 mq.quarter = (1 + (int)floor(angres.angle / 90.0)) % 4; 4825 srchres = Astronomy_SearchMoonPhase(90.0 * mq.quarter, startTime, 10.0); 4826 if (srchres.status != ASTRO_SUCCESS) 4827 return MoonQuarterError(srchres.status); 4828 4829 mq.status = ASTRO_SUCCESS; 4830 mq.time = srchres.time; 4831 return mq; 4832 } 4833 4834 /** 4835 * @brief 4836 * Continues searching for lunar quarters from a previous search. 4837 * 4838 * After calling #Astronomy_SearchMoonQuarter, this function can be called 4839 * one or more times to continue finding consecutive lunar quarters. 4840 * This function finds the next consecutive moon quarter event after the one passed in as the parameter `mq`. 4841 * 4842 * @param mq 4843 * A value returned by a prior call to #Astronomy_SearchMoonQuarter or #Astronomy_NextMoonQuarter. 4844 * 4845 * @return 4846 * If `mq` is valid, this function should always succeed, indicated by the `status` field 4847 * in the returned structure holding `ASTRO_SUCCESS`. Any other value indicates 4848 * an internal error, which (after confirming that `mq` is valid) should be 4849 * [reported as an issue](https://github.com/cosinekitty/astronomy/issues). 4850 * To be safe, calling code should always check the `status` field for errors. 4851 */ 4852 astro_moon_quarter_t Astronomy_NextMoonQuarter(astro_moon_quarter_t mq) 4853 { 4854 astro_time_t time; 4855 astro_moon_quarter_t next_mq; 4856 4857 if (mq.status != ASTRO_SUCCESS) 4858 return MoonQuarterError(ASTRO_INVALID_PARAMETER); 4859 4860 /* Skip 6 days past the previous found moon quarter to find the next one. */ 4861 /* This is less than the minimum possible increment. */ 4862 /* So far I have seen the interval well contained by the range (6.5, 8.3) days. */ 4863 4864 time = Astronomy_AddDays(mq.time, 6.0); 4865 next_mq = Astronomy_SearchMoonQuarter(time); 4866 if (next_mq.status == ASTRO_SUCCESS) 4867 { 4868 /* Verify that we found the expected moon quarter. */ 4869 if (next_mq.quarter != (1 + mq.quarter) % 4) 4870 return MoonQuarterError(ASTRO_WRONG_MOON_QUARTER); /* internal error! we found the wrong moon quarter */ 4871 } 4872 return next_mq; 4873 } 4874 4875 static astro_func_result_t rlon_offset(astro_body_t body, astro_time_t time, int direction, double targetRelLon) 4876 { 4877 astro_func_result_t result; 4878 astro_angle_result_t plon, elon; 4879 double diff; 4880 4881 plon = Astronomy_EclipticLongitude(body, time); 4882 if (plon.status != ASTRO_SUCCESS) 4883 return FuncError(plon.status); 4884 4885 elon = Astronomy_EclipticLongitude(BODY_EARTH, time); 4886 if (elon.status != ASTRO_SUCCESS) 4887 return FuncError(elon.status); 4888 4889 diff = direction * (elon.angle - plon.angle); 4890 result.value = LongitudeOffset(diff - targetRelLon); 4891 result.status = ASTRO_SUCCESS; 4892 return result; 4893 } 4894 4895 /** 4896 * @brief 4897 * Searches for the time when the Earth and another planet are separated by a specified angle 4898 * in ecliptic longitude, as seen from the Sun. 4899 * 4900 * A relative longitude is the angle between two bodies measured in the plane of the Earth's orbit 4901 * (the ecliptic plane). The distance of the bodies above or below the ecliptic plane is ignored. 4902 * If you imagine the shadow of the body cast onto the ecliptic plane, and the angle measured around 4903 * that plane from one body to the other in the direction the planets orbit the Sun, you will get an 4904 * angle somewhere between 0 and 360 degrees. This is the relative longitude. 4905 * 4906 * Given a planet other than the Earth in `body` and a time to start the search in `startTime`, 4907 * this function searches for the next time that the relative longitude measured from the planet 4908 * to the Earth is `targetRelLon`. 4909 * 4910 * Certain astronomical events are defined in terms of relative longitude between the Earth and another planet: 4911 * 4912 * - When the relative longitude is 0 degrees, it means both planets are in the same direction from the Sun. 4913 * For planets that orbit closer to the Sun (Mercury and Venus), this is known as *inferior conjunction*, 4914 * a time when the other planet becomes very difficult to see because of being lost in the Sun's glare. 4915 * (The only exception is in the rare event of a transit, when we see the silhouette of the planet passing 4916 * between the Earth and the Sun.) 4917 * 4918 * - When the relative longitude is 0 degrees and the other planet orbits farther from the Sun, 4919 * this is known as *opposition*. Opposition is when the planet is closest to the Earth, and 4920 * also when it is visible for most of the night, so it is considered the best time to observe the planet. 4921 * 4922 * - When the relative longitude is 180 degrees, it means the other planet is on the opposite side of the Sun 4923 * from the Earth. This is called *superior conjunction*. Like inferior conjunction, the planet is 4924 * very difficult to see from the Earth. Superior conjunction is possible for any planet other than the Earth. 4925 * 4926 * @param body 4927 * A planet other than the Earth. If `body` is not a planet other than the Earth, an error occurs. 4928 * 4929 * @param targetRelLon 4930 * The desired relative longitude, expressed in degrees. Must be in the range [0, 360). 4931 * 4932 * @param startTime 4933 * The date and time at which to begin the search. 4934 * 4935 * @return 4936 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 4937 * and `time` will hold the date and time of the relative longitude event. 4938 * Otherwise `status` will hold some other value that indicates an error condition. 4939 */ 4940 astro_search_result_t Astronomy_SearchRelativeLongitude(astro_body_t body, double targetRelLon, astro_time_t startTime) 4941 { 4942 astro_search_result_t result; 4943 astro_func_result_t syn; 4944 astro_func_result_t error_angle; 4945 double prev_angle; 4946 astro_time_t time; 4947 int iter, direction; 4948 4949 if (body == BODY_EARTH) 4950 return SearchError(ASTRO_EARTH_NOT_ALLOWED); 4951 4952 if (body == BODY_MOON || body == BODY_SUN) 4953 return SearchError(ASTRO_INVALID_BODY); 4954 4955 syn = SynodicPeriod(body); 4956 if (syn.status != ASTRO_SUCCESS) 4957 return SearchError(syn.status); 4958 4959 direction = IsSuperiorPlanet(body) ? +1 : -1; 4960 4961 /* Iterate until we converge on the desired event. */ 4962 /* Calculate the error angle, which will be a negative number of degrees, */ 4963 /* meaning we are "behind" the target relative longitude. */ 4964 4965 error_angle = rlon_offset(body, startTime, direction, targetRelLon); 4966 if (error_angle.status != ASTRO_SUCCESS) 4967 return SearchError(error_angle.status); 4968 4969 if (error_angle.value > 0) 4970 error_angle.value -= 360; /* force searching forward in time */ 4971 4972 time = startTime; 4973 for (iter = 0; iter < 100; ++iter) 4974 { 4975 /* Estimate how many days in the future (positive) or past (negative) */ 4976 /* we have to go to get closer to the target relative longitude. */ 4977 double day_adjust = (-error_angle.value/360.0) * syn.value; 4978 time = Astronomy_AddDays(time, day_adjust); 4979 if (fabs(day_adjust) * SECONDS_PER_DAY < 1.0) 4980 { 4981 result.time = time; 4982 result.status = ASTRO_SUCCESS; 4983 return result; 4984 } 4985 4986 prev_angle = error_angle.value; 4987 error_angle = rlon_offset(body, time, direction, targetRelLon); 4988 if (error_angle.status != ASTRO_SUCCESS) 4989 return SearchError(error_angle.status); 4990 4991 if (fabs(prev_angle) < 30.0 && (prev_angle != error_angle.value)) 4992 { 4993 /* Improve convergence for Mercury/Mars (eccentric orbits) */ 4994 /* by adjusting the synodic period to more closely match the */ 4995 /* variable speed of both planets in this part of their respective orbits. */ 4996 double ratio = prev_angle / (prev_angle - error_angle.value); 4997 if (ratio > 0.5 && ratio < 2.0) 4998 syn.value *= ratio; 4999 } 5000 } 5001 5002 return SearchError(ASTRO_NO_CONVERGE); 5003 } 5004 5005 /** 5006 * @brief 5007 * Searches for the time when a celestial body reaches a specified hour angle as seen by an observer on the Earth. 5008 * 5009 * The *hour angle* of a celestial body indicates its position in the sky with respect 5010 * to the Earth's rotation. The hour angle depends on the location of the observer on the Earth. 5011 * The hour angle is 0 when the body reaches its highest angle above the horizon in a given day. 5012 * The hour angle increases by 1 unit for every sidereal hour that passes after that point, up 5013 * to 24 sidereal hours when it reaches the highest point again. So the hour angle indicates 5014 * the number of hours that have passed since the most recent time that the body has culminated, 5015 * or reached its highest point. 5016 * 5017 * This function searches for the next time a celestial body reaches the given hour angle 5018 * after the date and time specified by `startTime`. 5019 * To find when a body culminates, pass 0 for `hourAngle`. 5020 * To find when a body reaches its lowest point in the sky, pass 12 for `hourAngle`. 5021 * 5022 * Note that, especially close to the Earth's poles, a body as seen on a given day 5023 * may always be above the horizon or always below the horizon, so the caller cannot 5024 * assume that a culminating object is visible nor that an object is below the horizon 5025 * at its minimum altitude. 5026 * 5027 * On success, the function reports the date and time, along with the horizontal coordinates 5028 * of the body at that time, as seen by the given observer. 5029 * 5030 * @param body 5031 * The celestial body, which can the Sun, the Moon, or any planet other than the Earth. 5032 * 5033 * @param observer 5034 * Indicates a location on or near the surface of the Earth where the observer is located. 5035 * Call #Astronomy_MakeObserver to create an observer structure. 5036 * 5037 * @param hourAngle 5038 * An hour angle value in the range [0, 24) indicating the number of sidereal hours after the 5039 * body's most recent culmination. 5040 * 5041 * @param startTime 5042 * The date and time at which to start the search. 5043 * 5044 * @return 5045 * If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS` 5046 * and the other structure fields are valid. Otherwise, `status` holds some other value 5047 * that indicates an error condition. 5048 */ 5049 astro_hour_angle_t Astronomy_SearchHourAngle( 5050 astro_body_t body, 5051 astro_observer_t observer, 5052 double hourAngle, 5053 astro_time_t startTime) 5054 { 5055 int iter = 0; 5056 astro_time_t time; 5057 astro_equatorial_t ofdate; 5058 astro_hour_angle_t result; 5059 double delta_sidereal_hours, delta_days, gast; 5060 5061 if (body < MIN_BODY || body > MAX_BODY) 5062 return HourAngleError(ASTRO_INVALID_BODY); 5063 5064 if (body == BODY_EARTH) 5065 return HourAngleError(ASTRO_EARTH_NOT_ALLOWED); 5066 5067 if (hourAngle < 0.0 || hourAngle >= 24.0) 5068 return HourAngleError(ASTRO_INVALID_PARAMETER); 5069 5070 time = startTime; 5071 for(;;) 5072 { 5073 ++iter; 5074 5075 /* Calculate Greenwich Apparent Sidereal Time (GAST) at the given time. */ 5076 gast = sidereal_time(&time); 5077 5078 /* Obtain equatorial coordinates of date for the body. */ 5079 ofdate = Astronomy_Equator(body, &time, observer, EQUATOR_OF_DATE, ABERRATION); 5080 if (ofdate.status != ASTRO_SUCCESS) 5081 return HourAngleError(ofdate.status); 5082 5083 /* Calculate the adjustment needed in sidereal time */ 5084 /* to bring the hour angle to the desired value. */ 5085 5086 delta_sidereal_hours = fmod((hourAngle + ofdate.ra - observer.longitude/15) - gast, 24.0); 5087 if (iter == 1) 5088 { 5089 /* On the first iteration, always search forward in time. */ 5090 if (delta_sidereal_hours < 0) 5091 delta_sidereal_hours += 24; 5092 } 5093 else 5094 { 5095 /* On subsequent iterations, we make the smallest possible adjustment, */ 5096 /* either forward or backward in time. */ 5097 if (delta_sidereal_hours < -12.0) 5098 delta_sidereal_hours += 24.0; 5099 else if (delta_sidereal_hours > +12.0) 5100 delta_sidereal_hours -= 24.0; 5101 } 5102 5103 /* If the error is tolerable (less than 0.1 seconds), the search has succeeded. */ 5104 if (fabs(delta_sidereal_hours) * 3600.0 < 0.1) 5105 { 5106 result.hor = Astronomy_Horizon(&time, observer, ofdate.ra, ofdate.dec, REFRACTION_NORMAL); 5107 result.time = time; 5108 result.status = ASTRO_SUCCESS; 5109 return result; 5110 } 5111 5112 /* We need to loop another time to get more accuracy. */ 5113 /* Update the terrestrial time (in solar days) adjusting by sidereal time (sidereal hours). */ 5114 delta_days = (delta_sidereal_hours / 24.0) * SOLAR_DAYS_PER_SIDEREAL_DAY; 5115 time = Astronomy_AddDays(time, delta_days); 5116 } 5117 } 5118 5119 /** @cond DOXYGEN_SKIP */ 5120 typedef struct 5121 { 5122 astro_body_t body; 5123 int direction; 5124 astro_observer_t observer; 5125 double body_radius_au; 5126 } 5127 context_peak_altitude_t; 5128 /** @endcond */ 5129 5130 static astro_func_result_t peak_altitude(void *context, astro_time_t time) 5131 { 5132 astro_func_result_t result; 5133 astro_equatorial_t ofdate; 5134 astro_horizon_t hor; 5135 const context_peak_altitude_t *p = context; 5136 5137 /* 5138 Return the angular altitude above or below the horizon 5139 of the highest part (the peak) of the given object. 5140 This is defined as the apparent altitude of the center of the body plus 5141 the body's angular radius. 5142 The 'direction' parameter controls whether the angle is measured 5143 positive above the horizon or positive below the horizon, 5144 depending on whether the caller wants rise times or set times, respectively. 5145 */ 5146 5147 ofdate = Astronomy_Equator(p->body, &time, p->observer, EQUATOR_OF_DATE, ABERRATION); 5148 if (ofdate.status != ASTRO_SUCCESS) 5149 return FuncError(ofdate.status); 5150 5151 /* We calculate altitude without refraction, then add fixed refraction near the horizon. */ 5152 /* This gives us the time of rise/set without the extra work. */ 5153 hor = Astronomy_Horizon(&time, p->observer, ofdate.ra, ofdate.dec, REFRACTION_NONE); 5154 result.value = p->direction * (hor.altitude + RAD2DEG*(p->body_radius_au / ofdate.dist) + REFRACTION_NEAR_HORIZON); 5155 result.status = ASTRO_SUCCESS; 5156 return result; 5157 } 5158 5159 /** 5160 * @brief 5161 * Searches for the next time a celestial body rises or sets as seen by an observer on the Earth. 5162 * 5163 * This function finds the next rise or set time of the Sun, Moon, or planet other than the Earth. 5164 * Rise time is when the body first starts to be visible above the horizon. 5165 * For example, sunrise is the moment that the top of the Sun first appears to peek above the horizon. 5166 * Set time is the moment when the body appears to vanish below the horizon. 5167 * 5168 * This function corrects for typical atmospheric refraction, which causes celestial 5169 * bodies to appear higher above the horizon than they would if the Earth had no atmosphere. 5170 * It also adjusts for the apparent angular radius of the observed body (significant only for the Sun and Moon). 5171 * 5172 * Note that rise or set may not occur in every 24 hour period. 5173 * For example, near the Earth's poles, there are long periods of time where 5174 * the Sun stays below the horizon, never rising. 5175 * Also, it is possible for the Moon to rise just before midnight but not set during the subsequent 24-hour day. 5176 * This is because the Moon sets nearly an hour later each day due to orbiting the Earth a 5177 * significant amount during each rotation of the Earth. 5178 * Therefore callers must not assume that the function will always succeed. 5179 * 5180 * @param body 5181 * The Sun, Moon, or any planet other than the Earth. 5182 * 5183 * @param observer 5184 * The location where observation takes place. 5185 * You can create an observer structure by calling #Astronomy_MakeObserver. 5186 * 5187 * @param direction 5188 * Either `DIRECTION_RISE` to find a rise time or `DIRECTION_SET` to find a set time. 5189 * 5190 * @param startTime 5191 * The date and time at which to start the search. 5192 * 5193 * @param limitDays 5194 * Limits how many days to search for a rise or set time. 5195 * To limit a rise or set time to the same day, you can use a value of 1 day. 5196 * In cases where you want to find the next rise or set time no matter how far 5197 * in the future (for example, for an observer near the south pole), you can 5198 * pass in a larger value like 365. 5199 * 5200 * @return 5201 * On success, the `status` field in the returned structure contains `ASTRO_SUCCESS` 5202 * and the `time` field contains the date and time of the rise or set time as requested. 5203 * If the `status` field contains `ASTRO_SEARCH_FAILURE`, it means the rise or set 5204 * event does not occur within `limitDays` days of `startTime`. This is a normal condition, 5205 * not an error. Any other value of `status` indicates an error of some kind. 5206 */ 5207 astro_search_result_t Astronomy_SearchRiseSet( 5208 astro_body_t body, 5209 astro_observer_t observer, 5210 astro_direction_t direction, 5211 astro_time_t startTime, 5212 double limitDays) 5213 { 5214 context_peak_altitude_t context; 5215 double ha_before, ha_after; 5216 astro_time_t time_start, time_before; 5217 astro_func_result_t alt_before, alt_after; 5218 astro_hour_angle_t evt_before, evt_after; 5219 5220 if (body == BODY_EARTH) 5221 return SearchError(ASTRO_EARTH_NOT_ALLOWED); 5222 5223 switch (direction) 5224 { 5225 case DIRECTION_RISE: 5226 ha_before = 12.0; /* minimum altitude (bottom) happens BEFORE the body rises. */ 5227 ha_after = 0.0; /* maximum altitude (culmination) happens AFTER the body rises. */ 5228 break; 5229 5230 case DIRECTION_SET: 5231 ha_before = 0.0; /* culmination happens BEFORE the body sets. */ 5232 ha_after = 12.0; /* bottom happens AFTER the body sets. */ 5233 break; 5234 5235 default: 5236 return SearchError(ASTRO_INVALID_PARAMETER); 5237 } 5238 5239 /* Set up the context structure for the search function 'peak_altitude'. */ 5240 context.body = body; 5241 context.direction = (int)direction; 5242 context.observer = observer; 5243 switch (body) 5244 { 5245 case BODY_SUN: context.body_radius_au = SUN_RADIUS_AU; break; 5246 case BODY_MOON: context.body_radius_au = MOON_EQUATORIAL_RADIUS_AU; break; 5247 default: context.body_radius_au = 0.0; break; 5248 } 5249 5250 /* 5251 See if the body is currently above/below the horizon. 5252 If we are looking for next rise time and the body is below the horizon, 5253 we use the current time as the lower time bound and the next culmination 5254 as the upper bound. 5255 If the body is above the horizon, we search for the next bottom and use it 5256 as the lower bound and the next culmination after that bottom as the upper bound. 5257 The same logic applies for finding set times, only we swap the hour angles. 5258 */ 5259 5260 time_start = startTime; 5261 alt_before = peak_altitude(&context, time_start); 5262 if (alt_before.status != ASTRO_SUCCESS) 5263 return SearchError(alt_before.status); 5264 5265 if (alt_before.value > 0.0) 5266 { 5267 /* We are past the sought event, so we have to wait for the next "before" event (culm/bottom). */ 5268 evt_before = Astronomy_SearchHourAngle(body, observer, ha_before, time_start); 5269 if (evt_before.status != ASTRO_SUCCESS) 5270 return SearchError(evt_before.status); 5271 5272 time_before = evt_before.time; 5273 5274 alt_before = peak_altitude(&context, time_before); 5275 if (alt_before.status != ASTRO_SUCCESS) 5276 return SearchError(alt_before.status); 5277 } 5278 else 5279 { 5280 /* We are before or at the sought event, so we find the next "after" event (bottom/culm), */ 5281 /* and use the current time as the "before" event. */ 5282 time_before = time_start; 5283 } 5284 5285 evt_after = Astronomy_SearchHourAngle(body, observer, ha_after, time_before); 5286 if (evt_after.status != ASTRO_SUCCESS) 5287 return SearchError(evt_after.status); 5288 5289 alt_after = peak_altitude(&context, evt_after.time); 5290 if (alt_after.status != ASTRO_SUCCESS) 5291 return SearchError(alt_after.status); 5292 5293 for(;;) 5294 { 5295 if (alt_before.value <= 0.0 && alt_after.value > 0.0) 5296 { 5297 /* Search between evt_before and evt_after for the desired event. */ 5298 astro_search_result_t result = Astronomy_Search(peak_altitude, &context, time_before, evt_after.time, 1.0); 5299 5300 /* ASTRO_SEARCH_FAILURE is a special error that indicates a normal lack of finding a solution. */ 5301 /* If successful, or any other error, return immediately. */ 5302 if (result.status != ASTRO_SEARCH_FAILURE) 5303 return result; 5304 } 5305 5306 /* If we didn't find the desired event, use evt_after.time to find the next before-event. */ 5307 evt_before = Astronomy_SearchHourAngle(body, observer, ha_before, evt_after.time); 5308 if (evt_before.status != ASTRO_SUCCESS) 5309 return SearchError(evt_before.status); 5310 5311 evt_after = Astronomy_SearchHourAngle(body, observer, ha_after, evt_before.time); 5312 if (evt_after.status != ASTRO_SUCCESS) 5313 return SearchError(evt_after.status); 5314 5315 if (evt_before.time.ut >= time_start.ut + limitDays) 5316 return SearchError(ASTRO_SEARCH_FAILURE); 5317 5318 time_before = evt_before.time; 5319 5320 alt_before = peak_altitude(&context, evt_before.time); 5321 if (alt_before.status != ASTRO_SUCCESS) 5322 return SearchError(alt_before.status); 5323 5324 alt_after = peak_altitude(&context, evt_after.time); 5325 if (alt_after.status != ASTRO_SUCCESS) 5326 return SearchError(alt_after.status); 5327 } 5328 } 5329 5330 static double MoonMagnitude(double phase, double helio_dist, double geo_dist) 5331 { 5332 /* https://astronomy.stackexchange.com/questions/10246/is-there-a-simple-analytical-formula-for-the-lunar-phase-brightness-curve */ 5333 double rad = phase * DEG2RAD; 5334 double rad2 = rad * rad; 5335 double rad4 = rad2 * rad2; 5336 double mag = -12.717 + 1.49*fabs(rad) + 0.0431*rad4; 5337 double moon_mean_distance_au = 385000.6 / KM_PER_AU; 5338 double geo_au = geo_dist / moon_mean_distance_au; 5339 mag += 5*log10(helio_dist * geo_au); 5340 return mag; 5341 } 5342 5343 static astro_status_t SaturnMagnitude( 5344 double phase, 5345 double helio_dist, 5346 double geo_dist, 5347 astro_vector_t gc, 5348 astro_time_t time, 5349 double *mag, 5350 double *ring_tilt) 5351 { 5352 astro_ecliptic_t eclip; 5353 double ir, Nr, lat, lon, tilt, sin_tilt; 5354 5355 *mag = *ring_tilt = NAN; 5356 5357 /* Based on formulas by Paul Schlyter found here: */ 5358 /* http://www.stjarnhimlen.se/comp/ppcomp.html#15 */ 5359 5360 /* We must handle Saturn's rings as a major component of its visual magnitude. */ 5361 /* Find geocentric ecliptic coordinates of Saturn. */ 5362 eclip = Astronomy_Ecliptic(gc); 5363 if (eclip.status != ASTRO_SUCCESS) 5364 return eclip.status; 5365 5366 ir = DEG2RAD * 28.06; /* tilt of Saturn's rings to the ecliptic, in radians */ 5367 Nr = DEG2RAD * (169.51 + (3.82e-5 * time.tt)); /* ascending node of Saturn's rings, in radians */ 5368 5369 /* Find tilt of Saturn's rings, as seen from Earth. */ 5370 lat = DEG2RAD * eclip.elat; 5371 lon = DEG2RAD * eclip.elon; 5372 tilt = asin(sin(lat)*cos(ir) - cos(lat)*sin(ir)*sin(lon-Nr)); 5373 sin_tilt = sin(fabs(tilt)); 5374 5375 *mag = -9.0 + 0.044*phase; 5376 *mag += sin_tilt*(-2.6 + 1.2*sin_tilt); 5377 *mag += 5.0 * log10(helio_dist * geo_dist); 5378 5379 *ring_tilt = RAD2DEG * tilt; 5380 5381 return ASTRO_SUCCESS; 5382 } 5383 5384 static astro_status_t VisualMagnitude( 5385 astro_body_t body, 5386 double phase, 5387 double helio_dist, 5388 double geo_dist, 5389 double *mag) 5390 { 5391 /* For Mercury and Venus, see: https://iopscience.iop.org/article/10.1086/430212 */ 5392 double c0, c1=0, c2=0, c3=0, x; 5393 *mag = NAN; 5394 switch (body) 5395 { 5396 case BODY_MERCURY: c0 = -0.60, c1 = +4.98, c2 = -4.88, c3 = +3.02; break; 5397 case BODY_VENUS: 5398 if (phase < 163.6) 5399 c0 = -4.47, c1 = +1.03, c2 = +0.57, c3 = +0.13; 5400 else 5401 c0 = 0.98, c1 = -1.02; 5402 break; 5403 case BODY_MARS: c0 = -1.52, c1 = +1.60; break; 5404 case BODY_JUPITER: c0 = -9.40, c1 = +0.50; break; 5405 case BODY_URANUS: c0 = -7.19, c1 = +0.25; break; 5406 case BODY_NEPTUNE: c0 = -6.87; break; 5407 case BODY_PLUTO: c0 = -1.00, c1 = +4.00; break; 5408 default: return ASTRO_INVALID_BODY; 5409 } 5410 5411 x = phase / 100; 5412 *mag = c0 + x*(c1 + x*(c2 + x*c3)); 5413 *mag += 5.0 * log10(helio_dist * geo_dist); 5414 return ASTRO_SUCCESS; 5415 } 5416 5417 /** 5418 * @brief 5419 * Finds visual magnitude, phase angle, and other illumination information about a celestial body. 5420 * 5421 * This function calculates information about how bright a celestial body appears from the Earth, 5422 * reported as visual magnitude, which is a smaller (or even negative) number for brighter objects 5423 * and a larger number for dimmer objects. 5424 * 5425 * For bodies other than the Sun, it reports a phase angle, which is the angle in degrees between 5426 * the Sun and the Earth, as seen from the center of the body. Phase angle indicates what fraction 5427 * of the body appears illuminated as seen from the Earth. For example, when the phase angle is 5428 * near zero, it means the body appears "full" as seen from the Earth. A phase angle approaching 5429 * 180 degrees means the body appears as a thin crescent as seen from the Earth. A phase angle 5430 * of 90 degrees means the body appears "half full". 5431 * For the Sun, the phase angle is always reported as 0; the Sun emits light rather than reflecting it, 5432 * so it doesn't have a phase angle. 5433 * 5434 * When the body is Saturn, the returned structure contains a field `ring_tilt` that holds 5435 * the tilt angle in degrees of Saturn's rings as seen from the Earth. A value of 0 means 5436 * the rings appear edge-on, and are thus nearly invisible from the Earth. The `ring_tilt` holds 5437 * 0 for all bodies other than Saturn. 5438 * 5439 * @param body 5440 * The Sun, Moon, or any planet other than the Earth. 5441 * 5442 * @param time 5443 * The date and time of the observation. 5444 * 5445 * @return 5446 * On success, the `status` field of the return structure holds `ASTRO_SUCCESS` 5447 * and the other structure fields are valid. 5448 * Any other value indicates an error, in which case the remaining structure fields are not valid. 5449 */ 5450 astro_illum_t Astronomy_Illumination(astro_body_t body, astro_time_t time) 5451 { 5452 astro_vector_t earth; /* vector from Sun to Earth */ 5453 astro_vector_t hc; /* vector from Sun to body */ 5454 astro_vector_t gc; /* vector from Earth to body */ 5455 double mag; /* visual magnitude */ 5456 astro_angle_result_t phase; /* phase angle in degrees between Earth and Sun as seen from body */ 5457 double helio_dist; /* distance from Sun to body */ 5458 double geo_dist; /* distance from Earth to body */ 5459 double ring_tilt = 0.0; /* Saturn's ring tilt (0 for all other bodies) */ 5460 astro_illum_t illum; 5461 astro_status_t status; 5462 5463 if (body == BODY_EARTH) 5464 return IllumError(ASTRO_EARTH_NOT_ALLOWED); 5465 5466 earth = CalcEarth(time); 5467 if (earth.status != ASTRO_SUCCESS) 5468 return IllumError(earth.status); 5469 5470 if (body == BODY_SUN) 5471 { 5472 gc.status = ASTRO_SUCCESS; 5473 gc.t = time; 5474 gc.x = -earth.x; 5475 gc.y = -earth.y; 5476 gc.z = -earth.z; 5477 5478 hc.status = ASTRO_SUCCESS; 5479 hc.t = time; 5480 hc.x = 0.0; 5481 hc.y = 0.0; 5482 hc.z = 0.0; 5483 5484 /* The Sun emits light instead of reflecting it, */ 5485 /* so we report a placeholder phase angle of 0. */ 5486 phase.status = ASTRO_SUCCESS; 5487 phase.angle = 0.0; 5488 } 5489 else 5490 { 5491 if (body == BODY_MOON) 5492 { 5493 /* For extra numeric precision, use geocentric Moon formula directly. */ 5494 gc = Astronomy_GeoMoon(time); 5495 if (gc.status != ASTRO_SUCCESS) 5496 return IllumError(gc.status); 5497 5498 hc.status = ASTRO_SUCCESS; 5499 hc.t = time; 5500 hc.x = earth.x + gc.x; 5501 hc.y = earth.y + gc.y; 5502 hc.z = earth.z + gc.z; 5503 } 5504 else 5505 { 5506 /* For planets, the heliocentric vector is more direct to calculate. */ 5507 hc = Astronomy_HelioVector(body, time); 5508 if (hc.status != ASTRO_SUCCESS) 5509 return IllumError(hc.status); 5510 5511 gc.status = ASTRO_SUCCESS; 5512 gc.t = time; 5513 gc.x = hc.x - earth.x; 5514 gc.y = hc.y - earth.y; 5515 gc.z = hc.z - earth.z; 5516 } 5517 5518 phase = AngleBetween(gc, hc); 5519 if (phase.status != ASTRO_SUCCESS) 5520 return IllumError(phase.status); 5521 } 5522 5523 geo_dist = Astronomy_VectorLength(gc); 5524 helio_dist = Astronomy_VectorLength(hc); 5525 5526 switch (body) 5527 { 5528 case BODY_SUN: 5529 mag = -0.17 + 5.0*log10(geo_dist / AU_PER_PARSEC); 5530 break; 5531 5532 case BODY_MOON: 5533 mag = MoonMagnitude(phase.angle, helio_dist, geo_dist); 5534 break; 5535 5536 case BODY_SATURN: 5537 status = SaturnMagnitude(phase.angle, helio_dist, geo_dist, gc, time, &mag, &ring_tilt); 5538 if (status != ASTRO_SUCCESS) 5539 return IllumError(status); 5540 break; 5541 5542 default: 5543 status = VisualMagnitude(body, phase.angle, helio_dist, geo_dist, &mag); 5544 break; 5545 } 5546 5547 illum.status = ASTRO_SUCCESS; 5548 illum.time = time; 5549 illum.mag = mag; 5550 illum.phase_angle = phase.angle; 5551 illum.helio_dist = helio_dist; 5552 illum.ring_tilt = ring_tilt; 5553 5554 return illum; 5555 } 5556 5557 static astro_func_result_t mag_slope(void *context, astro_time_t time) 5558 { 5559 /* 5560 The Search() function finds a transition from negative to positive values. 5561 The derivative of magnitude y with respect to time t (dy/dt) 5562 is negative as an object gets brighter, because the magnitude numbers 5563 get smaller. At peak magnitude dy/dt = 0, then as the object gets dimmer, 5564 dy/dt > 0. 5565 */ 5566 static const double dt = 0.01; 5567 astro_illum_t y1, y2; 5568 astro_body_t body = *((astro_body_t *)context); 5569 astro_time_t t1 = Astronomy_AddDays(time, -dt/2); 5570 astro_time_t t2 = Astronomy_AddDays(time, +dt/2); 5571 astro_func_result_t result; 5572 5573 y1 = Astronomy_Illumination(body, t1); 5574 if (y1.status != ASTRO_SUCCESS) 5575 return FuncError(y1.status); 5576 5577 y2 = Astronomy_Illumination(body, t2); 5578 if (y2.status != ASTRO_SUCCESS) 5579 return FuncError(y2.status); 5580 5581 result.value = (y2.mag - y1.mag) / dt; 5582 result.status = ASTRO_SUCCESS; 5583 return result; 5584 } 5585 5586 /** 5587 * @brief 5588 * Searches for the date and time Venus will next appear brightest as seen from the Earth. 5589 * 5590 * This function searches for the date and time Venus appears brightest as seen from the Earth. 5591 * Currently only Venus is supported for the `body` parameter, though this could change in the future. 5592 * Mercury's peak magnitude occurs at superior conjunction, when it is virtually impossible to see from the Earth, 5593 * so peak magnitude events have little practical value for that planet. 5594 * Planets other than Venus and Mercury reach peak magnitude at opposition, which can 5595 * be found using #Astronomy_SearchRelativeLongitude. 5596 * The Moon reaches peak magnitude at full moon, which can be found using 5597 * #Astronomy_SearchMoonQuarter or #Astronomy_SearchMoonPhase. 5598 * The Sun reaches peak magnitude at perihelion, which occurs each year in January. 5599 * However, the difference is minor and has little practical value. 5600 * 5601 * @param body 5602 * Currently only `BODY_VENUS` is allowed. Any other value results in the error `ASTRO_INVALID_BODY`. 5603 * See function remarks for more details. 5604 * 5605 * @param startTime 5606 * The date and time to start searching for the next peak magnitude event. 5607 * 5608 * @return 5609 * See documentation about the return value from #Astronomy_Illumination. 5610 */ 5611 astro_illum_t Astronomy_SearchPeakMagnitude(astro_body_t body, astro_time_t startTime) 5612 { 5613 /* s1 and s2 are relative longitudes within which peak magnitude of Venus can occur. */ 5614 static const double s1 = 10.0; 5615 static const double s2 = 30.0; 5616 int iter; 5617 astro_angle_result_t plon, elon; 5618 astro_search_result_t t1, t2, tx; 5619 astro_func_result_t syn, m1, m2; 5620 astro_time_t t_start; 5621 double rlon, rlon_lo, rlon_hi, adjust_days; 5622 5623 if (body != BODY_VENUS) 5624 return IllumError(ASTRO_INVALID_BODY); 5625 5626 iter = 0; 5627 while (++iter <= 2) 5628 { 5629 /* Find current heliocentric relative longitude between the */ 5630 /* inferior planet and the Earth. */ 5631 plon = Astronomy_EclipticLongitude(body, startTime); 5632 if (plon.status != ASTRO_SUCCESS) 5633 return IllumError(plon.status); 5634 5635 elon = Astronomy_EclipticLongitude(BODY_EARTH, startTime); 5636 if (elon.status != ASTRO_SUCCESS) 5637 return IllumError(elon.status); 5638 5639 rlon = LongitudeOffset(plon.angle - elon.angle); /* clamp to (-180, +180]. */ 5640 5641 /* The slope function is not well-behaved when rlon is near 0 degrees or 180 degrees */ 5642 /* because there is a cusp there that causes a discontinuity in the derivative. */ 5643 /* So we need to guard against searching near such times. */ 5644 5645 if (rlon >= -s1 && rlon < +s1) 5646 { 5647 /* Seek to the window [+s1, +s2]. */ 5648 adjust_days = 0.0; 5649 /* Search forward for the time t1 when rel lon = +s1. */ 5650 rlon_lo = +s1; 5651 /* Search forward for the time t2 when rel lon = +s2. */ 5652 rlon_hi = +s2; 5653 } 5654 else if (rlon >= +s2 || rlon < -s2) 5655 { 5656 /* Seek to the next search window at [-s2, -s1]. */ 5657 adjust_days = 0.0; 5658 /* Search forward for the time t1 when rel lon = -s2. */ 5659 rlon_lo = -s2; 5660 /* Search forward for the time t2 when rel lon = -s1. */ 5661 rlon_hi = -s1; 5662 } 5663 else if (rlon >= 0) 5664 { 5665 /* rlon must be in the middle of the window [+s1, +s2]. */ 5666 /* Search BACKWARD for the time t1 when rel lon = +s1. */ 5667 syn = SynodicPeriod(body); 5668 if (syn.status != ASTRO_SUCCESS) 5669 return IllumError(syn.status); 5670 adjust_days = -syn.value / 4; 5671 rlon_lo = +s1; 5672 /* Search forward from t1 to find t2 such that rel lon = +s2. */ 5673 rlon_hi = +s2; 5674 } 5675 else 5676 { 5677 /* rlon must be in the middle of the window [-s2, -s1]. */ 5678 /* Search BACKWARD for the time t1 when rel lon = -s2. */ 5679 syn = SynodicPeriod(body); 5680 if (syn.status != ASTRO_SUCCESS) 5681 return IllumError(syn.status); 5682 adjust_days = -syn.value / 4; 5683 rlon_lo = -s2; 5684 /* Search forward from t1 to find t2 such that rel lon = -s1. */ 5685 rlon_hi = -s1; 5686 } 5687 t_start = Astronomy_AddDays(startTime, adjust_days); 5688 t1 = Astronomy_SearchRelativeLongitude(body, rlon_lo, t_start); 5689 if (t1.status != ASTRO_SUCCESS) 5690 return IllumError(t1.status); 5691 t2 = Astronomy_SearchRelativeLongitude(body, rlon_hi, t1.time); 5692 if (t2.status != ASTRO_SUCCESS) 5693 return IllumError(t2.status); 5694 5695 /* Now we have a time range [t1,t2] that brackets a maximum magnitude event. */ 5696 /* Confirm the bracketing. */ 5697 m1 = mag_slope(&body, t1.time); 5698 if (m1.status != ASTRO_SUCCESS) 5699 return IllumError(m1.status); 5700 if (m1.value >= 0.0) 5701 return IllumError(ASTRO_INTERNAL_ERROR); /* should never happen! */ 5702 5703 m2 = mag_slope(&body, t2.time); 5704 if (m2.status != ASTRO_SUCCESS) 5705 return IllumError(m2.status); 5706 if (m2.value <= 0.0) 5707 return IllumError(ASTRO_INTERNAL_ERROR); /* should never happen! */ 5708 5709 /* Use the generic search algorithm to home in on where the slope crosses from negative to positive. */ 5710 tx = Astronomy_Search(mag_slope, &body, t1.time, t2.time, 10.0); 5711 if (tx.status != ASTRO_SUCCESS) 5712 return IllumError(tx.status); 5713 5714 if (tx.time.tt >= startTime.tt) 5715 return Astronomy_Illumination(body, tx.time); 5716 5717 /* This event is in the past (earlier than startTime). */ 5718 /* We need to search forward from t2 to find the next possible window. */ 5719 /* We never need to search more than twice. */ 5720 startTime = Astronomy_AddDays(t2.time, 1.0); 5721 } 5722 5723 return IllumError(ASTRO_SEARCH_FAILURE); 5724 } 5725 5726 static double MoonDistance(astro_time_t t) 5727 { 5728 double lon, lat, dist; 5729 CalcMoon(t.tt / 36525.0, &lon, &lat, &dist); 5730 return dist; 5731 } 5732 5733 static astro_func_result_t moon_distance_slope(void *context, astro_time_t time) 5734 { 5735 static const double dt = 0.001; 5736 astro_time_t t1 = Astronomy_AddDays(time, -dt/2.0); 5737 astro_time_t t2 = Astronomy_AddDays(time, +dt/2.0); 5738 double dist1, dist2; 5739 int direction = *((int *)context); 5740 astro_func_result_t result; 5741 5742 dist1 = MoonDistance(t1); 5743 dist2 = MoonDistance(t2); 5744 result.value = direction * (dist2 - dist1) / dt; 5745 result.status = ASTRO_SUCCESS; 5746 return result; 5747 } 5748 5749 /** 5750 * @brief 5751 * Finds the date and time of the Moon's closest distance (perigee) 5752 * or farthest distance (apogee) with respect to the Earth. 5753 * 5754 * Given a date and time to start the search in `startTime`, this function finds the 5755 * next date and time that the center of the Moon reaches the closest or farthest point 5756 * in its orbit with respect to the center of the Earth, whichever comes first 5757 * after `startTime`. 5758 * 5759 * The closest point is called *perigee* and the farthest point is called *apogee*. 5760 * The word *apsis* refers to either event. 5761 * 5762 * To iterate through consecutive alternating perigee and apogee events, call `Astronomy_SearchLunarApsis` 5763 * once, then use the return value to call #Astronomy_NextLunarApsis. After that, 5764 * keep feeding the previous return value from `Astronomy_NextLunarApsis` into another 5765 * call of `Astronomy_NextLunarApsis` as many times as desired. 5766 * 5767 * @param startTime 5768 * The date and time at which to start searching for the next perigee or apogee. 5769 * 5770 * @return 5771 * If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`, 5772 * `time` holds the date and time of the next lunar apsis, `kind` holds either 5773 * `APSIS_PERICENTER` for perigee or `APSIS_APOCENTER` for apogee, and the distance 5774 * values `dist_au` (astronomical units) and `dist_km` (kilometers) are valid. 5775 * If the function fails, `status` holds some value other than `ASTRO_SUCCESS` that 5776 * indicates what went wrong, and the other structure fields are invalid. 5777 */ 5778 astro_apsis_t Astronomy_SearchLunarApsis(astro_time_t startTime) 5779 { 5780 astro_time_t t1, t2; 5781 astro_search_result_t search; 5782 astro_func_result_t m1, m2; 5783 int positive_direction = +1; 5784 int negative_direction = -1; 5785 const double increment = 5.0; /* number of days to skip in each iteration */ 5786 astro_apsis_t result; 5787 int iter; 5788 5789 /* 5790 Check the rate of change of the distance dr/dt at the start time. 5791 If it is positive, the Moon is currently getting farther away, 5792 so start looking for apogee. 5793 Conversely, if dr/dt < 0, start looking for perigee. 5794 Either way, the polarity of the slope will change, so the product will be negative. 5795 Handle the crazy corner case of exactly touching zero by checking for m1*m2 <= 0. 5796 */ 5797 5798 t1 = startTime; 5799 m1 = moon_distance_slope(&positive_direction, t1); 5800 if (m1.status != ASTRO_SUCCESS) 5801 return ApsisError(m1.status); 5802 5803 for (iter=0; iter * increment < 2.0 * MEAN_SYNODIC_MONTH; ++iter) 5804 { 5805 t2 = Astronomy_AddDays(t1, increment); 5806 m2 = moon_distance_slope(&positive_direction, t2); 5807 if (m2.status != ASTRO_SUCCESS) 5808 return ApsisError(m2.status); 5809 5810 if (m1.value * m2.value <= 0.0) 5811 { 5812 /* There is a change of slope polarity within the time range [t1, t2]. */ 5813 /* Therefore this time range contains an apsis. */ 5814 /* Figure out whether it is perigee or apogee. */ 5815 5816 if (m1.value < 0.0 || m2.value > 0.0) 5817 { 5818 /* We found a minimum-distance event: perigee. */ 5819 /* Search the time range for the time when the slope goes from negative to positive. */ 5820 search = Astronomy_Search(moon_distance_slope, &positive_direction, t1, t2, 1.0); 5821 result.kind = APSIS_PERICENTER; 5822 } 5823 else if (m1.value > 0.0 || m2.value < 0.0) 5824 { 5825 /* We found a maximum-distance event: apogee. */ 5826 /* Search the time range for the time when the slope goes from positive to negative. */ 5827 search = Astronomy_Search(moon_distance_slope, &negative_direction, t1, t2, 1.0); 5828 result.kind = APSIS_APOCENTER; 5829 } 5830 else 5831 { 5832 /* This should never happen. It should not be possible for both slopes to be zero. */ 5833 return ApsisError(ASTRO_INTERNAL_ERROR); 5834 } 5835 5836 if (search.status != ASTRO_SUCCESS) 5837 return ApsisError(search.status); 5838 5839 result.status = ASTRO_SUCCESS; 5840 result.time = search.time; 5841 result.dist_au = MoonDistance(search.time); 5842 result.dist_km = result.dist_au * KM_PER_AU; 5843 return result; 5844 } 5845 5846 /* We have not yet found a slope polarity change. Keep searching. */ 5847 t1 = t2; 5848 m1 = m2; 5849 } 5850 5851 /* It should not be possible to fail to find an apsis within 2 synodic months. */ 5852 return ApsisError(ASTRO_INTERNAL_ERROR); 5853 } 5854 5855 /** 5856 * @brief 5857 * Finds the next lunar perigee or apogee event in a series. 5858 * 5859 * This function requires an #astro_apsis_t value obtained from a call 5860 * to #Astronomy_SearchLunarApsis or `Astronomy_NextLunarApsis`. Given 5861 * an apogee event, this function finds the next perigee event, and vice versa. 5862 * 5863 * See #Astronomy_SearchLunarApsis for more details. 5864 * 5865 * @param apsis 5866 * An apsis event obtained from a call to #Astronomy_SearchLunarApsis or `Astronomy_NextLunarApsis`. 5867 * See #Astronomy_SearchLunarApsis for more details. 5868 * 5869 * @return 5870 * Same as the return value for #Astronomy_SearchLunarApsis. 5871 */ 5872 astro_apsis_t Astronomy_NextLunarApsis(astro_apsis_t apsis) 5873 { 5874 static const double skip = 11.0; /* number of days to skip to start looking for next apsis event */ 5875 astro_apsis_t next; 5876 astro_time_t time; 5877 5878 if (apsis.status != ASTRO_SUCCESS) 5879 return ApsisError(ASTRO_INVALID_PARAMETER); 5880 5881 if (apsis.kind != APSIS_APOCENTER && apsis.kind != APSIS_PERICENTER) 5882 return ApsisError(ASTRO_INVALID_PARAMETER); 5883 5884 time = Astronomy_AddDays(apsis.time, skip); 5885 next = Astronomy_SearchLunarApsis(time); 5886 if (next.status == ASTRO_SUCCESS) 5887 { 5888 /* Verify that we found the opposite apsis from the previous one. */ 5889 if (next.kind + apsis.kind != 1) 5890 return ApsisError(ASTRO_INTERNAL_ERROR); 5891 } 5892 return next; 5893 } 5894 5895 5896 /** @cond DOXYGEN_SKIP */ 5897 typedef struct 5898 { 5899 int direction; 5900 astro_body_t body; 5901 } 5902 planet_distance_context_t; 5903 /** @endcond */ 5904 5905 5906 static astro_func_result_t planet_distance_slope(void *context, astro_time_t time) 5907 { 5908 static const double dt = 0.001; 5909 const planet_distance_context_t *pc = context; 5910 astro_time_t t1 = Astronomy_AddDays(time, -dt/2.0); 5911 astro_time_t t2 = Astronomy_AddDays(time, +dt/2.0); 5912 astro_func_result_t dist1, dist2, result; 5913 5914 dist1 = Astronomy_HelioDistance(pc->body, t1); 5915 if (dist1.status != ASTRO_SUCCESS) 5916 return dist1; 5917 5918 dist2 = Astronomy_HelioDistance(pc->body, t2); 5919 if (dist2.status != ASTRO_SUCCESS) 5920 return dist2; 5921 5922 result.value = pc->direction * (dist2.value - dist1.value) / dt; 5923 result.status = ASTRO_SUCCESS; 5924 return result; 5925 } 5926 5927 static astro_apsis_t PlanetExtreme( 5928 astro_body_t body, 5929 astro_apsis_kind_t kind, 5930 astro_time_t start_time, 5931 double dayspan) 5932 { 5933 astro_apsis_t apsis; 5934 const double direction = (kind == APSIS_APOCENTER) ? +1.0 : -1.0; 5935 const int npoints = 10; 5936 int i, best_i; 5937 double interval; 5938 double dist, best_dist; 5939 astro_time_t time; 5940 astro_func_result_t result; 5941 5942 for(;;) 5943 { 5944 interval = dayspan / (npoints - 1); 5945 5946 if (interval < 1.0 / 1440.0) /* iterate until uncertainty is less than one minute */ 5947 { 5948 apsis.status = ASTRO_SUCCESS; 5949 apsis.kind = kind; 5950 apsis.time = Astronomy_AddDays(start_time, interval / 2.0); 5951 result = Astronomy_HelioDistance(body, apsis.time); 5952 if (result.status != ASTRO_SUCCESS) 5953 return ApsisError(result.status); 5954 apsis.dist_au = result.value; 5955 apsis.dist_km = apsis.dist_au * KM_PER_AU; 5956 return apsis; 5957 } 5958 5959 best_i = -1; 5960 best_dist = 0.0; 5961 for (i=0; i < npoints; ++i) 5962 { 5963 time = Astronomy_AddDays(start_time, i * interval); 5964 result = Astronomy_HelioDistance(body, time); 5965 if (result.status != ASTRO_SUCCESS) 5966 return ApsisError(result.status); 5967 dist = direction * result.value; 5968 if (i==0 || dist > best_dist) 5969 { 5970 best_i = i; 5971 best_dist = dist; 5972 } 5973 } 5974 5975 /* Narrow in on the extreme point. */ 5976 start_time = Astronomy_AddDays(start_time, (best_i - 1) * interval); 5977 dayspan = 2.0 * interval; 5978 } 5979 } 5980 5981 5982 static astro_apsis_t BruteSearchPlanetApsis(astro_body_t body, astro_time_t startTime) 5983 { 5984 const int npoints = 100; 5985 int i; 5986 astro_time_t t1, t2, time, t_min, t_max; 5987 double dist, max_dist, min_dist; 5988 astro_apsis_t perihelion, aphelion; 5989 double interval; 5990 double period; 5991 astro_func_result_t result; 5992 5993 /* 5994 Neptune is a special case for two reasons: 5995 1. Its orbit is nearly circular (low orbital eccentricity). 5996 2. It is so distant from the Sun that the orbital period is very long. 5997 Put together, this causes wobbling of the Sun around the Solar System Barycenter (SSB) 5998 to be so significant that there are 3 local minima in the distance-vs-time curve 5999 near each apsis. Therefore, unlike for other planets, we can't use an optimized 6000 algorithm for finding dr/dt = 0. 6001 Instead, we use a dumb, brute-force algorithm of sampling and finding min/max 6002 heliocentric distance. 6003 6004 There is a similar problem in the TOP2013 model for Pluto: 6005 Its position vector has high-frequency oscillations that confuse the 6006 slope-based determination of apsides. 6007 */ 6008 6009 /* 6010 Rewind approximately 30 degrees in the orbit, 6011 then search forward for 270 degrees. 6012 This is a very cautious way to prevent missing an apsis. 6013 Typically we will find two apsides, and we pick whichever 6014 apsis is ealier, but after startTime. 6015 Sample points around this orbital arc and find when the distance 6016 is greatest and smallest. 6017 */ 6018 period = PlanetOrbitalPeriod(body); 6019 t1 = Astronomy_AddDays(startTime, period * ( -30.0 / 360.0)); 6020 t2 = Astronomy_AddDays(startTime, period * (+270.0 / 360.0)); 6021 t_min = t_max = t1; 6022 min_dist = max_dist = -1.0; /* prevent warning about uninitialized variables */ 6023 interval = (t2.ut - t1.ut) / (npoints - 1.0); 6024 6025 for (i=0; i < npoints; ++i) 6026 { 6027 double ut = t1.ut + (i * interval); 6028 time = Astronomy_TimeFromDays(ut); 6029 result = Astronomy_HelioDistance(body, time); 6030 if (result.status != ASTRO_SUCCESS) 6031 return ApsisError(result.status); 6032 dist = result.value; 6033 if (i == 0) 6034 { 6035 max_dist = min_dist = dist; 6036 } 6037 else 6038 { 6039 if (dist > max_dist) 6040 { 6041 max_dist = dist; 6042 t_max = time; 6043 } 6044 if (dist < min_dist) 6045 { 6046 min_dist = dist; 6047 t_min = time; 6048 } 6049 } 6050 } 6051 6052 t1 = Astronomy_AddDays(t_min, -2 * interval); 6053 perihelion = PlanetExtreme(body, APSIS_PERICENTER, t1, 4 * interval); 6054 6055 t1 = Astronomy_AddDays(t_max, -2 * interval); 6056 aphelion = PlanetExtreme(body, APSIS_APOCENTER, t1, 4 * interval); 6057 6058 if (perihelion.status == ASTRO_SUCCESS && perihelion.time.tt >= startTime.tt) 6059 { 6060 if (aphelion.status == ASTRO_SUCCESS && aphelion.time.tt >= startTime.tt) 6061 { 6062 /* Perihelion and aphelion are both valid. Pick the one that comes first. */ 6063 if (aphelion.time.tt < perihelion.time.tt) 6064 return aphelion; 6065 } 6066 return perihelion; 6067 } 6068 6069 if (aphelion.status == ASTRO_SUCCESS && aphelion.time.tt >= startTime.tt) 6070 return aphelion; 6071 6072 return ApsisError(ASTRO_FAIL_APSIS); 6073 } 6074 6075 6076 /** 6077 * @brief 6078 * Finds the date and time of a planet's perihelion (closest approach to the Sun) 6079 * or aphelion (farthest distance from the Sun) after a given time. 6080 * 6081 * Given a date and time to start the search in `startTime`, this function finds the 6082 * next date and time that the center of the specified planet reaches the closest or farthest point 6083 * in its orbit with respect to the center of the Sun, whichever comes first 6084 * after `startTime`. 6085 * 6086 * The closest point is called *perihelion* and the farthest point is called *aphelion*. 6087 * The word *apsis* refers to either event. 6088 * 6089 * To iterate through consecutive alternating perihelion and aphelion events, 6090 * call `Astronomy_SearchPlanetApsis` once, then use the return value to call 6091 * #Astronomy_NextPlanetApsis. After that, keep feeding the previous return value 6092 * from `Astronomy_NextPlanetApsis` into another call of `Astronomy_NextPlanetApsis` 6093 * as many times as desired. 6094 * 6095 * @param body 6096 * The planet for which to find the next perihelion/aphelion event. 6097 * Not allowed to be `BODY_SUN` or `BODY_MOON`. 6098 * 6099 * @param startTime 6100 * The date and time at which to start searching for the next perihelion or aphelion. 6101 * 6102 * @return 6103 * If successful, the `status` field in the returned structure holds `ASTRO_SUCCESS`, 6104 * `time` holds the date and time of the next planetary apsis, `kind` holds either 6105 * `APSIS_PERICENTER` for perihelion or `APSIS_APOCENTER` for aphelion, and the distance 6106 * values `dist_au` (astronomical units) and `dist_km` (kilometers) are valid. 6107 * If the function fails, `status` holds some value other than `ASTRO_SUCCESS` that 6108 * indicates what went wrong, and the other structure fields are invalid. 6109 */ 6110 astro_apsis_t Astronomy_SearchPlanetApsis(astro_body_t body, astro_time_t startTime) 6111 { 6112 astro_time_t t1, t2; 6113 astro_search_result_t search; 6114 astro_func_result_t m1, m2; 6115 planet_distance_context_t context; 6116 astro_apsis_t result; 6117 int iter; 6118 double orbit_period_days; 6119 double increment; /* number of days to skip in each iteration */ 6120 astro_func_result_t dist; 6121 6122 if (body == BODY_NEPTUNE || body == BODY_PLUTO) 6123 return BruteSearchPlanetApsis(body, startTime); 6124 6125 orbit_period_days = PlanetOrbitalPeriod(body); 6126 if (orbit_period_days == 0.0) 6127 return ApsisError(ASTRO_INVALID_BODY); /* The body must be a planet. */ 6128 6129 increment = orbit_period_days / 6.0; 6130 6131 context.body = body; 6132 6133 t1 = startTime; 6134 context.direction = +1; 6135 m1 = planet_distance_slope(&context, t1); 6136 if (m1.status != ASTRO_SUCCESS) 6137 return ApsisError(m1.status); 6138 6139 for (iter=0; iter * increment < 2.0 * orbit_period_days; ++iter) 6140 { 6141 t2 = Astronomy_AddDays(t1, increment); 6142 context.direction = +1; 6143 m2 = planet_distance_slope(&context, t2); 6144 if (m2.status != ASTRO_SUCCESS) 6145 return ApsisError(m2.status); 6146 6147 if (m1.value * m2.value <= 0.0) 6148 { 6149 /* There is a change of slope polarity within the time range [t1, t2]. */ 6150 /* Therefore this time range contains an apsis. */ 6151 /* Figure out whether it is perihelion or aphelion. */ 6152 6153 if (m1.value < 0.0 || m2.value > 0.0) 6154 { 6155 /* We found a minimum-distance event: perihelion. */ 6156 /* Search the time range for the time when the slope goes from negative to positive. */ 6157 context.direction = +1; 6158 result.kind = APSIS_PERICENTER; 6159 } 6160 else if (m1.value > 0.0 || m2.value < 0.0) 6161 { 6162 /* We found a maximum-distance event: aphelion. */ 6163 /* Search the time range for the time when the slope goes from positive to negative. */ 6164 context.direction = -1; 6165 result.kind = APSIS_APOCENTER; 6166 } 6167 else 6168 { 6169 /* This should never happen. It should not be possible for both slopes to be zero. */ 6170 return ApsisError(ASTRO_INTERNAL_ERROR); 6171 } 6172 6173 search = Astronomy_Search(planet_distance_slope, &context, t1, t2, 1.0); 6174 if (search.status != ASTRO_SUCCESS) 6175 return ApsisError(search.status); 6176 6177 dist = Astronomy_HelioDistance(body, search.time); 6178 if (dist.status != ASTRO_SUCCESS) 6179 return ApsisError(dist.status); 6180 6181 result.status = ASTRO_SUCCESS; 6182 result.time = search.time; 6183 result.dist_au = dist.value; 6184 result.dist_km = dist.value * KM_PER_AU; 6185 return result; 6186 } 6187 6188 /* We have not yet found a slope polarity change. Keep searching. */ 6189 t1 = t2; 6190 m1 = m2; 6191 } 6192 6193 /* It should not be possible to fail to find an apsis within 2 orbits. */ 6194 return ApsisError(ASTRO_INTERNAL_ERROR); 6195 } 6196 6197 /** 6198 * @brief 6199 * Finds the next planetary perihelion or aphelion event in a series. 6200 * 6201 * This function requires an #astro_apsis_t value obtained from a call 6202 * to #Astronomy_SearchPlanetApsis or `Astronomy_NextPlanetApsis`. 6203 * Given an aphelion event, this function finds the next perihelion event, and vice versa. 6204 * 6205 * See #Astronomy_SearchPlanetApsis for more details. 6206 * 6207 * @param body 6208 * The planet for which to find the next perihelion/aphelion event. 6209 * Not allowed to be `BODY_SUN` or `BODY_MOON`. 6210 * Must match the body passed into the call that produced the `apsis` parameter. 6211 * 6212 * @param apsis 6213 * An apsis event obtained from a call to #Astronomy_SearchPlanetApsis or `Astronomy_NextPlanetApsis`. 6214 * 6215 * @return 6216 * Same as the return value for #Astronomy_SearchPlanetApsis. 6217 */ 6218 astro_apsis_t Astronomy_NextPlanetApsis(astro_body_t body, astro_apsis_t apsis) 6219 { 6220 double skip; /* number of days to skip to start looking for next apsis event */ 6221 astro_apsis_t next; 6222 astro_time_t time; 6223 6224 if (apsis.status != ASTRO_SUCCESS) 6225 return ApsisError(ASTRO_INVALID_PARAMETER); 6226 6227 if (apsis.kind != APSIS_APOCENTER && apsis.kind != APSIS_PERICENTER) 6228 return ApsisError(ASTRO_INVALID_PARAMETER); 6229 6230 skip = 0.25 * PlanetOrbitalPeriod(body); /* skip 1/4 of an orbit before starting search again */ 6231 if (skip <= 0.0) 6232 return ApsisError(ASTRO_INVALID_BODY); /* body must be a planet */ 6233 6234 time = Astronomy_AddDays(apsis.time, skip); 6235 next = Astronomy_SearchPlanetApsis(body, time); 6236 if (next.status == ASTRO_SUCCESS) 6237 { 6238 /* Verify that we found the opposite apsis from the previous one. */ 6239 if (next.kind + apsis.kind != 1) 6240 return ApsisError(ASTRO_INTERNAL_ERROR); 6241 } 6242 return next; 6243 } 6244 6245 6246 /** 6247 * @brief Calculates the inverse of a rotation matrix. 6248 * 6249 * Given a rotation matrix that performs some coordinate transform, 6250 * this function returns the matrix that reverses that trasnform. 6251 * 6252 * @param rotation 6253 * The rotation matrix to be inverted. 6254 * 6255 * @return 6256 * A rotation matrix that performs the opposite transformation. 6257 */ 6258 astro_rotation_t Astronomy_InverseRotation(astro_rotation_t rotation) 6259 { 6260 astro_rotation_t inverse; 6261 6262 if (rotation.status != ASTRO_SUCCESS) 6263 return RotationErr(ASTRO_INVALID_PARAMETER); 6264 6265 inverse.status = ASTRO_SUCCESS; 6266 inverse.rot[0][0] = rotation.rot[0][0]; 6267 inverse.rot[0][1] = rotation.rot[1][0]; 6268 inverse.rot[0][2] = rotation.rot[2][0]; 6269 inverse.rot[1][0] = rotation.rot[0][1]; 6270 inverse.rot[1][1] = rotation.rot[1][1]; 6271 inverse.rot[1][2] = rotation.rot[2][1]; 6272 inverse.rot[2][0] = rotation.rot[0][2]; 6273 inverse.rot[2][1] = rotation.rot[1][2]; 6274 inverse.rot[2][2] = rotation.rot[2][2]; 6275 6276 return inverse; 6277 } 6278 6279 /** 6280 * @brief Creates a rotation based on applying one rotation followed by another. 6281 * 6282 * Given two rotation matrices, returns a combined rotation matrix that is 6283 * equivalent to rotating based on the first matrix, followed by the second. 6284 * 6285 * @param a 6286 * The first rotation to apply. 6287 * 6288 * @param b 6289 * The second rotation to apply. 6290 * 6291 * @return 6292 * The combined rotation matrix. 6293 */ 6294 astro_rotation_t Astronomy_CombineRotation(astro_rotation_t a, astro_rotation_t b) 6295 { 6296 astro_rotation_t c; 6297 6298 if (a.status != ASTRO_SUCCESS || b.status != ASTRO_SUCCESS) 6299 return RotationErr(ASTRO_INVALID_PARAMETER); 6300 6301 /* 6302 Use matrix multiplication: c = b*a. 6303 We put 'b' on the left and 'a' on the right because, 6304 just like when you use a matrix M to rotate a vector V, 6305 you put the M on the left in the product M*V. 6306 We can think of this as 'b' rotating all the 3 column vectors in 'a'. 6307 */ 6308 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]; 6309 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]; 6310 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]; 6311 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]; 6312 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]; 6313 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]; 6314 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]; 6315 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]; 6316 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]; 6317 6318 c.status = ASTRO_SUCCESS; 6319 return c; 6320 } 6321 6322 /** 6323 * @brief Converts spherical coordinates to Cartesian coordinates. 6324 * 6325 * Given spherical coordinates and a time at which they are valid, 6326 * returns a vector of Cartesian coordinates. The returned value 6327 * includes the time, as required by the type #astro_vector_t. 6328 * 6329 * @param sphere 6330 * Spherical coordinates to be converted. 6331 * 6332 * @param time 6333 * The time that should be included in the return value. 6334 * 6335 * @return 6336 * The vector form of the supplied spherical coordinates. 6337 */ 6338 astro_vector_t Astronomy_VectorFromSphere(astro_spherical_t sphere, astro_time_t time) 6339 { 6340 astro_vector_t vector; 6341 double radlat, radlon, rcoslat; 6342 6343 if (sphere.status != ASTRO_SUCCESS) 6344 return VecError(ASTRO_INVALID_PARAMETER, time); 6345 6346 radlat = sphere.lat * DEG2RAD; 6347 radlon = sphere.lon * DEG2RAD; 6348 rcoslat = sphere.dist * cos(radlat); 6349 6350 vector.status = ASTRO_SUCCESS; 6351 vector.t = time; 6352 vector.x = rcoslat * cos(radlon); 6353 vector.y = rcoslat * sin(radlon); 6354 vector.z = sphere.dist * sin(radlat); 6355 6356 return vector; 6357 } 6358 6359 6360 /** 6361 * @brief Converts Cartesian coordinates to spherical coordinates. 6362 * 6363 * Given a Cartesian vector, returns latitude, longitude, and distance. 6364 * 6365 * @param vector 6366 * Cartesian vector to be converted to spherical coordinates. 6367 * 6368 * @return 6369 * Spherical coordinates that are equivalent to the given vector. 6370 */ 6371 astro_spherical_t Astronomy_SphereFromVector(astro_vector_t vector) 6372 { 6373 double xyproj; 6374 astro_spherical_t sphere; 6375 6376 xyproj = vector.x*vector.x + vector.y*vector.y; 6377 sphere.dist = sqrt(xyproj + vector.z*vector.z); 6378 if (xyproj == 0.0) 6379 { 6380 if (vector.z == 0.0) 6381 { 6382 /* Indeterminate coordinates; pos vector has zero length. */ 6383 return SphereError(ASTRO_INVALID_PARAMETER); 6384 } 6385 6386 sphere.lon = 0.0; 6387 sphere.lat = (vector.z < 0.0) ? -90.0 : +90.0; 6388 } 6389 else 6390 { 6391 sphere.lon = RAD2DEG * atan2(vector.y, vector.x); 6392 if (sphere.lon < 0.0) 6393 sphere.lon += 360.0; 6394 6395 sphere.lat = RAD2DEG * atan2(vector.z, sqrt(xyproj)); 6396 } 6397 6398 sphere.status = ASTRO_SUCCESS; 6399 return sphere; 6400 } 6401 6402 6403 /** 6404 * @brief 6405 * Given angular equatorial coordinates in `equ`, calculates equatorial vector. 6406 * 6407 * @param equ 6408 * Angular equatorial coordinates to be converted to a vector. 6409 * 6410 * @param time 6411 * The date and time of the observation. This is needed because the returned 6412 * vector requires a valid time value when passed to certain other functions. 6413 * 6414 * @return 6415 * A vector in the equatorial system. 6416 */ 6417 astro_vector_t Astronomy_VectorFromEquator(astro_equatorial_t equ, astro_time_t time) 6418 { 6419 astro_spherical_t sphere; 6420 6421 if (equ.status != ASTRO_SUCCESS) 6422 return VecError(ASTRO_INVALID_PARAMETER, time); 6423 6424 sphere.status = ASTRO_SUCCESS; 6425 sphere.lat = equ.dec; 6426 sphere.lon = 15.0 * equ.ra; /* convert sidereal hours to degrees */ 6427 sphere.dist = equ.dist; 6428 6429 return Astronomy_VectorFromSphere(sphere, time); 6430 } 6431 6432 6433 /** 6434 * @brief 6435 * Given an equatorial vector, calculates equatorial angular coordinates. 6436 * 6437 * @param vector 6438 * A vector in an equatorial coordinate system. 6439 * 6440 * @return 6441 * Angular coordinates expressed in the same equatorial system as `vector`. 6442 */ 6443 astro_equatorial_t Astronomy_EquatorFromVector(astro_vector_t vector) 6444 { 6445 astro_equatorial_t equ; 6446 astro_spherical_t sphere; 6447 6448 sphere = Astronomy_SphereFromVector(vector); 6449 if (sphere.status != ASTRO_SUCCESS) 6450 return EquError(sphere.status); 6451 6452 equ.status = ASTRO_SUCCESS; 6453 equ.dec = sphere.lat; 6454 equ.ra = sphere.lon / 15.0; /* convert degrees to sidereal hours */ 6455 equ.dist = sphere.dist; 6456 6457 return equ; 6458 } 6459 6460 6461 static double ToggleAzimuthDirection(double az) 6462 { 6463 az = 360.0 - az; 6464 if (az >= 360.0) 6465 az -= 360.0; 6466 else if (az < 0.0) 6467 az += 360.0; 6468 return az; 6469 } 6470 6471 /** 6472 * @brief Converts Cartesian coordinates to horizontal coordinates. 6473 * 6474 * Given a horizontal Cartesian vector, returns horizontal azimuth and altitude. 6475 * 6476 * *IMPORTANT:* This function differs from #Astronomy_SphereFromVector in two ways: 6477 * - `Astronomy_SphereFromVector` returns a `lon` value that represents azimuth defined counterclockwise 6478 * from north (e.g., west = +90), but this function represents a clockwise rotation 6479 * (e.g., east = +90). The difference is because `Astronomy_SphereFromVector` is intended 6480 * to preserve the vector "right-hand rule", while this function defines azimuth in a more 6481 * traditional way as used in navigation and cartography. 6482 * - This function optionally corrects for atmospheric refraction, while `Astronomy_SphereFromVector` 6483 * does not. 6484 * 6485 * The returned structure contains the azimuth in `lon`. 6486 * It is measured in degrees clockwise from north: east = +90 degrees, west = +270 degrees. 6487 * 6488 * The altitude is stored in `lat`. 6489 * 6490 * The distance to the observed object is stored in `dist`, 6491 * and is expressed in astronomical units (AU). 6492 * 6493 * @param vector 6494 * Cartesian vector to be converted to horizontal coordinates. 6495 * 6496 * @param refraction 6497 * `REFRACTION_NORMAL`: correct altitude for atmospheric refraction (recommended). 6498 * `REFRACTION_NONE`: no atmospheric refraction correction is performed. 6499 * `REFRACTION_JPLHOR`: for JPL Horizons compatibility testing only; not recommended for normal use. 6500 * 6501 * @return 6502 * If successful, `status` holds `ASTRO_SUCCESS` and the other fields are valid as described 6503 * in the function remarks. 6504 * Otherwise `status` holds an error code and the other fields are undefined. 6505 */ 6506 astro_spherical_t Astronomy_HorizonFromVector(astro_vector_t vector, astro_refraction_t refraction) 6507 { 6508 astro_spherical_t sphere; 6509 6510 sphere = Astronomy_SphereFromVector(vector); 6511 if (sphere.status == ASTRO_SUCCESS) 6512 { 6513 /* Convert azimuth from counterclockwise-from-north to clockwise-from-north. */ 6514 sphere.lon = ToggleAzimuthDirection(sphere.lon); 6515 sphere.lat += Astronomy_Refraction(refraction, sphere.lat); 6516 } 6517 6518 return sphere; 6519 } 6520 6521 6522 /** 6523 * @brief 6524 * Given apparent angular horizontal coordinates in `sphere`, calculate horizontal vector. 6525 * 6526 * @param sphere 6527 * A structure that contains apparent horizontal coordinates: 6528 * `lat` holds the refracted azimuth angle, 6529 * `lon` holds the azimuth in degrees clockwise from north, 6530 * and `dist` holds the distance from the observer to the object in AU. 6531 * 6532 * @param time 6533 * The date and time of the observation. This is needed because the returned 6534 * #astro_vector_t structure requires a valid time value when passed to certain other functions. 6535 * 6536 * @param refraction 6537 * The refraction option used to model atmospheric lensing. See #Astronomy_Refraction. 6538 * This specifies how refraction is to be removed from the altitude stored in `sphere.lat`. 6539 * 6540 * @return 6541 * A vector in the horizontal system: `x` = north, `y` = west, and `z` = zenith (up). 6542 */ 6543 astro_vector_t Astronomy_VectorFromHorizon(astro_spherical_t sphere, astro_time_t time, astro_refraction_t refraction) 6544 { 6545 if (sphere.status != ASTRO_SUCCESS) 6546 return VecError(ASTRO_INVALID_PARAMETER, time); 6547 6548 /* Convert azimuth from clockwise-from-north to counterclockwise-from-north. */ 6549 sphere.lon = ToggleAzimuthDirection(sphere.lon); 6550 6551 /* Reverse any applied refraction. */ 6552 sphere.lat += Astronomy_InverseRefraction(refraction, sphere.lat); 6553 6554 return Astronomy_VectorFromSphere(sphere, time); 6555 } 6556 6557 6558 /** 6559 * @brief 6560 * Calculates the amount of "lift" to an altitude angle caused by atmospheric refraction. 6561 * 6562 * Given an altitude angle and a refraction option, calculates 6563 * the amount of "lift" caused by atmospheric refraction. 6564 * This is the number of degrees higher in the sky an object appears 6565 * due to the lensing of the Earth's atmosphere. 6566 * 6567 * @param refraction 6568 * The option selecting which refraction correction to use. 6569 * If `REFRACTION_NORMAL`, uses a well-behaved refraction model that works well for 6570 * all valid values (-90 to +90) of `altitude`. 6571 * If `REFRACTION_JPLHOR`, this function returns a compatible value with the JPL Horizons tool. 6572 * If any other value (including `REFRACTION_NONE`), this function returns 0. 6573 * 6574 * @param altitude 6575 * An altitude angle in a horizontal coordinate system. Must be a value between -90 and +90. 6576 * 6577 * @return 6578 * The angular adjustment in degrees to be added to the altitude angle to correct for atmospheric lensing. 6579 */ 6580 double Astronomy_Refraction(astro_refraction_t refraction, double altitude) 6581 { 6582 double refr, hd; 6583 6584 if (altitude < -90.0 || altitude > +90.0) 6585 return 0.0; /* no attempt to correct an invalid altitude */ 6586 6587 if (refraction == REFRACTION_NORMAL || refraction == REFRACTION_JPLHOR) 6588 { 6589 // http://extras.springer.com/1999/978-1-4471-0555-8/chap4/horizons/horizons.pdf 6590 // JPL Horizons says it uses refraction algorithm from 6591 // Meeus "Astronomical Algorithms", 1991, p. 101-102. 6592 // I found the following Go implementation: 6593 // https://github.com/soniakeys/meeus/blob/master/v3/refraction/refract.go 6594 // This is a translation from the function "Saemundsson" there. 6595 // I found experimentally that JPL Horizons clamps the angle to 1 degree below the horizon. 6596 // This is important because the 'refr' formula below goes crazy near hd = -5.11. 6597 hd = altitude; 6598 if (hd < -1.0) 6599 hd = -1.0; 6600 6601 refr = (1.02 / tan((hd+10.3/(hd+5.11))*DEG2RAD)) / 60.0; 6602 6603 if (refraction == REFRACTION_NORMAL && altitude < -1.0) 6604 { 6605 // In "normal" mode we gradually reduce refraction toward the nadir 6606 // so that we never get an altitude angle less than -90 degrees. 6607 // When horizon angle is -1 degrees, the factor is exactly 1. 6608 // As altitude approaches -90 (the nadir), the fraction approaches 0 linearly. 6609 refr *= (altitude + 90.0) / 89.0; 6610 } 6611 } 6612 else 6613 { 6614 /* No refraction, or the refraction option is invalid. */ 6615 refr = 0.0; 6616 } 6617 6618 return refr; 6619 } 6620 6621 6622 /** 6623 * @brief 6624 * Calculates the inverse of an atmospheric refraction angle. 6625 * 6626 * Given an observed altitude angle that includes atmospheric refraction, 6627 * calculate the negative angular correction to obtain the unrefracted 6628 * altitude. This is useful for cases where observed horizontal 6629 * coordinates are to be converted to another orientation system, 6630 * but refraction first must be removed from the observed position. 6631 * 6632 * @param refraction 6633 * The option selecting which refraction correction to use. 6634 * See #Astronomy_Refraction. 6635 * 6636 * @param bent_altitude 6637 * The apparent altitude that includes atmospheric refraction. 6638 * 6639 * @return 6640 * The angular adjustment in degrees to be added to the 6641 * altitude angle to correct for atmospheric lensing. 6642 * This will be less than or equal to zero. 6643 */ 6644 double Astronomy_InverseRefraction(astro_refraction_t refraction, double bent_altitude) 6645 { 6646 double altitude, diff; 6647 6648 if (bent_altitude < -90.0 || bent_altitude > +90.0) 6649 return 0.0; /* no attempt to correct an invalid altitude */ 6650 6651 /* Find the pre-adjusted altitude whose refraction correction leads to 'altitude'. */ 6652 altitude = bent_altitude - Astronomy_Refraction(refraction, bent_altitude); 6653 for(;;) 6654 { 6655 /* See how close we got. */ 6656 diff = (altitude + Astronomy_Refraction(refraction, altitude)) - bent_altitude; 6657 if (fabs(diff) < 1.0e-14) 6658 return altitude - bent_altitude; 6659 6660 altitude -= diff; 6661 } 6662 } 6663 6664 /** 6665 * @brief 6666 * Applies a rotation to a vector, yielding a rotated vector. 6667 * 6668 * This function transforms a vector in one orientation to a vector 6669 * in another orientation. 6670 * 6671 * @param rotation 6672 * A rotation matrix that specifies how the orientation of the vector is to be changed. 6673 * 6674 * @param vector 6675 * The vector whose orientation is to be changed. 6676 * 6677 * @return 6678 * A vector in the orientation specified by `rotation`. 6679 */ 6680 astro_vector_t Astronomy_RotateVector(astro_rotation_t rotation, astro_vector_t vector) 6681 { 6682 astro_vector_t target; 6683 6684 if (rotation.status != ASTRO_SUCCESS || vector.status != ASTRO_SUCCESS) 6685 return VecError(ASTRO_INVALID_PARAMETER, vector.t); 6686 6687 target.status = ASTRO_SUCCESS; 6688 target.t = vector.t; 6689 target.x = rotation.rot[0][0]*vector.x + rotation.rot[1][0]*vector.y + rotation.rot[2][0]*vector.z; 6690 target.y = rotation.rot[0][1]*vector.x + rotation.rot[1][1]*vector.y + rotation.rot[2][1]*vector.z; 6691 target.z = rotation.rot[0][2]*vector.x + rotation.rot[1][2]*vector.y + rotation.rot[2][2]*vector.z; 6692 6693 return target; 6694 } 6695 6696 6697 /** 6698 * @brief 6699 * Calculates a rotation matrix from equatorial J2000 (EQJ) to ecliptic J2000 (ECL). 6700 * 6701 * This is one of the family of functions that returns a rotation matrix 6702 * for converting from one orientation to another. 6703 * Source: EQJ = equatorial system, using equator at J2000 epoch. 6704 * Target: ECL = ecliptic system, using equator at J2000 epoch. 6705 * 6706 * @return 6707 * A rotation matrix that converts EQJ to ECL. 6708 */ 6709 astro_rotation_t Astronomy_Rotation_EQJ_ECL(void) 6710 { 6711 /* ob = mean obliquity of the J2000 ecliptic = 0.40909260059599012 radians. */ 6712 static const double c = 0.9174821430670688; /* cos(ob) */ 6713 static const double s = 0.3977769691083922; /* sin(ob) */ 6714 astro_rotation_t r; 6715 6716 r.status = ASTRO_SUCCESS; 6717 r.rot[0][0] = 1.0; r.rot[1][0] = 0.0; r.rot[2][0] = 0.0; 6718 r.rot[0][1] = 0.0; r.rot[1][1] = +c; r.rot[2][1] = +s; 6719 r.rot[0][2] = 0.0; r.rot[1][2] = -s; r.rot[2][2] = +c; 6720 return r; 6721 } 6722 6723 /** 6724 * @brief 6725 * Calculates a rotation matrix from ecliptic J2000 (ECL) to equatorial J2000 (EQJ). 6726 * 6727 * This is one of the family of functions that returns a rotation matrix 6728 * for converting from one orientation to another. 6729 * Source: ECL = ecliptic system, using equator at J2000 epoch. 6730 * Target: EQJ = equatorial system, using equator at J2000 epoch. 6731 * 6732 * @return 6733 * A rotation matrix that converts ECL to EQJ. 6734 */ 6735 astro_rotation_t Astronomy_Rotation_ECL_EQJ(void) 6736 { 6737 /* ob = mean obliquity of the J2000 ecliptic = 0.40909260059599012 radians. */ 6738 static const double c = 0.9174821430670688; /* cos(ob) */ 6739 static const double s = 0.3977769691083922; /* sin(ob) */ 6740 astro_rotation_t r; 6741 6742 r.status = ASTRO_SUCCESS; 6743 r.rot[0][0] = 1.0; r.rot[1][0] = 0.0; r.rot[2][0] = 0.0; 6744 r.rot[0][1] = 0.0; r.rot[1][1] = +c; r.rot[2][1] = -s; 6745 r.rot[0][2] = 0.0; r.rot[1][2] = +s; r.rot[2][2] = +c; 6746 return r; 6747 } 6748 6749 /** 6750 * @brief 6751 * Calculates a rotation matrix from equatorial J2000 (EQJ) to equatorial of-date (EQD). 6752 * 6753 * This is one of the family of functions that returns a rotation matrix 6754 * for converting from one orientation to another. 6755 * Source: EQJ = equatorial system, using equator at J2000 epoch. 6756 * Target: EQD = equatorial system, using equator of the specified date/time. 6757 * 6758 * @param time 6759 * The date and time at which the Earth's equator defines the target orientation. 6760 * 6761 * @return 6762 * A rotation matrix that converts EQJ to EQD at `time`. 6763 */ 6764 astro_rotation_t Astronomy_Rotation_EQJ_EQD(astro_time_t time) 6765 { 6766 astro_rotation_t prec, nut; 6767 6768 prec = precession_rot(0.0, time.tt); 6769 nut = nutation_rot(&time, 0); 6770 return Astronomy_CombineRotation(prec, nut); 6771 } 6772 6773 /** 6774 * @brief 6775 * Calculates a rotation matrix from equatorial of-date (EQD) to equatorial J2000 (EQJ). 6776 * 6777 * This is one of the family of functions that returns a rotation matrix 6778 * for converting from one orientation to another. 6779 * Source: EQD = equatorial system, using equator of the specified date/time. 6780 * Target: EQJ = equatorial system, using equator at J2000 epoch. 6781 * 6782 * @param time 6783 * The date and time at which the Earth's equator defines the source orientation. 6784 * 6785 * @return 6786 * A rotation matrix that converts EQD at `time` to EQJ. 6787 */ 6788 astro_rotation_t Astronomy_Rotation_EQD_EQJ(astro_time_t time) 6789 { 6790 astro_rotation_t prec, nut; 6791 6792 nut = nutation_rot(&time, 1); 6793 prec = precession_rot(time.tt, 0.0); 6794 return Astronomy_CombineRotation(nut, prec); 6795 } 6796 6797 6798 /** 6799 * @brief 6800 * Calculates a rotation matrix from equatorial of-date (EQD) to horizontal (HOR). 6801 * 6802 * This is one of the family of functions that returns a rotation matrix 6803 * for converting from one orientation to another. 6804 * Source: EQD = equatorial system, using equator of the specified date/time. 6805 * Target: HOR = horizontal system. 6806 * 6807 * @param time 6808 * The date and time at which the Earth's equator applies. 6809 * 6810 * @param observer 6811 * A location near the Earth's mean sea level that defines the observer's horizon. 6812 * 6813 * @return 6814 * A rotation matrix that converts EQD to HOR at `time` and for `observer`. 6815 * The components of the horizontal vector are: 6816 * x = north, y = west, z = zenith (straight up from the observer). 6817 * These components are chosen so that the "right-hand rule" works for the vector 6818 * and so that north represents the direction where azimuth = 0. 6819 */ 6820 astro_rotation_t Astronomy_Rotation_EQD_HOR(astro_time_t time, astro_observer_t observer) 6821 { 6822 astro_rotation_t rot; 6823 double uze[3], une[3], uwe[3]; 6824 double uz[3], un[3], uw[3]; 6825 double spin_angle; 6826 6827 double sinlat = sin(observer.latitude * DEG2RAD); 6828 double coslat = cos(observer.latitude * DEG2RAD); 6829 double sinlon = sin(observer.longitude * DEG2RAD); 6830 double coslon = cos(observer.longitude * DEG2RAD); 6831 6832 uze[0] = coslat * coslon; 6833 uze[1] = coslat * sinlon; 6834 uze[2] = sinlat; 6835 6836 une[0] = -sinlat * coslon; 6837 une[1] = -sinlat * sinlon; 6838 une[2] = coslat; 6839 6840 uwe[0] = sinlon; 6841 uwe[1] = -coslon; 6842 uwe[2] = 0.0; 6843 6844 spin_angle = -15.0 * sidereal_time(&time); 6845 spin(spin_angle, uze, uz); 6846 spin(spin_angle, une, un); 6847 spin(spin_angle, uwe, uw); 6848 6849 rot.rot[0][0] = un[0]; rot.rot[1][0] = un[1]; rot.rot[2][0] = un[2]; 6850 rot.rot[0][1] = uw[0]; rot.rot[1][1] = uw[1]; rot.rot[2][1] = uw[2]; 6851 rot.rot[0][2] = uz[0]; rot.rot[1][2] = uz[1]; rot.rot[2][2] = uz[2]; 6852 6853 rot.status = ASTRO_SUCCESS; 6854 return rot; 6855 } 6856 6857 6858 /** 6859 * @brief 6860 * Calculates a rotation matrix from horizontal (HOR) to equatorial of-date (EQD). 6861 * 6862 * This is one of the family of functions that returns a rotation matrix 6863 * for converting from one orientation to another. 6864 * Source: HOR = horizontal system (x=North, y=West, z=Zenith). 6865 * Target: EQD = equatorial system, using equator of the specified date/time. 6866 * 6867 * @param time 6868 * The date and time at which the Earth's equator applies. 6869 * 6870 * @param observer 6871 * A location near the Earth's mean sea level that defines the observer's horizon. 6872 * 6873 * @return 6874 * A rotation matrix that converts HOR to EQD at `time` and for `observer`. 6875 */ 6876 astro_rotation_t Astronomy_Rotation_HOR_EQD(astro_time_t time, astro_observer_t observer) 6877 { 6878 astro_rotation_t rot = Astronomy_Rotation_EQD_HOR(time, observer); 6879 return Astronomy_InverseRotation(rot); 6880 } 6881 6882 6883 /** 6884 * @brief 6885 * Calculates a rotation matrix from horizontal (HOR) to J2000 equatorial (EQJ). 6886 * 6887 * This is one of the family of functions that returns a rotation matrix 6888 * for converting from one orientation to another. 6889 * Source: HOR = horizontal system (x=North, y=West, z=Zenith). 6890 * Target: EQJ = equatorial system, using equator at the J2000 epoch. 6891 * 6892 * @param time 6893 * The date and time of the observation. 6894 * 6895 * @param observer 6896 * A location near the Earth's mean sea level that defines the observer's horizon. 6897 * 6898 * @return 6899 * A rotation matrix that converts HOR to EQD at `time` and for `observer`. 6900 */ 6901 astro_rotation_t Astronomy_Rotation_HOR_EQJ(astro_time_t time, astro_observer_t observer) 6902 { 6903 astro_rotation_t hor_eqd, eqd_eqj; 6904 6905 hor_eqd = Astronomy_Rotation_HOR_EQD(time, observer); 6906 eqd_eqj = Astronomy_Rotation_EQD_EQJ(time); 6907 return Astronomy_CombineRotation(hor_eqd, eqd_eqj); 6908 } 6909 6910 6911 /** 6912 * @brief 6913 * Calculates a rotation matrix from equatorial J2000 (EQJ) to horizontal (HOR). 6914 * 6915 * This is one of the family of functions that returns a rotation matrix 6916 * for converting from one orientation to another. 6917 * Source: EQJ = equatorial system, using the equator at the J2000 epoch. 6918 * Target: HOR = horizontal system. 6919 * 6920 * @param time 6921 * The date and time of the desired horizontal orientation. 6922 * 6923 * @param observer 6924 * A location near the Earth's mean sea level that defines the observer's horizon. 6925 * 6926 * @return 6927 * A rotation matrix that converts EQJ to HOR at `time` and for `observer`. 6928 * The components of the horizontal vector are: 6929 * x = north, y = west, z = zenith (straight up from the observer). 6930 * These components are chosen so that the "right-hand rule" works for the vector 6931 * and so that north represents the direction where azimuth = 0. 6932 */ 6933 astro_rotation_t Astronomy_Rotation_EQJ_HOR(astro_time_t time, astro_observer_t observer) 6934 { 6935 astro_rotation_t rot = Astronomy_Rotation_HOR_EQJ(time, observer); 6936 return Astronomy_InverseRotation(rot); 6937 } 6938 6939 6940 /** 6941 * @brief 6942 * Calculates a rotation matrix from equatorial of-date (EQD) to ecliptic J2000 (ECL). 6943 * 6944 * This is one of the family of functions that returns a rotation matrix 6945 * for converting from one orientation to another. 6946 * Source: EQD = equatorial system, using equator of date. 6947 * Target: ECL = ecliptic system, using equator at J2000 epoch. 6948 * 6949 * @param time 6950 * The date and time of the source equator. 6951 * 6952 * @return 6953 * A rotation matrix that converts EQD to ECL. 6954 */ 6955 astro_rotation_t Astronomy_Rotation_EQD_ECL(astro_time_t time) 6956 { 6957 astro_rotation_t eqd_eqj; 6958 astro_rotation_t eqj_ecl; 6959 6960 eqd_eqj = Astronomy_Rotation_EQD_EQJ(time); 6961 eqj_ecl = Astronomy_Rotation_EQJ_ECL(); 6962 return Astronomy_CombineRotation(eqd_eqj, eqj_ecl); 6963 } 6964 6965 6966 /** 6967 * @brief 6968 * Calculates a rotation matrix from ecliptic J2000 (ECL) to equatorial of-date (EQD). 6969 * 6970 * This is one of the family of functions that returns a rotation matrix 6971 * for converting from one orientation to another. 6972 * Source: ECL = ecliptic system, using equator at J2000 epoch. 6973 * Target: EQD = equatorial system, using equator of date. 6974 * 6975 * @param time 6976 * The date and time of the desired equator. 6977 * 6978 * @return 6979 * A rotation matrix that converts ECL to EQD. 6980 */ 6981 astro_rotation_t Astronomy_Rotation_ECL_EQD(astro_time_t time) 6982 { 6983 astro_rotation_t rot = Astronomy_Rotation_EQD_ECL(time); 6984 return Astronomy_InverseRotation(rot); 6985 } 6986 6987 /** 6988 * @brief 6989 * Calculates a rotation matrix from ecliptic J2000 (ECL) to horizontal (HOR). 6990 * 6991 * This is one of the family of functions that returns a rotation matrix 6992 * for converting from one orientation to another. 6993 * Source: ECL = ecliptic system, using equator at J2000 epoch. 6994 * Target: HOR = horizontal system. 6995 * 6996 * @param time 6997 * The date and time of the desired horizontal orientation. 6998 * 6999 * @param observer 7000 * A location near the Earth's mean sea level that defines the observer's horizon. 7001 * 7002 * @return 7003 * A rotation matrix that converts ECL to HOR at `time` and for `observer`. 7004 * The components of the horizontal vector are: 7005 * x = north, y = west, z = zenith (straight up from the observer). 7006 * These components are chosen so that the "right-hand rule" works for the vector 7007 * and so that north represents the direction where azimuth = 0. 7008 */ 7009 astro_rotation_t Astronomy_Rotation_ECL_HOR(astro_time_t time, astro_observer_t observer) 7010 { 7011 astro_rotation_t ecl_eqd = Astronomy_Rotation_ECL_EQD(time); 7012 astro_rotation_t eqd_hor = Astronomy_Rotation_EQD_HOR(time, observer); 7013 return Astronomy_CombineRotation(ecl_eqd, eqd_hor); 7014 } 7015 7016 /** 7017 * @brief 7018 * Calculates a rotation matrix from horizontal (HOR) to ecliptic J2000 (ECL). 7019 * 7020 * This is one of the family of functions that returns a rotation matrix 7021 * for converting from one orientation to another. 7022 * Source: HOR = horizontal system. 7023 * Target: ECL = ecliptic system, using equator at J2000 epoch. 7024 * 7025 * @param time 7026 * The date and time of the horizontal observation. 7027 * 7028 * @param observer 7029 * The location of the horizontal observer. 7030 * 7031 * @return 7032 * A rotation matrix that converts HOR to ECL. 7033 */ 7034 astro_rotation_t Astronomy_Rotation_HOR_ECL(astro_time_t time, astro_observer_t observer) 7035 { 7036 astro_rotation_t rot = Astronomy_Rotation_ECL_HOR(time, observer); 7037 return Astronomy_InverseRotation(rot); 7038 } 7039 7040 7041 /** @cond DOXYGEN_SKIP */ 7042 typedef struct 7043 { 7044 const char *symbol; 7045 const char *name; 7046 } 7047 constel_info_t; 7048 7049 7050 typedef struct 7051 { 7052 int index; 7053 double ra_lo; 7054 double ra_hi; 7055 double dec_lo; 7056 } 7057 constel_boundary_t; 7058 /** @endcond */ 7059 7060 #define NUM_CONSTELLATIONS 88 7061 7062 static const constel_info_t ConstelInfo[] = { 7063 /* 0 */ { "And", "Andromeda" } 7064 , /* 1 */ { "Ant", "Antila" } 7065 , /* 2 */ { "Aps", "Apus" } 7066 , /* 3 */ { "Aql", "Aquila" } 7067 , /* 4 */ { "Aqr", "Aquarius" } 7068 , /* 5 */ { "Ara", "Ara" } 7069 , /* 6 */ { "Ari", "Aries" } 7070 , /* 7 */ { "Aur", "Auriga" } 7071 , /* 8 */ { "Boo", "Bootes" } 7072 , /* 9 */ { "Cae", "Caelum" } 7073 , /* 10 */ { "Cam", "Camelopardis" } 7074 , /* 11 */ { "Cap", "Capricornus" } 7075 , /* 12 */ { "Car", "Carina" } 7076 , /* 13 */ { "Cas", "Cassiopeia" } 7077 , /* 14 */ { "Cen", "Centaurus" } 7078 , /* 15 */ { "Cep", "Cepheus" } 7079 , /* 16 */ { "Cet", "Cetus" } 7080 , /* 17 */ { "Cha", "Chamaeleon" } 7081 , /* 18 */ { "Cir", "Circinus" } 7082 , /* 19 */ { "CMa", "Canis Major" } 7083 , /* 20 */ { "CMi", "Canis Minor" } 7084 , /* 21 */ { "Cnc", "Cancer" } 7085 , /* 22 */ { "Col", "Columba" } 7086 , /* 23 */ { "Com", "Coma Berenices" } 7087 , /* 24 */ { "CrA", "Corona Australis" } 7088 , /* 25 */ { "CrB", "Corona Borealis" } 7089 , /* 26 */ { "Crt", "Crater" } 7090 , /* 27 */ { "Cru", "Crux" } 7091 , /* 28 */ { "Crv", "Corvus" } 7092 , /* 29 */ { "CVn", "Canes Venatici" } 7093 , /* 30 */ { "Cyg", "Cygnus" } 7094 , /* 31 */ { "Del", "Delphinus" } 7095 , /* 32 */ { "Dor", "Dorado" } 7096 , /* 33 */ { "Dra", "Draco" } 7097 , /* 34 */ { "Equ", "Equuleus" } 7098 , /* 35 */ { "Eri", "Eridanus" } 7099 , /* 36 */ { "For", "Fornax" } 7100 , /* 37 */ { "Gem", "Gemini" } 7101 , /* 38 */ { "Gru", "Grus" } 7102 , /* 39 */ { "Her", "Hercules" } 7103 , /* 40 */ { "Hor", "Horologium" } 7104 , /* 41 */ { "Hya", "Hydra" } 7105 , /* 42 */ { "Hyi", "Hydrus" } 7106 , /* 43 */ { "Ind", "Indus" } 7107 , /* 44 */ { "Lac", "Lacerta" } 7108 , /* 45 */ { "Leo", "Leo" } 7109 , /* 46 */ { "Lep", "Lepus" } 7110 , /* 47 */ { "Lib", "Libra" } 7111 , /* 48 */ { "LMi", "Leo Minor" } 7112 , /* 49 */ { "Lup", "Lupus" } 7113 , /* 50 */ { "Lyn", "Lynx" } 7114 , /* 51 */ { "Lyr", "Lyra" } 7115 , /* 52 */ { "Men", "Mensa" } 7116 , /* 53 */ { "Mic", "Microscopium" } 7117 , /* 54 */ { "Mon", "Monoceros" } 7118 , /* 55 */ { "Mus", "Musca" } 7119 , /* 56 */ { "Nor", "Norma" } 7120 , /* 57 */ { "Oct", "Octans" } 7121 , /* 58 */ { "Oph", "Ophiuchus" } 7122 , /* 59 */ { "Ori", "Orion" } 7123 , /* 60 */ { "Pav", "Pavo" } 7124 , /* 61 */ { "Peg", "Pegasus" } 7125 , /* 62 */ { "Per", "Perseus" } 7126 , /* 63 */ { "Phe", "Phoenix" } 7127 , /* 64 */ { "Pic", "Pictor" } 7128 , /* 65 */ { "PsA", "Pisces Austrinus" } 7129 , /* 66 */ { "Psc", "Pisces" } 7130 , /* 67 */ { "Pup", "Puppis" } 7131 , /* 68 */ { "Pyx", "Pyxis" } 7132 , /* 69 */ { "Ret", "Reticulum" } 7133 , /* 70 */ { "Scl", "Sculptor" } 7134 , /* 71 */ { "Sco", "Scorpius" } 7135 , /* 72 */ { "Sct", "Scutum" } 7136 , /* 73 */ { "Ser", "Serpens" } 7137 , /* 74 */ { "Sex", "Sextans" } 7138 , /* 75 */ { "Sge", "Sagitta" } 7139 , /* 76 */ { "Sgr", "Sagittarius" } 7140 , /* 77 */ { "Tau", "Taurus" } 7141 , /* 78 */ { "Tel", "Telescopium" } 7142 , /* 79 */ { "TrA", "Triangulum Australe" } 7143 , /* 80 */ { "Tri", "Triangulum" } 7144 , /* 81 */ { "Tuc", "Tucana" } 7145 , /* 82 */ { "UMa", "Ursa Major" } 7146 , /* 83 */ { "UMi", "Ursa Minor" } 7147 , /* 84 */ { "Vel", "Vela" } 7148 , /* 85 */ { "Vir", "Virgo" } 7149 , /* 86 */ { "Vol", "Volans" } 7150 , /* 87 */ { "Vul", "Vulpecula" } 7151 }; 7152 7153 static const constel_boundary_t ConstelBounds[] = { 7154 { 83, 0.00000000000000, 24.00000000000000, 88.00000000000000 } /* UMi */ 7155 , { 83, 8.00000000000000, 14.50000000000000, 86.50000000000000 } /* UMi */ 7156 , { 83, 21.00000000000000, 23.00000000000000, 86.16666666666667 } /* UMi */ 7157 , { 83, 18.00000000000000, 21.00000000000000, 86.00000000000000 } /* UMi */ 7158 , { 15, 0.00000000000000, 8.00000000000000, 85.00000000000000 } /* Cep */ 7159 , { 10, 9.16666666666667, 10.66666666666667, 82.00000000000000 } /* Cam */ 7160 , { 15, 0.00000000000000, 5.00000000000000, 80.00000000000000 } /* Cep */ 7161 , { 10, 10.66666666666667, 14.50000000000000, 80.00000000000000 } /* Cam */ 7162 , { 83, 17.50000000000000, 18.00000000000000, 80.00000000000000 } /* UMi */ 7163 , { 33, 20.16666666666667, 21.00000000000000, 80.00000000000000 } /* Dra */ 7164 , { 15, 0.00000000000000, 3.50833333333333, 77.00000000000000 } /* Cep */ 7165 , { 10, 11.50000000000000, 13.58333333333333, 77.00000000000000 } /* Cam */ 7166 , { 83, 16.53333333333333, 17.50000000000000, 75.00000000000000 } /* UMi */ 7167 , { 15, 20.16666666666667, 20.66666666666667, 75.00000000000000 } /* Cep */ 7168 , { 10, 7.96666666666667, 9.16666666666667, 73.50000000000000 } /* Cam */ 7169 , { 33, 9.16666666666667, 11.33333333333333, 73.50000000000000 } /* Dra */ 7170 , { 83, 13.00000000000000, 16.53333333333333, 70.00000000000000 } /* UMi */ 7171 , { 13, 3.10000000000000, 3.41666666666667, 68.00000000000000 } /* Cas */ 7172 , { 33, 20.41666666666667, 20.66666666666667, 67.00000000000000 } /* Dra */ 7173 , { 33, 11.33333333333333, 12.00000000000000, 66.50000000000000 } /* Dra */ 7174 , { 15, 0.00000000000000, 0.33333333333333, 66.00000000000000 } /* Cep */ 7175 , { 83, 14.00000000000000, 15.66666666666667, 66.00000000000000 } /* UMi */ 7176 , { 15, 23.58333333333333, 24.00000000000000, 66.00000000000000 } /* Cep */ 7177 , { 33, 12.00000000000000, 13.50000000000000, 64.00000000000000 } /* Dra */ 7178 , { 33, 13.50000000000000, 14.41666666666667, 63.00000000000000 } /* Dra */ 7179 , { 15, 23.16666666666667, 23.58333333333333, 63.00000000000000 } /* Cep */ 7180 , { 10, 6.10000000000000, 7.00000000000000, 62.00000000000000 } /* Cam */ 7181 , { 33, 20.00000000000000, 20.41666666666667, 61.50000000000000 } /* Dra */ 7182 , { 15, 20.53666666666667, 20.60000000000000, 60.91666666666666 } /* Cep */ 7183 , { 10, 7.00000000000000, 7.96666666666667, 60.00000000000000 } /* Cam */ 7184 , { 82, 7.96666666666667, 8.41666666666667, 60.00000000000000 } /* UMa */ 7185 , { 33, 19.76666666666667, 20.00000000000000, 59.50000000000000 } /* Dra */ 7186 , { 15, 20.00000000000000, 20.53666666666667, 59.50000000000000 } /* Cep */ 7187 , { 15, 22.86666666666667, 23.16666666666667, 59.08333333333334 } /* Cep */ 7188 , { 13, 0.00000000000000, 2.43333333333333, 58.50000000000000 } /* Cas */ 7189 , { 33, 19.41666666666667, 19.76666666666667, 58.00000000000000 } /* Dra */ 7190 , { 13, 1.70000000000000, 1.90833333333333, 57.50000000000000 } /* Cas */ 7191 , { 13, 2.43333333333333, 3.10000000000000, 57.00000000000000 } /* Cas */ 7192 , { 10, 3.10000000000000, 3.16666666666667, 57.00000000000000 } /* Cam */ 7193 , { 15, 22.31666666666667, 22.86666666666667, 56.25000000000000 } /* Cep */ 7194 , { 10, 5.00000000000000, 6.10000000000000, 56.00000000000000 } /* Cam */ 7195 , { 82, 14.03333333333333, 14.41666666666667, 55.50000000000000 } /* UMa */ 7196 , { 33, 14.41666666666667, 19.41666666666667, 55.50000000000000 } /* Dra */ 7197 , { 10, 3.16666666666667, 3.33333333333333, 55.00000000000000 } /* Cam */ 7198 , { 15, 22.13333333333333, 22.31666666666667, 55.00000000000000 } /* Cep */ 7199 , { 15, 20.60000000000000, 21.96666666666667, 54.83333333333334 } /* Cep */ 7200 , { 13, 0.00000000000000, 1.70000000000000, 54.00000000000000 } /* Cas */ 7201 , { 50, 6.10000000000000, 6.50000000000000, 54.00000000000000 } /* Lyn */ 7202 , { 82, 12.08333333333333, 13.50000000000000, 53.00000000000000 } /* UMa */ 7203 , { 33, 15.25000000000000, 15.75000000000000, 53.00000000000000 } /* Dra */ 7204 , { 15, 21.96666666666667, 22.13333333333333, 52.75000000000000 } /* Cep */ 7205 , { 10, 3.33333333333333, 5.00000000000000, 52.50000000000000 } /* Cam */ 7206 , { 13, 22.86666666666667, 23.33333333333333, 52.50000000000000 } /* Cas */ 7207 , { 33, 15.75000000000000, 17.00000000000000, 51.50000000000000 } /* Dra */ 7208 , { 62, 2.04166666666667, 2.51666666666667, 50.50000000000000 } /* Per */ 7209 , { 33, 17.00000000000000, 18.23333333333333, 50.50000000000000 } /* Dra */ 7210 , { 13, 0.00000000000000, 1.36666666666667, 50.00000000000000 } /* Cas */ 7211 , { 62, 1.36666666666667, 1.66666666666667, 50.00000000000000 } /* Per */ 7212 , { 50, 6.50000000000000, 6.80000000000000, 50.00000000000000 } /* Lyn */ 7213 , { 13, 23.33333333333333, 24.00000000000000, 50.00000000000000 } /* Cas */ 7214 , { 82, 13.50000000000000, 14.03333333333333, 48.50000000000000 } /* UMa */ 7215 , { 13, 0.00000000000000, 1.11666666666667, 48.00000000000000 } /* Cas */ 7216 , { 13, 23.58333333333333, 24.00000000000000, 48.00000000000000 } /* Cas */ 7217 , { 39, 18.17500000000000, 18.23333333333333, 47.50000000000000 } /* Her */ 7218 , { 33, 18.23333333333333, 19.08333333333333, 47.50000000000000 } /* Dra */ 7219 , { 30, 19.08333333333333, 19.16666666666667, 47.50000000000000 } /* Cyg */ 7220 , { 62, 1.66666666666667, 2.04166666666667, 47.00000000000000 } /* Per */ 7221 , { 82, 8.41666666666667, 9.16666666666667, 47.00000000000000 } /* UMa */ 7222 , { 13, 0.16666666666667, 0.86666666666667, 46.00000000000000 } /* Cas */ 7223 , { 82, 12.00000000000000, 12.08333333333333, 45.00000000000000 } /* UMa */ 7224 , { 50, 6.80000000000000, 7.36666666666667, 44.50000000000000 } /* Lyn */ 7225 , { 30, 21.90833333333333, 21.96666666666667, 44.00000000000000 } /* Cyg */ 7226 , { 30, 21.87500000000000, 21.90833333333333, 43.75000000000000 } /* Cyg */ 7227 , { 30, 19.16666666666667, 19.40000000000000, 43.50000000000000 } /* Cyg */ 7228 , { 82, 9.16666666666667, 10.16666666666667, 42.00000000000000 } /* UMa */ 7229 , { 82, 10.16666666666667, 10.78333333333333, 40.00000000000000 } /* UMa */ 7230 , { 8, 15.43333333333333, 15.75000000000000, 40.00000000000000 } /* Boo */ 7231 , { 39, 15.75000000000000, 16.33333333333333, 40.00000000000000 } /* Her */ 7232 , { 50, 9.25000000000000, 9.58333333333333, 39.75000000000000 } /* Lyn */ 7233 , { 0, 0.00000000000000, 2.51666666666667, 36.75000000000000 } /* And */ 7234 , { 62, 2.51666666666667, 2.56666666666667, 36.75000000000000 } /* Per */ 7235 , { 51, 19.35833333333333, 19.40000000000000, 36.50000000000000 } /* Lyr */ 7236 , { 62, 4.50000000000000, 4.69166666666667, 36.00000000000000 } /* Per */ 7237 , { 30, 21.73333333333333, 21.87500000000000, 36.00000000000000 } /* Cyg */ 7238 , { 44, 21.87500000000000, 22.00000000000000, 36.00000000000000 } /* Lac */ 7239 , { 7, 6.53333333333333, 7.36666666666667, 35.50000000000000 } /* Aur */ 7240 , { 50, 7.36666666666667, 7.75000000000000, 35.50000000000000 } /* Lyn */ 7241 , { 0, 0.00000000000000, 2.00000000000000, 35.00000000000000 } /* And */ 7242 , { 44, 22.00000000000000, 22.81666666666667, 35.00000000000000 } /* Lac */ 7243 , { 44, 22.81666666666667, 22.86666666666667, 34.50000000000000 } /* Lac */ 7244 , { 0, 22.86666666666667, 23.50000000000000, 34.50000000000000 } /* And */ 7245 , { 62, 2.56666666666667, 2.71666666666667, 34.00000000000000 } /* Per */ 7246 , { 82, 10.78333333333333, 11.00000000000000, 34.00000000000000 } /* UMa */ 7247 , { 29, 12.00000000000000, 12.33333333333333, 34.00000000000000 } /* CVn */ 7248 , { 50, 7.75000000000000, 9.25000000000000, 33.50000000000000 } /* Lyn */ 7249 , { 48, 9.25000000000000, 9.88333333333333, 33.50000000000000 } /* LMi */ 7250 , { 0, 0.71666666666667, 1.40833333333333, 33.00000000000000 } /* And */ 7251 , { 8, 15.18333333333333, 15.43333333333333, 33.00000000000000 } /* Boo */ 7252 , { 0, 23.50000000000000, 23.75000000000000, 32.08333333333334 } /* And */ 7253 , { 29, 12.33333333333333, 13.25000000000000, 32.00000000000000 } /* CVn */ 7254 , { 0, 23.75000000000000, 24.00000000000000, 31.33333333333333 } /* And */ 7255 , { 29, 13.95833333333333, 14.03333333333333, 30.75000000000000 } /* CVn */ 7256 , { 80, 2.41666666666667, 2.71666666666667, 30.66666666666667 } /* Tri */ 7257 , { 62, 2.71666666666667, 4.50000000000000, 30.66666666666667 } /* Per */ 7258 , { 7, 4.50000000000000, 4.75000000000000, 30.00000000000000 } /* Aur */ 7259 , { 51, 18.17500000000000, 19.35833333333333, 30.00000000000000 } /* Lyr */ 7260 , { 82, 11.00000000000000, 12.00000000000000, 29.00000000000000 } /* UMa */ 7261 , { 30, 19.66666666666667, 20.91666666666667, 29.00000000000000 } /* Cyg */ 7262 , { 7, 4.75000000000000, 5.88333333333333, 28.50000000000000 } /* Aur */ 7263 , { 48, 9.88333333333333, 10.50000000000000, 28.50000000000000 } /* LMi */ 7264 , { 29, 13.25000000000000, 13.95833333333333, 28.50000000000000 } /* CVn */ 7265 , { 0, 0.00000000000000, 0.06666666666667, 28.00000000000000 } /* And */ 7266 , { 80, 1.40833333333333, 1.66666666666667, 28.00000000000000 } /* Tri */ 7267 , { 7, 5.88333333333333, 6.53333333333333, 28.00000000000000 } /* Aur */ 7268 , { 37, 7.88333333333333, 8.00000000000000, 28.00000000000000 } /* Gem */ 7269 , { 30, 20.91666666666667, 21.73333333333333, 28.00000000000000 } /* Cyg */ 7270 , { 30, 19.25833333333333, 19.66666666666667, 27.50000000000000 } /* Cyg */ 7271 , { 80, 1.91666666666667, 2.41666666666667, 27.25000000000000 } /* Tri */ 7272 , { 25, 16.16666666666667, 16.33333333333333, 27.00000000000000 } /* CrB */ 7273 , { 8, 15.08333333333333, 15.18333333333333, 26.00000000000000 } /* Boo */ 7274 , { 25, 15.18333333333333, 16.16666666666667, 26.00000000000000 } /* CrB */ 7275 , { 51, 18.36666666666667, 18.86666666666667, 26.00000000000000 } /* Lyr */ 7276 , { 48, 10.75000000000000, 11.00000000000000, 25.50000000000000 } /* LMi */ 7277 , { 51, 18.86666666666667, 19.25833333333333, 25.50000000000000 } /* Lyr */ 7278 , { 80, 1.66666666666667, 1.91666666666667, 25.00000000000000 } /* Tri */ 7279 , { 66, 0.71666666666667, 0.85000000000000, 23.75000000000000 } /* Psc */ 7280 , { 48, 10.50000000000000, 10.75000000000000, 23.50000000000000 } /* LMi */ 7281 , { 87, 21.25000000000000, 21.41666666666667, 23.50000000000000 } /* Vul */ 7282 , { 77, 5.70000000000000, 5.88333333333333, 22.83333333333333 } /* Tau */ 7283 , { 0, 0.06666666666667, 0.14166666666667, 22.00000000000000 } /* And */ 7284 , { 73, 15.91666666666667, 16.03333333333333, 22.00000000000000 } /* Ser */ 7285 , { 37, 5.88333333333333, 6.21666666666667, 21.50000000000000 } /* Gem */ 7286 , { 87, 19.83333333333333, 20.25000000000000, 21.25000000000000 } /* Vul */ 7287 , { 87, 18.86666666666667, 19.25000000000000, 21.08333333333333 } /* Vul */ 7288 , { 0, 0.14166666666667, 0.85000000000000, 21.00000000000000 } /* And */ 7289 , { 87, 20.25000000000000, 20.56666666666667, 20.50000000000000 } /* Vul */ 7290 , { 37, 7.80833333333333, 7.88333333333333, 20.00000000000000 } /* Gem */ 7291 , { 87, 20.56666666666667, 21.25000000000000, 19.50000000000000 } /* Vul */ 7292 , { 87, 19.25000000000000, 19.83333333333333, 19.16666666666667 } /* Vul */ 7293 , { 6, 3.28333333333333, 3.36666666666667, 19.00000000000000 } /* Ari */ 7294 , { 75, 18.86666666666667, 19.00000000000000, 18.50000000000000 } /* Sge */ 7295 , { 59, 5.70000000000000, 5.76666666666667, 18.00000000000000 } /* Ori */ 7296 , { 37, 6.21666666666667, 6.30833333333333, 17.50000000000000 } /* Gem */ 7297 , { 75, 19.00000000000000, 19.83333333333333, 16.16666666666667 } /* Sge */ 7298 , { 77, 4.96666666666667, 5.33333333333333, 16.00000000000000 } /* Tau */ 7299 , { 39, 15.91666666666667, 16.08333333333333, 16.00000000000000 } /* Her */ 7300 , { 75, 19.83333333333333, 20.25000000000000, 15.75000000000000 } /* Sge */ 7301 , { 77, 4.61666666666667, 4.96666666666667, 15.50000000000000 } /* Tau */ 7302 , { 77, 5.33333333333333, 5.60000000000000, 15.50000000000000 } /* Tau */ 7303 , { 23, 12.83333333333333, 13.50000000000000, 15.00000000000000 } /* Com */ 7304 , { 39, 17.25000000000000, 18.25000000000000, 14.33333333333333 } /* Her */ 7305 , { 23, 11.86666666666667, 12.83333333333333, 14.00000000000000 } /* Com */ 7306 , { 37, 7.50000000000000, 7.80833333333333, 13.50000000000000 } /* Gem */ 7307 , { 39, 16.75000000000000, 17.25000000000000, 12.83333333333333 } /* Her */ 7308 , { 61, 0.00000000000000, 0.14166666666667, 12.50000000000000 } /* Peg */ 7309 , { 77, 5.60000000000000, 5.76666666666667, 12.50000000000000 } /* Tau */ 7310 , { 37, 7.00000000000000, 7.50000000000000, 12.50000000000000 } /* Gem */ 7311 , { 61, 21.11666666666667, 21.33333333333333, 12.50000000000000 } /* Peg */ 7312 , { 37, 6.30833333333333, 6.93333333333333, 12.00000000000000 } /* Gem */ 7313 , { 39, 18.25000000000000, 18.86666666666667, 12.00000000000000 } /* Her */ 7314 , { 31, 20.87500000000000, 21.05000000000000, 11.83333333333333 } /* Del */ 7315 , { 61, 21.05000000000000, 21.11666666666667, 11.83333333333333 } /* Peg */ 7316 , { 45, 11.51666666666667, 11.86666666666667, 11.00000000000000 } /* Leo */ 7317 , { 59, 6.24166666666667, 6.30833333333333, 10.00000000000000 } /* Ori */ 7318 , { 37, 6.93333333333333, 7.00000000000000, 10.00000000000000 } /* Gem */ 7319 , { 21, 7.80833333333333, 7.92500000000000, 10.00000000000000 } /* Cnc */ 7320 , { 61, 23.83333333333333, 24.00000000000000, 10.00000000000000 } /* Peg */ 7321 , { 6, 1.66666666666667, 3.28333333333333, 9.91666666666667 } /* Ari */ 7322 , { 31, 20.14166666666667, 20.30000000000000, 8.50000000000000 } /* Del */ 7323 , { 8, 13.50000000000000, 15.08333333333333, 8.00000000000000 } /* Boo */ 7324 , { 61, 22.75000000000000, 23.83333333333333, 7.50000000000000 } /* Peg */ 7325 , { 21, 7.92500000000000, 9.25000000000000, 7.00000000000000 } /* Cnc */ 7326 , { 45, 9.25000000000000, 10.75000000000000, 7.00000000000000 } /* Leo */ 7327 , { 58, 18.25000000000000, 18.66222222222222, 6.25000000000000 } /* Oph */ 7328 , { 3, 18.66222222222222, 18.86666666666667, 6.25000000000000 } /* Aql */ 7329 , { 31, 20.83333333333333, 20.87500000000000, 6.00000000000000 } /* Del */ 7330 , { 20, 7.00000000000000, 7.01666666666667, 5.50000000000000 } /* CMi */ 7331 , { 73, 18.25000000000000, 18.42500000000000, 4.50000000000000 } /* Ser */ 7332 , { 39, 16.08333333333333, 16.75000000000000, 4.00000000000000 } /* Her */ 7333 , { 58, 18.25000000000000, 18.42500000000000, 3.00000000000000 } /* Oph */ 7334 , { 61, 21.46666666666667, 21.66666666666667, 2.75000000000000 } /* Peg */ 7335 , { 66, 0.00000000000000, 2.00000000000000, 2.00000000000000 } /* Psc */ 7336 , { 73, 18.58333333333333, 18.86666666666667, 2.00000000000000 } /* Ser */ 7337 , { 31, 20.30000000000000, 20.83333333333333, 2.00000000000000 } /* Del */ 7338 , { 34, 20.83333333333333, 21.33333333333333, 2.00000000000000 } /* Equ */ 7339 , { 61, 21.33333333333333, 21.46666666666667, 2.00000000000000 } /* Peg */ 7340 , { 61, 22.00000000000000, 22.75000000000000, 2.00000000000000 } /* Peg */ 7341 , { 61, 21.66666666666667, 22.00000000000000, 1.75000000000000 } /* Peg */ 7342 , { 20, 7.01666666666667, 7.20000000000000, 1.50000000000000 } /* CMi */ 7343 , { 77, 3.58333333333333, 4.61666666666667, 0.00000000000000 } /* Tau */ 7344 , { 59, 4.61666666666667, 4.66666666666667, 0.00000000000000 } /* Ori */ 7345 , { 20, 7.20000000000000, 8.08333333333333, 0.00000000000000 } /* CMi */ 7346 , { 85, 14.66666666666667, 15.08333333333333, 0.00000000000000 } /* Vir */ 7347 , { 58, 17.83333333333333, 18.25000000000000, 0.00000000000000 } /* Oph */ 7348 , { 16, 2.65000000000000, 3.28333333333333, -1.75000000000000 } /* Cet */ 7349 , { 77, 3.28333333333333, 3.58333333333333, -1.75000000000000 } /* Tau */ 7350 , { 73, 15.08333333333333, 16.26666666666667, -3.25000000000000 } /* Ser */ 7351 , { 59, 4.66666666666667, 5.08333333333333, -4.00000000000000 } /* Ori */ 7352 , { 59, 5.83333333333333, 6.24166666666667, -4.00000000000000 } /* Ori */ 7353 , { 73, 17.83333333333333, 17.96666666666667, -4.00000000000000 } /* Ser */ 7354 , { 73, 18.25000000000000, 18.58333333333333, -4.00000000000000 } /* Ser */ 7355 , { 3, 18.58333333333333, 18.86666666666667, -4.00000000000000 } /* Aql */ 7356 , { 66, 22.75000000000000, 23.83333333333333, -4.00000000000000 } /* Psc */ 7357 , { 45, 10.75000000000000, 11.51666666666667, -6.00000000000000 } /* Leo */ 7358 , { 85, 11.51666666666667, 11.83333333333333, -6.00000000000000 } /* Vir */ 7359 , { 66, 0.00000000000000, 0.33333333333333, -7.00000000000000 } /* Psc */ 7360 , { 66, 23.83333333333333, 24.00000000000000, -7.00000000000000 } /* Psc */ 7361 , { 85, 14.25000000000000, 14.66666666666667, -8.00000000000000 } /* Vir */ 7362 , { 58, 15.91666666666667, 16.26666666666667, -8.00000000000000 } /* Oph */ 7363 , { 3, 20.00000000000000, 20.53333333333333, -9.00000000000000 } /* Aql */ 7364 , { 4, 21.33333333333333, 21.86666666666667, -9.00000000000000 } /* Aqr */ 7365 , { 58, 17.16666666666667, 17.96666666666667, -10.00000000000000 } /* Oph */ 7366 , { 54, 5.83333333333333, 8.08333333333333, -11.00000000000000 } /* Mon */ 7367 , { 35, 4.91666666666667, 5.08333333333333, -11.00000000000000 } /* Eri */ 7368 , { 59, 5.08333333333333, 5.83333333333333, -11.00000000000000 } /* Ori */ 7369 , { 41, 8.08333333333333, 8.36666666666667, -11.00000000000000 } /* Hya */ 7370 , { 74, 9.58333333333333, 10.75000000000000, -11.00000000000000 } /* Sex */ 7371 , { 85, 11.83333333333333, 12.83333333333333, -11.00000000000000 } /* Vir */ 7372 , { 58, 17.58333333333333, 17.66666666666667, -11.66666666666667 } /* Oph */ 7373 , { 3, 18.86666666666667, 20.00000000000000, -12.03333333333333 } /* Aql */ 7374 , { 35, 4.83333333333333, 4.91666666666667, -14.50000000000000 } /* Eri */ 7375 , { 4, 20.53333333333333, 21.33333333333333, -15.00000000000000 } /* Aqr */ 7376 , { 73, 17.16666666666667, 18.25000000000000, -16.00000000000000 } /* Ser */ 7377 , { 72, 18.25000000000000, 18.86666666666667, -16.00000000000000 } /* Sct */ 7378 , { 41, 8.36666666666667, 8.58333333333333, -17.00000000000000 } /* Hya */ 7379 , { 58, 16.26666666666667, 16.37500000000000, -18.25000000000000 } /* Oph */ 7380 , { 41, 8.58333333333333, 9.08333333333333, -19.00000000000000 } /* Hya */ 7381 , { 26, 10.75000000000000, 10.83333333333333, -19.00000000000000 } /* Crt */ 7382 , { 71, 16.26666666666667, 16.37500000000000, -19.25000000000000 } /* Sco */ 7383 , { 47, 15.66666666666667, 15.91666666666667, -20.00000000000000 } /* Lib */ 7384 , { 28, 12.58333333333333, 12.83333333333333, -22.00000000000000 } /* Crv */ 7385 , { 85, 12.83333333333333, 14.25000000000000, -22.00000000000000 } /* Vir */ 7386 , { 41, 9.08333333333333, 9.75000000000000, -24.00000000000000 } /* Hya */ 7387 , { 16, 1.66666666666667, 2.65000000000000, -24.38333333333333 } /* Cet */ 7388 , { 35, 2.65000000000000, 3.75000000000000, -24.38333333333333 } /* Eri */ 7389 , { 26, 10.83333333333333, 11.83333333333333, -24.50000000000000 } /* Crt */ 7390 , { 28, 11.83333333333333, 12.58333333333333, -24.50000000000000 } /* Crv */ 7391 , { 47, 14.25000000000000, 14.91666666666667, -24.50000000000000 } /* Lib */ 7392 , { 58, 16.26666666666667, 16.75000000000000, -24.58333333333333 } /* Oph */ 7393 , { 16, 0.00000000000000, 1.66666666666667, -25.50000000000000 } /* Cet */ 7394 , { 11, 21.33333333333333, 21.86666666666667, -25.50000000000000 } /* Cap */ 7395 , { 4, 21.86666666666667, 23.83333333333333, -25.50000000000000 } /* Aqr */ 7396 , { 16, 23.83333333333333, 24.00000000000000, -25.50000000000000 } /* Cet */ 7397 , { 41, 9.75000000000000, 10.25000000000000, -26.50000000000000 } /* Hya */ 7398 , { 35, 4.70000000000000, 4.83333333333333, -27.25000000000000 } /* Eri */ 7399 , { 46, 4.83333333333333, 6.11666666666667, -27.25000000000000 } /* Lep */ 7400 , { 11, 20.00000000000000, 21.33333333333333, -28.00000000000000 } /* Cap */ 7401 , { 41, 10.25000000000000, 10.58333333333333, -29.16666666666667 } /* Hya */ 7402 , { 41, 12.58333333333333, 14.91666666666667, -29.50000000000000 } /* Hya */ 7403 , { 47, 14.91666666666667, 15.66666666666667, -29.50000000000000 } /* Lib */ 7404 , { 71, 15.66666666666667, 16.00000000000000, -29.50000000000000 } /* Sco */ 7405 , { 35, 4.58333333333333, 4.70000000000000, -30.00000000000000 } /* Eri */ 7406 , { 58, 16.75000000000000, 17.60000000000000, -30.00000000000000 } /* Oph */ 7407 , { 76, 17.60000000000000, 17.83333333333333, -30.00000000000000 } /* Sgr */ 7408 , { 41, 10.58333333333333, 10.83333333333333, -31.16666666666667 } /* Hya */ 7409 , { 19, 6.11666666666667, 7.36666666666667, -33.00000000000000 } /* CMa */ 7410 , { 41, 12.25000000000000, 12.58333333333333, -33.00000000000000 } /* Hya */ 7411 , { 41, 10.83333333333333, 12.25000000000000, -35.00000000000000 } /* Hya */ 7412 , { 36, 3.50000000000000, 3.75000000000000, -36.00000000000000 } /* For */ 7413 , { 68, 8.36666666666667, 9.36666666666667, -36.75000000000000 } /* Pyx */ 7414 , { 35, 4.26666666666667, 4.58333333333333, -37.00000000000000 } /* Eri */ 7415 , { 76, 17.83333333333333, 19.16666666666667, -37.00000000000000 } /* Sgr */ 7416 , { 65, 21.33333333333333, 23.00000000000000, -37.00000000000000 } /* PsA */ 7417 , { 70, 23.00000000000000, 23.33333333333333, -37.00000000000000 } /* Scl */ 7418 , { 36, 3.00000000000000, 3.50000000000000, -39.58333333333334 } /* For */ 7419 , { 1, 9.36666666666667, 11.00000000000000, -39.75000000000000 } /* Ant */ 7420 , { 70, 0.00000000000000, 1.66666666666667, -40.00000000000000 } /* Scl */ 7421 , { 36, 1.66666666666667, 3.00000000000000, -40.00000000000000 } /* For */ 7422 , { 35, 3.86666666666667, 4.26666666666667, -40.00000000000000 } /* Eri */ 7423 , { 70, 23.33333333333333, 24.00000000000000, -40.00000000000000 } /* Scl */ 7424 , { 14, 14.16666666666667, 14.91666666666667, -42.00000000000000 } /* Cen */ 7425 , { 49, 15.66666666666667, 16.00000000000000, -42.00000000000000 } /* Lup */ 7426 , { 71, 16.00000000000000, 16.42083333333333, -42.00000000000000 } /* Sco */ 7427 , { 9, 4.83333333333333, 5.00000000000000, -43.00000000000000 } /* Cae */ 7428 , { 22, 5.00000000000000, 6.58333333333333, -43.00000000000000 } /* Col */ 7429 , { 67, 8.00000000000000, 8.36666666666667, -43.00000000000000 } /* Pup */ 7430 , { 35, 3.41666666666667, 3.86666666666667, -44.00000000000000 } /* Eri */ 7431 , { 71, 16.42083333333333, 17.83333333333333, -45.50000000000000 } /* Sco */ 7432 , { 24, 17.83333333333333, 19.16666666666667, -45.50000000000000 } /* CrA */ 7433 , { 76, 19.16666666666667, 20.33333333333333, -45.50000000000000 } /* Sgr */ 7434 , { 53, 20.33333333333333, 21.33333333333333, -45.50000000000000 } /* Mic */ 7435 , { 35, 3.00000000000000, 3.41666666666667, -46.00000000000000 } /* Eri */ 7436 , { 9, 4.50000000000000, 4.83333333333333, -46.50000000000000 } /* Cae */ 7437 , { 49, 15.33333333333333, 15.66666666666667, -48.00000000000000 } /* Lup */ 7438 , { 63, 0.00000000000000, 2.33333333333333, -48.16666666666666 } /* Phe */ 7439 , { 35, 2.66666666666667, 3.00000000000000, -49.00000000000000 } /* Eri */ 7440 , { 40, 4.08333333333333, 4.26666666666667, -49.00000000000000 } /* Hor */ 7441 , { 9, 4.26666666666667, 4.50000000000000, -49.00000000000000 } /* Cae */ 7442 , { 38, 21.33333333333333, 22.00000000000000, -50.00000000000000 } /* Gru */ 7443 , { 67, 6.00000000000000, 8.00000000000000, -50.75000000000000 } /* Pup */ 7444 , { 84, 8.00000000000000, 8.16666666666667, -50.75000000000000 } /* Vel */ 7445 , { 35, 2.41666666666667, 2.66666666666667, -51.00000000000000 } /* Eri */ 7446 , { 40, 3.83333333333333, 4.08333333333333, -51.00000000000000 } /* Hor */ 7447 , { 63, 0.00000000000000, 1.83333333333333, -51.50000000000000 } /* Phe */ 7448 , { 12, 6.00000000000000, 6.16666666666667, -52.50000000000000 } /* Car */ 7449 , { 84, 8.16666666666667, 8.45000000000000, -53.00000000000000 } /* Vel */ 7450 , { 40, 3.50000000000000, 3.83333333333333, -53.16666666666666 } /* Hor */ 7451 , { 32, 3.83333333333333, 4.00000000000000, -53.16666666666666 } /* Dor */ 7452 , { 63, 0.00000000000000, 1.58333333333333, -53.50000000000000 } /* Phe */ 7453 , { 35, 2.16666666666667, 2.41666666666667, -54.00000000000000 } /* Eri */ 7454 , { 64, 4.50000000000000, 5.00000000000000, -54.00000000000000 } /* Pic */ 7455 , { 49, 15.05000000000000, 15.33333333333333, -54.00000000000000 } /* Lup */ 7456 , { 84, 8.45000000000000, 8.83333333333333, -54.50000000000000 } /* Vel */ 7457 , { 12, 6.16666666666667, 6.50000000000000, -55.00000000000000 } /* Car */ 7458 , { 14, 11.83333333333333, 12.83333333333333, -55.00000000000000 } /* Cen */ 7459 , { 49, 14.16666666666667, 15.05000000000000, -55.00000000000000 } /* Lup */ 7460 , { 56, 15.05000000000000, 15.33333333333333, -55.00000000000000 } /* Nor */ 7461 , { 32, 4.00000000000000, 4.33333333333333, -56.50000000000000 } /* Dor */ 7462 , { 84, 8.83333333333333, 11.00000000000000, -56.50000000000000 } /* Vel */ 7463 , { 14, 11.00000000000000, 11.25000000000000, -56.50000000000000 } /* Cen */ 7464 , { 5, 17.50000000000000, 18.00000000000000, -57.00000000000000 } /* Ara */ 7465 , { 78, 18.00000000000000, 20.33333333333333, -57.00000000000000 } /* Tel */ 7466 , { 38, 22.00000000000000, 23.33333333333333, -57.00000000000000 } /* Gru */ 7467 , { 40, 3.20000000000000, 3.50000000000000, -57.50000000000000 } /* Hor */ 7468 , { 64, 5.00000000000000, 5.50000000000000, -57.50000000000000 } /* Pic */ 7469 , { 12, 6.50000000000000, 6.83333333333333, -58.00000000000000 } /* Car */ 7470 , { 63, 0.00000000000000, 1.33333333333333, -58.50000000000000 } /* Phe */ 7471 , { 35, 1.33333333333333, 2.16666666666667, -58.50000000000000 } /* Eri */ 7472 , { 63, 23.33333333333333, 24.00000000000000, -58.50000000000000 } /* Phe */ 7473 , { 32, 4.33333333333333, 4.58333333333333, -59.00000000000000 } /* Dor */ 7474 , { 56, 15.33333333333333, 16.42083333333333, -60.00000000000000 } /* Nor */ 7475 , { 43, 20.33333333333333, 21.33333333333333, -60.00000000000000 } /* Ind */ 7476 , { 64, 5.50000000000000, 6.00000000000000, -61.00000000000000 } /* Pic */ 7477 , { 18, 15.16666666666667, 15.33333333333333, -61.00000000000000 } /* Cir */ 7478 , { 5, 16.42083333333333, 16.58333333333333, -61.00000000000000 } /* Ara */ 7479 , { 18, 14.91666666666667, 15.16666666666667, -63.58333333333334 } /* Cir */ 7480 , { 5, 16.58333333333333, 16.75000000000000, -63.58333333333334 } /* Ara */ 7481 , { 64, 6.00000000000000, 6.83333333333333, -64.00000000000000 } /* Pic */ 7482 , { 12, 6.83333333333333, 9.03333333333333, -64.00000000000000 } /* Car */ 7483 , { 14, 11.25000000000000, 11.83333333333333, -64.00000000000000 } /* Cen */ 7484 , { 27, 11.83333333333333, 12.83333333333333, -64.00000000000000 } /* Cru */ 7485 , { 14, 12.83333333333333, 14.53333333333333, -64.00000000000000 } /* Cen */ 7486 , { 18, 13.50000000000000, 13.66666666666667, -65.00000000000000 } /* Cir */ 7487 , { 5, 16.75000000000000, 16.83333333333333, -65.00000000000000 } /* Ara */ 7488 , { 40, 2.16666666666667, 3.20000000000000, -67.50000000000000 } /* Hor */ 7489 , { 69, 3.20000000000000, 4.58333333333333, -67.50000000000000 } /* Ret */ 7490 , { 18, 14.75000000000000, 14.91666666666667, -67.50000000000000 } /* Cir */ 7491 , { 5, 16.83333333333333, 17.50000000000000, -67.50000000000000 } /* Ara */ 7492 , { 60, 17.50000000000000, 18.00000000000000, -67.50000000000000 } /* Pav */ 7493 , { 81, 22.00000000000000, 23.33333333333333, -67.50000000000000 } /* Tuc */ 7494 , { 32, 4.58333333333333, 6.58333333333333, -70.00000000000000 } /* Dor */ 7495 , { 18, 13.66666666666667, 14.75000000000000, -70.00000000000000 } /* Cir */ 7496 , { 79, 14.75000000000000, 17.00000000000000, -70.00000000000000 } /* TrA */ 7497 , { 81, 0.00000000000000, 1.33333333333333, -75.00000000000000 } /* Tuc */ 7498 , { 42, 3.50000000000000, 4.58333333333333, -75.00000000000000 } /* Hyi */ 7499 , { 86, 6.58333333333333, 9.03333333333333, -75.00000000000000 } /* Vol */ 7500 , { 12, 9.03333333333333, 11.25000000000000, -75.00000000000000 } /* Car */ 7501 , { 55, 11.25000000000000, 13.66666666666667, -75.00000000000000 } /* Mus */ 7502 , { 60, 18.00000000000000, 21.33333333333333, -75.00000000000000 } /* Pav */ 7503 , { 43, 21.33333333333333, 23.33333333333333, -75.00000000000000 } /* Ind */ 7504 , { 81, 23.33333333333333, 24.00000000000000, -75.00000000000000 } /* Tuc */ 7505 , { 81, 0.75000000000000, 1.33333333333333, -76.00000000000000 } /* Tuc */ 7506 , { 42, 0.00000000000000, 3.50000000000000, -82.50000000000000 } /* Hyi */ 7507 , { 17, 7.66666666666667, 13.66666666666667, -82.50000000000000 } /* Cha */ 7508 , { 2, 13.66666666666667, 18.00000000000000, -82.50000000000000 } /* Aps */ 7509 , { 52, 3.50000000000000, 7.66666666666667, -85.00000000000000 } /* Men */ 7510 , { 57, 0.00000000000000, 24.00000000000000, -90.00000000000000 } /* Oct */ 7511 }; 7512 7513 #define NUM_CONSTEL_BOUNDARIES 357 7514 7515 7516 7517 /** 7518 * @brief 7519 * Determines the constellation that contains the given point in the sky. 7520 * 7521 * Given J2000 equatorial (EQJ) coordinates of a point in the sky, determines the 7522 * constellation that contains that point. 7523 * 7524 * @param ra 7525 * The right ascension (RA) of a point in the sky, using the J2000 equatorial system. 7526 * 7527 * @param dec 7528 * The declination (DEC) of a point in the sky, using the J2000 equatorial system. 7529 * 7530 * @return 7531 * If successful, `status` holds `ASTRO_SUCCESS`, 7532 * `symbol` holds a pointer to a 3-character string like "Ori", and 7533 * `name` holds a pointer to the full constellation name like "Orion". 7534 */ 7535 astro_constellation_t Astronomy_Constellation(double ra, double dec) 7536 { 7537 static astro_time_t epoch2000; 7538 static astro_rotation_t rot = { ASTRO_NOT_INITIALIZED }; 7539 astro_constellation_t constel; 7540 astro_equatorial_t j2000, b1875; 7541 astro_vector_t vec2000, vec1875; 7542 int i, c; 7543 7544 if (dec < -90.0 || dec > +90.0) 7545 return ConstelErr(ASTRO_INVALID_PARAMETER); 7546 7547 /* Allow right ascension to "wrap around". Clamp to [0, 24) sidereal hours. */ 7548 ra = fmod(ra, 24.0); 7549 if (ra < 0.0) 7550 ra += 24.0; 7551 7552 /* Lazy-initialize the rotation matrix for converting J2000 to B1875. */ 7553 if (rot.status != ASTRO_SUCCESS) 7554 { 7555 /* 7556 Need to calculate the B1875 epoch. Based on this: 7557 https://en.wikipedia.org/wiki/Epoch_(astronomy)#Besselian_years 7558 B = 1900 + (JD - 2415020.31352) / 365.242198781 7559 I'm interested in using TT instead of JD, giving: 7560 B = 1900 + ((TT+2451545) - 2415020.31352) / 365.242198781 7561 B = 1900 + (TT + 36524.68648) / 365.242198781 7562 TT = 365.242198781*(B - 1900) - 36524.68648 = -45655.741449525 7563 But Astronomy_TimeFromDays() wants UT, not TT. 7564 Near that date, I get a historical correction of ut-tt = 3.2 seconds. 7565 That gives UT = -45655.74141261017 for the B1875 epoch, 7566 or 1874-12-31T18:12:21.950Z. 7567 */ 7568 astro_time_t time = Astronomy_TimeFromDays(-45655.74141261017); 7569 rot = Astronomy_Rotation_EQJ_EQD(time); 7570 if (rot.status != ASTRO_SUCCESS) 7571 return ConstelErr(rot.status); 7572 7573 epoch2000 = Astronomy_TimeFromDays(0.0); 7574 } 7575 7576 /* Convert coordinates from J2000 to year 1875. */ 7577 j2000.status = ASTRO_SUCCESS; 7578 j2000.ra = ra; 7579 j2000.dec = dec; 7580 j2000.dist = 1.0; 7581 vec2000 = Astronomy_VectorFromEquator(j2000, epoch2000); 7582 if (vec2000.status != ASTRO_SUCCESS) 7583 return ConstelErr(vec2000.status); 7584 7585 vec1875 = Astronomy_RotateVector(rot, vec2000); 7586 if (vec1875.status != ASTRO_SUCCESS) 7587 return ConstelErr(vec1875.status); 7588 7589 b1875 = Astronomy_EquatorFromVector(vec1875); 7590 if (b1875.status != ASTRO_SUCCESS) 7591 return ConstelErr(b1875.status); 7592 7593 /* Search for the constellation using the B1875 coordinates. */ 7594 c = -1; /* constellation not (yet) found */ 7595 for (i=0; i < NUM_CONSTEL_BOUNDARIES; ++i) 7596 { 7597 const constel_boundary_t *b = &ConstelBounds[i]; 7598 if ((b->dec_lo <= b1875.dec) && (b->ra_hi > b1875.ra) && (b->ra_lo <= b1875.ra)) 7599 { 7600 c = b->index; 7601 break; 7602 } 7603 } 7604 7605 if (c < 0 || c >= NUM_CONSTELLATIONS) 7606 return ConstelErr(ASTRO_INTERNAL_ERROR); /* should have been able to find the constellation */ 7607 7608 constel.status = ASTRO_SUCCESS; 7609 constel.symbol = ConstelInfo[c].symbol; 7610 constel.name = ConstelInfo[c].name; 7611 constel.ra_1875 = b1875.ra; 7612 constel.dec_1875 = b1875.dec; 7613 return constel; 7614 } 7615 7616 7617 static astro_lunar_eclipse_t LunarEclipseError(astro_status_t status) 7618 { 7619 astro_lunar_eclipse_t eclipse; 7620 eclipse.status = status; 7621 eclipse.kind = ECLIPSE_NONE; 7622 eclipse.peak = TimeError(); 7623 eclipse.sd_penum = eclipse.sd_partial = eclipse.sd_total = NAN; 7624 return eclipse; 7625 } 7626 7627 7628 /** @cond DOXYGEN_SKIP */ 7629 typedef struct 7630 { 7631 astro_status_t status; 7632 astro_time_t time; 7633 double u; /* dot product of (heliocentric earth) and (geocentric moon): defines the shadow plane where the Moon is */ 7634 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). */ 7635 double k; /* umbra radius in km, at the shadow plane */ 7636 double p; /* penumbra radius in km, at the shadow plane */ 7637 astro_vector_t target; /* coordinates of target body relative to shadow-casting body at 'time' */ 7638 astro_vector_t dir; /* heliocentric coordinates of shadow-casting body at 'time' */ 7639 } 7640 shadow_t; /* Represents alignment of the Moon/Earth with the Earth's/Moon's shadow, for finding eclipses. */ 7641 7642 typedef struct 7643 { 7644 double radius_limit; 7645 double direction; 7646 } 7647 shadow_context_t; 7648 /** @endcond */ 7649 7650 7651 static shadow_t ShadowError(astro_status_t status) 7652 { 7653 shadow_t shadow; 7654 memset(&shadow, 0, sizeof(shadow)); 7655 shadow.status = status; 7656 return shadow; 7657 } 7658 7659 7660 static shadow_t CalcShadow( 7661 double body_radius_km, 7662 astro_time_t time, 7663 astro_vector_t target, 7664 astro_vector_t dir) 7665 { 7666 double dx, dy, dz; 7667 shadow_t shadow; 7668 7669 shadow.target = target; 7670 shadow.dir = dir; 7671 7672 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); 7673 7674 dx = (shadow.u * dir.x) - target.x; 7675 dy = (shadow.u * dir.y) - target.y; 7676 dz = (shadow.u * dir.z) - target.z; 7677 shadow.r = KM_PER_AU * sqrt(dx*dx + dy*dy + dz*dz); 7678 7679 shadow.k = +SUN_RADIUS_KM - (1.0 + shadow.u)*(SUN_RADIUS_KM - body_radius_km); 7680 shadow.p = -SUN_RADIUS_KM + (1.0 + shadow.u)*(SUN_RADIUS_KM + body_radius_km); 7681 shadow.status = ASTRO_SUCCESS; 7682 shadow.time = time; 7683 7684 return shadow; 7685 } 7686 7687 7688 static shadow_t PlanetShadow(astro_body_t body, double planet_radius_km, astro_time_t time) 7689 { 7690 astro_vector_t e, p, g; 7691 7692 /* Calculate light-travel-corrected vector from Earth to planet. */ 7693 g = Astronomy_GeoVector(body, time, NO_ABERRATION); 7694 if (g.status != ASTRO_SUCCESS) 7695 return ShadowError(g.status); 7696 7697 /* Calculate light-travel-corrected vector from Earth to Sun. */ 7698 e = Astronomy_GeoVector(BODY_SUN, time, NO_ABERRATION); 7699 if (e.status != ASTRO_SUCCESS) 7700 return ShadowError(e.status); 7701 7702 /* Deduce light-travel-corrected vector from Sun to planet. */ 7703 p.t = time; 7704 p.x = g.x - e.x; 7705 p.y = g.y - e.y; 7706 p.z = g.z - e.z; 7707 7708 /* Calcluate Earth's position from the planet's point of view. */ 7709 e.x = -g.x; 7710 e.y = -g.y; 7711 e.z = -g.z; 7712 7713 return CalcShadow(planet_radius_km, time, e, p); 7714 } 7715 7716 7717 static shadow_t EarthShadow(astro_time_t time) 7718 { 7719 /* This function helps find when the Earth's shadow falls upon the Moon. */ 7720 astro_vector_t e, m; 7721 7722 e = CalcEarth(time); /* This function never fails; no need to check return value */ 7723 m = Astronomy_GeoMoon(time); /* This function never fails; no need to check return value */ 7724 7725 return CalcShadow(EARTH_ECLIPSE_RADIUS_KM, time, m, e); 7726 } 7727 7728 7729 static shadow_t MoonShadow(astro_time_t time) 7730 { 7731 /* This function helps find when the Moon's shadow falls upon the Earth. */ 7732 7733 astro_vector_t h, e, m; 7734 7735 /* 7736 This is a variation on the logic in EarthShadow(). 7737 Instead of a heliocentric Earth and a geocentric Moon, 7738 we want a heliocentric Moon and a lunacentric Earth. 7739 */ 7740 7741 h = CalcEarth(time); /* heliocentric Earth */ 7742 m = Astronomy_GeoMoon(time); /* geocentric Moon */ 7743 7744 /* Calculate lunacentric Earth. */ 7745 e.status = m.status; 7746 e.x = -m.x; 7747 e.y = -m.y; 7748 e.z = -m.z; 7749 e.t = m.t; 7750 7751 /* Convert geocentric moon to heliocentric Moon. */ 7752 m.x += h.x; 7753 m.y += h.y; 7754 m.z += h.z; 7755 7756 return CalcShadow(MOON_MEAN_RADIUS_KM, time, e, m); 7757 } 7758 7759 7760 /** @cond DOXYGEN_SKIP */ 7761 typedef shadow_t (* shadow_func_t) (astro_time_t time); 7762 /** @endcond */ 7763 7764 7765 static astro_func_result_t shadow_distance_slope(void *context, astro_time_t time) 7766 { 7767 const double dt = 1.0 / 86400.0; 7768 astro_time_t t1, t2; 7769 astro_func_result_t result; 7770 shadow_t shadow1, shadow2; 7771 shadow_func_t shadowfunc = context; 7772 7773 t1 = Astronomy_AddDays(time, -dt); 7774 t2 = Astronomy_AddDays(time, +dt); 7775 7776 shadow1 = shadowfunc(t1); 7777 if (shadow1.status != ASTRO_SUCCESS) 7778 return FuncError(shadow1.status); 7779 7780 shadow2 = shadowfunc(t2); 7781 if (shadow2.status != ASTRO_SUCCESS) 7782 return FuncError(shadow2.status); 7783 7784 result.value = (shadow2.r - shadow1.r) / dt; 7785 result.status = ASTRO_SUCCESS; 7786 return result; 7787 } 7788 7789 7790 static shadow_t PeakEarthShadow(astro_time_t search_center_time) 7791 { 7792 /* Search for when the Earth's shadow axis is closest to the center of the Moon. */ 7793 7794 astro_time_t t1, t2; 7795 astro_search_result_t result; 7796 const double window = 0.03; /* days before/after full moon to search for minimum shadow distance */ 7797 7798 t1 = Astronomy_AddDays(search_center_time, -window); 7799 t2 = Astronomy_AddDays(search_center_time, +window); 7800 7801 result = Astronomy_Search(shadow_distance_slope, EarthShadow, t1, t2, 1.0); 7802 if (result.status != ASTRO_SUCCESS) 7803 return ShadowError(result.status); 7804 7805 return EarthShadow(result.time); 7806 } 7807 7808 7809 static shadow_t PeakMoonShadow(astro_time_t search_center_time) 7810 { 7811 /* Search for when the Moon's shadow axis is closest to the center of the Earth. */ 7812 7813 astro_time_t t1, t2; 7814 astro_search_result_t result; 7815 const double window = 0.03; /* days before/after new moon to search for minimum shadow distance */ 7816 7817 t1 = Astronomy_AddDays(search_center_time, -window); 7818 t2 = Astronomy_AddDays(search_center_time, +window); 7819 7820 result = Astronomy_Search(shadow_distance_slope, MoonShadow, t1, t2, 1.0); 7821 if (result.status != ASTRO_SUCCESS) 7822 return ShadowError(result.status); 7823 7824 return MoonShadow(result.time); 7825 } 7826 7827 7828 /** @cond DOXYGEN_SKIP */ 7829 typedef struct 7830 { 7831 astro_body_t body; 7832 double planet_radius_km; 7833 double direction; /* used for transit start/finish search only */ 7834 } 7835 planet_shadow_context_t; 7836 /** @endcond */ 7837 7838 7839 static astro_func_result_t planet_shadow_distance_slope(void *context, astro_time_t time) 7840 { 7841 const double dt = 1.0 / 86400.0; 7842 astro_time_t t1, t2; 7843 astro_func_result_t result; 7844 shadow_t shadow1, shadow2; 7845 const planet_shadow_context_t *p = context; 7846 7847 t1 = Astronomy_AddDays(time, -dt); 7848 t2 = Astronomy_AddDays(time, +dt); 7849 7850 shadow1 = PlanetShadow(p->body, p->planet_radius_km, t1); 7851 if (shadow1.status != ASTRO_SUCCESS) 7852 return FuncError(shadow1.status); 7853 7854 shadow2 = PlanetShadow(p->body, p->planet_radius_km, t2); 7855 if (shadow2.status != ASTRO_SUCCESS) 7856 return FuncError(shadow2.status); 7857 7858 result.value = (shadow2.r - shadow1.r) / dt; 7859 result.status = ASTRO_SUCCESS; 7860 return result; 7861 } 7862 7863 7864 static shadow_t PeakPlanetShadow(astro_body_t body, double planet_radius_km, astro_time_t search_center_time) 7865 { 7866 /* Search for when the body's shadow is closest to the center of the Earth. */ 7867 7868 astro_time_t t1, t2; 7869 astro_search_result_t result; 7870 planet_shadow_context_t context; 7871 const double window = 1.0; /* days before/after inferior conjunction to search for minimum shadow distance */ 7872 7873 t1 = Astronomy_AddDays(search_center_time, -window); 7874 t2 = Astronomy_AddDays(search_center_time, +window); 7875 7876 context.body = body; 7877 context.planet_radius_km = planet_radius_km; 7878 context.direction = 0.0; /* not used in this search */ 7879 7880 result = Astronomy_Search(planet_shadow_distance_slope, &context, t1, t2, 1.0); 7881 if (result.status != ASTRO_SUCCESS) 7882 return ShadowError(result.status); 7883 7884 return PlanetShadow(body, planet_radius_km, result.time); 7885 } 7886 7887 7888 static astro_func_result_t shadow_distance(void *context, astro_time_t time) 7889 { 7890 astro_func_result_t result; 7891 const shadow_context_t *p = context; 7892 shadow_t shadow = EarthShadow(time); 7893 if (shadow.status != ASTRO_SUCCESS) 7894 return FuncError(shadow.status); 7895 7896 result.value = p->direction * (shadow.r - p->radius_limit); 7897 result.status = ASTRO_SUCCESS; 7898 return result; 7899 } 7900 7901 7902 static double ShadowSemiDurationMinutes(astro_time_t center_time, double radius_limit, double window_minutes) 7903 { 7904 /* Search backwards and forwards from the center time until shadow axis distance crosses radius limit. */ 7905 double window = window_minutes / (24.0 * 60.0); 7906 shadow_context_t context; 7907 astro_search_result_t s1, s2; 7908 astro_time_t before, after; 7909 7910 before = Astronomy_AddDays(center_time, -window); 7911 after = Astronomy_AddDays(center_time, +window); 7912 7913 context.radius_limit = radius_limit; 7914 context.direction = -1.0; 7915 s1 = Astronomy_Search(shadow_distance, &context, before, center_time, 1.0); 7916 7917 context.direction = +1.0; 7918 s2 = Astronomy_Search(shadow_distance, &context, center_time, after, 1.0); 7919 7920 if (s1.status != ASTRO_SUCCESS || s2.status != ASTRO_SUCCESS) 7921 return -1.0; /* something went wrong! */ 7922 7923 return (s2.time.ut - s1.time.ut) * ((24.0 * 60.0) / 2.0); /* convert days to minutes and average the semi-durations. */ 7924 } 7925 7926 7927 /** 7928 * @brief Searches for a lunar eclipse. 7929 * 7930 * This function finds the first lunar eclipse that occurs after `startTime`. 7931 * A lunar eclipse may be penumbral, partial, or total. 7932 * See #astro_lunar_eclipse_t for more information. 7933 * To find a series of lunar eclipses, call this function once, 7934 * then keep calling #Astronomy_NextLunarEclipse as many times as desired, 7935 * passing in the `peak` value returned from the previous call. 7936 * 7937 * @param startTime 7938 * The date and time for starting the search for a lunar eclipse. 7939 * 7940 * @return 7941 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 7942 * and the remaining structure fields will be valid. 7943 * Any other value indicates an error. 7944 */ 7945 astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) 7946 { 7947 const double PruneLatitude = 1.8; /* full Moon's ecliptic latitude above which eclipse is impossible */ 7948 astro_time_t fmtime; 7949 astro_lunar_eclipse_t eclipse; 7950 astro_search_result_t fullmoon; 7951 shadow_t shadow; 7952 int fmcount; 7953 double eclip_lat, eclip_lon, distance; 7954 7955 /* Iterate through consecutive full moons until we find any kind of lunar eclipse. */ 7956 fmtime = startTime; 7957 for (fmcount=0; fmcount < 12; ++fmcount) 7958 { 7959 /* Search for the next full moon. Any eclipse will be near it. */ 7960 fullmoon = Astronomy_SearchMoonPhase(180.0, fmtime, 40.0); 7961 if (fullmoon.status != ASTRO_SUCCESS) 7962 return LunarEclipseError(fullmoon.status); 7963 7964 /* Pruning: if the full Moon's ecliptic latitude is too large, a lunar eclipse is not possible. */ 7965 CalcMoon(fullmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); 7966 if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) 7967 { 7968 /* Search near the full moon for the time when the center of the Moon */ 7969 /* is closest to the line passing through the centers of the Sun and Earth. */ 7970 shadow = PeakEarthShadow(fullmoon.time); 7971 if (shadow.status != ASTRO_SUCCESS) 7972 return LunarEclipseError(shadow.status); 7973 7974 if (shadow.r < shadow.p + MOON_MEAN_RADIUS_KM) 7975 { 7976 /* This is at least a penumbral eclipse. We will return a result. */ 7977 eclipse.status = ASTRO_SUCCESS; 7978 eclipse.kind = ECLIPSE_PENUMBRAL; 7979 eclipse.peak = shadow.time; 7980 eclipse.sd_total = 0.0; 7981 eclipse.sd_partial = 0.0; 7982 eclipse.sd_penum = ShadowSemiDurationMinutes(shadow.time, shadow.p + MOON_MEAN_RADIUS_KM, 200.0); 7983 if (eclipse.sd_penum <= 0.0) 7984 return LunarEclipseError(ASTRO_SEARCH_FAILURE); 7985 7986 if (shadow.r < shadow.k + MOON_MEAN_RADIUS_KM) 7987 { 7988 /* This is at least a partial eclipse. */ 7989 eclipse.kind = ECLIPSE_PARTIAL; 7990 eclipse.sd_partial = ShadowSemiDurationMinutes(shadow.time, shadow.k + MOON_MEAN_RADIUS_KM, eclipse.sd_penum); 7991 if (eclipse.sd_partial <= 0.0) 7992 return LunarEclipseError(ASTRO_SEARCH_FAILURE); 7993 7994 if (shadow.r + MOON_MEAN_RADIUS_KM < shadow.k) 7995 { 7996 /* This is a total eclipse. */ 7997 eclipse.kind = ECLIPSE_TOTAL; 7998 eclipse.sd_total = ShadowSemiDurationMinutes(shadow.time, shadow.k - MOON_MEAN_RADIUS_KM, eclipse.sd_partial); 7999 if (eclipse.sd_total <= 0.0) 8000 return LunarEclipseError(ASTRO_SEARCH_FAILURE); 8001 } 8002 } 8003 return eclipse; 8004 } 8005 } 8006 8007 /* We didn't find an eclipse on this full moon, so search for the next one. */ 8008 fmtime = Astronomy_AddDays(fullmoon.time, 10.0); 8009 } 8010 8011 /* Safety valve to prevent infinite loop. */ 8012 /* This should never happen, because at least 2 lunar eclipses happen per year. */ 8013 return LunarEclipseError(ASTRO_INTERNAL_ERROR); 8014 } 8015 8016 /** 8017 * @brief Searches for the next lunar eclipse in a series. 8018 * 8019 * After using #Astronomy_SearchLunarEclipse to find the first lunar eclipse 8020 * in a series, you can call this function to find the next consecutive lunar eclipse. 8021 * Pass in the `peak` value from the #astro_lunar_eclipse_t returned by the 8022 * previous call to `Astronomy_SearchLunarEclipse` or `Astronomy_NextLunarEclipse` 8023 * to find the next lunar eclipse. 8024 * 8025 * @param prevEclipseTime 8026 * A date and time near a full moon. Lunar eclipse search will start at the next full moon. 8027 * 8028 * @return 8029 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 8030 * and the remaining structure fields will be valid. 8031 * Any other value indicates an error. 8032 */ 8033 astro_lunar_eclipse_t Astronomy_NextLunarEclipse(astro_time_t prevEclipseTime) 8034 { 8035 astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0); 8036 return Astronomy_SearchLunarEclipse(startTime); 8037 } 8038 8039 8040 static astro_global_solar_eclipse_t GlobalSolarEclipseError(astro_status_t status) 8041 { 8042 astro_global_solar_eclipse_t eclipse; 8043 8044 eclipse.status = status; 8045 eclipse.kind = ECLIPSE_NONE; 8046 eclipse.peak = TimeError(); 8047 eclipse.distance = eclipse.latitude = eclipse.longitude = NAN; 8048 8049 return eclipse; 8050 } 8051 8052 /* The umbra radius tells us what kind of eclipse the observer sees. */ 8053 /* If the umbra radius is positive, this is a total eclipse. Otherwise, it's annular. */ 8054 /* HACK: I added a tiny bias (14 meters) to match Espenak test data. */ 8055 #define EclipseKindFromUmbra(k) (((k) > 0.014) ? ECLIPSE_TOTAL : ECLIPSE_ANNULAR) 8056 8057 static astro_global_solar_eclipse_t GeoidIntersect(shadow_t shadow) 8058 { 8059 astro_global_solar_eclipse_t eclipse; 8060 astro_rotation_t rot, inv; 8061 astro_vector_t v, e, o; 8062 shadow_t surface; 8063 double A, B, C, radic, u, R; 8064 double px, py, pz, proj; 8065 double gast; 8066 8067 eclipse.status = ASTRO_SUCCESS; 8068 eclipse.kind = ECLIPSE_PARTIAL; 8069 eclipse.peak = shadow.time; 8070 eclipse.distance = shadow.r; 8071 eclipse.latitude = eclipse.longitude = NAN; 8072 8073 /* 8074 We want to calculate the intersection of the shadow axis with the Earth's geoid. 8075 First we must convert EQJ (equator of J2000) coordinates to EQD (equator of date) 8076 coordinates that are perfectly aligned with the Earth's equator at this 8077 moment in time. 8078 */ 8079 rot = Astronomy_Rotation_EQJ_EQD(shadow.time); 8080 if (rot.status != ASTRO_SUCCESS) 8081 return GlobalSolarEclipseError(rot.status); 8082 8083 v = Astronomy_RotateVector(rot, shadow.dir); /* shadow-axis vector in equator-of-date coordinates */ 8084 if (v.status != ASTRO_SUCCESS) 8085 return GlobalSolarEclipseError(v.status); 8086 8087 e = Astronomy_RotateVector(rot, shadow.target); /* lunacentric Earth in equator-of-date coordinates */ 8088 if (e.status != ASTRO_SUCCESS) 8089 return GlobalSolarEclipseError(e.status); 8090 8091 /* 8092 Convert all distances from AU to km. 8093 But dilate the z-coordinates so that the Earth becomes a perfect sphere. 8094 Then find the intersection of the vector with the sphere. 8095 See p 184 in Montenbruck & Pfleger's "Astronomy on the Personal Computer", second edition. 8096 */ 8097 v.x *= KM_PER_AU; 8098 v.y *= KM_PER_AU; 8099 v.z *= KM_PER_AU / EARTH_FLATTENING; 8100 8101 e.x *= KM_PER_AU; 8102 e.y *= KM_PER_AU; 8103 e.z *= KM_PER_AU / EARTH_FLATTENING; 8104 8105 /* 8106 Solve the quadratic equation that finds whether and where 8107 the shadow axis intersects with the Earth in the dilated coordinate system. 8108 */ 8109 R = EARTH_EQUATORIAL_RADIUS_KM; 8110 A = v.x*v.x + v.y*v.y + v.z*v.z; 8111 B = -2.0 * (v.x*e.x + v.y*e.y + v.z*e.z); 8112 C = (e.x*e.x + e.y*e.y + e.z*e.z) - R*R; 8113 radic = B*B - 4*A*C; 8114 8115 if (radic > 0.0) 8116 { 8117 /* Calculate the closer of the two intersection points. */ 8118 /* This will be on the day side of the Earth. */ 8119 u = (-B - sqrt(radic)) / (2 * A); 8120 8121 /* Convert lunacentric dilated coordinates to geocentric coordinates. */ 8122 px = u*v.x - e.x; 8123 py = u*v.y - e.y; 8124 pz = (u*v.z - e.z) * EARTH_FLATTENING; 8125 8126 /* Convert cartesian coordinates into geodetic latitude/longitude. */ 8127 proj = sqrt(px*px + py*py) * (EARTH_FLATTENING * EARTH_FLATTENING); 8128 if (proj == 0.0) 8129 eclipse.latitude = (pz > 0.0) ? +90.0 : -90.0; 8130 else 8131 eclipse.latitude = RAD2DEG * atan(pz / proj); 8132 8133 /* Adjust longitude for Earth's rotation at the given UT. */ 8134 gast = sidereal_time(&eclipse.peak); 8135 eclipse.longitude = fmod((RAD2DEG*atan2(py, px)) - (15*gast), 360.0); 8136 if (eclipse.longitude <= -180.0) 8137 eclipse.longitude += 360.0; 8138 else if (eclipse.longitude > +180.0) 8139 eclipse.longitude -= 360.0; 8140 8141 /* We want to determine whether the observer sees a total eclipse or an annular eclipse. */ 8142 /* We need to perform a series of vector calculations... */ 8143 /* Calculate the inverse rotation matrix, so we can convert EQD to EQJ. */ 8144 inv = Astronomy_InverseRotation(rot); 8145 if (inv.status != ASTRO_SUCCESS) 8146 return GlobalSolarEclipseError(inv.status); 8147 8148 /* Put the EQD geocentric coordinates of the observer into the vector 'o'. */ 8149 /* Also convert back from kilometers to astronomical units. */ 8150 o.status = ASTRO_SUCCESS; 8151 o.t = shadow.time; 8152 o.x = px / KM_PER_AU; 8153 o.y = py / KM_PER_AU; 8154 o.z = pz / KM_PER_AU; 8155 8156 /* Rotate the observer's geocentric EQD back to the EQJ system. */ 8157 o = Astronomy_RotateVector(inv, o); 8158 8159 /* Convert geocentric vector to lunacentric vector. */ 8160 o.x += shadow.target.x; 8161 o.y += shadow.target.y; 8162 o.z += shadow.target.z; 8163 8164 /* Recalculate the shadow using a vector from the Moon's center toward the observer. */ 8165 surface = CalcShadow(MOON_POLAR_RADIUS_KM, shadow.time, o, shadow.dir); 8166 8167 /* If we did everything right, the shadow distance should be very close to zero. */ 8168 /* That's because we already determined the observer 'o' is on the shadow axis! */ 8169 if (surface.r > 1.0e-9 || surface.r < 0.0) 8170 return GlobalSolarEclipseError(ASTRO_INTERNAL_ERROR); 8171 8172 eclipse.kind = EclipseKindFromUmbra(surface.k); 8173 } 8174 8175 return eclipse; 8176 } 8177 8178 8179 /** 8180 * @brief Searches for a solar eclipse visible anywhere on the Earth's surface. 8181 * 8182 * This function finds the first solar eclipse that occurs after `startTime`. 8183 * A solar eclipse may be partial, annular, or total. 8184 * See #astro_global_solar_eclipse_t for more information. 8185 * To find a series of solar eclipses, call this function once, 8186 * then keep calling #Astronomy_NextGlobalSolarEclipse as many times as desired, 8187 * passing in the `peak` value returned from the previous call. 8188 * 8189 * @param startTime 8190 * The date and time for starting the search for a solar eclipse. 8191 * 8192 * @return 8193 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 8194 * and the remaining structure fields are as described in #astro_global_solar_eclipse_t. 8195 * Any other value indicates an error. 8196 */ 8197 astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t startTime) 8198 { 8199 const double PruneLatitude = 1.8; /* Moon's ecliptic latitude beyond which eclipse is impossible */ 8200 astro_time_t nmtime; 8201 astro_search_result_t newmoon; 8202 shadow_t shadow; 8203 int nmcount; 8204 double eclip_lat, eclip_lon, distance; 8205 8206 /* Iterate through consecutive new moons until we find a solar eclipse visible somewhere on Earth. */ 8207 nmtime = startTime; 8208 for (nmcount=0; nmcount < 12; ++nmcount) 8209 { 8210 /* Search for the next new moon. Any eclipse will be near it. */ 8211 newmoon = Astronomy_SearchMoonPhase(0.0, nmtime, 40.0); 8212 if (newmoon.status != ASTRO_SUCCESS) 8213 return GlobalSolarEclipseError(newmoon.status); 8214 8215 /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */ 8216 CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); 8217 if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) 8218 { 8219 /* Search near the new moon for the time when the center of the Earth */ 8220 /* is closest to the line passing through the centers of the Sun and Moon. */ 8221 shadow = PeakMoonShadow(newmoon.time); 8222 if (shadow.status != ASTRO_SUCCESS) 8223 return GlobalSolarEclipseError(shadow.status); 8224 8225 if (shadow.r < shadow.p + EARTH_MEAN_RADIUS_KM) 8226 { 8227 /* This is at least a partial solar eclipse visible somewhere on Earth. */ 8228 /* Try to find an intersection between the shadow axis and the Earth's oblate geoid. */ 8229 return GeoidIntersect(shadow); 8230 } 8231 } 8232 8233 /* We didn't find an eclipse on this new moon, so search for the next one. */ 8234 nmtime = Astronomy_AddDays(newmoon.time, 10.0); 8235 } 8236 8237 /* Safety valve to prevent infinite loop. */ 8238 /* This should never happen, because at least 2 solar eclipses happen per year. */ 8239 return GlobalSolarEclipseError(ASTRO_INTERNAL_ERROR); 8240 } 8241 8242 8243 /** 8244 * @brief Searches for the next global solar eclipse in a series. 8245 * 8246 * After using #Astronomy_SearchGlobalSolarEclipse to find the first solar eclipse 8247 * in a series, you can call this function to find the next consecutive solar eclipse. 8248 * Pass in the `peak` value from the #astro_global_solar_eclipse_t returned by the 8249 * previous call to `Astronomy_SearchGlobalSolarEclipse` or `Astronomy_NextGlobalSolarEclipse` 8250 * to find the next solar eclipse. 8251 * 8252 * @param prevEclipseTime 8253 * A date and time near a new moon. Solar eclipse search will start at the next new moon. 8254 * 8255 * @return 8256 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 8257 * and the remaining structure fields are as described in #astro_global_solar_eclipse_t. 8258 * Any other value indicates an error. 8259 */ 8260 astro_global_solar_eclipse_t Astronomy_NextGlobalSolarEclipse(astro_time_t prevEclipseTime) 8261 { 8262 astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0); 8263 return Astronomy_SearchGlobalSolarEclipse(startTime); 8264 } 8265 8266 8267 static astro_eclipse_event_t EclipseEventError(void) 8268 { 8269 astro_eclipse_event_t evt; 8270 evt.time = TimeError(); 8271 evt.altitude = NAN; 8272 return evt; 8273 } 8274 8275 8276 static astro_local_solar_eclipse_t LocalSolarEclipseError(astro_status_t status) 8277 { 8278 astro_local_solar_eclipse_t eclipse; 8279 8280 eclipse.status = status; 8281 eclipse.kind = ECLIPSE_NONE; 8282 8283 eclipse.partial_begin = EclipseEventError(); 8284 eclipse.total_begin = EclipseEventError(); 8285 eclipse.peak = EclipseEventError(); 8286 eclipse.total_end = EclipseEventError(); 8287 eclipse.partial_end = EclipseEventError(); 8288 8289 return eclipse; 8290 } 8291 8292 8293 static shadow_t LocalMoonShadow(astro_time_t time, astro_observer_t observer) 8294 { 8295 astro_vector_t h, o, m; 8296 double pos[3]; 8297 8298 /* Calculate observer's geocentric position. */ 8299 /* For efficiency, do this first, to populate the earth rotation parameters in 'time'. */ 8300 /* That way they can be recycled instead of recalculated. */ 8301 geo_pos(&time, observer, pos); 8302 8303 h = CalcEarth(time); /* heliocentric Earth */ 8304 m = Astronomy_GeoMoon(time); /* geocentric Moon */ 8305 8306 /* Calculate lunacentric location of an observer on the Earth's surface. */ 8307 o.status = m.status; 8308 o.x = pos[0] - m.x; 8309 o.y = pos[1] - m.y; 8310 o.z = pos[2] - m.z; 8311 o.t = m.t; 8312 8313 /* Convert geocentric moon to heliocentric Moon. */ 8314 m.x += h.x; 8315 m.y += h.y; 8316 m.z += h.z; 8317 8318 return CalcShadow(MOON_MEAN_RADIUS_KM, time, o, m); 8319 } 8320 8321 8322 static astro_func_result_t local_shadow_distance_slope(void *context, astro_time_t time) 8323 { 8324 const double dt = 1.0 / 86400.0; 8325 astro_time_t t1, t2; 8326 astro_func_result_t result; 8327 shadow_t shadow1, shadow2; 8328 const astro_observer_t *observer = context; 8329 8330 t1 = Astronomy_AddDays(time, -dt); 8331 t2 = Astronomy_AddDays(time, +dt); 8332 8333 shadow1 = LocalMoonShadow(t1, *observer); 8334 if (shadow1.status != ASTRO_SUCCESS) 8335 return FuncError(shadow1.status); 8336 8337 shadow2 = LocalMoonShadow(t2, *observer); 8338 if (shadow2.status != ASTRO_SUCCESS) 8339 return FuncError(shadow2.status); 8340 8341 result.value = (shadow2.r - shadow1.r) / dt; 8342 result.status = ASTRO_SUCCESS; 8343 return result; 8344 } 8345 8346 8347 static shadow_t PeakLocalMoonShadow(astro_time_t search_center_time, astro_observer_t observer) 8348 { 8349 astro_time_t t1, t2; 8350 astro_search_result_t result; 8351 const double window = 0.2; 8352 8353 /* 8354 Search for the time near search_center_time that the Moon's shadow comes 8355 closest to the given observer. 8356 */ 8357 8358 t1 = Astronomy_AddDays(search_center_time, -window); 8359 t2 = Astronomy_AddDays(search_center_time, +window); 8360 8361 result = Astronomy_Search(local_shadow_distance_slope, &observer, t1, t2, 1.0); 8362 if (result.status != ASTRO_SUCCESS) 8363 return ShadowError(result.status); 8364 8365 return LocalMoonShadow(result.time, observer); 8366 } 8367 8368 8369 static double local_partial_distance(const shadow_t *shadow) 8370 { 8371 return shadow->p - shadow->r; 8372 } 8373 8374 static double local_total_distance(const shadow_t *shadow) 8375 { 8376 /* Must take the absolute value of the umbra radius 'k' */ 8377 /* because it can be negative for an annular eclipse. */ 8378 return fabs(shadow->k) - shadow->r; 8379 } 8380 8381 /** @cond DOXYGEN_SKIP */ 8382 typedef double (* local_distance_func) (const shadow_t *shadow); 8383 8384 typedef struct 8385 { 8386 local_distance_func func; 8387 double direction; 8388 astro_observer_t observer; 8389 } 8390 eclipse_transition_t; 8391 /* @endcond */ 8392 8393 8394 static astro_func_result_t local_eclipse_func(void *context, astro_time_t time) 8395 { 8396 const eclipse_transition_t *trans = context; 8397 shadow_t shadow; 8398 astro_func_result_t result; 8399 8400 shadow = LocalMoonShadow(time, trans->observer); 8401 if (shadow.status != ASTRO_SUCCESS) 8402 return FuncError(shadow.status); 8403 8404 result.value = trans->direction * trans->func(&shadow); 8405 result.status = ASTRO_SUCCESS; 8406 return result; 8407 } 8408 8409 8410 astro_func_result_t SunAltitude( 8411 astro_time_t time, 8412 astro_observer_t observer) 8413 { 8414 astro_equatorial_t equ; 8415 astro_horizon_t hor; 8416 astro_func_result_t result; 8417 8418 equ = Astronomy_Equator(BODY_SUN, &time, observer, EQUATOR_OF_DATE, ABERRATION); 8419 if (equ.status != ASTRO_SUCCESS) 8420 return FuncError(equ.status); 8421 8422 hor = Astronomy_Horizon(&time, observer, equ.ra, equ.dec, REFRACTION_NORMAL); 8423 result.value = hor.altitude; 8424 result.status = ASTRO_SUCCESS; 8425 return result; 8426 } 8427 8428 8429 static astro_status_t CalcEvent( 8430 astro_observer_t observer, 8431 astro_time_t time, 8432 astro_eclipse_event_t *evt) 8433 { 8434 astro_func_result_t result; 8435 8436 result = SunAltitude(time, observer); 8437 if (result.status != ASTRO_SUCCESS) 8438 { 8439 evt->time = TimeError(); 8440 evt->altitude = NAN; 8441 return result.status; 8442 } 8443 8444 evt->time = time; 8445 evt->altitude = result.value; 8446 return ASTRO_SUCCESS; 8447 } 8448 8449 8450 static astro_status_t LocalEclipseTransition( 8451 astro_observer_t observer, 8452 double direction, 8453 local_distance_func func, 8454 astro_time_t t1, 8455 astro_time_t t2, 8456 astro_eclipse_event_t *evt) 8457 { 8458 eclipse_transition_t trans; 8459 astro_search_result_t search; 8460 8461 trans.func = func; 8462 trans.direction = direction; 8463 trans.observer = observer; 8464 8465 search = Astronomy_Search(local_eclipse_func, &trans, t1, t2, 1.0); 8466 if (search.status != ASTRO_SUCCESS) 8467 { 8468 evt->time = TimeError(); 8469 evt->altitude = NAN; 8470 return search.status; 8471 } 8472 8473 return CalcEvent(observer, search.time, evt); 8474 } 8475 8476 8477 static astro_local_solar_eclipse_t LocalEclipse( 8478 shadow_t shadow, 8479 astro_observer_t observer) 8480 { 8481 const double PARTIAL_WINDOW = 0.2; 8482 const double TOTAL_WINDOW = 0.01; 8483 astro_local_solar_eclipse_t eclipse; 8484 astro_time_t t1, t2; 8485 astro_status_t status; 8486 8487 status = CalcEvent(observer, shadow.time, &eclipse.peak); 8488 if (status != ASTRO_SUCCESS) 8489 return LocalSolarEclipseError(status); 8490 8491 t1 = Astronomy_AddDays(shadow.time, -PARTIAL_WINDOW); 8492 t2 = Astronomy_AddDays(shadow.time, +PARTIAL_WINDOW); 8493 8494 status = LocalEclipseTransition(observer, +1.0, local_partial_distance, t1, shadow.time, &eclipse.partial_begin); 8495 if (status != ASTRO_SUCCESS) 8496 return LocalSolarEclipseError(status); 8497 8498 status = LocalEclipseTransition(observer, -1.0, local_partial_distance, shadow.time, t2, &eclipse.partial_end); 8499 if (status != ASTRO_SUCCESS) 8500 return LocalSolarEclipseError(status); 8501 8502 if (shadow.r < fabs(shadow.k)) /* take absolute value of 'k' to handle annular eclipses too. */ 8503 { 8504 t1 = Astronomy_AddDays(shadow.time, -TOTAL_WINDOW); 8505 t2 = Astronomy_AddDays(shadow.time, +TOTAL_WINDOW); 8506 8507 status = LocalEclipseTransition(observer, +1.0, local_total_distance, t1, shadow.time, &eclipse.total_begin); 8508 if (status != ASTRO_SUCCESS) 8509 return LocalSolarEclipseError(status); 8510 8511 status = LocalEclipseTransition(observer, -1.0, local_total_distance, shadow.time, t2, &eclipse.total_end); 8512 if (status != ASTRO_SUCCESS) 8513 return LocalSolarEclipseError(status); 8514 8515 eclipse.kind = EclipseKindFromUmbra(shadow.k); 8516 } 8517 else 8518 { 8519 eclipse.total_begin = eclipse.total_end = EclipseEventError(); 8520 eclipse.kind = ECLIPSE_PARTIAL; 8521 } 8522 8523 eclipse.status = ASTRO_SUCCESS; 8524 return eclipse; 8525 } 8526 8527 8528 /** 8529 * @brief Searches for a solar eclipse visible at a specific location on the Earth's surface. 8530 * 8531 * This function finds the first solar eclipse that occurs after `startTime`. 8532 * A solar eclipse may be partial, annular, or total. 8533 * See #astro_local_solar_eclipse_t for more information. 8534 * To find a series of solar eclipses, call this function once, 8535 * then keep calling #Astronomy_NextLocalSolarEclipse as many times as desired, 8536 * passing in the `peak` value returned from the previous call. 8537 * 8538 * IMPORTANT: An eclipse reported by this function might be partly or 8539 * completely invisible to the observer due to the time of day. 8540 * 8541 * @param startTime 8542 * The date and time for starting the search for a solar eclipse. 8543 * 8544 * @param observer 8545 * The geographic location of the observer. 8546 * 8547 * @return 8548 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 8549 * and the remaining structure fields are as described in #astro_local_solar_eclipse_t. 8550 * Any other value indicates an error. 8551 */ 8552 astro_local_solar_eclipse_t Astronomy_SearchLocalSolarEclipse( 8553 astro_time_t startTime, 8554 astro_observer_t observer) 8555 { 8556 const double PruneLatitude = 1.8; /* Moon's ecliptic latitude beyond which eclipse is impossible */ 8557 astro_time_t nmtime; 8558 astro_search_result_t newmoon; 8559 shadow_t shadow; 8560 double eclip_lat, eclip_lon, distance; 8561 astro_local_solar_eclipse_t eclipse; 8562 8563 /* Iterate through consecutive new moons until we find a solar eclipse visible somewhere on Earth. */ 8564 nmtime = startTime; 8565 for(;;) 8566 { 8567 /* Search for the next new moon. Any eclipse will be near it. */ 8568 newmoon = Astronomy_SearchMoonPhase(0.0, nmtime, 40.0); 8569 if (newmoon.status != ASTRO_SUCCESS) 8570 return LocalSolarEclipseError(newmoon.status); 8571 8572 /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */ 8573 CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); 8574 if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) 8575 { 8576 /* Search near the new moon for the time when the observer */ 8577 /* is closest to the line passing through the centers of the Sun and Moon. */ 8578 shadow = PeakLocalMoonShadow(newmoon.time, observer); 8579 if (shadow.status != ASTRO_SUCCESS) 8580 return LocalSolarEclipseError(shadow.status); 8581 8582 if (shadow.r < shadow.p) 8583 { 8584 /* This is at least a partial solar eclipse for the observer. */ 8585 eclipse = LocalEclipse(shadow, observer); 8586 8587 /* If any error occurs, something is really wrong and we should bail out. */ 8588 if (eclipse.status != ASTRO_SUCCESS) 8589 return eclipse; 8590 8591 /* Ignore any eclipse that happens completely at night. */ 8592 /* More precisely, the center of the Sun must be above the horizon */ 8593 /* at the beginning or the end of the eclipse, or we skip the event. */ 8594 if (eclipse.partial_begin.altitude > 0.0 || eclipse.partial_end.altitude > 0.0) 8595 return eclipse; 8596 } 8597 } 8598 8599 /* We didn't find an eclipse on this new moon, so search for the next one. */ 8600 nmtime = Astronomy_AddDays(newmoon.time, 10.0); 8601 } 8602 } 8603 8604 8605 /** 8606 * @brief Searches for the next local solar eclipse in a series. 8607 * 8608 * After using #Astronomy_SearchLocalSolarEclipse to find the first solar eclipse 8609 * in a series, you can call this function to find the next consecutive solar eclipse. 8610 * Pass in the `peak` value from the #astro_local_solar_eclipse_t returned by the 8611 * previous call to `Astronomy_SearchLocalSolarEclipse` or `Astronomy_NextLocalSolarEclipse` 8612 * to find the next solar eclipse. 8613 * 8614 * @param prevEclipseTime 8615 * A date and time near a new moon. Solar eclipse search will start at the next new moon. 8616 * 8617 * @param observer 8618 * The geographic location of the observer. 8619 * 8620 * @return 8621 * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` 8622 * and the remaining structure fields are as described in #astro_local_solar_eclipse_t. 8623 * Any other value indicates an error. 8624 */ 8625 astro_local_solar_eclipse_t Astronomy_NextLocalSolarEclipse( 8626 astro_time_t prevEclipseTime, 8627 astro_observer_t observer) 8628 { 8629 astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0); 8630 return Astronomy_SearchLocalSolarEclipse(startTime, observer); 8631 } 8632 8633 8634 static astro_func_result_t planet_transit_bound(void *context, astro_time_t time) 8635 { 8636 shadow_t shadow; 8637 astro_func_result_t result; 8638 const planet_shadow_context_t *p = context; 8639 8640 shadow = PlanetShadow(p->body, p->planet_radius_km, time); 8641 if (shadow.status != ASTRO_SUCCESS) 8642 return FuncError(shadow.status); 8643 8644 result.status = ASTRO_SUCCESS; 8645 result.value = p->direction * (shadow.r - shadow.p); 8646 return result; 8647 } 8648 8649 8650 static astro_search_result_t PlanetTransitBoundary( 8651 astro_body_t body, 8652 double planet_radius_km, 8653 astro_time_t t1, 8654 astro_time_t t2, 8655 double direction) 8656 { 8657 /* Search for the time the planet's penumbra begins/ends making contact with the center of the Earth. */ 8658 planet_shadow_context_t context; 8659 8660 context.body = body; 8661 context.planet_radius_km = planet_radius_km; 8662 context.direction = direction; 8663 8664 return Astronomy_Search(planet_transit_bound, &context, t1, t2, 1.0); 8665 } 8666 8667 8668 /** 8669 * @brief Searches for the first transit of Mercury or Venus after a given date. 8670 * 8671 * Finds the first transit of Mercury or Venus after a specified date. 8672 * A transit is when an inferior planet passes between the Sun and the Earth 8673 * so that the silhouette of the planet is visible against the Sun in the background. 8674 * To continue the search, pass the `finish` time in the returned structure to 8675 * #Astronomy_NextTransit. 8676 * 8677 * @param body 8678 * The planet whose transit is to be found. Must be `BODY_MERCURY` or `BODY_VENUS`. 8679 * 8680 * @param startTime 8681 * The date and time for starting the search for a transit. 8682 * 8683 * @return 8684 * If successful, the `status` field in the returned structure hold `ASTRO_SUCCESS` 8685 * and the other fields are as documented in #astro_transit_t. 8686 * Otherwise, `status` holds an error code and the other structure members are undefined. 8687 */ 8688 astro_transit_t Astronomy_SearchTransit(astro_body_t body, astro_time_t startTime) 8689 { 8690 astro_time_t search_time; 8691 astro_transit_t transit; 8692 astro_search_result_t conj, search; 8693 astro_angle_result_t conj_separation, min_separation; 8694 shadow_t shadow; 8695 double planet_radius_km; 8696 astro_time_t tx; 8697 const double threshold_angle = 0.4; /* maximum angular separation to attempt transit calculation */ 8698 const double dt_days = 1.0; 8699 8700 /* Validate the planet and find its mean radius. */ 8701 switch (body) 8702 { 8703 case BODY_MERCURY: planet_radius_km = 2439.7; break; 8704 case BODY_VENUS: planet_radius_km = 6051.8; break; 8705 default: 8706 return TransitErr(ASTRO_INVALID_BODY); 8707 } 8708 8709 search_time = startTime; 8710 for(;;) 8711 { 8712 /* 8713 Search for the next inferior conjunction of the given planet. 8714 This is the next time the Earth and the other planet have the same 8715 ecliptic longitude as seen from the Sun. 8716 */ 8717 conj = Astronomy_SearchRelativeLongitude(body, 0.0, search_time); 8718 if (conj.status != ASTRO_SUCCESS) 8719 return TransitErr(conj.status); 8720 8721 /* Calculate the angular separation between the body and the Sun at this time. */ 8722 conj_separation = Astronomy_AngleFromSun(body, conj.time); 8723 if (conj_separation.status != ASTRO_SUCCESS) 8724 return TransitErr(conj_separation.status); 8725 8726 if (conj_separation.angle < threshold_angle) 8727 { 8728 /* 8729 The planet's angular separation from the Sun is small enough 8730 to consider it a transit candidate. 8731 Search for the moment when the line passing through the Sun 8732 and planet are closest to the Earth's center. 8733 */ 8734 shadow = PeakPlanetShadow(body, planet_radius_km, conj.time); 8735 if (shadow.status != ASTRO_SUCCESS) 8736 return TransitErr(shadow.status); 8737 8738 if (shadow.r < shadow.p) /* does the planet's penumbra touch the Earth's center? */ 8739 { 8740 /* Find the beginning and end of the penumbral contact. */ 8741 tx = Astronomy_AddDays(shadow.time, -dt_days); 8742 search = PlanetTransitBoundary(body, planet_radius_km, tx, shadow.time, -1.0); 8743 if (search.status != ASTRO_SUCCESS) 8744 return TransitErr(search.status); 8745 transit.start = search.time; 8746 8747 tx = Astronomy_AddDays(shadow.time, +dt_days); 8748 search = PlanetTransitBoundary(body, planet_radius_km, shadow.time, tx, +1.0); 8749 if (search.status != ASTRO_SUCCESS) 8750 return TransitErr(search.status); 8751 transit.finish = search.time; 8752 transit.status = ASTRO_SUCCESS; 8753 transit.peak = shadow.time; 8754 8755 min_separation = Astronomy_AngleFromSun(body, shadow.time); 8756 if (min_separation.status != ASTRO_SUCCESS) 8757 return TransitErr(min_separation.status); 8758 8759 transit.separation = 60.0 * min_separation.angle; /* convert degrees to arcminutes */ 8760 return transit; 8761 } 8762 } 8763 8764 /* This inferior conjunction was not a transit. Try the next inferior conjunction. */ 8765 search_time = Astronomy_AddDays(conj.time, 10.0); 8766 } 8767 } 8768 8769 8770 /** 8771 * @brief Searches for another transit of Mercury or Venus. 8772 * 8773 * After calling #Astronomy_SearchTransit to find a transit of Mercury or Venus, 8774 * this function finds the next transit after that. 8775 * Keep calling this function as many times as you want to keep finding more transits. 8776 * 8777 * @param body 8778 * The planet whose transit is to be found. Must be `BODY_MERCURY` or `BODY_VENUS`. 8779 * 8780 * @param prevTransitTime 8781 * A date and time near the previous transit. 8782 * 8783 * @return 8784 * If successful, the `status` field in the returned structure hold `ASTRO_SUCCESS` 8785 * and the other fields are as documented in #astro_transit_t. 8786 * Otherwise, `status` holds an error code and the other structure members are undefined. 8787 */ 8788 astro_transit_t Astronomy_NextTransit(astro_body_t body, astro_time_t prevTransitTime) 8789 { 8790 astro_time_t startTime; 8791 8792 startTime = Astronomy_AddDays(prevTransitTime, 100.0); 8793 return Astronomy_SearchTransit(body, startTime); 8794 } 8795 8796 8797 /** 8798 * @brief Frees up all dynamic memory allocated by Astronomy Engine. 8799 * 8800 * Astronomy Engine uses dynamic memory allocation in only one place: 8801 * it makes calculation of Pluto's orbit more efficient by caching 11 KB 8802 * segments recycling them. To force purging this cache and 8803 * freeing all the dynamic memory, you can call this function at any time. 8804 * It is always safe to call, although it will slow down the very next 8805 * calculation of Pluto's position for a nearby time value. 8806 * Calling this function before your program exits is optional, but 8807 * it will be helpful for leak-checkers like valgrind. 8808 */ 8809 void Astronomy_Reset(void) 8810 { 8811 int i; 8812 for (i=0; i < PLUTO_NUM_STATES-1; ++i) 8813 { 8814 free(pluto_cache[i]); 8815 pluto_cache[i] = NULL; 8816 } 8817 } 8818 8819 8820 #ifdef __cplusplus 8821 } 8822 #endif