Last active
May 9, 2021 07:20
-
-
Save taprs/6f352317e3af40401fcf2bd37bf0f987 to your computer and use it in GitHub Desktop.
JPEG coordinates from GPS track
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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