/*
Read and write TomTom .ov2 files.
Copyright (C) 2005 Ronald Parker (babeltomtom@parkrrrr.com) and
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.
*/
/*
This module is based on my reverse-engineering of the .ov2 format, so
it might not be aware of all record types. In particular, I've seen
a type-3 record that may contain additional strings, but since I haven't
seen any of those from a legitimate source, I don't know what they are
supposed to contain. Thus, they are not currently supported. (The one
I saw was due to an errant pair of double-quotes in the input to
makeov2.exe.) -- Ron Parker, 28 April 2005
Because they've been seen in the wild, I have updated the reader to
deal with type 3 as if they were type 2. I still haven't seen any
records that fill in the other two strings, so until I know for sure
that they are indeed strings, I'm just putting them on the end of the
description string beyond the NUL terminator. -- Ron Parker, 17 July 2006
*/
#include <cstdio> // for printf, snprintf, SEEK_CUR, EOF
#include <cstdlib> // for qsort
#include <cstring> // for strlen
#include <QString> // for QString
#include <QtGlobal> // for foreach
#include "defs.h"
#include "gbfile.h" // for gbfgetint32, gbfputint32, gbfclose, gbfgetc, gbfputc, gbfseek, gbfile, gbfeof, gbfread, gbftell, gbfwrite, gbfopen_le
#define MYNAME "TomTom"
static gbfile* file_in;
static gbfile* file_out;
static
QVector<arglist_t> tomtom_args = {
};
static void
rd_init(const QString& fname)
{
file_in = gbfopen_le(fname, "rb", MYNAME);
}
static void
rd_deinit()
{
gbfclose(file_in);
}
static void
wr_init(const QString& fname)
{
file_out = gbfopen_le(fname, "wb", MYNAME);
}
static void
wr_deinit()
{
gbfclose(file_out);
}
#define read_long(f) gbfgetint32((f))
#define read_char(f) gbfgetc((f))
#ifdef DEAD_CODE_IS_REBORN
/*
* Decode a type 8 compressed record
*/
static char*
decode_8(int sz, const unsigned char* inbuf)
{
static const char encoding_8[] = "X. SaerionstldchumgpbkfzvACBMPG-";
static const int encoding_8_high[8] = {0x2,0x3,0x4,0x5,0x6,0x7,0xe,0xf};
// Maximally sized for laziness.
char* rval = (char*) xmalloc(sz * 3 + 1);
char* out = rval;
int i;
for (i = 0; i < sz;) {
if (inbuf[0] & 0x80) {
int idx;
int res;
idx = (inbuf[0] & 0x70) >> 4;
res = inbuf[0] & 0x0f;
res |= encoding_8_high[idx] << 4;
*out++ = res;
inbuf++;
i++;
} else {
int c1 = (inbuf[0] & 0x7c) >> 2;
int c2 = ((inbuf[0] & 3) << 3) | (inbuf[1] & 0xe0) >> 5;
int c3 = inbuf[1] & 0x1f;
if ((c1 | c2 | c3) > 0x1f) {
fatal("bit unpacking error");
}
*out++ = encoding_8[c1];
*out++ = encoding_8[c2];
*out++ = encoding_8[c3];
inbuf+=2;
i+=2;
}
}
return rval;
}
#endif
#ifdef DEAD_CODE_IS_REBORN
static void
decode_latlon(double* lat, double* lon)
{
unsigned char latbuf[3];
unsigned char lonbuf[3];
double rlat, rlon;
gbfread(&lonbuf, 3, 1, file_in);
gbfread(&latbuf, 3, 1, file_in);
rlat = ((latbuf[2] << 16) + (latbuf[1] << 8) + latbuf[0]) / 1000000.0;
*lat = 80 - rlat;
*lon = rlon = 123.456;
}
#endif
static void
check_recsize(int sz)
{
if ((sz > 100000) || (sz < 0)) {
fatal(MYNAME ":malformed file. Bad record size.");
}
}
static void
data_read()
{
long recsize;
long x;
long y;
char* desc;
Waypoint* wpt_tmp;
while (!gbfeof(file_in)) {
int rectype = read_char(file_in);
if (rectype == EOF) {
fatal(MYNAME ":Unexpected EOF.");
}
if (global_opts.debug_level >= 5) {
printf("Reading record type %d\n", rectype);
}
switch (rectype) {
case 0:
case 100:
if (global_opts.debug_level >= 5) {
printf("Skipping deleted record\n");
}
recsize = read_long(file_in) - 5;
check_recsize(recsize);
if (global_opts.debug_level >= 5) {
printf("Skipping %li bytes\n", recsize);
}
gbfseek(file_in, recsize, SEEK_CUR);
break;
case 1:
/* a block header; ignored on read */
read_long(file_in);
read_long(file_in);
read_long(file_in);
read_long(file_in);
read_long(file_in);
break;
case 2:
case 3:
recsize = read_long(file_in);
check_recsize(recsize);
x = read_long(file_in);
y = read_long(file_in);
desc = (char*)xmalloc(recsize - 13);
gbfread(desc, recsize-13, 1, file_in);
wpt_tmp = new Waypoint;
wpt_tmp->longitude = x/100000.0;
wpt_tmp->latitude = y/100000.0;
wpt_tmp->description = STRTOUNICODE(desc);
xfree(desc);
desc = nullptr;
// TODO:: description in rectype 3 contains two zero-terminated strings
// First is same as rectype 2, second apparently contains the unique ID of the waypoint
// See http://www.tomtom.com/lib/doc/PRO/TTN6_SDK_documentation.zip
if (rectype == 3) {
warning("Unexpected waypoint record type %d encountered.\nThe unique ID of the POI may have been dropped.\n", rectype);
}
waypt_add(wpt_tmp);
break;
case 8:
case 24:
#if 0 // Fallthrough for now to silently ignore these until this is done.
recsize = read_char(file_in) ;
check_recsize(recsize);
wpt_tmp = new Waypoint;
decode_latlon(&wpt_tmp->latitude, &wpt_tmp->longitude);
gbfread(tbuf, 3, 1, file_in);
gbfread(tbuf, 3, 1, file_in);
gbfread(tbuf, recsize, 1, file_in);
wpt_tmp->shortname = decode_8(recsize, tbuf);
waypt_add(wpt_tmp);
break;
#else
#endif
case 9:
case 25:
recsize = read_char(file_in) + 6;
check_recsize(recsize);
if (global_opts.debug_level >= 5)
warning("Unknown record type 0x%x; skipping %ld bytes.\n",
rectype, recsize);
gbfseek(file_in, recsize, SEEK_CUR);
break;
default:
if (global_opts.debug_level >= 1) {
warning("Unexpected waypoint record type: %d at offset 0x%x\n", rectype, gbftell(file_in));
}
}
}
}
struct hdr {
const Waypoint* wpt;
};
static int compare_lon(const void* a, const void* b);
static
int
compare_lat(const void* a, const void* b)
{
const auto* wa = (const struct hdr*) a;
const auto* wb = (const struct hdr*) b;
double difference = wa->wpt->latitude - wb->wpt->latitude;
if (difference < 0) {
return -1;
}
if (difference) {
return 1;
}
if (wa->wpt->longitude - wb->wpt->longitude == 0) {
return wa->wpt->shortname.compare(wb->wpt->shortname);
}
return compare_lon(a,b);
}
static
int
compare_lon(const void* a, const void* b)
{
const auto* wa = (const struct hdr*)a;
const auto* wb = (const struct hdr*)b;
double difference = wa->wpt->longitude - wb->wpt->longitude;
if (difference < 0) {
return -1;
}
if (difference) {
return 1;
}
if (wa->wpt->latitude - wb->wpt->latitude == 0) {
return wa->wpt->shortname.compare(wb->wpt->shortname);
}
return compare_lat(a,b);
}
#define write_long(f,v) gbfputint32((v),f)
static void
write_float_as_long(gbfile* file, double value)
{
long tmp = (value + 0.500000000001);
write_long(file, tmp);
}
#define write_char(f,c) gbfputc((c),f)
#define write_string(f,s) gbfwrite((s),1,strlen(s)+1,f)
struct blockheader {
struct hdr* start;
long count;
long size;
double minlat;
double maxlat;
double minlon;
double maxlon;
struct blockheader* ch1;
struct blockheader* ch2;
};
static void
write_blocks(gbfile* f, struct blockheader* blocks)
{
write_char(f, 1);
write_long(f, blocks->size);
write_float_as_long(f, blocks->maxlon*100000);
write_float_as_long(f, blocks->maxlat*100000);
write_float_as_long(f, blocks->minlon*100000);
write_float_as_long(f, blocks->minlat*100000);
if (blocks->ch1) {
write_blocks(f, blocks->ch1);
}
if (blocks->ch2) {
write_blocks(f, blocks->ch2);
}
if (!blocks->ch1 && !blocks->ch2) {
for (int i = 0; i < blocks->count; i++) {
char desc_field [256];
write_char(f, 2);
if (global_opts.smart_names &&
blocks->start[i].wpt->gc_data->diff &&
blocks->start[i].wpt->gc_data->terr) {
snprintf(desc_field,sizeof(desc_field),"%s(t%ud%u)%s(type%dcont%d)",STRFROMUNICODE(blocks->start[i].wpt->description),
blocks->start[i].wpt->gc_data->terr/10,
blocks->start[i].wpt->gc_data->diff/10,
STRFROMUNICODE(blocks->start[i].wpt->shortname),
(int) blocks->start[i].wpt->gc_data->type,
(int) blocks->start[i].wpt->gc_data->container);
//Unfortunately enums mean we get numbers for cache type and container.
} else {
snprintf(desc_field, sizeof(desc_field), "%s",
STRFROMUNICODE(blocks->start[i].wpt->description));
}
write_long(f, strlen(desc_field) + 14);
write_float_as_long(f, blocks->start[i].wpt->longitude*100000);
write_float_as_long(f, blocks->start[i].wpt->latitude*100000);
write_string(f, desc_field);
}
}
}
static struct blockheader*
compute_blocks(struct hdr* start, int count,
double minlon, double maxlon, double minlat, double maxlat)
{
auto* newblock = (struct blockheader*)xcalloc(1, sizeof(struct blockheader));
newblock->start = start;
newblock->count = count;
newblock->minlon = minlon;
newblock->maxlon = maxlon;
newblock->minlat = minlat;
newblock->maxlat = maxlat;
newblock->size = 4 * 5 + 1; /* hdr is 5 longs, 1 char */
if (count < 20) {
for (int i = 0; i < count; i++) {
newblock->size += 4 * 3 + 1;
/* wpt const part 3 longs, 1 char */
const Waypoint* wpt = start[i].wpt;
newblock->size += wpt->description.length() + 1;
}
} else {
if ((maxlat-minlat)>(maxlon-minlon)) {
/* split along lats */
qsort(start, count, sizeof(*start), compare_lat);
newblock->ch1 = compute_blocks(start, count/2,
minlon, maxlon, minlat,
start[count/2].wpt->latitude);
newblock->ch2 = compute_blocks(start+count/2,
count-count/2, minlon, maxlon,
start[count/2].wpt->latitude, maxlat);
} else {
/* split along lons */
qsort(start, count, sizeof(*start), compare_lon);
newblock->ch1 = compute_blocks(start, count/2,
minlon, start[count/2].wpt->longitude,
minlat, maxlat);
newblock->ch2 = compute_blocks(start+count/2,
count-count/2, start[count/2].wpt->longitude,
maxlon, minlat, maxlat);
}
if (newblock->ch1) {
newblock->size += newblock->ch1->size;
}
if (newblock->ch2) {
newblock->size += newblock->ch2->size;
}
}
return newblock;
}
static void
free_blocks(struct blockheader* block)
{
if (block->ch1) {
free_blocks(block->ch1);
}
if (block->ch2) {
free_blocks(block->ch2);
}
xfree(block);
}
static void
data_write()
{
int ct = waypt_count();
struct hdr* htable, *bh;
extern WaypointList* global_waypoint_list;
double minlon = 200;
double maxlon = -200;
double minlat = 200;
double maxlat = -200;
struct blockheader* blocks = nullptr;
htable = (struct hdr*) xmalloc(ct * sizeof(*htable));
bh = htable;
// Iterate with waypt_disp_all?
for (const Waypoint* waypointp : qAsConst(*global_waypoint_list)) {
bh->wpt = waypointp;
if (waypointp->longitude > maxlon) {
maxlon = waypointp->longitude;
}
if (waypointp->longitude < minlon) {
minlon = waypointp->longitude;
}
if (waypointp->latitude > maxlat) {
maxlat = waypointp->latitude;
}
if (waypointp->latitude < minlat) {
minlat = waypointp->latitude;
}
bh ++;
}
blocks = compute_blocks(htable, ct, minlon, maxlon, minlat, maxlat);
write_blocks(file_out, blocks);
free_blocks(blocks);
xfree(htable);
}
ff_vecs_t tomtom_vecs = {
ff_type_file,
FF_CAP_RW_WPT,
rd_init,
wr_init,
rd_deinit,
wr_deinit,
data_read,
data_write,
nullptr,
&tomtom_args,
CET_CHARSET_MS_ANSI, 0 /* CET-REVIEW */
, NULL_POS_OPS,
nullptr
};