]> err.no Git - mapper/blobdiff - src/path.c
Path: misc adjustments
[mapper] / src / path.c
index 516a095fde86a6cbb6fd7df19b5d2ad95b638145..ec2de141585a29fa8bf27516eadcae970f5719ce 100644 (file)
+/*
+ * This file is part of mapper
+ *
+ * Copyright (C) 2008 Kaj-Michael Lang
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
 #include <config.h>
 
-#include "utils.h"
-#include "map.h"
-#include "route.h"
-#include "mapper-types.h"
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <sqlite3.h>
+#include <libxml/parser.h>
+#include <libgnomevfs/gnome-vfs.h>
 
-#include "track.h"
 #include "path.h"
+#include "position.h"
+#include "utils.h"
+#include "settings.h"
+#include "latlon.h"
 
-void 
+struct sql_select_stmt {
+       sqlite3_stmt *select_paths;
+       sqlite3_stmt *select_path_nodes;
+
+       sqlite3_stmt *insert_path;
+       sqlite3_stmt *insert_path_node;
+
+       sqlite3_stmt *delete_path;
+       sqlite3_stmt *delete_path_nodes;
+};
+static struct sql_select_stmt sql;
+
+#define PATH_TABLE_PATHS "create table IF NOT EXISTS paths ( \
+       nid             int primary key, \
+       name    text not null, \
+       desc    text, \
+       t               int not null);"
+
+#define PATH_TABLE_NODES "create table IF NOT EXISTS path_nodes ( \
+       nid             int not null, \
+       lat     real not null, \
+       lon     real not null, \
+       sats    int, \
+       speed   real, \
+       course  real, \
+       pdop    real, \
+       hdop    real, \
+       vdop    real, \
+       name    text default null, \
+       t               int not null);"
+
+#define PATH_SQL_SELECT_TRACKS "select id,name,slat,slon,elat,elon,len,sdate,edate from tracks order by sdate"
+#define PATH_SQL_INSERT_TRACK "insert into tracks (id,name,sloc,sdate) values (?,?,?,?);
+#define PATH_SQL_INSERT_TRACK_POINT "insert into trackpoints (tid,dt,lat,lon,alt,hdop,vdop,pdop,sat,fix) values (?,?,?,?,?,?,?,?,?,?)"
+#define PATH_SQL_SELECT_TRACK_POINTS "select tid,dt,lat,lon,alt,hdop,vdop,pdop,sat,fix from trackpoints where tid=? order by dt"
+
+enum {
+       NEW_POINT,                              /* A new point was appended to track track */
+       NEW_BREAK,                              /* A break was appended to the track */
+       NEW_WAYPOINT,                   /* A new waypoint/marker has been added */
+       NEAR_WAYPOINT,                  /* We are near the next route waypoint, announce it */
+       REACHED_WAYPOINT,               /* We have reached the next route waypoint, announce it */
+       REACHED_DESTINATION,    /* We have reached the last route waypoint */
+       CLEARED,                                /* Path was cleared */
+       LAST_SIGNAL
+};
+static guint32 signals[LAST_SIGNAL] = {0};
+
+/** This enum defines the states of the SAX parsing state machine. */
+typedef enum {
+       START=1,
+       INSIDE_GPX=100,
+       INSIDE_METADATA,
+       INSIDE_PATH,
+       INSIDE_PATH_SEGMENT,
+       INSIDE_PATH_POINT,
+       INSIDE_PATH_POINT_ELE,
+       INSIDE_PATH_POINT_TIME,
+       INSIDE_PATH_POINT_DESC,
+       INSIDE_PATH_POINT_NAME,
+       FINISH=5000,
+       UNKNOWN=6666,
+       ERROR=9999,
+} SaxState;
+
+/** Data used during the SAX parsing operation. */
+typedef struct _SaxData SaxData;
+struct _SaxData {
+       Path *path;
+       SaxState state;
+       SaxState prev_state;
+       guint unknown_depth;
+       gboolean at_least_one_trkpt;
+       GString *chars;
+};
+
+static gchar XML_TZONE[7];
+
+G_DEFINE_TYPE(Path, path, G_TYPE_OBJECT);
+
+#define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), PATH_TYPE, PathPrivate))
+
+static void
+path_dispose(GObject *object)
+{
+g_debug("path_dispose");
+
+G_OBJECT_CLASS(path_parent_class)->dispose(object);
+}
+
+static void
+path_finalize(GObject *object)
+{
+Path *path=PATH(object);
+
+g_debug("path_finalize");
+MACRO_PATH_FREE(*path);
+
+if (path->name)
+       g_free(path->name);
+if (path->desc)
+       g_free(path->desc);
+if (path->author)
+       g_free(path->author);
+if (path->keywords)
+       g_free(path->keywords);
+if (path->copyright)
+       g_free(path->copyright);
+if (path->src)
+       g_free(path->src);
+}
+
+static void
+path_class_init(PathClass *klass)
+{
+GObjectClass *object_class=G_OBJECT_CLASS(klass);
+
+g_debug("path_class_init");
+
+object_class->dispose=path_dispose;
+object_class->finalize=path_finalize;
+/* g_type_class_add_private (klass, sizeof(PathPrivate)); */
+
+signals[NEW_POINT]=g_signal_new("new-point", G_OBJECT_CLASS_TYPE(object_class),
+       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(PathClass, new_point),
+       NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
+signals[NEW_BREAK]=g_signal_new("new-break", G_OBJECT_CLASS_TYPE(object_class),
+       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(PathClass, new_break),
+       NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
+signals[NEW_WAYPOINT]=g_signal_new("new-waypoint", G_OBJECT_CLASS_TYPE(object_class),
+       G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(PathClass, new_waypoint),
+       NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
+}
+
+static void
+path_init(Path *path)
+{
+g_debug("path_init");
+MACRO_PATH_INIT(*path);
+}
+
+Path *
+path_new(PathType type, guint id)
+{
+Path *p;
+static time_t time1=0;
+struct tm time2;
+
+p=g_object_new(PATH_TYPE, NULL);
+p->type=type;
+p->id=id;
+p->sensitivity=3;
+
+if (time1==0) {
+       time1=time(NULL);
+       localtime_r(&time1, &time2);
+       g_snprintf(XML_TZONE, sizeof(XML_TZONE), "%+03ld:%02ld", (time2.tm_gmtoff / 60 / 60), (time2.tm_gmtoff / 60) % 60);
+}
+
+return p;
+}
+
+void
+path_free(Path *path)
+{
+g_return_if_fail(path);
+g_object_unref(path);
+}
+
+void
+path_clear(Path *path)
+{
+g_return_if_fail(path);
+MACRO_PATH_FREE(*path);
+path->length=path->avgspeed=0.0;
+path->points=0;
+}
+
+static gboolean
 path_resize(Path *path, guint size)
 {
+g_return_val_if_fail(path, FALSE);
+
 if (path->head + size != path->cap) {
-       Point *old_head = path->head;
        WayPoint *curr;
+       Point *old_head = path->head;
+
        path->head = g_renew(Point, old_head, size);
+       g_assert(path->head);
        path->cap = path->head + size;
        if (path->head != old_head) {
                path->tail = path->head + (path->tail - old_head);
@@ -23,16 +228,1199 @@ if (path->head + size != path->cap) {
                for (curr = path->whead - 1; curr++ != path->wtail;)
                        curr->point = path->head + (curr->point - old_head);
        }
+       return TRUE;
 }
+return FALSE;
 }
 
-void 
+static gboolean 
 path_wresize(Path *path, guint wsize)
 {
+g_return_val_if_fail(path, FALSE);
+
 if (path->whead + wsize != path->wcap) {
        WayPoint *old_whead = path->whead;
+
        path->whead = g_renew(WayPoint, old_whead, wsize);
        path->wtail = path->whead + (path->wtail - old_whead);
        path->wcap = path->whead + wsize;
+
+       return TRUE;
+}
+return FALSE;
+}
+
+/**
+ * path_add_waypoint:
+ * @path
+ * @lat
+ * @lon
+ * @desc
+ *
+ * Append a waypoint to path at given lat, lon with description desc.
+ *
+ * Returns: TRUE
+ */
+gboolean
+path_add_waypoint(Path *path, gdouble lat, gdouble lon, gchar *desc)
+{
+guint unitx, unity;
+
+latlon2unit(lat, lon, unitx, unity);
+MACRO_PATH_INCREMENT_TAIL(*path);
+path->tail->unitx=unitx;
+path->tail->unity=unity;
+path->tail->time=0;
+path->tail->altitude=NAN;
+
+MACRO_PATH_INCREMENT_WTAIL(*path);
+path->wtail->point=path->tail;
+path->wtail->desc=desc;
+
+g_signal_emit(G_OBJECT(path), signals[NEW_WAYPOINT], 0, NULL);
+
+return TRUE;
+}
+
+/**
+ * path_add_latlon:
+ * @path
+ * @lat
+ * @lon
+ * @ptime
+ * @speed
+ * @altitude
+ *
+ * Append a point with given lat,lon,ptime,speed and altitude to given path.
+ *
+ * Returns: TRUE if the new point was added. FALSE is returned if new point distance was under sensitivity setting.
+ */
+gboolean 
+path_add_latlon(Path *path, gdouble lat, gdouble lon, time_t ptime, gfloat speed, gfloat altitude)
+{
+guint unitx, unity;
+
+latlon2unit(lat, lon, unitx, unity);
+
+if (abs((gint)unitx-path->tail->unitx) > path->sensitivity || abs((gint)unity-path->tail->unity) > path->sensitivity) {
+       if (path->tail->unity && path->tail->unitx) {
+               gdouble plat, plon;
+
+               unit2latlon(path->tail->unitx, path->tail->unity, lat, lon);
+               path->length+=calculate_distance(plat, plon, lat, lon);
+       }
+       MACRO_PATH_INCREMENT_TAIL(*path);
+       path->tail->unitx=unitx;
+       path->tail->unity=unity;
+       path->tail->time=ptime;
+       path->tail->altitude=altitude;
+       if (speed>path->maxspeed)
+               path->maxspeed=speed;
+       path->tspeed+=speed;
+       path->avgspeed=(path->points>0) ? path->tspeed/path->points : 0.0;
+       path->points++;
+       g_debug("TRACK: %f %f (%d)", path->length, path->avgspeed, path->points);
+
+       g_signal_emit(G_OBJECT(path), signals[NEW_POINT], 0, NULL);
+       return TRUE;
+}
+
+return FALSE;
+
+}
+
+/**
+ * path_add_break:
+ * @path
+ *
+ * Add a break point to the path.
+ * 
+ * Returns: TRUE if break was added, FALSE if last point was a break already.
+ */
+gboolean 
+path_add_break(Path *path)
+{
+g_return_val_if_fail(path, FALSE);
+g_return_val_if_fail(path->tail, FALSE);
+
+if (path->tail->unity && path->tail->unitx) {
+       /* To mark a "break" in a track, we'll add a (0, 0) point and then 
+          another instance of the most recent track point. */
+
+       MACRO_PATH_INCREMENT_TAIL(*path);
+       *path->tail=_point_null;
+       MACRO_PATH_INCREMENT_TAIL(*path);
+       *path->tail=path->tail[-2];
+
+       g_signal_emit(G_OBJECT(path), signals[NEW_BREAK], 0, NULL);
+
+       return TRUE;
+}
+return FALSE;
+}
+
+/**
+ * path_has_points:
+ * @path
+ *
+ * Checks if given path has any path points
+ *
+ * Returns: TRUE if there are points, FALSE otherwise
+ */
+gboolean
+path_has_points(Path *path)
+{
+g_return_val_if_fail(path, FALSE);
+return path->head==path->tail ? FALSE : TRUE;
+}
+
+/**
+ * path_has_waypoints:
+ * 
+ * Checks if given path has any waypoints
+ * 
+ * Returns: TRUE if there are waypoints, FALSE otherwise
+ */
+gboolean
+path_has_waypoints(Path *path)
+{
+g_return_val_if_fail(path, FALSE);
+return path->whead==path->wtail ? FALSE : TRUE;
+}
+
+/**
+ * path_find_last_point:
+ * @path
+ *
+ * Returns: The last path point or NULL if path is empty
+ */
+Point *
+path_find_last_point(Path *path)
+{
+Point *p=NULL;
+
+g_return_val_if_fail(path, NULL);
+
+if (!path_has_points(path))
+       return p;
+
+for (p=path->tail; !p->unity; p--) {
+}
+return p;
 }
+
+/**
+ * Updates near_point, next_way, and next_wpt.  If quick is FALSE (as
+ * it is when this function is called from route_find_nearest_point), then
+ * the entire list (starting from near_point) is searched.  Otherwise, we
+ * stop searching when we find a point that is farther away.
+ */
+static gboolean 
+path_update_nears(Path *path, Point *point, gboolean quick)
+{
+gboolean ret = FALSE;
+Point *curr, *near;
+WayPoint *wcurr, *wnext;
+guint64 near_dist_squared;
+
+g_return_val_if_fail(path, FALSE);
+g_return_val_if_fail(point, FALSE);
+
+/* If we have waypoints (_next_way != NULL), then determine the "next
+ * waypoint", which is defined as the waypoint after the nearest point,
+ * UNLESS we've passed that waypoint, in which case the waypoint after
+ * that waypoint becomes the "next" waypoint. */
+if (path->next_way) {
+       /* First, set near_dist_squared with the new distance from near_point. */
+       near = path->near_point;
+       near_dist_squared = DISTANCE_SQUARED(*point, *near);
+
+       /* Now, search path for a closer point.  If quick is TRUE, then we'll
+        * only search forward, only as long as we keep finding closer points.
+        */
+       for (curr = path->near_point; curr++ != path->tail;) {
+               if (curr->unity) {
+                       guint dist_squared = DISTANCE_SQUARED(*point, *curr);
+                       if (dist_squared <= near_dist_squared) {
+                               near = curr;
+                               near_dist_squared = dist_squared;
+                       } else if (quick)
+                               break;
+               }
+       }
+
+       /* Update _near_point. */
+       path->near_point = near;
+       path->near_point_dist_squared = near_dist_squared;
+
+       for (wnext = wcurr = path->next_way; wcurr != path->wtail; wcurr++) {
+               if (wcurr->point < near || (wcurr->point == near && quick 
+                               && (path->next_wpt && (DISTANCE_SQUARED(*point, *near) > path->next_way_dist_squared
+                               && DISTANCE_SQUARED(*point, *path->next_wpt) < path->next_wpt_dist_squared))))
+                   /* Okay, this else if expression warrants explanation.  If the
+                    * nearest track point happens to be a waypoint, then we want to
+                    * check if we have "passed" that waypoint.  To check this, we
+                    * test the distance from _gps to the waypoint and from _gps to
+                    * _next_wpt, and if the former is increasing and the latter is
+                    * decreasing, then we have passed the waypoint, and thus we
+                    * should skip it.  Note that if there is no _next_wpt, then
+                    * there is no next waypoint, so we do not skip it in that case. */
+                       wnext = wcurr + 1;
+               else
+                       break;
+       }
+
+       if (wnext == path->wtail && (wnext->point < near || (wnext->point == near && quick
+                                         && (path->next_wpt && (DISTANCE_SQUARED (*point, *near) > path->next_way_dist_squared
+                                              && DISTANCE_SQUARED(*point, *path->next_wpt) < path->next_wpt_dist_squared)))))
+       {
+               path->next_way = NULL;
+               path->next_wpt = NULL;
+               path->next_way_dist_squared = -1;
+               path->next_wpt_dist_squared = -1;
+               ret = TRUE;
+       }
+       /* Only update next_way (and consequently _next_wpt) if _next_way is
+        * different, and record that fact for return. */
+       else {
+               if (!quick || path->next_way != wnext) {
+                       path->next_way = wnext;
+                       path->next_wpt = wnext->point;
+                       if (path->next_wpt == path->tail)
+                               path->next_wpt = NULL;
+                       else {
+                               while (!(++path->next_wpt)->unity) {
+                                       if (path->next_wpt == path->tail) {
+                                               path->next_wpt = NULL;
+                                               break;
+                                       }
+                               }
+                       }
+                       ret = TRUE;
+               }
+               path->next_way_dist_squared = DISTANCE_SQUARED(*point, *wnext->point);
+               if (path->next_wpt)
+                       path->next_wpt_dist_squared = DISTANCE_SQUARED(*point, *path->next_wpt);
+       }
+}
+return ret;
+}
+
+/**
+ * path_find_nearest_point:
+ *
+ * Reset the near_point data by searching the entire path for the nearest point and waypoint.
+ */
+void 
+path_find_nearest_point(Path *path, gdouble lat, gdouble lon)
+{
+Point p;
+
+g_return_if_fail(path);
+
+/* Initialize near_point to first non-zero point. */
+path->near_point=path->head;
+while (!path->near_point->unity && path->near_point != path->tail)
+       path->near_point++;
+
+/* Initialize next_way */
+if (path->wtail==path->whead)
+       path->next_way=NULL;
+else
+       path->next_way=path->whead;
+path->next_way_dist_squared=-1;
+
+/* Initialize next_wpt */
+path->next_wpt=NULL;
+path->next_wpt_dist_squared=-1;
+
+latlon2unit(lat, lon, p.unitx, p.unity);
+path_update_nears(path, &p, FALSE);
+}
+
+/**
+ * path_find_nearest_waypoint:
+ *
+ */
+WayPoint *
+path_find_nearest_waypoint(Path *path, guint unitx, guint unity)
+{
+WayPoint *wcurr;
+WayPoint *wnear;
+guint64 nearest_squared;
+Point pos = { unitx, unity, 0, NAN };
+
+g_return_val_if_fail(path, NULL);
+
+wcurr=wnear=path->whead;
+if (wcurr && wcurr->point && wcurr!=path->wtail) {
+       nearest_squared=DISTANCE_SQUARED(pos, *(wcurr->point));
+
+       while (wcurr++!=path->wtail) {
+               guint64 test_squared=DISTANCE_SQUARED(pos, *(wcurr->point));
+               if (test_squared < nearest_squared) {
+                       wnear=wcurr;
+                       nearest_squared=test_squared;
+               }
+       }
+}
+
+if (wnear && wnear->point) {
+       if (abs(unitx - wnear->point->unitx) < path->sensitivity && abs(unity - wnear->point->unity) < path->sensitivity)
+               return wnear;
+}
+
+return NULL;
+}
+
+/**
+ * path_check_waypoint_announce:
+ * @path
+ * @gps 
+ *
+ * Check if we should announce upcoming waypoint.
+ *
+ */
+static void
+path_check_waypoint_announce(Path *path, GpsData *gps)
+{
+guint a_thres_near, a_thres_at;
+
+g_return_if_fail(path);
+g_return_if_fail(gps);
+
+if (!path->next_way)
+       return;
+
+a_thres_near=(20+(guint)gps->speed)*path->announce_notice_ratio*3;
+a_thres_at=(20+(guint)gps->speed);
+
+if (path->next_way_dist_squared<(a_thres_near * a_thres_near) && path->next_way!=path->announced_waypoint) {
+       g_signal_emit(G_OBJECT(path), signals[NEAR_WAYPOINT], 0, NULL);
+       path->announced_waypoint=path->next_way;
+} else if (path->next_way_dist_squared<(a_thres_at * a_thres_at) && path->next_way!=path->announced_waypoint) {
+       g_signal_emit(G_OBJECT(path), signals[REACHED_WAYPOINT], 0, NULL);
+       path->announced_waypoint=path->next_way;
+}
+}
+
+/******************************************************************************/
+
+/**
+ *
+ *
+ */
+gdouble
+path_calculate_distance_from(Path *path, Point *point)
+{
+Point *curr;
+gdouble lat1, lon1, lat2, lon2;
+gdouble sum=0.0;
+
+for (curr = path->tail; curr > point; --curr) {
+       if (curr->unity) {
+               unit2latlon(curr->unitx, curr->unity, &lat2, &lon2);
+               sum += calculate_distance(lat1, lon1, lat2, lon2);
+               lat1 = lat2;
+               lon1 = lon2;
+       }
+}
+return sum;
+}
+
+/**
+ * path_get_distance_to:
+ * @path
+ * @point
+ * @lat
+ * @lon
+ *
+ * Calculate distancance from lat,lon to point in given path.
+ *
+ * Returns: The distance, or 0.0 if path or point is invalid.
+ */
+gdouble
+path_get_distance_to(Path *path, Point *point, gdouble lat, gdouble lon)
+{
+gdouble lat1, lon1, lat2, lon2;
+gdouble sum=0.0;
+
+g_return_val_if_fail(path, 0.0);
+
+/* If point is NULL, use the next waypoint. */
+if (point == NULL && path->next_way)
+       point = path->next_way->point;
+
+/* If point is still NULL, return an error. */
+if (point==NULL)
+       return FALSE;
+
+lat1=lat;
+lon1=lon;
+
+if (point > path->near_point) {
+       Point *curr;
+       /* Skip _near_point in case we have already passed it. */
+       for (curr = path->near_point + 1; curr <= point; ++curr) {
+               if (curr->unity) {
+                       unit2latlon(curr->unitx, curr->unity, lat2, lon2);
+                       sum += calculate_distance(lat1, lon1, lat2, lon2);
+                       lat1 = lat2;
+                       lon1 = lon2;
+               }
+       }
+} else if (point < path->near_point) {
+       Point *curr;
+       /* Skip near_point in case we have already passed it. */
+       for (curr = path->near_point - 1; curr >= point; --curr) {
+               if (curr->unity) {
+                       unit2latlon(curr->unitx, curr->unity, lat2, lon2);
+                       sum += calculate_distance(lat1, lon1, lat2, lon2);
+                       lat1 = lat2;
+                       lon1 = lon2;
+               }
+       }
+} else {
+       /* Waypoint _is_ the nearest point. */
+       unit2latlon(path->near_point->unitx, path->near_point->unity, lat2, lon2);
+       sum += calculate_distance(lat1, lon1, lat2, lon2);
+}
+return sum;
+}
+
+/******************************************************************************/
+
+gboolean
+path_load(Path *path, const gchar *file)
+{
+gchar *pfile;
+gchar *bytes;
+gint size;
+
+g_return_val_if_fail(path, FALSE);
+g_return_val_if_fail(file, FALSE);
+
+if (gnome_vfs_read_entire_file(file, &size, &bytes)==GNOME_VFS_OK)
+       path_gpx_parse(path, bytes, size, GPX_PATH_NEW);
+g_free(pfile);
+return TRUE;
+}
+
+gboolean 
+path_save(Path *path, const gchar *file)
+{
+GnomeVFSHandle *handle;
+gchar *tfile;
+
+g_return_val_if_fail(path, FALSE);
+g_return_val_if_fail(file, FALSE);
+
+if (gnome_vfs_create(&handle, file, GNOME_VFS_OPEN_WRITE, FALSE, 0600)==GNOME_VFS_OK) {
+       path_gpx_write(path, handle, NULL);
+       gnome_vfs_close(handle);
+}
+g_free(tfile);
+return TRUE;
+}
+
+/******************************************************************************/
+
+gboolean
+path_set_destination_from_last(Path *path, Position *pos)
+{
+Point *p;
+gdouble lat,lon;
+
+g_return_val_if_fail(path, FALSE);
+g_return_val_if_fail(pos, FALSE);
+
+if (path->head==path->tail) {
+       position_set(pos, FALSE, NAN, NAN, NAN);
+       return FALSE;
+}
+
+p=path_find_last_point(path);
+if (p) {
+       unit2latlon(p->unitx, p->unity, &lat, &lon);
+       position_set(pos, TRUE, lat, lon, 0);
+} else {
+       position_set(pos, FALSE, NAN, NAN, NAN);
+       return FALSE;
+}
+return TRUE;
+}
+
+/**
+ * path_get_waypoint_latlon:
+ * @way
+ * @lat
+ * @lon
+ *
+ * Get the real latitude and longitude of given waypoint
+ *
+ * Returns: TRUE if waypoint was valid, FALSE otherwise.
+ */
+gboolean
+path_get_waypoint_latlon(WayPoint *way, gdouble *lat, gdouble *lon)
+{
+gdouble tlat, tlon;
+
+g_return_val_if_fail(way, FALSE);
+g_return_val_if_fail(way->point, FALSE);
+g_return_val_if_fail(lat, FALSE);
+g_return_val_if_fail(lon, FALSE);
+
+unit2latlon(way->point->unitx, way->point->unity, tlat, tlon);
+
+*lat=tlat;
+*lon=tlon;
+
+return TRUE;
+}
+
+/**
+ * path_insert_mark_text:
+ * @path
+ * @text
+ *
+ * Add a new waypoint to path with given text at the last point.
+ *
+ */
+void 
+path_insert_mark_text(Path *path, gchar *text)
+{
+g_return_if_fail(path);
+MACRO_PATH_INCREMENT_WTAIL(*path);
+path->wtail->point=path->tail;
+if (text) {
+       path->wtail->desc=text;
+} else {
+       path->wpcnt++;
+       path->wtail->desc=g_strdup_printf("WPT:#%u", path->wpcnt);
+}
+g_signal_emit(G_OBJECT(path), signals[NEW_WAYPOINT], 0, NULL);
+}
+
+/**
+ * path_insert_mark_autonumber:
+ * @path
+ *
+ * Add autonumbered waypoint to given path
+ *
+ */
+void
+path_insert_mark_autonumber(Path *path)
+{
+path_insert_mark_text(path, NULL);
+}
+
+/******************************************************************************/
+
+#define PATH_GPX_WRITE_ERROR path_gpx_write_error_quark()
+
+static GQuark
+path_gpx_write_error_quark(void)
+{
+return g_quark_from_static_string ("path-gpx-write-error-quark");
+}
+
+#define XML_DATE_FORMAT "%FT%T"
+
+#define WRITE_STRING(string) { \
+       if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write(handle, (string), strlen((string)), &size))) { \
+               g_set_error(error, \
+                       PATH_GPX_WRITE_ERROR, G_FILE_ERROR_FAILED, \
+                       "%s:\n%s\n%s", \
+                       _("Error while writing to file"), \
+                       _("File is incomplete."), \
+                       gnome_vfs_result_to_string(vfs_result)); \
+               return FALSE; \
+       } \
+}
+
+gboolean 
+path_gpx_write(Path *path, GnomeVFSHandle *handle, GError **error)
+{
+Point *curr = NULL;
+WayPoint *wcurr = NULL;
+gboolean trkseg_break = FALSE;
+gchar buffer[80];
+time_t gpx_time;
+GnomeVFSResult vfs_result;
+GnomeVFSFileSize size;
+
+g_return_val_if_fail((error == NULL || *error == NULL), FALSE);
+
+gpx_time = time(NULL);
+
+/* Find first non-zero point. */
+for (curr = path->head - 1, wcurr = path->whead; curr++ != path->tail;) {
+       if (curr->unity)
+               break;
+       else if (wcurr && curr == wcurr->point)
+               wcurr++;
+}
+
+/* Write the header. */
+WRITE_STRING("<?xml version=\"1.0\"?>\n"
+       "<gpx version=\"1.0\" creator=\"Mapper\" xmlns=\"http://www.topografix.com/GPX/1/0\">\n");
+
+/* Write any metadata */
+WRITE_STRING("<metadata>\n");
+
+WRITE_STRING("<time>");
+strftime(buffer, sizeof(buffer), XML_DATE_FORMAT, localtime(&gpx_time));
+WRITE_STRING(buffer);
+WRITE_STRING(XML_TZONE);
+WRITE_STRING("</time>\n");
+
+WRITE_STRING("</metadata>\n");
+
+/* Write track(s) and waypoint(s) */
+WRITE_STRING("<trk>\n<trkseg>\n");
+
+/* Curr points to first non-zero point. */
+for (curr--; curr++ != path->tail;) {
+       gdouble lat, lon;
+       if (curr->unity) {
+               gboolean first_sub = TRUE;
+               if (trkseg_break) {
+                       /* First trkpt of the segment - write trkseg header. */
+                       WRITE_STRING("</trkseg>\n<trkseg>\n");
+                       trkseg_break = FALSE;
+               }
+               unit2latlon(curr->unitx, curr->unity, &lat, &lon);
+               WRITE_STRING("<trkpt lat=\"");
+               g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lat);
+               WRITE_STRING(buffer);
+               WRITE_STRING("\" lon=\"");
+               g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lon);
+               WRITE_STRING(buffer);
+               WRITE_STRING("\"");
+
+               /* write the elevation */
+               if (!isnan(curr->altitude)) {
+                       if (first_sub) {
+                               WRITE_STRING(">\n");
+                               first_sub = FALSE;
+                       }
+                       WRITE_STRING("<ele>");
+                       g_ascii_formatd(buffer, sizeof(buffer), "%.2f", curr->altitude);
+                       WRITE_STRING(buffer);
+                       WRITE_STRING("</ele>\n");
+               }
+
+               /* write the time */
+               if (curr->time) {
+                       if (first_sub) {
+                               WRITE_STRING(">\n");
+                               first_sub = FALSE;
+                       }
+                       WRITE_STRING("<time>");
+                       strftime(buffer, sizeof(buffer), XML_DATE_FORMAT, localtime(&curr->time));
+                       WRITE_STRING(buffer);
+                       WRITE_STRING(XML_TZONE);
+                       WRITE_STRING("</time>\n");
+               }
+
+               if (wcurr && curr == wcurr->point) {
+                       gchar *desc;
+                       if (first_sub) {
+                               WRITE_STRING(">\n");
+                               first_sub = FALSE;
+                       }
+                       if (wcurr->desc) {
+                               desc=g_markup_printf_escaped("<desc>%s</desc>\n", wcurr->desc);
+                               WRITE_STRING(desc);
+                               g_free(desc);
+                       }
+                       wcurr++;
+               }
+               if (first_sub) {
+                       WRITE_STRING("/>\n");
+               } else {
+                       WRITE_STRING("</trkpt>\n");
+               }
+       } else
+               trkseg_break = TRUE;
+}
+
+/* Write the footer. */
+WRITE_STRING("</trkseg>\n</trk>\n</gpx>\n");
+
+return TRUE;
+}
+
+/**
+ * Handle a start tag in the parsing of a GPX file.
+ */
+#define MACRO_SET_UNKNOWN(utag) { \
+    data->prev_state = data->state; \
+    data->state = UNKNOWN; \
+    data->unknown_depth = 1; \
+       g_debug("GPX: unknown tag [%s]", (gchar *)utag); }
+
+static void
+gpx_start_element(SaxData *data, const xmlChar *name, const xmlChar **attrs)
+{
+switch (data->state) {
+case ERROR:
+break;
+case START:
+       if (!strcmp((gchar *) name, "gpx"))
+               data->state = INSIDE_GPX;
+       else
+               MACRO_SET_UNKNOWN(name);
+break;
+case INSIDE_GPX:
+       if (!strcmp((gchar *) name, "trk"))
+               data->state = INSIDE_PATH;
+       else if (!strcmp((gchar *) name, "metadata"))
+               data->state = INSIDE_METADATA;
+       else
+               MACRO_SET_UNKNOWN(name);
+break;
+case INSIDE_METADATA:
+break;
+case INSIDE_PATH:
+       if (!strcmp((gchar *) name, "trkseg")) {
+               data->state = INSIDE_PATH_SEGMENT;
+               data->at_least_one_trkpt = FALSE;
+       } else
+               MACRO_SET_UNKNOWN(name);
+break;
+case INSIDE_PATH_SEGMENT:
+       if (!strcmp((gchar *) name, "trkpt")) {
+               const xmlChar **curr_attr;
+               gchar *error_check;
+               gdouble lat = 0.f, lon = 0.f;
+               gboolean has_lat, has_lon;
+               has_lat = FALSE;
+               has_lon = FALSE;
+               for (curr_attr = attrs; *curr_attr != NULL;) {
+                       const gchar *attr_name = *curr_attr++;
+                       const gchar *attr_val = *curr_attr++;
+                       if (!strcmp(attr_name, "lat")) {
+                               lat = g_ascii_strtod(attr_val, &error_check);
+                               if (error_check != attr_val)
+                                       has_lat = TRUE;
+                       } else if (!strcmp(attr_name, "lon")) {
+                               lon = g_ascii_strtod(attr_val, &error_check);
+                               if (error_check != attr_val)
+                                       has_lon = TRUE;
+                       }
+               }
+               if (has_lat && has_lon) {
+                       path_add_latlon(data->path, lat, lon, 0, 0, NAN);
+                       data->state = INSIDE_PATH_POINT;
+               } else
+                       data->state = ERROR;
+       } else
+               MACRO_SET_UNKNOWN(name);
+break;
+case INSIDE_PATH_POINT:
+       if (!strcmp((gchar *) name, "time"))
+               data->state = INSIDE_PATH_POINT_TIME;
+       else if (!strcmp((gchar *) name, "ele"))
+               data->state = INSIDE_PATH_POINT_ELE;
+       else if (!strcmp((gchar *) name, "desc"))
+               data->state = INSIDE_PATH_POINT_DESC;
+       else if (!strcmp((gchar *) name, "name"))
+               data->state = INSIDE_PATH_POINT_NAME;
+       else
+               MACRO_SET_UNKNOWN(name);
+break;
+case UNKNOWN:
+       data->unknown_depth++;
+break;
+default: ;
+}
+
+}
+
+/**
+ * Handle an end tag in the parsing of a GPX file.
+ */
+static void 
+gpx_end_element(SaxData * data, const xmlChar * name)
+{
+switch (data->state) {
+case ERROR:
+
+break;
+case START:
+       data->state = ERROR;
+break;
+case INSIDE_GPX:
+       if (!strcmp((gchar *) name, "gpx"))
+               data->state = FINISH;
+       else
+               data->state = ERROR;
+break;
+case INSIDE_METADATA:
+       if (!strcmp((gchar *) name, "metadata"))
+               data->state = INSIDE_GPX;
+break;
+case INSIDE_PATH:
+       if (!strcmp((gchar *) name, "trk"))
+               data->state = INSIDE_GPX;
+       else
+               data->state = ERROR;
+break;
+case INSIDE_PATH_SEGMENT:
+       if (!strcmp((gchar *) name, "trkseg")) {
+               if (data->at_least_one_trkpt) {
+                       path_add_break(data->path);
+               }
+               data->state = INSIDE_PATH;
+       } else
+               data->state = ERROR;
+break;
+case INSIDE_PATH_POINT:
+       if (!strcmp((gchar *) name, "trkpt")) {
+               data->state = INSIDE_PATH_SEGMENT;
+               data->at_least_one_trkpt = TRUE;
+       } else
+               data->state = ERROR;
+break;
+case INSIDE_PATH_POINT_ELE:
+       if (!strcmp((gchar *) name, "ele")) {
+               gchar *error_check;
+               data->path->tail->altitude = g_ascii_strtod(data->chars->str, &error_check);
+               if (error_check == data->chars->str)
+                       data->path->tail->altitude = NAN;
+               data->state = INSIDE_PATH_POINT;
+               g_string_free(data->chars, TRUE);
+               data->chars = g_string_new("");
+       } else
+               data->state = ERROR;
+break;
+case INSIDE_PATH_POINT_TIME:
+       if (!strcmp((gchar *) name, "time")) {
+               struct tm time;
+               gchar *ptr;
+
+               if (NULL == (ptr = strptime(data->chars->str, XML_DATE_FORMAT, &time)))
+                       /* Failed to parse dateTime format. */
+                       data->state = ERROR;
+               else {
+                       /* Parse was successful. Now we have to parse timezone.
+                        * From here on, if there is an error, I just assume local
+                        * timezone.  Yes, this is not proper XML, but I don't
+                        * care. */
+                       gchar *error_check;
+
+                       /* First, set time in "local" time zone. */
+                       data->path->tail->time = (mktime(&time));
+
+                       /* Now, skip inconsequential characters */
+                       while (*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
+                               ptr++;
+
+                       /* Check if we ran to the end of the string. */
+                       if (*ptr) {
+                               /* Next character is either 'Z', '-', or '+' */
+                               if (*ptr == 'Z')
+                                       /* Zulu (UTC) time. Undo the local time zone's offset. */
+                                       data->path->tail->time += time.tm_gmtoff;
+                               else {
+                                       /* Not Zulu (UTC). Must parse hours and minutes. */
+                                       gint offhours = strtol(ptr, &error_check, 10);
+                                       if (error_check != ptr && *(ptr = error_check) == ':') {
+                                               /* Parse of hours worked. Check minutes. */
+                                               gint offmins = strtol(ptr + 1, &error_check, 10);
+                                               if (error_check != (ptr + 1)) {
+                                                       /* Parse of minutes worked. Calculate. */
+                                                       data->path->tail->time += (time.tm_gmtoff - (offhours * 60 * 60 + offmins * 60));
+                                               }
+                                       }
+                               }
+                       }
+                       /* Successfully parsed dateTime. */
+                       data->state = INSIDE_PATH_POINT;
+               }
+
+               g_string_free(data->chars, TRUE);
+               data->chars = g_string_new("");
+       } else {
+               data->state = ERROR;
+       }
+break;
+case INSIDE_PATH_POINT_DESC:
+       /* only parse description for routes */
+       if (!strcmp((gchar *) name, "desc")) {
+               path_insert_mark_text(data->path, g_string_free(data->chars, FALSE));
+               data->chars=g_string_new("");
+               data->state=INSIDE_PATH_POINT;
+       } else {
+               data->state=ERROR;
+       }
+break;
+case INSIDE_PATH_POINT_NAME:
+       /* Just ignore these for now */
+       g_string_free(data->chars, FALSE);
+       data->chars=g_string_new("");
+       data->state=INSIDE_PATH_POINT;
+break;
+case UNKNOWN:
+       if (!--data->unknown_depth)
+               data->state=data->prev_state;
+       else
+               data->state=ERROR;
+break;
+default: ;
+}
+
+}
+
+/**
+ * Handle char data in the parsing of a GPX file.
+ */
+static void 
+gpx_chars(SaxData *data, const xmlChar *ch, int len)
+{
+guint i;
+
+switch (data->state) {
+case ERROR:
+case UNKNOWN:
+       break;
+case INSIDE_PATH_POINT_ELE:
+case INSIDE_PATH_POINT_TIME:
+case INSIDE_PATH_POINT_DESC:
+case INSIDE_PATH_POINT_NAME:
+       for (i = 0; i < len; i++)
+               data->chars = g_string_append_c(data->chars, ch[i]);
+       /* g_debug("GPXC: %s", data->chars->str); */
+       break;
+default:
+break;
+}
+
+}
+
+/**
+ * Handle an entity in the parsing of a GPX file.  We don't do anything
+ * special here.
+ */
+static xmlEntityPtr 
+gpx_get_entity(SaxData *data, const xmlChar *name)
+{
+return xmlGetPredefinedEntity(name);
+}
+
+/**
+ * Handle an error in the parsing of a GPX file.
+ */
+static void 
+gpx_error(SaxData *data, const gchar *msg, ...)
+{
+va_list args;
+
+va_start(args, msg);
+g_logv("GPX", G_LOG_LEVEL_WARNING, msg, args);
+va_end(args);
+
+data->state = ERROR;
+}
+
+/**
+ * gpx_parse:
+ * @path
+ * @buffer
+ * @size
+ * @policy
+ *
+ * Parse a buffer of GPX data.
+ * XXX: Add support for parsing directly from file if the file is local.
+ *
+ */
+gboolean
+path_gpx_parse(Path *path, gchar *buffer, gint size, gpx_path_policy policy)
+{
+SaxData data;
+xmlSAXHandler sax_handler;
+
+data.path=path;
+data.state=START;
+data.chars=g_string_new("");
+
+if (policy==GPX_PATH_NEW)
+       path_clear(path);
+
+memset(&sax_handler, 0, sizeof(sax_handler));
+sax_handler.characters=(charactersSAXFunc) gpx_chars;
+sax_handler.startElement=(startElementSAXFunc) gpx_start_element;
+sax_handler.endElement=(endElementSAXFunc) gpx_end_element;
+sax_handler.entityDecl=(entityDeclSAXFunc) gpx_get_entity;
+sax_handler.warning=(warningSAXFunc) gpx_error;
+sax_handler.error=(errorSAXFunc) gpx_error;
+sax_handler.fatalError=(fatalErrorSAXFunc) gpx_error;
+
+xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
+
+g_string_free(data.chars, TRUE);
+
+if (data.state!=FINISH) {
+       g_warning("GPX: Parser stopped in error state %d", data.state);
+       return FALSE;
+}
+
+switch (policy) {
+case GPX_PATH_APPEND:
+case GPX_PATH_PREPEND:
+       {
+       Point *src_first;
+       Path *src, *dest;
+
+       if (policy==GPX_PATH_APPEND) {
+               /* Append to current path. Make sure last path point is zero. */
+               path_add_break(path);
+               src=data.path;
+               dest=path;
+       } else {
+               /* Prepend to current route. */
+               src=path;
+               dest=data.path;
+       }
+
+       /* Find src_first non-zero point. */
+       for (src_first = src->head; src_first++ != src->tail;)
+               if (src_first->unity && src_first->unitx)
+                       break;
+
+       /* Append route points from src to dest. */
+       if (src->tail >= src_first) {
+               WayPoint *curr;
+               guint num_dest_points = dest->tail - dest->head + 1;
+               guint num_src_points = src->tail - src_first + 1;
+
+               /* Adjust dest->tail to be able to fit src route data
+                * plus room for more route data. */
+               path_resize(dest, num_dest_points + num_src_points);
+
+               memcpy(dest->tail + 1, src_first, num_src_points * sizeof(Point));
+
+               dest->tail += num_src_points;
+
+               /* Append waypoints from src to dest->. */
+               path_wresize(dest, (dest->wtail - dest->whead) + (src->wtail - src->whead) + 2);
+               for (curr = src->whead - 1; curr++ != src->wtail;) {
+                       (++(dest->wtail))->point = dest->head + num_dest_points + (curr->point - src_first);
+                       dest->wtail->desc = curr->desc;
+               }
+       }
+
+       /* Kill old route - don't use MACRO_PATH_FREE(), because that
+        * would free the string desc's that we just moved to data.route. */
+       g_free(src->head);
+       g_free(src->whead);
+       if (policy==GPX_PATH_PREPEND)
+               (*path) = *dest;
+       }
+break;
+case GPX_PATH_NEW:
+       /* */
+break;
+default:
+       g_assert_not_reached();
+break;
+}
+
+return TRUE;
+}
+
+/******************************************************************************/
+
+#if 0
+static void
+path_store_append_waypoint(GtkListStore *, WayPoint *w)
+{
+gchar tmp1[16], tmp2[16];
+gdouble lat2, lon2;
+
+unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat2, lon2);
+lat_format(_degformat, lat2, tmp1);
+lon_format(_degformat, lon2, tmp2);
+
+g_snprintf(buffer1, sizeof(buffer1), "%s,%s", tmp1, tmp2);
+sum += calculate_distance(lat1, lon1, lat2, lon2);
+g_snprintf(buffer2, sizeof(buffer2), "%.02f %s", sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
+
+gtk_list_store_append(store, &iter);
+gtk_list_store_set(store, &iter,
+       PATH_LATLON, buffer1,
+       PATH_DISTANCE, buffer2,
+       PATH_WAYPOINT, wcurr->desc,
+       PATH_LAT, lat2,
+       PATH_LON, lon2,
+       -1);
+
+lat1=lat2;
+lon1=lon2;
+}
+#endif
+
+/**
+ * Generate a GtkListStore with information about path waypoints (location, distance)
+ */
+GtkListStore *
+path_get_waypoints_store(Path *path)
+{
+WayPoint *wcurr;
+GtkTreeIter iter;
+GtkListStore *store;
+gchar buffer1[80];
+gchar buffer2[32];
+gchar tmp1[16], tmp2[16];
+gdouble lat1, lon1, lat2, lon2;
+gdouble sum=0.0;
+
+g_return_val_if_fail(path!=NULL, NULL);
+
+wcurr=path->whead;
+
+g_return_val_if_fail(wcurr!=NULL, NULL);
+g_return_val_if_fail(wcurr->point!=NULL, NULL);
+if (!path_has_waypoints(path))
+       return NULL;
+
+store=gtk_list_store_new(PATH_NUM_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat1, lon1);
+
+while (wcurr!=path->wtail) {
+       if (!wcurr)
+               break;
+
+       if (!wcurr->point) {
+               g_debug("PSTORE: No point for waypoint (%s) skipping", wcurr->desc);
+               wcurr++;
+               continue;
+       }
+
+       unit2latlon(wcurr->point->unitx, wcurr->point->unity, lat2, lon2);
+       lat_format(_degformat, lat2, tmp1);
+       lon_format(_degformat, lon2, tmp2);
+
+       g_snprintf(buffer1, sizeof(buffer1), "%s,%s", tmp1, tmp2);
+       sum += calculate_distance(lat1, lon1, lat2, lon2);
+       g_snprintf(buffer2, sizeof(buffer2), "%.02f %s", sum * UNITS_CONVERT[_units], UNITS_TEXT[_units]);
+
+       gtk_list_store_append(store, &iter);
+       gtk_list_store_set(store, &iter,
+               PATH_LATLON, buffer1,
+               PATH_DISTANCE, buffer2,
+               PATH_WAYPOINT, wcurr->desc,
+               PATH_LAT, lat2,
+               PATH_LON, lon2,
+               -1);
+
+       lat1=lat2;
+       lon1=lon2;
+
+       wcurr++;
+}
+
+return store;
 }