Afficher un trajet ou une trace GPS sur un site avec OpenStreetMap

Petit tutorial pour expliquer comment mettre en ligne un trajet ou une trace GPS sur un fond de carte OpenStreetMap.

C’est la technique que j’ai utilisée pour la carte de la Traversée de l’Inde à vélo de Jérémie et Nathalie.

L’ensemble des fichiers utilisés est disponible ici : osm_trajet.tar.gz

Mode opératoire

La trace va être créée avec Google-Earth. On peut bien sûr la créer au moyen d’autres outils, le tout c’est d’obtenir à la fin un ficher .KML, comprenant la trace et éventuellement les différents points d’intérêts.

Cette trace sera ensuite simplifié au moyen de GPSBabel afin de limiter le nombre de points à afficher et diminuer la taille du fichier.

Pour faire un graphe altimétrique en fonction de la distance, GPS Visualizer sera utilisé pour récupérer l’information altimétrique et GnuPlot pour tracer la courbe.

Le tout sera affiché dans une page web au moyen de la librairie OpenLayers.

Prérequis

Créer le trajet

Il s’agit d’utiliser Google Earth pour tracer une route. Je ne vais pas détailler le processus, vous trouverez beaucoup d’information sur le sujet sur le net. Par example :

Ensuite, il faut la sauvegarder au format .KML (attention, pas .KMZ).

Simplifier la trace

Si vous avez utilisé le routage automatique de Google Earth, la route comporte beaucoup de points. Elle est très précise mais du coup est assez lourde à manipuler pour le navigateur lors de l’affichage.

En utilisant GPSBabel, on peut réduire plus ou moins drastiquement le nombre de points en lui demandant d’agréger les points qui sont suffisamment proches. Certes le trajet ne colle plus absolument à la route… mais le fichier est beaucoup moins lourd.

Pour cela, on utilise la commande suivante :

gpsbabel -i kml -f Trace_GE.kml -x simplify,error=0.5k -o kml -F Trace_Simple.kml

Le nom du fichier en entrée est ici Trace_GE.kml (c’est celui qui vient de Google Earth) et celui de sortie est Trace_Simple.kml. Le paramètre -x simplify,error=0.5k indique que les points situés dans un rayon de 0, 5km seront réunis en un seul point. On aura donc une erreur de 500m au maximum. Ce chiffre est bien sûr à affiner en fonction de votre cas. L’idéal étant d’obtenir un fichier d’une taille inférieure à 100ko.

Déterminer l’altitude des points de la trace

Google Earth n’inclue pas de données altimétriques. Pour pouvoir tracer un graphique de l’altitude en fonction de la distance parcourue, il faut récupérer cette information au moyen de GPS Visualizer

On choisit notre fichier Trace_Simple.kml, sortie plain text et on clique sur Convert and add elevation.

On sauve le fichier qui se présente sous forme :

type    latitude    longitude   altitude (m)    name    desc
T   34.152550000    77.577090000    3408.9  Path1   
T   34.135330000    77.587770000    3350.4      
T   34.106000000    77.590360000    3224.2      
T   34.087590000    77.608350000    3230.8      
T   34.087380000    77.623550000    3229.6      
T   34.069620000    77.632720000    3238.1      
T   34.073660000    77.639590000    3252.3      
T   34.059540000    77.667320000    3269.2

On l’appellera Trace_altimetrie.txt

Tracer le graphique altimétrique

Dans un premier temps, il faut transformer les écarts points de coordonnées en distance kilométrique et y associer la hauteur. Pour cela nous utiliserons le script Python conversion_coord_gps_hauteur.py pour produire un ficher km_altitude.txt.

from numpy import pi, sin, cos, arcsin
import fileinput


def distance_between(point1, point2):
    """
    Return distance in meters between two GPS point
    distance is ground distance !
    """

    # Earth diameter
    R=6378000
    dlat, dlon = point1
    slat, slon = point2

    # Radian rules
    dlon = dlon*2*pi/360
    dlat = dlat*2*pi/360
    slat = slat*2*pi/360
    slon = slon*2*pi/360

    # Distance...
    d = R * (pi/2 - arcsin(sin(dlat) * sin(slat) + cos(dlon - slon) \
                           * cos(dlat) * cos(slat)))
    return d

def extract(line):
    """
    Decoupe une ligne sous la forme :
    T   8.171710000 77.415120000    55.9
    """
    try:
        lat, lon, alt = line.strip().split()[1:4]
    except:
        print(line)
        raise
    return float(lat), float(lon), float(alt)



tot = -1
plat, plon, palt = (0, 0, 0)
for cline in fileinput.input():
    if cline[0] == 'T':
        if tot == -1:
            plat, plon, palt = extract(cline)
            print('{0} {1}'.format(0, palt))
            tot = 0
        else:
            clat, clon, calt = extract(cline)
            d = distance_between((plat, plon), (clat, clon))/1000
            tot += d
            print('{0} {1}'.format(tot, palt))
            plat, plon, palt = clat, clon, calt

Pour l’appeler, il faut utiliser la ligne de commande suivante :

python conversion_coord_gps_hauteur.py < Trace_altimetrie.txt > km_altitude.txt

Création du graphique

À partir du fichier de données précédent, nous allons enfin créer le graphique… Pour cela on utilise le script graphique_km_altitude.gnuplot GnuPlot suivant :

set terminal png font arial 17  size 1200,400  
set output 'graphique_km_altitude.png'

# Etiquettes axes
set xlabel "Kilomètres parcourus"  offset 0,1
set ylabel "Altitude (en mètres)"  offset 6,0


# No border with tics
set border 0
set noxtics 
set noytics

set ytics 0, 5071 nomirror border out offset character 0, 0, 0 
set xtics 0, 5200 nomirror border out offset character 0, -0.5, 0

set border linewidth 1

set style line 1 lc rgb '#80800' lt 1 lw 3
set xzeroaxis ls 1
set yzeroaxis ls 1

set key bottom

set yrange [0:5200] noreverse nowriteback
set xrange [0:5200] noreverse nowriteback
show xrange
show yrange

set style fill solid 1.0

plot 'km_altitude.txt' using 1:2 title ` with boxes lc rgb '#6e70bc'

Que l’on appelle par la ligne de commande suivante :

gnuplot graphique_km_altitude.gnuplot

Et nous voilà en possession du fichier graphique_km_altitude.png :

Graphe de l'altitude en fonction de la distance

Afficher le résultat

Il ne reste plus qu’à mettre tout ça dans une page HTML :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
  <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
    <title>Traversée de l'Inde à vélo: 6 mois d'itinérance, de l'Himalaya au Cap Indira, de Juillet à Noël 2014</title>
    <style type="text/css">
      html, body, #Map {
          width: 100%;
          height: 100%;
          margin: 0;
      }

      .hidden {
          display: none;
      }

      .olControlAttribution {
          position: absolute;
          font-size: 10px;
          text-align: right;
          color: #eeeeee;
          bottom: 0;
          right: 0;
          background: #130085; /* fallback for IE - IE6 requires background shorthand*/
          background: rgba(0, 60, 136, 0.3);
          filter: alpha(opacity=30);
          font-family: 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif;
          padding: 2px 4px;
          border-radius: 5px 0 0 0;
      }
      .olControlAttribution a {
          color: #eeeeee;
          font-weight: bold;
      }
      .olControlZoom a:hover {
          color: white;
      }
      div.olControlZoomPanel {
          top: 15px;
          left: 15px;
      }
    </style>


    <script type="text/javascript">
      function showHideBlock(identifiant) {
        if (document.getElementById(identifiant).className == "hidden") {
          document.getElementById(identifiant).className = "visibleblock";
        }   
        else {
          document.getElementById(identifiant).className = "hidden";
        }
      }
    </script>


    <script src="OpenLayers-2.13.1/OpenLayers.js" type="text/javascript"></script>

  </head>
  <body>

    <div id="Map"></div>

    <script type="text/javascript">
      /* Coordonnees de l'Inde pour centrer la carte */
      var lat            = 27.225;
      var lon            = 76.860;
      var zoom           = 6;

      var fromProjection = new OpenLayers.Projection("EPSG:4326");   // Transform from WGS 1984
      var toProjection   = new OpenLayers.Projection("EPSG:900913"); // to Spherical Mercator Projection
      var gpsProjection  = new OpenLayers.Projection("EPSG:4326");   // to WSG84
      var position       = new OpenLayers.LonLat(lon, lat).transform( fromProjection, toProjection);

      /* On initialise la carte avec les controles kivonbien */
      map = new OpenLayers.Map("Map", {
        controls: [
          new OpenLayers.Control.Navigation(),
          new OpenLayers.Control.PanZoomBar(),
          new OpenLayers.Control.LayerSwitcher({'ascending':false}),
          new OpenLayers.Control.ScaleLine(),
          new OpenLayers.Control.MousePosition({'prefix':'lon / lat : ', 'displayProjection':gpsProjection}),
          new OpenLayers.Control.OverviewMap(),
          new OpenLayers.Control.KeyboardDefaults()
        ],
        numZoomLevels: 5
      });

      /* Rendu Cyclemap */
      var mapnikcm = new OpenLayers.Layer.OSM("CycleMap", ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
                                                           "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
                                                           "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
      map.addLayer(mapnikcm);

      /* Rendu OSM */
      var mapnik = new OpenLayers.Layer.OSM(); 
      map.addLayer(mapnik);

      /* On charge le fichier kml, produit avec Google-Earth, par exemple */
      var trajet = new OpenLayers.Layer.Vector("Trace", {
        strategies: [new OpenLayers.Strategy.Fixed()],
        protocol: new OpenLayers.Protocol.HTTP({
        url: "Trace_Simple.kml",
        format: new OpenLayers.Format.KML({
          extractStyles: true, 
          extractAttributes: true,
          maxDepth: 2
          })
      })});
      map.addLayer(trajet);


      /* En prospective, des liens directement sur les icones */
      /*    var markers = new OpenLayers.Layer.Markers( "Trajet" );
      marker = new OpenLayers.Marker(position);
      marker.setOpacity(1);
      marker.events.register('mousedown', marker, function(evt) { alert(this.icon.url); OpenLayers.Event.stop(evt); });
      markers.addMarker(marker); 
      map.addLayer(markers);
      */

      /* On centre la carte sur la partie qui nous intesse */
      map.setCenter(position, zoom);
    </script>

    <div id="altishow" style="position:fixed; bottom: 100px; right:0px;z-index:10000;" onClick="javascript:showHideBlock('alti');showHideBlock('altihide');showHideBlock('altishow');" class="hidden">
      <img src="OpenLayers-2.13.1/img/layer-switcher-maximize.png"/>
    </div>
    <div id="alti" style="position:fixed; bottom: 100px; right:0px;z-index:10000;border:solid 1px;" >
      <img src="graphique_km_altitude.png" width="600" align="right"/>
    </div>
    <div id="altihide" style="position:fixed; bottom: 280px; right:0px;z-index:10002;" onClick="javascript:showHideBlock('alti');showHideBlock('altihide');showHideBlock('altishow');">
      <img src="OpenLayers-2.13.1/img/east-mini.png" />
    </div>
  </body>      
</html>

Ne pas oublier de mettre dans le même répertoire les fichiers :

  • Trace_Simple.kml
  • graphique_km_altitude.png
  • Le répertoire décompressé de la librairie OpenLayers

Et voilà  !