/*
Garmin GPS Database Reader/Writer
Copyright (C) 2005-2008 Olaf Klein, o.b.klein@gpsbabel.org
Mainly based on mapsource.c,
Copyright (C) 2005 Robert Lipe, robertlipe+source@gpsbabel.org
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
A format description obtained from reverse-engineering is at
https://www.memotech.franken.de/FileFormats/Garmin_MPS_GDB_and_GFI_Format.pdf
*/
#include <cmath> // for fabs
#include <cstdio> // for printf, snprintf, sscanf, SEEK_SET, NULL
#include <cstdlib> // for atoi, strtol, NULL
#include <cstring> // for memset, strcmp, strstr, strchr, strlen, strncpy
#include <ctime> // for strftime
#include <iterator> // for next
#include <QByteArray> // for QByteArray
#include <QList> // for QList
#include <QString> // for QString, operator!=, operator==
#include <QVector> // for QVector
#include <Qt> // for CaseInsensitive
#include <QtGlobal> // for qPrintable, Q_UNUSED, foreach
#include "defs.h"
#include "cet_util.h" // for cet_convert_init
#include "garmin_fs.h" // for garmin_fs_t, garmin_fs_flags_t, garmin_ilink_t, GMSD_GET, GMSD_SETSTR, GMSD_SET, garmin_fs_alloc, GMSD_FIND, GMSD_HAS
#include "garmin_tables.h" // for gt_get_icao_country, gt_waypt_class_map_point, gt_color_index_by_rgb, gt_color_value, gt_waypt_classes_e, gt_find_desc_from_icon_number, gt_find_icon_number_from_desc, gt_gdb_display_mode_symbol, gt_waypt_class_user_waypoint, GDB, gt_display_mode_symbol
#include "gbfile.h" // for gbfputint32, gbfgetint32, gbfread, gbfwrite, gbfgetc, gbfputc, gbfgetdbl, gbfgetcstr, gbfile, gbfclose, gbfputcstr, gbfcopyfrom, gbfrewind, gbfseek, gbftell, gbfopen_le, gbfgetcstr_old, gbfgetint16, gbfputdbl, gbfputint16
#include "grtcirc.h" // for RAD, gcdist, radtometers
#include "jeeps/gpsmath.h" // for GPS_Math_Deg_To_Semi, GPS_Math_Semi_To_Deg
#include "src/core/datetime.h" // for DateTime
#define MYNAME "gdb"
#define GDB_VER_1 1
#define GDB_VER_2 2
#define GDB_VER_3 3
#define GDB_VER_UTF8 GDB_VER_3
#define GDB_VER_MIN GDB_VER_1
#define GDB_VER_MAX GDB_VER_3
#define GDB_DEF_CLASS gt_waypt_class_user_waypoint
#define GDB_DEF_HIDDEN_CLASS gt_waypt_class_map_point
#define GDB_DEF_ICON 18
#define GDB_NAME_BUFFERLEN 1024
#define GDB_DBG_WPT 1
#define GDB_DBG_RTE 2
#define GDB_DBG_TRK 4
#define GDB_DBG_WPTe 8
#define GDB_DBG_RTEe 16
#define GDB_DBG_TRKe 32
#define GDB_DEBUG (GDB_DBG_WPTe) /* | GDB_DBG_RTE) */
#undef GDB_DEBUG
// #define GDB_DEBUG 0xff
#define DBG(a,b) if ((GDB_DEBUG & (a)) && (b))
/*******************************************************************************/
/* static char gdb_release[] = "$Revision: 1.74 $"; */
static char gdb_release_date[] = "$Date: 2011-04-14 01:30:01 $";
static gbfile* fin, *fout, *ftmp;
static int gdb_ver, gdb_category;
static bool gdb_roadbook;
static bool gdb_hide_wpt;
static bool gdb_hide_rpt;
static QList<Waypoint*> wayptq_in, wayptq_out, wayptq_in_hidden;
static short_handle short_h;
static char* gdb_opt_category;
static char* gdb_opt_ver;
static char* gdb_opt_via;
static char* gdb_opt_roadbook;
static char* gdb_opt_bitcategory;
static char* gdb_opt_drop_hidden_wpt;
static int waypt_flag;
static int route_flag;
static int waypt_ct; /* informational: total number of waypoints in/out */
static int waypth_ct; /* informational: total number of hidden waypoints in/out */
static int rtept_ct; /* informational: total number of route points in/out */
static int trkpt_ct; /* informational: total number of track points in/out */
static int rte_ct; /* informational: total number of routes in/out */
static int trk_ct; /* informational: total number of tracks in/out */
/*******************************************************************************/
#define ELEMENTS(a) a->rte_waypt_ct()
#define NOT_EMPTY(a) (a && *a)
static void
gdb_flush_waypt_queue(QList<Waypoint*>* Q)
{
while (!Q->isEmpty()) {
const Waypoint* wpt = Q->takeFirst();
if (wpt->extra_data) {
// FIXME
// wpt->extra_data may be holding a pointer to a QString, courtesy
// the grossness at the end of write_waypt_cb(). If that leaks,
// (and I think it will) find some way to do the approximate equivalent
// of:
// delete static_cast<QString*>(wpt->extra_data);
}
delete wpt;
}
}
#if GDB_DEBUG
static void
disp_summary(const gbfile* f)
{
int i, len;
len = strlen(f->name);
warning(MYNAME ": =====================");
for (i = 0; i < len; i++) {
warning("=");
}
warning("\n" MYNAME ": %s summary for \"%s\"\n",
(f->mode == 'r') ? "Reader" : "Writer", f->name);
warning(MYNAME ": ---------------------");
for (i = 0; i < len; i++) {
warning("-");
}
warning("\n" MYNAME ": %d waypoint(s)\n", waypt_ct - waypth_ct);
warning(MYNAME ": %d hidden waypoint(s)\n", waypth_ct);
warning(MYNAME ": %d route(s) with total %d point(s)\n", rte_ct, rtept_ct);
warning(MYNAME ": %d track(s) with total %d point(s)\n", trk_ct, trkpt_ct);
warning(MYNAME ": ---------------------");
for (i = 0; i < len; i++) {
warning("-");
}
warning("\n");
}
#else
#define disp_summary(a)
#endif
/*******************************************************************************/
/* TOOLS AND MACROS FOR THE READER */
/*-----------------------------------------------------------------------------*/
#define FREAD_C gbfgetc(fin)
#define FREAD(a,b) gbfread(a,(b),1,fin)
#define FREAD_i32 gbfgetint32(fin)
#define FREAD_i16 gbfgetint16(fin)
#define FREAD_DBL gbfgetdbl(fin)
#define FREAD_LATLON GPS_Math_Semi_To_Deg(gbfgetint32(fin))
#define FREAD_STR() gbfgetnativecstr(fin)
// This is all very messy. Some versions of GDB store strings as
// 8859-1 strings and others as UTF8. This wrapper tries to hide
// all that while (while keeping the character sets correct) and
// not pushing that decision down into gbfread. This module is
// still pretty messy and the points as to which fields are encode
// which ways in which versions are not at all clear, leading to
// encoding issues on read and leaks because of the differences
// in calling conventions on who owns/destroys the result.
#define FREAD_CSTR_AS_QSTR gbfgetcstr(fin)
static char* gdb_fread_cstr(gbfile* fin);
static QString fread_cstr()
{
QString rv;
char* s = gdb_fread_cstr(fin);
if (gdb_ver >= GDB_VER_UTF8) {
rv = QString::fromUtf8(s);
} else {
rv = QString::fromLatin1(s);
}
xfree(s);
return rv;
}
static char*
gdb_fread_cstr(gbfile* file_in)
{
char* result = gbfgetcstr_old(file_in);
if (result && (*result == '\0')) {
xfree(result);
result = nullptr;
}
return result;
}
static QString
gdb_fread_strlist()
{
QString res;
int count = FREAD_i32;
while (count > 0) {
QString str = fread_cstr();
if (!str.isEmpty()) {
res = str;
}
count--;
}
QString qres = res;
return qres;
}
static Waypoint*
gdb_find_wayptq(const QList<Waypoint*>* Q, const Waypoint* wpt, const char exact)
{
QString name = wpt->shortname;
foreach (Waypoint* tmp, *Q) {
if (name.compare(tmp->shortname,Qt::CaseInsensitive) == 0) {
if (! exact) {
return tmp;
}
if ((tmp->latitude == wpt->latitude) &&
(tmp->longitude == wpt->longitude)) {
return tmp;
}
}
}
return nullptr;
}
static Waypoint*
gdb_reader_find_waypt(const Waypoint* wpt, const char exact)
{
Waypoint* res = gdb_find_wayptq(&wayptq_in, wpt, exact);
if (res == nullptr) {
res = gdb_find_wayptq(&wayptq_in_hidden, wpt, exact);
}
return res;
}
static Waypoint*
gdb_add_route_waypt(route_head* rte, Waypoint* ref, const int wpt_class)
{
Waypoint* tmp = gdb_reader_find_waypt(ref, 1);
if (tmp == nullptr) {
tmp = find_waypt_by_name(ref->shortname);
if (tmp == nullptr) {
route_add_wpt(rte, ref);
return ref;
}
/* At this point we have found a waypoint with same name,
but probably from another data stream. Check coordinates!
*/
double dist = radtometers(gcdist(
RAD(ref->latitude), RAD(ref->longitude),
RAD(tmp->latitude), RAD(tmp->longitude)));
if (fabs(dist) > 100) {
warning(MYNAME ": Route point mismatch!\n");
warning(MYNAME ": \"%s\" from waypoints differs to \"%s\"\n",
qPrintable(tmp->shortname), qPrintable(ref->shortname));
fatal(MYNAME ": from route table by more than %0.1f meters!\n",
dist);
}
}
Waypoint* res = nullptr;
int turn_point = (gdb_roadbook && (wpt_class > gt_waypt_class_map_point) && !tmp->description.isEmpty());
if (turn_point || !gdb_hide_rpt || (wpt_class < gt_waypt_class_map_point)) {
res = new Waypoint(*tmp);
route_add_wpt(rte, res);
}
delete ref;
return res;
}
static QString gdb_to_ISO8601_duration(unsigned int seconds)
{
if (seconds == 0u) {
return QString("PT0S");
}
unsigned int days = seconds / 86400u;
QString out = "P";
if (days != 0) {
out.append(QString("D%1").arg(days));
seconds -= 86400u * days;
}
out.append(QString("T"));
unsigned int hours = seconds / 3600u;
if (hours != 0) {
out.append(QString("%1H").arg(hours));
seconds -= 3600u * hours;
}
unsigned int minutes = seconds / 60u;
if (minutes != 0) {
out.append(QString("%1M").arg(minutes));
seconds -= 60u * minutes;
}
if (seconds != 0) {
out.append(QString("%1S").arg(seconds));
}
return out;
}
/*******************************************************************************/
/* TOOLS AND MACROS FOR THE WRITER */
/*-----------------------------------------------------------------------------*/
static void FWRITE_CSTR(const QString& a)
{
if (a.isEmpty()) {
gbfputc(0, fout);
return;
}
if (gdb_ver >= GDB_VER_UTF8) {
gbfputcstr(a.toUtf8().constData(), fout);
} else {
gbfputcstr(a.toLatin1().constData(), fout);
}
}
#define FWRITE_i16(a) gbfputint16((a),fout)
#define FWRITE_i32(a) gbfputint32((a),fout)
#define FWRITE(a, b) gbfwrite(a,(b),1,fout)
#define FWRITE_C(a) gbfputc((a),fout)
#define FWRITE_DBL(a,b) gdb_write_dbl((a),(b))
#define FWRITE_TIME(a) gdb_write_time((a))
#define FWRITE_CSTR_LIST(a) gdb_write_cstr_list((a))
#define FWRITE_LATLON(a) gbfputint32(GPS_Math_Deg_To_Semi((a)),fout)
static void
gdb_write_cstr_list(const char* str)
{
if NOT_EMPTY(str) {
gbfputint32(1, fout);
gbfputcstr(str, fout);
} else {
gbfputint32(0, fout);
}
}
static void
gdb_write_cstr_list(const QString& str)
{
gdb_write_cstr_list(CSTRc(str));
}
static void
gdb_write_dbl(const double value, const double def)
{
if (value == def) {
gbfputc(0, fout);
} else {
gbfputc(1, fout);
gbfputdbl(value, fout);
}
}
static void
gdb_write_time(const int time)
{
if (time > 0) {
gbfputc(1, fout);
gbfputint32(time, fout);
} else {
gbfputc(0, fout);
}
}
/*******************************************************************************/
/* GDB "Garmin Database" READER CODE */
/*-----------------------------------------------------------------------------*/
static void
read_file_header()
{
char buf[128];
/*
We are beginning with a simple binary read.
*/
FREAD(buf, 6);
/*
A "gbfgetcstr" (FREAD_CSTR) works too, but if we get a wrong file as input,
the file validation my be comes too late. For example a XML base file normally
has no binary zeros inside and produce, if big enough, a buffer overflow.
The following message "local buffer overflow detected..." could be
misinterpreted.
*/
if (strcmp(buf, "MsRcf") != 0) {
fatal(MYNAME ": Invalid file \"%s\"!", fin->name);
}
int reclen = FREAD_i32;
Q_UNUSED(reclen);
QByteArray drec = FREAD_STR();
if (drec.at(0) != 'D') {
fatal(MYNAME ": Invalid file \"%s\"!", fin->name);
}
gdb_ver = drec.at(1) - 'k' + 1;
if ((gdb_ver < GDB_VER_MIN) || (gdb_ver > GDB_VER_MAX)) {
fatal(MYNAME ": Unknown or/and unsupported GDB version (%d.0)!", gdb_ver);
}
if (global_opts.verbose_status > 0) {
printf(MYNAME ": Reading Garmin GPS Database version %d.0\n", gdb_ver);
}
reclen = FREAD_i32;
if (reclen + 1 > int(sizeof(buf))) {
fatal(MYNAME ": Invalid record length\n");
}
(void) FREAD(buf, reclen + 1);
if (global_opts.verbose_status > 0) {
const char* name = buf+2;
if (strstr(name, "SQA") == nullptr) {
name = "MapSource";
} else if (strstr(name, "neaderhi") == nullptr) {
name = "MapSource BETA";
}
warning(MYNAME ": File created with \"%s\"\n", name);
}
QByteArray applicationField = FREAD_STR();
if (!((applicationField == "MapSource") || (applicationField == "BaseCamp"))) {
fatal(MYNAME ": Not a recognized signature in header");
}
}
/*-----------------------------------------------------------------------------*/
static Waypoint*
read_waypoint(gt_waypt_classes_e* waypt_class_out)
{
char buf[128]; /* used for temporary stuff */
gt_waypt_classes_e wpt_class;
Waypoint* res;
garmin_fs_t* gmsd;
#ifdef GMSD_EXPERIMENTAL
char subclass[22];
#endif
waypt_ct++;
res = new Waypoint;
gmsd = garmin_fs_alloc(-1);
res->fs.FsChainAdd(gmsd);
res->shortname = fread_cstr();
wpt_class = (gt_waypt_classes_e) FREAD_i32;
garmin_fs_t::set_wpt_class(gmsd, wpt_class);
if (wpt_class != 0) {
waypth_ct++;
}
garmin_fs_t::set_cc(gmsd, fread_cstr());
#ifdef GMSD_EXPERIMENTAL
FREAD(subclass, sizeof(subclass));
if (gmsd && (wpt_class >= gt_waypt_class_map_point)) {
memcpy(gmsd->subclass, subclass, sizeof(gmsd->subclass));
gmsd->flags.subclass = 1;
}
#else
FREAD(buf, 22);
#endif
res->latitude = FREAD_LATLON;
res->longitude = FREAD_LATLON;
if (FREAD_C == 1) {
double alt = FREAD_DBL;
if (alt < 1.0e24) {
res->altitude = alt;
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): Altitude = %.1f\n",
qPrintable(res->shortname), wpt_class, alt);
#endif
}
}
#if GDB_DEBUG
DBG(GDB_DBG_WPT, 1)
printf(MYNAME "-wpt \"%s\": coordinates = %c%0.6f %c%0.6f\n",
qPrintable(res->shortname),
res->latitude < 0 ? 'S' : 'N', res->latitude,
res->longitude < 0 ? 'W' : 'E', res->longitude);
#endif
res->notes = fread_cstr();
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, !res->notes.isNull())
printf(MYNAME "-wpt \"%s\" (%d): notes = %s\n",
qPrintable(res->shortname), wpt_class,
qPrintable(QString(res->notes).replace("\r\n", ", ")));
#endif
if (FREAD_C == 1) {
WAYPT_SET(res, proximity, FREAD_DBL);
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): Proximity = %.1f\n",
qPrintable(res->shortname), wpt_class, res->proximity / 1000);
#endif
}
int display = FREAD_i32;
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): display = %d\n",
qPrintable(res->shortname), wpt_class, display);
#endif
switch (display) { /* display value */
case gt_gdb_display_mode_symbol:
display = gt_display_mode_symbol;
break;
case gt_gdb_display_mode_symbol_and_comment:
display = gt_display_mode_symbol_and_comment;
break;
default:
display = gt_display_mode_symbol_and_name;
break;
}
garmin_fs_t::set_display(gmsd, display);
FREAD_i32; /* color !not implemented! */
int icon = FREAD_i32;
garmin_fs_t::set_icon(gmsd, icon);
garmin_fs_t::set_city(gmsd, fread_cstr());
garmin_fs_t::set_state(gmsd, fread_cstr());
garmin_fs_t::set_facility(gmsd, fread_cstr());
FREAD(buf, 1);
if (FREAD_C == 1) {
WAYPT_SET(res, depth, FREAD_DBL);
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): Depth = %.1f\n",
qPrintable(res->shortname), wpt_class, res->depth);
#endif
}
/* VERSION DEPENDENT CODE */
if (gdb_ver <= GDB_VER_2) {
FREAD(buf, 2); /* ?????????????????????????????????? */
waypt_flag = FREAD_C;
if (waypt_flag == 0) {
FREAD(buf, 3);
} else {
FREAD(buf, 2);
}
#if GDB_DEBUG
QString temp = FREAD_CSTR_AS_QSTR; /* undocumented & unused string */
DBG(GDB_DBG_WPTe, !temp.isEmpty())
printf(MYNAME "-wpt \"%s\" (%d): Unknown string = %s\n",
qPrintable(res->shortname), wpt_class, qPrintable(temp));
#else
(void) FREAD_CSTR_AS_QSTR; /* undocumented & unused string */
#endif
QString linky = FREAD_CSTR_AS_QSTR;
UrlLink l(linky);
if (!linky.isEmpty()) {
res->AddUrlLink(l);
}
if (wpt_class != 0) {
res->description = l.url_;
}
} else { // if (gdb_ver >= GDB_VER_3)
waypt_flag = 0;
garmin_fs_t::set_addr(gmsd, fread_cstr());
FREAD(buf, 1);
unsigned int duration = gbfgetuint32(fin);
res->description = FREAD_CSTR_AS_QSTR; /* instruction */
if (wpt_class == gt_waypt_class_map_intersection || wpt_class == gt_waypt_class_map_line) {
garmin_fs_t::set_duration(gmsd, duration);
res->notes = QString("[%1]").arg(gdb_to_ISO8601_duration(duration));
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): duration = %u\n",
qPrintable(res->shortname), wpt_class, duration);
#endif
}
int url_ct = FREAD_i32;
for (int i = url_ct; (i); i--) {
QString str = FREAD_CSTR_AS_QSTR;
if (!str.isEmpty()) {
waypt_add_url(res, str, nullptr);
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): url(%d) = %s\n",
qPrintable(res->shortname), wpt_class, url_ct - i, qPrintable(str));
#endif
}
}
}
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, !res->description.isNull())
printf(MYNAME "-wpt \"%s\" (%d): description = %s\n",
qPrintable(res->shortname), wpt_class, qPrintable(res->description));
DBG(GDB_DBG_WPTe, res->urls.HasUrlLink())
printf(MYNAME "-wpt \"%s\" (%d): url = %s\n",
qPrintable(res->shortname), wpt_class, qPrintable(res->urls.GetUrlLink().url_));
#endif
int category = FREAD_i16;
if (category != 0) {
garmin_fs_t::set_category(gmsd, category);
}
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, category)
printf(MYNAME "-wpt \"%s\" (%d): category = %d\n",
qPrintable(res->shortname), wpt_class, category);
#endif
if (FREAD_C == 1) {
WAYPT_SET(res, temperature, FREAD_DBL);
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, 1)
printf(MYNAME "-wpt \"%s\" (%d): temperature = %.1f\n",
qPrintable(res->shortname), wpt_class, res->temperature);
#endif
}
/* VERSION DEPENDENT CODE */
if (gdb_ver <= GDB_VER_2) {
if (waypt_flag != 0) {
FREAD(buf, 1);
}
}
if (FREAD_C == 1) {
res->SetCreationTime(FREAD_i32);
}
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_3) {
if (FREAD_i32 == 1) {
garmin_fs_t::set_phone_nr(gmsd, fread_cstr());
(void) FREAD_STR(); /* ?? fax / mobile ?? */
}
garmin_fs_t::set_country(gmsd, fread_cstr());
garmin_fs_t::set_postal_code(gmsd, fread_cstr());
}
res->icon_descr = gt_find_desc_from_icon_number(icon, GDB);
#if GDB_DEBUG
DBG(GDB_DBG_WPTe, icon != GDB_DEF_ICON)
printf(MYNAME "-wpt \"%s\" (%d): icon = \"%s\" (MapSource symbol %d)\n",
qPrintable(res->shortname), wpt_class, qPrintable(res->icon_descr), icon);
#endif
QString str;
if (!(str = garmin_fs_t::get_cc(gmsd, nullptr)).isEmpty()) {
if (!(garmin_fs_t::has_country(gmsd))) {
garmin_fs_t::set_country(gmsd, gt_get_icao_country(str));
}
}
if (gdb_roadbook && (wpt_class > gt_waypt_class_map_point) && !res->description.isEmpty()) {
wpt_class = gt_waypt_class_user_waypoint;
garmin_fs_t::set_wpt_class(gmsd, wpt_class);
#ifdef GMSD_EXPERIMENTAL
garmin_fs_t::unset_subclass(gmsd);
#endif
}
*waypt_class_out = wpt_class;
return res;
}
/*-----------------------------------------------------------------------------*/
static route_head*
read_route()
{
rte_ct++;
int warnings = 0;
auto* rte = new route_head;
rte->rte_name = fread_cstr();
char tbuf[128];
FREAD(tbuf, 1); /* display/autoname - 1 byte */
if (FREAD_C == 0) { /* max. data flag */
/* maxlat = */ (void) FREAD_i32;
/* maxlon = */
(void) FREAD_i32;
if (FREAD_C == 1) { /* maxalt = */
FREAD_DBL;
}
/* minlat = */ (void) FREAD_i32;
/* minlon = */
(void) FREAD_i32;
if (FREAD_C == 1) { /* minalt = */
FREAD_DBL;
}
}
int points = FREAD_i32;
#if GDB_DEBUG
DBG(GDB_DBG_RTE, 1)
printf(MYNAME "-rte \"%s\": loading route with %d point(s)...\n",
qPrintable(rte->rte_name), points);
#endif
for (int i = 0; i < points; i++) {
char buf[128];
auto* wpt = new Waypoint;
rtept_ct++;
wpt->shortname = fread_cstr(); /* shortname */
int wpt_class = FREAD_i32; /* waypoint class */
(void) FREAD_STR(); /* country code */
FREAD(buf, 18 + 4); /* subclass part 1-3 / unknown */
if (FREAD_C != 0) {
FREAD(buf, 8); /* aviation data (?); only seen with class "1" (Airport) */
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_3) {
FREAD(buf, 8); /* a second block since V3 */
}
}
FREAD(buf, 18); /* unknown 18 bytes; but first should be 0x01 or 0x03 */
/* seen also 0 with VER3 */
if ((buf[0] != 0x00) && (buf[0] != 0x01) && (buf[0] != 0x03)) {
warnings++;
if (warnings > 3) {
fatal(MYNAME "-rte_pt \"%s\": too many warnings!\n", qPrintable(wpt->shortname));
}
warning(MYNAME "-rte_pt \"%s\" (class %d): possible error in route.\n", qPrintable(wpt->shortname), wpt_class);
warning(MYNAME "-rte_pt (dump):");
for (int idx = 0; idx < 18; idx++) {
warning(" %02x", (unsigned char)buf[idx]);
}
warning("\n");
}
int links = FREAD_i32;
garmin_ilink_t* il_anchor = nullptr;
garmin_ilink_t* il_root = nullptr;
#if GDB_DEBUG
DBG(GDB_DBG_RTE, links)
printf(MYNAME "-rte_pt \"%s\" (%d): %d interlink step(s)\n",
qPrintable(wpt->shortname), wpt_class, links);
#endif
for (int j = 0; j < links; j++) {
auto* il_step = (garmin_ilink_t*) xmalloc(sizeof(garmin_ilink_t));
il_step->ref_count = 1;
il_step->lat = FREAD_LATLON;
il_step->lon = FREAD_LATLON;
if (FREAD_C == 1) {
il_step->alt = FREAD_DBL;
} else {
il_step->alt = unknown_alt;
}
if (j == 0) {
wpt->latitude = il_step->lat;
wpt->longitude = il_step->lon;
wpt->altitude = il_step->alt;
}
il_step->next = nullptr;
if (il_anchor == nullptr) {
il_root = il_step;
} else {
il_anchor->next = il_step;
}
il_anchor = il_step;
#if GDB_DEBUG
DBG(GDB_DBG_RTEe, 1) {
printf(MYNAME "-rte_il \"%s\" (%d of %d): %c%0.6f %c%0.6f\n",
qPrintable(wpt->shortname), j + 1, links,
il_step->lat < 0 ? 'S' : 'N', il_step->lat,
il_step->lon < 0 ? 'W' : 'E', il_step->lon);
}
#endif
}
bounds bounds;
waypt_init_bounds(&bounds);
if (FREAD_C == 0) { /* interlink bounds */
bounds.max_lat = FREAD_LATLON;
bounds.max_lon = FREAD_LATLON;
if (FREAD_C == 1) {
bounds.max_alt = FREAD_DBL;
}
bounds.min_lat = FREAD_LATLON;
bounds.min_lon = FREAD_LATLON;
if (FREAD_C == 1) {
bounds.min_alt = FREAD_DBL;
}
}
if (links == 0) {
/* Without links we need all information from wpt */
Waypoint* tmp = gdb_reader_find_waypt(wpt, 0);
if (tmp != nullptr) {
delete wpt;
wpt = new Waypoint(*tmp);
} else {
if (waypt_bounds_valid(&bounds)) {
warning(MYNAME ": (has bounds)\n");
}
warning(MYNAME ": Data corruption detected!\n");
fatal(MYNAME ": Sleeping route point without coordinates!\n");
}
}
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_2) {
FREAD(buf, 8);
if (gdb_ver >= GDB_VER_3) {
FREAD(buf, 2);
}
}
#if GDB_DEBUG
DBG(GDB_DBG_RTE, 1)
printf(MYNAME "-rte_pt \"%s\": coordinates = %c%0.6f, %c%0.6f\n",
qPrintable(wpt->shortname),
wpt->latitude < 0 ? 'S' : 'N', wpt->latitude,
wpt->longitude < 0 ? 'W' : 'E', wpt->longitude);
#endif
wpt = gdb_add_route_waypt(rte, wpt, wpt_class);
if (wpt != nullptr) {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
if (gmsd == nullptr) {
gmsd = garmin_fs_alloc(-1);
wpt->fs.FsChainAdd(gmsd);
}
garmin_fs_t::set_wpt_class(gmsd, wpt_class);
gmsd->ilinks = il_root;
il_root = nullptr;
}
while (il_root) {
garmin_ilink_t* il = il_root;
il_root = il_root->next;
xfree(il);
}
} /* ENDFOR: for (i = 0; i < points; i++) */
/* VERSION DEPENDENT CODE */
if (gdb_ver <= GDB_VER_2) {
rte->rte_urls.AddUrlLink(UrlLink(fread_cstr()));
} else {
rte->rte_urls.AddUrlLink(UrlLink(gdb_fread_strlist()));
int color_idx = FREAD_i32;
rte->line_color.bbggrr = gt_color_value(color_idx);
int autoroute = FREAD_C;
if (autoroute == 1) {
FREAD(tbuf, 6); /* unknown bytes */
int route_style = FREAD_C;
int calc_type = FREAD_i32;
int vehicle_type = FREAD_C;
int road_selection = FREAD_i32;
double driving_speed[5];
driving_speed[0] = FREAD_DBL;
driving_speed[1] = FREAD_DBL;
driving_speed[2] = FREAD_DBL;
driving_speed[3] = FREAD_DBL;
driving_speed[4] = FREAD_DBL;
FREAD(tbuf, 8); /* unknown bytes */
#if GDB_DEBUG
DBG(GDB_DBG_RTE, 1)
printf(MYNAME "-rte_pt: autoroute info: route style %d, calculation type %d, vehicle type %d, road selection %d\n"
" driving speeds (kph) %.0f, %.0f, %.0f, %.0f, %.0f\n",
route_style, calc_type, vehicle_type, road_selection,
driving_speed[0], driving_speed[1], driving_speed[2], driving_speed[3], driving_speed[4]);
#else
Q_UNUSED(route_style);
Q_UNUSED(calc_type);
Q_UNUSED(vehicle_type);
Q_UNUSED(road_selection);
Q_UNUSED(driving_speed);
#endif
}
rte->rte_desc = fread_cstr();
#if 0
/* replace CRLF's with ", " */
if (rte->rte_desc) {
char* c = rte->rte_desc;
while ((c = strstr(c, "\r\n"))) {
*c++ = ',';
*c++ = ' ';
}
}
#endif
}
return rte;
}
/*-----------------------------------------------------------------------------*/
static route_head*
read_track()
{
char dummy;
trk_ct++;
auto* res = new route_head;
res->rte_name = fread_cstr();
// res->rte_num = trk_ct;
FREAD(&dummy, 1); /* display - 1 byte */
int color_idx = FREAD_i32; /* color - 1 dword */
res->line_color.bbggrr = gt_color_value(color_idx);
int points = FREAD_i32;
for (int index = 0; index < points; index++) {
auto* wpt = new Waypoint;
trkpt_ct++;
wpt->latitude = FREAD_LATLON;
wpt->longitude = FREAD_LATLON;
if (FREAD_C == 1) {
double alt = FREAD_DBL;
if (alt < 1.0e24) {
wpt->altitude = alt;
}
}
if (FREAD_C == 1) {
wpt->SetCreationTime(FREAD_i32);
}
if (FREAD_C == 1) {
WAYPT_SET(wpt, depth, FREAD_DBL);
}
if (FREAD_C == 1) {
WAYPT_SET(wpt, temperature, FREAD_DBL);
}
track_add_wpt(res, wpt);
}
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_3) {
res->rte_urls.AddUrlLink(UrlLink(gdb_fread_strlist()));
} else { /* if (gdb_ver <= GDB_VER_2) */
res->rte_urls.AddUrlLink(UrlLink(FREAD_CSTR_AS_QSTR));
}
#if GDB_DEBUG
DBG(GDB_DBG_TRK, res->rte_urls.HasUrlLink())
printf(MYNAME "-trk \"%s\": url = %s\n",
qPrintable(res->rte_name), qPrintable(res->rte_urls.GetUrlLink().url_));
#endif
return res;
}
/*******************************************************************************/
static void
gdb_rd_init(const QString& fname)
{
fin = gbfopen_le(fname, "rb", MYNAME);
ftmp = gbfopen_le(nullptr, "wb", MYNAME);
read_file_header();
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_UTF8) {
cet_convert_init(CET_CHARSET_UTF8, 1);
}
wayptq_in.clear();
wayptq_in_hidden.clear();
bool via = gdb_opt_via;
bool drop_wpt = gdb_opt_drop_hidden_wpt;
gdb_roadbook = gdb_opt_roadbook;
gdb_hide_wpt = via || drop_wpt || gdb_roadbook;
gdb_hide_rpt = via || gdb_roadbook;
waypt_ct = 0;
waypth_ct = 0;
rtept_ct = 0;
trkpt_ct = 0;
rte_ct = 0;
trk_ct = 0;
}
static void
gdb_rd_deinit()
{
disp_summary(fin);
gdb_flush_waypt_queue(&wayptq_in);
gdb_flush_waypt_queue(&wayptq_in_hidden);
gbfclose(ftmp);
gbfclose(fin);
}
static void
read_data()
{
int incomplete = 0; /* number of incomplete reads */
for (;;) {
char typ;
gt_waypt_classes_e wpt_class;
Waypoint* wpt;
route_head* trk, *rte;
int len = FREAD_i32;
if (FREAD(&typ, 1) < 1) {
fatal(MYNAME ": Attempt to read past EOF.");
}
if (typ == 'V') {
break; /* break the loop */
}
gbfrewind(ftmp);
gbfwrite(nullptr, 0, 0, ftmp); /* truncate */
gbfcopyfrom(ftmp, fin, len);
gbfrewind(ftmp);
gbfile* fsave = fin; /* swap standard 'fin' with cached input */
fin = ftmp;
char dump = 1;
wpt_class = GDB_DEF_CLASS;
switch (typ) {
case 'W':
wpt = read_waypoint(&wpt_class);
if (!gdb_hide_wpt || (wpt_class == 0)) {
waypt_add(wpt);
auto* dupe = new Waypoint(*wpt);
wayptq_in.append(dupe);
} else {
wayptq_in_hidden.append(wpt);
}
break;
case 'R':
rte = read_route();
if (rte) {
route_add_head(rte);
}
break;
case 'T':
trk = read_track();
if (trk) {
track_add_head(trk);
}
break;
default:
dump = 0; /* make a dump only for main types */
break;
}
int delta = len - gbftell(ftmp);
if (delta > 1000000) {
fatal("Internal consistency error. Delta too big");
}
// Avoid finite loop on bogus beta files from '06.
// THe 100000 is totally pulled from my hat.
// is_fatal((delta > 1000000) || (delta < 0), "Internal GDB error; invalid delta.");
if (dump && delta) {
if (! incomplete++) {
warning(MYNAME ":==========================================\n");
warning(MYNAME ":=== W A R N I N G ===\n");
}
if (typ == 'W')
warning(MYNAME ":(%d%c-%02d): delta = %d (flag=%3d/%02x)-",
gdb_ver, typ, wpt_class, delta, waypt_flag, waypt_flag);
else {
warning(MYNAME ":(%d%c): delta = %d -", gdb_ver, typ, delta);
}
if (delta > 0) {
char* buf = (char*) xmalloc(delta);
if (FREAD(buf, delta) < 1) {
fatal(MYNAME ": Attempt to read past EOF.\n");
}
for (int i = 0; i < delta; i++) {
warning(" %02x", (unsigned char)buf[i]);
}
xfree(buf);
}
warning("\n");
}
fin = fsave;
}
if (incomplete) {
warning(MYNAME ":------------------------------------------\n");
warning(MYNAME ": \"%s\"\n", fin->name);
warning(MYNAME ":------------------------------------------\n");
warning(MYNAME ": Please mail this information\n");
warning(MYNAME " and, if you can, the used GDB file\n");
warning(MYNAME ": to gpsbabel-misc@lists.sourceforge.net\n");
warning(MYNAME ":==========================================\n");
}
}
/*******************************************************************************/
/*
* reset_short_handle: used for waypoint, route and track names
*/
static void
reset_short_handle(const char* defname)
{
if (short_h != nullptr) {
mkshort_del_handle(&short_h);
}
short_h = mkshort_new_handle();
setshort_length(short_h, GDB_NAME_BUFFERLEN);
setshort_badchars(short_h, "\r\n\t");
setshort_mustupper(short_h, 0);
setshort_mustuniq(short_h, 1);
setshort_whitespace_ok(short_h, 1);
setshort_repeating_whitespace_ok(short_h, 1);
setshort_defname(short_h, defname);
}
/* ----------------------------------------------------------------------------*/
static void
write_header()
{
char buff[128], tbuff[32];
char* c;
int len, n = 0;
struct tm tm;
FWRITE_CSTR("MsRcf");
FWRITE_i32(2);
strncpy(buff, "Dx", sizeof(buff));
buff[1] = 'k' - 1 + gdb_ver;
FWRITE_CSTR(buff);
#if 0
/* Take this if anything is wrong with our self generated watermark */
strncpy(buff, "A].SQA*Dec 27 2004*17:40:51", sizeof(buff)); /* MapSource V6.5 */
#else
/* This is our "Watermark" to show this file was created by GPSbabel */
/* history:
"A].GPSBabel_1.2.7-beta*Sep 13 2005*20:10:00" - gpsbabel V1.2.7 BETA
"A].GPSBabel_1.2.8-beta*Jan 18 2006*20:11:00" - gpsbabel 1.2.8-beta01182006_clyde
"A].GPSBabel_1.2.8-beta*Apr 18 2006*20:12:00" - gpsbabel 1.2.8-beta20060405
"A].GPSBabel-1.3*Jul 02 2006*20:13:00" - gpsbabel 1.3.0
"A].GPSBabel-1.3.1*Sep 03 2006*20:14:00" - gpsbabel 1.3.1
"A].GPSBabel-1.3.2*Nov 01 2006*22:23:39" - gpsbabel 1.3.2
New since 11/01/2006:
version: version and release of gpsbabel (defined in configure.ac)
timestamp: date and time of gdb.c (handled by CVS)
"A].GPSBabel-1.3.2*Nov 01 2006*22:23:39" - gpsbabel 1.3.2
"A].GPSBabel-beta20061125*Feb 06 2007*23:24:14" gpsbabel beta20061125
"A].GPSBabel-1.3.3*Feb 20 2007*20:51:15" - gpsbabel 1.3.3
*/
memset(&tm, 0, sizeof(tm));
n = sscanf(gdb_release_date+7, "%d-%d-%d %d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
if (n != 6) {
// The $Date string in gdb_release_date[] above is bad.
fatal(MYNAME ": internal date format error on %s\n", gdb_release_date + 7);
}
tm.tm_year -= 1900;
tm.tm_mon -= 1;
n = strftime(tbuff, sizeof(tbuff), "%b %d %Y*%H:%M:%S", &tm);
if (n == 0) {
// The build of the struct tm was bad.
fatal(MYNAME ": internal date generation error for %s\n", gdb_release_date + 7);
}
snprintf(buff, sizeof(buff), "A].GPSBabel-%s*%s", gpsbabel_version, tbuff);
#endif
len = strlen(buff);
buff[2] = 2;
c = buff;
while ((c = strchr(c, '*'))) {
*c++ = '\0';
}
FWRITE_i32(len);
FWRITE(buff, len + 1);
FWRITE_CSTR("MapSource"); /* MapSource magic */
}
/*-----------------------------------------------------------------------------*/
/*
* gdb_check_waypt: As implemented in waypt_add, but we have some leaks where
* waypoints are modified after waypt_add. Maybe we need a data check
* after each input module.
*/
static void
gdb_check_waypt(Waypoint* wpt)
{
double lat_orig = wpt->latitude;
double lon_orig = wpt->longitude;
if (wpt->latitude < -90) {
wpt->latitude += 180;
} else if (wpt->latitude > +90) {
wpt->latitude -= 180;
}
if (wpt->longitude < -180) {
wpt->longitude += 360;
} else if (wpt->longitude > +180) {
wpt->longitude -= 360;
}
if ((wpt->latitude < -90) || (wpt->latitude > 90.0))
fatal("Invalid latitude %f in waypoint %s.\n",
lat_orig, !wpt->shortname.isEmpty() ? qPrintable(wpt->shortname) : "<no name>");
if ((wpt->longitude < -180) || (wpt->longitude > 180.0))
fatal("Invalid longitude %f in waypoint %s.\n",
lon_orig, !wpt->shortname.isEmpty() ? qPrintable(wpt->shortname) : "<no name>");
}
/*-----------------------------------------------------------------------------*/
static void
write_waypoint(
const Waypoint* wpt, const QString& shortname, garmin_fs_t* gmsd,
const int icon, const int display)
{
char zbuf[32], ffbuf[32];
waypt_ct++; /* increase informational number of written waypoints */
memset(zbuf, 0, sizeof(zbuf));
memset(ffbuf, 0xFF, sizeof(ffbuf));
int wpt_class = wpt->wpt_flags.fmt_use; /* trick */
FWRITE_CSTR(shortname); /* unique (!!!) shortname */
FWRITE_i32(wpt_class); /* waypoint class */
FWRITE_CSTR(garmin_fs_t::get_cc(gmsd, "")); /* country code */
if (wpt_class != 0) {
waypth_ct++;
}
#ifdef GMSD_EXPERIMENTAL
if (gmsd && gmsd->flags.subclass && (wpt_class >= gt_waypt_class_map_point)) {
FWRITE(gmsd->subclass, sizeof(gmsd->subclass));
} else
#endif
{
FWRITE(zbuf, 4); /* subclass part 1 */
FWRITE(ffbuf, 12); /* subclass part 2 */
FWRITE(zbuf, 2); /* subclass part 3 */
FWRITE(ffbuf, 4); /* unknown */
}
FWRITE_LATLON(wpt->latitude); /* latitude */
FWRITE_LATLON(wpt->longitude); /* longitude */
FWRITE_DBL(wpt->altitude, unknown_alt); /* altitude */
if (!wpt->notes.isEmpty()) {
FWRITE_CSTR(wpt->notes);
} else {
FWRITE_CSTR(wpt->description);
}
FWRITE_DBL(WAYPT_GET(wpt, proximity, unknown_alt), unknown_alt); /* proximity */
FWRITE_i32(display); /* display */
FWRITE_i32(0); /* color */
FWRITE_i32(icon); /* icon */
FWRITE_CSTR(garmin_fs_t::get_city(gmsd, "")); /* city */
FWRITE_CSTR(garmin_fs_t::get_state(gmsd, "")); /* state */
FWRITE_CSTR(garmin_fs_t::get_facility(gmsd, "")); /* facility */
FWRITE_C(0); /* unknown */
FWRITE_DBL(WAYPT_GET(wpt, depth, unknown_alt), unknown_alt); /* depth */
/* VERSION DEPENDENT CODE */
if (gdb_ver <= GDB_VER_2) {
FWRITE(zbuf, 3);
FWRITE(zbuf, 4);
QString ld;
if (wpt->HasUrlLink()) {
UrlLink l = wpt->GetUrlLink();
ld = l.url_;
}
QString descr = (wpt_class < gt_waypt_class_map_point) ?
ld : wpt->description;
if ((descr != nullptr) && (wpt_class >= gt_waypt_class_map_point) && \
descr == CSTRc(wpt->shortname)) {
descr.clear();
}
FWRITE_CSTR(descr);
} else { /* if (gdb_ver > GDB_VER_3) */
// url_link* url_next;
// const char* str;
QString str;
if (wpt_class < gt_waypt_class_map_point) { /* street address */
str = garmin_fs_t::get_addr(gmsd, "");
} else {
str = "";
}
FWRITE_CSTR(str);
FWRITE(zbuf, 5); /* instruction dependent */
/* GBD doesn't have a native description field */
/* here we misuse the instruction field */
#if 1
QString d = wpt->description;
if (wpt->description == wpt->shortname) {
d.clear();
}
if (str == wpt->notes) {
d.clear();
}
FWRITE_CSTR(d); /* instruction */
#else
str = wpt->description;
if (str && (strcmp(str, wpt->shortname) == 0)) {
str = NULL;
}
if (str && wpt->notes && (strcmp(str, wpt->notes) == 0)) {
str = NULL;
}
FWRITE_CSTR(str); /* instruction */
#endif
FWRITE_i32(wpt->urls.size());
foreach (UrlLink l, wpt->urls) {
FWRITE_CSTR(l.url_);
}
}
FWRITE_i16(garmin_fs_t::get_category(gmsd, gdb_category));
FWRITE_DBL(WAYPT_GET(wpt, temperature, 0), 0);
FWRITE_TIME(wpt->GetCreationTime().toTime_t());
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_3) {
QString str = garmin_fs_t::get_phone_nr(gmsd, "");
if (!str.isEmpty()) {
FWRITE_i32(1);
FWRITE_CSTR(str);
FWRITE_CSTR("");
} else {
FWRITE_i32(0);
}
FWRITE_CSTR(garmin_fs_t::get_country(gmsd, ""));
FWRITE_CSTR(garmin_fs_t::get_postal_code(gmsd, ""));
}
}
static void
route_compute_bounds(const route_head* rte, bounds* bounds)
{
waypt_init_bounds(bounds);
foreach (Waypoint* wpt, rte->waypoint_list) {
gdb_check_waypt(wpt);
waypt_add_to_bounds(bounds, wpt);
}
}
static void
route_write_bounds(bounds* bounds)
{
if (waypt_bounds_valid(bounds)) {
FWRITE_C(0);
FWRITE_LATLON(bounds->max_lat);
FWRITE_LATLON(bounds->max_lon);
FWRITE_DBL(bounds->max_alt, unknown_alt);
FWRITE_LATLON(bounds->min_lat);
FWRITE_LATLON(bounds->min_lon);
FWRITE_DBL(bounds->min_alt, -(unknown_alt));
} else {
FWRITE_C(1);
}
}
static void
write_route(const route_head* rte, const QString& rte_name)
{
bounds bounds;
char zbuf[32], ffbuf[32];
memset(zbuf, 0, sizeof(zbuf));
memset(ffbuf, 0xFF, sizeof(ffbuf));
FWRITE_CSTR(rte_name);
FWRITE_C(0); /* display/autoname - 1 byte */
route_compute_bounds(rte, &bounds);
route_write_bounds(&bounds);
int points = ELEMENTS(rte);
FWRITE_i32(points);
int index = 0;
for (auto it =rte->waypoint_list.cbegin(); it != rte->waypoint_list.cend(); ++it) {
Waypoint* wpt = *it;
Waypoint* next = nullptr;
if (index < points) {
next = *std::next(it);
}
index++;
rtept_ct++; /* increase informational number of written route points */
if (index == 1) {
gdb_check_waypt(wpt);
}
if (index < points) {
gdb_check_waypt(next);
}
Waypoint* test = gdb_find_wayptq(&wayptq_out, wpt, 1);
if (test != nullptr) {
wpt = test;
} else {
fatal(MYNAME ": Sorry, that should never happen!!!\n");
}
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
/* extra_data may contain a modified shortname */
FWRITE_CSTR((wpt->extra_data) ? (char*)wpt->extra_data : wpt->shortname);
int wpt_class = wpt->wpt_flags.fmt_use; /* trick */
FWRITE_i32(wpt_class); /* waypoint class */
FWRITE_CSTR(garmin_fs_t::get_cc(gmsd, "")); /* country */
#ifdef GMSD_EXPERIMENTAL
if (gmsd && gmsd->flags.subclass && (wpt_class >= gt_waypt_class_map_point)) {
FWRITE(gmsd->subclass, sizeof(gmsd->subclass));
} else
#endif
{
FWRITE(zbuf, 4); /* subclass part 1 */
FWRITE(ffbuf, 12); /* subclass part 2 */
FWRITE(zbuf, 2); /* subclass part 3 */
FWRITE(ffbuf, 4); /* unknown */
}
FWRITE_C(0); /* unknown value or string */
FWRITE_C(3); /* unknown 18 bytes starting with 0x03 */
FWRITE(zbuf, 3);
FWRITE(ffbuf, 4);
FWRITE(zbuf, 10);
if (index == points) {
FWRITE_i32(0); /* no more steps */
FWRITE_C(1); /* skip bounds */
} else { /* if (index < points) */
FWRITE_i32(2); /* two interstep links */
FWRITE_LATLON(wpt->latitude);
FWRITE_LATLON(wpt->longitude);
FWRITE_DBL(wpt->altitude, unknown_alt);
FWRITE_LATLON(next->latitude);
FWRITE_LATLON(next->longitude);
FWRITE_DBL(next->altitude, unknown_alt);
waypt_init_bounds(&bounds);
waypt_add_to_bounds(&bounds, wpt);
waypt_add_to_bounds(&bounds, next);
route_write_bounds(&bounds);
}
/* VERSION DEPENDENT CODE */
if (gdb_ver >= GDB_VER_2) {
FWRITE(ffbuf, 8);
if (gdb_ver >= GDB_VER_3) {
FWRITE(zbuf, 2);
}
}
}
/* VERSION DEPENDENT CODE */
if (gdb_ver <= GDB_VER_2) {
if (rte->rte_urls.HasUrlLink()) {
FWRITE_CSTR(rte->rte_urls.GetUrlLink().url_);
} else {
FWRITE_CSTR("");
}
} else { /* if (gdb_ver >= GDB_VER_3) */
if (rte->rte_urls.HasUrlLink()) {
FWRITE_CSTR_LIST(rte->rte_urls.GetUrlLink().url_);
} else {
FWRITE_CSTR_LIST("");
}
/* "Magenta" (14) is MapSource default */
FWRITE_i32((rte->line_color.bbggrr < 0) ? 14 : gt_color_index_by_rgb(rte->line_color.bbggrr));
FWRITE_C(0);
FWRITE_CSTR(rte->rte_desc);
}
}
static void
write_track(const route_head* trk, const QString& trk_name)
{
int points = ELEMENTS(trk);
FWRITE_CSTR(trk_name);
FWRITE_C(0);
/* "Unknown" (0) is MapSource default */
FWRITE_i32(gt_color_index_by_rgb(trk->line_color.bbggrr));
FWRITE_i32(points); /* total number of waypoints in waypoint list */
foreach (const Waypoint* wpt, trk->waypoint_list) {
trkpt_ct++; /* increase informational number of written route points */
FWRITE_LATLON(wpt->latitude);
FWRITE_LATLON(wpt->longitude);
FWRITE_DBL(wpt->altitude, unknown_alt);
FWRITE_TIME(wpt->GetCreationTime().toTime_t());
double d = WAYPT_GET(wpt, depth, unknown_alt);
FWRITE_DBL(d, unknown_alt);
d = WAYPT_GET(wpt, temperature, -99999);
FWRITE_DBL(d, -99999);
}
/* finalize track */
/* VERSION DEPENDENT CODE */
if (gdb_ver <= GDB_VER_2) {
if (trk->rte_urls.HasUrlLink()) {
FWRITE_CSTR(trk->rte_urls.GetUrlLink().url_);
} else {
FWRITE_CSTR("");
}
} else { /* if (gdb_ver >= GDB_VER_3 */
if (trk->rte_urls.HasUrlLink()) {
FWRITE_CSTR_LIST(trk->rte_urls.GetUrlLink().url_);
} else {
FWRITE_CSTR_LIST("");
}
}
}
/*-----------------------------------------------------------------------------*/
static void
finalize_item(gbfile* origin, const char identifier)
{
int len = gbftell(fout);
fout = origin;
gbfseek(ftmp, 0, SEEK_SET);
FWRITE_i32(len);
FWRITE_C(identifier);
gbfcopyfrom(fout, ftmp, len);
gbfseek(ftmp, 0, SEEK_SET); /* Truncate memory stream */
gbfwrite(nullptr, 0, 0, ftmp);
}
/*-----------------------------------------------------------------------------*/
static void
write_waypoint_cb(const Waypoint* refpt)
{
/* do this when backup always happens in main */
// but, but, casting away the const here is wrong...
(const_cast<Waypoint*>(refpt))->shortname = refpt->shortname.trimmed();
Waypoint* test = gdb_find_wayptq(&wayptq_out, refpt, 1);
if (refpt->HasUrlLink() && test && test->HasUrlLink() && route_flag == 0) {
UrlLink orig_link = refpt->GetUrlLink();
UrlLink test_link = test->GetUrlLink();
if (orig_link.url_ != test_link.url_) {
test = nullptr;
}
}
if ((test != nullptr) && (route_flag == 0)) {
if (test->notes != refpt->notes) {
test = nullptr;
}
}
if (test == nullptr) {
int display;
auto* wpt = new Waypoint(*refpt);
gdb_check_waypt(wpt);
wayptq_out.append(wpt);
gbfile* fsave = fout;
fout = ftmp;
/* prepare the waypoint */
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
int wpt_class = garmin_fs_t::get_wpt_class(gmsd, -1);
if (wpt_class == -1) {
wpt_class = (route_flag) ? GDB_DEF_HIDDEN_CLASS : GDB_DEF_CLASS;
}
wpt->wpt_flags.fmt_use = wpt_class; /* trick, we need this for the route(s) */
int icon = garmin_fs_t::get_icon(gmsd, -1);
if (icon < 0) {
if (wpt->icon_descr.isNull()) {
icon = GDB_DEF_ICON;
} else {
icon = gt_find_icon_number_from_desc(wpt->icon_descr, GDB);
}
}
switch (garmin_fs_t::get_display(gmsd, -1)) { /* display */
case -1:
if (wpt_class < 8) {
display = gt_gdb_display_mode_symbol_and_name;
} else {
display = gt_gdb_display_mode_symbol;
}
break;
case gt_display_mode_symbol:
display = gt_gdb_display_mode_symbol;
break;
case gt_display_mode_symbol_and_comment:
display = gt_gdb_display_mode_symbol_and_comment;
break;
default:
display = gt_gdb_display_mode_symbol_and_name;
break;
}
QString name = wpt->shortname;
if (global_opts.synthesize_shortnames || name.isEmpty()) {
name = wpt->notes;
if (name.isEmpty()) {
name = wpt->description;
}
if (name.isEmpty()) {
name = wpt->shortname;
}
}
name = mkshort(short_h, name);
write_waypoint(wpt, name, gmsd, icon, display);
finalize_item(fsave, 'W');
}
}
static void
write_route_cb(const route_head* rte)
{
if (ELEMENTS(rte) <= 0) {
return;
}
QString name;
if (rte->rte_name.isNull()) {
name = mkshort(short_h, QString::asprintf("Route%04d", rte->rte_num));
} else {
name = mkshort(short_h, rte->rte_name);
}
rte_ct++; /* increase informational number of written routes */
gbfile* fsave = fout;
fout = ftmp;
write_route(rte, name);
finalize_item(fsave, 'R');
}
static void
write_track_cb(const route_head* trk)
{
if (ELEMENTS(trk) <= 0) {
return;
}
QString name;
if (trk->rte_name.isNull()) {
name = mkshort(short_h, QString::asprintf("Track%04d", trk->rte_num));
} else {
name = mkshort(short_h, trk->rte_name);
}
trk_ct++; /* increase informational number of written tracks */
gbfile* fsave = fout;
fout = ftmp;
write_track(trk, name);
finalize_item(fsave, 'T');
}
/*-----------------------------------------------------------------------------*/
static void
gdb_wr_init(const QString& fname)
{
fout = gbfopen_le(fname, "wb", MYNAME);
ftmp = gbfopen_le(nullptr, "wb", MYNAME);
gdb_category = (gdb_opt_category) ? atoi(gdb_opt_category) : 0;
gdb_ver = (gdb_opt_ver && *gdb_opt_ver) ? atoi(gdb_opt_ver) : 0;
if (gdb_category) {
if ((gdb_category < 1) || (gdb_category > 16)) {
fatal(MYNAME ": cat must be between 1 and 16!");
}
gdb_category = 1 << (gdb_category - 1);
}
if (gdb_opt_bitcategory) {
gdb_category = strtol(gdb_opt_bitcategory, nullptr, 0);
}
if (gdb_ver >= GDB_VER_UTF8) {
cet_convert_init(CET_CHARSET_UTF8, 1);
}
wayptq_out.clear();
short_h = nullptr;
waypt_ct = 0;
waypth_ct = 0;
rtept_ct = 0;
trkpt_ct = 0;
rte_ct = 0;
trk_ct = 0;
}
static void
gdb_wr_deinit()
{
disp_summary(fout);
gdb_flush_waypt_queue(&wayptq_out);
mkshort_del_handle(&short_h);
gbfclose(fout);
gbfclose(ftmp);
}
static void
write_data()
{
if (gdb_opt_ver) {
gdb_ver = atoi(gdb_opt_ver);
}
write_header();
reset_short_handle("WPT");
route_flag = 0;
waypt_disp_all(write_waypoint_cb);
route_flag = 1;
route_disp_all(nullptr, nullptr, write_waypoint_cb);
reset_short_handle("Route");
route_disp_all(write_route_cb, nullptr, nullptr);
reset_short_handle("Track");
track_disp_all(write_track_cb, nullptr, nullptr);
FWRITE_i32(2); /* finalize gdb with empty map segment */
FWRITE_CSTR("V");
FWRITE_C(1);
}
/*******************************************************************************/
static QVector<arglist_t> gdb_args = {
{
"cat", &gdb_opt_category,
"Default category on output (1..16)",
nullptr, ARGTYPE_INT, "1", "16", nullptr
},
{
"bitscategory", &gdb_opt_bitcategory, "Bitmap of categories",
nullptr, ARGTYPE_INT, "1", "65535", nullptr
},
{
"ver", &gdb_opt_ver,
"Version of gdb file to generate (1..3)",
"2", ARGTYPE_INT, "1", "3", nullptr
},
{
"via", &gdb_opt_via,
"Drop route points that do not have an equivalent waypoint (hidden points)",
nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
},
{
"dropwpt", &gdb_opt_drop_hidden_wpt,
"Don't create waypoints for non-user points",
nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
},
{
"roadbook", &gdb_opt_roadbook,
"Include major turn points (with description) from calculated route",
nullptr, ARGTYPE_BOOL, ARG_NOMINMAX, nullptr
},
};
ff_vecs_t gdb_vecs = {
ff_type_file,
FF_CAP_RW_ALL,
gdb_rd_init,
gdb_wr_init,
gdb_rd_deinit,
gdb_wr_deinit,
read_data,
write_data,
nullptr,
&gdb_args,
CET_CHARSET_MS_ANSI, 0 /* O.K.: changed to NON-FIXED */
/* because of utf8 strings since GDB V3 */
, NULL_POS_OPS,
nullptr
};
/*******************************************************************************/