/* * FAI/IGC data format translation. * * Refer to Appendix 1 of * http://www.fai.org:81/gliding/gnss/tech_spec_gnss.asp for the * specification of the IGC data format. This translation code was * written when the latest ammendment list for the specification was AL6. * * Copyright (C) 2004 Chris Jones * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111 USA */ #include "defs.h" #include static gbfile* file_in, *file_out; static char manufacturer[4]; static const route_head* head; static char* timeadj = NULL; static int lineno; #define MYNAME "IGC" #define MAXRECLEN 79 // Includes null terminator and CR/LF #define MAXDESCLEN 1024 #define PRESTRKNAME "PRESALTTRK" #define GNSSTRKNAME "GNSSALTTRK" #define HDRMAGIC "IGCHDRS" #define HDRDELIM "~" #define DATEMAGIC "IGCDATE" /* * IGC record types. * These appear as the first char in each record. */ typedef enum { rec_manuf_id = 'A', // FR manufacturer and identification rec_fix = 'B', // Fix rec_task = 'C', // Task/declaration rec_diff_gps = 'D', // Differential GPS rec_event = 'E', // Event rec_constel = 'F', // Constellation rec_security = 'G', // Security rec_header = 'H', // File header rec_fix_defn = 'I', // List of extension data included at end of each fix (B) record rec_extn_defn = 'J', // List of data included in each extension (K) record rec_extn_data = 'K', // Extension data rec_log_book = 'L', // Logbook/comments // M..Z are spare rec_none = 0, // No record rec_bad = 1, // Bad record } igc_rec_type_t; /* * See if two lat/lon pairs are approximately equal. * @param lat1 The latitude of coordinate pair 1 * @param lon1 The longitude of coordinate pair 1 * @param lat2 The latitude of coordinate pair 2 * @param lon2 The longitude of coordinate pair 2 * @retval 1 The coordinates are approximately equal * @retval 0 The coordinates are significantly different */ static unsigned char coords_match(double lat1, double lon1, double lat2, double lon2) { return (fabs(lat1 - lat2) < 0.0001 && fabs(lon1 - lon2) < 0.0001) ? 1 : 0; } /************************************************************************************************* * Input file processing */ /* * Get an IGC record from the input file * @param rec Caller allocated storage for the record. At least MAXRECLEN chars must be allocated. * @return the record type. rec_none on EOF, rec_bad on fgets() or parse error. */ static igc_rec_type_t get_record(char** rec) { size_t len; char* c; retry: *rec = c = gbfgetstr(file_in); if ((lineno++ == 0) && file_in->unicode) { cet_convert_init(CET_CHARSET_UTF8, 1); } if (c == NULL) { return rec_none; } len = strlen(c); /* Trackwiev writes (bogus) blank links between each record */ if (len == 0) { goto retry; } if (len < 3 || c[0] < 'A' || c[0] > 'Z') { warning(MYNAME " bad input record: '%s'\n", c); return rec_bad; } return (igc_rec_type_t) c[0]; } static void rd_init(const char* fname) { char* ibuf; file_in = gbfopen(fname, "r", MYNAME); lineno = 0; // File must begin with a manufacturer/ID record if (get_record(&ibuf) != rec_manuf_id || sscanf(ibuf, "A%3[A-Z]", manufacturer) != 1) { fatal(MYNAME ": %s is not an IGC file\n", fname); } } static void rd_deinit(void) { gbfclose(file_in); } typedef enum { id, takeoff, start, turnpoint, finish, landing } state_t; #if __cplusplus inline state_t operator++(state_t& rs, int) { return rs = (state_t)((int)rs + 1); } #endif /** * Handle pre- or post-flight task declarations. * A route is created for each set of waypoints in a task declaration. * @param rec A single task record */ static void igc_task_rec(const char* rec) { static char flight_date[7]; static unsigned int num_tp, tp_ct; static route_head* rte_head; static time_t creation; char task_num[5]; char task_desc[MAXRECLEN]; waypoint* wpt; unsigned int lat_deg, lat_min, lat_frac; unsigned int lon_deg, lon_min, lon_frac; char lat_hemi[2], lon_hemi[2]; char short_name[8]; char tmp_str[MAXRECLEN]; struct tm tm; static state_t state = id; // First task record identifies the task to follow if (id == state) { task_desc[0] = '\0'; if (sscanf(rec, "C%2u%2u%2u%2u%2u%2u%6[0-9]%4c%2u%[^\r]\r\n", &tm.tm_mday, &tm.tm_mon, &tm.tm_year, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, flight_date, task_num, &num_tp, task_desc) < 9) { fatal(MYNAME ": task id (C) record parse error\n'%s'", rec); } task_num[4] = '\0'; tm.tm_mon -= 1; if (tm.tm_year < 70) { tm.tm_year += 100; } tm.tm_isdst = 0; creation = mkgmtime(&tm); // Create a route to store the task data in. rte_head = route_head_alloc(); rte_head->rte_name = xstrdup(task_num); sprintf(tmp_str, DATEMAGIC "%s: %s", flight_date, task_desc); rte_head->rte_desc = xstrdup(tmp_str); route_add_head(rte_head); state++; return; } // Get the waypoint tmp_str[0] = '\0'; if (sscanf(rec, "C%2u%2u%3u%1[NS]%3u%2u%3u%1[WE]%[^\r]\r\n", &lat_deg, &lat_min, &lat_frac, lat_hemi, &lon_deg, &lon_min, &lon_frac, lon_hemi, tmp_str) < 8) { fatal(MYNAME ": task waypoint (C) record parse error\n%s", rec); } wpt = waypt_new(); wpt->latitude = ('N' == lat_hemi[0] ? 1 : -1) * (lat_deg + (lat_min * 1000 + lat_frac) / 1000.0 / 60); wpt->longitude = ('E' == lon_hemi[0] ? 1 : -1) * (lon_deg + (lon_min * 1000 + lon_frac) / 1000.0 / 60); wpt->creation_time = creation; wpt->description = xstrdup(tmp_str); // Name the waypoint according to the order of the task record switch (state) { case takeoff: snprintf(short_name, 8, "TAKEOFF"); state++; break; case start: snprintf(short_name, 8, "START"); tp_ct = 0; state++; break; case turnpoint: if (++tp_ct == num_tp) { state++; } snprintf(short_name, 8, "TURN%02u", tp_ct); break; case finish: snprintf(short_name, 8, "FINISH"); state++; break; case landing: snprintf(short_name, 8, "LANDING"); state = id; break; default: fatal(MYNAME ": task id (C) record internal error\n%s", rec); break; } // Zero lat and lon indicates an unknown waypoint if (coords_match(wpt->latitude, wpt->longitude, 0.0, 0.0)) { waypt_free(wpt); return; } wpt->shortname = xstrdup(short_name); route_add_wpt(rte_head, wpt); } static void data_read(void) { char* ibuf; igc_rec_type_t rec_type; unsigned int hours, mins, secs; unsigned int lat_deg, lat_min, lat_frac; unsigned int lon_deg, lon_min, lon_frac; char lat_hemi[2], lon_hemi[2]; char validity; route_head* pres_head = NULL; route_head* gnss_head = NULL; int pres_alt, gnss_alt; char pres_valid = 0; char gnss_valid = 0; waypoint* pres_wpt = NULL; waypoint* gnss_wpt = NULL; time_t date = 0; time_t prev_tod = 0; time_t tod; struct tm tm; char tmp_str[20]; char* hdr_data; size_t remain; char trk_desc[MAXDESCLEN + 1]; strcpy(trk_desc, HDRMAGIC HDRDELIM); while (1) { rec_type = get_record(&ibuf); switch (rec_type) { case rec_manuf_id: // Manufacturer/ID record already found in rd_init(). warning(MYNAME ": duplicate manufacturer/ID record\n"); break; case rec_header: // Get the header sub type if (sscanf(ibuf, "H%*1[FOP]%3s", tmp_str) != 1) { fatal(MYNAME ": header (H) record parse error\n%s\n%s\n", ibuf, tmp_str); } // Optional long name of record sub type is followed by a // colon. Actual header data follows that. if (NULL == (hdr_data = strchr(ibuf, ':'))) { hdr_data = ibuf + 5; } else { hdr_data++; } // Date sub type if (strcmp(tmp_str, "DTE") == 0) { if (sscanf(hdr_data, "%2u%2u%2u", &tm.tm_mday, &tm.tm_mon, &tm.tm_year) != 3) { fatal(MYNAME ": date (H) record parse error\n'%s'\n", ibuf); } tm.tm_sec = tm.tm_min = tm.tm_hour = 0; tm.tm_mon -= 1; if (tm.tm_year < 70) { tm.tm_year += 100; } tm.tm_isdst = 0; date = mkgmtime(&tm); } else { // Store other header data in the track descriptions if (strlen(trk_desc) < MAXDESCLEN) { strcat(ibuf, HDRDELIM); remain = MAXDESCLEN - strlen(trk_desc); strncat(trk_desc, ibuf, remain); } } break; case rec_fix: // Date must appear in file before the first fix record if (date < 1000000L) { fatal(MYNAME ": bad date %d\n", (int)date); } // Create a track for pressure altitude waypoints if (!pres_head) { pres_head = route_head_alloc(); pres_head->rte_name = xstrdup(PRESTRKNAME); pres_head->rte_desc = xstrdup(trk_desc); track_add_head(pres_head); } // Create a second track for GNSS altitude waypoints if (!gnss_head) { gnss_head = route_head_alloc(); gnss_head->rte_name = xstrdup(GNSSTRKNAME); gnss_head->rte_desc = xstrdup(trk_desc); track_add_head(gnss_head); } // Create a waypoint from the fix record data if (sscanf(ibuf, "B%2u%2u%2u%2u%2u%3u%1[NS]%3u%2u%3u%1[WE]%c%5d%5d", &hours, &mins, &secs, &lat_deg, &lat_min, &lat_frac, lat_hemi, &lon_deg, &lon_min, &lon_frac, lon_hemi, &validity, &pres_alt, &gnss_alt) != 14) { fatal(MYNAME ": fix (B) record parse error\n%s\n", ibuf); } pres_wpt = waypt_new(); pres_wpt->latitude = ('N' == lat_hemi[0] ? 1 : -1) * (lat_deg + (lat_min * 1000 + lat_frac) / 1000.0 / 60); pres_wpt->longitude = ('E' == lon_hemi[0] ? 1 : -1) * (lon_deg + (lon_min * 1000 + lon_frac) / 1000.0 / 60); // Increment date if we pass midnight UTC tod = (hours * 60 + mins) * 60 + secs; if (tod < prev_tod) { date += 24 * 60 * 60; } prev_tod = tod; pres_wpt->creation_time = date + tod; // Add the waypoint to the pressure altitude track if (pres_alt) { pres_valid = 1; pres_wpt->altitude = pres_alt; } else { pres_wpt->altitude = unknown_alt; } track_add_wpt(pres_head, pres_wpt); // Add the same waypoint with GNSS altitude to the second // track gnss_wpt = waypt_dupe(pres_wpt); if (gnss_alt) { gnss_valid = 1; gnss_wpt->altitude = gnss_alt; } else { gnss_wpt->altitude = unknown_alt; } track_add_wpt(gnss_head, gnss_wpt); break; case rec_task: // Create a route for each pre-flight declaration igc_task_rec(ibuf); break; case rec_log_book: // Get the log book sub type if (sscanf(ibuf, "L%3s", tmp_str) != 1) { fatal(MYNAME ": log book (L) record parse error\n'%s'\n", ibuf); } if (strcmp(tmp_str, "PFC") == 0) { // Create a route for each post-flight declaration igc_task_rec(ibuf + 4); break; } else if (global_opts.debug_level) { if (strcmp(tmp_str, "OOI") == 0) { fputs(MYNAME ": Observer Input> ", stdout); } else if (strcmp(tmp_str, "PLT") == 0) { fputs(MYNAME ": Pilot Input> ", stdout); } else if (strcmp(tmp_str, manufacturer) == 0) { fputs(MYNAME ": Manufacturer Input> ", stdout); } else { fputs(MYNAME ": Anonymous Input> ", stdout); fputs(ibuf + 1, stdout); break; } fputs(ibuf + 4, stdout); putchar('\n'); } break; // These record types are discarded case rec_diff_gps: case rec_event: case rec_constel: case rec_security: case rec_fix_defn: case rec_extn_defn: case rec_extn_data: break; // No more records case rec_none: // Include pressure altitude track only if it has useful // altitude data or if it is the only track available. if (pres_head && !pres_valid && gnss_head) { track_del_head(pres_head); pres_head = NULL; } // Include GNSS altitude track only if it has useful altitude // data or if it is the only track available. if (gnss_head && !gnss_valid && pres_head) { track_del_head(gnss_head); } return; // All done so bail default: case rec_bad: fatal(MYNAME ": failure reading file\n"); break; } } } /************************************************************************************************* * Output file processing */ /************************************************* * Callbacks used to scan for specific track types */ static void detect_pres_track(const route_head* rh) { if (rh->rte_name && strncmp(rh->rte_name, PRESTRKNAME, 6) == 0) { head = rh; } } static void detect_gnss_track(const route_head* rh) { if (rh->rte_name && strncmp(rh->rte_name, GNSSTRKNAME, 6) == 0) { head = rh; } } static void detect_other_track(const route_head* rh) { static int max_waypt_ct; if (!head) { max_waypt_ct = 0; } // Find other track with the most waypoints if (rh->rte_waypt_ct > max_waypt_ct && (!rh->rte_name || (strncmp(rh->rte_name, PRESTRKNAME, 6) != 0 && strncmp(rh->rte_name, GNSSTRKNAME, 6) != 0))) { head = rh; max_waypt_ct = rh->rte_waypt_ct; } } /* * Identify the pressure altitude and GNSS altitude tracks. * @param pres_track Set by the function to the pressure altitude track * head. NULL if not found. * @param gnss_track Set by the function to the GNSS altitude track * head. NULL if not found. */ static void get_tracks(const route_head** pres_track, const route_head** gnss_track) { head = NULL; track_disp_all(detect_pres_track, NULL, NULL); *pres_track = head; head = NULL; track_disp_all(detect_gnss_track, NULL, NULL); *gnss_track = head; head = NULL; track_disp_all(detect_other_track, NULL, NULL); if (!*pres_track && *gnss_track && head) { *pres_track = head; } if (!*gnss_track && head) { *gnss_track = head; } } /************************************************* * IGC string formatting functions */ static char* latlon2str(const waypoint* wpt) { static char str[18] = ""; char lat_hemi = wpt->latitude < 0 ? 'S' : 'N'; char lon_hemi = wpt->longitude < 0 ? 'W' : 'E'; unsigned char lat_deg = fabs(wpt->latitude); unsigned char lon_deg = fabs(wpt->longitude); unsigned int lat_min = (fabs(wpt->latitude) - lat_deg) * 60000 + 0.500000000001; unsigned int lon_min = (fabs(wpt->longitude) - lon_deg) * 60000 + 0.500000000001; if (snprintf(str, 18, "%02u%05u%c%03u%05u%c", lat_deg, lat_min, lat_hemi, lon_deg, lon_min, lon_hemi) != 17) { fatal(MYNAME ": Bad waypoint format '%s'\n", str); } return str; } static char* date2str(struct tm* dt) { static char str[7] = ""; if (snprintf(str, 7, "%02u%02u%02u", dt->tm_mday, dt->tm_mon + 1, dt->tm_year % 100) != 6) { fatal(MYNAME ": Bad date format '%s'\n", str); } return str; } static char* tod2str(struct tm* tod) { static char str[7] = ""; if (snprintf(str, 7, "%02u%02u%02u", tod->tm_hour, tod->tm_min, tod->tm_sec) != 6) { fatal(MYNAME ": Bad time of day format '%s'\n", str); } return str; } /* * Write header records */ static void wr_header(void) { const route_head* pres_track; const route_head* track; struct tm* tm; time_t date; static const char dflt_str[] = "Unknown"; const char* str; waypoint* wpt; get_tracks(&pres_track, &track); if (!track && pres_track) { track = pres_track; } // Date in header record is that of the first fix record date = !track ? current_time() : ((waypoint*) QUEUE_FIRST(&track->waypoint_list))->creation_time; if (NULL == (tm = gmtime(&date))) { fatal(MYNAME ": Bad track timestamp\n"); } gbfprintf(file_out, "HFDTE%s\r\n", date2str(tm)); // Other header data may have been stored in track description if (track && track->rte_desc && strncmp(track->rte_desc, HDRMAGIC, strlen(HDRMAGIC)) == 0) { for (str = strtok(track->rte_desc + strlen(HDRMAGIC) + strlen(HDRDELIM), HDRDELIM); str; str = strtok(NULL, HDRDELIM)) { gbfprintf(file_out, "%s\r\n", str); } } else { // IGC header info not found so synthesise it. // If a waypoint is supplied with a short name of "PILOT", use // its description as the pilot's name in the header. str = dflt_str; if (NULL != (wpt = find_waypt_by_name("PILOT")) && wpt->description) { str = wpt->description; } gbfprintf(file_out, "HFPLTPILOT:%s\r\n", str); } } /************************************************* * Generation of IGC task declaration records */ static void wr_task_wpt_name(const waypoint* wpt, const char* alt_name) { gbfprintf(file_out, "C%s%s\r\n", latlon2str(wpt), wpt->description ? wpt->description : wpt->shortname ? wpt->shortname : alt_name); } static void wr_task_hdr(const route_head* rte) { unsigned char have_takeoff = 0; const waypoint* wpt; char flight_date[7] = "000000"; char task_desc[MAXRECLEN] = ""; int num_tps = rte->rte_waypt_ct - 2; struct tm* tm; time_t rte_time; static unsigned int task_num = 1; if (num_tps < 0) { fatal(MYNAME ": Empty task route\n"); } // See if the takeoff and landing waypoints are there or if we need to // generate them. wpt = (waypoint*) QUEUE_LAST(&rte->waypoint_list); if (wpt->shortname && strncmp(wpt->shortname, "LANDING", 6) == 0) { num_tps--; } wpt = (waypoint*) QUEUE_FIRST(&rte->waypoint_list); if (wpt->shortname && strncmp(wpt->shortname, "TAKEOFF", 6) == 0) { have_takeoff = 1; num_tps--; } if (num_tps < 0) { fatal(MYNAME ": Too few waypoints in task route\n"); } else if (num_tps > 99) { fatal(MYNAME ": Too much waypoints (more than 99) in task route.\n"); } // Gather data to write to the task identification (first) record rte_time = wpt->creation_time ? wpt->creation_time : current_time(); if (NULL == (tm = gmtime(&rte_time))) { fatal(MYNAME ": Bad task route timestamp\n"); } if (rte->rte_desc) { sscanf(rte->rte_desc, DATEMAGIC "%6[0-9]: %s", flight_date, task_desc); } gbfprintf(file_out, "C%s%s%s%04u%02u%s\r\n", date2str(tm), tod2str(tm), flight_date, task_num++, num_tps, task_desc); if (!have_takeoff) { // Generate the takeoff waypoint wr_task_wpt_name(wpt, "TAKEOFF"); } } static void wr_task_wpt(const waypoint* wpt) { wr_task_wpt_name(wpt, ""); } static void wr_task_tlr(const route_head* rte) { // If the landing waypoint is not supplied we need to generate it. const waypoint* wpt = (waypoint*) QUEUE_LAST(&rte->waypoint_list); if (!wpt->shortname || strncmp(wpt->shortname, "LANDIN", 6) != 0) { wr_task_wpt_name(wpt, "LANDING"); } } static void wr_tasks(void) { route_disp_all(wr_task_hdr, wr_task_tlr, wr_task_wpt); } /* * Write a single fix record */ static void wr_fix_record(const waypoint* wpt, int pres_alt, int gnss_alt) { struct tm* tm; if (NULL == (tm = gmtime(&wpt->creation_time))) { fatal(MYNAME ": bad track timestamp\n"); } if (unknown_alt == pres_alt) { pres_alt = 0; } if (unknown_alt == gnss_alt) { gnss_alt = 0; } gbfprintf(file_out, "B%02u%02u%02u%sA%05d%05d\r\n", tm->tm_hour, tm->tm_min, tm->tm_sec, latlon2str(wpt), pres_alt, gnss_alt); } /** * Attempt to align the pressure and GNSS tracks in time. * This is useful when trying to merge a track (lat/lon/time) recorded by a * GPS with a barograph (alt/time) recorded by a seperate instrument with * independent clocks which are not closely synchronised. * @return The number of seconds to add to the GNSS track in order to align * it with the pressure track. */ static int correlate_tracks(const route_head* pres_track, const route_head* gnss_track) { const queue* elem; double last_alt, alt_diff; double speed; time_t pres_time, gnss_time; int time_diff; const waypoint* wpt; // Deduce the landing time from the pressure altitude track based on // when we last descended to within 10m of the final track altitude. elem = QUEUE_LAST(&pres_track->waypoint_list); last_alt = ((waypoint*) elem)->altitude; do { elem = elem->prev; if (&pres_track->waypoint_list == elem) { // No track left return 0; } alt_diff = last_alt - ((waypoint*) elem)->altitude; if (alt_diff > 10.0) { // Last part of track was ascending return 0; } } while (alt_diff > -10.0); pres_time = ((waypoint*) elem->next)->creation_time; if (global_opts.debug_level >= 1) { printf(MYNAME ": pressure landing time %s", ctime(&pres_time)); } // Deduce the landing time from the GNSS altitude track based on // when the groundspeed last dropped below a certain level. elem = QUEUE_LAST(&gnss_track->waypoint_list); last_alt = ((waypoint*) elem)->altitude; do { wpt = (waypoint*) elem; elem = elem->prev; if (&gnss_track->waypoint_list == elem) { // No track left return 0; } // Get a crude indication of groundspeed from the change in lat/lon time_diff = wpt->creation_time - ((waypoint*) elem)->creation_time; speed = !time_diff ? 0 : (fabs(wpt->latitude - ((waypoint*) elem)->latitude) + fabs(wpt->longitude - ((waypoint*) elem)->longitude)) / time_diff; if (global_opts.debug_level >= 2) { printf(MYNAME ": speed=%f\n", speed); } } while (speed < 0.00003); gnss_time = ((waypoint*) elem->next)->creation_time; if (global_opts.debug_level >= 1) { printf(MYNAME ": gnss landing time %s", ctime(&gnss_time)); } // Time adjustment is difference between the two estimated landing times if (15 * 60 < abs(time_diff = pres_time - gnss_time)) { warning(MYNAME ": excessive time adjustment %ds\n", time_diff); } return time_diff; } /** * Interpolate altitude from a track at a given time. * @param track The track containing altitude data. * @param time The time that we are interested in. * @return The altitude interpolated from the track. */ static double interpolate_alt(const route_head* track, time_t time) { static const queue* prev_elem = NULL; static const queue* curr_elem = NULL; const waypoint* prev_wpt; const waypoint* curr_wpt; int time_diff; double alt_diff; // Start search at the beginning of the track if (!prev_elem) { curr_elem = prev_elem = QUEUE_FIRST(&track->waypoint_list); } // Find the track points either side of the requested time while (((waypoint*) curr_elem)->creation_time < time) { if (QUEUE_LAST(&track->waypoint_list) == curr_elem) { // Requested time later than all track points, we can't interpolate return unknown_alt; } prev_elem = curr_elem; curr_elem = QUEUE_NEXT(prev_elem); } prev_wpt = (waypoint*) prev_elem; curr_wpt = (waypoint*) curr_elem; if (QUEUE_FIRST(&track->waypoint_list) == curr_elem) { if (curr_wpt->creation_time == time) { // First point's creation time is an exact match so use it's altitude return curr_wpt->altitude; } else { // Requested time is prior to any track points, we can't interpolate return unknown_alt; } } // Interpolate if (0 == (time_diff = curr_wpt->creation_time - prev_wpt->creation_time)) { // Avoid divide by zero return curr_wpt->altitude; } alt_diff = curr_wpt->altitude - prev_wpt->altitude; return prev_wpt->altitude + (alt_diff / time_diff) * (time - prev_wpt->creation_time); } /* * Pressure altitude and GNSS altitude may be provided in two seperate * tracks. This function attempts to merge them into one. */ static void wr_track(void) { const route_head* pres_track; const route_head* gnss_track; const waypoint* wpt; const queue* elem; const queue* tmp; int time_adj; double pres_alt; // Find pressure altitude and GNSS altitude tracks get_tracks(&pres_track, &gnss_track); // If both found, attempt to merge them if (pres_track && gnss_track) { if (timeadj) { if (strcmp(timeadj, "auto") == 0) { time_adj = correlate_tracks(pres_track, gnss_track); } else if (sscanf(timeadj, "%d", &time_adj) != 1) { fatal(MYNAME ": bad timeadj argument '%s'\n", timeadj); } } else { time_adj = 0; } if (global_opts.debug_level >= 1) { printf(MYNAME ": adjusting time by %ds\n", time_adj); } // Iterate through waypoints in both tracks simultaneously QUEUE_FOR_EACH(&gnss_track->waypoint_list, elem, tmp) { wpt = (waypoint*) elem; pres_alt = interpolate_alt(pres_track, wpt->creation_time + time_adj); wr_fix_record(wpt, (int) pres_alt, (int) wpt->altitude); } } else { if (pres_track) { // Only the pressure altitude track was found so generate fix // records from it alone. QUEUE_FOR_EACH(&pres_track->waypoint_list, elem, tmp) { wr_fix_record((waypoint*) elem, (int)((waypoint*) elem)->altitude, (int) unknown_alt); } } else if (gnss_track) { // Only the GNSS altitude track was found so generate fix // records from it alone. QUEUE_FOR_EACH(&gnss_track->waypoint_list, elem, tmp) { wr_fix_record((waypoint*) elem, (int) unknown_alt, (int)((waypoint*) elem)->altitude); } } else { // No tracks found so nothing to do return; } } } static void wr_init(const char* fname) { file_out = gbfopen(fname, "wb", MYNAME); } static void wr_deinit(void) { gbfclose(file_out); } static void data_write(void) { gbfputs("AXXXZZZGPSBabel\r\n", file_out); wr_header(); wr_tasks(); wr_track(); gbfprintf(file_out, "LXXXGenerated by GPSBabel Version %s\r\n", gpsbabel_version); gbfputs("GGPSBabelSecurityRecordGuaranteedToFailVALIChecks\r\n", file_out); } static arglist_t igc_args[] = { { "timeadj", &timeadj, "(integer sec or 'auto') Barograph to GPS time diff", NULL, ARGTYPE_STRING, ARG_NOMINMAX }, ARG_TERMINATOR }; ff_vecs_t igc_vecs = { ff_type_file, { ff_cap_none , (ff_cap)(ff_cap_read | ff_cap_write), (ff_cap)(ff_cap_read | ff_cap_write) }, rd_init, wr_init, rd_deinit, wr_deinit, data_read, data_write, NULL, igc_args, CET_CHARSET_ASCII, 0 /* CET-REVIEW */ };