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"
29
#include "VecMath.hpp"
34
#include <QStringList>
35
#include <QTextStream>
44
double hmsToRad(unsigned int h, unsigned int m, double s )
46
return (double)M_PI/24.*h*2.+(double)M_PI/12.*m/60.+s*M_PI/43200.;
49
double dmsToRad(int d, unsigned int m, double s)
52
return (double)M_PI/180.*d+(double)M_PI/10800.*m+s*M_PI/648000.;
53
return (double)M_PI/180.*d-(double)M_PI/10800.*m-s*M_PI/648000.;
56
/*************************************************************************
57
Convert an angle in radian to hms
58
*************************************************************************/
59
void radToHms(double angle, unsigned int& h, unsigned int& m, double& s)
61
angle = std::fmod(angle,2.0*M_PI);
62
if (angle < 0.0) angle += 2.0*M_PI; // range: [0..2.0*M_PI)
66
h = (unsigned int)angle;
67
m = (unsigned int)((angle-h)*60);
68
s = (angle-h)*3600.-60.*m;
71
/*************************************************************************
72
Convert an angle in radian to dms
73
*************************************************************************/
74
void radToDms(double angle, bool& sign, unsigned int& d, unsigned int& m, double& s)
76
angle = std::fmod(angle,2.0*M_PI);
85
d = (unsigned int)angle;
86
m = (unsigned int)((angle - d)*60);
87
s = (angle-d)*3600-60*m;
90
/*************************************************************************
91
Convert an angle in radian to a hms formatted string
92
If the minute and second part are null are too small, don't print them
93
*************************************************************************/
94
QString radToHmsStrAdapt(double angle)
100
StelUtils::radToHms(angle+0.005*M_PI/12/(60*60), h, m, s);
102
if (std::fabs(s*100-(int)s*100)>=1)
105
ts.setRealNumberNotation(QTextStream::FixedNotation);
108
ts.setRealNumberPrecision(1);
115
ts << m << 'm' << (int)s << 's';
124
/*************************************************************************
125
Convert an angle in radian to a hms formatted string
126
If decimal is true, output should be like this: " 16h29m55.3s"
127
If decimal is true, output should be like this: " 16h20m0.4s"
128
If decimal is false, output should be like this: "0h26m5s"
129
*************************************************************************/
130
QString radToHmsStr(double angle, bool decimal)
134
StelUtils::radToHms(angle+0.005*M_PI/12/(60*60), h, m, s);
135
int width, precision;
150
// handle carry case (when seconds are rounded up)
151
if (QString("%1").arg(s, 0, 'f', precision) == carry)
161
if (h==24 && m==0 && s==0)
164
return QString("%1h%2m%3s").arg(h, width).arg(m).arg(s, 0, 'f', precision);
167
/*************************************************************************
168
Convert an angle in radian to a dms formatted string
169
If the minute and second part are null are too small, don't print them
170
*************************************************************************/
171
QString radToDmsStrAdapt(double angle, bool useD)
181
StelUtils::radToDms(angle+0.005*M_PI/180/(60*60)*(angle<0?-1.:1.), sign, d, m, s);
183
QTextStream os(&str);
185
os << (sign?'+':'-') << d << degsign;
186
if (std::fabs(s*100-(int)s*100)>=1)
188
os << m << '\'' << fixed << qSetRealNumberPrecision(2) << qSetFieldWidth(5) << qSetPadChar('0') << s << qSetFieldWidth(0) << '\"';
192
os << m << '\'' << (int)s << '\"';
198
//qDebug() << "radToDmsStrAdapt(" << angle << ", " << useD << ") = " << str;
203
/*************************************************************************
204
Convert an angle in radian to a dms formatted string
205
*************************************************************************/
206
QString radToDmsStr(double angle, bool decimal, bool useD)
216
StelUtils::radToDms(angle+0.005*M_PI/180/(60*60)*(angle<0?-1.:1.), sign, d, m, s);
218
QTextStream os(&str);
219
os << (sign?'+':'-') << d << degsign;
224
os << qSetRealNumberPrecision(1);
229
os << qSetRealNumberPrecision(0);
233
os << qSetFieldWidth(width) << m << qSetFieldWidth(0) << '\''
234
<< fixed << qSetFieldWidth(width) << qSetPadChar('0') << s
235
<< qSetFieldWidth(0) << '\"';
240
// Obtains a Vec3f from a string with the form x,y,z
241
Vec3f strToVec3f(const QStringList& s)
244
return Vec3f(0.f,0.f,0.f);
246
return Vec3f(s[0].toFloat(),s[1].toFloat(),s[2].toFloat());
249
Vec3f strToVec3f(const QString& s)
251
return strToVec3f(s.split(","));
254
// Converts a Vec3f to HTML color notation.
255
QString vec3fToHtmlColor(const Vec3f& v)
257
return QString("#%1%2%3")
258
.arg(qMin(255, int(v[0] * 255)), 2, 16, QChar('0'))
259
.arg(qMin(255, int(v[1] * 255)), 2, 16, QChar('0'))
260
.arg(qMin(255, int(v[2] * 255)), 2, 16, QChar('0'));
263
Vec3f htmlColorToVec3f(const QString& c)
266
QRegExp re("^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$");
267
if (re.exactMatch(c))
270
int i = re.capturedTexts().at(1).toInt(&ok, 16);
271
v[0] = (float)i / 255.;
272
i = re.capturedTexts().at(2).toInt(&ok, 16);
273
v[1] = (float)i / 255.;
274
i = re.capturedTexts().at(3).toInt(&ok, 16);
275
v[2] = (float)i / 255.;
286
void spheToRect(double lng, double lat, Vec3d& v)
288
const double cosLat = cos(lat);
289
v.set(cos(lng) * cosLat, sin(lng) * cosLat, sin(lat));
292
void spheToRect(float lng, float lat, Vec3f& v)
294
const double cosLat = cos(lat);
295
v.set(cos(lng) * cosLat, sin(lng) * cosLat, sin(lat));
298
void rectToSphe(double *lng, double *lat, const Vec3d& v)
300
double r = v.length();
302
*lng = atan2(v[1],v[0]);
305
void rectToSphe(float *lng, float *lat, const Vec3d& v)
307
double r = v.length();
309
*lng = atan2(v[1],v[0]);
312
void rectToSphe(float *lng, float *lat, const Vec3f& v)
314
double r = v.length();
316
*lng = atan2(v[1],v[0]);
319
void rectToSphe(double *lng, double *lat, const Vec3f& v)
321
double r = v.length();
323
*lng = atan2(v[1],v[0]);
326
double getDecAngle(const QString& str)
328
QRegExp re1("^\\s*([\\+\\-])?\\s*(\\d+)\\s*([hHDd\xBA])\\s*(\\d+)\\s*['Mm]\\s*(\\d+(\\.\\d+)?)\\s*[\"Ss]\\s*([NSEWnsew])?\\s*$"); // DMS/HMS
329
QRegExp re2("^\\s*([\\+\\-])?\\s*(\\d+(\\.\\d+)?).?([NSEWnsew])?\\s*$"); // Decimal
331
if (re1.exactMatch(str))
333
bool neg = (re1.capturedTexts().at(1) == "-");
334
double d = re1.capturedTexts().at(2).toDouble();
335
double m = re1.capturedTexts().at(4).toDouble();
336
double s = re1.capturedTexts().at(5).toDouble();
337
if (re1.capturedTexts().at(3).toUpper() == "H")
343
QString cardinal = re1.capturedTexts().at(7);
344
double deg = d + (m/60) + (s/3600);
345
if (cardinal.toLower() == "s" || cardinal.toLower() == "w" || neg)
347
return (deg * 2 * M_PI / 360.);
349
else if (re2.exactMatch(str))
351
bool neg = (re2.capturedTexts().at(1) == "-");
352
double deg = re2.capturedTexts().at(2).toDouble();
353
QString cardinal = re2.capturedTexts().at(4);
354
if (cardinal.toLower() == "s" || cardinal.toLower() == "w" || neg)
356
return (deg * 2 * M_PI / 360.);
359
qDebug() << "getDecAngle failed to parse angle string:" << str;
363
// Check if a number is a power of 2
364
bool isPowerOfTwo(int value)
366
return (value & -value) == value;
369
// Return the first power of two bigger than the given value
370
int getBiggerPowerOfTwo(int value)
378
// Return the inverse sinus hyperbolic of z
379
double asinh(double z)
381
return std::log(z+std::sqrt(z*z+1));
384
/*************************************************************************
385
Convert a QT QDateTime class to julian day
386
*************************************************************************/
387
double qDateTimeToJd(const QDateTime& dateTime)
389
return (double)(dateTime.date().toJulianDay())+(double)1./(24*60*60*1000)*QTime().msecsTo(dateTime.time())-0.5;
392
QDateTime jdToQDateTime(const double& jd)
394
int year, month, day;
395
getDateFromJulianDay(jd, &year, &month, &day);
396
QDateTime result = QDateTime::fromString(QString("%1.%2.%3").arg(year, 4, 10, QLatin1Char('0')).arg(month).arg(day), "yyyy.M.d");
397
result.setTime(jdFractionToQTime(jd));
401
// based on QDateTime's original handling, but expanded to handle 0.0 and earlier.
402
void getDateFromJulianDay(double jd, int *year, int *month, int *day)
406
// put us in the right calendar day for the time of day.
407
double fraction = jd - floor(jd);
415
// Gregorian calendar starting from October 15, 1582
416
// This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern
417
qulonglong ell, n, i, j;
418
ell = qulonglong(floor(jd)) + 68569;
419
n = (4 * ell) / 146097;
420
ell = ell - (146097 * n + 3) / 4;
421
i = (4000 * (ell + 1)) / 1461001;
422
ell = ell - (1461 * i) / 4 + 31;
423
j = (80 * ell) / 2447;
424
d = ell - (2447 * j) / 80;
426
m = j + 2 - (12 * ell);
427
y = 100 * (n - 49) + i + ell;
431
// Julian calendar until October 4, 1582
432
// Algorithm from Frequently Asked Questions about Calendars by Claus Toendering
433
int julianDay = (int)floor(jd);
435
int dd = (4 * julianDay + 3) / 1461;
436
int ee = julianDay - (1461 * dd) / 4;
437
int mm = ((5 * ee) + 2) / 153;
438
d = ee - (153 * mm + 2) / 5 + 1;
439
m = mm + 3 - 12 * (mm / 10);
440
y = dd - 4800 + (mm / 10);
447
void getTimeFromJulianDay(double julianDay, int *hour, int *minute, int *second)
449
double frac = julianDay - (floor(julianDay));
450
int s = (int)floor(frac * 24 * 60 * 60);
452
*hour = ((s / (60 * 60))+12)%24;
453
*minute = (s/(60))%60;
457
QString sixIntsToIsoString( int year, int month, int day, int hour, int minute, int second )
459
// formatting a negative doesnt work the way i expect
461
QString dt = QString("%1-%2-%3T%4:%5:%6")
462
.arg((year >= 0 ? year : -1* year),4,10,QLatin1Char('0'))
463
.arg(month,2,10,QLatin1Char('0'))
464
.arg(day,2,10,QLatin1Char('0'))
465
.arg(hour,2,10,QLatin1Char('0'))
466
.arg(minute,2,10,QLatin1Char('0'))
467
.arg(second,2,10,QLatin1Char('0'));
476
QString jdToIsoString(double jd)
478
int year, month, day, hour, minute, second;
479
getDateFromJulianDay(jd, &year, &month, &day);
480
getTimeFromJulianDay(jd, &hour, &minute, &second);
482
return sixIntsToIsoString(year, month, day, hour, minute, second);
485
// Format the date per the fmt.
486
QString localeDateString(int year, int month, int day, int dayOfWeek, QString fmt)
488
/* we have to handle the year zero, and the years before qdatetime can represent. */
489
const QLatin1Char quote('\'');
491
int quotestartedat = -1;
493
for (int i = 0; i < (int)fmt.length(); i++)
495
if (fmt.at(i) == quote)
497
if (quotestartedat >= 0)
499
if ((quotestartedat+1) == i)
514
else if (quotestartedat > 0)
518
else if (fmt.at(i) == QLatin1Char('d') ||
519
fmt.at(i) == QLatin1Char('M') ||
520
fmt.at(i) == QLatin1Char('y'))
523
while ((j < fmt.length()) && (fmt.at(j) == fmt.at(i)) && (4 >= (j-i+1)))
528
QString frag = fmt.mid(i,(j-i));
532
out += QString("%1").arg(day);
534
else if (frag == "dd")
536
out += QString("%1").arg(day, 2, 10, QLatin1Char('0'));
538
else if (frag == "ddd")
540
out += QDate::shortDayName(dayOfWeek+1);
542
else if (frag == "dddd")
544
out += QDate::longDayName(dayOfWeek+1);
546
else if (frag == "M")
548
out += QString("%1").arg(month);
550
else if (frag == "MM")
552
out += QString("%1").arg(month, 2, 10, QLatin1Char('0'));
554
else if (frag == "MMM")
556
out += QDate::shortMonthName(month);
558
else if (frag == "MMMM")
560
out += QDate::longMonthName(month);
562
else if (frag == "y")
566
else if (frag == "yy")
568
int dispyear = year % 100;
569
out += QString("%1").arg(dispyear,2,10,QLatin1Char('0'));
571
else if (frag == "yyy")
573
// assume greedy: understand yy before y.
574
int dispyear = year % 100;
575
out += QString("%1").arg(dispyear,2,10,QLatin1Char('0'));
576
out += QLatin1Char('y');
578
else if (frag == "yyyy")
580
int dispyear = (year >= 0 ? year : -1 * year);
583
out += QLatin1Char('-');
585
out += QString("%1").arg(dispyear,4,10,QLatin1Char('0'));
601
//! try to get a reasonable locale date string from the system, trying to work around
602
//! limitations of qdatetime for large dates in the past. see QDateTime::toString().
603
QString localeDateString(int year, int month, int day, int dayOfWeek)
606
// try the QDateTime first
607
QDate test(year, month, day);
609
// try to avoid QDate's non-astronomical time here, don't do BCE or year 0.
610
if (year > 0 && test.isValid() && !test.toString(Qt::LocaleDate).isEmpty())
612
return test.toString(Qt::LocaleDate);
616
return localeDateString(year,month,day,dayOfWeek,QLocale().dateFormat(QLocale::ShortFormat));
621
//! use QDateTime to get a Julian Date from the system's current time.
622
//! this is an acceptable use of QDateTime because the system's current
623
//! time is more than likely always going to be expressible by QDateTime.
624
double getJDFromSystem(void)
626
return qDateTimeToJd(QDateTime::currentDateTime().toUTC());
629
double qTimeToJDFraction(const QTime& time)
631
return (double)1./(24*60*60*1000)*QTime().msecsTo(time)-0.5;
634
QTime jdFractionToQTime(const double jd)
636
double decHours = std::fmod(jd+0.5, 1.0);
637
int hours = (int)(decHours/0.041666666666666666666);
638
int mins = (int)((decHours-(hours*0.041666666666666666666))/0.00069444444444444444444);
639
return QTime::fromString(QString("%1.%2").arg(hours).arg(mins), "h.m");
642
// Use Qt's own sense of time and offset instead of platform specific code.
643
float getGMTShiftFromQT(double JD)
645
int year, month, day, hour, minute, second;
646
getDateFromJulianDay(JD, &year, &month, &day);
647
getTimeFromJulianDay(JD, &hour, &minute, &second);
648
// as analogous to second statement in getJDFromDate, nkerr
652
QDateTime current(QDate(year, month, day), QTime(hour, minute, second));
653
if (! current.isValid())
655
//qWarning() << "JD " << QString("%1").arg(JD) << " out of bounds of QT help with GMT shift, using current datetime";
656
// Assumes the GMT shift was always the same before year -4710
657
current = QDateTime(QDate(-4710, month, day), QTime(hour, minute, second));
659
QDateTime c1 = QDateTime::fromString(current.toString(Qt::ISODate),Qt::ISODate);
660
QDateTime u1 = QDateTime::fromString(current.toUTC().toString(Qt::ISODate),Qt::ISODate);
662
int secsto = u1.secsTo(c1);
663
float hrsto = secsto / 3600.0f;
668
bool getJDFromDate(double* newjd, int y, int m, int d, int h, int min, int s)
670
double deltaTime = (h / 24.0) + (min / (24.0*60.0)) + (s / (24.0 * 60.0 * 60.0)) - 0.5;
671
QDate test((y <= 0 ? y-1 : y), m, d);
672
// if QDate will oblige, do so.
673
if ( test.isValid() )
675
double qdjd = (double)test.toJulianDay();
681
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;
689
double getJDFromDate_alg2(int y, int m, int d, int h, int min, int s)
691
double extra = (100.0* y) + m - 190002.5;
692
double rjd = 367.0 * y;
693
rjd -= floor(7.0*(y+floor((m+9.0)/12.0))/4.0);
694
rjd += floor(275.0*m/9.0) ;
696
rjd += (h + (min + s/60.0)/60.)/24.0;
698
rjd -= 0.5*extra/std::fabs(extra);
703
int numberOfDaysInMonthInYear(int month, int year)
728
if ( year % 100 == 0 )
730
if ( year % 400 == 0 )
763
return numberOfDaysInMonthInYear(12, year-1);
766
return numberOfDaysInMonthInYear(1, year+1);
775
//! given the submitted year/month/day hour:minute:second, try to
776
//! normalize into an actual year/month/day. values can be positive, 0,
777
//! or negative. start assessing from seconds to larger increments.
778
bool changeDateTimeForRollover(int oy, int om, int od, int oh, int omin, int os,
779
int* ry, int* rm, int* rd, int* rh, int* rmin, int* rs)
816
while ( od > numberOfDaysInMonthInYear(om, oy) ) {
817
od -= numberOfDaysInMonthInYear(om, oy);
826
od += numberOfDaysInMonthInYear(om-1,oy);
846
// and the julian-gregorian epoch hole: round up to the 15th
847
if ( oy == 1582 && om == 10 && ( od > 4 && od < 15 ) ) {
863
void debugQVariantMap(const QVariant& m, const QString& indent, const QString& key)
865
QVariant::Type t = m.type();
866
if (t == QVariant::Map)
868
qDebug() << indent + key + "(map):";
869
QList<QString> keys = m.toMap().keys();
871
foreach(QString k, keys)
873
debugQVariantMap(m.toMap()[k], indent + " ", k);
876
else if (t == QVariant::List)
878
qDebug() << indent + key + "(list):";
879
foreach(QVariant item, m.toList())
881
debugQVariantMap(item, indent + " ");
885
qDebug() << indent + key + " => " + m.toString();
889
QList<int> getIntsFromISO8601String(const QString & dt)
891
// Represents a valid, complete date string.
892
static QRegExp finalRe("(-0*[1-9][0-9]{0,5}|0+|0*[1-9][0-9]{0,5})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[10])[T ]([01][0-9]|2[0123]):([012345][0-9]):([012345][0-9])");
895
if (finalRe.exactMatch(dt))
897
QStringList number_strings = finalRe.capturedTexts();
900
for (int i = 1; i < number_strings.size(); i++)
902
qWarning() << ":: at capture " << i << " got a " << number_strings[i];
904
v = number_strings[i].toInt(&ok, 10);
905
qWarning() << " :: and it was a " << v << " " << ok;
913
qWarning() << "StelUtils::getIntsFromISO8601String: input string failed to be an exact date at capture " << i << ", returning nothing: " << dt;
920
qWarning() << "StelUtils::getIntsFromISO8601String: input string failed to be an exact date, returning nothing: " << dt;
926
} // end of the StelUtils namespace