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