1 /*
2 
3     Track manipulation filter
4     Copyright (c) 2009, 2010 Robert Lipe, robertlipe@gpsbabel.org
5     Copyright (C) 2005-2006 Olaf Klein, o.b.klein@gpsbabel.org
6 
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 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
20 
21  */
22 /*
23    2005-07-20: implemented interval option from Etienne Tasse
24    2005-07-26: implemented range option
25    2005-07-26: implemented move option
26    2005-07-26: implemented merge option
27    2005-07-29: warning fixes
28    2005-08-01: Add 'static' qualifier when we can (robertl)
29    2005-10-04: Add filterdefs to hold protos for filter functions... (robertl)
30    2005-10-04: Fix range-check max. value; exit filter, if no more tracks left
31    2006-04-06: Add fix, course, and speed options (parkrrrr)
32    2006-06-01: Add name option
33    2007-01-08: if not really needed disable check for valid timestamps
34 	(based on patch from Vladimir Kondratiev)
35    2007-07-26: Allow 'range' together with trackpoints without timestamp
36    2010-06-02: Add specified timestamp to each trackpoint (added by sven_luzar)
37    2012-05-04: Added 'discard' option to 'merge' to throw out track points without timestamp
38 */
39 
40 #include <ctype.h>
41 #include "defs.h"
42 #include "filterdefs.h"
43 #include "strptime.h"
44 #include "grtcirc.h"
45 #include "xmlgeneric.h"
46 
47 #if FILTERS_ENABLED || MINIMAL_FILTERS
48 #define MYNAME "trackfilter"
49 
50 #define TRACKFILTER_PACK_OPTION		"pack"
51 #define TRACKFILTER_SPLIT_OPTION	"split"
52 #define TRACKFILTER_SDIST_OPTION	"sdistance"
53 #define TRACKFILTER_TITLE_OPTION	"title"
54 #define TRACKFILTER_MERGE_OPTION	"merge"
55 #define TRACKFILTER_NAME_OPTION		"name"
56 #define TRACKFILTER_STOP_OPTION		"stop"
57 #define TRACKFILTER_START_OPTION	"start"
58 #define TRACKFILTER_MOVE_OPTION		"move"
59 #define TRACKFILTER_FIX_OPTION          "fix"
60 #define TRACKFILTER_COURSE_OPTION       "course"
61 #define TRACKFILTER_SPEED_OPTION        "speed"
62 #define TRACKFILTER_SEG2TRK_OPTION      "seg2trk"
63 #define TRACKFILTER_TRK2SEG_OPTION      "trk2seg"
64 #define TRACKFILTER_SEGMENT_OPTION      "segment"
65 #define TRACKFILTER_FAKETIME_OPTION     "faketime"
66 #define TRACKFILTER_DISCARD_OPTION      "discard"
67 
68 #undef TRACKF_DBG
69 
70 static char *opt_merge = NULL;
71 static char *opt_pack = NULL;
72 static char *opt_split = NULL;
73 static char *opt_sdistance = NULL;
74 static char *opt_move = NULL;
75 static char *opt_title = NULL;
76 static char *opt_start = NULL;
77 static char *opt_stop = NULL;
78 static char *opt_fix = NULL;
79 static char *opt_course = NULL;
80 static char *opt_speed = NULL;
81 static char *opt_name = NULL;
82 static char *opt_seg2trk = NULL;
83 static char *opt_trk2seg = NULL;
84 static char *opt_segment = NULL;
85 static char *opt_faketime = NULL;
86 static char *opt_discard = NULL;
87 
88 static
89 arglist_t trackfilter_args[] = {
90   {
91     TRACKFILTER_MOVE_OPTION, &opt_move,
92     "Correct trackpoint timestamps by a delta", NULL, ARGTYPE_STRING,
93     ARG_NOMINMAX
94   },
95   {
96     TRACKFILTER_PACK_OPTION,  &opt_pack,
97     "Pack all tracks into one", NULL, ARGTYPE_BOOL, ARG_NOMINMAX
98   },
99   {
100     TRACKFILTER_SPLIT_OPTION, &opt_split,
101     "Split by date or time interval (see README)", NULL,
102     ARGTYPE_STRING, ARG_NOMINMAX
103   },
104   {
105     TRACKFILTER_SDIST_OPTION, &opt_sdistance,
106     "Split by distance", NULL,
107     ARGTYPE_STRING, ARG_NOMINMAX
108   },
109   {
110     TRACKFILTER_MERGE_OPTION, &opt_merge,
111     "Merge multiple tracks for the same way", NULL, ARGTYPE_STRING,
112     ARG_NOMINMAX
113   },
114   {
115     TRACKFILTER_NAME_OPTION, &opt_name,
116     "Use only track(s) where title matches given name", NULL, ARGTYPE_STRING,
117     ARG_NOMINMAX
118   },
119   {
120     TRACKFILTER_START_OPTION, &opt_start,
121     "Use only track points after this timestamp", NULL, ARGTYPE_INT,
122     ARG_NOMINMAX
123   },
124   {
125     TRACKFILTER_STOP_OPTION, &opt_stop,
126     "Use only track points before this timestamp", NULL, ARGTYPE_INT,
127     ARG_NOMINMAX
128   },
129   {
130     TRACKFILTER_TITLE_OPTION, &opt_title,
131     "Basic title for new track(s)", NULL, ARGTYPE_STRING, ARG_NOMINMAX
132   },
133   {
134     TRACKFILTER_FIX_OPTION, &opt_fix,
135     "Synthesize GPS fixes (PPS, DGPS, 3D, 2D, NONE)", NULL,
136     ARGTYPE_STRING, ARG_NOMINMAX
137   },
138   {
139     TRACKFILTER_COURSE_OPTION, &opt_course, "Synthesize course",
140     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
141   },
142   {
143     TRACKFILTER_SPEED_OPTION, &opt_speed, "Synthesize speed",
144     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
145   },
146   {
147     TRACKFILTER_SEG2TRK_OPTION, &opt_seg2trk,
148     "Split track at segment boundaries into multiple tracks",
149     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
150   },
151   {
152     TRACKFILTER_TRK2SEG_OPTION, &opt_trk2seg,
153     "Merge tracks inserting segment separators at boundaries",
154     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
155   },
156   {
157     TRACKFILTER_SEGMENT_OPTION, &opt_segment,
158     "segment tracks with abnormally long gaps",
159     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
160   },
161   {
162     TRACKFILTER_FAKETIME_OPTION, &opt_faketime,
163     "Add specified timestamp to each trackpoint",
164     NULL, ARGTYPE_STRING, ARG_NOMINMAX
165   },
166   {
167     TRACKFILTER_DISCARD_OPTION,  &opt_discard,
168     "Discard track points without timestamps during merge",
169     NULL, ARGTYPE_BOOL, ARG_NOMINMAX
170   },
171   ARG_TERMINATOR
172 };
173 
174 
175 typedef struct trkflt_s {
176   route_head *track;
177   time_t first_time;
178   time_t last_time;
179 } trkflt_t;
180 
181 static trkflt_t *track_list = NULL;
182 static int track_ct = 0;
183 static int track_pts = 0;
184 static int timeless_pts = 0;
185 static int opt_interval = 0;
186 static int opt_distance = 0;
187 static char need_time;		/* initialized within trackfilter_init */
188 
189 /*******************************************************************************
190 * helpers
191 *******************************************************************************/
192 
193 static int
trackfilter_opt_count(void)194 trackfilter_opt_count(void)
195 {
196   int res = 0;
197   arglist_t *a = trackfilter_args;
198 
199   while (a->argstring) {
200     if (*a->argval != NULL) {
201       res++;
202     }
203     a++;
204   }
205   return res;
206 }
207 
208 static int
trackfilter_parse_time_opt(const char * arg)209 trackfilter_parse_time_opt(const char *arg)
210 {
211   time_t t0, t1;
212   int sign = 1;
213   char *cin = (char *)arg;
214   char c;
215 
216   t0 = t1 = 0;
217 
218   while ((c = *cin++)) {
219     time_t seconds;
220 
221     if (c >= '0' && c <= '9') {
222       t1 = (t1 * 10) + (c - '0');
223       continue;
224     }
225     switch (tolower(c)) {
226     case 'd':
227       seconds = SECONDS_PER_DAY;
228       break;
229     case 'h':
230       seconds = SECONDS_PER_HOUR;
231       break;
232     case 'm':
233       seconds = 60;
234       break;
235     case 's':
236       seconds = 1;
237       break;
238     case '+':
239       sign = +1;
240       continue;
241     case '-':
242       sign = -1;
243       continue;
244     default:
245       fatal(MYNAME "-time: invalid character in time option!\n");
246     }
247     t0 += (t1 * seconds * sign);
248     sign = +1;
249     t1 = 0;
250   }
251   t0 += t1;
252   return t0;
253 }
254 
255 static int
trackfilter_init_qsort_cb(const void * a,const void * b)256 trackfilter_init_qsort_cb(const void *a, const void *b)
257 {
258   const trkflt_t *ra = (const trkflt_t*) a;
259   const trkflt_t *rb = (const trkflt_t*) b;
260 
261   return ra->first_time - rb->first_time;
262 }
263 
264 static int
trackfilter_merge_qsort_cb(const void * a,const void * b)265 trackfilter_merge_qsort_cb(const void *a, const void *b)
266 {
267   const waypoint *wa = *(waypoint **)a;
268   const waypoint *wb = *(waypoint **)b;
269 
270   return wa->creation_time - wb->creation_time;
271 }
272 
273 static fix_type
trackfilter_parse_fix(int * nsats)274 trackfilter_parse_fix(int *nsats)
275 {
276   if (!opt_fix) {
277     return fix_unknown;
278   }
279   if (!case_ignore_strcmp(opt_fix, "pps")) {
280     *nsats = 4;
281     return fix_pps;
282   }
283   if (!case_ignore_strcmp(opt_fix, "dgps")) {
284     *nsats = 4;
285     return fix_dgps;
286   }
287   if (!case_ignore_strcmp(opt_fix, "3d")) {
288     *nsats = 4;
289     return fix_3d;
290   }
291   if (!case_ignore_strcmp(opt_fix, "2d")) {
292     *nsats = 3;
293     return fix_2d;
294   }
295   if (!case_ignore_strcmp(opt_fix, "none")) {
296     *nsats = 0;
297     return fix_none;
298   }
299   fatal(MYNAME ": invalid fix type\n");
300   return fix_unknown;
301 }
302 
303 static void
trackfilter_fill_track_list_cb(const route_head * track)304 trackfilter_fill_track_list_cb(const route_head *track) 	/* callback for track_disp_all */
305 {
306   int i;
307   waypoint *wpt, *prev;
308   queue *elem, *tmp;
309 
310   if (track->rte_waypt_ct == 0) {
311     track_del_head((route_head *)track);
312     return;
313   }
314 
315   if (opt_name != NULL) {
316     if ((track->rte_name == NULL) ||
317         (case_ignore_str_match(track->rte_name, opt_name) == 0)) {
318       QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
319         waypoint *wpt = (waypoint *)elem;
320         track_del_wpt((route_head *)track, wpt);
321         waypt_free(wpt);
322       }
323       track_del_head((route_head *)track);
324       return;
325     }
326   }
327 
328   track_list[track_ct].track = (route_head *)track;
329 
330   i = 0;
331   prev = NULL;
332 
333   QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
334     track_pts++;
335 
336     wpt = (waypoint *)elem;
337     if(wpt->creation_time == 0) timeless_pts++;
338     if (!(opt_merge && opt_discard) && (need_time != 0) && (wpt->creation_time == 0)) {
339       fatal(MYNAME "-init: Found track point at %f,%f without time!\n",
340             wpt->latitude, wpt->longitude);
341     }
342 
343     i++;
344     if (i == 1) {
345       track_list[track_ct].first_time = wpt->creation_time;
346     } else if (i == track->rte_waypt_ct) {
347       track_list[track_ct].last_time = wpt->creation_time;
348     }
349 
350     if ((need_time != 0) && (prev != NULL) && (prev->creation_time > wpt->creation_time)) {
351       if (opt_merge == NULL) {
352         char t1[64], t2[64];
353         xml_fill_in_time(t1, prev->creation_time, 0, XML_LONG_TIME);
354         xml_fill_in_time(t2, wpt->creation_time, 0, XML_LONG_TIME);
355         fatal(MYNAME "-init: Track points badly ordered (timestamp %s > %s)!\n", t1, t2);
356       }
357     }
358     prev = wpt;
359   }
360   track_ct++;
361 }
362 
363 /*******************************************************************************
364 * track title producers
365 *******************************************************************************/
366 
367 static void
trackfilter_split_init_rte_name(route_head * track,const time_t time)368 trackfilter_split_init_rte_name(route_head *track, const time_t time)
369 {
370   char buff[128], tbuff[128];
371   struct tm tm;
372 
373   tm = *localtime(&time);
374 
375   (opt_interval != 0) ?
376   strftime(tbuff, sizeof(tbuff), "%Y%m%d%H%M%S", &tm) :
377   strftime(tbuff, sizeof(tbuff), "%Y%m%d", &tm);
378 
379   if ((opt_title != NULL) && (strlen(opt_title) > 0)) {
380     if (strchr(opt_title, '%') != NULL) {
381       strftime(buff, sizeof(buff), opt_title, &tm);
382     } else {
383       snprintf(buff, sizeof(buff), "%s-%s", opt_title, tbuff);
384     }
385   } else if ((track->rte_name != NULL) && (strlen(track->rte_name) > 0)) {
386     snprintf(buff, sizeof(buff), "%s-%s", track->rte_name, tbuff);
387   } else {
388     strncpy(buff, tbuff, sizeof(buff));
389   }
390 
391   if (track->rte_name != NULL) {
392     xfree(track->rte_name);
393   }
394   track->rte_name = xstrdup(buff);
395 }
396 
397 static void
trackfilter_pack_init_rte_name(route_head * track,const time_t default_time)398 trackfilter_pack_init_rte_name(route_head *track, const time_t default_time)
399 {
400   char buff[128];
401 
402   if (strchr(opt_title, '%') != NULL) {
403     struct tm tm;
404     waypoint *wpt;
405 
406     if (track->rte_waypt_ct == 0) {
407       tm = *localtime(&default_time);
408     } else {
409       wpt = (waypoint *) QUEUE_FIRST((queue *)&track->waypoint_list);
410       tm = *localtime(&wpt->creation_time);
411     }
412     strftime(buff, sizeof(buff), opt_title, &tm);
413   } else {
414     strncpy(buff, opt_title, sizeof(buff));
415   }
416 
417   if (track->rte_name != NULL) {
418     xfree(track->rte_name);
419   }
420   track->rte_name = xstrdup(buff);
421 }
422 
423 /*******************************************************************************
424 * option "title"
425 *******************************************************************************/
426 
427 static void
trackfilter_title(void)428 trackfilter_title(void)
429 {
430   int i;
431 
432   if (opt_title == NULL) {
433     return;
434   }
435 
436   if (strlen(opt_title) == 0) {
437     fatal(MYNAME "-title: Missing your title!\n");
438   }
439   for (i = 0; i < track_ct; i++) {
440     route_head *track = track_list[i].track;
441     trackfilter_pack_init_rte_name(track, 0);
442   }
443 }
444 
445 /*******************************************************************************
446 * option "pack" (default)
447 *******************************************************************************/
448 
449 static void
trackfilter_pack(void)450 trackfilter_pack(void)
451 {
452   int i, j;
453   trkflt_t prev;
454   route_head *master;
455 
456   for (i = 1, j = 0; i < track_ct; i++, j++) {
457     prev = track_list[j];
458     if (prev.last_time >= track_list[i].first_time) {
459       fatal(MYNAME "-pack: Tracks overlap in time!\n");
460     }
461   }
462 
463   /* we fill up the first track by all other track points */
464 
465   master = track_list[0].track;
466 
467   for (i = 1; i < track_ct; i++) {
468     queue *elem, *tmp;
469     route_head *curr = track_list[i].track;
470 
471     QUEUE_FOR_EACH((queue *)&curr->waypoint_list, elem, tmp) {
472       waypoint *wpt = (waypoint *)elem;
473       track_del_wpt(curr, wpt);
474       track_add_wpt(master, wpt);
475     }
476     track_del_head(curr);
477     track_list[i].track = NULL;
478   }
479   track_ct = 1;
480 }
481 
482 /*******************************************************************************
483 * option "merge"
484 *******************************************************************************/
485 
486 static void
trackfilter_merge(void)487 trackfilter_merge(void)
488 {
489   int i, j, dropped;
490 
491   queue *elem, *tmp;
492   waypoint **buff;
493   waypoint *prev, *wpt;
494   route_head *master = track_list[0].track;
495 
496   if (track_pts-timeless_pts < 1) {
497     return;
498   }
499 
500   buff = (waypoint **)xcalloc(track_pts-timeless_pts, sizeof(*buff));
501 
502   j = 0;
503   for (i = 0; i < track_ct; i++) {	/* put all points into temp buffer */
504     route_head *track = track_list[i].track;
505     QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
506       wpt = (waypoint *)elem;
507       if(wpt->creation_time != 0) {
508         buff[j++] = waypt_dupe(wpt);
509       }
510       track_del_wpt(track, wpt);
511       waypt_free(wpt);
512     }
513     if (track != master) {	/* i > 0 */
514       track_del_head(track);
515     }
516   }
517   track_ct = 1;
518 
519   qsort(buff, track_pts-timeless_pts, sizeof(*buff), trackfilter_merge_qsort_cb);
520 
521   dropped = timeless_pts;
522   prev = NULL;
523 
524   for (i = 0; i < track_pts-timeless_pts; i++) {
525     wpt = buff[i];
526     if ((prev == NULL) || (prev->creation_time != wpt->creation_time)) {
527       route_add_wpt(master, wpt);
528       prev = wpt;
529     } else {
530       waypt_free(wpt);
531       dropped++;
532     }
533   }
534   xfree(buff);
535 
536   if (global_opts.verbose_status > 0) {
537     printf(MYNAME "-merge: %d track point(s) merged, %d dropped.\n", track_pts - dropped, dropped);
538   }
539 }
540 
541 /*******************************************************************************
542 * option "split"
543 *******************************************************************************/
544 
545 static void
trackfilter_split(void)546 trackfilter_split(void)
547 {
548   route_head *curr;
549   route_head *master = track_list[0].track;
550   int count = master->rte_waypt_ct;
551 
552   waypoint **buff;
553   waypoint *wpt;
554   queue *elem, *tmp;
555   int i, j;
556   double interval = -1;
557   double distance = -1;
558 
559   if (count <= 1) {
560     return;
561   }
562 
563   /* check additional options */
564 
565   opt_interval = (opt_split && (strlen(opt_split) > 0) && (0 != strcmp(opt_split, TRACKFILTER_SPLIT_OPTION)));
566   opt_distance = (opt_sdistance && (strlen(opt_sdistance) > 0) && (0 != strcmp(opt_sdistance, TRACKFILTER_SDIST_OPTION)));
567 
568   if (opt_interval != 0) {
569     double base;
570     char   unit;
571 
572     switch (strlen(opt_split)) {
573     case 0:
574       fatal(MYNAME ": No time interval specified.\n");
575       break; /* ? */
576 
577     case 1:
578       unit = *opt_split;
579       interval = 1;
580       break;
581 
582     default:
583       i = sscanf(opt_split,"%lf%c", &interval, &unit);
584       if (i == 0) {
585         /* test reverse order */
586         i = sscanf(opt_split,"%c%lf", &unit, &interval);
587       }
588       if ((i != 2) || (interval <= 0)) {
589         fatal(MYNAME ": invalid time interval specified, must be one a positive number.\n");
590       }
591       break;
592     }
593 
594     switch (tolower(unit)) {
595     case 's':
596       base = 1;
597       break;
598     case 'm':
599       base = 60;
600       break;
601     case 'h':
602       base = 60 * 60;
603       break;
604     case 'd':
605       base = 24 * 60 * 60;
606       break;
607     default:
608       fatal(MYNAME ": invalid time interval specified, must be one of [dhms].\n");
609       break;
610     }
611 #ifdef TRACKF_DBG
612     printf(MYNAME ": unit \"%c\", interval %g -> %g\n", unit, interval, base * interval);
613 #endif
614     interval *= base;
615   }
616 
617   if (opt_distance != 0) {
618     double base;
619     char   unit;
620 
621     switch (strlen(opt_sdistance)) {
622     case 0:
623       fatal(MYNAME ": No distance specified.\n");
624       break; /* ? */
625 
626     case 1:
627       unit = *opt_sdistance;
628       distance = 1;
629       break;
630 
631     default:
632       i = sscanf(opt_sdistance,"%lf%c", &distance, &unit);
633       if (i == 0) {
634         /* test reverse order */
635         i = sscanf(opt_sdistance,"%c%lf", &unit, &distance);
636       }
637       if ((i != 2) || (distance <= 0)) {
638         fatal(MYNAME ": invalid distance specified, must be one a positive number.\n");
639       }
640       break;
641     }
642 
643     switch (tolower(unit)) {
644     case 'k': /* kilometers */
645       base = 0.6214;
646       break;
647     case 'm': /* miles */
648       base = 1;
649       break;
650     default:
651       fatal(MYNAME ": invalid distance specified, must be one of [km].\n");
652       break;
653     }
654 #ifdef TRACKF_DBG
655     printf(MYNAME ": unit \"%c\", distance %g -> %g\n", unit, distance, base * distance);
656 #endif
657     distance *= base;
658   }
659 
660   trackfilter_split_init_rte_name(master, track_list[0].first_time);
661 
662   buff = (waypoint **) xcalloc(count, sizeof(*buff));
663 
664   i = 0;
665   QUEUE_FOR_EACH((queue *)&master->waypoint_list, elem, tmp) {
666     wpt = (waypoint *)elem;
667     buff[i++] = wpt;
668   }
669 
670   curr = NULL;	/* will be set by first new track */
671 
672   for (i=0, j=1; j<count; i++, j++) {
673     int new_track_flag;
674 
675     if ((opt_interval == 0) && (opt_distance == 0)) {
676       struct tm t1, t2;
677 
678       t1 = *localtime(&buff[i]->creation_time);
679       t2 = *localtime(&buff[j]->creation_time);
680 
681       new_track_flag = ((t1.tm_year != t2.tm_year) || (t1.tm_mon != t2.tm_mon) ||
682                         (t1.tm_mday != t2.tm_mday));
683 #ifdef TRACKF_DBG
684       if (new_track_flag != 0) {
685         printf(MYNAME ": new day %02d.%02d.%04d\n", t2.tm_mday, t2.tm_mon+1, t2.tm_year+1900);
686       }
687 #endif
688     } else {
689       new_track_flag = 1;
690 
691       if (distance > 0) {
692         double rt1 = RAD(buff[i]->latitude);
693         double rn1 = RAD(buff[i]->longitude);
694         double rt2 = RAD(buff[j]->latitude);
695         double rn2 = RAD(buff[j]->longitude);
696         double curdist = gcdist(rt1, rn1, rt2, rn2);
697         curdist = radtomiles(curdist);
698         if (curdist <= distance) {
699           new_track_flag = 0;
700         }
701 #ifdef TRACKF_DBG
702         else {
703           printf(MYNAME ": sdistance, %g > %g\n", curdist, distance);
704         }
705 #endif
706       }
707 
708       if (interval > 0) {
709         double tr_interval = difftime(buff[j]->creation_time,buff[i]->creation_time);
710         if (tr_interval <= interval) {
711           new_track_flag = 0;
712         }
713 #ifdef TRACKF_DBG
714         else {
715           printf(MYNAME ": split, %g > %g\n", tr_interval, interval);
716         }
717 #endif
718       }
719 
720     }
721     if (new_track_flag != 0) {
722 #ifdef TRACKF_DBG
723       printf(MYNAME ": splitting new track\n");
724 #endif
725       curr = (route_head *) route_head_alloc();
726       trackfilter_split_init_rte_name(curr, buff[j]->creation_time);
727       track_add_head(curr);
728     }
729     if (curr != NULL) {
730       wpt = buff[j];
731       track_del_wpt(master, wpt);
732       track_add_wpt(curr, wpt);
733       buff[j] = wpt;
734     }
735   }
736   xfree(buff);
737 }
738 
739 /*******************************************************************************
740 * option "move"
741 *******************************************************************************/
742 
743 static void
trackfilter_move(void)744 trackfilter_move(void)
745 {
746   int i;
747   queue *elem, *tmp;
748   waypoint *wpt;
749   time_t delta;
750 
751   delta = trackfilter_parse_time_opt(opt_move);
752   if (delta == 0) {
753     return;
754   }
755 
756   for (i = 0; i < track_ct; i++) {
757     route_head *track = track_list[i].track;
758     QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
759       wpt = (waypoint *)elem;
760       wpt->creation_time += delta;
761     }
762     track_list[i].first_time += delta;
763     track_list[i].last_time += delta;
764   }
765 }
766 
767 /*******************************************************************************
768 * options "fix", "course", "speed"
769 *******************************************************************************/
770 
771 static void
trackfilter_synth(void)772 trackfilter_synth(void)
773 {
774   int i;
775   queue *elem, *tmp;
776   waypoint *wpt;
777 
778   double oldlat = -999;
779   double oldlon = -999;
780   time_t oldtime = 0;
781   int first = 1;
782   fix_type fix;
783   int nsats = 0;
784 
785   fix = trackfilter_parse_fix(&nsats);
786 
787   for (i = 0; i < track_ct; i++) {
788     route_head *track = track_list[i].track;
789     first = 1;
790     QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
791       wpt = (waypoint *)elem;
792       if (opt_fix) {
793         wpt->fix = fix;
794         if (wpt->sat == 0) {
795           wpt->sat = nsats;
796         }
797       }
798       if (first) {
799         if (opt_course) {
800           WAYPT_SET(wpt, course, 0);
801         }
802         if (opt_speed) {
803           WAYPT_SET(wpt, speed, 0);
804         }
805         first = 0;
806       } else {
807         if (opt_course) {
808           WAYPT_SET(wpt, course, heading_true_degrees(RAD(oldlat),
809                     RAD(oldlon),RAD(wpt->latitude),
810                     RAD(wpt->longitude)));
811         }
812         if (opt_speed) {
813           if (oldtime != wpt->creation_time) {
814             WAYPT_SET(wpt, speed, radtometers(gcdist(
815                                                 RAD(oldlat), RAD(oldlon),
816                                                 RAD(wpt->latitude),
817                                                 RAD(wpt->longitude))) /
818                       labs(wpt->creation_time-oldtime));
819           } else {
820             WAYPT_UNSET(wpt, speed);
821           }
822         }
823       }
824       oldlat = wpt->latitude;
825       oldlon = wpt->longitude;
826       oldtime = wpt->creation_time;
827     }
828   }
829 }
830 
831 
832 /*******************************************************************************
833 * option: "start" / "stop"
834 *******************************************************************************/
835 
836 static time_t
trackfilter_range_check(const char * timestr)837 trackfilter_range_check(const char *timestr)
838 {
839   int i;
840   char fmt[20];
841   char c;
842   char *cin;
843   struct tm time;
844 
845 
846   i = 0;
847   strncpy(fmt, "00000101000000", sizeof(fmt));
848   cin = (char *)timestr;
849 
850   while ((c = *cin++)) {
851     if (fmt[i] == '\0') {
852       fatal(MYNAME "-range: parameter too long \"%s\"!\n", timestr);
853     }
854     if (isdigit(c) == 0) {
855       fatal(MYNAME "-range: invalid character \"%c\"!\n", c);
856     }
857     fmt[i++] = c;
858   }
859   cin = strptime(fmt, "%Y%m%d%H%M%S", &time);
860   if ((cin != NULL) && (*cin != '\0')) {
861     fatal(MYNAME "-range-check: Invalid time stamp (stopped at %s of %s)!\n", cin, fmt);
862   }
863 
864   return mkgmtime(&time);
865 }
866 
867 static int
trackfilter_range(void)868 trackfilter_range(void)		/* returns number of track points left after filtering */
869 {
870   time_t start, stop;
871   queue *elem, *tmp;
872   int i, dropped, inside = 0;
873 
874   if (opt_start != 0) {
875     start = trackfilter_range_check(opt_start);
876   } else {
877     start = 0;
878   }
879 
880   if (opt_stop != 0) {
881     stop = trackfilter_range_check(opt_stop);
882   } else {
883     stop = 0x7FFFFFFF;
884   }
885 
886   dropped = inside = 0;
887 
888   for (i = 0; i < track_ct; i++) {
889     route_head *track = track_list[i].track;
890 
891     QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
892       waypoint *wpt = (waypoint *)elem;
893       if (wpt->creation_time > 0) {
894         inside = ((wpt->creation_time >= start) && (wpt->creation_time <= stop));
895       }
896       // If the time is mangled so horribly that it's
897       // negative, toss it.
898       if (wpt->creation_time < 0) {
899         inside = 0;
900       }
901 
902       if (! inside) {
903         track_del_wpt(track, wpt);
904         waypt_free(wpt);
905         dropped++;
906       }
907     }
908 
909     if (track->rte_waypt_ct == 0) {
910       track_del_head(track);
911       track_list[i].track = NULL;
912     }
913   }
914 
915   if ((track_pts > 0) && (dropped == track_pts)) {
916     warning(MYNAME "-range: All %d track points have been dropped!\n", track_pts);
917   }
918 
919   return track_pts - dropped;
920 }
921 
922 /*******************************************************************************
923 * option "seg2trk"
924 *******************************************************************************/
925 
926 static void
trackfilter_seg2trk(void)927 trackfilter_seg2trk(void)
928 {
929   int i;
930 
931   for (i = 0; i < track_ct; i++) {
932     queue *elem, *tmp;
933     route_head *src = track_list[i].track;
934     route_head *dest = NULL;
935     route_head *insert_point = src;
936     int trk_seg_num = 1, first = 1;
937 
938     QUEUE_FOR_EACH((queue *)&src->waypoint_list, elem, tmp) {
939       waypoint *wpt = (waypoint *)elem;
940       if (wpt->wpt_flags.new_trkseg && !first) {
941         char trk_seg_num_buf[10];
942 
943         dest = route_head_alloc();
944         dest->rte_num = src->rte_num;
945         /* name in the form TRACKNAME #n */
946         snprintf(trk_seg_num_buf, sizeof(trk_seg_num_buf), "%d", ++trk_seg_num);
947         dest->rte_name = (char*) xmalloc(strlen(src->rte_name)+strlen(trk_seg_num_buf)+3);
948         sprintf(dest->rte_name, "%s #%s", src->rte_name, trk_seg_num_buf);
949 
950         /* Insert after original track or after last newly
951          * created track */
952         track_insert_head(dest, insert_point);
953         insert_point = dest;
954       }
955 
956       /* If we found a track separator, transfer from original to
957        * new track. We have to reset new_trkseg temporarily to
958        * prevent track_del_wpt() from copying it to the next track
959        * point.
960        */
961       if (dest) {
962         int orig_new_trkseg = wpt->wpt_flags.new_trkseg;
963         wpt->wpt_flags.new_trkseg = 0;
964         track_del_wpt(src, wpt);
965         wpt->wpt_flags.new_trkseg = orig_new_trkseg;
966         track_add_wpt(dest, wpt);
967       }
968       first = 0;
969     }
970   }
971 }
972 
973 /*******************************************************************************
974 * option "trk2seg"
975 *******************************************************************************/
976 
977 static void
trackfilter_trk2seg(void)978 trackfilter_trk2seg(void)
979 {
980   int i, first;
981   route_head *master;
982 
983   master = track_list[0].track;
984 
985   for (i = 1; i < track_ct; i++) {
986     queue *elem, *tmp;
987     route_head *curr = track_list[i].track;
988 
989     first = 1;
990     QUEUE_FOR_EACH((queue *)&curr->waypoint_list, elem, tmp) {
991       waypoint *wpt = (waypoint *)elem;
992 
993 
994       int orig_new_trkseg = wpt->wpt_flags.new_trkseg;
995       wpt->wpt_flags.new_trkseg = 0;
996       track_del_wpt(curr, wpt);
997       wpt->wpt_flags.new_trkseg = orig_new_trkseg;
998       track_add_wpt(master, wpt);
999       if (first) {
1000         wpt->wpt_flags.new_trkseg = 1;
1001         first = 0;
1002       }
1003     }
1004     track_del_head(curr);
1005     track_list[i].track = NULL;
1006   }
1007   track_ct = 1;
1008 }
1009 
1010 /*******************************************************************************
1011 * option: "faketime"
1012 *******************************************************************************/
1013 
1014 typedef struct faketime_s {
1015   time_t start;
1016   int    step;
1017   int   force;
1018 } faketime_t;
1019 
1020 static faketime_t
trackfilter_faketime_check(const char * timestr)1021 trackfilter_faketime_check(const char *timestr)
1022 {
1023   int i, j;
1024   char fmtstart[20];
1025   char fmtstep[20];
1026   char c;
1027   const char *cin;
1028   struct tm time;
1029   int timeparse = 1;
1030   faketime_t result;
1031   result.force = 0;
1032 
1033   i = j = 0;
1034   strncpy(fmtstart, "00000101000000", sizeof(fmtstart));
1035   strncpy(fmtstep,  "00000000000000", sizeof(fmtstep));
1036   cin = timestr;
1037 
1038   while ((c = *cin++)) {
1039     if (c=='f') {
1040       result.force = 1;
1041       continue;
1042     }
1043 
1044     if (c!='+' && isdigit(c) == 0) {
1045       fatal(MYNAME "-faketime: invalid character \"%c\"!\n", c);
1046     }
1047 
1048     if (timeparse) {
1049       if (c == '+') {
1050         fmtstart[i++] = '\0';
1051         timeparse = 0;
1052       } else {
1053         if (fmtstart[i] == '\0') {
1054           fatal(MYNAME "-faketime: parameter too long \"%s\"!\n", timestr);
1055         }
1056         fmtstart[i++] = c;
1057       }
1058     } else {
1059       if (fmtstep[j] == '\0') {
1060         fatal(MYNAME "-faketime: parameter too long \"%s\"!\n", timestr);
1061       }
1062       fmtstep[j++] = c;
1063     }
1064   }
1065   fmtstep[j++] = '\0';
1066 
1067   cin = strptime(fmtstart, "%Y%m%d%H%M%S", &time);
1068   result.step = atoi(fmtstep);
1069   if ((cin != NULL) && (*cin != '\0')) {
1070     fatal(MYNAME "-faketime-check: Invalid time stamp (stopped at %s of %s)!\n", cin, fmtstart);
1071   }
1072 
1073   result.start = mkgmtime(&time);
1074   return result;
1075 }
1076 
1077 static int
trackfilter_faketime(void)1078 trackfilter_faketime(void)             /* returns number of track points left after filtering */
1079 {
1080   faketime_t faketime;
1081 
1082   queue *elem, *tmp;
1083   int i, dropped, inside = 0;
1084 
1085   if (opt_faketime != 0) {
1086     faketime = trackfilter_faketime_check(opt_faketime);
1087   }
1088 
1089   dropped = inside = 0;
1090 
1091   for (i = 0; i < track_ct; i++) {
1092     route_head *track = track_list[i].track;
1093 
1094     QUEUE_FOR_EACH((queue *)&track->waypoint_list, elem, tmp) {
1095       waypoint *wpt = (waypoint *)elem;
1096 
1097       if (opt_faketime != 0 && (wpt->creation_time == 0 || faketime.force)) {
1098         wpt->creation_time = faketime.start;
1099         faketime.start += faketime.step;
1100       }
1101     }
1102   }
1103 
1104   return track_pts - dropped;
1105 }
1106 
1107 static int
trackfilter_points_are_same(const waypoint * wpta,const waypoint * wptb)1108 trackfilter_points_are_same(const waypoint *wpta, const waypoint *wptb)
1109 {
1110   // We use a simpler (non great circle) test for lat/lon here as this
1111   // is used for keeping the 'bookends' of non-moving points.
1112   //
1113   // Latitude spacing is about 27 feet per .00001 degree.
1114   // Longitude spacing varies, but the reality is that anything closer
1115   // than 27 feet does little but clutter the output.
1116   // As this is about the limit of consumer grade GPS, it seems a
1117   // reasonable tradeoff.
1118 
1119   return
1120     fabs(wpta->latitude - wptb->latitude) < .00001 &&
1121     fabs(wpta->longitude - wptb->longitude) < .00001 &&
1122     abs(wpta->altitude - wptb->altitude) < 20 &&
1123     (WAYPT_HAS(wpta,course) == WAYPT_HAS(wptb,course)) &&
1124     (wpta->course == wptb->course) &&
1125     (wpta->speed == wptb->speed) &&
1126     (wpta->heartrate == wptb->heartrate) &&
1127     (wpta->cadence == wptb->cadence) &&
1128     (wpta->temperature == wptb->temperature);
1129 }
1130 
1131 static void
trackfilter_segment_head(const route_head * rte)1132 trackfilter_segment_head(const route_head *rte)
1133 {
1134   queue *elem, *tmp;
1135   double avg_dist = 0;
1136   int index = 0;
1137   waypoint *prev_wpt = NULL;
1138   // Consider tossing trackpoints closer than this in radians.
1139   // (Empirically determined; It's a few dozen feet.)
1140   const double ktoo_close = 0.000005;
1141 
1142   QUEUE_FOR_EACH(&rte->waypoint_list, elem, tmp) {
1143     waypoint *wpt = (waypoint *)elem;
1144     if (index > 0) {
1145       double cur_dist = gcdist(RAD(prev_wpt->latitude),
1146                                RAD(prev_wpt->longitude),
1147                                RAD(wpt->latitude),
1148                                RAD(wpt->longitude));
1149       // Denoise points that are on top of each other.
1150       if (avg_dist == 0) {
1151         avg_dist = cur_dist;
1152       }
1153 
1154       if (cur_dist < ktoo_close) {
1155         if (wpt != (waypoint *) QUEUE_LAST(&rte->waypoint_list)) {
1156           waypoint *next_wpt = (waypoint *) QUEUE_NEXT(&wpt->Q);
1157           if (trackfilter_points_are_same(prev_wpt, wpt) &&
1158               trackfilter_points_are_same(wpt, next_wpt)) {
1159             track_del_wpt((route_head *)rte, wpt);
1160             continue;
1161           }
1162         }
1163       }
1164       if (cur_dist > .001 && cur_dist > 1.2* avg_dist) {
1165         avg_dist = cur_dist = 0;
1166         wpt->wpt_flags.new_trkseg = 1;
1167       }
1168       // Update weighted moving average;
1169       avg_dist = (cur_dist + 4.0 * avg_dist) / 5.0;
1170     }
1171     prev_wpt = wpt;
1172     index++;
1173   }
1174 }
1175 
1176 /*******************************************************************************
1177 * global cb's
1178 *******************************************************************************/
1179 
1180 static void
trackfilter_init(const char * args)1181 trackfilter_init(const char *args)
1182 {
1183 
1184   int count = track_count();
1185 
1186   /*
1187    * check time presence only if required. Options that NOT require time:
1188    *
1189    * - opt_title (!!! only if no format specifier is present !!!)
1190    * - opt_course
1191    * - opt_name
1192    */
1193   need_time = (
1194                 opt_merge || opt_pack || opt_split || opt_sdistance ||
1195                 opt_move || opt_fix || opt_speed ||
1196                 (trackfilter_opt_count() == 0)	/* do pack by default */
1197               );
1198   /* in case of a formated title we also need valid timestamps */
1199   if ((opt_title != NULL) && (strchr(opt_title, '%') != NULL)) {
1200     need_time = 1;
1201   }
1202 
1203   track_ct = 0;
1204   track_pts = 0;
1205 
1206   // Perform segmenting first.
1207   if (opt_segment) {
1208     track_disp_all(trackfilter_segment_head, NULL, NULL);
1209   }
1210 
1211   if (count > 0) {
1212     track_list = (trkflt_t *) xcalloc(count, sizeof(*track_list));
1213 
1214     /* check all tracks for time and order (except merging) */
1215 
1216     track_disp_all(trackfilter_fill_track_list_cb, NULL, NULL);
1217     if (need_time) {
1218       qsort(track_list, track_ct, sizeof(*track_list), trackfilter_init_qsort_cb);
1219     }
1220   }
1221 }
1222 
1223 static void
trackfilter_deinit(void)1224 trackfilter_deinit(void)
1225 {
1226   if (track_list != NULL) {
1227     xfree(track_list);
1228     track_list = NULL;
1229   }
1230   track_ct = 0;
1231   track_pts = 0;
1232 }
1233 
1234 /*******************************************************************************
1235 * trackfilter_process: called from gpsbabel central engine
1236 *******************************************************************************/
1237 
1238 static void
trackfilter_process(void)1239 trackfilter_process(void)
1240 {
1241   int opts, something_done;
1242 
1243   if (track_ct == 0) {
1244     return;  /* no track(s), no fun */
1245   }
1246 
1247   opts = trackfilter_opt_count();
1248   if (opts == 0) {
1249     opts = -1;  /* flag for do "pack" by default */
1250   }
1251 
1252   if (opt_name != NULL) {
1253     if (--opts == 0) {
1254       return;
1255     }
1256   }
1257 
1258   if (opt_move != NULL) {		/* Correct timestamps before any other op */
1259     trackfilter_move();
1260     if (--opts == 0) {
1261       return;
1262     }
1263   }
1264 
1265   if (opt_speed || opt_course || opt_fix) {
1266     trackfilter_synth();
1267     if (opt_speed) {
1268       opts--;
1269     }
1270     if (opt_course) {
1271       opts--;
1272     }
1273     if (opt_fix) {
1274       opts--;
1275     }
1276     if (!opts) {
1277       return;
1278     }
1279   }
1280 
1281   if ((opt_faketime != NULL)) {
1282     opts--;
1283 
1284     trackfilter_faketime();
1285 
1286     if (opts == 0) {
1287       return;
1288     }
1289 
1290     trackfilter_deinit();       /* reinitialize */
1291     trackfilter_init(NULL);
1292 
1293     if (track_ct == 0) {
1294       return;  /* no more track(s), no more fun */
1295     }
1296   }
1297 
1298   if ((opt_stop != NULL) || (opt_start != NULL)) {
1299     if (opt_start != NULL) {
1300       opts--;
1301     }
1302     if (opt_stop != NULL) {
1303       opts--;
1304     }
1305 
1306     trackfilter_range();
1307 
1308     if (opts == 0) {
1309       return;
1310     }
1311 
1312     trackfilter_deinit();	/* reinitialize */
1313     trackfilter_init(NULL);
1314 
1315     if (track_ct == 0) {
1316       return;  /* no more track(s), no more fun */
1317     }
1318 
1319   }
1320 
1321   if (opt_seg2trk != NULL) {
1322     trackfilter_seg2trk();
1323     if (--opts == 0) {
1324       return;
1325     }
1326 
1327     trackfilter_deinit();	/* reinitialize */
1328     trackfilter_init(NULL);
1329   }
1330 
1331   if (opt_trk2seg != NULL) {
1332     trackfilter_trk2seg();
1333     if (--opts == 0) {
1334       return;
1335     }
1336   }
1337 
1338   if (opt_title != NULL) {
1339     if (--opts == 0) {
1340       trackfilter_title();
1341       return;
1342     }
1343   }
1344 
1345   something_done = 0;
1346 
1347   if ((opt_pack != NULL) || (opts == -1)) {	/* call our default option */
1348     trackfilter_pack();
1349     something_done = 1;
1350   } else if (opt_merge != NULL) {
1351     trackfilter_merge();
1352     something_done = 1;
1353   }
1354 
1355   if ((something_done == 1) && (--opts <= 0)) {
1356     if (opt_title != NULL) {
1357       trackfilter_title();
1358     }
1359     return;
1360   }
1361 
1362   if ((opt_split != NULL) || (opt_sdistance != NULL)) {
1363     if (track_ct > 1) {
1364       fatal(MYNAME "-split: Cannot split more than one track, please pack (or merge) before!\n");
1365     }
1366 
1367     trackfilter_split();
1368   }
1369 }
1370 
1371 /******************************************************************************************/
1372 
1373 filter_vecs_t trackfilter_vecs = {
1374   trackfilter_init,
1375   trackfilter_process,
1376   trackfilter_deinit,
1377   NULL,
1378   trackfilter_args
1379 };
1380 
1381 /******************************************************************************************/
1382 #endif // FILTERS_ENABLED
1383