You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

igo8.cc 12KB

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