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.

v900.cc 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. Support for Columbus/Visiontac V900 csv format
  3. This format pads fields with NULL up to a fixed per field length.
  4. Because of that, and because xcsv does not allows a regex as a field delimiter,
  5. a special c module is required.
  6. Copyright (C) 2009 Tal Benavidor
  7. This program is free software; you can redistribute it and/or modify
  8. it under the terms of the GNU General Public License as published by
  9. the Free Software Foundation; either version 2 of the License, or
  10. (at your option) any later version.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU General Public License for more details.
  15. You should have received a copy of the GNU General Public License
  16. along with this program; if not, write to the Free Software
  17. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  18. TODO:
  19. - QUESTION: course = heading ??
  20. - HEIGHT: Altitude in meters (not corrected to WGS84...) ??
  21. */
  22. /******************************************************************************
  23. FILE FORMAT INFO
  24. =================
  25. File has csv extention, and is somewhat csv like creature...
  26. All lines end with \r\n
  27. First line is a header line. It contains no nulls.
  28. Following lines are record lines. They are comma separated, but fields always
  29. have the exact same length (per field), and therfore, the commas are always
  30. at the exact same position on the line. Fields are padded with nulls, in case
  31. they have shorter value then the fixed field length.
  32. Two modes are available: basic and advanced.
  33. The following two examples show "*" where null appears.
  34. ------basic mode - start-------------------------
  35. INDEX,TAG,DATE,TIME,LATITUDE N/S,LONGITUDE E/W,HEIGHT,SPEED,HEADING,VOX
  36. 1*****,T,090404,063401,31.765931N,035.206969E,821**,0***,0**,*********
  37. 2*****,T,090404,063402,31.765931N,035.206969E,821**,0***,0**,*********
  38. 3*****,T,090404,063403,31.765933N,035.206971E,821**,0***,0**,*********
  39. 4*****,T,090404,063404,31.765933N,035.206971E,822**,0***,0**,*********
  40. 5*****,T,090404,063407,31.765934N,035.206971E,824**,0***,0**,*********
  41. ------basic mode - end---------------------------
  42. ------advanced mode - start-------------------------
  43. INDEX,TAG,DATE,TIME,LATITUDE N/S,LONGITUDE E/W,HEIGHT,SPEED,HEADING,FIX MODE,VALID,PDOP,HDOP,VDOP,VOX
  44. 1*****,T,090204,055722,31.768380N,035.209656E,149**,0***,0**,3D,SPS ,2.6**,2.4**,1.0**,*********
  45. 2*****,T,090204,055723,31.768380N,035.209656E,149**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  46. 3*****,T,090204,055724,31.768378N,035.209658E,149**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  47. 4*****,T,090204,055725,31.768378N,035.209658E,149**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  48. 5*****,T,090204,055728,31.768376N,035.209660E,150**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  49. 6*****,T,090204,055729,31.768376N,035.209660E,150**,0***,0**,3D,SPS ,4.0**,2.8**,2.9**,*********
  50. 7*****,T,090204,055730,31.768376N,035.209661E,150**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  51. 8*****,T,090204,055731,31.768376N,035.209661E,150**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  52. 9*****,T,090204,055737,31.768326N,035.209993E,150**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  53. 10****,T,090204,055738,31.768339N,035.209976E,153**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  54. 11****,T,090204,055739,31.768338N,035.209991E,155**,0***,0**,3D,SPS ,2.5**,2.3**,0.9**,*********
  55. 42****,C,090724,162320,31.763841N,035.205461E,788**,9***,344,3D,SPS ,1.2**,0.9**,0.8**,*********
  56. 121***,V,090724,162502,31.769619N,035.208964E,786**,16**,306,3D,SPS ,1.1**,0.8**,0.8**,VOX00003*
  57. ------advanced mode - end---------------------------
  58. for a little more info, see structures:
  59. one_line_advanced_mode, one_line_basic_mode, one_line_common_start.
  60. ******************************************************************************/
  61. #include "defs.h"
  62. #include <assert.h>
  63. #include <stdio.h>
  64. #include <stdlib.h> // atoi
  65. #if _MSC_VER
  66. #define __func__ __FUNCTION__
  67. #endif
  68. /* the start of each record (line) is common to both advanced and basic mode.
  69. it will be parsed by a single common code. hence, it will be easier and clearer
  70. to have a common structure for it.
  71. */
  72. struct one_line_common_start {
  73. char index[6]; /* record number */
  74. char comma1; /* ',' */
  75. char tag; /* tag type. T=trackpoint. TODO: more options??? */
  76. char comma2; /* ',' */
  77. char date[6]; /* YYMMDD. YY=09 is 2009. */
  78. char comma3; /* ',' */
  79. char time[6]; /* HHMMSS */
  80. char comma4; /* ',' */
  81. char latitude_num[9]; /* example: "31.768380" */
  82. char latitude_NS; /* 'N' or 'S' */
  83. char comma5; /* ',' */
  84. char longitude_num[10]; /* example: "035.209656" */
  85. char longitude_EW; /* 'E' or 'W' */
  86. char comma6; /* ',' */
  87. char height[5]; /* Altitude in meters.
  88. * (not corrected to WGS84 ??) */
  89. char comma7; /* ',' */
  90. char speed[4]; /* speed in km/h. no decimal point. */
  91. char comma8; /* ',' */
  92. char heading[3]; /* heading in degrees */
  93. char comma9; /* ',' */
  94. };
  95. /* this structure holds one record (line) in advanced logging mode.
  96. advanced mode lines looks like this ('*' means NULL):
  97. 1717**,T,090204,062634,31.765528N,035.207730E,772**,0***,0**,2D,SPS ,2.1**,1.9**,1.0**,*********
  98. */
  99. struct one_line_advanced_mode {
  100. struct one_line_common_start common;
  101. char fixmode[2]; /* "2D" or "3D" */
  102. char comma10; /* ',' */
  103. char valid[4]; /* "SPS " or "DGPS" */
  104. char comma11; /* ',' */
  105. char pdop[5];
  106. char comma12; /* ',' */
  107. char hdop[5];
  108. char comma13; /* ',' */
  109. char vdop[5];
  110. char comma14; /* ',' */
  111. char vox[9]; /* voicetag recorded */
  112. char cr; /* '\r' */
  113. char lf; /* '\n' */
  114. };
  115. /* this structure holds one record (line) in basic logging mode.
  116. basic mode lines looks like this ('*' means NULL):
  117. 1*****,T,090404,063401,31.765931N,035.206969E,821**,0***,0**,*********
  118. */
  119. struct one_line_basic_mode {
  120. struct one_line_common_start common;
  121. char vox[9]; /* voicetag recorded */
  122. char cr; /* '\r' */
  123. char lf; /* '\n' */
  124. };
  125. static FILE* fin = NULL;
  126. /* copied from dg-100.cpp */
  127. static void
  128. v900_log(const char* fmt, ...)
  129. {
  130. va_list ap;
  131. if (global_opts.debug_level < 1) {
  132. return;
  133. }
  134. va_start(ap, fmt);
  135. vfprintf(stderr, fmt, ap);
  136. va_end(ap);
  137. }
  138. static void
  139. v900_rd_init(const QString& fname)
  140. {
  141. v900_log("%s(%s)\n",__func__,qPrintable(fname));
  142. /* note: file is opened in binary mode, since lines end with \r\n, and in windows text mode
  143. that will be translated to a single \n, making the line len one character shorter than
  144. on linux machines.
  145. */
  146. fin = fopen(qPrintable(fname),"rb");
  147. if (!fin) {
  148. fatal("v900: could not open '%s'.\n", qPrintable(fname));
  149. }
  150. }
  151. static void
  152. v900_rd_deinit(void)
  153. {
  154. v900_log("%s\n",__func__);
  155. if (fin) {
  156. fclose(fin);
  157. }
  158. }
  159. /* copied from dg-100.c - slight (incompatible) modification to how the date parameter is used */
  160. QDateTime
  161. bintime2utc(int date, int time) {
  162. int secs = time % 100;
  163. time /= 100;
  164. int mins = time % 100;
  165. time /= 100;
  166. // What's left in 'time' is hours, ranged 0-23.
  167. QTime tm(time, mins, secs);
  168. // 'date' starts at 2000 and is YYMMDD
  169. int day = date % 100;
  170. date /= 100;
  171. int month = date % 100;
  172. date /= 100;
  173. // What's left in 'date' is year.
  174. QDate dt(date + 2000, month, day);
  175. return QDateTime(dt, tm, Qt::UTC);
  176. }
  177. static void
  178. v900_read(void)
  179. {
  180. /* use line buffer large enough to hold either basic or advanced mode lines. */
  181. union {
  182. struct one_line_basic_mode bas;
  183. struct one_line_advanced_mode adv;
  184. char text[200]; /* used to read the header line, which is normal text */
  185. } line;
  186. int is_advanced_mode = 0;
  187. int lc = 0;
  188. route_head* track;
  189. v900_log("%s\n",__func__);
  190. /*
  191. Basic mode: INDEX,TAG,DATE,TIME,LATITUDE N/S,LONGITUDE E/W,HEIGHT,SPEED,HEADING,VOX
  192. Advanced mode: INDEX,TAG,DATE,TIME,LATITUDE N/S,LONGITUDE E/W,HEIGHT,SPEED,HEADING,FIX MODE,VALID,PDOP,HDOP,VDOP,VOX
  193. */
  194. /* first, determine if this is advanced mode by reading the first line.
  195. since the first line does not contain any nulls, it can be safely read by fgets(). */
  196. if (!fgets(line.text, sizeof(line), fin)) {
  197. fatal("v900: error reading header (first) line from input file\n");
  198. }
  199. is_advanced_mode = (NULL != strstr(line.text,"PDOP")); /* PDOP field appears only in advanced mode */
  200. v900_log("header line: %s",line.text);
  201. v900_log("is_advance_mode=%d\n",is_advanced_mode);
  202. track = route_head_alloc();
  203. track->rte_name = "V900 tracklog";
  204. track->rte_desc = "V900 GPS tracklog data";
  205. track_add_head(track);
  206. while (1) {
  207. Waypoint* wpt;
  208. char c;
  209. int bad = 0;
  210. int record_len = is_advanced_mode ? sizeof(line.adv) : sizeof(line.bas);
  211. if (fread(&line, record_len, 1, fin) != 1) {
  212. break;
  213. }
  214. lc++;
  215. /* change all "," characters to NULLs.
  216. so every field is null terminated.
  217. */
  218. bad |= (line.bas.common.comma1 != ',');
  219. bad |= (line.bas.common.comma2 != ',');
  220. bad |= (line.bas.common.comma3 != ',');
  221. bad |= (line.bas.common.comma4 != ',');
  222. bad |= (line.bas.common.comma5 != ',');
  223. bad |= (line.bas.common.comma6 != ',');
  224. bad |= (line.bas.common.comma7 != ',');
  225. bad |= (line.bas.common.comma8 != ',');
  226. bad |= (line.bas.common.comma9 != ',');
  227. if (bad) {
  228. warning("v900: skipping malformed record at line %d\n", lc);
  229. }
  230. line.bas.common.comma1 = 0;
  231. line.bas.common.comma2 = 0;
  232. line.bas.common.comma3 = 0;
  233. line.bas.common.comma4 = 0;
  234. line.bas.common.comma5 = 0;
  235. line.bas.common.comma6 = 0;
  236. line.bas.common.comma7 = 0;
  237. line.bas.common.comma8 = 0;
  238. line.bas.common.comma9 = 0;
  239. if (is_advanced_mode) {
  240. /* change all "," characters to NULLs.
  241. so every field is null terminated.
  242. */
  243. assert(line.adv.comma10==','); // TODO: abort with fatal()
  244. assert(line.adv.comma11==',');
  245. assert(line.adv.comma12==',');
  246. assert(line.adv.comma13==',');
  247. assert(line.adv.comma14==',');
  248. assert(line.adv.cr=='\r');
  249. assert(line.adv.lf=='\n');
  250. line.adv.comma10 = 0;
  251. line.adv.comma11 = 0;
  252. line.adv.comma12 = 0;
  253. line.adv.comma13 = 0;
  254. line.adv.comma14 = 0;
  255. line.adv.cr = 0; /* null terminate vox field */
  256. } else {
  257. assert(line.bas.cr=='\r');
  258. assert(line.bas.lf=='\n');
  259. line.bas.cr = 0; /* null terminate vox field */
  260. }
  261. wpt = new Waypoint;
  262. /* lat is a string in the form: 31.768380N */
  263. c = line.bas.common.latitude_NS; /* N/S */
  264. assert(c == 'N' || c == 'S');
  265. wpt->latitude = atof(line.bas.common.latitude_num);
  266. if (c == 'S') {
  267. wpt->latitude = -wpt->latitude;
  268. }
  269. /* lon is a string in the form: 035.209656E */
  270. c = line.bas.common.longitude_EW; /* get E/W */
  271. assert(c == 'E' || c == 'W');
  272. line.bas.common.longitude_EW = 0; /* the E will confuse atof(), if not removed */
  273. wpt->longitude = atof(line.bas.common.longitude_num);
  274. if (c == 'W') {
  275. wpt->longitude = -wpt->longitude;
  276. }
  277. wpt->altitude = atoi(line.bas.common.height);
  278. /* handle date/time fields */
  279. {
  280. int date, time;
  281. date = atoi(line.bas.common.date);
  282. time = atoi(line.bas.common.time);
  283. wpt->SetCreationTime(bintime2utc(date, time));
  284. }
  285. wpt->speed = KPH_TO_MPS(atoi(line.bas.common.speed));
  286. wpt->wpt_flags.speed = 1;
  287. wpt->course = atoi(line.bas.common.heading);
  288. wpt->wpt_flags.course = 1;
  289. if (is_advanced_mode) {
  290. wpt->hdop = atof(line.adv.hdop);
  291. wpt->vdop = atof(line.adv.vdop);
  292. wpt->pdop = atof(line.adv.pdop);
  293. /* handle fix mode (2d, 3d, etc.) */
  294. if (!strcmp(line.adv.valid,"DGPS")) {
  295. wpt->fix = fix_dgps;
  296. } else if (!strcmp(line.adv.fixmode,"3D")) {
  297. wpt->fix = fix_3d;
  298. } else if (!strcmp(line.adv.fixmode,"2D")) {
  299. wpt->fix = fix_2d;
  300. } else
  301. /* possible values: fix_unknown,fix_none,fix_2d,fix_3d,fix_dgps,fix_pps */
  302. {
  303. wpt->fix = fix_unknown;
  304. }
  305. }
  306. track_add_wpt(track, wpt);
  307. if (line.bas.common.tag != 'T') {
  308. Waypoint* wpt2;
  309. // A 'G' tag appears to be a 'T' tag, but generated on the trailing
  310. // edge of a DGPS fix as it decays to an SPS fix. See 1/13/13 email
  311. // thread on gpsbabel-misc with Jamie Robertson.
  312. assert(line.bas.common.tag == 'C' || line.bas.common.tag == 'G' ||
  313. line.bas.common.tag == 'V');
  314. wpt2 = new Waypoint(*wpt);
  315. if (line.bas.common.tag == 'V') { // waypoint with voice recording?
  316. char vox_file_name[sizeof(line.adv.vox)+5];
  317. const char* vox = is_advanced_mode ? line.adv.vox : line.bas.vox;
  318. assert(vox[0] != '\0');
  319. strcpy(vox_file_name,vox);
  320. strcat(vox_file_name,".WAV");
  321. wpt2->shortname = vox_file_name;
  322. wpt2->description = vox_file_name;
  323. waypt_add_url(wpt2, vox_file_name, vox_file_name);
  324. }
  325. waypt_add(wpt2);
  326. }
  327. }
  328. }
  329. ff_vecs_t v900_vecs = {
  330. ff_type_file,
  331. {ff_cap_read, ff_cap_read, ff_cap_none}, /* Read only format. May only read trackpoints and waypoints. */
  332. v900_rd_init,
  333. NULL, /* wr_init */
  334. v900_rd_deinit,
  335. NULL, /* wr_deinit */
  336. v900_read,
  337. NULL, /* write */
  338. NULL,
  339. NULL, /* args */
  340. CET_CHARSET_UTF8, 1, /* Could be US-ASCII, since we only read "0-9,A-Z\n\r" */
  341. {NULL,NULL,NULL,NULL,NULL,NULL}
  342. };