|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the Qt Mobility Components. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 #include "qlocationutils_p.h" |
|
42 #include "qgeopositioninfo.h" |
|
43 |
|
44 #include <QTime> |
|
45 #include <QList> |
|
46 #include <QByteArray> |
|
47 #include <QDebug> |
|
48 |
|
49 #include <math.h> |
|
50 |
|
51 QTM_BEGIN_NAMESPACE |
|
52 |
|
53 // converts e.g. 15306.0235 from NMEA sentence to 153.100392 |
|
54 static double qlocationutils_nmeaDegreesToDecimal(double nmeaDegrees) |
|
55 { |
|
56 double deg; |
|
57 double min = 100.0 * modf(nmeaDegrees / 100.0, °); |
|
58 return deg + (min / 60.0); |
|
59 } |
|
60 |
|
61 static void qlocationutils_readGga(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) |
|
62 { |
|
63 QByteArray sentence(data, size); |
|
64 QList<QByteArray> parts = sentence.split(','); |
|
65 QGeoCoordinate coord; |
|
66 |
|
67 if (hasFix && parts.count() > 6 && parts[6].count() > 0) |
|
68 *hasFix = parts[6].toInt() > 0; |
|
69 |
|
70 if (parts.count() > 1 && parts[1].count() > 0) { |
|
71 QTime time; |
|
72 if (QLocationUtils::getNmeaTime(parts[1], &time)) |
|
73 info->setTimestamp(QDateTime(QDate(), time, Qt::UTC)); |
|
74 } |
|
75 |
|
76 if (parts.count() > 5 && parts[3].count() == 1 && parts[5].count() == 1) { |
|
77 double lat; |
|
78 double lng; |
|
79 if (QLocationUtils::getNmeaLatLong(parts[2], parts[3][0], parts[4], parts[5][0], &lat, &lng)) { |
|
80 coord.setLatitude(lat); |
|
81 coord.setLongitude(lng); |
|
82 } |
|
83 } |
|
84 |
|
85 if (parts.count() > 9 && parts[9].count() > 0) { |
|
86 bool hasAlt = false; |
|
87 double alt = parts[9].toDouble(&hasAlt); |
|
88 if (hasAlt) |
|
89 coord.setAltitude(alt); |
|
90 } |
|
91 |
|
92 if (coord.type() != QGeoCoordinate::InvalidCoordinate) |
|
93 info->setCoordinate(coord); |
|
94 } |
|
95 |
|
96 static void qlocationutils_readGll(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) |
|
97 { |
|
98 QByteArray sentence(data, size); |
|
99 QList<QByteArray> parts = sentence.split(','); |
|
100 QGeoCoordinate coord; |
|
101 |
|
102 if (hasFix && parts.count() > 6 && parts[6].count() > 0) |
|
103 *hasFix = (parts[6][0] == 'A'); |
|
104 |
|
105 if (parts.count() > 5 && parts[5].count() > 0) { |
|
106 QTime time; |
|
107 if (QLocationUtils::getNmeaTime(parts[5], &time)) |
|
108 info->setTimestamp(QDateTime(QDate(), time, Qt::UTC)); |
|
109 } |
|
110 |
|
111 if (parts.count() > 4 && parts[2].count() == 1 && parts[4].count() == 1) { |
|
112 double lat; |
|
113 double lng; |
|
114 if (QLocationUtils::getNmeaLatLong(parts[1], parts[2][0], parts[3], parts[4][0], &lat, &lng)) { |
|
115 coord.setLatitude(lat); |
|
116 coord.setLongitude(lng); |
|
117 } |
|
118 } |
|
119 |
|
120 if (coord.type() != QGeoCoordinate::InvalidCoordinate) |
|
121 info->setCoordinate(coord); |
|
122 } |
|
123 |
|
124 static void qlocationutils_readRmc(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) |
|
125 { |
|
126 QByteArray sentence(data, size); |
|
127 QList<QByteArray> parts = sentence.split(','); |
|
128 QGeoCoordinate coord; |
|
129 QDate date; |
|
130 QTime time; |
|
131 |
|
132 if (hasFix && parts.count() > 2 && parts[2].count() > 0) |
|
133 *hasFix = (parts[2][0] == 'A'); |
|
134 |
|
135 if (parts.count() > 9 && parts[9].count() == 6) { |
|
136 date = QDate::fromString(parts[9], "ddMMyy"); |
|
137 if (date.isValid()) |
|
138 date = date.addYears(100); // otherwise starts from 1900 |
|
139 else |
|
140 date = QDate(); |
|
141 } |
|
142 |
|
143 if (parts.count() > 1 && parts[1].count() > 0) |
|
144 QLocationUtils::getNmeaTime(parts[1], &time); |
|
145 |
|
146 if (parts.count() > 6 && parts[4].count() == 1 && parts[6].count() == 1) { |
|
147 double lat; |
|
148 double lng; |
|
149 if (QLocationUtils::getNmeaLatLong(parts[3], parts[4][0], parts[5], parts[6][0], &lat, &lng)) { |
|
150 coord.setLatitude(lat); |
|
151 coord.setLongitude(lng); |
|
152 } |
|
153 } |
|
154 |
|
155 bool parsed = false; |
|
156 double value = 0.0; |
|
157 if (parts.count() > 7 && parts[7].count() > 0) { |
|
158 value = parts[7].toDouble(&parsed); |
|
159 if (parsed) |
|
160 info->setAttribute(QGeoPositionInfo::GroundSpeed, qreal(value * 1.852 / 3.6)); // knots -> m/s |
|
161 } |
|
162 if (parts.count() > 8 && parts[8].count() > 0) { |
|
163 value = parts[8].toDouble(&parsed); |
|
164 if (parsed) |
|
165 info->setAttribute(QGeoPositionInfo::Direction, qreal(value)); |
|
166 } |
|
167 if (parts.count() > 11 && parts[11].count() == 1 |
|
168 && (parts[11][0] == 'E' || parts[11][0] == 'W')) { |
|
169 value = parts[10].toDouble(&parsed); |
|
170 if (parsed) { |
|
171 if (parts[11][0] == 'W') |
|
172 value *= -1; |
|
173 info->setAttribute(QGeoPositionInfo::MagneticVariation, qreal(value)); |
|
174 } |
|
175 } |
|
176 |
|
177 if (coord.type() != QGeoCoordinate::InvalidCoordinate) |
|
178 info->setCoordinate(coord); |
|
179 |
|
180 info->setTimestamp(QDateTime(date, time, Qt::UTC)); |
|
181 } |
|
182 |
|
183 static void qlocationutils_readVtg(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) |
|
184 { |
|
185 if (hasFix) |
|
186 *hasFix = false; |
|
187 |
|
188 QByteArray sentence(data, size); |
|
189 QList<QByteArray> parts = sentence.split(','); |
|
190 |
|
191 bool parsed = false; |
|
192 double value = 0.0; |
|
193 if (parts.count() > 1 && parts[1].count() > 0) { |
|
194 value = parts[1].toDouble(&parsed); |
|
195 if (parsed) |
|
196 info->setAttribute(QGeoPositionInfo::Direction, qreal(value)); |
|
197 } |
|
198 if (parts.count() > 7 && parts[7].count() > 0) { |
|
199 value = parts[7].toDouble(&parsed); |
|
200 if (parsed) |
|
201 info->setAttribute(QGeoPositionInfo::GroundSpeed, qreal(value / 3.6)); // km/h -> m/s |
|
202 } |
|
203 } |
|
204 |
|
205 static void qlocationutils_readZda(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) |
|
206 { |
|
207 if (hasFix) |
|
208 *hasFix = false; |
|
209 |
|
210 QByteArray sentence(data, size); |
|
211 QList<QByteArray> parts = sentence.split(','); |
|
212 QDate date; |
|
213 QTime time; |
|
214 |
|
215 if (parts.count() > 1 && parts[1].count() > 0) |
|
216 QLocationUtils::getNmeaTime(parts[1], &time); |
|
217 |
|
218 if (parts.count() > 4 && parts[2].count() > 0 && parts[3].count() > 0 |
|
219 && parts[4].count() == 4) { // must be full 4-digit year |
|
220 int day = parts[2].toUInt(); |
|
221 int month = parts[3].toUInt(); |
|
222 int year = parts[4].toUInt(); |
|
223 if (day > 0 && month > 0 && year > 0) |
|
224 date.setDate(year, month, day); |
|
225 } |
|
226 |
|
227 info->setTimestamp(QDateTime(date, time, Qt::UTC)); |
|
228 } |
|
229 |
|
230 bool QLocationUtils::getPosInfoFromNmea(const char *data, int size, QGeoPositionInfo *info, bool *hasFix) |
|
231 { |
|
232 if (!info) |
|
233 return false; |
|
234 |
|
235 if (hasFix) |
|
236 *hasFix = false; |
|
237 if (size < 6 || data[0] != '$' || !hasValidNmeaChecksum(data, size)) |
|
238 return false; |
|
239 |
|
240 if (data[3] == 'G' && data[4] == 'G' && data[5] == 'A') { |
|
241 // "$--GGA" sentence. |
|
242 qlocationutils_readGga(data, size, info, hasFix); |
|
243 return true; |
|
244 } |
|
245 |
|
246 if (data[3] == 'G' && data[4] == 'L' && data[5] == 'L') { |
|
247 // "$--GLL" sentence. |
|
248 qlocationutils_readGll(data, size, info, hasFix); |
|
249 return true; |
|
250 } |
|
251 |
|
252 if (data[3] == 'R' && data[4] == 'M' && data[5] == 'C') { |
|
253 // "$--RMC" sentence. |
|
254 qlocationutils_readRmc(data, size, info, hasFix); |
|
255 return true; |
|
256 } |
|
257 |
|
258 if (data[3] == 'V' && data[4] == 'T' && data[5] == 'G') { |
|
259 // "$--VTG" sentence. |
|
260 qlocationutils_readVtg(data, size, info, hasFix); |
|
261 return true; |
|
262 } |
|
263 |
|
264 if (data[3] == 'Z' && data[4] == 'D' && data[5] == 'A') { |
|
265 // "$--ZDA" sentence. |
|
266 qlocationutils_readZda(data, size, info, hasFix); |
|
267 return true; |
|
268 } |
|
269 |
|
270 return false; |
|
271 } |
|
272 |
|
273 bool QLocationUtils::hasValidNmeaChecksum(const char *data, int size) |
|
274 { |
|
275 int asteriskIndex = -1; |
|
276 for (int i = 0; i < size; i++) { |
|
277 if (data[i] == '*') { |
|
278 asteriskIndex = i; |
|
279 break; |
|
280 } |
|
281 } |
|
282 |
|
283 const int CSUM_LEN = 2; |
|
284 if (asteriskIndex < 0 || asteriskIndex + CSUM_LEN >= size) |
|
285 return false; |
|
286 |
|
287 // XOR byte value of all characters between '$' and '*' |
|
288 int result = 0; |
|
289 for (int i = 1; i < asteriskIndex; i++) |
|
290 result ^= data[i]; |
|
291 /* |
|
292 char calc[CSUM_LEN + 1]; |
|
293 ::snprintf(calc, CSUM_LEN + 1, "%02x", result); |
|
294 return ::strncmp(calc, &data[asteriskIndex+1], 2) == 0; |
|
295 */ |
|
296 |
|
297 QString s; |
|
298 s.sprintf("%02x", result); |
|
299 return s.toAscii() == QByteArray(&data[asteriskIndex+1], 2); |
|
300 } |
|
301 |
|
302 bool QLocationUtils::getNmeaTime(const QByteArray &bytes, QTime *time) |
|
303 { |
|
304 int dotIndex = bytes.indexOf('.'); |
|
305 QTime tempTime; |
|
306 |
|
307 if (dotIndex < 0) { |
|
308 tempTime = QTime::fromString(bytes, "hhmmss"); |
|
309 } else { |
|
310 tempTime = QTime::fromString(bytes.mid(0, dotIndex), "hhmmss"); |
|
311 bool hasMsecs = false; |
|
312 int midLen = qMin(3, bytes.size() - dotIndex - 1); |
|
313 int msecs = bytes.mid(dotIndex + 1, midLen).toUInt(&hasMsecs); |
|
314 if (hasMsecs) |
|
315 tempTime = tempTime.addMSecs(msecs); |
|
316 } |
|
317 |
|
318 if (tempTime.isValid()) { |
|
319 *time = tempTime; |
|
320 return true; |
|
321 } |
|
322 return false; |
|
323 } |
|
324 |
|
325 bool QLocationUtils::getNmeaLatLong(const QByteArray &latString, char latDirection, const QByteArray &lngString, char lngDirection, double *lat, double *lng) |
|
326 { |
|
327 if ((latDirection != 'N' && latDirection != 'S') |
|
328 || (lngDirection != 'E' && lngDirection != 'W')) { |
|
329 return false; |
|
330 } |
|
331 |
|
332 bool hasLat = false; |
|
333 bool hasLong = false; |
|
334 double tempLat = latString.toDouble(&hasLat); |
|
335 double tempLng = lngString.toDouble(&hasLong); |
|
336 if (hasLat && hasLong) { |
|
337 tempLat = qlocationutils_nmeaDegreesToDecimal(tempLat); |
|
338 if (latDirection == 'S') |
|
339 tempLat *= -1; |
|
340 tempLng = qlocationutils_nmeaDegreesToDecimal(tempLng); |
|
341 if (lngDirection == 'W') |
|
342 tempLng *= -1; |
|
343 |
|
344 if (isValidLat(tempLat) && isValidLong(tempLng)) { |
|
345 *lat = tempLat; |
|
346 *lng = tempLng; |
|
347 return true; |
|
348 } |
|
349 } |
|
350 return false; |
|
351 } |
|
352 |
|
353 QTM_END_NAMESPACE |
|
354 |