You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

trackfilter.cc 35KB

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