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, " 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]\"/> ($[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 = "<font color=\"red\">This cache has been archived.</font><br/>\n";
1404 } else if (waypointp->gc_data->is_available == status_false) {
1405 issues = "<font color=\"red\">This cache is temporarily unavailable.</font><br/>\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