3 #include "ceres/ceres.h"
4 #include "gflags/gflags.h"
5 #include "glog/logging.h"
6 #include "LatLong-UTMconversion.h"
10 #define GPS_EXTRA_SIGMA_PER_SECOND 1.0
12 #define KD_LEAF_SIZE 5
14 using namespace ceres;
26 double utm_north, utm_east;
42 double len_sq, inv_len_sq;
44 vector<LineSegment> roads;
48 double a, b, d; // split plane
49 vector<int> roads_this_node;
50 BSPTreeNode *left, *right;
52 BSPTreeNode *root = NULL;
54 double signed_distance(double a, double b, double d, Coord c)
56 return a * c.x + b * c.y + d;
59 double split_cost(const vector<int>& remaining_roads, double a, double b, double d)
61 unsigned in_left = 0, in_right = 0, in_both = 0;
62 for (int i = 0; i < remaining_roads.size(); ++i) {
63 double dist_from = signed_distance(a, b, d, roads[remaining_roads[i]].from);
64 double dist_to = signed_distance(a, b, d, roads[remaining_roads[i]].to);
65 if (dist_from > 0.0 && dist_to > 0.0) {
67 } else if (dist_from < 0.0 && dist_to < 0.0) {
74 return max(in_left, in_right) + 1.1 * in_both;
77 BSPTreeNode* make_bsp_tree(const vector<int>& remaining_roads)
79 BSPTreeNode *node = new BSPTreeNode;
80 if (remaining_roads.size() <= KD_LEAF_SIZE) {
82 node->left = node->right = NULL;
83 node->roads_this_node = remaining_roads;
87 node->is_leaf = false;
89 // try all possible split planes (TODO: +/- epsilon)
90 double best_a = 0.0, best_b = 0.0, best_d = 0.0;
91 double best_cost = HUGE_VAL;
92 for (unsigned i = 0; i < remaining_roads.size(); ++i) {
93 const LineSegment& ls = roads[remaining_roads[i]];
96 double a = -(ls.to.x - ls.from.x);
97 double b = ls.to.y - ls.from.y;
98 double invlen = 1.0 / hypot(a, b);
99 a *= invlen, b *= invlen;
101 double d = -(a * ls.from.x + b * ls.from.y);
102 double cost = split_cost(remaining_roads, a, b, d - 1e-6);
103 if (cost < best_cost) {
104 best_a = a, best_b = b, best_d = d - 1e-6;
108 cost = split_cost(remaining_roads, a, b, d + 1e+6);
109 if (cost < best_cost) {
110 best_a = a, best_b = b, best_d = d + 1e+6;
118 vector<int> left, right;
119 for (unsigned i = 0; i < remaining_roads.size(); ++i) {
120 double dist_from = signed_distance(best_a, best_b, best_d, roads[remaining_roads[i]].from);
121 double dist_to = signed_distance(best_a, best_b, best_d, roads[remaining_roads[i]].to);
122 if (dist_from > 0.0 && dist_to > 0.0) {
123 right.push_back(remaining_roads[i]);
124 } else if (dist_from < 0.0 && dist_to < 0.0) {
125 left.push_back(remaining_roads[i]);
127 node->roads_this_node.push_back(remaining_roads[i]);
131 if (left.size() == 0) {
134 node->left = make_bsp_tree(left);
136 if (right.size() == 0) {
139 node->right = make_bsp_tree(right);
144 void intersect_line_line_x(double a1, double b1, double d1, double a2, double b2, double d2, double *min_x, double *max_x)
146 // find somewhere (x,y) where:
148 // A1x + B1y + D1 = 0 [I]
149 // A2x + B2y + D2 = 0 [II]
151 // [I] gives: y = -((A1/B1) x + D1/B1)
154 // A2x - B2 ((A1/B1) x + D1/B1) + D2 = 0
155 // A2x - A1 B2 / B1 x - D1 B2 / B1 + D2 = 0
156 // ((A2 B1 - A1 B2) / B1) x = D1 B2 / B1 - D2 [A2 B1 - A1 B2 = det]
157 // x = D1 B2 / det - D2 B1 / det = (D1 B2 - D2 B1) / det
158 double det = a2 * b1 - a1 * b2;
159 if (fabs(det) <= 1e-6) {
162 double intersect_x = (d1 * b2 - d2 * b1) / det;
164 if (-det * (*max_x - *min_x) < 0) {
165 *min_x = min(*min_x, intersect_x);
166 *max_x = min(*max_x, intersect_x);
168 *min_x = max(*min_x, intersect_x);
169 *max_x = max(*max_x, intersect_x);
173 void intersect_line_line_y(double a1, double b1, double d1, double a2, double b2, double d2, double *min_y, double *max_y)
175 // find somewhere (x,y) where:
177 // A1x + B1y + D1 = 0 [I]
178 // A2x + B2y + D2 = 0 [II]
180 // [I] gives: y = -((B1/A1) x + D1/A1)
183 // B2y - A2 ((B1/A1) x + D1/A1) + D2 = 0
184 // B2y - B1 A2 / A1 x - D1 A2 / A1 + D2 = 0
185 // ((A1 B2 - B1 A2) / A1) y = D1 A2 / A1 - D2 [A1 B2 - B1 A2 = det]
186 // y = D1 A2 / det - D2 A1 / det = (D1 B2 - D2 B1) / det
187 double det = a1 * b2 - a2 * b1;
188 if (fabs(det) <= 1e-6) {
191 double intersect_y = (d1 * a2 - d2 * a1) / det;
192 if (det * (*max_y - *min_y) < 0) {
193 *min_y = min(*min_y, intersect_y);
194 *max_y = min(*max_y, intersect_y);
196 *min_y = max(*min_y, intersect_y);
197 *max_y = max(*max_y, intersect_y);
201 void plot_line_based_on_x(FILE *fp, double a, double b, double d, double x1, double x2)
203 // see intersect_line_line()
204 double y1 = - (a * x1 + d) / b;
205 double y2 = - (a * x2 + d) / b;
206 fprintf(fp, "%f %f %f %f\n", x1, y1, x2, y2);
209 void plot_line_based_on_y(FILE *fp, double a, double b, double d, double y1, double y2)
211 // see intersect_line_line()
212 double x1 = - (b * y1 + d) / a;
213 double x2 = - (b * y2 + d) / a;
214 fprintf(fp, "%f %f %f %f\n", x1, y1, x2, y2);
217 void plot_line(double a, double b, double d)
219 if (fabs(a) > fabs(b)) {
220 double x1 = -2000.0, x2 = 2000.0;
221 if (b > 0) swap(x1, x2);
222 intersect_line_line_x(a, b, d, 0, 1.0, 2000, &x1, &x2);
223 intersect_line_line_x(a, b, d, 0, -1.0, 2000, &x1, &x2);
224 plot_line_based_on_x(stdout, a, b, d, x1, x2);
226 double y1 = -2000.0, y2 = 2000.0;
227 if (a < 0) swap(y1, y2);
228 intersect_line_line_y(a, b, d, 1.0, 0.0, 2000, &y1, &y2);
229 intersect_line_line_y(a, b, d, -1.0, 0.0, 2000, &y1, &y2);
230 plot_line_based_on_y(stdout, a, b, d, y1, y2);
234 void plot_bsp_tree(FILE *fp, BSPTreeNode *node, vector<pair<BSPTreeNode*, bool> > previous_visited)
236 double a = node->a, b = node->b, d = node->d;
238 double x1, x2, y1, y2;
244 if (fabs(a) > fabs(b)) {
245 x1 = -2000.0, x2 = 2000.0;
246 if (b > 0) swap(x1, x2);
247 intersect_line_line_x(a, b, d, 0, 1.0, 2000, &x1, &x2);
248 intersect_line_line_x(a, b, d, 0, -1.0, 2000, &x1, &x2);
250 // clip against all previous lines
251 for (unsigned i = 0; i < previous_visited.size(); ++i) {
252 BSPTreeNode* o = previous_visited[i].first;
253 if (previous_visited[i].second) {
254 intersect_line_line_x(a, b, d, o->a, o->b, o->d, &x1, &x2);
256 intersect_line_line_x(a, b, d, -o->a, -o->b, -o->d, &x1, &x2);
260 if (fabs(x1 - x2) > 1e-6) {
261 plot_line_based_on_x(fp, a, b, d, x1, x2);
264 // copy of the stuff above
265 y1 = -2000.0, y2 = 2000.0;
266 if (a < 0) swap(y1, y2);
267 intersect_line_line_y(a, b, d, 1.0, 0.0, 2000, &y1, &y2);
268 intersect_line_line_y(a, b, d, -1.0, 0.0, 2000, &y1, &y2);
270 // clip against all previous lines
271 for (unsigned i = 0; i < previous_visited.size(); ++i) {
272 BSPTreeNode* o = previous_visited[i].first;
273 if (previous_visited[i].second) {
274 intersect_line_line_y(a, b, d, o->a, o->b, o->d, &y1, &y2);
276 intersect_line_line_y(a, b, d, -o->a, -o->b, -o->d, &y1, &y2);
280 if (fabs(y1 - y2) > 1e-6) {
281 plot_line_based_on_y(fp, a, b, d, y1, y2);
285 previous_visited.push_back(make_pair(node, false));
286 if (node->left != NULL) {
287 plot_bsp_tree(fp, node->left, previous_visited);
289 if (node->right != NULL) {
290 previous_visited.back().second = true;
291 plot_bsp_tree(fp, node->right, previous_visited);
295 char* write_time(time_t t)
297 static char buf[256];
298 struct tm *foo = gmtime(&t);
299 strftime(buf, 256, "%Y-%m-%dT%H:%M:%SZ", foo);
303 class MinimizeAccelerationCostFunction {
305 MinimizeAccelerationCostFunction() {}
308 bool operator() (const T * const p0, const T * const p1, const T * const p2, T* e) const {
309 e[0] = p0[0] - T(2.0) * p1[0] + p2[0];
310 e[1] = p0[1] - T(2.0) * p1[1] + p2[1];
315 double sq_distance_point_point(const Coord& a, const Coord& b)
317 //return hypot(b.x - a.x, b.y - a.y);
318 return (b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y);
321 double sq_distance_point_line_segment(const Coord& c, LineSegment &ls)
323 if (ls.len_sq < 1e-6) {
324 return sq_distance_point_point(c, ls.from);
326 const double pv_x = c.x - ls.from.x;
327 const double pv_y = c.y - ls.from.y;
328 const double t = (pv_x * ls.wv_x + pv_y * ls.wv_y) * ls.inv_len_sq;
330 return sq_distance_point_point(c, ls.from);
333 return sq_distance_point_point(c, ls.to);
337 r.x = ls.from.x + t * ls.wv_x;
338 r.y = ls.from.y + t * ls.wv_y;
339 return sq_distance_point_point(c, r);
342 pair<int, double> find_closest_road_nonbsp(Coord c)
345 double sq_best_distance = HUGE_VAL;
346 for (unsigned i = 0; i < roads.size(); ++i) {
347 double sq_distance = sq_distance_point_line_segment(c, roads[i]);
348 if (sq_distance < sq_best_distance) {
350 sq_best_distance = sq_distance;
353 return make_pair(best_idx, std::sqrt(sq_best_distance));
357 BSPTreeJob(BSPTreeNode* node, BSPTreeNode* parent) : node(node), parent(parent) {}
358 BSPTreeJob(const BSPTreeJob& other) : node(other.node), parent(other.parent) {}
363 vector<BSPTreeJob> bsp_todo;
364 //vector<BSPTreeNode*> bsp_todo;
366 pair<int, double> find_closest_road(Coord c)
369 double sq_best_distance = HUGE_VAL;
372 bsp_todo.push_back(BSPTreeJob(root, NULL));
375 while (bsp_todo.size() > bsp_pos) {
376 BSPTreeJob job = bsp_todo[bsp_pos++];
377 BSPTreeNode* node = job.node;
379 // Re-verify versus sq_best_distance here, since it might have changed.
380 if (job.parent != NULL) {
381 double dist = signed_distance(job.parent->a, job.parent->b, job.parent->d, c);
382 if (dist*dist > sq_best_distance) {
383 // OK, later changes to best_distance has rendered this job moot.
389 // Check all nodes that are interior to this.
390 for (unsigned i = 0; i < node->roads_this_node.size(); ++i) {
391 int idx = node->roads_this_node[i];
392 double sq_distance = sq_distance_point_line_segment(c, roads[idx]);
393 if (sq_distance < sq_best_distance || (sq_distance == sq_best_distance && idx < best_idx)) {
395 sq_best_distance = sq_distance;
403 double dist = signed_distance(node->a, node->b, node->d, c);
404 if (dist * dist <= sq_best_distance) {
405 // gah! we have to put both in there.
406 // try at least the correct side first.
408 if (node->left != NULL) {
409 bsp_todo.push_back(BSPTreeJob(node->left, node));
416 if (node->right != NULL) {
417 bsp_todo.push_back(BSPTreeJob(node->right, node));
425 // woo, we can do with going on only one side
427 // go on to the right side
435 // go on to the left side
445 return make_pair(best_idx, std::sqrt(sq_best_distance));
448 class DistanceFromRoadCostFunction : public SizedCostFunction<1, 2, 2> {
450 virtual bool Evaluate(double const* const* p,
452 double** jacobians) const {
453 (void) jacobians; // Ignored; filled in by numeric differentiation.
463 pair<int, double> closest_road = find_closest_road(c);
465 // verify bsp behavior
466 pair<int, double> closest_road_nonbsp = find_closest_road_nonbsp(c);
467 if (closest_road.first != closest_road_nonbsp.first) {
468 printf("oops! for <%f,%f> bsp=(%d,%.10f) and nonbsp=(%d,%.10f)\n",
469 c.x, c.y, closest_road.first, closest_road.second,
470 closest_road_nonbsp.first, closest_road_nonbsp.second);
471 printf("bsp:\n%f %f\n%f %f\n",
472 roads[closest_road.first].from.x,
473 roads[closest_road.first].from.y,
474 roads[closest_road.first].to.x,
475 roads[closest_road.first].to.y);
476 printf("Nd:\n%f %f\n%f %f\n",
477 roads[closest_road_nonbsp.first].from.x,
478 roads[closest_road_nonbsp.first].from.y,
479 roads[closest_road_nonbsp.first].to.x,
480 roads[closest_road_nonbsp.first].to.y);
484 double prev_distance = std::sqrt(sq_distance_point_line_segment(prev_c, roads[closest_road.first]));
487 //e[0] = 2.0 - std::exp(-(best_distance * best_distance)) - std::exp(-(prev_distance * prev_distance));
488 //e[0] = 0.2 * (best_distance + 0.5 * prev_distance);
489 //e[0] = ceres::log(best_distance);
490 e[0] = closest_road.second + 0.5 * prev_distance;
495 class FootDiffCostFunction {
497 FootDiffCostFunction(double speed) : speed_(speed) {}
500 bool operator() (const T * const p0, const T * const p1, T* e) const {
501 T ex = p1[0] - p0[0];
502 T ey = p1[1] - p0[1];
503 T speed = 0.5 * ceres::sqrt(ex * ex + ey * ey);
504 e[0] = speed - speed_;
512 class GPSDiffCostFunction {
514 GPSDiffCostFunction(double x, double y) : x_(x), y_(y) {}
517 bool operator() (const T * const p, T* e) const {
518 T x_dist = p[0] - T(x_);
519 T y_dist = p[1] - T(y_);
520 T sq_dist = x_dist * x_dist + y_dist * y_dist;
521 e[0] = T(10.0) * (T(1.0) - ceres::exp(-sq_dist / T(GPS_SIGMA * GPS_SIGMA)));
522 //e[0] = sq_dist / GPS_SIGMA;
531 class MyIterationCallback : public IterationCallback {
533 MyIterationCallback(Coord *c, int num_points) : c_(c), num_points_(num_points) {}
535 virtual CallbackReturnType operator()(const IterationSummary& summary)
538 sprintf(buf, "temp%04d.plot", summary.iteration);
539 FILE *fp = fopen(buf, "w");
540 for (int i = 0; i < num_points_; ++i) {
541 fprintf(fp, "%f %f\n", c_[i].x, c_[i].y);
544 return SOLVER_CONTINUE;
553 int main(int argc, char** argv) {
554 google::ParseCommandLineFlags(&argc, &argv, true);
555 google::InitGoogleLogging(argv[0]);
557 int max_time = INT_MIN, min_time = INT_MAX;
559 vector<TCXPoint> tcx_points;
560 vector<FootPoint> foot_points;
562 FILE *fp = fopen(argv[1], "r");
570 if (fgets(buf, 256, fp) == NULL) {
573 if (strncmp(buf, "tcx ", 4) == 0) {
575 if (sscanf(buf, "tcx %d %lf %lf %lf %d", &p.time, &p.lat, &p.lon, &p.alt, &p.hr) != 5) {
576 fprintf(stderr, "parse error: %s\n", buf);
580 LLtoUTM(23, p.lat * rad2deg, p.lon * rad2deg, p.utm_north, p.utm_east, p.utm_zone);
582 tcx_points.push_back(p);
583 max_time = max(max_time, p.time);
584 min_time = min(min_time, p.time);
587 if (strncmp(buf, "footpod ", 8) == 0) {
589 if (sscanf(buf, "footpod %d %d %d %lf %lf", &p.time, &p.hr, &p.cadence, &p.speed, &p.distance) != 5) {
590 fprintf(stderr, "parse error: %s\n", buf);
593 foot_points.push_back(p);
594 max_time = max(max_time, p.time);
595 min_time = min(min_time, p.time);
601 // normalize the utm guys a bit
602 double utm_north_sum = 0.0, utm_east_sum = 0.0;
603 for (unsigned i = 0; i < tcx_points.size(); ++i) {
604 utm_north_sum += tcx_points[i].utm_north;
605 utm_east_sum += tcx_points[i].utm_east;
607 utm_north_sum /= tcx_points.size();
608 utm_east_sum /= tcx_points.size();
609 for (unsigned i = 0; i < tcx_points.size(); ++i) {
610 tcx_points[i].utm_north -= utm_north_sum;
611 tcx_points[i].utm_east -= utm_east_sum;
615 fp = fopen(argv[2], "r");
622 double lat1, lon1, lat2, lon2;
623 if (fscanf(fp, "%lf %lf %lf %lf\n", &lat1, &lon1, &lat2, &lon2) != 4) {
629 LLtoUTM(23, lat1, lon1, seg.from.y, seg.from.x, utm_zone);
630 LLtoUTM(23, lat2, lon2, seg.to.y, seg.to.x, utm_zone);
632 seg.from.y -= utm_north_sum;
633 seg.from.x -= utm_east_sum;
634 seg.to.y -= utm_north_sum;
635 seg.to.x -= utm_east_sum;
637 seg.len_sq = (seg.to.y - seg.from.y) * (seg.to.y - seg.from.y) + (seg.to.x - seg.from.x) * (seg.to.x - seg.from.x);
638 seg.inv_len_sq = 1.0 / seg.len_sq;
639 seg.wv_x = seg.to.x - seg.from.x;
640 seg.wv_y = seg.to.y - seg.from.y;
642 roads.push_back(seg);
646 vector<int> all_road_indexes;
647 for (unsigned i = 0; i < roads.size(); ++i) {
648 all_road_indexes.push_back(i);
650 root = make_bsp_tree(all_road_indexes);
652 fp = fopen("bsptree.plot", "w");
653 plot_bsp_tree(fp, root, vector<pair<BSPTreeNode*, bool> >());
656 fp = fopen("map.plot", "w");
657 for (unsigned i = 0; i < roads.size(); ++i) {
658 fprintf(fp, "%f %f %f %f\n", roads[i].from.x, roads[i].from.y, roads[i].to.x, roads[i].to.y);
662 int num_points = max_time - min_time + 1;
667 // Seed the initial path with some guesses.
668 for (int t = min_time; t <= max_time; ++t) {
669 // find the closest TCX point (yeah, yeah, linear search...)
671 int best_distance = INT_MAX;
672 for (unsigned i = 0; i < tcx_points.size(); ++i) {
673 int distance = abs(t - tcx_points[i].time);
674 if (distance < best_distance) {
676 best_distance = distance;
680 c[t - min_time].x = tcx_points[best_idx].utm_east + (rand() % 1000) / 1000.0;
681 c[t - min_time].y = tcx_points[best_idx].utm_north + (rand() % 1000) / 1000.0;
684 // Create regularization constraints.
685 for (int t = min_time + 1; t <= max_time - 1; ++t) {
686 problem.AddResidualBlock(
687 new AutoDiffCostFunction<MinimizeAccelerationCostFunction, 2, 2, 2, 2>(
688 new MinimizeAccelerationCostFunction),
690 reinterpret_cast<double *>(&c[(t - min_time) - 1]),
691 reinterpret_cast<double *>(&c[(t - min_time) + 0]),
692 reinterpret_cast<double *>(&c[(t - min_time) + 1]));
695 // Create close-to-road constraints.
696 for (int t = min_time + 1; t <= max_time; ++t) {
697 problem.AddResidualBlock(
698 new NumericDiffCostFunction<DistanceFromRoadCostFunction, FORWARD, 1, 2, 2>(
699 new DistanceFromRoadCostFunction,
700 ceres::TAKE_OWNERSHIP),
702 reinterpret_cast<double *>(&c[t - min_time]),
703 reinterpret_cast<double *>(&c[t - min_time - 1]));
706 // Now create one contraint per GPS point.
707 for (unsigned i = 0; i < tcx_points.size(); ++i) {
708 int t = tcx_points[i].time;
709 problem.AddResidualBlock(
710 new AutoDiffCostFunction<GPSDiffCostFunction, 1, 2>(
711 new GPSDiffCostFunction(tcx_points[i].utm_east, tcx_points[i].utm_north)),
713 reinterpret_cast<double *>(&c[t - min_time]));
716 // And per foot point.
717 for (unsigned i = 0; i < foot_points.size(); ++i) {
718 int t = foot_points[i].time;
719 if (t - 1 < min_time || t + 1 >= max_time) {
722 problem.AddResidualBlock(
723 new AutoDiffCostFunction<FootDiffCostFunction, 1, 2, 2>(
724 new FootDiffCostFunction(foot_points[i].speed)),
726 reinterpret_cast<double *>(&c[(t - min_time) - 1]),
727 reinterpret_cast<double *>(&c[(t - min_time) + 1]));
730 printf("pushed %d+%d points\n", int(tcx_points.size()), int(foot_points.size()));
731 printf("time from %d to %d\n", min_time, max_time);
734 Solver::Options options;
735 options.max_num_iterations = 1000000;
736 options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
737 //options.linear_solver_type = ceres::DENSE_QR;
738 options.minimizer_progress_to_stdout = true;
741 options.update_state_every_iteration = true;
742 options.callbacks.push_back(new MyIterationCallback(c, num_points));
744 Solver::Summary summary;
745 Solve(options, &problem, &summary);
746 // if (summary.termination_type == NUMERICAL_FAILURE) {
750 fp = fopen("out.plot", "w");
751 for (int t = min_time; t <= max_time; ++t) {
752 fprintf(fp, "%d %f %f\n", t, c[t - min_time].x, c[t - min_time].y);
756 fp = fopen("out.tcx", "w");
757 fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
758 fprintf(fp, "<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");
759 fprintf(fp, " <Activities>\n");
760 fprintf(fp, " <Activity Sport=\"Running\">\n");
761 fprintf(fp, " <Id>smoothed</Id>\n");
762 fprintf(fp, " <Lap StartTime=\"%s\">\n", write_time(min_time));
763 fprintf(fp, " <TotalTimeSeconds>%f</TotalTimeSeconds>\n", float(max_time - min_time + 1));
764 fprintf(fp, " <DistanceMeters>3720.0</DistanceMeters>\n"); // FIXME
765 fprintf(fp, " <Intensity>Active</Intensity>\n");
766 fprintf(fp, " <TriggerMethod>Manual</TriggerMethod>\n");
767 fprintf(fp, " <Track>\n");
769 for (int t = min_time; t <= max_time; ++t) {
771 double north = c[t - min_time].y + utm_north_sum;
772 double east = c[t - min_time].x + utm_east_sum;
776 tcx_points[0].utm_zone,
778 fprintf(fp, " <TrackPoint>\n");
779 fprintf(fp, " <Time>%s</Time>\n", write_time(t));
780 fprintf(fp, " <Position>\n");
781 fprintf(fp, " <LatitudeDegrees>%.10f</LatitudeDegrees>\n", lat);
782 fprintf(fp, " <LongitudeDegrees>%.10f</LongitudeDegrees>\n", lon);
783 fprintf(fp, " </Position>\n");
784 fprintf(fp, " </TrackPoint>\n");
786 fprintf(fp, " </Track>\n");
787 fprintf(fp, " </Lap>\n");
788 fprintf(fp, " <Notes>Created by auto-smoothing points</Notes>\n");
789 fprintf(fp, " </Activity>\n");
790 fprintf(fp, " </Activities>\n");
791 fprintf(fp, "</TrainingCenterDatabase>\n");
794 std::cout << summary.BriefReport() << "\n";