2 * Copyright (C) 2012 Adam Boggs <boggs@aircrafter.org>
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.
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.
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/>.
26 #include <glib/gstdio.h>
34 #include "gps-plugin.h"
36 #include "../aweather-location.h"
38 /* interval to update map with new gps data in seconds. */
39 #define GPS_UPDATE_INTERVAL (2)
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"
45 /* interval to update SpotterNetwork in seconds. Should not be less
46 * than 3 minutes per SpotterNetwork policy.
48 #define GPS_SN_UPDATE_INTERVAL (5 * 60)
49 #define GPS_SN_URI "http://www.spotternetwork.org/update.php"
51 /* For updating the status bar conveniently */
52 #define GPS_STATUSBAR_CONTEXT "GPS"
54 #define GPS_STATUS(gps_state, format, args...) \
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), \
65 #define GPS_STATUS(gps_state, format, args...) \
67 char *buf = g_strdup_printf(format, ##args); \
68 g_warning("STATUS: %s", buf); \
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);
77 /* SpotterNetwork support */
78 static void gps_init_spotter_network_frame(GritsPluginGPS *gps_state,
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);
85 static void gps_init_range_rings(GritsPluginGPS *gps_state,
87 static gboolean on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data);
89 static void gps_init_status_info(GritsPluginGPS *gps_state,
91 static void gps_init_control_frame(GritsPluginGPS *gps_state,
93 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data);
95 /* GPS logging support */
96 static void gps_init_track_log_frame(GritsPluginGPS *gps_state,
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);
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 *);
109 /* Describes a line in the gps table */
110 struct gps_status_info {
113 char *(*get_data)(struct gps_data_t *);
114 unsigned int font_size;
115 GtkWidget *label_widget;
116 GtkWidget *value_widget;
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},
129 static void _gtk_bin_set_child(GtkBin *bin, GtkWidget *new)
131 GtkWidget *old = gtk_bin_get_child(bin);
133 gtk_widget_destroy(old);
134 gtk_container_add(GTK_CONTAINER(bin), new);
135 gtk_widget_show_all(new);
139 gboolean gps_data_is_valid(struct gps_data_t *gps_data)
141 if (gps_data != NULL && gps_data->online != -1.0 &&
142 gps_data->fix.mode >= MODE_2D &&
143 gps_data->status > STATUS_NO_FIX) {
151 gps_get_date_string(double gps_time)
153 static char buf[256];
154 time_t int_time = (time_t)gps_time;
157 gmtime_r(&int_time, &tm_time);
159 snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
160 tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
167 gps_get_time_string(time_t gps_time)
169 static char buf[256];
170 time_t int_time = (time_t)gps_time;
173 gmtime_r(&int_time, &tm_time);
175 snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
176 tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
183 update_gps_status(GritsPluginGPS *gps_state)
185 struct gps_data_t *gps_data = &gps_state->gps_data;
187 /* gps table update */
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)));
198 gps_init_control_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
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);
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,
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,
221 on_gps_track_clicked_event (GtkWidget *widget, gpointer user_data)
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.");
228 /* XXX stop logging trip history */
229 GPS_STATUS(gps_state, "Disabled GPS track.");
236 on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data)
238 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
242 g_debug("gps interval changed, value = %f",
243 gtk_range_get_value(GTK_RANGE(widget)));
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);
249 /* disable old timeout */
250 g_source_remove(gps_state->ui.gps_log_timeout_id);
251 gps_state->ui.gps_log_timeout_id = 0;
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);
264 gps_write_log(gpointer data)
266 GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
267 struct gps_data_t *gps_data = &gps_state->gps_data;
271 gboolean new_file = FALSE;
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.");
281 /* get filename from text entry box. If empty, generate a name from
282 * the date and time and set it.
284 if (strlen(gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)))
286 snprintf(filename, sizeof(filename),
288 gps_get_date_string(gps_state->gps_data.fix.time),
289 gps_get_time_string(gps_state->gps_data.fix.time),
291 gtk_entry_set_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry),
296 gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)),
299 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
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));
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));
317 gps_state->ui.gps_log_number = 1;
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,
332 gps_data->fix.speed * METERS_TO_FEET,
333 'T'); /* position due to timer expired */
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));
341 GPS_STATUS(gps_state, "Updated GPS log file %s.", filename);
348 on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
350 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
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;
357 gps_state->follow_gps = FALSE;
364 gps_init_track_log_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
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,
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,
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,
387 gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
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),
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),
405 gtk_box_pack_start (GTK_BOX(ubox), gps_state->ui.gps_log_interval_slider,
407 gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
411 on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
413 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
415 g_debug("on_gps_log_clicked_event called!");
417 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
418 gps_write_log(gps_state);
420 /* Schedule log file write */
421 gps_state->ui.gps_log_timeout_id = g_timeout_add(
423 GTK_RANGE(gps_state->ui.gps_log_interval_slider))*1000,
424 gps_write_log, gps_state);
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.");
436 gps_init_spotter_network_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
438 /* Spotter network control box with enable checkbox, active
439 * checkbox, and sn ID entry
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);
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,
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),
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,
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,
470 gtk_box_pack_start (GTK_BOX(sbox), ebox, FALSE, FALSE, 0);
472 gtk_box_pack_start (GTK_BOX(gbox), gps_sn_frame, FALSE, FALSE, 0);
476 on_gps_sn_clicked_event (GtkWidget *widget, gpointer user_data)
478 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
479 g_debug("on_gps_sn_clicked_event called");
481 /* When pressed, log a position report to spotternetwork and
482 * schedule another one for the future.
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",
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);
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;
511 on_gps_sn_active_clicked_event (GtkWidget *widget, gpointer user_data)
513 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
515 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
516 gps_state->ui.gps_sn_active = TRUE;
518 gps_state->ui.gps_sn_active = FALSE;
524 /* Send position report to spotternetwork */
526 gps_update_sn(gpointer user_data)
528 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
529 struct gps_data_t *gps_data = &gps_state->gps_data;
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!");
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)));
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",
554 gps_data->fix.latitude,
555 gps_data->fix.longitude,
556 gps_data->fix.altitude * METERS_TO_FEET,
559 gps_state->gps_sn_active == TRUE ? 1 : 0,
560 gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_sn_entry)));
562 g_debug("gps_update_sn called uri: %s", uri);
564 msg = soup_message_new (SOUP_METHOD_GET, uri);
566 soup_session_queue_message (gps_state->ui.soup_session, msg,
567 gps_sn_update_complete, gps_state);
569 g_warning("unable to create spotternetwork message");
572 /* reschedule next update */
577 gps_sn_update_complete (SoupSession *session, SoupMessage *msg, gpointer user_data)
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.");
584 GPS_STATUS(gps_state, "Could not update SpotterNetwork: %s",
586 g_warning("Could not update SpotterNetwork: %s",
594 gps_init_status_info(GritsPluginGPS *gps_state, GtkWidget *gbox)
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);
601 /* gps data table setup */
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),
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);
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);
620 gtk_box_pack_start (GTK_BOX(gbox), gps_state->ui.gps_status_frame,
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);
633 gps_init_range_rings(GritsPluginGPS *gps_state, GtkWidget *gbox)
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);
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,
649 on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data)
651 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
653 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
654 gps_state->gps_rangering_active = TRUE;
656 gps_state->gps_rangering_active = FALSE;
659 /* XXX force a redraw */
664 /* external interface to update UI from latest GPS data. */
665 gboolean gps_redraw_all(gpointer data)
667 GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
670 struct gps_data_t *gps_data = &gps_state->gps_data;
672 g_debug("gps_redraw_all called");
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 */
681 /* update position labels */
682 update_gps_status(gps_state);
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);
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;
698 gps_state->marker = grits_marker_new("gps");
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);
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);
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);
728 char *gps_get_status(struct gps_data_t *gps_data)
733 switch (gps_data->fix.mode) {
735 status_color = "red";
736 status_text = "No Signal";
739 status_color = "red";
740 status_text = "No Fix";
743 status_color = "yellow";
744 status_text = "2D Mode";
747 status_color = "green";
748 status_text = "3D Mode";
751 status_color = "black";
752 status_text = "Unknown";
755 return g_strdup_printf("<span foreground=\"%s\">%s</span>",
756 status_color, status_text);
760 static char *gps_get_online(struct gps_data_t *);
763 char *gps_get_online(struct gps_data_t *gps_data)
768 if (gps_data->online == -1.0) {
769 online_str = "Offline";
771 online_str = "Online";
774 switch (gps_data->status) {
776 status_str = "No Fix";
779 status_str = "Fix Acquired";
782 status_str = "DGPS Fix";
785 status_str = "Unknown Status";
789 return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
796 char *gps_get_latitude(struct gps_data_t *gps_data)
798 return g_strdup_printf("%3.4f", gps_data->fix.latitude);
802 char *gps_get_longitude(struct gps_data_t *gps_data)
804 return g_strdup_printf("%3.4f", gps_data->fix.longitude);
808 char *gps_get_elevation(struct gps_data_t *gps_data)
810 /* XXX Make units (m/ft) settable */
811 return g_strdup_printf("%.1lf %s",
812 (gps_data->fix.altitude * METERS_TO_FEET), "ft");
816 char *gps_get_heading(struct gps_data_t *gps_data)
818 /* XXX Make units (m/ft) settable */
819 return g_strdup_printf("%03.0lf", gps_data->fix.track);
823 char *gps_get_speed(struct gps_data_t *gps_data)
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");
832 gint initialize_gpsd(char *server, char *port,
833 struct gps_data_t *gps_data)
835 #if GPSD_API_MAJOR_VERSION < 5
836 #error "GPSD protocol version 5 or greater required."
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));
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);
853 process_gps(gpointer data, gint source, GdkInputCondition condition)
855 struct gps_data_t *gps_data = (struct gps_data_t *)data;
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);
863 g_debug("process_gps: gps_data == NULL.");
867 /************************** GPS Object Methods *************************/
870 GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
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;
878 g_debug("grits_plugin_gps_new()");
880 initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &self->gps_data);
881 self->follow_gps = FALSE;
882 self->gps_sn_active = FALSE;
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);
893 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self)
895 GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self);
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));
905 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
907 g_debug("GritsPluginGPS: plugin_init");
908 /* Add methods to the interface */
909 iface->get_config = grits_plugin_gps_get_config;
912 static void grits_plugin_gps_init(GritsPluginGPS *self)
914 g_debug("GritsPluginGPS: in gps_init()");
916 self->config = gtk_notebook_new();
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);
926 static void grits_plugin_gps_dispose(GObject *gobject)
928 g_debug("GritsPluginGPS: dispose");
929 GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
930 g_signal_handler_disconnect(self->config, self->tab_id);
932 /* Drop references */
933 G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
936 static void grits_plugin_gps_finalize(GObject *gobject)
938 g_debug("GritsPluginGPS: finalize");
939 GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
941 gtk_widget_destroy(self->config);
942 G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
945 static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass)
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;