gpxplot.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. //==============================================================================
  2. //
  3. // GPXPlot - the gpx plotter class
  4. //
  5. // Copyright (C) 2018 Dick van Oudheusden
  6. //
  7. // This library is free software; you can redistribute it and/or
  8. // modify it under the terms of the GNU Lesser General Public
  9. // License as published by the Free Software Foundation; either
  10. // version 3 of the License, or (at your option) any later version.
  11. //
  12. // This library 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 GNU
  15. // Lesser General Public License for more details.
  16. //
  17. // You should have received a copy of the GNU Lesser General Public
  18. // License along with this library; if not, write to the Free
  19. // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20. //
  21. //==============================================================================
  22. #include <string>
  23. #include <fstream>
  24. #include <sstream>
  25. #include <vector>
  26. #include <ctime>
  27. #include <iomanip>
  28. #include <limits>
  29. #include <algorithm>
  30. #include "Arguments.h"
  31. #include "XMLParser.h"
  32. #include "Algorithm.h"
  33. namespace gpxtools
  34. {
  35. // -- PlotAxis interface ------------------------------------------
  36. class PlotAxis
  37. {
  38. public:
  39. PlotAxis() { }
  40. virtual ~PlotAxis() {}
  41. virtual bool values(const std::string &latitude, const std::string &longitude, const std::string &elevation, const std::string &timestamp) = 0;
  42. virtual void rollback() = 0;
  43. virtual void heading(std::ostream &str, const std::string &xyz) const = 0;
  44. virtual bool start() = 0;
  45. virtual bool next(std::ostream &str) = 0;
  46. };
  47. // -- DistanceAxis ------------------------------------------------
  48. class DistanceAxis : public PlotAxis
  49. {
  50. public:
  51. DistanceAxis() :
  52. PlotAxis(),
  53. _prevLat(0.0),
  54. _prevLon(0.0) { }
  55. ~DistanceAxis() { }
  56. virtual bool values(const std::string &latitude, const std::string &longitude, const std::string &, const std::string &)
  57. {
  58. if (latitude.empty()) return false;
  59. if (longitude.empty()) return false;
  60. double lat;
  61. double lon;
  62. try
  63. {
  64. lat = std::stod(latitude);
  65. lon = std::stod(longitude);
  66. }
  67. catch(...)
  68. {
  69. return false;
  70. }
  71. if (_distances.empty())
  72. {
  73. _distances.push_back(0.0);
  74. }
  75. else
  76. {
  77. _distances.push_back(_distances.back() + gpx::calcDistance(_prevLat, _prevLon, lat, lon)); // Cumulative distance
  78. }
  79. _prevLat = lat;
  80. _prevLon = lon;
  81. return true;
  82. }
  83. virtual void rollback()
  84. {
  85. _distances.pop_back();
  86. }
  87. virtual void heading(std::ostream &str, const std::string &xyz) const
  88. {
  89. str << "set " << xyz << "label \"Distance (m)\"" << std::endl;
  90. str << "set " << xyz << "range ["
  91. << std::fixed << std::setprecision(1) << _distances.front() << ':'
  92. << std::fixed << std::setprecision(1) << _distances.back() << ']' << std::endl;
  93. }
  94. virtual bool start()
  95. {
  96. _current = _distances.begin();
  97. return (_current != _distances.end());
  98. }
  99. virtual bool next(std::ostream &str)
  100. {
  101. if (_current == _distances.end()) return false;
  102. str << std::fixed << std::setprecision(1) << *_current;
  103. _current++;
  104. return true;
  105. }
  106. private:
  107. double _prevLat;
  108. double _prevLon;
  109. std::vector<double> _distances;
  110. std::vector<double>::iterator _current;
  111. };
  112. // -- ElevationAxis -----------------------------------------------
  113. class ElevationAxis : public PlotAxis
  114. {
  115. public:
  116. ElevationAxis() :
  117. PlotAxis() { }
  118. ~ElevationAxis() { }
  119. virtual bool values(const std::string &, const std::string &, const std::string &elevation, const std::string &)
  120. {
  121. if (elevation.empty()) return false;
  122. double ele;
  123. try
  124. {
  125. ele = std::stod(elevation);
  126. }
  127. catch(...)
  128. {
  129. return false;
  130. }
  131. _elevations.push_back(ele);
  132. return true;
  133. }
  134. virtual void rollback()
  135. {
  136. _elevations.pop_back();
  137. }
  138. virtual void heading(std::ostream &str, const std::string &xyz) const
  139. {
  140. str << "set " << xyz << "label \"Elevation (m)\"" << std::endl;
  141. double minEle = std::numeric_limits<double>::max();
  142. double maxEle = -std::numeric_limits<double>::max();
  143. for (auto elevation = _elevations.begin(); elevation != _elevations.end(); ++elevation)
  144. {
  145. minEle = std::min(minEle, *elevation);
  146. maxEle = std::max(maxEle, *elevation);
  147. }
  148. str << "set " << xyz << "range ["
  149. << std::fixed << std::setprecision(1) << minEle << ':'
  150. << std::fixed << std::setprecision(1) << maxEle << ']' << std::endl;
  151. }
  152. virtual bool start()
  153. {
  154. _current = _elevations.begin();
  155. return (_current != _elevations.end());
  156. }
  157. virtual bool next(std::ostream &str)
  158. {
  159. if (_current == _elevations.end()) return false;
  160. str << std::fixed << std::setprecision(1) << *_current;
  161. _current++;
  162. return true;
  163. }
  164. private:
  165. std::vector<double> _elevations;
  166. std::vector<double>::iterator _current;
  167. };
  168. // -- SpeedAxis ------------------------------------------------
  169. class SpeedAxis : public PlotAxis
  170. {
  171. public:
  172. SpeedAxis() :
  173. PlotAxis(),
  174. _prevLat(0.0),
  175. _prevLon(0.0),
  176. _prevTime(-1) { }
  177. ~SpeedAxis() { }
  178. virtual bool values(const std::string &latitude, const std::string &longitude, const std::string &, const std::string &timestamp)
  179. {
  180. if (latitude.empty()) return false;
  181. if (longitude.empty()) return false;
  182. if (timestamp.empty()) return false;
  183. time_t time;
  184. double lat;
  185. double lon;
  186. // Time format: 2009-10-17T18:37:31Z
  187. struct tm fields = {0};
  188. if (strptime(timestamp.c_str(), "%Y-%m-%dT%TZ", &fields) == nullptr) return false;
  189. time = mktime(&fields);
  190. if (time == time_t(-1)) return false;
  191. try
  192. {
  193. lat = std::stod(latitude);
  194. lon = std::stod(longitude);
  195. }
  196. catch(...)
  197. {
  198. return false;
  199. }
  200. if (_speeds.empty())
  201. {
  202. _speeds.push_back(0.0);
  203. }
  204. else
  205. {
  206. double distance = gpx::calcDistance(_prevLat, _prevLon, lat, lon);
  207. if ((_prevTime != time_t(-1)) && (time != _prevTime))
  208. {
  209. _speeds.push_back((distance / difftime(time, _prevTime)) * 3.6);
  210. }
  211. else
  212. {
  213. _speeds.push_back(0.0);
  214. }
  215. }
  216. _prevLat = lat;
  217. _prevLon = lon;
  218. _prevTime = time;
  219. return true;
  220. }
  221. virtual void rollback()
  222. {
  223. _speeds.pop_back();
  224. }
  225. virtual void heading(std::ostream &str, const std::string &xyz) const
  226. {
  227. str << "set " << xyz << "label \"Speed (km/h)\"" << std::endl;
  228. double minSpeed = std::numeric_limits<double>::max();
  229. double maxSpeed = -std::numeric_limits<double>::max();
  230. for (auto speed = _speeds.begin(); speed != _speeds.end(); ++speed)
  231. {
  232. minSpeed = std::min(minSpeed, *speed);
  233. maxSpeed = std::max(maxSpeed, *speed);
  234. }
  235. str << "set " << xyz << "range ["
  236. << std::fixed << std::setprecision(1) << minSpeed << ':'
  237. << std::fixed << std::setprecision(1) << maxSpeed << ']' << std::endl;
  238. }
  239. virtual bool start()
  240. {
  241. _current = _speeds.begin();
  242. return (_current != _speeds.end());
  243. }
  244. virtual bool next(std::ostream &str)
  245. {
  246. if (_current == _speeds.end()) return false;
  247. str << std::fixed << std::setprecision(1) << *_current;
  248. _current++;
  249. return true;
  250. }
  251. private:
  252. double _prevLat;
  253. double _prevLon;
  254. time_t _prevTime;
  255. std::vector<double> _speeds;
  256. std::vector<double>::iterator _current;
  257. };
  258. // -- TimeAxis ------------------------------------------------
  259. class TimeAxis : public PlotAxis
  260. {
  261. public:
  262. TimeAxis() :
  263. PlotAxis() { }
  264. ~TimeAxis() { }
  265. virtual bool values(const std::string &, const std::string &, const std::string &, const std::string &timestamp)
  266. {
  267. time_t time;
  268. // Time format: 2009-10-17T18:37:31Z
  269. if (timestamp.empty()) return false;
  270. struct tm fields = {0};
  271. if (strptime(timestamp.c_str(), "%Y-%m-%dT%TZ", &fields) == nullptr) return false;
  272. time = timegm(&fields); // trick: gnuplot uses UTC
  273. if (time == time_t(-1)) return false;
  274. _times.push_back(time);
  275. return true;
  276. }
  277. virtual void rollback()
  278. {
  279. _times.pop_back();
  280. }
  281. virtual void heading(std::ostream &str, const std::string &xyz) const
  282. {
  283. str << "set " << xyz << "label \"Time\"" << std::endl;
  284. str << "set timefmt \"" << "%s" << "\"" << std::endl;
  285. str << "set " << xyz << "data time" << std::endl;
  286. str << "set format " << xyz << " \"%H:%M\"" << std::endl;
  287. str << "set " << xyz << "range [\"" << _times.front() << "\":\"" << _times.back() << "\"]" << std::endl;
  288. }
  289. virtual bool start()
  290. {
  291. _current = _times.begin();
  292. return (_current != _times.end());
  293. }
  294. virtual bool next(std::ostream &str)
  295. {
  296. if (_current == _times.end()) return false;
  297. str << *_current;
  298. _current++;
  299. return true;
  300. }
  301. private:
  302. std::vector<time_t> _times;
  303. std::vector<time_t>::iterator _current;
  304. };
  305. // -- GPXPlot -----------------------------------------------------
  306. class GPXPlot : public XMLParserHandler
  307. {
  308. public:
  309. GPXPlot() :
  310. _arguments("gpxplot [OPTION].. [FILE]\nGenerate a GnuPlot command file from a GPX-file on standard output.\n", "gpxplot v0.1",
  311. "Generate a GnuPlot command file from the distance, elevation, speed and time in a GPX-file on standard output."),
  312. _title (_arguments, true, 't', "title", "TITLE", "set the title of the plot", ""),
  313. _xTitle (_arguments, true, 'x', "x", "SOURCE", "set the x-axis source (distance, time) (def:time)", "time"),
  314. _yTitle (_arguments, true, 'y', "y", "SOURCE", "set the y-axis source (elevation, distance, speed) (def:speed)", "speed"),
  315. _style (_arguments, true, 'p', "plot", "STYLE", "set the plot style (lines,..) (def:lines)", "lines"),
  316. _color (_arguments, true, 'c', "color", "COLOR", "set the line color", ""),
  317. _term (_arguments, true, 'm', "term", "x11|qt|..", "set the output device and options", ""),
  318. _width (_arguments, true, 'w', "width", "WIDTH", "set the width of the plot", "1024"),
  319. _height (_arguments, true, 'h', "height", "HEIGHT", "set the height of the plot", "768"),
  320. _output (_arguments, true, 'o', "output", "FILE", "set the output file of the plot", ""),
  321. _xmlParser(this),
  322. _xAxis(nullptr),
  323. _yAxis(nullptr)
  324. {
  325. }
  326. ~GPXPlot()
  327. {
  328. }
  329. bool parseArguments(int argc, char *argv[])
  330. {
  331. std::vector<std::string> filenames;
  332. if (!_arguments.parse(argc,argv, filenames))
  333. {
  334. return false;
  335. }
  336. else if (!checkArguments(filenames))
  337. {
  338. return false;
  339. }
  340. else if (filenames.empty())
  341. {
  342. return _xmlParser.parse(std::cin);
  343. }
  344. else
  345. {
  346. return parseFile(filenames.front());
  347. }
  348. }
  349. bool checkArguments(const std::vector<std::string> &filenames)
  350. {
  351. if (filenames.size() > 1)
  352. {
  353. std::cerr << "Too many input files." << std::endl;
  354. return false;
  355. }
  356. if (_xTitle.value() == "distance")
  357. {
  358. _xAxis = new DistanceAxis();
  359. }
  360. else if (_xTitle.value() == "time")
  361. {
  362. _xAxis = new TimeAxis();
  363. }
  364. else
  365. {
  366. std::cerr << "Invalid value for --" << _xTitle.longOption() << ": " << _xTitle.value() << std::endl;
  367. return false;
  368. }
  369. if (_yTitle.value() == "elevation")
  370. {
  371. _yAxis = new ElevationAxis();
  372. }
  373. else if (_yTitle.value() == "distance")
  374. {
  375. _yAxis = new DistanceAxis();
  376. }
  377. else if (_yTitle.value() == "speed")
  378. {
  379. _yAxis = new SpeedAxis();
  380. }
  381. else
  382. {
  383. std::cerr << "Invalid value for --" << _yTitle.longOption() << ": " << _yTitle.value() << std::endl;
  384. return false;
  385. }
  386. if (_style.value() != "lines")
  387. {
  388. std::cerr << "Invalid value for --" << _style.longOption() << ": " << _style.value() << std::endl;
  389. return false;
  390. }
  391. return true;
  392. }
  393. bool parseFile(const std::string &filename)
  394. {
  395. bool ok =false;
  396. std::ifstream file(filename);
  397. if (file.is_open())
  398. {
  399. ok = _xmlParser.parse(file);
  400. file.close();
  401. }
  402. else
  403. {
  404. std::cerr << "Unable to open: " << filename << std::endl;
  405. }
  406. return ok;
  407. }
  408. void generate(std::ostream &output)
  409. {
  410. if ((!_xAxis->start()) || (!_yAxis->start()))
  411. {
  412. std::cerr << "No values present for the plot." << std::endl;
  413. return;
  414. }
  415. // Export the data
  416. output << "$DATA << EOD" << std::endl;
  417. while(true)
  418. {
  419. if (!_xAxis->next(output)) break;
  420. output << ' ';
  421. if (!_yAxis->next(output)) break;
  422. output << std::endl;
  423. }
  424. output << "EOD" <<std::endl;
  425. if (!_title.value().empty())
  426. output << "set title \"" << _title.value() << "\"" << std::endl;
  427. output << "unset key" << std::endl;
  428. _xAxis->heading(output, "x");
  429. _yAxis->heading(output, "y");
  430. if (!_output.value().empty())
  431. {
  432. output << "set output \"" << _output.value() << "\"" << std::endl;
  433. if (!_term.value().empty())
  434. {
  435. output << "set term " << _term.value() << std::endl;
  436. }
  437. else
  438. {
  439. std::size_t pos = _output.value().find_last_of('.');
  440. if (pos != std::string::npos)
  441. {
  442. std::string extension = _output.value().substr(pos+1);
  443. std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
  444. if (extension == "jpg")
  445. {
  446. extension = "jpeg";
  447. }
  448. output << "set term " << extension;
  449. if ((!_width.value().empty()) && (!_height.value().empty()))
  450. {
  451. output << " size " << _width.value() << ',' << _height.value();
  452. }
  453. output << std::endl;
  454. }
  455. }
  456. }
  457. else if (!_term.value().empty())
  458. {
  459. output << "set term " << _term.value() << std::endl;
  460. }
  461. output << "plot $DATA using 1:2 with " << _style.value();
  462. if (!_color.value().empty())
  463. {
  464. output << " lt rgb \"" << _color.value() << "\"";
  465. }
  466. output << std::endl;
  467. if (_output.value().empty())
  468. {
  469. output << "pause -1" << std::endl;
  470. }
  471. }
  472. virtual void startElement(const std::string &path, const std::string &, const Attributes &attributes)
  473. {
  474. if (path == "/gpx/trk/trkseg/trkpt")
  475. {
  476. _lat.clear();
  477. _lon.clear();
  478. _elevation.clear();
  479. _time.clear();
  480. _lat = fetch(attributes, "lat");
  481. _lon = fetch(attributes, "lon");
  482. }
  483. }
  484. virtual void text(const std::string &path, const std::string &text)
  485. {
  486. if (path == "/gpx/trk/trkseg/trkpt/ele")
  487. {
  488. _elevation.append(text);
  489. }
  490. if (path == "/gpx/trk/trkseg/trkpt/time")
  491. {
  492. _time.append(text);
  493. }
  494. }
  495. virtual void endElement(const std::string &path, const std::string &)
  496. {
  497. if (path == "/gpx/trk/trkseg/trkpt")
  498. {
  499. XMLParser::trim(_lat);
  500. XMLParser::trim(_lon);
  501. XMLParser::trim(_elevation);
  502. XMLParser::trim(_time);
  503. if (_xAxis->values(_lat, _lon, _elevation, _time))
  504. {
  505. if (!_yAxis->values(_lat, _lon, _elevation, _time))
  506. {
  507. _xAxis->rollback();
  508. }
  509. }
  510. }
  511. }
  512. private:
  513. std::string fetch(const Attributes &attributes, const std::string &key)
  514. {
  515. auto attribute = attributes.find(key);
  516. return (attribute != attributes.end() ? attribute->second : "");
  517. }
  518. private:
  519. arg::Arguments _arguments;
  520. arg::Argument _title;
  521. arg::Argument _xTitle;
  522. arg::Argument _yTitle;
  523. arg::Argument _style;
  524. arg::Argument _color;
  525. arg::Argument _term;
  526. arg::Argument _width;
  527. arg::Argument _height;
  528. arg::Argument _output;
  529. XMLParser _xmlParser;
  530. std::string _lat;
  531. std::string _lon;
  532. std::string _elevation;
  533. std::string _time;
  534. PlotAxis *_xAxis;
  535. PlotAxis *_yAxis;
  536. };
  537. }
  538. int main(int argc, char *argv[])
  539. {
  540. gpxtools::GPXPlot gpxPlot;
  541. if (gpxPlot.parseArguments(argc, argv))
  542. {
  543. gpxPlot.generate(std::cout);
  544. return 0;
  545. }
  546. return 1;
  547. }