]> Pileus Git - aweather/blob - src/plugins/gps-plugin.c
Initial checkin of prototype gps plugin support.
[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 #define _XOPEN_SOURCE
19 #include <stdio.h>
20 #include <fcntl.h>
21 #include <errno.h>
22 #include <time.h>
23 #include <config.h>
24 #include <assert.h>
25 #include <string.h>
26 #include <glib/gstdio.h>
27 #include <gtk/gtk.h>
28 #include <gio/gio.h>
29 #include <math.h>
30
31 #include <grits.h>
32 #include <gps.h>
33
34 #include "gps-plugin.h"
35 #include "level2.h"
36 #include "../aweather-location.h"
37
38 /* interval to update map with new gps data in seconds. */
39 #define GPS_UPDATE_INTERVAL (2)
40
41 /* interval to update log file in seconds (default value for slider) */
42 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
43 #define GPS_LOG_EXT "csv"
44
45 /* interval to update SpotterNetwork in seconds.  Should not be less
46  * than 3 minutes per SpotterNetwork policy.
47  */
48 #define GPS_SN_UPDATE_INTERVAL (5 * 60)
49 #define GPS_SN_URI "http://www.spotternetwork.org/update.php"
50
51 /* For updating the status bar conveniently */
52 #define GPS_STATUSBAR_CONTEXT "GPS"
53 #if 0
54 #define GPS_STATUS(gps_state, format, args...) \
55     do { \
56         char *buf = g_strdup_printf(format, ##args); \
57         gtk_statusbar_push(GTK_STATUSBAR(gps_state->status_bar), \
58             gtk_statusbar_get_context_id( \
59                 GTK_STATUSBAR(gps_state->status_bar), \
60                 GPS_STATUSBAR_CONTEXT), \
61             buf); \
62     } while (0)
63 #endif
64
65 #define GPS_STATUS(gps_state, format, args...) \
66     do { \
67         char *buf = g_strdup_printf(format, ##args); \
68         g_warning("STATUS: %s", buf); \
69     } while (0)
70
71
72 static gboolean gps_data_is_valid(struct gps_data_t *gps_data);
73 static char *gps_get_time_string(time_t gps_time);
74 static char *gps_get_date_string(double gps_time);
75 static void process_gps( gpointer, gint, GdkInputCondition);
76
77 /* SpotterNetwork support */
78 static void gps_init_spotter_network_frame(GritsPluginGPS *gps_state,
79             GtkWidget *gbox);
80 static gboolean gps_update_sn(gpointer data);
81 static void gps_sn_update_complete(SoupSession *, SoupMessage *, gpointer);
82 static gboolean on_gps_sn_clicked_event (GtkWidget *widget, gpointer user_data);
83 static gboolean on_gps_sn_active_clicked_event (GtkWidget *widget, gpointer user_data);
84
85 static void gps_init_range_rings(GritsPluginGPS *gps_state,
86             GtkWidget *gbox);
87 static gboolean on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data);
88
89 static void gps_init_status_info(GritsPluginGPS *gps_state,
90             GtkWidget *gbox);
91 static void gps_init_control_frame(GritsPluginGPS *gps_state,
92             GtkWidget *gbox);
93 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data);
94
95 /* GPS logging support */
96 static void gps_init_track_log_frame(GritsPluginGPS *gps_state,
97             GtkWidget *gbox);
98 static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data);
99 static gboolean on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data);
100 static gboolean gps_write_log(gpointer data);
101
102 static char *gps_get_status(struct gps_data_t *);
103 static char *gps_get_latitude(struct gps_data_t *);
104 static char *gps_get_longitude(struct gps_data_t *);
105 static char *gps_get_elevation(struct gps_data_t *);
106 static char *gps_get_heading(struct gps_data_t *);
107 static char *gps_get_speed(struct gps_data_t *);
108
109 /* Describes a line in the gps table */
110 struct gps_status_info {
111     char *label;
112     char *initial_val;
113     char *(*get_data)(struct gps_data_t *);
114     unsigned int font_size;
115     GtkWidget *label_widget;
116     GtkWidget *value_widget;
117 };
118
119 struct gps_status_info gps_table[] = {
120     {"Status:", "No Data", gps_get_status, 14, NULL, NULL},
121 //    {"Online:", "No Data", gps_get_online, 14, NULL, NULL},
122     {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL},
123     {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
124     {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
125     {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL},
126     {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL},
127 };
128
129 static void _gtk_bin_set_child(GtkBin *bin, GtkWidget *new)
130 {
131         GtkWidget *old = gtk_bin_get_child(bin);
132         if (old)
133                 gtk_widget_destroy(old);
134         gtk_container_add(GTK_CONTAINER(bin), new);
135         gtk_widget_show_all(new);
136 }
137
138 static
139 gboolean gps_data_is_valid(struct gps_data_t *gps_data)
140 {
141     if (gps_data != NULL && gps_data->online != -1.0 &&
142         gps_data->fix.mode >= MODE_2D &&
143         gps_data->status > STATUS_NO_FIX) {
144         return TRUE;
145     }
146
147     return FALSE;
148 }
149
150 static char *
151 gps_get_date_string(double gps_time)
152 {
153     static char buf[256];
154     time_t      int_time = (time_t)gps_time;
155     struct tm   tm_time;
156
157     gmtime_r(&int_time, &tm_time);
158
159     snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
160                  tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
161
162
163     return buf;
164 }
165
166 static char *
167 gps_get_time_string(time_t gps_time)
168 {
169     static char buf[256];
170     time_t      int_time = (time_t)gps_time;
171     struct tm   tm_time;
172
173     gmtime_r(&int_time, &tm_time);
174
175     snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
176                  tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
177
178     return buf;
179 }
180
181
182 static void
183 update_gps_status(GritsPluginGPS *gps_state)
184 {
185     struct gps_data_t *gps_data = gps_state->gps_data;
186
187     /* gps table update */
188     int i;
189     gchar *str;
190     for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
191         gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget),
192                 (str = gps_table[i].get_data(gps_data)));
193         g_free(str);
194     }
195 }
196
197 static void
198 gps_init_control_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
199 {
200     /* Control checkboxes */
201     GtkWidget *gps_control_frame = gtk_frame_new ("GPS Control");
202     GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
203     gtk_container_add (GTK_CONTAINER (gps_control_frame), cbox);
204     gtk_box_pack_start (GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
205
206     gps_state->ui.gps_follow_checkbox = gtk_check_button_new_with_label("Follow GPS");
207     g_signal_connect (G_OBJECT (gps_state->ui.gps_follow_checkbox), "clicked",
208                       G_CALLBACK (on_gps_follow_clicked_event),
209                       (gpointer)gps_state);
210     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_follow_checkbox,
211                         FALSE, FALSE, 0);
212     gps_state->ui.gps_track_checkbox = gtk_check_button_new_with_label("Show Track");
213     g_signal_connect (G_OBJECT (gps_state->ui.gps_track_checkbox), "clicked",
214                       G_CALLBACK (on_gps_track_clicked_event),
215                       (gpointer)gps_state);
216     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_track_checkbox,
217                         FALSE, FALSE, 0);
218 }
219
220 static gboolean
221 on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data)
222 {
223     g_debug("on_gps_track_clicked_event called!");
224     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
225         /* XXX start logging trip history */
226         GPS_STATUS(gps_state, "Enabled GPS track.");
227     } else {
228         /* XXX stop logging trip history */
229         GPS_STATUS(gps_state, "Disabled GPS track.");
230     }
231
232     return FALSE;
233 }
234
235 static gboolean
236 on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data)
237 {
238     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
239
240     assert(gps_state);
241
242     g_debug("gps interval changed, value = %f",
243         gtk_range_get_value(GTK_RANGE(widget)));
244
245     if (gtk_toggle_button_get_active(
246                         GTK_TOGGLE_BUTTON(gps_state->ui.gps_log_checkbox))) {
247         assert(gps_state->ui.gps_log_timeout_id != 0);
248
249         /* disable old timeout */
250         g_source_remove(gps_state->ui.gps_log_timeout_id);
251         gps_state->ui.gps_log_timeout_id = 0;
252
253         /* Schedule new log file write */
254         gps_state->ui.gps_log_timeout_id = g_timeout_add(
255                                 gtk_range_get_value(GTK_RANGE(widget))*1000,
256                                 gps_write_log, gps_state);
257         gps_write_log(gps_state);
258     }
259
260     return FALSE;
261 }
262
263 static gboolean
264 gps_write_log(gpointer data)
265 {
266     GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
267     struct gps_data_t *gps_data = gps_state->gps_data;
268     char buf[256];
269     char filename[256];
270     int fd;
271     gboolean new_file = FALSE;
272
273     if (gps_data == NULL) {
274         g_warning("Skipped write to GPS log file: "
275                 "can not get GPS coordinates.");
276         GPS_STATUS(gps_state, "Skipped write to GPS log file: "
277                 "can not get GPS coordinates.");
278         return TRUE;
279     }
280
281     /* get filename from text entry box.  If empty, generate a name from
282      * the date and time and set it.
283      */
284     if (strlen(gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)))
285                                                                     == 0) {
286         snprintf(filename, sizeof(filename),
287                             "%sT%s.%s",
288                             gps_get_date_string(gps_state->gps_data->fix.time),
289                             gps_get_time_string(gps_state->gps_data->fix.time),
290                             GPS_LOG_EXT);
291         gtk_entry_set_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry),
292                             filename);
293     }
294
295     strncpy(filename,
296             gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)),
297             sizeof (filename));
298
299     if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
300         new_file = TRUE;
301     }
302
303     if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
304         g_warning("Error opening log file %s: %s",
305                         filename, strerror(errno));
306         return FALSE;
307     }
308
309     if (new_file) {
310         /* write header and reset record counter */
311         snprintf(buf, sizeof(buf),
312                 "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
313         if (write(fd, buf, strlen(buf)) == -1) {
314             g_warning("Error writing header to log file %s: %s",
315                             filename, strerror(errno));
316         }
317         gps_state->ui.gps_log_number = 1;
318     }
319
320     /* Write log entry.  Make sure this matches the header */
321     /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
322     /* RTR values: T=time, B=button push, S=speed, D=distance */
323     snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
324         gps_state->ui.gps_log_number++,
325         gps_get_date_string(gps_state->gps_data->fix.time),
326         gps_get_time_string(gps_state->gps_data->fix.time),
327 //      gps_data->fix.time,
328         gps_data->fix.latitude,
329         gps_data->fix.longitude,
330         gps_data->fix.altitude * METERS_TO_FEET,
331         gps_data->fix.track,
332         gps_data->fix.speed * METERS_TO_FEET,
333         'T'); /* position due to timer expired  */
334
335     if (write(fd, buf, strlen(buf)) == -1) {
336         g_warning("Could not write log number %d to log file %s: %s",
337             gps_state->ui.gps_log_number-1, filename, strerror(errno));
338     }
339     close(fd);
340
341     GPS_STATUS(gps_state, "Updated GPS log file %s.", filename);
342
343     /* reschedule */
344     return TRUE;
345 }
346
347 static gboolean
348 on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
349 {
350     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
351
352     g_debug("on_gps_follow_clicked_event called!, button status %d",
353         gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
354     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
355         gps_state->follow_gps = TRUE;
356     else
357         gps_state->follow_gps = FALSE;
358
359     return FALSE;
360 }
361
362
363 static void
364 gps_init_track_log_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
365 {
366     /* Track log box with enable checkbox and filename entry */
367     GtkWidget *gps_log_frame = gtk_frame_new ("Track Log");
368     GtkWidget *lbox = gtk_vbox_new (FALSE, 2);
369     gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox);
370     gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame,
371                         FALSE, FALSE, 0);
372
373     gps_state->ui.gps_log_checkbox = gtk_check_button_new_with_label("Log Position to File");
374     g_signal_connect (G_OBJECT (gps_state->ui.gps_log_checkbox), "clicked",
375                       G_CALLBACK (on_gps_log_clicked_event),
376                       (gpointer)gps_state);
377     gtk_box_pack_start (GTK_BOX(lbox), gps_state->ui.gps_log_checkbox,
378                         FALSE, FALSE, 0);
379
380     /* Set up filename entry box */
381     GtkWidget *fbox = gtk_hbox_new (FALSE, 2);
382     GtkWidget *filename_label = gtk_label_new ("Filename:");
383     gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
384     gps_state->ui.gps_log_filename_entry = gtk_entry_new();
385     gtk_box_pack_start (GTK_BOX(fbox), gps_state->ui.gps_log_filename_entry,
386                         TRUE, TRUE, 0);
387     gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
388
389     /* set up gps log interval slider */
390     GtkWidget *ubox = gtk_hbox_new (FALSE, 4);
391     GtkWidget *interval_label = gtk_label_new ("Update Interval:");
392     gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
393     gps_state->ui.gps_log_interval_slider =
394                     gtk_hscale_new_with_range(1.0, 600.0, 30.0);
395     gtk_range_set_value (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
396                     GPS_LOG_DEFAULT_UPDATE_INTERVAL);
397     g_signal_connect (G_OBJECT (gps_state->ui.gps_log_interval_slider),
398                     "value-changed",
399                     G_CALLBACK(on_gps_log_interval_changed_event),
400                     (gpointer)gps_state);
401     gtk_range_set_increments (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
402                     10.0 /* step */, 30.0 /* page up/down */);
403     gtk_range_set_update_policy (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
404                     GTK_UPDATE_DELAYED);
405     gtk_box_pack_start (GTK_BOX(ubox), gps_state->ui.gps_log_interval_slider,
406                     TRUE, TRUE, 0);
407     gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
408 }
409
410 static gboolean
411 on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
412 {
413     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
414
415     g_debug("on_gps_log_clicked_event called!");
416
417     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
418         gps_write_log(gps_state);
419
420         /* Schedule log file write */
421         gps_state->ui.gps_log_timeout_id = g_timeout_add(
422                     gtk_range_get_value(
423                         GTK_RANGE(gps_state->ui.gps_log_interval_slider))*1000,
424                         gps_write_log, gps_state);
425     } else {
426         /* button unchecked */
427         g_source_remove(gps_state->ui.gps_log_timeout_id);
428         gps_state->ui.gps_log_timeout_id = 0;
429         g_debug("Closed log file.");
430     }
431
432     return FALSE;
433 }
434
435 static void
436 gps_init_spotter_network_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
437 {
438     /* Spotter network control box with enable checkbox, active
439      * checkbox, and sn ID entry
440      */
441     GtkWidget *gps_sn_frame = gtk_frame_new ("SpotterNetwork Control");
442     GtkWidget *sbox = gtk_vbox_new (FALSE, 2);
443     gtk_container_add (GTK_CONTAINER (gps_sn_frame), sbox);
444
445     /* create spotter network update checkbox */
446     gps_state->ui.gps_sn_checkbox = gtk_check_button_new_with_label("Update SpotterNetwork");
447     g_signal_connect (G_OBJECT (gps_state->ui.gps_sn_checkbox), "clicked",
448                       G_CALLBACK (on_gps_sn_clicked_event),
449                       (gpointer)gps_state);
450     gtk_box_pack_start (GTK_BOX(sbox), gps_state->ui.gps_sn_checkbox,
451                         FALSE, FALSE, 0);
452
453     /* Create spotter network active checkbox */
454     gps_state->ui.gps_sn_active_checkbox = gtk_check_button_new_with_label("Active");
455     g_signal_connect (G_OBJECT (gps_state->ui.gps_sn_active_checkbox),
456                         "clicked",
457                         G_CALLBACK (on_gps_sn_active_clicked_event),
458                         (gpointer)gps_state);
459     gtk_box_pack_start (GTK_BOX(sbox),
460                         gps_state->ui.gps_sn_active_checkbox,
461                         FALSE, FALSE, 0);
462
463     /* Create spotter network ID entry box and label */
464     GtkWidget *ebox = gtk_hbox_new (FALSE, 2);
465     GtkWidget *sn_label = gtk_label_new ("SpotterID:");
466     gtk_box_pack_start (GTK_BOX(ebox), sn_label, FALSE, FALSE, 0);
467     gps_state->ui.gps_sn_entry = gtk_entry_new();
468     gtk_box_pack_start (GTK_BOX(ebox), gps_state->ui.gps_sn_entry,
469                         FALSE, FALSE, 0);
470     gtk_box_pack_start (GTK_BOX(sbox), ebox, FALSE, FALSE, 0);
471
472     gtk_box_pack_start (GTK_BOX(gbox), gps_sn_frame, FALSE, FALSE, 0);
473 }
474
475 static gboolean
476 on_gps_sn_clicked_event (GtkWidget *widget, gpointer user_data)
477 {
478     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
479     g_debug("on_gps_sn_clicked_event called");
480
481     /* When pressed, log a position report to spotternetwork and
482      * schedule another one for the future.
483      */
484     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
485         /* Change naumber of concurrent connections option? */
486         assert(gps_state->ui.soup_session == NULL);
487         gps_state->ui.soup_session = soup_session_async_new_with_options(
488                          SOUP_SESSION_USER_AGENT,
489                          "Mozilla/5.0 (Windows; U; Windows NT 5.1; "
490                          "en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11",
491                          NULL);
492
493         /* XXX don't log right away to give the user a chance to cancel */
494         gps_update_sn(gps_state);
495         gps_state->ui.gps_sn_timeout_id = g_timeout_add(
496                         GPS_SN_UPDATE_INTERVAL*1000,
497                         gps_update_sn, gps_state);
498     } else {
499         /* stop any pending timer events */
500         g_source_remove(gps_state->ui.gps_sn_timeout_id);
501         gps_state->ui.gps_sn_timeout_id = 0;
502         soup_session_abort(gps_state->ui.soup_session);
503         g_object_unref(gps_state->ui.soup_session);
504         gps_state->ui.soup_session = NULL;
505     }
506
507     return FALSE;
508 }
509
510 static gboolean
511 on_gps_sn_active_clicked_event (GtkWidget *widget, gpointer user_data)
512 {
513     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
514
515     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
516         gps_state->ui.gps_sn_active = TRUE;
517     } else {
518         gps_state->ui.gps_sn_active = FALSE;
519     }
520
521     return FALSE;
522 }
523
524 /* Send position report to spotternetwork */
525 static gboolean
526 gps_update_sn(gpointer user_data)
527 {
528     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
529     struct gps_data_t *gps_data = gps_state->gps_data;
530     SoupMessage *msg;
531     char uri[256];
532
533     /* Make sure gps data is valid */
534     if (!gps_data_is_valid(gps_data)) {
535         g_warning("Skipping SpotterNetwork update, GPS data is invalid!");
536         GPS_STATUS(gps_state, "Skipped SpotterNetwork update, "
537             "GPS data is invalid!");
538         return TRUE;
539     }
540
541     /* Make sure SpotterNetwork ID is valid */
542     if (gtk_entry_get_text_length(GTK_ENTRY(gps_state->ui.gps_sn_entry)) == 0) {
543         g_warning("Skipping SpotterNetwork update, ID \"%s\" is invalid!",
544                 gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry)));
545         GPS_STATUS(gps_state, "Skipping SpotterNetwork update, "
546                 "ID \"%s\" is invalid!",
547                 gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry)));
548         return TRUE;
549     }
550
551     snprintf(uri, sizeof(uri),
552     "%s?lat=%lf&lon=%lf&elev=%lf&dir=%lf&mph=%lf&gps=1&active=%d&ver=1&id=%s",
553         GPS_SN_URI,
554         gps_data->fix.latitude,
555         gps_data->fix.longitude,
556         gps_data->fix.altitude * METERS_TO_FEET,
557         gps_data->fix.track,
558         gps_data->fix.speed,
559         gps_state->gps_sn_active == TRUE ? 1 : 0,
560         gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry)));
561
562     g_debug("gps_update_sn called uri: %s", uri);
563
564     msg = soup_message_new (SOUP_METHOD_GET, uri);
565     if (msg) {
566         soup_session_queue_message (gps_state->ui.soup_session, msg,
567                                     gps_sn_update_complete, gps_state);
568     } else {
569         g_warning("unable to create spotternetwork message");
570     }
571
572     /* reschedule next update */
573     return TRUE;
574 }
575
576 static void
577 gps_sn_update_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
578 {
579     if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
580         g_debug("gps_sn_update_complete: %s",
581                         msg->response_body->data);
582         GPS_STATUS(gps_state, "Updated SpotterNetwork.");
583     } else {
584         GPS_STATUS(gps_state, "Could not update SpotterNetwork: %s",
585                         msg->reason_phrase);
586         g_warning("Could not update SpotterNetwork: %s",
587                         msg->reason_phrase);
588     }
589 }
590
591
592
593 static void
594 gps_init_status_info(GritsPluginGPS *gps_state, GtkWidget *gbox)
595 {
596     gps_state->ui.gps_status_frame = gtk_frame_new ("GPS Data");
597     gps_state->ui.gps_status_table = gtk_table_new (5, 2, TRUE);
598     gtk_container_add (GTK_CONTAINER (gps_state->ui.gps_status_frame),
599                         gps_state->ui.gps_status_table);
600
601     /* gps data table setup */
602     int i;
603     for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
604         gps_table[i].label_widget = gtk_label_new (gps_table[i].label);
605         gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget),
606                                         GTK_JUSTIFY_LEFT);
607         gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
608                         gps_table[i].label_widget, 0, 1, i, i+1, 0, 0, 0, 0);
609         gps_table[i].value_widget = gtk_label_new (gps_table[i].initial_val);
610         gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
611                         gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0);
612
613         PangoFontDescription *font_desc = pango_font_description_new ();
614         pango_font_description_set_size (font_desc,
615                         gps_table[i].font_size*PANGO_SCALE);
616         gtk_widget_modify_font (gps_table[i].label_widget, font_desc);
617         gtk_widget_modify_font (gps_table[i].value_widget, font_desc);
618         pango_font_description_free (font_desc);
619     }
620     gtk_box_pack_start (GTK_BOX(gbox), gps_state->ui.gps_status_frame,
621                         FALSE, FALSE, 0);
622
623     /* Start UI refresh task, which will reschedule itself periodically. */
624     gps_redraw_all(gps_state);
625     gps_state->gps_update_timeout_id = g_timeout_add(
626                     GPS_UPDATE_INTERVAL*1000,
627                     gps_redraw_all, gps_state);
628
629 }
630
631
632 static void
633 gps_init_range_rings(GritsPluginGPS *gps_state, GtkWidget *gbox)
634 {
635     GtkWidget *gps_range_ring_frame = gtk_frame_new ("Range Rings");
636     GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
637     gtk_container_add (GTK_CONTAINER (gps_range_ring_frame), cbox);
638     gtk_box_pack_start (GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0);
639
640     gps_state->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings");
641     g_signal_connect (G_OBJECT (gps_state->ui.gps_rangering_checkbox), "clicked",
642                       G_CALLBACK (on_gps_rangering_clicked_event),
643                       (gpointer)gps_state);
644     gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_rangering_checkbox,
645                         FALSE, FALSE, 0);
646 }
647
648 static gboolean
649 on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data)
650 {
651     GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
652
653     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))  {
654         gps_state->gps_rangering_active = TRUE;
655     } else {
656         gps_state->gps_rangering_active = FALSE;
657     }
658
659     /* XXX force a redraw */
660
661     return FALSE;
662 }
663
664 /* external interface to update UI from latest GPS data. */
665 gboolean gps_redraw_all(gpointer data)
666 {
667     GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
668     assert(gps_state);
669
670     struct gps_data_t *gps_data = gps_state->gps_data;
671
672     g_debug("gps_redraw_all called");
673
674     assert(gps_data);
675     if (!gps_data_is_valid(gps_data)) {
676         g_debug("gps_data is not valid.");
677         /* XXX Change marker to indicate data is not valid */
678         return TRUE;
679     }
680
681     /* update position labels */
682     update_gps_status(gps_state);
683
684     if (gps_data_is_valid(gps_data)) {
685         /* Update track and marker position */
686         g_debug("Updating track at lat = %f, long = %f, track = %f",
687                         gps_data->fix.latitude,
688                         gps_data->fix.longitude,
689                         gps_data->fix.track);
690
691         if (gps_state->marker) {
692             grits_viewer_remove(gps_state->viewer,
693                     GRITS_OBJECT(gps_state->marker));
694             g_object_unref(gps_state->viewer);
695             gps_state->marker = NULL;
696         }
697
698         gps_state->marker = grits_marker_new("gps");
699         GRITS_OBJECT(gps_state->marker)->center.lat  = gps_data->fix.latitude;
700         GRITS_OBJECT(gps_state->marker)->center.lon  = gps_data->fix.longitude;
701         GRITS_OBJECT(gps_state->marker)->center.elev =   0.0;
702         GRITS_OBJECT(gps_state->marker)->lod         = EARTH_R;
703         grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->marker),
704                         GRITS_LEVEL_OVERLAY, FALSE);
705         grits_viewer_refresh(gps_state->viewer);
706     }
707
708     if (gps_state->follow_gps && gps_data_is_valid(gps_data)) {
709         /* Center map at current GPS position. */
710         g_debug("Centering map at lat = %f, long = %f, track = %f",
711                         gps_data->fix.latitude,
712                         gps_data->fix.longitude,
713                         gps_data->fix.track);
714
715         double lat, lon, elev;
716         grits_viewer_get_location(gps_state->viewer, &lat, &lon, &elev);
717         grits_viewer_set_location(gps_state->viewer, gps_data->fix.latitude,
718                                           gps_data->fix.longitude, elev);
719         grits_viewer_set_rotation(gps_state->viewer, 0, 0, 0);
720     }
721
722     /* reschedule */
723     return TRUE;
724 }
725
726 static
727 char *gps_get_status(struct gps_data_t *gps_data)
728 {
729     gchar *status_color;
730     gchar *status_text;
731
732     switch (gps_data->fix.mode) {
733         case MODE_NOT_SEEN:
734             status_color = "red";
735             status_text = "No Signal";
736             break;
737         case MODE_NO_FIX:
738             status_color = "red";
739             status_text = "No Fix";
740             break;
741         case MODE_2D:
742             status_color = "yellow";
743             status_text = "2D Mode";
744             break;
745         case MODE_3D:
746             status_color = "green";
747             status_text = "3D Mode";
748             break;
749         default:
750             status_color = "black";
751             status_text = "Unknown";
752             break;
753     }
754     return g_strdup_printf("<span foreground=\"%s\">%s</span>",
755                                 status_color, status_text);
756 }
757
758 #if 0
759 static char *gps_get_online(struct gps_data_t *);
760
761 static
762 char *gps_get_online(struct gps_data_t *gps_data)
763 {
764     char *status_str;
765     char *online_str;
766
767     if (gps_data->online == -1.0) {
768         online_str = "Offline";
769     } else {
770         online_str = "Online";
771     }
772
773     switch (gps_data->status) {
774     case 0:
775         status_str = "No Fix";
776         break;
777     case 1:
778         status_str = "Fix Acquired";
779         break;
780     case 2:
781         status_str = "DGPS Fix";
782         break;
783     default:
784         status_str = "Unknown Status";
785         break;
786     }
787
788     return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
789 }
790 #endif
791
792
793
794 static
795 char *gps_get_latitude(struct gps_data_t *gps_data)
796 {
797     return g_strdup_printf("%3.4f", gps_data->fix.latitude);
798 }
799
800 static
801 char *gps_get_longitude(struct gps_data_t *gps_data)
802 {
803     return g_strdup_printf("%3.4f", gps_data->fix.longitude);
804 }
805
806 static
807 char *gps_get_elevation(struct gps_data_t *gps_data)
808 {
809     /* XXX Make units (m/ft) settable */
810     return g_strdup_printf("%.1lf %s",
811                     (gps_data->fix.altitude * METERS_TO_FEET), "ft");
812 }
813
814 static
815 char *gps_get_heading(struct gps_data_t *gps_data)
816 {
817     /* XXX Make units (m/ft) settable */
818     return g_strdup_printf("%03.0lf", gps_data->fix.track);
819 }
820
821 static
822 char *gps_get_speed(struct gps_data_t *gps_data)
823 {
824     /* XXX Make units (m/ft) settable */
825     return g_strdup_printf("%1.1f %s",
826         (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
827 }
828
829
830 static
831 struct gps_data_t *initialize_gpsd(char *device, char *port)
832 {
833     struct gps_data_t *gps_data;
834
835     if ((gps_data = gps_open(device, port)) == NULL) {
836         g_warning("Unable to open gpsd connection");
837         gps_data = NULL;
838     } else {
839
840 #if GPSD_API_MAJOR_VERSION < 4
841         /* set data smothing */
842         gps_query(gps_data, "j=1\n");
843
844         /* Put gpsd in "watcher" mode so it continually tells us of updates */
845         gps_query(gps_data, "w+ox");
846
847         if (gps_query(gps_data, "o\n") == -1) {
848             g_warning("Error querying gps data\n");
849         }
850         gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
851         gps_clear_fix(&gps_data->fix);
852
853 #else /* Version > 4 uses gps_stream() instead */
854         (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
855         g_debug("initialize_gpsd(): gpsd fd %u.", gps_data->gps_fd);
856         gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
857 #endif
858     }
859
860     return gps_data;
861 }
862
863 static void
864 process_gps(gpointer data, gint source, GdkInputCondition condition)
865 {
866     struct gps_data_t *gps_data = (struct gps_data_t *)data;
867
868     /* Process any data from the gps and call the hook function */
869     g_debug("In process_gps()");
870     if (gps_data != NULL) {
871         int result = gps_read(gps_data);
872         g_debug("In process_gps(), gps_read returned %d, position %f, %f.", result, gps_data->fix.latitude, gps_data->fix.longitude);
873     } else {
874         g_debug("process_gps: gps_data == NULL.");
875     }
876 }
877
878 /************************** GPS Object Methods *************************/
879
880 /* Methods */
881 GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
882 {
883         /* TODO: move to constructor if possible */
884         g_debug("GritsPluginGPS: new");
885         GritsPluginGPS *self = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
886         self->viewer = viewer;
887         self->prefs  = prefs;
888
889         g_debug("grits_plugin_gps_new()");
890
891         self->gps_data = initialize_gpsd("localhost", DEFAULT_GPSD_PORT);
892         self->follow_gps = FALSE;
893         self->gps_sn_active = FALSE;
894
895         gps_init_status_info(self, self->hbox);
896         gps_init_control_frame(self, self->hbox);
897         gps_init_track_log_frame(self, self->hbox);
898         gps_init_spotter_network_frame(self, self->hbox);
899         gps_init_range_rings(self, self->hbox);
900
901         return self;
902 }
903
904 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self)
905 {
906         GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self);
907         return self->config;
908 }
909
910 /* GObject code */
911 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
912 G_DEFINE_TYPE_WITH_CODE(GritsPluginGPS, grits_plugin_gps, G_TYPE_OBJECT,
913                 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
914                         grits_plugin_gps_plugin_init));
915
916 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
917 {
918         g_debug("GritsPluginGPS: plugin_init");
919         /* Add methods to the interface */
920         iface->get_config = grits_plugin_gps_get_config;
921 }
922
923 static void grits_plugin_gps_init(GritsPluginGPS *self)
924 {
925         g_debug("GritsPluginGPS: in gps_init()");
926
927         self->config     = gtk_notebook_new();
928
929          self->hbox = gtk_hbox_new(FALSE, 2);
930          gtk_notebook_insert_page(GTK_NOTEBOOK(self->config),
931                                 GTK_WIDGET(self->hbox),
932                                 gtk_label_new("GPS"), 0);
933         /* Need to position on the top because of Win32 bug */
934         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(self->config), GTK_POS_LEFT);
935 }
936
937 static void grits_plugin_gps_dispose(GObject *gobject)
938 {
939         g_debug("GritsPluginGPS: dispose");
940         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
941         g_signal_handler_disconnect(self->config, self->tab_id);
942
943         /* Drop references */
944         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
945 }
946
947 static void grits_plugin_gps_finalize(GObject *gobject)
948 {
949         g_debug("GritsPluginGPS: finalize");
950         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
951         /* Free data */
952         gtk_widget_destroy(self->config);
953         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
954 }
955
956 static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass)
957 {
958         g_debug("GritsPluginGPS: class_init");
959         GObjectClass *gobject_class = (GObjectClass*)klass;
960         gobject_class->dispose  = grits_plugin_gps_dispose;
961         gobject_class->finalize = grits_plugin_gps_finalize;
962 }