/*
Support for PathAway Palm Database,
Copyright (C) 2005-2006 Olaf Klein, o.b.klein@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., 59 Temple Place - Suite 330, Boston, MA 02111 USA
*/
/*
remarks:
The german release 3.0 of PathAway violates the PathAway standards:
* N.. .... O.. .... instead of N.. .... E.. ....
* date is formatted in DDMMYYYY instead of YYYYMMDD
Release 4.x store only numeric coordinates and uses a six-number date.
*/
#include <ctype.h>
#include "defs.h"
#if PDBFMTS_ENABLED
#include "csv_util.h"
#include "pdbfile.h"
#include "strptime.h"
#define MYNAME "pathaway"
#define PPDB_MAGIC_TRK 0x55735472 /* UsTr */
#define PPDB_MAGIC_WPT 0x506f4c69 /* PoLi */
#define PPDB_MAGIC 0x4b6e5772 /* KwNr */
static pdbfile *file_in, *file_out;
static char *fname_out;
static short_handle mkshort_handle;
static gpsdata_type ppdb_type;
static unsigned char german_release = 0;
static char *datefmt;
static int ct;
typedef struct ppdb_appdata
{
unsigned char reservedA[274]; /* all 0 */
unsigned char dirtyFlag;
unsigned char dataBaseSubType; /* 0 = Track, 1 = Route */
short int dbAttributes; /* 0 */
char vehicleStr[100];
unsigned char reservedB[100]; /* all 0 */
} ppdb_appdata_t;
#define PPDB_APPINFO_SIZE sizeof(struct ppdb_appdata)
static ppdb_appdata_t *appinfo;
static char *opt_dbname = NULL;
static char *opt_deficon = NULL;
static char *opt_snlen = NULL;
static char *opt_date = NULL;
static arglist_t ppdb_args[] =
{
{"date", &opt_date, "Read/Write date format (i.e. DDMMYYYY)", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
{"dbname", &opt_dbname, "Database name", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
{"deficon", &opt_deficon, "Default icon name", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
{"snlen", &opt_snlen, "Length of generated shortnames", "10", ARGTYPE_INT, "1", NULL },
ARG_TERMINATOR
};
/*#undef PPDB_DEBUG*/
#define PPDB_DEBUG 1
#if PPDB_DEBUG
static void
internal_debug1(const char *filename, int fileline)
{
static int ct=1;
printf("DBG(%d): file %s, line %d: ", ct++, filename, fileline);
}
static void
internal_debug2(const char *format, ... )
{
va_list args;
va_start(args, format);
vprintf(format, args);
puts("");
va_end(args);
}
#define DBG(args) internal_debug1(__FILE__, __LINE__);internal_debug2 args
#else
#define DBG(args) ;
#endif
#define CHECK_INP(i, j, k) is_fatal((i != j), "Error in data structure (%s).", (k))
/*
* utilities
*/
static
char *ppdb_strcat(char *dest, char *src, char *def, int *size)
{
int len;
char *res, *tmp;
tmp = src;
if (tmp == NULL)
{
tmp = def;
if (tmp == NULL) return dest;
}
if (*tmp == '\0') return dest;
len = strlen(dest) + strlen(tmp) + 1;
if (len > *size)
{
*size = len;
res = xrealloc(dest, *size);
}
else
res = dest;
strcat(res, tmp);
return res;
}
#define STR_POOL_SIZE 16 /* !!! any power of 2 !!! */
static char *str_pool[STR_POOL_SIZE];
static size_t str_pool_s[STR_POOL_SIZE];
static int str_poolp = -1;
static
void str_pool_init(void)
{
int i;
for (i = 0; i < STR_POOL_SIZE; i++)
{
str_pool[i] = NULL;
str_pool_s[i] = 0;
}
}
static
void str_pool_deinit(void)
{
int i;
for (i = 0; i < STR_POOL_SIZE; i++)
if ( str_pool_s[i] != 0 )
{
xfree(str_pool[i]);
str_pool[i] = NULL;
str_pool_s[i] = 0;
}
}
static
char *str_pool_get(size_t size)
{
char *tmp;
str_poolp = ((str_poolp + 1) & (STR_POOL_SIZE - 1));
tmp = str_pool[str_poolp];
if (str_pool_s[str_poolp] == 0)
tmp = xmalloc(size);
else if (str_pool_s[str_poolp] < size)
tmp = xrealloc(tmp, size);
else
return tmp;
str_pool[str_poolp] = tmp;
str_pool_s[str_poolp] = size;
return tmp;
}
static
char *str_pool_getcpy(char *src, char *def)
{
char *res;
if (src == NULL)
{
src = def;
if (src == NULL) src = "";
}
res = str_pool_get(strlen(src) + 1);
strcpy(res, src);
return res;
}
/*
* decoding/formatting functions
*/
static
char *ppdb_fmt_float(const double val)
{
char *str = str_pool_get(32);
char *c;
snprintf(str, 32, "%.8f", val);
c = str + strlen(str) - 1;
while ((c > str) && (*c == '0'))
{
*c = '\0';
c--;
if (*c == '.')
{
c++;
*c = '0';
break;
}
}
return str;
}
static
char *ppdb_fmt_degrees(char dir, double val)
{
char *str = str_pool_get(32);
int deg = fabs(val);
double min = 60.0 * (fabs(val) - deg);
char *tmp;
snprintf(str, 31, "%c%0*d %.8f", dir, (deg > 99) ? 3 : 2, deg, min);
tmp = str + strlen(str) - 1; /* trim trailing nulls */
while ((tmp > str) && (*tmp == '0'))
{
*tmp = '\0';
tmp--;
if (*tmp == '.')
{
tmp++;
*tmp = '0';
break;
}
}
return str;
}
static
double ppdb_decode_coord(const char *str)
{
double val;
int deg;
char dir;
if (*str < 'A') /* only numeric */
{
CHECK_INP(1, sscanf(str,"%lf", &val), "decode_coord(1)");
return val;
}
else
{
char *tmp;
if (*str == 'O') german_release = 1;
tmp = strchr(str, ' ');
if ((tmp) && (tmp - str < 5))
{
CHECK_INP(3, sscanf(str,"%c%d %lf", &dir, °, &val), "decode_coord(2)");
val = deg + (val / 60.0);
}
else
{
CHECK_INP(2, sscanf(str,"%c%lf", &dir, &val), "decode_coord(3)");
}
if ((dir == 'S') || (dir == 'W'))
val = -val;
}
return val;
}
static
int ppdb_decode_tm(char *str, struct tm *tm)
{
int msec, d1, d2, d3, d4;
int year;
char *cx;
if (*str == '\0') return 0; /* empty date and time */
if (strchr(str, '.')) /* time in hhmmss.ms */
{
CHECK_INP(4, sscanf(str, "%02d%02d%02d.%d",
&tm->tm_hour, &tm->tm_min, &tm->tm_sec, &msec),
"decode_tm(1)");
}
else
{
CHECK_INP(3, sscanf(str, "%02d%02d%02d",
&tm->tm_hour, &tm->tm_min, &tm->tm_sec),
"decode_tm(2)");
}
cx = strchr(str, ' ');
if (cx == NULL) return 0; /* no date */
while (*cx == ' ') cx++;
if (*cx == '\0') return 0; /* no date */
if (datefmt)
{
struct tm tm2;
if (NULL == strptime(cx, datefmt, &tm2))
{
fatal(MYNAME ": Unable to convert date '%s' using format '%s' (%s)!\n", cx, datefmt, opt_date);
}
tm->tm_year = tm2.tm_year + 1900;
tm->tm_mon = tm2.tm_mon + 1;
tm->tm_mday = tm2.tm_mday;
}
else
{
time_t tnow;
struct tm now;
if (strlen(cx) != 8)
{
printf(MYNAME ": Date from first record is %s.\n", cx);
printf(MYNAME ": Please use option 'date' to specify how this is formatted.\n");
fatal(MYNAME ": (... -i pathaway,date=DDMMYY ...)\n");
}
CHECK_INP(4, sscanf(cx, "%02d%02d%02d%02d", &d1, &d2, &d3, &d4), "decode_tm(3)");
tnow = current_time();
now = *localtime(&tnow);
now.tm_year += 1900;
now.tm_mon++;
year = (d1 * 100) + d2;
/* the coordinates comes before date and time in
the dataset, so the flag "german_release" is set yet. */
/* next code works for most, except for 19. and 20. of month */
if ((german_release != 0) || (year < 1980) || (year > now.tm_year)) /* YYYYMMDD or DDMMYYY ????? */
{
tm->tm_year = (d3 * 100) + d4;
tm->tm_mon = d2;
tm->tm_mday = d1;
}
else
{
tm->tm_year = (d1 * 100) + d2;
tm->tm_mon = d3;
tm->tm_mday = d4;
}
}
return 1;
}
static
int ppdb_read_wpt(route_head *head, int isRoute)
{
char *data, *str;
double altfeet;
struct tm tm;
while (pdb_read_rec(file_in, NULL, NULL, NULL, (void *)&data) >= 0) {
waypoint *wpt_tmp = waypt_new();
int line = 0;
char *tmp = data;
while ((str = csv_lineparse(tmp, ",", """", line++))) {
tmp = NULL;
switch(line)
{
case 1:
wpt_tmp->latitude = ppdb_decode_coord(str);
break;
case 2:
wpt_tmp->longitude = ppdb_decode_coord(str);
break;
case 3:
if (*str != '\0')
{
CHECK_INP(1, sscanf(str, "%lf", &altfeet), "altitude");
if (altfeet != -9999)
wpt_tmp->altitude = FEET_TO_METERS(altfeet);
}
break;
case 4:
memset(&tm, 0, sizeof(tm));
if (ppdb_decode_tm(str, &tm))
{
tm.tm_year -= 1900;
tm.tm_mon--;
wpt_tmp->creation_time = mkgmtime(&tm);
}
break;
case 5:
if (*str != '\0')
wpt_tmp->shortname = xstrdup(str);
break;
case 6: /* icon, ignore */
break;
case 7:
if (*str != '\0')
wpt_tmp->notes = xstrdup(str);
break;
}
}
if (head && isRoute )
route_add_wpt(head, wpt_tmp);
else if (head)
track_add_wpt(head, wpt_tmp);
else
waypt_add(wpt_tmp);
}
return 0;
}
/* ============================================================================================
* &&& gobal callbacks &&&
* ----------------------------------------------------------------------------------------- */
static void ppdb_rd_init(const char *fname)
{
str_pool_init();
file_in = pdb_open(fname, MYNAME);
ct = 0;
if (opt_date)
datefmt = convert_human_date_format(opt_date);
else
datefmt = NULL;
}
static void ppdb_rd_deinit(void)
{
pdb_close(file_in);
str_pool_deinit();
if (datefmt) xfree(datefmt);
}
static void ppdb_read(void)
{
ppdb_appdata_t *info = NULL;
route_head *track_head, *route_head;
const char *descr = NULL;
if (file_in->creator != PPDB_MAGIC) /* identify the database */
fatal(MYNAME ": Not a PathAway pdb file.\n");
if (file_in->version != 3) /* Currently we support only version 3 */
fatal(MYNAME ": This file is from an untested version (%d) of PathAway and is unsupported.\n", file_in->version);
if ((file_in->appinfo_len > 0) && (file_in->appinfo != NULL))
{
info = (ppdb_appdata_t *) file_in->appinfo;
descr = info->vehicleStr;
}
switch(file_in->type)
{
case PPDB_MAGIC_TRK:
ppdb_type = trkdata; /* as default */
if (info != NULL)
{
switch(info->dataBaseSubType)
{
case 0:
ppdb_type = trkdata;
break;
case 1:
ppdb_type = rtedata;
break;
default:
fatal(MYNAME": Invalid database subtype.\n");
}
}
break;
case PPDB_MAGIC_WPT:
ppdb_type = wptdata;
break;
default:
fatal(MYNAME ": It looks like a PathAway pdb, but has no gps magic.\n");
}
switch(ppdb_type)
{
case trkdata:
track_head = route_head_alloc();
track_add_head(track_head);
track_head->rte_name = xstrdup(file_in->name);
ppdb_read_wpt(track_head, 0);
break;
case rtedata:
route_head = route_head_alloc();
route_add_head(route_head);
route_head->rte_name = xstrdup(file_in->name);
ppdb_read_wpt(route_head, 1);
break;
case wptdata:
ppdb_read_wpt(NULL, 0);
break;
case posndata:
fatal(MYNAME ": Realtime positioning not supported.\n");
break;
}
}
/* ============================================================================================
* PPDB: Write support
* -------------------------------------------------------------------------------------------*/
static void ppdb_wr_init(const char *fname)
{
int len;
fname_out = xstrdup(fname);
str_pool_init();
file_out = pdb_create(fname, MYNAME);
mkshort_handle = mkshort_new_handle();
ct = 0;
appinfo = NULL;
if (global_opts.synthesize_shortnames != 0)
{
len = atoi(opt_snlen);
setshort_length(mkshort_handle, len);
setshort_mustupper(mkshort_handle, 1);
setshort_badchars(mkshort_handle, ",");
setshort_whitespace_ok(mkshort_handle, 0);
}
if (opt_date)
{
char *c = convert_human_date_format(opt_date);
xasprintf(&datefmt, "%s %s", "%H%M%S", c);
xfree(c);
}
else
datefmt = xstrdup("%H%M%S %Y%m%d");
}
static void ppdb_wr_deinit(void)
{
mkshort_del_handle(&mkshort_handle);
pdb_close(file_out);
str_pool_deinit();
xfree(fname_out);
if (datefmt) xfree(datefmt);
if (appinfo) xfree(appinfo);
}
/*
* ppdb_write_wpt: callback for waypoint output
*/
#define REC_SIZE 128
static void ppdb_write_wpt(const waypoint *wpt)
{
char *buff, *tmp;
char latdir, longdir;
int len;
struct tm tm;
buff = xcalloc(REC_SIZE, 1);
if (wpt->latitude < 0)
latdir = 'S';
else
latdir = 'N';
if (wpt->longitude < 0)
longdir = 'W';
else
longdir = 'E';
snprintf(buff, REC_SIZE, "%s,%s,",
ppdb_fmt_degrees(latdir, wpt->latitude),
ppdb_fmt_degrees(longdir, wpt->longitude)
);
len = REC_SIZE; /* we have coordinates in buff, now optional stuff */
if (fabs(wpt->altitude) < 9999.0)
{
tmp = str_pool_get(32);
snprintf(tmp, 32, ppdb_fmt_float(METERS_TO_FEET(wpt->altitude)));
buff = ppdb_strcat(buff, tmp, NULL, &len);
}
buff = ppdb_strcat(buff, ",", NULL, &len);
if ( wpt->creation_time != 0)
{
tmp = str_pool_get(20);
tm = *gmtime(&wpt->creation_time);
strftime(tmp, 20, datefmt, &tm);
buff = ppdb_strcat(buff, tmp, NULL, &len);
}
buff = ppdb_strcat(buff, ",", NULL, &len);
if (global_opts.synthesize_shortnames != 0)
{
tmp = mkshort_from_wpt(mkshort_handle, wpt);
DBG(("shortname %s from %s", tmp, wpt->shortname));
}
else
{
tmp = str_pool_getcpy(wpt->shortname, "");
while (strchr(tmp, ',') != NULL)
*strchr(tmp, ',') = '.';
}
buff = ppdb_strcat(buff, tmp, "", &len);
buff = ppdb_strcat(buff, ",", NULL, &len);
buff = ppdb_strcat(buff, opt_deficon, "0", &len);
buff = ppdb_strcat(buff, ",", NULL, &len);
tmp = str_pool_getcpy(wpt->description, "");
if (strchr(tmp, ',') != NULL )
{
buff = ppdb_strcat(buff, "\"", NULL, &len);
while (strchr(tmp, '"') != NULL)
*strchr(tmp, '"') = '\'';
buff = ppdb_strcat(buff, tmp, NULL, &len);
buff = ppdb_strcat(buff, "\"", NULL, &len);
}
else
buff = ppdb_strcat(buff, tmp, "", &len);
len = strlen(buff) + 1;
pdb_write_rec(file_out, 0, 0, ct++, buff, len);
xfree(buff);
}
/*
* track and route write callbacks
*/
static void ppdb_write(void)
{
if (opt_dbname)
strncpy(file_out->name, opt_dbname, PDB_DBNAMELEN);
file_out->attr = PDB_FLAG_BACKUP;
file_out->ctime = file_out->mtime = current_time() + 2082844800U;
file_out->creator = PPDB_MAGIC;
file_out->version = 3;
if (global_opts.objective != wptdata) /* Waypoint target do not need appinfo block */
{
appinfo = xcalloc(1, sizeof(*appinfo));
file_out->appinfo = (void *)appinfo;
file_out->appinfo_len = PPDB_APPINFO_SIZE;
}
switch(global_opts.objective) /* Only one target is possible */
{
case wptdata:
if (opt_dbname == NULL) strncpy(file_out->name, "PathAway Waypoints", PDB_DBNAMELEN);
file_out->type = PPDB_MAGIC_WPT;
waypt_disp_all(ppdb_write_wpt);
break;
case trkdata:
if (opt_dbname == NULL) strncpy(file_out->name, "PathAway Track", PDB_DBNAMELEN);
file_out->type = PPDB_MAGIC_TRK;
appinfo->dataBaseSubType = 0;
track_disp_all(NULL, NULL, ppdb_write_wpt);
break;
case rtedata:
if (opt_dbname == NULL) strncpy(file_out->name, "PathAway Route", PDB_DBNAMELEN);
file_out->type = PPDB_MAGIC_TRK;
appinfo->dataBaseSubType = 1;
route_disp_all(NULL, NULL, ppdb_write_wpt);
break;
case posndata:
fatal(MYNAME ": Realtime positioning not supported.\n");
break;
}
}
ff_vecs_t ppdb_vecs = {
ff_type_file,
FF_CAP_RW_ALL,
ppdb_rd_init,
ppdb_wr_init,
ppdb_rd_deinit,
ppdb_wr_deinit,
ppdb_read,
ppdb_write,
NULL,
ppdb_args,
CET_CHARSET_ASCII, 0 /* CET-REVIEW */
};
#endif
syntax highlighted by Code2HTML, v. 0.9.1