]> Pileus Git - aweather/blob - src/plugins/gps-plugin.c
Update GPSD interface to use new version 5 protocol. Much more stable
[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                 
700         GRITS_OBJECT(gps_state->marker)->center.lat  = gps_data->fix.latitude;
701         GRITS_OBJECT(gps_state->marker)->center.lon  = gps_data->fix.longitude;
702         GRITS_OBJECT(gps_state->marker)->center.elev =   0.0;
703         GRITS_OBJECT(gps_state->marker)->lod         = EARTH_R;
704         grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->marker),
705                         GRITS_LEVEL_OVERLAY, FALSE);
706         grits_viewer_refresh(gps_state->viewer);
707     }
708
709     if (gps_state->follow_gps && gps_data_is_valid(gps_data)) {
710         /* Center map at current GPS position. */
711         g_debug("Centering map at lat = %f, long = %f, track = %f",
712                         gps_data->fix.latitude,
713                         gps_data->fix.longitude,
714                         gps_data->fix.track);
715
716         double lat, lon, elev;
717         grits_viewer_get_location(gps_state->viewer, &lat, &lon, &elev);
718         grits_viewer_set_location(gps_state->viewer, gps_data->fix.latitude,
719                                           gps_data->fix.longitude, elev);
720         grits_viewer_set_rotation(gps_state->viewer, 0, 0, 0);
721     }
722
723     /* reschedule */
724     return TRUE;
725 }
726
727 static
728 char *gps_get_status(struct gps_data_t *gps_data)
729 {
730     gchar *status_color;
731     gchar *status_text;
732
733     switch (gps_data->fix.mode) {
734         case MODE_NOT_SEEN:
735             status_color = "red";
736             status_text = "No Signal";
737             break;
738         case MODE_NO_FIX:
739             status_color = "red";
740             status_text = "No Fix";
741             break;
742         case MODE_2D:
743             status_color = "yellow";
744             status_text = "2D Mode";
745             break;
746         case MODE_3D:
747             status_color = "green";
748             status_text = "3D Mode";
749             break;
750         default:
751             status_color = "black";
752             status_text = "Unknown";
753             break;
754     }
755     return g_strdup_printf("<span foreground=\"%s\">%s</span>",
756                                 status_color, status_text);
757 }
758
759 #if 0
760 static char *gps_get_online(struct gps_data_t *);
761
762 static
763 char *gps_get_online(struct gps_data_t *gps_data)
764 {
765     char *status_str;
766     char *online_str;
767
768     if (gps_data->online == -1.0) {
769         online_str = "Offline";
770     } else {
771         online_str = "Online";
772     }
773
774     switch (gps_data->status) {
775     case 0:
776         status_str = "No Fix";
777         break;
778     case 1:
779         status_str = "Fix Acquired";
780         break;
781     case 2:
782         status_str = "DGPS Fix";
783         break;
784     default:
785         status_str = "Unknown Status";
786         break;
787     }
788
789     return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
790 }
791 #endif
792
793
794
795 static
796 char *gps_get_latitude(struct gps_data_t *gps_data)
797 {
798     return g_strdup_printf("%3.4f", gps_data->fix.latitude);
799 }
800
801 static
802 char *gps_get_longitude(struct gps_data_t *gps_data)
803 {
804     return g_strdup_printf("%3.4f", gps_data->fix.longitude);
805 }
806
807 static
808 char *gps_get_elevation(struct gps_data_t *gps_data)
809 {
810     /* XXX Make units (m/ft) settable */
811     return g_strdup_printf("%.1lf %s",
812                     (gps_data->fix.altitude * METERS_TO_FEET), "ft");
813 }
814
815 static
816 char *gps_get_heading(struct gps_data_t *gps_data)
817 {
818     /* XXX Make units (m/ft) settable */
819     return g_strdup_printf("%03.0lf", gps_data->fix.track);
820 }
821
822 static
823 char *gps_get_speed(struct gps_data_t *gps_data)
824 {
825     /* XXX Make units (m/ft) settable */
826     return g_strdup_printf("%1.1f %s",
827         (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
828 }
829
830
831 static
832 gint initialize_gpsd(char *server, char *port,
833         struct gps_data_t *gps_data)
834 {
835 #if GPSD_API_MAJOR_VERSION < 5
836 #error "GPSD protocol version 5 or greater required."
837 #endif
838     int result;
839
840     if ((result = gps_open(server, port, gps_data)) != 0) {
841         g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
842         server, port, result, errno, gps_errstr(errno));
843     } else {
844         (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
845         g_debug("initialize_gpsd(): gpsd fd %u.", gps_data->gps_fd);
846         gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
847     }
848
849     return result;
850 }
851
852 static void
853 process_gps(gpointer data, gint source, GdkInputCondition condition)
854 {
855     struct gps_data_t *gps_data = (struct gps_data_t *)data;
856
857     /* Process any data from the gps and call the hook function */
858     g_debug("In process_gps()");
859     if (gps_data != NULL) {
860         int result = gps_read(gps_data);
861         g_debug("In process_gps(), gps_read returned %d, position %f, %f.", result, gps_data->fix.latitude, gps_data->fix.longitude);
862     } else {
863         g_debug("process_gps: gps_data == NULL.");
864     }
865 }
866
867 /************************** GPS Object Methods *************************/
868
869 /* Methods */
870 GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
871 {
872         /* TODO: move to constructor if possible */
873         g_debug("GritsPluginGPS: new");
874         GritsPluginGPS *self = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
875         self->viewer = viewer;
876         self->prefs  = prefs;
877
878         g_debug("grits_plugin_gps_new()");
879
880         initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &self->gps_data);
881         self->follow_gps = FALSE;
882         self->gps_sn_active = FALSE;
883
884         gps_init_status_info(self, self->hbox);
885         gps_init_control_frame(self, self->hbox);
886         gps_init_track_log_frame(self, self->hbox);
887         gps_init_spotter_network_frame(self, self->hbox);
888         gps_init_range_rings(self, self->hbox);
889
890         return self;
891 }
892
893 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self)
894 {
895         GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self);
896         return self->config;
897 }
898
899 /* GObject code */
900 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
901 G_DEFINE_TYPE_WITH_CODE(GritsPluginGPS, grits_plugin_gps, G_TYPE_OBJECT,
902                 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
903                         grits_plugin_gps_plugin_init));
904
905 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
906 {
907         g_debug("GritsPluginGPS: plugin_init");
908         /* Add methods to the interface */
909         iface->get_config = grits_plugin_gps_get_config;
910 }
911
912 static void grits_plugin_gps_init(GritsPluginGPS *self)
913 {
914         g_debug("GritsPluginGPS: in gps_init()");
915
916         self->config     = gtk_notebook_new();
917
918          self->hbox = gtk_hbox_new(FALSE, 2);
919          gtk_notebook_insert_page(GTK_NOTEBOOK(self->config),
920                                 GTK_WIDGET(self->hbox),
921                                 gtk_label_new("GPS"), 0);
922         /* Need to position on the top because of Win32 bug */
923         gtk_notebook_set_tab_pos(GTK_NOTEBOOK(self->config), GTK_POS_LEFT);
924 }
925
926 static void grits_plugin_gps_dispose(GObject *gobject)
927 {
928         g_debug("GritsPluginGPS: dispose");
929         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
930         g_signal_handler_disconnect(self->config, self->tab_id);
931
932         /* Drop references */
933         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
934 }
935
936 static void grits_plugin_gps_finalize(GObject *gobject)
937 {
938         g_debug("GritsPluginGPS: finalize");
939         GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
940         /* Free data */
941         gtk_widget_destroy(self->config);
942         G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
943 }
944
945 static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass)
946 {
947         g_debug("GritsPluginGPS: class_init");
948         GObjectClass *gobject_class = (GObjectClass*)klass;
949         gobject_class->dispose  = grits_plugin_gps_dispose;
950         gobject_class->finalize = grits_plugin_gps_finalize;
951 }