1 /*
2 SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "dms.h"
8
9 #include <QLocale>
10
11 #include <QRegExp>
12
13 #ifdef COUNT_DMS_SINCOS_CALLS
14 long unsigned dms::dms_constructor_calls = 0;
15 long unsigned dms::dms_with_sincos_called = 0;
16 long unsigned dms::trig_function_calls = 0;
17 long unsigned dms::redundant_trig_function_calls = 0;
18 double dms::seconds_in_trig = 0;
19 #endif
20
setD(const int & d,const int & m,const int & s,const int & ms)21 void dms::setD(const int &d, const int &m, const int &s, const int &ms)
22 {
23 D = (double)abs(d) + ((double)m + ((double)s + (double)ms / 1000.) / 60.) / 60.;
24 if (d < 0)
25 {
26 D = -1.0 * D;
27 }
28 #ifdef COUNT_DMS_SINCOS_CALLS
29 m_cosDirty = m_sinDirty = true;
30 #endif
31 }
32
setH(const int & h,const int & m,const int & s,const int & ms)33 void dms::setH(const int &h, const int &m, const int &s, const int &ms)
34 {
35 D = 15.0 * ((double)abs(h) + ((double)m + ((double)s + (double)ms / 1000.) / 60.) / 60.);
36 if (h < 0)
37 {
38 D = -1.0 * D;
39 }
40 #ifdef COUNT_DMS_SINCOS_CALLS
41 m_cosDirty = m_sinDirty = true;
42 #endif
43 }
44
setFromString(const QString & str,bool isDeg)45 bool dms::setFromString(const QString &str, bool isDeg)
46 {
47 int d(0), m(0);
48 double s(0.0);
49 bool checkValue(false), badEntry(false), negative(false);
50 QString entry = str.trimmed();
51 entry.remove(QRegExp("[hdms'\"°]"));
52
53 //Account for localized decimal-point settings
54 //QString::toDouble() requires that the decimal symbol is "."
55 entry.replace(QLocale().decimalPoint(), ".");
56
57 //empty entry returns false
58 if (entry.isEmpty())
59 {
60 dms::setD(NaN::d);
61 return false;
62 }
63
64 //try parsing a simple integer
65 d = entry.toInt(&checkValue);
66 if (checkValue)
67 {
68 if (isDeg)
69 dms::setD(d, 0, 0);
70 else
71 dms::setH(d, 0, 0);
72 return true;
73 }
74
75 //try parsing a simple double
76 double x = entry.toDouble(&checkValue);
77 if (checkValue)
78 {
79 if (isDeg)
80 dms::setD(x);
81 else
82 dms::setH(x);
83 return true;
84 }
85
86 //try parsing multiple fields.
87 QStringList fields;
88
89 //check for colon-delimiters or space-delimiters
90 if (entry.contains(':'))
91 fields = entry.split(':', QString::SkipEmptyParts);
92 else
93 fields = entry.split(' ', QString::SkipEmptyParts);
94
95 //anything with one field is invalid!
96 if (fields.count() == 1)
97 {
98 dms::setD(NaN::d);
99 return false;
100 }
101
102 //If two fields we will add a third one, and then parse with
103 //the 3-field code block. If field[1] is an int, add a third field equal to "0".
104 //If field[1] is a double, convert it to integer arcmin, and convert
105 //the remainder to integer arcsec
106 //If field[1] is neither int nor double, return false.
107 if (fields.count() == 2)
108 {
109 m = fields[1].toInt(&checkValue);
110 if (checkValue)
111 fields.append(QString("0"));
112 else
113 {
114 double mx = fields[1].toDouble(&checkValue);
115 if (checkValue)
116 {
117 fields[1] = QString::number(int(mx));
118 fields.append(QString::number(int(60.0 * (mx - int(mx)))));
119 }
120 else
121 {
122 dms::setD(NaN::d);
123 return false;
124 }
125 }
126 }
127
128 //Now have (at least) three fields ( h/d m s );
129 //we can ignore anything after 3rd field
130 if (fields.count() >= 3)
131 {
132 //See if first two fields parse as integers, and third field as a double
133
134 d = fields[0].toInt(&checkValue);
135 if (!checkValue)
136 badEntry = true;
137 m = fields[1].toInt(&checkValue);
138 if (!checkValue)
139 badEntry = true;
140 s = fields[2].toDouble(&checkValue);
141 if (!checkValue)
142 badEntry = true;
143
144 //Special case: If first field is "-0", store the negative sign.
145 //(otherwise it gets dropped)
146 if (fields[0].at(0) == '-' && d == 0)
147 negative = true;
148 }
149
150 if (!badEntry)
151 {
152 double D = (double)abs(d) + (double)abs(m) / 60. + (double)fabs(s) / 3600.;
153
154 if (negative || d < 0 || m < 0 || s < 0)
155 {
156 D = -1.0 * D;
157 }
158
159 if (isDeg)
160 {
161 dms::setD(D);
162 }
163 else
164 {
165 dms::setH(D);
166 }
167 }
168 else
169 {
170 dms::setD(NaN::d);
171 return false;
172 }
173
174 return true;
175 }
176
arcmin(void) const177 int dms::arcmin(void) const
178 {
179 if (std::isnan(D))
180 return 0;
181
182 int am = int(60.0 * (fabs(D) - abs(degree())));
183 if (D < 0.0 && D > -1.0) //angle less than zero, but greater than -1.0
184 {
185 am = -1 * am; //make minute negative
186 }
187 return am; // Warning: Will return 0 if the value is NaN
188 }
189
arcsec(void) const190 int dms::arcsec(void) const
191 {
192 if (std::isnan(D))
193 return 0;
194
195 int as = int(60.0 * (60.0 * (fabs(D) - abs(degree())) - abs(arcmin())));
196 //If the angle is slightly less than 0.0, give ArcSec a neg. sgn.
197 if (degree() == 0 && arcmin() == 0 && D < 0.0)
198 {
199 as = -1 * as;
200 }
201 return as; // Warning: Will return 0 if the value is NaN
202 }
203
marcsec(void) const204 int dms::marcsec(void) const
205 {
206 if (std::isnan(D))
207 return 0;
208
209 int as = int(1000.0 * (60.0 * (60.0 * (fabs(D) - abs(degree())) - abs(arcmin())) - abs(arcsec())));
210 //If the angle is slightly less than 0.0, give ArcSec a neg. sgn.
211 if (degree() == 0 && arcmin() == 0 && arcsec() == 0 && D < 0.0)
212 {
213 as = -1 * as;
214 }
215 return as; // Warning: Will return 0 if the value is NaN
216 }
217
minute(void) const218 int dms::minute(void) const
219 {
220 int hm = int(60.0 * (fabs(Hours()) - abs(hour())));
221 if (Hours() < 0.0 && Hours() > -1.0) //angle less than zero, but greater than -1.0
222 {
223 hm = -1 * hm; //make minute negative
224 }
225 return hm; // Warning: Will return 0 if the value is NaN
226 }
227
second(void) const228 int dms::second(void) const
229 {
230 int hs = int(60.0 * (60.0 * (fabs(Hours()) - abs(hour())) - abs(minute())));
231 if (hour() == 0 && minute() == 0 && Hours() < 0.0)
232 {
233 hs = -1 * hs;
234 }
235 return hs; // Warning: Will return 0 if the value is NaN
236 }
237
msecond(void) const238 int dms::msecond(void) const
239 {
240 int hs = int(1000.0 * (60.0 * (60.0 * (fabs(Hours()) - abs(hour())) - abs(minute())) - abs(second())));
241 if (hour() == 0 && minute() == 0 && second() == 0 && Hours() < 0.0)
242 {
243 hs = -1 * hs;
244 }
245 return hs; // Warning: Will return 0 if the value is NaN
246 }
247
reduce(void) const248 const dms dms::reduce(void) const
249 {
250 if (std::isnan(D))
251 return dms(0);
252
253 return dms(D - 360.0 * floor(D / 360.0));
254 }
255
deltaAngle(dms angle) const256 const dms dms::deltaAngle(dms angle) const
257 {
258 double angleDiff = D - angle.Degrees();
259
260 // Put in the range of [-360,360]
261 while (angleDiff > 360)
262 angleDiff -= 360;
263 while (angleDiff < -360)
264 angleDiff += 360;
265 // angleDiff in the range [180,360]
266 if (angleDiff > 180)
267 return dms(360 - angleDiff);
268 // angleDiff in the range [-360,-180]
269 else if (angleDiff < -180)
270 return dms(-(360 + angleDiff));
271 // angleDiff in the range [-180,180]
272 else
273 return dms(angleDiff);
274 }
275
toDMSString(const bool forceSign,const bool machineReadable,const bool highPrecision) const276 const QString dms::toDMSString(const bool forceSign, const bool machineReadable, const bool highPrecision) const
277 {
278 QString dummy;
279 char pm(' ');
280 QChar zero('0');
281 int dd, dm, ds;
282
283 if (machineReadable || !highPrecision)
284 // minimize the mean angle representation error of DMS format
285 // set LSD transition in the middle of +- half precision range
286 {
287 double half_precision = 1.0 / 7200.0;
288 if (Degrees() < 0.0)
289 half_precision = -half_precision;
290 dms angle(Degrees() + half_precision);
291 dd = abs(angle.degree());
292 dm = abs(angle.arcmin());
293 ds = abs(angle.arcsec());
294 }
295 else
296 {
297 dd = abs(degree());
298 dm = abs(arcmin());
299 ds = abs(arcsec());
300 }
301
302 if (Degrees() < 0.0)
303 pm = '-';
304 else if (forceSign && Degrees() > 0.0)
305 pm = '+';
306
307 if (machineReadable)
308 return QString("%1%2:%3:%4").arg(pm)
309 .arg(dd, 2, 10, QChar('0'))
310 .arg(dm, 2, 10, QChar('0'))
311 .arg(ds, 2, 10, QChar('0'));
312
313 if (highPrecision)
314 {
315 double sec = arcsec() + marcsec() / 1000.;
316 return QString("%1%2° %3\' %L4\"").arg(pm)
317 .arg(dd, 2, 10, zero)
318 .arg(dm, 2, 10, zero)
319 .arg(sec, 2,'f', 2, zero);
320 }
321
322 return QString("%1%2° %3\' %4\"").arg(pm)
323 .arg(dd, 2, 10, zero)
324 .arg(dm, 2, 10, zero)
325 .arg(ds, 2, 10, zero);
326
327 #if 0
328 if (!machineReadable && dd < 10)
329 {
330 if (highPrecision)
331 {
332 double sec = arcsec() + marcsec() / 1000.;
333 return dummy.sprintf("%c%1d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec);
334 }
335
336 return dummy.sprintf("%c%1d%c %02d\' %02d\"", pm, dd, 176, dm, ds);
337 }
338
339 if (!machineReadable && dd < 100)
340 {
341 if (highPrecision)
342 {
343 double sec = arcsec() + marcsec() / 1000.;
344 return dummy.sprintf("%c%2d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec);
345 }
346
347 return dummy.sprintf("%c%2d%c %02d\' %02d\"", pm, dd, 176, dm, ds);
348 }
349 if (machineReadable && dd < 100)
350 return dummy.sprintf("%c%02d:%02d:%02d", pm, dd, dm, ds);
351
352 if (!machineReadable)
353 {
354 if (highPrecision)
355 {
356 double sec = arcsec() + marcsec() / 1000.;
357 return dummy.sprintf("%c%3d%c %02d\' %05.2f\"", pm, dd, 176, dm, sec);
358 }
359 else
360 return dummy.sprintf("%c%3d%c %02d\' %02d\"", pm, dd, 176, dm, ds);
361 }
362 else
363 return dummy.sprintf("%c%03d:%02d:%02d", pm, dd, dm, ds);
364 #endif
365 }
366
toHMSString(const bool machineReadable,const bool highPrecision) const367 const QString dms::toHMSString(const bool machineReadable, const bool highPrecision) const
368 {
369 QChar zero('0');
370 dms angle;
371 int hh, hm, hs;
372
373 if (machineReadable || !highPrecision)
374 // minimize the mean angle representation error of HMS format
375 // set LSD transition in the middle of +- half precision range
376 {
377 double half_precision = 15.0 / 7200.0;
378 angle.setD(Degrees() + half_precision);
379 hh = angle.hour();
380 hm = angle.minute();
381 hs = angle.second();
382 }
383
384 if (machineReadable)
385 return QString("%1:%2:%3").arg(hh, 2, 10, zero)
386 .arg(hm, 2, 10, zero)
387 .arg(hs, 2, 10, zero);
388
389 if (highPrecision)
390 {
391 double sec = second() + msecond() / 1000.;
392 return QString("%1h %2m %L3s").arg(hour(), 2, 10, zero)
393 .arg(minute(), 2, 10, zero)
394 .arg(sec, 2, 'f', 2, zero);
395 }
396
397 return QString("%1h %2m %3s").arg(hh, 2, 10, zero)
398 .arg(hm, 2, 10, zero)
399 .arg(hs, 2, 10, zero);
400
401 #if 0
402 QString dummy;
403 if (!machineReadable)
404 {
405 if (highPrecision)
406 {
407 double sec = second() + msecond() / 1000.;
408 return dummy.sprintf("%02dh %02dm %05.2f", hour(), minute(), sec);
409 }
410 else
411 return dummy.sprintf("%02dh %02dm %02ds", hh, hm, hs);
412 }
413 else
414 return dummy.sprintf("%02d:%02d:%02d", hh, hm, hs);
415 #endif
416 }
417
fromString(const QString & st,bool deg)418 dms dms::fromString(const QString &st, bool deg)
419 {
420 dms result;
421 result.setFromString(st, deg);
422 return result;
423 //bool ok( false );
424
425 //ok = result.setFromString( st, deg );
426
427 // if ( ok )
428 //return result;
429 // else {
430 // kDebug() << i18n( "Could Not Set Angle from string: " ) << st;
431 // return result;
432 // }
433 }
434
reduceToRange(enum dms::AngleRanges range)435 void dms::reduceToRange(enum dms::AngleRanges range)
436 {
437 if (std::isnan(D))
438 return;
439
440 switch (range)
441 {
442 case MINUSPI_TO_PI:
443 D -= 360. * floor((D + 180.) / 360.);
444 break;
445 case ZERO_TO_2PI:
446 D -= 360. * floor(D / 360.);
447 }
448 }
449
operator <<(QDataStream & out,const dms & d)450 QDataStream &operator<<(QDataStream &out, const dms &d)
451 {
452 out << d.D;
453 return out;
454 }
455
operator >>(QDataStream & in,dms & d)456 QDataStream &operator>>(QDataStream &in, dms &d){
457 double D;
458 in >> D;
459 d = dms(D);
460 return in;
461 }
462
463