added defines MIN/MAX (uppercase) as replacement for min/max (lowercase); started...
[public/netxms.git] / src / libnetxms / geolocation.cpp
CommitLineData
4899db4d 1/*
e2babedf 2** NetXMS - Network Management System
3a4c13cc 3** Copyright (C) 2003-2017 Victor Kirhenshtein
e2babedf
VK
4**
5** This program is free software; you can redistribute it and/or modify
68f384ea
VK
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
e2babedf
VK
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**
68f384ea 15** You should have received a copy of the GNU Lesser General Public License
e2babedf
VK
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
27static const double ROUND_OFF = 0.00000001;
28
76b1d3d8
VK
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
2478a40a
VK
37/**
38 * Default constructor - create location of type UNSET
39 */
e2babedf
VK
40GeoLocation::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;
2478a40a 48 m_accuracy = 0;
f2d5b2c4 49 m_timestamp = 0;
e2babedf
VK
50}
51
2478a40a
VK
52/**
53 * Constructor - create location from double lat/lon values
54 */
f2d5b2c4 55GeoLocation::GeoLocation(int type, double lat, double lon, int accuracy, time_t timestamp)
e2babedf
VK
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;
2478a40a 63 m_accuracy = accuracy;
f2d5b2c4 64 m_timestamp = timestamp;
e2babedf
VK
65}
66
2478a40a
VK
67/**
68 * Constructor - create location from string lat/lon values
69 */
f2d5b2c4 70GeoLocation::GeoLocation(int type, const TCHAR *lat, const TCHAR *lon, int accuracy, time_t timestamp)
e2babedf
VK
71{
72 m_type = type;
73 m_isValid = parseLatitude(lat) && parseLongitude(lon);
74 posToString(true, m_lat);
75 posToString(false, m_lon);
2478a40a 76 m_accuracy = accuracy;
f2d5b2c4 77 m_timestamp = timestamp;
e2babedf
VK
78}
79
2478a40a
VK
80/**
81 * Copy constructor
82 */
0322eed7 83GeoLocation::GeoLocation(const GeoLocation &src)
e2babedf
VK
84{
85 m_type = src.m_type;
86 m_lat = src.m_lat;
87 m_lon = src.m_lon;
3a4c13cc
VK
88 _tcslcpy(m_latStr, src.m_latStr, 20);
89 _tcslcpy(m_lonStr, src.m_lonStr, 20);
e2babedf 90 m_isValid = src.m_isValid;
2478a40a 91 m_accuracy = src.m_accuracy;
f2d5b2c4 92 m_timestamp = src.m_timestamp;
e2babedf
VK
93}
94
2478a40a
VK
95/**
96 * Create geolocation object from data in NXCP message
97 */
b368969c 98GeoLocation::GeoLocation(NXCPMessage &msg)
40bd1038 99{
b368969c 100 m_type = (int)msg.getFieldAsUInt16(VID_GEOLOCATION_TYPE);
4101571e 101
b368969c 102 if (msg.getFieldType(VID_LATITUDE) == NXCP_DT_INT32)
4101571e
VK
103 m_lat = (double)msg.getFieldAsInt32(VID_LATITUDE) / 1000000;
104 else
105 m_lat = msg.getFieldAsDouble(VID_LATITUDE);
106
b368969c 107 if (msg.getFieldType(VID_LONGITUDE) == NXCP_DT_INT32)
4101571e
VK
108 m_lon = (double)msg.getFieldAsInt32(VID_LONGITUDE) / 1000000;
109 else
110 m_lon = msg.getFieldAsDouble(VID_LONGITUDE);
111
b368969c 112 m_accuracy = (int)msg.getFieldAsUInt16(VID_ACCURACY);
c57d020a
VK
113
114 m_timestamp = 0;
115 int ft = msg.getFieldType(VID_GEOLOCATION_TIMESTAMP);
b368969c 116 if (ft == NXCP_DT_INT64)
c57d020a 117 {
b368969c 118 m_timestamp = (time_t)msg.getFieldAsUInt64(VID_GEOLOCATION_TIMESTAMP);
c57d020a 119 }
b368969c 120 else if (ft == NXCP_DT_INT32)
c57d020a 121 {
b368969c 122 m_timestamp = (time_t)msg.getFieldAsUInt32(VID_GEOLOCATION_TIMESTAMP);
c57d020a 123 }
b368969c 124 else if (ft == NXCP_DT_STRING)
c57d020a
VK
125 {
126 char ts[256];
b368969c 127 msg.getFieldAsMBString(VID_GEOLOCATION_TIMESTAMP, ts, 256);
c57d020a
VK
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 }
e170efb5 136 if(m_timestamp == 0)
a83a24ea 137 m_timestamp = time(0);
c57d020a 138
40bd1038
VK
139 posToString(true, m_lat);
140 posToString(false, m_lon);
141 m_isValid = true;
142}
143
2478a40a
VK
144/**
145 * Destructor
146 */
e2babedf
VK
147GeoLocation::~GeoLocation()
148{
149}
150
2478a40a
VK
151/**
152 * Assignment operator
153 */
e2babedf
VK
154GeoLocation& GeoLocation::operator =(const GeoLocation &src)
155{
156 m_type = src.m_type;
157 m_lat = src.m_lat;
158 m_lon = src.m_lon;
3a4c13cc
VK
159 _tcslcpy(m_latStr, src.m_latStr, 20);
160 _tcslcpy(m_lonStr, src.m_lonStr, 20);
e2babedf 161 m_isValid = src.m_isValid;
2478a40a 162 m_accuracy = src.m_accuracy;
f2d5b2c4 163 m_timestamp = src.m_timestamp;
e2babedf
VK
164 return *this;
165}
166
2478a40a
VK
167/**
168 * Fill NXCP message
169 */
63e99e56 170void GeoLocation::fillMessage(NXCPMessage &msg) const
40bd1038 171{
6336bba3 172 msg.setField(VID_GEOLOCATION_TYPE, (UINT16)m_type);
b368969c
VK
173 msg.setField(VID_LATITUDE, m_lat);
174 msg.setField(VID_LONGITUDE, m_lon);
6336bba3
VK
175 msg.setField(VID_ACCURACY, (UINT16)m_accuracy);
176 msg.setField(VID_GEOLOCATION_TIMESTAMP, (UINT64)m_timestamp);
177}
178
179/**
180 * Convert to JSON
181 */
182json_t *GeoLocation::toJson() const
183{
184 json_t *root = json_object();
185 json_object_set_new(root, "type", json_integer(m_type));
186 json_object_set_new(root, "latitude", json_real(m_lat));
187 json_object_set_new(root, "longitude", json_real(m_lon));
188 json_object_set_new(root, "accuracy", json_integer(m_accuracy));
189 json_object_set_new(root, "timestamp", json_integer(m_timestamp));
190 return root;
40bd1038
VK
191}
192
2478a40a
VK
193/**
194 * Getters degree from double value
195 */
e2babedf
VK
196int GeoLocation::getIntegerDegree(double pos)
197{
198 return (int)(fabs(pos) + ROUND_OFF);
199}
200
2478a40a
VK
201/**
202 * Getters minutes from double value
203 */
e2babedf
VK
204int GeoLocation::getIntegerMinutes(double pos)
205{
206 double d = fabs(pos) + ROUND_OFF;
207 return (int)((d - (double)((int)d)) * 60.0);
208}
209
2478a40a
VK
210/**
211 * Getters seconds from double value
212 */
e2babedf
VK
213double GeoLocation::getDecimalSeconds(double pos)
214{
215 double d = fabs(pos) * 60.0 + ROUND_OFF;
216 return (d - (double)((int)d)) * 60.0;
217}
218
2478a40a
VK
219/**
220 * Convert position to string
221 */
e2babedf
VK
222void GeoLocation::posToString(bool isLat, double pos)
223{
224 TCHAR *buffer = isLat ? m_latStr : m_lonStr;
225
76b1d3d8 226 // Check range
e2babedf
VK
227 if ((pos < -180.0) || (pos > 180.0))
228 {
229 _tcscpy(buffer, _T("<invalid>"));
230 return;
231 }
232
233 // Encode hemisphere
234 if (isLat)
235 {
236 *buffer = (pos < 0) ? _T('S') : _T('N');
237 }
238 else
239 {
240 *buffer = (pos < 0) ? _T('W') : _T('E');
241 }
242 buffer++;
243 *buffer++ = _T(' ');
244
993c85f5 245 _sntprintf(buffer, 18, _T("%02d") DEGREE_SIGN_STR _T(" %02d' %06.3f\""), getIntegerDegree(pos), getIntegerMinutes(pos), getDecimalSeconds(pos));
e2babedf
VK
246}
247
2478a40a
VK
248/**
249 * Parse latitude/longitude string
250 */
e2babedf
VK
251double GeoLocation::parse(const TCHAR *str, bool isLat, bool *isValid)
252{
253 *isValid = false;
254
255 // Prepare input string
256 TCHAR *in = _tcsdup(str);
257 StrStrip(in);
258
259 // Check if string given is just double value
260 TCHAR *eptr;
261 double value = _tcstod(in, &eptr);
262 if (*eptr == 0)
263 {
264 *isValid = true;
265 }
266 else // If not a valid double, check if it's in DMS form
267 {
268 // Check for invalid characters
d96286e3 269 if (_tcsspn(in, isLat ? _T("0123456789.,'\" NS") DEGREE_SIGN_STR : _T("0123456789.,'\" EW") DEGREE_SIGN_STR) == _tcslen(in))
e2babedf 270 {
d96286e3 271 TranslateStr(in, _T(","), _T("."));
e2babedf
VK
272 TCHAR *curr = in;
273
274 int sign = 0;
275 if ((*curr == _T('N')) || (*curr == _T('E')))
276 {
277 sign = 1;
278 curr++;
279 }
280 else if ((*curr == _T('S')) || (*curr == _T('W')))
281 {
282 sign = -1;
283 curr++;
284 }
285
286 while(*curr == _T(' '))
287 curr++;
288
289 double deg = 0.0, min = 0.0, sec = 0.0;
290
291 deg = _tcstod(curr, &eptr);
292 if (*eptr == 0) // End of string
293 goto finish_parsing;
76b1d3d8 294 if ((*eptr != DEGREE_SIGN_CHR) && (*eptr != _T(' ')))
e2babedf
VK
295 goto cleanup; // Unexpected character
296 curr = eptr + 1;
297 while(*curr == _T(' '))
298 curr++;
299
300 min = _tcstod(curr, &eptr);
301 if (*eptr == 0) // End of string
302 goto finish_parsing;
303 if (*eptr != _T('\''))
304 goto cleanup; // Unexpected character
305 curr = eptr + 1;
306 while(*curr == _T(' '))
307 curr++;
308
309 sec = _tcstod(curr, &eptr);
310 if (*eptr == 0) // End of string
311 goto finish_parsing;
312 if (*eptr != _T('"'))
313 goto cleanup; // Unexpected character
314 curr = eptr + 1;
315 while(*curr == _T(' '))
316 curr++;
317
318 if ((*curr == _T('N')) || (*curr == _T('E')))
319 {
320 sign = 1;
321 curr++;
322 }
323 else if ((*curr == _T('S')) || (*curr == _T('W')))
324 {
325 sign = -1;
326 curr++;
327 }
328
329 if (sign == 0)
330 goto cleanup; // Hemisphere was not specified
331
332finish_parsing:
333 value = sign * (deg + min / 60.0 + sec / 3600.0);
334 *isValid = true;
335 }
336 }
337
338cleanup:
339 free(in);
340 return value;
341}
342
2478a40a
VK
343/**
344 * Parse latitude
345 */
e2babedf
VK
346bool GeoLocation::parseLatitude(const TCHAR *lat)
347{
348 bool isValid;
349
350 m_lat = parse(lat, true, &isValid);
351 if (!isValid)
352 m_lat = 0.0;
353 return isValid;
354}
355
2478a40a
VK
356/**
357 * Parse longitude
358 */
e2babedf
VK
359bool GeoLocation::parseLongitude(const TCHAR *lon)
360{
361 bool isValid;
362
363 m_lon = parse(lon, false, &isValid);
364 if (!isValid)
365 m_lon = 0.0;
366 return isValid;
367}
4899db4d 368
f3e402b0
VK
369/**
370 * Convert degrees to radians
371 */
372#define DegreesToRadians(a) ((a) * 3.14159265 / 180.0)
373
e8b3beef
VK
374/**
375 * Check if this locations is (almost) same as given location
376 */
63e99e56 377bool GeoLocation::sameLocation(double lat, double lon, int oldAccuracy) const
4899db4d 378{
f3e402b0
VK
379 const double R = 6371000; // Earth radius in meters
380
381 double f1 = DegreesToRadians(lat);
382 double f2 = DegreesToRadians(m_lat);
383 double df = DegreesToRadians(m_lat - lat);
384 double dl = DegreesToRadians(m_lon - lon);
4899db4d 385
f3e402b0
VK
386 double a = pow(sin(df / 2), 2) + cos(f1) * cos(f2) * pow(sin(dl / 2), 2);
387 double c = 2 * atan2(sqrt(a), sqrt(1 - a));
4899db4d 388
f3e402b0 389 double distance = R * c;
b9792835 390 return distance <= std::min(oldAccuracy, m_accuracy);
4899db4d 391}
2e22746d
VK
392
393/**
394 * Parse data sent by agent (signal,fix,lat,lon,accuracy,elevation,speed,direction,HDOP,time)
395 */
396GeoLocation GeoLocation::parseAgentData(const TCHAR *data)
397{
398 double lat, lon;
399 int acc, signal, fix;
400 time_t timestamp;
401
402 TCHAR buffer[256];
3a4c13cc 403 _tcslcpy(buffer, data, 256);
2e22746d
VK
404
405 int pos = 0;
406 TCHAR *curr = buffer;
407 TCHAR *next;
408 do
409 {
410 next = _tcschr(curr, _T(','));
411 if (next != NULL)
412 *next = 0;
413 switch(pos)
414 {
415 case 0:
416 signal = _tcstol(curr, NULL, 10);
417 break;
418 case 1:
419 fix = _tcstol(curr, NULL, 10);
420 break;
421 case 2:
422 lat = _tcstod(curr, NULL);
423 break;
424 case 3:
425 lon = _tcstod(curr, NULL);
426 break;
427 case 4:
428 acc = _tcstol(curr, NULL, 10);
429 break;
430 case 9:
431 timestamp = (time_t)_tcstoll(curr, NULL, 10);
432 break;
433 default: // ignore the rest
434 break;
435 }
436 pos++;
437 curr = next + 1;
438 }
439 while(next != NULL);
440
441 if ((pos < 10) || (signal == 0) || (fix == 0))
442 return GeoLocation(); // parsing error or location is unknown
443 return GeoLocation(GL_GPS, lat, lon, acc, timestamp);
444}
379f8228 445
446int GeoLocation::calculateDistance(GeoLocation &location) const
447{
448 const double R = 6371000; // Earth radius in meters
449
450 double f1 = DegreesToRadians(location.m_lat);
451 double f2 = DegreesToRadians(m_lat);
452 double df = DegreesToRadians(m_lat - location.m_lat);
453 double dl = DegreesToRadians(m_lon - location.m_lon);
454
455 double a = pow(sin(df / 2), 2) + cos(f1) * cos(f2) * pow(sin(dl / 2), 2);
456 double c = 2 * atan2(sqrt(a), sqrt(1 - a));
457
458 double distance = R * c;
459 return (int)(distance+0.5);
460}