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