]> git.street.me.uk Git - andy/viking.git/commitdiff
Merge branch 'Geotagging'
authorRob Norris <rw_norris@hotmail.com>
Mon, 19 Dec 2011 22:15:02 +0000 (22:15 +0000)
committerRob Norris <rw_norris@hotmail.com>
Mon, 19 Dec 2011 22:15:02 +0000 (22:15 +0000)
Conflicts:
src/file.c

Resolve KML units setting with babel function parameter ordering from commits:
60dbd0ad7333bcc0502106e003ce321dfbf75ea2 + 9181183a2880930d694fddef2582c75ffffa3d53

34 files changed:
configure.ac
doc/GEOCODED-PHOTOS [deleted file]
help/C/viking.xml
po/POTFILES.in
src/Makefile.am
src/acquire.c
src/acquire.h
src/babel.c
src/babel.h
src/datasource_bfilter.c
src/datasource_file.c
src/datasource_gc.c
src/datasource_geotag.c [new file with mode: 0644]
src/datasource_google.c
src/datasource_gps.c
src/datasource_osm.c
src/datasources.h
src/file.c
src/geotag_exif.c [new file with mode: 0644]
src/geotag_exif.h [new file with mode: 0644]
src/libjpeg/COPYING.orig [new file with mode: 0644]
src/libjpeg/README [new file with mode: 0644]
src/libjpeg/README.orig [new file with mode: 0644]
src/libjpeg/jpeg-data.c [new file with mode: 0644]
src/libjpeg/jpeg-data.h [new file with mode: 0644]
src/libjpeg/jpeg-marker.c [new file with mode: 0644]
src/libjpeg/jpeg-marker.h [new file with mode: 0644]
src/menu.xml.h
src/vikgpslayer.c
src/viktrwlayer.c
src/viktrwlayer.h
src/viktrwlayer_geotag.c [new file with mode: 0644]
src/viktrwlayer_geotag.h [new file with mode: 0644]
src/vikwindow.c

index e2d2fd511f1320ea983259f82662d39af238afb3..b5e7b35774fce7d348f47913b6755622ee6c0f6f 100644 (file)
@@ -222,6 +222,20 @@ case $ac_cv_enable_geocaches in
 esac
 AM_CONDITIONAL([GEOCACHES], [test x$ac_cv_enable_geocaches = xyes])
 
+AC_ARG_ENABLE(geotag, AC_HELP_STRING([--enable-geotag],
+             [enable Geotag Support (default is enable).]),
+              [ac_cv_enable_geotag=$enableval],
+              [ac_cv_enable_geotag=yes])
+AC_CACHE_CHECK([whether to enable Geotag Support],
+               [ac_cv_enable_geotag], [ac_cv_enable_geotag=yes])
+case $ac_cv_enable_geotag in
+  yes)
+    AC_CHECK_LIB(exif,exif_loader_new,,AC_MSG_ERROR([libexif is needed for Geotag features[,] but is not found. The feature can be disabled with --disable-geotag]))
+    AC_DEFINE(VIK_CONFIG_GEOTAG, [], [GEOTAG STUFF])
+    ;;
+esac
+AM_CONDITIONAL([GEOTAG], [test x$ac_cv_enable_geotag = xyes])
+
 AC_ARG_ENABLE(spotmaps, AC_HELP_STRING([--enable-spotmaps],
              [enable SPOTMaps map (default is enable).]),
               [ac_cv_enable_spotmaps=$enableval],
@@ -359,6 +373,7 @@ echo "BlueMarble                       : $ac_cv_enable_bluemarble"
 echo "SPOTMaps                         : $ac_cv_enable_spotmaps"
 echo "Geonames                         : $ac_cv_enable_geonames"
 echo "Geocaches Acquire                : $ac_cv_enable_geocaches"
+echo "Geotag Support                   : $ac_cv_enable_geotag"
 echo "USGS 24k DEM                     : $ac_cv_enable_dem24k"
 echo "Realtime GPS Tracking            : $ac_cv_enable_realtimegpstracking"
 echo "Size of map cache (in memory)    : ${VIK_CONFIG_MAPCACHE_SIZE}"
diff --git a/doc/GEOCODED-PHOTOS b/doc/GEOCODED-PHOTOS
deleted file mode 100644 (file)
index 2ed8d72..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-HOWTO GEOCODE YOUR PHOTOS AND SEE THEM IN VIKING
-
-1) Synchronize your camera's clock with your GPS clock. If your camera's clock doesn't have a seconds value you can change, you might try setting the minute value just when the minute changes.
-
-2) Activate the track log on your GPS.
-
-3) Get outside and take some pictures with your camera while making sure your GPS has reception. If you forgot to turn your GPS on or forgot to bring it when you took a picture but you know where you took the picture, you can make a waypoint with a name of the format "YYMMDDhhmm", representing the time the picture was taken, to record where you were at that time.
-
-4) Get home and download your pictures and GPS data. You can download the GPS data several ways. The first is using gpspoint to download from a Garmin GPS:
-
-gpspoint -p /dev/ttyS0 -dw -dt -of 2004-04-02-trip1
-
-Where /dev/ttyS0 is the serial port the GPS is connected to.
-If you don't have a Garmin GPS, or if you don't feel like install gpspoint, you can use my version of gpsbabel with support for gpspoint files. You can use gpsbabel to either translate the GPS data from a format you already have the data in or download it directly from your GPS reciever. A typical command line would something like this:
-
-gpsbabel -i garmin -f /dev/ttyS0 -o gpspoint -F 2004-04-02-trip1
-
-5) Launch GPSPhoto.
-
-6) You must now bring your photos into GPSPhoto. Either drag them (or the directory containing them) from your favorite file manager to the list on the left of the GPSPhoto window, or use the buttons below the list to find them.
-
-7) Do the same for your GPS data file(s), dragging them to the list on the right of the GPSPhoto Window.
-
-8) Click the "Execute" button, and choose a location to save the gpspoint file containing the waypoints for your photos. Then click OK to write the waypoints to this file.
-
-9) Open Viking and open both your original GPS data file and your image waypoint file just created.
index f1659788ee92317c5a7b53a825f6b52934d48799..0f62848771dc63afdf0f2b1b357650be7af35951 100644 (file)
@@ -166,6 +166,11 @@ Show the live GPS position on the map (for use on a mobile device - e.g. a lapto
 Import track+waypoint files of various types via GPSBabel
 </para>
 </listitem>
+<listitem>
+<para>
+View, create and update Geotagged Images (using EXIF data).
+</para>
+</listitem>
 </itemizedlist>
 <note>
 <para>
@@ -350,6 +355,13 @@ This gets 'interesting' points from Wikipedia for the specified view: either wit
 </para>
 </section>
 
+<section><title>Geotag Images</title>
+<para>
+This starts the Geotagging Images dialog against all tracks in the layer.
+See <link linkend="geotagging">Geotagging</link> for further detail.
+</para>
+</section>
+
 <section id="osm_upload"><title>Upload To OSM</title>
 <para>
 This opens a dialog to upload all tracks and waypoints to <ulink url="http://openstreetmap.org/">OpenStreetMap</ulink> traces.
@@ -397,6 +409,13 @@ The result is generated in a new Track/Waypoint layer.
 </para>
 </section>
 
+<section><title>Geotag Images</title>
+<para>
+This starts the Geotagging Images dialog using the specific track.
+See <link linkend="geotagging">Geotagging</link> for further detail.
+</para>
+</section>
+
 </section><!-- End TRW Layer Options -->
 
 <section><title>Track Properties</title>
@@ -550,6 +569,10 @@ The record of the Google route is stored in the track's comment, so if the comme
 <para>
 This shows a dialog with detailed information for the waypoint. Many properties of the waypoint can changed here, such as the comment, the symbol used in drawing or the image (normally a photograph taken at this position) assiocated with it. When a waypoint has an image, a thumbnail of it is drawn in the viewport for the waypoint (in preference to the symbol).
 </para>
+<para>
+If the waypoint has an associated image, then the Geotag information may be updated, either with updating the file's modification timestamp or not.
+This can be useful when the waypoint has been moved.
+</para>
 
 </section><!-- WP Prop END -->
 
@@ -575,6 +598,32 @@ Same as the layer <link linkend="new_wp">New Waypoint</link>.
 
 </section><!-- WP END -->
 
+<section id="geotagging"><title>Version1.3+: Geotag Images</title>
+<para>
+This dialog allows geotagging images (normally taken with a digital camera) via interpolation against a specific track or all tracks in the TrackWaypoint layer - depending on how it is invoked.
+</para>
+<para>
+Images need to have an EXIF DATE_TIME_ORIGINAL (nearly always set by a camera). This timestamp is then used to find the location when the image(s) was taken by searching through the track(s) to find the nearest time - interpolating between points if necessary to set the location.
+</para>
+<note>
+<para>
+Generally it is good policy to synchronize your camera's clock with your GPS clock before taking photographs. However the times can be adjusted afterwards (if necessary) to take into consideration clock differences.
+</para>
+</note>
+<para>
+Various options allow control of how the geotagging process is performed:
+</para>
+<itemizedlist>
+<listitem><para>Images - Add the images used for geotagging</para></listitem>
+<listitem><para>Create Waypoints</para></listitem>
+<listitem><para>Write EXIF</para></listitem>
+<listitem><para>Overwrite Existing GPS Information</para></listitem>
+<listitem><para>Interpolate Between Track Segments</para></listitem>
+<listitem><para>Image Time Offset - The number of seconds to ADD to the photos time to make it match the GPS data. Calculate this with (GPS - Photo). Can be negative or positive. Useful to adjust times when a camera's timestamp was incorrect.</para></listitem>
+<listitem><para>Image Timezone - The timezone that the used when the images were created. For example, if a camera is set to AWST or +8:00 hours. Enter +8:00 here so that the correct adjustment to the images' time can be made. GPS data is always in UTC.</para></listitem>
+</itemizedlist>
+</section>
+
 </section><!-- TRW Layer END -->
 
 <section><title>GPS Layer</title>
@@ -661,6 +710,7 @@ Some maps are continually improving over time (e.g. OpenStreetMap) and so in ord
 Inbuilt maps include various <ulink url="http://en.wikipedia.org/wiki/TerraServer-USA">Terraserver</ulink> (Primarily USA coverage) and <ulink url="http://openstreetmap.org/">OpenStreetMap (OSM)</ulink> ones and more:
 </para>
 <itemizedlist>
+<listitem><para>Bing Bird's Eye (Aerial) Maps (&appname; Version1.2+)</para></listitem>
 <listitem><para>Terraserver Topos</para></listitem>
 <listitem><para>Terraserver Aerials</para></listitem>
 <listitem><para>Terraserver Urban Areas</para></listitem>
@@ -791,6 +841,13 @@ Left or Middle-click: make the clicked point on the map the center
 Middle-click and drag: pan
 </para>
 
+<section><title>Pan</title>
+<para>
+Pan moves the viewpoint. A single click centers the viewport at that point, whereas click and drag dynamically moves the view around.
+This is the default tool.
+</para>
+</section>
+
 <section><title>Zoom</title>
 <para>
 Zooms in and out on the clicked part of the map.
@@ -804,6 +861,15 @@ A second click will 'freeze' the ruler at that point.
 </para>
 </section>
 
+<section><title>Version1.1+: Select</title>
+<para>
+The select tool allows one to choose any waypoint or track by clicking on it (or reasonably near) in the viewpoint.
+</para>
+<para>
+ATM to select a track one must actually click on a trackpoint, which is also selected.
+</para>
+</section>
+
 <section><title>TrackWaypoint Layer Tools</title>
 <para>
 You must have a TrackWaypoint Layer selected to use these tools.
@@ -1258,57 +1324,6 @@ See <ulink url="http://wiki.openstreetmap.org/wiki/API_v0.6#GPS_Traces"/> for fu
 </section>
 </section>
 
-<section><title>Geocoded Photo</title>
-<para>
-HOWTO GEOCODE YOUR PHOTOS AND SEE THEM IN VIKING 
-</para>
-<para>1) Synchronize your camera's clock with your GPS clock. If your
-camera's clock doesn't have a seconds value you can change, you might
-try setting the minute value just when the minute changes.
-</para>
-<para>
-2) Activate the track log on your GPS.
-</para>
-<para>3) Get outside and take some pictures with your camera while
-making sure your GPS has reception. If you forgot to turn your GPS on
-or forgot to bring it when you took a picture but you know where you
-took the picture, you can make a waypoint with a name of the format
-YYMMDDhhmm, representing the time the picture was taken, to record
-where you were at that time.
-</para>
-<para>4) Get home and download your pictures and GPS data. You can
-download the GPS data several ways. The first is using gpspoint to
-download from a Garmin GPS: <programlisting>gpspoint -p /dev/ttyS0 -dw -dt -of 2004-04-02-trip1</programlisting>
-</para>
-<para>
-Where /dev/ttyS0 is the serial port the GPS is connected to. If you
-don't have a Garmin GPS, or if you don't feel like install gpspoint,
-you can use my version of gpsbabel with support for gpspoint files. You
-can use gpsbabel to either translate the GPS data from a format you
-already have the data in or download it directly from your GPS
-reciever. A typical command line would something like this:<programlisting>gpsbabel -i garmin -f /dev/ttyS0 -o gpspoint -F 2004-04-02-trip1</programlisting>
-</para>
-<para>
-5) Launch GPSPhoto.
-</para>
-<para>
-6) You must now bring your photos into GPSPhoto. Either drag them (or
-the directory containing them) from your favorite file manager to the
-list on the left of the GPSPhoto window, or use the buttons below the
-list to find them.
-</para>
-<para>
-7) Do the same for your GPS data file(s), dragging them to the list on the right of the GPSPhoto Window.
-</para>
-<para>8) Click the Execute button, and choose a location to save the
-gpspoint file containing the waypoints for your photos. Then click OK
-to write the waypoints to this file.
-</para>
-<para>
-9) Open Viking and open both your original GPS data file and your image waypoint file just created.
-</para>
-</section>
-
 <section id="extend_viking">
     <title>Extending Viking</title>
 
index de12d13ff31877ac24cc74e95d780b9c07f79fe3..5139c97fe276a5063e65bcbfc77e4ba5608343e1 100644 (file)
@@ -11,6 +11,7 @@ src/google.c
 src/googlesearch.c
 src/datasource_file.c
 src/datasource_gc.c
+src/datasource_geotag.c
 src/datasource_google.c
 src/datasource_gps.c
 src/datasource_osm.c
@@ -42,5 +43,6 @@ src/viktreeview.c
 src/viktrwlayer.c
 src/viktrwlayer_propwin.c
 src/viktrwlayer_tpwin.c
+src/viktrwlayer_geotag.c
 src/vikwindow.c
 src/viking.desktop.in
index 765e1f47079501a082b14361dc09562e361bff22..426c3e94b5557a9a18749a7d36e20cf007223f7a 100644 (file)
@@ -169,6 +169,15 @@ libviking_a_SOURCES += \
        spotmaps.c spotmaps.h
 #endif
 
+if GEOTAG
+libviking_a_SOURCES += \
+       datasource_geotag.c \
+       geotag_exif.c geotag_exif.h \
+       viktrwlayer_geotag.c viktrwlayer_geotag.h \
+       libjpeg/jpeg-data.c libjpeg/jpeg-data.h \
+       libjpeg/jpeg-marker.c libjpeg/jpeg-marker.h
+endif
+
 viking_SOURCES = main.c
 
 LDADD           = libviking.a $(PACKAGE_LIBS) @EXPAT_LIBS@ @LIBCURL@ icons/libicons.a
index 1a65e91f9f42be58406a0248f910325fef91d3eb..69ec1efaea584912f83d5b2658e29a11ee39e8a8 100644 (file)
@@ -119,9 +119,10 @@ static void get_from_anything ( w_and_interface_t *wi )
   }
   gdk_threads_leave();
 
+  // TODO consider removing 'type' and make everything run via the specficied process function
   switch ( source_interface->type ) {
   case VIK_DATASOURCE_GPSBABEL_DIRECT:
-    result = a_babel_convert_from (vtl, cmd, (BabelStatusFunc) progress_func, extra, w);
+    result = a_babel_convert_from (vtl, cmd, extra, (BabelStatusFunc) progress_func, w);
     break;
   case VIK_DATASOURCE_URL:
     result = a_babel_convert_from_url (vtl, cmd, extra, (BabelStatusFunc) progress_func, w);
@@ -129,6 +130,10 @@ static void get_from_anything ( w_and_interface_t *wi )
   case VIK_DATASOURCE_SHELL_CMD:
     result = a_babel_convert_from_shellcommand ( vtl, cmd, extra, (BabelStatusFunc) progress_func, w);
     break;
+  case VIK_DATASOURCE_INTERNAL:
+    if ( source_interface->process_func )
+      result = source_interface->process_func ( vtl, cmd, extra, (BabelStatusFunc) progress_func, w );
+    break;
   default:
     g_critical("Houston, we've had a problem.");
   }
@@ -150,8 +155,10 @@ static void get_from_anything ( w_and_interface_t *wi )
       if ( creating_new_layer ) {
        /* Only create the layer if it actually contains anything useful */
        if ( g_hash_table_size (vik_trw_layer_get_tracks(vtl)) ||
-            g_hash_table_size (vik_trw_layer_get_waypoints(vtl)) )
+            g_hash_table_size (vik_trw_layer_get_waypoints(vtl)) ) {
+         vik_layer_post_read ( VIK_LAYER(vtl), w->vvp, TRUE );
          vik_aggregate_layer_add_layer( vik_layers_panel_get_top_layer(w->vlp), VIK_LAYER(vtl));
+       }
        else
          gtk_label_set_text ( GTK_LABEL(w->status), _("No data.") );
       }
@@ -224,7 +231,8 @@ static void acquire ( VikWindow *vw, VikLayersPanel *vlp, VikViewport *vvp, VikD
   /* for manual dialogs */
   GtkWidget *dialog = NULL;
   GtkWidget *status;
-  gchar *cmd, *extra;
+  gchar *cmd = NULL;
+  gchar *extra = NULL;
   gchar *cmd_off = NULL;
   gchar *extra_off = NULL;
   acq_dialog_widgets_t *w;
@@ -315,8 +323,8 @@ static void acquire ( VikWindow *vw, VikLayersPanel *vlp, VikViewport *vvp, VikD
        ( pass_along_data, &cmd, &extra, name_src_track );
 
     g_free ( name_src_track );
-  } else
-    source_interface->get_cmd_string_func ( pass_along_data, &cmd, &extra );
+  } else if ( source_interface->get_cmd_string_func )
+      source_interface->get_cmd_string_func ( pass_along_data, &cmd, &extra );
 
   /* Get data for Off command */
   if ( source_interface->off_func ) {
@@ -373,7 +381,7 @@ static void acquire ( VikWindow *vw, VikLayersPanel *vlp, VikViewport *vvp, VikD
   else {
     if ( cmd_off ) {
       /* Turn off */
-      a_babel_convert_from (NULL, cmd_off, NULL, extra_off, NULL);
+      a_babel_convert_from (NULL, cmd_off, extra_off, NULL, NULL);
     }
     g_free ( w ); /* thread has finished; free w */
   }
index d6cd224635295bb9b6e3e4dd1f88ad7c583fe71d..8b8eb929a0bd01e609adcd4b916922025b13655a 100644 (file)
@@ -27,6 +27,7 @@
 #include "vikwindow.h"
 #include "viklayerspanel.h"
 #include "vikviewport.h"
+#include "babel.h"
 
 typedef struct _VikDataSourceInterface VikDataSourceInterface;
 
@@ -42,10 +43,12 @@ typedef struct {
   gpointer user_data;
 } acq_dialog_widgets_t;
 
+/* Direct, URL & Shell types process the results with GPSBabel to create tracks/waypoint */
 typedef enum {
   VIK_DATASOURCE_GPSBABEL_DIRECT,
   VIK_DATASOURCE_URL,
-  VIK_DATASOURCE_SHELL_CMD
+  VIK_DATASOURCE_SHELL_CMD,
+  VIK_DATASOURCE_INTERNAL
 } vik_datasource_type_t;
 
 typedef enum {
@@ -79,6 +82,9 @@ typedef void (*VikDataSourceGetCmdStringFunc) ( gpointer user_data, gchar **babe
 typedef void (*VikDataSourceGetCmdStringFuncWithInput) ( gpointer user_data, gchar **babelargs_or_shellcmd, gchar **inputfile_or_inputtype, const gchar *input_file_name );
 typedef void (*VikDataSourceGetCmdStringFuncWithInputInput) ( gpointer user_data, gchar **babelargs_or_shellcmd, gchar **inputfile_or_inputtype, const gchar *input_file_name, const gchar *input_track_file_name );
 
+/* The actual function to do stuff - must report success/failure */
+typedef gboolean (*VikDataSourceProcessFunc)  ( gpointer vtl, const gchar *cmd, const gchar *extra, BabelStatusFunc status_cb, acq_dialog_widgets_t *adw );
+
 /* */
 typedef void  (*VikDataSourceProgressFunc)  (gpointer c, gpointer data, acq_dialog_widgets_t *w);
 
@@ -109,6 +115,8 @@ struct _VikDataSourceInterface {
   /* or VikDataSourceGetCmdStringFuncWithInput, if inputtype is not NONE */
   VikDataSourceGetCmdStringFunc get_cmd_string_func; 
 
+  VikDataSourceProcessFunc process_func;
+
   VikDataSourceProgressFunc progress_func;
   VikDataSourceAddProgressWidgetsFunc add_progress_widgets_func;
   VikDataSourceCleanupFunc cleanup_func;
index 27bc918d3ec806ba97fa05efd757b8a4bda7f70d..3d63400d5018295ddab7114ecb5cfb9843c012f2 100644 (file)
@@ -96,7 +96,7 @@ gboolean a_babel_convert( VikTrwLayer *vt, const char *babelargs, BabelStatusFun
     a_gpx_write_file(vt, f);
     fclose(f);
     f = NULL;
-    ret = a_babel_convert_from ( vt, bargs, cb, name_src, user_data );
+    ret = a_babel_convert_from ( vt, bargs, name_src, cb, user_data );
     g_remove(name_src);
     g_free(name_src);
   }
@@ -257,7 +257,7 @@ static gboolean babel_general_convert_from( VikTrwLayer *vt, BabelStatusFunc cb,
  *
  * Returns: %TRUE on success
  */
-gboolean a_babel_convert_from( VikTrwLayer *vt, const char *babelargs, BabelStatusFunc cb, const char *from, gpointer user_data )
+gboolean a_babel_convert_from( VikTrwLayer *vt, const char *babelargs, const char *from, BabelStatusFunc cb, gpointer user_data )
 {
   int i,j;
   int fd_dst;
@@ -362,7 +362,7 @@ gboolean a_babel_convert_from_url ( VikTrwLayer *vt, const char *url, const char
 
     fetch_ret = a_http_download_get_url(url, "", name_src, &options, NULL);
     if (fetch_ret == 0)
-      ret = a_babel_convert_from( vt, babelargs, NULL, name_src, NULL);
+      ret = a_babel_convert_from( vt, babelargs, name_src, NULL, NULL);
  
     g_remove(name_src);
     g_free(babelargs);
@@ -382,7 +382,7 @@ static gboolean babel_general_convert_to( VikTrwLayer *vt, BabelStatusFunc cb, g
   return babel_general_convert (cb, args, user_data);
 }
 
-gboolean a_babel_convert_to( VikTrwLayer *vt, const char *babelargs, BabelStatusFunc cb, const char *to, gpointer user_data )
+gboolean a_babel_convert_to( VikTrwLayer *vt, const char *babelargs, const char *to, BabelStatusFunc cb, gpointer user_data )
 {
   int i,j;
   int fd_src;
index 5c168fa243cc0b2f467ad8315118847eb3c8b930..b19a31270e3d9ee44f48fbe38d6ce44981f6c2b0 100644 (file)
@@ -93,10 +93,10 @@ GList *a_babel_file_list;
 GList *a_babel_device_list;
 
 gboolean a_babel_convert( VikTrwLayer *vt, const char *babelargs, BabelStatusFunc cb, gpointer user_data );
-gboolean a_babel_convert_from( VikTrwLayer *vt, const char *babelargs, BabelStatusFunc cb, const char *file, gpointer user_data );
+gboolean a_babel_convert_from( VikTrwLayer *vt, const char *babelargs, const char *file, BabelStatusFunc cb, gpointer user_data );
 gboolean a_babel_convert_from_shellcommand ( VikTrwLayer *vt, const char *input_cmd, const char *input_file_type, BabelStatusFunc cb, gpointer user_data );
 gboolean a_babel_convert_from_url ( VikTrwLayer *vt, const char *url, const char *input_type, BabelStatusFunc cb, gpointer user_data );
-gboolean a_babel_convert_to( VikTrwLayer *vt, const char *babelargs, BabelStatusFunc cb, const char *file, gpointer user_data );
+gboolean a_babel_convert_to( VikTrwLayer *vt, const char *babelargs, const char *file, BabelStatusFunc cb, gpointer user_data );
 
 void a_babel_init ();
 void a_babel_uninit ();
index fb237496296b067ffd3055b229fb3afc6a3c581c..78dbe9c713e41c00dd7a5ef89bf3d040b94459b6 100644 (file)
@@ -66,6 +66,7 @@ VikDataSourceInterface vik_datasource_bfilter_simplify_interface = {
   FALSE, /* keep dialog open after success */
   NULL, NULL, NULL,
   (VikDataSourceGetCmdStringFunc)      datasource_bfilter_simplify_get_cmd_string,
+  (VikDataSourceProcessFunc) NULL,
   NULL, NULL, NULL,
   (VikDataSourceOffFunc) NULL,
 
@@ -101,6 +102,7 @@ VikDataSourceInterface vik_datasource_bfilter_dup_interface = {
   FALSE, /* keep dialog open after success */
   NULL, NULL, NULL,
   (VikDataSourceGetCmdStringFunc)      datasource_bfilter_dup_get_cmd_string,
+  (VikDataSourceProcessFunc) NULL,
   NULL, NULL, NULL,
   (VikDataSourceOffFunc) NULL,
 
@@ -133,6 +135,7 @@ VikDataSourceInterface vik_datasource_bfilter_polygon_interface = {
   FALSE, /* keep dialog open after success */
   NULL, NULL, NULL,
   (VikDataSourceGetCmdStringFunc)      datasource_bfilter_polygon_get_cmd_string,
+  (VikDataSourceProcessFunc) NULL,
   NULL, NULL, NULL,
   (VikDataSourceOffFunc) NULL,
 
@@ -168,6 +171,7 @@ VikDataSourceInterface vik_datasource_bfilter_exclude_polygon_interface = {
   FALSE, /* keep dialog open after success */
   NULL, NULL, NULL,
   (VikDataSourceGetCmdStringFunc)      datasource_bfilter_exclude_polygon_get_cmd_string,
+  (VikDataSourceProcessFunc) NULL,
   NULL, NULL, NULL,
   (VikDataSourceOffFunc) NULL,
 
index 43896bc0cd829a2991de937baadfc553d8c56ea8..1b4daeaeaa0e9b444c26edf8f36081ae857fd8c6 100644 (file)
@@ -67,6 +67,7 @@ VikDataSourceInterface vik_datasource_file_interface = {
   (VikDataSourceCheckExistenceFunc)    NULL,
   (VikDataSourceAddSetupWidgetsFunc)   datasource_file_add_setup_widgets,
   (VikDataSourceGetCmdStringFunc)      datasource_file_get_cmd_string,
+  (VikDataSourceProcessFunc)           NULL,
   (VikDataSourceProgressFunc)          NULL,
   (VikDataSourceAddProgressWidgetsFunc)        NULL,
   (VikDataSourceCleanupFunc)           datasource_file_cleanup,
index 1f68e82f444ec15c79cb5e211e8d677e81e35924..b3b6b2c545aca22ad3121f350f2ca7ad54e65fb6 100644 (file)
@@ -74,6 +74,7 @@ VikDataSourceInterface vik_datasource_gc_interface = {
   (VikDataSourceCheckExistenceFunc)    datasource_gc_check_existence,
   (VikDataSourceAddSetupWidgetsFunc)   datasource_gc_add_setup_widgets,
   (VikDataSourceGetCmdStringFunc)      datasource_gc_get_cmd_string,
+  (VikDataSourceProcessFunc)           NULL,
   (VikDataSourceProgressFunc)          NULL,
   (VikDataSourceAddProgressWidgetsFunc)        NULL,
   (VikDataSourceCleanupFunc)           datasource_gc_cleanup,
diff --git a/src/datasource_geotag.c b/src/datasource_geotag.c
new file mode 100644 (file)
index 0000000..f061f7b
--- /dev/null
@@ -0,0 +1,188 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
+ *
+ * Copyright (C) 2011, Guilhem Bonnefille <guilhem.bonnefille@gmail.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <string.h>
+
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+
+#include <gtk/gtk.h>
+
+#include "viking.h"
+#include "acquire.h"
+#include "geotag_exif.h"
+
+typedef struct {
+       GtkWidget *files;
+       VikViewport *vvp;
+       GSList *filelist;  // Files selected
+} datasource_geotag_user_data_t;
+
+/* The last used directory */
+static gchar *last_folder_uri = NULL;
+
+static gpointer datasource_geotag_init( );
+static void datasource_geotag_add_setup_widgets ( GtkWidget *dialog, VikViewport *vvp, gpointer user_data );
+static void datasource_geotag_get_cmd_string ( gpointer user_data, gchar **babelargs_or_shellcmd, gchar **inputfile_or_inputtype );
+static gboolean datasource_geotag_process ( VikTrwLayer *vtl, const gchar *cmd, const gchar *extra, BabelStatusFunc status_cb, acq_dialog_widgets_t *adw );
+static void datasource_geotag_cleanup ( gpointer user_data );
+
+VikDataSourceInterface vik_datasource_geotag_interface = {
+  N_("Create Waypoints from Geotagged Images"),
+  N_("Geotagged Images"),
+  VIK_DATASOURCE_INTERNAL,
+  VIK_DATASOURCE_ADDTOLAYER,
+  VIK_DATASOURCE_INPUTTYPE_NONE,
+  TRUE,
+  TRUE,
+  (VikDataSourceInitFunc)                      datasource_geotag_init,
+  (VikDataSourceCheckExistenceFunc)        NULL,
+  (VikDataSourceAddSetupWidgetsFunc)    datasource_geotag_add_setup_widgets,
+  (VikDataSourceGetCmdStringFunc)          datasource_geotag_get_cmd_string,
+  (VikDataSourceProcessFunc)               datasource_geotag_process,
+  (VikDataSourceProgressFunc)              NULL,
+  (VikDataSourceAddProgressWidgetsFunc)        NULL,
+  (VikDataSourceCleanupFunc)               datasource_geotag_cleanup,
+  (VikDataSourceOffFunc)                NULL,
+
+  NULL,
+  0,
+  NULL,
+  NULL,
+  0
+};
+
+/* See VikDataSourceInterface */
+static gpointer datasource_geotag_init ( )
+{
+       datasource_geotag_user_data_t *user_data = g_malloc(sizeof(datasource_geotag_user_data_t));
+       user_data->filelist = NULL;
+       return user_data;
+}
+
+/* See VikDataSourceInterface */
+static void datasource_geotag_add_setup_widgets ( GtkWidget *dialog, VikViewport *vvp, gpointer user_data )
+{
+       datasource_geotag_user_data_t *userdata = (datasource_geotag_user_data_t *)user_data;
+
+       userdata->vvp = vvp;
+
+       /* The files selector */
+       userdata->files = gtk_file_chooser_widget_new ( GTK_FILE_CHOOSER_ACTION_OPEN );
+
+       // try to make it a nice size - otherwise seems to default to something impractically small
+       gtk_window_set_default_size ( GTK_WINDOW (dialog) , 600, 300 );
+
+       if ( last_folder_uri )
+               gtk_file_chooser_set_current_folder_uri ( GTK_FILE_CHOOSER(userdata->files), last_folder_uri );
+
+       GtkFileChooser *chooser = GTK_FILE_CHOOSER ( userdata->files );
+
+       /* Add filters */
+       GtkFileFilter *filter;
+       filter = gtk_file_filter_new ();
+       gtk_file_filter_set_name ( filter, _("All") );
+       gtk_file_filter_add_pattern ( filter, "*" );
+       gtk_file_chooser_add_filter ( chooser, filter );
+
+       filter = gtk_file_filter_new ();
+       gtk_file_filter_set_name ( filter, _("JPG") );
+       gtk_file_filter_add_mime_type ( filter, "image/jpeg");
+       gtk_file_chooser_add_filter ( chooser, filter );
+
+       // Default to jpgs
+       gtk_file_chooser_set_filter ( chooser, filter );
+
+       // Allow selecting more than one
+       gtk_file_chooser_set_select_multiple ( chooser, TRUE );
+
+       // Could add code to setup a default symbol (see dialog.c for symbol usage)
+       //  Store in user_data type and then apply when creating the waypoints
+       //  However not much point since these will have images associated with them!
+
+       /* Packing all widgets */
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(dialog)->vbox), userdata->files, TRUE, TRUE, 0 );
+
+       gtk_widget_show_all ( dialog );
+}
+
+static void datasource_geotag_get_cmd_string ( gpointer user_data, gchar **babelargs_or_shellcmd, gchar **inputfile_or_inputtype )
+{
+       datasource_geotag_user_data_t *userdata = (datasource_geotag_user_data_t *)user_data;
+       /* Retrieve the files selected */
+       userdata->filelist = gtk_file_chooser_get_filenames ( GTK_FILE_CHOOSER(userdata->files) ); // Not reusable !!
+
+       /* Memorize the directory for later use */
+       g_free ( last_folder_uri );
+       last_folder_uri = gtk_file_chooser_get_current_folder_uri ( GTK_FILE_CHOOSER(userdata->files) );
+       last_folder_uri = g_strdup ( last_folder_uri );
+
+       /* TODO Memorize the file filter for later use... */
+       //GtkFileFilter *filter = gtk_file_chooser_get_filter ( GTK_FILE_CHOOSER(userdata->files) );
+
+       // return some value so processing will continue
+       *babelargs_or_shellcmd = g_strdup ("fake command"); // Not really used, thus no translations
+}
+
+
+/**
+ * Process selected files and try to generate waypoints storing them in the given vtl
+ */
+static gboolean datasource_geotag_process ( VikTrwLayer *vtl, const gchar *cmd, const gchar *extra, BabelStatusFunc status_cb, acq_dialog_widgets_t *adw )
+{
+       datasource_geotag_user_data_t *user_data = (datasource_geotag_user_data_t *)adw->user_data;
+
+       // Process selected files
+       // In prinicple this loading should be quite fast and so don't need to have any progress monitoring
+       GSList *cur_file = user_data->filelist;
+       while ( cur_file ) {
+               gchar *filename = cur_file->data;
+               gchar *name;
+               VikWaypoint *wp = a_geotag_create_waypoint_from_file ( filename, vik_viewport_get_coord_mode ( user_data->vvp ), &name );
+               if ( wp ) {
+                       // Create name if geotag method didn't return one
+                       if ( !name )
+                               name = g_strdup ( a_file_basename ( filename ) );
+                       vik_trw_layer_filein_add_waypoint ( vtl, name, wp );
+                       g_free ( name );
+               }
+               else {
+                       g_warning ( _("Unable to create waypoint from %s"), filename );
+               }
+               g_free ( filename );
+               cur_file = g_slist_next ( cur_file );
+       }
+
+       /* Free memory */
+       g_slist_free ( user_data->filelist );
+
+       // No failure
+       return TRUE;
+}
+
+/* See VikDataSourceInterface */
+static void datasource_geotag_cleanup ( gpointer user_data )
+{
+       g_free ( user_data );
+}
index 95d4516a1e577bbf58d3c699804064c219fe21a9..10d4991a73ce7fa7831866a7ab8dd95ef8371713 100644 (file)
@@ -57,6 +57,7 @@ VikDataSourceInterface vik_datasource_google_interface = {
   (VikDataSourceCheckExistenceFunc)    NULL,
   (VikDataSourceAddSetupWidgetsFunc)   datasource_google_add_setup_widgets,
   (VikDataSourceGetCmdStringFunc)      datasource_google_get_cmd_string,
+  (VikDataSourceProcessFunc)           NULL,
   (VikDataSourceProgressFunc)          NULL,
   (VikDataSourceAddProgressWidgetsFunc)        NULL,
   (VikDataSourceCleanupFunc)           datasource_google_cleanup,
index 005902210e6e7d5a423a2326d921dbe7f02b1263..41023e927e686d61f80c7132549831e16da390f1 100644 (file)
@@ -63,6 +63,7 @@ VikDataSourceInterface vik_datasource_gps_interface = {
   (VikDataSourceCheckExistenceFunc)    NULL,
   (VikDataSourceAddSetupWidgetsFunc)   datasource_gps_add_setup_widgets,
   (VikDataSourceGetCmdStringFunc)      datasource_gps_get_cmd_string,
+  (VikDataSourceProcessFunc)           NULL,
   (VikDataSourceProgressFunc)          datasource_gps_progress,
   (VikDataSourceAddProgressWidgetsFunc)        datasource_gps_add_progress_widgets,
   (VikDataSourceCleanupFunc)           datasource_gps_cleanup,
index c7f91fe58334d289390bcb1145fec26f9fffe94e..9315f5c8b84ef31f915035ce826cb76811413d39 100644 (file)
@@ -60,6 +60,7 @@ VikDataSourceInterface vik_datasource_osm_interface = {
   (VikDataSourceCheckExistenceFunc)    NULL,
   (VikDataSourceAddSetupWidgetsFunc)   datasource_osm_add_setup_widgets,
   (VikDataSourceGetCmdStringFunc)      datasource_osm_get_cmd_string,
+  (VikDataSourceProcessFunc)           NULL,
   (VikDataSourceProgressFunc)          NULL,
   (VikDataSourceAddProgressWidgetsFunc)        NULL,
   (VikDataSourceCleanupFunc)           datasource_osm_cleanup,
index 48d896eb1fa0360d066c20496746e3fb5ad858f7..431abdc88c12829fb83653a19058fc6af3a987c8 100644 (file)
@@ -32,4 +32,7 @@ extern VikDataSourceInterface vik_datasource_osm_interface;
 #ifdef VIK_CONFIG_GEOCACHES
 extern VikDataSourceInterface vik_datasource_gc_interface;
 #endif
+#ifdef VIK_CONFIG_GEOTAG
+extern VikDataSourceInterface vik_datasource_geotag_interface;
+#endif
 #endif
index b7a98b2bbc4187a314fd0dd1be9f8cd24c5dc076..3fb200b36e7d6bb960b9a29db88af079e8bd8943 100644 (file)
@@ -603,7 +603,7 @@ VikLoadType_t a_file_load ( VikAggregateLayer *top, VikViewport *vp, const gchar
     // 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 ) ) {
       // Implicit Conversion
-      if ( ! a_babel_convert_from ( VIK_TRW_LAYER(vtl), "-i kml", NULL, filename, NULL ) ) {
+      if ( ! a_babel_convert_from ( VIK_TRW_LAYER(vtl), "-i kml", filename, NULL, NULL ) ) {
        // Probably want to remove the vtl, but I'm not sure how yet...
        xfclose(f);
        return LOAD_TYPE_GPSBABEL_FAILURE;
@@ -713,14 +713,14 @@ gboolean a_file_export ( VikTrwLayer *vtl, const gchar *filename, VikFileType_t
          f = NULL;
          switch ( a_vik_get_kml_export_units () ) {
            case VIK_KML_EXPORT_UNITS_STATUTE:
-             return a_babel_convert_to ( vtl, "-o kml", NULL, filename, NULL );
+             return a_babel_convert_to ( vtl, "-o kml", filename, NULL, NULL );
              break;
            case VIK_KML_EXPORT_UNITS_NAUTICAL:
-             return a_babel_convert_to ( vtl, "-o kml,units=n", NULL, filename, NULL );
+             return a_babel_convert_to ( vtl, "-o kml,units=n", filename, NULL, NULL );
              break;
            default:
              // VIK_KML_EXPORT_UNITS_METRIC:
-             return a_babel_convert_to ( vtl, "-o kml,units=m", NULL, filename, NULL );
+             return a_babel_convert_to ( vtl, "-o kml,units=m", filename, NULL, NULL );
              break;
          }
          break;
diff --git a/src/geotag_exif.c b/src/geotag_exif.c
new file mode 100644 (file)
index 0000000..4b77ca7
--- /dev/null
@@ -0,0 +1,664 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
+ *
+ * Copyright (C) 2011, Rob Norris <rw_norris@hotmail.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+/*
+ * This uses EXIF information from images to create waypoints at those positions
+ * TODO: allow writing of image location:
+ *  . Via correlation with a track (c.f. gpscorrelate) (multiple images)
+ *  . Via screen position (individual image) on an existing waypoint
+ *
+ * For the implementation I have chosen to use libexif, which keeps Viking a pure C program
+ * For an alternative implementation (a la gpscorrelate), one could use libeviv2 but it appears to be C++ only.
+ */
+#include <string.h>
+#include "geotag_exif.h"
+#include "globals.h"
+#include "file.h"
+
+#include <sys/stat.h>
+#include <utime.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <libexif/exif-data.h>
+#include "libjpeg/jpeg-data.h"
+
+/**
+ * Attempt to get a single comment from the various exif fields
+ */
+static gchar* geotag_get_exif_comment ( ExifData *ed )
+{
+       gchar str[128];
+       ExifEntry *ee;
+       //
+       // Try various options to create a comment
+       //
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_IMAGE_DESCRIPTION);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               return g_strdup ( str );
+       }
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_COMMENT);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               return g_strdup ( str );
+       }
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_SUBJECT);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               return g_strdup ( str );
+       }
+
+       // Consider using these for existing GPS info??
+       //#define EXIF_TAG_GPS_TIME_STAMP        0x0007
+       //#define EXIF_TAG_GPS_DATE_STAMP         0x001d
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               return g_strdup ( str );
+       }
+       
+       // Otherwise nothing found
+       return NULL;
+}
+
+/**
+ * Handles 3 part location Rationals
+ * Handles 1 part rational (must specify 0 for the offset)
+ */
+static gdouble Rational2Double ( unsigned char *data, int offset, ExifByteOrder order )
+{
+       // Explaination from GPS Correlate 'exif-gps.cpp' v 1.6.1
+       // What we are trying to do here is convert the three rationals:
+       //    dd/v mm/v ss/v
+       // To a decimal
+       //    dd.dddddd...
+       // dd/v is easy: result = dd/v.
+       // mm/v is harder:
+       //    mm
+       //    -- / 60 = result.
+       //     v
+       // ss/v is sorta easy.
+       //     ss
+       //     -- / 3600 = result
+       //      v
+       // Each part is added to the final number.
+       gdouble ans;
+       ExifRational er;
+       er = exif_get_rational (data, order);
+       ans = (gdouble)er.numerator / (gdouble)er.denominator;
+       if (offset <= 0)
+               return ans;
+
+       er = exif_get_rational (data+(1*offset), order);
+       ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 60.0 );
+       er = exif_get_rational (data+(2*offset), order);
+       ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 3600.0 );
+
+       return ans;
+}
+
+/**
+ * a_geotag_create_waypoint_from_file:
+ * @filename: The image file to process
+ * @vcmode:   The current location mode to use in the positioning of Waypoint
+ * @name:     Returns a name for the Waypoint (can be NULL)
+ *
+ * Returns: An allocated Waypoint or NULL if Waypoint could not be generated (e.g. no EXIF info)
+ *
+ */
+VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoordMode vcmode, gchar **name )
+{
+       // Default return values (for failures)
+       *name = NULL;
+       VikWaypoint *wp = NULL;
+
+       // TODO use log?
+       //ExifLog *log = NULL;
+
+       // open image with libexif
+       ExifData *ed = exif_data_new_from_file ( filename );
+
+       // Detect EXIF load failure
+       if ( !ed )
+               // return with no Waypoint
+               return wp;
+
+       struct LatLon ll;
+
+       gchar str[128];
+       ExifEntry *ee;
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
+       // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
+       if ( ! ( ee && ee->components == 4 ) )
+               goto MyReturn;
+       // Could test for these versions explicitly but may have byte order issues...
+       //if ( ! ( ee->data[0] == 2 && ee->data[2] == 0 && ee->data[3] == 0 ) )
+       //      goto MyReturn;
+
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_MAP_DATUM);
+       if ( ! ( ee && ee->components > 0 && ee->format == EXIF_FORMAT_ASCII ) )
+               goto MyReturn;
+
+       // If map datum specified - only deal in WGS-84 - the defacto standard
+       if ( ee && ee->components > 0 ) {
+               exif_entry_get_value ( ee, str, 128 );
+               if ( strncmp (str, "WGS-84", 6) )
+                       goto MyReturn;
+       }
+
+       //
+       // Lat & Long is necessary to form a waypoint.
+       //
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
+       if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
+               goto MyReturn;
+  
+       ll.lat = Rational2Double ( ee->data,
+                                                          exif_format_get_size(ee->format),
+                                                          exif_data_get_byte_order(ed) );
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE_REF);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               if ( str[0] == 'S' )
+                       ll.lat = -ll.lat;
+       }
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
+       if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
+               goto MyReturn;
+
+       ll.lon = Rational2Double ( ee->data,
+                                                          exif_format_get_size(ee->format),
+                                                          exif_data_get_byte_order(ed) );
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE_REF);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               if ( str[0] == 'W' )
+                       ll.lon = -ll.lon;
+       }
+
+       //
+       // Not worried if none of the other fields exist, as can default the values to something
+       //
+
+       gdouble alt = VIK_DEFAULT_ALTITUDE;
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE);
+       if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_RATIONAL ) {
+               alt = Rational2Double ( ee->data,
+                                                               0,
+                                                               exif_data_get_byte_order(ed) );
+
+               ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE_REF);
+               if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_BYTE && ee->data[0] == 1 )
+                       alt = -alt;
+       }
+
+       // Name
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               *name = g_strdup ( str );
+       }
+
+       //
+       // Now create Waypoint with acquired information
+       //
+       wp = vik_waypoint_new();
+       wp->visible = TRUE;
+       // Set info from exif values
+       // Location
+       vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
+       // Altitude
+       wp->altitude = alt;
+
+       wp->comment = geotag_get_exif_comment ( ed );
+
+       vik_waypoint_set_image ( wp, filename );
+
+MyReturn:
+       // Finished with EXIF
+       exif_data_free ( ed );
+
+       return wp;
+}
+
+/**
+ * a_geotag_create_waypoint_positioned:
+ * @filename: The image file to process
+ * @coord:    The location for positioning the Waypoint
+ * @name:     Returns a name for the Waypoint (can be NULL)
+ *
+ * Returns: An allocated Waypoint or NULL if Waypoint could not be generated
+ *
+ *  Here EXIF processing is used to get non position related information (i.e. just the comment)
+ *
+ */
+VikWaypoint* a_geotag_create_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name )
+{
+       *name = NULL;
+       VikWaypoint *wp = vik_waypoint_new();
+       wp->visible = TRUE;
+       wp->coord = coord;
+       wp->altitude = alt;
+
+       ExifData *ed = exif_data_new_from_file ( filename );
+
+       // Set info from exif values
+       if ( ed ) {
+               wp->comment = geotag_get_exif_comment ( ed );
+
+               gchar str[128];
+               ExifEntry *ee;
+               // Name
+               ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
+               if ( ee ) {
+                       exif_entry_get_value ( ee, str, 128 );
+                       *name = g_strdup ( str );
+               }
+
+               // Finished with EXIF
+               exif_data_free ( ed );
+       }
+
+       vik_waypoint_set_image ( wp, filename );
+
+
+       return wp;
+}
+
+/**
+ * a_geotag_get_exif_date_from_file:
+ * @filename: The image file to process
+ * @has_GPS_info: Returns whether the file has existing GPS information
+ *
+ * Returns: An allocated string with the date and time in EXIF_DATE_FORMAT, otherwise NULL if some kind of failure
+ *
+ *  Here EXIF processing is used to get time information
+ *
+ */
+gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_GPS_info )
+{
+       gchar* datetime = NULL;
+
+       ExifData *ed = exif_data_new_from_file ( filename );
+
+       // Detect EXIF load failure
+       if ( !ed )
+               return datetime;
+
+       gchar str[128];
+       ExifEntry *ee;
+
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
+       if ( ee ) {
+               exif_entry_get_value ( ee, str, 128 );
+               datetime = g_strdup ( str );
+       }
+
+       // Check GPS Info
+       *has_GPS_info = FALSE;
+       
+       ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
+       // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
+       if ( ee && ee->components == 4 )
+               *has_GPS_info = TRUE;
+
+       exif_data_free ( ed );
+
+       return datetime;
+}
+
+
+/**! If the entry doesn't exist, create it.
+ * Based on exif command line action_create_value function in exif 0.6.20
+ */
+static ExifEntry* my_exif_create_value (ExifData *ed, ExifTag tag, ExifIfd ifd)
+{
+       ExifEntry *e = exif_content_get_entry (ed->ifd[ifd], tag);
+       if ( !e ) {
+           e = exif_entry_new ();
+           exif_content_add_entry (ed->ifd[ifd], e);
+
+               exif_entry_initialize (e, tag);
+
+               // exif_entry_initialize doesn't seem to do much, especially for the GPS tags
+               //   so have to setup fields ourselves:
+               e->tag = tag;
+
+               if ( tag == EXIF_TAG_GPS_VERSION_ID ) {
+                       e->format = EXIF_FORMAT_BYTE;
+                       e->components = 4;
+                       e->size = sizeof (char) * e->components;
+                       if ( e->data )
+                               g_free (e->data);
+                       e->data = g_malloc (e->size);
+               }
+               if ( tag == EXIF_TAG_GPS_MAP_DATUM ||
+                        tag == EXIF_TAG_GPS_LATITUDE_REF || tag == EXIF_TAG_GPS_LONGITUDE_REF ||
+                        tag == EXIF_TAG_GPS_PROCESSING_METHOD ) {
+                       e->format = EXIF_FORMAT_ASCII;
+                       // NB Allocation is handled later on when the actual string used is known
+               }
+               if ( tag == EXIF_TAG_GPS_LATITUDE || tag == EXIF_TAG_GPS_LONGITUDE ) {
+                       e->format = EXIF_FORMAT_RATIONAL;
+                       e->components = 3;
+                       e->size = sizeof (ExifRational) * e->components;
+                       if ( e->data )
+                               g_free (e->data);
+                       e->data = g_malloc (e->size);
+               }
+               if ( tag == EXIF_TAG_GPS_ALTITUDE ) {
+                       e->format = EXIF_FORMAT_RATIONAL;
+                       e->components = 1;
+                       e->size = sizeof (ExifRational) * e->components;
+                       if ( e->data )
+                               g_free (e->data);
+                       e->data = g_malloc (e->size);
+               }
+               if ( tag == EXIF_TAG_GPS_ALTITUDE_REF ) {
+                       e->components = 1;
+                       e->size = sizeof (char) * e->components;
+                       if ( e->data )
+                               g_free (e->data);
+                       e->data = g_malloc (e->size);
+               }
+           /* The entry has been added to the IFD, so we can unref it */
+           //exif_entry_unref(e);
+               // Crashes later on, when saving to jpeg if the above unref is enabled!!
+               // ?Some other malloc problem somewhere?
+       }
+       return e;
+}
+
+/** Heavily based on convert_arg_to_entry from exif command line tool.
+ *  But without ExifLog, exitting, use of g_* io functions
+ *   and can take a gdouble value instead of a string
+ */
+static void convert_to_entry (const char *set_value, gdouble gdvalue, ExifEntry *e, ExifByteOrder o)
+{
+       unsigned int i, numcomponents;
+       char *value_p = NULL;
+       char *buf = NULL;
+       /*
+        * ASCII strings are handled separately,
+        * since they don't require any conversion.
+        */
+       if (e->format == EXIF_FORMAT_ASCII ||
+           e->tag == EXIF_TAG_USER_COMMENT) {
+               if (e->data) g_free (e->data);
+               e->components = strlen (set_value) + 1;
+               if (e->tag == EXIF_TAG_USER_COMMENT)
+                       e->components += 8 - 1;
+               e->size = sizeof (char) * e->components;
+               e->data = g_malloc (e->size);
+               if (!e->data) {
+                       g_warning (_("Not enough memory."));
+                       return;
+               }
+               if (e->tag == EXIF_TAG_USER_COMMENT) {
+                       /* assume ASCII charset */
+                       /* TODO: get this from the current locale */
+                       memcpy ((char *) e->data, "ASCII\0\0\0", 8);
+                       memcpy ((char *) e->data + 8, set_value,
+                                       strlen (set_value));
+               } else
+                       strcpy ((char *) e->data, set_value);
+               return;
+       }
+
+       /*
+        * Make sure we can handle this entry
+        */
+       if ((e->components == 0) && *set_value) {
+               g_warning (_("Setting a value for this tag is unsupported!"));
+               return;
+       }
+
+       gboolean use_string = (set_value != NULL);
+       if ( use_string ) {
+               /* Copy the string so we can modify it */
+               buf = g_strdup (set_value);
+               if (!buf)
+                       return;
+               value_p = strtok (buf, " ");
+       }
+
+       numcomponents = e->components;
+       for (i = 0; i < numcomponents; ++i) {
+               unsigned char s;
+
+               if ( use_string ) {
+                       if (!value_p) {
+                               g_warning (_("Too few components specified (need %d, found %d)\n"), numcomponents, i);
+                               return;
+                       }
+                       if (!isdigit(*value_p) && (*value_p != '+') && (*value_p != '-')) {
+                               g_warning (_("Numeric value expected\n"));
+                               return;
+                       }
+               }
+
+               s = exif_format_get_size (e->format);
+               switch (e->format) {
+               case EXIF_FORMAT_ASCII:
+                       g_warning (_("This shouldn't happen!"));
+                       return;
+                       break;
+               case EXIF_FORMAT_SHORT:
+                       exif_set_short (e->data + (s * i), o, atoi (value_p));
+                       break;
+               case EXIF_FORMAT_SSHORT:
+                       exif_set_sshort (e->data + (s * i), o, atoi (value_p));
+                       break;
+               case EXIF_FORMAT_RATIONAL: {
+                       ExifRational er;
+
+                       double val = 0.0 ;
+                       if ( use_string && value_p )
+                               val = fabs (atol (value_p));
+                       else
+                               val = fabs (gdvalue);
+
+                       if ( i == 0 ) {
+                               // One (or first) part rational
+
+                               // Sneak peek into tag as location tags need rounding down to give just the degrees part
+                               if ( e->tag == EXIF_TAG_GPS_LATITUDE || e->tag == EXIF_TAG_GPS_LONGITUDE ) {
+                                       er.numerator = (ExifLong) floor ( val );
+                                       er.denominator = 1.0;
+                               }
+                               else {
+                                       // I don't see any point in doing anything too complicated here,
+                                       //   such as trying to work out the 'best' denominator
+                                       // For the moment use KISS principle.
+                                       // Fix a precision of 1/100 metre as that's more than enough for GPS accuracy especially altitudes!
+                                       er.denominator = 100.0;
+                                       er.numerator = (ExifLong) (val * er.denominator);
+                               }
+                       }
+
+                       // Now for Location 3 part rationals do Mins and Seconds format
+
+                       // Rounded down minutes
+                       if ( i == 1 ) {
+                               er.denominator = 1.0;
+                               er.numerator = (ExifLong) ( (int) floor ( ( val - floor (val) ) * 60.0 ) );
+                       }
+
+                       // Finally seconds
+                       if ( i == 2 ) {
+                               er.denominator = 100.0;
+
+                               // Fractional minute.
+                               double FracPart = ((val - floor(val)) * 60) - (double)(int) floor ( ( val - floor (val) ) * 60.0 );
+                               er.numerator = (ExifLong) ( (int)floor(FracPart * 6000) ); // Convert to seconds.
+                       }
+                       exif_set_rational (e->data + (s * i), o, er );
+                       break;
+               }
+               case EXIF_FORMAT_LONG:
+                       exif_set_long (e->data + (s * i), o, atol (value_p));
+                       break;
+               case EXIF_FORMAT_SLONG:
+                       exif_set_slong (e->data + (s * i), o, atol (value_p));
+                       break;
+               case EXIF_FORMAT_BYTE:
+               case EXIF_FORMAT_SBYTE:
+               case EXIF_FORMAT_UNDEFINED: /* treat as byte array */
+                       e->data[s * i] = atoi (value_p);
+                       break;
+               case EXIF_FORMAT_FLOAT:
+               case EXIF_FORMAT_DOUBLE:
+               case EXIF_FORMAT_SRATIONAL:
+               default:
+                       g_warning (_("Not yet implemented!"));
+                       return;
+               }
+               
+               if ( use_string )
+                       value_p = strtok (NULL, " ");
+
+       }
+
+       g_free (buf);
+
+       if ( use_string )
+               if ( value_p )
+                       g_warning (_("Warning; Too many components specified!"));
+}
+
+/**
+ * a_geotag_write_exif_gps:
+ * @filename: The image file to save information in
+ * @coord:    The location
+ * @alt:      The elevation
+ *
+ * Returns: A value indicating success: 0, or some other value for failure
+ *
+ */
+gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble alt, gboolean no_change_mtime )
+{
+       gint result = 0; // OK so far...
+
+       // Save mtime for later use
+       struct stat stat_save;
+       if ( no_change_mtime )
+               stat ( filename, &stat_save );
+
+       /*
+         Appears libexif doesn't actually support writing EXIF data directly to files
+         Thus embed command line exif writing method within Viking
+         (for example this is done by Enlightment - http://www.enlightenment.org/ )
+         This appears to be JPEG only, but is probably 99% of our use case
+         Alternatively consider using libexiv2 and C++...
+       */
+
+       // Actual EXIF settings here...
+       JPEGData *jdata;
+
+       /* Parse the JPEG file. */
+       jdata = jpeg_data_new ();
+       jpeg_data_load_file (jdata, filename);
+
+       // Get current values
+       ExifData *ed = exif_data_new_from_file ( filename );
+       if ( !ed )
+               ed = exif_data_new ();
+
+       // Update ExifData with our new settings
+       ExifEntry *ee;
+       //
+       // I don't understand it, but when saving the 'ed' nothing gets set after putting in the GPS ID tag - so it must come last
+       // (unless of course there is some bug in the setting of the ID, that prevents subsequent tags)
+       //
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE, EXIF_IFD_GPS);
+       convert_to_entry ( NULL, alt, ee, exif_data_get_byte_order(ed) );
+
+       // byte 0 meaning "sea level" or 1 if the value is negative.
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE_REF, EXIF_IFD_GPS);
+       convert_to_entry ( alt < 0.0 ? "1" : "0", 0.0, ee, exif_data_get_byte_order(ed) );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_PROCESSING_METHOD, EXIF_IFD_GPS);
+       // see http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
+       convert_to_entry ( "MANUAL", 0.0, ee, exif_data_get_byte_order(ed) );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_MAP_DATUM, EXIF_IFD_GPS);
+       convert_to_entry ( "WGS-84", 0.0, ee, exif_data_get_byte_order(ed) );
+
+       struct LatLon ll;
+    vik_coord_to_latlon ( &coord, &ll );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE_REF, EXIF_IFD_GPS);
+       // N or S
+       convert_to_entry ( ll.lat < 0.0 ? "S" : "N", 0.0, ee, exif_data_get_byte_order(ed) );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE, EXIF_IFD_GPS);
+       convert_to_entry ( NULL, ll.lat, ee, exif_data_get_byte_order(ed) );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE_REF, EXIF_IFD_GPS);
+       // E or W
+       convert_to_entry ( ll.lon < 0.0 ? "W" : "E", 0.0, ee, exif_data_get_byte_order(ed) );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE, EXIF_IFD_GPS);
+       convert_to_entry ( NULL, ll.lon, ee, exif_data_get_byte_order(ed) );
+
+       ee = my_exif_create_value (ed, EXIF_TAG_GPS_VERSION_ID, EXIF_IFD_GPS);
+       //convert_to_entry ( "2 0 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
+       convert_to_entry ( "2 2 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
+
+       jpeg_data_set_exif_data (jdata, ed);
+
+       if ( jdata ) {
+               /* Save the modified image. */
+               result = jpeg_data_save_file (jdata, filename);
+
+               // Convert result from 1 for success, 0 for failure into our scheme
+               result = !result;
+               
+               jpeg_data_unref (jdata);
+       }
+       else {
+               // Epic fail - file probably not a JPEG
+               result = 2;
+       }
+
+       if ( no_change_mtime ) {
+               // Restore mtime, using the saved value
+               struct stat stat_tmp;
+               struct utimbuf utb;
+               stat ( filename, &stat_tmp );
+               utb.actime = stat_tmp.st_atime;
+               utb.modtime = stat_save.st_mtime;
+               utime ( filename, &utb );
+       }
+
+       return result;
+}
diff --git a/src/geotag_exif.h b/src/geotag_exif.h
new file mode 100644 (file)
index 0000000..985f1b3
--- /dev/null
@@ -0,0 +1,37 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
+ *
+ * Copyright (C) 2011, Rob Norris <rw_norris@hotmail.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _VIKING_GEOTAG_EXIF_H
+#define _VIKING_GEOTAG_EXIF_H
+
+#include "vikwaypoint.h"
+#include "vikcoord.h"
+
+VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoordMode vcmode, gchar **name );
+
+VikWaypoint* a_geotag_create_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name );
+
+gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_GPS_info );
+
+gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble alt, gboolean no_change_mtime );
+
+#endif // _VIKING_GEOTAG_EXIF_H
diff --git a/src/libjpeg/COPYING.orig b/src/libjpeg/COPYING.orig
new file mode 100644 (file)
index 0000000..602bfc9
--- /dev/null
@@ -0,0 +1,504 @@
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                           NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/src/libjpeg/README b/src/libjpeg/README
new file mode 100644 (file)
index 0000000..d268f19
--- /dev/null
@@ -0,0 +1,2 @@
+Theses files come from the libexif binary command line tools
+Used version 0.6.20
diff --git a/src/libjpeg/README.orig b/src/libjpeg/README.orig
new file mode 100644 (file)
index 0000000..97a0ef0
--- /dev/null
@@ -0,0 +1,50 @@
+                                     EXIF
+                                     ----
+
+'exif' is a small command-line utility to show EXIF information hidden
+in JPEG files. I wrote it to demonstrate the power of libexif.
+
+It depends on 
+ * libexif (http://www.sourceforge.net/projects/libexif)
+ * popt
+
+Have fun!
+
+Lutz <lutz@users.sourceforge.net>
+
+
+BUILDING
+--------
+
+Assuming libexif and popt are installed, building exif from the source
+tar ball should be a simple matter of:
+
+  ./configure
+  make
+  sudo make install
+
+If your libexif is installed in a non-standard location, you must point
+configure at the location of its libexif.pc file using the pkg-config
+path, like this:
+
+  ./configure PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
+
+When building from CVS, run this before configuring:
+
+  autoreconf -i
+
+
+INTERNATIONALIZATION
+--------------------
+
+All translations for exif, except cs, ru and en_CA, are coordinated by
+the Translation Project at http://translationproject.org/.  This means
+that ALL changes to the .po files (except those few exceptions) MUST be
+made through the TP web site, or they will be eventually overwritten
+and lost.  If you are interested in translating exif into a new
+language, simply join TP and start!  A translation disclaimer is NOT
+required for exif; by making a translation, you agree implicitly to
+provide it under the same license terms as the rest of exif (GPL).
+When a new version of exif is available for translation, an exif
+maintainer will contact the Translation Project and all interested TP
+members will be automatically notified.
diff --git a/src/libjpeg/jpeg-data.c b/src/libjpeg/jpeg-data.c
new file mode 100644 (file)
index 0000000..c175f59
--- /dev/null
@@ -0,0 +1,474 @@
+/* jpeg-data.c
+ *
+ * Copyright Â© 2001 Lutz Müller <lutz@users.sourceforge.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301  USA.
+ */
+
+#include "config.h"
+#include "jpeg-data.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+/* realloc that cleans up on memory failure and returns to caller */
+#define CLEANUP_REALLOC(p,s) { \
+       unsigned char *cleanup_ptr = realloc((p),(s)); \
+       if (!cleanup_ptr) { free(p); (p) = NULL; return; } \
+       (p) = cleanup_ptr; \
+}
+
+struct _JPEGDataPrivate
+{
+       unsigned int ref_count;
+
+       ExifLog *log;
+};
+
+JPEGData *
+jpeg_data_new (void)
+{
+       JPEGData *data;
+
+       data = malloc (sizeof (JPEGData));
+       if (!data)
+               return (NULL);
+       memset (data, 0, sizeof (JPEGData));
+       data->priv = malloc (sizeof (JPEGDataPrivate));
+       if (!data->priv) {
+               free (data);
+               return (NULL);
+       }
+       memset (data->priv, 0, sizeof (JPEGDataPrivate));
+       data->priv->ref_count = 1;
+
+       return (data);
+}
+
+void
+jpeg_data_append_section (JPEGData *data)
+{
+       JPEGSection *s;
+
+       if (!data) return;
+
+       if (!data->count)
+               s = malloc (sizeof (JPEGSection));
+       else
+               s = realloc (data->sections,
+                            sizeof (JPEGSection) * (data->count + 1));
+       if (!s) {
+               EXIF_LOG_NO_MEMORY (data->priv->log, "jpeg-data", 
+                               sizeof (JPEGSection) * (data->count + 1));
+               return;
+       }
+       memset(s + data->count, 0, sizeof (JPEGSection));
+       data->sections = s;
+       data->count++;
+}
+
+/*! jpeg_data_save_file returns 1 on success, 0 on failure */
+int
+jpeg_data_save_file (JPEGData *data, const char *path)
+{
+       FILE *f;
+       unsigned char *d = NULL;
+       unsigned int size = 0, written;
+
+       jpeg_data_save_data (data, &d, &size);
+       if (!d)
+               return 0;
+
+       remove (path);
+       f = fopen (path, "wb");
+       if (!f) {
+               free (d);
+               return 0;
+       }
+       written = fwrite (d, 1, size, f);
+       fclose (f);
+       free (d);
+       if (written == size)  {
+               return 1;
+       }
+       remove(path);
+       return 0;
+}
+
+void
+jpeg_data_save_data (JPEGData *data, unsigned char **d, unsigned int *ds)
+{
+       unsigned int i, eds = 0;
+       JPEGSection s;
+       unsigned char *ed = NULL;
+
+       if (!data)
+               return;
+       if (!d)
+               return;
+       if (!ds)
+               return;
+
+       for (*ds = i = 0; i < data->count; i++) {
+               s = data->sections[i];
+
+               /* Write the marker */
+               CLEANUP_REALLOC (*d, sizeof (char) * (*ds + 2));
+               (*d)[*ds + 0] = 0xff;
+               (*d)[*ds + 1] = s.marker;
+               *ds += 2;
+
+               switch (s.marker) {
+               case JPEG_MARKER_SOI:
+               case JPEG_MARKER_EOI:
+                       break;
+               case JPEG_MARKER_APP1:
+                       exif_data_save_data (s.content.app1, &ed, &eds);
+                       if (!ed) break;
+                       CLEANUP_REALLOC (*d, sizeof (char) * (*ds + 2));
+                       (*d)[*ds + 0] = (eds + 2) >> 8;
+                       (*d)[*ds + 1] = (eds + 2) >> 0;
+                       *ds += 2;
+                       CLEANUP_REALLOC (*d, sizeof (char) * (*ds + eds));
+                       memcpy (*d + *ds, ed, eds);
+                       *ds += eds;
+                       free (ed);
+                       break;
+               default:
+                       CLEANUP_REALLOC (*d, sizeof (char) *
+                                       (*ds + s.content.generic.size + 2));
+                       (*d)[*ds + 0] = (s.content.generic.size + 2) >> 8;
+                       (*d)[*ds + 1] = (s.content.generic.size + 2) >> 0;
+                       *ds += 2;
+                       memcpy (*d + *ds, s.content.generic.data,
+                               s.content.generic.size);
+                       *ds += s.content.generic.size;
+
+                       /* In case of SOS, we need to write the data. */
+                       if (s.marker == JPEG_MARKER_SOS) {
+                               CLEANUP_REALLOC (*d, *ds + data->size);
+                               memcpy (*d + *ds, data->data, data->size);
+                               *ds += data->size;
+                       }
+                       break;
+               }
+       }
+}
+
+JPEGData *
+jpeg_data_new_from_data (const unsigned char *d,
+                        unsigned int size)
+{
+       JPEGData *data;
+
+       data = jpeg_data_new ();
+       jpeg_data_load_data (data, d, size);
+       return (data);
+}
+
+void
+jpeg_data_load_data (JPEGData *data, const unsigned char *d,
+                    unsigned int size)
+{
+       unsigned int i, o, len;
+       JPEGSection *s;
+       JPEGMarker marker;
+
+       if (!data) return;
+       if (!d) return;
+
+       for (o = 0; o < size;) {
+
+               /*
+                * JPEG sections start with 0xff. The first byte that is
+                * not 0xff is a marker (hopefully).
+                */
+               for (i = 0; i < MIN(7, size - o); i++)
+                       if (d[o + i] != 0xff)
+                               break;
+               if (!JPEG_IS_MARKER (d[o + i])) {
+                       exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "jpeg-data",
+                                       _("Data does not follow JPEG specification."));
+                       return;
+               }
+               marker = d[o + i];
+
+               /* Append this section */
+               jpeg_data_append_section (data);
+               if (!data->count) return;
+               s = &data->sections[data->count - 1];
+               s->marker = marker;
+               o += i + 1;
+
+               switch (s->marker) {
+               case JPEG_MARKER_SOI:
+               case JPEG_MARKER_EOI:
+                       break;
+               default:
+
+                       /* Read the length of the section */
+                       len = ((d[o] << 8) | d[o + 1]) - 2;
+                       if (len > size) { o = size; break; }
+                       o += 2;
+                       if (o + len > size) { o = size; break; }
+
+                       switch (s->marker) {
+                       case JPEG_MARKER_APP1:
+                               s->content.app1 = exif_data_new_from_data (
+                                                       d + o - 4, len + 4);
+                               break;
+                       default:
+                               s->content.generic.data =
+                                               malloc (sizeof (char) * len);
+                               if (!s->content.generic.data) return;
+                               s->content.generic.size = len;
+                               memcpy (s->content.generic.data, &d[o], len);
+
+                               /* In case of SOS, image data will follow. */
+                               if (s->marker == JPEG_MARKER_SOS) {
+                                       /* -2 means 'take all but the last 2 bytes which are hoped to be JPEG_MARKER_EOI */
+                                       data->size = size - 2 - o - len;
+                                       if (d[o + len + data->size] != 0xFF) {
+                                               /* A truncated file (i.e. w/o JPEG_MARKER_EOI at the end).
+                                                  Instead of trying to use the last two bytes as marker,
+                                                  touching memory beyond allocated memory and posssibly saving
+                                                  back screwed file, we rather take the rest of the file. */
+                                               data->size += 2;
+                                       }
+                                       data->data = malloc (
+                                               sizeof (char) * data->size);
+                                       if (!data->data) return;
+                                       memcpy (data->data, d + o + len,
+                                               data->size);
+                                       o += data->size;
+                               }
+                               break;
+                       }
+                       o += len;
+                       break;
+               }
+       }
+}
+
+JPEGData *
+jpeg_data_new_from_file (const char *path)
+{
+       JPEGData *data;
+
+       data = jpeg_data_new ();
+       jpeg_data_load_file (data, path);
+       return (data);
+}
+
+void
+jpeg_data_load_file (JPEGData *data, const char *path)
+{
+       FILE *f;
+       unsigned char *d;
+       unsigned int size;
+
+       if (!data) return;
+       if (!path) return;
+
+       f = fopen (path, "rb");
+       if (!f) {
+               exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "jpeg-data",
+                               _("Path '%s' invalid."), path);
+               return;
+       }
+
+       /* For now, we read the data into memory. Patches welcome... */
+       fseek (f, 0, SEEK_END);
+       size = ftell (f);
+       fseek (f, 0, SEEK_SET);
+       d = malloc (size);
+       if (!d) {
+               EXIF_LOG_NO_MEMORY (data->priv->log, "jpeg-data", size);
+               fclose (f);
+               return;
+       }
+       if (fread (d, 1, size, f) != size) {
+               free (d);
+               fclose (f);
+               exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "jpeg-data",
+                               _("Could not read '%s'."), path);
+               return;
+       }
+       fclose (f);
+
+       jpeg_data_load_data (data, d, size);
+       free (d);
+}
+
+void
+jpeg_data_ref (JPEGData *data)
+{
+       if (!data)
+               return;
+
+       data->priv->ref_count++;
+}
+
+void
+jpeg_data_unref (JPEGData *data)
+{
+       if (!data)
+               return;
+
+       if (data->priv) {
+               data->priv->ref_count--;
+               if (!data->priv->ref_count)
+                       jpeg_data_free (data);
+       }
+}
+
+void
+jpeg_data_free (JPEGData *data)
+{
+       unsigned int i;
+       JPEGSection s;
+
+       if (!data)
+               return;
+
+       if (data->count) {
+               for (i = 0; i < data->count; i++) {
+                       s = data->sections[i];
+                       switch (s.marker) {
+                       case JPEG_MARKER_SOI:
+                       case JPEG_MARKER_EOI:
+                               break;
+                       case JPEG_MARKER_APP1:
+                               exif_data_unref (s.content.app1);
+                               break;
+                       default:
+                               free (s.content.generic.data);
+                               break;
+                       }
+               }
+               free (data->sections);
+       }
+
+       if (data->data)
+               free (data->data);
+
+       if (data->priv) {
+               if (data->priv->log) {
+                       exif_log_unref (data->priv->log);
+                       data->priv->log = NULL;
+               }
+               free (data->priv);
+       }
+
+       free (data);
+}
+
+void
+jpeg_data_dump (JPEGData *data)
+{
+       unsigned int i;
+       JPEGContent content;
+       JPEGMarker marker;
+
+       if (!data)
+               return;
+
+       printf ("Dumping JPEG data (%i bytes of data)...\n", data->size);
+       for (i = 0; i < data->count; i++) {
+               marker = data->sections[i].marker;
+               content = data->sections[i].content;
+               printf ("Section %i (marker 0x%x - %s):\n", i, marker,
+                       jpeg_marker_get_name (marker));
+               printf ("  Description: %s\n",
+                       jpeg_marker_get_description (marker));
+               switch (marker) {
+                case JPEG_MARKER_SOI:
+                case JPEG_MARKER_EOI:
+                       break;
+                case JPEG_MARKER_APP1:
+                       exif_data_dump (content.app1);
+                       break;
+                default:
+                       printf ("  Size: %i\n", content.generic.size);
+                        printf ("  Unknown content.\n");
+                        break;
+                }
+        }
+}
+
+static JPEGSection *
+jpeg_data_get_section (JPEGData *data, JPEGMarker marker)
+{
+       unsigned int i;
+
+       if (!data)
+               return (NULL);
+
+       for (i = 0; i < data->count; i++)
+               if (data->sections[i].marker == marker)
+                       return (&data->sections[i]);
+       return (NULL);
+}
+
+ExifData *
+jpeg_data_get_exif_data (JPEGData *data)
+{
+       JPEGSection *section;
+
+       if (!data)
+               return NULL;
+
+       section = jpeg_data_get_section (data, JPEG_MARKER_APP1);
+       if (section) {
+               exif_data_ref (section->content.app1);
+               return (section->content.app1);
+       }
+
+       return (NULL);
+}
+
+void
+jpeg_data_set_exif_data (JPEGData *data, ExifData *exif_data)
+{
+       JPEGSection *section;
+
+       if (!data) return;
+
+       section = jpeg_data_get_section (data, JPEG_MARKER_APP1);
+       if (!section) {
+               jpeg_data_append_section (data);
+               if (data->count < 2) return;
+               memmove (&data->sections[2], &data->sections[1],
+                        sizeof (JPEGSection) * (data->count - 2));
+               section = &data->sections[1];
+       } else {
+               exif_data_unref (section->content.app1);
+       }
+       section->marker = JPEG_MARKER_APP1;
+       section->content.app1 = exif_data;
+       exif_data_ref (exif_data);
+}
+
+void
+jpeg_data_log (JPEGData *data, ExifLog *log)
+{
+       if (!data || !data->priv) return;
+       if (data->priv->log) exif_log_unref (data->priv->log);
+       data->priv->log = log;
+       exif_log_ref (log);
+}
diff --git a/src/libjpeg/jpeg-data.h b/src/libjpeg/jpeg-data.h
new file mode 100644 (file)
index 0000000..bb6ba0c
--- /dev/null
@@ -0,0 +1,92 @@
+/* jpeg-data.h
+ *
+ * Copyright Â© 2001 Lutz Müller <lutz@users.sourceforge.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details. 
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301  USA.
+ */
+
+#ifndef __JPEG_DATA_H__
+#define __JPEG_DATA_H__
+
+#include "libjpeg/jpeg-marker.h"
+
+#include <libexif/exif-data.h>
+#include <libexif/exif-log.h>
+
+typedef ExifData * JPEGContentAPP1;
+
+typedef struct _JPEGContentGeneric JPEGContentGeneric;
+struct _JPEGContentGeneric
+{
+       unsigned char *data;
+       unsigned int size;
+};
+
+typedef union _JPEGContent JPEGContent;
+union _JPEGContent
+{
+       JPEGContentGeneric generic;
+       JPEGContentAPP1    app1;
+};
+
+typedef struct _JPEGSection JPEGSection;
+struct _JPEGSection
+{
+       JPEGMarker marker;
+       JPEGContent content;
+};
+
+typedef struct _JPEGData        JPEGData;
+typedef struct _JPEGDataPrivate JPEGDataPrivate;
+
+struct _JPEGData
+{
+       JPEGSection *sections;
+       unsigned int count;
+
+       unsigned char *data;
+       unsigned int size;
+
+       JPEGDataPrivate *priv;
+};
+
+JPEGData *jpeg_data_new           (void);
+JPEGData *jpeg_data_new_from_file (const char *path);
+JPEGData *jpeg_data_new_from_data (const unsigned char *data,
+                                  unsigned int size);
+
+void      jpeg_data_ref   (JPEGData *data);
+void      jpeg_data_unref (JPEGData *data);
+void      jpeg_data_free  (JPEGData *data);
+
+void      jpeg_data_load_data     (JPEGData *data, const unsigned char *d,
+                                  unsigned int size);
+void      jpeg_data_save_data     (JPEGData *data, unsigned char **d,
+                                  unsigned int *size);
+
+void      jpeg_data_load_file     (JPEGData *data, const char *path);
+int       jpeg_data_save_file     (JPEGData *data, const char *path);
+
+void      jpeg_data_set_exif_data (JPEGData *data, ExifData *exif_data);
+ExifData *jpeg_data_get_exif_data (JPEGData *data);
+
+void      jpeg_data_dump (JPEGData *data);
+
+void      jpeg_data_append_section (JPEGData *data);
+
+void      jpeg_data_log (JPEGData *data, ExifLog *log);
+
+#endif /* __JPEG_DATA_H__ */
diff --git a/src/libjpeg/jpeg-marker.c b/src/libjpeg/jpeg-marker.c
new file mode 100644 (file)
index 0000000..10824d2
--- /dev/null
@@ -0,0 +1,122 @@
+/* jpeg-marker.c
+ *
+ * Copyright Â© 2001-2008 Lutz Müller <lutz@users.sourceforge.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details. 
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301  USA.
+ */
+#include "config.h"
+#include "jpeg-marker.h"
+
+#include <stdlib.h>
+
+static const struct {
+        JPEGMarker marker;
+        const char *name;
+        const char *description;
+} JPEGMarkerTable[] = {
+        {JPEG_MARKER_SOF0, "SOF0", "Encoding (baseline)"},
+        {JPEG_MARKER_SOF1, "SOF1", "Encoding (extended sequential)"},
+        {JPEG_MARKER_SOF2, "SOF2", "Encoding (progressive)"},
+        {JPEG_MARKER_SOF3, "SOF3", "Encoding (lossless)"},
+        {JPEG_MARKER_SOF5, "SOF5", "Encoding (differential sequential)"},
+        {JPEG_MARKER_SOF6, "SOF6", "Encoding (differential progressive)"},
+        {JPEG_MARKER_SOF7, "SOF7", "Encoding (differential lossless)"},
+        {JPEG_MARKER_SOF9, "SOF9",
+               "Encoding (extended sequential, arithmetic)"},
+        {JPEG_MARKER_SOF10, "SOF10", "Encoding (progressive, arithmetic)"},
+        {JPEG_MARKER_SOF11, "SOF11", "Encoding (lossless, arithmetic)"},
+        {JPEG_MARKER_SOF13, "SOF13",
+               "Encoding (differential sequential, arithmetic)"},
+        {JPEG_MARKER_SOF14, "SOF14",
+               "Encoding (differential progressive, arithmetic)"},
+        {JPEG_MARKER_SOF15, "SOF15",
+               "Encoding (differential lossless, arithmetic)"},
+       {JPEG_MARKER_SOI, "SOI", "Start of image"},
+       {JPEG_MARKER_EOI, "EOI", "End of image"},
+       {JPEG_MARKER_SOS, "SOS", "Start of scan"},
+       {JPEG_MARKER_COM, "COM", "Comment"},
+       {JPEG_MARKER_DHT, "DHT", "Define Huffman table"},
+       {JPEG_MARKER_JPG, "JPG", "Extension"},
+       {JPEG_MARKER_DAC, "DAC", "Define arithmetic coding conditioning"},
+       {JPEG_MARKER_RST1, "RST1", "Restart 1"},
+       {JPEG_MARKER_RST2, "RST2", "Restart 2"},
+       {JPEG_MARKER_RST3, "RST3", "Restart 3"},
+       {JPEG_MARKER_RST4, "RST4", "Restart 4"},
+       {JPEG_MARKER_RST5, "RST5", "Restart 5"},
+       {JPEG_MARKER_RST6, "RST6", "Restart 6"},
+       {JPEG_MARKER_RST7, "RST7", "Restart 7"},
+       {JPEG_MARKER_DQT, "DQT", "Define quantization table"},
+       {JPEG_MARKER_DNL, "DNL", "Define number of lines"},
+       {JPEG_MARKER_DRI, "DRI", "Define restart interval"},
+       {JPEG_MARKER_DHP, "DHP", "Define hierarchical progression"},
+       {JPEG_MARKER_EXP, "EXP", "Expand reference component"},
+       {JPEG_MARKER_APP0, "APP0", "Application segment 0"},
+       {JPEG_MARKER_APP1, "APP1", "Application segment 1"},
+       {JPEG_MARKER_APP2, "APP2", "Application segment 2"},
+       {JPEG_MARKER_APP3, "APP3", "Application segment 3"},
+       {JPEG_MARKER_APP4, "APP4", "Application segment 4"},
+       {JPEG_MARKER_APP5, "APP5", "Application segment 5"},
+       {JPEG_MARKER_APP6, "APP6", "Application segment 6"},
+       {JPEG_MARKER_APP7, "APP7", "Application segment 7"},
+       {JPEG_MARKER_APP8, "APP8", "Application segment 8"},
+       {JPEG_MARKER_APP9, "APP9", "Application segment 9"},
+       {JPEG_MARKER_APP10, "APP10", "Application segment 10"},
+       {JPEG_MARKER_APP11, "APP11", "Application segment 11"},
+       {JPEG_MARKER_APP12, "APP12", "Application segment 12"},
+       {JPEG_MARKER_APP13, "APP13", "Application segment 13"},
+       {JPEG_MARKER_APP14, "APP14", "Application segment 14"},
+       {JPEG_MARKER_APP15, "APP15", "Application segment 15"},
+       {JPEG_MARKER_JPG0, "JPG0", "Extension 0"},
+       {JPEG_MARKER_JPG1, "JPG1", "Extension 1"},
+       {JPEG_MARKER_JPG2, "JPG2", "Extension 2"},
+       {JPEG_MARKER_JPG3, "JPG3", "Extension 3"},
+       {JPEG_MARKER_JPG4, "JPG4", "Extension 4"},
+       {JPEG_MARKER_JPG5, "JPG5", "Extension 5"},
+       {JPEG_MARKER_JPG6, "JPG6", "Extension 6"},
+       {JPEG_MARKER_JPG7, "JPG7", "Extension 7"},
+       {JPEG_MARKER_JPG8, "JPG8", "Extension 8"},
+       {JPEG_MARKER_JPG9, "JPG9", "Extension 9"},
+       {JPEG_MARKER_JPG10, "JPG10", "Extension 10"},
+       {JPEG_MARKER_JPG11, "JPG11", "Extension 11"},
+       {JPEG_MARKER_JPG12, "JPG12", "Extension 12"},
+       {JPEG_MARKER_JPG13, "JPG13", "Extension 13"},
+       {0, NULL, NULL}
+};
+
+const char *
+jpeg_marker_get_name (JPEGMarker marker)
+{
+       unsigned int i;
+
+       for (i = 0; JPEGMarkerTable[i].name; i++)
+               if (JPEGMarkerTable[i].marker == marker)
+                       break;
+
+       return (JPEGMarkerTable[i].name);
+}
+
+const char *
+jpeg_marker_get_description (JPEGMarker marker)
+{
+       unsigned int i;
+
+       for (i = 0; JPEGMarkerTable[i].description; i++)
+               if (JPEGMarkerTable[i].marker == marker)
+                       break;
+
+       return (JPEGMarkerTable[i].description);
+}
+
diff --git a/src/libjpeg/jpeg-marker.h b/src/libjpeg/jpeg-marker.h
new file mode 100644 (file)
index 0000000..88bfe48
--- /dev/null
@@ -0,0 +1,103 @@
+/* jpeg-marker.h
+ *
+ * Copyright Â© 2001 Lutz Müller <lutz@users.sourceforge.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details. 
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301  USA.
+ */
+#ifndef __JPEG_MARKER_H__
+#define __JPEG_MARKER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {
+        JPEG_MARKER_SOF0        = 0xc0,
+        JPEG_MARKER_SOF1        = 0xc1,
+        JPEG_MARKER_SOF2        = 0xc2,
+        JPEG_MARKER_SOF3        = 0xc3,
+       JPEG_MARKER_DHT         = 0xc4,
+        JPEG_MARKER_SOF5        = 0xc5,
+        JPEG_MARKER_SOF6        = 0xc6,
+        JPEG_MARKER_SOF7        = 0xc7,
+       JPEG_MARKER_JPG         = 0xc8,
+        JPEG_MARKER_SOF9        = 0xc9,
+        JPEG_MARKER_SOF10       = 0xca,
+        JPEG_MARKER_SOF11       = 0xcb,
+       JPEG_MARKER_DAC         = 0xcc,
+        JPEG_MARKER_SOF13       = 0xcd,
+        JPEG_MARKER_SOF14       = 0xce,
+        JPEG_MARKER_SOF15       = 0xcf,
+        JPEG_MARKER_RST0       = 0xd0,
+        JPEG_MARKER_RST1       = 0xd1,
+        JPEG_MARKER_RST2       = 0xd2,
+        JPEG_MARKER_RST3       = 0xd3,
+        JPEG_MARKER_RST4       = 0xd4,
+       JPEG_MARKER_RST5        = 0xd5,
+        JPEG_MARKER_RST6       = 0xd6,
+        JPEG_MARKER_RST7       = 0xd7,
+        JPEG_MARKER_SOI         = 0xd8,
+        JPEG_MARKER_EOI         = 0xd9,
+        JPEG_MARKER_SOS         = 0xda,
+        JPEG_MARKER_DQT                = 0xdb,
+        JPEG_MARKER_DNL                = 0xdc,
+        JPEG_MARKER_DRI                = 0xdd,
+        JPEG_MARKER_DHP                = 0xde,
+        JPEG_MARKER_EXP                = 0xdf,
+       JPEG_MARKER_APP0        = 0xe0,
+        JPEG_MARKER_APP1        = 0xe1,
+       JPEG_MARKER_APP2        = 0xe2,
+       JPEG_MARKER_APP3        = 0xe3,
+       JPEG_MARKER_APP4        = 0xe4,
+       JPEG_MARKER_APP5        = 0xe5,
+       JPEG_MARKER_APP6        = 0xe6,
+       JPEG_MARKER_APP7        = 0xe7,
+       JPEG_MARKER_APP8        = 0xe8,
+       JPEG_MARKER_APP9        = 0xe9,
+       JPEG_MARKER_APP10       = 0xea,
+       JPEG_MARKER_APP11       = 0xeb,
+       JPEG_MARKER_APP12       = 0xec,
+       JPEG_MARKER_APP13       = 0xed,
+       JPEG_MARKER_APP14       = 0xee,
+       JPEG_MARKER_APP15       = 0xef,
+       JPEG_MARKER_JPG0        = 0xf0,
+       JPEG_MARKER_JPG1        = 0xf1,
+       JPEG_MARKER_JPG2        = 0xf2,
+       JPEG_MARKER_JPG3        = 0xf3,
+       JPEG_MARKER_JPG4        = 0xf4,
+       JPEG_MARKER_JPG5        = 0xf5,
+       JPEG_MARKER_JPG6        = 0xf6,
+       JPEG_MARKER_JPG7        = 0xf7,
+       JPEG_MARKER_JPG8        = 0xf8,
+       JPEG_MARKER_JPG9        = 0xf9,
+       JPEG_MARKER_JPG10       = 0xfa,
+       JPEG_MARKER_JPG11       = 0xfb,
+       JPEG_MARKER_JPG12       = 0xfc,
+       JPEG_MARKER_JPG13       = 0xfd,
+        JPEG_MARKER_COM         = 0xfe
+} JPEGMarker;
+
+#define JPEG_IS_MARKER(m) (((m) >= JPEG_MARKER_SOF0) &&                \
+                          ((m) <= JPEG_MARKER_COM))
+
+const char *jpeg_marker_get_name        (JPEGMarker marker);
+const char *jpeg_marker_get_description (JPEGMarker marker);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __JPEG_MARKER_H__ */
index ef1fef25812a3fd351e21cd0c6d2e83ce7233526..672186dd6847dc41c7dd527df656e9617cdb7f64 100644 (file)
@@ -21,6 +21,9 @@ static const char *menu_xml =
 #endif
 #ifdef VIK_CONFIG_GEOCACHES
        "        <menuitem action='AcquireGC'/>"
+#endif
+#ifdef VIK_CONFIG_GEOTAG
+       "        <menuitem action='AcquireGeotag'/>"
 #endif
        "      </menu>"
        "      <separator/>"
index 9d40b838aad5774b22c9ac9cb2d2267a3d0b6171..8c3a4a470c5e95e10ed0acf1b9d53a6c32330321 100644 (file)
@@ -1068,11 +1068,11 @@ static void gps_comm_thread(GpsSession *sess)
   gboolean result;
 
   if (sess->direction == GPS_DOWN)
-    result = a_babel_convert_from (sess->vtl, sess->cmd_args,
-        (BabelStatusFunc) gps_download_progress_func, sess->port, sess);
+    result = a_babel_convert_from (sess->vtl, sess->cmd_args, sess->port,
+        (BabelStatusFunc) gps_download_progress_func, sess);
   else
-    result = a_babel_convert_to (sess->vtl, sess->cmd_args,
-        (BabelStatusFunc) gps_upload_progress_func, sess->port, sess);
+    result = a_babel_convert_to (sess->vtl, sess->cmd_args, sess->port,
+        (BabelStatusFunc) gps_upload_progress_func, sess);
 
   if (!result) {
     gtk_label_set_text ( GTK_LABEL(sess->status_label), _("Error: couldn't find gpsbabel.") );
index 3abe2154f7d3cc7f413480079a8154fb9facde73..3b2df1dec1a2a184d4a5dde6fde2c7e128281ed1 100644 (file)
 #include "vikmapslayer.h"
 #include "viktrwlayer_tpwin.h"
 #include "viktrwlayer_propwin.h"
+#ifdef VIK_CONFIG_GEOTAG
+#include "viktrwlayer_geotag.h"
+#include "geotag_exif.h"
+#endif
 #include "garminsymbols.h"
 #include "thumbnails.h"
 #include "background.h"
@@ -253,6 +257,12 @@ static void trw_layer_delete_all_waypoints ( gpointer lav[2] );
 static void trw_layer_delete_waypoints_from_selection ( gpointer lav[2] );
 static void trw_layer_new_wikipedia_wp_viewport ( gpointer lav[2] );
 static void trw_layer_new_wikipedia_wp_layer ( gpointer lav[2] );
+#ifdef VIK_CONFIG_GEOTAG
+static void trw_layer_geotagging_waypoint_mtime_keep ( gpointer pass_along[6] );
+static void trw_layer_geotagging_waypoint_mtime_update ( gpointer pass_along[6] );
+static void trw_layer_geotagging_track ( gpointer pass_along[6] );
+static void trw_layer_geotagging ( gpointer lav[2] );
+#endif
 static void trw_layer_acquire_gps_cb ( gpointer lav[2] );
 static void trw_layer_acquire_google_cb ( gpointer lav[2] );
 #ifdef VIK_CONFIG_OPENSTREETMAP
@@ -261,6 +271,9 @@ static void trw_layer_acquire_osm_cb ( gpointer lav[2] );
 #ifdef VIK_CONFIG_GEOCACHES
 static void trw_layer_acquire_geocache_cb ( gpointer lav[2] );
 #endif
+#ifdef VIK_CONFIG_GEOTAG
+static void trw_layer_acquire_geotagged_cb ( gpointer lav[2] );
+#endif
 
 /* pop-up items */
 static void trw_layer_properties_item ( gpointer pass_along[6] );
@@ -415,7 +428,6 @@ static VikTrwLayer* trw_layer_new ( gint drawmode );
 /* Layer Interface function definitions */
 static VikTrwLayer* trw_layer_create ( VikViewport *vp );
 static void trw_layer_realize ( VikTrwLayer *vtl, VikTreeview *vt, GtkTreeIter *layer_iter );
-static void trw_layer_verify_thumbnails ( VikTrwLayer *vtl, GtkWidget *vp );
 static void trw_layer_free ( VikTrwLayer *trwlayer );
 static void trw_layer_draw ( VikTrwLayer *l, gpointer data );
 static void trw_layer_change_coord_mode ( VikTrwLayer *vtl, VikCoordMode dest_mode );
@@ -2393,6 +2405,52 @@ static void trw_layer_new_wikipedia_wp_layer ( gpointer lav[2] )
   a_geonames_wikipedia_box((VikWindow *)(VIK_GTK_WINDOW_FROM_LAYER(vtl)), vtl, vlp, maxmin);
 }
 
+#ifdef VIK_CONFIG_GEOTAG
+static void trw_layer_geotagging_waypoint_mtime_keep ( gpointer pass_along[6] )
+{
+  VikWaypoint *wp = g_hash_table_lookup ( VIK_TRW_LAYER(pass_along[0])->waypoints, pass_along[3] );
+  if ( wp )
+    // Update directly - not changing the mtime
+    a_geotag_write_exif_gps ( wp->image, wp->coord, wp->altitude, TRUE );
+}
+
+static void trw_layer_geotagging_waypoint_mtime_update ( gpointer pass_along[6] )
+{
+  VikWaypoint *wp = g_hash_table_lookup ( VIK_TRW_LAYER(pass_along[0])->waypoints, pass_along[3] );
+  if ( wp )
+    // Update directly
+    a_geotag_write_exif_gps ( wp->image, wp->coord, wp->altitude, FALSE );
+}
+
+/*
+ * Use code in separate file for this feature as reasonably complex
+ */
+static void trw_layer_geotagging_track ( gpointer pass_along[6] )
+{
+  VikTrwLayer *vtl = VIK_TRW_LAYER(pass_along[0]);
+  VikTrack *track = g_hash_table_lookup ( VIK_TRW_LAYER(pass_along[0])->tracks, pass_along[3] );
+  // Unset so can be reverified later if necessary
+  vtl->has_verified_thumbnails = FALSE;
+
+  trw_layer_geotag_dialog ( VIK_GTK_WINDOW_FROM_LAYER(vtl),
+                           vtl,
+                           track,
+                           pass_along[3] );
+}
+
+static void trw_layer_geotagging ( gpointer lav[2] )
+{
+  VikTrwLayer *vtl = VIK_TRW_LAYER(lav[0]);
+  // Unset so can be reverified later if necessary
+  vtl->has_verified_thumbnails = FALSE;
+
+  trw_layer_geotag_dialog ( VIK_GTK_WINDOW_FROM_LAYER(vtl),
+                           vtl,
+                           NULL,
+                           NULL);
+}
+#endif
+
 // 'Acquires' - Same as in File Menu -> Acquire - applies into the selected TRW Layer //
 
 /*
@@ -2452,6 +2510,26 @@ static void trw_layer_acquire_geocache_cb ( gpointer lav[2] )
 }
 #endif
 
+#ifdef VIK_CONFIG_GEOTAG
+/*
+ * Acquire into this TRW Layer from images
+ */
+static void trw_layer_acquire_geotagged_cb ( gpointer lav[2] )
+{
+  VikTrwLayer *vtl = VIK_TRW_LAYER(lav[0]);
+  VikLayersPanel *vlp = VIK_LAYERS_PANEL(lav[1]);
+  VikWindow *vw = (VikWindow *)(VIK_GTK_WINDOW_FROM_LAYER(vtl));
+  VikViewport *vvp =  vik_window_viewport(vw);
+
+  vik_datasource_geotag_interface.mode = VIK_DATASOURCE_ADDTOLAYER;
+  a_acquire ( vw, vlp, vvp, &vik_datasource_geotag_interface );
+
+  // Reverify thumbnails as they may have changed
+  vtl->has_verified_thumbnails = FALSE;
+  trw_layer_verify_thumbnails ( vtl, NULL );
+}
+#endif
+
 static void trw_layer_new_wp ( gpointer lav[2] )
 {
   VikTrwLayer *vtl = VIK_TRW_LAYER(lav[0]);
@@ -2597,6 +2675,13 @@ static void trw_layer_add_menu_items ( VikTrwLayer *vtl, GtkMenu *menu, gpointer
   gtk_widget_show ( item );
 #endif
 
+#ifdef VIK_CONFIG_GEOTAG
+  item = gtk_menu_item_new_with_mnemonic ( _("Geotag _Images...") );
+  g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_geotagging), pass_along );
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+  gtk_widget_show ( item );
+#endif
+
   GtkWidget *acquire_submenu = gtk_menu_new ();
   item = gtk_image_menu_item_new_with_mnemonic ( _("Ac_quire") );
   gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU) );
@@ -2628,6 +2713,13 @@ static void trw_layer_add_menu_items ( VikTrwLayer *vtl, GtkMenu *menu, gpointer
   gtk_widget_show ( item );
 #endif
 
+#ifdef VIK_CONFIG_GEOTAG
+  item = gtk_menu_item_new_with_mnemonic ( _("From Geotagged _Images...") );
+  g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_acquire_geotagged_cb), pass_along );
+  gtk_menu_shell_append (GTK_MENU_SHELL (acquire_submenu), item);
+  gtk_widget_show ( item );
+#endif
+
 #ifdef VIK_CONFIG_OPENSTREETMAP 
   item = gtk_image_menu_item_new_with_mnemonic ( _("Upload to _OSM...") );
   gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU) );
@@ -3949,6 +4041,25 @@ static gboolean trw_layer_sublayer_add_menu_items ( VikTrwLayer *l, GtkMenu *men
         g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_show_picture), pass_along );
         gtk_menu_shell_append ( GTK_MENU_SHELL(menu), item );
         gtk_widget_show ( item );
+
+#ifdef VIK_CONFIG_GEOTAG
+       GtkWidget *geotag_submenu = gtk_menu_new ();
+       item = gtk_image_menu_item_new_with_mnemonic ( _("Update Geotag on _Image") );
+       gtk_image_menu_item_set_image ( (GtkImageMenuItem*)item, gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU) );
+       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+       gtk_widget_show ( item );
+       gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), geotag_submenu );
+  
+       item = gtk_menu_item_new_with_mnemonic ( _("_Update") );
+       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_geotagging_waypoint_mtime_update), pass_along );
+       gtk_menu_shell_append (GTK_MENU_SHELL (geotag_submenu), item);
+       gtk_widget_show ( item );
+
+       item = gtk_menu_item_new_with_mnemonic ( _("Update and _Keep File Timestamp") );
+       g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_geotagging_waypoint_mtime_keep), pass_along );
+       gtk_menu_shell_append (GTK_MENU_SHELL (geotag_submenu), item);
+       gtk_widget_show ( item );
+#endif
       }
 
     }
@@ -4163,6 +4274,13 @@ static gboolean trw_layer_sublayer_add_menu_items ( VikTrwLayer *l, GtkMenu *men
       }
     }
 
+#ifdef VIK_CONFIG_GEOTAG
+  item = gtk_menu_item_new_with_mnemonic ( _("Geotag _Images...") );
+  g_signal_connect_swapped ( G_OBJECT(item), "activate", G_CALLBACK(trw_layer_geotagging_track), pass_along );
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+  gtk_widget_show ( item );
+#endif
+
     // Only show on viewport popmenu when a trackpoint is selected
     if ( ! vlp && l->current_tpl ) {
       // Add separator
@@ -5655,7 +5773,7 @@ static void thumbnail_create_thread_free ( thumbnail_create_thread_data *tctd )
   g_free ( tctd );
 }
 
-static void trw_layer_verify_thumbnails ( VikTrwLayer *vtl, GtkWidget *vp )
+void trw_layer_verify_thumbnails ( VikTrwLayer *vtl, GtkWidget *vp )
 {
   if ( ! vtl->has_verified_thumbnails )
   {
index 6df399ae02119802016e171803322fed26d4f660..11ea6de5a19e26bf6ef6d6cfbea5cec858e1460b 100644 (file)
@@ -71,4 +71,8 @@ void vik_trw_layer_delete_all_waypoints ( VikTrwLayer *vtl );
 void vik_trw_layer_delete_all_tracks ( VikTrwLayer *vtl );
 void trw_layer_cancel_tps_of_track ( VikTrwLayer *vtl, const gchar *trk_name );
 
+/* Exposed Layer Interface function definitions */
+// Intended only for use by other trw_layer subwindows
+void trw_layer_verify_thumbnails ( VikTrwLayer *vtl, GtkWidget *vp );
+
 #endif
diff --git a/src/viktrwlayer_geotag.c b/src/viktrwlayer_geotag.c
new file mode 100644 (file)
index 0000000..cab2d5e
--- /dev/null
@@ -0,0 +1,547 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
+ *
+ * Copyright (C) 2011, Rob Norris <rw_norris@hotmail.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+/*
+ *  Similar to the track and trackpoint properties dialogs,
+ *   this is made a separate file for ease of grouping related stuff together
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <math.h>
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "viking.h"
+#include "vikfilelist.h"
+#include "geotag_exif.h"
+#include "thumbnails.h"
+#include "background.h"
+
+// Function taken from GPSCorrelate 1.6.1
+// ConvertToUnixTime Copyright 2005 Daniel Foote. GPL2+
+
+#define EXIF_DATE_FORMAT "%d:%d:%d %d:%d:%d"
+
+time_t ConvertToUnixTime(char* StringTime, char* Format, int TZOffsetHours, int TZOffsetMinutes)
+{
+       /* Read the time using the specified format.
+        * The format and string being read from must
+        * have the most significant time on the left,
+        * and the least significant on the right:
+        * ie, Year on the left, seconds on the right. */
+
+       /* Sanity check... */
+       if (StringTime == NULL || Format == NULL)
+       {
+               return 0;
+       }
+
+       /* Define and set up our structure. */
+       struct tm Time;
+       Time.tm_wday = 0;
+       Time.tm_yday = 0;
+       Time.tm_isdst = -1;
+
+       /* Read out the time from the string using our format. */
+       sscanf(StringTime, Format, &Time.tm_year, &Time.tm_mon,
+                       &Time.tm_mday, &Time.tm_hour,
+                       &Time.tm_min, &Time.tm_sec);
+
+       /* Adjust the years for the mktime function to work. */
+       Time.tm_year -= 1900;
+       Time.tm_mon  -= 1;
+
+       /* Add our timezone offset to the time.
+        * We don't check to see if it overflowed anything;
+        * mktime does this and fixes it for us. */
+       /* Note also that we SUBTRACT these times. We want the
+        * result to be in UTC. */
+
+       Time.tm_hour -= TZOffsetHours;
+       Time.tm_min  -= TZOffsetMinutes;
+
+       /* Calculate and return the unix time. */
+       return mktime(&Time);
+}
+
+// GPSCorrelate END
+
+typedef struct {
+       GtkWidget *dialog;
+       VikFileList *files;
+       VikTrwLayer *vtl;    // to pass on
+       VikTrack *track;     // Use specified track or all tracks if NULL
+       GtkCheckButton *create_waypoints_b;
+       GtkCheckButton *write_exif_b;
+       GtkLabel *overwrite_gps_exif_l; // Referenced so the sensitivity can be changed
+       GtkCheckButton *overwrite_gps_exif_b;
+       GtkLabel *no_change_mtime_l; // Referenced so the sensitivity can be changed
+       GtkCheckButton *no_change_mtime_b;
+       GtkCheckButton *interpolate_segments_b;
+       GtkEntry *time_zone_b; // TODO consider a more user friendly tz widget eg libtimezonemap or similar
+       GtkEntry *time_offset_b;
+} GeoTagWidgets;
+
+static GeoTagWidgets *geotag_widgets_new()
+{
+       GeoTagWidgets *widgets = g_malloc0(sizeof(GeoTagWidgets));
+       return widgets;
+}
+
+static void geotag_widgets_free ( GeoTagWidgets *widgets )
+{
+       // Need to free VikFileList??
+       g_free(widgets);
+}
+
+typedef struct {
+       gboolean create_waypoints;
+       gboolean write_exif;
+       gboolean overwrite_gps_exif;
+       gboolean no_change_mtime;
+       gboolean interpolate_segments;
+       gint time_offset;
+       gint TimeZoneHours;
+       gint TimeZoneMins;
+} option_values_t;
+
+typedef struct {
+       VikTrwLayer *vtl;
+       gchar *image;
+       VikTrack *track;     // Use specified track or all tracks if NULL
+       // User options...
+       option_values_t ov;
+       GList *files;
+       time_t PhotoTime;
+       // Store answer from interpolation for an image
+       gboolean found_match;
+       VikCoord coord;
+       gdouble altitude;
+       // If anything has changed
+       gboolean redraw;
+} geotag_options_t;
+
+static option_values_t default_values = {
+       TRUE,
+       TRUE,
+       FALSE,
+       TRUE,
+       TRUE,
+       0,
+       0,
+       0,
+};
+
+/**
+ * Correlate the image against the specified track
+ */
+static void trw_layer_geotag_track ( const gchar *name, VikTrack *track, geotag_options_t *options )
+{
+       // If already found match then don't need to check this track
+       if ( options->found_match )
+               return;
+
+       VikTrackpoint *trkpt;
+       VikTrackpoint *trkpt_next;
+
+       GList *mytrkpt = track->trackpoints;
+       for ( mytrkpt = mytrkpt; mytrkpt; mytrkpt = mytrkpt->next ) {
+
+               // Do something for this trackpoint...
+
+               trkpt = VIK_TRACKPOINT(mytrkpt->data);
+
+               // is it exactly this point?
+               if ( options->PhotoTime == trkpt->timestamp ) {
+                       options->coord = trkpt->coord;
+                       options->altitude = trkpt->altitude;
+                       options->found_match = TRUE;
+                       break;
+               }
+
+               // Now need two trackpoints, hence check next is available
+               if ( !mytrkpt->next ) break;
+               trkpt_next = VIK_TRACKPOINT(mytrkpt->next->data);
+
+               // TODO need to use 'has_timestamp' property
+               if ( trkpt->timestamp == trkpt_next->timestamp ) continue;
+               if ( trkpt->timestamp > trkpt_next->timestamp ) continue;
+
+               // When interpolating between segments, no need for any special segment handling
+               if ( !options->ov.interpolate_segments )
+                       // Don't check between segments
+                       if ( trkpt_next->newsegment )
+                               // Simply move on to consider next point
+                               continue;
+
+               // Too far
+               if ( trkpt->timestamp > options->PhotoTime ) break;
+
+               // Is is between this and the next point?
+               if ( (options->PhotoTime > trkpt->timestamp) && (options->PhotoTime < trkpt_next->timestamp) ) {
+                       options->found_match = TRUE;
+                       // Interpolate
+                       /* Calculate the "scale": a decimal giving the relative distance
+                        * in time between the two points. Ie, a number between 0 and 1 -
+                        * 0 is the first point, 1 is the next point, and 0.5 would be
+                        * half way. */
+                       gdouble scale = (gdouble)trkpt_next->timestamp - (gdouble)trkpt->timestamp;
+                       scale = ((gdouble)options->PhotoTime - (gdouble)trkpt->timestamp) / scale;
+
+                       struct LatLon ll_result, ll1, ll2;
+
+                       vik_coord_to_latlon ( &(trkpt->coord), &ll1 );
+                       vik_coord_to_latlon ( &(trkpt_next->coord), &ll2 );
+
+                       ll_result.lat = ll1.lat + ((ll2.lat - ll1.lat) * scale);
+
+                       // NB This won't cope with going over the 180 degrees longitude boundary
+                       ll_result.lon = ll1.lon + ((ll2.lon - ll1.lon) * scale);
+
+                       // set coord
+                       vik_coord_load_from_latlon ( &(options->coord), VIK_COORD_LATLON, &ll_result );
+
+                       // Interpolate elevation
+                       options->altitude = trkpt->altitude + ((trkpt_next->altitude - trkpt->altitude) * scale);
+                       break;
+               }
+                       
+       }
+}
+
+/**
+ * Correlate the image to any track within the TrackWaypoint layer
+ */
+static void trw_layer_geotag_process ( geotag_options_t *options )
+{
+       if ( !options->vtl || !IS_VIK_LAYER(options->vtl) )
+               return;
+
+       if ( !options->image )
+               return;
+
+       gboolean has_gps_exif = FALSE;
+       gchar* datetime = a_geotag_get_exif_date_from_file ( options->image, &has_gps_exif );
+
+       if ( datetime ) {
+       
+               // If image already has gps info - don't attempt to change it.
+               if ( !options->ov.overwrite_gps_exif && has_gps_exif ) {
+                       if ( options->ov.create_waypoints ) {
+                               // Create waypoint with file information
+                               gchar *name = NULL;
+                               VikWaypoint *wp = a_geotag_create_waypoint_from_file ( options->image, vik_trw_layer_get_coord_mode (options->vtl), &name );
+                               if ( !name )
+                                       name = g_strdup ( a_file_basename ( options->image ) );
+                               vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp );
+                               g_free ( name );
+                               
+                               // Mark for redraw
+                               options->redraw = TRUE;
+                       }
+                       return;
+               }
+
+               options->PhotoTime = ConvertToUnixTime ( datetime, EXIF_DATE_FORMAT, options->ov.TimeZoneHours, options->ov.TimeZoneMins);
+               g_free ( datetime );
+               
+               // Apply any offset
+               options->PhotoTime = options->PhotoTime + options->ov.time_offset;
+
+               options->found_match = FALSE;
+
+               if ( options->track ) {
+                       // Single specified track
+                       // NB Doesn't care about track name
+                       trw_layer_geotag_track ( NULL, options->track, options );
+               }
+               else {
+                       // Try all tracks
+                       GHashTable *tracks = vik_trw_layer_get_tracks ( options->vtl );
+                       if ( g_hash_table_size (tracks) > 0 ) {
+                               g_hash_table_foreach ( tracks, (GHFunc) trw_layer_geotag_track, options );
+                       }
+               }
+
+               // Match found ?
+               if ( options->found_match ) {
+
+                       if ( options->ov.create_waypoints ) {
+
+                               // Create waypoint with found position
+                               gchar *name = NULL;
+                               VikWaypoint *wp = a_geotag_create_waypoint_positioned ( options->image, options->coord, options->altitude, &name );
+                               if ( !name )
+                                       name = g_strdup ( a_file_basename ( options->image ) );
+                               vik_trw_layer_filein_add_waypoint ( options->vtl, name, wp );
+                               g_free ( name );
+                               
+                               // Mark for redraw
+                               options->redraw = TRUE;
+                       }
+
+                       // Write EXIF if specified
+                       if ( options->ov.write_exif ) {
+                               a_geotag_write_exif_gps ( options->image, options->coord, options->altitude, options->ov.no_change_mtime );
+                       }
+               }
+       }
+}
+
+/*
+ * Tidy up
+ */
+static void trw_layer_geotag_thread_free ( geotag_options_t *gtd )
+{
+       if ( gtd->files )
+               g_list_free ( gtd->files );
+       g_free ( gtd );
+}
+
+/**
+ * Run geotagging process in a separate thread
+ */
+static int trw_layer_geotag_thread ( geotag_options_t *options, gpointer threaddata )
+{
+       guint total = g_list_length(options->files), done = 0;
+
+       // TODO decide how to report any issues to the user ...
+
+       // Foreach file attempt to geotag it
+       while ( options->files ) {
+               options->image = (gchar *) ( options->files->data );
+               trw_layer_geotag_process ( options );
+               options->files = options->files->next;
+
+               // Update thread progress and detect stop requests
+               int result = a_background_thread_progress ( threaddata, ((gdouble) ++done) / total );
+               if ( result != 0 )
+                       return -1; /* Abort thread */
+       }
+
+       if ( options->redraw ) {
+               if ( IS_VIK_LAYER(options->vtl) ) {
+                       // Ensure any new images get shown
+                       trw_layer_verify_thumbnails ( options->vtl, NULL ); // NB second parameter not used ATM
+                       // Force redraw as verify only redraws if there are new thumbnails (they may already exist)
+                       vik_layer_emit_update ( VIK_LAYER(options->vtl), TRUE ); // Update from background
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * Parse user input from dialog response
+ */
+static void trw_layer_geotag_response_cb ( GtkDialog *dialog, gint resp, GeoTagWidgets *widgets )
+{
+       switch (resp) {
+    case GTK_RESPONSE_DELETE_EVENT: /* received delete event (not from buttons) */
+    case GTK_RESPONSE_REJECT:
+               break;
+       default: {
+               //GTK_RESPONSE_ACCEPT:
+               // Get options
+               geotag_options_t *options = g_malloc ( sizeof(geotag_options_t) );
+               options->vtl = widgets->vtl;
+               options->track = widgets->track;
+               // Values extracted from the widgets:
+               options->ov.create_waypoints = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b) );
+               options->ov.write_exif = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->write_exif_b) );
+               options->ov.overwrite_gps_exif = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_gps_exif_b) );
+               options->ov.no_change_mtime = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->no_change_mtime_b) );
+               options->ov.interpolate_segments = gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(widgets->interpolate_segments_b) );
+               options->ov.TimeZoneHours = 0;
+               options->ov.TimeZoneMins = 0;
+               const gchar* TZString = gtk_entry_get_text(GTK_ENTRY(widgets->time_zone_b));
+               /* Check the string. If there is a colon, then (hopefully) it's a time in xx:xx format.
+                * If not, it's probably just a +/-xx format. In all other cases,
+                * it will be interpreted as +/-xx, which, if given a string, returns 0. */
+               if (strstr(TZString, ":")) {
+                       /* Found colon. Split into two. */
+                       sscanf(TZString, "%d:%d", &options->ov.TimeZoneHours, &options->ov.TimeZoneMins);
+                       if (options->ov.TimeZoneHours < 0)
+                               options->ov.TimeZoneMins *= -1;
+               } else {
+                       /* No colon. Just parse. */
+                       options->ov.TimeZoneHours = atoi(TZString);
+               }
+               options->ov.time_offset = atoi ( gtk_entry_get_text ( GTK_ENTRY(widgets->time_offset_b) ) );
+
+               options->redraw = FALSE;
+
+               // Save settings for reuse
+               default_values = options->ov;
+
+               options->files = g_list_copy ( vik_file_list_get_files ( widgets->files ) );
+
+               gint len = g_list_length ( options->files );
+               gchar *tmp = g_strdup_printf ( _("Geotagging %d Images..."), len );
+
+               // Processing lots of files can take time - so run a background effort
+               a_background_thread ( VIK_GTK_WINDOW_FROM_LAYER(options->vtl),
+                                                         tmp,
+                                                         (vik_thr_func) trw_layer_geotag_thread,
+                                                         options,
+                                                         (vik_thr_free_func) trw_layer_geotag_thread_free,
+                                                         NULL,
+                                                         len );
+
+               g_free ( tmp );
+
+               break;
+       }
+       }
+       geotag_widgets_free ( widgets );
+       gtk_widget_destroy ( GTK_WIDGET(dialog) );
+}
+
+/**
+ * Handle widget sensitivities
+ */
+static void write_exif_b_cb ( GtkWidget *gw, GeoTagWidgets *gtw )
+{
+       // Overwriting & file modification times are irrelevant if not going to write EXIF!
+       if ( gtk_toggle_button_get_active ( GTK_TOGGLE_BUTTON(gtw->write_exif_b) ) ) {
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_b), TRUE );
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_l), TRUE );
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_b), TRUE );
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_l), TRUE );
+       }
+       else {
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_b), FALSE );
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->overwrite_gps_exif_l), FALSE );
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_b), FALSE );
+               gtk_widget_set_sensitive ( GTK_WIDGET(gtw->no_change_mtime_l), FALSE );
+       }
+}
+
+
+/**
+ * trw_layer_geotag_dialog:
+ * @parent: The Window of the calling process
+ * @vtl: The VikTrwLayer to use for correlating images to tracks
+ * @track: Optional - The particular track to use (if specified) for correlating images
+ * @track_name: Optional - The name of specified track to use
+ */
+void trw_layer_geotag_dialog ( GtkWindow *parent, VikTrwLayer *vtl, VikTrack *track, const gchar *track_name )
+{
+       GeoTagWidgets *widgets = geotag_widgets_new();
+
+       widgets->dialog = gtk_dialog_new_with_buttons ( _("Geotag Images"),
+                                                                                                       parent,
+                                                                                                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                                                                                                       GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+                                                                                                       GTK_STOCK_OK,     GTK_RESPONSE_ACCEPT,
+                                                                                                       NULL );
+       widgets->files = VIK_FILE_LIST(vik_file_list_new ( _("Images") )); // TODO would be nice to be able to set a filefilter
+       widgets->vtl = vtl;
+       widgets->track = track;
+       widgets->create_waypoints_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
+       widgets->write_exif_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
+       widgets->overwrite_gps_exif_l = GTK_LABEL ( gtk_label_new ( _("Overwrite Existing GPS Information:") ) );
+       widgets->overwrite_gps_exif_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
+       widgets->no_change_mtime_l = GTK_LABEL ( gtk_label_new ( _("Keep File Modification Timestamp:") ) );
+       widgets->no_change_mtime_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
+       widgets->interpolate_segments_b = GTK_CHECK_BUTTON ( gtk_check_button_new () );
+       widgets->time_zone_b = GTK_ENTRY ( gtk_entry_new () );
+       widgets->time_offset_b = GTK_ENTRY ( gtk_entry_new () );
+
+       gtk_entry_set_width_chars ( widgets->time_zone_b, 7);
+       gtk_entry_set_width_chars ( widgets->time_offset_b, 7);
+
+       // Defaults - TODO restore previous values / save settings somewhere??
+       gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->create_waypoints_b), default_values.create_waypoints );
+       gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->write_exif_b), default_values.write_exif );
+       gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->overwrite_gps_exif_b), default_values.overwrite_gps_exif );
+       gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->no_change_mtime_b), default_values.no_change_mtime );
+       gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON(widgets->interpolate_segments_b), default_values.interpolate_segments );
+       gchar tmp_string[7];
+       snprintf (tmp_string, 7, "%+02d:%02d", default_values.TimeZoneHours, abs (default_values.TimeZoneMins) );
+       gtk_entry_set_text ( widgets->time_zone_b, tmp_string );
+       snprintf (tmp_string, 7, "%d", default_values.time_offset );
+       gtk_entry_set_text ( widgets->time_offset_b, tmp_string );
+
+       // Ensure sensitivities setup
+       write_exif_b_cb ( GTK_WIDGET(widgets->write_exif_b), widgets );
+
+       g_signal_connect ( G_OBJECT(widgets->write_exif_b), "toggled", G_CALLBACK(write_exif_b_cb), widgets );
+
+       GtkWidget *cw_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(cw_hbox), gtk_label_new ( _("Create Waypoints:") ), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(cw_hbox), GTK_WIDGET(widgets->create_waypoints_b), FALSE, FALSE, 5 );
+
+       GtkWidget *we_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(we_hbox), gtk_label_new ( _("Write EXIF:") ), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(we_hbox), GTK_WIDGET(widgets->write_exif_b), FALSE, FALSE, 5 );
+
+       GtkWidget *og_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(og_hbox), GTK_WIDGET(widgets->overwrite_gps_exif_l), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(og_hbox), GTK_WIDGET(widgets->overwrite_gps_exif_b), FALSE, FALSE, 5 );
+
+       GtkWidget *fm_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(fm_hbox), GTK_WIDGET(widgets->no_change_mtime_l), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(fm_hbox), GTK_WIDGET(widgets->no_change_mtime_b), FALSE, FALSE, 5 );
+
+       GtkWidget *is_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(is_hbox), gtk_label_new ( _("Interpolate Between Track Segments:") ), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(is_hbox), GTK_WIDGET(widgets->interpolate_segments_b), FALSE, FALSE, 5 );
+
+       GtkWidget *to_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(to_hbox), gtk_label_new ( _("Image Time Offset (Seconds):") ), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(to_hbox), GTK_WIDGET(widgets->time_offset_b), FALSE, FALSE, 5 );
+       gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->time_offset_b), _("The number of seconds to ADD to the photos time to make it match the GPS data. Calculate this with (GPS - Photo). Can be negative or positive. Useful to adjust times when a camera's timestamp was incorrect.") );
+
+       GtkWidget *tz_hbox = gtk_hbox_new ( FALSE, 0 );
+       gtk_box_pack_start ( GTK_BOX(tz_hbox), gtk_label_new ( _("Image Timezone:") ), FALSE, FALSE, 5 );
+       gtk_box_pack_start ( GTK_BOX(tz_hbox), GTK_WIDGET(widgets->time_zone_b), FALSE, FALSE, 5 );
+       gtk_widget_set_tooltip_text ( GTK_WIDGET(widgets->time_zone_b), _("The timezone that was used when the images were created. For example, if a camera is set to AWST or +8:00 hours. Enter +8:00 here so that the correct adjustment to the images' time can be made. GPS data is always in UTC.") );
+
+       gchar *track_string = NULL;
+       if ( widgets->track )
+               track_string = g_strdup_printf ( _("Using track: %s"), track_name );
+       else
+               track_string = g_strdup_printf ( _("Using all tracks in: %s"), VIK_LAYER(widgets->vtl)->name );
+
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), gtk_label_new ( track_string ), FALSE, FALSE, 5 );
+
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), GTK_WIDGET(widgets->files), TRUE, TRUE, 0 );
+
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), cw_hbox,  FALSE, FALSE, 0);
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), we_hbox,  FALSE, FALSE, 0);
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), og_hbox,  FALSE, FALSE, 0);
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), fm_hbox,  FALSE, FALSE, 0);
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), is_hbox,  FALSE, FALSE, 0);
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), to_hbox,  FALSE, FALSE, 0);
+       gtk_box_pack_start ( GTK_BOX(GTK_DIALOG(widgets->dialog)->vbox), tz_hbox,  FALSE, FALSE, 0);
+
+       g_signal_connect ( widgets->dialog, "response", G_CALLBACK(trw_layer_geotag_response_cb), widgets );
+
+       gtk_dialog_set_default_response ( GTK_DIALOG(widgets->dialog), GTK_RESPONSE_REJECT );
+
+       gtk_widget_show_all ( widgets->dialog );
+
+       g_free ( track_string );
+}
diff --git a/src/viktrwlayer_geotag.h b/src/viktrwlayer_geotag.h
new file mode 100644 (file)
index 0000000..8b856cb
--- /dev/null
@@ -0,0 +1,32 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
+ *
+ * Copyright (C) 2011, Rob Norris <rw_norris@hotmail.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#ifndef _VIKING_TRWLAYER_GEOTAG_H
+#define _VIKING_TRWLAYER_GEOTAG_H
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "viktrwlayer.h"
+
+// To be only called from within viktrwlayer
+void trw_layer_geotag_dialog ( GtkWindow *parent, VikTrwLayer *vtl, VikTrack *track, const gchar *track_name );
+
+#endif
index 1ea18f1549671e4040aa2446ae97ab90b227d697..51c51ba9d02f2cd7bf26f53733a0a58bc3c59dae 100644 (file)
@@ -1962,6 +1962,14 @@ static void acquire_from_gc ( GtkAction *a, VikWindow *vw )
 }
 #endif
 
+#ifdef VIK_CONFIG_GEOTAG
+static void acquire_from_geotag ( GtkAction *a, VikWindow *vw )
+{
+  vik_datasource_geotag_interface.mode = VIK_DATASOURCE_CREATENEWLAYER;
+  a_acquire(vw, vw->viking_vlp, vw->viking_vvp, &vik_datasource_geotag_interface );
+}
+#endif
+
 static void goto_default_location( GtkAction *a, VikWindow *vw)
 {
   struct LatLon ll;
@@ -2531,6 +2539,9 @@ static GtkActionEntry entries[] = {
 #endif
 #ifdef VIK_CONFIG_GEOCACHES
   { "AcquireGC",   NULL,                 N_("Geo_caches..."),            NULL,         N_("Get Geocaches from geocaching.com"),            (GCallback)acquire_from_gc       },
+#endif
+#ifdef VIK_CONFIG_GEOTAG
+  { "AcquireGeotag", NULL,               N_("From Geotagged _Images..."), NULL,         N_("Create waypoints from geotagged images"),       (GCallback)acquire_from_geotag   },
 #endif
   { "Save",      GTK_STOCK_SAVE,         N_("_Save"),                         "<control>S", N_("Save the file"),                                (GCallback)save_file             },
   { "SaveAs",    GTK_STOCK_SAVE_AS,      N_("Save _As..."),                      NULL,  N_("Save the file under different name"),           (GCallback)save_file_as          },