]> Pileus Git - aweather/blob - src/plugins/gps-plugin.c
2b6265d2d364609cbde510b9b11e15915c372188
[aweather] / src / plugins / gps-plugin.c
1 /*
2  * Copyright (C) 2012 Adam Boggs <boggs@aircrafter.org>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18  /* TODO:
19   *    If gpsd connection fails, try to connect again periodically.
20   *    If gps stops sending data there should be an indication that it's stale.
21   */
22
23 #define _XOPEN_SOURCE
24 #include <stdio.h>
25 #include <fcntl.h>
26 #include <errno.h>
27 #include <time.h>
28 #include <config.h>
29 #include <string.h>
30 #include <glib/gstdio.h>
31 #include <gtk/gtk.h>
32 #include <gio/gio.h>
33 #include <math.h>
34
35 #include <grits.h>
36 #include <gps.h>
37
38 #include "gps-plugin.h"
39 #include "level2.h"
40 #include "../aweather-location.h"
41
42 /* interval to update map with new gps data in seconds. */
43 #define GPS_UPDATE_INTERVAL (2)
44
45 /* Filename and search path to use for gps marker, should be configurable */
46 #define GPS_MARKER_ICON_PATH ".:" PKGDATADIR
47 #define GPS_MARKER_ICON "arrow.png"
48
49 /* number of track points per group and number of groups to maintain */
50 #define NUM_TRACK_POINTS (6)
51 #define NUM_TRACK_GROUPS (4)
52 #define NUM_TRACK_POINTS_FACTOR (1.5)
53
54 /* interval to update log file in seconds (default value for slider) */
55 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
56 #define GPS_LOG_EXT "csv"
57
58 /* For updating the status bar conveniently */
59 #define GPS_STATUSBAR_CONTEXT "GPS"
60 #if 0
61 #define GPS_STATUS(gps, format, args...) \
62         do { \
63                 gchar *buf = g_strdup_printf(format, ##args); \
64                 gtk_statusbar_push(GTK_STATUSBAR(gps->status_bar), \
65                     gtk_statusbar_get_context_id( \
66                       GTK_STATUSBAR(gps->status_bar), \
67                       GPS_STATUSBAR_CONTEXT), \
68                 buf); \
69         } while (0)
70 #endif
71
72 #define GPS_STATUS(gps, format, args...) \
73         do { \
74                 gchar *buf = g_strdup_printf(format, ##args); \
75                 g_debug("STATUS: %s", buf); \
76         } while (0)
77
78
79 static gboolean gps_data_is_valid(struct gps_data_t *gps_data);
80 static gchar *gps_get_time_string(time_t gps_time);
81 static gchar *gps_get_date_string(double gps_time);
82 static void process_gps( gpointer, gint, GdkInputCondition);
83
84 #ifdef GPS_RANGE_RINGS
85 static void gps_init_range_rings(GritsPluginGps *gps, GtkWidget *gbox);
86 static gboolean on_gps_rangering_clicked_event(GtkWidget *widget, gpointer user_data);
87 #endif
88
89 static void gps_init_status_info(GritsPluginGps *gps, GtkWidget *gbox);
90 static void gps_init_control_frame(GritsPluginGps *gps, GtkWidget *gbox);
91 static gboolean on_gps_follow_clicked_event(GtkWidget *widget, gpointer user_data);
92
93 /* GPS logging support */
94 static void gps_init_track_log_frame(GritsPluginGps *gps, GtkWidget *gbox);
95 static gboolean on_gps_log_clicked_event(GtkWidget *widget, gpointer user_data);
96
97 /* Track management */
98 static void gps_track_init(struct gps_track_t *track);
99 static void gps_track_free(struct gps_track_t *track);
100 static void gps_track_clear(struct gps_track_t *track);
101 static void gps_track_add_point(struct gps_track_t *track, gdouble lat,
102     gdouble lon, gdouble elevation);
103 static void gps_track_group_incr(struct gps_track_t *track);
104
105 static gboolean on_gps_track_enable_clicked_event(GtkWidget *widget,
106     gpointer user_data);
107 static gboolean on_gps_track_clear_clicked_event(GtkWidget *widget,
108     gpointer user_data);
109 static gboolean gps_write_log(gpointer data);
110
111 static gchar *gps_get_status(struct gps_data_t *);
112 static gchar *gps_get_latitude(struct gps_data_t *);
113 static gchar *gps_get_longitude(struct gps_data_t *);
114 static gchar *gps_get_elevation(struct gps_data_t *);
115 static gchar *gps_get_heading(struct gps_data_t *);
116 static gchar *gps_get_speed(struct gps_data_t *);
117
118 /* Describes a line in the gps table */
119 struct gps_status_info {
120         gchar *label;
121         gchar *initial_val;
122         gchar *(*get_data)(struct gps_data_t *);
123         guint font_size;
124         GtkWidget *label_widget;
125         GtkWidget *value_widget;
126 };
127
128 struct gps_status_info gps_table[] = {
129         {"Status:", "No Data", gps_get_status, 14, NULL, NULL},
130 //      {"Online:", "No Data", gps_get_online, 14, NULL, NULL},
131         {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL},
132         {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
133         {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
134         {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL},
135         {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL},
136 };
137
138 /* Find a readable file in a colon delimeted path */
139 static gchar *find_path(const gchar *path, const gchar *filename)
140 {
141         gchar *end_ptr, *fullpath;
142
143         end_ptr = (gchar *)path;
144
145         /* find first : */
146         while (*end_ptr != ':' && *end_ptr != '\0')
147                 end_ptr++;
148         fullpath = g_strdup_printf("%.*s/%s", (int)(end_ptr-path), path,
149                                filename);
150         g_debug("GritsPluginGps: find_path - searching %s", fullpath);
151         if (access(fullpath, R_OK) == 0) {
152                 g_debug("GritsPluginGps: find_path - found %s", fullpath);
153                 return fullpath;        /* caller frees */
154         }
155
156         g_free(fullpath);
157         /* recurse */
158         if (*end_ptr == '\0') {
159                 return NULL;
160         } else {
161                 return find_path(end_ptr + 1, filename);
162         }
163 }
164
165 static gboolean gps_data_is_valid(struct gps_data_t *gps_data)
166 {
167         if (gps_data != NULL && gps_data->online != -1.0 &&
168                 gps_data->fix.mode >= MODE_2D &&
169                 gps_data->status > STATUS_NO_FIX) {
170                 return TRUE;
171         }
172
173         return FALSE;
174 }
175
176 static gchar *gps_get_date_string(double gps_time)
177 {
178         static gchar    buf[256];
179         time_t int_time = (time_t)gps_time;
180         struct tm       tm_time;
181
182         gmtime_r(&int_time, &tm_time);
183
184         snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
185                  tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
186
187         return buf;
188 }
189
190 static gchar *gps_get_time_string(time_t gps_time)
191 {
192         static gchar buf[256];
193         time_t int_time = (time_t)gps_time;
194         struct tm tm_time;
195
196         gmtime_r(&int_time, &tm_time);
197
198         snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
199                  tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
200
201         return buf;
202 }
203
204
205 static void update_gps_status(GritsPluginGps *gps)
206 {
207         struct gps_data_t *gps_data = &gps->gps_data;
208
209         /* gps table update */
210         gint i;
211         gchar *str;
212         for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
213                 gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget),
214                             (str = gps_table[i].get_data(gps_data)));
215                 g_free(str);
216         }
217 }
218
219 static void gps_init_control_frame(GritsPluginGps *gps, GtkWidget *gbox)
220 {
221         /* Control checkboxes */
222         GtkWidget *gps_control_frame = gtk_frame_new("GPS Control");
223         GtkWidget *cbox = gtk_vbox_new(FALSE, 2);
224         gtk_container_add(GTK_CONTAINER(gps_control_frame), cbox);
225         gtk_box_pack_start(GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
226
227         gps->ui.gps_follow_checkbox =
228                       gtk_check_button_new_with_label("Follow GPS");
229         g_signal_connect(G_OBJECT(gps->ui.gps_follow_checkbox), "clicked",
230                       G_CALLBACK (on_gps_follow_clicked_event),
231                       (gpointer)gps);
232         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_follow_checkbox,
233                        FALSE, FALSE, 0);
234
235         gps->ui.gps_track_checkbox =
236                        gtk_check_button_new_with_label("Record Track");
237         g_signal_connect(G_OBJECT(gps->ui.gps_track_checkbox), "clicked",
238                        G_CALLBACK (on_gps_track_enable_clicked_event),
239                        (gpointer)gps);
240         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_track_checkbox,
241                        FALSE, FALSE, 0);
242
243         gps->ui.gps_clear_button = gtk_button_new_with_label("Clear Track");
244         g_signal_connect(G_OBJECT(gps->ui.gps_clear_button), "clicked",
245                       G_CALLBACK (on_gps_track_clear_clicked_event),
246                       (gpointer)gps);
247         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_clear_button,
248                        FALSE, FALSE, 0);
249 }
250
251 static gboolean on_gps_track_enable_clicked_event(GtkWidget *widget,
252     gpointer user_data)
253 {
254         GritsPluginGps *gps = (GritsPluginGps *)user_data;
255
256         g_debug("GritsPluginGps: track_enable_clicked_event");
257
258         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
259                 /* start logging trip history */
260                 GPS_STATUS(gps, "Enabled GPS track.");
261                 gps->track.active = TRUE;
262         } else {
263                 /* stop logging trip history */
264                 GPS_STATUS(gps, "Disabled GPS track.");
265                 gps->track.active = FALSE;
266                 /* advance to the next track group, moving everything down if
267                  * it's full.
268                  */
269                 gps_track_group_incr(&gps->track);
270         }
271
272         return FALSE;
273 }
274
275 static gboolean on_gps_track_clear_clicked_event(GtkWidget *widget, gpointer user_data)
276 {
277         GritsPluginGps *gps = (GritsPluginGps *)user_data;
278
279         g_debug("GritsPluginGps: track_clear_clicked_event");
280         GPS_STATUS(gps, "Cleared GPS track.");
281         gps_track_clear(&gps->track);
282
283         return FALSE;
284 }
285
286 static gboolean on_gps_log_interval_changed_event(GtkWidget *widget,
287     gpointer user_data)
288 {
289         GritsPluginGps *gps = (GritsPluginGps *)user_data;
290
291         g_assert(gps);
292
293         g_debug("GritsPluginGps: log_interval_changed_event - value = %f",
294         gtk_range_get_value(GTK_RANGE(widget)));
295
296         if (gtk_toggle_button_get_active(
297                         GTK_TOGGLE_BUTTON(gps->ui.gps_log_checkbox))) {
298                 g_assert(gps->ui.gps_log_timeout_id != 0);
299
300                 /* disable old timeout */
301                 g_source_remove(gps->ui.gps_log_timeout_id);
302                 gps->ui.gps_log_timeout_id = 0;
303
304                 /* Schedule new log file write */
305                 gps->ui.gps_log_timeout_id = g_timeout_add(
306                          gtk_range_get_value(GTK_RANGE(widget))*1000,
307                          gps_write_log, gps);
308                 gps_write_log(gps);
309         }
310
311         return FALSE;
312 }
313
314 static gboolean gps_write_log(gpointer data)
315 {
316         GritsPluginGps *gps = (GritsPluginGps *)data;
317         struct gps_data_t *gps_data = &gps->gps_data;
318         gchar buf[256];
319         gchar filename[256];
320         gint fd;
321         gboolean new_file = FALSE;
322
323         if (gps_data == NULL) {
324                 g_warning("Skipped write to GPS log file: "
325                           "can not get GPS coordinates.");
326                 GPS_STATUS(gps, "Skipped write to GPS log file: "
327                           "can not get GPS coordinates.");
328                 return TRUE;
329         }
330
331         /* get filename from text entry box.  If empty, generate a name from
332          * the date and time and set it.
333          */
334         if (strlen(gtk_entry_get_text(
335                       GTK_ENTRY(gps->ui.gps_log_filename_entry))) == 0) {
336                 snprintf(filename, sizeof(filename),
337                             "%sT%s.%s",
338                             gps_get_date_string(gps->gps_data.fix.time),
339                             gps_get_time_string(gps->gps_data.fix.time),
340                             GPS_LOG_EXT);
341                 gtk_entry_set_text(GTK_ENTRY(gps->ui.gps_log_filename_entry),
342                             filename);
343         }
344
345         strncpy(filename,
346             gtk_entry_get_text(GTK_ENTRY(gps->ui.gps_log_filename_entry)),
347             sizeof (filename));
348
349         if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
350                 new_file = TRUE;
351         }
352
353         if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
354                 g_warning("Error opening log file %s: %s",
355                                 filename, strerror(errno));
356                 return FALSE;
357         }
358
359         if (new_file) {
360                 /* write header and reset record counter */
361                 snprintf(buf, sizeof(buf),
362                         "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
363                 if (write(fd, buf, strlen(buf)) == -1) {
364                     g_warning("Error writing header to log file %s: %s",
365                                     filename, strerror(errno));
366                 }
367         gps->ui.gps_log_number = 1;
368         }
369
370         /* Write log entry.  Make sure this matches the header */
371         /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
372         /* RTR values: T=time, B=button push, S=speed, D=distance */
373         snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
374         gps->ui.gps_log_number++,
375         gps_get_date_string(gps->gps_data.fix.time),
376         gps_get_time_string(gps->gps_data.fix.time),
377 //      gps_data->fix.time,
378         gps_data->fix.latitude,
379         gps_data->fix.longitude,
380         gps_data->fix.altitude * METERS_TO_FEET,
381         gps_data->fix.track,
382         gps_data->fix.speed * METERS_TO_FEET,
383         'T'); /* position due to timer expired  */
384
385         if (write(fd, buf, strlen(buf)) == -1) {
386                 g_warning("Could not write log number %d to log file %s: %s",
387                     gps->ui.gps_log_number-1, filename, strerror(errno));
388         }
389         close(fd);
390
391         GPS_STATUS(gps, "Updated GPS log file %s.", filename);
392
393         /* reschedule */
394         return TRUE;
395 }
396
397 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
398 {
399         GritsPluginGps *gps = (GritsPluginGps *)user_data;
400
401         g_debug("GritsPluginGps: follow_clicked_event - button status %d",
402                 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
403         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
404                 gps->follow_gps = TRUE;
405         } else {
406                 gps->follow_gps = FALSE;
407         }
408
409         return FALSE;
410 }
411
412
413 static void gps_init_track_log_frame(GritsPluginGps *gps, GtkWidget *gbox)
414 {
415         /* Track log box with enable checkbox and filename entry */
416         GtkWidget *gps_log_frame = gtk_frame_new ("Track Log");
417         GtkWidget *lbox = gtk_vbox_new (FALSE, 2);
418         gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox);
419         gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame,
420                         FALSE, FALSE, 0);
421
422         gps->ui.gps_log_checkbox =
423                gtk_check_button_new_with_label("Log Position to File");
424         g_signal_connect (G_OBJECT (gps->ui.gps_log_checkbox), "clicked",
425                G_CALLBACK (on_gps_log_clicked_event),
426                (gpointer)gps);
427         gtk_box_pack_start (GTK_BOX(lbox), gps->ui.gps_log_checkbox,
428                FALSE, FALSE, 0);
429
430         /* Set up filename entry box */
431         GtkWidget *fbox = gtk_hbox_new (FALSE, 2);
432         GtkWidget *filename_label = gtk_label_new ("Filename:");
433         gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
434         gps->ui.gps_log_filename_entry = gtk_entry_new();
435         gtk_box_pack_start (GTK_BOX(fbox), gps->ui.gps_log_filename_entry,
436                TRUE, TRUE, 0);
437         gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
438
439         /* set up gps log interval slider */
440         GtkWidget *ubox = gtk_hbox_new (FALSE, 4);
441         GtkWidget *interval_label = gtk_label_new ("Update Interval:");
442         gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
443         gps->ui.gps_log_interval_slider =
444                     gtk_hscale_new_with_range(1.0, 600.0, 30.0);
445         gtk_range_set_value (GTK_RANGE(gps->ui.gps_log_interval_slider),
446                     GPS_LOG_DEFAULT_UPDATE_INTERVAL);
447         g_signal_connect (G_OBJECT (gps->ui.gps_log_interval_slider),
448                     "value-changed",
449                     G_CALLBACK(on_gps_log_interval_changed_event),
450                     (gpointer)gps);
451         gtk_range_set_increments(
452                     GTK_RANGE(gps->ui.gps_log_interval_slider),
453                     10.0 /* step */, 30.0 /* page up/down */);
454         gtk_range_set_update_policy(
455                     GTK_RANGE(gps->ui.gps_log_interval_slider),
456                     GTK_UPDATE_DELAYED);
457         gtk_box_pack_start (GTK_BOX(ubox), gps->ui.gps_log_interval_slider,
458                     TRUE, TRUE, 0);
459         gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
460 }
461
462 static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
463 {
464         GritsPluginGps *gps = (GritsPluginGps *)user_data;
465
466         g_debug("GritsPluginGps: log_clicked_event");
467
468         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
469                 gps_write_log(gps);
470
471                 /* Schedule log file write */
472                 gps->ui.gps_log_timeout_id = g_timeout_add(
473                       gtk_range_get_value(
474                         GTK_RANGE(gps->ui.gps_log_interval_slider))*1000,
475                         gps_write_log, gps);
476         } else {
477                 /* button unchecked */
478                 g_source_remove(gps->ui.gps_log_timeout_id);
479                 gps->ui.gps_log_timeout_id = 0;
480                 g_debug("GritsPluginGps: log_clicked_event - closed log file.");
481         }
482
483         return FALSE;
484 }
485
486
487 static void gps_init_status_info(GritsPluginGps *gps, GtkWidget *gbox)
488 {
489         gps->ui.gps_status_frame = gtk_frame_new("GPS Data");
490         gps->ui.gps_status_table = gtk_table_new(5, 2, TRUE);
491         gtk_container_add(GTK_CONTAINER (gps->ui.gps_status_frame),
492                    gps->ui.gps_status_table);
493
494         /* gps data table setup */
495         gint i;
496         for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
497                 gps_table[i].label_widget = gtk_label_new (gps_table[i].label);
498                 gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget),
499                                       GTK_JUSTIFY_LEFT);
500                 gtk_table_attach(GTK_TABLE(gps->ui.gps_status_table),
501                                            gps_table[i].label_widget,
502                                            0, 1, i, i+1, 0, 0, 0, 0);
503                 gps_table[i].value_widget = gtk_label_new(gps_table[i].initial_val);
504                 gtk_table_attach( GTK_TABLE(gps->ui.gps_status_table),
505                                 gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0);
506
507                 PangoFontDescription *font_desc = pango_font_description_new ();
508                 pango_font_description_set_size (font_desc,
509                                 gps_table[i].font_size*PANGO_SCALE);
510                 gtk_widget_modify_font (gps_table[i].label_widget, font_desc);
511                 gtk_widget_modify_font (gps_table[i].value_widget, font_desc);
512                 pango_font_description_free (font_desc);
513         }
514         gtk_box_pack_start(GTK_BOX(gbox), gps->ui.gps_status_frame,
515                             FALSE, FALSE, 0);
516
517         /* Start UI refresh task, which will reschedule itgps. */
518         gps_redraw_all(gps);
519         gps->gps_update_timeout_id = g_timeout_add(
520                     GPS_UPDATE_INTERVAL*1000,
521                     gps_redraw_all, gps);
522
523 }
524
525
526 #ifdef GPS_RANGE_RINGS
527 static void gps_init_range_rings(GritsPluginGps *gps, GtkWidget *gbox)
528 {
529         GtkWidget *gps_range_ring_frame = gtk_frame_new("Range Rings");
530         GtkWidget *cbox = gtk_vbox_new(FALSE, 2);
531         gtk_container_add(GTK_CONTAINER(gps_range_ring_frame), cbox);
532         gtk_box_pack_start(GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0);
533
534         gps->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings");
535         g_signal_connect(G_OBJECT(gps->ui.gps_rangering_checkbox),
536                       "clicked", G_CALLBACK(on_gps_rangering_clicked_event),
537                       (gpointer)gps);
538         gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_rangering_checkbox,
539                       FALSE, FALSE, 0);
540 }
541
542 static gboolean on_gps_rangering_clicked_event(GtkWidget *widget, gpointer user_data)
543 {
544         GritsPluginGps *gps = (GritsPluginGps *)user_data;
545
546         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))  {
547                 gps->gps_rangering_active = TRUE;
548         } else {
549                 gps->gps_rangering_active = FALSE;
550         }
551
552         /* XXX force a redraw */
553
554         return FALSE;
555 }
556 #endif /* GPS_RANGE_RINGS */
557
558 /* external interface to update UI from latest GPS data. */
559 gboolean gps_redraw_all(gpointer data)
560 {
561         GritsPluginGps *gps = (GritsPluginGps *)data;
562         g_assert(gps);
563
564         struct gps_data_t *gps_data = &gps->gps_data;
565
566         g_debug("GritsPluginGps: redraw_all");
567
568         g_assert(gps_data);
569         if (!gps_data_is_valid(gps_data)) {
570                 g_debug("GritsPluginGps: redraw_all - gps_data is not valid.");
571                 /* XXX Change marker to indicate data is not valid */
572                 return TRUE;
573         }
574
575         /* update position labels */
576         update_gps_status(gps);
577
578         /* Update track and marker position */
579         if (gps_data_is_valid(gps_data) && gps->track.active) {
580                 g_debug("GritsPluginGps: redraw_all - updating track group %u "
581                         "point %u at lat = %f, long = %f, track = %f",
582                         gps->track.cur_group,
583                         gps->track.cur_point,
584                         gps_data->fix.latitude,
585                         gps_data->fix.longitude,
586                         gps_data->fix.track);
587
588                 gps_track_add_point(&gps->track,
589                           gps_data->fix.latitude, gps_data->fix.longitude, 0.0);
590
591                 if (gps->track.line) {
592                         grits_viewer_remove(gps->viewer,
593                             GRITS_OBJECT(gps->track.line));
594                         gps->track.line = NULL;
595                 }
596
597                 gps->track.line = grits_line_new(gps->track.points);
598                 gps->track.line->color[0]  = 1.0;
599                 gps->track.line->color[1]  = 0;
600                 gps->track.line->color[2]  = 0.1;
601                 gps->track.line->color[3]  = 0.5;
602                 gps->track.line->width     = 3;
603
604                 grits_viewer_add(gps->viewer, GRITS_OBJECT(gps->track.line),
605                             GRITS_LEVEL_OVERLAY, TRUE);
606                 grits_object_queue_draw(GRITS_OBJECT(gps->track.line));
607         }
608
609         if (gps_data_is_valid(gps_data)) {
610                 if (gps->marker) {
611                         grits_viewer_remove(gps->viewer,
612                             GRITS_OBJECT(gps->marker));
613                         gps->marker = NULL;
614                 }
615
616         gchar *path = find_path(GPS_MARKER_ICON_PATH, GPS_MARKER_ICON);
617         if (path) {
618                 gps->marker = grits_marker_icon_new("GPS", path,
619                               gps_data->fix.track, TRUE, MARKER_DMASK_ICON);
620                 g_free(path);
621         } else {
622                 /* if icon not found just use a point */
623                 g_warning("Could not find GPS marker icon %s in path %s.",
624                           GPS_MARKER_ICON, GPS_MARKER_ICON_PATH);
625                 gps->marker = grits_marker_icon_new("GPS", NULL,
626                               gps_data->fix.track, FALSE,
627                               MARKER_DMASK_POINT|MARKER_DMASK_LABEL);
628         }
629                 
630         GRITS_OBJECT(gps->marker)->center.lat  = gps_data->fix.latitude;
631         GRITS_OBJECT(gps->marker)->center.lon  = gps_data->fix.longitude;
632         GRITS_OBJECT(gps->marker)->center.elev = 0.0;
633         GRITS_OBJECT(gps->marker)->lod         = EARTH_R;
634
635                 grits_viewer_add(gps->viewer,
636                         GRITS_OBJECT(gps->marker),
637                         GRITS_LEVEL_OVERLAY, TRUE);
638                 grits_object_queue_draw(GRITS_OBJECT(gps->marker));
639         }
640
641         if (gps->follow_gps && gps_data_is_valid(gps_data)) {
642                 /* Center map at current GPS position. */
643                 g_debug("GritsPluginGps: redraw_all - centering map at "
644                         "lat = %f, long = %f, track = %f",
645                             gps_data->fix.latitude,
646                             gps_data->fix.longitude,
647                             gps_data->fix.track);
648
649                 double lat, lon, elev;
650                 grits_viewer_get_location(gps->viewer, &lat, &lon, &elev);
651                 grits_viewer_set_location(gps->viewer, gps_data->fix.latitude,
652                                           gps_data->fix.longitude, elev);
653                 //grits_viewer_set_rotation(gps->viewer, 0, 0, 0);
654         }
655
656         /* reschedule */
657         return TRUE;
658 }
659
660
661 /******************* Track handling routines *****************/
662
663 static void gps_track_init(struct gps_track_t *track)
664 {
665         /* Save a spot at the end for the NULL termination */
666         track->points = (gpointer)g_new0(double*, NUM_TRACK_GROUPS + 1);
667         track->cur_point  = 0;
668         track->cur_group  = 0;
669         track->num_points = 1;  /* starts at 1 so realloc logic works */
670         track->line = NULL;
671 }
672
673 static void gps_track_clear(struct gps_track_t *track)
674 {
675         gint pi;
676         for (pi = 0; pi < NUM_TRACK_GROUPS; pi++) {
677                 if (track->points[pi] != NULL) {
678                         g_free(track->points[pi]);
679                         track->points[pi] = NULL;
680                 }
681         }
682         track->cur_point  = 0;
683         track->cur_group  = 0;
684         track->num_points = 1;  /* starts at 1 so realloc logic works */
685 }
686
687 static void gps_track_free(struct gps_track_t *track)
688 {
689         gps_track_clear(track);
690         g_free(track->points);
691 }
692
693 /* add a new track group (points in a track group are connected, and
694  * separated from points in other track groups).
695  */
696 static void gps_track_group_incr(struct gps_track_t *track)
697 {
698         gdouble (**points)[3] = track->points; /* for simplicity */
699
700         /* Just return if they increment it again before any points have
701          * been added.
702          */
703         if (points[track->cur_group] == NULL) {
704                 return;
705         }
706
707         g_debug("GritsPluginGps: track_group_incr - track group %u->%u.",
708                 track->cur_group, track->cur_group + 1);
709
710         track->cur_group++;
711         track->cur_point  = 0;
712         track->num_points = 1;  /* starts at 1 so realloc logic works */
713
714         if (track->cur_group >= NUM_TRACK_GROUPS) {
715                 g_debug("GritsPluginGps: track_group_incr - track group %u "
716                         "is at max %u, shifting groups",
717                         track->cur_group, NUM_TRACK_GROUPS);
718
719                 /* Free the oldest one which falls off the end */
720                 g_free(points[0]);
721
722                 /* shift the rest down, last one should be NULL already */
723                 /* note we alloc NUM_TRACK_GROUPS+1 */
724                 for (int pi = 0; pi < NUM_TRACK_GROUPS; pi++) {
725                         points[pi] = points[pi+1];
726                 }
727
728                 /* always write into the last group */
729                 track->cur_group = NUM_TRACK_GROUPS - 1;
730         }
731 }
732
733 static void gps_track_add_point(struct gps_track_t *track,
734     gdouble lat, gdouble lon, gdouble elevation)
735 {
736         gdouble (**points)[3] = track->points; /* for simplicity */
737
738         g_debug("GritsPluginGps: track_add_point");
739
740         g_assert(track->cur_group < NUM_TRACK_GROUPS &&
741                 (track->cur_point <= track->num_points));
742
743         /* resize/allocate the point group if the current one is full */
744         if (track->cur_point >= track->num_points - 1) {
745                 guint new_size = track->num_points == 1 ?
746                             NUM_TRACK_POINTS :
747                             track->num_points * NUM_TRACK_POINTS_FACTOR;
748                 g_debug("GritsPluginGps: track_add_point - reallocating points "
749                         "array from %u points to %u points.\n",
750                         track->num_points, new_size);
751                 points[track->cur_group] = (gpointer)g_renew(gdouble,
752                             points[track->cur_group], 3*(new_size+1));
753                 track->num_points = new_size;
754         }
755
756         g_assert(points[track->cur_group] != NULL);
757
758         /* Add the coordinate */
759         lle2xyz(lat, lon, elevation,
760             &points[track->cur_group][track->cur_point][0],
761             &points[track->cur_group][track->cur_point][1],
762             &points[track->cur_group][track->cur_point][2]);
763
764         track->cur_point++;
765
766         /* make sure last point is always 0s so the line drawing stops. */
767         points[track->cur_group][track->cur_point][0] = 0.0;
768         points[track->cur_group][track->cur_point][1] = 0.0;
769         points[track->cur_group][track->cur_point][2] = 0.0;
770 }
771
772
773 static gchar *gps_get_status(struct gps_data_t *gps_data)
774 {
775         gchar *status_color;
776         gchar *status_text;
777
778         switch (gps_data->fix.mode) {
779         case MODE_NOT_SEEN:
780                 status_color = "red";
781                 status_text = "No Signal";
782                 break;
783         case MODE_NO_FIX:
784                 status_color = "red";
785                 status_text = "No Fix";
786                 break;
787         case MODE_2D:
788                 status_color = "yellow";
789                 status_text = "2D Mode";
790                 break;
791         case MODE_3D:
792                 status_color = "green";
793                 status_text = "3D Mode";
794                 break;
795         default:
796                 status_color = "black";
797                 status_text = "Unknown";
798                 break;
799         }
800         return g_strdup_printf("<span foreground=\"%s\">%s</span>",
801                                 status_color, status_text);
802 }
803
804 #if 0
805 static gchar *gps_get_online(struct gps_data_t *);
806
807 static gchar *gps_get_online(struct gps_data_t *gps_data)
808 {
809         gchar *status_str;
810         gchar *online_str;
811
812         if (gps_data->online == -1.0) {
813                 online_str = "Offline";
814         } else {
815                 online_str = "Online";
816         }
817
818         switch (gps_data->status) {
819         case 0:
820                 status_str = "No Fix";
821                 break;
822         case 1:
823                 status_str = "Fix Acquired";
824                 break;
825         case 2:
826                 status_str = "DGPS Fix";
827                 break;
828         default:
829                 status_str = "Unknown Status";
830                 break;
831         }
832
833         return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
834 }
835 #endif
836
837
838
839 static gchar *gps_get_latitude(struct gps_data_t *gps_data)
840 {
841         return g_strdup_printf("%3.4f", gps_data->fix.latitude);
842 }
843
844 static gchar *gps_get_longitude(struct gps_data_t *gps_data)
845 {
846         return g_strdup_printf("%3.4f", gps_data->fix.longitude);
847 }
848
849 static gchar *gps_get_elevation(struct gps_data_t *gps_data)
850 {
851         /* XXX Make units (m/ft) settable */
852         return g_strdup_printf("%.1lf %s",
853                     (gps_data->fix.altitude * METERS_TO_FEET), "ft");
854 }
855
856 static gchar *gps_get_heading(struct gps_data_t *gps_data)
857 {
858         /* XXX Make units (m/ft) settable */
859         return g_strdup_printf("%03.0lf", gps_data->fix.track);
860 }
861
862 static gchar *gps_get_speed(struct gps_data_t *gps_data)
863 {
864         /* XXX Make units (m/ft) settable */
865         return g_strdup_printf("%1.1f %s",
866                 (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
867 }
868
869
870 static gint initialize_gpsd(char *server, gchar *port,
871         struct gps_data_t *gps_data)
872 {
873 #if GPSD_API_MAJOR_VERSION < 5
874 #error "GPSD protocol version 5 or greater required."
875 #endif
876         gint result;
877
878         if ((result = gps_open(server, port, gps_data)) != 0) {
879                 g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
880                 server, port, result, errno, gps_errstr(errno));
881         } else {
882                 (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
883                 g_debug("GritsPluginGps: initialize_gpsd - gpsd fd %u.",
884                         gps_data->gps_fd);
885                 gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
886         }
887
888         return result;
889 }
890
891 static void process_gps(gpointer data, gint source, GdkInputCondition condition)
892 {
893         struct gps_data_t *gps_data = (struct gps_data_t *)data;
894
895         g_debug("GritsPluginGps: process_gps");
896
897         /* Process any data from the gps and call the hook function */
898         if (gps_data != NULL) {
899                 gint result = gps_read(gps_data);
900                 g_debug("GritsPluginGps: process_gps - gps_read returned %d, "
901                         "position %f, %f.", result,
902                         gps_data->fix.latitude, gps_data->fix.longitude);
903         } else {
904                 g_warning("GritsPluginGps: process_gps - gps_data == NULL.");
905         }
906 }
907
908 /************************** GPS Object Methods *************************/
909
910 /* Methods */
911 GritsPluginGps *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
912 {
913         /* TODO: move to constructor if possible */
914         g_debug("GritsPluginGps: new");
915         GritsPluginGps *gps = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
916         gps->viewer = viewer;
917         gps->prefs  = prefs;
918
919         initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &gps->gps_data);
920         gps->follow_gps = FALSE;
921         
922         gps_track_init(&gps->track);
923         gps_init_status_info(gps, gps->hbox);
924         gps_init_control_frame(gps, gps->hbox);
925         gps_init_track_log_frame(gps, gps->hbox);
926 #ifdef GPS_RANGE_RINGS
927         gps_init_range_rings(gps, gps->hbox);
928 #endif
929
930         return gps;
931 }
932
933
934 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_gps)
935 {
936         GritsPluginGps *gps = GRITS_PLUGIN_GPS(_gps);
937         return gps->config;
938 }
939
940 /* GObject code */
941 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
942 G_DEFINE_TYPE_WITH_CODE(GritsPluginGps, grits_plugin_gps, G_TYPE_OBJECT,
943                 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
944                         grits_plugin_gps_plugin_init));
945
946 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
947 {
948         g_debug("GritsPluginGps: plugin_init");
949         /* Add methods to the interface */
950         iface->get_config = grits_plugin_gps_get_config;
951 }
952
953 static void grits_plugin_gps_init(GritsPluginGps *gps)
954 {
955         g_debug("GritsPluginGps: gps_init");
956
957         gps->config     = gtk_notebook_new();
958
959         gps->hbox = gtk_hbox_new(FALSE, 2);
960         gtk_notebook_insert_page(GTK_NOTEBOOK(gps->config),
961                                 GTK_WIDGET(gps->hbox),
962                                 gtk_label_new("GPS"), 0);
963         /* Need to position on the top because of Win32 bug */
964         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gps->config), GTK_POS_LEFT);
965 }
966
967 static void grits_plugin_gps_dispose(GObject *gobject)
968 {
969         GritsPluginGps *gps = GRITS_PLUGIN_GPS(gobject);
970
971         g_debug("GritsPluginGps: dispose");
972
973         if (gps->viewer) {
974                 if (gps->marker) {
975                         grits_viewer_remove(gps->viewer,
976                                GRITS_OBJECT(gps->marker));
977                 }
978                 g_object_unref(gps->viewer);
979                 gps->viewer = NULL;
980         }
981
982         gps_track_free(&gps->track);
983
984         /* Drop references */
985         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
986 }
987
988 static void grits_plugin_gps_finalize(GObject *gobject)
989 {
990         GritsPluginGps *gps = GRITS_PLUGIN_GPS(gobject);
991
992         g_debug("GritsPluginGps: finalize");
993
994         /* Free data */
995         gtk_widget_destroy(gps->config);
996         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
997 }
998
999 static void grits_plugin_gps_class_init(GritsPluginGpsClass *klass)
1000 {
1001         g_debug("GritsPluginGps: class_init");
1002         GObjectClass *gobject_class = (GObjectClass*)klass;
1003         gobject_class->dispose  = grits_plugin_gps_dispose;
1004         gobject_class->finalize = grits_plugin_gps_finalize;
1005 }