]> err.no Git - mapper/blob - src/path.c
Remove old map sources
[mapper] / src / path.c
1 /*
2  * This file is part of mapper
3  *
4  * Copyright (C) 2008 Kaj-Michael Lang
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 #include <config.h>
21
22 #include <glib.h>
23 #include <gtk/gtk.h>
24 #include <sqlite3.h>
25 #include <libxml/parser.h>
26 #include <libgnomevfs/gnome-vfs.h>
27
28 #include "path.h"
29 #include "position.h"
30 #include "utils.h"
31 #include "gps.h"
32 #include "settings.h"
33 #include "latlon.h"
34
35 struct sql_select_stmt {
36         sqlite3_stmt *select_paths;
37         sqlite3_stmt *select_path_nodes;
38
39         sqlite3_stmt *insert_path;
40         sqlite3_stmt *insert_path_node;
41
42         sqlite3_stmt *delete_path;
43         sqlite3_stmt *delete_path_nodes;
44 };
45 static struct sql_select_stmt sql;
46
47 #define PATH_TABLE_PATHS "create table IF NOT EXISTS paths ( \
48         nid             int primary key, \
49         name    text not null, \
50         desc    text, \
51         t               int not null);"
52
53 #define PATH_TABLE_NODES "create table IF NOT EXISTS path_nodes ( \
54         nid             int not null, \
55         lat     real not null, \
56         lon     real not null, \
57         sats    int, \
58         speed   real, \
59         course  real, \
60         pdop    real, \
61         hdop    real, \
62         vdop    real, \
63         name    text default null, \
64         t               int not null);"
65
66 enum {
67         NEW_POINT,                              /* A new point was appended to track track */
68         NEW_BREAK,                              /* A break was appended to the track */
69         NEW_WAYPOINT,                   /* A new waypoint/marker has been added */
70         NEAR_WAYPOINT,                  /* We are near the next route waypoint, announce it */
71         REACHED_WAYPOINT,               /* We have reached the next route waypoint, announce it */
72         REACHED_DESTINATION,    /* We have reached the last route waypoint */
73         CLEARED,                                /* Path was cleared */
74         LAST_SIGNAL
75 };
76 static guint32 signals[LAST_SIGNAL] = {0};
77
78 /** This enum defines the states of the SAX parsing state machine. */
79 typedef enum {
80         START=1,
81         INSIDE_GPX=100,
82         INSIDE_METADATA,
83         INSIDE_PATH,
84         INSIDE_PATH_SEGMENT,
85         INSIDE_PATH_POINT,
86         INSIDE_PATH_POINT_ELE,
87         INSIDE_PATH_POINT_TIME,
88         INSIDE_PATH_POINT_DESC,
89         INSIDE_PATH_POINT_NAME,
90         FINISH=5000,
91         UNKNOWN=6666,
92         ERROR=9999,
93 } SaxState;
94
95 /** Data used during the SAX parsing operation. */
96 typedef struct _SaxData SaxData;
97 struct _SaxData {
98         Path *path;
99         SaxState state;
100         SaxState prev_state;
101         guint unknown_depth;
102         gboolean at_least_one_trkpt;
103         GString *chars;
104 };
105
106 static gchar XML_TZONE[7];
107
108 G_DEFINE_TYPE(Path, path, G_TYPE_OBJECT);
109
110 #define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PATH_TYPE, PathPrivate))
111
112 static void
113 path_dispose(GObject *object)
114 {
115 g_debug("path_dispose");
116
117 G_OBJECT_CLASS(path_parent_class)->dispose(object);
118 }
119
120 static void
121 path_finalize(GObject *object)
122 {
123 Path *path=PATH(object);
124
125 g_debug("path_finalize");
126 MACRO_PATH_FREE(*path);
127
128 if (path->name)
129         g_free(path->name);
130 if (path->desc)
131         g_free(path->desc);
132 if (path->author)
133         g_free(path->author);
134 if (path->keywords)
135         g_free(path->keywords);
136 if (path->copyright)
137         g_free(path->copyright);
138 if (path->src)
139         g_free(path->src);
140 }
141
142 static void
143 path_class_init(PathClass *klass)
144 {
145 GObjectClass *object_class=G_OBJECT_CLASS(klass);
146
147 g_debug("path_class_init");
148
149 object_class->dispose=path_dispose;
150 object_class->finalize=path_finalize;
151 /* g_type_class_add_private (klass, sizeof(PathPrivate)); */
152
153 signals[NEW_POINT]=g_signal_new("new-point", G_OBJECT_CLASS_TYPE(object_class),
154         G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(PathClass, new_point),
155         NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
156 signals[NEW_BREAK]=g_signal_new("new-break", G_OBJECT_CLASS_TYPE(object_class),
157         G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(PathClass, new_break),
158         NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
159 signals[NEW_WAYPOINT]=g_signal_new("new-waypoint", G_OBJECT_CLASS_TYPE(object_class),
160         G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(PathClass, new_waypoint),
161         NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
162 }
163
164 static void
165 path_init(Path *path)
166 {
167 g_debug("path_init");
168 MACRO_PATH_INIT(*path);
169 }
170
171 Path *
172 path_new(PathType type, guint id)
173 {
174 Path *p;
175 static time_t time1=0;
176 struct tm time2;
177
178 p=g_object_new(PATH_TYPE, NULL);
179 p->type=type;
180 p->id=id;
181 p->sensitivity=3;
182
183 if (time1==0) {
184         time1=time(NULL);
185         localtime_r(&time1, &time2);
186         g_snprintf(XML_TZONE, sizeof(XML_TZONE), "%+03ld:%02ld", (time2.tm_gmtoff / 60 / 60), (time2.tm_gmtoff / 60) % 60);
187 }
188
189 return p;
190 }
191
192 void
193 path_free(Path *path)
194 {
195 g_return_if_fail(path);
196 g_object_unref(path);
197 }
198
199 void
200 path_clear(Path *path)
201 {
202 g_return_if_fail(path);
203 MACRO_PATH_FREE(*path);
204 path->length=path->avgspeed=0.0;
205 path->points=0;
206 }
207
208 static gboolean
209 path_resize(Path *path, guint size)
210 {
211 g_return_val_if_fail(path, FALSE);
212
213 if (path->head + size != path->cap) {
214         WayPoint *curr;
215         Point *old_head = path->head;
216
217         path->head = g_renew(Point, old_head, size);
218         g_assert(path->head);
219         path->cap = path->head + size;
220         if (path->head != old_head) {
221                 path->tail = path->head + (path->tail - old_head);
222
223                 /* Adjust all of the waypoints. */
224                 for (curr = path->whead - 1; curr++ != path->wtail;)
225                         curr->point = path->head + (curr->point - old_head);
226         }
227         return TRUE;
228 }
229 return FALSE;
230 }
231
232 static gboolean 
233 path_wresize(Path *path, guint wsize)
234 {
235 g_return_val_if_fail(path, FALSE);
236
237 if (path->whead + wsize != path->wcap) {
238         WayPoint *old_whead = path->whead;
239
240         path->whead = g_renew(WayPoint, old_whead, wsize);
241         path->wtail = path->whead + (path->wtail - old_whead);
242         path->wcap = path->whead + wsize;
243
244         return TRUE;
245 }
246 return FALSE;
247 }
248
249 /**
250  * path_add_waypoint:
251  * @path
252  * @lat
253  * @lon
254  * @desc
255  *
256  * Append a waypoint to path at given lat, lon with description desc.
257  *
258  * Returns: TRUE
259  */
260 gboolean
261 path_add_waypoint(Path *path, gdouble lat, gdouble lon, gchar *desc)
262 {
263 guint unitx, unity;
264
265 latlon2unit(lat, lon, unitx, unity);
266 MACRO_PATH_INCREMENT_TAIL(*path);
267 path->tail->unitx=unitx;
268 path->tail->unity=unity;
269 path->tail->time=0;
270 path->tail->altitude=NAN;
271
272 MACRO_PATH_INCREMENT_WTAIL(*path);
273 path->wtail->point=path->tail;
274 path->wtail->desc=desc;
275
276 path_find_nearest_point(path);
277
278 g_signal_emit(G_OBJECT(path), signals[NEW_WAYPOINT], 0, NULL);
279
280 return TRUE;
281 }
282
283 /**
284  * path_add_point:
285  * @path
286  * @gps
287  *
288  * Append a point using given gps data. Adds a path break if gps is not valid.
289  *
290  * Returns: TRUE if the new point was added. FALSE is returned if new point distance was under sensitivity setting.
291  */
292 gboolean
293 path_add_point(Path *path, GpsData *gps)
294 {
295 guint unitx, unity;
296
297 g_return_val_if_fail(path, FALSE);
298
299 if (!gps) {
300         path_add_break(path);
301         return FALSE;
302 }
303
304 return path_add_latlon(path, gps->lat, gps->lon, gps->time, gps->speed, gps->altitude);
305 }
306
307 /**
308  * path_add_latlon:
309  * @path
310  * @lat
311  * @lon
312  * @ptime
313  * @speed
314  * @altitude
315  *
316  * Append a point with given lat,lon,ptime,speed and altitude to given path.
317  *
318  * Returns: TRUE if the new point was added. FALSE is returned if new point distance was under sensitivity setting.
319  */
320 gboolean 
321 path_add_latlon(Path *path, gdouble lat, gdouble lon, time_t ptime, gfloat speed, gfloat altitude)
322 {
323 guint unitx, unity;
324
325 latlon2unit(lat, lon, unitx, unity);
326
327 if (abs((gint)unitx-path->tail->unitx) > path->sensitivity || abs((gint)unity-path->tail->unity) > path->sensitivity) {
328         if (path->tail->unity && path->tail->unitx) {
329                 gdouble plat, plon;
330
331                 unit2latlon(path->tail->unitx, path->tail->unity, lat, lon);
332                 path->length+=calculate_distance(plat, plon, lat, lon);
333         }
334         MACRO_PATH_INCREMENT_TAIL(*path);
335         path->tail->unitx=unitx;
336         path->tail->unity=unity;
337         path->tail->time=ptime;
338         path->tail->altitude=altitude;
339         if (speed>path->maxspeed)
340                 path->maxspeed=speed;
341         path->tspeed+=speed;
342         path->avgspeed=(path->points>0) ? path->tspeed/path->points : 0.0;
343         path->points++;
344         g_debug("TRACK: %f %f (%d)", path->length, path->avgspeed, path->points);
345
346         g_signal_emit(G_OBJECT(path), signals[NEW_POINT], 0, NULL);
347         return TRUE;
348 }
349
350 return FALSE;
351
352 }
353
354 /**
355  * path_add_break:
356  * @path
357  *
358  * Add a break point to the path.
359  * 
360  * Returns: TRUE if break was added, FALSE if last point was a break already.
361  */
362 gboolean 
363 path_add_break(Path *path)
364 {
365 g_return_val_if_fail(path, FALSE);
366 g_return_val_if_fail(path->tail, FALSE);
367
368 if (path->tail->unity && path->tail->unitx) {
369         /* To mark a "break" in a track, we'll add a (0, 0) point and then 
370            another instance of the most recent track point. */
371
372         MACRO_PATH_INCREMENT_TAIL(*path);
373         *path->tail=_point_null;
374         MACRO_PATH_INCREMENT_TAIL(*path);
375         *path->tail=path->tail[-2];
376
377         g_signal_emit(G_OBJECT(path), signals[NEW_BREAK], 0, NULL);
378
379         return TRUE;
380 }
381 return FALSE;
382 }
383
384 /**
385  * path_has_points:
386  * @path
387  *
388  * Checks if given path has any path points
389  *
390  * Returns: TRUE if there are points, FALSE otherwise
391  */
392 gboolean
393 path_has_points(Path *path)
394 {
395 g_return_val_if_fail(path, FALSE);
396 return path->head==path->tail ? FALSE : TRUE;
397 }
398
399 /**
400  * path_has_waypoints:
401  * 
402  * Checks if given path has any waypoints
403  * 
404  * Returns: TRUE if there are waypoints, FALSE otherwise
405  */
406 gboolean
407 path_has_waypoints(Path *path)
408 {
409 g_return_val_if_fail(path, FALSE);
410 return path->whead==path->wtail ? FALSE : TRUE;
411 }
412
413 /**
414  * path_find_last_point:
415  * @path
416  *
417  * Returns: The last path point or NULL if path is empty
418  */
419 Point *
420 path_find_last_point(Path *path)
421 {
422 Point *p=NULL;
423
424 g_return_val_if_fail(path, NULL);
425
426 if (!path_has_points(path))
427         return p;
428
429 for (p=path->tail; !p->unity; p--) {
430 }
431 return p;
432 }
433
434 /**
435  * Updates near_point, next_way, and next_wpt.  If quick is FALSE (as
436  * it is when this function is called from route_find_nearest_point), then
437  * the entire list (starting from near_point) is searched.  Otherwise, we
438  * stop searching when we find a point that is farther away.
439  */
440 static gboolean 
441 path_update_nears(Path *path, Point *point, gboolean quick)
442 {
443 gboolean ret = FALSE;
444 Point *curr, *near;
445 WayPoint *wcurr, *wnext;
446 guint64 near_dist_squared;
447
448 g_return_val_if_fail(path, FALSE);
449
450 /* If we have waypoints (_next_way != NULL), then determine the "next
451  * waypoint", which is defined as the waypoint after the nearest point,
452  * UNLESS we've passed that waypoint, in which case the waypoint after
453  * that waypoint becomes the "next" waypoint. */
454 if (path->next_way) {
455         /* First, set near_dist_squared with the new distance from
456          * near_point. */
457         near = path->near_point;
458         near_dist_squared = DISTANCE_SQUARED(*point, *near);
459
460         /* Now, search path for a closer point.  If quick is TRUE, then we'll
461          * only search forward, only as long as we keep finding closer points.
462          */
463         for (curr = path->near_point; curr++ != path->tail;) {
464                 if (curr->unity) {
465                         guint dist_squared = DISTANCE_SQUARED(*point, *curr);
466                         if (dist_squared <= near_dist_squared) {
467                                 near = curr;
468                                 near_dist_squared = dist_squared;
469                         } else if (quick)
470                                 break;
471                 }
472         }
473
474         /* Update _near_point. */
475         path->near_point = near;
476         path->near_point_dist_squared = near_dist_squared;
477
478         for (wnext = wcurr = path->next_way; wcurr != path->wtail; wcurr++) {
479                 if (wcurr->point < near || (wcurr->point == near && quick 
480                                 && (path->next_wpt && (DISTANCE_SQUARED(*point, *near) > path->next_way_dist_squared
481                                 && DISTANCE_SQUARED(*point, *path->next_wpt) < path->next_wpt_dist_squared))))
482                     /* Okay, this else if expression warrants explanation.  If the
483                      * nearest track point happens to be a waypoint, then we want to
484                      * check if we have "passed" that waypoint.  To check this, we
485                      * test the distance from _gps to the waypoint and from _gps to
486                      * _next_wpt, and if the former is increasing and the latter is
487                      * decreasing, then we have passed the waypoint, and thus we
488                      * should skip it.  Note that if there is no _next_wpt, then
489                      * there is no next waypoint, so we do not skip it in that case. */
490                         wnext = wcurr + 1;
491                 else
492                         break;
493         }
494
495         if (wnext == path->wtail && (wnext->point < near || (wnext->point == near && quick
496                                           && (path->next_wpt && (DISTANCE_SQUARED (*point, *near) > path->next_way_dist_squared
497                                                && DISTANCE_SQUARED(*point, *path->next_wpt) < path->next_wpt_dist_squared)))))
498         {
499                 path->next_way = NULL;
500                 path->next_wpt = NULL;
501                 path->next_way_dist_squared = -1;
502                 path->next_wpt_dist_squared = -1;
503                 ret = TRUE;
504         }
505         /* Only update next_way (and consequently _next_wpt) if _next_way is
506          * different, and record that fact for return. */
507         else {
508                 if (!quick || path->next_way != wnext) {
509                         path->next_way = wnext;
510                         path->next_wpt = wnext->point;
511                         if (path->next_wpt == path->tail)
512                                 path->next_wpt = NULL;
513                         else {
514                                 while (!(++path->next_wpt)->unity) {
515                                         if (path->next_wpt == path->tail) {
516                                                 path->next_wpt = NULL;
517                                                 break;
518                                         }
519                                 }
520                         }
521                         ret = TRUE;
522                 }
523                 path->next_way_dist_squared = DISTANCE_SQUARED(*point, *wnext->point);
524                 if (path->next_wpt)
525                         path->next_wpt_dist_squared = DISTANCE_SQUARED(*point, *path->next_wpt);
526         }
527 }
528 return ret;
529 }
530
531 /**
532  * path_find_nearest_point:
533  *
534  * Reset the near_point data by searching the entire path for the nearest point and waypoint.
535  */
536 void 
537 path_find_nearest_point(Path *path)
538 {
539 g_return_if_fail(path);
540
541 /* Initialize near_point to first non-zero point. */
542 path->near_point=path->head;
543 while (!path->near_point->unity && path->near_point != path->tail)
544         path->near_point++;
545
546 /* Initialize next_way */
547 if (path->wtail==path->whead)
548         path->next_way=NULL;
549 else
550         path->next_way=path->whead;
551 path->next_way_dist_squared=-1;
552
553 /* Initialize next_wpt */
554 path->next_wpt=NULL;
555 path->next_wpt_dist_squared=-1;
556
557 }
558
559 /**
560  * path_find_nearest_waypoint:
561  *
562  */
563 WayPoint *
564 path_find_nearest_waypoint(Path *path, guint unitx, guint unity)
565 {
566 WayPoint *wcurr;
567 WayPoint *wnear;
568 guint64 nearest_squared;
569 Point pos = { unitx, unity, 0, NAN };
570
571 g_return_val_if_fail(path, NULL);
572
573 wcurr=wnear=path->whead;
574 if (wcurr && wcurr->point && wcurr!=path->wtail) {
575         nearest_squared=DISTANCE_SQUARED(pos, *(wcurr->point));
576
577         while (wcurr++!=path->wtail) {
578                 guint64 test_squared=DISTANCE_SQUARED(pos, *(wcurr->point));
579                 if (test_squared < nearest_squared) {
580                         wnear=wcurr;
581                         nearest_squared=test_squared;
582                 }
583         }
584 }
585
586 if (wnear && wnear->point) {
587         if (abs(unitx - wnear->point->unitx) < path->sensitivity && abs(unity - wnear->point->unity) < path->sensitivity)
588                 return wnear;
589 }
590
591 return NULL;
592 }
593
594 /**
595  * path_check_waypoint_announce:
596  * @path
597  * @gps 
598  *
599  * Check if we should announce upcoming waypoint.
600  *
601  */
602 static void
603 path_check_waypoint_announce(Path *path, GpsData *gps)
604 {
605 guint a_thres_near, a_thres_at;
606
607 g_return_if_fail(path);
608 g_return_if_fail(gps);
609
610 if (!path->next_way)
611         return;
612
613 a_thres_near=(20+(guint)gps->speed)*path->announce_notice_ratio*3;
614 a_thres_at=(20+(guint)gps->speed);
615
616 if (path->next_way_dist_squared<(a_thres_near * a_thres_near) && path->next_way!=path->announced_waypoint) {
617         g_signal_emit(G_OBJECT(path), signals[NEAR_WAYPOINT], 0, NULL);
618         path->announced_waypoint=path->next_way;
619 } else if (path->next_way_dist_squared<(a_thres_at * a_thres_at) && path->next_way!=path->announced_waypoint) {
620         g_signal_emit(G_OBJECT(path), signals[REACHED_WAYPOINT], 0, NULL);
621         path->announced_waypoint=path->next_way;
622 }
623 }
624
625 /******************************************************************************/
626
627 gdouble
628 path_get_distance_to(Path *path, Point *point, gdouble lat, gdouble lon)
629 {
630 gdouble lat1, lon1, lat2, lon2;
631 gdouble sum=0.0;
632
633 g_return_val_if_fail(path, 0.0);
634
635 /* If point is NULL, use the next waypoint. */
636 if (point == NULL && path->next_way)
637         point = path->next_way->point;
638
639 /* If point is still NULL, return an error. */
640 if (point==NULL)
641         return FALSE;
642
643 lat1=lat;
644 lon1=lon;
645
646 if (point > path->near_point) {
647         Point *curr;
648         /* Skip _near_point in case we have already passed it. */
649         for (curr = path->near_point + 1; curr <= point; ++curr) {
650                 if (curr->unity) {
651                         unit2latlon(curr->unitx, curr->unity, lat2, lon2);
652                         sum += calculate_distance(lat1, lon1, lat2, lon2);
653                         lat1 = lat2;
654                         lon1 = lon2;
655                 }
656         }
657 } else if (point < path->near_point) {
658         Point *curr;
659         /* Skip near_point in case we have already passed it. */
660         for (curr = path->near_point - 1; curr >= point; --curr) {
661                 if (curr->unity) {
662                         unit2latlon(curr->unitx, curr->unity, lat2, lon2);
663                         sum += calculate_distance(lat1, lon1, lat2, lon2);
664                         lat1 = lat2;
665                         lon1 = lon2;
666                 }
667         }
668 } else {
669         /* Waypoint _is_ the nearest point. */
670         unit2latlon(path->near_point->unitx, path->near_point->unity, lat2, lon2);
671         sum += calculate_distance(lat1, lon1, lat2, lon2);
672 }
673 return sum;
674 }
675
676 /******************************************************************************/
677
678 gboolean
679 path_load(Path *path, const gchar *file)
680 {
681 gchar *pfile;
682 gchar *bytes;
683 gint size;
684
685 g_return_val_if_fail(path, FALSE);
686 g_return_val_if_fail(file, FALSE);
687
688 if (gnome_vfs_read_entire_file(file, &size, &bytes)==GNOME_VFS_OK)
689         path_gpx_parse(path, bytes, size, GPX_PATH_NEW);
690 g_free(pfile);
691 return TRUE;
692 }
693
694 gboolean 
695 path_save(Path *path, const gchar *file)
696 {
697 GnomeVFSHandle *handle;
698 gchar *tfile;
699
700 g_return_val_if_fail(path, FALSE);
701 g_return_val_if_fail(file, FALSE);
702
703 if (gnome_vfs_create(&handle, file, GNOME_VFS_OPEN_WRITE, FALSE, 0600)==GNOME_VFS_OK) {
704         path_gpx_write(path, handle, NULL);
705         gnome_vfs_close(handle);
706 }
707 g_free(tfile);
708 return TRUE;
709 }
710
711 /******************************************************************************/
712
713 gboolean
714 path_set_destination_from_last(Path *path, Position *pos)
715 {
716 Point *p;
717 gdouble lat,lon;
718
719 g_return_val_if_fail(path, FALSE);
720 g_return_val_if_fail(pos, FALSE);
721
722 if (path->head==path->tail) {
723         position_set(pos, FALSE, NAN, NAN, NAN);
724         return FALSE;
725 }
726
727 p=path_find_last_point(path);
728 if (p) {
729         unit2latlon(p->unitx, p->unity, &lat, &lon);
730         position_set(pos, TRUE, lat, lon, 0);
731 } else {
732         position_set(pos, FALSE, NAN, NAN, NAN);
733         return FALSE;
734 }
735 return TRUE;
736 }
737
738 /**
739  * path_get_waypoint_latlon:
740  * @way
741  * @lat
742  * @lon
743  *
744  * Get the real latitude and longitude of given waypoint
745  *
746  * Returns: TRUE if waypoint was valid, FALSE otherwise.
747  */
748 gboolean
749 path_get_waypoint_latlon(WayPoint *way, gdouble *lat, gdouble *lon)
750 {
751 gdouble tlat, tlon;
752
753 g_return_val_if_fail(way, FALSE);
754 g_return_val_if_fail(way->point, FALSE);
755 g_return_val_if_fail(lat, FALSE);
756 g_return_val_if_fail(lon, FALSE);
757
758 unit2latlon(way->point->unitx, way->point->unity, tlat, tlon);
759
760 *lat=tlat;
761 *lon=tlon;
762
763 return TRUE;
764 }
765
766 /**
767  * path_insert_mark_text:
768  * @path
769  * @text
770  *
771  * Add a new waypoint to path with given text at the last point.
772  *
773  */
774 void 
775 path_insert_mark_text(Path *path, gchar *text)
776 {
777 g_return_if_fail(path);
778 MACRO_PATH_INCREMENT_WTAIL(*path);
779 path->wtail->point=path->tail;
780 if (text) {
781         path->wtail->desc=text;
782 } else {
783         path->wpcnt++;
784         path->wtail->desc=g_strdup_printf("WPT:#%u", path->wpcnt);
785 }
786 g_signal_emit(G_OBJECT(path), signals[NEW_WAYPOINT], 0, NULL);
787 }
788
789 /**
790  * path_insert_mark_autonumber:
791  * @path
792  *
793  * Add autonumbered waypoint to given path
794  *
795  */
796 void
797 path_insert_mark_autonumber(Path *path)
798 {
799 path_insert_mark_text(path, NULL);
800 }
801
802 /******************************************************************************/
803
804 #define PATH_GPX_WRITE_ERROR path_gpx_write_error_quark()
805
806 static GQuark
807 path_gpx_write_error_quark(void)
808 {
809 return g_quark_from_static_string ("path-gpx-write-error-quark");
810 }
811
812 #define XML_DATE_FORMAT "%FT%T"
813
814 #define WRITE_STRING(string) { \
815         if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write(handle, (string), strlen((string)), &size))) { \
816                 g_set_error(error, \
817                         PATH_GPX_WRITE_ERROR, G_FILE_ERROR_FAILED, \
818                         "%s:\n%s\n%s", \
819                         _("Error while writing to file"), \
820                         _("File is incomplete."), \
821                         gnome_vfs_result_to_string(vfs_result)); \
822                 return FALSE; \
823         } \
824 }
825
826 gboolean 
827 path_gpx_write(Path *path, GnomeVFSHandle *handle, GError **error)
828 {
829 Point *curr = NULL;
830 WayPoint *wcurr = NULL;
831 gboolean trkseg_break = FALSE;
832 gchar buffer[80];
833 time_t gpx_time;
834 GnomeVFSResult vfs_result;
835 GnomeVFSFileSize size;
836
837 g_return_val_if_fail((error == NULL || *error == NULL), FALSE);
838
839 gpx_time = time(NULL);
840
841 /* Find first non-zero point. */
842 for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
843         if (curr->unity)
844                 break;
845         else if (wcurr && curr == wcurr->point)
846                 wcurr++;
847 }
848
849 /* Write the header. */
850 WRITE_STRING("<?xml version=\"1.0\"?>\n"
851         "<gpx version=\"1.0\" creator=\"Mapper\" xmlns=\"http://www.topografix.com/GPX/1/0\">\n");
852
853 /* Write any metadata */
854 WRITE_STRING("<metadata>\n");
855
856 WRITE_STRING("<time>");
857 strftime(buffer, sizeof(buffer), XML_DATE_FORMAT, localtime(&gpx_time));
858 WRITE_STRING(buffer);
859 WRITE_STRING(XML_TZONE);
860 WRITE_STRING("</time>\n");
861
862 WRITE_STRING("</metadata>\n");
863
864 /* Write track(s) and waypoint(s) */
865 WRITE_STRING("<trk>\n<trkseg>\n");
866
867 /* Curr points to first non-zero point. */
868 for (curr--; curr++ != path->tail;) {
869         gdouble lat, lon;
870         if (curr->unity) {
871                 gboolean first_sub = TRUE;
872                 if (trkseg_break) {
873                         /* First trkpt of the segment - write trkseg header. */
874                         WRITE_STRING("</trkseg>\n<trkseg>\n");
875                         trkseg_break = FALSE;
876                 }
877                 unit2latlon(curr->unitx, curr->unity, &lat, &lon);
878                 WRITE_STRING("<trkpt lat=\"");
879                 g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lat);
880                 WRITE_STRING(buffer);
881                 WRITE_STRING("\" lon=\"");
882                 g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lon);
883                 WRITE_STRING(buffer);
884                 WRITE_STRING("\"");
885
886                 /* write the elevation */
887                 if (!isnan(curr->altitude)) {
888                         if (first_sub) {
889                                 WRITE_STRING(">\n");
890                                 first_sub = FALSE;
891                         }
892                         WRITE_STRING("<ele>");
893                         g_ascii_formatd(buffer, sizeof(buffer), "%.2f", curr->altitude);
894                         WRITE_STRING(buffer);
895                         WRITE_STRING("</ele>\n");
896                 }
897
898                 /* write the time */
899                 if (curr->time) {
900                         if (first_sub) {
901                                 WRITE_STRING(">\n");
902                                 first_sub = FALSE;
903                         }
904                         WRITE_STRING("<time>");
905                         strftime(buffer, sizeof(buffer), XML_DATE_FORMAT, localtime(&curr->time));
906                         WRITE_STRING(buffer);
907                         WRITE_STRING(XML_TZONE);
908                         WRITE_STRING("</time>\n");
909                 }
910
911                 if (wcurr && curr == wcurr->point) {
912                         gchar *desc;
913                         if (first_sub) {
914                                 WRITE_STRING(">\n");
915                                 first_sub = FALSE;
916                         }
917                         if (wcurr->desc) {
918                                 desc=g_markup_printf_escaped("<desc>%s</desc>\n", wcurr->desc);
919                                 WRITE_STRING(desc);
920                                 g_free(desc);
921                         }
922                         wcurr++;
923                 }
924                 if (first_sub) {
925                         WRITE_STRING("/>\n");
926                 } else {
927                         WRITE_STRING("</trkpt>\n");
928                 }
929         } else
930                 trkseg_break = TRUE;
931 }
932
933 /* Write the footer. */
934 WRITE_STRING("</trkseg>\n</trk>\n</gpx>\n");
935
936 return TRUE;
937 }
938
939 /**
940  * Handle a start tag in the parsing of a GPX file.
941  */
942 #define MACRO_SET_UNKNOWN(utag) { \
943     data->prev_state = data->state; \
944     data->state = UNKNOWN; \
945     data->unknown_depth = 1; \
946         g_debug("GPX: unknown tag [%s]", (gchar *)utag); }
947
948 static void
949 gpx_start_element(SaxData *data, const xmlChar *name, const xmlChar **attrs)
950 {
951 switch (data->state) {
952 case ERROR:
953 break;
954 case START:
955         if (!strcmp((gchar *) name, "gpx"))
956                 data->state = INSIDE_GPX;
957         else
958                 MACRO_SET_UNKNOWN(name);
959 break;
960 case INSIDE_GPX:
961         if (!strcmp((gchar *) name, "trk"))
962                 data->state = INSIDE_PATH;
963         else if (!strcmp((gchar *) name, "metadata"))
964                 data->state = INSIDE_METADATA;
965         else
966                 MACRO_SET_UNKNOWN(name);
967 break;
968 case INSIDE_METADATA:
969 break;
970 case INSIDE_PATH:
971         if (!strcmp((gchar *) name, "trkseg")) {
972                 data->state = INSIDE_PATH_SEGMENT;
973                 data->at_least_one_trkpt = FALSE;
974         } else
975                 MACRO_SET_UNKNOWN(name);
976 break;
977 case INSIDE_PATH_SEGMENT:
978         if (!strcmp((gchar *) name, "trkpt")) {
979                 const xmlChar **curr_attr;
980                 gchar *error_check;
981                 gdouble lat = 0.f, lon = 0.f;
982                 gboolean has_lat, has_lon;
983                 has_lat = FALSE;
984                 has_lon = FALSE;
985                 for (curr_attr = attrs; *curr_attr != NULL;) {
986                         const gchar *attr_name = *curr_attr++;
987                         const gchar *attr_val = *curr_attr++;
988                         if (!strcmp(attr_name, "lat")) {
989                                 lat = g_ascii_strtod(attr_val, &error_check);
990                                 if (error_check != attr_val)
991                                         has_lat = TRUE;
992                         } else if (!strcmp(attr_name, "lon")) {
993                                 lon = g_ascii_strtod(attr_val, &error_check);
994                                 if (error_check != attr_val)
995                                         has_lon = TRUE;
996                         }
997                 }
998                 if (has_lat && has_lon) {
999                         path_add_latlon(data->path, lat, lon, 0, 0, NAN);
1000                         data->state = INSIDE_PATH_POINT;
1001                 } else
1002                         data->state = ERROR;
1003         } else
1004                 MACRO_SET_UNKNOWN(name);
1005 break;
1006 case INSIDE_PATH_POINT:
1007         if (!strcmp((gchar *) name, "time"))
1008                 data->state = INSIDE_PATH_POINT_TIME;
1009         else if (!strcmp((gchar *) name, "ele"))
1010                 data->state = INSIDE_PATH_POINT_ELE;
1011         else if (!strcmp((gchar *) name, "desc"))
1012                 data->state = INSIDE_PATH_POINT_DESC;
1013         else if (!strcmp((gchar *) name, "name"))
1014                 data->state = INSIDE_PATH_POINT_NAME;
1015         else
1016                 MACRO_SET_UNKNOWN(name);
1017 break;
1018 case UNKNOWN:
1019         data->unknown_depth++;
1020 break;
1021 default: ;
1022 }
1023
1024 }
1025
1026 /**
1027  * Handle an end tag in the parsing of a GPX file.
1028  */
1029 static void 
1030 gpx_end_element(SaxData * data, const xmlChar * name)
1031 {
1032 switch (data->state) {
1033 case ERROR:
1034
1035 break;
1036 case START:
1037         data->state = ERROR;
1038 break;
1039 case INSIDE_GPX:
1040         if (!strcmp((gchar *) name, "gpx"))
1041                 data->state = FINISH;
1042         else
1043                 data->state = ERROR;
1044 break;
1045 case INSIDE_METADATA:
1046         if (!strcmp((gchar *) name, "metadata"))
1047                 data->state = INSIDE_GPX;
1048 break;
1049 case INSIDE_PATH:
1050         if (!strcmp((gchar *) name, "trk"))
1051                 data->state = INSIDE_GPX;
1052         else
1053                 data->state = ERROR;
1054 break;
1055 case INSIDE_PATH_SEGMENT:
1056         if (!strcmp((gchar *) name, "trkseg")) {
1057                 if (data->at_least_one_trkpt) {
1058                         path_add_break(data->path);
1059                 }
1060                 data->state = INSIDE_PATH;
1061         } else
1062                 data->state = ERROR;
1063 break;
1064 case INSIDE_PATH_POINT:
1065         if (!strcmp((gchar *) name, "trkpt")) {
1066                 data->state = INSIDE_PATH_SEGMENT;
1067                 data->at_least_one_trkpt = TRUE;
1068         } else
1069                 data->state = ERROR;
1070 break;
1071 case INSIDE_PATH_POINT_ELE:
1072         if (!strcmp((gchar *) name, "ele")) {
1073                 gchar *error_check;
1074                 data->path->tail->altitude = g_ascii_strtod(data->chars->str, &error_check);
1075                 if (error_check == data->chars->str)
1076                         data->path->tail->altitude = NAN;
1077                 data->state = INSIDE_PATH_POINT;
1078                 g_string_free(data->chars, TRUE);
1079                 data->chars = g_string_new("");
1080         } else
1081                 data->state = ERROR;
1082 break;
1083 case INSIDE_PATH_POINT_TIME:
1084         if (!strcmp((gchar *) name, "time")) {
1085                 struct tm time;
1086                 gchar *ptr;
1087
1088                 if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
1089                         /* Failed to parse dateTime format. */
1090                         data->state = ERROR;
1091                 else {
1092                         /* Parse was successful. Now we have to parse timezone.
1093                          * From here on, if there is an error, I just assume local
1094                          * timezone.  Yes, this is not proper XML, but I don't
1095                          * care. */
1096                         gchar *error_check;
1097
1098                         /* First, set time in "local" time zone. */
1099                         data->path->tail->time = (mktime(&time));
1100
1101                         /* Now, skip inconsequential characters */
1102                         while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
1103                                 ptr++;
1104
1105                         /* Check if we ran to the end of the string. */
1106                         if (*ptr) {
1107                                 /* Next character is either 'Z', '-', or '+' */
1108                                 if (*ptr == 'Z')
1109                                         /* Zulu (UTC) time. Undo the local time zone's offset. */
1110                                         data->path->tail->time += time.tm_gmtoff;
1111                                 else {
1112                                         /* Not Zulu (UTC). Must parse hours and minutes. */
1113                                         gint offhours = strtol(ptr, &error_check, 10);
1114                                         if (error_check != ptr && *(ptr = error_check) == ':') {
1115                                                 /* Parse of hours worked. Check minutes. */
1116                                                 gint offmins = strtol(ptr + 1, &error_check, 10);
1117                                                 if (error_check != (ptr + 1)) {
1118                                                         /* Parse of minutes worked. Calculate. */
1119                                                         data->path->tail->time += (time.tm_gmtoff - (offhours * 60 * 60 + offmins * 60));
1120                                                 }
1121                                         }
1122                                 }
1123                         }
1124                         /* Successfully parsed dateTime. */
1125                         data->state = INSIDE_PATH_POINT;
1126                 }
1127
1128                 g_string_free(data->chars, TRUE);
1129                 data->chars = g_string_new("");
1130         } else {
1131                 data->state = ERROR;
1132         }
1133 break;
1134 case INSIDE_PATH_POINT_DESC:
1135         /* only parse description for routes */
1136         if (!strcmp((gchar *) name, "desc")) {
1137                 path_insert_mark_text(data->path, g_string_free(data->chars, FALSE));
1138                 data->chars=g_string_new("");
1139                 data->state=INSIDE_PATH_POINT;
1140         } else {
1141                 data->state=ERROR;
1142         }
1143 break;
1144 case INSIDE_PATH_POINT_NAME:
1145         /* Just ignore these for now */
1146         g_string_free(data->chars, FALSE);
1147         data->chars=g_string_new("");
1148         data->state=INSIDE_PATH_POINT;
1149 break;
1150 case UNKNOWN:
1151         if (!--data->unknown_depth)
1152                 data->state=data->prev_state;
1153         else
1154                 data->state=ERROR;
1155 break;
1156 default: ;
1157 }
1158
1159 }
1160
1161 /**
1162  * Handle char data in the parsing of a GPX file.
1163  */
1164 static void 
1165 gpx_chars(SaxData *data, const xmlChar *ch, int len)
1166 {
1167 guint i;
1168
1169 switch (data->state) {
1170 case ERROR:
1171 case UNKNOWN:
1172         break;
1173 case INSIDE_PATH_POINT_ELE:
1174 case INSIDE_PATH_POINT_TIME:
1175 case INSIDE_PATH_POINT_DESC:
1176 case INSIDE_PATH_POINT_NAME:
1177         for (i = 0; i < len; i++)
1178                 data->chars = g_string_append_c(data->chars, ch[i]);
1179         /* g_debug("GPXC: %s", data->chars->str); */
1180         break;
1181 default:
1182 break;
1183 }
1184
1185 }
1186
1187 /**
1188  * Handle an entity in the parsing of a GPX file.  We don't do anything
1189  * special here.
1190  */
1191 static xmlEntityPtr 
1192 gpx_get_entity(SaxData *data, const xmlChar *name)
1193 {
1194 return xmlGetPredefinedEntity(name);
1195 }
1196
1197 /**
1198  * Handle an error in the parsing of a GPX file.
1199  */
1200 static void 
1201 gpx_error(SaxData *data, const gchar *msg, ...)
1202 {
1203 va_list args;
1204
1205 va_start(args, msg);
1206 g_logv("GPX", G_LOG_LEVEL_WARNING, msg, args);
1207 va_end(args);
1208
1209 data->state = ERROR;
1210 }
1211
1212 /**
1213  * gpx_parse:
1214  * @path
1215  * @buffer
1216  * @size
1217  * @policy
1218  *
1219  * Parse a buffer of GPX data.
1220  * XXX: Add support for parsing directly from file if the file is local.
1221  *
1222  */
1223 gboolean
1224 path_gpx_parse(Path *path, gchar *buffer, gint size, gpx_path_policy policy)
1225 {
1226 SaxData data;
1227 xmlSAXHandler sax_handler;
1228
1229 data.path=path;
1230 data.state=START;
1231 data.chars=g_string_new("");
1232
1233 if (policy==GPX_PATH_NEW)
1234         path_clear(path);
1235
1236 memset(&sax_handler, 0, sizeof(sax_handler));
1237 sax_handler.characters=(charactersSAXFunc) gpx_chars;
1238 sax_handler.startElement=(startElementSAXFunc) gpx_start_element;
1239 sax_handler.endElement=(endElementSAXFunc) gpx_end_element;
1240 sax_handler.entityDecl=(entityDeclSAXFunc) gpx_get_entity;
1241 sax_handler.warning=(warningSAXFunc) gpx_error;
1242 sax_handler.error=(errorSAXFunc) gpx_error;
1243 sax_handler.fatalError=(fatalErrorSAXFunc) gpx_error;
1244
1245 xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
1246
1247 g_string_free(data.chars, TRUE);
1248
1249 if (data.state!=FINISH) {
1250         g_warning("GPX: Parser stopped in error state %d", data.state);
1251         return FALSE;
1252 }
1253
1254 switch (policy) {
1255 case GPX_PATH_APPEND:
1256 case GPX_PATH_PREPEND:
1257         {
1258         Point *src_first;
1259         Path *src, *dest;
1260
1261         if (policy==GPX_PATH_APPEND) {
1262                 /* Append to current path. Make sure last path point is zero. */
1263                 path_add_break(path);
1264                 src=data.path;
1265                 dest=path;
1266         } else {
1267                 /* Prepend to current route. */
1268                 src=path;
1269                 dest=data.path;
1270         }
1271
1272         /* Find src_first non-zero point. */
1273         for (src_first = src->head; src_first++ != src->tail;)
1274                 if (src_first->unity && src_first->unitx)
1275                         break;
1276
1277         /* Append route points from src to dest. */
1278         if (src->tail >= src_first) {
1279                 WayPoint *curr;
1280                 guint num_dest_points = dest->tail - dest->head + 1;
1281                 guint num_src_points = src->tail - src_first + 1;
1282
1283                 /* Adjust dest->tail to be able to fit src route data
1284                  * plus room for more route data. */
1285                 path_resize(dest, num_dest_points + num_src_points);
1286
1287                 memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
1288
1289                 dest->tail += num_src_points;
1290
1291                 /* Append waypoints from src to dest->. */
1292                 path_wresize(dest, (dest->wtail - dest->whead) + (src->wtail - src->whead) + 2);
1293                 for (curr = src->whead - 1; curr++ != src->wtail;) {
1294                         (++(dest->wtail))->point = dest->head + num_dest_points + (curr->point - src_first);
1295                         dest->wtail->desc = curr->desc;
1296                 }
1297         }
1298
1299         /* Kill old route - don't use MACRO_PATH_FREE(), because that
1300          * would free the string desc's that we just moved to data.route. */
1301         g_free(src->head);
1302         g_free(src->whead);
1303         if (policy==GPX_PATH_PREPEND)
1304                 (*path) = *dest;
1305         }
1306 break;
1307 case GPX_PATH_NEW:
1308         /* */
1309 break;
1310 default:
1311         g_assert_not_reached();
1312 break;
1313 }
1314
1315 return TRUE;
1316 }
1317
1318 /******************************************************************************/
1319
1320 #if 0
1321 static void
1322 path_store_append_waypoint(GtkListStore *, WayPoint *w)
1323 {
1324 gchar tmp1[16], tmp2[16];
1325 gdouble lat2, lon2;
1326
1327 unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat2, lon2);
1328 lat_format(_degformat, lat2, tmp1);
1329 lon_format(_degformat, lon2, tmp2);
1330
1331 g_snprintf(buffer1, sizeof(buffer1), "%s,%s", tmp1, tmp2);
1332 sum += calculate_distance(lat1, lon1, lat2, lon2);
1333 g_snprintf(buffer2, sizeof(buffer2), "%.02f %s", sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
1334
1335 gtk_list_store_append(store, &iter);
1336 gtk_list_store_set(store, &iter,
1337         PATH_LATLON, buffer1,
1338         PATH_DISTANCE, buffer2,
1339         PATH_WAYPOINT, wcurr->desc,
1340         PATH_LAT, lat2,
1341         PATH_LON, lon2,
1342         -1);
1343
1344 lat1=lat2;
1345 lon1=lon2;
1346 }
1347 #endif
1348
1349 /**
1350  * Generate a GtkListStore with information about path waypoints (location, distance)
1351  */
1352 GtkListStore *
1353 path_get_waypoints_store(Path *path)
1354 {
1355 WayPoint *wcurr;
1356 GtkTreeIter iter;
1357 GtkListStore *store;
1358 gchar buffer1[80];
1359 gchar buffer2[32];
1360 gchar tmp1[16], tmp2[16];
1361 gdouble lat1, lon1, lat2, lon2;
1362 gdouble sum=0.0;
1363
1364 g_return_val_if_fail(path!=NULL, NULL);
1365
1366 wcurr=path->whead;
1367
1368 g_return_val_if_fail(wcurr!=NULL, NULL);
1369 g_return_val_if_fail(wcurr->point!=NULL, NULL);
1370 if (!path_has_waypoints(path))
1371         return NULL;
1372
1373 store=gtk_list_store_new(PATH_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
1374
1375 unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat1, lon1);
1376
1377 while (wcurr!=path->wtail) {
1378         if (!wcurr)
1379                 break;
1380
1381         if (!wcurr->point) {
1382                 g_debug("PSTORE: No point for waypoint (%s) skipping", wcurr->desc);
1383                 wcurr++;
1384                 continue;
1385         }
1386
1387         unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat2, lon2);
1388         lat_format(_degformat, lat2, tmp1);
1389         lon_format(_degformat, lon2, tmp2);
1390
1391         g_snprintf(buffer1, sizeof(buffer1), "%s,%s", tmp1, tmp2);
1392         sum += calculate_distance(lat1, lon1, lat2, lon2);
1393         g_snprintf(buffer2, sizeof(buffer2), "%.02f %s", sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
1394
1395         gtk_list_store_append(store, &iter);
1396         gtk_list_store_set(store, &iter,
1397                 PATH_LATLON, buffer1,
1398                 PATH_DISTANCE, buffer2,
1399                 PATH_WAYPOINT, wcurr->desc,
1400                 PATH_LAT, lat2,
1401                 PATH_LON, lon2,
1402                 -1);
1403
1404         lat1=lat2;
1405         lon1=lon2;
1406
1407         wcurr++;
1408 }
1409
1410 return store;
1411 }