From: Kaj-Michael Lang Date: Wed, 11 Jun 2008 14:08:34 +0000 (+0300) Subject: Path: Move GPX parsing and writing here X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f36c74bdae5ad842ab1fcb2cf3472c910b8b6577;p=mapper Path: Move GPX parsing and writing here --- diff --git a/src/path.c b/src/path.c index 2a8c3e7..ce9b4a4 100644 --- a/src/path.c +++ b/src/path.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include "path.h" #include "position.h" @@ -29,7 +31,6 @@ #include "gps.h" #include "settings.h" #include "latlon.h" -#include "gpx.h" struct sql_select_stmt { sqlite3_stmt *select_paths; @@ -74,6 +75,36 @@ enum { }; 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)) @@ -141,11 +172,20 @@ 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; } @@ -213,7 +253,7 @@ return FALSE; * @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 */ @@ -636,36 +676,32 @@ return sum; /******************************************************************************/ 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); @@ -732,7 +768,7 @@ return TRUE; * @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 @@ -764,6 +800,523 @@ 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("\n" + "\n"); + +/* Write any metadata */ +WRITE_STRING("\n"); + +WRITE_STRING("\n"); + +WRITE_STRING("\n"); + +/* Write track(s) and waypoint(s) */ +WRITE_STRING("\n\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("\n\n"); + trkseg_break = FALSE; + } + unit2latlon(curr->unitx, curr->unity, &lat, &lon); + WRITE_STRING("altitude)) { + if (first_sub) { + WRITE_STRING(">\n"); + first_sub = FALSE; + } + WRITE_STRING(""); + g_ascii_formatd(buffer, sizeof(buffer), "%.2f", curr->altitude); + WRITE_STRING(buffer); + WRITE_STRING("\n"); + } + + /* write the time */ + if (curr->time) { + if (first_sub) { + WRITE_STRING(">\n"); + first_sub = FALSE; + } + WRITE_STRING("\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("%s\n", wcurr->desc); + WRITE_STRING(desc); + g_free(desc); + } + wcurr++; + } + if (first_sub) { + WRITE_STRING("/>\n"); + } else { + WRITE_STRING("\n"); + } + } else + trkseg_break = TRUE; +} + +/* Write the footer. */ +WRITE_STRING("\n\n\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) diff --git a/src/path.h b/src/path.h index 2f9ecd1..7ba8464 100644 --- a/src/path.h +++ b/src/path.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "gpsdata.h" @@ -42,6 +43,12 @@ typedef enum { PATH_TYPE_FRIEND, } PathType; +typedef enum { + GPX_PATH_PREPEND=-1, + GPX_PATH_NEW=0, + GPX_PATH_APPEND=1, +} gpx_path_policy; + /* Path store items */ typedef enum { PATH_LATLON, @@ -165,6 +172,10 @@ Path *path_new(PathType type, guint id); void path_free(Path *p); void path_clear(Path *p); +gboolean path_gpx_read(Path *path, GnomeVFSHandle *handle); +gboolean path_gpx_write(Path *path, GnomeVFSHandle *handle, GError **error); +gboolean path_gpx_parse(Path *to_replace, gchar *buffer, gint size, gpx_path_policy policy); + Point *path_find_last_point(Path *path); gboolean path_resize(Path *path, guint size);