gpxsim.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. #include <cmath>
  2. #include <cstring>
  3. #include <fstream>
  4. #include <iomanip>
  5. #include <iostream>
  6. #include <limits>
  7. #include <list>
  8. #include "Algorithm.h"
  9. #include "Arguments.h"
  10. #include "XMLRawParser.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. bool isEmpty(const std::string &text)
  172. {
  173. for (auto ch = text.begin(); ch != text.end(); ++ch)
  174. {
  175. if (!::isspace(*ch)) return false;
  176. }
  177. return true;
  178. }
  179. void outputChunks()
  180. {
  181. ChunkType last = ChunkType::POINT;
  182. while (!_chunks.empty())
  183. {
  184. if ((_chunks.front()._type == ChunkType::POINT) || (last == ChunkType::POINT) || (!isEmpty(_chunks.front()._text)))
  185. {
  186. if (!_verbose.active())
  187. {
  188. std::cout << _chunks.front()._text;
  189. }
  190. }
  191. last = _chunks.front()._type;
  192. _chunks.pop_front();
  193. }
  194. }
  195. void verboseChunks(const std::string &title)
  196. {
  197. int points = 0;
  198. double distance = 0.0;
  199. auto prev = _chunks.end();
  200. for (auto iter = _chunks.begin(); iter != _chunks.end(); ++iter)
  201. {
  202. if (iter->_type == ChunkType::POINT)
  203. {
  204. points++;
  205. if (prev != _chunks.end())
  206. {
  207. distance += gpx::calcDistance(prev->_lat, prev->_lon, iter->_lat, iter->_lon);
  208. }
  209. prev = iter;
  210. }
  211. }
  212. std::cout << title << " Points: " << std::setw(4) << points << " Distance: " << std::setw(10) << std::setprecision(2) << std::fixed << distance << " m" << std::endl;
  213. }
  214. void simplifyDistance()
  215. {
  216. auto iter = _chunks.begin();
  217. auto prev = _chunks.end();
  218. while (iter != _chunks.end())
  219. {
  220. if (iter->_type == ChunkType::POINT)
  221. {
  222. if (prev != _chunks.end() && gpx::calcDistance(prev->_lat, prev->_lon, iter->_lat, iter->_lon) < _simplifyDistance)
  223. {
  224. iter = _chunks.erase(iter);
  225. }
  226. else
  227. {
  228. prev = iter++;
  229. }
  230. }
  231. else
  232. {
  233. ++iter;
  234. }
  235. }
  236. }
  237. int setCrossTracks()
  238. {
  239. int points = 0;
  240. auto p1 = _chunks.end();
  241. auto p2 = _chunks.end();
  242. for (auto p3 = _chunks.begin(); p3 != _chunks.end(); ++p3)
  243. {
  244. if (p3->_type != ChunkType::POINT) continue;
  245. points++;
  246. p3->_crossTrack = std::numeric_limits<double>::max();
  247. if (p1 != _chunks.end() && p2 != _chunks.end())
  248. {
  249. p2->_crossTrack = fabs(gpx::calcCrosstrack(p1->_lat, p1->_lon, p3->_lat, p3->_lon, p2->_lat, p2->_lon));
  250. }
  251. p1 = p2;
  252. p2 = p3;
  253. }
  254. return points;
  255. }
  256. std::list<Chunk>::iterator forwards(std::list<Chunk>::iterator p)
  257. {
  258. do
  259. {
  260. ++p;
  261. }
  262. while ((p != _chunks.end()) && (p->_type != ChunkType::POINT));
  263. return p;
  264. }
  265. std::list<Chunk>::iterator backwards(std::list<Chunk>::iterator p)
  266. {
  267. while (p != _chunks.begin())
  268. {
  269. if ((--p)->_type == ChunkType::POINT)
  270. {
  271. return p;
  272. }
  273. }
  274. return _chunks.end();
  275. }
  276. void UpdateCrossTrack(std::list<Chunk>::iterator p1, std::list<Chunk>::iterator p2, std::list<Chunk>::iterator p3)
  277. {
  278. if (p2 != _chunks.end())
  279. {
  280. if (p1 != _chunks.end() && p3 != _chunks.end())
  281. {
  282. p2->_crossTrack = fabs(gpx::calcCrosstrack(p1->_lat, p1->_lon, p3->_lat, p3->_lon, p2->_lat, p2->_lon));
  283. }
  284. else
  285. {
  286. p2->_crossTrack = std::numeric_limits<double>::max();
  287. }
  288. }
  289. }
  290. void simplifyByCrossTrack(int points)
  291. {
  292. while (true)
  293. {
  294. auto lowest = _chunks.end();
  295. for (auto p = _chunks.begin(); p != _chunks.end(); ++p)
  296. {
  297. if ((p->_type == ChunkType::POINT) && ((lowest == _chunks.end()) || (lowest->_crossTrack > p->_crossTrack)))
  298. {
  299. lowest = p;
  300. }
  301. }
  302. if (lowest == _chunks.end()) break;
  303. if ((_simplifyCrossTrack > 0.0) && (lowest->_crossTrack > _simplifyCrossTrack)) break;
  304. auto p2 = backwards(lowest);
  305. auto p1 = (p2 != _chunks.end() ? backwards(p2) : _chunks.end());
  306. // p3 = lowest
  307. auto p4 = forwards(lowest);
  308. auto p5 = (p4 != _chunks.end() ? forwards(p4) : _chunks.end());
  309. _chunks.erase(lowest);
  310. points--;
  311. if ((_simplifyToNumber > 0) && (points <= _simplifyToNumber)) break;
  312. // Update the crosstracks
  313. UpdateCrossTrack(p1, p2, p4);
  314. UpdateCrossTrack(p2, p4, p5);
  315. }
  316. }
  317. void simplifyCrossTrack()
  318. {
  319. int points = setCrossTracks();
  320. if ((_simplifyToNumber > 0) && (points <= _simplifyToNumber)) return;
  321. simplifyByCrossTrack(points);
  322. }
  323. static bool getDoubleAttribute(const Attributes &atts, const std::string &key, double &value)
  324. {
  325. auto iter = atts.find(key);
  326. if (iter == atts.end()) return false;
  327. if (iter->second.empty()) return false;
  328. try
  329. {
  330. value = std::stod(iter->second);
  331. return true;
  332. }
  333. catch(...)
  334. {
  335. return false;
  336. }
  337. }
  338. public:
  339. // -- Callbacks -------------------------------------------------------------
  340. virtual void startElement(const std::string &path, const std::string &text, const std::string &, const Attributes &attributes)
  341. {
  342. if (path == "/gpx/trk/trkseg" || path == "/gpx/rte")
  343. {
  344. _current.clear();
  345. _inPoints = true;
  346. }
  347. else if (path == "/gpx/trk/trkseg/trkpt" || path == "/gpx/rte/rtept")
  348. {
  349. if (!_current._text.empty()) _chunks.push_back(_current);
  350. _current.clear();
  351. double lat,lon;
  352. if (getDoubleAttribute(attributes, "lat", lat) && getDoubleAttribute(attributes, "lon", lon))
  353. {
  354. _current.point(lat, lon);
  355. }
  356. }
  357. store(text);
  358. }
  359. virtual void text(const std::string &, const std::string &text)
  360. {
  361. store(text);
  362. }
  363. virtual void unhandled(const std::string &, const std::string &text)
  364. {
  365. store(text);
  366. }
  367. virtual void endElement(const std::string &path, const std::string &text, const std::string &)
  368. {
  369. store(text);
  370. if (path == "/gpx/trk/trkseg" || path == "/gpx/rte")
  371. {
  372. if (!_current._text.empty())
  373. {
  374. _chunks.push_back(_current);
  375. }
  376. if (_verbose.active())
  377. {
  378. verboseChunks("Original segment:");
  379. }
  380. if (_simplifyDistance > 0.0) simplifyDistance();
  381. if ((_simplifyCrossTrack > 0.0) || (_simplifyToNumber > 0)) simplifyCrossTrack();
  382. if (_verbose.active())
  383. {
  384. verboseChunks("Optimized segment:");
  385. }
  386. outputChunks();
  387. _inPoints = false;
  388. }
  389. else if (path == "/gpx/trk/trkseg/trkpt" || path == "/gpx/rte/rtept")
  390. {
  391. _chunks.push_back(_current);
  392. _current.clear();
  393. }
  394. }
  395. private:
  396. // Members
  397. arg::Arguments _arguments;
  398. arg::Argument _verbose;
  399. arg::Argument _distance;
  400. arg::Argument _number;
  401. arg::Argument _crossTrack;
  402. XMLRawParser _xmlParser;
  403. double _simplifyDistance;
  404. double _simplifyCrossTrack;
  405. int _simplifyToNumber;
  406. bool _inPoints;
  407. Chunk _current;
  408. std::list<Chunk> _chunks;
  409. };
  410. }
  411. // -- Main program ------------------------------------------------------------
  412. int main(int argc, char *argv[])
  413. {
  414. gpxtools::GPXSim gpxSim;
  415. return gpxSim.processArguments(argc, argv) ? 0 : 1;
  416. }