File indexing completed on 2024-04-21 03:42:14
0001 /* 0002 SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "dms.h" 0008 0009 #include <QLocale> 0010 0011 #include <QRegExp> 0012 0013 // Qt version calming 0014 #include <qtskipemptyparts.h> 0015 0016 #ifdef COUNT_DMS_SINCOS_CALLS 0017 long unsigned dms::dms_constructor_calls = 0; 0018 long unsigned dms::dms_with_sincos_called = 0; 0019 long unsigned dms::trig_function_calls = 0; 0020 long unsigned dms::redundant_trig_function_calls = 0; 0021 double dms::seconds_in_trig = 0; 0022 #endif 0023 0024 void dms::setD(const int &d, const int &m, const int &s, const int &ms) 0025 { 0026 D = (double)abs(d) + ((double)m + ((double)s + (double)ms / 1000.) / 60.) / 60.; 0027 if (d < 0) 0028 { 0029 D = -1.0 * D; 0030 } 0031 #ifdef COUNT_DMS_SINCOS_CALLS 0032 m_cosDirty = m_sinDirty = true; 0033 #endif 0034 } 0035 0036 void dms::setH(const int &h, const int &m, const int &s, const int &ms) 0037 { 0038 D = 15.0 * ((double)abs(h) + ((double)m + ((double)s + (double)ms / 1000.) / 60.) / 60.); 0039 if (h < 0) 0040 { 0041 D = -1.0 * D; 0042 } 0043 #ifdef COUNT_DMS_SINCOS_CALLS 0044 m_cosDirty = m_sinDirty = true; 0045 #endif 0046 } 0047 0048 bool dms::setFromString(const QString &str, bool isDeg) 0049 { 0050 int d(0), m(0); 0051 double s(0.0); 0052 bool checkValue(false), badEntry(false), negative(false); 0053 QString entry = str.trimmed(); 0054 entry.remove(QRegExp("[hdms'\"°]")); 0055 0056 //Account for localized decimal-point settings 0057 //QString::toDouble() requires that the decimal symbol is "." 0058 entry.replace(QLocale().decimalPoint(), "."); 0059 0060 //empty entry returns false 0061 if (entry.isEmpty()) 0062 { 0063 dms::setD(NaN::d); 0064 return false; 0065 } 0066 0067 //try parsing a simple integer 0068 d = entry.toInt(&checkValue); 0069 if (checkValue) 0070 { 0071 if (isDeg) 0072 dms::setD(d, 0, 0); 0073 else 0074 dms::setH(d, 0, 0); 0075 return true; 0076 } 0077 0078 //try parsing a simple double 0079 double x = entry.toDouble(&checkValue); 0080 if (checkValue) 0081 { 0082 if (isDeg) 0083 dms::setD(x); 0084 else 0085 dms::setH(x); 0086 return true; 0087 } 0088 0089 //try parsing multiple fields. 0090 QStringList fields; 0091 0092 //check for colon-delimiters or space-delimiters 0093 if (entry.contains(':')) 0094 fields = entry.split(':', Qt::SkipEmptyParts); 0095 else 0096 fields = entry.split(' ', Qt::SkipEmptyParts); 0097 0098 //anything with one field is invalid! 0099 if (fields.count() == 1) 0100 { 0101 dms::setD(NaN::d); 0102 return false; 0103 } 0104 0105 //If two fields we will add a third one, and then parse with 0106 //the 3-field code block. If field[1] is an int, add a third field equal to "0". 0107 //If field[1] is a double, convert it to integer arcmin, and convert 0108 //the remainder to integer arcsec 0109 //If field[1] is neither int nor double, return false. 0110 if (fields.count() == 2) 0111 { 0112 m = fields[1].toInt(&checkValue); 0113 if (checkValue) 0114 fields.append(QString("0")); 0115 else 0116 { 0117 double mx = fields[1].toDouble(&checkValue); 0118 if (checkValue) 0119 { 0120 fields[1] = QString::number(int(mx)); 0121 fields.append(QString::number(int(60.0 * (mx - int(mx))))); 0122 } 0123 else 0124 { 0125 dms::setD(NaN::d); 0126 return false; 0127 } 0128 } 0129 } 0130 0131 //Now have (at least) three fields ( h/d m s ); 0132 //we can ignore anything after 3rd field 0133 if (fields.count() >= 3) 0134 { 0135 //See if first two fields parse as integers, and third field as a double 0136 0137 d = fields[0].toInt(&checkValue); 0138 if (!checkValue) 0139 badEntry = true; 0140 m = fields[1].toInt(&checkValue); 0141 if (!checkValue) 0142 badEntry = true; 0143 s = fields[2].toDouble(&checkValue); 0144 if (!checkValue) 0145 badEntry = true; 0146 0147 //Special case: If first field is "-0", store the negative sign. 0148 //(otherwise it gets dropped) 0149 if (fields[0].at(0) == '-' && d == 0) 0150 negative = true; 0151 } 0152 0153 if (!badEntry) 0154 { 0155 double D = (double)abs(d) + (double)abs(m) / 60. + (double)fabs(s) / 3600.; 0156 0157 if (negative || d < 0 || m < 0 || s < 0) 0158 { 0159 D = -1.0 * D; 0160 } 0161 0162 if (isDeg) 0163 { 0164 dms::setD(D); 0165 } 0166 else 0167 { 0168 dms::setH(D); 0169 } 0170 } 0171 else 0172 { 0173 dms::setD(NaN::d); 0174 return false; 0175 } 0176 0177 return true; 0178 } 0179 0180 int dms::arcmin(void) const 0181 { 0182 if (std::isnan(D)) 0183 return 0; 0184 0185 int am = int(60.0 * (fabs(D) - abs(degree()))); 0186 if (D < 0.0 && D > -1.0) //angle less than zero, but greater than -1.0 0187 { 0188 am = -1 * am; //make minute negative 0189 } 0190 return am; // Warning: Will return 0 if the value is NaN 0191 } 0192 0193 int dms::arcsec(void) const 0194 { 0195 if (std::isnan(D)) 0196 return 0; 0197 0198 int as = int(60.0 * (60.0 * (fabs(D) - abs(degree())) - abs(arcmin()))); 0199 //If the angle is slightly less than 0.0, give ArcSec a neg. sgn. 0200 if (degree() == 0 && arcmin() == 0 && D < 0.0) 0201 { 0202 as = -1 * as; 0203 } 0204 return as; // Warning: Will return 0 if the value is NaN 0205 } 0206 0207 int dms::marcsec(void) const 0208 { 0209 if (std::isnan(D)) 0210 return 0; 0211 0212 int as = int(1000.0 * (60.0 * (60.0 * (fabs(D) - abs(degree())) - abs(arcmin())) - abs(arcsec()))); 0213 //If the angle is slightly less than 0.0, give ArcSec a neg. sgn. 0214 if (degree() == 0 && arcmin() == 0 && arcsec() == 0 && D < 0.0) 0215 { 0216 as = -1 * as; 0217 } 0218 return as; // Warning: Will return 0 if the value is NaN 0219 } 0220 0221 int dms::minute(void) const 0222 { 0223 int hm = int(60.0 * (fabs(Hours()) - abs(hour()))); 0224 if (Hours() < 0.0 && Hours() > -1.0) //angle less than zero, but greater than -1.0 0225 { 0226 hm = -1 * hm; //make minute negative 0227 } 0228 return hm; // Warning: Will return 0 if the value is NaN 0229 } 0230 0231 int dms::second(void) const 0232 { 0233 int hs = int(60.0 * (60.0 * (fabs(Hours()) - abs(hour())) - abs(minute()))); 0234 if (hour() == 0 && minute() == 0 && Hours() < 0.0) 0235 { 0236 hs = -1 * hs; 0237 } 0238 return hs; // Warning: Will return 0 if the value is NaN 0239 } 0240 0241 int dms::msecond(void) const 0242 { 0243 int hs = int(1000.0 * (60.0 * (60.0 * (fabs(Hours()) - abs(hour())) - abs(minute())) - abs(second()))); 0244 if (hour() == 0 && minute() == 0 && second() == 0 && Hours() < 0.0) 0245 { 0246 hs = -1 * hs; 0247 } 0248 return hs; // Warning: Will return 0 if the value is NaN 0249 } 0250 0251 const dms dms::reduce(void) const 0252 { 0253 if (std::isnan(D)) 0254 return dms(0); 0255 0256 return dms(D - 360.0 * floor(D / 360.0)); 0257 } 0258 0259 const dms dms::deltaAngle(dms angle) const 0260 { 0261 double angleDiff = D - angle.Degrees(); 0262 0263 // Put in the range of [-360,360] 0264 while (angleDiff > 360) 0265 angleDiff -= 360; 0266 while (angleDiff < -360) 0267 angleDiff += 360; 0268 // angleDiff in the range [180,360] 0269 if (angleDiff > 180) 0270 return dms(360 - angleDiff); 0271 // angleDiff in the range [-360,-180] 0272 else if (angleDiff < -180) 0273 return dms(-(360 + angleDiff)); 0274 // angleDiff in the range [-180,180] 0275 else 0276 return dms(angleDiff); 0277 } 0278 0279 const QString dms::toDMSString(const bool forceSign, const bool machineReadable, const bool highPrecision) const 0280 { 0281 QString dummy; 0282 char pm(' '); 0283 QChar zero('0'); 0284 int dd, dm, ds; 0285 0286 if (machineReadable || !highPrecision) 0287 // minimize the mean angle representation error of DMS format 0288 // set LSD transition in the middle of +- half precision range 0289 { 0290 double half_precision = 1.0 / 7200.0; 0291 if (Degrees() < 0.0) 0292 half_precision = -half_precision; 0293 dms angle(Degrees() + half_precision); 0294 dd = abs(angle.degree()); 0295 dm = abs(angle.arcmin()); 0296 ds = abs(angle.arcsec()); 0297 } 0298 else 0299 { 0300 dd = abs(degree()); 0301 dm = abs(arcmin()); 0302 ds = abs(arcsec()); 0303 } 0304 0305 if (Degrees() < 0.0) 0306 pm = '-'; 0307 else if (forceSign && Degrees() > 0.0) 0308 pm = '+'; 0309 0310 if (machineReadable) 0311 return QString("%1%2:%3:%4").arg(pm) 0312 .arg(dd, 2, 10, QChar('0')) 0313 .arg(dm, 2, 10, QChar('0')) 0314 .arg(ds, 2, 10, QChar('0')); 0315 0316 if (highPrecision) 0317 { 0318 double sec = arcsec() + marcsec() / 1000.; 0319 return QString("%1%2° %3\' %L4\"").arg(pm) 0320 .arg(dd, 2, 10, zero) 0321 .arg(dm, 2, 10, zero) 0322 .arg(sec, 2,'f', 2, zero); 0323 } 0324 0325 return QString("%1%2° %3\' %4\"").arg(pm) 0326 .arg(dd, 2, 10, zero) 0327 .arg(dm, 2, 10, zero) 0328 .arg(ds, 2, 10, zero); 0329 0330 #if 0 0331 if (!machineReadable && dd < 10) 0332 { 0333 if (highPrecision) 0334 { 0335 double sec = arcsec() + marcsec() / 1000.; 0336 return dummy.sprintf("%c%1d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec); 0337 } 0338 0339 return dummy.sprintf("%c%1d%c %02d\' %02d\"", pm, dd, 176, dm, ds); 0340 } 0341 0342 if (!machineReadable && dd < 100) 0343 { 0344 if (highPrecision) 0345 { 0346 double sec = arcsec() + marcsec() / 1000.; 0347 return dummy.sprintf("%c%2d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec); 0348 } 0349 0350 return dummy.sprintf("%c%2d%c %02d\' %02d\"", pm, dd, 176, dm, ds); 0351 } 0352 if (machineReadable && dd < 100) 0353 return dummy.sprintf("%c%02d:%02d:%02d", pm, dd, dm, ds); 0354 0355 if (!machineReadable) 0356 { 0357 if (highPrecision) 0358 { 0359 double sec = arcsec() + marcsec() / 1000.; 0360 return dummy.sprintf("%c%3d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec); 0361 } 0362 else 0363 return dummy.sprintf("%c%3d%c %02d\' %02d\"", pm, dd, 176, dm, ds); 0364 } 0365 else 0366 return dummy.sprintf("%c%03d:%02d:%02d", pm, dd, dm, ds); 0367 #endif 0368 } 0369 0370 const QString dms::toHMSString(const bool machineReadable, const bool highPrecision) const 0371 { 0372 QChar zero('0'); 0373 dms angle; 0374 int hh, hm, hs; 0375 0376 if (machineReadable || !highPrecision) 0377 // minimize the mean angle representation error of HMS format 0378 // set LSD transition in the middle of +- half precision range 0379 { 0380 double half_precision = 15.0 / 7200.0; 0381 angle.setD(Degrees() + half_precision); 0382 hh = angle.hour(); 0383 hm = angle.minute(); 0384 hs = angle.second(); 0385 } 0386 0387 if (machineReadable) 0388 return QString("%1:%2:%3").arg(hh, 2, 10, zero) 0389 .arg(hm, 2, 10, zero) 0390 .arg(hs, 2, 10, zero); 0391 0392 if (highPrecision) 0393 { 0394 double sec = second() + msecond() / 1000.; 0395 return QString("%1h %2m %L3s").arg(hour(), 2, 10, zero) 0396 .arg(minute(), 2, 10, zero) 0397 .arg(sec, 2, 'f', 2, zero); 0398 } 0399 0400 return QString("%1h %2m %3s").arg(hh, 2, 10, zero) 0401 .arg(hm, 2, 10, zero) 0402 .arg(hs, 2, 10, zero); 0403 0404 #if 0 0405 QString dummy; 0406 if (!machineReadable) 0407 { 0408 if (highPrecision) 0409 { 0410 double sec = second() + msecond() / 1000.; 0411 return dummy.sprintf("%02dh %02dm %05.2f", hour(), minute(), sec); 0412 } 0413 else 0414 return dummy.sprintf("%02dh %02dm %02ds", hh, hm, hs); 0415 } 0416 else 0417 return dummy.sprintf("%02d:%02d:%02d", hh, hm, hs); 0418 #endif 0419 } 0420 0421 dms dms::fromString(const QString &st, bool deg) 0422 { 0423 dms result; 0424 result.setFromString(st, deg); 0425 return result; 0426 //bool ok( false ); 0427 0428 //ok = result.setFromString( st, deg ); 0429 0430 // if ( ok ) 0431 //return result; 0432 // else { 0433 // kDebug() << i18n( "Could Not Set Angle from string: " ) << st; 0434 // return result; 0435 // } 0436 } 0437 0438 void dms::reduceToRange(enum dms::AngleRanges range) 0439 { 0440 if (std::isnan(D)) 0441 return; 0442 0443 switch (range) 0444 { 0445 case MINUSPI_TO_PI: 0446 D -= 360. * floor((D + 180.) / 360.); 0447 break; 0448 case ZERO_TO_2PI: 0449 D -= 360. * floor(D / 360.); 0450 } 0451 } 0452 0453 QDataStream &operator<<(QDataStream &out, const dms &d) 0454 { 0455 out << d.D; 0456 return out; 0457 } 0458 0459 QDataStream &operator>>(QDataStream &in, dms &d){ 0460 double D; 0461 in >> D; 0462 d = dms(D); 0463 return in; 0464 }