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