Skip to content

Commit

Permalink
Use time zone tag from photo when available
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dfandrich committed Dec 31, 2024
1 parent a699abc commit 394e835
Show file tree
Hide file tree
Showing 42 changed files with 1,242 additions and 555 deletions.
30 changes: 19 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ jobs:
cxx: g++
target: gpscorrelate
install_target: install
valgrind: yes
check_options: -m
- name: 'locale tests'
cc: gcc
gtk: 3
Expand All @@ -173,7 +173,6 @@ jobs:
cxxflags: -std=c++11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra -Werror -Wno-error=deprecated-declarations -O3 -DENABLE_NLS=1
target: all
install_target: install install-po install-desktop-file
osver: 20.04
exclude:
# Exclude configurations intended for a single OS version
- osver: 22.04
Expand All @@ -194,16 +193,19 @@ jobs:
run: env ASAN_OPTIONS="${{ matrix.build.asan_options }}" ./gpscorrelate -V
- name: 'test'
run: |
if [[ -z "${{ matrix.build.failing_tests }}" ]] ; then
make check CHECK_OPTIONS=-v ASAN_OPTIONS="${{ matrix.build.asan_options }}"
if [[ "${{ matrix.osver }}" == "20.04" ]]; then
# Ubuntu 20.04 has an old version of exiv2 that doesn't support
# time zone tags, so those 8 tests are expected to fail
FAILING_TESTS="$((8 + ${{ matrix.build.failing_tests || '0'}}))"
else
# Debug logging enabled makes some tests fail due to differing output
echo 'Expecting ${{ matrix.build.failing_tests }} test failures'
make check CHECK_OPTIONS=-v | tee /dev/stderr | grep -q '${{ matrix.build.failing_tests }} test(s) have FAILED'
FAILING_TESTS="${{ matrix.build.failing_tests }}"
fi
if [[ -z "$FAILING_TESTS" ]] ; then
make check CHECK_OPTIONS="-v ${{ matrix.build.check_options }}" ASAN_OPTIONS="${{ matrix.build.asan_options }}"
else
echo "Expecting $FAILING_TESTS test failures"
make check CHECK_OPTIONS="-v ${{ matrix.build.check_options }}" | tee /dev/stderr | grep -Fq "$FAILING_TESTS test(s) have FAILED"
fi
- if: matrix.build.valgrind == 'yes'
name: 'valgrind test'
run: make check CHECK_OPTIONS='-v -m'
- if: matrix.build.locale
name: 'locale test'
env:
Expand All @@ -220,7 +222,13 @@ jobs:
LC_SOURCED: ${{ matrix.build.locale }}
LC_TELEPHONE: ${{ matrix.build.locale }}
LC_TIME: ${{ matrix.build.locale }}
run: make check CHECK_OPTIONS='-v'
run: |
if [[ -z "$FAILING_TESTS" ]] ; then
make check CHECK_OPTIONS=-v ASAN_OPTIONS="${{ matrix.build.asan_options }}"
else
echo "Expecting $FAILING_TESTS test failures"
make check CHECK_OPTIONS=-v | tee /dev/stderr | grep -Fq "$FAILING_TESTS test(s) have FAILED"
fi
- name: 'install test'
run: make prefix= DESTDIR="${PWD}" ${{ matrix.build.install_target }}

Expand Down
2 changes: 2 additions & 0 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Installation of GPS Correlate
To build, you will need these prerequisites:

* The Exiv2 library (C++ EXIF tag handling): https://www.exiv2.org/
(ver. 0.27.4 or newer is required for time zone tag support)
* libxml2 (XML parsing): http://www.xmlsoft.org/
* pkgconfig: https://pkg-config.freedesktop.org/
* make, such as GNU makes: https://www.gnu.org/software/make/make.html
Expand All @@ -12,6 +13,7 @@ To build, you will need these prerequisites:
https://docbook.sourceforge.net/ or an OS package (often named something like
docbook-xsl or docbook-style-xsl)
* intltool (only for rebuilding the AppStream metadata file)
* exiv2 CLI (only for running the test suite)

You can build the command line version and the GUI together simply with:
make
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ version of the program.
* The resolution of GPX positions is down to one second, but sub-second photo
time is used (when available in the photo) to more accurately estimate the
camera position between those points.
* A time zone tag embedded in the photo is used to determine the local time of
the image when found, otherwise the time zone is assumed to be same as the
local machine.
* For best results, you should synchronise your camera to the GPS time before
you start taking photos. Note: digital cameras clocks drift quickly - even
over a short period of time (say, a week).
1 change: 1 addition & 0 deletions RELEASES
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ v2.x: X
- When interpolation is disabled, round up when exactly half way
- Allow fractional times in --photooffset
- Fix handling of --timeadd values between -1:00 and 0:00
- Read time zone from photos when available or --no-photo-tz to disable

v2.2: 17 October 2024
- Fix metainfo nits
Expand Down
29 changes: 20 additions & 9 deletions correlate.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ void SetAutoTimeZoneOptions(const char *Time,
/* PhotoTime isn't a true epoch time, but is rather out
* by the local offset from UTC */
struct timespec PhotoTime =
ConvertToUnixTime(Time, EXIF_DATE_FORMAT, 0, 0);
ConvertToUnixTime(Time, EXIF_DATE_FORMAT, 0);

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

/* Convert a time into Unixtime with the configured time zone conversion. */
struct timespec ConvertTimeToUnixTime(const char *Time, const char *TimeFormat,
const struct CorrelateOptions* Options)
long OffsetTime, long *UsedOffset, const struct CorrelateOptions* Options)
{
struct timespec PhotoTime =
ConvertToUnixTime(Time, TimeFormat,
Options->TimeZoneHours, Options->TimeZoneMins);
long TZOffset;
if (Options->TimeZoneFromPhoto && OffsetTime != NO_OFFSET_TIME)
/* Use the photo's time zone */
TZOffset = OffsetTime;
else
/* Use the automatic or manually-specified time zone */
TZOffset = Options->TimeZoneHours * 3600 + Options->TimeZoneMins * 60;
if (UsedOffset)
*UsedOffset = TZOffset;
struct timespec PhotoTime = ConvertToUnixTime(Time, TimeFormat, TZOffset);

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

struct GPSPoint* CorrelatePhoto(const char* Filename,
struct CorrelateOptions* Options)
long *UsedOffset, struct CorrelateOptions* Options)
{
*UsedOffset = NO_OFFSET_TIME;
/* Read out the timestamp from the EXIF data. */
int IncludesGPS = 0;
char* TimeTemp = ReadExifData(Filename, &IncludesGPS, NULL, NULL, NULL);
long OffsetTime = 0;
char* TimeTemp = ReadExifData(Filename, &IncludesGPS, NULL, NULL, NULL,
&OffsetTime);
if (!TimeTemp)
{
/* Error reading the time from the file. Abort. */
Expand All @@ -141,10 +151,11 @@ struct GPSPoint* CorrelatePhoto(const char* Filename,
SetAutoTimeZoneOptions(TimeTemp, Options);
Options->AutoTimeZone = 0;
}
//printf("Using offset %02d:%02d\n", Options->TimeZoneHours, Options->TimeZoneMins);
//printf("Using offset %ld sec.\n", OffsetTime);

/* Now convert the time into Unixtime with the configured time zone conversion. */
struct timespec PhotoTime = ConvertTimeToUnixTime(TimeTemp, EXIF_DATE_FORMAT, Options);
struct timespec PhotoTime = ConvertTimeToUnixTime(TimeTemp, EXIF_DATE_FORMAT, OffsetTime,
UsedOffset, Options);

/* Free the memory for the time string - it won't otherwise
* be freed for us. */
Expand Down
5 changes: 3 additions & 2 deletions correlate.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ struct CorrelateOptions {
int TimeZoneHours; /* To add to photos to make them UTC. */
int TimeZoneMins;
int AutoTimeZone;
int TimeZoneFromPhoto;
int FeatherTime;
int WriteHeading;
int HeadingOffset;
Expand Down Expand Up @@ -81,8 +82,8 @@ struct CorrelateOptions {


struct GPSPoint* CorrelatePhoto(const char* Filename,
struct CorrelateOptions* Options);
long *UsedOffset, struct CorrelateOptions* Options);
void SetAutoTimeZoneOptions(const char *TimeTemp,
struct CorrelateOptions* Options);
struct timespec ConvertTimeToUnixTime(const char *TimeTemp, const char *TimeFormat,
const struct CorrelateOptions* Options);
long OffsetTime, long *UsedOffset, const struct CorrelateOptions* Options);
106 changes: 79 additions & 27 deletions doc/gpscorrelate-manpage.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,32 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>&dhpackage;</command>
<group>
<arg choice="req">
-g <replaceable>file.gpx</replaceable>
</arg>

<arg choice="req">
<group>
<arg choice="plain">-l</arg>
<arg choice="plain">--latlong
<replaceable>latitude,longitude<group><arg choice="plain">,elevation</arg></group></replaceable>
</arg>
</group>
</arg>
</group>

<group>
<arg choice="plain">-z</arg>
<arg choice="plain">
--timeadd +/-<replaceable>HH</replaceable>[:<replaceable>MM</replaceable>]
<arg choice="plain">--timeadd
+/-<replaceable>HH</replaceable>[:<replaceable>MM</replaceable>]
</arg>
</group>

<group>
<arg choice="plain">--no-photo-tz</arg>
</group>

<group>
<arg choice="plain">-O</arg>
<arg choice="plain">
Expand Down Expand Up @@ -125,38 +144,50 @@
<arg choice="plain">--degmins</arg>
</group>

<group>
<arg choice="plain">
-g <replaceable>file.gpx</replaceable>
</arg>

<arg choice="plain">
<group>
<arg choice="plain">-l</arg>
<arg choice="plain">--latlong</arg>
</group>
<replaceable>latitude,longitude<group><arg choice="plain">,elevation</arg></group></replaceable>
</arg>
</group>

<arg rep="repeat" choice="plain">
<replaceable>image.jpg</replaceable>
</arg>
</cmdsynopsis>

<cmdsynopsis>
<command>&dhpackage;</command>
<group choice="plain">
<group choice="req">
<arg choice="plain">-s</arg>
<arg choice="plain">--show</arg>
<arg choice="plain">-x</arg>
<arg choice="plain">--show-gpx</arg>
<arg choice="plain">-o</arg>
<arg choice="plain">--machine</arg>
</group>
<arg rep="repeat" choice="plain"><replaceable>image.jpg</replaceable></arg>
</cmdsynopsis>


<cmdsynopsis>
<command>&dhpackage;</command>
<group choice="req">
<arg choice="plain">-x</arg>
<arg choice="plain">--show-gpx</arg>
</group>

<group>
<arg choice="plain">-z</arg>
<arg choice="plain">
--timeadd +/-<replaceable>HH</replaceable>[:<replaceable>MM</replaceable>]
</arg>
</group>

<group>
<arg choice="plain">--no-photo-tz</arg>
</group>

<group>
<arg choice="plain">-O</arg>
<arg choice="plain">
--photooffset <replaceable>seconds</replaceable>
</arg>
</group>
<arg rep="repeat" choice="plain"><replaceable>image.jpg</replaceable></arg>
</cmdsynopsis>

<cmdsynopsis>
<command>&dhpackage;</command>
<group choice="req">
Expand Down Expand Up @@ -282,7 +313,8 @@
</term>
<listitem>
<para>Only show the GPS data already in the given image's EXIF tags
instead of correlating them.</para>
instead of correlating them. The time shown comes directly from
the image without adjustments.</para>
</listitem>
</varlistentry>

Expand Down Expand Up @@ -312,7 +344,8 @@
</term>
<listitem>
<para>Only show the GPS data of the given images in a
machine-readable CSV format. Images without GPS tags are
machine-readable CSV format. The time shown comes directly from the
image without adjustments. Images without GPS tags are
ignored. The fields output are file name, date and time, latitude,
longitude, elevation, where the first value is the filename, as
passed, the second is the timestamp, and the last three are floating
Expand Down Expand Up @@ -345,9 +378,28 @@
in local time. Enter the timezone used when taking the images; e.g.,
<userinput>+8</userinput> for Perth, Western Australia or
<userinput>-2:30</userinput> for St. John's, Newfoundland.
This defaults to the UTC offset of the local time zone as of the
time of the first image processed (versions before 1.7 defaulted to
00:00). </para>
This defaults to the time zone embedded in the image, or if that is not
available (or when <userinput>--no-photo-tz</userinput> is given), the
UTC offset of the local time zone as of the time of the first image
processed (versions before 1.7 defaulted to 00:00).</para>
</listitem>
</varlistentry>

<varlistentry>
<term>
<option>--no-photo-tz</option>
</term>
<listitem>
<para>
Ignore any <userinput>OffsetTimeOriginal</userinput> EXIF tags in
photos that specify the time zone in which the photo was taken.
If the tag is wrong, such as if the user forgot to update the time
zone manually when travelling, then this will prevent it from being
used. If this option is given, then <command>gpscorrelate</command>
reverts to using automatic time zone detection for the photo, or
a manually-specified one (if <userinput>--timeadd</userinput> is
given).
</para>
</listitem>
</varlistentry>

Expand Down Expand Up @@ -681,10 +733,10 @@

<para>
Correlate a photo taken from a camera with a fast clock (i.e., the clock
was 77.5 seconds ahead of GPS time):
</para>
was 77.5 seconds ahead of GPS time) and with incorrectly-specified time zone
tags:</para>
<para>
<userinput>gpscorrelate -g Test.gpx -O -77.5 photo.jpg</userinput>
<userinput>gpscorrelate -g Test.gpx -O -77.5 --no-photo-tz photo.jpg</userinput>
</para>

<para>
Expand Down
2 changes: 2 additions & 0 deletions doc/gui.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ <h3>Step 3: Set options</h3>

<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>

<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>

<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>

<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>
Expand Down
25 changes: 23 additions & 2 deletions exif-gps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ int main(int argc, char* argv[])
printf("Done write, now reading...\n");
int GPS = 0;
char* Ret = ReadExifData(argv[1], &GPS, NULL, NULL, NULL);
char* Ret = ReadExifData(argv[1], &GPS, NULL, NULL, NULL, NULL);
if (Ret)
{
printf("Date: %s.\n", Ret);
Expand All @@ -103,7 +103,8 @@ int main(int argc, char* argv[])
};
*/

char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long, double* Elev)
char* ReadExifData(const char* File, int* IncludesGPS, double* Lat, double* Long, double* Elev,
long *OffsetTime)
{
*IncludesGPS = 0;

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

// Look for time offset
if (OffsetTime) {
#if EXIV2_TEST_VERSION(0, 27, 4)
Exiv2::Exifdatum& OffsetTimeTag = ExifRead["Exif.Photo.OffsetTimeOriginal"];
std::string OffsetTimeValue = OffsetTimeTag.toString();
if (OffsetTimeValue.length() == 6) {
/* Split into hours, minutes with the same sign for both. */
int OffsetHours, OffsetMins;
if (sscanf(OffsetTimeValue.c_str(), "%d:%d", &OffsetHours, &OffsetMins) == 2) {
/* Can't look at sign of hours because it might be -0 */
if (OffsetTimeValue[0] == '-')
OffsetMins *= -1;
*OffsetTime = OffsetHours * 3600 + OffsetMins * 60;
} else
*OffsetTime = NO_OFFSET_TIME;
} else
#endif
*OffsetTime = NO_OFFSET_TIME;
}

// Check if we have GPS tags.
Exiv2::Exifdatum GPSData = ExifRead["Exif.GPSInfo.GPSVersionID"];

Expand Down
Loading

0 comments on commit 394e835

Please sign in to comment.