3
* Copyright (C) 2002 Fabien Chereau
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public License
7
* as published by the Free Software Foundation; either version 2
8
* of the License, or (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
#include <cmath> // std::fmod
28
#include "StelUtils.hpp"
34
#include <QStringList>
35
#include <QTextStream>
43
double hmsToRad(unsigned int h, unsigned int m, double s )
45
return (double)M_PI/24.*h*2.+(double)M_PI/12.*m/60.+s*M_PI/43200.;
48
double dmsToRad(int d, unsigned int m, double s)
51
return (double)M_PI/180.*d+(double)M_PI/10800.*m+s*M_PI/648000.;
52
return (double)M_PI/180.*d-(double)M_PI/10800.*m-s*M_PI/648000.;
55
/*************************************************************************
56
Convert an angle in radian to hms
57
*************************************************************************/
58
void radToHms(double angle, unsigned int& h, unsigned int& m, double& s)
60
angle = std::fmod(angle,2.0*M_PI);
61
if (angle < 0.0) angle += 2.0*M_PI; // range: [0..2.0*M_PI)
65
h = (unsigned int)angle;
66
m = (unsigned int)((angle-h)*60);
67
s = (angle-h)*3600.-60.*m;
70
/*************************************************************************
71
Convert an angle in radian to dms
72
*************************************************************************/
73
void radToDms(double angle, bool& sign, unsigned int& d, unsigned int& m, double& s)
75
angle = std::fmod(angle,2.0*M_PI);
84
d = (unsigned int)angle;
85
m = (unsigned int)((angle - d)*60);
86
s = (angle-d)*3600-60*m;
89
/*************************************************************************
90
Convert an angle in radian to a hms formatted string
91
If the minute and second part are null are too small, don't print them
92
*************************************************************************/
93
QString radToHmsStrAdapt(double angle)
99
StelUtils::radToHms(angle+0.005*M_PI/12/(60*60), h, m, s);
101
if (std::fabs(s*100-(int)s*100)>=1)
104
ts.setRealNumberNotation(QTextStream::FixedNotation);
107
ts.setRealNumberPrecision(1);
114
ts << m << 'm' << (int)s << 's';
123
/*************************************************************************
124
Convert an angle in radian to a hms formatted string
125
If decimal is true, output should be like this: " 16h29m55.3s"
126
If decimal is true, output should be like this: " 16h20m0.4s"
127
If decimal is false, output should be like this: "0h26m5s"
128
*************************************************************************/
129
QString radToHmsStr(double angle, bool decimal)
133
StelUtils::radToHms(angle+0.005*M_PI/12/(60*60), h, m, s);
134
int width, precision;
149
// handle carry case (when seconds are rounded up)
150
if (QString("%1").arg(s, 0, 'f', precision) == carry)
160
if (h==24 && m==0 && s==0)
163
return QString("%1h%2m%3s").arg(h, width).arg(m).arg(s, 0, 'f', precision);
166
/*************************************************************************
167
Convert an angle in radian to a dms formatted string
168
If the minute and second part are null are too small, don't print them
169
*************************************************************************/
170
QString radToDmsStrAdapt(double angle, bool useD)
180
StelUtils::radToDms(angle+0.005*M_PI/180/(60*60)*(angle<0?-1.:1.), sign, d, m, s);
182
QTextStream os(&str);
184
os << (sign?'+':'-') << d << degsign;
185
if (std::fabs(s*100-(int)s*100)>=1)
187
os << m << '\'' << fixed << qSetRealNumberPrecision(2) << qSetFieldWidth(5) << qSetPadChar('0') << s << qSetFieldWidth(0) << '\"';
191
os << m << '\'' << (int)s << '\"';
197
//qDebug() << "radToDmsStrAdapt(" << angle << ", " << useD << ") = " << str;
202
/*************************************************************************
203
Convert an angle in radian to a dms formatted string
204
*************************************************************************/
205
QString radToDmsStr(double angle, bool decimal, bool useD)
215
StelUtils::radToDms(angle+0.005*M_PI/180/(60*60)*(angle<0?-1.:1.), sign, d, m, s);
217
QTextStream os(&str);
218
os << (sign?'+':'-') << d << degsign;
223
os << qSetRealNumberPrecision(1);
228
os << qSetRealNumberPrecision(0);
232
os << qSetFieldWidth(width) << m << qSetFieldWidth(0) << '\''
233
<< fixed << qSetFieldWidth(width) << qSetPadChar('0') << s
234
<< qSetFieldWidth(0) << '\"';
239
// Obtains a Vec3f from a string with the form x,y,z
240
Vec3f strToVec3f(const QStringList& s)
243
return Vec3f(0.f,0.f,0.f);
245
return Vec3f(s[0].toFloat(),s[1].toFloat(),s[2].toFloat());
248
Vec3f strToVec3f(const QString& s)
250
return strToVec3f(s.split(","));
253
// Converts a Vec3f to HTML color notation.
254
QString vec3fToHtmlColor(const Vec3f& v)
256
return QString("#%1%2%3")
257
.arg(qMin(255, int(v[0] * 255)), 2, 16, QChar('0'))
258
.arg(qMin(255, int(v[1] * 255)), 2, 16, QChar('0'))
259
.arg(qMin(255, int(v[2] * 255)), 2, 16, QChar('0'));
262
void spheToRect(double lng, double lat, Vec3d& v)
264
const double cosLat = cos(lat);
265
v.set(cos(lng) * cosLat, sin(lng) * cosLat, sin(lat));
268
void spheToRect(float lng, float lat, Vec3f& v)
270
const double cosLat = cos(lat);
271
v.set(cos(lng) * cosLat, sin(lng) * cosLat, sin(lat));
274
void rectToSphe(double *lng, double *lat, const Vec3d& v)
276
double r = v.length();
278
*lng = atan2(v[1],v[0]);
281
void rectToSphe(float *lng, float *lat, const Vec3d& v)
283
double r = v.length();
285
*lng = atan2(v[1],v[0]);
288
void rectToSphe(float *lng, float *lat, const Vec3f& v)
290
double r = v.length();
292
*lng = atan2(v[1],v[0]);
295
void rectToSphe(double *lng, double *lat, const Vec3f& v)
297
double r = v.length();
299
*lng = atan2(v[1],v[0]);
302
double getDecAngle(const QString& str)
304
QRegExp re1("^\\s*([\\+\\-])?\\s*(\\d+)\\s*[Dd\xBA]\\s*(\\d+)\\s*['Mm]\\s*(\\d+(\\.\\d+)?)\\s*[\"Ss]\\s*([NSEWnsew])?\\s*$");
305
QRegExp re2("^\\s*([\\+\\-])?\\s*(\\d+(\\.\\d+)?).?([NSEWnsew])\\s*$");
307
if (re1.exactMatch(str))
309
bool neg = (re1.capturedTexts().at(1) == "-");
310
double d = re1.capturedTexts().at(2).toDouble();
311
double m = re1.capturedTexts().at(3).toDouble();
312
double s = re1.capturedTexts().at(4).toDouble();
313
QString cardinal = re1.capturedTexts().at(6);
314
double deg = d + (m/60) + (s/3600);
315
if (cardinal.toLower() == "s" || cardinal.toLower() == "w" || neg)
317
return (deg * 2 * M_PI / 360.);
319
else if (re2.exactMatch(str))
321
bool neg = (re2.capturedTexts().at(1) == "-");
322
double deg = re2.capturedTexts().at(2).toDouble();
323
QString cardinal = re2.capturedTexts().at(4);
324
if (cardinal.toLower() == "s" || cardinal.toLower() == "w" || neg)
326
return (deg * 2 * M_PI / 360.);
329
qDebug() << "getDecAngle failed to parse angle string:" << str;
334
// Check if a number is a power of 2
335
bool isPowerOfTwo(int value)
337
return (value & -value) == value;
340
// Return the first power of two bigger than the given value
341
int getBiggerPowerOfTwo(int value)
349
// Return the inverse sinus hyperbolic of z
350
double asinh(double z)
352
return std::log(z+std::sqrt(z*z+1));
355
/*************************************************************************
356
Convert a QT QDateTime class to julian day
357
*************************************************************************/
358
double qDateTimeToJd(const QDateTime& dateTime)
360
return (double)(dateTime.date().toJulianDay())+(double)1./(24*60*60*1000)*QTime().msecsTo(dateTime.time())-0.5;
363
QDateTime jdToQDateTime(const double& jd)
365
int year, month, day;
366
getDateFromJulianDay(jd, &year, &month, &day);
367
QDateTime result = QDateTime::fromString(QString("%1.%2.%3").arg(year, 4, 10, QLatin1Char('0')).arg(month).arg(day), "yyyy.M.d");
368
result.setTime(jdFractionToQTime(jd));
372
// based on QDateTime's original handling, but expanded to handle 0.0 and earlier.
373
void getDateFromJulianDay(double jd, int *year, int *month, int *day)
377
// put us in the right calendar day for the time of day.
378
double fraction = jd - floor(jd);
386
// Gregorian calendar starting from October 15, 1582
387
// This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern
388
qulonglong ell, n, i, j;
389
ell = qulonglong(floor(jd)) + 68569;
390
n = (4 * ell) / 146097;
391
ell = ell - (146097 * n + 3) / 4;
392
i = (4000 * (ell + 1)) / 1461001;
393
ell = ell - (1461 * i) / 4 + 31;
394
j = (80 * ell) / 2447;
395
d = ell - (2447 * j) / 80;
397
m = j + 2 - (12 * ell);
398
y = 100 * (n - 49) + i + ell;
402
// Julian calendar until October 4, 1582
403
// Algorithm from Frequently Asked Questions about Calendars by Claus Toendering
404
int julianDay = (int)floor(jd);
406
int dd = (4 * julianDay + 3) / 1461;
407
int ee = julianDay - (1461 * dd) / 4;
408
int mm = ((5 * ee) + 2) / 153;
409
d = ee - (153 * mm + 2) / 5 + 1;
410
m = mm + 3 - 12 * (mm / 10);
411
y = dd - 4800 + (mm / 10);
418
void getTimeFromJulianDay(double julianDay, int *hour, int *minute, int *second)
420
double frac = julianDay - (floor(julianDay));
421
int s = (int)floor(frac * 24 * 60 * 60);
423
*hour = ((s / (60 * 60))+12)%24;
424
*minute = (s/(60))%60;
428
QString sixIntsToIsoString( int year, int month, int day, int hour, int minute, int second )
430
// formatting a negative doesnt work the way i expect
432
QString dt = QString("%1-%2-%3T%4:%5:%6")
433
.arg((year >= 0 ? year : -1* year),4,10,QLatin1Char('0'))
434
.arg(month,2,10,QLatin1Char('0'))
435
.arg(day,2,10,QLatin1Char('0'))
436
.arg(hour,2,10,QLatin1Char('0'))
437
.arg(minute,2,10,QLatin1Char('0'))
438
.arg(second,2,10,QLatin1Char('0'));
447
QString jdToIsoString(double jd)
449
int year, month, day, hour, minute, second;
450
getDateFromJulianDay(jd, &year, &month, &day);
451
getTimeFromJulianDay(jd, &hour, &minute, &second);
453
return sixIntsToIsoString(year, month, day, hour, minute, second);
456
// Format the date per the fmt.
457
QString localeDateString(int year, int month, int day, int dayOfWeek, QString fmt)
459
/* we have to handle the year zero, and the years before qdatetime can represent. */
460
const QLatin1Char quote('\'');
462
int quotestartedat = -1;
464
for (int i = 0; i < (int)fmt.length(); i++)
466
if (fmt.at(i) == quote)
468
if (quotestartedat >= 0)
470
if ((quotestartedat+1) == i)
485
else if (quotestartedat > 0)
489
else if (fmt.at(i) == QLatin1Char('d') ||
490
fmt.at(i) == QLatin1Char('M') ||
491
fmt.at(i) == QLatin1Char('y'))
494
while ((j < fmt.length()) && (fmt.at(j) == fmt.at(i)) && (4 >= (j-i+1)))
499
QString frag = fmt.mid(i,(j-i));
503
out += QString("%1").arg(day);
505
else if (frag == "dd")
507
out += QString("%1").arg(day, 2, 10, QLatin1Char('0'));
509
else if (frag == "ddd")
511
out += QDate::shortDayName(dayOfWeek+1);
513
else if (frag == "dddd")
515
out += QDate::longDayName(dayOfWeek+1);
517
else if (frag == "M")
519
out += QString("%1").arg(month);
521
else if (frag == "MM")
523
out += QString("%1").arg(month, 2, 10, QLatin1Char('0'));
525
else if (frag == "MMM")
527
out += QDate::shortMonthName(month);
529
else if (frag == "MMMM")
531
out += QDate::longMonthName(month);
533
else if (frag == "y")
537
else if (frag == "yy")
539
int dispyear = year % 100;
540
out += QString("%1").arg(dispyear,2,10,QLatin1Char('0'));
542
else if (frag == "yyy")
544
// assume greedy: understand yy before y.
545
int dispyear = year % 100;
546
out += QString("%1").arg(dispyear,2,10,QLatin1Char('0'));
547
out += QLatin1Char('y');
549
else if (frag == "yyyy")
551
int dispyear = (year >= 0 ? year : -1 * year);
554
out += QLatin1Char('-');
556
out += QString("%1").arg(dispyear,4,10,QLatin1Char('0'));
572
//! try to get a reasonable locale date string from the system, trying to work around
573
//! limitations of qdatetime for large dates in the past. see QDateTime::toString().
574
QString localeDateString(int year, int month, int day, int dayOfWeek)
577
// try the QDateTime first
578
QDate test(year, month, day);
580
if (test.isValid() && !test.toString(Qt::LocaleDate).isEmpty())
582
return test.toString(Qt::LocaleDate);
586
return localeDateString(year,month,day,dayOfWeek,QLocale().dateFormat(QLocale::ShortFormat));
591
//! use QDateTime to get a Julian Date from the system's current time.
592
//! this is an acceptable use of QDateTime because the system's current
593
//! time is more than likely always going to be expressible by QDateTime.
594
double getJDFromSystem(void)
596
return qDateTimeToJd(QDateTime::currentDateTime().toUTC());
599
double qTimeToJDFraction(const QTime& time)
601
return (double)1./(24*60*60*1000)*QTime().msecsTo(time)-0.5;
604
QTime jdFractionToQTime(const double jd)
606
double decHours = std::fmod(jd+0.5, 1.0);
607
int hours = (int)(decHours/0.041666666666666666666);
608
int mins = (int)((decHours-(hours*0.041666666666666666666))/0.00069444444444444444444);
609
return QTime::fromString(QString("%1.%2").arg(hours).arg(mins), "h.m");
612
// Use Qt's own sense of time and offset instead of platform specific code.
613
float getGMTShiftFromQT(double JD)
615
int year, month, day, hour, minute, second;
616
getDateFromJulianDay(JD, &year, &month, &day);
617
getTimeFromJulianDay(JD, &hour, &minute, &second);
618
QDateTime current(QDate(year, month, day), QTime(hour, minute, second));
619
if (! current.isValid())
621
qWarning() << "JD " << QString("%1").arg(JD) << " out of bounds of QT help with GMT shift, using current datetime";
622
current = QDateTime::currentDateTime();
624
QDateTime c1 = QDateTime::fromString(current.toString(Qt::ISODate),Qt::ISODate);
625
QDateTime u1 = QDateTime::fromString(current.toUTC().toString(Qt::ISODate),Qt::ISODate);
627
int secsto = u1.secsTo(c1);
628
float hrsto = secsto / 3600.0f;
633
bool getJDFromDate(double* newjd, int y, int m, int d, int h, int min, int s)
635
double deltaTime = (h / 24.0) + (min / (24.0*60.0)) + (s / (24.0 * 60.0 * 60.0)) - 0.5;
636
QDate test((y <= 0 ? y-1 : y), m, d);
637
// if QDate will oblige, do so.
638
if ( test.isValid() )
640
double qdjd = (double)test.toJulianDay();
646
double jd = (double)((1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 - (3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075) - 38;
654
double getJDFromDate_alg2(int y, int m, int d, int h, int min, int s)
656
double extra = (100.0* y) + m - 190002.5;
657
double rjd = 367.0 * y;
658
rjd -= floor(7.0*(y+floor((m+9.0)/12.0))/4.0);
659
rjd += floor(275.0*m/9.0) ;
661
rjd += (h + (min + s/60.0)/60.)/24.0;
663
rjd -= 0.5*extra/std::fabs(extra);
668
int numberOfDaysInMonthInYear(int month, int year)
693
if ( year % 100 == 0 )
695
if ( year % 400 == 0 )
728
return numberOfDaysInMonthInYear(12, year-1);
731
return numberOfDaysInMonthInYear(1, year+1);
740
//! given the submitted year/month/day hour:minute:second, try to
741
//! normalize into an actual year/month/day. values can be positive, 0,
742
//! or negative. start assessing from seconds to larger increments.
743
bool changeDateTimeForRollover(int oy, int om, int od, int oh, int omin, int os,
744
int* ry, int* rm, int* rd, int* rh, int* rmin, int* rs)
781
while ( od > numberOfDaysInMonthInYear(om, oy) ) {
782
od -= numberOfDaysInMonthInYear(om, oy);
791
od += numberOfDaysInMonthInYear(om-1,oy);
811
// and the julian-gregorian epoch hole: round up to the 15th
812
if ( oy == 1582 && om == 10 && ( od > 4 && od < 15 ) ) {
829
} // end of the StelUtils namespace