From 7d42ca045284da44b249b42564421163c5969aab Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Sun, 14 May 2017 21:57:53 +0100 Subject: [PATCH] Geocode image by drag-drop If an image is not geocoded, drag-and-drop on the map to write latitude and longitude to metadata --- doc/docbook/GuideReferenceDecodeLatLong.xml | 245 ++++++++++---------- doc/docbook/GuideSidebarsInfo.xml | 38 ++- src/bar_gps.c | 204 +++++++++++++++- src/metadata.c | 40 ++++ src/metadata.h | 1 + src/misc.c | 42 ++++ src/misc.h | 1 + src/search.c | 40 ---- 8 files changed, 438 insertions(+), 173 deletions(-) diff --git a/doc/docbook/GuideReferenceDecodeLatLong.xml b/doc/docbook/GuideReferenceDecodeLatLong.xml index 83f31e50..22b4c6a6 100644 --- a/doc/docbook/GuideReferenceDecodeLatLong.xml +++ b/doc/docbook/GuideReferenceDecodeLatLong.xml @@ -1,14 +1,18 @@
Decoding Latitude and Longitude - This section is relevent to the search option "Search on geo-location". + This section is relevent to geocode searches and the map display of Geeqie. The result of some internet or other searches for placenames can contain a latitude and longitude embedded in a text string. For example an openstreetmap.org search can give a URL such as: https://www.openstreetmap.org/search?query=51.5542%2C-0.1816#map=12/51.5542/-0.1818 - If you paste such a string into the search box, the latitude/longitude can be automatically extracted and used as the origin of the search. To do this create the file + If you paste such a string into the search box, the latitude/longitude can be automatically extracted and used as the origin of the search. + + You may also drag-and-drop a URL of this type onto the map to cause the map to be re-centered. + + To enable this feature, create the file ~/.config/geeqie/geocode-parameters.awk @@ -16,125 +20,124 @@ -# Store this file in: -# ~/.config/geeqie/geocode-parameters.awk -# -# This file is used by the Search option "search on geo-position". -# It is used to decode the results of internet or other searches -# to extract a geo-position from a text string. -# To include other searches, follow the examples below and -# ensure the returned value is either in the format: -# 89.123 179.123 -# or -# Error: $0 -# - -function check_parameters(latitude, longitude) - { - # Ensure the parameters are numbers - if ((latitude == (latitude+0)) && (longitude == (longitude+0))) - { - if (latitude >= -90 && latitude <= 90 && - longitude >= -180 && longitude <= 180) - { - return latitude " " longitude - } - else - { - return "Error: " latitude " " longitude - } - } - else - { - return "Error: " latitude " " longitude - } - } - -# This awk file is accessed by the decode_geo_parameters() function -# in search.c. The call is of the format: -# echo "string_to_be_searched" | awk -f geocode-parameters.awk -# -# Search the input string for known formats. -{ -if (index($0, "http://www.geonames.org/maps/google_")) - { - # This is a drag-and-drop or copy-paste from a geonames.org search - # in the format e.g. - # http://www.geonames.org/maps/google_51.513_-0.092.html - - gsub(/http:\/\/www.geonames.org\/maps\/google_/, "") - gsub(/.html/, "") - gsub(/_/, " ") - print check_parameters($1, $2) - } - -else if (index($0, "https://www.openstreetmap.org/search?query=")) - { - # This is a copy-paste from an openstreetmap.org search - # in the format e.g. - # https://www.openstreetmap.org/search?query=51.4878%2C-0.1353#map=11/51.4880/-0.1356 - - gsub(/https:\/\/www.openstreetmap.org\/search\?query=/, "") - gsub(/#map=.*/, "") - gsub(/%2C/, " ") - print check_parameters($1, $2) - } - -else if (index($0, "https://www.openstreetmap.org/#map=")) - { - # This is a copy-paste from an openstreetmap.org search - # in the format e.g. - # https://www.openstreetmap.org/#map=5/18.271/16.084 - - gsub(/https:\/\/www.openstreetmap.org\/#map=[^\/]*/,"") - gsub(/\//," ") - print check_parameters($1, $2) - } - -else if (index($0, "https://www.google.com/maps/")) - { - # This is a copy-paste from a google.com maps search - # in the format e.g. - # https://www.google.com/maps/place/London,+UK/@51.5283064,-0.3824815,10z/data=.... - - gsub(/https:\/\/www.google.com\/maps.*@/,"") - sub(/,/," ") - gsub(/,.*/,"") - print check_parameters($1, $2) - } - -else if (index($0,".html")) - { - # This is an unknown html address - - print "Error: " $0 - } - -else if (index($0,"http")) - { - # This is an unknown html address - - print "Error: " $0 - } - -else if (index($0, ",")) - { - # This is assumed to be a simple lat/long of the format: - # 89.123,179.123 - - split($0, latlong, ",") - print check_parameters(latlong[1], latlong[2]) - } - -else - { - # This is assumed to be a simple lat/long of the format: - # 89.123 179.123 - - split($0, latlong, " ") - print check_parameters(latlong[1], latlong[2]) - } -} + # Store this file in: + # ~/.config/geeqie/geocode-parameters.awk + # + # This file is used to decode the results of internet or other searches + # to extract a geo-position from a text string. + # To include other searches, follow the examples below and + # ensure the returned value is either in the format: + # 89.123 179.123 + # or + # Error: $0 + # + + function check_parameters(latitude, longitude) + { + # Ensure the parameters are numbers + if ((latitude == (latitude+0)) && (longitude == (longitude+0))) + { + if (latitude >= -90 && latitude <= 90 && + longitude >= -180 && longitude <= 180) + { + return latitude " " longitude + } + else + { + return "Error: " latitude " " longitude + } + } + else + { + return "Error: " latitude " " longitude + } + } + + # This awk file is accessed by an internal function. + # The call is of the format: + # echo "string_to_be_searched" | awk -f geocode-parameters.awk + # + # Search the input string for known formats. + { + if (index($0, "http://www.geonames.org/maps/google_")) + { + # This is a drag-and-drop or copy-paste from a geonames.org search + # in the format e.g. + # http://www.geonames.org/maps/google_51.513_-0.092.html + + gsub(/http:\/\/www.geonames.org\/maps\/google_/, "") + gsub(/.html/, "") + gsub(/_/, " ") + print check_parameters($1, $2) + } + + else if (index($0, "https://www.openstreetmap.org/search?query=")) + { + # This is a copy-paste from an openstreetmap.org search + # in the format e.g. + # https://www.openstreetmap.org/search?query=51.4878%2C-0.1353#map=11/51.4880/-0.1356 + + gsub(/https:\/\/www.openstreetmap.org\/search\?query=/, "") + gsub(/#map=.*/, "") + gsub(/%2C/, " ") + print check_parameters($1, $2) + } + + else if (index($0, "https://www.openstreetmap.org/#map=")) + { + # This is a copy-paste from an openstreetmap.org search + # in the format e.g. + # https://www.openstreetmap.org/#map=5/18.271/16.084 + + gsub(/https:\/\/www.openstreetmap.org\/#map=[^\/]*/,"") + gsub(/\//," ") + print check_parameters($1, $2) + } + + else if (index($0, "https://www.google.com/maps/")) + { + # This is a copy-paste from a google.com maps search + # in the format e.g. + # https://www.google.com/maps/place/London,+UK/@51.5283064,-0.3824815,10z/data=.... + + gsub(/https:\/\/www.google.com\/maps.*@/,"") + sub(/,/," ") + gsub(/,.*/,"") + print check_parameters($1, $2) + } + + else if (index($0,".html")) + { + # This is an unknown html address + + print "Error: " $0 + } + + else if (index($0,"http")) + { + # This is an unknown html address + + print "Error: " $0 + } + + else if (index($0, ",")) + { + # This is assumed to be a simple lat/long of the format: + # 89.123,179.123 + + split($0, latlong, ",") + print check_parameters(latlong[1], latlong[2]) + } + + else + { + # This is assumed to be a simple lat/long of the format: + # 89.123 179.123 + + split($0, latlong, " ") + print check_parameters(latlong[1], latlong[2]) + } + } diff --git a/doc/docbook/GuideSidebarsInfo.xml b/doc/docbook/GuideSidebarsInfo.xml index 869d986d..9be78aed 100644 --- a/doc/docbook/GuideSidebarsInfo.xml +++ b/doc/docbook/GuideSidebarsInfo.xml @@ -306,7 +306,41 @@ openstreetmap.org . To use this facility, Geeqie must have been compiled with the --enable-map option. - Maps are useful when working with geocoded images. All geocoded images in the currently displayed folder will show as small icons on the map. Clicking the icon will expand the icon to show an image thumbnail, plus other pre-defined image data. - Right-click on the map will show other map options. + + + + Image location display + + All geocoded images in the currently displayed folder will show as small icons on the map. Clicking the icon will expand the icon to show an image thumbnail, plus other pre-defined image data. + + Right-click on the map will show other map options. + Middle-click controls the map-centering function. + + + + Geo-coded search + + Left-click stores the latitude/longitude under the cursor into the clipboard. This may be used to define the origin of a + geocode search + . + + + + + Geo-coding Images + + If an image is not geocoded, the filename or icon on the file pane can be dragged-and-dropped onto the map. The image latitude and longitude xmp meta-data will be updated to the drop position on the map. + + + + + Map Centering + + If an internet URL containg a valid latitude and longitude is dropped on the map, the map will be re-centered on that location. The zoom level will not change. Refer to + Decoding Latitude and Longitude + . + + +
diff --git a/src/bar_gps.c b/src/bar_gps.c index d21b824c..40bb3684 100644 --- a/src/bar_gps.c +++ b/src/bar_gps.c @@ -30,9 +30,12 @@ #include "layout.h" #include "metadata.h" #include "menu.h" +#include "misc.h" #include "rcfile.h" #include "thumb.h" #include "ui_menu.h" +#include "uri_utils.h" +#include "ui_utildlg.h" #include #include @@ -72,8 +75,189 @@ struct _PaneGPSData gint selection_count; gboolean centre_map_checked; gboolean enable_markers_checked; + gdouble dest_latitude; + gdouble dest_longitude; + GList *geocode_list; }; +/* + *------------------------------------------------------------------- + * drag-and-drop + *------------------------------------------------------------------- + */ +enum { + TARGET_APP_COLLECTION_MEMBER, + TARGET_APP_EXIF_ENTRY, + TARGET_APP_KEYWORD_PATH, + TARGET_URI_LIST, + TARGET_TEXT_PLAIN +}; + +static GtkTargetEntry bar_pane_gps_drop_types[] = { + { "text/uri-list", 0, TARGET_URI_LIST }, + { "text/plain", 0, TARGET_TEXT_PLAIN } +}; +static gint n_gps_entry_drop_types = 2; + +static void bar_pane_gps_close_cancel_cb(GenericDialog *gd, gpointer data) +{ + PaneGPSData *pgd = data; + + g_list_free(pgd->geocode_list); +} + +static void bar_pane_gps_close_save_cb(GenericDialog *gd, gpointer data) +{ + PaneGPSData *pgd = data; + FileData *fd; + GList *work; + + work = g_list_first(pgd->geocode_list); + while (work) + { + fd = work->data; + if (fd->name && !fd->parent) + { + work = work->next; + metadata_write_GPS_coord(fd, "Xmp.exif.GPSLatitude", pgd->dest_latitude); + metadata_write_GPS_coord(fd, "Xmp.exif.GPSLongitude", pgd->dest_longitude); + } + } + g_list_free(work); + g_list_free(pgd->geocode_list); +} + + static void bar_pane_gps_dnd_receive(GtkWidget *pane, GdkDragContext *context, + gint x, gint y, + GtkSelectionData *selection_data, guint info, + guint time, gpointer data) +{ + PaneGPSData *pgd; + GenericDialog *gd; + FileData *fd, *fd_found; + GList *work, *list; + gint count, geocoded_count; + gdouble latitude, longitude; + GString *message; + gchar *location; + gchar **latlong; + + pgd = g_object_get_data(G_OBJECT(pane), "pane_data"); + if (!pgd) return; + + if (info == TARGET_URI_LIST) + { + pgd->dest_longitude = champlain_view_x_to_longitude(CHAMPLAIN_VIEW(pgd->gps_view), x); + pgd->dest_latitude = champlain_view_y_to_latitude(CHAMPLAIN_VIEW(pgd->gps_view), y); + + count = 0; + geocoded_count = 0; + pgd->geocode_list = NULL; + + list = uri_filelist_from_gtk_selection_data(selection_data); + + if (list) + { + work = list; + while (work) + { + fd = work->data; + work = work->next; + if (fd->name && !fd->parent) + { + count++; + pgd->geocode_list = g_list_append(pgd->geocode_list, fd); + latitude = metadata_read_GPS_coord(fd, "Xmp.exif.GPSLatitude", 1000); + longitude = metadata_read_GPS_coord(fd, "Xmp.exif.GPSLongitude", 1000); + if (latitude != 1000 && longitude != 1000) + { + geocoded_count++; + } + } + } + g_list_free(work); + + if(count) + { + message = g_string_new(""); + if (count == 1) + { + fd_found = g_list_first(pgd->geocode_list)->data; + g_string_append_printf(message, + _("\nDo you want to geocode image %s?"), fd_found->name); + } + else + { + g_string_append_printf(message, + _("\nDo you want to geocode %i images?"), count); + } + if (geocoded_count == 1 && count == 1) + { + g_string_append_printf(message, + _("\nThis image is already geocoded!")); + } + else if (geocoded_count == 1 && count > 1) + { + g_string_append_printf(message, + _("\nOne image is already geocoded!")); + } + else if (geocoded_count > 1 && count > 1) + { + g_string_append_printf(message, + _("\n%i Images are already geocoded!"), geocoded_count); + } + + location = g_strdup_printf("%lf %lf", pgd->dest_latitude, + pgd->dest_longitude); + g_string_append_printf(message, _("\n\nPosition: %s \n"), location); + + gd = generic_dialog_new(_("Geocode images"), + "geocode_images", NULL, TRUE, + bar_pane_gps_close_cancel_cb, pgd); + generic_dialog_add_message(gd, GTK_STOCK_DIALOG_QUESTION, + _("Write lat/long to meta-data?"), + message->str); + + generic_dialog_add_button(gd, GTK_STOCK_SAVE, NULL, + bar_pane_gps_close_save_cb, TRUE); + + gtk_widget_show(gd->dialog); + g_free(location); + g_string_free(message, TRUE); + } + } + } + + if (info == TARGET_TEXT_PLAIN) + { + location = decode_geo_parameters(gtk_selection_data_get_data(selection_data)); + if (!(g_strstr_len(location,-1,"Error"))) + { + latlong = g_strsplit(location, " ", 2); + champlain_view_center_on(CHAMPLAIN_VIEW(pgd->gps_view), + g_ascii_strtod(latlong[0],NULL), + g_ascii_strtod(latlong[1],NULL)); + g_strfreev(latlong); + } + g_free(location); + } + + return; +} + +static void bar_pane_gps_dnd_init(gpointer data) +{ + PaneGPSData *pgd = data; + + gtk_drag_dest_set(pgd->widget, + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP, + bar_pane_gps_drop_types, n_gps_entry_drop_types, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + g_signal_connect(G_OBJECT(pgd->widget), "drag_data_received", + G_CALLBACK(bar_pane_gps_dnd_receive), NULL); + +} + static gboolean bar_gps_draw_direction (ClutterCanvas *canvas, cairo_t *cr, gpointer data) { @@ -649,7 +833,7 @@ static GtkWidget *bar_pane_gps_menu(PaneGPSData *pgd) */ void bar_pane_gps_map_centreing(PaneGPSData *pgd) { - GtkWidget *dialog; + GenericDialog *gd; GString *message = g_string_new(""); if (pgd->centre_map_checked) @@ -663,16 +847,14 @@ void bar_pane_gps_map_centreing(PaneGPSData *pgd) pgd->centre_map_checked = TRUE; } - dialog = gtk_message_dialog_new(NULL, - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_INFO, - GTK_BUTTONS_CLOSE, - "%s", message->str); - gtk_window_set_title(GTK_WINDOW(dialog), _("Map Centreing")); - gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); - gtk_dialog_run(GTK_DIALOG(dialog)); + gd = generic_dialog_new(_("Map centering"), + "map_centering", NULL, TRUE, NULL, pgd); + generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO, + "Map Centering", message->str); + generic_dialog_add_button(gd, GTK_STOCK_OK, NULL, NULL, TRUE); + + gtk_widget_show(gd->dialog); - gtk_widget_destroy(dialog); g_string_free(message, TRUE); } @@ -819,6 +1001,8 @@ GtkWidget *bar_pane_gps_new(const gchar *id, const gchar *title, const gchar *ma g_signal_connect(pgd->gps_view, "notify::zoom-level", G_CALLBACK(bar_pane_gps_view_state_changed_cb), pgd); g_signal_connect(G_OBJECT(slider), "value-changed", G_CALLBACK(bar_pane_gps_slider_changed_cb), pgd); + bar_pane_gps_dnd_init(pgd); + file_data_register_notify_func(bar_pane_gps_notify_cb, pgd, NOTIFY_PRIORITY_LOW); pgd->create_markers_id = 0; diff --git a/src/metadata.c b/src/metadata.c index 8c8e9abc..26d83500 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -833,6 +833,46 @@ gboolean metadata_append_string(FileData *fd, const gchar *key, const char *valu } } +gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value) +{ + gint deg; + gdouble min; + gdouble param; + char *coordinate; + char *ref; + gboolean ok = TRUE; + + param = value; + if (param < 0) + param = -param; + deg = param; + min = (param * 60) - (deg * 60); + if (g_strcmp0(key, "Xmp.exif.GPSLongitude") == 0) + if (value < 0) + ref = "W"; + else + ref = "E"; + else if (g_strcmp0(key, "Xmp.exif.GPSLatitude") == 0) + if (value < 0) + ref = "S"; + else + ref = "N"; + else + { + log_printf("unknown GPS parameter key '%s'\n", key); + ok = FALSE; + } + + if (ok) + { + coordinate = g_strdup_printf("%i,%lf,%s", deg, min, ref); + metadata_write_string(fd, key, coordinate ); + g_free(coordinate); + } + + return ok; +} + gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values) { GList *list = metadata_read_list(fd, key, METADATA_PLAIN); diff --git a/src/metadata.h b/src/metadata.h index 5b2c06d6..5c9b851e 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -46,6 +46,7 @@ gchar *metadata_read_string(FileData *fd, const gchar *key, MetadataFormat forma guint64 metadata_read_int(FileData *fd, const gchar *key, guint64 fallback); gdouble metadata_read_GPS_coord(FileData *fd, const gchar *key, gdouble fallback); gdouble metadata_read_GPS_direction(FileData *fd, const gchar *key, gdouble fallback); +gboolean metadata_write_GPS_coord(FileData *fd, const gchar *key, gdouble value); gboolean metadata_append_string(FileData *fd, const gchar *key, const char *value); gboolean metadata_append_list(FileData *fd, const gchar *key, const GList *values); diff --git a/src/misc.c b/src/misc.c index ca174831..0f74402b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -20,6 +20,7 @@ #include "main.h" #include "misc.h" +#include "ui_fileops.h" gdouble get_zoom_increment(void) { @@ -123,6 +124,47 @@ gchar *expand_tilde(const gchar *filename) #endif } +/* Search for latitude/longitude parameters in a string + */ + +#define GEOCODE_NAME "geocode-parameters.awk" +#define BUFSIZE 128 + +gchar *decode_geo_parameters(const gchar *input_text) +{ + gchar *message; + gchar *path = g_build_filename(get_rc_dir(), GEOCODE_NAME, NULL); + gchar *cmd = g_strconcat("echo \'", input_text, "\' | awk -f ", path, NULL); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) + { + gchar buf[BUFSIZE]; + FILE *fp; + + if ((fp = popen(cmd, "r")) == NULL) + { + message = g_strconcat("Error: opening pipe\n", input_text, NULL); + } + else + { + fgets(buf, BUFSIZE, fp); + message = g_strconcat(buf, NULL); + + if(pclose(fp)) + { + message = g_strconcat("Error: Command not found or exited with error status\n", input_text, NULL); + } + } + } + else + { + message = g_strconcat(input_text, NULL); + } + + g_free(path); + g_free(cmd); + return message; +} /* Run a command like system() but may output debug messages. */ int runcmd(gchar *cmd) diff --git a/src/misc.h b/src/misc.h index dac9fc20..1be461e1 100644 --- a/src/misc.h +++ b/src/misc.h @@ -26,6 +26,7 @@ gchar *utf8_validate_or_convert(const gchar *text); gint utf8_compare(const gchar *s1, const gchar *s2, gboolean case_sensitive); gchar *expand_tilde(const gchar *filename); int runcmd(gchar *cmd); +gchar *decode_geo_parameters(const gchar *input_text); #endif /* MISC_H */ /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ diff --git a/src/search.c b/src/search.c index 4bc53348..f48fef7c 100644 --- a/src/search.c +++ b/src/search.c @@ -61,8 +61,6 @@ #define SEARCH_BUFFER_MATCH_MISS 1 #define SEARCH_BUFFER_FLUSH_SIZE 99 -#define GEOCODE_NAME "geocode-parameters.awk" - typedef enum { SEARCH_MATCH_NONE, SEARCH_MATCH_EQUAL, @@ -1429,44 +1427,6 @@ static void search_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointe } } -#define BUFSIZE 128 - -static gchar *decode_geo_parameters(const gchar *input_text) -{ - gchar *message; - gchar *path = g_build_filename(get_rc_dir(), GEOCODE_NAME, NULL); - gchar *cmd = g_strconcat("echo \'", input_text, "\' | awk -f ", path, NULL); - - if (g_file_test(path, G_FILE_TEST_EXISTS)) - { - gchar buf[BUFSIZE]; - FILE *fp; - - if ((fp = popen(cmd, "r")) == NULL) - { - message = g_strconcat("Error: opening pipe\n", input_text, NULL); - } - else - { - fgets(buf, BUFSIZE, fp); - message = g_strconcat(buf, NULL); - - if(pclose(fp)) - { - message = g_strconcat("Error: Command not found or exited with error status\n", input_text, NULL); - } - } - } - else - { - message = g_strconcat(input_text, NULL); - } - - g_free(path); - g_free(cmd); - return message; -} - static void search_gps_dnd_received_cb(GtkWidget *pane, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint info, -- 2.20.1