#include <glib.h>
#include <gtk/gtk.h>
#include <sqlite3.h>
+#include <libxml/parser.h>
+#include <libgnomevfs/gnome-vfs.h>
#include "path.h"
#include "position.h"
#include "gps.h"
#include "settings.h"
#include "latlon.h"
-#include "gpx.h"
struct sql_select_stmt {
sqlite3_stmt *select_paths;
};
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))
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;
}
* @lon
* @desc
*
- * Append a waypoint to path at given lat, lon with description desc
+ * Append a waypoint to path at given lat, lon with description desc.
*
* Returns: TRUE
*/
/******************************************************************************/
gboolean
-path_load(Path *path, const gchar *config_dir, const gchar *file)
+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(config_dir, FALSE);
g_return_val_if_fail(file, FALSE);
-pfile = gnome_vfs_uri_make_full_from_relative(config_dir, file);
-if (gnome_vfs_read_entire_file(pfile, &size, &bytes)==GNOME_VFS_OK)
- gpx_parse(path, bytes, size, GPX_PATH_NEW);
+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 *config_dir, const gchar *file)
+path_save(Path *path, const gchar *file)
{
GnomeVFSHandle *handle;
gchar *tfile;
g_return_val_if_fail(path, FALSE);
-g_return_val_if_fail(config_dir, FALSE);
g_return_val_if_fail(file, FALSE);
-tfile=gnome_vfs_uri_make_full_from_relative(config_dir, file);
-if (gnome_vfs_create(&handle, tfile, GNOME_VFS_OPEN_WRITE, FALSE, 0600)==GNOME_VFS_OK) {
- gpx_write(path, handle);
+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);
* @path
* @text
*
- * Add a new waypoint to path with given text.
+ * Add a new waypoint to path with given text at the last point.
*
*/
void
}
/******************************************************************************/
+
+#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)