Skip to content

Commit 37cb8f2

Browse files
committed
Use time zone tag from photo when available
If a time zone is found in an image, use that in preference to an automatically-determined time zone. If a manually-specified time zone is given, that takes precedence over all. Only the German translations are fully updated. Fixes #24
1 parent a699abc commit 37cb8f2

40 files changed

+1219
-544
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ version of the program.
4242
* The resolution of GPX positions is down to one second, but sub-second photo
4343
time is used (when available in the photo) to more accurately estimate the
4444
camera position between those points.
45+
* A time zone tag embedded in the photo is used to determine the local time of
46+
the image when found, otherwise the time zone is assumed to be same as the
47+
local machine.
4548
* For best results, you should synchronise your camera to the GPS time before
4649
you start taking photos. Note: digital cameras clocks drift quickly - even
4750
over a short period of time (say, a week).

RELEASES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ v2.x: X
55
- When interpolation is disabled, round up when exactly half way
66
- Allow fractional times in --photooffset
77
- Fix handling of --timeadd values between -1:00 and 0:00
8+
- Read time zone from photos when available or --no-photo-tz to disable
89

910
v2.2: 17 October 2024
1011
- Fix metainfo nits

correlate.c

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void SetAutoTimeZoneOptions(const char *Time,
6565
/* PhotoTime isn't a true epoch time, but is rather out
6666
* by the local offset from UTC */
6767
struct timespec PhotoTime =
68-
ConvertToUnixTime(Time, EXIF_DATE_FORMAT, 0, 0);
68+
ConvertToUnixTime(Time, EXIF_DATE_FORMAT, 0);
6969

7070
/* Extract the component time values */
7171
struct tm *PhotoTm = gmtime(&PhotoTime.tv_sec);
@@ -82,11 +82,18 @@ void SetAutoTimeZoneOptions(const char *Time,
8282

8383
/* Convert a time into Unixtime with the configured time zone conversion. */
8484
struct timespec ConvertTimeToUnixTime(const char *Time, const char *TimeFormat,
85-
const struct CorrelateOptions* Options)
85+
long OffsetTime, long *UsedOffset, const struct CorrelateOptions* Options)
8686
{
87-
struct timespec PhotoTime =
88-
ConvertToUnixTime(Time, TimeFormat,
89-
Options->TimeZoneHours, Options->TimeZoneMins);
87+
long TZOffset;
88+
if (Options->TimeZoneFromPhoto && OffsetTime != NO_OFFSET_TIME)
89+
/* Use the photo's time zone */
90+
TZOffset = OffsetTime;
91+
else
92+
/* Use the automatic or manually-specified time zone */
93+
TZOffset = Options->TimeZoneHours * 3600 + Options->TimeZoneMins * 60;
94+
if (UsedOffset)
95+
*UsedOffset = TZOffset;
96+
struct timespec PhotoTime = ConvertToUnixTime(Time, TimeFormat, TZOffset);
9097

9198
/* Add the PhotoOffset time. This is to make the Photo time match
9299
* the GPS time - i.e., it is (GPS - Photo). */
@@ -112,11 +119,14 @@ struct timespec ConvertTimeToUnixTime(const char *Time, const char *TimeFormat,
112119
* the files - ie, just correlate and keep into memory... */
113120

114121
struct GPSPoint* CorrelatePhoto(const char* Filename,
115-
struct CorrelateOptions* Options)
122+
long *UsedOffset, struct CorrelateOptions* Options)
116123
{
124+
*UsedOffset = NO_OFFSET_TIME;
117125
/* Read out the timestamp from the EXIF data. */
118126
int IncludesGPS = 0;
119-
char* TimeTemp = ReadExifData(Filename, &IncludesGPS, NULL, NULL, NULL);
127+
long OffsetTime = 0;
128+
char* TimeTemp = ReadExifData(Filename, &IncludesGPS, NULL, NULL, NULL,
129+
&OffsetTime);
120130
if (!TimeTemp)
121131
{
122132
/* Error reading the time from the file. Abort. */
@@ -141,10 +151,11 @@ struct GPSPoint* CorrelatePhoto(const char* Filename,
141151
SetAutoTimeZoneOptions(TimeTemp, Options);
142152
Options->AutoTimeZone = 0;
143153
}
144-
//printf("Using offset %02d:%02d\n", Options->TimeZoneHours, Options->TimeZoneMins);
154+
//printf("Using offset %ld sec.\n", OffsetTime);
145155

146156
/* Now convert the time into Unixtime with the configured time zone conversion. */
147-
struct timespec PhotoTime = ConvertTimeToUnixTime(TimeTemp, EXIF_DATE_FORMAT, Options);
157+
struct timespec PhotoTime = ConvertTimeToUnixTime(TimeTemp, EXIF_DATE_FORMAT, OffsetTime,
158+
UsedOffset, Options);
148159

149160
/* Free the memory for the time string - it won't otherwise
150161
* be freed for us. */

correlate.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct CorrelateOptions {
3636
int TimeZoneHours; /* To add to photos to make them UTC. */
3737
int TimeZoneMins;
3838
int AutoTimeZone;
39+
int TimeZoneFromPhoto;
3940
int FeatherTime;
4041
int WriteHeading;
4142
int HeadingOffset;
@@ -81,8 +82,8 @@ struct CorrelateOptions {
8182

8283

8384
struct GPSPoint* CorrelatePhoto(const char* Filename,
84-
struct CorrelateOptions* Options);
85+
long *UsedOffset, struct CorrelateOptions* Options);
8586
void SetAutoTimeZoneOptions(const char *TimeTemp,
8687
struct CorrelateOptions* Options);
8788
struct timespec ConvertTimeToUnixTime(const char *TimeTemp, const char *TimeFormat,
88-
const struct CorrelateOptions* Options);
89+
long OffsetTime, long *UsedOffset, const struct CorrelateOptions* Options);

doc/gpscorrelate-manpage.xml.in

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,32 @@
4949
<refsynopsisdiv>
5050
<cmdsynopsis>
5151
<command>&dhpackage;</command>
52+
<group>
53+
<arg choice="req">
54+
-g <replaceable>file.gpx</replaceable>
55+
</arg>
56+
57+
<arg choice="req">
58+
<group>
59+
<arg choice="plain">-l</arg>
60+
<arg choice="plain">--latlong
61+
<replaceable>latitude,longitude<group><arg choice="plain">,elevation</arg></group></replaceable>
62+
</arg>
63+
</group>
64+
</arg>
65+
</group>
66+
5267
<group>
5368
<arg choice="plain">-z</arg>
54-
<arg choice="plain">
55-
--timeadd +/-<replaceable>HH</replaceable>[:<replaceable>MM</replaceable>]
69+
<arg choice="plain">--timeadd
70+
+/-<replaceable>HH</replaceable>[:<replaceable>MM</replaceable>]
5671
</arg>
5772
</group>
5873

74+
<group>
75+
<arg choice="plain">--no-photo-tz</arg>
76+
</group>
77+
5978
<group>
6079
<arg choice="plain">-O</arg>
6180
<arg choice="plain">
@@ -125,38 +144,50 @@
125144
<arg choice="plain">--degmins</arg>
126145
</group>
127146

128-
<group>
129-
<arg choice="plain">
130-
-g <replaceable>file.gpx</replaceable>
131-
</arg>
132-
133-
<arg choice="plain">
134-
<group>
135-
<arg choice="plain">-l</arg>
136-
<arg choice="plain">--latlong</arg>
137-
</group>
138-
<replaceable>latitude,longitude<group><arg choice="plain">,elevation</arg></group></replaceable>
139-
</arg>
140-
</group>
141-
142147
<arg rep="repeat" choice="plain">
143148
<replaceable>image.jpg</replaceable>
144149
</arg>
145150
</cmdsynopsis>
146151

147152
<cmdsynopsis>
148153
<command>&dhpackage;</command>
149-
<group choice="plain">
154+
<group choice="req">
150155
<arg choice="plain">-s</arg>
151156
<arg choice="plain">--show</arg>
152-
<arg choice="plain">-x</arg>
153-
<arg choice="plain">--show-gpx</arg>
154157
<arg choice="plain">-o</arg>
155158
<arg choice="plain">--machine</arg>
156159
</group>
157160
<arg rep="repeat" choice="plain"><replaceable>image.jpg</replaceable></arg>
158161
</cmdsynopsis>
159162

163+
164+
<cmdsynopsis>
165+
<command>&dhpackage;</command>
166+
<group choice="req">
167+
<arg choice="plain">-x</arg>
168+
<arg choice="plain">--show-gpx</arg>
169+
</group>
170+
171+
<group>
172+
<arg choice="plain">-z</arg>
173+
<arg choice="plain">
174+
--timeadd +/-<replaceable>HH</replaceable>[:<replaceable>MM</replaceable>]
175+
</arg>
176+
</group>
177+
178+
<group>
179+
<arg choice="plain">--no-photo-tz</arg>
180+
</group>
181+
182+
<group>
183+
<arg choice="plain">-O</arg>
184+
<arg choice="plain">
185+
--photooffset <replaceable>seconds</replaceable>
186+
</arg>
187+
</group>
188+
<arg rep="repeat" choice="plain"><replaceable>image.jpg</replaceable></arg>
189+
</cmdsynopsis>
190+
160191
<cmdsynopsis>
161192
<command>&dhpackage;</command>
162193
<group choice="req">
@@ -282,7 +313,8 @@
282313
</term>
283314
<listitem>
284315
<para>Only show the GPS data already in the given image's EXIF tags
285-
instead of correlating them.</para>
316+
instead of correlating them. The time shown comes directly from
317+
the image without adjustments.</para>
286318
</listitem>
287319
</varlistentry>
288320

@@ -312,7 +344,8 @@
312344
</term>
313345
<listitem>
314346
<para>Only show the GPS data of the given images in a
315-
machine-readable CSV format. Images without GPS tags are
347+
machine-readable CSV format. The time shown comes directly from the
348+
image without adjustments. Images without GPS tags are
316349
ignored. The fields output are file name, date and time, latitude,
317350
longitude, elevation, where the first value is the filename, as
318351
passed, the second is the timestamp, and the last three are floating
@@ -345,9 +378,28 @@
345378
in local time. Enter the timezone used when taking the images; e.g.,
346379
<userinput>+8</userinput> for Perth, Western Australia or
347380
<userinput>-2:30</userinput> for St. John's, Newfoundland.
348-
This defaults to the UTC offset of the local time zone as of the
349-
time of the first image processed (versions before 1.7 defaulted to
350-
00:00). </para>
381+
This defaults to the time zone embedded in the image, or if that is not
382+
available (or when <userinput>--no-photo-tz</userinput> is given), the
383+
UTC offset of the local time zone as of the time of the first image
384+
processed (versions before 1.7 defaulted to 00:00).</para>
385+
</listitem>
386+
</varlistentry>
387+
388+
<varlistentry>
389+
<term>
390+
<option>--no-photo-tz</option>
391+
</term>
392+
<listitem>
393+
<para>
394+
Ignore any <userinput>OffsetTimeOriginal</userinput> EXIF tags in
395+
photos that specify the time zone in which the photo was taken.
396+
If the tag is wrong, such as if the user forgot to update the time
397+
zone manually when travelling, then this will prevent it from being
398+
used. If this option is given, then <command>gpscorrelate</command>
399+
reverts to using automatic time zone detection for the photo, or
400+
a manually-specified one (if <userinput>--timeadd</userinput> is
401+
given).
402+
</para>
351403
</listitem>
352404
</varlistentry>
353405

@@ -681,10 +733,10 @@
681733

682734
<para>
683735
Correlate a photo taken from a camera with a fast clock (i.e., the clock
684-
was 77.5 seconds ahead of GPS time):
685-
</para>
736+
was 77.5 seconds ahead of GPS time) and with incorrectly-specified time zone
737+
tags:</para>
686738
<para>
687-
<userinput>gpscorrelate -g Test.gpx -O -77.5 photo.jpg</userinput>
739+
<userinput>gpscorrelate -g Test.gpx -O -77.5 --no-photo-tz photo.jpg</userinput>
688740
</para>
689741

690742
<para>

doc/gui.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ <h3>Step 3: Set options</h3>
4242

4343
<p>The <b>"Auto time zone"</b> checkbox, if checked, will choose an appropriate value for "Time Zone", based on the local time zone and the time of the first photo in the list to be correlated.</p>
4444

45+
<p>The <b>"Use photo time zone"</b> checkbox, if checked, will use each photo's time zone tag to determine the local time of the image. If this tag is not available, the regular "Auto time zone" behaviour will take effect.</p>
46+
4547
<p>The <b>"Write heading"</b> checkbox, if checked, will write tags into the picture indicating the direction of movement at the time the picture was taken, when these data are available in the GPX file. These tags are not written during times of rapid turning, determined by the <b>Max heading change</b> setting.</p>
4648

4749
<p>The <b>"Write camera direction"</b> checkbox, if checked, will write tags into the picture indicating the direction that the camera was facing at the time the picture was taken. This is determined by adding the <b>Camera direction</b> value to the heading written in <b>Write heading</b>.</p>

exif-gps.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ int main(int argc, char* argv[])
8585
printf("Done write, now reading...\n");
8686
8787
int GPS = 0;
88-
char* Ret = ReadExifData(argv[1], &GPS, NULL, NULL, NULL);
88+
char* Ret = ReadExifData(argv[1], &GPS, NULL, NULL, NULL, NULL);
8989
if (Ret)
9090
{
9191
printf("Date: %s.\n", Ret);
@@ -103,7 +103,8 @@ int main(int argc, char* argv[])
103103
};
104104
*/
105105

106-
char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long, double* Elev)
106+
char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long, double* Elev,
107+
long *OffsetTime)
107108
{
108109
*IncludesGPS = 0;
109110

@@ -155,6 +156,24 @@ char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long
155156
// Copy the tag and return that.
156157
char* DateTime = strdup(Value.c_str());
157158

159+
// Look for time offset
160+
if (OffsetTime) {
161+
Exiv2::Exifdatum& OffsetTimeTag = ExifRead["Exif.Photo.OffsetTimeOriginal"];
162+
std::string OffsetTimeValue = OffsetTimeTag.toString();
163+
if (OffsetTimeValue.length() == 6) {
164+
/* Split into hours, minutes with the same sign for both. */
165+
int OffsetHours, OffsetMins;
166+
if (sscanf(OffsetTimeValue.c_str(), "%d:%d", &OffsetHours, &OffsetMins) == 2) {
167+
/* Can't look at sign of hours because it might be -0 */
168+
if (OffsetTimeValue[0] == '-')
169+
OffsetMins *= -1;
170+
*OffsetTime = OffsetHours * 3600 + OffsetMins * 60;
171+
} else
172+
*OffsetTime = NO_OFFSET_TIME;
173+
} else
174+
*OffsetTime = NO_OFFSET_TIME;
175+
}
176+
158177
// Check if we have GPS tags.
159178
Exiv2::Exifdatum GPSData = ExifRead["Exif.GPSInfo.GPSVersionID"];
160179

exif-gps.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,13 @@
3030
extern "C" {
3131
#endif
3232

33-
char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long, double* Elevation);
33+
#include <limits.h>
34+
35+
// No offset time tag was found in photo
36+
#define NO_OFFSET_TIME INT_MIN
37+
38+
char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long, double* Elevation,
39+
long *OffsetTime);
3440
char* ReadGPSTimestamp(const char* File, char* DateStamp, char* TimeStamp, int* IncludesGPS);
3541
int WriteGPSData(const char* File, const struct GPSPoint* Point,
3642
const char* Datum, int NoChangeMtime, int DegMinSecs);

gpx-read.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,13 @@ static void ExtractTrackPoints(xmlNodePtr Start)
208208
LastPoint->MoveHeading = atof(Heading);
209209
LastPoint->HeadingRef = HeadingRef;
210210
}
211-
struct timespec Timespec = ConvertToUnixTime(Time, GPX_DATE_FORMAT, 0, 0);
211+
struct timespec Timespec = ConvertToUnixTime(Time, GPX_DATE_FORMAT, 0);
212212
LastPoint->Time = Timespec.tv_sec;
213213

214214
/* Debug...
215215
printf("TrackPoint. Lat %s (%f), Long %s (%f). Elev %s (%f), Time %d.\n",
216216
Lat, atof(Lat), Long, atof(Long), Elev, atof(Elev),
217-
ConvertToUnixTime(Time, GPX_DATE_FORMAT, 0, 0));
217+
ConvertToUnixTime(Time, GPX_DATE_FORMAT, 0));
218218
printf("Decimals %d %d %d\n", LastPoint->LatDecimals, LastPoint->LongDecimals, LastPoint->ElevDecimals);
219219
*/
220220

0 commit comments

Comments
 (0)