1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
/*
IGO8 Track Format
Copyright (C) 2008 Dustin Johnson, Dustin@Dustinj.us
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
July 26, 2008 - Dustin: Added tracknum, title, and description options
July 26, 2008 - Dustin: Validated the new code for char to Unicode conversion
*/
/*
iGo8 (*.trk) Format Overview
|------------------------------| <--\
| ID Block (20B) | |
|------------------------------| |
| | |
| |
| | H
| | e
| | a
| Description Block (256B) | d
| | e
| | r
| |
| | |
| | |
| | |
|------------------------------| <--/
| Information Block (12B) |
|------------------------------|
| Waypoint 1 |
|------------------------------|
| Waypoint 2 |
|------------------------------|
| Waypoint 3 |
|------------------------------|
| ... |
|------------------------------|
ID Block: defined by igo8_id_block
Description Block: Two null-terminated unicode 2 strings.
The first is the title of the track,
the second is the description.
Information Block: defined by igo8_information_block
Waypoint: defined by igo8_point
*/
#include <ctype.h>
#include <time.h>
#include "defs.h"
#include "cet.h"
#include "cet_util.h"
#define FLOAT_TO_INT(x) ((int)((x) + ((x)<0?-0.5:0.5)))
#define IGO8_HEADER_SIZE (sizeof(igo8_id_block) + 256)
#define MYNAME "IGO8"
typedef struct _igo8_id_block {
gbuint32 unknown_1;
gbuint32 unknown_2;
gbuint32 unknown_3;
gbuint32 track_number;
gbuint32 unknown_4;
} igo8_id_block, *p_igo8_id_block;
typedef struct _igo8_information_block {
gbuint32 start_time; // In Unix time
gbuint32 zero; // Doesn't appear to serve a purpose
gbuint32 total_file_size; // In bytes
} igo8_information_block, *p_igo8_information_block;
typedef struct _igo8_point {
gbuint32 unix_time;
gbuint32 lon;
gbuint32 lat;
} igo8_point, *p_igo8_point;
// Files
static gbfile* igo8_file_in;
static gbfile* igo8_file_out;
// Options
static char* igo8_option_tracknum = NULL;
static char* igo8_option_title = NULL;
static char* igo8_option_description = NULL;
// Internal state
static gbuint32 invented_time;
static gbuint32 point_count;
static int in_point_count;
// Exported options list
static arglist_t igo8_options[] = {
{ "tracknum", &igo8_option_tracknum, "Track identification number", NULL, ARGTYPE_INT, ARG_NOMINMAX },
{ "title", &igo8_option_title, "Track title", NULL, ARGTYPE_STRING, ARG_NOMINMAX },
{ "description", &igo8_option_description, "Track description", NULL, ARGTYPE_STRING, ARG_NOMINMAX },
ARG_TERMINATOR
};
// Sanity check
static void igo8_check_type_sizes()
{
if (sizeof(igo8_point) != 12) {
fatal(MYNAME ": igo8_point is %ld bytes instead of the required 12.\n",
(long) sizeof(igo8_point));
}
if (sizeof(igo8_information_block) != 12) {
fatal(MYNAME ": igo8_information_block is %ld bytes instead of the required 12.\n",
(long) sizeof(igo8_information_block));
}
if (sizeof(igo8_id_block) != 20) {
fatal(MYNAME ": igo8_id_block is %ld bytes instead of the required 20.\n",
(long) sizeof(igo8_id_block));
}
}
// Reader initialization callback
static void igo8_read_init(const char* fname)
{
igo8_file_in = gbfopen_le(fname, "rb", MYNAME);
// Make sure that we are in the environment we expect and require
igo8_check_type_sizes();
// Seek past the header and most of the Information Block. Read
// the last word for trackpoint count since latest igo8 seems to
// zero-pad the files.
gbfseek(igo8_file_in, IGO8_HEADER_SIZE + sizeof(igo8_information_block) - 4, SEEK_SET);
in_point_count = (gbfgetint32(igo8_file_in) - IGO8_HEADER_SIZE -
sizeof(igo8_information_block)) / sizeof(igo8_point);
}
// Reader callback
static void igo8_read(void)
{
waypoint* wpt_tmp;
route_head* track_head;
igo8_point point;
track_head = route_head_alloc();
track_add_head(track_head);
while (in_point_count &&
gbfread(&point, sizeof(point), 1, igo8_file_in) > 0) {
in_point_count--;
wpt_tmp = waypt_new();
wpt_tmp->latitude = le_read32(&point.lat) / (double)0x800000;
wpt_tmp->longitude = le_read32(&point.lon) / (double)0x800000;
wpt_tmp->creation_time = le_read32(&point.unix_time);
track_add_wpt(track_head, wpt_tmp);
}
}
// Reader close callback
static void igo8_read_deinit(void)
{
gbfclose(igo8_file_in);
}
// Writer initialize callback
static void igo8_write_init(const char* fname)
{
igo8_file_out = gbfopen_le(fname, "wb", MYNAME);
igo8_check_type_sizes();
invented_time = 1;
point_count = 0;
}
// Writer close callback
static void igo8_write_deinit(void)
{
gbuint32 normalized_file_size;
// Seek to the start of the third long in the Information Block, this is
// where we will write out the total size of the file.
gbfseek(igo8_file_out, IGO8_HEADER_SIZE + sizeof(gbuint32)*2, SEEK_SET);
// The total size of the file is the number of points written + Information block + Header
le_write32(&normalized_file_size, sizeof(igo8_point)*(point_count) + sizeof(igo8_information_block) + IGO8_HEADER_SIZE);
// Write the size
gbfwrite(&normalized_file_size, sizeof(normalized_file_size), 1, igo8_file_out);
gbfclose(igo8_file_out);
}
// Write point callback
static void write_igo8_track_point(const waypoint* wpt)
{
igo8_point point;
memset(&point, 0, sizeof(point));
// iGo8 appears to expect a time, if one isn't provided
// then we shall make our own, where each point is one
// second apart.
if (wpt->creation_time == 0) {
le_write32(&point.unix_time, invented_time++);
} else {
le_write32(&point.unix_time, wpt->creation_time);
}
// Write the first part of the Information Block, the start time
if (point_count == 0) {
gbfwrite(&point, sizeof(point), 1, igo8_file_out);
}
le_write32(&point.lon, FLOAT_TO_INT(wpt->longitude * 0x800000));
le_write32(&point.lat, FLOAT_TO_INT(wpt->latitude * 0x800000));
gbfwrite(&point, sizeof(point), 1, igo8_file_out);
// Count the number of point printed, we will use this at the end to
// finish filling out the Information Block.
point_count++;
}
// Write src unicode str to the dst cstring using unicode characters
// All lengths are in bytes
unsigned int print_unicode(char* dst, const unsigned int dst_max_length, short* src, unsigned int src_len)
{
// Check to see what length we were passed, if the length doesn't include the null
// then we make it include the null
if (src[(src_len/2) - 1] != 0) {
// If the last character isn't null check the next one
if (src[(src_len/2)] != 0) {
// If the next character also inst' null, make it null
src[(src_len/2)] = 0;
} else {
// The next character is null, adjust the total length of the str to account for this
src_len += 2;
}
}
// Make sure we fit in our dst size
if (src_len > dst_max_length) {
src_len = dst_max_length;
src[(src_len/2) - 1] = 0; // Make sure we keep that terminating null around
}
// Copy the str
memcpy(dst, src, src_len);
return src_len;
}
// This is a sort of hacked together ascii-> unicode 2 converter. I have no idea
// if iGo8 even supports real unicode 2, but is does look like it as every ascii
// character is a short with the ascii character as the least significant 7 bits
//
// Please replace this with a much more filled out and correct version if you see
// fit.
/* 2008/06/24, O.K.: Use CET library for ascii-> unicode 2 converter */
// 2008/07/25, Dustin: Slight fix to make sure that we always null terminate the
// string, validate that the use of the CET library provides
// conmforming output, remove my old junk converter code.
unsigned int ascii_to_unicode_2(char* dst, const unsigned int dst_max_length, const char* src)
{
short* unicode;
int len;
unicode = cet_str_any_to_uni(src, &cet_cs_vec_ansi_x3_4_1968, &len);
len *= 2; /* real size in bytes */
len = print_unicode(dst, dst_max_length, unicode, len);
xfree(unicode);
return len;
}
void write_header()
{
char header[IGO8_HEADER_SIZE] = {'\0'};
igo8_id_block tmp_id_block;
p_igo8_id_block id_block = (p_igo8_id_block)header;
gbuint32 current_position = 0;
const char* title = "Title";
const char* description = "Description";
// These values seem to be constant for me, but I have no idea what they are.
tmp_id_block.unknown_1 = 0x0000029B;
tmp_id_block.unknown_2 = 0x000003E7;
tmp_id_block.unknown_3 = 0x00000003;
// This appears to be a unique number that IDs the track.
// It is mono-incrementing and offset by 2 above the track number.
// e.g. "Track 1" --> track_number = 3
// XXX - Dustin: My guess is that this number is used as the key for the track color, if
// XXX - Dustin: multiple tracks have the same color they will be colored the same, just
// XXX - Dustin: a guess though.
if (igo8_option_tracknum) {
tmp_id_block.track_number = atoi(igo8_option_tracknum);
} else {
tmp_id_block.track_number = 0x00000010;
}
tmp_id_block.unknown_4 = 0x00000001;
// Byte swap out to the header buffer.
le_write32(&id_block->unknown_1, tmp_id_block.unknown_1);
le_write32(&id_block->unknown_2, tmp_id_block.unknown_2);
le_write32(&id_block->unknown_3, tmp_id_block.unknown_3);
le_write32(&id_block->track_number, tmp_id_block.track_number);
le_write32(&id_block->unknown_4, tmp_id_block.unknown_4);
// Move past the ID block, we have just filled it.
current_position += sizeof(*id_block);
// Set the title of the track
// Note: we shorten the length of the potential title by 2 because we need to leave at
// least enough room to have a null for the description string that follows it.
if (igo8_option_title) {
title = igo8_option_title;
}
current_position += ascii_to_unicode_2((char*)(header+current_position), IGO8_HEADER_SIZE - current_position - 2, title);
// Set the description of the track
if (igo8_option_description) {
description = igo8_option_description;
}
current_position += ascii_to_unicode_2((char*)(header+current_position), IGO8_HEADER_SIZE - current_position, description);
gbfwrite(&header, IGO8_HEADER_SIZE, 1, igo8_file_out);
}
// Writer callback
static void igo8_write(void)
{
write_header();
track_disp_all(NULL, NULL, write_igo8_track_point);
}
// Callback definitions
ff_vecs_t igo8_vecs = {
ff_type_file,
{ ff_cap_none, (ff_cap)(ff_cap_read | ff_cap_write), ff_cap_none },
igo8_read_init,
igo8_write_init,
igo8_read_deinit,
igo8_write_deinit,
igo8_read,
igo8_write,
NULL,
igo8_options,
CET_CHARSET_UTF8,
1
};
|