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