1 /* vim: set et ts=8 sw=8: */
2 /* gclue-location.c
3 *
4 * Copyright 2012 Bastien Nocera
5 * Copyright 2015 Ankit (Verma)
6 *
7 * Geoclue is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * Geoclue is distributed in the hope that it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with Geoclue; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 * Authors: Bastien Nocera <hadess@hadess.net>
22 * Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
23 * Ankit (Verma) <ankitstarski@gmail.com>
24 */
25
26 #include "gclue-location.h"
27 #include <math.h>
28 #include <string.h>
29 #include <stdlib.h>
30
31 #define TIME_DIFF_THRESHOLD 60000000 /* 60 seconds */
32 #define EARTH_RADIUS_KM 6372.795
33
34 struct _GClueLocationPrivate {
35 char *description;
36
37 gdouble longitude;
38 gdouble latitude;
39 gdouble altitude;
40 gdouble accuracy;
41 guint64 timestamp;
42 gdouble speed;
43 gdouble heading;
44 };
45
46 enum {
47 PROP_0,
48
49 PROP_LATITUDE,
50 PROP_LONGITUDE,
51 PROP_ACCURACY,
52 PROP_DESCRIPTION,
53 PROP_TIMESTAMP,
54 PROP_ALTITUDE,
55 PROP_SPEED,
56 PROP_HEADING,
57 };
58
59 G_DEFINE_TYPE_WITH_CODE (GClueLocation,
60 gclue_location,
61 G_TYPE_OBJECT,
62 G_ADD_PRIVATE (GClueLocation));
63
64 static void
gclue_location_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)65 gclue_location_get_property (GObject *object,
66 guint property_id,
67 GValue *value,
68 GParamSpec *pspec)
69 {
70 GClueLocation *location = GCLUE_LOCATION (object);
71
72 switch (property_id) {
73 case PROP_DESCRIPTION:
74 g_value_set_string (value,
75 gclue_location_get_description (location));
76 break;
77
78 case PROP_LATITUDE:
79 g_value_set_double (value,
80 gclue_location_get_latitude (location));
81 break;
82
83 case PROP_LONGITUDE:
84 g_value_set_double (value,
85 gclue_location_get_longitude (location));
86 break;
87
88 case PROP_ALTITUDE:
89 g_value_set_double (value,
90 gclue_location_get_altitude (location));
91 break;
92
93 case PROP_ACCURACY:
94 g_value_set_double (value,
95 gclue_location_get_accuracy (location));
96 break;
97
98 case PROP_TIMESTAMP:
99 g_value_set_uint64 (value,
100 gclue_location_get_timestamp (location));
101 break;
102 case PROP_SPEED:
103 g_value_set_double (value,
104 gclue_location_get_speed (location));
105 break;
106
107 case PROP_HEADING:
108 g_value_set_double (value,
109 gclue_location_get_heading (location));
110 break;
111
112 default:
113 /* We don't have any other property... */
114 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
115 break;
116 }
117 }
118
119 static void
gclue_location_set_latitude(GClueLocation * loc,gdouble latitude)120 gclue_location_set_latitude (GClueLocation *loc,
121 gdouble latitude)
122 {
123 g_return_if_fail (latitude >= -90.0 && latitude <= 90.0);
124
125 loc->priv->latitude = latitude;
126 }
127
128 static void
gclue_location_set_longitude(GClueLocation * loc,gdouble longitude)129 gclue_location_set_longitude (GClueLocation *loc,
130 gdouble longitude)
131 {
132 g_return_if_fail (longitude >= -180.0 && longitude <= 180.0);
133
134 loc->priv->longitude = longitude;
135 }
136
137 static void
gclue_location_set_altitude(GClueLocation * loc,gdouble altitude)138 gclue_location_set_altitude (GClueLocation *loc,
139 gdouble altitude)
140 {
141 loc->priv->altitude = altitude;
142 }
143
144 static void
gclue_location_set_accuracy(GClueLocation * loc,gdouble accuracy)145 gclue_location_set_accuracy (GClueLocation *loc,
146 gdouble accuracy)
147 {
148 g_return_if_fail (accuracy >= GCLUE_LOCATION_ACCURACY_UNKNOWN);
149
150 loc->priv->accuracy = accuracy;
151 }
152
153 static void
gclue_location_set_timestamp(GClueLocation * loc,guint64 timestamp)154 gclue_location_set_timestamp (GClueLocation *loc,
155 guint64 timestamp)
156 {
157 g_return_if_fail (GCLUE_IS_LOCATION (loc));
158
159 loc->priv->timestamp = timestamp;
160 }
161
162 void
gclue_location_set_description(GClueLocation * loc,const char * description)163 gclue_location_set_description (GClueLocation *loc,
164 const char *description)
165 {
166 g_return_if_fail (GCLUE_IS_LOCATION (loc));
167
168 g_free (loc->priv->description);
169 loc->priv->description = g_strdup (description);
170 }
171
172 static void
gclue_location_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)173 gclue_location_set_property (GObject *object,
174 guint property_id,
175 const GValue *value,
176 GParamSpec *pspec)
177 {
178 GClueLocation *location = GCLUE_LOCATION (object);
179
180 switch (property_id) {
181 case PROP_DESCRIPTION:
182 gclue_location_set_description (location,
183 g_value_get_string (value));
184 break;
185
186 case PROP_LATITUDE:
187 gclue_location_set_latitude (location,
188 g_value_get_double (value));
189 break;
190
191 case PROP_LONGITUDE:
192 gclue_location_set_longitude (location,
193 g_value_get_double (value));
194 break;
195
196 case PROP_ALTITUDE:
197 gclue_location_set_altitude (location,
198 g_value_get_double (value));
199 break;
200
201 case PROP_ACCURACY:
202 gclue_location_set_accuracy (location,
203 g_value_get_double (value));
204 break;
205
206 case PROP_TIMESTAMP:
207 gclue_location_set_timestamp (location,
208 g_value_get_uint64 (value));
209 break;
210 case PROP_SPEED:
211 gclue_location_set_speed (location,
212 g_value_get_double (value));
213 break;
214
215 case PROP_HEADING:
216 gclue_location_set_heading (location,
217 g_value_get_double (value));
218 break;
219
220 default:
221 /* We don't have any other property... */
222 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
223 break;
224 }
225 }
226
227 static void
gclue_location_constructed(GObject * object)228 gclue_location_constructed (GObject *object)
229 {
230 GClueLocation *location = GCLUE_LOCATION (object);
231 GTimeVal tv;
232
233 if (location->priv->timestamp != 0)
234 return;
235
236 g_get_current_time (&tv);
237 gclue_location_set_timestamp (location, tv.tv_sec);
238 }
239
240 static void
gclue_location_finalize(GObject * glocation)241 gclue_location_finalize (GObject *glocation)
242 {
243 g_clear_pointer (&GCLUE_LOCATION (glocation)->priv->description,
244 g_free);
245
246 G_OBJECT_CLASS (gclue_location_parent_class)->finalize (glocation);
247 }
248
249 static void
gclue_location_class_init(GClueLocationClass * klass)250 gclue_location_class_init (GClueLocationClass *klass)
251 {
252 GObjectClass *glocation_class = G_OBJECT_CLASS (klass);
253 GParamSpec *pspec;
254
255 glocation_class->constructed = gclue_location_constructed;
256 glocation_class->finalize = gclue_location_finalize;
257 glocation_class->get_property = gclue_location_get_property;
258 glocation_class->set_property = gclue_location_set_property;
259
260 /**
261 * GClueLocation:description:
262 *
263 * The description of this location.
264 */
265 pspec = g_param_spec_string ("description",
266 "Description",
267 "Description of this location",
268 NULL,
269 G_PARAM_READWRITE |
270 G_PARAM_STATIC_STRINGS);
271 g_object_class_install_property (glocation_class, PROP_DESCRIPTION, pspec);
272
273 /**
274 * GClueLocation:latitude:
275 *
276 * The latitude of this location in degrees.
277 */
278 pspec = g_param_spec_double ("latitude",
279 "Latitude",
280 "The latitude of this location in degrees",
281 -90.0,
282 90.0,
283 0.0,
284 G_PARAM_READWRITE |
285 G_PARAM_STATIC_STRINGS);
286 g_object_class_install_property (glocation_class, PROP_LATITUDE, pspec);
287
288 /**
289 * GClueLocation:longitude:
290 *
291 * The longitude of this location in degrees.
292 */
293 pspec = g_param_spec_double ("longitude",
294 "Longitude",
295 "The longitude of this location in degrees",
296 -180.0,
297 180.0,
298 0.0,
299 G_PARAM_READWRITE |
300 G_PARAM_STATIC_STRINGS);
301 g_object_class_install_property (glocation_class, PROP_LONGITUDE, pspec);
302
303 /**
304 * GClueLocation:altitude:
305 *
306 * The altitude of this location in meters.
307 */
308 pspec = g_param_spec_double ("altitude",
309 "Altitude",
310 "The altitude of this location in meters",
311 GCLUE_LOCATION_ALTITUDE_UNKNOWN,
312 G_MAXDOUBLE,
313 GCLUE_LOCATION_ALTITUDE_UNKNOWN,
314 G_PARAM_READWRITE |
315 G_PARAM_STATIC_STRINGS);
316 g_object_class_install_property (glocation_class, PROP_ALTITUDE, pspec);
317
318 /**
319 * GClueLocation:accuracy:
320 *
321 * The accuracy of this location in meters.
322 */
323 pspec = g_param_spec_double ("accuracy",
324 "Accuracy",
325 "The accuracy of this location in meters",
326 GCLUE_LOCATION_ACCURACY_UNKNOWN,
327 G_MAXDOUBLE,
328 GCLUE_LOCATION_ACCURACY_UNKNOWN,
329 G_PARAM_READWRITE |
330 G_PARAM_STATIC_STRINGS);
331 g_object_class_install_property (glocation_class, PROP_ACCURACY, pspec);
332
333
334 /**
335 * GClueLocation:timestamp:
336 *
337 * A timestamp in seconds since
338 * <ulink url="http://en.wikipedia.org/wiki/Unix_epoch">Epoch</ulink>,
339 * giving when the location was resolved from an address.
340 *
341 * A value of 0 (zero) will be interpreted as the current time.
342 */
343 pspec = g_param_spec_uint64 ("timestamp",
344 "Timestamp",
345 "The timestamp of this location "
346 "in seconds since Epoch",
347 0,
348 G_MAXINT64,
349 0,
350 G_PARAM_READWRITE |
351 G_PARAM_CONSTRUCT_ONLY |
352 G_PARAM_STATIC_STRINGS);
353 g_object_class_install_property (glocation_class, PROP_TIMESTAMP, pspec);
354
355 /**
356 * GClueLocation:speed
357 *
358 * The speed in meters per second.
359 */
360 pspec = g_param_spec_double ("speed",
361 "Speed",
362 "Speed in meters per second",
363 GCLUE_LOCATION_SPEED_UNKNOWN,
364 G_MAXDOUBLE,
365 GCLUE_LOCATION_SPEED_UNKNOWN,
366 G_PARAM_READWRITE |
367 G_PARAM_STATIC_STRINGS);
368 g_object_class_install_property (glocation_class, PROP_SPEED, pspec);
369
370 /**
371 * GClueLocation:heading
372 *
373 * The positive angle between the direction of movement and the North
374 * direction, in clockwise direction. The angle is measured in degrees.
375 */
376 pspec = g_param_spec_double ("heading",
377 "Heading",
378 "The positive Angle between the direction"
379 " of movement and the North direction, in"
380 " clockwise direction. The angle is "
381 "measured in degrees.",
382 GCLUE_LOCATION_HEADING_UNKNOWN,
383 G_MAXDOUBLE,
384 GCLUE_LOCATION_HEADING_UNKNOWN,
385 G_PARAM_READWRITE |
386 G_PARAM_STATIC_STRINGS);
387 g_object_class_install_property (glocation_class, PROP_HEADING, pspec);
388 }
389
390 static void
gclue_location_init(GClueLocation * location)391 gclue_location_init (GClueLocation *location)
392 {
393 location->priv = G_TYPE_INSTANCE_GET_PRIVATE ((location),
394 GCLUE_TYPE_LOCATION,
395 GClueLocationPrivate);
396
397 location->priv->altitude = GCLUE_LOCATION_ALTITUDE_UNKNOWN;
398 location->priv->accuracy = GCLUE_LOCATION_ACCURACY_UNKNOWN;
399 location->priv->speed = GCLUE_LOCATION_SPEED_UNKNOWN;
400 location->priv->heading = GCLUE_LOCATION_HEADING_UNKNOWN;
401 }
402
403 static gdouble
get_accuracy_from_hdop(gdouble hdop)404 get_accuracy_from_hdop (gdouble hdop)
405 {
406 /* FIXME: These are really just rough estimates based on:
407 * http://en.wikipedia.org/wiki/Dilution_of_precision_%28GPS%29#Meaning_of_DOP_Values
408 */
409 if (hdop <= 1)
410 return 0;
411 else if (hdop <= 2)
412 return 1;
413 else if (hdop <= 5)
414 return 3;
415 else if (hdop <= 10)
416 return 50;
417 else if (hdop <= 20)
418 return 100;
419 else
420 return 300;
421 }
422
423 static gdouble
parse_coordinate_string(const char * coordinate,const char * direction)424 parse_coordinate_string (const char *coordinate,
425 const char *direction)
426 {
427 gdouble minutes, degrees, out;
428 gchar *degrees_str;
429 gchar *dot_str;
430 gint dot_offset;
431
432 if (coordinate[0] == '\0' ||
433 direction[0] == '\0' ||
434 direction[0] == '\0')
435 return INVALID_COORDINATE;
436
437 if (direction[0] != 'N' &&
438 direction[0] != 'S' &&
439 direction[0] != 'E' &&
440 direction[0] != 'W') {
441 g_warning ("Unknown direction '%s' for coordinates, ignoring..",
442 direction);
443 return INVALID_COORDINATE;
444 }
445
446 dot_str = g_strstr_len (coordinate, 6, ".");
447 if (dot_str == NULL)
448 return INVALID_COORDINATE;
449 dot_offset = dot_str - coordinate;
450
451 degrees_str = g_strndup (coordinate, dot_offset - 2);
452 degrees = g_ascii_strtod (degrees_str, NULL);
453 g_free (degrees_str);
454
455 minutes = g_ascii_strtod (dot_str - 2, NULL);
456
457 /* Include the minutes as part of the degrees */
458 out = degrees + (minutes / 60.0);
459
460 if (direction[0] == 'S' || direction[0] == 'W')
461 out = 0 - out;
462
463 return out;
464 }
465
466 static gdouble
parse_altitude_string(const char * altitude,const char * unit)467 parse_altitude_string (const char *altitude,
468 const char *unit)
469 {
470 if (altitude[0] == '\0' || unit[0] == '\0')
471 return GCLUE_LOCATION_ALTITUDE_UNKNOWN;
472
473 if (unit[0] != 'M') {
474 g_warning ("Unknown unit '%s' for altitude, ignoring..",
475 unit);
476
477 return GCLUE_LOCATION_ALTITUDE_UNKNOWN;
478 }
479
480 return g_ascii_strtod (altitude, NULL);
481 }
482
483 static gint64
parse_nmea_timestamp(const char * nmea_ts)484 parse_nmea_timestamp (const char *nmea_ts)
485 {
486 char parts[3][3];
487 int i, hours, minutes, seconds;
488 GDateTime *now, *ts = NULL;
489 guint64 ret;
490
491 now = g_date_time_new_now_utc ();
492 ret = g_date_time_to_unix (now);
493
494 if (strlen (nmea_ts) < 6) {
495 if (strlen (nmea_ts) >= 1)
496 /* Empty string just means no ts, so no warning */
497 g_warning ("Failed to parse NMEA timestamp '%s'",
498 nmea_ts);
499
500 goto parse_error;
501 }
502
503 for (i = 0; i < 3; i++) {
504 memmove (parts[i], nmea_ts + (i * 2), 2);
505 parts[i][2] = '\0';
506 }
507 hours = atoi (parts[0]);
508 minutes = atoi (parts[1]);
509 seconds = atoi (parts[2]);
510
511 ts = g_date_time_new_utc (g_date_time_get_year (now),
512 g_date_time_get_month (now),
513 g_date_time_get_day_of_month (now),
514 hours,
515 minutes,
516 seconds);
517
518 if (g_date_time_difference (ts, now) > TIME_DIFF_THRESHOLD) {
519 g_debug ("NMEA timestamp '%s' in future. Assuming yesterday's.",
520 nmea_ts);
521 g_date_time_unref (ts);
522
523 ts = g_date_time_new_utc (g_date_time_get_year (now),
524 g_date_time_get_month (now),
525 g_date_time_get_day_of_month (now) - 1,
526 hours,
527 minutes,
528 seconds);
529 }
530
531 ret = g_date_time_to_unix (ts);
532 g_date_time_unref (ts);
533 parse_error:
534 g_date_time_unref (now);
535
536 return ret;
537 }
538
539 /**
540 * gclue_location_new:
541 * @latitude: a valid latitude
542 * @longitude: a valid longitude
543 * @accuracy: accuracy of location in meters
544 *
545 * Creates a new #GClueLocation object.
546 *
547 * Returns: a new #GClueLocation object. Use g_object_unref() when done.
548 **/
549 GClueLocation *
gclue_location_new(gdouble latitude,gdouble longitude,gdouble accuracy)550 gclue_location_new (gdouble latitude,
551 gdouble longitude,
552 gdouble accuracy)
553 {
554 return g_object_new (GCLUE_TYPE_LOCATION,
555 "latitude", latitude,
556 "longitude", longitude,
557 "accuracy", accuracy,
558 NULL);
559 }
560
561 /**
562 * gclue_location_new_full:
563 * @latitude: a valid latitude
564 * @longitude: a valid longitude
565 * @accuracy: accuracy of location in meters
566 * @speed: speed in meters per second
567 * @heading: heading in degrees
568 * @altitude: altitude of location in meters
569 * @timestamp: timestamp in seconds since the Epoch
570 * @description: a description for the location
571 *
572 * Creates a new #GClueLocation object.
573 *
574 * Returns: a new #GClueLocation object. Use g_object_unref() when done.
575 **/
576 GClueLocation *
gclue_location_new_full(gdouble latitude,gdouble longitude,gdouble accuracy,gdouble speed,gdouble heading,gdouble altitude,guint64 timestamp,const char * description)577 gclue_location_new_full (gdouble latitude,
578 gdouble longitude,
579 gdouble accuracy,
580 gdouble speed,
581 gdouble heading,
582 gdouble altitude,
583 guint64 timestamp,
584 const char *description)
585 {
586 return g_object_new (GCLUE_TYPE_LOCATION,
587 "latitude", latitude,
588 "longitude", longitude,
589 "accuracy", accuracy,
590 "speed", speed,
591 "heading", heading,
592 "altitude", altitude,
593 "timestamp", timestamp,
594 "description", description,
595 NULL);
596 }
597
598 /**
599 * gclue_location_create_from_gga:
600 * @gga: NMEA GGA sentence
601 * @error: Place-holder for errors.
602 *
603 * Creates a new #GClueLocation object from a GGA sentence.
604 *
605 * Returns: a new #GClueLocation object, or %NULL on error. Unref using
606 * #g_object_unref() when done with it.
607 **/
608 GClueLocation *
gclue_location_create_from_gga(const char * gga,GError ** error)609 gclue_location_create_from_gga (const char *gga, GError **error)
610 {
611 GClueLocation *location = NULL;
612 gdouble latitude, longitude, accuracy, altitude;
613 gdouble hdop; /* Horizontal Dilution Of Precision */
614 guint64 timestamp;
615 char **parts;
616
617 parts = g_strsplit (gga, ",", -1);
618 if (g_strv_length (parts) < 14) {
619 g_set_error_literal (error,
620 G_IO_ERROR,
621 G_IO_ERROR_INVALID_ARGUMENT,
622 "Invalid NMEA GGA sentence");
623 goto out;
624 }
625
626 /* For syntax of GGA sentences:
627 * http://www.gpsinformation.org/dale/nmea.htm#GGA
628 */
629 timestamp = parse_nmea_timestamp (parts[1]);
630 latitude = parse_coordinate_string (parts[2], parts[3]);
631 longitude = parse_coordinate_string (parts[4], parts[5]);
632 if (latitude == INVALID_COORDINATE || longitude == INVALID_COORDINATE) {
633 g_set_error_literal (error,
634 G_IO_ERROR,
635 G_IO_ERROR_INVALID_ARGUMENT,
636 "Invalid NMEA GGA sentence");
637 goto out;
638 }
639
640 altitude = parse_altitude_string (parts[9], parts[10]);
641
642 hdop = g_ascii_strtod (parts[8], NULL);
643 accuracy = get_accuracy_from_hdop (hdop);
644
645 location = g_object_new (GCLUE_TYPE_LOCATION,
646 "latitude", latitude,
647 "longitude", longitude,
648 "accuracy", accuracy,
649 "timestamp", timestamp,
650 NULL);
651 if (altitude != GCLUE_LOCATION_ALTITUDE_UNKNOWN)
652 g_object_set (location, "altitude", altitude, NULL);
653
654 out:
655 g_strfreev (parts);
656 return location;
657 }
658
659 /**
660 * gclue_location_duplicate:
661 * @location: the #GClueLocation instance to duplicate.
662 *
663 * Creates a new copy of @location object.
664 *
665 * Returns: a new #GClueLocation object. Use g_object_unref() when done.
666 **/
667 GClueLocation *
gclue_location_duplicate(GClueLocation * location)668 gclue_location_duplicate (GClueLocation *location)
669 {
670 g_return_val_if_fail (GCLUE_IS_LOCATION (location), NULL);
671
672 return g_object_new
673 (GCLUE_TYPE_LOCATION,
674 "latitude", location->priv->latitude,
675 "longitude", location->priv->longitude,
676 "accuracy", location->priv->accuracy,
677 "altitude", location->priv->altitude,
678 "timestamp", location->priv->timestamp,
679 "speed", location->priv->speed,
680 "heading", location->priv->heading,
681 NULL);
682 }
683
684 const char *
gclue_location_get_description(GClueLocation * loc)685 gclue_location_get_description (GClueLocation *loc)
686 {
687 g_return_val_if_fail (GCLUE_IS_LOCATION (loc), NULL);
688
689 return loc->priv->description;
690 }
691
692 /**
693 * gclue_location_get_latitude:
694 * @loc: a #GClueLocation
695 *
696 * Gets the latitude of location @loc.
697 *
698 * Returns: The latitude of location @loc.
699 **/
700 gdouble
gclue_location_get_latitude(GClueLocation * loc)701 gclue_location_get_latitude (GClueLocation *loc)
702 {
703 g_return_val_if_fail (GCLUE_IS_LOCATION (loc), 0.0);
704
705 return loc->priv->latitude;
706 }
707
708 /**
709 * gclue_location_get_longitude:
710 * @loc: a #GClueLocation
711 *
712 * Gets the longitude of location @loc.
713 *
714 * Returns: The longitude of location @loc.
715 **/
716 gdouble
gclue_location_get_longitude(GClueLocation * loc)717 gclue_location_get_longitude (GClueLocation *loc)
718 {
719 g_return_val_if_fail (GCLUE_IS_LOCATION (loc), 0.0);
720
721 return loc->priv->longitude;
722 }
723
724 /**
725 * gclue_location_get_altitude:
726 * @loc: a #GClueLocation
727 *
728 * Gets the altitude of location @loc.
729 *
730 * Returns: The altitude of location @loc.
731 **/
732 gdouble
gclue_location_get_altitude(GClueLocation * loc)733 gclue_location_get_altitude (GClueLocation *loc)
734 {
735 g_return_val_if_fail (GCLUE_IS_LOCATION (loc),
736 GCLUE_LOCATION_ALTITUDE_UNKNOWN);
737
738 return loc->priv->altitude;
739 }
740
741 /**
742 * gclue_location_get_accuracy:
743 * @loc: a #GClueLocation
744 *
745 * Gets the accuracy (in meters) of location @loc.
746 *
747 * Returns: The accuracy of location @loc.
748 **/
749 gdouble
gclue_location_get_accuracy(GClueLocation * loc)750 gclue_location_get_accuracy (GClueLocation *loc)
751 {
752 g_return_val_if_fail (GCLUE_IS_LOCATION (loc),
753 GCLUE_LOCATION_ACCURACY_UNKNOWN);
754
755 return loc->priv->accuracy;
756 }
757
758 /**
759 * gclue_location_get_timestamp:
760 * @loc: a #GClueLocation
761 *
762 * Gets the timestamp (in seconds since the Epoch) of location @loc. See
763 * #GClueLocation:timestamp.
764 *
765 * Returns: The timestamp of location @loc.
766 **/
767 guint64
gclue_location_get_timestamp(GClueLocation * loc)768 gclue_location_get_timestamp (GClueLocation *loc)
769 {
770 g_return_val_if_fail (GCLUE_IS_LOCATION (loc), 0);
771
772 return loc->priv->timestamp;
773 }
774
775 /**
776 * gclue_location_get_speed:
777 * @location: a #GClueLocation
778 *
779 * Gets the speed in meters per second.
780 *
781 * Returns: The speed, or %GCLUE_LOCATION_SPEED_UNKNOWN if speed in unknown.
782 **/
783 gdouble
gclue_location_get_speed(GClueLocation * location)784 gclue_location_get_speed (GClueLocation *location)
785 {
786 g_return_val_if_fail (GCLUE_IS_LOCATION (location),
787 GCLUE_LOCATION_SPEED_UNKNOWN);
788
789 return location->priv->speed;
790 }
791
792 /**
793 * gclue_location_set_speed:
794 * @location: a #GClueLocation
795 * @speed: speed in meters per second
796 *
797 * Sets the speed.
798 **/
799 void
gclue_location_set_speed(GClueLocation * location,gdouble speed)800 gclue_location_set_speed (GClueLocation *location,
801 gdouble speed)
802 {
803 location->priv->speed = speed;
804
805 g_object_notify (G_OBJECT (location), "speed");
806 }
807
808 /**
809 * gclue_location_set_speed_from_prev_location:
810 * @location: a #GClueLocation
811 * @prev_location: a #GClueLocation
812 *
813 * Calculates the speed based on provided previous location @prev_location
814 * and sets it on @location.
815 **/
816 void
gclue_location_set_speed_from_prev_location(GClueLocation * location,GClueLocation * prev_location)817 gclue_location_set_speed_from_prev_location (GClueLocation *location,
818 GClueLocation *prev_location)
819 {
820 gdouble speed;
821 guint64 timestamp, prev_timestamp;
822
823 g_return_if_fail (GCLUE_IS_LOCATION (location));
824 g_return_if_fail (prev_location == NULL ||
825 GCLUE_IS_LOCATION (prev_location));
826
827 if (prev_location == NULL) {
828 speed = GCLUE_LOCATION_SPEED_UNKNOWN;
829
830 goto out;
831 }
832
833 timestamp = gclue_location_get_timestamp (location);
834 prev_timestamp = gclue_location_get_timestamp (prev_location);
835
836 if (timestamp <= prev_timestamp) {
837 speed = GCLUE_LOCATION_SPEED_UNKNOWN;
838
839 goto out;
840 }
841
842 speed = gclue_location_get_distance_from (location, prev_location) *
843 1000.0 / (timestamp - prev_timestamp);
844
845 out:
846 location->priv->speed = speed;
847
848 g_object_notify (G_OBJECT (location), "speed");
849 }
850
851 /**
852 * gclue_location_get_heading:
853 * @location: a #GClueLocation
854 *
855 * Gets the positive angle between direction of movement and North direction.
856 * The angle is measured in degrees.
857 *
858 * Returns: The heading, or %GCLUE_LOCATION_HEADING_UNKNOWN if heading is
859 * unknown.
860 **/
861 gdouble
gclue_location_get_heading(GClueLocation * location)862 gclue_location_get_heading (GClueLocation *location)
863 {
864 g_return_val_if_fail (GCLUE_IS_LOCATION (location),
865 GCLUE_LOCATION_HEADING_UNKNOWN);
866
867 return location->priv->heading;
868 }
869
870 /**
871 * gclue_location_set_heading:
872 * @location: a #GClueLocation
873 * @heading: heading in degrees
874 *
875 * Sets the heading.
876 **/
877 void
gclue_location_set_heading(GClueLocation * location,gdouble heading)878 gclue_location_set_heading (GClueLocation *location,
879 gdouble heading)
880 {
881 location->priv->heading = heading;
882
883 g_object_notify (G_OBJECT (location), "heading");
884 }
885
886 /**
887 * gclue_location_set_heading_from_prev_location:
888 * @location: a #GClueLocation
889 * @prev_location: a #GClueLocation
890 *
891 * Calculates the heading direction in degrees with respect to North direction
892 * based on provided @prev_location and sets it on @location.
893 **/
894 void
gclue_location_set_heading_from_prev_location(GClueLocation * location,GClueLocation * prev_location)895 gclue_location_set_heading_from_prev_location (GClueLocation *location,
896 GClueLocation *prev_location)
897 {
898 gdouble dx, dy, angle, lat, lon, prev_lat, prev_lon;
899
900 g_return_if_fail (GCLUE_IS_LOCATION (location));
901 g_return_if_fail (prev_location == NULL ||
902 GCLUE_IS_LOCATION (prev_location));
903
904 if (prev_location == NULL) {
905 location->priv->heading = GCLUE_LOCATION_HEADING_UNKNOWN;
906
907 return;
908 }
909
910 lat = gclue_location_get_latitude (location);
911 lon = gclue_location_get_longitude (location);
912 prev_lat = gclue_location_get_latitude (prev_location);
913 prev_lon = gclue_location_get_longitude (prev_location);
914
915 dx = (lat - prev_lat);
916 dy = (lon - prev_lon);
917
918 /* atan2 takes in coordinate values of a 2D space and returns the angle
919 * which the line from origin to that coordinate makes with the positive
920 * X-axis, in the range (-PI,+PI]. Converting it into degrees we get the
921 * angle in range (-180,180]. This means East = 0 degree,
922 * West = -180 degrees, North = 90 degrees, South = -90 degrees.
923 *
924 * Passing atan2 a negative value of dx will flip the angles about
925 * Y-axis. This means the angle now returned will be the angle with
926 * respect to negative X-axis. Which makes West = 0 degree,
927 * East = 180 degrees, North = 90 degrees, South = -90 degrees. */
928 angle = atan2(dy, -dx) * 180.0 / M_PI;
929
930 /* Now, North is supposed to be 0 degree. Lets subtract 90 degrees
931 * from angle. After this step West = -90 degrees, East = 90 degrees,
932 * North = 0 degree, South = -180 degrees. */
933 angle -= 90.0;
934
935 /* As we know, angle ~= angle + 360; using this on negative values would
936 * bring the the angle in range [0,360).
937 *
938 * After this step West = 270 degrees, East = 90 degrees,
939 * North = 0 degree, South = 180 degrees. */
940 if (angle < 0)
941 angle += 360.0;
942
943 location->priv->heading = angle;
944
945 g_object_notify (G_OBJECT (location), "heading");
946 }
947
948 /**
949 * gclue_location_get_distance_from:
950 * @loca: a #GClueLocation
951 * @locb: a #GClueLocation
952 *
953 * Calculates the distance in km, along the curvature of the Earth,
954 * between 2 locations. Note that altitude changes are not
955 * taken into account.
956 *
957 * Returns: a distance in km.
958 **/
959 double
gclue_location_get_distance_from(GClueLocation * loca,GClueLocation * locb)960 gclue_location_get_distance_from (GClueLocation *loca,
961 GClueLocation *locb)
962 {
963 gdouble dlat, dlon, lat1, lat2;
964 gdouble a, c;
965
966 g_return_val_if_fail (GCLUE_IS_LOCATION (loca), 0.0);
967 g_return_val_if_fail (GCLUE_IS_LOCATION (locb), 0.0);
968
969 /* Algorithm from:
970 * http://www.movable-type.co.uk/scripts/latlong.html */
971
972 dlat = (locb->priv->latitude - loca->priv->latitude) * M_PI / 180.0;
973 dlon = (locb->priv->longitude - loca->priv->longitude) * M_PI / 180.0;
974 lat1 = loca->priv->latitude * M_PI / 180.0;
975 lat2 = locb->priv->latitude * M_PI / 180.0;
976
977 a = sin (dlat / 2) * sin (dlat / 2) +
978 sin (dlon / 2) * sin (dlon / 2) * cos (lat1) * cos (lat2);
979 c = 2 * atan2 (sqrt (a), sqrt (1-a));
980 return EARTH_RADIUS_KM * c;
981 }
982