Engauge Digitizer  2
FormatDegreesMinutesSecondsBase.cpp
1 /******************************************************************************************************
2  * (C) 2014 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5  ******************************************************************************************************/
6 
7 #include "CoordSymbol.h"
8 #include "FormatDegreesMinutesSecondsBase.h"
9 #include "Logger.h"
10 #include <QDoubleValidator>
11 #include <qmath.h>
12 #include <QRegExp>
13 #include <QStringList>
14 #include <QValidator>
15 
16 const double DEGREES_TO_MINUTES = 60.0;
17 const double MINUTES_TO_SECONDS = 60.0;
18 const double DEGREES_TO_SECONDS = DEGREES_TO_MINUTES * MINUTES_TO_SECONDS;
19 const double MINUTES_TO_DEGREES = 1.0 / DEGREES_TO_MINUTES;
20 const double SECONDS_TO_DEGREES = 1.0 / (DEGREES_TO_MINUTES * MINUTES_TO_SECONDS);
21 
23 {
24 }
25 
26 FormatDegreesMinutesSecondsBase::~FormatDegreesMinutesSecondsBase()
27 {
28 }
29 
31 {
32  LOG4CPP_INFO_S ((*mainCat)) << "FormatDegreesMinutesSecondsBase::formatOutputDegreesMinutesSeconds"
33  << " value=" << value;
34 
35  // Only smallest resolution value is floating point
36  bool negative = (value < 0);
37  value = qAbs (value);
38  int degrees = qFloor (value);
39  value -= degrees;
40  int minutes = value * DEGREES_TO_MINUTES;
41  value -= minutes * MINUTES_TO_DEGREES;
42  double seconds = value * DEGREES_TO_SECONDS;
43  degrees *= (negative ? -1.0 : 1.0);
44 
45  return QString ("%1%2 %3%4 %5%6")
46  .arg (degrees)
47  .arg (QChar (COORD_SYMBOL_DEGREES))
48  .arg (minutes)
49  .arg (QChar (COORD_SYMBOL_MINUTES_PRIME))
50  .arg (seconds)
51  .arg (QChar (COORD_SYMBOL_SECONDS_DOUBLE_PRIME));
52 }
53 
55  bool isNsHemisphere) const
56 {
57  LOG4CPP_INFO_S ((*mainCat)) << "FormatDegreesMinutesSecondsBase::formatOutputDegreesMinutesSecondsNsew"
58  << " value=" << value
59  << " isNsHemisphere=" << (isNsHemisphere ? "true" : "false");
60 
61  // Only smallest resolution value is floating point
62  bool negative = (value < 0);
63  value = qAbs (value);
64  int degrees = qFloor (value);
65  value -= degrees;
66  int minutes = value * DEGREES_TO_MINUTES;
67  value -= minutes * MINUTES_TO_DEGREES;
68  double seconds = value * DEGREES_TO_SECONDS;
69 
70  QString hemisphere;
71  if (isNsHemisphere) {
72  hemisphere = (negative ? "S" : "N");
73  } else {
74  hemisphere = (negative ? "W" : "E");
75  }
76 
77  return QString ("%1%2 %3%4 %5%6 %7")
78  .arg (degrees)
79  .arg (QChar (COORD_SYMBOL_DEGREES))
80  .arg (minutes)
81  .arg (QChar (COORD_SYMBOL_MINUTES_PRIME))
82  .arg (seconds)
83  .arg (QChar (COORD_SYMBOL_SECONDS_DOUBLE_PRIME))
84  .arg (hemisphere);
85 }
86 
87 QValidator::State FormatDegreesMinutesSecondsBase::parseInput (const QString &stringUntrimmed,
88  double &value) const
89 {
90  LOG4CPP_INFO_S ((*mainCat)) << "FormatDegreesMinutesSecondsBase::parseInput"
91  << " string=" << stringUntrimmed.toLatin1().data();
92 
93  const QString string = stringUntrimmed.trimmed ();
94 
95  if (string.isEmpty()) {
96 
97  return QValidator::Intermediate;
98  }
99 
100  // Split on spaces
101  QStringList fields = string.split (QRegExp ("\\s+"),
102  QString::SkipEmptyParts);
103 
104  QString field0, field1, field2; // Degrees, minutes and seconds components
105  if (fields.count() == 0) {
106  return QValidator::Invalid; // Empty
107  } else {
108  field0 = fields.at(0);
109  if (fields.count() > 1) {
110  field1 = fields.at(1);
111  if (fields.count() > 2) {
112  field2 = fields.at(2);
113  if (fields.count() > 3) {
114  return QValidator::Invalid; // Too many values
115  }
116  }
117  }
118  }
119 
120  stripSymbols (field0,
121  field1,
122  field2);
123 
124  int pos;
125 
126  // Validators
127  QDoubleValidator valDegrees;
128  QDoubleValidator valMinutesOrSeconds;
129  valMinutesOrSeconds.setBottom (0);
130 
131  double valueDegrees = 0, valueMinutes = 0, valueSeconds = 0;
132 
133  // Required degrees
134  QValidator::State state = valDegrees.validate (field0,
135  pos);
136  if (state == QValidator::Acceptable) {
137 
138  valueDegrees = field0.toDouble();
139 
140  if (fields.count() > 1) {
141 
142  // Optional minutes
143  state = valMinutesOrSeconds.validate (field1,
144  pos);
145  if (state == QValidator::Acceptable) {
146 
147  valueMinutes = field1.toDouble();
148 
149  if (fields.count() > 2) {
150 
151  // Optional seconds
152  state = valMinutesOrSeconds.validate (field2,
153  pos);
154  if (state == QValidator::Acceptable) {
155 
156  valueSeconds = field2.toDouble();
157 
158  }
159  }
160  }
161  }
162  }
163 
164  if (state == QValidator::Acceptable) {
165  if (valueDegrees < 0) {
166 
167  // Apply the negative sign on the degrees components to minutes and seconds components also
168  value = valueDegrees - valueMinutes * MINUTES_TO_DEGREES - valueSeconds * SECONDS_TO_DEGREES;
169 
170  } else {
171 
172  // All components are positive
173  value = valueDegrees + valueMinutes * MINUTES_TO_DEGREES + valueSeconds * SECONDS_TO_DEGREES;
174 
175  }
176  }
177 
178  return state;
179 }
180 
181 void FormatDegreesMinutesSecondsBase::stripSymbols (QString &field0,
182  QString &field1,
183  QString &field2) const
184 {
185  const int FIELD_WIDTH = 0, BASE_8 = 8, BASE_16 = 16;
186 
187  // Clean up degrees symbols (167 or 248 in base 10 which are 247 and 370 in base 8) and single quotes
188  QString strExpDegrees = QString (".*\\0%1$")
189  .arg (COORD_SYMBOL_DEGREES, FIELD_WIDTH, BASE_8);
190 
191  QRegExp regExpDegrees (strExpDegrees);
192 
193  if (regExpDegrees.exactMatch (field0)) {
194  field0 = field0.left (field0.count() - 1);
195  }
196 
197  // Clean up minutes
198  QString strExpMinutes = QString (".*[\\0%1\\x%2]$")
199  .arg (COORD_SYMBOL_MINUTES_APOSTROPHE, FIELD_WIDTH, BASE_8)
200  .arg (COORD_SYMBOL_MINUTES_PRIME, FIELD_WIDTH, BASE_16);
201 
202  QRegExp regExpMinutes (strExpMinutes);
203 
204  if (regExpMinutes.exactMatch (field1)) {
205  field1 = field1.left (field1.count() - 1);
206  }
207 
208  // Clean up seconds
209  QString strExpSeconds1Char = QString (".*[\\x%1\\x%2]$")
210  .arg (COORD_SYMBOL_SECONDS_DOUBLE_PRIME, FIELD_WIDTH, BASE_16)
211  .arg (COORD_SYMBOL_SECONDS_QUOTATIONS, FIELD_WIDTH, BASE_16);
212  QString strExpSeconds2Chars = QString (".*\\0%1\\0%2$")
213  .arg (COORD_SYMBOL_MINUTES_PRIME, FIELD_WIDTH, BASE_8)
214  .arg (COORD_SYMBOL_MINUTES_PRIME, FIELD_WIDTH, BASE_8);
215 
216  QRegExp regExpSeconds1Char (strExpSeconds1Char), regExpSeconds2Chars (strExpSeconds2Chars);
217 
218  if (regExpSeconds1Char.exactMatch (field2)) {
219  field2 = field2.left (field2.count() - 1);
220  }
221  if (regExpSeconds2Chars.exactMatch (field2)) {
222  field2 = field2.left (field2.count() - 2);
223  }
224 }
QString formatOutputDegreesMinutesSecondsNsew(double value, bool isNsHemisphere) const
Format as degrees, minutes and seconds with hemisphere.
QString formatOutputDegreesMinutesSeconds(double value) const
Format as degrees, minutes and seconds without hemisphere.
QValidator::State parseInput(const QString &stringUntrimmed, double &value) const
Parse the input string into a number value.