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.

kml.cc 66KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205
  1. /*
  2. Support for Google Earth & Keyhole "kml" format.
  3. Copyright (C) 2005-2013 Robert Lipe, robertlipe+source@gpsbabel.org
  4. Updates by Andrew Kirmse, akirmse at google.com
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software
  15. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  16. */
  17. #ifdef __WIN32__
  18. # include <windows.h>
  19. #endif
  20. #include "defs.h"
  21. #include "xmlgeneric.h"
  22. #include "grtcirc.h"
  23. #include "src/core/file.h"
  24. #include "src/core/xmlstreamwriter.h"
  25. #include "src/core/xmltag.h"
  26. #include <QtCore/QRegExp>
  27. #include <QtCore/QXmlStreamAttributes>
  28. #include <math.h>
  29. #include <stdlib.h>
  30. #include <stdio.h>
  31. // options
  32. static char* opt_deficon = NULL;
  33. static char* opt_export_lines = NULL;
  34. static char* opt_export_points = NULL;
  35. static char* opt_export_track = NULL;
  36. static char* opt_line_width = NULL;
  37. static char* opt_line_color = NULL;
  38. static char* opt_floating = NULL;
  39. static char* opt_extrude = NULL;
  40. static char* opt_trackdata = NULL;
  41. static char* opt_trackdirection = NULL;
  42. static char* opt_units = NULL;
  43. static char* opt_labels = NULL;
  44. static char* opt_max_position_points = NULL;
  45. static char* opt_rotate_colors = NULL;
  46. static char* opt_precision = NULL;
  47. static int export_lines;
  48. static int export_points;
  49. static int export_track;
  50. static int floating;
  51. static int extrude;
  52. static int trackdata;
  53. static int trackdirection;
  54. static int max_position_points;
  55. static int rotate_colors;
  56. static int line_width;
  57. static int html_encrypt;
  58. static int precision;
  59. static Waypoint* wpt_tmp;
  60. static int wpt_tmp_queued;
  61. static QString posnfilename;
  62. static QString posnfilenametmp;
  63. static route_head* gx_trk_head;
  64. static QList<gpsbabel::DateTime>* gx_trk_times;
  65. static gpsbabel::File* oqfile;
  66. static gpsbabel::XmlStreamWriter* writer;
  67. typedef enum {
  68. kmlpt_unknown,
  69. kmlpt_waypoint,
  70. kmlpt_track,
  71. kmlpt_route,
  72. kmlpt_multitrack,
  73. kmlpt_other
  74. } kml_point_type;
  75. static int realtime_positioning;
  76. static bounds kml_bounds;
  77. static gpsbabel::DateTime kml_time_min;
  78. static gpsbabel::DateTime kml_time_max;
  79. #define AUTOFORMATTING_OFF(AF) bool AF=writer->autoFormatting(); writer->setAutoFormatting(false);
  80. #define AUTOFORMATTING_RESTORE(AF) writer->setAutoFormatting(af);
  81. #define DEFAULT_PRECISION "6"
  82. // Icons provided and hosted by Google. Used with permission.
  83. #define ICON_BASE "http://earth.google.com/images/kml-icons/"
  84. // Multitrack ids to correlate Schema to SchemaData
  85. static const char kmt_heartrate[] = "heartrate";
  86. static const char kmt_cadence[] = "cadence";
  87. static const char kmt_temperature[] = "temperature";
  88. static const char kmt_depth[] = "depth";
  89. static const char kmt_power[] = "power";
  90. static
  91. arglist_t kml_args[] = {
  92. {"deficon", &opt_deficon, "Default icon name", NULL, ARGTYPE_STRING, ARG_NOMINMAX },
  93. {
  94. "lines", &opt_export_lines,
  95. "Export linestrings for tracks and routes",
  96. "1", ARGTYPE_BOOL, ARG_NOMINMAX
  97. },
  98. {
  99. "points", &opt_export_points,
  100. "Export placemarks for tracks and routes",
  101. "1", ARGTYPE_BOOL, ARG_NOMINMAX
  102. },
  103. {
  104. "line_width", &opt_line_width,
  105. "Width of lines, in pixels",
  106. "6", ARGTYPE_INT, ARG_NOMINMAX
  107. },
  108. {
  109. "line_color", &opt_line_color,
  110. "Line color, specified in hex AABBGGRR",
  111. "99ffac59", ARGTYPE_STRING, ARG_NOMINMAX
  112. },
  113. {
  114. "floating", &opt_floating,
  115. "Altitudes are absolute and not clamped to ground",
  116. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  117. },
  118. {
  119. "extrude", &opt_extrude,
  120. "Draw extrusion line from trackpoint to ground",
  121. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  122. },
  123. {
  124. "track", &opt_export_track,
  125. "Write KML track (default = 0)",
  126. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  127. },
  128. {
  129. "trackdata", &opt_trackdata,
  130. "Include extended data for trackpoints (default = 1)",
  131. "1", ARGTYPE_BOOL, ARG_NOMINMAX
  132. },
  133. {
  134. "trackdirection", &opt_trackdirection,
  135. "Indicate direction of travel in track icons (default = 0)",
  136. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  137. },
  138. {
  139. "units", &opt_units,
  140. "Units used when writing comments ('s'tatute, 'm'etric,' 'n'autical, 'a'viation)",
  141. "s", ARGTYPE_STRING, ARG_NOMINMAX
  142. },
  143. {
  144. "labels", &opt_labels,
  145. "Display labels on track and routepoints (default = 1)",
  146. "1", ARGTYPE_BOOL, ARG_NOMINMAX
  147. },
  148. {
  149. "max_position_points", &opt_max_position_points,
  150. "Retain at most this number of position points (0 = unlimited)",
  151. "0", ARGTYPE_INT, ARG_NOMINMAX
  152. },
  153. {
  154. "rotate_colors", &opt_rotate_colors,
  155. "Rotate colors for tracks and routes (default automatic)",
  156. NULL, ARGTYPE_FLOAT, "0", "360"
  157. },
  158. {
  159. "prec", &opt_precision,
  160. "Precision of coordinates, number of decimals",
  161. DEFAULT_PRECISION, ARGTYPE_INT, ARG_NOMINMAX
  162. },
  163. ARG_TERMINATOR
  164. };
  165. static
  166. struct {
  167. int freshness;
  168. const char* icon;
  169. } kml_tracking_icons[] = {
  170. { 60, ICON_BASE "youarehere-60.png" }, // Red
  171. { 30, ICON_BASE "youarehere-30.png" }, // Yellow
  172. { 0, ICON_BASE "youarehere-0.png" }, // Green
  173. };
  174. #define ICON_NOSAT ICON_BASE "youarehere-warning.png";
  175. #define ICON_WPT "http://maps.google.com/mapfiles/kml/pal4/icon61.png"
  176. #define ICON_TRK ICON_BASE "track-directional/track-none.png"
  177. #define ICON_RTE ICON_BASE "track-directional/track-none.png"
  178. #define ICON_MULTI_TRK ICON_BASE "track-directional/track-0.png"
  179. #define ICON_DIR ICON_BASE "track-directional/track-%1.png" // format string where next arg is rotational degrees.
  180. static struct {
  181. float seq;
  182. float step;
  183. gb_color color;
  184. } kml_color_sequencer;
  185. #define KML_COLOR_LIMIT 204 /* allowed range [0,255] */
  186. #define MYNAME "kml"
  187. static void kml_init_color_sequencer(unsigned int steps_per_rev)
  188. {
  189. if (rotate_colors) {
  190. float color_step = atof(opt_rotate_colors);
  191. if (color_step > 0.0f) {
  192. // step around circle by given number of degrees for each track(route)
  193. kml_color_sequencer.step = ((float)KML_COLOR_LIMIT) * 6.0f * color_step / 360.0f;
  194. } else {
  195. // one cycle around circle for all the tracks(routes)
  196. kml_color_sequencer.step = ((float)KML_COLOR_LIMIT) * 6.0f / ((float)steps_per_rev);
  197. }
  198. kml_color_sequencer.color.opacity=255;
  199. kml_color_sequencer.seq = 0.0f;
  200. }
  201. }
  202. static void kml_step_color(void)
  203. {
  204. int color_seq;
  205. // Map kml_color_sequencer.seq to an integer in the range [0, KML_COLOR_LIMIT*6).
  206. // Note that color_seq may be outside this range if the cast from float to int fails.
  207. color_seq = ((int) kml_color_sequencer.seq) % (KML_COLOR_LIMIT * 6);
  208. if (global_opts.debug_level >= 1) {
  209. printf(MYNAME ": kml_color_sequencer seq %f %d, step %f\n",kml_color_sequencer.seq, color_seq, kml_color_sequencer.step);
  210. }
  211. if ((color_seq >= (0*KML_COLOR_LIMIT)) && (color_seq < (1*KML_COLOR_LIMIT))) {
  212. kml_color_sequencer.color.bbggrr = (0)<<16 | (color_seq)<<8 | (KML_COLOR_LIMIT);
  213. } else if ((color_seq >= (1*KML_COLOR_LIMIT)) && (color_seq < (2*KML_COLOR_LIMIT))) {
  214. kml_color_sequencer.color.bbggrr = (0)<<16 | (KML_COLOR_LIMIT)<<8 | (2*KML_COLOR_LIMIT-color_seq);
  215. } else if ((color_seq >= (2*KML_COLOR_LIMIT)) && (color_seq < (3*KML_COLOR_LIMIT))) {
  216. kml_color_sequencer.color.bbggrr = (color_seq-2*KML_COLOR_LIMIT)<<16 | (KML_COLOR_LIMIT)<<8 | (0);
  217. } else if ((color_seq >= (3*KML_COLOR_LIMIT)) && (color_seq < (4*KML_COLOR_LIMIT))) {
  218. kml_color_sequencer.color.bbggrr = (KML_COLOR_LIMIT)<<16 | (4*KML_COLOR_LIMIT-color_seq)<<8 | (0);
  219. } else if ((color_seq >= (4*KML_COLOR_LIMIT)) && (color_seq < (5*KML_COLOR_LIMIT))) {
  220. kml_color_sequencer.color.bbggrr = (KML_COLOR_LIMIT)<<16 | (0)<<8 | (color_seq-4*KML_COLOR_LIMIT);
  221. } else if ((color_seq >= (5*KML_COLOR_LIMIT)) && (color_seq < (6*KML_COLOR_LIMIT))) {
  222. kml_color_sequencer.color.bbggrr = (6*KML_COLOR_LIMIT-color_seq)<<16 | (0)<<8 | (KML_COLOR_LIMIT);
  223. } else { // should not occur, but to be safe generate a legal color.
  224. warning(MYNAME ": Error in color conversion - using default color.\n");
  225. kml_color_sequencer.color.bbggrr = (102)<<16 | (102)<<8 | (102);
  226. }
  227. // compute next color.
  228. kml_color_sequencer.seq = kml_color_sequencer.seq + kml_color_sequencer.step;
  229. }
  230. static xg_callback wpt_s, wpt_e;
  231. static xg_callback wpt_name, wpt_desc, wpt_coord, wpt_icon, trk_coord, wpt_time;
  232. static xg_callback gx_trk_s, gx_trk_e;
  233. static xg_callback gx_trk_when, gx_trk_coord;
  234. static
  235. xg_tag_mapping kml_map[] = {
  236. { wpt_s, cb_start, "/Placemark" },
  237. { wpt_e, cb_end, "/Placemark" },
  238. { wpt_name, cb_cdata, "/Placemark/name" },
  239. { wpt_desc, cb_cdata, "/Placemark/description" },
  240. { wpt_time, cb_cdata, "/Placemark/TimeStamp/when" },
  241. // Alias for above used in KML 2.0
  242. { wpt_time, cb_cdata, "/Placemark/TimeInstant/timePosition" },
  243. { wpt_coord, cb_cdata, "/Placemark/Point/coordinates" },
  244. { wpt_icon, cb_cdata, "/Placemark/Style/Icon/href" },
  245. { trk_coord, cb_cdata, "/Placemark/MultiGeometry/LineString/coordinates" },
  246. { trk_coord, cb_cdata, "/Placemark/GeometryCollection/LineString/coordinates" },
  247. { trk_coord, cb_cdata, "/Placemark/Polygon/outerBoundaryIs/LinearRing/coordinates" },
  248. { trk_coord, cb_cdata, "/Placemark/LineString/coordinates" },
  249. { gx_trk_s, cb_start, "/Placemark/*gx:Track" },
  250. { gx_trk_e, cb_end, "/Placemark/*gx:Track" },
  251. { gx_trk_when, cb_cdata, "/Placemark/*gx:Track/when" },
  252. { gx_trk_coord, cb_cdata, "/Placemark/*gx:Track/gx:coord" },
  253. { NULL, (xg_cb_type) 0, NULL }
  254. };
  255. static
  256. const char* kml_tags_to_ignore[] = {
  257. "kml",
  258. "Document",
  259. "Folder",
  260. NULL,
  261. };
  262. void wpt_s(xg_string, const QXmlStreamAttributes*)
  263. {
  264. if (wpt_tmp) {
  265. fatal(MYNAME ": wpt_s: invalid kml file\n");
  266. }
  267. wpt_tmp = new Waypoint;
  268. wpt_tmp_queued = 0;
  269. }
  270. void wpt_e(xg_string, const QXmlStreamAttributes*)
  271. {
  272. if (!wpt_tmp) {
  273. fatal(MYNAME ": wpt_e: invalid kml file\n");
  274. }
  275. if (wpt_tmp_queued) {
  276. waypt_add(wpt_tmp);
  277. wpt_tmp = NULL;
  278. } else {
  279. delete wpt_tmp;
  280. wpt_tmp = NULL;
  281. }
  282. wpt_tmp_queued = 0;
  283. }
  284. void wpt_name(xg_string args, const QXmlStreamAttributes*)
  285. {
  286. if (!wpt_tmp) {
  287. fatal(MYNAME ": wpt_name: invalid kml file\n");
  288. }
  289. wpt_tmp->shortname = args;
  290. }
  291. void wpt_desc(const QString& args, const QXmlStreamAttributes*)
  292. {
  293. if (!wpt_tmp) {
  294. fatal(MYNAME ": wpt_desc: invalid kml file\n");
  295. }
  296. wpt_tmp->description += args.trimmed();
  297. }
  298. void wpt_time(xg_string args, const QXmlStreamAttributes*)
  299. {
  300. if (!wpt_tmp) {
  301. fatal(MYNAME ": wpt_time: invalid kml file\n");
  302. }
  303. wpt_tmp->SetCreationTime(xml_parse_time(args));
  304. }
  305. void wpt_coord(const QString& args, const QXmlStreamAttributes*)
  306. {
  307. int n = 0;
  308. double lat, lon, alt;
  309. if (! wpt_tmp) {
  310. return;
  311. }
  312. // Alt is actually optional.
  313. n = sscanf(CSTRc(args), "%lf,%lf,%lf", &lon, &lat, &alt);
  314. if (n >= 2) {
  315. wpt_tmp->latitude = lat;
  316. wpt_tmp->longitude = lon;
  317. }
  318. if (n == 3) {
  319. wpt_tmp->altitude = alt;
  320. }
  321. wpt_tmp_queued = 1;
  322. }
  323. void wpt_icon(xg_string args, const QXmlStreamAttributes*)
  324. {
  325. if (wpt_tmp) {
  326. wpt_tmp->icon_descr = args;
  327. }
  328. }
  329. void trk_coord(xg_string args, const QXmlStreamAttributes*)
  330. {
  331. int consumed = 0;
  332. double lat, lon, alt;
  333. Waypoint* trkpt;
  334. int n = 0;
  335. route_head* trk_head = route_head_alloc();
  336. QString iargs = args;
  337. if (wpt_tmp && !wpt_tmp->shortname.isEmpty()) {
  338. trk_head->rte_name = wpt_tmp->shortname;
  339. }
  340. track_add_head(trk_head);
  341. while ((n = sscanf(CSTRc(iargs), "%lf,%lf,%lf%n", &lon, &lat, &alt, &consumed)) > 0) {
  342. trkpt = new Waypoint;
  343. trkpt->latitude = lat;
  344. trkpt->longitude = lon;
  345. // Line malformed or two-arg format without alt . Rescan.
  346. if (2 == n) {
  347. sscanf(CSTRc(iargs), "%lf,%lf%n", &lon, &lat, &consumed);
  348. }
  349. if (3 == n) {
  350. trkpt->altitude = alt;
  351. }
  352. track_add_wpt(trk_head, trkpt);
  353. iargs = iargs.mid(consumed);
  354. }
  355. }
  356. void gx_trk_s(xg_string, const QXmlStreamAttributes*)
  357. {
  358. gx_trk_head = route_head_alloc();
  359. if (wpt_tmp && !wpt_tmp->shortname.isEmpty()) {
  360. gx_trk_head->rte_name = wpt_tmp->shortname;
  361. }
  362. if (wpt_tmp && !wpt_tmp->description.isEmpty()) {
  363. gx_trk_head->rte_desc = wpt_tmp->description;
  364. }
  365. track_add_head(gx_trk_head);
  366. if (gx_trk_times) {
  367. delete gx_trk_times;
  368. }
  369. gx_trk_times = new QList<gpsbabel::DateTime>;
  370. }
  371. void gx_trk_e(xg_string, const QXmlStreamAttributes*)
  372. {
  373. if (!gx_trk_head->rte_waypt_ct) {
  374. track_del_head(gx_trk_head);
  375. }
  376. delete gx_trk_times;
  377. gx_trk_times = NULL;
  378. }
  379. void gx_trk_when(xg_string args, const QXmlStreamAttributes*)
  380. {
  381. if (! gx_trk_times) {
  382. fatal(MYNAME ": gx_trk_when: invalid kml file\n");
  383. }
  384. gx_trk_times->append(xml_parse_time(args));
  385. }
  386. void gx_trk_coord(xg_string args, const QXmlStreamAttributes*)
  387. {
  388. Waypoint* trkpt;
  389. double lat, lon, alt;
  390. int n;
  391. if (! gx_trk_times || gx_trk_times->isEmpty()) {
  392. fatal(MYNAME ": There were more gx:coord elements than the number of when elements.\n");
  393. }
  394. trkpt = new Waypoint;
  395. trkpt->SetCreationTime(gx_trk_times->takeFirst());
  396. n = sscanf(CSTR(args), "%lf %lf %lf", &lon, &lat, &alt);
  397. // Empty gx_coord elements are allowed to balance the number of when elements,
  398. // but if we get one we will throw away the time as we don't have a location.
  399. // It is not clear that coord elements without altitude are allowed, but our
  400. // writer produces them.
  401. if (0 != n && 2 != n && 3 != n) {
  402. fatal(MYNAME ": gx:coord field decode failure on \"%s\".\n", qPrintable(args));
  403. }
  404. if (n >= 2) {
  405. trkpt->latitude = lat;
  406. trkpt->longitude = lon;
  407. if (n >= 3) {
  408. trkpt->altitude = alt;
  409. }
  410. track_add_wpt(gx_trk_head, trkpt);
  411. } else {
  412. delete trkpt;
  413. }
  414. }
  415. static
  416. void
  417. kml_rd_init(const QString& fname)
  418. {
  419. xml_init(fname, kml_map, NULL);
  420. xml_ignore_tags(kml_tags_to_ignore);
  421. }
  422. static
  423. void
  424. kml_read(void)
  425. {
  426. xml_read();
  427. }
  428. static void
  429. kml_rd_deinit(void)
  430. {
  431. xml_deinit();
  432. }
  433. static void
  434. kml_wr_init(const QString& fname)
  435. {
  436. char u = 's';
  437. waypt_init_bounds(&kml_bounds);
  438. kml_time_min = QDateTime();
  439. kml_time_max = QDateTime();
  440. if (opt_units) {
  441. u = tolower(opt_units[0]);
  442. }
  443. switch (u) {
  444. case 's':
  445. fmt_setunits(units_statute);
  446. break;
  447. case 'm':
  448. fmt_setunits(units_metric);
  449. break;
  450. case 'n':
  451. fmt_setunits(units_nautical);
  452. break;
  453. case 'a':
  454. fmt_setunits(units_aviation);
  455. break;
  456. default:
  457. fatal("Units argument '%s' should be 's' for statute units, 'm' for metric, 'n' for nautical or 'a' for aviation.\n", opt_units);
  458. break;
  459. }
  460. /*
  461. * Reduce race conditions with network read link.
  462. */
  463. oqfile = new gpsbabel::File(fname);
  464. oqfile->open(QIODevice::WriteOnly | QIODevice::Text);
  465. writer = new gpsbabel::XmlStreamWriter(oqfile);
  466. writer->setAutoFormattingIndent(2);
  467. }
  468. /*
  469. * The magic here is to try to ensure that posnfilename is atomically
  470. * updated.
  471. */
  472. static void
  473. kml_wr_position_init(const QString& fname)
  474. {
  475. posnfilename = fname;
  476. posnfilenametmp = QString("%1-").arg(fname);
  477. realtime_positioning = 1;
  478. /*
  479. * 30% of our output file is whitespace. Since parse time
  480. * matters in this mode, turn the pretty formatting off.
  481. */
  482. writer->setAutoFormatting(false);
  483. max_position_points = atoi(opt_max_position_points);
  484. }
  485. static void
  486. kml_wr_deinit(void)
  487. {
  488. writer->writeEndDocument();
  489. delete writer;
  490. writer = NULL;
  491. oqfile->close();
  492. delete oqfile;
  493. oqfile = NULL;
  494. if (!posnfilenametmp.isEmpty()) {
  495. #if __WIN32__
  496. MoveFileExA(qPrintable(posnfilenametmp), qPrintable(posnfilename),
  497. MOVEFILE_REPLACE_EXISTING);
  498. #endif
  499. QFile::rename(posnfilenametmp, posnfilename);
  500. }
  501. }
  502. static void
  503. kml_wr_position_deinit(void)
  504. {
  505. // kml_wr_deinit();
  506. posnfilename.clear();
  507. posnfilenametmp.clear();
  508. }
  509. void
  510. kml_output_linestyle(char* /*color*/, int width)
  511. {
  512. // Style settings for line strings
  513. writer->writeStartElement("LineStyle");
  514. writer->writeTextElement("color", opt_line_color);
  515. writer->writeTextElement("width", QString::number(width));
  516. writer->writeEndElement(); // Close LineStyle tag
  517. }
  518. #define hovertag(h) h ? 'h' : 'n'
  519. static void kml_write_bitmap_style_(const QString& style, const QString& bitmap,
  520. int highlighted, int force_heading)
  521. {
  522. int is_track = style.startsWith("track");
  523. int is_multitrack = style.startsWith("multiTrack");
  524. writer->writeComment(QString(" ") + QString(highlighted ? "Highlighted" : "Normal") + QString(" ") + style + QString(" style "));
  525. writer->writeStartElement("Style");
  526. writer->writeAttribute("id", style + QString("_") + QString(hovertag(highlighted)));
  527. writer->writeStartElement("IconStyle");
  528. if (highlighted) {
  529. writer->writeTextElement("scale", "1.2");
  530. } else {
  531. if (is_track) {
  532. writer->writeTextElement("scale", ".5");
  533. }
  534. }
  535. /* Our icons are pre-rotated, so nail them to the maps. */
  536. if (force_heading) {
  537. writer->writeTextElement("heading", "0");
  538. }
  539. writer->writeStartElement("Icon");
  540. writer->writeTextElement("href", bitmap);
  541. writer->writeEndElement(); // Close Icon tag
  542. writer->writeEndElement(); // Close IconStyle tag
  543. if (is_track && !highlighted) {
  544. writer->writeStartElement("LabelStyle");
  545. writer->writeTextElement("scale", "0");
  546. writer->writeEndElement(); //Close LabelStyle tag
  547. }
  548. if (is_multitrack) {
  549. kml_output_linestyle(opt_line_color,
  550. highlighted ? line_width + 2 :
  551. line_width);
  552. }
  553. writer->writeEndElement(); // Close Style tag
  554. }
  555. /* A wrapper for the above function to emit both a highlighted
  556. * and non-highlighted version of the style to allow the icons
  557. * to magnify slightly on a rollover.
  558. */
  559. static void kml_write_bitmap_style(kml_point_type pt_type, const QString& bitmap,
  560. const QString& customstyle)
  561. {
  562. int force_heading = 0;
  563. QString style;
  564. switch (pt_type) {
  565. case kmlpt_track:
  566. style = "track";
  567. break;
  568. case kmlpt_route:
  569. style = "route";
  570. break;
  571. case kmlpt_waypoint:
  572. style = "waypoint";
  573. break;
  574. case kmlpt_multitrack:
  575. style = "multiTrack";
  576. break;
  577. case kmlpt_other:
  578. style = customstyle;
  579. force_heading = 1;
  580. break;
  581. default:
  582. fatal("kml_output_point: unknown point type");
  583. break;
  584. }
  585. kml_write_bitmap_style_(style, bitmap, 0, force_heading);
  586. kml_write_bitmap_style_(style, bitmap, 1, force_heading);
  587. writer->writeStartElement("StyleMap");
  588. writer->writeAttribute("id", style);
  589. writer->writeStartElement("Pair");
  590. writer->writeTextElement("key", "normal");
  591. writer->writeTextElement("styleUrl", QString("#") + style + QString("_") + QString(hovertag(0)));
  592. writer->writeEndElement(); // Close Pair tag
  593. writer->writeStartElement("Pair");
  594. writer->writeTextElement("key", "highlight");
  595. writer->writeTextElement("styleUrl", QString("#") + style + QString("_") + QString(hovertag(1)));
  596. writer->writeEndElement(); // Close Pair tag
  597. writer->writeEndElement(); // Close StyleMap tag
  598. }
  599. static void kml_output_timestamp(const Waypoint* waypointp)
  600. {
  601. QString time_string = waypointp->CreationTimeXML();
  602. if (!time_string.isEmpty()) {
  603. writer->writeStartElement("TimeStamp");
  604. AUTOFORMATTING_OFF(af); // FIXME: we turn off autoformatting just to match old writer test references.
  605. writer->writeTextElement("when", time_string);
  606. writer->writeEndElement(); // Close TimeStamp tag
  607. AUTOFORMATTING_RESTORE(af);
  608. }
  609. }
  610. static
  611. void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& boldData, const QString& data)
  612. {
  613. hwriter.writeCharacters("\n");
  614. hwriter.writeStartElement("tr");
  615. hwriter.writeStartElement("td");
  616. hwriter.writeTextElement("b", boldData);
  617. hwriter.writeCharacters(data);
  618. hwriter.writeEndElement(); // Close td tag
  619. hwriter.writeEndElement(); // Close tr tag
  620. }
  621. static
  622. void kml_td(gpsbabel::XmlStreamWriter& hwriter, const QString& data)
  623. {
  624. hwriter.writeCharacters("\n");
  625. hwriter.writeStartElement("tr");
  626. hwriter.writeStartElement("td");
  627. hwriter.writeCharacters(data);
  628. hwriter.writeEndElement(); // Close td tag
  629. hwriter.writeEndElement(); // Close tr tag
  630. }
  631. /*
  632. * Output the track summary.
  633. */
  634. static
  635. void kml_output_trkdescription(const route_head* header, computed_trkdata* td)
  636. {
  637. const char* max_alt_units;
  638. double max_alt;
  639. const char* min_alt_units;
  640. double min_alt;
  641. const char* distance_units;
  642. double distance;
  643. if (!td || !trackdata) {
  644. return;
  645. }
  646. QString hstring;
  647. gpsbabel::XmlStreamWriter hwriter(&hstring);
  648. max_alt = fmt_altitude(td->max_alt, &max_alt_units);
  649. min_alt = fmt_altitude(td->min_alt, &min_alt_units);
  650. distance = fmt_distance(td->distance_meters, &distance_units);
  651. writer->writeEmptyElement("snippet");
  652. writer->writeStartElement("description");
  653. hwriter.writeStartElement("table");
  654. if (!header->rte_desc.isEmpty()) {
  655. kml_td(hwriter, "Description", QString(" %1 ").arg(header->rte_desc));
  656. }
  657. kml_td(hwriter, "Distance", QString(" %1 %2 ").arg(QString::number(distance, 'f', 1)).arg(distance_units));
  658. if (td->min_alt != -unknown_alt) {
  659. kml_td(hwriter, "Min Alt", QString(" %1 %2 ").arg(QString::number(min_alt, 'f', 3)).arg(min_alt_units));
  660. }
  661. if (td->max_alt != unknown_alt) {
  662. kml_td(hwriter, "Max Alt", QString(" %1 %2 ").arg(QString::number(max_alt, 'f', 3)).arg(max_alt_units));
  663. }
  664. if (td->min_spd) {
  665. const char* spd_units;
  666. double spd = fmt_speed(td->min_spd, &spd_units);
  667. kml_td(hwriter, "Min Speed", QString(" %1 %2 ").arg(QString::number(spd, 'f', 1)).arg(spd_units));
  668. }
  669. if (td->max_spd) {
  670. const char* spd_units;
  671. double spd = fmt_speed(td->max_spd, &spd_units);
  672. kml_td(hwriter, "Max Speed", QString(" %1 %2 ").arg(QString::number(spd, 'f', 1)).arg(spd_units));
  673. }
  674. if (td->max_spd && td->start && td->end) {
  675. const char* spd_units;
  676. time_t elapsed = td->end - td->start;
  677. double spd = fmt_speed(td->distance_meters / elapsed, &spd_units);
  678. if (spd > 1.0) {
  679. kml_td(hwriter, "Avg Speed", QString(" %1 %2 ").arg(QString::number(spd, 'f', 1)).arg(spd_units));
  680. }
  681. }
  682. if (td->avg_hrt) {
  683. kml_td(hwriter, "Avg Heart Rate", QString(" %1 bpm ").arg(QString::number(td->avg_hrt, 'f', 1)));
  684. }
  685. if (td->min_hrt < td->max_hrt) {
  686. kml_td(hwriter, "Min Heart Rate", QString(" %1 bpm ").arg(QString::number(td->min_hrt)));
  687. }
  688. if (td->max_hrt) {
  689. kml_td(hwriter, "Max Heart Rate", QString(" %1 bpm ").arg(QString::number(td->max_hrt)));
  690. }
  691. if (td->avg_cad) {
  692. kml_td(hwriter, "Avg Cadence", QString(" %1 rpm ").arg(QString::number(td->avg_cad, 'f', 1)));
  693. }
  694. if (td->max_cad) {
  695. kml_td(hwriter, "Max Cadence", QString(" %1 rpm ").arg(QString::number(td->max_cad)));
  696. }
  697. if (td->start && td->end) {
  698. gpsbabel::DateTime t;
  699. t = QDateTime::fromTime_t(td->start);
  700. if (t.isValid()) {
  701. kml_td(hwriter, "Start Time", t.toPrettyString());
  702. }
  703. t = QDateTime::fromTime_t(td->end);
  704. if (t.isValid()) {
  705. kml_td(hwriter, "End Time", t.toPrettyString());
  706. }
  707. }
  708. hwriter.writeCharacters("\n");
  709. hwriter.writeEndElement(); // Close table tag
  710. //hwriter.writeEndDocument(); // FIXME: it seems like we should end the doc but it causes a reference mismatch by adding a final \n
  711. writer->writeCharacters("\n");
  712. writer->writeCDATA(hstring);
  713. writer->writeCharacters("\n");
  714. writer->writeEndElement(); // Close description tag
  715. /* We won't always have times. Garmin saved tracks, for example... */
  716. if (td->start && td->end) {
  717. writer->writeStartElement("TimeSpan");
  718. gpsbabel::DateTime t;
  719. t = QDateTime::fromTime_t(td->start);
  720. writer->writeTextElement("begin", t.toPrettyString());
  721. t = QDateTime::fromTime_t(td->end);
  722. writer->writeTextElement("end", t.toPrettyString());
  723. writer->writeEndElement(); // Close TimeSpan tag
  724. }
  725. }
  726. static
  727. void kml_output_header(const route_head* header, computed_trkdata* td)
  728. {
  729. if (!realtime_positioning) {
  730. writer->writeStartElement("Folder");
  731. }
  732. writer->writeOptionalTextElement("name", header->rte_name);
  733. kml_output_trkdescription(header, td);
  734. if (export_points && header->rte_waypt_ct > 0) {
  735. // Put the points in a subfolder
  736. writer->writeStartElement("Folder");
  737. writer->writeTextElement("name", "Points");
  738. }
  739. }
  740. static
  741. int kml_altitude_known(const Waypoint* waypoint)
  742. {
  743. if (waypoint->altitude == unknown_alt) {
  744. return 0;
  745. }
  746. // We see way more data that's sourceed at 'zero' than is actually
  747. // precisely at 0 MSL.
  748. if (fabs(waypoint->altitude) < 0.01) {
  749. return 0;
  750. }
  751. return 1;
  752. }
  753. static
  754. void kml_write_coordinates(const Waypoint* waypointp)
  755. {
  756. if (kml_altitude_known(waypointp)) {
  757. writer->writeTextElement("coordinates",
  758. QString::number(waypointp->longitude, 'f', precision) + QString(",") +
  759. QString::number(waypointp->latitude, 'f', precision) + QString(",") +
  760. QString::number(waypointp->altitude, 'f', 2)
  761. );
  762. } else {
  763. writer->writeTextElement("coordinates",
  764. QString::number(waypointp->longitude, 'f', precision) + QString(",") +
  765. QString::number(waypointp->latitude, 'f', precision)
  766. );
  767. }
  768. }
  769. /* Rather than a default "top down" view, view from the side to highlight
  770. * topo features.
  771. */
  772. static void kml_output_lookat(const Waypoint* waypointp)
  773. {
  774. writer->writeStartElement("LookAt");
  775. writer->writeTextElement("longitude", QString::number(waypointp->longitude, 'f', precision));
  776. writer->writeTextElement("latitude", QString::number(waypointp->latitude, 'f', precision));
  777. writer->writeTextElement("tilt","66");
  778. writer->writeEndElement(); // Close LookAt tag
  779. }
  780. static void kml_output_positioning(bool tessellate)
  781. {
  782. // These elements must be output as a sequence, i.e. in order.
  783. if (extrude) {
  784. writer->writeTextElement("extrude", "1");
  785. }
  786. if (tessellate) {
  787. writer->writeTextElement("tessellate", "1");
  788. }
  789. if (floating) {
  790. writer->writeTextElement("altitudeMode", "absolute");
  791. }
  792. }
  793. /* Output something interesing when we can for route and trackpoints */
  794. static void kml_output_description(const Waypoint* pt)
  795. {
  796. const char* alt_units;
  797. double alt;
  798. if (!trackdata) {
  799. return;
  800. }
  801. QString hstring;
  802. gpsbabel::XmlStreamWriter hwriter(&hstring);
  803. alt = fmt_altitude(pt->altitude, &alt_units);
  804. writer->writeStartElement("description");
  805. hwriter.writeCharacters("\n");
  806. hwriter.writeStartElement("table");
  807. kml_td(hwriter, QString("Longitude: %1 ").arg(QString::number(pt->longitude, 'f', precision)));
  808. kml_td(hwriter, QString("Latitude: %1 ").arg(QString::number(pt->latitude, 'f', precision)));
  809. if (kml_altitude_known(pt)) {
  810. kml_td(hwriter, QString("Altitude: %1 %2 ").arg(QString::number(alt, 'f', 3)).arg(alt_units));
  811. }
  812. if (pt->heartrate) {
  813. kml_td(hwriter, QString("Heart rate: %1 ").arg(QString::number(pt->heartrate)));
  814. }
  815. if (pt->cadence) {
  816. kml_td(hwriter, QString("Cadence: %1 ").arg(QString::number(pt->cadence)));
  817. }
  818. /* Which unit is this temp in? C? F? K? */
  819. if WAYPT_HAS(pt, temperature) {
  820. kml_td(hwriter, QString("Temperature: %1 ").arg(QString::number(pt->temperature, 'f', 1)));
  821. }
  822. if WAYPT_HAS(pt, depth) {
  823. const char* depth_units;
  824. double depth = fmt_distance(pt->depth, &depth_units);
  825. kml_td(hwriter, QString("Depth: %1 %2 ").arg(QString::number(depth, 'f', 1)).arg(depth_units));
  826. }
  827. if WAYPT_HAS(pt, speed) {
  828. const char* spd_units;
  829. double spd = fmt_speed(pt->speed, &spd_units);
  830. kml_td(hwriter, QString("Speed: %1 %2 ").arg(QString::number(spd, 'f', 1)).arg(spd_units));
  831. }
  832. if WAYPT_HAS(pt, course) {
  833. kml_td(hwriter, QString("Heading: %1 ").arg(QString::number(pt->course, 'f', 1)));
  834. }
  835. /* This really shouldn't be here, but as of this writing,
  836. * Earth can't edit/display the TimeStamp.
  837. */
  838. if (pt->GetCreationTime().isValid()) {
  839. QString time_string = pt->CreationTimeXML();
  840. if (!time_string.isEmpty()) {
  841. kml_td(hwriter, QString("Time: %1 ").arg(time_string));
  842. }
  843. }
  844. hwriter.writeCharacters("\n");
  845. hwriter.writeEndElement(); // Close table tag
  846. hwriter.writeEndDocument();
  847. writer->writeCDATA(hstring);
  848. writer->writeEndElement(); // Close description tag
  849. }
  850. static void kml_recompute_time_bounds(const Waypoint* waypointp)
  851. {
  852. if (waypointp->GetCreationTime().isValid()) {
  853. if (!(kml_time_min.isValid()) ||
  854. (waypointp->GetCreationTime() < kml_time_min)) {
  855. kml_time_min = waypointp->GetCreationTime();
  856. }
  857. if (!(kml_time_max.isValid()) ||
  858. (waypointp->GetCreationTime() > kml_time_max)) {
  859. kml_time_max = waypointp->GetCreationTime();
  860. }
  861. }
  862. }
  863. static void kml_add_to_bounds(const Waypoint* waypointp)
  864. {
  865. waypt_add_to_bounds(&kml_bounds, waypointp);
  866. kml_recompute_time_bounds(waypointp);
  867. }
  868. static void kml_output_point(const Waypoint* waypointp, kml_point_type pt_type)
  869. {
  870. QString style;
  871. switch (pt_type) {
  872. case kmlpt_track:
  873. style = "#track";
  874. break;
  875. case kmlpt_route:
  876. style = "#route";
  877. break;
  878. default:
  879. fatal("kml_output_point: unknown point type");
  880. break;
  881. }
  882. if (export_points) {
  883. writer->writeStartElement("Placemark");
  884. if (atoi(opt_labels)) {
  885. writer->writeOptionalTextElement("name", waypointp->shortname);
  886. }
  887. writer->writeEmptyElement("snippet");
  888. kml_output_description(waypointp);
  889. kml_output_lookat(waypointp);
  890. kml_output_timestamp(waypointp);
  891. if (opt_deficon) {
  892. writer->writeStartElement("Style");
  893. writer->writeStartElement("IconStyle");
  894. writer->writeStartElement("Icon");
  895. writer->writeTextElement("href", opt_deficon);
  896. writer->writeEndElement(); // Close Icon tag
  897. writer->writeEndElement(); // Close IconStyle tag
  898. writer->writeEndElement(); // Close Style tag
  899. } else {
  900. if (trackdirection && (pt_type == kmlpt_track)) {
  901. QString value;
  902. if (waypointp->speed < 1) {
  903. value = QString("%1-none").arg(style);
  904. } else {
  905. value = QString("%1-%2").arg(style)
  906. .arg((int)(waypointp->course / 22.5 + .5) % 16);
  907. }
  908. writer->writeTextElement("styleUrl", value);
  909. } else {
  910. writer->writeTextElement("styleUrl", style);
  911. }
  912. }
  913. writer->writeStartElement("Point");
  914. kml_output_positioning(false);
  915. kml_write_coordinates(waypointp);
  916. writer->writeEndElement(); // Close Point tag
  917. writer->writeEndElement(); // Close Placemark tag
  918. }
  919. }
  920. static void kml_output_tailer(const route_head* header)
  921. {
  922. if (export_points && header->rte_waypt_ct > 0) {
  923. writer->writeEndElement(); // Close Folder tag
  924. }
  925. // Add a linestring for this track?
  926. if (export_lines && header->rte_waypt_ct > 0) {
  927. int needs_multigeometry = 0;
  928. queue* elem, *tmp;
  929. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  930. Waypoint* tpt = (Waypoint*) elem;
  931. int first_in_trk = tpt->Q.prev == &header->waypoint_list;
  932. if (!first_in_trk && tpt->wpt_flags.new_trkseg) {
  933. needs_multigeometry = 1;
  934. break;
  935. }
  936. }
  937. writer->writeStartElement("Placemark");
  938. writer->writeTextElement("name", "Path");
  939. if (!rotate_colors) {
  940. writer->writeTextElement("styleUrl", "#lineStyle");
  941. }
  942. if (header->line_color.bbggrr >= 0 || header->line_width >= 0 || rotate_colors) {
  943. writer->writeStartElement("Style");
  944. writer->writeStartElement("LineStyle");
  945. if (rotate_colors) {
  946. kml_step_color();
  947. writer->writeTextElement("color", QString("%1%2")
  948. .arg(kml_color_sequencer.color.opacity, 2, 16, QChar('0')).arg(kml_color_sequencer.color.bbggrr, 6, 16, QChar('0')));
  949. writer->writeTextElement("width", opt_line_width);
  950. } else {
  951. if (header->line_color.bbggrr >= 0) {
  952. writer->writeTextElement("color", QString("%1%2")
  953. .arg(header->line_color.opacity, 2, 16, QChar('0')).arg(header->line_color.bbggrr, 6, 16, QChar('0')));
  954. }
  955. if (header->line_width >= 0) {
  956. writer->writeTextElement("width", QString::number(header->line_width));
  957. }
  958. }
  959. writer->writeEndElement(); // Close LineStyle tag
  960. writer->writeEndElement(); // Close Style tag
  961. }
  962. if (needs_multigeometry) {
  963. writer->writeStartElement("MultiGeometry");
  964. }
  965. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  966. Waypoint* tpt = (Waypoint*) elem;
  967. int first_in_trk = tpt->Q.prev == &header->waypoint_list;
  968. if (tpt->wpt_flags.new_trkseg) {
  969. if (!first_in_trk) {
  970. writer->writeEndElement(); // Close coordinates tag
  971. writer->writeEndElement(); // Close LineString tag
  972. }
  973. writer->writeStartElement("LineString");
  974. kml_output_positioning(true);
  975. writer->writeStartElement("coordinates");
  976. writer->writeCharacters("\n");
  977. }
  978. if (kml_altitude_known(tpt)) {
  979. writer->writeCharacters(QString::number(tpt->longitude, 'f', precision) + QString(",") +
  980. QString::number(tpt->latitude, 'f', precision) + QString(",") +
  981. QString::number(tpt->altitude, 'f', 2) + QString("\n")
  982. );
  983. } else {
  984. writer->writeCharacters(QString::number(tpt->longitude, 'f', precision) + QString(",") +
  985. QString::number(tpt->latitude, 'f', precision) + QString("\n")
  986. );
  987. }
  988. }
  989. writer->writeEndElement(); // Close coordinates tag
  990. writer->writeEndElement(); // Close LineString tag
  991. if (needs_multigeometry) {
  992. writer->writeEndElement(); // Close MultiGeometry tag
  993. }
  994. writer->writeEndElement(); // Close Placemark tag
  995. }
  996. if (!realtime_positioning) {
  997. writer->writeEndElement(); // Close folder tag
  998. }
  999. }
  1000. /*
  1001. * Completely different writer for geocaches.
  1002. */
  1003. // Text that's common to all tabs.
  1004. static
  1005. void kml_gc_all_tabs_text(QString& cdataStr)
  1006. {
  1007. // cdataStr.append("<a href=\"http://www.geocaching.com\"><img style=\"float: left; padding: 10px\" src=\"http://www.geocaching.com/images/nav/logo_sub.gif\" /> </a>\n");
  1008. cdataStr.append("<img align=\"right\" src=\"$[gc_icon]\" />\n");
  1009. cdataStr.append("<a href=\"http://www.geocaching.com/seek/cache_details.aspx?wp=$[gc_num]\"><b>$[gc_num]</b></a> <b>$[gc_name]</b> \n");
  1010. cdataStr.append("a $[gc_type],<br />on $[gc_placed] by <a href=\"http://www.geocaching.com/profile?id=$[gc_placer_id\">$[gc_placer]</a><br/>\n");
  1011. cdataStr.append("Difficulty: <img src=\"http://www.geocaching.com/images/stars/$[gc_diff_stars].gif\" alt=\"$[gc_diff]\" width=\"61\" height=\"13\" />\n");
  1012. cdataStr.append("&nbsp;Terrain: <img src=\"http://www.geocaching.com/images/stars/$[gc_terr_stars].gif\" alt=\"$[gc_terr]\" width=\"61\" height=\"13\" /><br />\n");
  1013. cdataStr.append("Size: <img src=\"http://www.geocaching.com/images/icons/container/$[gc_cont_icon].gif\" width=\"45\" height=\"12\" alt=\"$[gc_cont_icon]\"/>&nbsp;($[gc_cont_icon])<br />\n");
  1014. }
  1015. static const QString map_templates[] = {
  1016. "<a href=\"http://maps.google.com/maps?q=$[gc_lat],$[gc_lon]\" target=\"_blank\">Google Maps</a>",
  1017. "<a href=\"http://maps.google.com/maps?q=$[gc_lat],$[gc_lon]\" target=\"_blank\">Google Street View</a>",
  1018. "<a href=\"http://www.geocaching.com/map/default.aspx?lat=$[gc_lat]&lng=$[gc_lon]\" target=\"_blank\">Geocaching.com Google Map</a>",
  1019. "<a href=\"http://www.mytopo.com/maps.cfm?lat=$[gc_lat]&lon=$[gc_lon]&pid=groundspeak\" target=\"_blank\">MyTopo Maps</a>",
  1020. "<a href=\"http://www.mapquest.com/maps/map.adp?searchtype=address&formtype=latlong&latlongtype=decimal&latitude=$[gc_lat]&longitude=$[gc_lon]&zoom=10\" target=\"_blank\">MapQuest</a>",
  1021. "<a href=\"http://www.bing.com/maps/default.aspx?v=2&sp=point.$[gc_lat]$[gc_lon]\" target=\"_blank\">Bing Maps</a>",
  1022. "<a href=\"http://maps.yahoo.com/maps_result?lat=$[gc_lat]&lon=$[gc_lon]\" target=\"_blank\">Yahoo Maps</a>",
  1023. "<a href=\"http://maps.randmcnally.com/#s=screen&lat=$[gc_lat]&lon=$[gc_lon]&zoom=13&loc1=$[gc_lat],$[gc_lon]\" target=\"_blank\">Rand McNally</a>",
  1024. "<a href=\"http://msrmaps.com/image.aspx?Lon=$[gc_lon]&Lat=$[gc_lat]&w=1&ref=G|$[gc_lon],$[gc_lat]\" target=\"_blank\">MSR Maps (Formerly Terraserver)</a>",
  1025. "<a href=\"http://www.opencyclemap.org/?zoom=12&lat=$[gc_lat]&lon=$[gc_lon]\" target=\"_blank\">Open Cycle Maps</a>",
  1026. "<a href=\"http://www.openstreetmap.org/?mlat=$[gc_lat]&mlon=$[gc_lon]&zoom=12\" target=\"_blank\">Open Street Maps</a>",
  1027. NULL
  1028. };
  1029. static
  1030. void kml_gc_make_balloonstyletext(void)
  1031. {
  1032. QString cdataStr;
  1033. writer->writeStartElement("BalloonStyle");
  1034. writer->writeStartElement("text");
  1035. cdataStr.append("\n");
  1036. cdataStr.append("<!DOCTYPE html>\n");
  1037. cdataStr.append("<html>\n");
  1038. cdataStr.append("<head>\n");
  1039. cdataStr.append("<link href=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css\" rel=\"stylesheet\" type=\"text/css\"/>\n");
  1040. cdataStr.append("<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js\"></script>\n");
  1041. cdataStr.append("<script src=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js\"></script>\n");
  1042. cdataStr.append("<script>\n");
  1043. cdataStr.append("$(document).ready(function() {\n");
  1044. cdataStr.append(" $(\"#tabs\").tabs();\n");
  1045. cdataStr.append("});\n");
  1046. cdataStr.append("</script>\n");
  1047. cdataStr.append("</head>\n");
  1048. cdataStr.append("<body>\n");
  1049. cdataStr.append("<div id=\"tabs\">\n");
  1050. // The tabbed menu bar. Oddly, it has to be on top.
  1051. cdataStr.append("<ul>\n");
  1052. cdataStr.append(" <li><a href=\"#fragment-1\"><span>Description</span></a></li>\n");
  1053. cdataStr.append(" <li><a href=\"#fragment-2\"><span>Logs</span></a></li>\n");
  1054. cdataStr.append(" <li><a href=\"#fragment-3\"><span>Extras</span></a></li>\n");
  1055. cdataStr.append("</ul>\n");
  1056. cdataStr.append("\n");
  1057. cdataStr.append("<div id=\"fragment-1\">\n");
  1058. kml_gc_all_tabs_text(cdataStr);
  1059. cdataStr.append(" <p />$[gc_issues]\n");
  1060. cdataStr.append(" $[gc_short_desc]\n");
  1061. cdataStr.append(" $[gc_long_desc]\n");
  1062. cdataStr.append("</div>\n");
  1063. cdataStr.append("<div id=\"fragment-2\">\n");
  1064. kml_gc_all_tabs_text(cdataStr);
  1065. cdataStr.append(" $[gc_logs]\n");
  1066. cdataStr.append("</div>\n");
  1067. // "Extra" stuff tab.
  1068. cdataStr.append("<div id=\"fragment-3\">\n");
  1069. kml_gc_all_tabs_text(cdataStr);
  1070. cdataStr.append(" <h1>Extra Maps</h1>\n");
  1071. cdataStr.append(" <ul>\n");
  1072. // Fortunately, all the mappy map URLs take lat/longs in the URLs, so
  1073. // the substition is easy.
  1074. for (int tp = 0; !map_templates[tp].isEmpty(); tp++) {
  1075. cdataStr.append(" <li>\n");
  1076. cdataStr.append(" ");
  1077. cdataStr.append(map_templates[tp]);
  1078. cdataStr.append("</li>\n");
  1079. }
  1080. cdataStr.append(" <ul>\n");
  1081. cdataStr.append("</div>\n"); // fragment-3.
  1082. cdataStr.append("</div>\n"); // tabs.
  1083. cdataStr.append("</body>\n");
  1084. cdataStr.append("</html>\n");
  1085. writer->writeCDATA(cdataStr);
  1086. writer->writeEndElement(); // Close text tag
  1087. writer->writeEndElement(); // Close BalloonStyle tag
  1088. }
  1089. static
  1090. void kml_gc_make_balloonstyle(void)
  1091. {
  1092. // For Normal style of gecoaches, scale of label is set to zero
  1093. // to make the label invisible. On hover (highlight?) enlarge
  1094. // the icon sightly and set the scale of the label to 1 so the
  1095. // label pops.
  1096. // It's unfortunate that we have to repeat so much of the template
  1097. // but KML doesn't have a cascading style-like substance.
  1098. //
  1099. writer->writeStartElement("Style");
  1100. writer->writeAttribute("id", "geocache_n");
  1101. writer->writeStartElement("IconStyle");
  1102. writer->writeTextElement("scale", ".6");
  1103. writer->writeEndElement(); // Close IconStyle tag
  1104. writer->writeStartElement("LabelStyle");
  1105. writer->writeTextElement("scale", "0");
  1106. writer->writeEndElement(); // Close LabelStyle tag
  1107. kml_gc_make_balloonstyletext();
  1108. writer->writeEndElement(); // Close Style tag
  1109. writer->writeStartElement("Style");
  1110. writer->writeAttribute("id", "geocache_h");
  1111. writer->writeStartElement("IconStyle");
  1112. writer->writeTextElement("scale", ".8");
  1113. writer->writeEndElement(); // Close IconStyle tag
  1114. writer->writeStartElement("LabelStyle");
  1115. writer->writeTextElement("scale", "1");
  1116. writer->writeEndElement(); // Close LabelStyle tag
  1117. kml_gc_make_balloonstyletext();
  1118. writer->writeEndElement(); // Close Style tag
  1119. writer->writeStartElement("StyleMap");
  1120. writer->writeAttribute("id", "geocache");
  1121. writer->writeStartElement("Pair");
  1122. writer->writeTextElement("key", "normal");
  1123. writer->writeTextElement("styleUrl", "#geocache_n");
  1124. writer->writeEndElement(); // Close Pair tag
  1125. writer->writeStartElement("Pair");
  1126. writer->writeTextElement("key", "highlight");
  1127. writer->writeTextElement("styleUrl", "#geocache_h");
  1128. writer->writeEndElement(); // Close Pair tag
  1129. writer->writeEndElement(); // Close StyleMap tag
  1130. }
  1131. static
  1132. QString
  1133. kml_lookup_gc_icon(const Waypoint* waypointp)
  1134. {
  1135. const char* icon;
  1136. /* This could be done so much better in C99 with designated
  1137. * initializers...
  1138. */
  1139. switch (waypointp->gc_data->type) {
  1140. case gt_traditional:
  1141. icon = "2.png";
  1142. break;
  1143. case gt_multi:
  1144. icon = "3.png";
  1145. break;
  1146. case gt_virtual:
  1147. icon = "4.png";
  1148. break;
  1149. case gt_letterbox:
  1150. icon = "5.png";
  1151. break;
  1152. case gt_event:
  1153. icon = "6.png";
  1154. break;
  1155. case gt_ape:
  1156. icon = "7.png";
  1157. break;
  1158. case gt_locationless:
  1159. icon = "8.png";
  1160. break; // No unique icon.
  1161. case gt_suprise:
  1162. icon = "8.png";
  1163. break;
  1164. case gt_webcam:
  1165. icon = "11.png";
  1166. break;
  1167. case gt_cito:
  1168. icon = "13.png";
  1169. break;
  1170. case gt_earth:
  1171. icon = "earthcache.png";
  1172. break;
  1173. case gt_mega:
  1174. icon = "453.png";
  1175. break;
  1176. case gt_wherigo:
  1177. icon = "1858.png";
  1178. break;
  1179. default:
  1180. icon = "8.png";
  1181. break;
  1182. }
  1183. return QString("http://www.geocaching.com/images/kml/%1").arg(icon);
  1184. }
  1185. static const
  1186. char*
  1187. kml_lookup_gc_container(const Waypoint* waypointp)
  1188. {
  1189. const char* cont;
  1190. switch (waypointp->gc_data->container) {
  1191. case gc_micro:
  1192. cont="micro";
  1193. break;
  1194. case gc_regular:
  1195. cont="regular";
  1196. break;
  1197. case gc_large:
  1198. cont="large";
  1199. break;
  1200. case gc_small:
  1201. cont="small";
  1202. break;
  1203. case gc_virtual:
  1204. cont="virtual";
  1205. break;
  1206. case gc_other:
  1207. cont="other";
  1208. break;
  1209. default:
  1210. cont="not_chosen";
  1211. break;
  1212. }
  1213. return cont;
  1214. }
  1215. static QString kml_gc_mkstar(int rating)
  1216. {
  1217. QString star_content;
  1218. if (rating < 0 || rating > 50 || rating % 5 != 0) {
  1219. fatal("Bogus difficulty or terrain rating.");
  1220. }
  1221. if (0 == rating % 10) {
  1222. star_content = QString("stars%1").arg(rating / 10);
  1223. } else {
  1224. star_content = QString("stars%1_%2").arg(rating / 10).arg(rating % 10);
  1225. }
  1226. return star_content;
  1227. }
  1228. static QString kml_geocache_get_logs(const Waypoint* wpt)
  1229. {
  1230. QString r;
  1231. fs_xml* fs_gpx = (fs_xml*)fs_chain_find(wpt->fs, FS_GPX);
  1232. xml_tag* root = NULL;
  1233. xml_tag* curlog = NULL;
  1234. xml_tag* logpart = NULL;
  1235. if (!fs_gpx) {
  1236. return r;
  1237. }
  1238. root = fs_gpx->tag;
  1239. curlog = xml_findfirst(root, "groundspeak:log");
  1240. while (curlog) {
  1241. // Unless we have a broken GPX input, these logparts
  1242. // branches will always be taken.
  1243. logpart = xml_findfirst(curlog, "groundspeak:type");
  1244. if (logpart) {
  1245. r = r + "<p><b>" + logpart->cdata + "</b>";
  1246. }
  1247. logpart = xml_findfirst(curlog, "groundspeak:finder");
  1248. if (logpart) {
  1249. r = r + " by " + logpart->cdata;
  1250. }
  1251. logpart = xml_findfirst(curlog, "groundspeak:date");
  1252. if (logpart) {
  1253. gpsbabel::DateTime t = xml_parse_time(logpart->cdata);
  1254. if (t.isValid()) {
  1255. r += t.date().toString(Qt::ISODate);
  1256. }
  1257. }
  1258. logpart = xml_findfirst(curlog, "groundspeak:text");
  1259. if (logpart) {
  1260. char* encstr = NULL;
  1261. char* t = NULL;
  1262. int encoded = 0;
  1263. encstr = xml_attribute(logpart, "encoded");
  1264. encoded = (toupper(encstr[0]) != 'F');
  1265. QString s;
  1266. if (html_encrypt && encoded) {
  1267. s = rot13(logpart->cdata);
  1268. } else {
  1269. s = logpart->cdata;
  1270. }
  1271. r = r + "<br />";
  1272. t = html_entitize(s);
  1273. r = r + t;
  1274. xfree(t);
  1275. }
  1276. r += "</p>";
  1277. curlog = xml_findnext(root, curlog, "groundspeak:log");
  1278. }
  1279. return r;
  1280. }
  1281. static void kml_write_data_element(const QString& name, const QString& value)
  1282. {
  1283. writer->writeStartElement("Data");
  1284. writer->writeAttribute("name", name);
  1285. writer->writeTextElement("value", value);
  1286. writer->writeEndElement(); // Close Data tag
  1287. }
  1288. static void kml_write_data_element(const QString& name, const int value)
  1289. {
  1290. writer->writeStartElement("Data");
  1291. writer->writeAttribute("name", name);
  1292. writer->writeTextElement("value", QString::number(value));
  1293. writer->writeEndElement(); // Close Data tag
  1294. }
  1295. static void kml_write_data_element(const QString& name, const double value)
  1296. {
  1297. writer->writeStartElement("Data");
  1298. writer->writeAttribute("name", name);
  1299. writer->writeTextElement("value", QString::number(value, 'f', 6));
  1300. writer->writeEndElement(); // Close Data tag
  1301. }
  1302. static void kml_write_cdata_element(const QString& name, const QString& value)
  1303. {
  1304. writer->writeStartElement("Data");
  1305. writer->writeAttribute("name", name);
  1306. writer->writeStartElement("value");
  1307. writer->writeCDATA(value);
  1308. writer->writeEndElement(); // Close value tag
  1309. writer->writeEndElement(); // Close Data tag
  1310. }
  1311. static void kml_geocache_pr(const Waypoint* waypointp)
  1312. {
  1313. const char* issues = "";
  1314. writer->writeStartElement("Placemark");
  1315. writer->writeStartElement("name");
  1316. if (waypointp->HasUrlLink()) {
  1317. UrlLink link = waypointp->GetUrlLink();
  1318. writer->writeCDATA(link.url_link_text_);
  1319. }
  1320. writer->writeEndElement(); // Close name tag
  1321. // Timestamp
  1322. kml_output_timestamp(waypointp);
  1323. QString date_placed;
  1324. if (waypointp->GetCreationTime().isValid()) {
  1325. date_placed = waypointp->GetCreationTime().toString("dd-MMM-yyyy");
  1326. }
  1327. writer->writeTextElement("styleUrl", "#geocache");
  1328. writer->writeStartElement("Style");
  1329. writer->writeStartElement("IconStyle");
  1330. writer->writeStartElement("Icon");
  1331. QString is = kml_lookup_gc_icon(waypointp);
  1332. writer->writeTextElement("href", is);
  1333. writer->writeEndElement(); // Close Icon tag
  1334. writer->writeEndElement(); // Close IconStyle tag
  1335. writer->writeEndElement(); // Close Style tag
  1336. writer->writeStartElement("ExtendedData");
  1337. if (!waypointp->shortname.isEmpty()) {
  1338. kml_write_data_element("gc_num", waypointp->shortname);
  1339. }
  1340. if (waypointp->HasUrlLink()) {
  1341. UrlLink link = waypointp->GetUrlLink();
  1342. kml_write_data_element("gc_name", link.url_link_text_);
  1343. }
  1344. if (!waypointp->gc_data->placer.isEmpty()) {
  1345. kml_write_data_element("gc_placer", waypointp->gc_data->placer);
  1346. }
  1347. kml_write_data_element("gc_placer_id", waypointp->gc_data->placer_id);
  1348. kml_write_data_element("gc_placed", date_placed);
  1349. kml_write_data_element("gc_diff_stars", kml_gc_mkstar(waypointp->gc_data->diff));
  1350. kml_write_data_element("gc_terr_stars", kml_gc_mkstar(waypointp->gc_data->terr));
  1351. kml_write_data_element("gc_cont_icon", kml_lookup_gc_container(waypointp));
  1352. // Highlight any issues with the cache, such as temp unavail
  1353. // or archived.
  1354. if (waypointp->gc_data->is_archived == status_true) {
  1355. issues = "&lt;font color=\"red\"&gt;This cache has been archived.&lt;/font&gt;&lt;br/&gt;\n";
  1356. } else if (waypointp->gc_data->is_available == status_false) {
  1357. issues = "&lt;font color=\"red\"&gt;This cache is temporarily unavailable.&lt;/font&gt;&lt;br/&gt;\n";
  1358. }
  1359. kml_write_data_element("gc_issues", issues);
  1360. kml_write_data_element("gc_lat", waypointp->latitude);
  1361. kml_write_data_element("gc_lon", waypointp->longitude);
  1362. kml_write_data_element("gc_type", gs_get_cachetype(waypointp->gc_data->type));
  1363. kml_write_data_element("gc_icon", is);
  1364. kml_write_cdata_element("gc_short_desc", waypointp->gc_data->desc_short.utfstring);
  1365. kml_write_cdata_element("gc_long_desc", waypointp->gc_data->desc_long.utfstring);
  1366. QString logs = kml_geocache_get_logs(waypointp);
  1367. kml_write_cdata_element("gc_logs", logs);
  1368. writer->writeEndElement(); // Close ExtendedData tag
  1369. // Location
  1370. writer->writeStartElement("Point");
  1371. kml_write_coordinates(waypointp);
  1372. writer->writeEndElement(); // Close Point tag
  1373. writer->writeEndElement(); // Close Placemark tag
  1374. }
  1375. /*
  1376. * WAYPOINTS
  1377. */
  1378. static void kml_waypt_pr(const Waypoint* waypointp)
  1379. {
  1380. QString icon;
  1381. #if 0 // Experimental
  1382. if (realtime_positioning) {
  1383. writer->wrteStartTag("LookAt");
  1384. writer->writeTextElement("longitude", QString::number(waypointp->longitude, 'f', precision);
  1385. writer->writeTextElement("latitude", QString::number(waypointp->latitude, 'f', precision);
  1386. writer->writeTextElement("altitude", "1000");
  1387. writer->writeEndElement(); // Close LookAt tag
  1388. }
  1389. #endif
  1390. if (waypointp->gc_data->diff && waypointp->gc_data->terr) {
  1391. kml_geocache_pr(waypointp);
  1392. return;
  1393. }
  1394. writer->writeStartElement("Placemark");
  1395. writer->writeOptionalTextElement("name", waypointp->shortname);
  1396. // Description
  1397. if (waypointp->HasUrlLink()) {
  1398. writer->writeEmptyElement("snippet");
  1399. UrlLink link = waypointp->GetUrlLink();
  1400. if (!link.url_link_text_.isEmpty()) {
  1401. QString odesc = link.url_;
  1402. QString olink = link.url_link_text_;
  1403. writer->writeStartElement("description");
  1404. writer->writeCDATA(QString("<a href=\"%1\">%2</a>").arg(odesc, olink));
  1405. writer->writeEndElement(); // Close description tag
  1406. } else {
  1407. writer->writeTextElement("description", link.url_);
  1408. }
  1409. } else {
  1410. if (waypointp->shortname != waypointp->description) {
  1411. writer->writeOptionalTextElement("description", waypointp->description);
  1412. }
  1413. }
  1414. // Timestamp
  1415. kml_output_timestamp(waypointp);
  1416. // Icon - but only if it looks like a URL.
  1417. icon = opt_deficon ? opt_deficon : waypointp->icon_descr;
  1418. if (icon.contains("://")) {
  1419. writer->writeStartElement("Style");
  1420. writer->writeStartElement("IconStyle");
  1421. writer->writeStartElement("Icon");
  1422. writer->writeTextElement("href", icon);
  1423. writer->writeEndElement(); // Close Icon tag
  1424. writer->writeEndElement(); // Close IconStyle tag
  1425. writer->writeEndElement(); // Close Style tag
  1426. } else {
  1427. writer->writeTextElement("styleUrl", "#waypoint");
  1428. }
  1429. // Location
  1430. writer->writeStartElement("Point");
  1431. kml_output_positioning(false);
  1432. kml_write_coordinates(waypointp);
  1433. writer->writeEndElement(); // Close Point tag
  1434. writer->writeEndElement(); // Close Placemark tag
  1435. }
  1436. /*
  1437. * TRACKPOINTS
  1438. */
  1439. static void kml_track_hdr(const route_head* header)
  1440. {
  1441. computed_trkdata* td;
  1442. track_recompute(header, &td);
  1443. if (header->rte_waypt_ct > 0 && (export_lines || export_points)) {
  1444. kml_output_header(header, td);
  1445. }
  1446. xfree(td);
  1447. }
  1448. static void kml_track_disp(const Waypoint* waypointp)
  1449. {
  1450. kml_output_point(waypointp, kmlpt_track);
  1451. }
  1452. static void kml_track_tlr(const route_head* header)
  1453. {
  1454. if (header->rte_waypt_ct > 0 && (export_lines || export_points)) {
  1455. kml_output_tailer(header);
  1456. }
  1457. }
  1458. /*
  1459. * New for 2010, Earth adds "MultiTrack" as an extension.
  1460. * Unlike every other format, we do the bulk of the work in the header
  1461. * callback as we have to make multiple passes over the track queues.
  1462. */
  1463. // Helper to write gx:SimpleList, iterating over a route queue and writing out.
  1464. typedef enum {
  1465. fld_cadence,
  1466. fld_depth,
  1467. fld_heartrate,
  1468. fld_temperature,
  1469. fld_power
  1470. } wp_field;
  1471. static void kml_mt_simple_array(const route_head* header,
  1472. const char* name,
  1473. wp_field member)
  1474. {
  1475. queue* elem, *tmp;
  1476. writer->writeStartElement("gx:SimpleArrayData");
  1477. writer->writeAttribute("name", name);
  1478. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  1479. Waypoint* wpt = (Waypoint*) elem;
  1480. switch (member) {
  1481. case fld_power:
  1482. writer->writeTextElement("gx:value", QString::number(wpt->power, 'f', 1));
  1483. break;
  1484. case fld_cadence:
  1485. writer->writeTextElement("gx:value", QString::number(wpt->cadence));
  1486. break;
  1487. case fld_depth:
  1488. writer->writeTextElement("gx:value", QString::number(wpt->depth, 'f', 1));
  1489. break;
  1490. case fld_heartrate:
  1491. writer->writeTextElement("gx:value", QString::number(wpt->heartrate));
  1492. break;
  1493. case fld_temperature:
  1494. writer->writeTextElement("gx:value", QString::number(wpt->temperature, 'f', 1));
  1495. break;
  1496. default:
  1497. fatal("Bad member type");
  1498. }
  1499. }
  1500. writer->writeEndElement(); // Close SimpleArrayData tag
  1501. }
  1502. // True if at least two points in the track have timestamps.
  1503. static int track_has_time(const route_head* header)
  1504. {
  1505. queue* elem, *tmp;
  1506. int points_with_time = 0;
  1507. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  1508. Waypoint* tpt = (Waypoint*)elem;
  1509. if (tpt->GetCreationTime().isValid()) {
  1510. points_with_time++;
  1511. if (points_with_time >= 2) {
  1512. return 1;
  1513. }
  1514. }
  1515. }
  1516. return 0;
  1517. }
  1518. // Simulate a track_disp_all callback sequence for a single track.
  1519. static void write_as_linestring(const route_head* header)
  1520. {
  1521. queue* elem, *tmp;
  1522. kml_track_hdr(header);
  1523. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  1524. Waypoint* tpt = (Waypoint*)elem;
  1525. kml_track_disp(tpt);
  1526. }
  1527. kml_track_tlr(header);
  1528. }
  1529. static void kml_mt_hdr(const route_head* header)
  1530. {
  1531. queue* elem, *tmp;
  1532. int has_cadence = 0;
  1533. int has_depth = 0;
  1534. int has_heartrate = 0;
  1535. int has_temperature = 0;
  1536. int has_power = 0;
  1537. // This logic is kind of inside-out for GPSBabel. If a track doesn't
  1538. // have enough interesting timestamps, just write it as a LineString.
  1539. if (!track_has_time(header)) {
  1540. write_as_linestring(header);
  1541. return;
  1542. }
  1543. writer->writeStartElement("Placemark");
  1544. writer->writeOptionalTextElement("name", header->rte_name);
  1545. writer->writeTextElement("styleUrl", "#multiTrack");
  1546. writer->writeStartElement("gx:Track");
  1547. kml_output_positioning(false);
  1548. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  1549. Waypoint* tpt = (Waypoint*)elem;
  1550. if (tpt->GetCreationTime().isValid()) {
  1551. QString time_string = tpt->CreationTimeXML();
  1552. writer->writeOptionalTextElement("when", time_string);
  1553. } else {
  1554. writer->writeStartElement("when");
  1555. writer->writeEndElement(); // Close when tag
  1556. }
  1557. }
  1558. // TODO: How to handle clamped, floating, extruded, etc.?
  1559. QUEUE_FOR_EACH(&header->waypoint_list, elem, tmp) {
  1560. Waypoint* tpt = (Waypoint*)elem;
  1561. if (kml_altitude_known(tpt)) {
  1562. writer->writeTextElement("gx:coord",
  1563. QString::number(tpt->longitude, 'f', precision) + QString(" ") +
  1564. QString::number(tpt->latitude, 'f', precision) + QString(" ") +
  1565. QString::number(tpt->altitude, 'f', 2)
  1566. );
  1567. } else {
  1568. writer->writeTextElement("gx:coord",
  1569. QString::number(tpt->longitude, 'f', precision) + QString(" ") +
  1570. QString::number(tpt->latitude, 'f', precision)
  1571. );
  1572. }
  1573. // Capture interesting traits to see if we need to do an ExtendedData
  1574. // section later.
  1575. if (tpt->cadence) {
  1576. has_cadence = 1;
  1577. }
  1578. if (WAYPT_HAS(tpt, depth)) {
  1579. has_depth = 1;
  1580. }
  1581. if (tpt->heartrate) {
  1582. has_heartrate = 1;
  1583. }
  1584. if (WAYPT_HAS(tpt, temperature)) {
  1585. has_temperature = 1;
  1586. }
  1587. if (tpt->power) {
  1588. has_power = 1;
  1589. }
  1590. }
  1591. if (has_cadence || has_depth || has_heartrate || has_temperature ||
  1592. has_power) {
  1593. writer->writeStartElement("ExtendedData");
  1594. writer->writeStartElement("SchemaData");
  1595. writer->writeAttribute("schemaUrl", "#schema");
  1596. if (has_cadence) {
  1597. kml_mt_simple_array(header, kmt_cadence, fld_cadence);
  1598. }
  1599. if (has_depth) {
  1600. kml_mt_simple_array(header, kmt_depth, fld_depth);
  1601. }
  1602. if (has_heartrate) {
  1603. kml_mt_simple_array(header, kmt_heartrate, fld_heartrate);
  1604. }
  1605. if (has_temperature) {
  1606. kml_mt_simple_array(header, kmt_temperature, fld_temperature);
  1607. }
  1608. if (has_power) {
  1609. kml_mt_simple_array(header, kmt_power, fld_power);
  1610. }
  1611. writer->writeEndElement(); // Close SchemaData tag
  1612. writer->writeEndElement(); // Close ExtendedData tag
  1613. }
  1614. }
  1615. static void kml_mt_tlr(const route_head* header)
  1616. {
  1617. if (track_has_time(header)) {
  1618. writer->writeEndElement(); // Close gx:Track tag
  1619. writer->writeEndElement(); // Close Placemark tag
  1620. }
  1621. }
  1622. /*
  1623. * ROUTES
  1624. */
  1625. static void kml_route_hdr(const route_head* header)
  1626. {
  1627. kml_output_header(header, NULL);
  1628. }
  1629. static void kml_route_disp(const Waypoint* waypointp)
  1630. {
  1631. kml_output_point(waypointp, kmlpt_route);
  1632. }
  1633. static void kml_route_tlr(const route_head* header)
  1634. {
  1635. kml_output_tailer(header);
  1636. }
  1637. // For Earth 5.0 and later, we write a LookAt that encompasses
  1638. // the bounding box of our entire data set and set the event times
  1639. // to include all our data.
  1640. void kml_write_AbstractView(void)
  1641. {
  1642. double bb_size;
  1643. // Make a pass through all the points to find the bounds.
  1644. if (waypt_count()) {
  1645. waypt_disp_all(kml_add_to_bounds);
  1646. }
  1647. if (track_waypt_count()) {
  1648. track_disp_all(NULL, NULL, kml_add_to_bounds);
  1649. }
  1650. if (route_waypt_count()) {
  1651. route_disp_all(NULL, NULL, kml_add_to_bounds);
  1652. }
  1653. writer->writeStartElement("LookAt");
  1654. if (kml_time_min.isValid() || kml_time_max.isValid()) {
  1655. writer->writeStartElement("gx:TimeSpan");
  1656. if (kml_time_min.isValid()) {
  1657. writer->writeTextElement("begin", kml_time_min.toPrettyString());
  1658. }
  1659. if (kml_time_max.isValid()) {
  1660. gpsbabel::DateTime time_max;
  1661. // In realtime tracking mode, we fudge the end time by a few minutes
  1662. // to ensure that the freshest data (our current location) is contained
  1663. // within the timespan. Earth's time may not match the GPS because
  1664. // we may not be running NTP, plus it's polling a file (sigh) to read
  1665. // the network position. So we shove the end of the timespan out to
  1666. // ensure the right edge of that time slider includes us.
  1667. //
  1668. time_max = realtime_positioning ? kml_time_max.addSecs(600) : kml_time_max;
  1669. writer->writeTextElement("end", time_max.toPrettyString());
  1670. }
  1671. writer->writeEndElement(); // Close gx:TimeSpan tag
  1672. }
  1673. // If our BB spans the antemeridian, flip sign on one.
  1674. // This doesn't make our BB optimal, but it at least prevents us from
  1675. // zooming to the wrong hemisphere.
  1676. if (kml_bounds.min_lon * kml_bounds.max_lon < 0) {
  1677. kml_bounds.min_lon = -kml_bounds.max_lon;
  1678. }
  1679. writer->writeTextElement("longitude", QString::number((kml_bounds.min_lon + kml_bounds.max_lon) / 2, 'f', precision));
  1680. writer->writeTextElement("latitude", QString::number((kml_bounds.min_lat + kml_bounds.max_lat) / 2, 'f', precision));
  1681. // It turns out the length of the diagonal of the bounding box gives us a
  1682. // reasonable guess for setting the camera altitude.
  1683. bb_size = gcgeodist(kml_bounds.min_lat, kml_bounds.min_lon,
  1684. kml_bounds.max_lat, kml_bounds.max_lon);
  1685. // Clamp bottom zoom level. Otherwise, a single point zooms to grass.
  1686. if (bb_size < 1000) {
  1687. bb_size = 1000;
  1688. }
  1689. writer->writeTextElement("range", QString::number(bb_size * 1.3, 'f', 6));
  1690. writer->writeEndElement(); // Close LookAt tag
  1691. }
  1692. static
  1693. void kml_mt_array_schema(const char* field_name, const char* display_name,
  1694. const char* type)
  1695. {
  1696. writer->writeStartElement("gx:SimpleArrayField");
  1697. writer->writeAttribute("name", field_name);
  1698. writer->writeAttribute("type", type);
  1699. writer->writeTextElement("displayName", display_name);
  1700. writer->writeEndElement(); // Close gx:SimpleArrayField tag
  1701. }
  1702. void kml_write(void)
  1703. {
  1704. const global_trait* traits = get_traits();
  1705. // Parse options
  1706. export_lines = (0 == strcmp("1", opt_export_lines));
  1707. export_points = (0 == strcmp("1", opt_export_points));
  1708. export_track = (0 == strcmp("1", opt_export_track));
  1709. floating = (!! strcmp("0", opt_floating));
  1710. extrude = (!! strcmp("0", opt_extrude));
  1711. rotate_colors = (!! opt_rotate_colors);
  1712. trackdata = (!! strcmp("0", opt_trackdata));
  1713. trackdirection = (!! strcmp("0", opt_trackdirection));
  1714. line_width = atol(opt_line_width);
  1715. precision = atol(opt_precision);
  1716. writer->writeStartDocument();
  1717. // FIXME: This write of a blank line is needed for Qt 4.6 (as on Centos 6.3)
  1718. // to include just enough whitespace between <xml/> and <gpx...> to pass
  1719. // diff -w. It's here for now to shim compatibility with our zillion
  1720. // reference files, but this blank link can go away some day.
  1721. writer->writeCharacters("\n");
  1722. writer->setAutoFormatting(true);
  1723. writer->writeStartElement("kml");
  1724. writer->writeAttribute("xmlns", "http://www.opengis.net/kml/2.2");
  1725. writer->writeAttribute("xmlns:gx","http://www.google.com/kml/ext/2.2");
  1726. writer->writeStartElement("Document");
  1727. if (realtime_positioning) {
  1728. writer->writeTextElement("name", "GPS position");
  1729. } else {
  1730. writer->writeTextElement("name", "GPS device");
  1731. }
  1732. if (current_time().isValid()) {
  1733. writer->writeTextElement("snippet", QString("Created ") +
  1734. current_time().toString());
  1735. }
  1736. kml_write_AbstractView();
  1737. // Style settings for bitmaps
  1738. if (route_waypt_count()) {
  1739. kml_write_bitmap_style(kmlpt_route, ICON_RTE, NULL);
  1740. }
  1741. if (track_waypt_count()) {
  1742. if (trackdirection) {
  1743. kml_write_bitmap_style(kmlpt_other, ICON_TRK, "track-none");
  1744. for (int i = 0; i < 16; i++) {
  1745. kml_write_bitmap_style(kmlpt_other, QString(ICON_DIR).arg(i), QString("track-%1").arg(i));
  1746. }
  1747. } else {
  1748. kml_write_bitmap_style(kmlpt_track, ICON_TRK, NULL);
  1749. }
  1750. if (export_track)
  1751. kml_write_bitmap_style(kmlpt_multitrack, ICON_MULTI_TRK,
  1752. "track-none");
  1753. }
  1754. kml_write_bitmap_style(kmlpt_waypoint, ICON_WPT, NULL);
  1755. if (track_waypt_count() || route_waypt_count()) {
  1756. writer->writeStartElement("Style");
  1757. writer->writeAttribute("id", "lineStyle");
  1758. kml_output_linestyle(opt_line_color, line_width);
  1759. writer->writeEndElement(); // Close Style tag
  1760. }
  1761. if (traits->trait_geocaches) {
  1762. kml_gc_make_balloonstyle();
  1763. }
  1764. if (traits->trait_heartrate ||
  1765. traits->trait_cadence ||
  1766. traits->trait_power ||
  1767. traits->trait_temperature ||
  1768. traits->trait_depth) {
  1769. writer->writeStartElement("Schema");
  1770. writer->writeAttribute("id", "schema");
  1771. if (traits->trait_heartrate) {
  1772. kml_mt_array_schema(kmt_heartrate, "Heart Rate", "int");
  1773. }
  1774. if (traits->trait_cadence) {
  1775. kml_mt_array_schema(kmt_cadence, "Cadence", "int");
  1776. }
  1777. if (traits->trait_power) {
  1778. kml_mt_array_schema(kmt_power, "Power", "float");
  1779. }
  1780. if (traits->trait_temperature) {
  1781. kml_mt_array_schema(kmt_temperature, "Temperature", "float");
  1782. }
  1783. if (traits->trait_depth) {
  1784. kml_mt_array_schema(kmt_depth, "Depth", "float");
  1785. }
  1786. writer->writeEndElement(); // Close Schema tag
  1787. }
  1788. if (waypt_count()) {
  1789. if (!realtime_positioning) {
  1790. writer->writeStartElement("Folder");
  1791. writer->writeTextElement("name", "Waypoints");
  1792. }
  1793. waypt_disp_all(kml_waypt_pr);
  1794. if (!realtime_positioning) {
  1795. writer->writeEndElement(); // Close Folder tag
  1796. }
  1797. }
  1798. // Output trackpoints
  1799. if (track_waypt_count()) {
  1800. if (!realtime_positioning) {
  1801. writer->writeStartElement("Folder");
  1802. writer->writeTextElement("name", "Tracks");
  1803. }
  1804. kml_init_color_sequencer(track_count());
  1805. if (export_track) {
  1806. track_disp_all(kml_mt_hdr, kml_mt_tlr, NULL);
  1807. }
  1808. track_disp_all(kml_track_hdr, kml_track_tlr,
  1809. kml_track_disp);
  1810. if (!realtime_positioning) {
  1811. writer->writeEndElement(); // Close Folder tag
  1812. }
  1813. }
  1814. // Output routes
  1815. if (route_waypt_count()) {
  1816. if (!realtime_positioning) {
  1817. writer->writeStartElement("Folder");
  1818. writer->writeTextElement("name", "Routes");
  1819. kml_init_color_sequencer(route_count());
  1820. route_disp_all(kml_route_hdr,
  1821. kml_route_tlr, kml_route_disp);
  1822. writer->writeEndElement(); // Close Folder tag
  1823. }
  1824. }
  1825. writer->writeEndElement(); // Close Document tag.
  1826. writer->writeEndElement(); // Close kml tag.
  1827. }
  1828. /*
  1829. * This depends on the table being sorted correctly.
  1830. */
  1831. static const
  1832. char*
  1833. kml_get_posn_icon(int freshness)
  1834. {
  1835. int n_stations = sizeof(kml_tracking_icons) / sizeof(kml_tracking_icons[0]);
  1836. for (int i = 0; i < n_stations ; i++) {
  1837. if (freshness >= kml_tracking_icons[i].freshness) {
  1838. return kml_tracking_icons[i].icon;
  1839. }
  1840. }
  1841. return ICON_NOSAT;
  1842. }
  1843. static route_head* posn_trk_head = NULL;
  1844. static void
  1845. kml_wr_position(Waypoint* wpt)
  1846. {
  1847. static gpsbabel::DateTime last_valid_fix;
  1848. kml_wr_init(posnfilenametmp);
  1849. if (!posn_trk_head) {
  1850. posn_trk_head = route_head_alloc();
  1851. track_add_head(posn_trk_head);
  1852. }
  1853. if (!last_valid_fix.isValid()) {
  1854. last_valid_fix = current_time();
  1855. }
  1856. /* We want our waypoint to have a name, but not our trackpoint */
  1857. if (wpt->shortname.isEmpty()) {
  1858. if (wpt->fix == fix_none) {
  1859. wpt->shortname = "ESTIMATED Position";
  1860. } else {
  1861. wpt->shortname = "Position";
  1862. }
  1863. }
  1864. switch (wpt->fix) {
  1865. case fix_none:
  1866. wpt->shortname = "ESTIMATED Position";
  1867. break;
  1868. case fix_unknown:
  1869. break;
  1870. default:
  1871. last_valid_fix = wpt->GetCreationTime();
  1872. }
  1873. wpt->icon_descr = kml_get_posn_icon(wpt->GetCreationTime().toTime_t() - last_valid_fix.toTime_t());
  1874. /* In order to avoid clutter while we're sitting still, don't add
  1875. track points if we've not moved a minimum distance from the
  1876. beginnning of our accumulated track. */
  1877. {
  1878. Waypoint* newest_posn= (Waypoint*) QUEUE_LAST(&posn_trk_head->waypoint_list);
  1879. if (radtometers(gcdist(RAD(wpt->latitude), RAD(wpt->longitude),
  1880. RAD(newest_posn->latitude), RAD(newest_posn->longitude))) > 50) {
  1881. track_add_wpt(posn_trk_head, new Waypoint(*wpt));
  1882. } else {
  1883. /* If we haven't move more than our threshold, pretend
  1884. * we didn't move at all to prevent Earth from jittering
  1885. * the zoom levels on us.
  1886. */
  1887. wpt->latitude = newest_posn->latitude;
  1888. wpt->longitude = newest_posn->longitude;
  1889. }
  1890. }
  1891. waypt_add(wpt);
  1892. kml_write();
  1893. waypt_del(wpt);
  1894. /*
  1895. * If we are keeping only a recent subset of the trail, trim the
  1896. * head here.
  1897. */
  1898. while (max_position_points &&
  1899. (posn_trk_head->rte_waypt_ct >= max_position_points)) {
  1900. Waypoint* tonuke = (Waypoint*) QUEUE_FIRST(&posn_trk_head->waypoint_list);
  1901. track_del_wpt(posn_trk_head, tonuke);
  1902. }
  1903. kml_wr_deinit();
  1904. }
  1905. ff_vecs_t kml_vecs = {
  1906. ff_type_file,
  1907. FF_CAP_RW_ALL, /* Format can do RW_ALL */
  1908. kml_rd_init,
  1909. kml_wr_init,
  1910. kml_rd_deinit,
  1911. kml_wr_deinit,
  1912. kml_read,
  1913. kml_write,
  1914. NULL,
  1915. kml_args,
  1916. CET_CHARSET_UTF8, 1, /* CET-REVIEW */
  1917. { NULL, NULL, NULL, kml_wr_position_init, kml_wr_position, kml_wr_position_deinit }
  1918. };