860 lines
22 KiB
C++
860 lines
22 KiB
C++
// astro.cpp
|
|
//
|
|
// Copyright (C) 2001-2009, the Celestia Development Team
|
|
// Original version by Chris Laurel <claurel@gmail.com>
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <utility>
|
|
#include <ctime>
|
|
#include "astro.h"
|
|
#include "univcoord.h"
|
|
#include <celutil/gettext.h>
|
|
#include <celmath/geomutil.h>
|
|
|
|
|
|
using namespace Eigen;
|
|
using namespace std;
|
|
using namespace celmath;
|
|
|
|
static const Quaterniond ECLIPTIC_TO_EQUATORIAL_ROTATION = XRotation(-astro::J2000Obliquity);
|
|
static const Matrix3d ECLIPTIC_TO_EQUATORIAL_MATRIX = ECLIPTIC_TO_EQUATORIAL_ROTATION.toRotationMatrix();
|
|
|
|
static const Quaterniond EQUATORIAL_TO_ECLIPTIC_ROTATION =
|
|
Quaterniond(AngleAxis<double>(-astro::J2000Obliquity, Vector3d::UnitX()));
|
|
static const Matrix3d EQUATORIAL_TO_ECLIPTIC_MATRIX = EQUATORIAL_TO_ECLIPTIC_ROTATION.toRotationMatrix();
|
|
static const Matrix3f EQUATORIAL_TO_ECLIPTIC_MATRIX_F = EQUATORIAL_TO_ECLIPTIC_MATRIX.cast<float>();
|
|
|
|
// Equatorial to galactic coordinate transformation
|
|
// North galactic pole at:
|
|
// RA 12h 51m 26.282s (192.85958 deg)
|
|
// Dec 27 d 07' 42.01" (27.1283361 deg)
|
|
// Zero longitude at position angle 122.932
|
|
// (J2000 coordinates)
|
|
static const double GALACTIC_NODE = 282.85958;
|
|
static const double GALACTIC_INCLINATION = 90.0 - 27.1283361;
|
|
static const double GALACTIC_LONGITUDE_AT_NODE = 32.932;
|
|
|
|
static const Quaterniond EQUATORIAL_TO_GALACTIC_ROTATION =
|
|
ZRotation(degToRad(GALACTIC_NODE)) *
|
|
XRotation(degToRad(GALACTIC_INCLINATION)) *
|
|
ZRotation(degToRad(-GALACTIC_LONGITUDE_AT_NODE));
|
|
static const Matrix3d EQUATORIAL_TO_GALACTIC_MATRIX = EQUATORIAL_TO_GALACTIC_ROTATION.toRotationMatrix();
|
|
|
|
// epoch B1950: 22:09 UT on 21 Dec 1949
|
|
#define B1950 2433282.423
|
|
|
|
// Difference in seconds between Terrestrial Time and International
|
|
// Atomic Time
|
|
static const double dTA = 32.184;
|
|
|
|
struct LeapSecondRecord
|
|
{
|
|
int seconds;
|
|
double t;
|
|
};
|
|
|
|
|
|
// Table of leap second insertions. The leap second always
|
|
// appears as the last second of the day immediately prior
|
|
// to the date in the table.
|
|
static const LeapSecondRecord LeapSeconds[] =
|
|
{
|
|
{ 11, 2441499.5 }, // 1 Jul 1972
|
|
{ 12, 2441683.5 }, // 1 Jan 1973
|
|
{ 13, 2442048.5 }, // 1 Jan 1974
|
|
{ 14, 2442413.5 }, // 1 Jan 1975
|
|
{ 15, 2442778.5 }, // 1 Jan 1976
|
|
{ 16, 2443144.5 }, // 1 Jan 1977
|
|
{ 17, 2443509.5 }, // 1 Jan 1978
|
|
{ 18, 2443874.5 }, // 1 Jan 1979
|
|
{ 19, 2444239.5 }, // 1 Jan 1980
|
|
{ 20, 2444786.5 }, // 1 Jul 1981
|
|
{ 21, 2445151.5 }, // 1 Jul 1982
|
|
{ 22, 2445516.5 }, // 1 Jul 1983
|
|
{ 23, 2446247.5 }, // 1 Jul 1985
|
|
{ 24, 2447161.5 }, // 1 Jan 1988
|
|
{ 25, 2447892.5 }, // 1 Jan 1990
|
|
{ 26, 2448257.5 }, // 1 Jan 1991
|
|
{ 27, 2448804.5 }, // 1 Jul 1992
|
|
{ 28, 2449169.5 }, // 1 Jul 1993
|
|
{ 29, 2449534.5 }, // 1 Jul 1994
|
|
{ 30, 2450083.5 }, // 1 Jan 1996
|
|
{ 31, 2450630.5 }, // 1 Jul 1997
|
|
{ 32, 2451179.5 }, // 1 Jan 1999
|
|
{ 33, 2453736.5 }, // 1 Jan 2006
|
|
{ 34, 2454832.5 }, // 1 Jan 2009
|
|
{ 35, 2456109.5 }, // 1 Jul 2012
|
|
{ 36, 2457204.5 }, // 1 Jul 2015
|
|
{ 37, 2457754.5 }, // 1 Jan 2017
|
|
};
|
|
|
|
|
|
#if !defined(__GNUC__) || defined(_WIN32)
|
|
static const char* MonthAbbrList[12] =
|
|
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
#endif
|
|
|
|
|
|
struct UnitDefinition
|
|
{
|
|
std::string_view name;
|
|
double conversion;
|
|
};
|
|
|
|
|
|
static const UnitDefinition lengthUnits[] =
|
|
{
|
|
{ "km", 1.0 },
|
|
{ "m", 0.001 },
|
|
{ "rE", (double) EARTH_RADIUS },
|
|
{ "rJ", (double) JUPITER_RADIUS },
|
|
{ "rS", (double) SOLAR_RADIUS },
|
|
{ "AU", (double) KM_PER_AU },
|
|
{ "ly", (double) KM_PER_LY },
|
|
{ "pc", (double) KM_PER_PARSEC },
|
|
{ "kpc", 1000.0 * ((double) KM_PER_PARSEC) },
|
|
{ "Mpc", 1000000.0 * ((double) KM_PER_PARSEC) },
|
|
};
|
|
|
|
|
|
static const UnitDefinition timeUnits[] =
|
|
{
|
|
{ "s", 1.0 / SECONDS_PER_DAY },
|
|
{ "min", 1.0 / MINUTES_PER_DAY },
|
|
{ "h", 1.0 / HOURS_PER_DAY },
|
|
{ "d", 1.0 },
|
|
{ "y", DAYS_PER_YEAR },
|
|
};
|
|
|
|
|
|
static const UnitDefinition angleUnits[] =
|
|
{
|
|
{ "mas", 0.001 / SECONDS_PER_DEG },
|
|
{ "arcsec", 1.0 / SECONDS_PER_DEG },
|
|
{ "arcmin", 1.0 / MINUTES_PER_DEG },
|
|
{ "deg", 1.0 },
|
|
{ "hRA", DEG_PER_HRA },
|
|
{ "rad", 180.0 / PI },
|
|
};
|
|
|
|
|
|
static const UnitDefinition massUnits[] =
|
|
{
|
|
{ "kg", 1.0 / astro::EarthMass },
|
|
{ "mE", 1.0 },
|
|
{ "mJ", astro::JupiterMass / astro::EarthMass },
|
|
};
|
|
|
|
|
|
float astro::lumToAbsMag(float lum)
|
|
{
|
|
return (float) (SOLAR_ABSMAG - log(lum) * LN_MAG);
|
|
}
|
|
|
|
// Return the apparent magnitude of a star with lum times solar
|
|
// luminosity viewed at lyrs light years
|
|
float astro::lumToAppMag(float lum, float lyrs)
|
|
{
|
|
return absToAppMag(lumToAbsMag(lum), lyrs);
|
|
}
|
|
|
|
float astro::absMagToLum(float mag)
|
|
{
|
|
return (float) exp((SOLAR_ABSMAG - mag) / LN_MAG);
|
|
}
|
|
|
|
float astro::appMagToLum(float mag, float lyrs)
|
|
{
|
|
return absMagToLum(appToAbsMag(mag, lyrs));
|
|
}
|
|
|
|
void astro::decimalToDegMinSec(double angle, int& degrees, int& minutes, double& seconds)
|
|
{
|
|
double A, B, C;
|
|
|
|
degrees = (int) angle;
|
|
|
|
A = angle - (double) degrees;
|
|
B = A * 60.0;
|
|
minutes = (int) B;
|
|
C = B - (double) minutes;
|
|
seconds = C * 60.0;
|
|
}
|
|
|
|
double astro::degMinSecToDecimal(int degrees, int minutes, double seconds)
|
|
{
|
|
return (double)degrees + (seconds/60.0 + (double)minutes)/60.0;
|
|
}
|
|
|
|
void astro::decimalToHourMinSec(double angle, int& hours, int& minutes, double& seconds)
|
|
{
|
|
double A, B;
|
|
|
|
A = angle / 15.0;
|
|
hours = (int) A;
|
|
B = (A - (double) hours) * 60.0;
|
|
minutes = (int) (B);
|
|
seconds = (B - (double) minutes) * 60.0;
|
|
}
|
|
|
|
// Convert equatorial coordinates to Cartesian celestial (or ecliptical)
|
|
// coordinates.
|
|
Eigen::Vector3f
|
|
astro::equatorialToCelestialCart(float ra, float dec, float distance)
|
|
{
|
|
double theta = ra / 24.0 * PI * 2 + PI;
|
|
double phi = (dec / 90.0 - 1.0) * PI / 2;
|
|
double x = cos(theta) * sin(phi) * distance;
|
|
double y = cos(phi) * distance;
|
|
double z = -sin(theta) * sin(phi) * distance;
|
|
|
|
return EQUATORIAL_TO_ECLIPTIC_MATRIX_F * Vector3f((float) x, (float) y, (float) z);
|
|
}
|
|
|
|
|
|
// Convert equatorial coordinates to Cartesian celestial (or ecliptical)
|
|
// coordinates.
|
|
Eigen::Vector3d
|
|
astro::equatorialToCelestialCart(double ra, double dec, double distance)
|
|
{
|
|
double theta = ra / 24.0 * PI * 2 + PI;
|
|
double phi = (dec / 90.0 - 1.0) * PI / 2;
|
|
double x = cos(theta) * sin(phi) * distance;
|
|
double y = cos(phi) * distance;
|
|
double z = -sin(theta) * sin(phi) * distance;
|
|
|
|
return EQUATORIAL_TO_ECLIPTIC_MATRIX * Vector3d(x, y, z);
|
|
}
|
|
|
|
|
|
/** Convert spherical coordinates in the J2000 equatorial frame to cartesian
|
|
* coordinates in the J2000 ecliptic frame. RA in hours, dec in degrees.
|
|
*/
|
|
Eigen::Vector3f
|
|
astro::equatorialToEclipticCartesian(float ra, float dec, float distance)
|
|
{
|
|
double theta = ra / 24.0 * PI * 2 + PI;
|
|
double phi = (dec / 90.0 - 1.0) * PI / 2;
|
|
double x = cos(theta) * sin(phi) * distance;
|
|
double y = cos(phi) * distance;
|
|
double z = -sin(theta) * sin(phi) * distance;
|
|
|
|
return EQUATORIAL_TO_ECLIPTIC_MATRIX_F * Eigen::Vector3f((float) x, (float) y, (float) z);
|
|
}
|
|
|
|
|
|
void astro::anomaly(double meanAnomaly, double eccentricity,
|
|
double& trueAnomaly, double& eccentricAnomaly)
|
|
{
|
|
double e, delta, err;
|
|
double tol = 0.00000001745;
|
|
int iterations = 20; // limit while() to maximum of 20 iterations.
|
|
|
|
e = meanAnomaly - 2*PI * (int) (meanAnomaly / (2*PI));
|
|
err = 1;
|
|
while(abs(err) > tol && iterations > 0)
|
|
{
|
|
err = e - eccentricity*sin(e) - meanAnomaly;
|
|
delta = err / (1 - eccentricity * cos(e));
|
|
e -= delta;
|
|
iterations--;
|
|
}
|
|
|
|
trueAnomaly = 2*atan(sqrt((1+eccentricity)/(1-eccentricity))*tan(e/2));
|
|
eccentricAnomaly = e;
|
|
}
|
|
|
|
|
|
/*! Return the angle between the mean ecliptic plane and mean equator at
|
|
* the specified Julian date.
|
|
*/
|
|
// TODO: replace this with a better precession model
|
|
double astro::meanEclipticObliquity(double jd)
|
|
{
|
|
double t, de;
|
|
jd -= 2451545.0;
|
|
t = jd / 36525;
|
|
de = (46.815 * t + 0.0006 * t * t - 0.00181 * t * t * t) / 3600;
|
|
|
|
return J2000Obliquity - de;
|
|
}
|
|
|
|
|
|
/*! Return a quaternion giving the transformation from the J2000 ecliptic
|
|
* coordinate system to the J2000 Earth equatorial coordinate system.
|
|
*/
|
|
Quaterniond astro::eclipticToEquatorial()
|
|
{
|
|
return ECLIPTIC_TO_EQUATORIAL_ROTATION;
|
|
}
|
|
|
|
|
|
/*! Rotate a vector in the J2000 ecliptic coordinate system to
|
|
* the J2000 Earth equatorial coordinate system.
|
|
*/
|
|
Vector3d astro::eclipticToEquatorial(const Vector3d& v)
|
|
{
|
|
return ECLIPTIC_TO_EQUATORIAL_MATRIX.transpose() * v;
|
|
}
|
|
|
|
|
|
/*! Return a quaternion giving the transformation from the J2000 Earth
|
|
* equatorial coordinate system to the galactic coordinate system.
|
|
*/
|
|
Quaterniond astro::equatorialToGalactic()
|
|
{
|
|
return EQUATORIAL_TO_GALACTIC_ROTATION;
|
|
}
|
|
|
|
|
|
/*! Rotate a vector int the J2000 Earth equatorial coordinate system to
|
|
* the galactic coordinate system.
|
|
*/
|
|
Vector3d astro::equatorialToGalactic(const Vector3d& v)
|
|
{
|
|
return EQUATORIAL_TO_GALACTIC_MATRIX.transpose() * v;
|
|
}
|
|
|
|
|
|
|
|
astro::Date::Date() : Date(0, 0, 0)
|
|
{
|
|
}
|
|
|
|
astro::Date::Date(int Y, int M, int D) :
|
|
year(Y),
|
|
month(M),
|
|
day(D),
|
|
hour(0),
|
|
minute(0),
|
|
seconds(0.0),
|
|
wday(0),
|
|
utc_offset(0),
|
|
tzname("UTC")
|
|
{
|
|
}
|
|
|
|
astro::Date::Date(double jd)
|
|
{
|
|
auto a = (int64_t) floor(jd + 0.5);
|
|
wday = (a + 1) % 7;
|
|
double c;
|
|
if (a < 2299161)
|
|
{
|
|
c = (double) (a + 1524);
|
|
}
|
|
else
|
|
{
|
|
double b = (double) ((int64_t) floor((a - 1867216.25) / 36524.25));
|
|
c = a + b - (int64_t) floor(b / 4) + 1525;
|
|
}
|
|
|
|
auto d = (int64_t) floor((c - 122.1) / 365.25);
|
|
auto e = (int64_t) floor(365.25 * d);
|
|
auto f = (int64_t) floor((c - e) / 30.6001);
|
|
|
|
double dday = c - e - (int64_t) floor(30.6001 * f) + ((jd + 0.5) - a);
|
|
|
|
// This following used to be 14.0, but gcc was computing it incorrectly, so
|
|
// it was changed to 14
|
|
month = (int) (f - 1 - 12 * (int64_t) (f / 14));
|
|
year = (int) (d - 4715 - (int64_t) ((7.0 + month) / 10.0));
|
|
day = (int) dday;
|
|
|
|
double dhour = (dday - day) * 24;
|
|
hour = (int) dhour;
|
|
|
|
double dminute = (dhour - hour) * 60;
|
|
minute = (int) dminute;
|
|
|
|
seconds = (dminute - minute) * 60;
|
|
utc_offset = 0;
|
|
tzname = "UTC";
|
|
}
|
|
|
|
const char* astro::Date::toCStr(Format format) const
|
|
{
|
|
static char date[255];
|
|
|
|
if (format == ISO8601)
|
|
{
|
|
snprintf(date, sizeof(date), "%04d-%02d-%02dT%02d:%02d:%08.5fZ",
|
|
year, month, day, hour, minute, seconds);
|
|
return date;
|
|
}
|
|
// MinGW's libraries don't have the tm_gmtoff and tm_zone fields for
|
|
// struct tm.
|
|
#if defined(__GNUC__) && !defined(_WIN32)
|
|
struct tm cal_time {};
|
|
cal_time.tm_year = year-1900;
|
|
cal_time.tm_mon = month-1;
|
|
cal_time.tm_mday = day;
|
|
cal_time.tm_hour = hour;
|
|
cal_time.tm_min = minute;
|
|
cal_time.tm_sec = (int)seconds;
|
|
cal_time.tm_wday = wday;
|
|
cal_time.tm_gmtoff = utc_offset;
|
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
|
// tm_zone is a non-const string field on the Mac and FreeBSD (why?)
|
|
cal_time.tm_zone = const_cast<char*>(tzname.c_str());
|
|
#else
|
|
cal_time.tm_zone = tzname.c_str();
|
|
#endif
|
|
|
|
const char* strftime_format;
|
|
switch(format)
|
|
{
|
|
case Locale:
|
|
strftime_format = "%c";
|
|
break;
|
|
case TZName:
|
|
strftime_format = "%Y %b %d %H:%M:%S %Z";
|
|
break;
|
|
default:
|
|
strftime_format = "%Y %b %d %H:%M:%S %z";
|
|
break;
|
|
}
|
|
|
|
strftime(date, sizeof(date), strftime_format, &cal_time);
|
|
#else
|
|
switch(format)
|
|
{
|
|
case Locale:
|
|
case TZName:
|
|
snprintf(date, sizeof(date), "%04d %s %02d %02d:%02d:%02d %s",
|
|
year, _(MonthAbbrList[month-1]), day,
|
|
hour, minute, (int)seconds, tzname.c_str());
|
|
break;
|
|
case UTCOffset:
|
|
{
|
|
int sign = utc_offset < 0 ? -1:1;
|
|
int h_offset = sign * utc_offset / 3600;
|
|
int m_offset = (sign * utc_offset - h_offset * 3600) / 60;
|
|
snprintf(date, sizeof(date), "%04d %s %02d %02d:%02d:%02d %c%02d%02d",
|
|
year, _(MonthAbbrList[month-1]), day,
|
|
hour, minute, (int)seconds, (sign==1?'+':'-'), h_offset, m_offset);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return date;
|
|
|
|
}
|
|
|
|
// Convert a calendar date to a Julian date
|
|
astro::Date::operator double() const
|
|
{
|
|
int y = year, m = month;
|
|
if (month <= 2)
|
|
{
|
|
y = year - 1;
|
|
m = month + 12;
|
|
}
|
|
|
|
// Correct for the lost days in Oct 1582 when the Gregorian calendar
|
|
// replaced the Julian calendar.
|
|
int B = -2;
|
|
if (year > 1582 || (year == 1582 && (month > 10 || (month == 10 && day >= 15))))
|
|
{
|
|
B = y / 400 - y / 100;
|
|
}
|
|
|
|
return (floor(365.25 * y) +
|
|
floor(30.6001 * (m + 1)) + B + 1720996.5 +
|
|
day + hour / HOURS_PER_DAY + minute / MINUTES_PER_DAY + seconds / SECONDS_PER_DAY);
|
|
}
|
|
|
|
|
|
// TODO: need option to parse UTC times (with leap seconds)
|
|
bool astro::parseDate(const string& s, astro::Date& date)
|
|
{
|
|
int year = 0;
|
|
unsigned int month = 1;
|
|
unsigned int day = 1;
|
|
unsigned int hour = 0;
|
|
unsigned int minute = 0;
|
|
double second = 0.0;
|
|
|
|
if (sscanf(s.c_str(), "%d-%u-%uT%u:%u:%lf",
|
|
&year, &month, &day, &hour, &minute, &second) == 6 ||
|
|
sscanf(s.c_str(), " %d %u %u %u:%u:%lf ",
|
|
&year, &month, &day, &hour, &minute, &second) == 6 ||
|
|
sscanf(s.c_str(), " %d %u %u %u:%u ",
|
|
&year, &month, &day, &hour, &minute) == 5 ||
|
|
sscanf(s.c_str(), " %d %u %u ", &year, &month, &day) == 3)
|
|
{
|
|
if (month < 1 || month > 12)
|
|
return false;
|
|
if (hour > 23 || minute > 59 || second >= 60.0 || second < 0.0)
|
|
return false;
|
|
|
|
// Days / month calculation . . .
|
|
int maxDay = 31 - ((0xa50 >> month) & 0x1);
|
|
if (month == 2)
|
|
{
|
|
// Check for a leap year
|
|
if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
|
|
maxDay = 29;
|
|
else
|
|
maxDay = 28;
|
|
}
|
|
if (day > (unsigned int) maxDay || day < 1)
|
|
return false;
|
|
|
|
date.year = year;
|
|
date.month = month;
|
|
date.day = day;
|
|
date.hour = hour;
|
|
date.minute = minute;
|
|
date.seconds = second;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
astro::Date
|
|
astro::Date::systemDate()
|
|
{
|
|
time_t t = time(nullptr);
|
|
struct tm *gmt = gmtime(&t);
|
|
astro::Date d;
|
|
d.year = gmt->tm_year + 1900;
|
|
d.month = gmt->tm_mon + 1;
|
|
d.day = gmt->tm_mday;
|
|
d.hour = gmt->tm_hour;
|
|
d.minute = gmt->tm_min;
|
|
d.seconds = (int) gmt->tm_sec;
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
ostream& operator<<(ostream& s, const astro::Date& d)
|
|
{
|
|
s << d.toCStr();
|
|
return s;
|
|
}
|
|
|
|
|
|
/********* Time scale conversion functions ***********/
|
|
|
|
// Convert from Atomic Time to UTC
|
|
astro::Date
|
|
astro::TAItoUTC(double tai)
|
|
{
|
|
unsigned int nRecords = sizeof(LeapSeconds) / sizeof(LeapSeconds[0]);
|
|
double dAT = LeapSeconds[0].seconds;
|
|
/*double dD = 0.0; Unused*/
|
|
int extraSecs = 0;
|
|
|
|
for (unsigned int i = nRecords - 1; i > 0; i--)
|
|
{
|
|
if (tai - secsToDays(LeapSeconds[i].seconds) >= LeapSeconds[i].t)
|
|
{
|
|
dAT = LeapSeconds[i].seconds;
|
|
break;
|
|
}
|
|
if (tai - secsToDays(LeapSeconds[i - 1].seconds) >= LeapSeconds[i].t)
|
|
{
|
|
dAT = LeapSeconds[i].seconds;
|
|
extraSecs = LeapSeconds[i].seconds - LeapSeconds[i - 1].seconds;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Date utcDate(tai - secsToDays(dAT));
|
|
utcDate.seconds += extraSecs;
|
|
|
|
return utcDate;
|
|
}
|
|
|
|
|
|
// Convert from UTC to Atomic Time
|
|
double
|
|
astro::UTCtoTAI(const astro::Date& utc)
|
|
{
|
|
unsigned int nRecords = sizeof(LeapSeconds) / sizeof(LeapSeconds[0]);
|
|
double dAT = LeapSeconds[0].seconds;
|
|
double utcjd = (double) Date(utc.year, utc.month, utc.day);
|
|
|
|
for (unsigned int i = nRecords - 1; i > 0; i--)
|
|
{
|
|
if (utcjd >= LeapSeconds[i].t)
|
|
{
|
|
dAT = LeapSeconds[i].seconds;
|
|
break;
|
|
}
|
|
}
|
|
|
|
double tai = utcjd + secsToDays(utc.hour * 3600.0 + utc.minute * 60.0 + utc.seconds + dAT);
|
|
|
|
return tai;
|
|
}
|
|
|
|
|
|
// Convert from Terrestrial Time to Atomic Time
|
|
double
|
|
astro::TTtoTAI(double tt)
|
|
{
|
|
return tt - secsToDays(dTA);
|
|
}
|
|
|
|
|
|
// Convert from Atomic Time to Terrestrial TIme
|
|
double
|
|
astro::TAItoTT(double tai)
|
|
{
|
|
return tai + secsToDays(dTA);
|
|
}
|
|
|
|
|
|
// Correction for converting from Terrestrial Time to Barycentric Dynamical
|
|
// Time. Constants and algorithm from "Time Routines in CSPICE",
|
|
// http://sohowww.nascom.nasa.gov/solarsoft/stereo/gen/exe/icy/doc/time.req
|
|
static const double K = 1.657e-3;
|
|
static const double EB = 1.671e-2;
|
|
static const double M0 = 6.239996;
|
|
static const double M1 = 1.99096871e-7;
|
|
|
|
// Input is a TDB Julian Date; result is in seconds
|
|
double TDBcorrection(double tdb)
|
|
{
|
|
// t is seconds from J2000.0
|
|
double t = astro::daysToSecs(tdb - astro::J2000);
|
|
|
|
// Approximate calculation of Earth's mean anomaly
|
|
double M = M0 + M1 * t;
|
|
|
|
// Compute the eccentric anomaly
|
|
double E = M + EB * sin(M);
|
|
|
|
return K * sin(E);
|
|
}
|
|
|
|
|
|
// Convert from Terrestrial Time to Barycentric Dynamical Time
|
|
double
|
|
astro::TTtoTDB(double tt)
|
|
{
|
|
return tt + secsToDays(TDBcorrection(tt));
|
|
}
|
|
|
|
|
|
// Convert from Barycentric Dynamical Time to Terrestrial Time
|
|
double
|
|
astro::TDBtoTT(double tdb)
|
|
{
|
|
return tdb - secsToDays(TDBcorrection(tdb));
|
|
}
|
|
|
|
|
|
// Convert from Coordinated Universal time to Barycentric Dynamical Time
|
|
astro::Date
|
|
astro::TDBtoUTC(double tdb)
|
|
{
|
|
return TAItoUTC(TTtoTAI(TDBtoTT(tdb)));
|
|
}
|
|
|
|
// Convert from Barycentric Dynamical Time to local calendar if possible
|
|
// otherwise convert to UTC
|
|
astro::Date
|
|
astro::TDBtoLocal(double tdb)
|
|
{
|
|
double tai = astro::TTtoTAI(astro::TDBtoTT(tdb));
|
|
double jdutc = astro::TAItoJDUTC(tai);
|
|
if (jdutc < 2465442 &&
|
|
jdutc > 2415733)
|
|
{
|
|
time_t time = (int) astro::julianDateToSeconds(jdutc - 2440587.5);
|
|
struct tm *localt = localtime(&time);
|
|
if (localt != nullptr)
|
|
{
|
|
astro::Date d;
|
|
d.year = localt->tm_year + 1900;
|
|
d.month = localt->tm_mon + 1;
|
|
d.day = localt->tm_mday;
|
|
d.hour = localt->tm_hour;
|
|
d.minute = localt->tm_min;
|
|
d.seconds = (int) localt->tm_sec;
|
|
d.wday = localt->tm_wday;
|
|
#if defined(__GNUC__) && !defined(_WIN32)
|
|
d.utc_offset = localt->tm_gmtoff;
|
|
d.tzname = localt->tm_zone;
|
|
#else
|
|
{
|
|
astro::Date utcDate = astro::TAItoUTC(tai);
|
|
int daydiff = d.day - utcDate.day;
|
|
int hourdiff;
|
|
if (daydiff == 0)
|
|
hourdiff = 0;
|
|
else if (daydiff > 1 || daydiff == -1)
|
|
hourdiff = -24;
|
|
else
|
|
hourdiff = 24;
|
|
d.utc_offset = (hourdiff + d.hour - utcDate.hour) * 3600
|
|
+ (d.minute - utcDate.minute) * 60;
|
|
}
|
|
d.tzname = localt->tm_isdst ? _("DST"): _("STD");
|
|
#endif
|
|
return d;
|
|
}
|
|
}
|
|
return astro::TDBtoUTC(tdb);
|
|
}
|
|
|
|
// Convert from Barycentric Dynamical Time to UTC
|
|
double
|
|
astro::UTCtoTDB(const astro::Date& utc)
|
|
{
|
|
return TTtoTDB(TAItoTT(UTCtoTAI(utc)));
|
|
}
|
|
|
|
|
|
// Convert from TAI to Julian Date UTC. The Julian Date UTC functions should
|
|
// generally be avoided because there's no provision for dealing with leap
|
|
// seconds.
|
|
double
|
|
astro::JDUTCtoTAI(double utc)
|
|
{
|
|
unsigned int nRecords = sizeof(LeapSeconds) / sizeof(LeapSeconds[0]);
|
|
double dAT = LeapSeconds[0].seconds;
|
|
|
|
for (unsigned int i = nRecords - 1; i > 0; i--)
|
|
{
|
|
if (utc > LeapSeconds[i].t)
|
|
{
|
|
dAT = LeapSeconds[i].seconds;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return utc + secsToDays(dAT);
|
|
}
|
|
|
|
|
|
// Convert from Julian Date UTC to TAI
|
|
double
|
|
astro::TAItoJDUTC(double tai)
|
|
{
|
|
unsigned int nRecords = sizeof(LeapSeconds) / sizeof(LeapSeconds[0]);
|
|
double dAT = LeapSeconds[0].seconds;
|
|
|
|
for (unsigned int i = nRecords - 1; i > 0; i--)
|
|
{
|
|
if (tai - secsToDays(LeapSeconds[i - 1].seconds) > LeapSeconds[i].t)
|
|
{
|
|
dAT = LeapSeconds[i].seconds;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return tai - secsToDays(dAT);
|
|
}
|
|
|
|
|
|
// Get scale of given length unit in kilometers
|
|
bool astro::getLengthScale(std::string_view unitName, double& scale)
|
|
{
|
|
unsigned int nUnits = sizeof(lengthUnits) / sizeof(lengthUnits[0]);
|
|
bool foundMatch = false;
|
|
for(unsigned int i = 0; i < nUnits; i++)
|
|
{
|
|
if (lengthUnits[i].name == unitName)
|
|
{
|
|
foundMatch = true;
|
|
scale = lengthUnits[i].conversion;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return foundMatch;
|
|
}
|
|
|
|
|
|
// Get scale of given time unit in days
|
|
bool astro::getTimeScale(std::string_view unitName, double& scale)
|
|
{
|
|
for (const auto& timeUnit : timeUnits)
|
|
{
|
|
if (timeUnit.name == unitName)
|
|
{
|
|
scale = timeUnit.conversion;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// Get scale of given angle unit in degrees
|
|
bool astro::getAngleScale(std::string_view unitName, double& scale)
|
|
{
|
|
for (const auto& angleUnit : angleUnits)
|
|
{
|
|
if (angleUnit.name == unitName)
|
|
{
|
|
scale = angleUnit.conversion;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool astro::getMassScale(std::string_view unitName, double& scale)
|
|
{
|
|
for (const auto& massUnit : massUnits)
|
|
{
|
|
if (massUnit.name == unitName)
|
|
{
|
|
scale = massUnit.conversion;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// Check if unit is a length unit
|
|
bool astro::isLengthUnit(std::string_view unitName)
|
|
{
|
|
double dummy;
|
|
return getLengthScale(unitName, dummy);
|
|
}
|
|
|
|
|
|
// Check if unit is a time unit
|
|
bool astro::isTimeUnit(std::string_view unitName)
|
|
{
|
|
double dummy;
|
|
return getTimeScale(unitName, dummy);
|
|
}
|
|
|
|
|
|
// Check if unit is an angle unit
|
|
bool astro::isAngleUnit(std::string_view unitName)
|
|
{
|
|
double dummy;
|
|
return getAngleScale(unitName, dummy);
|
|
}
|
|
|
|
|
|
bool astro::isMassUnit(std::string_view unitName)
|
|
{
|
|
double dummy;
|
|
return getMassScale(unitName, dummy);
|
|
}
|