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
- Récupérer et décompresser la librairie OpenLayers, disponible ici : OpenLayers
- Installer GPSBabel
- Installer GnuPlot
- Installer Python
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 :
- http://www.memoclic.com/618-google-earth/7886-creer-parcours-google-earth.html
- https://www.youtube.com/watch?v=TQSaZz5iwKQ
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
:
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à !