gpxsplit.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. #include <iostream>
  2. #include <cstring>
  3. #include <fstream>
  4. #include <vector>
  5. #include <cmath>
  6. #include <ctime>
  7. #include <limits>
  8. #include <iomanip>
  9. #include <stdexcept>
  10. #include "XMLRawParser.h"
  11. #include "Arguments.h"
  12. #include "Algorithm.h"
  13. // ----------------------------------------------------------------------------
  14. namespace gpxtools
  15. {
  16. class GPXSplit : public XMLParserHandler
  17. {
  18. public:
  19. // -- Constructor -----------------------------------------------------------
  20. GPXSplit() :
  21. _arguments("gpxsplit [OPTION].. [FILE]\nSplit the track segments in a GPX-file.\n", "gpxsplit v0.1",
  22. "Split the track segments in a GPX-file in multiple track segments based on distance or time and display the resulting GPX-file on standard output."),
  23. _analyse (_arguments, true, 'a', "analyse", "analyse the file for splitting"),
  24. _metres (_arguments, true, 'd', "distance", "METRES", "split based on a distance", ""),
  25. _timestamp(_arguments, true, 't', "time", "YYYY-MM-DD HH:MM:SS", "split based on a timestamp", ""),
  26. _seconds (_arguments, true, 's', "seconds", "SEC", "split based on a time difference in seconds", ""),
  27. _minutes (_arguments, true, 'm', "minutes", "MIN", "split based on a time difference in minutes", ""),
  28. _hours (_arguments, true, 'h', "hours", "HOURS", "split based on a time difference in hours", ""),
  29. _distance(0),
  30. _duration(0),
  31. _time(time_t(-1)),
  32. _xmlParser(this),
  33. _TrkNr(0),
  34. _TrkSegNr(0),
  35. _inTrkSeg(false)
  36. {
  37. }
  38. // -- Deconstructor ---------------------------------------------------------
  39. virtual ~GPXSplit()
  40. {
  41. }
  42. // -- Parse arguments -------------------------------------------------------
  43. bool processArguments(int argc, char *argv[])
  44. {
  45. std::vector<std::string> filenames;
  46. if (!_arguments.parse(argc,argv, filenames))
  47. {
  48. return false;
  49. }
  50. else if (!checkArguments(filenames))
  51. {
  52. return false;
  53. }
  54. else if (filenames.empty())
  55. {
  56. return _xmlParser.parse(std::cin);
  57. }
  58. else
  59. {
  60. return parseFile(filenames.front());
  61. }
  62. }
  63. // -- Check arguments ---------------------------------------------------------
  64. bool checkArguments(const std::vector<std::string> &filenames)
  65. {
  66. if (filenames.size() > 1)
  67. {
  68. std::cerr << "Too many input files." << std::endl;
  69. return false;
  70. }
  71. if (!_metres.value().empty())
  72. {
  73. try
  74. {
  75. _distance = std::stoi(_metres.value());
  76. if (_distance < 0)
  77. {
  78. std::cerr << "Invalid value for --" << _metres.longOption() << " : " << _metres.value() << std::endl;
  79. return false;
  80. }
  81. }
  82. catch(...)
  83. {
  84. std::cerr << "Invalid value for --" << _metres.longOption() << " : " << _metres.value() << std::endl;
  85. return false;
  86. }
  87. }
  88. if (!_timestamp.value().empty())
  89. {
  90. struct tm fields = {0};
  91. if (strptime(_timestamp.value().c_str(), "%Y-%m-%d %H:%M:%S", &fields) == nullptr)
  92. {
  93. std::cerr << "Invalid value for --" << _timestamp.longOption() << " : " << _timestamp.value() << std::endl;
  94. return false;
  95. }
  96. _time = mktime(&fields);
  97. if (_time == (time_t)-1)
  98. {
  99. std::cerr << "Invalid time for --" << _timestamp.longOption() << " : " << _timestamp.value() << std::endl;
  100. return false;
  101. }
  102. }
  103. _duration = 0;
  104. if (!_seconds.value().empty())
  105. {
  106. try
  107. {
  108. _duration = std::stoi(_seconds.value());
  109. if (_duration < 0)
  110. {
  111. std::cerr << "Invalid value for --" << _seconds.longOption() << " : " << _seconds.value() << std::endl;
  112. return false;
  113. }
  114. }
  115. catch(...)
  116. {
  117. std::cerr << "Invalid value for --" << _seconds.longOption() << " : " << _seconds.value() << std::endl;
  118. return false;
  119. }
  120. }
  121. if (!_minutes.value().empty())
  122. {
  123. try
  124. {
  125. int minutes = std::stoi(_minutes.value());
  126. if (minutes < 0)
  127. {
  128. std::cerr << "Invalid value for --" << _minutes.longOption() << " : " << _minutes.value() << std::endl;
  129. return false;
  130. }
  131. _duration += minutes * 60;
  132. }
  133. catch(...)
  134. {
  135. std::cerr << "Invalid value for --" << _minutes.longOption() << " : " << _minutes.value() << std::endl;
  136. return false;
  137. }
  138. }
  139. if (!_hours.value().empty())
  140. {
  141. try
  142. {
  143. int hours = std::stoi(_hours.value());
  144. if (hours < 0)
  145. {
  146. std::cerr << "Invalid value for --" << _hours.longOption() << " : " << _hours.value() << std::endl;
  147. return false;
  148. }
  149. _duration += hours * 3600;
  150. }
  151. catch(...)
  152. {
  153. std::cerr << "Invalid value for --" << _hours.longOption() << " : " << _hours.value() << std::endl;
  154. return false;
  155. }
  156. }
  157. return true;
  158. }
  159. // -- Parse a file ----------------------------------------------------------
  160. bool parseFile(const std::string &filename)
  161. {
  162. bool ok =false;
  163. std::ifstream file(filename);
  164. if (file.is_open())
  165. {
  166. ok = _xmlParser.parse(file);
  167. file.close();
  168. }
  169. else
  170. {
  171. std::cerr << "Unable to open: " << filename << std::endl;
  172. }
  173. return ok;
  174. }
  175. private:
  176. // -- Types -----------------------------------------------------------------
  177. enum ChunkType { TEXT, POINT };
  178. struct Chunk
  179. {
  180. Chunk()
  181. {
  182. clear(TEXT);
  183. }
  184. void clear(ChunkType type)
  185. {
  186. _type = type;
  187. _text.clear();
  188. _lat = 0.0;
  189. _lon = 0.0;
  190. _distance = -1.0;
  191. _timeStr.clear();
  192. _time = -1.0;
  193. }
  194. ChunkType _type;
  195. std::string _text;
  196. double _lat;
  197. double _lon;
  198. double _distance;
  199. std::string _timeStr;
  200. time_t _time;
  201. };
  202. // -- Methods ---------------------------------------------------------------
  203. static bool getDoubleAttribute(const Attributes &atts, const std::string &key, double &value)
  204. {
  205. auto iter = atts.find(key);
  206. if (iter == atts.end()) return false;
  207. if (iter->second.empty()) return false;
  208. try
  209. {
  210. value = std::stod(iter->second);
  211. return true;
  212. }
  213. catch(...)
  214. {
  215. return false;
  216. }
  217. }
  218. void store(const std::string &text)
  219. {
  220. if (_inTrkSeg)
  221. {
  222. _current._text.append(text);
  223. }
  224. else if (!_analyse.active())
  225. {
  226. std::cout << text;
  227. }
  228. }
  229. void processTimeStr(std::string &timeStr, time_t &time)
  230. {
  231. time = time_t(-1);
  232. XMLRawParser::trim(timeStr);
  233. if (timeStr.size() == 0) return;
  234. struct tm fields = {0};
  235. if (strptime(timeStr.c_str(), "%Y-%m-%dT%TZ", &fields) == nullptr) return;
  236. time = mktime(&fields);
  237. }
  238. void analyseChunks()
  239. {
  240. int trkPtNr = 0;
  241. Chunk previous;
  242. for (auto iter = _chunks.begin(); iter != _chunks.end(); ++iter)
  243. {
  244. if (iter->_type == POINT)
  245. {
  246. enum {NONE, DISTANCE, TIME, DURATION} reason = NONE;
  247. trkPtNr++;
  248. if (_distance > 0 && iter->_distance > _distance)
  249. {
  250. reason = DISTANCE;
  251. }
  252. else if (_time > 0 && previous._time > 0 && iter->_time > 0 && previous._time <= _time && iter->_time > _time)
  253. {
  254. reason = TIME;
  255. }
  256. else if (_duration > 0 && previous._time > 0 && iter->_time > 0 && (iter->_time - previous._time) > _duration)
  257. {
  258. reason = DURATION;
  259. }
  260. if (reason != NONE)
  261. {
  262. if (_analyse.active())
  263. {
  264. std::cout << "Track: " << _TrkNr << " Segment: " << _TrkSegNr << " Point: " << trkPtNr << " is split on ";
  265. switch(reason)
  266. {
  267. case DISTANCE:
  268. std::cout << "distance: " << iter->_distance << "m (" << previous._lat << ',' << previous._lon << ") and (" << iter->_lat << ',' << iter->_lon << ")" << std::endl;
  269. break;
  270. case TIME:
  271. std::cout << "time: " << previous._timeStr << " and " << iter->_timeStr << std::endl;
  272. break;
  273. case DURATION:
  274. std::cout << "time duration: " << previous._timeStr << " and " << iter->_timeStr << std::endl;
  275. break;
  276. }
  277. }
  278. else
  279. {
  280. std::cout << _endTrkSeg;
  281. std::cout << _startTrkSeg;
  282. }
  283. }
  284. previous = *iter;
  285. }
  286. if (!_analyse.active())
  287. {
  288. std::cout << iter->_text;
  289. }
  290. }
  291. }
  292. public:
  293. // -- Callbacks -------------------------------------------------------------
  294. virtual void unhandled(const std::string &path, const std::string &text)
  295. {
  296. store(text);
  297. }
  298. virtual void startElement(const std::string &path, const std::string &text, const std::string &, const Attributes &attributes)
  299. {
  300. if (path == "/gpx/trk")
  301. {
  302. _TrkNr++;
  303. _TrkSegNr = 0;
  304. }
  305. else if (path == "/gpx/trk/trkseg")
  306. {
  307. _startTrkSeg = text;
  308. _chunks.clear();
  309. _current.clear(TEXT);
  310. _previous.clear(TEXT);
  311. _inTrkSeg = true;
  312. _TrkSegNr++;
  313. }
  314. else if (path == "/gpx/trk/trkseg/trkpt")
  315. {
  316. if (!_current._text.empty())
  317. {
  318. _chunks.push_back(_current);
  319. }
  320. _current.clear(TEXT);
  321. if (getDoubleAttribute(attributes, "lat", _current._lat) &&
  322. getDoubleAttribute(attributes, "lon", _current._lon))
  323. {
  324. _current._type = POINT;
  325. if (_previous._type == POINT)
  326. {
  327. _current._distance = gpx::calcDistance(_previous._lat, _previous._lon, _current._lat, _current._lon);
  328. }
  329. }
  330. }
  331. else if (path == "/gpx/trk/trkseg/trkpt/time")
  332. {
  333. // <time>2012-12-03T13:13:38Z</time>
  334. _current._timeStr.clear();
  335. }
  336. store(text);
  337. }
  338. virtual void text(const std::string &path, const std::string &text)
  339. {
  340. if (path == "/gpx/trk/trkseg/trkpt/time")
  341. {
  342. _current._timeStr.append(text);
  343. }
  344. store(text);
  345. }
  346. virtual void endElement(const std::string &path, const std::string &text, const std::string &)
  347. {
  348. store(text);
  349. if (path == "/gpx/trk/trkseg")
  350. {
  351. _endTrkSeg = text;
  352. if (!_current._text.empty())
  353. {
  354. _chunks.push_back(_current);
  355. }
  356. analyseChunks();
  357. _inTrkSeg = false;
  358. }
  359. else if (path == "/gpx/trk/trkseg/trkpt")
  360. {
  361. _chunks.push_back(_current);
  362. if (_current._type == POINT)
  363. {
  364. _previous = _current;
  365. }
  366. _current.clear(TEXT);
  367. }
  368. else if (path == "/gpx/trk/trkseg/trkpt/time")
  369. {
  370. processTimeStr(_current._timeStr, _current._time);
  371. }
  372. }
  373. private:
  374. // -- Members ---------------------------------------------------------------
  375. arg::Arguments _arguments;
  376. arg::Argument _analyse;
  377. arg::Argument _metres;
  378. arg::Argument _timestamp;
  379. arg::Argument _seconds;
  380. arg::Argument _minutes;
  381. arg::Argument _hours;
  382. XMLRawParser _xmlParser;
  383. int _distance;
  384. int _duration;
  385. time_t _time;
  386. int _TrkNr;
  387. int _TrkSegNr;
  388. bool _inTrkSeg;
  389. std::string _startTrkSeg;
  390. std::string _endTrkSeg;
  391. Chunk _current;
  392. Chunk _previous;
  393. std::vector<Chunk> _chunks;
  394. };
  395. }
  396. // -- Main program ------------------------------------------------------------
  397. int main(int argc, char *argv[])
  398. {
  399. gpxtools::GPXSplit gpxSplit;
  400. return gpxSplit.processArguments(argc, argv) ? 0 : 1;
  401. }