]> git.street.me.uk Git - andy/viking.git/blob - src/geotag_exif.c
Remove the now unused viewport alpha pixbuf and gdk pixbuf compositing method.
[andy/viking.git] / src / geotag_exif.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
4  *
5  * Copyright (C) 2011-2014, Rob Norris <rw_norris@hotmail.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  */
22
23 /*
24  * This uses EXIF information from images to create waypoints at those positions
25  *
26  * The initial implementation uses libexif, which keeps Viking a pure C program.
27  * Now libgexiv2 is available (in C as a wrapper around the more powerful libexiv2 [C++]) so this is the preferred build.
28  *  The attentative reader will notice the use of gexiv2 is a lot simpler as well.
29  * For the time being the libexif code + build is still made available.
30  */
31 #include <string.h>
32 #include "geotag_exif.h"
33 #include "config.h"
34 #include "globals.h"
35 #include "file.h"
36
37 #include <sys/stat.h>
38 #ifdef HAVE_UTIME_H
39 #include <utime.h>
40 #endif
41 #include <stdlib.h>
42 #include <ctype.h>
43 #include <math.h>
44 #include <glib/gi18n.h>
45 #include <glib/gstdio.h>
46 #ifdef HAVE_LIBGEXIV2
47 #include <gexiv2/gexiv2.h>
48 #endif
49 #ifdef HAVE_LIBEXIF
50 #include <libexif/exif-data.h>
51 #include "libjpeg/jpeg-data.h"
52 #endif
53
54 #ifdef HAVE_LIBGEXIV2
55 /**
56  * Attempt to get a single comment from the various exif fields
57  */
58 static gchar* geotag_get_exif_comment ( GExiv2Metadata *gemd )
59 {
60         //
61         // Try various options to create a comment
62         //
63         if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.ImageDescription" ) )
64                 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.ImageDescription" ) );
65
66         if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPComment" ) )
67                 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPComment" ) );
68
69         if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPSubject" ) )
70                 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPSubject" ) );
71
72         if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.DateTimeOriginal" ) )
73                 return g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.DateTimeOriginal" ) );
74
75         // Otherwise nothing found
76         return NULL;
77 }
78 #endif
79
80 #ifdef HAVE_LIBEXIF
81 /**
82  * Attempt to get a single comment from the various exif fields
83  */
84 static gchar* geotag_get_exif_comment ( ExifData *ed )
85 {
86         gchar str[128];
87         ExifEntry *ee;
88         //
89         // Try various options to create a comment
90         //
91         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_IMAGE_DESCRIPTION);
92         if ( ee ) {
93                 exif_entry_get_value ( ee, str, 128 );
94                 return g_strdup ( str );
95         }
96
97         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_COMMENT);
98         if ( ee ) {
99                 exif_entry_get_value ( ee, str, 128 );
100                 return g_strdup ( str );
101         }
102
103         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_SUBJECT);
104         if ( ee ) {
105                 exif_entry_get_value ( ee, str, 128 );
106                 return g_strdup ( str );
107         }
108
109         // Consider using these for existing GPS info??
110         //#define EXIF_TAG_GPS_TIME_STAMP        0x0007
111         //#define EXIF_TAG_GPS_DATE_STAMP         0x001d
112         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
113         if ( ee ) {
114                 exif_entry_get_value ( ee, str, 128 );
115                 return g_strdup ( str );
116         }
117
118         // Otherwise nothing found
119         return NULL;
120 }
121
122 /**
123  * Handles 3 part location Rationals
124  * Handles 1 part rational (must specify 0 for the offset)
125  */
126 static gdouble Rational2Double ( unsigned char *data, int offset, ExifByteOrder order )
127 {
128         // Explaination from GPS Correlate 'exif-gps.cpp' v 1.6.1
129         // What we are trying to do here is convert the three rationals:
130         //    dd/v mm/v ss/v
131         // To a decimal
132         //    dd.dddddd...
133         // dd/v is easy: result = dd/v.
134         // mm/v is harder:
135         //    mm
136         //    -- / 60 = result.
137         //     v
138         // ss/v is sorta easy.
139         //     ss
140         //     -- / 3600 = result
141         //      v
142         // Each part is added to the final number.
143         gdouble ans;
144         ExifRational er;
145         er = exif_get_rational (data, order);
146         ans = (gdouble)er.numerator / (gdouble)er.denominator;
147         if (offset <= 0)
148                 return ans;
149
150         er = exif_get_rational (data+(1*offset), order);
151         ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 60.0 );
152         er = exif_get_rational (data+(2*offset), order);
153         ans = ans + ( ( (gdouble)er.numerator / (gdouble)er.denominator ) / 3600.0 );
154
155         return ans;
156 }
157
158 static struct LatLon get_latlon ( ExifData *ed )
159 {
160         struct LatLon ll = { 0.0, 0.0 };
161         const struct LatLon ll0 = { 0.0, 0.0 };
162
163         gchar str[128];
164         ExifEntry *ee;
165         //
166         // Lat & Long is necessary to form a waypoint.
167         //
168         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
169         if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
170                 return ll0;
171
172         ll.lat = Rational2Double ( ee->data,
173                                                            exif_format_get_size(ee->format),
174                                                            exif_data_get_byte_order(ed) );
175
176         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE_REF);
177         if ( ee ) {
178                 exif_entry_get_value ( ee, str, 128 );
179                 if ( str[0] == 'S' )
180                         ll.lat = -ll.lat;
181         }
182
183         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
184         if ( ! ( ee && ee->components == 3 && ee->format == EXIF_FORMAT_RATIONAL ) )
185                 return ll0;
186
187         ll.lon = Rational2Double ( ee->data,
188                                                            exif_format_get_size(ee->format),
189                                                            exif_data_get_byte_order(ed) );
190
191         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE_REF);
192         if ( ee ) {
193                 exif_entry_get_value ( ee, str, 128 );
194                 if ( str[0] == 'W' )
195                         ll.lon = -ll.lon;
196         }
197
198         return ll;
199 }
200 #endif
201
202 /**
203  * a_geotag_get_position:
204  *
205  * @filename: The (JPG) file with EXIF information in it
206  *
207  * Returns: The position in LatLon format.
208  *  It will be 0,0 if some kind of failure occurs.
209  */
210 struct LatLon a_geotag_get_position ( const gchar *filename )
211 {
212         struct LatLon ll = { 0.0, 0.0 };
213
214 #ifdef HAVE_LIBGEXIV2
215         GExiv2Metadata *gemd = gexiv2_metadata_new ();
216         if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
217                 gdouble lat;
218                 gdouble lon;
219                 gdouble alt;
220                 if ( gexiv2_metadata_get_gps_info ( gemd, &lon, &lat, &alt ) ) {
221                         ll.lat = lat;
222                         ll.lon = lon;
223                 }
224         }
225         gexiv2_metadata_free  ( gemd );
226 #else
227 #ifdef HAVE_LIBEXIF
228         // open image with libexif
229         ExifData *ed = exif_data_new_from_file ( filename );
230
231         // Detect EXIF load failure
232         if ( !ed )
233                 return ll;
234
235         ExifEntry *ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
236         // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
237         if ( ! ( ee && ee->components == 4 ) )
238                 goto MyReturn0;
239
240         ll = get_latlon ( ed );
241
242 MyReturn0:
243         // Finished with EXIF
244         exif_data_free ( ed );
245 #endif
246 #endif
247
248         return ll;
249 }
250
251 /**
252  * a_geotag_create_waypoint_from_file:
253  * @filename: The image file to process
254  * @vcmode:   The current location mode to use in the positioning of Waypoint
255  * @name:     Returns a name for the Waypoint (can be NULL)
256  *
257  * Returns: An allocated Waypoint or NULL if Waypoint could not be generated (e.g. no EXIF info)
258  *
259  */
260 VikWaypoint* a_geotag_create_waypoint_from_file ( const gchar *filename, VikCoordMode vcmode, gchar **name )
261 {
262         // Default return values (for failures)
263         *name = NULL;
264         VikWaypoint *wp = NULL;
265
266 #ifdef HAVE_LIBGEXIV2
267         GExiv2Metadata *gemd = gexiv2_metadata_new ();
268         if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
269                 gdouble lat;
270                 gdouble lon;
271                 gdouble alt;
272                 if ( gexiv2_metadata_get_gps_info ( gemd, &lon, &lat, &alt ) ) {
273                         struct LatLon ll;
274                         ll.lat = lat;
275                         ll.lon = lon;
276
277                         //
278                         // Now create Waypoint with acquired information
279                         //
280                         wp = vik_waypoint_new();
281                         wp->visible = TRUE;
282                         // Set info from exif values
283                         // Location
284                         vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
285                         // Altitude
286                         wp->altitude = alt;
287
288                         if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPTitle" ) )
289                                 *name = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPTitle" ) );
290                         wp->comment = geotag_get_exif_comment ( gemd );
291
292                         vik_waypoint_set_image ( wp, filename );
293                 }
294         }
295         gexiv2_metadata_free ( gemd );
296 #else
297 #ifdef HAVE_LIBEXIF
298         // TODO use log?
299         //ExifLog *log = NULL;
300
301         // open image with libexif
302         ExifData *ed = exif_data_new_from_file ( filename );
303
304         // Detect EXIF load failure
305         if ( !ed )
306                 // return with no Waypoint
307                 return wp;
308
309         struct LatLon ll;
310
311         gchar str[128];
312         ExifEntry *ee;
313
314         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
315         // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
316         if ( ! ( ee && ee->components == 4 ) )
317                 goto MyReturn;
318         // Could test for these versions explicitly but may have byte order issues...
319         //if ( ! ( ee->data[0] == 2 && ee->data[2] == 0 && ee->data[3] == 0 ) )
320         //      goto MyReturn;
321
322         ll = get_latlon ( ed );
323
324         // Hopefully won't have valid images at 0,0!
325         if ( ll.lat == 0.0 && ll.lon == 0.0 )
326                 goto MyReturn;
327
328         //
329         // Not worried if none of the other fields exist, as can default the values to something
330         //
331
332         gdouble alt = VIK_DEFAULT_ALTITUDE;
333         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE);
334         if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_RATIONAL ) {
335                 alt = Rational2Double ( ee->data,
336                                                                 0,
337                                                                 exif_data_get_byte_order(ed) );
338
339                 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_ALTITUDE_REF);
340                 if ( ee && ee->components == 1 && ee->format == EXIF_FORMAT_BYTE && ee->data[0] == 1 )
341                         alt = -alt;
342         }
343
344         // Name
345         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
346         if ( ee ) {
347                 exif_entry_get_value ( ee, str, 128 );
348                 *name = g_strdup ( str );
349         }
350
351         //
352         // Now create Waypoint with acquired information
353         //
354         wp = vik_waypoint_new();
355         wp->visible = TRUE;
356         // Set info from exif values
357         // Location
358         vik_coord_load_from_latlon ( &(wp->coord), vcmode, &ll );
359         // Altitude
360         wp->altitude = alt;
361
362         wp->comment = geotag_get_exif_comment ( ed );
363
364         vik_waypoint_set_image ( wp, filename );
365
366 MyReturn:
367         // Finished with EXIF
368         exif_data_free ( ed );
369 #endif
370 #endif
371
372         return wp;
373 }
374
375 /**
376  * a_geotag_waypoint_positioned:
377  * @filename: The image file to process
378  * @coord:    The location for positioning the Waypoint
379  * @name:     Returns a name for the Waypoint (can be NULL)
380  * @waypoint: An existing waypoint to update (can be NULL to generate a new waypoint)
381  *
382  * Returns: An allocated waypoint if the input waypoint is NULL,
383  *  otherwise the passed in waypoint is updated
384  *
385  *  Here EXIF processing is used to get non position related information (i.e. just the comment)
386  *
387  */
388 VikWaypoint* a_geotag_waypoint_positioned ( const gchar *filename, VikCoord coord, gdouble alt, gchar **name, VikWaypoint *wp )
389 {
390         *name = NULL;
391         if ( wp == NULL ) {
392                 // Need to create waypoint
393                 wp = vik_waypoint_new();
394                 wp->visible = TRUE;
395         }
396         wp->coord = coord;
397         wp->altitude = alt;
398
399 #ifdef HAVE_LIBGEXIV2
400         GExiv2Metadata *gemd = gexiv2_metadata_new ();
401         if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
402                         wp->comment = geotag_get_exif_comment ( gemd );
403                         if ( gexiv2_metadata_has_tag ( gemd, "Exif.Image.XPTitle" ) )
404                                 *name = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.XPTitle" ) );
405         }
406         gexiv2_metadata_free ( gemd );
407 #else
408 #ifdef HAVE_LIBEXIF
409         ExifData *ed = exif_data_new_from_file ( filename );
410
411         // Set info from exif values
412         if ( ed ) {
413                 wp->comment = geotag_get_exif_comment ( ed );
414
415                 gchar str[128];
416                 ExifEntry *ee;
417                 // Name
418                 ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], EXIF_TAG_XP_TITLE);
419                 if ( ee ) {
420                         exif_entry_get_value ( ee, str, 128 );
421                         *name = g_strdup ( str );
422                 }
423
424                 // Finished with EXIF
425                 exif_data_free ( ed );
426         }
427 #endif
428 #endif
429
430         vik_waypoint_set_image ( wp, filename );
431
432         return wp;
433 }
434
435 /**
436  * a_geotag_get_exif_date_from_file:
437  * @filename: The image file to process
438  * @has_GPS_info: Returns whether the file has existing GPS information
439  *
440  * Returns: An allocated string with the date and time in EXIF_DATE_FORMAT, otherwise NULL if some kind of failure
441  *
442  *  Here EXIF processing is used to get time information
443  *
444  */
445 gchar* a_geotag_get_exif_date_from_file ( const gchar *filename, gboolean *has_GPS_info )
446 {
447         gchar* datetime = NULL;
448         *has_GPS_info = FALSE;
449
450 #ifdef HAVE_LIBGEXIV2
451         GExiv2Metadata *gemd = gexiv2_metadata_new ();
452         if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
453                 gdouble lat, lon;
454                 *has_GPS_info = ( gexiv2_metadata_get_gps_longitude(gemd,&lon) && gexiv2_metadata_get_gps_latitude(gemd,&lat) );
455
456                 // Prefer 'Photo' version over 'Image'
457                 if ( gexiv2_metadata_has_tag ( gemd, "Exif.Photo.DateTimeOriginal" ) )
458                         datetime = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Photo.DateTimeOriginal" ) );
459                 else
460                         datetime = g_strdup ( gexiv2_metadata_get_tag_interpreted_string ( gemd, "Exif.Image.DateTimeOriginal" ) );
461         }
462         gexiv2_metadata_free ( gemd );
463 #else
464 #ifdef HAVE_LIBEXIF
465         ExifData *ed = exif_data_new_from_file ( filename );
466
467         // Detect EXIF load failure
468         if ( !ed )
469                 return datetime;
470
471         gchar str[128];
472         ExifEntry *ee;
473
474         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL);
475         if ( ee ) {
476                 exif_entry_get_value ( ee, str, 128 );
477                 datetime = g_strdup ( str );
478         }
479
480         // Check GPS Info
481
482         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_VERSION_ID);
483         // Confirm this has a GPS Id - normally "2.0.0.0" or "2.2.0.0"
484         if ( ee && ee->components == 4 )
485                 *has_GPS_info = TRUE;
486
487         // Check other basic GPS fields exist too
488         // I have encountered some images which have just the EXIF_TAG_GPS_VERSION_ID but nothing else
489         // So to confirm check more EXIF GPS TAGS:
490         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LATITUDE);
491         if ( !ee )
492                 *has_GPS_info = FALSE;
493         ee = exif_content_get_entry (ed->ifd[EXIF_IFD_GPS], EXIF_TAG_GPS_LONGITUDE);
494         if ( !ee )
495                 *has_GPS_info = FALSE;
496
497         exif_data_free ( ed );
498 #endif
499 #endif
500         return datetime;
501 }
502
503
504 #ifdef HAVE_LIBEXIF
505 /**! If the entry doesn't exist, create it.
506  * Based on exif command line action_create_value function in exif 0.6.20
507  */
508 static ExifEntry* my_exif_create_value (ExifData *ed, ExifTag tag, ExifIfd ifd)
509 {
510         ExifEntry *e = exif_content_get_entry (ed->ifd[ifd], tag);
511         if ( !e ) {
512             e = exif_entry_new ();
513             exif_content_add_entry (ed->ifd[ifd], e);
514
515                 exif_entry_initialize (e, tag);
516
517                 // exif_entry_initialize doesn't seem to do much, especially for the GPS tags
518                 //   so have to setup fields ourselves:
519                 e->tag = tag;
520
521                 if ( tag == EXIF_TAG_GPS_VERSION_ID ) {
522                         e->format = EXIF_FORMAT_BYTE;
523                         e->components = 4;
524                         e->size = sizeof (char) * e->components;
525                         if ( e->data )
526                                 g_free (e->data);
527                         e->data = g_malloc (e->size);
528                 }
529                 if ( tag == EXIF_TAG_GPS_MAP_DATUM ||
530                          tag == EXIF_TAG_GPS_LATITUDE_REF || tag == EXIF_TAG_GPS_LONGITUDE_REF ||
531                          tag == EXIF_TAG_GPS_PROCESSING_METHOD ) {
532                         e->format = EXIF_FORMAT_ASCII;
533                         // NB Allocation is handled later on when the actual string used is known
534                 }
535                 if ( tag == EXIF_TAG_GPS_LATITUDE || tag == EXIF_TAG_GPS_LONGITUDE ) {
536                         e->format = EXIF_FORMAT_RATIONAL;
537                         e->components = 3;
538                         e->size = sizeof (ExifRational) * e->components;
539                         if ( e->data )
540                                 g_free (e->data);
541                         e->data = g_malloc (e->size);
542                 }
543                 if ( tag == EXIF_TAG_GPS_ALTITUDE ) {
544                         e->format = EXIF_FORMAT_RATIONAL;
545                         e->components = 1;
546                         e->size = sizeof (ExifRational) * e->components;
547                         if ( e->data )
548                                 g_free (e->data);
549                         e->data = g_malloc (e->size);
550                 }
551                 if ( tag == EXIF_TAG_GPS_ALTITUDE_REF ) {
552                         e->components = 1;
553                         e->size = sizeof (char) * e->components;
554                         if ( e->data )
555                                 g_free (e->data);
556                         e->data = g_malloc (e->size);
557                 }
558             /* The entry has been added to the IFD, so we can unref it */
559             //exif_entry_unref(e);
560                 // Crashes later on, when saving to jpeg if the above unref is enabled!!
561                 // ?Some other malloc problem somewhere?
562         }
563         return e;
564 }
565
566 /** Heavily based on convert_arg_to_entry from exif command line tool.
567  *  But without ExifLog, exitting, use of g_* io functions
568  *   and can take a gdouble value instead of a string
569  */
570 static void convert_to_entry (const char *set_value, gdouble gdvalue, ExifEntry *e, ExifByteOrder o)
571 {
572         unsigned int i, numcomponents;
573         char *value_p = NULL;
574         char *buf = NULL;
575         /*
576          * ASCII strings are handled separately,
577          * since they don't require any conversion.
578          */
579         if (e->format == EXIF_FORMAT_ASCII ||
580             e->tag == EXIF_TAG_USER_COMMENT) {
581                 if (e->data) g_free (e->data);
582                 e->components = strlen (set_value) + 1;
583                 if (e->tag == EXIF_TAG_USER_COMMENT)
584                         e->components += 8 - 1;
585                 e->size = sizeof (char) * e->components;
586                 e->data = g_malloc (e->size);
587                 if (!e->data) {
588                         g_warning (_("Not enough memory."));
589                         return;
590                 }
591                 if (e->tag == EXIF_TAG_USER_COMMENT) {
592                         /* assume ASCII charset */
593                         /* TODO: get this from the current locale */
594                         memcpy ((char *) e->data, "ASCII\0\0\0", 8);
595                         memcpy ((char *) e->data + 8, set_value,
596                                         strlen (set_value));
597                 } else
598                         strcpy ((char *) e->data, set_value);
599                 return;
600         }
601
602         /*
603          * Make sure we can handle this entry
604          */
605         if ((e->components == 0) && *set_value) {
606                 g_warning (_("Setting a value for this tag is unsupported!"));
607                 return;
608         }
609
610         gboolean use_string = (set_value != NULL);
611         if ( use_string ) {
612                 /* Copy the string so we can modify it */
613                 buf = g_strdup (set_value);
614                 if (!buf)
615                         return;
616                 value_p = strtok (buf, " ");
617         }
618
619         numcomponents = e->components;
620         for (i = 0; i < numcomponents; ++i) {
621                 unsigned char s;
622
623                 if ( use_string ) {
624                         if (!value_p) {
625                                 g_warning (_("Too few components specified (need %d, found %d)\n"), numcomponents, i);
626                                 return;
627                         }
628                         if (!isdigit(*value_p) && (*value_p != '+') && (*value_p != '-')) {
629                                 g_warning (_("Numeric value expected\n"));
630                                 return;
631                         }
632                 }
633
634                 s = exif_format_get_size (e->format);
635                 switch (e->format) {
636                 case EXIF_FORMAT_ASCII:
637                         g_warning (_("This shouldn't happen!"));
638                         return;
639                         break;
640                 case EXIF_FORMAT_SHORT:
641                         exif_set_short (e->data + (s * i), o, atoi (value_p));
642                         break;
643                 case EXIF_FORMAT_SSHORT:
644                         exif_set_sshort (e->data + (s * i), o, atoi (value_p));
645                         break;
646                 case EXIF_FORMAT_RATIONAL: {
647                         ExifRational er;
648
649                         double val = 0.0 ;
650                         if ( use_string && value_p )
651                                 val = fabs (atol (value_p));
652                         else
653                                 val = fabs (gdvalue);
654
655                         if ( i == 0 ) {
656                                 // One (or first) part rational
657
658                                 // Sneak peek into tag as location tags need rounding down to give just the degrees part
659                                 if ( e->tag == EXIF_TAG_GPS_LATITUDE || e->tag == EXIF_TAG_GPS_LONGITUDE ) {
660                                         er.numerator = (ExifLong) floor ( val );
661                                         er.denominator = 1.0;
662                                 }
663                                 else {
664                                         // I don't see any point in doing anything too complicated here,
665                                         //   such as trying to work out the 'best' denominator
666                                         // For the moment use KISS principle.
667                                         // Fix a precision of 1/100 metre as that's more than enough for GPS accuracy especially altitudes!
668                                         er.denominator = 100.0;
669                                         er.numerator = (ExifLong) (val * er.denominator);
670                                 }
671                         }
672
673                         // Now for Location 3 part rationals do Mins and Seconds format
674
675                         // Rounded down minutes
676                         if ( i == 1 ) {
677                                 er.denominator = 1.0;
678                                 er.numerator = (ExifLong) ( (int) floor ( ( val - floor (val) ) * 60.0 ) );
679                         }
680
681                         // Finally seconds
682                         if ( i == 2 ) {
683                                 er.denominator = 100.0;
684
685                                 // Fractional minute.
686                                 double FracPart = ((val - floor(val)) * 60) - (double)(int) floor ( ( val - floor (val) ) * 60.0 );
687                                 er.numerator = (ExifLong) ( (int)floor(FracPart * 6000) ); // Convert to seconds.
688                         }
689                         exif_set_rational (e->data + (s * i), o, er );
690                         break;
691                 }
692                 case EXIF_FORMAT_LONG:
693                         exif_set_long (e->data + (s * i), o, atol (value_p));
694                         break;
695                 case EXIF_FORMAT_SLONG:
696                         exif_set_slong (e->data + (s * i), o, atol (value_p));
697                         break;
698                 case EXIF_FORMAT_BYTE:
699                 case EXIF_FORMAT_SBYTE:
700                 case EXIF_FORMAT_UNDEFINED: /* treat as byte array */
701                         e->data[s * i] = atoi (value_p);
702                         break;
703                 case EXIF_FORMAT_FLOAT:
704                 case EXIF_FORMAT_DOUBLE:
705                 case EXIF_FORMAT_SRATIONAL:
706                 default:
707                         g_warning (_("Not yet implemented!"));
708                         return;
709                 }
710                 
711                 if ( use_string )
712                         value_p = strtok (NULL, " ");
713
714         }
715
716         g_free (buf);
717
718         if ( use_string )
719                 if ( value_p )
720                         g_warning (_("Warning; Too many components specified!"));
721 }
722 #endif
723
724 /**
725  * a_geotag_write_exif_gps:
726  * @filename: The image file to save information in
727  * @coord:    The location
728  * @alt:      The elevation
729  *
730  * Returns: A value indicating success: 0, or some other value for failure
731  *
732  */
733 gint a_geotag_write_exif_gps ( const gchar *filename, VikCoord coord, gdouble alt, gboolean no_change_mtime )
734 {
735         gint result = 0; // OK so far...
736
737         // Save mtime for later use
738         struct stat stat_save;
739         if ( no_change_mtime )
740                 if ( stat ( filename, &stat_save ) != 0 )
741                         g_warning ( "%s couldn't read: %s", __FUNCTION__, filename );
742
743 #ifdef HAVE_LIBGEXIV2
744         GExiv2Metadata *gemd = gexiv2_metadata_new ();
745         if ( gexiv2_metadata_open_path ( gemd, filename, NULL ) ) {
746                 struct LatLon ll;
747                 vik_coord_to_latlon ( &coord, &ll );
748                 if ( ! gexiv2_metadata_set_gps_info ( gemd, ll.lon, ll.lat, alt ) ) {
749                         result = 1; // Failed
750                 }
751                 else {
752                         GError *error = NULL;
753                         if ( ! gexiv2_metadata_save_file ( gemd, filename, &error ) ) {
754                                 result = 2;
755                                 g_warning ( "Write EXIF failure:%s" , error->message );
756                                 g_error_free ( error );
757                         }
758                 }
759         }
760         gexiv2_metadata_free ( gemd );
761 #else
762 #ifdef HAVE_LIBEXIF
763         /*
764           Appears libexif doesn't actually support writing EXIF data directly to files
765           Thus embed command line exif writing method within Viking
766           (for example this is done by Enlightment - http://www.enlightenment.org/ )
767           This appears to be JPEG only, but is probably 99% of our use case
768           Alternatively consider using libexiv2 and C++...
769         */
770
771         // Actual EXIF settings here...
772         JPEGData *jdata;
773
774         /* Parse the JPEG file. */
775         jdata = jpeg_data_new ();
776         jpeg_data_load_file (jdata, filename);
777
778         // Get current values
779         ExifData *ed = exif_data_new_from_file ( filename );
780         if ( !ed )
781                 ed = exif_data_new ();
782
783         // Update ExifData with our new settings
784         ExifEntry *ee;
785         //
786         // 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
787         // (unless of course there is some bug in the setting of the ID, that prevents subsequent tags)
788         //
789
790         ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE, EXIF_IFD_GPS);
791         convert_to_entry ( NULL, alt, ee, exif_data_get_byte_order(ed) );
792
793         // byte 0 meaning "sea level" or 1 if the value is negative.
794         ee = my_exif_create_value (ed, EXIF_TAG_GPS_ALTITUDE_REF, EXIF_IFD_GPS);
795         convert_to_entry ( alt < 0.0 ? "1" : "0", 0.0, ee, exif_data_get_byte_order(ed) );
796
797         ee = my_exif_create_value (ed, EXIF_TAG_GPS_PROCESSING_METHOD, EXIF_IFD_GPS);
798         // see http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/GPS.html
799         convert_to_entry ( "MANUAL", 0.0, ee, exif_data_get_byte_order(ed) );
800
801         ee = my_exif_create_value (ed, EXIF_TAG_GPS_MAP_DATUM, EXIF_IFD_GPS);
802         convert_to_entry ( "WGS-84", 0.0, ee, exif_data_get_byte_order(ed) );
803
804         struct LatLon ll;
805     vik_coord_to_latlon ( &coord, &ll );
806
807         ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE_REF, EXIF_IFD_GPS);
808         // N or S
809         convert_to_entry ( ll.lat < 0.0 ? "S" : "N", 0.0, ee, exif_data_get_byte_order(ed) );
810
811         ee = my_exif_create_value (ed, EXIF_TAG_GPS_LATITUDE, EXIF_IFD_GPS);
812         convert_to_entry ( NULL, ll.lat, ee, exif_data_get_byte_order(ed) );
813
814         ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE_REF, EXIF_IFD_GPS);
815         // E or W
816         convert_to_entry ( ll.lon < 0.0 ? "W" : "E", 0.0, ee, exif_data_get_byte_order(ed) );
817
818         ee = my_exif_create_value (ed, EXIF_TAG_GPS_LONGITUDE, EXIF_IFD_GPS);
819         convert_to_entry ( NULL, ll.lon, ee, exif_data_get_byte_order(ed) );
820
821         ee = my_exif_create_value (ed, EXIF_TAG_GPS_VERSION_ID, EXIF_IFD_GPS);
822         //convert_to_entry ( "2 0 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
823         convert_to_entry ( "2 2 0 0", 0.0, ee, exif_data_get_byte_order(ed) );
824
825         jpeg_data_set_exif_data (jdata, ed);
826
827         if ( jdata ) {
828                 /* Save the modified image. */
829                 result = jpeg_data_save_file (jdata, filename);
830
831                 // Convert result from 1 for success, 0 for failure into our scheme
832                 result = !result;
833                 
834                 jpeg_data_unref (jdata);
835         }
836         else {
837                 // Epic fail - file probably not a JPEG
838                 result = 2;
839         }
840
841         exif_data_free ( ed );
842 #endif
843 #endif
844
845         if ( no_change_mtime ) {
846                 // Restore mtime, using the saved value
847                 struct stat stat_tmp;
848                 struct utimbuf utb;
849                 (void)stat ( filename, &stat_tmp );
850                 utb.actime = stat_tmp.st_atime;
851                 utb.modtime = stat_save.st_mtime;
852                 // Not security critical, thus potential Time of Check Time of Use race condition is not bad
853                 // coverity[toctou]
854                 if ( g_utime ( filename, &utb ) != 0 )
855                         g_warning ( "%s couldn't set time on: %s", __FUNCTION__, filename );
856         }
857
858         return result;
859 }