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