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.

1630 lines
40KB

  1. /*
  2. Support for Garmin Points of Interest (.gpi files)
  3. Copyright (C) 2007 Olaf Klein, o.b.klein@gpsbabel.org
  4. Copyright (C) 2007-2012 Robert Lipe, robertlipe+source@gpsbabel.org
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  16. */
  17. /*
  18. History:
  19. * 2007/05/18: initial release (only a reader)
  20. * 2007/05/20: added writer code with embedded bitmap
  21. * 2007/05/22: add support for multiple bounding boxes
  22. (useful / required!) for large waypoints lists
  23. * 2007/05/23: add optional user bitmap
  24. * 2007/06/02: new method to compute center (mean) of bounds
  25. avoid endless loop in group splitting
  26. * 2007/07/10: put address fields (i.e. city) into GMSD
  27. * 2007/07/12: add write support for new address fields
  28. * 2007/10/20: add option unique
  29. * 2007/12/02: support speed and proximity distance (+ alerts)
  30. * 2008/01/14: fix structure error after adding speed/proximity
  31. * 2008/03/22: add options "speed" and "proximity" (default values) and "sleep"
  32. ToDo:
  33. * Display mode ("Symbol & Name") ??? not in gpi ???
  34. * support category from GMSD "Garmin Special Data"
  35. */
  36. #include "defs.h"
  37. #include "cet_util.h"
  38. #include "jeeps/gpsmath.h"
  39. #include "garmin_fs.h"
  40. #include "garmin_gpi.h"
  41. #include <stdlib.h>
  42. #include <QtCore/QTextCodec>
  43. #define MYNAME "garmin_gpi"
  44. #define GPI_DBG 1
  45. #undef GPI_DBG
  46. #define DEFAULT_ICON "Waypoint"
  47. #define WAYPOINTS_PER_BLOCK 128
  48. /* flags used in the gpi address mask */
  49. #define GPI_ADDR_CITY 1
  50. #define GPI_ADDR_COUNTRY 2
  51. #define GPI_ADDR_STATE 4
  52. #define GPI_ADDR_POSTAL_CODE 8
  53. #define GPI_ADDR_ADDR 16
  54. static char* opt_cat, *opt_pos, *opt_notes, *opt_hide_bitmap, *opt_descr, *opt_bitmap;
  55. static char* opt_unique, *opt_alerts, *opt_units, *opt_speed, *opt_proximity, *opt_sleep;
  56. static char* opt_writecodec;
  57. static double defspeed, defproximity;
  58. static int alerts;
  59. static arglist_t garmin_gpi_args[] = {
  60. {
  61. "alerts", &opt_alerts, "Enable alerts on speed or proximity distance",
  62. NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  63. },
  64. {
  65. "bitmap", &opt_bitmap, "Use specified bitmap on output",
  66. NULL, ARGTYPE_FILE, ARG_NOMINMAX
  67. },
  68. {
  69. "category", &opt_cat, "Default category on output",
  70. "My points", ARGTYPE_STRING, ARG_NOMINMAX
  71. },
  72. {
  73. "hide", &opt_hide_bitmap, "Don't show gpi bitmap on device",
  74. NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  75. },
  76. {
  77. "descr", &opt_descr, "Write description to address field",
  78. NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  79. },
  80. {
  81. "notes", &opt_notes, "Write notes to address field",
  82. NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  83. },
  84. {
  85. "position", &opt_pos, "Write position to address field",
  86. NULL, ARGTYPE_BOOL, ARG_NOMINMAX
  87. },
  88. {
  89. "proximity", &opt_proximity, "Default proximity",
  90. NULL, ARGTYPE_STRING, ARG_NOMINMAX
  91. },
  92. {
  93. "sleep", &opt_sleep, "After output job done sleep n second(s)",
  94. NULL, ARGTYPE_INT, "1", NULL
  95. },
  96. {
  97. "speed", &opt_speed, "Default speed",
  98. NULL, ARGTYPE_STRING, ARG_NOMINMAX
  99. },
  100. {
  101. "unique", &opt_unique, "Create unique waypoint names (default = yes)",
  102. "Y", ARGTYPE_BOOL, ARG_NOMINMAX
  103. },
  104. {
  105. "units", &opt_units, "Units used for names with @speed ('s'tatute or 'm'etric)",
  106. "m", ARGTYPE_STRING, ARG_NOMINMAX
  107. },
  108. {
  109. "writecodec", &opt_writecodec, "codec to use for writing strings",
  110. "windows-1252", ARGTYPE_STRING, ARG_NOMINMAX
  111. },
  112. ARG_TERMINATOR
  113. };
  114. typedef struct {
  115. public:
  116. int D2;
  117. char S3[9]; /* "GRMRECnn" */
  118. time_t crdate; /* creation date and time */
  119. char POI[4]; /* "POI" */
  120. char S8[3];
  121. QString group;
  122. QString category;
  123. } reader_data_t;
  124. typedef struct writer_data_s {
  125. queue Q;
  126. int ct;
  127. int sz;
  128. int alert;
  129. bounds bds;
  130. struct writer_data_s* top_left;
  131. struct writer_data_s* top_right;
  132. struct writer_data_s* buttom_left;
  133. struct writer_data_s* buttom_right;
  134. } writer_data_t;
  135. typedef struct gpi_waypt_data_s {
  136. int sz;
  137. char* addr;
  138. char* postal_code;
  139. } gpi_waypt_data_t;
  140. typedef struct {
  141. int32_t size;
  142. int16_t res1;
  143. int16_t res2;
  144. int32_t image_offset;
  145. int32_t header_size;
  146. int32_t width;
  147. int32_t height;
  148. int16_t planes;
  149. int16_t bpp;
  150. int32_t compression_type;
  151. int32_t image_data_size;
  152. int32_t resolution_h;
  153. int32_t resolution_v;
  154. int32_t used_colors;
  155. int32_t important_colors;
  156. } bmp_header_t;
  157. typedef struct {
  158. int16_t index;
  159. int16_t height;
  160. int16_t width;
  161. int16_t line_sz;
  162. int16_t bpp;
  163. int16_t fixed_0;
  164. int32_t image_size;
  165. int32_t fixed_2c;
  166. int32_t flag1;
  167. int32_t tr_color;
  168. int32_t flag2;
  169. int32_t size_2c;
  170. } gpi_bitmap_header_t;
  171. typedef struct {
  172. int sz;
  173. int alerts;
  174. short mask;
  175. char addr_is_dynamic;
  176. char* addr;
  177. char* city;
  178. char* country;
  179. char* phone_nr;
  180. char* postal_code;
  181. char* state;
  182. } gpi_waypt_t;
  183. static gbfile* fin, *fout;
  184. static int16_t codepage; /* code-page, i.e. 1252 */
  185. static reader_data_t* rdata;
  186. static writer_data_t* wdata;
  187. static short_handle short_h;
  188. static char units;
  189. static time_t gpi_timestamp = 0;
  190. #ifdef GPI_DBG
  191. # define PP warning("@%1$6x (%1$8d): ", gbftell(fin))
  192. # define dbginfo warning
  193. #else
  194. # define PP
  195. #endif
  196. /*******************************************************************************
  197. * %%% gpi reader %%% *
  198. *******************************************************************************/
  199. /* look for or initialize GMSD */
  200. static garmin_fs_t*
  201. gpi_gmsd_init(Waypoint* wpt)
  202. {
  203. garmin_fs_t* gmsd = GMSD_FIND(wpt);
  204. if (wpt == NULL) {
  205. fatal(MYNAME ": Error in file structure.\n");
  206. }
  207. if (gmsd == NULL) {
  208. gmsd = garmin_fs_alloc(-1);
  209. fs_chain_add(&wpt->fs, (format_specific_data*) gmsd);
  210. }
  211. return gmsd;
  212. }
  213. /* read a standard string with or without 'EN' (or whatever) header */
  214. static char*
  215. gpi_read_string_old(const char* field)
  216. {
  217. int l1;
  218. char* res = NULL;
  219. l1 = gbfgetint16(fin);
  220. if (l1 > 0) {
  221. short l2;
  222. char first;
  223. first = gbfgetc(fin);
  224. if (first == 0) {
  225. char en[2];
  226. is_fatal((gbfgetc(fin) != 0),
  227. MYNAME ": Error reading field '%s'!", field);
  228. gbfread(en, 1, sizeof(en), fin);
  229. l2 = gbfgetint16(fin);
  230. is_fatal((l2 + 4 != l1),
  231. MYNAME ": Error out of sync (wrong size %d/%d) on field '%s'!", l1, l2, field);
  232. if ((en[0] < 'A') || (en[0] > 'Z') || (en[1] < 'A') || (en[1] > 'Z')) {
  233. fatal(MYNAME ": Invalid country code!\n");
  234. }
  235. res = (char*) xmalloc(l2 + 1);
  236. res[l2] = '\0';
  237. PP;
  238. if (l2 > 0) {
  239. gbfread(res, 1, l2, fin);
  240. }
  241. } else {
  242. res = (char*) xmalloc(l1 + 1);
  243. *res = first;
  244. *(res + l1) = '\0';
  245. PP;
  246. l1--;
  247. if (l1 > 0) {
  248. gbfread(res + 1, 1, l1, fin);
  249. }
  250. }
  251. }
  252. #ifdef GPI_DBG
  253. dbginfo("%s: %s\n", field, (res == NULL) ? "<NULL>" : res);
  254. #endif
  255. return res;
  256. }
  257. static QString
  258. gpi_read_string(const char* field)
  259. {
  260. char*s = gpi_read_string_old(field);
  261. QString rv = STRTOUNICODE(s);
  262. xfree(s);
  263. return rv;
  264. }
  265. static void
  266. read_header(void)
  267. {
  268. int len, i;
  269. #ifdef GPI_DBG
  270. struct tm tm;
  271. char stime[32];
  272. #endif
  273. i = gbfgetint32(fin);
  274. if (i != 0) {
  275. i = gbfgetint32(fin);
  276. }
  277. rdata->D2 = gbfgetint32(fin);
  278. gbfread(&rdata->S3, 1, sizeof(rdata->S3) - 1, fin); /* GRMRECnn */
  279. if (strncmp(rdata->S3, "GRMREC", 6) != 0) {
  280. fatal(MYNAME ": No GPI file!\n");
  281. }
  282. PP;
  283. rdata->crdate = gbfgetint32(fin);
  284. #ifdef GPI_DBG
  285. tm = *localtime(&rdata->crdate);
  286. tm.tm_year += 20; /* !!! */
  287. tm.tm_mday -= 1; /* !!! */
  288. strftime(stime, sizeof(stime), "%Y/%m/%d %H:%M:%S", &tm);
  289. dbginfo("crdate = %lu (%s)\n", rdata->crdate, stime);
  290. #endif
  291. (void) gbfgetint16(fin); /* 0 */
  292. len = gbfgetint16(fin);
  293. gbfseek(fin, len, SEEK_CUR); /* "my.gpi" */
  294. i = gbfgetint32(fin); /* 1 */
  295. (void) gbfgetint32(fin); /* 12 */
  296. /* There are two dwords next. On most typical files, they're
  297. * "1" and "12". On files from garminoneline.de/extras/poi, the
  298. * next two words are "15" and "5" and there's 17 additional bytes
  299. * that I can't identify. So hardcode a seek here for now.
  300. */
  301. if (i == 15) {
  302. gbfseek(fin, 17, SEEK_CUR);
  303. }
  304. gbfread(&rdata->POI, 1, sizeof(rdata->POI) - 1, fin);
  305. if (strncmp(rdata->POI, "POI", 3) != 0) {
  306. fatal(MYNAME ": Wrong or unsupported GPI file!\n");
  307. }
  308. for (i = 0; i < 3; i++) {
  309. (void)gbfgetc(fin);
  310. }
  311. gbfread(&rdata->S8, 1, sizeof(rdata->S8) - 1, fin);
  312. codepage = gbfgetint16(fin);
  313. (void) gbfgetint16(fin); /* typically 0, but 0x11 in
  314. Garminonline.de files. */
  315. #ifdef GPI_DBG
  316. PP;
  317. dbginfo("< leaving header\n");
  318. #endif
  319. }
  320. /* gpi tag handler */
  321. static int read_tag(const char* caller, const int tag, Waypoint* wpt);
  322. /* read a single poi with all options */
  323. static void
  324. read_poi(const int sz, const int tag)
  325. {
  326. int pos, len;
  327. Waypoint* wpt;
  328. #ifdef GPI_DBG
  329. PP;
  330. dbginfo("> reading poi (size %d)\n", sz);
  331. #endif
  332. PP;
  333. len = 0;
  334. if (tag == 0x80002) {
  335. len = gbfgetint32(fin); /* sub-header size */
  336. }
  337. #ifdef GPI_DBG
  338. dbginfo("poi sublen = %1$d (0x%1$x)\n", len);
  339. #endif
  340. (void) len;
  341. pos = gbftell(fin);
  342. wpt = new Waypoint;
  343. wpt->icon_descr = DEFAULT_ICON;
  344. wpt->latitude = GPS_Math_Semi_To_Deg(gbfgetint32(fin));
  345. wpt->longitude = GPS_Math_Semi_To_Deg(gbfgetint32(fin));
  346. (void) gbfgetint16(fin); /* ? always 1 ? */
  347. (void) gbfgetc(fin); /* seems to 1 when extra options present */
  348. wpt->shortname = gpi_read_string("Shortname");
  349. while (gbftell(fin) < (gbsize_t)(pos + sz - 4)) {
  350. int tag = gbfgetint32(fin);
  351. if (! read_tag("read_poi", tag, wpt)) {
  352. break;
  353. }
  354. }
  355. if (wpt->description.isEmpty() && !wpt->notes.isEmpty()) {
  356. wpt->description = wpt->notes;
  357. }
  358. if (wpt->notes.isEmpty() && !wpt->description.isEmpty()) {
  359. wpt->notes = wpt->description;
  360. }
  361. waypt_add(wpt);
  362. #ifdef GPI_DBG
  363. PP;
  364. dbginfo("< leaving poi\n");
  365. #endif
  366. }
  367. /* read poi's following a group header */
  368. static void
  369. read_poi_list(const int sz)
  370. {
  371. int pos, i;
  372. pos = gbftell(fin);
  373. #ifdef GPI_DBG
  374. PP;
  375. dbginfo("> reading poi list (-> %1$x / %1$d )\n", pos + sz);
  376. #endif
  377. PP;
  378. i = gbfgetint32(fin); /* mostly 23 (0x17) */
  379. #ifdef GPI_DBG
  380. dbginfo("list sublen = %1$d (0x%1$x)\n", i);
  381. #else
  382. (void) i;
  383. #endif
  384. (void) gbfgetint32(fin); /* max-lat */
  385. (void) gbfgetint32(fin); /* max-lon */
  386. (void) gbfgetint32(fin); /* min-lat */
  387. (void) gbfgetint32(fin); /* min-lon */
  388. (void) gbfgetc(fin); /* three unknown bytes */
  389. (void) gbfgetc(fin); /* ? should be zero ? */
  390. (void) gbfgetc(fin);
  391. (void) gbfgetint32(fin); /* ? const 0x1000100 ? */
  392. while (gbftell(fin) < (gbsize_t)(pos + sz - 4)) {
  393. int tag = gbfgetint32(fin);
  394. if (! read_tag("read_poi_list", tag, NULL)) {
  395. return;
  396. }
  397. }
  398. #ifdef GPI_DBG
  399. PP;
  400. dbginfo("< leaving poi list\n");
  401. #endif
  402. }
  403. static void
  404. read_poi_group(const int sz, const int tag)
  405. {
  406. int pos;
  407. pos = gbftell(fin);
  408. #ifdef GPI_DBG
  409. PP;
  410. dbginfo("> reading poi group (-> %1$x / %1$d)\n", pos + sz);
  411. #endif
  412. if (tag == 0x80009) {
  413. int subsz;
  414. PP;
  415. subsz = gbfgetint32(fin); /* ? offset to category data ? */
  416. #ifdef GPI_DBG
  417. dbginfo("group sublen = %d (-> %x / %d)\n", subsz, pos + subsz + 4, pos + subsz + 4);
  418. #else
  419. (void)subsz;
  420. #endif
  421. }
  422. rdata->group = gpi_read_string("Group");
  423. while (gbftell(fin) < (gbsize_t)(pos + sz)) {
  424. int subtag = gbfgetint32(fin);
  425. if (! read_tag("read_poi_group", subtag, NULL)) {
  426. break;
  427. }
  428. }
  429. #ifdef GPI_DBG
  430. PP;
  431. dbginfo("< leaving poi group\n");
  432. #endif
  433. }
  434. // TODO: 'tag' is probably not a 32 bit value.
  435. // most likely it's a pair of 16's: the first pair is the tag number.
  436. // if the second 16 is "eight", then it's an
  437. // extended thingy and it has a 4-byte extended record length (total number
  438. // of bytes for all record fields and all nested records, starting after the
  439. // length field)
  440. /* gpi tag handler */
  441. static int
  442. read_tag(const char* caller, const int tag, Waypoint* wpt)
  443. {
  444. int pos, sz, dist;
  445. double speed;
  446. short mask;
  447. QString str;
  448. char* cstr;
  449. garmin_fs_t* gmsd;
  450. sz = gbfgetint32(fin);
  451. pos = gbftell(fin);
  452. #ifdef GPI_DBG
  453. PP;
  454. dbginfo("%s: tag = 0x%x (size %d)\n", caller, tag, sz);
  455. #endif
  456. if ((tag >= 0x80000) && (tag <= 0x800ff)) {
  457. sz += 4;
  458. }
  459. switch (tag) {
  460. case 0x3: /* size = 12 */
  461. case 0x80003: /* size = 12 */
  462. dist = gbfgetint16(fin); /* proximity distance in meters */
  463. speed = (double)gbfgetint16(fin) / 100; /* speed in meters per second */
  464. if (dist > 0) {
  465. WAYPT_SET(wpt, proximity, dist);
  466. }
  467. if (speed > 0) {
  468. /* speed isn't part of a normal waypoint
  469. WAYPT_SET(wpt, speed, speed);
  470. */
  471. if ((wpt->shortname.isEmpty() || ((wpt->shortname).indexOf('@'))==-1)) {
  472. if (units == 's') {
  473. speed = MPS_TO_MPH(speed);
  474. } else {
  475. speed = MPS_TO_KPH(speed);
  476. }
  477. QString base = wpt->shortname.isEmpty() ? "WPT" : wpt->shortname;
  478. wpt->shortname = base + QString("@%1").arg(speed,0,'f',0);
  479. }
  480. }
  481. (void) gbfgetint32(fin);
  482. (void) gbfgetint32(fin);
  483. break;
  484. case 0x4: /* size = 2 ? */
  485. case 0x6: /* size = 2 ? */
  486. break;
  487. case 0x5: /* group bitmap */
  488. break;
  489. case 0x7:
  490. (void) gbfgetint16(fin); /* category number */
  491. rdata->category = gpi_read_string("Category");
  492. break;
  493. case 0xa:
  494. wpt->description = gpi_read_string("Description");
  495. break;
  496. case 0xe: /* ? notes or description / or both ? */
  497. mask = gbfgetc(fin);
  498. // Olaf's code called this a mask, but the bits below have nothing
  499. // in common. I'm wondering if that first byte is something else and
  500. // a type e is always a note.
  501. switch (mask) {
  502. case 0x01:
  503. case 0x05:
  504. case 0x32:
  505. str = gpi_read_string("Notes");
  506. default:
  507. break;
  508. }
  509. if (!wpt->description.isEmpty()) {
  510. wpt->notes = str;
  511. } else {
  512. wpt->description = str;
  513. }
  514. break;
  515. case 0x2:
  516. case 0x80002:
  517. read_poi(sz, tag);
  518. break;
  519. case 0x80008:
  520. read_poi_list(sz);
  521. break;
  522. case 0x9: /* ? older versions / no category data ? */
  523. case 0x80009: /* current POI loader */
  524. read_poi_group(sz, tag);
  525. break;
  526. case 0x8000b: /* address (street/city...) */
  527. (void) gbfgetint32(fin);
  528. // FALLTHROUGH
  529. case 0xb: /* as seen in German POI files. */
  530. PP;
  531. mask = gbfgetint16(fin); /* address fields mask */
  532. #ifdef GPI_DBG
  533. dbginfo("GPI Address field mask: %d (0x%02x)\n", mask, mask);
  534. #endif
  535. if ((mask & GPI_ADDR_CITY) && (cstr = gpi_read_string_old("City"))) {
  536. gmsd = gpi_gmsd_init(wpt);
  537. GMSD_SET(city, cstr);
  538. }
  539. if ((mask & GPI_ADDR_COUNTRY) && (cstr = gpi_read_string_old("Country"))) {
  540. gmsd = gpi_gmsd_init(wpt);
  541. GMSD_SET(country, cstr);
  542. }
  543. if ((mask & GPI_ADDR_STATE) && (cstr = gpi_read_string_old("State"))) {
  544. gmsd = gpi_gmsd_init(wpt);
  545. GMSD_SET(state, cstr);
  546. }
  547. if ((mask & GPI_ADDR_POSTAL_CODE) && (cstr = gpi_read_string_old("Postal code"))) {
  548. gmsd = gpi_gmsd_init(wpt);
  549. GMSD_SET(postal_code, cstr);
  550. }
  551. if ((mask & GPI_ADDR_ADDR) && (cstr = gpi_read_string_old("Street address"))) {
  552. gmsd = gpi_gmsd_init(wpt);
  553. GMSD_SET(addr, cstr);
  554. }
  555. break;
  556. case 0xc:
  557. mask = gbfgetint16(fin);
  558. if ((mask & 1) && (cstr = gpi_read_string_old("Phone"))) {
  559. gmsd = gpi_gmsd_init(wpt);
  560. GMSD_SET(phone_nr, cstr);
  561. }
  562. if ((mask & 2) && (cstr = gpi_read_string_old("Phone2"))) {
  563. gmsd = gpi_gmsd_init(wpt);
  564. GMSD_SET(phone_nr2, cstr);
  565. }
  566. if ((mask & 4) && (cstr = gpi_read_string_old("Fax"))) {
  567. gmsd = gpi_gmsd_init(wpt);
  568. GMSD_SET(fax_nr, cstr);
  569. }
  570. if ((mask & 8) && (cstr = gpi_read_string_old("Email"))) {
  571. gmsd = gpi_gmsd_init(wpt);
  572. GMSD_SET(email, cstr);
  573. }
  574. if ((mask & 0x10) && (str = gpi_read_string("Link"), !str.isEmpty())) {
  575. waypt_add_url(wpt, str, str);
  576. }
  577. break;
  578. case 0x8000c: /* phone-number */
  579. (void) gbfgetint32(fin);
  580. PP;
  581. mask = gbfgetint16(fin); /* phone fields mask */
  582. #ifdef GPI_DBG
  583. dbginfo("GPI Phone field mask: %d (0x%02x)\n", mask, mask);
  584. #endif
  585. if ((mask & 1) && (cstr = gpi_read_string_old("Phone"))) {
  586. gmsd = gpi_gmsd_init(wpt);
  587. GMSD_SET(phone_nr, cstr);
  588. }
  589. break;
  590. case 0x80012: /* ? sounds / images ? */
  591. break;
  592. /* Images? Seen in http://geepeeex.com/Stonepages.gpi */
  593. case 0xd:
  594. break;
  595. case 0x11:
  596. case 0x80007:
  597. /* Looks like some kind of calendar information. */
  598. #ifdef GPI_DBG
  599. {
  600. int x;
  601. unsigned char* b = (unsigned char*) xmalloc(sz);
  602. fprintf(stderr, "Tag: %x\n", tag);
  603. gbfread(b, 1, sz, fin);
  604. fprintf(stderr, "\n");
  605. for (x = 0; x < sz; x++) {
  606. fprintf(stderr, "%02x ", b[x]);
  607. }
  608. fprintf(stderr, "\n");
  609. for (x = 0; x < sz; x++) {
  610. fprintf(stderr, "%c", isalnum(b[x]) ? b[x] : '.');
  611. }
  612. fprintf(stderr, "\n");
  613. }
  614. #endif // GPI_DBG
  615. break;
  616. default:
  617. warning(MYNAME ": Unknown tag (0x%x). Please report!\n", tag);
  618. return 0;
  619. }
  620. gbfseek(fin, pos + sz, SEEK_SET);
  621. return 1;
  622. }
  623. /*******************************************************************************
  624. * %%% gpi writer %%% *
  625. *******************************************************************************/
  626. static void
  627. write_string(const char* str, const char long_format)
  628. {
  629. int len;
  630. len = strlen(str);
  631. if (long_format) {
  632. gbfputint32(len + 4, fout);
  633. gbfwrite("EN", 1, 2, fout);
  634. }
  635. gbfputint16(len, fout);
  636. gbfwrite(str, 1, len, fout);
  637. }
  638. static void
  639. write_string(const QString& str, const char long_format)
  640. {
  641. write_string(STRFROMUNICODE(str), long_format);
  642. }
  643. static int
  644. compare_wpt_cb(const queue* a, const queue* b)
  645. {
  646. const Waypoint* wa = (Waypoint*) a;
  647. const Waypoint* wb = (Waypoint*) b;
  648. return wa->shortname.compare(wb->shortname);
  649. }
  650. static char
  651. compare_strings(const QString& s1, const QString& s2)
  652. {
  653. return s1.compare(s2);
  654. }
  655. static writer_data_t*
  656. wdata_alloc()
  657. {
  658. writer_data_t* res;
  659. res = (writer_data_t*) xcalloc(1, sizeof(*res));
  660. QUEUE_INIT(&res->Q);
  661. waypt_init_bounds(&res->bds);
  662. return res;
  663. }
  664. static void
  665. wdata_free(writer_data_t* data)
  666. {
  667. queue* elem, *tmp;
  668. QUEUE_FOR_EACH(&data->Q, elem, tmp) {
  669. Waypoint* wpt = (Waypoint*)elem;
  670. if (wpt->extra_data) {
  671. gpi_waypt_t* dt = (gpi_waypt_t*) wpt->extra_data;
  672. if (dt->addr_is_dynamic) {
  673. xfree(dt->addr);
  674. }
  675. xfree(dt);
  676. }
  677. delete wpt;
  678. }
  679. if (data->top_left) {
  680. wdata_free(data->top_left);
  681. }
  682. if (data->top_right) {
  683. wdata_free(data->top_right);
  684. }
  685. if (data->buttom_left) {
  686. wdata_free(data->buttom_left);
  687. }
  688. if (data->buttom_right) {
  689. wdata_free(data->buttom_right);
  690. }
  691. xfree(data);
  692. }
  693. static void
  694. wdata_add_wpt(writer_data_t* data, Waypoint* wpt)
  695. {
  696. data->ct++;
  697. ENQUEUE_TAIL(&data->Q, &wpt->Q);
  698. waypt_add_to_bounds(&data->bds, wpt);
  699. }
  700. static void
  701. wdata_check(writer_data_t* data)
  702. {
  703. queue* elem, *tmp;
  704. double center_lat, center_lon;
  705. if ((data->ct <= WAYPOINTS_PER_BLOCK) ||
  706. /* avoid endless loop for points (more than WAYPOINTS_PER_BLOCK)
  707. at same coordinates */
  708. ((data->bds.min_lat >= data->bds.max_lat) && (data->bds.min_lon >= data->bds.max_lon))) {
  709. if (data->ct > 1) {
  710. sortqueue(&data->Q, compare_wpt_cb);
  711. }
  712. return;
  713. }
  714. /* compute the (mean) center of current bounds */
  715. center_lat = center_lon = 0;
  716. QUEUE_FOR_EACH(&data->Q, elem, tmp) {
  717. Waypoint* wpt = (Waypoint*) elem;
  718. center_lat += wpt->latitude;
  719. center_lon += wpt->longitude;
  720. }
  721. center_lat /= data->ct;
  722. center_lon /= data->ct;
  723. QUEUE_FOR_EACH(&data->Q, elem, tmp) {
  724. Waypoint* wpt = (Waypoint*) elem;
  725. writer_data_t** ref;
  726. if (wpt->latitude < center_lat) {
  727. if (wpt->longitude < center_lon) {
  728. ref = &data->buttom_left;
  729. } else {
  730. ref = &data->buttom_right;
  731. }
  732. } else {
  733. if (wpt->longitude < center_lon) {
  734. ref = &data->top_left;
  735. } else {
  736. ref = &data->top_right;
  737. }
  738. }
  739. if (*ref == NULL) {
  740. *ref = wdata_alloc();
  741. }
  742. data->ct--;
  743. dequeue(&wpt->Q);
  744. wdata_add_wpt(*ref, wpt);
  745. }
  746. if (data->top_left) {
  747. wdata_check(data->top_left);
  748. }
  749. if (data->top_right) {
  750. wdata_check(data->top_right);
  751. }
  752. if (data->buttom_left) {
  753. wdata_check(data->buttom_left);
  754. }
  755. if (data->buttom_right) {
  756. wdata_check(data->buttom_right);
  757. }
  758. }
  759. static int
  760. wdata_compute_size(writer_data_t* data)
  761. {
  762. queue* elem, *tmp;
  763. int res = 0;
  764. if (QUEUE_EMPTY(&data->Q))
  765. goto skip_empty_block; /* do not issue an empty block */
  766. res = 23; /* bounds, ... of tag 0x80008 */
  767. QUEUE_FOR_EACH(&data->Q, elem, tmp) {
  768. Waypoint* wpt = (Waypoint*) elem;
  769. gpi_waypt_t* dt;
  770. garmin_fs_t* gmsd;
  771. QString str;
  772. res += 12; /* tag/sz/sub-sz */
  773. res += 19; /* poi fixed size */
  774. res += wpt->shortname.length();
  775. if (! opt_hide_bitmap) {
  776. res += 10; /* tag(4) */
  777. }
  778. dt = (gpi_waypt_t*) xcalloc(1, sizeof(*dt));
  779. wpt->extra_data = dt;
  780. if (alerts) {
  781. #if NEW_STRINGS
  782. // examine closely.
  783. const char* pos;
  784. int pidx;
  785. if ((pidx = wpt->shortname.indexOf('@')) != -1) {
  786. pos = CSTR(wpt->shortname.mid(pidx));
  787. #else
  788. char* pos;
  789. if ((pos = strchr(wpt->shortname, '@'))) {
  790. #endif
  791. double speed, scale;
  792. if (units == 's') {
  793. scale = MPH_TO_MPS(1);
  794. } else {
  795. scale = KPH_TO_MPS(1);
  796. }
  797. parse_speed(pos + 1, &speed, scale, MYNAME);
  798. if (speed > 0) {
  799. WAYPT_SET(wpt, speed, speed);
  800. }
  801. #if 0
  802. if (pos > wpt->shortname) {
  803. wpt->shortname[pos - wpt->shortname] = '\0';
  804. }
  805. #endif
  806. } else if ((opt_speed) && (! WAYPT_HAS(wpt, speed))) {
  807. WAYPT_SET(wpt, speed, defspeed);
  808. }
  809. if ((opt_proximity) && (! WAYPT_HAS(wpt, proximity))) {
  810. WAYPT_SET(wpt, proximity, defproximity);
  811. }
  812. if ((WAYPT_HAS(wpt, speed) && (wpt->speed > 0)) ||
  813. (WAYPT_HAS(wpt, proximity) && (wpt->proximity > 0))) {
  814. data->alert = 1;
  815. dt->alerts++;
  816. res += 20; /* tag(3) */
  817. }
  818. }
  819. str = QString();
  820. if (opt_descr) {
  821. if (!wpt->description.isEmpty()) {
  822. str = xstrdup(wpt->description);
  823. }
  824. } else if (opt_notes) {
  825. if (!wpt->notes.isEmpty()) {
  826. str = xstrdup(wpt->notes);
  827. }
  828. } else if (opt_pos) {
  829. str = pretty_deg_format(wpt->latitude, wpt->longitude, 's', " ", 0);
  830. }
  831. if (!str.isEmpty()) {
  832. dt->addr_is_dynamic = 1;
  833. dt->addr = xstrdup(str);
  834. dt->mask |= GPI_ADDR_ADDR;
  835. dt->sz += (8 + strlen(dt->addr));
  836. }
  837. if ((gmsd = GMSD_FIND(wpt))) {
  838. if ((dt->mask == 0) && ((dt->addr = GMSD_GET(addr, NULL)))) {
  839. dt->mask |= GPI_ADDR_ADDR;
  840. dt->sz += (8 + strlen(dt->addr));
  841. }
  842. if ((dt->city = GMSD_GET(city, NULL))) {
  843. dt->mask |= GPI_ADDR_CITY;
  844. dt->sz += (8 + strlen(dt->city));
  845. }
  846. if ((dt->country = GMSD_GET(country, NULL))) {
  847. dt->mask |= GPI_ADDR_COUNTRY;
  848. dt->sz += (8 + strlen(dt->country));
  849. }
  850. if ((dt->state = GMSD_GET(state, NULL))) {
  851. dt->mask |= GPI_ADDR_STATE;
  852. dt->sz += (8 + strlen(dt->state));
  853. }
  854. if ((dt->postal_code = GMSD_GET(postal_code, NULL))) {
  855. dt->mask |= GPI_ADDR_POSTAL_CODE;
  856. dt->sz += (2 + strlen(dt->postal_code)); /* short form */
  857. }
  858. if ((dt->phone_nr = GMSD_GET(phone_nr, NULL))) {
  859. res += (12 + 4 + strlen(dt->phone_nr));
  860. }
  861. }
  862. if (dt->mask) {
  863. dt->sz += 2; /* + mask (two bytes) */
  864. }
  865. if (dt->sz) {
  866. res += (dt->sz + 12); /* + header size */
  867. }
  868. str = wpt->description;
  869. if (str.isEmpty()) {
  870. str = wpt->notes;
  871. }
  872. // if (str && (strcmp(str, wpt->shortname) == 0)) str = NULL;
  873. if (!str.isEmpty()) {
  874. res += (12 + 4 + str.length());
  875. }
  876. }
  877. skip_empty_block:
  878. if (data->top_left) {
  879. res += wdata_compute_size(data->top_left);
  880. }
  881. if (data->top_right) {
  882. res += wdata_compute_size(data->top_right);
  883. }
  884. if (data->buttom_left) {
  885. res += wdata_compute_size(data->buttom_left);
  886. }
  887. if (data->buttom_right) {
  888. res += wdata_compute_size(data->buttom_right);
  889. }
  890. data->sz = res;
  891. if (QUEUE_EMPTY(&data->Q))
  892. return res;
  893. return res + 12; /* + 12 = caller needs info about tag header size */
  894. }
  895. static void
  896. wdata_write(const writer_data_t* data)
  897. {
  898. queue* elem, *tmp;
  899. if (QUEUE_EMPTY(&data->Q))
  900. goto skip_empty_block; /* do not issue an empty block */
  901. gbfputint32(0x80008, fout);
  902. gbfputint32(data->sz, fout);
  903. gbfputint32(23, fout); /* bounds + three bytes */
  904. gbfputint32(GPS_Math_Deg_To_Semi(data->bds.max_lat), fout);
  905. gbfputint32(GPS_Math_Deg_To_Semi(data->bds.max_lon), fout);
  906. gbfputint32(GPS_Math_Deg_To_Semi(data->bds.min_lat), fout);
  907. gbfputint32(GPS_Math_Deg_To_Semi(data->bds.min_lon), fout);
  908. gbfputint32(0, fout);
  909. gbfputint16(1, fout);
  910. gbfputc(data->alert, fout);
  911. QUEUE_FOR_EACH(&data->Q, elem, tmp) {
  912. QString str;
  913. int s0, s1;
  914. Waypoint* wpt = (Waypoint*)elem;
  915. gpi_waypt_t* dt = (gpi_waypt_t*) wpt->extra_data;
  916. str = wpt->description;
  917. if (str.isEmpty()) {
  918. str = wpt->notes;
  919. }
  920. gbfputint32(0x80002, fout);
  921. s0 = s1 = 19 + wpt->shortname.length();
  922. if (! opt_hide_bitmap) {
  923. s0 += 10; /* tag(4) */
  924. }
  925. if (!str.isEmpty()) {
  926. s0 += (12 + 4 + str.length()); /* descr */
  927. }
  928. if (dt->sz) {
  929. s0 += (12 + dt->sz); /* address part */
  930. }
  931. if (dt->phone_nr) {
  932. s0 += (12 + 4 + strlen(dt->phone_nr));
  933. }
  934. if (dt->alerts) {
  935. s0 += 20; /* tag(3) */
  936. }
  937. gbfputint32(s0, fout); /* size of following data (tag) */
  938. gbfputint32(s1, fout); /* basic size (without options) */
  939. gbfputint32(GPS_Math_Deg_To_Semi(wpt->latitude), fout);
  940. gbfputint32(GPS_Math_Deg_To_Semi(wpt->longitude), fout);
  941. gbfputint16(1, fout); /* ? always 1 ? */
  942. gbfputc(alerts, fout); /* seems to be 1 when extra options present */
  943. write_string(wpt->shortname, 1);
  944. if (dt->alerts) {
  945. char flag = 0;
  946. gbfputint32(3, fout); /* tag(3) */
  947. gbfputint32(12, fout); /* always 12 */
  948. if (WAYPT_HAS(wpt, proximity) && (wpt->proximity > 0)) {
  949. gbfputint16((int) wpt->proximity, fout);
  950. flag = 4;
  951. } else {
  952. gbfputint16(0, fout);
  953. }
  954. if (WAYPT_HAS(wpt, speed) && (wpt->speed > 0)) {
  955. gbfputint16((int)(wpt->speed * 100), fout);
  956. flag = 5;
  957. } else {
  958. gbfputint16(0, fout);
  959. }
  960. gbfputint32(0x100100, fout); /* ??? */
  961. gbfputc(1, fout); /* ??? */
  962. gbfputc(1, fout); /* ??? */
  963. gbfputc(flag, fout);
  964. gbfputc(0x10, fout); /* ??? */
  965. }
  966. if (! opt_hide_bitmap) {
  967. gbfputint32(4, fout); /* tag(4) */
  968. gbfputint32(2, fout); /* ? always 2 == version ??? */
  969. gbfputint16(0, fout);
  970. }
  971. if (!str.isEmpty()) {
  972. gbfputint32(0xa, fout);
  973. gbfputint32(str.length() + 8, fout); /* string + string header */
  974. write_string(str, 1);
  975. }
  976. if (dt->sz) { /* gpi address */
  977. gbfputint32(0x8000b, fout);
  978. gbfputint32(dt->sz, fout);
  979. gbfputint32(0x2, fout); /* ? always 2 ? */
  980. gbfputint16(dt->mask, fout);
  981. if (dt->mask & GPI_ADDR_CITY) {
  982. write_string(dt->city, 1);
  983. }
  984. if (dt->mask & GPI_ADDR_COUNTRY) {
  985. write_string(dt->country, 1);
  986. }
  987. if (dt->mask & GPI_ADDR_STATE) {
  988. write_string(dt->state, 1);
  989. }
  990. if (dt->mask & GPI_ADDR_POSTAL_CODE) {
  991. write_string(dt->postal_code, 0);
  992. }
  993. if (dt->mask & GPI_ADDR_ADDR) {
  994. write_string(dt->addr, 1);
  995. }
  996. }
  997. if (dt->phone_nr) {
  998. gbfputint32(0x8000c, fout);
  999. gbfputint32(strlen(dt->phone_nr) + 2 + 2, fout);
  1000. gbfputint32(0x2, fout); /* ? always 2 ? */
  1001. gbfputint16(1, fout); /* mask */
  1002. write_string(dt->phone_nr, 0);
  1003. }
  1004. }
  1005. skip_empty_block:
  1006. if (data->top_left) {
  1007. wdata_write(data->top_left);
  1008. }
  1009. if (data->top_right) {
  1010. wdata_write(data->top_right);
  1011. }
  1012. if (data->buttom_left) {
  1013. wdata_write(data->buttom_left);
  1014. }
  1015. if (data->buttom_right) {
  1016. wdata_write(data->buttom_right);
  1017. }
  1018. }
  1019. static void
  1020. write_category(const char* category, const unsigned char* image, const int image_sz)
  1021. {
  1022. int sz;
  1023. sz = wdata_compute_size(wdata);
  1024. sz += 8; /* string header */
  1025. sz += strlen(opt_cat);
  1026. gbfputint32(0x80009, fout);
  1027. if ((! opt_hide_bitmap) && image_sz) {
  1028. gbfputint32(sz + image_sz + 8, fout);
  1029. } else {
  1030. gbfputint32(sz, fout);
  1031. }
  1032. gbfputint32(sz, fout);
  1033. write_string(opt_cat, 1);
  1034. wdata_write(wdata);
  1035. if ((! opt_hide_bitmap) && image_sz) {
  1036. gbfputint32(5, fout);
  1037. gbfputint32(image_sz, fout);
  1038. gbfwrite(image, 1, image_sz, fout);
  1039. }
  1040. }
  1041. static void
  1042. write_header(void)
  1043. {
  1044. time_t time = gpi_timestamp;
  1045. if (time != 0) {
  1046. struct tm tm;
  1047. tm = *gmtime(&time);
  1048. tm.tm_year -= 20;
  1049. time = mkgmtime(&tm);
  1050. time += SECONDS_PER_DAY;
  1051. }
  1052. gbfputint32(0, fout);
  1053. gbfputint32(0x16, fout);
  1054. gbfwrite("GRMREC00", 1, 8, fout);
  1055. gbfputint32(time, fout);
  1056. gbfputint16(0, fout);
  1057. gbfputint16(6, fout);
  1058. gbfwrite("my.gpi", 1, 6, fout);
  1059. gbfputint32(1, fout);
  1060. gbfputint32(0xc, fout);
  1061. gbfwrite("POI", 1, 3, fout);
  1062. gbfputc(0, fout);
  1063. gbfputc(0, fout);
  1064. gbfputc(0, fout);
  1065. gbfwrite("00", 1, 2, fout);
  1066. gbfputint16(codepage, fout);
  1067. gbfputint16(0, fout);
  1068. }
  1069. static void
  1070. enum_waypt_cb(const Waypoint* ref)
  1071. {
  1072. Waypoint* wpt;
  1073. queue* elem, *tmp;
  1074. QUEUE_FOR_EACH(&wdata->Q, elem, tmp) {
  1075. Waypoint* cmp = (Waypoint*) elem;
  1076. /* sort out nearly equal waypoints */
  1077. if ((compare_strings(cmp->shortname, ref->shortname) == 0) &&
  1078. (cmp->latitude == ref->latitude) &&
  1079. (cmp->longitude == ref->longitude) &&
  1080. (compare_strings(cmp->description, ref->description) == 0) &&
  1081. (compare_strings(cmp->notes, ref->notes) == 0)) {
  1082. return;
  1083. }
  1084. }
  1085. wpt = new Waypoint(*ref);
  1086. if (*opt_unique == '1') {
  1087. wpt->shortname = mkshort(short_h, wpt->shortname);
  1088. }
  1089. wdata_add_wpt(wdata, wpt);
  1090. }
  1091. static void
  1092. load_bitmap_from_file(const char* fname, unsigned char** data, int* data_sz)
  1093. {
  1094. gbfile* f;
  1095. int i, sz;
  1096. int dest_bpp;
  1097. int src_line_sz, dest_line_sz;
  1098. bmp_header_t src_h;
  1099. int* color_table = NULL;
  1100. gpi_bitmap_header_t* dest_h;
  1101. unsigned char* ptr;
  1102. f = gbfopen_le(fname, "rb", MYNAME);
  1103. is_fatal(gbfgetint16(f) != 0x4d42, MYNAME ": No BMP image.");
  1104. /* read a standard bmp file header */
  1105. src_h.size = gbfgetint32(f);
  1106. src_h.res1 = gbfgetint16(f);
  1107. src_h.res2 = gbfgetint16(f);
  1108. src_h.image_offset = gbfgetint32(f);
  1109. src_h.header_size = gbfgetint32(f);
  1110. src_h.width = gbfgetint32(f);
  1111. src_h.height = gbfgetint32(f);
  1112. src_h.planes = gbfgetint16(f);
  1113. src_h.bpp = gbfgetint16(f);
  1114. src_h.compression_type = gbfgetint32(f);
  1115. src_h.image_data_size = gbfgetint32(f);
  1116. src_h.resolution_h = gbfgetint32(f);
  1117. src_h.resolution_v = gbfgetint32(f);
  1118. src_h.used_colors = gbfgetint32(f);
  1119. src_h.important_colors = gbfgetint32(f);
  1120. /* Workaround for indexed BMP's with used_colors = 0 */
  1121. if ((src_h.bpp == 8) && (src_h.used_colors == 0)) {
  1122. src_h.used_colors = (src_h.image_offset - gbftell(f)) / 4;
  1123. }
  1124. #ifdef GPI_DBG
  1125. printf("data size: 0x%1$x (%1$d)\n", src_h.size);
  1126. printf("image data offset: 0x%1$x (%1$d)\n", src_h.image_offset);
  1127. printf("header size: 0x%1$x (%1$d)\n", src_h.header_size);
  1128. printf("image width: 0x%1$x (%1$d)\n", src_h.width);
  1129. printf("image height: 0x%1$x (%1$d)\n", src_h.height);
  1130. printf("number of planes: 0x%1$x (%1$d)\n", src_h.planes);
  1131. printf("bits per pixel: 0x%1$x (%1$d)\n", src_h.bpp);
  1132. printf("compression type: 0x%1$x (%1$d)\n", src_h.compression_type);
  1133. printf("image size: 0x%1$x (%1$d)\n", src_h.image_data_size);
  1134. printf("horizontal resolution: 0x%1$x (%1$d)\n", src_h.resolution_h);
  1135. printf("vertical resolution: 0x%1$x (%1$d)\n", src_h.resolution_v);
  1136. printf("number of colors: 0x%1$x (%1$d)\n", src_h.used_colors);
  1137. printf("important colors: 0x%1$x (%1$d)\n", src_h.important_colors);
  1138. #endif
  1139. /* sort out unsupported files */
  1140. if (!((src_h.width <= 24) && (src_h.height <= 24) &&
  1141. (src_h.width > 0) && (src_h.height > 0))) {
  1142. fatal(MYNAME ": Unsupported format (%dx%d)!\n", src_h.width, src_h.height);
  1143. }
  1144. if (!((src_h.bpp == 8) || (src_h.bpp == 24) || (src_h.bpp == 32))) {
  1145. fatal(MYNAME ": Unsupported color depth (%d)!\n", src_h.bpp);
  1146. }
  1147. if (!(src_h.compression_type == 0)) {
  1148. fatal(MYNAME ": Sorry, we don't support compressed bitmaps.\n");
  1149. }
  1150. if (src_h.used_colors > 0) {
  1151. color_table = (int*) xmalloc(4 * src_h.used_colors);
  1152. gbfread(color_table, 1, 4 * src_h.used_colors, f);
  1153. for (i = 0; i < src_h.used_colors; i++) {
  1154. int color = color_table[i];
  1155. /* swap blue and red value */
  1156. color = (color >> 16) | (color << 16) | (color & 0x00ff00);
  1157. color_table[i] = color & 0xffffff;
  1158. }
  1159. }
  1160. /* calculate line-size for source and destination */
  1161. src_line_sz = (src_h.width * src_h.bpp) / 8;
  1162. src_line_sz = ((int)((src_line_sz + 3) / 4)) * 4;
  1163. if (src_h.bpp == 24) {
  1164. dest_bpp = 32;
  1165. } else {
  1166. dest_bpp = src_h.bpp;
  1167. }
  1168. dest_line_sz = (src_h.width * dest_bpp) / 8;
  1169. dest_line_sz = ((int)((dest_line_sz + 3) / 4)) * 4;
  1170. sz = sizeof(*dest_h) + (src_h.height * dest_line_sz);
  1171. if (src_h.used_colors) {
  1172. sz += (src_h.used_colors * 4);
  1173. }
  1174. ptr = (unsigned char*) xmalloc(sz);
  1175. dest_h = (gpi_bitmap_header_t*)ptr;
  1176. *data = ptr;
  1177. *data_sz = sz;
  1178. le_write16(&dest_h->index, 0);
  1179. le_write16(&dest_h->height, src_h.height);
  1180. le_write16(&dest_h->width, src_h.width);
  1181. le_write16(&dest_h->line_sz, dest_line_sz);
  1182. le_write16(&dest_h->bpp, dest_bpp);
  1183. le_write16(&dest_h->fixed_0, 0); /* seems to be fixed */
  1184. le_write32(&dest_h->image_size, dest_line_sz * src_h.height);
  1185. le_write32(&dest_h->fixed_2c, 0x2c); /* seems to be fixed */
  1186. le_write32(&dest_h->flag1, (dest_bpp == 8) ? 0x100 : 0);
  1187. le_write32(&dest_h->tr_color, 0xff00ff); /* magenta = transparent color */
  1188. le_write32(&dest_h->flag2, 0x1); /* ? enable transparent mode ? */
  1189. le_write32(&dest_h->size_2c, (dest_line_sz * src_h.height) + 0x2c);
  1190. /* copy and revert order of BMP lines */
  1191. ptr = (unsigned char*)dest_h;
  1192. ptr += (sizeof(*dest_h) + (dest_line_sz * (src_h.height - 1)));
  1193. gbfseek(f, src_h.image_offset, SEEK_SET);
  1194. if (src_h.bpp == 24) {
  1195. /* 24 bpp seems to be not supported, convert to 32 bpp */
  1196. for (i = 0; i < src_h.height; i++) {
  1197. int j;
  1198. unsigned char* p = ptr;
  1199. for (j = 0; j < src_h.width; j++) {
  1200. int color;
  1201. color = (int32_t)gbfgetint16(f) | (gbfgetc(f) << 16);
  1202. le_write32(p, color);
  1203. p += 4;
  1204. }
  1205. for (j = (src_h.width * src_h.bpp) / 8; j < src_line_sz; j++) {
  1206. gbfgetc(f); /* drop fill-in bytes */
  1207. }
  1208. ptr -= dest_line_sz;
  1209. }
  1210. } else for (i = 0; i < src_h.height; i++) {
  1211. gbfread(ptr, 1, src_line_sz, f);
  1212. ptr -= dest_line_sz;
  1213. }
  1214. if (src_h.used_colors > 0) {
  1215. ptr = (unsigned char*)dest_h;
  1216. ptr += (sizeof(*dest_h) + (src_h.height * src_line_sz));
  1217. for (i = 0; i < src_h.used_colors; i++) {
  1218. le_write32(ptr, color_table[i]);
  1219. ptr += 4;
  1220. }
  1221. }
  1222. if (color_table) {
  1223. xfree(color_table);
  1224. }
  1225. gbfclose(f);
  1226. }
  1227. /*******************************************************************************
  1228. * %%% global callbacks called by gpsbabel main process %%% *
  1229. *******************************************************************************/
  1230. static void
  1231. garmin_gpi_rd_init(const QString& fname)
  1232. {
  1233. fin = gbfopen_le(fname, "rb", MYNAME);
  1234. rdata = new reader_data_t;
  1235. read_header();
  1236. if ((codepage >= 1250) && (codepage <= 1257)) {
  1237. QString qCodecName = QString("windows-%1").arg(codepage);
  1238. cet_convert_init(CSTR(qCodecName), 1);
  1239. } else {
  1240. fatal(MYNAME ": Unsupported code page (%d). File is likely encrypted.\n", codepage);
  1241. }
  1242. units = tolower(opt_units[0]);
  1243. if ((units != 'm') && (units != 's')) {
  1244. fatal(MYNAME ": Unknown units parameter (%c).\n", opt_units[0]);
  1245. }
  1246. }
  1247. static void
  1248. garmin_gpi_wr_init(const QString& fname)
  1249. {
  1250. int i;
  1251. if (gpi_timestamp != 0) { /* not the first gpi output session */
  1252. time_t t = time(NULL);
  1253. if (t <= gpi_timestamp) {
  1254. gpi_timestamp++; /* don't create files with same timestamp */
  1255. } else {
  1256. gpi_timestamp = t;
  1257. }
  1258. } else {
  1259. gpi_timestamp = gpsbabel_time; /* always ZERO during 'testo' */
  1260. }
  1261. fout = gbfopen_le(fname, "wb", MYNAME);
  1262. short_h = mkshort_new_handle();
  1263. setshort_length(short_h, 1024);
  1264. setshort_badchars(short_h, "\r\n");
  1265. setshort_mustupper(short_h, 0);
  1266. setshort_mustuniq(short_h, 1);
  1267. setshort_whitespace_ok(short_h, 1);
  1268. setshort_repeating_whitespace_ok(short_h, 0);
  1269. setshort_defname(short_h, "POI");
  1270. codepage = 0;
  1271. for (i = 1250; i <= 1257; i++) {
  1272. if (QString("windows-%1").arg(i).compare(QString(opt_writecodec), Qt::CaseInsensitive) == 0) {
  1273. codepage = i;
  1274. break;
  1275. }
  1276. }
  1277. if (! codepage) {
  1278. warning(MYNAME ": Unsupported character set (%s)!\n", opt_writecodec);
  1279. fatal(MYNAME ": Valid values are windows-1250 to windows-1257.\n");
  1280. }
  1281. cet_convert_init(opt_writecodec,1);
  1282. units = tolower(opt_units[0]);
  1283. if ((units != 'm') && (units != 's')) {
  1284. fatal(MYNAME ": Unknown units parameter (%c).\n", opt_units[0]);
  1285. }
  1286. alerts = (opt_alerts) ? 1 : 0;
  1287. if (opt_speed) {
  1288. double scale;
  1289. alerts = 1; /* Force alerts to be enabled */
  1290. if (units == 's') {
  1291. scale = MPH_TO_MPS(1); /* We need speed in meters per second */
  1292. } else {
  1293. scale = KPH_TO_MPS(1);
  1294. }
  1295. parse_speed(opt_speed, &defspeed, scale, MYNAME);
  1296. }
  1297. if (opt_proximity) {
  1298. double scale;
  1299. alerts = 1; /* Force alerts to be enabled */
  1300. if (units == 's') {
  1301. scale = MILES_TO_METERS(1); /* We need proximity in meters */
  1302. } else {
  1303. scale = 1000.0; /* one kilometer in meters */
  1304. }
  1305. parse_distance(opt_proximity, &defproximity, scale, MYNAME);
  1306. }
  1307. wdata = wdata_alloc();
  1308. }
  1309. static void
  1310. garmin_gpi_rd_deinit(void)
  1311. {
  1312. delete rdata;
  1313. gbfclose(fin);
  1314. }
  1315. static void
  1316. garmin_gpi_wr_deinit(void)
  1317. {
  1318. wdata_free(wdata);
  1319. mkshort_del_handle(&short_h);
  1320. gbfclose(fout);
  1321. if ((opt_sleep) && (gpi_timestamp != 0)) { /* don't sleep during 'testo' */
  1322. int sleep = atoi(opt_sleep);
  1323. if (sleep < 1) {
  1324. sleep = 1;
  1325. }
  1326. gpi_timestamp += sleep;
  1327. while (gpi_timestamp > time(NULL)) {
  1328. gb_sleep(100);
  1329. }
  1330. }
  1331. }
  1332. static void
  1333. garmin_gpi_read(void)
  1334. {
  1335. while (1) {
  1336. int tag = gbfgetint32(fin);
  1337. if (tag == 0xffff) {
  1338. return;
  1339. }
  1340. if (! read_tag("garmin_gpi_read", tag, NULL)) {
  1341. return;
  1342. }
  1343. };
  1344. }
  1345. static void
  1346. garmin_gpi_write(void)
  1347. {
  1348. unsigned char* image;
  1349. int image_sz;
  1350. if (strlen(opt_cat) == 0) {
  1351. fatal(MYNAME ": Can't write empty category!\n");
  1352. }
  1353. if (opt_hide_bitmap) {
  1354. image = NULL;
  1355. image_sz = 0;
  1356. } else if (opt_bitmap && *opt_bitmap) {
  1357. load_bitmap_from_file(opt_bitmap, &image, &image_sz);
  1358. } else {
  1359. image = gpi_bitmap; /* embedded GPSBabel icon in gpi format */
  1360. image_sz = GPI_BITMAP_SIZE;
  1361. }
  1362. waypt_disp_all(enum_waypt_cb);
  1363. wdata_check(wdata);
  1364. write_header();
  1365. write_category(opt_cat, image, image_sz);
  1366. gbfputint32(0xffff, fout); /* final tag */
  1367. gbfputint32(0, fout); /* ? dummy size ? */
  1368. if (image != gpi_bitmap) {
  1369. xfree(image);
  1370. }
  1371. }
  1372. /**************************************************************************/
  1373. ff_vecs_t garmin_gpi_vecs = {
  1374. ff_type_file,
  1375. {
  1376. (ff_cap)(ff_cap_read | ff_cap_write) /* waypoints */,
  1377. ff_cap_none /* tracks */,
  1378. ff_cap_none /* routes */
  1379. },
  1380. garmin_gpi_rd_init,
  1381. garmin_gpi_wr_init,
  1382. garmin_gpi_rd_deinit,
  1383. garmin_gpi_wr_deinit,
  1384. garmin_gpi_read,
  1385. garmin_gpi_write,
  1386. NULL,
  1387. garmin_gpi_args,
  1388. CET_CHARSET_MS_ANSI, 0 /* WIN-CP1252 */
  1389. };
  1390. /**************************************************************************/