fixed formatting in GeoLocation class (C++)
[public/netxms.git] / src / libnetxms / geolocation.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2013 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU Lesser General Public License as published
7 ** by the Free Software Foundation; either version 3 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU Lesser General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: geolocation.cpp
20 **
21 **/
22
23 #include "libnetxms.h"
24 #include <geolocation.h>
25 #include <math.h>
26
27 static const double ROUND_OFF = 0.00000001;
28
29 #ifdef UNICODE
30 #define DEGREE_SIGN_CHR L'\x00B0'
31 #define DEGREE_SIGN_STR L"\x00B0"
32 #else
33 #define DEGREE_SIGN_CHR '\xF8'
34 #define DEGREE_SIGN_STR "\xF8"
35 #endif
36
37 /**
38 * Default constructor - create location of type UNSET
39 */
40 GeoLocation::GeoLocation()
41 {
42 m_type = GL_UNSET;
43 m_lat = 0;
44 m_lon = 0;
45 posToString(true, 0);
46 posToString(false, 0);
47 m_isValid = true;
48 m_accuracy = 0;
49 m_timestamp = 0;
50 }
51
52 /**
53 * Constructor - create location from double lat/lon values
54 */
55 GeoLocation::GeoLocation(int type, double lat, double lon, int accuracy, time_t timestamp)
56 {
57 m_type = type;
58 m_lat = lat;
59 m_lon = lon;
60 posToString(true, lat);
61 posToString(false, lon);
62 m_isValid = true;
63 m_accuracy = accuracy;
64 m_timestamp = timestamp;
65 }
66
67 /**
68 * Constructor - create location from string lat/lon values
69 */
70 GeoLocation::GeoLocation(int type, const TCHAR *lat, const TCHAR *lon, int accuracy, time_t timestamp)
71 {
72 m_type = type;
73 m_isValid = parseLatitude(lat) && parseLongitude(lon);
74 posToString(true, m_lat);
75 posToString(false, m_lon);
76 m_accuracy = accuracy;
77 m_timestamp = timestamp;
78 }
79
80 /**
81 * Copy constructor
82 */
83 GeoLocation::GeoLocation(const GeoLocation &src)
84 {
85 m_type = src.m_type;
86 m_lat = src.m_lat;
87 m_lon = src.m_lon;
88 nx_strncpy(m_latStr, src.m_latStr, 20);
89 nx_strncpy(m_lonStr, src.m_lonStr, 20);
90 m_isValid = src.m_isValid;
91 m_accuracy = src.m_accuracy;
92 m_timestamp = src.m_timestamp;
93 }
94
95 /**
96 * Create geolocation object from data in NXCP message
97 */
98 GeoLocation::GeoLocation(NXCPMessage &msg)
99 {
100 m_type = (int)msg.getFieldAsUInt16(VID_GEOLOCATION_TYPE);
101
102 if (msg.getFieldType(VID_LATITUDE) == NXCP_DT_INT32)
103 m_lat = (double)msg.getFieldAsInt32(VID_LATITUDE) / 1000000;
104 else
105 m_lat = msg.getFieldAsDouble(VID_LATITUDE);
106
107 if (msg.getFieldType(VID_LONGITUDE) == NXCP_DT_INT32)
108 m_lon = (double)msg.getFieldAsInt32(VID_LONGITUDE) / 1000000;
109 else
110 m_lon = msg.getFieldAsDouble(VID_LONGITUDE);
111
112 m_accuracy = (int)msg.getFieldAsUInt16(VID_ACCURACY);
113
114 m_timestamp = 0;
115 int ft = msg.getFieldType(VID_GEOLOCATION_TIMESTAMP);
116 if (ft == NXCP_DT_INT64)
117 {
118 m_timestamp = (time_t)msg.getFieldAsUInt64(VID_GEOLOCATION_TIMESTAMP);
119 }
120 else if (ft == NXCP_DT_INT32)
121 {
122 m_timestamp = (time_t)msg.getFieldAsUInt32(VID_GEOLOCATION_TIMESTAMP);
123 }
124 else if (ft == NXCP_DT_STRING)
125 {
126 char ts[256];
127 msg.getFieldAsMBString(VID_GEOLOCATION_TIMESTAMP, ts, 256);
128
129 struct tm timeBuff;
130 if (strptime(ts, "%Y/%m/%d %H:%M:%S", &timeBuff) != NULL)
131 {
132 timeBuff.tm_isdst = -1;
133 m_timestamp = timegm(&timeBuff);
134 }
135 }
136 if(m_timestamp == 0)
137 m_timestamp = time(0);
138
139 posToString(true, m_lat);
140 posToString(false, m_lon);
141 m_isValid = true;
142 }
143
144 /**
145 * Destructor
146 */
147 GeoLocation::~GeoLocation()
148 {
149 }
150
151 /**
152 * Assignment operator
153 */
154 GeoLocation& GeoLocation::operator =(const GeoLocation &src)
155 {
156 m_type = src.m_type;
157 m_lat = src.m_lat;
158 m_lon = src.m_lon;
159 nx_strncpy(m_latStr, src.m_latStr, 20);
160 nx_strncpy(m_lonStr, src.m_lonStr, 20);
161 m_isValid = src.m_isValid;
162 m_accuracy = src.m_accuracy;
163 m_timestamp = src.m_timestamp;
164 return *this;
165 }
166
167 /**
168 * Fill NXCP message
169 */
170 void GeoLocation::fillMessage(NXCPMessage &msg) const
171 {
172 msg.setField(VID_GEOLOCATION_TYPE, (WORD)m_type);
173 msg.setField(VID_LATITUDE, m_lat);
174 msg.setField(VID_LONGITUDE, m_lon);
175 msg.setField(VID_ACCURACY, (WORD)m_accuracy);
176 msg.setField(VID_GEOLOCATION_TIMESTAMP, (QWORD)m_timestamp);
177 }
178
179 /**
180 * Getters degree from double value
181 */
182 int GeoLocation::getIntegerDegree(double pos)
183 {
184 return (int)(fabs(pos) + ROUND_OFF);
185 }
186
187 /**
188 * Getters minutes from double value
189 */
190 int GeoLocation::getIntegerMinutes(double pos)
191 {
192 double d = fabs(pos) + ROUND_OFF;
193 return (int)((d - (double)((int)d)) * 60.0);
194 }
195
196 /**
197 * Getters seconds from double value
198 */
199 double GeoLocation::getDecimalSeconds(double pos)
200 {
201 double d = fabs(pos) * 60.0 + ROUND_OFF;
202 return (d - (double)((int)d)) * 60.0;
203 }
204
205 /**
206 * Convert position to string
207 */
208 void GeoLocation::posToString(bool isLat, double pos)
209 {
210 TCHAR *buffer = isLat ? m_latStr : m_lonStr;
211
212 // Check range
213 if ((pos < -180.0) || (pos > 180.0))
214 {
215 _tcscpy(buffer, _T("<invalid>"));
216 return;
217 }
218
219 // Encode hemisphere
220 if (isLat)
221 {
222 *buffer = (pos < 0) ? _T('S') : _T('N');
223 }
224 else
225 {
226 *buffer = (pos < 0) ? _T('W') : _T('E');
227 }
228 buffer++;
229 *buffer++ = _T(' ');
230
231 _sntprintf(buffer, 18, _T("%02d") DEGREE_SIGN_STR _T(" %02d' %06.3f\""), getIntegerDegree(pos), getIntegerMinutes(pos), getDecimalSeconds(pos));
232 }
233
234 /**
235 * Parse latitude/longitude string
236 */
237 double GeoLocation::parse(const TCHAR *str, bool isLat, bool *isValid)
238 {
239 *isValid = false;
240
241 // Prepare input string
242 TCHAR *in = _tcsdup(str);
243 StrStrip(in);
244
245 // Check if string given is just double value
246 TCHAR *eptr;
247 double value = _tcstod(in, &eptr);
248 if (*eptr == 0)
249 {
250 *isValid = true;
251 }
252 else // If not a valid double, check if it's in DMS form
253 {
254 // Check for invalid characters
255 if (_tcsspn(in, isLat ? _T("0123456789.,'\" NS") DEGREE_SIGN_STR : _T("0123456789.,'\" EW") DEGREE_SIGN_STR) == _tcslen(in))
256 {
257 TranslateStr(in, _T(","), _T("."));
258 TCHAR *curr = in;
259
260 int sign = 0;
261 if ((*curr == _T('N')) || (*curr == _T('E')))
262 {
263 sign = 1;
264 curr++;
265 }
266 else if ((*curr == _T('S')) || (*curr == _T('W')))
267 {
268 sign = -1;
269 curr++;
270 }
271
272 while(*curr == _T(' '))
273 curr++;
274
275 double deg = 0.0, min = 0.0, sec = 0.0;
276
277 deg = _tcstod(curr, &eptr);
278 if (*eptr == 0) // End of string
279 goto finish_parsing;
280 if ((*eptr != DEGREE_SIGN_CHR) && (*eptr != _T(' ')))
281 goto cleanup; // Unexpected character
282 curr = eptr + 1;
283 while(*curr == _T(' '))
284 curr++;
285
286 min = _tcstod(curr, &eptr);
287 if (*eptr == 0) // End of string
288 goto finish_parsing;
289 if (*eptr != _T('\''))
290 goto cleanup; // Unexpected character
291 curr = eptr + 1;
292 while(*curr == _T(' '))
293 curr++;
294
295 sec = _tcstod(curr, &eptr);
296 if (*eptr == 0) // End of string
297 goto finish_parsing;
298 if (*eptr != _T('"'))
299 goto cleanup; // Unexpected character
300 curr = eptr + 1;
301 while(*curr == _T(' '))
302 curr++;
303
304 if ((*curr == _T('N')) || (*curr == _T('E')))
305 {
306 sign = 1;
307 curr++;
308 }
309 else if ((*curr == _T('S')) || (*curr == _T('W')))
310 {
311 sign = -1;
312 curr++;
313 }
314
315 if (sign == 0)
316 goto cleanup; // Hemisphere was not specified
317
318 finish_parsing:
319 value = sign * (deg + min / 60.0 + sec / 3600.0);
320 *isValid = true;
321 }
322 }
323
324 cleanup:
325 free(in);
326 return value;
327 }
328
329 /**
330 * Parse latitude
331 */
332 bool GeoLocation::parseLatitude(const TCHAR *lat)
333 {
334 bool isValid;
335
336 m_lat = parse(lat, true, &isValid);
337 if (!isValid)
338 m_lat = 0.0;
339 return isValid;
340 }
341
342 /**
343 * Parse longitude
344 */
345 bool GeoLocation::parseLongitude(const TCHAR *lon)
346 {
347 bool isValid;
348
349 m_lon = parse(lon, false, &isValid);
350 if (!isValid)
351 m_lon = 0.0;
352 return isValid;
353 }
354
355 /**
356 * Convert degrees to radians
357 */
358 #define DegreesToRadians(a) ((a) * 3.14159265 / 180.0)
359
360 /**
361 * Check if this locations is (almost) same as given location
362 */
363 bool GeoLocation::sameLocation(double lat, double lon, int oldAccuracy) const
364 {
365 const double R = 6371000; // Earth radius in meters
366
367 double f1 = DegreesToRadians(lat);
368 double f2 = DegreesToRadians(m_lat);
369 double df = DegreesToRadians(m_lat - lat);
370 double dl = DegreesToRadians(m_lon - lon);
371
372 double a = pow(sin(df / 2), 2) + cos(f1) * cos(f2) * pow(sin(dl / 2), 2);
373 double c = 2 * atan2(sqrt(a), sqrt(1 - a));
374
375 double distance = R * c;
376 return distance <= min(oldAccuracy, m_accuracy);
377 }
378
379 /**
380 * Parse data sent by agent (signal,fix,lat,lon,accuracy,elevation,speed,direction,HDOP,time)
381 */
382 GeoLocation GeoLocation::parseAgentData(const TCHAR *data)
383 {
384 double lat, lon;
385 int acc, signal, fix;
386 time_t timestamp;
387
388 TCHAR buffer[256];
389 nx_strncpy(buffer, data, 256);
390
391 int pos = 0;
392 TCHAR *curr = buffer;
393 TCHAR *next;
394 do
395 {
396 next = _tcschr(curr, _T(','));
397 if (next != NULL)
398 *next = 0;
399 switch(pos)
400 {
401 case 0:
402 signal = _tcstol(curr, NULL, 10);
403 break;
404 case 1:
405 fix = _tcstol(curr, NULL, 10);
406 break;
407 case 2:
408 lat = _tcstod(curr, NULL);
409 break;
410 case 3:
411 lon = _tcstod(curr, NULL);
412 break;
413 case 4:
414 acc = _tcstol(curr, NULL, 10);
415 break;
416 case 9:
417 timestamp = (time_t)_tcstoll(curr, NULL, 10);
418 break;
419 default: // ignore the rest
420 break;
421 }
422 pos++;
423 curr = next + 1;
424 }
425 while(next != NULL);
426
427 if ((pos < 10) || (signal == 0) || (fix == 0))
428 return GeoLocation(); // parsing error or location is unknown
429 return GeoLocation(GL_GPS, lat, lon, acc, timestamp);
430 }