From 6ba42f1ebab48a73bb93b6cfc88737d0035a2802 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Mon, 28 Oct 2013 00:05:22 +0000 Subject: [PATCH] Basic support of some GPX Metadata values common to GPX1.0 and GPX1.1 Includes the fields: name, description, author, time, and keywords. ATM the time is a read-only value loaded from a GPX file (no GUI way to alter it). If there is no value or it's a new TrackWaypoint layer then the value auto generated from the earliest track or waypoint or else from the current time. --- src/file.c | 2 +- src/gpx.c | 114 ++++++++++++++++++++++++++++++++++++++++-- src/viktrwlayer.c | 123 +++++++++++++++++++++++++++++++++++++++++++++- src/viktrwlayer.h | 13 +++++ 4 files changed, 244 insertions(+), 8 deletions(-) diff --git a/src/file.c b/src/file.c index e3f1dc4a..f3f9b4c0 100644 --- a/src/file.c +++ b/src/file.c @@ -647,6 +647,7 @@ VikLoadType_t a_file_load ( VikAggregateLayer *top, VikViewport *vp, const gchar gboolean success = TRUE; // Detect load failures - mainly to remove the layer created as it's not required VikLayer *vtl = vik_layer_create ( VIK_LAYER_TRW, vp, NULL, FALSE ); + vik_layer_rename ( vtl, a_file_basename ( filename ) ); // In fact both kml & gpx files start the same as they are in xml if ( check_file_ext ( filename, ".kml" ) && check_magic ( f, GPX_MAGIC ) ) { @@ -677,7 +678,6 @@ VikLoadType_t a_file_load ( VikAggregateLayer *top, VikViewport *vp, const gchar } else { // Complete the setup from the successful load - vik_layer_rename ( vtl, a_file_basename ( filename ) ); vik_layer_post_read ( vtl, vp, TRUE ); vik_aggregate_layer_add_layer ( top, vtl, FALSE ); vik_trw_layer_auto_set_view ( VIK_TRW_LAYER(vtl), vp ); diff --git a/src/gpx.c b/src/gpx.c index 6a039b03..f42b8cc2 100644 --- a/src/gpx.c +++ b/src/gpx.c @@ -48,6 +48,11 @@ typedef enum { tt_unknown = 0, tt_gpx, + tt_gpx_name, + tt_gpx_desc, + tt_gpx_author, + tt_gpx_time, + tt_gpx_keywords, tt_wpt, tt_wpt_cmt, @@ -70,7 +75,7 @@ typedef enum { tt_trk_trkseg_trkpt_ele, tt_trk_trkseg_trkpt_time, tt_trk_trkseg_trkpt_name, - /* extended */ + /* extended */ tt_trk_trkseg_trkpt_course, tt_trk_trkseg_trkpt_speed, tt_trk_trkseg_trkpt_fix, @@ -96,13 +101,27 @@ typedef struct { } GpxWritingContext; /* - * xpath(ish) mappings between full tag paths and internal identifers. + * xpath(ish) mappings between full tag paths and internal identifiers. * These appear in the order they appear in the GPX specification. - * If it's not a tag we explictly handle, it doesn't go here. + * If it's not a tag we explicitly handle, it doesn't go here. */ tag_mapping tag_path_map[] = { + { tt_gpx, "/gpx" }, + { tt_gpx_name, "/gpx/name" }, + { tt_gpx_desc, "/gpx/desc" }, + { tt_gpx_time, "/gpx/time" }, + { tt_gpx_author, "/gpx/author" }, + { tt_gpx_keywords, "/gpx/keywords" }, + + // GPX 1.1 variant - basic properties moved into metadata namespace + { tt_gpx_name, "/gpx/metadata/name" }, + { tt_gpx_desc, "/gpx/metadata/desc" }, + { tt_gpx_time, "/gpx/metadata/time" }, + { tt_gpx_author, "/gpx/metadata/author" }, + { tt_gpx_keywords, "/gpx/metadata/keywords" }, + { tt_wpt, "/gpx/wpt" }, { tt_waypoint, "/loc/waypoint" }, @@ -127,8 +146,8 @@ tag_mapping tag_path_map[] = { { tt_trk_trkseg_trkpt_ele, "/gpx/trk/trkseg/trkpt/ele" }, { tt_trk_trkseg_trkpt_time, "/gpx/trk/trkseg/trkpt/time" }, { tt_trk_trkseg_trkpt_name, "/gpx/trk/trkseg/trkpt/name" }, - /* extended */ - { tt_trk_trkseg_trkpt_course, "/gpx/trk/trkseg/trkpt/course" }, + /* extended */ + { tt_trk_trkseg_trkpt_course, "/gpx/trk/trkseg/trkpt/course" }, { tt_trk_trkseg_trkpt_speed, "/gpx/trk/trkseg/trkpt/speed" }, { tt_trk_trkseg_trkpt_fix, "/gpx/trk/trkseg/trkpt/fix" }, { tt_trk_trkseg_trkpt_sat, "/gpx/trk/trkseg/trkpt/sat" }, @@ -168,6 +187,7 @@ GString *c_cdata = NULL; VikTrackpoint *c_tp = NULL; VikWaypoint *c_wp = NULL; VikTrack *c_tr = NULL; +VikTRWMetadata *c_md = NULL; gchar *c_wp_name = NULL; gchar *c_tr_name = NULL; @@ -212,6 +232,10 @@ static void gpx_start(VikTrwLayer *vtl, const char *el, const char **attr) switch ( current_tag ) { + case tt_gpx: + c_md = vik_trw_metadata_new(); + break; + case tt_wpt: if ( set_c_ll( attr ) ) { c_wp = vik_waypoint_new (); @@ -249,6 +273,11 @@ static void gpx_start(VikTrwLayer *vtl, const char *el, const char **attr) } break; + case tt_gpx_name: + case tt_gpx_author: + case tt_gpx_desc: + case tt_gpx_keywords: + case tt_gpx_time: case tt_trk_trkseg_trkpt_name: case tt_trk_trkseg_trkpt_ele: case tt_trk_trkseg_trkpt_time: @@ -296,6 +325,44 @@ static void gpx_end(VikTrwLayer *vtl, const char *el) switch ( current_tag ) { + case tt_gpx: + vik_trw_layer_set_metadata ( vtl, c_md ); + c_md = NULL; + break; + + case tt_gpx_name: + vik_layer_rename ( VIK_LAYER(vtl), c_cdata->str ); + g_string_erase ( c_cdata, 0, -1 ); + break; + + case tt_gpx_author: + if ( c_md->author ) + g_free ( c_md->description ); + c_md->author = g_strdup ( c_cdata->str ); + g_string_erase ( c_cdata, 0, -1 ); + break; + + case tt_gpx_desc: + if ( c_md->description ) + g_free ( c_md->description ); + c_md->description = g_strdup ( c_cdata->str ); + g_string_erase ( c_cdata, 0, -1 ); + break; + + case tt_gpx_keywords: + if ( c_md->keywords ) + g_free ( c_md->keywords ); + c_md->keywords = g_strdup ( c_cdata->str ); + g_string_erase ( c_cdata, 0, -1 ); + break; + + case tt_gpx_time: + if ( c_md->timestamp ) + g_free ( c_md->timestamp ); + c_md->timestamp = g_strdup ( c_cdata->str ); + g_string_erase ( c_cdata, 0, -1 ); + break; + case tt_waypoint: case tt_wpt: if ( ! c_wp_name ) @@ -445,6 +512,11 @@ static void gpx_end(VikTrwLayer *vtl, const char *el) static void gpx_cdata(void *dta, const XML_Char *s, int len) { switch ( current_tag ) { + case tt_gpx_name: + case tt_gpx_author: + case tt_gpx_desc: + case tt_gpx_keywords: + case tt_gpx_time: case tt_wpt_name: case tt_trk_name: case tt_wpt_ele: @@ -953,6 +1025,38 @@ void a_gpx_write_file ( VikTrwLayer *vtl, FILE *f, GpxWritingOptions *options ) gpx_write_header ( f ); + gchar *tmp; + const gchar *name = vik_layer_get_name(VIK_LAYER(vtl)); + if ( name ) { + tmp = entitize ( name ); + fprintf ( f, " %s\n", tmp ); + g_free ( tmp ); + } + + VikTRWMetadata *md = vik_trw_layer_get_metadata (vtl); + if ( md ) { + if ( md->author ) { + tmp = entitize ( md->author ); + fprintf ( f, " %s\n", tmp ); + g_free ( tmp ); + } + if ( md->description ) { + tmp = entitize ( md->description ); + fprintf ( f, " %s\n", tmp ); + g_free ( tmp ); + } + if ( md->timestamp ) { + tmp = entitize ( md->timestamp ); + fprintf ( f, " \n", tmp ); + g_free ( tmp ); + } + if ( md->keywords ) { + tmp = entitize ( md->keywords ); + fprintf ( f, " %s\n", tmp ); + g_free ( tmp ); + } + } + // gather waypoints in a list, then sort // g_hash_table_get_values: glib 2.14+ GList *gl = g_hash_table_get_values ( vik_trw_layer_get_waypoints ( vtl ) ); diff --git a/src/viktrwlayer.c b/src/viktrwlayer.c index ddebc9f1..80ecd745 100644 --- a/src/viktrwlayer.c +++ b/src/viktrwlayer.c @@ -149,6 +149,9 @@ struct _VikTrwLayer { guint8 bg_line_thickness; vik_layer_sort_order_t track_sort_order; + // Metadata + VikTRWMetadata *metadata; + PangoLayout *tracklabellayout; font_size_t track_font_size; gchar *track_fsize_str; @@ -481,8 +484,8 @@ enum { /****** PARAMETERS ******/ -static gchar *params_groups[] = { N_("Waypoints"), N_("Tracks"), N_("Waypoint Images"), N_("Tracks Advanced") }; -enum { GROUP_WAYPOINTS, GROUP_TRACKS, GROUP_IMAGES, GROUP_TRACKS_ADV }; +static gchar *params_groups[] = { N_("Waypoints"), N_("Tracks"), N_("Waypoint Images"), N_("Tracks Advanced"), N_("Metadata") }; +enum { GROUP_WAYPOINTS, GROUP_TRACKS, GROUP_IMAGES, GROUP_TRACKS_ADV, GROUP_METADATA }; static gchar *params_drawmodes[] = { N_("Draw by Track"), N_("Draw by Speed"), N_("All Tracks Same Color"), NULL }; static gchar *params_wpsymbols[] = { N_("Filled Square"), N_("Square"), N_("Circle"), N_("X"), 0 }; @@ -560,6 +563,13 @@ static VikLayerParamData image_cache_size_default ( void ) { return VIK_LPD_UINT static VikLayerParamData sort_order_default ( void ) { return VIK_LPD_UINT ( 0 ); } +static VikLayerParamData string_default ( void ) +{ + VikLayerParamData data; + data.s = ""; + return data; +} + VikLayerParam trw_layer_params[] = { { VIK_LAYER_TRW, "tracks_visible", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_NOT_IN_PROPERTIES, NULL, 0, NULL, NULL, NULL, vik_lpd_true_default, NULL, NULL }, { VIK_LAYER_TRW, "waypoints_visible", VIK_LAYER_PARAM_BOOLEAN, VIK_LAYER_NOT_IN_PROPERTIES, NULL, 0, NULL, NULL, NULL, vik_lpd_true_default, NULL, NULL }, @@ -604,6 +614,11 @@ VikLayerParam trw_layer_params[] = { { VIK_LAYER_TRW, "image_size", VIK_LAYER_PARAM_UINT, GROUP_IMAGES, N_("Image Size (pixels):"), VIK_LAYER_WIDGET_HSCALE, ¶ms_scales[3], NULL, NULL, image_size_default, NULL, NULL }, { VIK_LAYER_TRW, "image_alpha", VIK_LAYER_PARAM_UINT, GROUP_IMAGES, N_("Image Alpha:"), VIK_LAYER_WIDGET_HSCALE, ¶ms_scales[4], NULL, NULL, image_alpha_default, NULL, NULL }, { VIK_LAYER_TRW, "image_cache_size", VIK_LAYER_PARAM_UINT, GROUP_IMAGES, N_("Image Memory Cache Size:"), VIK_LAYER_WIDGET_HSCALE, ¶ms_scales[5], NULL, NULL, image_cache_size_default, NULL, NULL }, + + { VIK_LAYER_TRW, "metadatadesc", VIK_LAYER_PARAM_STRING, GROUP_METADATA, N_("Description"), VIK_LAYER_WIDGET_ENTRY, NULL, NULL, NULL, string_default, NULL, NULL }, + { VIK_LAYER_TRW, "metadataauthor", VIK_LAYER_PARAM_STRING, GROUP_METADATA, N_("Author"), VIK_LAYER_WIDGET_ENTRY, NULL, NULL, NULL, string_default, NULL, NULL }, + { VIK_LAYER_TRW, "metadatatime", VIK_LAYER_PARAM_STRING, GROUP_METADATA, N_("Creation Time"), VIK_LAYER_WIDGET_ENTRY, NULL, NULL, NULL, string_default, NULL, NULL }, + { VIK_LAYER_TRW, "metadatakeywords", VIK_LAYER_PARAM_STRING, GROUP_METADATA, N_("Keywords"), VIK_LAYER_WIDGET_ENTRY, NULL, NULL, NULL, string_default, NULL, NULL }, }; // ENUMERATION MUST BE IN THE SAME ORDER AS THE NAMED PARAMS ABOVE @@ -647,6 +662,11 @@ enum { PARAM_IS, PARAM_IA, PARAM_ICS, + // Metadata + PARAM_MDDESC, + PARAM_MDAUTH, + PARAM_MDTIME, + PARAM_MDKEYS, NUM_PARAMS }; @@ -775,6 +795,28 @@ GType vik_trw_layer_get_type () return vtl_type; } +VikTRWMetadata *vik_trw_metadata_new() +{ + return (VikTRWMetadata*)g_malloc0(sizeof(VikTRWMetadata)); +} + +void vik_trw_metadata_free ( VikTRWMetadata *metadata) +{ + g_free (metadata); +} + +VikTRWMetadata *vik_trw_layer_get_metadata ( VikTrwLayer *vtl ) +{ + return vtl->metadata; +} + +void vik_trw_layer_set_metadata ( VikTrwLayer *vtl, VikTRWMetadata *metadata) +{ + if ( vtl->metadata ) + vik_trw_metadata_free ( vtl->metadata ); + vtl->metadata = metadata; +} + typedef struct { gboolean found; const gchar *date_str; @@ -1156,6 +1198,12 @@ static gboolean trw_layer_set_param ( VikTrwLayer *vtl, guint16 id, VikLayerPara } break; case PARAM_WPSO: if ( data.u < VL_SO_LAST ) vtl->wp_sort_order = data.u; break; + // Metadata + case PARAM_MDDESC: if ( data.s && vtl->metadata ) vtl->metadata->description = g_strdup (data.s); break; + case PARAM_MDAUTH: if ( data.s && vtl->metadata ) vtl->metadata->author = g_strdup (data.s); break; + case PARAM_MDTIME: if ( data.s && vtl->metadata ) vtl->metadata->timestamp = g_strdup (data.s); break; + case PARAM_MDKEYS: if ( data.s && vtl->metadata ) vtl->metadata->keywords = g_strdup (data.s); break; + default: break; } return TRUE; } @@ -1200,6 +1248,12 @@ static VikLayerParamData trw_layer_get_param ( VikTrwLayer *vtl, guint16 id, gbo case PARAM_WPSYMS: rv.b = vtl->wp_draw_symbols; break; case PARAM_WPFONTSIZE: rv.u = vtl->wp_font_size; break; case PARAM_WPSO: rv.u = vtl->wp_sort_order; break; + // Metadata + case PARAM_MDDESC: if (vtl->metadata) { rv.s = vtl->metadata->description; } break; + case PARAM_MDAUTH: if (vtl->metadata) { rv.s = vtl->metadata->author; } break; + case PARAM_MDTIME: if (vtl->metadata) { rv.s = vtl->metadata->timestamp; } break; + case PARAM_MDKEYS: if (vtl->metadata) { rv.s = vtl->metadata->keywords; } break; + default: break; } return rv; } @@ -1267,6 +1321,12 @@ static void trw_layer_change_param ( GtkWidget *widget, ui_change_values values if ( w2 ) gtk_widget_set_sensitive ( w2, sensitive ); break; } + case PARAM_MDTIME: { + // Force metadata->timestamp to be always read-only for now. + GtkWidget **ww = values[UI_CHG_WIDGETS]; + GtkWidget *w1 = ww[OFFSET + PARAM_MDTIME]; + if ( w1 ) gtk_widget_set_sensitive ( w1, FALSE ); + } // NB Since other track settings have been split across tabs, // I don't think it's useful to set sensitivities on widgets you can't immediately see default: break; @@ -1460,6 +1520,7 @@ static VikTrwLayer* trw_layer_new1 ( VikViewport *vvp ) // Force to on after processing params (which defaults them to off with a zero value) rv->waypoints_visible = rv->tracks_visible = rv->routes_visible = TRUE; + rv->metadata = vik_trw_metadata_new (); rv->draw_sync_done = TRUE; rv->draw_sync_do = TRUE; // Everything else is 0, FALSE or NULL @@ -10099,6 +10160,64 @@ static void trw_layer_post_read ( VikTrwLayer *vtl, GtkWidget *vvp, gboolean fro // since the sorting of a treeview section is now very quick // NB sorting is also performed after every name change as well to maintain the list order trw_layer_sort_all ( vtl ); + + // Setting metadata time if not otherwise set + if ( vtl->metadata ) { + + gboolean need_to_set_time = TRUE; + if ( vtl->metadata->timestamp ) { + need_to_set_time = FALSE; + if ( !g_strcmp0(vtl->metadata->timestamp, "" ) ) + need_to_set_time = TRUE; + } + + if ( need_to_set_time ) { + // Could rewrite this as a general get first time of a TRW Layer function + GTimeVal timestamp; + timestamp.tv_usec = 0; + gboolean has_timestamp = FALSE; + + GList *gl = NULL; + gl = g_hash_table_get_values ( vtl->tracks ); + gl = g_list_sort ( gl, vik_track_compare_timestamp ); + gl = g_list_first ( gl ); + + // Check times of tracks + if ( gl ) { + // Only need to check the first track as they have been sorted by time + VikTrack *trk = (VikTrack*)gl->data; + // Assume trackpoints already sorted by time + VikTrackpoint *tpt = vik_track_get_tp_first(trk); + if ( tpt && tpt->has_timestamp ) { + timestamp.tv_sec = tpt->timestamp; + has_timestamp = TRUE; + } + g_list_free ( gl ); + } + + if ( !has_timestamp ) { + // 'Last' resort - current time + // Get before waypoint tests - so that if a waypoint time value (in the past) is found it should be used + g_get_current_time ( ×tamp ); + + // Check times of waypoints + gl = g_hash_table_get_values ( vtl->waypoints ); + GList *iter; + for (iter = g_list_first (gl); iter != NULL; iter = g_list_next (iter)) { + VikWaypoint *wpt = (VikWaypoint*)iter->data; + if ( wpt->has_timestamp ) { + if ( timestamp.tv_sec > wpt->timestamp ) { + timestamp.tv_sec = wpt->timestamp; + has_timestamp = TRUE; + } + } + } + g_list_free ( gl ); + } + + vtl->metadata->timestamp = g_time_val_to_iso8601 ( ×tamp ); + } + } } VikCoordMode vik_trw_layer_get_coord_mode ( VikTrwLayer *vtl ) diff --git a/src/viktrwlayer.h b/src/viktrwlayer.h index bf35ba58..5a1fc02e 100644 --- a/src/viktrwlayer.h +++ b/src/viktrwlayer.h @@ -57,6 +57,19 @@ GType vik_trw_layer_get_type (); typedef struct _VikTrwLayer VikTrwLayer; +typedef struct { + gchar *description; + gchar *author; + //gboolean has_time; + gchar *timestamp; // TODO: Consider storing as proper time_t. + gchar *keywords; // TODO: handling/storing a GList of individual tags? +} VikTRWMetadata; + +VikTRWMetadata *vik_trw_metadata_new(); +void vik_trw_metadata_free ( VikTRWMetadata *metadata); +VikTRWMetadata *vik_trw_layer_get_metadata ( VikTrwLayer *vtl ); +void vik_trw_layer_set_metadata ( VikTrwLayer *vtl, VikTRWMetadata *metadata); + gboolean vik_trw_layer_find_date ( VikTrwLayer *vtl, const gchar *date_str, VikCoord *position, VikViewport *vvp, gboolean do_tracks, gboolean select ); /* These are meant for use in file loaders (gpspoint.c, gpx.c, etc). -- 2.39.5