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.

pcx.cc 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /*
  2. Access to Garmin PCX5 files.
  3. Format described in: http://www.garmin.com/manuals/PCX5_OwnersManual.pdf
  4. Copyright (C) 2002-2006 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. #include "defs.h"
  18. #include "garmin_tables.h"
  19. #include "cet_util.h"
  20. #include "csv_util.h"
  21. #include <math.h>
  22. #include <stdlib.h>
  23. #include <stdio.h>
  24. static gbfile* file_in, *file_out;
  25. static short_handle mkshort_handle;
  26. static short_handle mkshort_handle2; /* for track and route names */
  27. static char* deficon = NULL;
  28. static char* cartoexploreur;
  29. static int read_as_degrees;
  30. static int read_gpsu;
  31. static int route_ctr;
  32. static int comment_col = 60; /* This has a default */
  33. static int sym_col;
  34. static int lat_col;
  35. static int lon_col;
  36. #define MYNAME "PCX"
  37. static
  38. arglist_t pcx_args[] = {
  39. {
  40. "deficon", &deficon, "Default icon name", "Waypoint",
  41. ARGTYPE_STRING, ARG_NOMINMAX
  42. },
  43. {
  44. "cartoexploreur", &cartoexploreur,
  45. "Write tracks compatible with Carto Exploreur", NULL,
  46. ARGTYPE_BOOL, ARG_NOMINMAX
  47. },
  48. ARG_TERMINATOR
  49. };
  50. static void
  51. rd_init(const QString& fname)
  52. {
  53. file_in = gbfopen(fname, "rb", MYNAME);
  54. }
  55. static void
  56. rd_deinit(void)
  57. {
  58. gbfclose(file_in);
  59. }
  60. static void
  61. wr_init(const QString& fname)
  62. {
  63. file_out = gbfopen(fname, "w", MYNAME);
  64. mkshort_handle = mkshort_new_handle();
  65. mkshort_handle2 = mkshort_new_handle();
  66. }
  67. static void
  68. wr_deinit(void)
  69. {
  70. gbfclose(file_out);
  71. mkshort_del_handle(&mkshort_handle);
  72. mkshort_del_handle(&mkshort_handle2);
  73. }
  74. static void
  75. data_read(void)
  76. {
  77. char name[7], desc[41];
  78. double lat = 0, lon = 0;
  79. long alt;
  80. int symnum;
  81. char date[10];
  82. char time[9];
  83. char month[4];
  84. Waypoint* wpt_tmp;
  85. char* buff;
  86. struct tm tm;
  87. route_head* track = NULL;
  88. route_head* route = NULL;
  89. int n;
  90. char lathemi, lonhemi;
  91. char tbuf[20];
  92. char nbuf[20];
  93. int points;
  94. int line = 0;
  95. read_as_degrees = 0;
  96. points = 0;
  97. while ((buff = gbfgetstr(file_in))) {
  98. char* ibuf = lrtrim(buff);
  99. char* cp;
  100. if ((line++ == 0) && file_in->unicode) {
  101. cet_convert_init(CET_CHARSET_UTF8, 1);
  102. }
  103. switch (ibuf[0]) {
  104. case 'W':
  105. time[0] = 0;
  106. date[0] = 0;
  107. desc[0] = 0;
  108. alt = -9999;
  109. sscanf(ibuf, "W %6c %s %s %s %s %ld",
  110. name, tbuf, nbuf, date, time, &alt);
  111. if (alt == -9999) {
  112. alt = unknown_alt;
  113. }
  114. if (comment_col) {
  115. strncpy(desc, &ibuf[comment_col], sizeof(desc)-1);
  116. } else {
  117. desc[0] = 0;
  118. }
  119. symnum = 18;
  120. if (sym_col) {
  121. symnum = atoi(&ibuf[sym_col]);
  122. }
  123. // If we have explicit columns for lat and lon,
  124. // copy those entire words (warning: no spaces)
  125. // into the respective coord buffers.
  126. if (lat_col) {
  127. sscanf(tbuf, "%s", ibuf + lat_col);
  128. }
  129. if (lon_col) {
  130. sscanf(nbuf, "%s", ibuf + lon_col);
  131. }
  132. name[sizeof(name)-1] = '\0';
  133. desc[sizeof(desc)-1] = '\0';
  134. wpt_tmp = new Waypoint;
  135. wpt_tmp->altitude = alt;
  136. cp = lrtrim(name);
  137. if (*cp != '\0') {
  138. wpt_tmp->shortname = cp;
  139. }
  140. cp = lrtrim(desc);
  141. if (*cp != '\0') {
  142. wpt_tmp->description = cp;
  143. }
  144. wpt_tmp->icon_descr = gt_find_desc_from_icon_number(symnum, PCX);
  145. if (read_as_degrees || read_gpsu) {
  146. human_to_dec(tbuf, &lat, &lon, 1);
  147. human_to_dec(nbuf, &lat, &lon, 2);
  148. wpt_tmp->longitude = lon;
  149. wpt_tmp->latitude = lat;
  150. } else {
  151. lat = atof(&tbuf[1]);
  152. lon = atof(&nbuf[1]);
  153. if (tbuf[0] == 'S') {
  154. lat = -lat;
  155. }
  156. if (nbuf[0] == 'W') {
  157. lon = -lon;
  158. }
  159. wpt_tmp->longitude = ddmm2degrees(lon);
  160. wpt_tmp->latitude = ddmm2degrees(lat);
  161. }
  162. if (route != NULL) {
  163. route_add_wpt(route, new Waypoint(*wpt_tmp));
  164. }
  165. waypt_add(wpt_tmp);
  166. points++;
  167. break;
  168. case 'H':
  169. /* Garmap2 has headers
  170. "H(2 spaces)LATITUDE(some spaces)LONGTITUDE(etc... followed by);track
  171. everything else is
  172. H(2 chars)TN(tracknane\0)
  173. */
  174. if (points > 0) {
  175. track = NULL;
  176. points = 0;
  177. }
  178. if (track == NULL) {
  179. if (ibuf[3] == 'L' && ibuf[4] == 'A') {
  180. track = route_head_alloc();
  181. track->rte_name = "track";
  182. track_add_head(track);
  183. } else if (ibuf[3] == 'T' && ibuf[4] == 'N') {
  184. track = route_head_alloc();
  185. track->rte_name = &ibuf[6];
  186. track_add_head(track);
  187. }
  188. }
  189. break;
  190. case 'R':
  191. n = 1;
  192. while (ibuf[n] == ' ') {
  193. n++;
  194. }
  195. route = route_head_alloc();
  196. route->rte_name = &ibuf[n];
  197. route_add_head(route);
  198. break;
  199. case 'T':
  200. n = sscanf(ibuf, "T %lf %lf %s %s %ld",
  201. &lat, &lon, date, time, &alt);
  202. if (n == 0) {
  203. /* Attempt alternate PCX format used by
  204. * www.radroutenplaner.nrw.de */
  205. n = sscanf(ibuf, "T %c%lf %c%lf %s %s %ld",
  206. &lathemi, &lat, &lonhemi, &lon, date,
  207. time, &alt);
  208. if (lathemi == 'S') {
  209. lat = -lat;
  210. }
  211. if (lonhemi == 'W') {
  212. lon = -lon;
  213. }
  214. } else if (n == 0) {
  215. fatal(MYNAME ":Unrecognized track line '%s'\n",
  216. ibuf);
  217. }
  218. memset(&tm, 0, sizeof(tm));
  219. tm.tm_hour = atoi(time);
  220. tm.tm_min = atoi(time+3);
  221. tm.tm_sec = atoi(time+6);
  222. tm.tm_mday = atoi(date);
  223. strncpy(month, date+3, 3);
  224. month[3] = 0;
  225. tm.tm_mon = month_lookup(month);
  226. tm.tm_year = atoi(date + 7);
  227. if (tm.tm_year < 70) {
  228. tm.tm_year += 100;
  229. }
  230. wpt_tmp = new Waypoint;
  231. wpt_tmp->SetCreationTime(mkgmtime(&tm));
  232. if (read_as_degrees) {
  233. wpt_tmp->longitude = lon;
  234. wpt_tmp->latitude = lat;
  235. } else {
  236. wpt_tmp->longitude = ddmm2degrees(lon);
  237. wpt_tmp->latitude = ddmm2degrees(lat);
  238. }
  239. wpt_tmp->altitude = alt;
  240. /* Did we get a track point before a track header? */
  241. if (track == NULL) {
  242. track = route_head_alloc();
  243. track->rte_name = "Default";
  244. track_add_head(track);
  245. }
  246. track_add_wpt(track, wpt_tmp);
  247. points++;
  248. break;
  249. case 'U':
  250. read_as_degrees = ! strncmp("LAT LON DEG", ibuf + 3, 11);
  251. if (strstr(ibuf, "UTM")) {
  252. fatal(MYNAME ": UTM is not supported.\n");
  253. }
  254. break;
  255. // GPSU is apparently PCX but with a different definition
  256. // of "LAT LON DM" - unlike the other, it actually IS decimal
  257. // minutes.
  258. case 'I':
  259. read_gpsu = !(strstr(ibuf, "GPSU") == NULL) ;
  260. break;
  261. // This is a format specifier. Use this line to figure out
  262. // where our other columns start.
  263. case 'F': {
  264. int col;
  265. char* i = ibuf;
  266. sym_col = 0;
  267. for (col = 0; *i; col++, i++) {
  268. if (0 == case_ignore_strncmp(i, "comment", 7)) {
  269. comment_col = col;
  270. }
  271. if (0 == case_ignore_strncmp(i, "symbol", 6)) {
  272. sym_col = col;
  273. }
  274. if (0 == case_ignore_strncmp(i, "latitude", 8)) {
  275. lat_col = col;
  276. }
  277. if (0 == case_ignore_strncmp(i, "longitude", 9)) {
  278. lon_col = col;
  279. }
  280. }
  281. }
  282. break;
  283. default:
  284. break;
  285. ;
  286. }
  287. }
  288. }
  289. static void
  290. gpsutil_disp(const Waypoint* wpt)
  291. {
  292. double lon,lat;
  293. int icon_token = 0;
  294. char tbuf[1024];
  295. time_t tm = wpt->GetCreationTime().toTime_t();
  296. lon = degrees2ddmm(wpt->longitude);
  297. lat = degrees2ddmm(wpt->latitude);
  298. if (tm == 0) {
  299. tm = current_time().toTime_t();
  300. }
  301. strftime(tbuf, sizeof(tbuf), "%d-%b-%y %I:%M:%S", localtime(&tm));
  302. strupper(tbuf);
  303. if (deficon) {
  304. icon_token = atoi(deficon);
  305. } else {
  306. icon_token = gt_find_icon_number_from_desc(wpt->icon_descr, PCX);
  307. if (get_cache_icon(wpt)) {
  308. icon_token = gt_find_icon_number_from_desc(get_cache_icon(wpt), PCX);
  309. }
  310. }
  311. gbfprintf(file_out, "W %-6.6s %c%08.5f %c%011.5f %s %5.f %-40.40s %5e %d\n",
  312. global_opts.synthesize_shortnames ?
  313. CSTRc(mkshort_from_wpt(mkshort_handle, wpt)) :
  314. CSTRc(wpt->shortname),
  315. lat < 0.0 ? 'S' : 'N',
  316. fabs(lat),
  317. lon < 0.0 ? 'W' : 'E',
  318. fabs(lon),
  319. tbuf,
  320. (wpt->altitude == unknown_alt) ? -9999 : wpt->altitude,
  321. (wpt->description != NULL) ? CSTRc(wpt->description) : "",
  322. 0.0,
  323. icon_token);
  324. }
  325. static void
  326. pcx_track_hdr(const route_head* trk)
  327. {
  328. char buff[20];
  329. route_ctr++;
  330. snprintf(buff, sizeof(buff)-1, "Trk%03d", route_ctr);
  331. QString name = mkshort(mkshort_handle2, trk->rte_name.isEmpty() ? buff : trk->rte_name);
  332. /* Carto Exploreur (popular in France) chokes on trackname headers,
  333. * so provide option to supppress these.
  334. */
  335. if (!cartoexploreur) {
  336. gbfprintf(file_out, "\n\nH TN %s\n", CSTR(name));
  337. }
  338. gbfprintf(file_out, "H LATITUDE LONGITUDE DATE TIME ALT ;track\n");
  339. }
  340. static void
  341. pcx_route_hdr(const route_head* rte)
  342. {
  343. char buff[20];
  344. route_ctr++;
  345. snprintf(buff, sizeof(buff)-1, "Rte%03d", route_ctr);
  346. QString name = mkshort(mkshort_handle2, (rte->rte_name != NULL) ? rte->rte_name : buff);
  347. /* see pcx_track_hdr */
  348. if (!cartoexploreur) {
  349. gbfprintf(file_out, "\n\nR %s\n", CSTR(name));
  350. }
  351. gbfprintf(file_out, "\n"
  352. "H IDNT LATITUDE LONGITUDE DATE TIME ALT DESCRIPTION PROXIMITY SYMBOL ;waypts\n");
  353. }
  354. void
  355. pcx_track_disp(const Waypoint* wpt)
  356. {
  357. double lon,lat;
  358. char tbuf[100];
  359. struct tm* tm;
  360. char* tp;
  361. lon = degrees2ddmm(wpt->longitude);
  362. lat = degrees2ddmm(wpt->latitude);
  363. const time_t ct = wpt->GetCreationTime().toTime_t();
  364. tm = gmtime(&ct);
  365. strftime(tbuf, sizeof(tbuf), "%d-%b-%y %H:%M:%S", tm); /* currently ...%T does nothing under Windows */
  366. for (tp = tbuf; *tp; tp++) {
  367. *tp = toupper(*tp);
  368. }
  369. gbfprintf(file_out, "T %c%08.5f %c%011.5f %s %.f\n",
  370. lat < 0.0 ? 'S' : 'N',
  371. fabs(lat),
  372. lon < 0.0 ? 'W' : 'E',
  373. fabs(lon),
  374. tbuf, wpt->altitude);
  375. }
  376. static void
  377. data_write(void)
  378. {
  379. gbfprintf(file_out,
  380. "H SOFTWARE NAME & VERSION\n"
  381. "I PCX5 2.09\n"
  382. "\n"
  383. "H R DATUM IDX DA DF DX DY DZ\n"
  384. "M G WGS 84 121 +0.000000e+00 +0.000000e+00 +0.000000e+00 +0.000000e+00 +0.000000e+00\n"
  385. "\n"
  386. "H COORDINATE SYSTEM\n"
  387. "U LAT LON DM\n");
  388. setshort_length(mkshort_handle, 6);
  389. setshort_length(mkshort_handle2, 20); /* for track and route names */
  390. setshort_whitespace_ok(mkshort_handle2, 0);
  391. setshort_mustuniq(mkshort_handle2, 0);
  392. if (global_opts.objective == wptdata) {
  393. gbfprintf(file_out,
  394. "\n"
  395. "H IDNT LATITUDE LONGITUDE DATE TIME ALT DESCRIPTION PROXIMITY SYMBOL ;waypts\n");
  396. waypt_disp_all(gpsutil_disp);
  397. } else if (global_opts.objective == trkdata) {
  398. route_ctr = 0;
  399. track_disp_all(pcx_track_hdr, NULL, pcx_track_disp);
  400. } else if (global_opts.objective == rtedata) {
  401. route_ctr = 0;
  402. route_disp_all(pcx_route_hdr, NULL, gpsutil_disp);
  403. }
  404. }
  405. ff_vecs_t pcx_vecs = {
  406. ff_type_file,
  407. FF_CAP_RW_ALL,
  408. rd_init,
  409. wr_init,
  410. rd_deinit,
  411. wr_deinit,
  412. data_read,
  413. data_write,
  414. NULL,
  415. pcx_args,
  416. CET_CHARSET_ASCII, 1 /* CET-REVIEW */
  417. };