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