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.
30 #include <glib/gstdio.h>
38 #include "gps-plugin.h"
40 #include "../aweather-location.h"
42 /* interval to update map with new gps data in seconds. */
43 #define GPS_UPDATE_INTERVAL (2)
45 /* Filename and search path to use for gps marker, should be configurable */
46 #define GPS_MARKER_ICON_PATH ".:" PKGDATADIR
47 #define GPS_MARKER_ICON "arrow.png"
49 /* number of track points per group and number of groups to maintain */
50 #define NUM_TRACK_POINTS (6)
51 #define NUM_TRACK_GROUPS (4)
52 #define NUM_TRACK_POINTS_FACTOR (1.5)
54 /* interval to update log file in seconds (default value for slider) */
55 #define GPS_LOG_DEFAULT_UPDATE_INTERVAL (30)
56 #define GPS_LOG_EXT "csv"
58 /* For updating the status bar conveniently */
59 #define GPS_STATUSBAR_CONTEXT "GPS"
61 #define GPS_STATUS(gps, format, args...) \
63 gchar *buf = g_strdup_printf(format, ##args); \
64 gtk_statusbar_push(GTK_STATUSBAR(gps->status_bar), \
65 gtk_statusbar_get_context_id( \
66 GTK_STATUSBAR(gps->status_bar), \
67 GPS_STATUSBAR_CONTEXT), \
72 #define GPS_STATUS(gps, format, args...) \
74 gchar *buf = g_strdup_printf(format, ##args); \
75 g_debug("STATUS: %s", buf); \
79 static gboolean gps_data_is_valid(struct gps_data_t *gps_data);
80 static gchar *gps_get_time_string(time_t gps_time);
81 static gchar *gps_get_date_string(double gps_time);
82 static void process_gps( gpointer, gint, GdkInputCondition);
84 #ifdef GPS_RANGE_RINGS
85 static void gps_init_range_rings(GritsPluginGps *gps, GtkWidget *gbox);
86 static gboolean on_gps_rangering_clicked_event(GtkWidget *widget, gpointer user_data);
89 static void gps_init_status_info(GritsPluginGps *gps, GtkWidget *gbox);
90 static void gps_init_control_frame(GritsPluginGps *gps, GtkWidget *gbox);
91 static gboolean on_gps_follow_clicked_event(GtkWidget *widget, gpointer user_data);
93 /* GPS logging support */
94 static void gps_init_track_log_frame(GritsPluginGps *gps, GtkWidget *gbox);
95 static gboolean on_gps_log_clicked_event(GtkWidget *widget, gpointer user_data);
97 /* Track management */
98 static void gps_track_init(struct gps_track_t *track);
99 static void gps_track_free(struct gps_track_t *track);
100 static void gps_track_clear(struct gps_track_t *track);
101 static void gps_track_add_point(struct gps_track_t *track, gdouble lat,
102 gdouble lon, gdouble elevation);
103 static void gps_track_group_incr(struct gps_track_t *track);
105 static gboolean on_gps_track_enable_clicked_event(GtkWidget *widget,
107 static gboolean on_gps_track_clear_clicked_event(GtkWidget *widget,
109 static gboolean gps_write_log(gpointer data);
111 static gchar *gps_get_status(struct gps_data_t *);
112 static gchar *gps_get_latitude(struct gps_data_t *);
113 static gchar *gps_get_longitude(struct gps_data_t *);
114 static gchar *gps_get_elevation(struct gps_data_t *);
115 static gchar *gps_get_heading(struct gps_data_t *);
116 static gchar *gps_get_speed(struct gps_data_t *);
118 /* Describes a line in the gps table */
119 struct gps_status_info {
122 gchar *(*get_data)(struct gps_data_t *);
124 GtkWidget *label_widget;
125 GtkWidget *value_widget;
128 struct gps_status_info gps_table[] = {
129 {"Status:", "No Data", gps_get_status, 14, NULL, NULL},
130 // {"Online:", "No Data", gps_get_online, 14, NULL, NULL},
131 {"Latitude:", "No Data", gps_get_latitude, 14, NULL, NULL},
132 {"Longitude:", "No Data", gps_get_longitude, 14, NULL, NULL},
133 {"Elevation:", "No Data", gps_get_elevation, 14, NULL, NULL},
134 {"Heading:", "No Data", gps_get_heading, 14, NULL, NULL},
135 {"Speed:", "No Data", gps_get_speed, 14, NULL, NULL},
138 /* Find a readable file in a colon delimeted path */
139 static gchar *find_path(const gchar *path, const gchar *filename)
141 gchar *end_ptr, *fullpath;
143 end_ptr = (gchar *)path;
146 while (*end_ptr != ':' && *end_ptr != '\0')
148 fullpath = g_strdup_printf("%.*s/%s", (int)(end_ptr-path), path,
150 g_debug("GritsPluginGps: find_path - searching %s", fullpath);
151 if (access(fullpath, R_OK) == 0) {
152 g_debug("GritsPluginGps: find_path - found %s", fullpath);
153 return fullpath; /* caller frees */
158 if (*end_ptr == '\0') {
161 return find_path(end_ptr + 1, filename);
165 static gboolean gps_data_is_valid(struct gps_data_t *gps_data)
167 if (gps_data != NULL && gps_data->online != -1.0 &&
168 gps_data->fix.mode >= MODE_2D &&
169 gps_data->status > STATUS_NO_FIX) {
176 static gchar *gps_get_date_string(double gps_time)
178 static gchar buf[256];
179 time_t int_time = (time_t)gps_time;
182 gmtime_r(&int_time, &tm_time);
184 snprintf(buf, sizeof(buf), "%04d-%02d-%02d",
185 tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday);
190 static gchar *gps_get_time_string(time_t gps_time)
192 static gchar buf[256];
193 time_t int_time = (time_t)gps_time;
196 gmtime_r(&int_time, &tm_time);
198 snprintf(buf, sizeof(buf), "%02d:%02d:%02dZ",
199 tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec);
205 static void update_gps_status(GritsPluginGps *gps)
207 struct gps_data_t *gps_data = &gps->gps_data;
209 /* gps table update */
212 for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
213 gtk_label_set_markup (GTK_LABEL(gps_table[i].value_widget),
214 (str = gps_table[i].get_data(gps_data)));
219 static void gps_init_control_frame(GritsPluginGps *gps, GtkWidget *gbox)
221 /* Control checkboxes */
222 GtkWidget *gps_control_frame = gtk_frame_new("GPS Control");
223 GtkWidget *cbox = gtk_vbox_new(FALSE, 2);
224 gtk_container_add(GTK_CONTAINER(gps_control_frame), cbox);
225 gtk_box_pack_start(GTK_BOX(gbox), gps_control_frame, FALSE, FALSE, 0);
227 gps->ui.gps_follow_checkbox =
228 gtk_check_button_new_with_label("Follow GPS");
229 g_signal_connect(G_OBJECT(gps->ui.gps_follow_checkbox), "clicked",
230 G_CALLBACK (on_gps_follow_clicked_event),
232 gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_follow_checkbox,
235 gps->ui.gps_track_checkbox =
236 gtk_check_button_new_with_label("Record Track");
237 g_signal_connect(G_OBJECT(gps->ui.gps_track_checkbox), "clicked",
238 G_CALLBACK (on_gps_track_enable_clicked_event),
240 gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_track_checkbox,
243 gps->ui.gps_clear_button = gtk_button_new_with_label("Clear Track");
244 g_signal_connect(G_OBJECT(gps->ui.gps_clear_button), "clicked",
245 G_CALLBACK (on_gps_track_clear_clicked_event),
247 gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_clear_button,
251 static gboolean on_gps_track_enable_clicked_event(GtkWidget *widget,
254 GritsPluginGps *gps = (GritsPluginGps *)user_data;
256 g_debug("GritsPluginGps: track_enable_clicked_event");
258 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
259 /* start logging trip history */
260 GPS_STATUS(gps, "Enabled GPS track.");
261 gps->track.active = TRUE;
263 /* stop logging trip history */
264 GPS_STATUS(gps, "Disabled GPS track.");
265 gps->track.active = FALSE;
266 /* advance to the next track group, moving everything down if
269 gps_track_group_incr(&gps->track);
275 static gboolean on_gps_track_clear_clicked_event(GtkWidget *widget, gpointer user_data)
277 GritsPluginGps *gps = (GritsPluginGps *)user_data;
279 g_debug("GritsPluginGps: track_clear_clicked_event");
280 GPS_STATUS(gps, "Cleared GPS track.");
281 gps_track_clear(&gps->track);
286 static gboolean on_gps_log_interval_changed_event(GtkWidget *widget,
289 GritsPluginGps *gps = (GritsPluginGps *)user_data;
293 g_debug("GritsPluginGps: log_interval_changed_event - value = %f",
294 gtk_range_get_value(GTK_RANGE(widget)));
296 if (gtk_toggle_button_get_active(
297 GTK_TOGGLE_BUTTON(gps->ui.gps_log_checkbox))) {
298 g_assert(gps->ui.gps_log_timeout_id != 0);
300 /* disable old timeout */
301 g_source_remove(gps->ui.gps_log_timeout_id);
302 gps->ui.gps_log_timeout_id = 0;
304 /* Schedule new log file write */
305 gps->ui.gps_log_timeout_id = g_timeout_add(
306 gtk_range_get_value(GTK_RANGE(widget))*1000,
314 static gboolean gps_write_log(gpointer data)
316 GritsPluginGps *gps = (GritsPluginGps *)data;
317 struct gps_data_t *gps_data = &gps->gps_data;
321 gboolean new_file = FALSE;
323 if (gps_data == NULL) {
324 g_warning("Skipped write to GPS log file: "
325 "can not get GPS coordinates.");
326 GPS_STATUS(gps, "Skipped write to GPS log file: "
327 "can not get GPS coordinates.");
331 /* get filename from text entry box. If empty, generate a name from
332 * the date and time and set it.
334 if (strlen(gtk_entry_get_text(
335 GTK_ENTRY(gps->ui.gps_log_filename_entry))) == 0) {
336 snprintf(filename, sizeof(filename),
338 gps_get_date_string(gps->gps_data.fix.time),
339 gps_get_time_string(gps->gps_data.fix.time),
341 gtk_entry_set_text(GTK_ENTRY(gps->ui.gps_log_filename_entry),
346 gtk_entry_get_text(GTK_ENTRY(gps->ui.gps_log_filename_entry)),
349 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
353 if ((fd = open(filename, O_CREAT|O_APPEND|O_WRONLY, 0644)) == -1) {
354 g_warning("Error opening log file %s: %s",
355 filename, strerror(errno));
360 /* write header and reset record counter */
361 snprintf(buf, sizeof(buf),
362 "No,Date,Time,Lat,Lon,Ele,Head,Speed,RTR\n");
363 if (write(fd, buf, strlen(buf)) == -1) {
364 g_warning("Error writing header to log file %s: %s",
365 filename, strerror(errno));
367 gps->ui.gps_log_number = 1;
370 /* Write log entry. Make sure this matches the header */
371 /* "No,Date,Time,Lat,Lon,Ele,Head,Speed,Fix,RTR\n" */
372 /* RTR values: T=time, B=button push, S=speed, D=distance */
373 snprintf(buf, sizeof(buf), "%d,%s,%s,%lf,%lf,%lf,%lf,%lf,%c\n",
374 gps->ui.gps_log_number++,
375 gps_get_date_string(gps->gps_data.fix.time),
376 gps_get_time_string(gps->gps_data.fix.time),
377 // gps_data->fix.time,
378 gps_data->fix.latitude,
379 gps_data->fix.longitude,
380 gps_data->fix.altitude * METERS_TO_FEET,
382 gps_data->fix.speed * METERS_TO_FEET,
383 'T'); /* position due to timer expired */
385 if (write(fd, buf, strlen(buf)) == -1) {
386 g_warning("Could not write log number %d to log file %s: %s",
387 gps->ui.gps_log_number-1, filename, strerror(errno));
391 GPS_STATUS(gps, "Updated GPS log file %s.", filename);
397 static gboolean on_gps_follow_clicked_event (GtkWidget *widget, gpointer user_data)
399 GritsPluginGps *gps = (GritsPluginGps *)user_data;
401 g_debug("GritsPluginGps: follow_clicked_event - button status %d",
402 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
403 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
404 gps->follow_gps = TRUE;
406 gps->follow_gps = FALSE;
413 static void gps_init_track_log_frame(GritsPluginGps *gps, GtkWidget *gbox)
415 /* Track log box with enable checkbox and filename entry */
416 GtkWidget *gps_log_frame = gtk_frame_new ("Track Log");
417 GtkWidget *lbox = gtk_vbox_new (FALSE, 2);
418 gtk_container_add (GTK_CONTAINER (gps_log_frame), lbox);
419 gtk_box_pack_start (GTK_BOX(gbox), gps_log_frame,
422 gps->ui.gps_log_checkbox =
423 gtk_check_button_new_with_label("Log Position to File");
424 g_signal_connect (G_OBJECT (gps->ui.gps_log_checkbox), "clicked",
425 G_CALLBACK (on_gps_log_clicked_event),
427 gtk_box_pack_start (GTK_BOX(lbox), gps->ui.gps_log_checkbox,
430 /* Set up filename entry box */
431 GtkWidget *fbox = gtk_hbox_new (FALSE, 2);
432 GtkWidget *filename_label = gtk_label_new ("Filename:");
433 gtk_box_pack_start (GTK_BOX(fbox), filename_label, FALSE, FALSE, 0);
434 gps->ui.gps_log_filename_entry = gtk_entry_new();
435 gtk_box_pack_start (GTK_BOX(fbox), gps->ui.gps_log_filename_entry,
437 gtk_box_pack_start (GTK_BOX(lbox), fbox, FALSE, FALSE, 0);
439 /* set up gps log interval slider */
440 GtkWidget *ubox = gtk_hbox_new (FALSE, 4);
441 GtkWidget *interval_label = gtk_label_new ("Update Interval:");
442 gtk_box_pack_start (GTK_BOX(ubox), interval_label, FALSE, FALSE, 0);
443 gps->ui.gps_log_interval_slider =
444 gtk_hscale_new_with_range(1.0, 600.0, 30.0);
445 gtk_range_set_value (GTK_RANGE(gps->ui.gps_log_interval_slider),
446 GPS_LOG_DEFAULT_UPDATE_INTERVAL);
447 g_signal_connect (G_OBJECT (gps->ui.gps_log_interval_slider),
449 G_CALLBACK(on_gps_log_interval_changed_event),
451 gtk_range_set_increments(
452 GTK_RANGE(gps->ui.gps_log_interval_slider),
453 10.0 /* step */, 30.0 /* page up/down */);
454 gtk_range_set_update_policy(
455 GTK_RANGE(gps->ui.gps_log_interval_slider),
457 gtk_box_pack_start (GTK_BOX(ubox), gps->ui.gps_log_interval_slider,
459 gtk_box_pack_start (GTK_BOX(lbox), ubox, FALSE, FALSE, 0);
462 static gboolean on_gps_log_clicked_event (GtkWidget *widget, gpointer user_data)
464 GritsPluginGps *gps = (GritsPluginGps *)user_data;
466 g_debug("GritsPluginGps: log_clicked_event");
468 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) {
471 /* Schedule log file write */
472 gps->ui.gps_log_timeout_id = g_timeout_add(
474 GTK_RANGE(gps->ui.gps_log_interval_slider))*1000,
477 /* button unchecked */
478 g_source_remove(gps->ui.gps_log_timeout_id);
479 gps->ui.gps_log_timeout_id = 0;
480 g_debug("GritsPluginGps: log_clicked_event - closed log file.");
487 static void gps_init_status_info(GritsPluginGps *gps, GtkWidget *gbox)
489 gps->ui.gps_status_frame = gtk_frame_new("GPS Data");
490 gps->ui.gps_status_table = gtk_table_new(5, 2, TRUE);
491 gtk_container_add(GTK_CONTAINER (gps->ui.gps_status_frame),
492 gps->ui.gps_status_table);
494 /* gps data table setup */
496 for (i = 0; i < sizeof(gps_table)/sizeof(*gps_table); i++) {
497 gps_table[i].label_widget = gtk_label_new (gps_table[i].label);
498 gtk_label_set_justify(GTK_LABEL(gps_table[i].label_widget),
500 gtk_table_attach(GTK_TABLE(gps->ui.gps_status_table),
501 gps_table[i].label_widget,
502 0, 1, i, i+1, 0, 0, 0, 0);
503 gps_table[i].value_widget = gtk_label_new(gps_table[i].initial_val);
504 gtk_table_attach( GTK_TABLE(gps->ui.gps_status_table),
505 gps_table[i].value_widget, 1, 2, i, i+1, 0, 0, 0, 0);
507 PangoFontDescription *font_desc = pango_font_description_new ();
508 pango_font_description_set_size (font_desc,
509 gps_table[i].font_size*PANGO_SCALE);
510 gtk_widget_modify_font (gps_table[i].label_widget, font_desc);
511 gtk_widget_modify_font (gps_table[i].value_widget, font_desc);
512 pango_font_description_free (font_desc);
514 gtk_box_pack_start(GTK_BOX(gbox), gps->ui.gps_status_frame,
517 /* Start UI refresh task, which will reschedule itgps. */
519 gps->gps_update_timeout_id = g_timeout_add(
520 GPS_UPDATE_INTERVAL*1000,
521 gps_redraw_all, gps);
526 #ifdef GPS_RANGE_RINGS
527 static void gps_init_range_rings(GritsPluginGps *gps, GtkWidget *gbox)
529 GtkWidget *gps_range_ring_frame = gtk_frame_new("Range Rings");
530 GtkWidget *cbox = gtk_vbox_new(FALSE, 2);
531 gtk_container_add(GTK_CONTAINER(gps_range_ring_frame), cbox);
532 gtk_box_pack_start(GTK_BOX(gbox), gps_range_ring_frame, FALSE, FALSE, 0);
534 gps->ui.gps_rangering_checkbox = gtk_check_button_new_with_label("Enable Range Rings");
535 g_signal_connect(G_OBJECT(gps->ui.gps_rangering_checkbox),
536 "clicked", G_CALLBACK(on_gps_rangering_clicked_event),
538 gtk_box_pack_start(GTK_BOX(cbox), gps->ui.gps_rangering_checkbox,
542 static gboolean on_gps_rangering_clicked_event(GtkWidget *widget, gpointer user_data)
544 GritsPluginGps *gps = (GritsPluginGps *)user_data;
546 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
547 gps->gps_rangering_active = TRUE;
549 gps->gps_rangering_active = FALSE;
552 /* XXX force a redraw */
556 #endif /* GPS_RANGE_RINGS */
558 /* external interface to update UI from latest GPS data. */
559 gboolean gps_redraw_all(gpointer data)
561 GritsPluginGps *gps = (GritsPluginGps *)data;
564 struct gps_data_t *gps_data = &gps->gps_data;
566 g_debug("GritsPluginGps: redraw_all");
569 if (!gps_data_is_valid(gps_data)) {
570 g_debug("GritsPluginGps: redraw_all - gps_data is not valid.");
571 /* XXX Change marker to indicate data is not valid */
575 /* update position labels */
576 update_gps_status(gps);
578 /* Update track and marker position */
579 if (gps_data_is_valid(gps_data) && gps->track.active) {
580 g_debug("GritsPluginGps: redraw_all - updating track group %u "
581 "point %u at lat = %f, long = %f, track = %f",
582 gps->track.cur_group,
583 gps->track.cur_point,
584 gps_data->fix.latitude,
585 gps_data->fix.longitude,
586 gps_data->fix.track);
588 gps_track_add_point(&gps->track,
589 gps_data->fix.latitude, gps_data->fix.longitude, 0.0);
591 if (gps->track.line) {
592 grits_viewer_remove(gps->viewer,
593 GRITS_OBJECT(gps->track.line));
594 gps->track.line = NULL;
597 gps->track.line = grits_line_new(gps->track.points);
598 gps->track.line->color[0] = 1.0;
599 gps->track.line->color[1] = 0;
600 gps->track.line->color[2] = 0.1;
601 gps->track.line->color[3] = 0.5;
602 gps->track.line->width = 3;
604 grits_viewer_add(gps->viewer, GRITS_OBJECT(gps->track.line),
605 GRITS_LEVEL_OVERLAY, TRUE);
606 grits_object_queue_draw(GRITS_OBJECT(gps->track.line));
609 if (gps_data_is_valid(gps_data)) {
611 grits_viewer_remove(gps->viewer,
612 GRITS_OBJECT(gps->marker));
616 gchar *path = find_path(GPS_MARKER_ICON_PATH, GPS_MARKER_ICON);
618 gps->marker = grits_marker_icon_new("GPS", path,
619 gps_data->fix.track, TRUE, MARKER_DMASK_ICON);
622 /* if icon not found just use a point */
623 g_warning("Could not find GPS marker icon %s in path %s.",
624 GPS_MARKER_ICON, GPS_MARKER_ICON_PATH);
625 gps->marker = grits_marker_icon_new("GPS", NULL,
626 gps_data->fix.track, FALSE,
627 MARKER_DMASK_POINT|MARKER_DMASK_LABEL);
630 GRITS_OBJECT(gps->marker)->center.lat = gps_data->fix.latitude;
631 GRITS_OBJECT(gps->marker)->center.lon = gps_data->fix.longitude;
632 GRITS_OBJECT(gps->marker)->center.elev = 0.0;
633 GRITS_OBJECT(gps->marker)->lod = EARTH_R;
635 grits_viewer_add(gps->viewer,
636 GRITS_OBJECT(gps->marker),
637 GRITS_LEVEL_OVERLAY, TRUE);
638 grits_object_queue_draw(GRITS_OBJECT(gps->marker));
641 if (gps->follow_gps && gps_data_is_valid(gps_data)) {
642 /* Center map at current GPS position. */
643 g_debug("GritsPluginGps: redraw_all - centering map at "
644 "lat = %f, long = %f, track = %f",
645 gps_data->fix.latitude,
646 gps_data->fix.longitude,
647 gps_data->fix.track);
649 double lat, lon, elev;
650 grits_viewer_get_location(gps->viewer, &lat, &lon, &elev);
651 grits_viewer_set_location(gps->viewer, gps_data->fix.latitude,
652 gps_data->fix.longitude, elev);
653 //grits_viewer_set_rotation(gps->viewer, 0, 0, 0);
661 /******************* Track handling routines *****************/
663 static void gps_track_init(struct gps_track_t *track)
665 /* Save a spot at the end for the NULL termination */
666 track->points = (gpointer)g_new0(double*, NUM_TRACK_GROUPS + 1);
667 track->cur_point = 0;
668 track->cur_group = 0;
669 track->num_points = 1; /* starts at 1 so realloc logic works */
673 static void gps_track_clear(struct gps_track_t *track)
676 for (pi = 0; pi < NUM_TRACK_GROUPS; pi++) {
677 if (track->points[pi] != NULL) {
678 g_free(track->points[pi]);
679 track->points[pi] = NULL;
682 track->cur_point = 0;
683 track->cur_group = 0;
684 track->num_points = 1; /* starts at 1 so realloc logic works */
687 static void gps_track_free(struct gps_track_t *track)
689 gps_track_clear(track);
690 g_free(track->points);
693 /* add a new track group (points in a track group are connected, and
694 * separated from points in other track groups).
696 static void gps_track_group_incr(struct gps_track_t *track)
698 gdouble (**points)[3] = track->points; /* for simplicity */
700 /* Just return if they increment it again before any points have
703 if (points[track->cur_group] == NULL) {
707 g_debug("GritsPluginGps: track_group_incr - track group %u->%u.",
708 track->cur_group, track->cur_group + 1);
711 track->cur_point = 0;
712 track->num_points = 1; /* starts at 1 so realloc logic works */
714 if (track->cur_group >= NUM_TRACK_GROUPS) {
715 g_debug("GritsPluginGps: track_group_incr - track group %u "
716 "is at max %u, shifting groups",
717 track->cur_group, NUM_TRACK_GROUPS);
719 /* Free the oldest one which falls off the end */
722 /* shift the rest down, last one should be NULL already */
723 /* note we alloc NUM_TRACK_GROUPS+1 */
724 for (int pi = 0; pi < NUM_TRACK_GROUPS; pi++) {
725 points[pi] = points[pi+1];
728 /* always write into the last group */
729 track->cur_group = NUM_TRACK_GROUPS - 1;
733 static void gps_track_add_point(struct gps_track_t *track,
734 gdouble lat, gdouble lon, gdouble elevation)
736 gdouble (**points)[3] = track->points; /* for simplicity */
738 g_debug("GritsPluginGps: track_add_point");
740 g_assert(track->cur_group < NUM_TRACK_GROUPS &&
741 (track->cur_point <= track->num_points));
743 /* resize/allocate the point group if the current one is full */
744 if (track->cur_point >= track->num_points - 1) {
745 guint new_size = track->num_points == 1 ?
747 track->num_points * NUM_TRACK_POINTS_FACTOR;
748 g_debug("GritsPluginGps: track_add_point - reallocating points "
749 "array from %u points to %u points.\n",
750 track->num_points, new_size);
751 points[track->cur_group] = (gpointer)g_renew(gdouble,
752 points[track->cur_group], 3*(new_size+1));
753 track->num_points = new_size;
756 g_assert(points[track->cur_group] != NULL);
758 /* Add the coordinate */
759 lle2xyz(lat, lon, elevation,
760 &points[track->cur_group][track->cur_point][0],
761 &points[track->cur_group][track->cur_point][1],
762 &points[track->cur_group][track->cur_point][2]);
766 /* make sure last point is always 0s so the line drawing stops. */
767 points[track->cur_group][track->cur_point][0] = 0.0;
768 points[track->cur_group][track->cur_point][1] = 0.0;
769 points[track->cur_group][track->cur_point][2] = 0.0;
773 static gchar *gps_get_status(struct gps_data_t *gps_data)
778 switch (gps_data->fix.mode) {
780 status_color = "red";
781 status_text = "No Signal";
784 status_color = "red";
785 status_text = "No Fix";
788 status_color = "yellow";
789 status_text = "2D Mode";
792 status_color = "green";
793 status_text = "3D Mode";
796 status_color = "black";
797 status_text = "Unknown";
800 return g_strdup_printf("<span foreground=\"%s\">%s</span>",
801 status_color, status_text);
805 static gchar *gps_get_online(struct gps_data_t *);
807 static gchar *gps_get_online(struct gps_data_t *gps_data)
812 if (gps_data->online == -1.0) {
813 online_str = "Offline";
815 online_str = "Online";
818 switch (gps_data->status) {
820 status_str = "No Fix";
823 status_str = "Fix Acquired";
826 status_str = "DGPS Fix";
829 status_str = "Unknown Status";
833 return g_strdup_printf("%lf,%s,%s", gps_data->online, online_str, status_str);
839 static gchar *gps_get_latitude(struct gps_data_t *gps_data)
841 return g_strdup_printf("%3.4f", gps_data->fix.latitude);
844 static gchar *gps_get_longitude(struct gps_data_t *gps_data)
846 return g_strdup_printf("%3.4f", gps_data->fix.longitude);
849 static gchar *gps_get_elevation(struct gps_data_t *gps_data)
851 /* XXX Make units (m/ft) settable */
852 return g_strdup_printf("%.1lf %s",
853 (gps_data->fix.altitude * METERS_TO_FEET), "ft");
856 static gchar *gps_get_heading(struct gps_data_t *gps_data)
858 /* XXX Make units (m/ft) settable */
859 return g_strdup_printf("%03.0lf", gps_data->fix.track);
862 static gchar *gps_get_speed(struct gps_data_t *gps_data)
864 /* XXX Make units (m/ft) settable */
865 return g_strdup_printf("%1.1f %s",
866 (gps_data->fix.speed*3600.0*METERS_TO_FEET/5280.0), "mph");
870 static gint initialize_gpsd(char *server, gchar *port,
871 struct gps_data_t *gps_data)
873 #if GPSD_API_MAJOR_VERSION < 5
874 #error "GPSD protocol version 5 or greater required."
878 if ((result = gps_open(server, port, gps_data)) != 0) {
879 g_warning("Unable to open gpsd connection to %s:%s: %d, %d, %s",
880 server, port, result, errno, gps_errstr(errno));
882 (void)gps_stream(gps_data, WATCH_ENABLE|WATCH_JSON, NULL);
883 g_debug("GritsPluginGps: initialize_gpsd - gpsd fd %u.",
885 gdk_input_add(gps_data->gps_fd, GDK_INPUT_READ, process_gps, gps_data);
891 static void process_gps(gpointer data, gint source, GdkInputCondition condition)
893 struct gps_data_t *gps_data = (struct gps_data_t *)data;
895 g_debug("GritsPluginGps: process_gps");
897 /* Process any data from the gps and call the hook function */
898 if (gps_data != NULL) {
899 gint result = gps_read(gps_data);
900 g_debug("GritsPluginGps: process_gps - gps_read returned %d, "
901 "position %f, %f.", result,
902 gps_data->fix.latitude, gps_data->fix.longitude);
904 g_warning("GritsPluginGps: process_gps - gps_data == NULL.");
908 /************************** GPS Object Methods *************************/
911 GritsPluginGps *grits_plugin_gps_new(GritsViewer *viewer, GritsPrefs *prefs)
913 /* TODO: move to constructor if possible */
914 g_debug("GritsPluginGps: new");
915 GritsPluginGps *gps = g_object_new(GRITS_TYPE_PLUGIN_GPS, NULL);
916 gps->viewer = viewer;
919 initialize_gpsd("localhost", DEFAULT_GPSD_PORT, &gps->gps_data);
920 gps->follow_gps = FALSE;
922 gps_track_init(&gps->track);
923 gps_init_status_info(gps, gps->hbox);
924 gps_init_control_frame(gps, gps->hbox);
925 gps_init_track_log_frame(gps, gps->hbox);
926 #ifdef GPS_RANGE_RINGS
927 gps_init_range_rings(gps, gps->hbox);
934 static GtkWidget *grits_plugin_gps_get_config(GritsPlugin *_gps)
936 GritsPluginGps *gps = GRITS_PLUGIN_GPS(_gps);
941 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface);
942 G_DEFINE_TYPE_WITH_CODE(GritsPluginGps, grits_plugin_gps, G_TYPE_OBJECT,
943 G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
944 grits_plugin_gps_plugin_init));
946 static void grits_plugin_gps_plugin_init(GritsPluginInterface *iface)
948 g_debug("GritsPluginGps: plugin_init");
949 /* Add methods to the interface */
950 iface->get_config = grits_plugin_gps_get_config;
953 static void grits_plugin_gps_init(GritsPluginGps *gps)
955 g_debug("GritsPluginGps: gps_init");
957 gps->config = gtk_notebook_new();
959 gps->hbox = gtk_hbox_new(FALSE, 2);
960 gtk_notebook_insert_page(GTK_NOTEBOOK(gps->config),
961 GTK_WIDGET(gps->hbox),
962 gtk_label_new("GPS"), 0);
963 /* Need to position on the top because of Win32 bug */
964 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(gps->config), GTK_POS_LEFT);
967 static void grits_plugin_gps_dispose(GObject *gobject)
969 GritsPluginGps *gps = GRITS_PLUGIN_GPS(gobject);
971 g_debug("GritsPluginGps: dispose");
975 grits_viewer_remove(gps->viewer,
976 GRITS_OBJECT(gps->marker));
978 g_object_unref(gps->viewer);
982 gps_track_free(&gps->track);
984 /* Drop references */
985 G_OBJECT_CLASS(grits_plugin_gps_parent_class)->dispose(gobject);
988 static void grits_plugin_gps_finalize(GObject *gobject)
990 GritsPluginGps *gps = GRITS_PLUGIN_GPS(gobject);
992 g_debug("GritsPluginGps: finalize");
995 gtk_widget_destroy(gps->config);
996 G_OBJECT_CLASS(grits_plugin_gps_parent_class)->finalize(gobject);
999 static void grits_plugin_gps_class_init(GritsPluginGpsClass *klass)
1001 g_debug("GritsPluginGps: class_init");
1002 GObjectClass *gobject_class = (GObjectClass*)klass;
1003 gobject_class->dispose = grits_plugin_gps_dispose;
1004 gobject_class->finalize = grits_plugin_gps_finalize;