gpxwind.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. #include <cmath>
  2. #include <cstring>
  3. #include <fstream>
  4. #include <iomanip>
  5. #include <iostream>
  6. #include <limits>
  7. #include <vector>
  8. #include "Algorithm.h"
  9. #include "Arguments.h"
  10. #include "XMLParser.h"
  11. namespace gpxtools
  12. {
  13. class GPXWind : public XMLParserHandler
  14. {
  15. public:
  16. // -- Constructor -----------------------------------------------------------
  17. GPXWind() :
  18. _arguments("gpxwind [OPTION].. [N|NE|E|SE|S|SW|W|NW] [FILE]\nShow the impact of the wind direction on the track or route in the GPX-file.\n", "gpxwind v0.1", "Show the wind impact on a track or route"),
  19. _modeArgument(_arguments, true, 'm', "mode", "summary|full|plot", "mode for showing the impact", "summary"),
  20. _xmlParser(this)
  21. {
  22. }
  23. // -- Deconstructor----------------------------------------------------------
  24. virtual ~GPXWind()
  25. {
  26. }
  27. // -- Parse arguments -------------------------------------------------------
  28. bool parseArguments(int argc, char *argv[])
  29. {
  30. std::vector<std::string> filenames;
  31. if (!_arguments.parse(argc,argv, filenames)) return false;
  32. if (!checkArguments()) return false;
  33. if (filenames.empty())
  34. {
  35. std::cerr << "Missing wind direction" << std::endl;
  36. _arguments.printHelp();
  37. return false;
  38. }
  39. if (!parseWindDirection(filenames.front())) return false;
  40. filenames.erase(filenames.begin());
  41. if (filenames.empty())
  42. {
  43. return _xmlParser.parse(std::cin);
  44. }
  45. else
  46. {
  47. if (!parseFile(filenames.front())) return false;
  48. }
  49. return true;
  50. }
  51. private:
  52. // -- Check arguments ---------------------------------------------------------
  53. bool checkArguments()
  54. {
  55. if (_modeArgument.value() == "summary")
  56. {
  57. _mode = SUMMARY;
  58. }
  59. else if (_modeArgument.value() == "full")
  60. {
  61. _mode = FULL;
  62. }
  63. else if (_modeArgument.value() == "plot")
  64. {
  65. _mode = PLOT;
  66. }
  67. else
  68. {
  69. std::cerr << "Invalid value for --" << _modeArgument.longOption() << ": " << _modeArgument.value() << std::endl;
  70. return false;
  71. }
  72. return true;
  73. }
  74. bool parseWindDirection(const std::string &direction)
  75. {
  76. if (direction == "N")
  77. {
  78. _wind = 180.0;
  79. }
  80. else if (direction == "NE")
  81. {
  82. _wind = 225.0;
  83. }
  84. else if (direction == "E")
  85. {
  86. _wind = 270.0;
  87. }
  88. else if (direction == "SE")
  89. {
  90. _wind = 315.0;
  91. }
  92. else if (direction == "S")
  93. {
  94. _wind = 0.0;
  95. }
  96. else if (direction == "SW")
  97. {
  98. _wind = 45.0;
  99. }
  100. else if (direction == "W")
  101. {
  102. _wind = 90.0;
  103. }
  104. else if (direction == "NW")
  105. {
  106. _wind = 135.0;
  107. }
  108. else
  109. {
  110. std::cerr << "Invalid value for the wind direction:" << direction << std::endl;
  111. return false;
  112. }
  113. return true;
  114. }
  115. // -- Parse a file ----------------------------------------------------------
  116. bool parseFile(const std::string &filename)
  117. {
  118. bool ok =false;
  119. std::ifstream file(filename);
  120. if (file.is_open())
  121. {
  122. ok = _xmlParser.parse(file);
  123. file.close();
  124. }
  125. else
  126. {
  127. std::cerr << "Unable to open: " << filename << std::endl;
  128. }
  129. return ok;
  130. }
  131. public:
  132. // -- Output the result -----------------------------------------------------
  133. void report()
  134. {
  135. switch(_mode)
  136. {
  137. case SUMMARY:
  138. reportSummary();
  139. break;
  140. case FULL:
  141. reportFull();
  142. break;
  143. case PLOT:
  144. reportPlot();
  145. break;
  146. }
  147. }
  148. // -- Callbacks -------------------------------------------------------------
  149. virtual void startElement(const std::string &path, const std::string &, const Attributes &attributes)
  150. {
  151. if (path == "/gpx/rte")
  152. {
  153. _type = "Route";
  154. _name.clear();
  155. _number = 0;
  156. _segment._points.clear();
  157. }
  158. else if (path == "/gpx/rte/rtept")
  159. {
  160. _point._lat = getDoubleAttribute(attributes, "lat");
  161. _point._lon = getDoubleAttribute(attributes, "lon");
  162. }
  163. else if (path == "/gpx/trk")
  164. {
  165. _type = "Track";
  166. _name.clear();
  167. _number = 0;
  168. _segment._points.clear();
  169. }
  170. else if (path == "/gpx/trk/trkseg")
  171. {
  172. _number++;
  173. _segment._number = _number;
  174. _segment._points.clear();
  175. }
  176. else if (path == "/gpx/trk/trkseg/trkpt")
  177. {
  178. _point._lat = getDoubleAttribute(attributes, "lat");
  179. _point._lon = getDoubleAttribute(attributes, "lon");
  180. }
  181. }
  182. virtual void text(const std::string &path, const std::string &text)
  183. {
  184. if (path == "/gpx/rte/name")
  185. {
  186. _name = text;
  187. }
  188. else if (path == "/gpx/rte/desc")
  189. {
  190. if (_name.empty()) _name = text;
  191. }
  192. else if (path == "/gpx/trk/name")
  193. {
  194. _name = text;
  195. }
  196. else if (path == "/gpx/trk/desc")
  197. {
  198. if (_name.empty()) _name = text;
  199. }
  200. }
  201. virtual void endElement(const std::string &path, const std::string &)
  202. {
  203. if (path == "/gpx/rte/rtept")
  204. {
  205. _segment._points.push_back(_point);
  206. }
  207. else if (path == "/gpx/rte")
  208. {
  209. _segment._name = _name;
  210. _segment._type = _type;
  211. _segments.push_back(_segment);
  212. }
  213. else if (path == "/gpx/trk")
  214. {
  215. }
  216. else if (path == "/gpx/trk/trkseg")
  217. {
  218. _segment._name = _name;
  219. _segment._type = _type;
  220. _segments.push_back(_segment);
  221. }
  222. else if (path == "/gpx/trk/trkseg/trkpt")
  223. {
  224. _segment._points.push_back(_point);
  225. }
  226. }
  227. // -- Privates ----------------------------------------------------------------
  228. private:
  229. struct LatLon
  230. {
  231. double _lat = NAN;
  232. double _lon = NAN;
  233. };
  234. struct Bounds
  235. {
  236. LatLon _min = {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()};
  237. LatLon _max = {std::numeric_limits<double>::min(), std::numeric_limits<double>::min()};
  238. void add(const LatLon &point)
  239. {
  240. _min._lat = std::min(point._lat, _min._lat);
  241. _min._lon = std::min(point._lon, _min._lon);
  242. _max._lat = std::max(point._lat, _max._lat);
  243. _max._lon = std::max(point._lon, _max._lon);
  244. }
  245. };
  246. struct Line
  247. {
  248. double _distance = NAN;
  249. double _bearing = NAN;
  250. };
  251. struct Segment
  252. {
  253. std::string _type;
  254. std::string _name;
  255. unsigned _number = 0;
  256. std::vector<LatLon> _points;
  257. };
  258. double getDouble(const std::string &value)
  259. {
  260. try
  261. {
  262. if (value.empty())
  263. {
  264. return NAN;
  265. }
  266. else
  267. {
  268. return std::stod(value);
  269. }
  270. }
  271. catch (...)
  272. {
  273. return NAN;
  274. }
  275. }
  276. double getDoubleAttribute(const Attributes &atts, const std::string &key)
  277. {
  278. auto iter = atts.find(key);
  279. return iter != atts.end() ? getDouble(iter->second) : NAN;
  280. }
  281. bool getLine(LatLon &prev, const LatLon &point, Line &line)
  282. {
  283. bool ok = false;
  284. if ((!std::isnan(prev._lat)) && (!std::isnan(prev._lon)) && (!std::isnan(point._lat)) && (!std::isnan(point._lon)))
  285. {
  286. line._distance = gpx::calcDistance(prev._lat, prev._lon, point._lat, point._lon);
  287. line._bearing = gpx::calcBearingInDeg(prev._lat, prev._lon, point._lat, point._lon);
  288. ok = true;
  289. }
  290. prev._lat = point._lat;
  291. prev._lon = point._lon;
  292. return ok;
  293. }
  294. void reportHeader(const Segment &segment)
  295. {
  296. std::cout << segment._type;
  297. if (!segment._name.empty()) std::cout << ": " << segment._name;
  298. if (segment._number > 0) std::cout << " (" << segment._number << ")";
  299. std::cout << std::endl;
  300. }
  301. void reportSummary()
  302. {
  303. for (const auto &segment : _segments)
  304. {
  305. double headwind = 0.0;
  306. double tailwind = 0.0;
  307. LatLon prev;
  308. Line line;
  309. for (const auto &point : segment._points)
  310. {
  311. if (!getLine(prev, point, line)) continue;
  312. double wind = std::abs(line._bearing - _wind);
  313. if (wind > 180.0) wind = 360.0 - wind; // Range 0..180.0
  314. if (wind > 90.0) // 90.0..180.0
  315. {
  316. headwind += line._distance;
  317. }
  318. else // 0..90
  319. {
  320. tailwind += line._distance;
  321. }
  322. }
  323. reportHeader(segment);
  324. std::cout << " Headwind: " << std::fixed << std::setw(8) << std::setprecision(3) << headwind / 1000.0 << " km" << std::endl;
  325. std::cout << " Tailwind: " << std::fixed << std::setw(8) << std::setprecision(3) << tailwind / 1000.0 << " km" << std::endl;
  326. }
  327. }
  328. void reportFull()
  329. {
  330. for (const auto &segment : _segments)
  331. {
  332. double headwind = 0.0;
  333. double headcrosswind = 0.0;
  334. double crosswind = 0.0;
  335. double tailcrosswind = 0.0;
  336. double tailwind = 0.0;
  337. LatLon prev;
  338. Line line;
  339. for (const auto &point : segment._points)
  340. {
  341. if (!getLine(prev, point, line)) continue;
  342. double wind = std::abs(line._bearing - _wind);
  343. if (wind > 180.0) wind = 360.0 - wind; // Range 0..180.0
  344. if (wind >= 170.0)
  345. {
  346. headwind += line._distance;
  347. }
  348. else if (wind > 100.0)
  349. {
  350. headcrosswind += line._distance;
  351. }
  352. else if (wind >= 80.0)
  353. {
  354. crosswind += line._distance;
  355. }
  356. else if (wind > 10.0)
  357. {
  358. tailcrosswind += line._distance;
  359. }
  360. else
  361. {
  362. tailwind += line._distance;
  363. }
  364. }
  365. reportHeader(segment);
  366. std::cout << " Headwind : " << std::fixed << std::setw(8) << std::setprecision(3) << headwind / 1000.0 << " km" << std::endl;
  367. std::cout << " Headcrosswind : " << std::fixed << std::setw(8) << std::setprecision(3) << headcrosswind / 1000.0 << " km" << std::endl;
  368. std::cout << " Crosswind : " << std::fixed << std::setw(8) << std::setprecision(3) << crosswind / 1000.0 << " km" << std::endl;
  369. std::cout << " Tailcrosswind : " << std::fixed << std::setw(8) << std::setprecision(3) << tailcrosswind / 1000.0 << " km" << std::endl;
  370. std::cout << " Tailwind : " << std::fixed << std::setw(8) << std::setprecision(3) << tailwind / 1000.0 << " km" << std::endl;
  371. }
  372. }
  373. void reportPlot()
  374. {
  375. Bounds bounds = reportPlotData(std::cout);
  376. std::cout << "set format x \"%D %E\" geographic" << std::endl;
  377. std::cout << "set format y \"%D %N\" geographic" << std::endl;
  378. std::cout << "unset key" << std::endl;
  379. std::cout << "unset parametric" << std::endl;
  380. std::cout << "unset border" << std::endl;
  381. std::cout << "unset xtics" << std::endl;
  382. std::cout << "unset ytics" << std::endl;
  383. std::cout << "unset colorbox" << std::endl;
  384. std::cout << "set style data lines" << std::endl;
  385. std::cout << "set xrange [ " << std::fixed << std::setprecision(6) << bounds._min._lon << " : " << bounds._max._lon << " ] noreverse nowriteback" << std::endl;
  386. std::cout << "set yrange [ " << std::fixed << std::setprecision(6) << bounds._min._lat << " : " << bounds._max._lat << " ] noreverse nowriteback" << std::endl;
  387. std::cout << "set cbrange [0.0:180.0]" << std::endl;
  388. std::cout << "set palette defined (0.0 \"green\", 90.0 \"yellow\", 180.0 \"red\")" << std::endl;
  389. std::cout << "set output \"gpxwind.png\"" << std::endl;
  390. reportPlotSize(std::cout, bounds);
  391. std::cout << "plot $DATA using 1:2:3 with lines linewidth 3 linecolor palette" << std::endl;
  392. }
  393. Bounds reportPlotData(std::ostream &str)
  394. {
  395. str << "$DATA << EOD" << std::endl;
  396. Bounds bounds;
  397. for (const auto &segment : _segments)
  398. {
  399. LatLon prev;
  400. Line line;
  401. for (const auto &point : segment._points)
  402. {
  403. if (!getLine(prev, point, line)) continue;
  404. double wind = std::abs(line._bearing - _wind);
  405. if (wind > 180.0) wind = 360.0 - wind;
  406. str << std::fixed << std::setprecision(6) << point._lon << " " << point._lat << " " << std::setprecision(1) << wind << std::endl;
  407. bounds.add(point);
  408. }
  409. }
  410. str << "EOD" << std::endl;
  411. return bounds;
  412. }
  413. void reportPlotSize(std::ostream &str, const Bounds &bounds)
  414. {
  415. const int pngSize = 1024;
  416. int xdistance = std::max(std::abs(static_cast<int>(gpx::calcDistance(bounds._min._lat, bounds._min._lon, bounds._min._lat, bounds._max._lon))), 1);
  417. int ydistance = std::max(std::abs(static_cast<int>(gpx::calcDistance(bounds._min._lat, bounds._min._lon, bounds._max._lat, bounds._min._lon))), 1);
  418. if (xdistance > ydistance)
  419. {
  420. ydistance = pngSize * ydistance / xdistance;
  421. xdistance = pngSize;
  422. }
  423. else
  424. {
  425. xdistance = pngSize * xdistance / ydistance;
  426. ydistance = pngSize;
  427. }
  428. str << "set term png size " << xdistance << ',' << ydistance << std::endl;
  429. }
  430. // -- Members ---------------------------------------------------------------
  431. arg::Arguments _arguments;
  432. arg::Argument _modeArgument;
  433. // Arguments
  434. enum Mode
  435. {
  436. SUMMARY, FULL, PLOT
  437. };
  438. Mode _mode = SUMMARY;
  439. double _wind = 0.0;
  440. // State of the parsing
  441. XMLParser _xmlParser;
  442. std::string _path;
  443. std::string _type;
  444. std::string _name;
  445. unsigned _number = 0;
  446. LatLon _point;
  447. Segment _segment;
  448. std::vector<Segment> _segments;
  449. };
  450. };
  451. // -- Main program ------------------------------------------------------------
  452. int main(int argc, char *argv[])
  453. {
  454. gpxtools::GPXWind gpxWind;
  455. if (gpxWind.parseArguments(argc, argv))
  456. {
  457. gpxWind.report();
  458. return EXIT_SUCCESS;
  459. }
  460. return EXIT_FAILURE;
  461. }