gpxsim.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. #include <iostream>
  2. #include <cstring>
  3. #include <fstream>
  4. #include <list>
  5. #include <cmath>
  6. #include <limits>
  7. #include <iomanip>
  8. #include "XMLRawParser.h"
  9. #include "Arguments.h"
  10. #include "Algorithm.h"
  11. // ----------------------------------------------------------------------------
  12. namespace gpxtools
  13. {
  14. class GPXSim : public XMLParserHandler
  15. {
  16. public:
  17. // -- Constructor -----------------------------------------------------------
  18. GPXSim() :
  19. _arguments ("gpxsim [OPTION].. [FILE]\nSimplify a GPX-file.\n", "gpxsim v0.1",
  20. "Simplify a route or track using the distance threshold and/or the Douglas-Peucker algorithm and display the resulting GPX-file on standard output."),
  21. _verbose (_arguments, true, 'v', "verbose", "display the results of the simplification"),
  22. _distance (_arguments, true, 'd', "distance", "METRES", "remove route or track points within distance of the previous point in METRES", ""),
  23. _number (_arguments, true, 'n', "number", "NUMBER", "remove route or track points until the route or track contains NUMBER points", ""),
  24. _crossTrack(_arguments, true, 'x', "crossTrack", "METRES", "remove route or track points with a cross track distance less than METRES", ""),
  25. _xmlParser(this),
  26. _simplifyDistance(0.0),
  27. _simplifyCrossTrack(0.0),
  28. _simplifyToNumber(0),
  29. _inPoints(false)
  30. {
  31. }
  32. // -- Deconstructor ---------------------------------------------------------
  33. virtual ~GPXSim()
  34. {
  35. }
  36. // -- Parse arguments -------------------------------------------------------
  37. bool processArguments(int argc, char *argv[])
  38. {
  39. std::vector<std::string> filenames;
  40. if (!_arguments.parse(argc,argv, filenames))
  41. {
  42. return false;
  43. }
  44. else if (!checkArguments(filenames))
  45. {
  46. return false;
  47. }
  48. else if (filenames.empty())
  49. {
  50. return _xmlParser.parse(std::cin);
  51. }
  52. else
  53. {
  54. return parseFile(filenames.front());
  55. }
  56. }
  57. // -- Check arguments ---------------------------------------------------------
  58. bool checkArguments(const std::vector<std::string> &filenames)
  59. {
  60. if (filenames.size() > 1)
  61. {
  62. std::cerr << "Too many input files." << std::endl;
  63. return false;
  64. }
  65. if (!_distance.value().empty())
  66. {
  67. try
  68. {
  69. _simplifyDistance = std::stoi(_distance.value());
  70. if (_simplifyDistance < 0)
  71. {
  72. std::cerr << "Invalid value for --" << _distance.longOption() << " : " << _distance.value() << std::endl;
  73. return false;
  74. }
  75. }
  76. catch(...)
  77. {
  78. std::cerr << "Invalid value for --" << _distance.longOption() << " : " << _distance.value() << std::endl;
  79. return false;
  80. }
  81. }
  82. if (!_number.value().empty())
  83. {
  84. try
  85. {
  86. _simplifyToNumber = std::stoi(_number.value());
  87. if (_simplifyToNumber < 0)
  88. {
  89. std::cerr << "Invalid value for --" << _number.longOption() << " : " << _number.value() << std::endl;
  90. return false;
  91. }
  92. }
  93. catch(...)
  94. {
  95. std::cerr << "Invalid value for --" << _number.longOption() << " : " << _number.value() << std::endl;
  96. return false;
  97. }
  98. }
  99. if (!_crossTrack.value().empty())
  100. {
  101. try
  102. {
  103. _simplifyCrossTrack = std::stoi(_crossTrack.value());
  104. if (_simplifyCrossTrack < 0)
  105. {
  106. std::cerr << "Invalid value for --" << _crossTrack.longOption() << " : " << _crossTrack.value() << std::endl;
  107. return false;
  108. }
  109. }
  110. catch(...)
  111. {
  112. std::cerr << "Invalid value for --" << _crossTrack.longOption() << " : " << _crossTrack.value() << std::endl;
  113. return false;
  114. }
  115. }
  116. return true;
  117. }
  118. // -- Parse a file ----------------------------------------------------------
  119. bool parseFile(const std::string &filename)
  120. {
  121. bool ok =false;
  122. std::ifstream file(filename);
  123. if (file.is_open())
  124. {
  125. ok = _xmlParser.parse(file);
  126. file.close();
  127. }
  128. else
  129. {
  130. std::cerr << "Unable to open: " << filename << std::endl;
  131. }
  132. return ok;
  133. }
  134. private:
  135. // Structs
  136. enum ChunkType { TEXT, POINT };
  137. struct Chunk
  138. {
  139. void clear()
  140. {
  141. _type = TEXT;
  142. _text.clear();
  143. _lat = 0.0;
  144. _lon = 0.0;
  145. _crossTrack = std::numeric_limits<double>::max();
  146. }
  147. void point(double lat, double lon)
  148. {
  149. _type = POINT;
  150. _lat = lat;
  151. _lon = lon;
  152. _crossTrack = std::numeric_limits<double>::max();
  153. }
  154. ChunkType _type;
  155. std::string _text;
  156. double _lat;
  157. double _lon;
  158. double _crossTrack;
  159. };
  160. void store(const std::string &text)
  161. {
  162. if (_inPoints)
  163. {
  164. _current._text.append(text);
  165. }
  166. else if (!_verbose.active())
  167. {
  168. std::cout << text;
  169. }
  170. }
  171. void outputChunks()
  172. {
  173. ChunkType last = ChunkType::POINT;
  174. while (!_chunks.empty())
  175. {
  176. if (last != ChunkType::TEXT || _chunks.front()._type != ChunkType::TEXT)
  177. {
  178. if (!_verbose.active())
  179. {
  180. std::cout << _chunks.front()._text;
  181. }
  182. }
  183. last = _chunks.front()._type;
  184. _chunks.pop_front();
  185. }
  186. }
  187. void verboseChunks(const std::string &title)
  188. {
  189. int points = 0;
  190. double distance = 0.0;
  191. auto prev = _chunks.end();
  192. for (auto iter = _chunks.begin(); iter != _chunks.end(); ++iter)
  193. {
  194. if (iter->_type == ChunkType::POINT)
  195. {
  196. points++;
  197. if (prev != _chunks.end())
  198. {
  199. distance += gpx::calcDistance(prev->_lat, prev->_lon, iter->_lat, iter->_lon);
  200. }
  201. prev = iter;
  202. }
  203. }
  204. std::cout << title << " Points: " << std::setw(4) << points << " Distance: " << std::setw(10) << std::setprecision(2) << std::fixed << distance << " m" << std::endl;
  205. }
  206. void simplifyDistance()
  207. {
  208. auto iter = _chunks.begin();
  209. auto prev = _chunks.end();
  210. while (iter != _chunks.end())
  211. {
  212. if (iter->_type == ChunkType::POINT)
  213. {
  214. if (prev != _chunks.end() && gpx::calcDistance(prev->_lat, prev->_lon, iter->_lat, iter->_lon) < _simplifyDistance)
  215. {
  216. iter = _chunks.erase(iter);
  217. }
  218. else
  219. {
  220. prev = iter++;
  221. }
  222. }
  223. else
  224. {
  225. ++iter;
  226. }
  227. }
  228. }
  229. void simplifyCrossTrack()
  230. {
  231. auto p1 = _chunks.end();
  232. auto p2 = _chunks.end();
  233. auto p3 = _chunks.begin();
  234. while (p3 != _chunks.end())
  235. {
  236. if (p3->_type == ChunkType::POINT)
  237. {
  238. if (p1 != _chunks.end() && p2 != _chunks.end() &&
  239. fabs(gpx::calcCrosstrack(p1->_lat, p1->_lon, p3->_lat, p3->_lon, p2->_lat, p2->_lon)) < _simplifyCrossTrack)
  240. {
  241. _chunks.erase(p2);
  242. p2 = p3;
  243. }
  244. else
  245. {
  246. p1 = p2;
  247. p2 = p3;
  248. }
  249. }
  250. ++p3;
  251. }
  252. }
  253. int setCrossTracks()
  254. {
  255. int points = 0;
  256. auto p1 = _chunks.end();
  257. auto p2 = _chunks.end();
  258. auto p3 = _chunks.begin();
  259. while (p3 != _chunks.end())
  260. {
  261. if (p3->_type == ChunkType::POINT)
  262. {
  263. points++;
  264. p3->_crossTrack = std::numeric_limits<double>::max();
  265. if (p1 != _chunks.end() && p2 != _chunks.end())
  266. {
  267. p2->_crossTrack = fabs(gpx::calcCrosstrack(p1->_lat, p1->_lon, p3->_lat, p3->_lon, p2->_lat, p2->_lon));
  268. }
  269. p1 = p2;
  270. p2 = p3;
  271. }
  272. ++p3;
  273. }
  274. return points;
  275. }
  276. std::list<Chunk>::iterator forwards(std::list<Chunk>::iterator p)
  277. {
  278. do
  279. {
  280. ++p;
  281. }
  282. while (p != _chunks.end() && p->_type != ChunkType::POINT);
  283. return p;
  284. }
  285. std::list<Chunk>::iterator backwards(std::list<Chunk>::iterator p)
  286. {
  287. while (p != _chunks.begin())
  288. {
  289. if ((--p)->_type == ChunkType::POINT)
  290. {
  291. return p;
  292. }
  293. }
  294. return _chunks.end();
  295. }
  296. void removeLowestCrossTrack()
  297. {
  298. auto lowest = _chunks.end();
  299. for (auto p = _chunks.begin(); p != _chunks.end(); ++p)
  300. {
  301. if (p->_type == ChunkType::POINT && (lowest == _chunks.end() || lowest->_crossTrack > p->_crossTrack))
  302. {
  303. lowest = p;
  304. }
  305. }
  306. if (lowest != _chunks.end())
  307. {
  308. auto p2 = backwards(lowest);
  309. auto p1 = (p2 != _chunks.end() ? backwards(p2) : _chunks.end());
  310. // p3 = lowest
  311. auto p4 = forwards(lowest);
  312. auto p5 = (p4 != _chunks.end() ? forwards(p4) : _chunks.end());
  313. _chunks.erase(lowest);
  314. // Update the crosstracks
  315. if (p2 != _chunks.end())
  316. {
  317. if (p1 != _chunks.end() && p4 != _chunks.end())
  318. {
  319. p2->_crossTrack = fabs(gpx::calcCrosstrack(p1->_lat, p1->_lon, p4->_lat, p4->_lon, p2->_lat, p2->_lon));
  320. }
  321. else
  322. {
  323. p2->_crossTrack = std::numeric_limits<double>::max();
  324. }
  325. }
  326. if (p4 != _chunks.end())
  327. {
  328. if (p2 != _chunks.end() && p5 != _chunks.end())
  329. {
  330. p4->_crossTrack = fabs(gpx::calcCrosstrack(p2->_lat, p2->_lon, p5->_lat, p5->_lon, p4->_lat, p4->_lon));
  331. }
  332. else
  333. {
  334. p4->_crossTrack = std::numeric_limits<double>::max();
  335. }
  336. }
  337. }
  338. }
  339. void simplifyToNumber()
  340. {
  341. int points = setCrossTracks();
  342. while (_simplifyToNumber < points)
  343. {
  344. removeLowestCrossTrack();
  345. points--;
  346. }
  347. }
  348. static bool getDoubleAttribute(const Attributes &atts, const std::string &key, double &value)
  349. {
  350. auto iter = atts.find(key);
  351. if (iter == atts.end()) return false;
  352. if (iter->second.empty()) return false;
  353. try
  354. {
  355. value = std::stod(iter->second);
  356. return true;
  357. }
  358. catch(...)
  359. {
  360. return false;
  361. }
  362. }
  363. public:
  364. // -- Callbacks -------------------------------------------------------------
  365. virtual void startElement(const std::string &path, const std::string &text, const std::string &, const Attributes &attributes)
  366. {
  367. if (path == "/gpx/trk/trkseg" || path == "/gpx/rte")
  368. {
  369. _current.clear();
  370. _inPoints = true;
  371. }
  372. else if (path == "/gpx/trk/trkseg/trkpt" || path == "/gpx/rte/rtept")
  373. {
  374. if (!_current._text.empty()) _chunks.push_back(_current);
  375. _current.clear();
  376. double lat,lon;
  377. if (getDoubleAttribute(attributes, "lat", lat) && getDoubleAttribute(attributes, "lon", lon))
  378. {
  379. _current.point(lat, lon);
  380. }
  381. }
  382. store(text);
  383. }
  384. virtual void text(const std::string &, const std::string &text)
  385. {
  386. store(text);
  387. }
  388. virtual void unhandled(const std::string &, const std::string &text)
  389. {
  390. store(text);
  391. }
  392. virtual void endElement(const std::string &path, const std::string &text, const std::string &)
  393. {
  394. store(text);
  395. if (path == "/gpx/trk/trkseg" || path == "/gpx/rte")
  396. {
  397. if (!_current._text.empty())
  398. {
  399. _chunks.push_back(_current);
  400. }
  401. if (_verbose.active())
  402. {
  403. verboseChunks("Original segment:");
  404. }
  405. if (_simplifyDistance > 0.0) simplifyDistance();
  406. if (_simplifyCrossTrack > 0.0) simplifyCrossTrack();
  407. if (_simplifyToNumber > 0) simplifyToNumber();
  408. if (_verbose.active())
  409. {
  410. verboseChunks("Optimized segment:");
  411. }
  412. outputChunks();
  413. _inPoints = false;
  414. }
  415. else if (path == "/gpx/trk/trkseg/trkpt" || path == "/gpx/rte/rtept")
  416. {
  417. _chunks.push_back(_current);
  418. _current.clear();
  419. }
  420. }
  421. private:
  422. // Members
  423. arg::Arguments _arguments;
  424. arg::Argument _verbose;
  425. arg::Argument _distance;
  426. arg::Argument _number;
  427. arg::Argument _crossTrack;
  428. XMLRawParser _xmlParser;
  429. double _simplifyDistance;
  430. double _simplifyCrossTrack;
  431. int _simplifyToNumber;
  432. bool _inPoints;
  433. Chunk _current;
  434. std::list<Chunk> _chunks;
  435. };
  436. }
  437. // -- Main program ------------------------------------------------------------
  438. int main(int argc, char *argv[])
  439. {
  440. gpxtools::GPXSim gpxSim;
  441. return gpxSim.processArguments(argc, argv) ? 0 : 1;
  442. }