Une fonctionnalité intéressante qui s’est développée est la possibilité d’indiquer la position GPS où a été prise une photo. Cela peut être fait de multiples façons, dans l’ordre de facilité :
Le géotaggage automatique par l’appareil photo lorsque celui-ci est compatible est bien sûr le plus facile. Cependant ce n’est pas 100% sans défauts : l’activation du GPS a tendance a utiliser 2 à 3 fois plus de batterie sur une utilisation permanente ; il est donc judicieux de couper la fonction GPS en dehors des séances photos. Par ailleurs la localisation GPS n’est pas disponible immédiatement si on vient de rallumer la fonctionnalité ou si l’on sort d’un endroit couvert (bâtiment, voiture,…). Dans ces cas, la photo disposera soit d’une mauvaise localisation, soit plus fréquemment pas de localisation du tout.
Dans le cas où l’appareil photo n’est pas compatible, un smartphone peut remplacer la fonction, et réaliser un relevé automatique qu’il sera possible d’appliquer automatiquement aux photos. J’utilise l’application MyTracks, développée par Google (pour Android ou iPhone) que j’ai jugée plus simple et performante que les autres que j’ai testées. L’application permet de démarrer et stopper simplement l’enregistrement, visualiser le tracé sur Google Maps, disposer de statistiques, et exporter les données soit automatiquement vers Google Maps, vers Google Drive en KML, et également en GPX. C’est ce dernier format que j’utilise, car plus répandu.
Pour appliquer le fichier GPX aux imags, il faut un logiciel qui permette de faire la correspondance. J’utilise GeoSetter qui est de loin le meilleur que j’ai testé. Il s’appuie sur l’excellent exiftool qui permet la manipulation des tags exif en ligne de command, et y ajoute une interface utilisateur très fonctionnelle. Pour tagguer automatiquement, il suffit de sélectionner le fichier GPX, ou le répertoire de fichiers GPX et d’indiquer le bon fuseau horaire à utiliser.
Deux points d’attention sont à noter :
Il reste maintenant à tagguer manuellement les fichiers que le processus automatique n’a pas pu prendre en compte, ou corriger des potentielles erreurs. Pour cela, le plus simple est de charger tous les GPX dans GeoSetter (Cliquer sur ouvrir dans le panneau des traces, et sélectionnez toutes les traces souhaitées, puis appuyez sur ouvrir pour les ouvrir toutes d’un seul coup).
Attention, GeoSetter utilise un cache pour les GPX et pour l’instant ne les mets pas à jour (voir bug 621) ; si vous modifiez un GPX sans modifier son nom, même en tentant de le réouvrir vous ne verrez pas de différence. Pour forcer le rechargement des GPX, lancer le .bat suivant avant de réouvrir les fichiers GPX :
@echo off echo Clear GeoSetter GPX Cache - 2013 Remi Peyronnet echo Remove files in %APPDATA%\GeoSetter\track_cache\tracks\*.* del "%APPDATA%\GeoSetter\track_cache\tracks\*.*"
Il est maintenant assez simple, entre le tracé des GPX et les photos, de trouver le bon endroit pour chaque photo à tagguer.
Cependant,toujours à cause de ces fameux fuseaux horaires, l’heure affichée pour les traces GPX peut ne pas correspondre à l’heure des photos ; ce n’est donc pas très pratique pour s’y retrouver. Pour remédier à cela, le plus simple est de changer l’heure du GPX.
Cela est faisable simplement en ligne de commande, soit avec gpxbabel :
gpsbabel -i gpx -f input.gpx -x track,move=+7200s -o gpx -F output.gpx
soit avec le script python ci-dessous, qui permet de ré-écrire tous les GPX concernés, soit avec un offset (-o=+02:00), ou soit avec un fuseau horaire, forcé ou nom (consulter l’aide pour plus de détails)
#! /usr/bin/python # -*- coding: utf-8 -*- #apt-get install python-dateutil import sys import os import argparse import datetime import re import codecs import xml.dom.minidom import xml.parsers.expat import dateutil.parser import dateutil.tz def argparse_timezone(str): tz = dateutil.tz.gettz(str) if tz is None: raise argparse.ArgumentTypeError("'%s' is not a valid timezone value" % str) return tz def argparse_timeoffset(str): m = re.match(r'^(?P<hours>[+-]?\d+):(?P<minutes>\d+)$',str) if m is None: raise argparse.ArgumentTypeError("'%s' is not a valid offset value (+xx:yy)" % str) d = datetime.timedelta(hours=int(m.group("hours")), minutes=int(m.group("minutes"))) return d def replace_tz(ts, tz): if ts.tzinfo is not None: ts = ts + ts.tzinfo.utcoffset(ts) ts = ts - tz.utcoffset(ts) ts = ts.astimezone(tz) return ts def process_gpx(gpxfile, args): # Parse XML File if args.verbose: print "Processing file %s ..." % (gpxfile) try: gpxxml = xml.dom.minidom.parse(gpxfile) itemlist = gpxxml.getElementsByTagName('time') for item in itemlist: timestamp = item.firstChild.nodeValue ts = dateutil.parser.parse(timestamp) if args.from_tz: ts = replace_tz(ts, args.from_tz) if args.change_tz: ts = ts.astimezone(args.change_tz) if args.replace_tz: ts = replace_tz(ts, args.replace_tz) if args.offset: ts = ts + args.offset # Alternative : tsiso = ts.strftime('%Y-%m-%dT%H:%M:%S %Z')) tsiso = ts.isoformat('T') if args.verbose: print "%s -> %s" % (timestamp, tsiso) item.firstChild.nodeValue = tsiso if not args.no_backup: gpxbackup = gpxfile + ".backup" if args.verbose: print "Backup %s to %s" % (gpxfile, gpxbackup) os.rename(gpxfile, gpxbackup) if args.verbose: print "Writing result to %s" % (gpxfile) f = codecs.open(gpxfile, "wb",'utf-8') f.write(gpxxml.toxml('utf-8').decode('utf-8')) f.close() except xml.parsers.expat.ExpatError: print "Error parsing file %s : %s : %s" % (gpxfile, sys.exc_info()[0], sys.exc_info()[1]) parser = argparse.ArgumentParser("Change TimeZone of GPX files.") parser.add_argument("files", help="GPX files to modify", nargs="*") parser.add_argument("-f", "--from-tz", type=argparse_timezone, help="Force input timezone (before change)") parser.add_argument("-c", "--change-tz", type=argparse_timezone, help="Change timezone and change time value accordingly") parser.add_argument("-r", "--replace-tz", type=argparse_timezone, help="Force output timezone (after change), without changing time value") parser.add_argument("-o", "--offset", type=argparse_timeoffset, help="Offset time (without changing timezone) ; for negative values use with = (ex: -o=-02:00)", metavar="+hh:mm") parser.add_argument("-v", "--verbose", action="store_true", help="Display detailled information about files and timestamps processed") parser.add_argument("--no-backup", action="store_true", help="Do not create backup files (overwrite files)") args = parser.parse_args() for file in args.files: process_gpx(file, args)