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