Skip to content

Instantly share code, notes, and snippets.

@taprs
Last active May 9, 2021 07:20
Show Gist options
  • Save taprs/6f352317e3af40401fcf2bd37bf0f987 to your computer and use it in GitHub Desktop.
Save taprs/6f352317e3af40401fcf2bd37bf0f987 to your computer and use it in GitHub Desktop.
JPEG coordinates from GPS track
import piexif
from datetime import datetime, timedelta, timezone
import gpxpy
import os
def geotag_photos(offset=0, datefmt="b'%Y:%m:%d %H:%M:%S'",
recursive=False, max_secs=300):
"""
Appends coordinates to metadata of JPEG images in the current working directory
(recursive search is possible). Note that it changes the metadata of original
files. Be careful!
The coordinates are taken from the GPX track files
located in the working directory based on
temporal proximity of track nodes and JPEG timestamps.
There are too many ways to store timezone info in JPEG.
Thus, here it is passed as the 'offset' argument.
Parameters
----------
offset : int or float, default 0
Timezone info (in hours ahead GMT).
datefmt : string, default "b'%Y:%m:%d %H:%M:%S'"
JPEG timestamp format (passed to datetime.strptime).
recursive : bool, default False
Recursive search of JPEG and GPX files, starting from current working directory
max_secs : int or float, default 300
Desired temporal accuracy of geotag coordinates (in seconds).
If a photo is geotagged using a point with timestamp differing from
JPEG timestamp by more than max_secs, the function gives a warning.
"""
# Extracting all jpg names from working directory
if recursive:
files = [dir[0] + '/' + f for dir in os.walk('./') for f in dir[2]]
else:
files = [*os.walk('./')][0][2]
# Parsing JPG metadata and GPX tracks
photos = {f: piexif.load(f) for f in files if f.lower().endswith('.jpg')}
gpsdata = [gpxpy.parse(open(f)) for f in files if f.lower().endswith('.gpx')]
# Comparing photo timestamp with gpx points timestamps
success = []
no_timestamp = []
for file, photo in photos.items():
if 36867 in photo['Exif']:
photo_time = datetime.strptime(f"{photo['Exif'][36867]}", datefmt)
photo_time = photo_time.replace(tzinfo=timezone(timedelta(seconds = offset * 3600)))
for gpx in gpsdata:
for track in gpx.tracks:
for segment in track.segments:
if segment.points[0].time<=photo_time<=segment.points[-1].time:
deltas = []
for point in segment.points:
deltas.append(point.time - photo_time)
deltas = list(map(abs, deltas))
geotag = segment.points[deltas.index(min(deltas))]
lat = int(abs(geotag.latitude) * 100000)
lon = int(abs(geotag.longitude) * 100000)
alt = int(abs(geotag.elevation) * 100)
if geotag.latitude > 0:
latr = b'N'
else:
latr = b'S'
if geotag.longitude > 0:
lonr = b'E'
else:
lonr = b'W'
if geotag.elevation > 0:
altr = 0
else:
altr = 1
zeroth_ifd = photo['0th']
exif_ifd = photo['Exif']
gps_ifd = {piexif.GPSIFD.GPSVersionID: (2, 0, 0, 0),
piexif.GPSIFD.GPSLatitudeRef: latr,
piexif.GPSIFD.GPSLatitude: ((lat, 100000), (0, 1), (0, 1)),
piexif.GPSIFD.GPSLongitudeRef: lonr,
piexif.GPSIFD.GPSLongitude: ((lon, 100000), (0, 1), (0, 1)),
piexif.GPSIFD.GPSAltitudeRef: altr,
piexif.GPSIFD.GPSAltitude: (alt, 100)
}
first_ifd = photo['1st']
thumbnail = photo['thumbnail']
exif_dict = {"0th":zeroth_ifd, "Exif":exif_ifd, "GPS":gps_ifd, "1st":first_ifd, "thumbnail":thumbnail}
exif_bytes = piexif.dump(exif_dict)
piexif.insert(exif_bytes, file)
text = f'{file} geotag found in track {track.name}'
if min(deltas) < timedelta(0, max_secs):
print(text)
else:
print(text + f' Warning! Timedelta between photo and track node exceeds {max_secs} seconds' + \
f' ({min(deltas).total_seconds()} seconds)')
success.append(file)
break
else:
continue
break
else:
continue
break
else:
continue
break
else:
no_timestamp.append(file)
# Returning paths to photos we failed to geotag
unsuccessful = [i for i in photos.keys() if i not in success + no_timestamp]
if len(no_timestamp) > 0:
print(f'No timestamp: {chr(10).join(no_timestamp)}') # chr(10) stands for '\n'
if len(unsuccessful) > 0:
print(f'No relevant track segments: {chr(10).join(unsuccessful)}')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment