You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gpx.cc 54KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977
  1. /*
  2. Access GPX data files.
  3. Copyright (C) 2002-2015 Robert Lipe, gpsbabel.org
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  15. */
  16. #include "defs.h"
  17. #include "cet_util.h"
  18. #include "garmin_fs.h"
  19. #include "garmin_tables.h"
  20. #include "src/core/logging.h"
  21. #include "src/core/file.h"
  22. #include "src/core/xmlstreamwriter.h"
  23. #include "src/core/xmltag.h"
  24. #include <QtCore/QXmlStreamReader>
  25. #include <QtCore/QRegExp>
  26. #include <QtCore/QDateTime>
  27. #include <QtCore/QDebug>
  28. #include <math.h>
  29. static QXmlStreamReader* reader;
  30. static xml_tag* cur_tag;
  31. static QString cdatastr;
  32. static char* opt_logpoint = NULL;
  33. static char* opt_humminbirdext = NULL;
  34. static char* opt_garminext = NULL;
  35. static int logpoint_ct = 0;
  36. // static char* gpx_version = NULL;
  37. QString gpx_version;
  38. static char* gpx_wversion;
  39. static int gpx_wversion_num;
  40. static QXmlStreamAttributes gpx_namespace_attribute;
  41. static QString current_tag;
  42. static Waypoint* wpt_tmp;
  43. static UrlLink* link_;
  44. static int cache_descr_is_html;
  45. static gpsbabel::File* iqfile;
  46. static gpsbabel::File* oqfile;
  47. static gpsbabel::XmlStreamWriter* writer;
  48. static short_handle mkshort_handle;
  49. static QString link_url;
  50. static QString link_text;
  51. static QString link_type;
  52. static char* snlen = NULL;
  53. static char* suppresswhite = NULL;
  54. static char* urlbase = NULL;
  55. static route_head* trk_head;
  56. static route_head* rte_head;
  57. static const route_head* current_trk_head; // Output.
  58. /* used for bounds calculation on output */
  59. static bounds all_bounds;
  60. static int next_trkpt_is_new_seg;
  61. static format_specific_data** fs_ptr;
  62. static void gpx_write_bounds();
  63. #define MYNAME "GPX"
  64. #ifndef CREATOR_NAME_URL
  65. # define CREATOR_NAME_URL "GPSBabel - http://www.gpsbabel.org"
  66. #endif
  67. typedef enum {
  68. gpxpt_waypoint,
  69. gpxpt_track,
  70. gpxpt_route
  71. } gpx_point_type;
  72. typedef enum {
  73. tt_unknown = 0,
  74. tt_gpx,
  75. tt_name, /* Optional file-level info */
  76. tt_desc,
  77. tt_author,
  78. tt_email,
  79. tt_url,
  80. tt_urlname,
  81. tt_keywords,
  82. tt_wpt,
  83. tt_wpttype_ele,
  84. tt_wpttype_time,
  85. tt_wpttype_geoidheight,
  86. tt_wpttype_name,
  87. tt_wpttype_cmt,
  88. tt_wpttype_desc,
  89. tt_wpttype_url, /* Not in GPX 1.1 */
  90. tt_wpttype_urlname, /* Not in GPX 1.1 */
  91. tt_wpttype_link, /* New in GPX 1.1 */
  92. tt_wpttype_link_text, /* New in GPX 1.1 */
  93. tt_wpttype_link_type, /* New in GPX 1.1 */
  94. tt_wpttype_sym,
  95. tt_wpttype_type,
  96. tt_wpttype_fix,
  97. tt_wpttype_sat,
  98. tt_wpttype_hdop, /* HDOPS are common for all three */
  99. tt_wpttype_vdop, /* VDOPS are common for all three */
  100. tt_wpttype_pdop, /* PDOPS are common for all three */
  101. tt_cache,
  102. tt_cache_name,
  103. tt_cache_container,
  104. tt_cache_type,
  105. tt_cache_difficulty,
  106. tt_cache_terrain,
  107. tt_cache_hint,
  108. tt_cache_desc_short,
  109. tt_cache_desc_long,
  110. tt_cache_log_wpt,
  111. tt_cache_log_type,
  112. tt_cache_log_date,
  113. tt_cache_placer,
  114. tt_cache_favorite_points,
  115. tt_cache_personal_note,
  116. tt_wpt_extensions,
  117. tt_garmin_wpt_extensions, /* don't change this order */
  118. tt_garmin_wpt_proximity,
  119. tt_garmin_wpt_temperature,
  120. tt_garmin_wpt_depth,
  121. tt_garmin_wpt_display_mode,
  122. tt_garmin_wpt_categories,
  123. tt_garmin_wpt_category,
  124. tt_garmin_wpt_addr,
  125. tt_garmin_wpt_city,
  126. tt_garmin_wpt_state,
  127. tt_garmin_wpt_country,
  128. tt_garmin_wpt_postal_code,
  129. tt_garmin_wpt_phone_nr, /* don't change this order */
  130. tt_rte,
  131. tt_rte_name,
  132. tt_rte_desc,
  133. tt_rte_cmt,
  134. tt_rte_number,
  135. tt_garmin_rte_display_color,
  136. tt_rte_rtept,
  137. tt_trk,
  138. tt_trk_desc,
  139. tt_trk_name,
  140. tt_trk_trkseg,
  141. tt_trk_number,
  142. tt_garmin_trk_display_color,
  143. tt_trk_trkseg_trkpt,
  144. tt_trk_trkseg_trkpt_course, /* Not in GPX 1.1 */
  145. tt_trk_trkseg_trkpt_speed, /* Not in GPX 1.1 */
  146. tt_trk_trkseg_trkpt_heartrate,
  147. tt_trk_trkseg_trkpt_cadence,
  148. tt_humminbird_wpt_depth,
  149. tt_humminbird_wpt_status,
  150. tt_humminbird_trk_trkseg_trkpt_depth,
  151. } tag_type;
  152. typedef struct {
  153. struct queue queue;
  154. char* tagdata;
  155. } gpx_global_entry;
  156. /*
  157. * The file-level information.
  158. */
  159. static
  160. struct gpx_global {
  161. gpx_global_entry name;
  162. gpx_global_entry desc;
  163. gpx_global_entry author;
  164. gpx_global_entry email;
  165. gpx_global_entry url;
  166. gpx_global_entry urlname;
  167. gpx_global_entry keywords;
  168. /* time and bounds aren't here; they're recomputed. */
  169. }* gpx_global ;
  170. static void
  171. gpx_add_to_global(gpx_global_entry* ge, const QString& s)
  172. {
  173. queue* elem, *tmp;
  174. gpx_global_entry* gep;
  175. QUEUE_FOR_EACH(&ge->queue, elem, tmp) {
  176. gep = BASE_STRUCT(elem, gpx_global_entry, queue);
  177. if (0 == s.compare(gep->tagdata)) {
  178. return;
  179. }
  180. }
  181. gep = (gpx_global_entry*) xcalloc(sizeof(*gep), 1);
  182. QUEUE_INIT(&gep->queue);
  183. gep->tagdata = xstrdup(s);
  184. ENQUEUE_TAIL(&ge->queue, &gep->queue);
  185. }
  186. static void
  187. gpx_rm_from_global(gpx_global_entry* ge)
  188. {
  189. queue* elem, *tmp;
  190. QUEUE_FOR_EACH(&ge->queue, elem, tmp) {
  191. gpx_global_entry* g = (gpx_global_entry*) dequeue(elem);
  192. xfree(g->tagdata);
  193. xfree(g);
  194. }
  195. }
  196. // Temporarily mock the old GPX writer's hardcoded fixed length for float/double
  197. // types. This can be removed once we have time/interest in regenerating all our
  198. // zillion reference files.
  199. static inline QString toString(double d)
  200. {
  201. return QString::number(d, 'f', 9);
  202. };
  203. static inline QString toString(float f)
  204. {
  205. return QString::number(f, 'f', 6);
  206. };
  207. /*
  208. * gpx_reset_short_handle: used for waypoint, route and track names
  209. * this allows gpx:wpt names to overlap gpx:rtept names, etc.
  210. */
  211. static void
  212. gpx_reset_short_handle(void)
  213. {
  214. if (mkshort_handle != NULL) {
  215. mkshort_del_handle(&mkshort_handle);
  216. }
  217. mkshort_handle = mkshort_new_handle();
  218. if (suppresswhite) {
  219. setshort_whitespace_ok(mkshort_handle, 0);
  220. }
  221. setshort_length(mkshort_handle, atoi(snlen));
  222. }
  223. static void
  224. gpx_write_gdata(gpx_global_entry* ge, const char* tag)
  225. {
  226. queue* elem, *tmp;
  227. gpx_global_entry* gep;
  228. if (!gpx_global || QUEUE_EMPTY(&ge->queue)) {
  229. return;
  230. }
  231. writer->writeStartElement(tag);
  232. QUEUE_FOR_EACH(&ge->queue, elem, tmp) {
  233. gep = BASE_STRUCT(elem, gpx_global_entry, queue);
  234. writer->writeCharacters(gep->tagdata);
  235. /* Some tags we just output once. */
  236. if ((0 == strcmp(tag, "url")) ||
  237. (0 == strcmp(tag, "email"))) {
  238. break;
  239. }
  240. }
  241. writer->writeEndElement();
  242. }
  243. typedef struct tag_mapping {
  244. tag_type tag_type_; /* enum from above for this tag */
  245. int tag_passthrough; /* true if we don't generate this */
  246. const char* tag_name; /* xpath-ish tag name */
  247. } tag_mapping;
  248. /*
  249. * xpath(ish) mappings between full tag paths and internal identifers.
  250. * These appear in the order they appear in the GPX specification.
  251. * If it's not a tag we explictly handle, it doesn't go here.
  252. */
  253. /* /gpx/<name> for GPX 1.0, /gpx/metadata/<name> for GPX 1.1 */
  254. #define METATAG(type,name) \
  255. {type, 0, "/gpx/" name}, \
  256. {type, 0, "/gpx/metadata/" name}
  257. tag_mapping tag_path_map[] = {
  258. { tt_gpx, 0, "/gpx" },
  259. METATAG(tt_name, "name"),
  260. METATAG(tt_desc, "desc"),
  261. { tt_author, 0, "/gpx/author" },
  262. { tt_email, 0, "/gpx/email" },
  263. { tt_url, 0, "/gpx/url" },
  264. { tt_urlname, 0, "/gpx/urlname" },
  265. METATAG(tt_keywords, "keywords"),
  266. { tt_wpt, 0, "/gpx/wpt" },
  267. /* Double up the GPX 1.0 and GPX 1.1 styles */
  268. #define GEOTAG(type,name) \
  269. {type, 1, "/gpx/wpt/groundspeak:cache/groundspeak:" name }, \
  270. {type, 1, "/gpx/wpt/extensions/cache/" name }, \
  271. {type, 1, "/gpx/wpt/geocache/" name } /* opencaching.de */
  272. #define GARMIN_RTE_EXT "/gpx/rte/extensions/gpxx:RouteExtension"
  273. #define GARMIN_TRK_EXT "/gpx/trk/extensions/gpxx:TrackExtension"
  274. #define GARMIN_WPT_EXT "/gpx/wpt/extensions/gpxx:WaypointExtension"
  275. #define GARMIN_TRKPT_EXT "/gpx/trk/trkseg/trkpt/extensions/gpxtpx:TrackPointExtension"
  276. #define GARMIN_RTEPT_EXT "/gpx/rte/rtept/extensions/gpxxx:RoutePointExtension"
  277. // GEOTAG( tt_cache, "cache"),
  278. { tt_cache, 1, "/gpx/wpt/groundspeak:cache" },
  279. GEOTAG(tt_cache_name, "name"),
  280. GEOTAG(tt_cache_container, "container"),
  281. GEOTAG(tt_cache_type, "type"),
  282. GEOTAG(tt_cache_difficulty, "difficulty"),
  283. GEOTAG(tt_cache_terrain, "terrain"),
  284. GEOTAG(tt_cache_hint, "encoded_hints"),
  285. GEOTAG(tt_cache_hint, "hints"), /* opencaching.de */
  286. GEOTAG(tt_cache_desc_short, "short_description"),
  287. GEOTAG(tt_cache_desc_long, "long_description"),
  288. GEOTAG(tt_cache_placer, "owner"),
  289. GEOTAG(tt_cache_favorite_points, "favorite_points"),
  290. GEOTAG(tt_cache_personal_note, "personal_note"),
  291. { tt_cache_log_wpt, 1, "/gpx/wpt/groundspeak:cache/groundspeak:logs/groundspeak:log/groundspeak:log_wpt"},
  292. { tt_cache_log_wpt, 1, "/gpx/wpt/extensions/cache/logs/log/log_wpt"},
  293. { tt_cache_log_type, 1, "/gpx/wpt/groundspeak:cache/groundspeak:logs/groundspeak:log/groundspeak:type"},
  294. { tt_cache_log_type, 1, "/gpx/wpt/extensions/cache/logs/log/type"},
  295. { tt_cache_log_date, 1, "/gpx/wpt/groundspeak:cache/groundspeak:logs/groundspeak:log/groundspeak:date"},
  296. { tt_cache_log_date, 1, "/gpx/wpt/extensions/cache/logs/log/date"},
  297. { tt_wpt_extensions, 0, "/gpx/wpt/extensions" },
  298. { tt_garmin_wpt_extensions, 0, GARMIN_WPT_EXT },
  299. { tt_garmin_wpt_proximity, 0, GARMIN_WPT_EXT "/gpxx:Proximity" },
  300. { tt_garmin_wpt_temperature, 0, GARMIN_WPT_EXT "/gpxx:Temperature" },
  301. { tt_garmin_wpt_temperature, 1, GARMIN_TRKPT_EXT "/gpxtpx:atemp" },
  302. { tt_garmin_wpt_depth, 0, GARMIN_WPT_EXT "/gpxx:Depth" },
  303. { tt_garmin_wpt_display_mode, 0, GARMIN_WPT_EXT "/gpxx:DisplayMode" },
  304. { tt_garmin_wpt_categories, 0, GARMIN_WPT_EXT "/gpxx:Categories" },
  305. { tt_garmin_wpt_category, 0, GARMIN_WPT_EXT "/gpxx:Categories/gpxx:Category" },
  306. { tt_garmin_wpt_addr, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:StreetAddress" },
  307. { tt_garmin_wpt_city, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:City" },
  308. { tt_garmin_wpt_state, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:State" },
  309. { tt_garmin_wpt_country, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:Country" },
  310. { tt_garmin_wpt_postal_code, 0, GARMIN_WPT_EXT "/gpxx:Address/gpxx:PostalCode" },
  311. { tt_garmin_wpt_phone_nr, 0, GARMIN_WPT_EXT "/gpxx:PhoneNumber"},
  312. // In Garmin space, but in core of waypoint.
  313. { tt_trk_trkseg_trkpt_heartrate, 1, GARMIN_TRKPT_EXT "/gpxtpx:hr" },
  314. { tt_trk_trkseg_trkpt_cadence, 1, GARMIN_TRKPT_EXT "/gpxtpx:cad" },
  315. { tt_humminbird_wpt_depth, 0, "/gpx/wpt/extensions/h:depth" }, // in centimeters.
  316. { tt_humminbird_wpt_status, 0, "/gpx/wpt/extensions/h:status" },
  317. { tt_rte, 0, "/gpx/rte" },
  318. { tt_rte_name, 0, "/gpx/rte/name" },
  319. { tt_rte_desc, 0, "/gpx/rte/desc" },
  320. { tt_rte_number, 0, "/gpx/rte/number" },
  321. { tt_garmin_rte_display_color, 1, GARMIN_RTE_EXT "/gpxx:DisplayColor"},
  322. { tt_rte_rtept, 0, "/gpx/rte/rtept" },
  323. { tt_trk, 0, "/gpx/trk" },
  324. { tt_trk_name, 0, "/gpx/trk/name" },
  325. { tt_trk_desc, 0, "/gpx/trk/desc" },
  326. { tt_trk_trkseg, 0, "/gpx/trk/trkseg" },
  327. { tt_trk_number, 0, "/gpx/trk/number" },
  328. { tt_garmin_trk_display_color, 1, GARMIN_TRK_EXT "/gpxx:DisplayColor"},
  329. { tt_trk_trkseg_trkpt, 0, "/gpx/trk/trkseg/trkpt" },
  330. { tt_trk_trkseg_trkpt_course, 0, "/gpx/trk/trkseg/trkpt/course" },
  331. { tt_trk_trkseg_trkpt_speed, 0, "/gpx/trk/trkseg/trkpt/speed" },
  332. { tt_humminbird_trk_trkseg_trkpt_depth, 0, "/gpx/trk/trkseg/trkpt/extensions/h:depth" }, // in centimeters.
  333. /* Common to tracks, routes, and waypts */
  334. #define GPXWPTTYPETAG(type,passthrough,name) \
  335. {type, passthrough, "/gpx/wpt/" name }, \
  336. {type, passthrough, "/gpx/trk/trkseg/trkpt/" name }, \
  337. {type, passthrough, "/gpx/rte/rtept/" name }
  338. GPXWPTTYPETAG(tt_wpttype_ele, 0, "ele"),
  339. GPXWPTTYPETAG(tt_wpttype_time, 0, "time"),
  340. GPXWPTTYPETAG(tt_wpttype_geoidheight, 0, "geoidheight"),
  341. GPXWPTTYPETAG(tt_wpttype_name, 0, "name"),
  342. GPXWPTTYPETAG(tt_wpttype_cmt, 0, "cmt"),
  343. GPXWPTTYPETAG(tt_wpttype_desc, 0, "desc"),
  344. GPXWPTTYPETAG(tt_wpttype_url, 0, "url"), /* GPX 1.0 */
  345. GPXWPTTYPETAG(tt_wpttype_urlname, 0, "urlname"), /* GPX 1.0 */
  346. GPXWPTTYPETAG(tt_wpttype_link, 0, "link"), /* GPX 1.1 */
  347. GPXWPTTYPETAG(tt_wpttype_link_text, 0, "link/text"), /* GPX 1.1 */
  348. GPXWPTTYPETAG(tt_wpttype_link_type, 0, "link/type"), /* GPX 1.1 */
  349. GPXWPTTYPETAG(tt_wpttype_sym, 0, "sym"),
  350. GPXWPTTYPETAG(tt_wpttype_type, 1, "type"),
  351. GPXWPTTYPETAG(tt_wpttype_fix, 0, "fix"),
  352. GPXWPTTYPETAG(tt_wpttype_sat, 0, "sat"),
  353. GPXWPTTYPETAG(tt_wpttype_hdop, 0, "hdop"),
  354. GPXWPTTYPETAG(tt_wpttype_vdop, 0, "vdop"),
  355. GPXWPTTYPETAG(tt_wpttype_pdop, 0, "pdop"),
  356. {(tag_type)0, 0, NULL}
  357. };
  358. // Maintain a fast mapping from full tag names to the struct above.
  359. QHash<QString, tag_mapping*> hash;
  360. static tag_type
  361. get_tag(const QString& t, int* passthrough)
  362. {
  363. tag_mapping* tm = hash[t];
  364. if (tm) {
  365. *passthrough = tm->tag_passthrough;
  366. return tm->tag_type_;
  367. }
  368. *passthrough = 1;
  369. return tt_unknown;
  370. }
  371. static void
  372. prescan_tags(void)
  373. {
  374. tag_mapping* tm;
  375. for (tm = tag_path_map; tm->tag_type_ != 0; tm++) {
  376. hash[tm->tag_name] = tm;
  377. }
  378. }
  379. static void
  380. tag_gpx(const QXmlStreamAttributes& attr)
  381. {
  382. if (attr.hasAttribute("version")) {
  383. /* Set the default output version to the highest input
  384. * version.
  385. */
  386. if (gpx_version.isEmpty()) {
  387. gpx_version = attr.value("version").toString();
  388. } else if ((gpx_version.toInt() * 10) < (attr.value("version").toString().toDouble() * 10)) {
  389. gpx_version = attr.value("version").toString();
  390. }
  391. }
  392. /* save namespace declarations in case we pass through elements
  393. * that use them to the writer.
  394. */
  395. const QXmlStreamNamespaceDeclarations ns = reader->namespaceDeclarations();
  396. for (int i = 0; i < ns.size(); ++i) {
  397. QString prefix = ns[i].prefix().toString();
  398. QString namespaceUri = ns[i].namespaceUri().toString();
  399. /* don't toss any xsi declaration, it might used for tt_unknown or passthrough. */
  400. if (!prefix.isEmpty()) {
  401. if (! gpx_namespace_attribute.hasAttribute(prefix.prepend("xmlns:"))) {
  402. gpx_namespace_attribute.append(prefix, namespaceUri);
  403. }
  404. }
  405. }
  406. }
  407. static void
  408. tag_wpt(const QXmlStreamAttributes& attr)
  409. {
  410. wpt_tmp = new Waypoint;
  411. link_ = new UrlLink;
  412. cur_tag = NULL;
  413. if (attr.hasAttribute("lat")) {
  414. wpt_tmp->latitude = attr.value("lat").toString().toDouble();
  415. }
  416. if (attr.hasAttribute("lon")) {
  417. wpt_tmp->longitude = attr.value("lon").toString().toDouble();
  418. }
  419. fs_ptr = &wpt_tmp->fs;
  420. }
  421. static void
  422. tag_cache_desc(const QXmlStreamAttributes& attr)
  423. {
  424. cache_descr_is_html = 0;
  425. if (attr.hasAttribute("html")) {
  426. if (attr.value("html").toString().compare("True") == 0) {
  427. cache_descr_is_html = 1;
  428. }
  429. }
  430. }
  431. static void
  432. tag_gs_cache(const QXmlStreamAttributes& attr)
  433. {
  434. geocache_data* gc_data = wpt_tmp->AllocGCData();
  435. if (attr.hasAttribute("id")) {
  436. gc_data->id = attr.value("id").toString().toInt();
  437. }
  438. if (attr.hasAttribute("available")) {
  439. if (attr.value("available").toString().compare("True", Qt::CaseInsensitive) == 0) {
  440. gc_data->is_available = status_true;
  441. } else if (attr.value("available").toString().compare("False", Qt::CaseInsensitive) == 0) {
  442. gc_data->is_available = status_false;
  443. }
  444. }
  445. if (attr.hasAttribute("archived")) {
  446. if (attr.value("archived").toString().compare("True", Qt::CaseInsensitive) == 0) {
  447. gc_data->is_archived = status_true;
  448. } else if (attr.value("archived").toString().compare("False", Qt::CaseInsensitive) == 0) {
  449. gc_data->is_archived = status_false;
  450. }
  451. }
  452. }
  453. static void
  454. start_something_else(const QString el, const QXmlStreamAttributes& attr)
  455. {
  456. char** avcp;
  457. int attr_count;
  458. xml_tag* new_tag;
  459. fs_xml* fs_gpx;
  460. if (!fs_ptr) {
  461. return;
  462. }
  463. new_tag = new xml_tag;
  464. new_tag->tagname = el;
  465. attr_count = attr.size();
  466. const QXmlStreamNamespaceDeclarations nsdecl = reader->namespaceDeclarations();
  467. const int ns_count = nsdecl.size();
  468. new_tag->attributes = (char**)xcalloc(sizeof(char*),2*(attr_count+ns_count)+1);
  469. avcp = new_tag->attributes;
  470. for (int i = 0; i < attr_count; i++) {
  471. *avcp = xstrdup(attr[i].qualifiedName().toString());
  472. avcp++;
  473. *avcp = xstrdup(attr[i].value().toString());
  474. avcp++;
  475. }
  476. for (int i = 0; i < ns_count; i++) {
  477. *avcp = xstrdup(nsdecl[i].prefix().toString().prepend(nsdecl[i].prefix().isEmpty()? "xmlns" : "xmlns:"));
  478. avcp++;
  479. *avcp = xstrdup(nsdecl[i].namespaceUri().toString());
  480. avcp++;
  481. }
  482. *avcp = NULL; // this indicates the end of the attribute name value pairs.
  483. if (cur_tag) {
  484. if (cur_tag->child) {
  485. cur_tag = cur_tag->child;
  486. while (cur_tag->sibling) {
  487. cur_tag = cur_tag->sibling;
  488. }
  489. cur_tag->sibling = new_tag;
  490. new_tag->parent = cur_tag->parent;
  491. } else {
  492. cur_tag->child = new_tag;
  493. new_tag->parent = cur_tag;
  494. }
  495. } else {
  496. fs_gpx = (fs_xml*)fs_chain_find(*fs_ptr, FS_GPX);
  497. if (fs_gpx && fs_gpx->tag) {
  498. cur_tag = fs_gpx->tag;
  499. while (cur_tag->sibling) {
  500. cur_tag = cur_tag->sibling;
  501. }
  502. cur_tag->sibling = new_tag;
  503. new_tag->parent = NULL;
  504. } else {
  505. fs_gpx = fs_xml_alloc(FS_GPX);
  506. fs_gpx->tag = new_tag;
  507. fs_chain_add(fs_ptr, (format_specific_data*)fs_gpx);
  508. new_tag->parent = NULL;
  509. }
  510. }
  511. cur_tag = new_tag;
  512. }
  513. static void
  514. end_something_else()
  515. {
  516. if (cur_tag) {
  517. cur_tag = cur_tag->parent;
  518. }
  519. }
  520. static void
  521. tag_log_wpt(const QXmlStreamAttributes& attr)
  522. {
  523. /* create a new waypoint */
  524. Waypoint* lwp_tmp = new Waypoint;
  525. /* extract the lat/lon attributes */
  526. if (attr.hasAttribute("lat")) {
  527. lwp_tmp->latitude = attr.value("lat").toString().toDouble();
  528. }
  529. if (attr.hasAttribute("lon")) {
  530. lwp_tmp->longitude = attr.value("lon").toString().toDouble();
  531. }
  532. /* Make a new shortname. Since this is a groundspeak extension,
  533. we assume that GCBLAH is the current shortname format and that
  534. wpt_tmp refers to the currently parsed waypoint. Unfortunatley,
  535. we need to keep track of log_wpt counts so we don't collide with
  536. dupe shortnames.
  537. */
  538. #if NEW_STRINGS
  539. if (wpt_tmp->shortname.size() > 2) {
  540. // FIXME: think harder about this later.
  541. lwp_tmp->shortname = wpt_tmp->shortname.mid(2, 4) + "-FIXME";
  542. #else
  543. if ((wpt_tmp->shortname) && (strlen(wpt_tmp->shortname) > 2)) {
  544. /* copy of the shortname */
  545. lwp_tmp->shortname = (char*) xcalloc(7, 1);
  546. sprintf(lwp_tmp->shortname, "%-4.4s%02d",
  547. &wpt_tmp->shortname[2], logpoint_ct++);
  548. #endif
  549. waypt_add(lwp_tmp);
  550. }
  551. }
  552. static void
  553. gpx_start(const QString& el, const QXmlStreamAttributes& attr)
  554. {
  555. int passthrough;
  556. int tag;
  557. /*
  558. * Reset end-of-string without actually emptying/reallocing cdatastr.
  559. */
  560. cdatastr = QString();
  561. tag = get_tag(current_tag, &passthrough);
  562. switch (tag) {
  563. case tt_gpx:
  564. tag_gpx(attr);
  565. break;
  566. case tt_wpt:
  567. tag_wpt(attr);
  568. break;
  569. case tt_wpttype_link:
  570. if (attr.hasAttribute("href")) {
  571. link_url = attr.value("href").toString();
  572. }
  573. break;
  574. case tt_rte:
  575. rte_head = route_head_alloc();
  576. route_add_head(rte_head);
  577. fs_ptr = &rte_head->fs;
  578. break;
  579. case tt_rte_rtept:
  580. tag_wpt(attr);
  581. break;
  582. case tt_trk:
  583. trk_head = route_head_alloc();
  584. track_add_head(trk_head);
  585. fs_ptr = &trk_head->fs;
  586. break;
  587. case tt_trk_trkseg_trkpt:
  588. tag_wpt(attr);
  589. if (next_trkpt_is_new_seg) {
  590. wpt_tmp->wpt_flags.new_trkseg = 1;
  591. next_trkpt_is_new_seg = 0;
  592. }
  593. break;
  594. case tt_unknown:
  595. start_something_else(el, attr);
  596. return;
  597. case tt_cache:
  598. tag_gs_cache(attr);
  599. break;
  600. case tt_cache_log_wpt:
  601. if (opt_logpoint) {
  602. tag_log_wpt(attr);
  603. }
  604. break;
  605. case tt_cache_desc_long:
  606. case tt_cache_desc_short:
  607. tag_cache_desc(attr);
  608. break;
  609. case tt_cache_placer:
  610. if (attr.hasAttribute("id")) {
  611. wpt_tmp->AllocGCData()->placer_id = attr.value("id").toString().toInt();
  612. }
  613. default:
  614. break;
  615. }
  616. if (passthrough) {
  617. start_something_else(el, attr);
  618. }
  619. }
  620. struct
  621. gs_type_mapping {
  622. geocache_type type;
  623. const char* name;
  624. } gs_type_map[] = {
  625. { gt_traditional, "Traditional Cache" },
  626. { gt_traditional, "Traditional" }, /* opencaching.de */
  627. { gt_multi, "Multi-cache" },
  628. { gt_multi, "Multi" }, /* opencaching.de */
  629. { gt_virtual, "Virtual Cache" },
  630. { gt_virtual, "Virtual" }, /* opencaching.de */
  631. { gt_event, "Event Cache" },
  632. { gt_event, "Event" }, /* opencaching.de */
  633. { gt_webcam, "Webcam Cache" },
  634. { gt_webcam, "Webcam" }, /* opencaching.de */
  635. { gt_suprise, "Unknown Cache" },
  636. { gt_earth, "Earthcache" },
  637. { gt_earth, "Earth" }, /* opencaching.de */
  638. { gt_cito, "Cache In Trash Out Event" },
  639. { gt_letterbox, "Letterbox Hybrid" },
  640. { gt_locationless, "Locationless (Reverse) Cache" },
  641. { gt_ape, "Project APE Cache" },
  642. { gt_mega, "Mega-Event Cache" },
  643. { gt_wherigo, "Wherigo Cache" },
  644. { gt_benchmark, "Benchmark" }, /* Not Groundspeak; for GSAK */
  645. };
  646. struct
  647. gs_container_mapping {
  648. geocache_container type;
  649. const char* name;
  650. } gs_container_map[] = {
  651. { gc_other, "Unknown" },
  652. { gc_other, "Other" }, /* Synonym on read. */
  653. { gc_micro, "Micro" },
  654. { gc_regular, "Regular" },
  655. { gc_large, "Large" },
  656. { gc_small, "Small" },
  657. { gc_virtual, "Virtual" }
  658. };
  659. geocache_type
  660. gs_mktype(const QString& t)
  661. {
  662. int i;
  663. int sz = sizeof(gs_type_map) / sizeof(gs_type_map[0]);
  664. for (i = 0; i < sz; i++) {
  665. if (!t.compare(gs_type_map[i].name,Qt::CaseInsensitive)) {
  666. return gs_type_map[i].type;
  667. }
  668. }
  669. return gt_unknown;
  670. }
  671. const char*
  672. gs_get_cachetype(geocache_type t)
  673. {
  674. int i;
  675. int sz = sizeof(gs_type_map) / sizeof(gs_type_map[0]);
  676. for (i = 0; i < sz; i++) {
  677. if (t == gs_type_map[i].type) {
  678. return gs_type_map[i].name;
  679. }
  680. }
  681. return "Unknown";
  682. }
  683. geocache_container
  684. gs_mkcont(const QString& t)
  685. {
  686. int i;
  687. int sz = sizeof(gs_container_map) / sizeof(gs_container_map[0]);
  688. for (i = 0; i < sz; i++) {
  689. if (!t.compare(gs_container_map[i].name,Qt::CaseInsensitive)) {
  690. return gs_container_map[i].type;
  691. }
  692. }
  693. return gc_unknown;
  694. }
  695. const char*
  696. gs_get_container(geocache_container t)
  697. {
  698. int i;
  699. int sz = sizeof(gs_container_map) / sizeof(gs_container_map[0]);
  700. for (i = 0; i < sz; i++) {
  701. if (t == gs_container_map[i].type) {
  702. return gs_container_map[i].name;
  703. }
  704. }
  705. return "Unknown";
  706. }
  707. gpsbabel::DateTime
  708. xml_parse_time(const QString& dateTimeString)
  709. {
  710. int off_hr = 0;
  711. int off_min = 0;
  712. int off_sign = 1;
  713. char* offsetstr = NULL;
  714. char* pointstr = NULL;
  715. char* timestr = xstrdup(dateTimeString);
  716. offsetstr = strchr(timestr, 'Z');
  717. if (offsetstr) {
  718. /* zulu time; offsets stay at defaults */
  719. *offsetstr = '\0';
  720. } else {
  721. offsetstr = strchr(timestr, '+');
  722. if (offsetstr) {
  723. /* positive offset; parse it */
  724. *offsetstr = '\0';
  725. sscanf(offsetstr + 1, "%d:%d", &off_hr, &off_min);
  726. } else {
  727. offsetstr = strchr(timestr, 'T');
  728. if (offsetstr) {
  729. offsetstr = strchr(offsetstr, '-');
  730. if (offsetstr) {
  731. /* negative offset; parse it */
  732. *offsetstr = '\0';
  733. sscanf(offsetstr + 1, "%d:%d", &off_hr, &off_min);
  734. off_sign = -1;
  735. }
  736. }
  737. }
  738. }
  739. double fsec = 0;
  740. pointstr = strchr(timestr, '.');
  741. if (pointstr) {
  742. sscanf(pointstr, "%le", &fsec);
  743. #if 0
  744. /* Round to avoid FP jitter */
  745. if (microsecs) {
  746. *microsecs = .5 + (fsec * 1000000.0) ;
  747. }
  748. #endif
  749. *pointstr = '\0';
  750. }
  751. int year = 0, mon = 0, mday = 0, hour = 0, min = 0, sec = 0;
  752. QDateTime dt;
  753. int res = sscanf(timestr, "%d-%d-%dT%d:%d:%d", &year, &mon, &mday, &hour,
  754. &min, &sec);
  755. if (res > 0) {
  756. QDate date(year, mon, mday);
  757. QTime time(hour, min, sec);
  758. dt = QDateTime(date, time, Qt::UTC);
  759. // Fractional part of time.
  760. if (fsec) {
  761. dt = dt.addMSecs(lround(fsec * 1000));
  762. }
  763. // Any offsets that were stuck at the end.
  764. dt = dt.addSecs(-off_sign * off_hr * 3600 - off_sign * off_min * 60);
  765. } else {
  766. dt = QDateTime();
  767. }
  768. xfree(timestr);
  769. return dt;
  770. }
  771. static void
  772. gpx_end(const QString& el)
  773. {
  774. float x;
  775. int passthrough;
  776. static QDateTime gc_log_date;
  777. tag_type tag;
  778. // Remove leading, trailing whitespace.
  779. cdatastr = cdatastr.trimmed();
  780. tag = get_tag(current_tag, &passthrough);
  781. switch (tag) {
  782. /*
  783. * First, the tags that are file-global.
  784. */
  785. case tt_name:
  786. gpx_add_to_global(&gpx_global->name, cdatastr);
  787. break;
  788. case tt_desc:
  789. gpx_add_to_global(&gpx_global->desc, cdatastr);
  790. break;
  791. case tt_author:
  792. gpx_add_to_global(&gpx_global->author, cdatastr);
  793. break;
  794. case tt_email:
  795. gpx_add_to_global(&gpx_global->email, cdatastr);
  796. break;
  797. case tt_url:
  798. gpx_add_to_global(&gpx_global->url, cdatastr);
  799. break;
  800. case tt_urlname:
  801. gpx_add_to_global(&gpx_global->urlname, cdatastr);
  802. break;
  803. case tt_keywords:
  804. gpx_add_to_global(&gpx_global->keywords, cdatastr);
  805. break;
  806. /*
  807. * Waypoint-specific tags.
  808. */
  809. case tt_wpt:
  810. if (link_) {
  811. if (!link_->url_.isEmpty()) {
  812. wpt_tmp->AddUrlLink(*link_);
  813. }
  814. delete link_;
  815. link_ = NULL;
  816. }
  817. waypt_add(wpt_tmp);
  818. logpoint_ct = 0;
  819. cur_tag = NULL;
  820. wpt_tmp = NULL;
  821. break;
  822. case tt_cache_name:
  823. wpt_tmp->notes = cdatastr;
  824. break;
  825. case tt_cache_container:
  826. wpt_tmp->AllocGCData()->container = gs_mkcont(cdatastr);
  827. break;
  828. case tt_cache_type:
  829. wpt_tmp->AllocGCData()->type = gs_mktype(cdatastr);
  830. break;
  831. case tt_cache_difficulty:
  832. x = cdatastr.toDouble();
  833. wpt_tmp->AllocGCData()->diff = x * 10;
  834. break;
  835. case tt_cache_hint:
  836. wpt_tmp->AllocGCData()->hint = cdatastr;
  837. break;
  838. case tt_cache_desc_long: {
  839. geocache_data* gc_data = wpt_tmp->AllocGCData();
  840. gc_data->desc_long.is_html = cache_descr_is_html;
  841. gc_data->desc_long.utfstring = cdatastr;
  842. }
  843. break;
  844. case tt_cache_desc_short: {
  845. geocache_data* gc_data = wpt_tmp->AllocGCData();
  846. gc_data->desc_short.is_html = cache_descr_is_html;
  847. gc_data->desc_short.utfstring = cdatastr;
  848. }
  849. break;
  850. case tt_cache_terrain:
  851. x = cdatastr.toDouble();
  852. wpt_tmp->AllocGCData()->terr = x * 10;
  853. break;
  854. case tt_cache_placer:
  855. wpt_tmp->AllocGCData()->placer = cdatastr;
  856. break;
  857. case tt_cache_log_date:
  858. gc_log_date = xml_parse_time(cdatastr);
  859. break;
  860. /*
  861. * "Found it" logs follow the date according to the schema,
  862. * if this is the first "found it" for this waypt, just use the
  863. * last date we saw in this log.
  864. */
  865. case tt_cache_log_type:
  866. if ((cdatastr.compare("Found it") == 0) &&
  867. (0 == wpt_tmp->gc_data->last_found.toTime_t())) {
  868. wpt_tmp->AllocGCData()->last_found = gc_log_date;
  869. }
  870. gc_log_date = QDateTime();
  871. break;
  872. case tt_cache_favorite_points:
  873. wpt_tmp->AllocGCData()->favorite_points = cdatastr.toInt();
  874. break;
  875. case tt_cache_personal_note:
  876. wpt_tmp->AllocGCData()->personal_note = cdatastr;
  877. break;
  878. /*
  879. * Garmin-waypoint-specific tags.
  880. */
  881. case tt_garmin_wpt_proximity:
  882. case tt_garmin_wpt_temperature:
  883. case tt_garmin_wpt_depth:
  884. case tt_garmin_wpt_display_mode:
  885. case tt_garmin_wpt_category:
  886. case tt_garmin_wpt_addr:
  887. case tt_garmin_wpt_city:
  888. case tt_garmin_wpt_state:
  889. case tt_garmin_wpt_country:
  890. case tt_garmin_wpt_postal_code:
  891. case tt_garmin_wpt_phone_nr:
  892. garmin_fs_xml_convert(tt_garmin_wpt_extensions, tag, cdatastr, wpt_tmp);
  893. break;
  894. /*
  895. * Humminbird-waypoint-specific tags.
  896. */
  897. case tt_humminbird_wpt_depth:
  898. case tt_humminbird_trk_trkseg_trkpt_depth:
  899. WAYPT_SET(wpt_tmp, depth, cdatastr.toDouble() / 100.0)
  900. break;
  901. /*
  902. * Route-specific tags.
  903. */
  904. case tt_rte_name:
  905. rte_head->rte_name = cdatastr;
  906. break;
  907. case tt_rte:
  908. break;
  909. case tt_rte_rtept:
  910. if (link_) {
  911. if (!link_->url_.isEmpty()) {
  912. wpt_tmp->AddUrlLink(*link_);
  913. }
  914. delete link_;
  915. link_ = NULL;
  916. }
  917. route_add_wpt(rte_head, wpt_tmp);
  918. wpt_tmp = NULL;
  919. break;
  920. case tt_rte_desc:
  921. rte_head->rte_desc = cdatastr;
  922. break;
  923. case tt_garmin_rte_display_color:
  924. rte_head->line_color.bbggrr = gt_color_value_by_name(cdatastr);
  925. break;
  926. case tt_rte_number:
  927. rte_head->rte_num = cdatastr.toInt();
  928. break;
  929. /*
  930. * Track-specific tags.
  931. */
  932. case tt_trk_name:
  933. trk_head->rte_name = cdatastr;
  934. break;
  935. case tt_trk:
  936. break;
  937. case tt_trk_trkseg:
  938. next_trkpt_is_new_seg = 1;
  939. break;
  940. case tt_trk_trkseg_trkpt:
  941. if (link_) {
  942. if (!link_->url_.isEmpty()) {
  943. wpt_tmp->AddUrlLink(*link_);
  944. }
  945. delete link_;
  946. link_ = NULL;
  947. }
  948. track_add_wpt(trk_head, wpt_tmp);
  949. wpt_tmp = NULL;
  950. break;
  951. case tt_trk_desc:
  952. trk_head->rte_desc = cdatastr;
  953. break;
  954. case tt_garmin_trk_display_color:
  955. trk_head->line_color.bbggrr = gt_color_value_by_name(cdatastr);
  956. break;
  957. case tt_trk_number:
  958. trk_head->rte_num = cdatastr.toInt();
  959. break;
  960. case tt_trk_trkseg_trkpt_course:
  961. WAYPT_SET(wpt_tmp, course, cdatastr.toDouble());
  962. break;
  963. case tt_trk_trkseg_trkpt_speed:
  964. WAYPT_SET(wpt_tmp, speed, cdatastr.toDouble());
  965. break;
  966. case tt_trk_trkseg_trkpt_heartrate:
  967. wpt_tmp->heartrate = cdatastr.toDouble();
  968. break;
  969. case tt_trk_trkseg_trkpt_cadence:
  970. wpt_tmp->cadence = cdatastr.toDouble();
  971. break;
  972. /*
  973. * Items that are actually in multiple categories.
  974. */
  975. case tt_wpttype_ele:
  976. wpt_tmp->altitude = cdatastr.toDouble();
  977. break;
  978. case tt_wpttype_name:
  979. wpt_tmp->shortname = cdatastr;
  980. break;
  981. case tt_wpttype_sym:
  982. wpt_tmp->icon_descr = cdatastr;
  983. break;
  984. case tt_wpttype_time:
  985. wpt_tmp->SetCreationTime(xml_parse_time(cdatastr));
  986. break;
  987. case tt_wpttype_geoidheight:
  988. WAYPT_SET(wpt_tmp, geoidheight, cdatastr.toDouble());
  989. break;
  990. case tt_wpttype_cmt:
  991. wpt_tmp->description = cdatastr;
  992. break;
  993. case tt_wpttype_desc:
  994. wpt_tmp->notes = cdatastr;
  995. break;
  996. case tt_wpttype_pdop:
  997. wpt_tmp->pdop = cdatastr.toDouble();
  998. break;
  999. case tt_wpttype_hdop:
  1000. wpt_tmp->hdop = cdatastr.toDouble();
  1001. break;
  1002. case tt_wpttype_vdop:
  1003. wpt_tmp->vdop = cdatastr.toDouble();
  1004. break;
  1005. case tt_wpttype_sat:
  1006. wpt_tmp->sat = cdatastr.toDouble();
  1007. break;
  1008. case tt_wpttype_fix:
  1009. if (cdatastr == QLatin1String("none")) {
  1010. wpt_tmp->fix = fix_none;
  1011. } else if (cdatastr == QLatin1String("2d")) {
  1012. wpt_tmp->fix = fix_2d;
  1013. } else if (cdatastr == QLatin1String("3d")) {
  1014. wpt_tmp->fix = fix_3d;
  1015. } else if (cdatastr == QLatin1String("dgps")) {
  1016. wpt_tmp->fix = fix_dgps;
  1017. } else if (cdatastr == QLatin1String("pps")) {
  1018. wpt_tmp->fix = fix_pps;
  1019. } else {
  1020. wpt_tmp->fix = fix_unknown;
  1021. }
  1022. break;
  1023. case tt_wpttype_url:
  1024. link_->url_ = cdatastr;
  1025. break;
  1026. case tt_wpttype_urlname:
  1027. link_->url_link_text_ = cdatastr;
  1028. break;
  1029. case tt_wpttype_link:
  1030. waypt_add_url(wpt_tmp, link_url, link_text, link_type);
  1031. link_type = QString();
  1032. link_text = QString();
  1033. link_url = QString();
  1034. break;
  1035. case tt_wpttype_link_text:
  1036. link_text = cdatastr;
  1037. break;
  1038. case tt_wpttype_link_type:
  1039. link_type = cdatastr;
  1040. break;
  1041. case tt_unknown:
  1042. end_something_else();
  1043. return;
  1044. default:
  1045. break;
  1046. }
  1047. if (passthrough) {
  1048. end_something_else();
  1049. }
  1050. }
  1051. static void
  1052. gpx_cdata(const QString& s)
  1053. {
  1054. QString* cdata;
  1055. xml_tag* tmp_tag;
  1056. cdatastr += s;
  1057. if (!cur_tag) {
  1058. return;
  1059. }
  1060. if (cur_tag->child) {
  1061. tmp_tag = cur_tag->child;
  1062. while (tmp_tag->sibling) {
  1063. tmp_tag = tmp_tag->sibling;
  1064. }
  1065. cdata = &(tmp_tag->parentcdata);
  1066. } else {
  1067. cdata = &(cur_tag->cdata);
  1068. }
  1069. *cdata = cdatastr.trimmed();
  1070. }
  1071. static void
  1072. gpx_rd_init(const QString& fname)
  1073. {
  1074. iqfile = new gpsbabel::File(fname);
  1075. iqfile->open(QIODevice::ReadOnly);
  1076. reader = new QXmlStreamReader(iqfile);
  1077. current_tag.clear();
  1078. prescan_tags();
  1079. cdatastr = QString();
  1080. if (NULL == gpx_global) {
  1081. gpx_global = (struct gpx_global*) xcalloc(sizeof(*gpx_global), 1);
  1082. QUEUE_INIT(&gpx_global->name.queue);
  1083. QUEUE_INIT(&gpx_global->desc.queue);
  1084. QUEUE_INIT(&gpx_global->author.queue);
  1085. QUEUE_INIT(&gpx_global->email.queue);
  1086. QUEUE_INIT(&gpx_global->url.queue);
  1087. QUEUE_INIT(&gpx_global->urlname.queue);
  1088. QUEUE_INIT(&gpx_global->keywords.queue);
  1089. }
  1090. fs_ptr = NULL;
  1091. }
  1092. static
  1093. void
  1094. gpx_rd_deinit(void)
  1095. {
  1096. delete reader;
  1097. reader = NULL;
  1098. iqfile->close();
  1099. delete iqfile;
  1100. iqfile = NULL;
  1101. wpt_tmp = NULL;
  1102. cur_tag = NULL;
  1103. }
  1104. static void
  1105. gpx_wr_init(const QString& fname)
  1106. {
  1107. mkshort_handle = NULL;
  1108. oqfile = new gpsbabel::File(fname);
  1109. oqfile->open(QIODevice::WriteOnly | QIODevice::Text);
  1110. writer = new gpsbabel::XmlStreamWriter(oqfile);
  1111. writer->setAutoFormattingIndent(2);
  1112. writer->writeStartDocument();
  1113. /* if an output version is not specified and an input version is
  1114. * available use it, otherwise use the default.
  1115. */
  1116. if (!gpx_wversion) {
  1117. if (gpx_version.isEmpty()) {
  1118. gpx_wversion = (char*)"1.0";
  1119. } else {
  1120. // FIXME: this is gross. The surrounding code is badly tortured by
  1121. // there being three concepts of "output version", each with a different
  1122. // data type (QString, int, char*). This section needs a rethink. For
  1123. // now, we stuff over the QString gpx_version into the global char *
  1124. // gpx_wversion without making a malloc'ed copy.
  1125. static char tmp[16];
  1126. strncpy(tmp, CSTR(gpx_version), sizeof(tmp));
  1127. gpx_wversion = tmp;
  1128. }
  1129. }
  1130. if (opt_humminbirdext || opt_garminext) {
  1131. gpx_wversion = (char*)"1.1";
  1132. }
  1133. gpx_wversion_num = strtod(gpx_wversion, NULL) * 10;
  1134. if (gpx_wversion_num <= 0) {
  1135. Fatal() << MYNAME << ": gpx version number of "
  1136. << gpx_wversion << "not valid.";
  1137. }
  1138. // FIXME: This write of a blank line is needed for Qt 4.6 (as on Centos 6.3)
  1139. // to include just enough whitespace between <xml/> and <gpx...> to pass
  1140. // diff -w. It's here for now to shim compatibility with our zillion
  1141. // reference files, but this blank link can go away some day.
  1142. writer->writeCharacters("\n");
  1143. writer->setAutoFormatting(true);
  1144. writer->writeStartElement("gpx");
  1145. writer->writeAttribute("version", gpx_wversion);
  1146. writer->writeAttribute("creator", CREATOR_NAME_URL);
  1147. writer->writeAttribute("xmlns", QString("http://www.topografix.com/GPX/%1/%2").arg(gpx_wversion[0]).arg(gpx_wversion[2]));
  1148. if (opt_humminbirdext || opt_garminext) {
  1149. if (opt_humminbirdext) {
  1150. writer->writeAttribute("xmlns:h","http://humminbird.com");
  1151. }
  1152. if (opt_garminext) {
  1153. writer->writeAttribute("xmlns:gpxx", "http://www.garmin.com/xmlschemas/GpxExtensions/v3");
  1154. writer->writeAttribute("xmlns:gpxtpx", "http://www.garmin.com/xmlschemas/TrackPointExtension/v1");
  1155. }
  1156. } else {
  1157. writer->writeAttributes(gpx_namespace_attribute);
  1158. }
  1159. if (gpx_wversion_num > 10) {
  1160. writer->writeStartElement("metadata");
  1161. }
  1162. if (gpx_global) {
  1163. gpx_write_gdata(&gpx_global->name, "name");
  1164. gpx_write_gdata(&gpx_global->desc, "desc");
  1165. }
  1166. /* In GPX 1.1, author changed from a string to a PersonType.
  1167. * since it's optional, we just drop it instead of rewriting it.
  1168. */
  1169. if (gpx_wversion_num < 11) {
  1170. if (gpx_global) {
  1171. gpx_write_gdata(&gpx_global->author, "author");
  1172. }
  1173. }
  1174. /* In GPX 1.1 email, url, urlname aren't allowed. */
  1175. if (gpx_wversion_num < 11) {
  1176. if (gpx_global) {
  1177. gpx_write_gdata(&gpx_global->email, "email");
  1178. gpx_write_gdata(&gpx_global->url, "url");
  1179. gpx_write_gdata(&gpx_global->urlname, "urlname");
  1180. }
  1181. }
  1182. gpsbabel::DateTime now = current_time();
  1183. writer->writeTextElement("time", now.toPrettyString());
  1184. if (gpx_global) {
  1185. gpx_write_gdata(&gpx_global->keywords, "keywords");
  1186. }
  1187. gpx_write_bounds();
  1188. if (gpx_wversion_num > 10) {
  1189. writer->writeEndElement();
  1190. }
  1191. }
  1192. static void
  1193. gpx_wr_deinit(void)
  1194. {
  1195. writer->writeEndDocument();
  1196. delete writer;
  1197. writer = NULL;
  1198. oqfile->close();
  1199. delete oqfile;
  1200. oqfile = NULL;
  1201. mkshort_del_handle(&mkshort_handle);
  1202. }
  1203. void
  1204. gpx_read(void)
  1205. {
  1206. for (bool atEnd = false; !reader->atEnd() && !atEnd;) {
  1207. reader->readNext();
  1208. // do processing
  1209. switch (reader->tokenType()) {
  1210. case QXmlStreamReader::StartElement:
  1211. current_tag.append("/");
  1212. current_tag.append(reader->qualifiedName());
  1213. {
  1214. const QXmlStreamAttributes attrs = reader->attributes();
  1215. gpx_start(reader->qualifiedName().toString(), attrs);
  1216. }
  1217. break;
  1218. case QXmlStreamReader::EndElement:
  1219. gpx_end(reader->qualifiedName().toString());
  1220. current_tag.chop(reader->qualifiedName().length() + 1);
  1221. cdatastr.clear();
  1222. break;
  1223. case QXmlStreamReader::Characters:
  1224. // It is tempting to skip this if reader->isWhitespace().
  1225. // That would lose all whitespace element values if the exist,
  1226. // but it would skip line endings and indentation that doesn't matter.
  1227. gpx_cdata(reader->text().toString());
  1228. break;
  1229. case QXmlStreamReader::Invalid:
  1230. atEnd = true;
  1231. break;
  1232. default:
  1233. break;
  1234. }
  1235. }
  1236. if (reader->hasError()) {
  1237. Fatal() << MYNAME << "Read error:" << reader->errorString()
  1238. << "File:" << iqfile->fileName()
  1239. << "Line:" << reader->lineNumber()
  1240. << "Column:" << reader->columnNumber();
  1241. }
  1242. }
  1243. static void
  1244. write_tag_attributes(xml_tag* tag)
  1245. {
  1246. char** pa;
  1247. pa = tag->attributes;
  1248. if (pa) {
  1249. while (*pa) {
  1250. writer->writeAttribute(pa[0], pa[1]);
  1251. pa += 2;
  1252. }
  1253. }
  1254. }
  1255. static void
  1256. fprint_xml_chain(xml_tag* tag, const Waypoint* wpt)
  1257. {
  1258. while (tag) {
  1259. writer->writeStartElement(tag->tagname);
  1260. if (tag->cdata.isEmpty() && !tag->child) {
  1261. write_tag_attributes(tag);
  1262. // No children? Self-closing tag.
  1263. writer->writeEndElement();
  1264. } else {
  1265. write_tag_attributes(tag);
  1266. if (!tag->cdata.isEmpty()) {
  1267. writer->writeCharacters(tag->cdata);
  1268. }
  1269. if (tag->child) {
  1270. fprint_xml_chain(tag->child, wpt);
  1271. }
  1272. if (wpt && wpt->gc_data->exported.isValid() &&
  1273. tag->tagname.compare("groundspeak:cache") == 0) {
  1274. writer->writeTextElement("time",
  1275. wpt->gc_data->exported.toPrettyString());
  1276. }
  1277. writer->writeEndElement();
  1278. }
  1279. if (!tag->parentcdata.isEmpty()) {
  1280. // FIXME: The length check is necessary to get line endings correct in our test suite.
  1281. // Writing the zero length string eats a newline, at least with Qt 4.6.2.
  1282. writer->writeCharacters(tag->parentcdata);
  1283. }
  1284. tag = tag->sibling;
  1285. }
  1286. }
  1287. void free_gpx_extras(xml_tag* tag)
  1288. {
  1289. xml_tag* next = NULL;
  1290. char** ap;
  1291. while (tag) {
  1292. if (tag->child) {
  1293. free_gpx_extras(tag->child);
  1294. }
  1295. if (tag->attributes) {
  1296. ap = tag->attributes;
  1297. while (*ap) {
  1298. xfree(*ap++);
  1299. }
  1300. xfree(tag->attributes);
  1301. }
  1302. next = tag->sibling;
  1303. delete tag;
  1304. tag = next;
  1305. }
  1306. }
  1307. /*
  1308. * Handle the grossness of GPX 1.0 vs. 1.1 handling of linky links.
  1309. */
  1310. static void
  1311. write_gpx_url(const Waypoint* waypointp)
  1312. {
  1313. if (!waypointp->HasUrlLink()) {
  1314. return;
  1315. }
  1316. if (gpx_wversion_num > 10) {
  1317. foreach(UrlLink l, waypointp->GetUrlLinks()) {
  1318. writer->writeStartElement("link");
  1319. writer->writeAttribute("href", l.url_);
  1320. writer->writeOptionalTextElement("text", l.url_link_text_);
  1321. writer->writeOptionalTextElement("type", l.url_link_type_);
  1322. writer->writeEndElement();
  1323. }
  1324. return;
  1325. }
  1326. UrlLink l = waypointp->GetUrlLink();
  1327. writer->writeTextElement("url", QString(urlbase) + QString(l.url_));
  1328. writer->writeOptionalTextElement("urlname", QString(l.url_link_text_));
  1329. }
  1330. /*
  1331. * Write optional accuracy information for a given (way|track|route)point
  1332. * to the output stream. Done in one place since it's common for all three.
  1333. * Order counts.
  1334. */
  1335. static void
  1336. gpx_write_common_acc(const Waypoint* waypointp)
  1337. {
  1338. const char* fix = NULL;
  1339. switch (waypointp->fix) {
  1340. case fix_2d:
  1341. fix = "2d";
  1342. break;
  1343. case fix_3d:
  1344. fix = "3d";
  1345. break;
  1346. case fix_dgps:
  1347. fix = "dgps";
  1348. break;
  1349. case fix_pps:
  1350. fix = "pps";
  1351. break;
  1352. case fix_none:
  1353. fix = "none";
  1354. break;
  1355. /* GPX spec says omit if we don't know. */
  1356. case fix_unknown:
  1357. default:
  1358. break;
  1359. }
  1360. if (fix) {
  1361. writer->writeTextElement("fix", fix);
  1362. }
  1363. if (waypointp->sat > 0) {
  1364. writer->writeTextElement("sat", QString::number(waypointp->sat));
  1365. }
  1366. if (waypointp->hdop) {
  1367. writer->writeTextElement("hdop", toString(waypointp->hdop));
  1368. }
  1369. if (waypointp->vdop) {
  1370. writer->writeTextElement("vdop", toString(waypointp->vdop));
  1371. }
  1372. if (waypointp->pdop) {
  1373. writer->writeTextElement("pdop", toString(waypointp->pdop));
  1374. }
  1375. /* TODO: ageofdgpsdata should go here */
  1376. /* TODO: dgpsid should go here */
  1377. }
  1378. static void
  1379. gpx_write_common_position(const Waypoint* waypointp, const gpx_point_type point_type)
  1380. {
  1381. if (waypointp->altitude != unknown_alt) {
  1382. writer->writeTextElement("ele", QString::number(waypointp->altitude, 'f', 6));
  1383. }
  1384. QString t = waypointp->CreationTimeXML();
  1385. writer->writeOptionalTextElement("time", t);
  1386. if (gpxpt_track==point_type && 10 == gpx_wversion_num) {
  1387. /* These were accidentally removed from 1.1, and were only a part of trkpts in 1.0 */
  1388. if WAYPT_HAS(waypointp, course) {
  1389. writer->writeTextElement("course", toString(waypointp->course));
  1390. }
  1391. if WAYPT_HAS(waypointp, speed) {
  1392. writer->writeTextElement("speed", toString(waypointp->speed));
  1393. }
  1394. }
  1395. /* TODO: magvar should go here */
  1396. if (WAYPT_HAS(waypointp, geoidheight)) {
  1397. writer->writeOptionalTextElement("geoidheight",QString::number(waypointp->geoidheight, 'f', 1));
  1398. }
  1399. }
  1400. static void
  1401. gpx_write_common_extensions(const Waypoint* waypointp, const gpx_point_type point_type)
  1402. {
  1403. // gpx version we are writing is >= 1.1.
  1404. if ((opt_humminbirdext && (WAYPT_HAS(waypointp, depth) || WAYPT_HAS(waypointp, temperature))) ||
  1405. (opt_garminext && gpxpt_waypoint==point_type && (WAYPT_HAS(waypointp, proximity) || WAYPT_HAS(waypointp, temperature) || WAYPT_HAS(waypointp, depth))) ||
  1406. (opt_garminext && gpxpt_track==point_type && (WAYPT_HAS(waypointp, temperature) || WAYPT_HAS(waypointp, depth) || waypointp->heartrate != 0 || waypointp->cadence != 0))) {
  1407. writer->writeStartElement("extensions");
  1408. if (opt_humminbirdext) {
  1409. if (WAYPT_HAS(waypointp, depth)) {
  1410. writer->writeTextElement("h:depth", toString(waypointp->depth * 100.0));
  1411. }
  1412. if (WAYPT_HAS(waypointp, temperature)) {
  1413. writer->writeTextElement("h:temperature", toString(waypointp->temperature));
  1414. }
  1415. }
  1416. if (opt_garminext) {
  1417. // Although not required by the schema we assume that gpxx:WaypointExtension must be a child of gpx:wpt.
  1418. // Although not required by the schema we assume that gpxx:RoutePointExtension must be a child of gpx:rtept.
  1419. // Although not required by the schema we assume that gpxx:TrackPointExtension must be a child of gpx:trkpt.
  1420. // Although not required by the schema we assume that gpxtpx:TrackPointExtension must be a child of gpx:trkpt.
  1421. switch (point_type) {
  1422. case gpxpt_waypoint:
  1423. if (WAYPT_HAS(waypointp, proximity) || WAYPT_HAS(waypointp, temperature) || WAYPT_HAS(waypointp, depth)) {
  1424. writer->writeStartElement("gpxx:WaypointExtension");
  1425. if (WAYPT_HAS(waypointp, proximity)) {
  1426. writer->writeTextElement("gpxx:Proximity", toString(waypointp->proximity));
  1427. }
  1428. if (WAYPT_HAS(waypointp, temperature)) {
  1429. writer->writeTextElement("gpxx:Temperature", toString(waypointp->temperature));
  1430. }
  1431. if (WAYPT_HAS(waypointp, depth)) {
  1432. writer->writeTextElement("gpxx:Depth", toString(waypointp->depth));
  1433. }
  1434. writer->writeEndElement(); // "gpxx:WaypointExtension"
  1435. }
  1436. break;
  1437. case gpxpt_route:
  1438. /* we don't have any appropriate data for the children of gpxx:RoutePointExtension */
  1439. break;
  1440. case gpxpt_track:
  1441. if (WAYPT_HAS(waypointp, temperature) || WAYPT_HAS(waypointp, depth) || waypointp->heartrate != 0 || waypointp->cadence != 0) {
  1442. // gpxtpx:TrackPointExtension is a replacement for gpxx:TrackPointExtension.
  1443. writer->writeStartElement("gpxtpx:TrackPointExtension");
  1444. if (WAYPT_HAS(waypointp, temperature)) {
  1445. writer->writeTextElement("gpxtpx:atemp", toString(waypointp->temperature));
  1446. }
  1447. if (WAYPT_HAS(waypointp, depth)) {
  1448. writer->writeTextElement("gpxtpx:depth", toString(waypointp->depth));
  1449. }
  1450. if (waypointp->heartrate != 0) {
  1451. writer->writeTextElement("gpxtpx:hr", QString::number(waypointp->heartrate));
  1452. }
  1453. if (waypointp->cadence != 0) {
  1454. writer->writeTextElement("gpxtpx:cad", QString::number(waypointp->cadence));
  1455. }
  1456. writer->writeEndElement(); // "gpxtpx:TrackPointExtension"
  1457. }
  1458. break;
  1459. }
  1460. }
  1461. writer->writeEndElement(); // "extensions"
  1462. }
  1463. }
  1464. static void
  1465. gpx_write_common_description(const Waypoint* waypointp, QString oname)
  1466. {
  1467. writer->writeOptionalTextElement("name", oname);
  1468. writer->writeOptionalTextElement("cmt", waypointp->description);
  1469. if (!waypointp->notes.isEmpty()) {
  1470. writer->writeTextElement("desc", waypointp->notes);
  1471. } else {
  1472. writer->writeOptionalTextElement("desc", waypointp->description);
  1473. }
  1474. /* TODO: src should go here */
  1475. write_gpx_url(waypointp);
  1476. writer->writeOptionalTextElement("sym", waypointp->icon_descr);
  1477. /* TODO: type should go here */
  1478. }
  1479. static void
  1480. gpx_waypt_pr(const Waypoint* waypointp)
  1481. {
  1482. QString oname;
  1483. fs_xml* fs_gpx;
  1484. garmin_fs_t* gmsd; /* gARmIN sPECIAL dATA */
  1485. writer->writeStartElement("wpt");
  1486. writer->writeAttribute("lat", toString(waypointp->latitude));
  1487. writer->writeAttribute("lon", toString(waypointp->longitude));
  1488. oname = global_opts.synthesize_shortnames ?
  1489. mkshort_from_wpt(mkshort_handle, waypointp) :
  1490. waypointp->shortname;
  1491. gpx_write_common_position(waypointp, gpxpt_waypoint);
  1492. gpx_write_common_description(waypointp, oname);
  1493. gpx_write_common_acc(waypointp);
  1494. if (!(opt_humminbirdext || opt_garminext)) {
  1495. fs_gpx = (fs_xml*)fs_chain_find(waypointp->fs, FS_GPX);
  1496. gmsd = GMSD_FIND(waypointp);
  1497. if (fs_gpx) {
  1498. if (! gmsd) {
  1499. fprint_xml_chain(fs_gpx->tag, waypointp);
  1500. }
  1501. }
  1502. if (gmsd && (gpx_wversion_num > 10)) {
  1503. /* MapSource doesn't accepts extensions from 1.0 */
  1504. garmin_fs_xml_fprint(waypointp, writer);
  1505. }
  1506. } else {
  1507. gpx_write_common_extensions(waypointp, gpxpt_waypoint);
  1508. }
  1509. writer->writeEndElement();
  1510. }
  1511. static void
  1512. gpx_track_hdr(const route_head* rte)
  1513. {
  1514. fs_xml* fs_gpx;
  1515. current_trk_head = rte;
  1516. writer->writeStartElement("trk");
  1517. writer->writeOptionalTextElement("name", rte->rte_name);
  1518. writer->writeOptionalTextElement("desc", rte->rte_desc);
  1519. if (rte->rte_num) {
  1520. writer->writeTextElement("number", QString::number(rte->rte_num));
  1521. }
  1522. if (gpx_wversion_num > 10) {
  1523. if (!(opt_humminbirdext || opt_garminext)) {
  1524. fs_gpx = (fs_xml*)fs_chain_find(rte->fs, FS_GPX);
  1525. if (fs_gpx) {
  1526. fprint_xml_chain(fs_gpx->tag, NULL);
  1527. }
  1528. } else if (opt_garminext) {
  1529. if (rte->line_color.bbggrr > unknown_color) {
  1530. int ci = gt_color_index_by_rgb(rte->line_color.bbggrr);
  1531. if (ci > 0) {
  1532. writer->writeStartElement("extensions");
  1533. writer->writeStartElement("gpxx:TrackExtension");
  1534. writer->writeTextElement("gpxx:DisplayColor", QString("%1")
  1535. .arg(gt_color_name(ci)));
  1536. writer->writeEndElement(); // Close gpxx:TrackExtension tag
  1537. writer->writeEndElement(); // Close extensions tag
  1538. }
  1539. }
  1540. }
  1541. }
  1542. }
  1543. static void
  1544. gpx_track_disp(const Waypoint* waypointp)
  1545. {
  1546. fs_xml* fs_gpx;
  1547. int first_in_trk;
  1548. first_in_trk = waypointp->Q.prev == &current_trk_head->waypoint_list;
  1549. if (waypointp->wpt_flags.new_trkseg) {
  1550. if (!first_in_trk) {
  1551. writer->writeEndElement();
  1552. }
  1553. writer->writeStartElement("trkseg");
  1554. }
  1555. writer->writeStartElement("trkpt");
  1556. writer->writeAttribute("lat", toString(waypointp->latitude));
  1557. writer->writeAttribute("lon", toString(waypointp->longitude));
  1558. gpx_write_common_position(waypointp, gpxpt_track);
  1559. /* GPX doesn't require a name on output, so if we made one up
  1560. * on input, we might as well say nothing.
  1561. */
  1562. QString oname;
  1563. oname = global_opts.synthesize_shortnames ?
  1564. mkshort_from_wpt(mkshort_handle, waypointp) :
  1565. waypointp->shortname;
  1566. gpx_write_common_description(waypointp,
  1567. waypointp->wpt_flags.shortname_is_synthetic ?
  1568. NULL : oname);
  1569. gpx_write_common_acc(waypointp);
  1570. if (!(opt_humminbirdext || opt_garminext)) {
  1571. fs_gpx = (fs_xml*)fs_chain_find(waypointp->fs, FS_GPX);
  1572. if (fs_gpx) {
  1573. fprint_xml_chain(fs_gpx->tag, waypointp);
  1574. }
  1575. } else {
  1576. gpx_write_common_extensions(waypointp, gpxpt_track);
  1577. }
  1578. writer->writeEndElement();
  1579. }
  1580. static void
  1581. gpx_track_tlr(const route_head*)
  1582. {
  1583. if (!QUEUE_EMPTY(&current_trk_head->waypoint_list)) {
  1584. writer->writeEndElement();
  1585. }
  1586. writer->writeEndElement();
  1587. current_trk_head = NULL;
  1588. }
  1589. static
  1590. void gpx_track_pr()
  1591. {
  1592. track_disp_all(gpx_track_hdr, gpx_track_tlr, gpx_track_disp);
  1593. }
  1594. static void
  1595. gpx_route_hdr(const route_head* rte)
  1596. {
  1597. fs_xml* fs_gpx;
  1598. writer->writeStartElement("rte");
  1599. writer->writeOptionalTextElement("name", rte->rte_name);
  1600. writer->writeOptionalTextElement("desc", rte->rte_desc);
  1601. if (rte->rte_num) {
  1602. writer->writeTextElement("number", QString::number(rte->rte_num));
  1603. }
  1604. if (gpx_wversion_num > 10) {
  1605. if (!(opt_humminbirdext || opt_garminext)) {
  1606. fs_gpx = (fs_xml*)fs_chain_find(rte->fs, FS_GPX);
  1607. if (fs_gpx) {
  1608. fprint_xml_chain(fs_gpx->tag, NULL);
  1609. }
  1610. } else if (opt_garminext) {
  1611. if (rte->line_color.bbggrr > unknown_color) {
  1612. int ci = gt_color_index_by_rgb(rte->line_color.bbggrr);
  1613. if (ci > 0) {
  1614. writer->writeStartElement("extensions");
  1615. writer->writeStartElement("gpxx:RouteExtension");
  1616. // FIXME: the value to use for IsAutoNamed is questionable.
  1617. writer->writeTextElement("gpxx:IsAutoNamed", rte->rte_name.isEmpty()? "true" : "false"); // Required element
  1618. writer->writeTextElement("gpxx:DisplayColor", QString("%1")
  1619. .arg(gt_color_name(ci)));
  1620. writer->writeEndElement(); // Close gpxx:RouteExtension tag
  1621. writer->writeEndElement(); // Close extensions tag
  1622. }
  1623. }
  1624. }
  1625. }
  1626. }
  1627. static void
  1628. gpx_route_disp(const Waypoint* waypointp)
  1629. {
  1630. QString oname;
  1631. fs_xml* fs_gpx;
  1632. writer->writeStartElement("rtept");
  1633. writer->writeAttribute("lat", toString(waypointp->latitude));
  1634. writer->writeAttribute("lon", toString(waypointp->longitude));
  1635. oname = global_opts.synthesize_shortnames ?
  1636. mkshort_from_wpt(mkshort_handle, waypointp) :
  1637. waypointp->shortname;
  1638. gpx_write_common_position(waypointp, gpxpt_route);
  1639. gpx_write_common_description(waypointp, oname);
  1640. gpx_write_common_acc(waypointp);
  1641. if (!(opt_humminbirdext || opt_garminext)) {
  1642. fs_gpx = (fs_xml*)fs_chain_find(waypointp->fs, FS_GPX);
  1643. if (fs_gpx) {
  1644. fprint_xml_chain(fs_gpx->tag, waypointp);
  1645. }
  1646. } else {
  1647. gpx_write_common_extensions(waypointp, gpxpt_route);
  1648. }
  1649. writer->writeEndElement();
  1650. }
  1651. static void
  1652. gpx_route_tlr(const route_head*)
  1653. {
  1654. writer->writeEndElement(); // Close rte tag.
  1655. }
  1656. static
  1657. void gpx_route_pr()
  1658. {
  1659. /* output routes */
  1660. route_disp_all(gpx_route_hdr, gpx_route_tlr, gpx_route_disp);
  1661. }
  1662. static void
  1663. gpx_waypt_bound_calc(const Waypoint* waypointp)
  1664. {
  1665. waypt_add_to_bounds(&all_bounds, waypointp);
  1666. }
  1667. static void
  1668. gpx_write_bounds(void)
  1669. {
  1670. waypt_init_bounds(&all_bounds);
  1671. waypt_disp_all(gpx_waypt_bound_calc);
  1672. route_disp_all(NULL, NULL, gpx_waypt_bound_calc);
  1673. track_disp_all(NULL, NULL, gpx_waypt_bound_calc);
  1674. if (waypt_bounds_valid(&all_bounds)) {
  1675. writer->writeStartElement("bounds");
  1676. writer->writeAttribute("minlat", toString(all_bounds.min_lat));
  1677. writer->writeAttribute("minlon", toString(all_bounds.min_lon));
  1678. writer->writeAttribute("maxlat", toString(all_bounds.max_lat));
  1679. writer->writeAttribute("maxlon", toString(all_bounds.max_lon));
  1680. writer->writeEndElement();
  1681. }
  1682. }
  1683. static void
  1684. gpx_write(void)
  1685. {
  1686. gpx_reset_short_handle();
  1687. waypt_disp_all(gpx_waypt_pr);
  1688. gpx_reset_short_handle();
  1689. gpx_route_pr();
  1690. gpx_reset_short_handle();
  1691. gpx_track_pr();
  1692. writer->writeEndElement(); // Close gpx tag.
  1693. }
  1694. static void
  1695. gpx_free_gpx_global(void)
  1696. {
  1697. gpx_rm_from_global(&gpx_global->name);
  1698. gpx_rm_from_global(&gpx_global->desc);
  1699. gpx_rm_from_global(&gpx_global->author);
  1700. gpx_rm_from_global(&gpx_global->email);
  1701. gpx_rm_from_global(&gpx_global->url);
  1702. gpx_rm_from_global(&gpx_global->urlname);
  1703. gpx_rm_from_global(&gpx_global->keywords);
  1704. xfree(gpx_global);
  1705. }
  1706. static void
  1707. gpx_exit(void)
  1708. {
  1709. gpx_version.clear();
  1710. gpx_namespace_attribute.clear();
  1711. if (gpx_global) {
  1712. gpx_free_gpx_global();
  1713. gpx_global = NULL;
  1714. }
  1715. }
  1716. static
  1717. arglist_t gpx_args[] = {
  1718. {
  1719. "snlen", &snlen, "Length of generated shortnames",
  1720. "32", ARGTYPE_INT, "1", NULL, NULL
  1721. },
  1722. {
  1723. "suppresswhite", &suppresswhite,
  1724. "No whitespace in generated shortnames",
  1725. NULL, ARGTYPE_BOOL, ARG_NOMINMAX, NULL
  1726. },
  1727. {
  1728. "logpoint", &opt_logpoint,
  1729. "Create waypoints from geocache log entries",
  1730. NULL, ARGTYPE_BOOL, ARG_NOMINMAX, NULL
  1731. },
  1732. {
  1733. "urlbase", &urlbase, "Base URL for link tag in output",
  1734. NULL, ARGTYPE_STRING, ARG_NOMINMAX, NULL
  1735. },
  1736. {
  1737. "gpxver", &gpx_wversion, "Target GPX version for output",
  1738. NULL, ARGTYPE_STRING, ARG_NOMINMAX, NULL
  1739. },
  1740. {
  1741. "humminbirdextensions", &opt_humminbirdext,
  1742. "Add info (depth) as Humminbird extension",
  1743. NULL, ARGTYPE_BOOL, ARG_NOMINMAX, NULL
  1744. },
  1745. {
  1746. "garminextensions", &opt_garminext,
  1747. "Add info (depth) as Garmin extension",
  1748. NULL, ARGTYPE_BOOL, ARG_NOMINMAX, NULL
  1749. },
  1750. ARG_TERMINATOR
  1751. };
  1752. ff_vecs_t gpx_vecs = {
  1753. ff_type_file,
  1754. FF_CAP_RW_ALL,
  1755. gpx_rd_init,
  1756. gpx_wr_init,
  1757. gpx_rd_deinit,
  1758. gpx_wr_deinit,
  1759. gpx_read,
  1760. gpx_write,
  1761. gpx_exit,
  1762. gpx_args,
  1763. CET_CHARSET_UTF8, 0, /* non-fixed to create non UTF-8 XML's for testing | CET-REVIEW */
  1764. NULL_POS_OPS,
  1765. NULL,
  1766. };