1 /*
2 
3 
4 	Copyright (C) 2008  Dr. J�rgen Neumann, Juergen.Neumann@online.de
5 	Copyright (C) 2005  Robert Lipe, robertlipe@usa.net (based on nmea.c)
6 	Copyright (C) 20XX  probably many others from the gpsbabel development team ;-)
7 
8 	This program is free software; you can redistribute it and/or modify
9 	it under the terms of the GNU General Public License as published by
10 	the Free Software Foundation; either version 2 of the License, or
11 	(at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 	GNU General Public License for more details.
17 
18 	You should have received a copy of the GNU General Public License
19 	along with this program; if not, write to the Free Software
20 	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
21 
22 	=====================================================================================
23 
24 	This file allows gpsbabel to read and write the internal track log format used by
25 	GoPal navigation systems. They produce a simple line-oriented format with one point per
26 	second. Unfortunately the the data does not contain a valid date, only some kind of timetick,
27 	together with each point (perhaps by mistake ??). So we have to parse the filename for a valid starting
28 	date and add the timeoffset. Second problem (at least to me) was that irregularly stupid errors were
29 	in the data, i.e. only one data point shows a totally wrong longitude or latitude. Everything else in
30 	the dataset seems ok, so I needed a way to sort out these errors. My solution is to calculate the speed
31 	between successive points and drop points not between minspeed and maxspeed. This way I can sort out most
32 	of this annoying bugs, a side effect is that if a minimum speed > 0 is set points with the same coodinates are also
33 	dropped.
34 
35 	Fileformat GoPal
36 	TICK;   TIME UTC; LONG;    LAT;       HEIGHT; SPEED km/h;  FIX; HDOP;     SAT
37 	3801444, 080558, 2.944362, 43.262117, 295.28, 0.12964, 2, 2.900000, 3
38 	Filenames:
39 		trackYYYYMMDD_HHMMSS.trk
40 		A_YYYYMMDD_HHMMSS.trk
41 	with HHMMSS local time (not UTC)
42 
43 	History
44 	2008-07-18 initial release of Version 0.1
45 	2008-07-26 bugfix: filenamehandling linux, format specification in write statement
46 
47 	ToDo:
48 	- check for midnight & adjust
49 */
50 
51 #include "defs.h"
52 #include <ctype.h>
53 #include "csv_util.h"
54 #include <time.h>
55 #include "strptime.h"
56 #include "jeeps/gpsmath.h"
57 #include "grtcirc.h"
58 #define MYNAME "gopal"
59 
60 static gbfile* fin, *fout;
61 
62 static struct tm tm,filenamedate, trackdate;
63 time_t		tx;
64 char tmp[64];
65 char routename[64];
66 static char* optdate=NULL;
67 static char* optmaxspeed=NULL;
68 static char* optminspeed=NULL;
69 static char* optclean= NULL;
70 static double minspeed,maxspeed;
71 static struct tm opt_tm;	/* converted "date" parameter */
72 static
73 arglist_t gopal_args[] = {
74   {"date", &optdate, "Complete date-free tracks with given date (YYYYMMDD).", NULL, ARGTYPE_INT, ARG_NOMINMAX },
75   {"maxspeed", &optmaxspeed, "The maximum speed (km/h) traveling from waypoint to waypoint.", "200", ARGTYPE_INT, "1", "1000" },
76   {"minspeed", &optminspeed, "The minimum speed (km/h) traveling from waypoint to waypoint. Set >0 to remove duplicate waypoints", "0", ARGTYPE_INT, "0", "999" },
77   {"clean", &optclean, "Cleanup common errors in trackdata", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
78   ARG_TERMINATOR
79 };
80 
81 #define CHECK_BOOL(a) if (a && (*a == '0')) a = NULL
82 
gopal_check_line(char * line)83 int gopal_check_line(char* line)
84 {
85   char* c = line;
86   int i = 0;
87   while ((c = strchr(c, ','))) {
88     c++;
89     i++;
90   }
91   return i;
92 }
93 
94 
95 
96 /*******************************************************************************
97 * %%%        global callbacks called by gpsbabel main process              %%% *
98 *******************************************************************************/
99 
100 static void
gopal_rd_init(const char * fname)101 gopal_rd_init(const char* fname)
102 {
103   char buff[32];
104   char* ck;
105   char* filename;
106   CHECK_BOOL(optclean);
107   if (optminspeed) {
108     minspeed=atof(optminspeed);
109     if (global_opts.debug_level > 1) {
110       fprintf(stderr,"options from command line : gopal minspeed = %s\n",optminspeed);
111     }
112   } else {
113     minspeed=0;
114   }
115   if (optmaxspeed) {
116     maxspeed=atof(optmaxspeed);
117     if (global_opts.debug_level > 1) {
118       fprintf(stderr,"options from command line : gopal maxspeed = %s\n",optmaxspeed);
119     }
120   } else {
121     maxspeed=200;
122   }
123   if (global_opts.debug_level > 1) {
124     fprintf(stderr,"setting minspeed to %5.1lf km/h and maxspeed to %5.1lf km/h\n",minspeed,maxspeed);
125   }
126 
127   fin = gbfopen(fname, "r", MYNAME);
128 
129   memset(buff,0,sizeof(buff));
130   if (optdate) {
131     memset(&opt_tm, 0, sizeof(opt_tm));
132 
133     ck = (char*)strptime(optdate, "%Y%m%d", &opt_tm);
134     if ((ck == NULL) || (*ck != '\0') || (strlen(optdate) != 8)) {
135       fatal(MYNAME ": Invalid date \"%s\"!\n", optdate);
136     } else if (opt_tm.tm_year < 70) {
137       fatal(MYNAME ": Date \"%s\" is out of range (have to be 19700101 or later)!\n", optdate);
138     }
139     tx = mkgmtime(&opt_tm);
140 
141   } else {
142     /* remove path */
143     filename = get_filename(fname);
144 
145     if ((strncmp(filename,"track",5)==0)&&(strlen(filename)>13)) { // we need at least 13 letters: trackYYYYMMDD...
146       strncpy(&buff[0],&filename[5],8);
147     } else if ((strncmp(filename,"A_",2)==0)&&(strlen(filename)>10)) { // here we expect at least 10 letters: A_YYYYMMDD...
148       strncpy(&buff[0],&filename[2],8);
149     }
150     // in buff we should now have something wich looks like a valid date starting with YYYYMMDD
151     ck = (char*)strptime(buff, "%Y%m%d", &filenamedate);
152     // if (((ck == NULL) || (*ck != '\0') )&&!(optdate))
153     // fatal(MYNAME ": Invalid date in filename \"%s\", try to set manually using \"date\" switch!\n", buff);
154     // /* else */ if (filenamedate.tm_year < 70)
155     // fatal(MYNAME ": Date \"%s\" is out of range (have to be 19700101 or later)!\n", buff);
156     // tx= mkgmtime(&filenamedate);
157   }
158 }
159 
160 static void
gopal_rd_deinit(void)161 gopal_rd_deinit(void)
162 {
163   gbfclose(fin);
164 }
165 
166 static void
gopal_read(void)167 gopal_read(void)
168 {
169 
170   char* buff;
171   char* str, *c;
172   int column;
173   long line;
174   double hmsd,speed;
175   int fix, hms;
176   route_head* route;
177   waypoint* wpt, *lastwpt=NULL;
178   double long_old,lat_old;
179   char tbuffer[64];
180   struct tm tm2;
181   long_old=0;
182   lat_old=0;
183   strftime(routename,sizeof(routename),"Tracklog %c",gmtime(&tx));
184 
185   route = route_head_alloc();
186   route->rte_name=xstrdup(routename);
187   route_add_head(route);
188 
189   line=0;
190   while ((buff = gbfgetstr(fin))) {
191     int nfields;
192     unsigned long microsecs;
193     if ((line == 0) && fin->unicode) {
194       cet_convert_init(CET_CHARSET_UTF8, 1);
195     }
196 
197     str = buff = lrtrim(buff);
198     if (*buff == '\0') {
199       continue;
200     }
201     nfields = gopal_check_line(buff);
202     if ((nfields != 8) && (nfields != 11)) {
203       continue;
204     }
205     // Old format.  Hassle for date.
206     if ((nfields == 8) && (tx == 0)) {
207       // fatal(MYNAME ": Invalid date in filename \"%s\", try to set manually using \"date\" switch!\n", buff);
208     }
209     wpt = waypt_new();
210 
211     column = -1;
212     // the format of gopal is quite simple. Unfortunately the developers forgot the date as the first element...
213     //TICK;    TIME;   LONG;     LAT;       HEIGHT; SPEED;  Fix; HDOP;    SAT
214     //3801444, 080558, 2.944362, 43.262117, 295.28, 0.12964, 2, 2.900000, 3
215     c = csv_lineparse(str, ",", "", column++);
216     while (c != NULL) {
217       switch (column) {
218       case  0: /* "-" */	/* unknown fields for the moment */
219         sscanf(c, "%lu", &microsecs);
220         wpt->microseconds += microsecs % 1000000;
221         wpt->creation_time += microsecs / 1000000;
222         break;
223       case  1:				/* Time UTC */
224         sscanf(c,"%lf",&hmsd);
225         hms = (int) hmsd;
226         tm.tm_sec = hms % 100;
227         hms = hms / 100;
228         tm.tm_min = hms % 100;
229         hms = hms / 100;
230         tm.tm_hour = hms % 100;
231         tm.tm_year=trackdate.tm_year;
232         tm.tm_mon=trackdate.tm_mon;
233         tm.tm_mday=trackdate.tm_mday;
234         wpt->creation_time = tx+((((time_t)tm.tm_hour * 60) + tm.tm_min) * 60) + tm.tm_sec;
235         if (global_opts.debug_level > 1) {
236           strftime(tbuffer, sizeof(tbuffer), "%c", gmtime(&wpt->creation_time));
237           printf("parsed timestamp: %s\n",tbuffer);
238         }
239         break;
240 
241       case  2: 				/* longitude */
242         sscanf(c, "%lf", &wpt->longitude);
243         break;
244 
245       case  3: 				/* latitude */
246         sscanf(c, "%lf", &wpt->latitude);
247         break;
248       case  4: 				/* altitude */
249         sscanf(c, "%lf", &wpt->altitude);
250         break;
251       case  5: 				/* speed */
252         //sscanf(c, "%lf", &wpt->speed);
253         wpt->speed=atof(c);
254         if (global_opts.debug_level > 1) {
255           printf("parsed speed: %8.5f\n",wpt->speed);
256         }
257         break;
258       case  6: 				/* type of fix */
259         sscanf(c, "%d", &fix);
260         //my device shows only 0 or 2
261         //should i guess from no of sats if 2d or 3d?
262         switch (fix) {
263         case 0:
264           wpt->fix = fix_none;
265           break;
266         case 2:
267           wpt->fix = fix_2d;
268           break;
269           //case 3: wpt->fix = fix_3d;break;
270           //case 4: wpt->fix = fix_dgps;break; /* 2D_diff */
271           //case 5: wpt->fix = fix_dgps;break; /* 3D_diff */
272         default:
273           wpt->fix = fix_unknown;
274           break;
275         }
276         break;
277       case  7: 				/* hdop */
278         wpt->hdop = atof(c);
279         //sscanf(c, "%lf", &wpt->hdop); does not work ???
280         //wpt->vdop=0;wpt->hdop=0;
281         break;
282       case  8: 				/* number of sats */
283         sscanf(c, "%d", &wpt->sat);
284         break;
285         // Somewhere around mid/late 2009, these files started
286         // seeing 11 fields.
287       case  9:
288         memset(&tm2, 0, sizeof(tm2));
289         if (!strptime(c, "%Y%m%d", &tm2)) {
290           fatal("Bad date '%s'.\n", c);
291         }
292         wpt->creation_time += mkgmtime(&tm2);
293         break;
294       case 10:  // Unknown.  Ignored.
295       case 11:  // Bearing.  Ignored.
296         break;
297       }
298       c = csv_lineparse(NULL, ",", "", column++);
299     }
300     line++;
301 
302     if ((wpt->fix != fix_none)&&(lat_old==0)) { //first-time init
303       lat_old=wpt->latitude;
304       long_old=wpt->longitude;
305       //route_add_wpt(route, wpt);
306       lastwpt=wpt;
307     }
308     //calculate the speed to reach this waypoint from the last. This way I try to sort out invalid waypoints
309     speed=0;
310     if (lastwpt !=NULL) {
311       speed=3.6*radtometers(gcdist(RAD(lastwpt->latitude), RAD(lastwpt->longitude), RAD(wpt->latitude), RAD(wpt->longitude))) / abs(wpt->creation_time - lastwpt->creation_time);
312       //printf("speed line %d %lf \n",line,speed);
313     }
314     /* Error handling: in the tracklog of my device sometimes "jump" waypoints ;-) */
315     if	((optclean) &&
316          (((wpt->longitude==0.0)|| (wpt->latitude==0.0)||(abs(wpt->latitude)>90)||(abs(wpt->longitude)>180))||
317           ((speed>maxspeed)||(speed<minspeed)))
318        ) {
319       if (global_opts.debug_level > 1) {
320         fprintf(stderr,"Problem in or around line %5lu: \"%s\" %lf km/h\n",line,buff,speed);
321       }
322     } else {
323       if (global_opts.debug_level > 1) {
324         fprintf(stderr,"valid                line %5lu: \"%s\" %lf km/h\n",line,buff,speed);
325       }
326       lastwpt=wpt;
327       long_old=wpt->longitude;
328       lat_old=wpt->latitude;
329       route_add_wpt(route,wpt);
330       waypt_add(waypt_dupe(wpt));
331     }
332   }
333 }
334 
335 static void
gopal_route_hdr(const route_head * route)336 gopal_route_hdr(const route_head* route)
337 {
338 
339 }
340 
341 static void
gopal_route_tlr(const route_head * rte)342 gopal_route_tlr(const route_head* rte)
343 {
344 }
345 
346 static void
gopal_write_waypt(const waypoint * wpt)347 gopal_write_waypt(const waypoint* wpt)
348 {
349   char tbuffer[64];
350   unsigned long timestamp;
351   int fix=fix_unknown;
352   //TICK;    TIME;   LONG;     LAT;       HEIGHT; SPEED;  UN; HDOP;     SAT
353   //3801444, 080558, 2.944362, 43.262117, 295.28, 0.12964, 2, 2.900000, 3
354   strftime(tbuffer, sizeof(tbuffer), "%H%M%S", gmtime(&wpt->creation_time));
355   if (wpt->fix!=fix_unknown) {
356     switch (wpt->fix) {
357     case fix_none:
358       fix = 0;
359       break;
360     case fix_2d:
361       fix = 2;
362       break;
363     default:
364       fix = 0;
365       break;
366     }
367   }
368   //MSVC handles time_t as int64, gcc and mac only int32, so convert it:
369   timestamp=(unsigned long)wpt->creation_time;
370   gbfprintf(fout, "%lu, %s, %lf, %lf, %5.1lf, %8.5lf, %d, %lf, %d\n",timestamp,tbuffer,  wpt->longitude, wpt->latitude,wpt->altitude,
371             wpt->speed,fix,wpt->hdop,wpt->sat);
372 }
373 
374 
375 static void
gopal_wr_init(const char * fname)376 gopal_wr_init(const char* fname)
377 {
378   fout = gbfopen(fname, "w", MYNAME);
379 }
380 
381 static void
gopal_wr_deinit(void)382 gopal_wr_deinit(void)
383 {
384   gbfclose(fout);
385 }
386 
387 static void
gopal_write(void)388 gopal_write(void)
389 {
390   route_disp_all(gopal_route_hdr, gopal_route_tlr, gopal_write_waypt);
391 }
392 
393 static void
gopal_exit(void)394 gopal_exit(void)		/* optional */
395 {
396 }
397 
398 /**************************************************************************/
399 
400 // capabilities below means: we can only read and write waypoints
401 //
402 
403 ff_vecs_t gopal_vecs = {
404   ff_type_file,
405   {
406     ff_cap_none 	 	/* waypoints */,
407     (ff_cap)(ff_cap_read | ff_cap_write)	/* tracks */,
408     ff_cap_none  	/* routes */
409   },
410   gopal_rd_init,
411   gopal_wr_init,
412   gopal_rd_deinit,
413   gopal_wr_deinit,
414   gopal_read,
415   gopal_write,
416   gopal_exit,
417   gopal_args,
418   CET_CHARSET_ASCII, 0	/* ascii is the expected character set */
419   /* not fixed, can be changed through command line parameter */
420 };
421 /**************************************************************************/
422