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

gtrnctr.cc 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. /*
  2. Access Garmin Training Center (Forerunner/Foretracker/Edge) data files.
  3. Copyright (C) 2006, 2007 Robert Lipe, robertlipe+source@gpsbabel.org
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  15. */
  16. /*
  17. * Relevant schema definitions can be found at
  18. * http://www8.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd
  19. * http://www8.garmin.com/xmlschemas/ActivityExtensionv2.xsd
  20. */
  21. #include <QtCore/QXmlStreamAttributes>
  22. #include "defs.h"
  23. #include "xmlgeneric.h"
  24. #include <stdio.h>
  25. static gbfile* ofd;
  26. static int lap_ct = 0;
  27. static int lap_s = 0;
  28. static Waypoint* wpt_tmp;
  29. static route_head* trk_head;
  30. static computed_trkdata* tdata;
  31. #define MYNAME "gtc"
  32. #define GTC_MAX_NAME_LEN 15
  33. #define MAX_SPORTS 4
  34. static char gtc_sportlist[MAX_SPORTS][16] = { "Biking", "Running", "MultiSport", "Other" };
  35. static int gtc_sport = 0;
  36. static int gtc_course_flag;
  37. static gpsbabel::DateTime gtc_least_time;
  38. static gpsbabel::DateTime gtc_most_time;
  39. static double gtc_start_lat;
  40. static double gtc_start_long;
  41. static double gtc_end_lat;
  42. static double gtc_end_long;
  43. static char* opt_sport, *opt_course;
  44. static
  45. arglist_t gtc_args[] = {
  46. {
  47. "course", &opt_course, "Write course rather than history, default yes",
  48. "1", ARGTYPE_BOOL, ARG_NOMINMAX
  49. },
  50. {
  51. "sport", &opt_sport, "Sport: Biking (deflt), Running, MultiSport, Other",
  52. "Biking", ARGTYPE_STRING, ARG_NOMINMAX
  53. },
  54. ARG_TERMINATOR
  55. };
  56. /* Tracks */
  57. static xg_callback gtc_trk_s;
  58. static xg_callback gtc_trk_ident;
  59. static xg_callback gtc_trk_lap_s, gtc_trk_lap_e;
  60. static xg_callback gtc_trk_pnt_s, gtc_trk_pnt_e;
  61. static xg_callback gtc_trk_utc;
  62. static xg_callback gtc_trk_lat;
  63. static xg_callback gtc_trk_long;
  64. static xg_callback gtc_trk_alt;
  65. static xg_callback gtc_trk_dist;
  66. static xg_callback gtc_trk_hr;
  67. static xg_callback gtc_trk_cad;
  68. static xg_callback gtc_trk_pwr;
  69. static xg_callback gtc_trk_spd;
  70. static xg_callback gtc_wpt_crs_s, gtc_wpt_crs_e;
  71. static xg_callback gtc_wpt_pnt_s, gtc_wpt_pnt_e;
  72. static xg_callback gtc_wpt_ident;
  73. static xg_callback gtc_wpt_lat;
  74. static xg_callback gtc_wpt_long;
  75. static xg_callback gtc_wpt_icon;
  76. static xg_callback gtc_wpt_notes;
  77. static xg_tag_mapping gtc_map[] = {
  78. /* courses tcx v1 & v2 */
  79. { gtc_trk_s, cb_start, "/Courses/Course" },
  80. { gtc_trk_ident,cb_cdata, "/Courses/Course/Name"},
  81. { gtc_trk_pnt_s,cb_start, "/Courses/Course/Track/Trackpoint" },
  82. { gtc_trk_pnt_e,cb_end, "/Courses/Course/Track/Trackpoint" },
  83. { gtc_trk_utc, cb_cdata, "/Courses/Course/Track/Trackpoint/Time" },
  84. { gtc_trk_lat, cb_cdata, "/Courses/Course/Track/Trackpoint/Position/LatitudeDegrees" },
  85. { gtc_trk_long, cb_cdata, "/Courses/Course/Track/Trackpoint/Position/LongitudeDegrees" },
  86. { gtc_trk_alt, cb_cdata, "/Courses/Course/Track/Trackpoint/AltitudeMeters" },
  87. { gtc_trk_hr, cb_cdata, "/Courses/Course/Track/Trackpoint/HeartRateBpm" },
  88. { gtc_trk_cad, cb_cdata, "/Courses/Course/Track/Trackpoint/Cadence" },
  89. { gtc_wpt_crs_s,cb_start, "/Courses/Course/CoursePoint" },
  90. { gtc_wpt_crs_e,cb_end, "/Courses/Course/CoursePoint" },
  91. { gtc_wpt_ident,cb_cdata, "/Courses/Course/CoursePoint/Name"},
  92. { gtc_trk_utc, cb_cdata, "/Courses/Course/CoursePoint/Time"},
  93. { gtc_wpt_lat, cb_cdata, "/Courses/Course/CoursePoint/Position/LatitudeDegrees"},
  94. { gtc_wpt_long, cb_cdata, "/Courses/Course/CoursePoint/Position/LongitudeDegrees"},
  95. { gtc_trk_alt, cb_cdata, "/Courses/Course/CoursePoint/AltitudeMeters" },
  96. { gtc_wpt_icon, cb_cdata, "/Courses/Course/CoursePoint/PointType" },
  97. { gtc_wpt_notes,cb_cdata, "/Courses/Course/CoursePoint/Notes" },
  98. /* history tcx v2 (activities) */
  99. { gtc_trk_s, cb_start, "/Activities/Activity" },
  100. { gtc_trk_ident,cb_cdata, "/Activities/Activity/Id" },
  101. { gtc_trk_lap_s,cb_start, "/Activities/Activity/Lap" },
  102. { gtc_trk_lap_e,cb_end, "/Activities/Activity/Lap" },
  103. { gtc_trk_pnt_s,cb_start, "/Activities/Activity/Lap/Track/Trackpoint" },
  104. { gtc_trk_pnt_e,cb_end, "/Activities/Activity/Lap/Track/Trackpoint" },
  105. { gtc_trk_utc, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Time" },
  106. { gtc_trk_lat, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Position/LatitudeDegrees" },
  107. { gtc_trk_long, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Position/LongitudeDegrees" },
  108. { gtc_trk_alt, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/AltitudeMeters" },
  109. { gtc_trk_dist, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/DistanceMeters" },
  110. { gtc_trk_hr, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/HeartRateBpm" },
  111. { gtc_trk_cad, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Cadence" },
  112. { gtc_trk_pwr, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Extensions/ns3:TPX/ns3:Watts" },
  113. // Sample from Marcelo Kittlein 5/2014 declares a default namespace with the start tag of the TPX element,
  114. // and thus doesn't use prefixes.
  115. { gtc_trk_pwr, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Extensions/TPX/Watts" },
  116. // It looks like Speed and Watts should be siblings, but Garmin can't get
  117. // their namespace act very consistent. This works for a sample provided
  118. // by Laurent Desmons in 5/2013.
  119. { gtc_trk_spd, cb_cdata, "/Activities/Activity/Lap/Track/Trackpoint/Extensions/TPX/Speed" },
  120. /* history tcx v1 */
  121. { gtc_trk_s, cb_start, "/History/Run" },
  122. { gtc_trk_ident,cb_cdata, "/History/Run/Id" },
  123. { gtc_trk_lap_s,cb_start, "/History/Run/Lap" },
  124. { gtc_trk_lap_e,cb_end, "/History/Run/Lap" },
  125. { gtc_trk_pnt_s,cb_start, "/History/Run/Lap/Track/Trackpoint" },
  126. { gtc_trk_pnt_e,cb_end, "/History/Run/Lap/Track/Trackpoint" },
  127. { gtc_trk_utc, cb_cdata, "/History/Run/Lap/Track/Trackpoint/Time" },
  128. { gtc_trk_lat, cb_cdata, "/History/Run/Lap/Track/Trackpoint/Position/LatitudeDegrees" },
  129. { gtc_trk_long, cb_cdata, "/History/Run/Lap/Track/Trackpoint/Position/LongitudeDegrees" },
  130. { gtc_trk_alt, cb_cdata, "/History/Run/Lap/Track/Trackpoint/AltitudeMeters" },
  131. { gtc_trk_hr, cb_cdata, "/History/Run/Lap/Track/Trackpoint/HeartRateBpm" },
  132. { gtc_trk_cad, cb_cdata, "/History/Run/Lap/Track/Trackpoint/Cadence" },
  133. { gtc_wpt_pnt_s,cb_start, "/Courses/Course/Lap/BeginPosition" },
  134. { gtc_wpt_pnt_e,cb_end, "/Courses/Course/Lap/BeginPosition" },
  135. { gtc_wpt_lat, cb_cdata, "/Courses/Course/Lap/BeginPosition/LatitudeDegrees" },
  136. { gtc_wpt_long, cb_cdata, "/Courses/Course/Lap/BeginPosition/LongitudeDegrees" },
  137. { gtc_trk_alt, cb_cdata, "/Courses/Course/Lap/BeginAltitudeMeters" },
  138. { NULL, (xg_cb_type)0, NULL}
  139. };
  140. static const char*
  141. gtc_tags_to_ignore[] = {
  142. "TrainingCenterDatabase",
  143. "CourseFolder",
  144. "Running",
  145. "Biking",
  146. "Other",
  147. "Multisport",
  148. NULL,
  149. };
  150. static void
  151. gtc_rd_init(const QString& fname)
  152. {
  153. xml_init(fname, gtc_map, NULL);
  154. xml_ignore_tags(gtc_tags_to_ignore);
  155. }
  156. static void
  157. gtc_read(void)
  158. {
  159. xml_read();
  160. }
  161. static void
  162. gtc_rd_deinit(void)
  163. {
  164. xml_deinit();
  165. }
  166. static void
  167. gtc_wr_init(const QString& fname)
  168. {
  169. int i;
  170. ofd = gbfopen(fname, "w", MYNAME);
  171. if (opt_sport) {
  172. for (i = 0; i < MAX_SPORTS; i++) {
  173. if (0 == case_ignore_strncmp(opt_sport, gtc_sportlist[i], 2)) {
  174. gtc_sport = i;
  175. break;
  176. }
  177. }
  178. }
  179. gtc_course_flag = atoi(opt_course);
  180. }
  181. static void
  182. gtc_wr_deinit(void)
  183. {
  184. gbfclose(ofd);
  185. }
  186. static int gtc_indent_level;
  187. static void
  188. gtc_write_xml(int indent, const char* fmt, ...)
  189. {
  190. va_list args;
  191. va_start(args, fmt);
  192. if (indent < 0) {
  193. gtc_indent_level--;
  194. }
  195. gbfprintf(ofd, "%*s", gtc_indent_level * 2, "");
  196. gbvfprintf(ofd, fmt, args);
  197. if (indent > 0) {
  198. gtc_indent_level++;
  199. }
  200. va_end(args);
  201. }
  202. static void
  203. gtc_write_xml(int indent, const QString s)
  204. {
  205. if (indent < 0) {
  206. gtc_indent_level--;
  207. }
  208. gbfprintf(ofd, "%*s", gtc_indent_level * 2, "");
  209. gbfputs(s, ofd);
  210. if (indent > 0) {
  211. gtc_indent_level++;
  212. }
  213. }
  214. static void
  215. gtc_lap_start(const route_head*)
  216. {
  217. gtc_least_time = gpsbabel::DateTime();
  218. gtc_most_time = gpsbabel::DateTime();
  219. }
  220. static void
  221. gtc_new_study_lap(const route_head* rte)
  222. {
  223. track_recompute(rte, &tdata); /* called routine allocates space for tdata */
  224. }
  225. static void
  226. gtc_study_lap(const Waypoint* wpt)
  227. {
  228. if (wpt->creation_time.isValid() && (!gtc_least_time.isValid())) {
  229. gtc_least_time = wpt->GetCreationTime();
  230. gtc_start_lat = wpt->latitude;
  231. gtc_start_long = wpt->longitude;
  232. }
  233. if (wpt->creation_time.isValid() && (gtc_least_time > wpt->GetCreationTime())) {
  234. gtc_least_time = wpt->GetCreationTime();
  235. gtc_start_lat = wpt->latitude;
  236. gtc_start_long = wpt->longitude;
  237. }
  238. if (wpt->creation_time > gtc_most_time) {
  239. gtc_most_time = wpt->GetCreationTime();
  240. gtc_end_lat = wpt->latitude;
  241. gtc_end_long = wpt->longitude;
  242. }
  243. }
  244. static void
  245. gtc_waypt_pr(const Waypoint* wpt)
  246. {
  247. if (wpt->wpt_flags.is_split != 0) {
  248. gtc_write_xml(1, "<Trackpoint split=\"yes\">\n");
  249. } else {
  250. gtc_write_xml(1, "<Trackpoint>\n");
  251. }
  252. if (wpt->creation_time.isValid()) {
  253. QString time_string = wpt->CreationTimeXML();
  254. if (!time_string.isEmpty()) {
  255. gtc_write_xml(0, "<Time>%s</Time>\n",
  256. qPrintable(time_string));
  257. }
  258. }
  259. if (wpt->latitude && wpt->longitude) {
  260. gtc_write_xml(1, "<Position>\n");
  261. gtc_write_xml(0, "<LatitudeDegrees>%.7f</LatitudeDegrees>\n", wpt->latitude);
  262. gtc_write_xml(0, "<LongitudeDegrees>%.7f</LongitudeDegrees>\n", wpt->longitude);
  263. gtc_write_xml(-1, "</Position>\n");
  264. }
  265. if (wpt->altitude != unknown_alt) {
  266. gtc_write_xml(0, "<AltitudeMeters>%.1f</AltitudeMeters>\n", wpt->altitude);
  267. }
  268. if (wpt->odometer_distance) {
  269. gtc_write_xml(0, "<DistanceMeters>%.2f</DistanceMeters>\n", wpt->odometer_distance);
  270. }
  271. // TODO: find a schema extension to include wpt->course and wpt->temperature
  272. // TODO: find a way to include DistanceMeters from odometer information
  273. if (wpt->heartrate) {
  274. //gtc_write_xml(0, "<HeartRateBpm>%d</HeartRateBpm>\n", wpt->heartrate);
  275. gtc_write_xml(1, "<HeartRateBpm xsi:type=\"HeartRateInBeatsPerMinute_t\">\n");
  276. gtc_write_xml(0,"<Value>%d</Value>\n", wpt->heartrate);
  277. gtc_write_xml(-1,"</HeartRateBpm>\n");
  278. }
  279. if (wpt->cadence) {
  280. gtc_write_xml(0, "<Cadence>%d</Cadence>\n", wpt->cadence);
  281. }
  282. if (wpt->speed || wpt->power) {
  283. gtc_write_xml(1, "<Extensions>\n");
  284. gtc_write_xml(1, "<TPX xmlns=\"http://www.garmin.com/xmlschemas/ActivityExtension/v2\">\n");
  285. /* see http://www8.garmin.com/xmlschemas/ActivityExtensionv2.xsd */
  286. if (wpt->speed) {
  287. gtc_write_xml(0, "<Speed>%.3f</Speed>\n", wpt->speed);
  288. }
  289. if (wpt->power) {
  290. gtc_write_xml(0, "<Watts>%.0f</Watts>\n", wpt->power);
  291. }
  292. gtc_write_xml(-1, "</TPX>\n");
  293. gtc_write_xml(-1, "</Extensions>\n");
  294. }
  295. gtc_write_xml(-1, "</Trackpoint>\n");
  296. }
  297. static void
  298. gtc_fake_hdr(void)
  299. {
  300. /* handle the CourseLap_t or the ActivityLap_t types. */
  301. /* note that the elements must appear in the order required by the schema. */
  302. /* also note some of the elements are required. */
  303. long secs = 0;
  304. if (gtc_least_time.isValid() && gtc_most_time.isValid()) {
  305. secs = gtc_most_time.toTime_t() - gtc_least_time.toTime_t();
  306. }
  307. /* write these in either case, course or activity format */
  308. gtc_write_xml(0, "<TotalTimeSeconds>%d</TotalTimeSeconds>\n", secs);
  309. gtc_write_xml(0, "<DistanceMeters>%.2f</DistanceMeters>\n", tdata->distance_meters);
  310. if (gtc_course_flag) { /* course format */
  311. gtc_write_xml(1, "<BeginPosition>\n");
  312. gtc_write_xml(0, "<LatitudeDegrees>%lf</LatitudeDegrees>\n", gtc_start_lat);
  313. gtc_write_xml(0, "<LongitudeDegrees>%lf</LongitudeDegrees>\n", gtc_start_long);
  314. gtc_write_xml(-1,"</BeginPosition>\n");
  315. gtc_write_xml(1, "<EndPosition>\n");
  316. gtc_write_xml(0, "<LatitudeDegrees>%lf</LatitudeDegrees>\n", gtc_end_lat);
  317. gtc_write_xml(0, "<LongitudeDegrees>%lf</LongitudeDegrees>\n", gtc_end_long);
  318. gtc_write_xml(-1,"</EndPosition>\n");
  319. } else { /* activity (history) format */
  320. if (tdata->max_spd) {
  321. gtc_write_xml(0, "<MaximumSpeed>%.3f</MaximumSpeed>\n", tdata->max_spd);
  322. }
  323. gtc_write_xml(0, "<Calories>0</Calories>\n"); /* element is required */
  324. }
  325. if (tdata->avg_hrt) {
  326. gtc_write_xml(1, "<AverageHeartRateBpm xsi:type=\"HeartRateInBeatsPerMinute_t\">\n");
  327. gtc_write_xml(0, "<Value>%d</Value>\n", (int)(tdata->avg_hrt + 0.5));
  328. gtc_write_xml(-1,"</AverageHeartRateBpm>\n");
  329. }
  330. if (tdata->max_hrt) {
  331. gtc_write_xml(1, "<MaximumHeartRateBpm xsi:type=\"HeartRateInBeatsPerMinute_t\">\n");
  332. gtc_write_xml(0, "<Value>%d</Value>\n", (int)(tdata->max_hrt + 0.5));
  333. gtc_write_xml(-1,"</MaximumHeartRateBpm>\n");
  334. }
  335. gtc_write_xml(0, "<Intensity>Active</Intensity>\n");
  336. if (tdata->avg_cad) {
  337. gtc_write_xml(0, "<Cadence>%d</Cadence>\n", tdata->avg_cad);
  338. }
  339. if (!gtc_course_flag) { /* activity (history) format */
  340. gtc_write_xml(0, "<TriggerMethod>Manual</TriggerMethod>\n");
  341. }
  342. }
  343. static void
  344. gtc_act_hdr(const route_head* rte)
  345. {
  346. gtc_write_xml(1, "<Activity Sport=\"%s\">\n", gtc_sportlist[gtc_sport]);
  347. gtc_lap_start(NULL);
  348. gtc_new_study_lap(rte);
  349. route_disp(rte, gtc_study_lap);
  350. if (gtc_least_time.isValid()) {
  351. gtc_write_xml(0, "<Id>%s</Id>\n",
  352. CSTR(gtc_least_time.toPrettyString()));
  353. gtc_write_xml(1, "<Lap StartTime=\"%s\">\n",
  354. CSTR(gtc_least_time.toPrettyString()));
  355. } else {
  356. gtc_write_xml(1, "<Lap>\n");
  357. }
  358. gtc_fake_hdr();
  359. xfree(tdata);
  360. gtc_write_xml(1,"<Track>\n");
  361. }
  362. static void
  363. gtc_act_ftr(const route_head*)
  364. {
  365. gtc_write_xml(-1, "</Track>\n");
  366. gtc_write_xml(-1, "</Lap>\n");
  367. gtc_write_xml(-1, "</Activity>\n");
  368. }
  369. static void
  370. gtc_crs_hdr(const route_head* rte)
  371. {
  372. gtc_write_xml(1, "<Course>\n");
  373. gtc_lap_start(NULL);
  374. gtc_new_study_lap(rte);
  375. route_disp(rte, gtc_study_lap);
  376. if (!rte->rte_name.isEmpty()) {
  377. QString name = rte->rte_name.left(GTC_MAX_NAME_LEN);
  378. gtc_write_xml(0, QString("<Name>%1</Name>\n").arg(name));
  379. } else {
  380. gtc_write_xml(0, "<Name>New Course</Name>\n");
  381. }
  382. /* write_optional_xml_entity(ofd, " ", "Name", rte->rte_name); */
  383. gtc_write_xml(1, "<Lap>\n");
  384. gtc_fake_hdr();
  385. gtc_write_xml(-1, "</Lap>\n");
  386. xfree(tdata);
  387. gtc_write_xml(1,"<Track>\n");
  388. }
  389. static void
  390. gtc_crs_ftr(const route_head*)
  391. {
  392. gtc_write_xml(-1,"</Track>\n");
  393. gtc_write_xml(-1, "</Course>\n");
  394. }
  395. void
  396. gtc_write(void)
  397. {
  398. gtc_write_xml(0, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n");
  399. gtc_write_xml(1, "<TrainingCenterDatabase xmlns=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd\">\n");
  400. if (gtc_course_flag) {
  401. gtc_write_xml(1, "<Courses>\n");
  402. track_disp_all(gtc_crs_hdr, gtc_crs_ftr, gtc_waypt_pr);
  403. gtc_write_xml(-1, "</Courses>\n");
  404. } else {
  405. gtc_write_xml(1, "<Activities>\n");
  406. track_disp_all(gtc_act_hdr, gtc_act_ftr, gtc_waypt_pr);
  407. gtc_write_xml(-1, "</Activities>\n");
  408. }
  409. gtc_write_xml(-1, "</TrainingCenterDatabase>\n");
  410. }
  411. void
  412. gtc_trk_s(xg_string, const QXmlStreamAttributes*)
  413. {
  414. trk_head = route_head_alloc();
  415. track_add_head(trk_head);
  416. }
  417. void
  418. gtc_trk_ident(xg_string args, const QXmlStreamAttributes*)
  419. {
  420. trk_head->rte_name = args;
  421. }
  422. void
  423. gtc_trk_lap_s(xg_string, const QXmlStreamAttributes*)
  424. {
  425. lap_ct++;
  426. lap_s = 1;
  427. }
  428. void
  429. gtc_trk_lap_e(xg_string, const QXmlStreamAttributes*)
  430. {
  431. lap_s = 0;
  432. }
  433. void
  434. gtc_trk_pnt_s(xg_string, const QXmlStreamAttributes*)
  435. {
  436. wpt_tmp = new Waypoint;
  437. }
  438. void
  439. gtc_trk_pnt_e(xg_string, const QXmlStreamAttributes*)
  440. {
  441. if (wpt_tmp->longitude != 0. && wpt_tmp->latitude != 0.) {
  442. if (lap_s) {
  443. /* Add the first point of an ActivityLap as
  444. a waypoint as well as a trackpoint. */
  445. char cbuf[10];
  446. Waypoint* wpt_lap_s = new Waypoint(*wpt_tmp);
  447. snprintf(cbuf, sizeof(cbuf), "LAP%03d", lap_ct);
  448. wpt_lap_s->shortname = cbuf;
  449. waypt_add(wpt_lap_s);
  450. lap_s = 0;
  451. }
  452. track_add_wpt(trk_head, wpt_tmp);
  453. } else {
  454. delete wpt_tmp;
  455. }
  456. wpt_tmp = NULL;
  457. }
  458. void
  459. gtc_trk_utc(xg_string args, const QXmlStreamAttributes*)
  460. {
  461. wpt_tmp->creation_time = xml_parse_time(args);
  462. }
  463. void
  464. gtc_trk_lat(xg_string args, const QXmlStreamAttributes*)
  465. {
  466. wpt_tmp->latitude = args.toDouble();
  467. }
  468. void
  469. gtc_trk_long(xg_string args, const QXmlStreamAttributes*)
  470. {
  471. wpt_tmp->longitude = args.toDouble();
  472. }
  473. void
  474. gtc_trk_alt(xg_string args, const QXmlStreamAttributes*)
  475. {
  476. wpt_tmp->altitude = args.toDouble();
  477. }
  478. void gtc_trk_dist(const QString& args, const QXmlStreamAttributes*)
  479. {
  480. wpt_tmp->odometer_distance = args.toDouble();
  481. }
  482. void gtc_trk_hr(const QString& args, const QXmlStreamAttributes*)
  483. {
  484. wpt_tmp->heartrate = args.toDouble();
  485. }
  486. void gtc_trk_cad(const QString& args, const QXmlStreamAttributes*)
  487. {
  488. wpt_tmp->cadence = args.toDouble();
  489. }
  490. void
  491. gtc_trk_pwr(xg_string args, const QXmlStreamAttributes*)
  492. {
  493. wpt_tmp->power = args.toDouble();
  494. }
  495. void
  496. gtc_trk_spd(xg_string args, const QXmlStreamAttributes*)
  497. {
  498. WAYPT_SET(wpt_tmp, speed, args.toDouble());
  499. }
  500. void
  501. gtc_wpt_crs_s(const QString&, const QXmlStreamAttributes*)
  502. {
  503. wpt_tmp = new Waypoint;
  504. }
  505. void
  506. gtc_wpt_crs_e(xg_string, const QXmlStreamAttributes*)
  507. {
  508. if (wpt_tmp->longitude != 0. && wpt_tmp->latitude != 0.) {
  509. waypt_add(wpt_tmp);
  510. } else {
  511. delete wpt_tmp;
  512. }
  513. wpt_tmp = NULL;
  514. }
  515. void
  516. gtc_wpt_pnt_s(xg_string, const QXmlStreamAttributes*)
  517. {
  518. wpt_tmp = new Waypoint;
  519. lap_ct++;
  520. }
  521. void
  522. gtc_wpt_pnt_e(xg_string, const QXmlStreamAttributes*)
  523. {
  524. if (wpt_tmp->longitude != 0. && wpt_tmp->latitude != 0.) {
  525. /* Add the begin position of a CourseLap as
  526. a waypoint. */
  527. wpt_tmp->shortname = QString().sprintf("LAP%03d", lap_ct);
  528. waypt_add(wpt_tmp);
  529. } else {
  530. delete wpt_tmp;
  531. }
  532. wpt_tmp = NULL;
  533. }
  534. void gtc_wpt_ident(const QString& args, const QXmlStreamAttributes*)
  535. {
  536. wpt_tmp->shortname = (args);
  537. /* Set also as notes for compatibility with garmin usb format */
  538. wpt_tmp->notes = (args);
  539. }
  540. void gtc_wpt_lat(const QString& args, const QXmlStreamAttributes*)
  541. {
  542. wpt_tmp->latitude = args.toDouble();
  543. }
  544. void gtc_wpt_long(const QString& args, const QXmlStreamAttributes*)
  545. {
  546. wpt_tmp->longitude = args.toDouble();
  547. }
  548. void gtc_wpt_icon(const QString& args, const QXmlStreamAttributes*)
  549. {
  550. wpt_tmp->icon_descr = args;
  551. }
  552. void gtc_wpt_notes(const QString& args, const QXmlStreamAttributes*)
  553. {
  554. wpt_tmp->description = args;
  555. }
  556. ff_vecs_t gtc_vecs = {
  557. ff_type_file,
  558. {
  559. ff_cap_read /* waypoints */,
  560. (ff_cap)(ff_cap_read | ff_cap_write) /* tracks */,
  561. ff_cap_none /* routes */
  562. },
  563. gtc_rd_init,
  564. gtc_wr_init,
  565. gtc_rd_deinit,
  566. gtc_wr_deinit,
  567. gtc_read,
  568. gtc_write,
  569. NULL,
  570. gtc_args,
  571. CET_CHARSET_ASCII, 0 /* CET-REVIEW */
  572. };