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

  • soit directement par l’appareil photo s’il est compatible (et maintenant quasiment tous les smartphones)
  • soit via un relevé GPS réalisé indépendamment
  • soit manuellement en pointant sur une carte

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 :

  • Les fuseaux horaires ne sont pas encore bien pratiques à utiliser, entre celui de l’appareil photo et celui du GPX ; personnellement je repère une photo dont je connais l’endroit , pour comparer l’heure du relevé GPS correspondant et l’heure de la photo, afin de calculer le fuseau horaire à appliquer.
  • GeoSetter n’accepte pas les fichier GPX avec plusieurs tracés ; assurez-vous de prendre les GPX directement émis par MyTracks, et non un éventuel GPX constitué de l’ensemble des traces (par fusion)

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 :

geosetter-clear-gpx-cache.bat

@echo off
echo Clear GeoSetter GPX Cache  - 2013 Remi Peyronnet
echo Remove files in %APPDATA%GeoSettertrack_cachetracks*.*
del "%APPDATA%GeoSettertrack_cachetracks*.*"

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)

gpx-change-timezone.py

#! /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)