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/>.
19 * If gpsd connection fails, try to connect again periodically.
20 * If gps stops sending data there should be an indication that it's stale.
31 #include <glib/gstdio.h>
39 #include "gps-plugin.h"
41 #include "../aweather-location.h"
43 /* interval to update map with new gps data in seconds. */
44 #define GPS_UPDATE_INTERVAL (2)
46 /* number of track points per group and number of groups to maintain */
47 #define NUM_TRACK_POINTS (6)
48 #define NUM_TRACK_GROUPS (4)
49 #define NUM_TRACK_POINTS_FACTOR (1.5)
51 /* interval to update log file in seconds (default value for slider) */
52 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
53 #define GPS_LOG_EXT "csv"
55 /* For updating the status bar conveniently */
56 #define GPS_STATUSBAR_CONTEXT "GPS"
58 #define GPS_STATUS(gps_state, format, args...) \
60 char *buf = g_strdup_printf(format, ##args); \
61 gtk_statusbar_push(GTK_STATUSBAR(gps_state->status_bar), \
62 gtk_statusbar_get_context_id( \
63 GTK_STATUSBAR(gps_state->status_bar), \
64 GPS_STATUSBAR_CONTEXT), \
69 #define GPS_STATUS(gps_state, format, args...) \
71 char *buf = g_strdup_printf(format, ##args); \
72 g_debug("STATUS: %s", buf); \
76 static gboolean gps_data_is_valid(struct gps_data_t *gps_data);
77 static char *gps_get_time_string(time_t gps_time);
78 static char *gps_get_date_string(double gps_time);
79 static void process_gps( gpointer, gint, GdkInputCondition);
81 #ifdef GPS_RANGE_RINGS
82 static void gps_init_range_rings(GritsPluginGPS *gps_state,
84 static gboolean on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data);
87 static void gps_init_status_info(GritsPluginGPS *gps_state,
89 static void gps_init_control_frame(GritsPluginGPS *gps_state,
91 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data);
93 /* GPS logging support */
94 static void gps_init_track_log_frame(GritsPluginGPS *gps_state,
96 static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data);
98 /* Track management */
99 static void gps_track_init(struct gps_track_t *track);
100 static void gps_track_free(struct gps_track_t *track);
101 static void gps_track_clear(struct gps_track_t *track);
102 static void gps_track_add_point(struct gps_track_t *track, gdouble lat, gdouble lon, gdouble elevation);
103 static void gps_track_group_incr(struct gps_track_t *track);
105 static gboolean on_gps_track_enable_clicked_event(GtkWidget *widget, gpointer user_data);
106 static gboolean on_gps_track_clear_clicked_event(GtkWidget *widget, gpointer user_data);
107 static gboolean gps_write_log(gpointer data);
109 static char *gps_get_status(struct gps_data_t *);
110 static char *gps_get_latitude(struct gps_data_t *);
111 static char *gps_get_longitude(struct gps_data_t *);
112 static char *gps_get_elevation(struct gps_data_t *);
113 static char *gps_get_heading(struct gps_data_t *);
114 static char *gps_get_speed(struct gps_data_t *);
116 /* Describes a line in the gps table */
117 struct gps_status_info {
120 char *(*get_data)(struct gps_data_t *);
121 unsigned int font_size;
122 GtkWidget *label_widget;
123 GtkWidget *value_widget;
126 struct gps_status_info gps_table[] = {
127 {"Status:", "No Data", gps_get_status, 14, NULL, NULL},
128 // {"Online:", "No Data", gps_get_online, 14, NULL, NULL},
129 {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL},
130 {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
131 {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
132 {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL},
133 {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL},
137 gboolean gps_data_is_valid(struct gps_data_t *gps_data)
139 if (gps_data != NULL && gps_data->online != -1.0 &&
140 gps_data->fix.mode >= MODE_2D &&
141 gps_data->status > STATUS_NO_FIX) {
149 gps_get_date_string(double gps_time)
151 static char buf[256];
152 time_t int_time = (time_t)gps_time;
155 gmtime_r(&int_time, &tm_time);
157 snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
158 tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
165 gps_get_time_string(time_t gps_time)
167 static char buf[256];
168 time_t int_time = (time_t)gps_time;
171 gmtime_r(&int_time, &tm_time);
173 snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
174 tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
181 update_gps_status(GritsPluginGPS *gps_state)
183 struct gps_data_t *gps_data = &gps_state->gps_data;
185 /* gps table update */
188 for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
189 gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget),
190 (str = gps_table[i].get_data(gps_data)));
196 gps_init_control_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
198 /* Control checkboxes */
199 GtkWidget *gps_control_frame = gtk_frame_new("GPS Control");
200 GtkWidget *cbox = gtk_vbox_new(FALSE, 2);
201 gtk_container_add(GTK_CONTAINER(gps_control_frame), cbox);
202 gtk_box_pack_start(GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
204 gps_state->ui.gps_follow_checkbox = gtk_check_button_new_with_label("Follow GPS");
205 g_signal_connect(G_OBJECT(gps_state->ui.gps_follow_checkbox), "clicked",
206 G_CALLBACK (on_gps_follow_clicked_event),
207 (gpointer)gps_state);
208 gtk_box_pack_start(GTK_BOX(cbox), gps_state->ui.gps_follow_checkbox,
211 gps_state->ui.gps_track_checkbox = gtk_check_button_new_with_label("Record Track");
212 g_signal_connect(G_OBJECT(gps_state->ui.gps_track_checkbox), "clicked",
213 G_CALLBACK (on_gps_track_enable_clicked_event),
214 (gpointer)gps_state);
215 gtk_box_pack_start(GTK_BOX(cbox), gps_state->ui.gps_track_checkbox,
218 gps_state->ui.gps_clear_button = gtk_button_new_with_label("Clear Track");
219 g_signal_connect(G_OBJECT(gps_state->ui.gps_clear_button), "clicked",
220 G_CALLBACK (on_gps_track_clear_clicked_event),
221 (gpointer)gps_state);
222 gtk_box_pack_start(GTK_BOX(cbox), gps_state->ui.gps_clear_button,
227 on_gps_track_enable_clicked_event(GtkWidget *widget, gpointer user_data)
229 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
231 g_debug("on_gps_track_enable_clicked_event called");
233 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
234 /* start logging trip history */
235 GPS_STATUS(gps_state, "Enabled GPS track.");
236 gps_state->track.active = TRUE;
238 /* stop logging trip history */
239 GPS_STATUS(gps_state, "Disabled GPS track.");
240 gps_state->track.active = FALSE;
241 /* advance to the next track group, moving everything down if
244 gps_track_group_incr(&gps_state->track);
251 on_gps_track_clear_clicked_event(GtkWidget *widget, gpointer user_data)
253 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
255 g_debug("on_gps_track_clear_clicked_event called");
257 GPS_STATUS(gps_state, "Cleared GPS track.");
259 gps_track_clear(&gps_state->track);
265 on_gps_log_interval_changed_event(GtkWidget *widget, gpointer user_data)
267 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
271 g_debug("gps interval changed, value = %f",
272 gtk_range_get_value(GTK_RANGE(widget)));
274 if (gtk_toggle_button_get_active(
275 GTK_TOGGLE_BUTTON(gps_state->ui.gps_log_checkbox))) {
276 assert(gps_state->ui.gps_log_timeout_id != 0);
278 /* disable old timeout */
279 g_source_remove(gps_state->ui.gps_log_timeout_id);
280 gps_state->ui.gps_log_timeout_id = 0;
282 /* Schedule new log file write */
283 gps_state->ui.gps_log_timeout_id = g_timeout_add(
284 gtk_range_get_value(GTK_RANGE(widget))*1000,
285 gps_write_log, gps_state);
286 gps_write_log(gps_state);
293 gps_write_log(gpointer data)
295 GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
296 struct gps_data_t *gps_data = &gps_state->gps_data;
300 gboolean new_file = FALSE;
302 if (gps_data == NULL) {
303 g_warning("Skipped write to GPS log file: "
304 "can not get GPS coordinates.");
305 GPS_STATUS(gps_state, "Skipped write to GPS log file: "
306 "can not get GPS coordinates.");
310 /* get filename from text entry box. If empty, generate a name from
311 * the date and time and set it.
313 if (strlen(gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)))
315 snprintf(filename, sizeof(filename),
317 gps_get_date_string(gps_state->gps_data.fix.time),
318 gps_get_time_string(gps_state->gps_data.fix.time),
320 gtk_entry_set_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry),
325 gtk_entry_get_text(GTK_ENTRY(gps_state->ui.gps_log_filename_entry)),
328 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
332 if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
333 g_warning("Error opening log file %s: %s",
334 filename, strerror(errno));
339 /* write header and reset record counter */
340 snprintf(buf, sizeof(buf),
341 "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
342 if (write(fd, buf, strlen(buf)) == -1) {
343 g_warning("Error writing header to log file %s: %s",
344 filename, strerror(errno));
346 gps_state->ui.gps_log_number = 1;
349 /* Write log entry. Make sure this matches the header */
350 /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
351 /* RTR values: T=time, B=button push, S=speed, D=distance */
352 snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
353 gps_state->ui.gps_log_number++,
354 gps_get_date_string(gps_state->gps_data.fix.time),
355 gps_get_time_string(gps_state->gps_data.fix.time),
356 // gps_data->fix.time,
357 gps_data->fix.latitude,
358 gps_data->fix.longitude,
359 gps_data->fix.altitude * METERS_TO_FEET,
361 gps_data->fix.speed * METERS_TO_FEET,
362 'T'); /* position due to timer expired */
364 if (write(fd, buf, strlen(buf)) == -1) {
365 g_warning("Could not write log number %d to log file %s: %s",
366 gps_state->ui.gps_log_number-1, filename, strerror(errno));
370 GPS_STATUS(gps_state, "Updated GPS log file %s.", filename);
377 on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
379 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
381 g_debug("on_gps_follow_clicked_event called!, button status %d",
382 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
383 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
384 gps_state->follow_gps = TRUE;
386 gps_state->follow_gps = FALSE;
393 gps_init_track_log_frame(GritsPluginGPS *gps_state, GtkWidget *gbox)
395 /* Track log box with enable checkbox and filename entry */
396 GtkWidget *gps_log_frame = gtk_frame_new ("Track Log");
397 GtkWidget *lbox = gtk_vbox_new (FALSE, 2);
398 gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox);
399 gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame,
402 gps_state->ui.gps_log_checkbox = gtk_check_button_new_with_label("Log Position to File");
403 g_signal_connect (G_OBJECT (gps_state->ui.gps_log_checkbox), "clicked",
404 G_CALLBACK (on_gps_log_clicked_event),
405 (gpointer)gps_state);
406 gtk_box_pack_start (GTK_BOX(lbox), gps_state->ui.gps_log_checkbox,
409 /* Set up filename entry box */
410 GtkWidget *fbox = gtk_hbox_new (FALSE, 2);
411 GtkWidget *filename_label = gtk_label_new ("Filename:");
412 gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
413 gps_state->ui.gps_log_filename_entry = gtk_entry_new();
414 gtk_box_pack_start (GTK_BOX(fbox), gps_state->ui.gps_log_filename_entry,
416 gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
418 /* set up gps log interval slider */
419 GtkWidget *ubox = gtk_hbox_new (FALSE, 4);
420 GtkWidget *interval_label = gtk_label_new ("Update Interval:");
421 gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
422 gps_state->ui.gps_log_interval_slider =
423 gtk_hscale_new_with_range(1.0, 600.0, 30.0);
424 gtk_range_set_value (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
425 GPS_LOG_DEFAULT_UPDATE_INTERVAL);
426 g_signal_connect (G_OBJECT (gps_state->ui.gps_log_interval_slider),
428 G_CALLBACK(on_gps_log_interval_changed_event),
429 (gpointer)gps_state);
430 gtk_range_set_increments (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
431 10.0 /* step */, 30.0 /* page up/down */);
432 gtk_range_set_update_policy (GTK_RANGE(gps_state->ui.gps_log_interval_slider),
434 gtk_box_pack_start (GTK_BOX(ubox), gps_state->ui.gps_log_interval_slider,
436 gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
440 on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
442 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
444 g_debug("on_gps_log_clicked_event called!");
446 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
447 gps_write_log(gps_state);
449 /* Schedule log file write */
450 gps_state->ui.gps_log_timeout_id = g_timeout_add(
452 GTK_RANGE(gps_state->ui.gps_log_interval_slider))*1000,
453 gps_write_log, gps_state);
455 /* button unchecked */
456 g_source_remove(gps_state->ui.gps_log_timeout_id);
457 gps_state->ui.gps_log_timeout_id = 0;
458 g_debug("Closed log file.");
466 gps_init_status_info(GritsPluginGPS *gps_state, GtkWidget *gbox)
468 gps_state->ui.gps_status_frame = gtk_frame_new ("GPS Data");
469 gps_state->ui.gps_status_table = gtk_table_new (5, 2, TRUE);
470 gtk_container_add (GTK_CONTAINER (gps_state->ui.gps_status_frame),
471 gps_state->ui.gps_status_table);
473 /* gps data table setup */
475 for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
476 gps_table[i].label_widget = gtk_label_new (gps_table[i].label);
477 gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget),
479 gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
480 gps_table[i].label_widget, 0, 1, i, i+1, 0, 0, 0, 0);
481 gps_table[i].value_widget = gtk_label_new (gps_table[i].initial_val);
482 gtk_table_attach( GTK_TABLE(gps_state->ui.gps_status_table),
483 gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0);
485 PangoFontDescription *font_desc = pango_font_description_new ();
486 pango_font_description_set_size (font_desc,
487 gps_table[i].font_size*PANGO_SCALE);
488 gtk_widget_modify_font (gps_table[i].label_widget, font_desc);
489 gtk_widget_modify_font (gps_table[i].value_widget, font_desc);
490 pango_font_description_free (font_desc);
492 gtk_box_pack_start (GTK_BOX(gbox), gps_state->ui.gps_status_frame,
495 /* Start UI refresh task, which will reschedule itself periodically. */
496 gps_redraw_all(gps_state);
497 gps_state->gps_update_timeout_id = g_timeout_add(
498 GPS_UPDATE_INTERVAL*1000,
499 gps_redraw_all, gps_state);
504 #ifdef GPS_RANGE_RINGS
506 gps_init_range_rings(GritsPluginGPS *gps_state, GtkWidget *gbox)
508 GtkWidget *gps_range_ring_frame = gtk_frame_new ("Range Rings");
509 GtkWidget *cbox = gtk_vbox_new (FALSE, 2);
510 gtk_container_add (GTK_CONTAINER (gps_range_ring_frame), cbox);
511 gtk_box_pack_start (GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0);
513 gps_state->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings");
514 g_signal_connect (G_OBJECT (gps_state->ui.gps_rangering_checkbox), "clicked",
515 G_CALLBACK (on_gps_rangering_clicked_event),
516 (gpointer)gps_state);
517 gtk_box_pack_start (GTK_BOX(cbox), gps_state->ui.gps_rangering_checkbox,
522 on_gps_rangering_clicked_event (GtkWidget *widget, gpointer user_data)
524 GritsPluginGPS *gps_state = (GritsPluginGPS *)user_data;
526 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
527 gps_state->gps_rangering_active = TRUE;
529 gps_state->gps_rangering_active = FALSE;
532 /* XXX force a redraw */
536 #endif /* GPS_RANGE_RINGS */
538 /* external interface to update UI from latest GPS data. */
539 gboolean gps_redraw_all(gpointer data)
541 GritsPluginGPS *gps_state = (GritsPluginGPS *)data;
544 struct gps_data_t *gps_data = &gps_state->gps_data;
546 g_debug("gps_redraw_all called");
549 if (!gps_data_is_valid(gps_data)) {
550 g_debug("gps_data is not valid.");
551 /* XXX Change marker to indicate data is not valid */
555 /* update position labels */
556 update_gps_status(gps_state);
558 /* Update track and marker position */
559 if (gps_data_is_valid(gps_data) && gps_state->track.active) {
560 g_debug("Updating track group %u point %u at "
561 "lat = %f, long = %f, track = %f",
562 gps_state->track.cur_group, gps_state->track.cur_point,
563 gps_data->fix.latitude,
564 gps_data->fix.longitude,
565 gps_data->fix.track);
567 gps_track_add_point(&gps_state->track,
568 gps_data->fix.latitude, gps_data->fix.longitude, 0.0);
570 if (gps_state->track.line) {
571 grits_viewer_remove(gps_state->viewer,
572 GRITS_OBJECT(gps_state->track.line));
573 gps_state->track.line = NULL;
576 gps_state->track.line = grits_line_new(gps_state->track.points);
577 gps_state->track.line->color[0] = 1.0;
578 gps_state->track.line->color[1] = 0;
579 gps_state->track.line->color[2] = 0.1;
580 gps_state->track.line->color[3] = 0.5;
581 gps_state->track.line->width = 3;
583 grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->track.line),
584 GRITS_LEVEL_OVERLAY, TRUE);
587 if (gps_data_is_valid(gps_data)) {
588 if (gps_state->marker) {
589 grits_viewer_remove(gps_state->viewer,
590 GRITS_OBJECT(gps_state->marker));
591 gps_state->marker = NULL;
594 gps_state->marker = grits_marker_icon_new("GPS", "car.png",
595 gps_data->fix.track, TRUE);
597 GRITS_OBJECT(gps_state->marker)->center.lat = gps_data->fix.latitude;
598 GRITS_OBJECT(gps_state->marker)->center.lon = gps_data->fix.longitude;
599 GRITS_OBJECT(gps_state->marker)->center.elev = 0.0;
600 GRITS_OBJECT(gps_state->marker)->lod = EARTH_R;
602 grits_viewer_add(gps_state->viewer, GRITS_OBJECT(gps_state->marker),
603 GRITS_LEVEL_OVERLAY, TRUE);
604 grits_viewer_refresh(gps_state->viewer);
607 if (gps_state->follow_gps && gps_data_is_valid(gps_data)) {
608 /* Center map at current GPS position. */
609 g_debug("Centering map at lat = %f, long = %f, track = %f",
610 gps_data->fix.latitude,
611 gps_data->fix.longitude,
612 gps_data->fix.track);
614 double lat, lon, elev;
615 grits_viewer_get_location(gps_state->viewer, &lat, &lon, &elev);
616 grits_viewer_set_location(gps_state->viewer, gps_data->fix.latitude,
617 gps_data->fix.longitude, elev);
618 //grits_viewer_set_rotation(gps_state->viewer, 0, 0, 0);
626 /******************* Track handling routines *****************/
629 gps_track_init(struct gps_track_t *track)
631 /* Save a spot at the end for the NULL termination */
632 track->points = (gpointer)g_new0(double*, NUM_TRACK_GROUPS + 1);
633 track->cur_point = 0;
634 track->cur_group = 0;
635 track->num_points = 1; /* starts at 1 so realloc logic works */
640 gps_track_clear(struct gps_track_t *track)
643 for (pi = 0; pi < NUM_TRACK_GROUPS; pi++) {
644 if (track->points[pi] != NULL) {
645 g_free(track->points[pi]);
646 track->points[pi] = NULL;
649 track->cur_point = 0;
650 track->cur_group = 0;
651 track->num_points = 1; /* starts at 1 so realloc logic works */
655 gps_track_free(struct gps_track_t *track)
657 gps_track_clear(track);
658 g_free(track->points);
661 /* add a new track group (points in a track group are connected, and
662 * separated from points in other track groups).
665 gps_track_group_incr(struct gps_track_t *track)
667 gdouble (**points)[3] = track->points; /* for simplicity */
669 /* Just return if they increment it again before any points have
672 if (points[track->cur_group] == NULL) {
676 g_debug("track_group_incr: incrementing track group to %u.",
677 track->cur_group + 1);
680 track->cur_point = 0;
681 track->num_points = 1; /* starts at 1 so realloc logic works */
683 if (track->cur_group >= NUM_TRACK_GROUPS) {
684 g_debug("track_group_incr: current track group %u is at max %u, "
686 track->cur_group, NUM_TRACK_GROUPS);
688 /* Free the oldest one which falls off the end */
691 /* shift the rest down, last one should always be NULL already */
692 /* note we alloc NUM_TRACK_GROUPS+1 */
693 for (int pi = 0; pi < NUM_TRACK_GROUPS; pi++) {
694 points[pi] = points[pi+1];
697 /* always write into the last group */
698 track->cur_group = NUM_TRACK_GROUPS - 1;
703 gps_track_add_point(struct gps_track_t *track, gdouble lat, gdouble lon,
706 gdouble (**points)[3] = track->points; /* for simplicity */
708 g_debug("GritsPluginGPS: track_add_point");
710 g_assert(track->cur_group < NUM_TRACK_GROUPS &&
711 (track->cur_point <= track->num_points));
713 /* resize/allocate the point group if the current one is full */
714 if (track->cur_point >= track->num_points - 1) {
715 guint new_size = track->num_points == 1 ?
717 track->num_points * NUM_TRACK_POINTS_FACTOR;
718 g_debug("GritsPluginGPS: track_add_point: reallocating points "
719 "array from %u points to %u points.\n",
720 track->num_points, new_size);
721 points[track->cur_group] = (gpointer)g_renew(gdouble,
722 points[track->cur_group], 3*(new_size+1));
723 track->num_points = new_size;
726 g_assert(points[track->cur_group] != NULL);
728 /* Add the coordinate */
729 lle2xyz(lat, lon, elevation,
730 &points[track->cur_group][track->cur_point][0],
731 &points[track->cur_group][track->cur_point][1],
732 &points[track->cur_group][track->cur_point][2]);
736 /* make sure last point is always 0s so the line drawing stops. */
737 points[track->cur_group][track->cur_point][0] = 0.0;
738 points[track->cur_group][track->cur_point][1] = 0.0;
739 points[track->cur_group][track->cur_point][2] = 0.0;
744 char *gps_get_status(struct gps_data_t *gps_data)
749 switch (gps_data->fix.mode) {
751 status_color = "red";
752 status_text = "No Signal";
755 status_color = "red";
756 status_text = "No Fix";
759 status_color = "yellow";
760 status_text = "2D Mode";
763 status_color = "green";
764 status_text = "3D Mode";
767 status_color = "black";
768 status_text = "Unknown";
771 return g_strdup_printf("<span foreground=\"%s\">%s</span>",
772 status_color, status_text);
776 static char *gps_get_online(struct gps_data_t *);
779 char *gps_get_online(struct gps_data_t *gps_data)
784 if (gps_data->online == -1.0) {
785 online_str = "Offline";
787 online_str = "Online";
790 switch (gps_data->status) {
792 status_str = "No Fix";
795 status_str = "Fix Acquired";
798 status_str = "DGPS Fix";
801 status_str = "Unknown Status";
805 return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
812 char *gps_get_latitude(struct gps_data_t *gps_data)
814 return g_strdup_printf("%3.4f", gps_data->fix.latitude);
818 char *gps_get_longitude(struct gps_data_t *gps_data)
820 return g_strdup_printf("%3.4f", gps_data->fix.longitude);
824 char *gps_get_elevation(struct gps_data_t *gps_data)
826 /* XXX Make units (m/ft) settable */
827 return g_strdup_printf("%.1lf %s",
828 (gps_data->fix.altitude * METERS_TO_FEET), "ft");
832 char *gps_get_heading(struct gps_data_t *gps_data)
834 /* XXX Make units (m/ft) settable */
835 return g_strdup_printf("%03.0lf", gps_data->fix.track);
839 char *gps_get_speed(struct gps_data_t *gps_data)
841 /* XXX Make units (m/ft) settable */
842 return g_strdup_printf("%1.1f %s",
843 (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
848 gint initialize_gpsd(char *server, char *port,
849 struct gps_data_t *gps_data)
851 #if GPSD_API_MAJOR_VERSION < 5
852 #error "GPSD protocol version 5 or greater required."
856 if ((result = gps_open(server, port, gps_data)) != 0) {
857 g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
858 server, port, result, errno, gps_errstr(errno));
860 (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
861 g_debug("initialize_gpsd(): gpsd fd %u.", gps_data->gps_fd);
862 gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
869 process_gps(gpointer data, gint source, GdkInputCondition condition)
871 struct gps_data_t *gps_data = (struct gps_data_t *)data;
873 /* Process any data from the gps and call the hook function */
874 g_debug("In process_gps()");
875 if (gps_data != NULL) {
876 int result = gps_read(gps_data);
877 g_debug("In process_gps(), gps_read returned %d, position %f, %f.", result, gps_data->fix.latitude, gps_data->fix.longitude);
879 g_debug("process_gps: gps_data == NULL.");
883 /************************** GPS Object Methods *************************/
886 GritsPluginGPS *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
888 /* TODO: move to constructor if possible */
889 g_debug("GritsPluginGPS: new");
890 GritsPluginGPS *self = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
891 self->viewer = viewer;
894 g_debug("grits_plugin_gps_new()");
896 initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &self->gps_data);
897 self->follow_gps = FALSE;
899 gps_track_init(&self->track);
900 gps_init_status_info(self, self->hbox);
901 gps_init_control_frame(self, self->hbox);
902 gps_init_track_log_frame(self, self->hbox);
903 #ifdef GPS_RANGE_RINGS
904 gps_init_range_rings(self, self->hbox);
911 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_self)
913 GritsPluginGPS *self = GRITS_PLUGIN_GPS(_self);
918 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
919 G_DEFINE_TYPE_WITH_CODE(GritsPluginGPS, grits_plugin_gps, G_TYPE_OBJECT,
920 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
921 grits_plugin_gps_plugin_init));
923 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
925 g_debug("GritsPluginGPS: plugin_init");
926 /* Add methods to the interface */
927 iface->get_config = grits_plugin_gps_get_config;
930 static void grits_plugin_gps_init(GritsPluginGPS *self)
932 g_debug("GritsPluginGPS: in gps_init()");
934 self->config = gtk_notebook_new();
936 self->hbox = gtk_hbox_new(FALSE, 2);
937 gtk_notebook_insert_page(GTK_NOTEBOOK(self->config),
938 GTK_WIDGET(self->hbox),
939 gtk_label_new("GPS"), 0);
940 /* Need to position on the top because of Win32 bug */
941 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(self->config), GTK_POS_LEFT);
944 static void grits_plugin_gps_dispose(GObject *gobject)
946 GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
948 g_debug("GritsPluginGPS: dispose");
952 grits_viewer_remove(self->viewer,
953 GRITS_OBJECT(self->marker));
955 g_object_unref(self->viewer);
959 gps_track_free(&self->track);
961 /* Drop references */
962 G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
965 static void grits_plugin_gps_finalize(GObject *gobject)
967 GritsPluginGPS *self = GRITS_PLUGIN_GPS(gobject);
969 g_debug("GritsPluginGPS: finalize");
972 gtk_widget_destroy(self->config);
973 G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
976 static void grits_plugin_gps_class_init(GritsPluginGPSClass *klass)
978 g_debug("GritsPluginGPS: class_init");
979 GObjectClass *gobject_class = (GObjectClass*)klass;
980 gobject_class->dispose = grits_plugin_gps_dispose;
981 gobject_class->finalize = grits_plugin_gps_finalize;