Pour faire suite à un ancien article ou je proposais une façon de charger en base de données un itinéraire GPX (et d’autres données), je propose cette fois d’afficher ces données sur une carte Leaflet.

Pour rappel, les données sont dans une base MySQL avec quelques champs dont un « geom » ou sont stockés les géométrise. MySQL propose seulement deux façon de lire les types GEOMETRY :

  • Le WKT, facilement lisible
  • Le WKB, la version binaire

Ici on va donc opter pour l’utilisation du WKT qui est plus simple à comprendre et pas forcement moins performante. Pour convertir du WKB, il existe la librairie GEOPHP

Afin d’afficher les trace sur la carte nous avons là encore plusieurs possibilités… Nous allons en voir deux un peu différente:

  • Créer un GeoJSON coté serveur (en PHP) à partir des données pour qu’elles soient lisibles nativement dans Leaflet
  • Envoyer les données telles quelles (la géométrie en WKT) et les transformer côté client (en JS)

Transformer les données côté serveur

Les données issues de la requête vont être directement transformer coté serveur pour former un GeoJSON qui sera envoyé au client. J’ai utilisé une classe PHP (php/wkt2json.php) que j’avais réalisé il y a quelque temps pour transformer une chaine WKT en géométrie de type GeoJSON.

Le code PHP est appelé en ajax(vià Jquery)  par le client qui lui donne en paramètre le type de trace (VTT, pedestre…)

<?php
// fichier de config où se trouve le mot de passe et les paramètres de connexion à la bdd
include_once('config.php');
include_once('wkt2json.php');

$type_trace = $_POST['type']; // on stocke le type dans une variable

// fonction qui fait appelle à la class pour retourner la géometrie du WKT en format geojson 
function wkt_to_json($wkt) {
    $geom = new WktToJson($wkt);
    return $geom->getGeometryGeojsonFromWkt();
}

// le type est null, on affiche toutes les traces
if ($type_trace == '*' ){
    $sql = 'SELECT id,nom,distance,d_plus,d_moins,type_trace, AsText(geom) as wkt FROM gpx';
    $req = $conn->prepare($sql);
    $req->execute();
}

// on filtre selon le type car on a envoyé un paramètre (via le menu déroulant)
else {
    $sql = 'SELECT id,nom,distance,d_plus,d_moins,type_trace, AsText(geom) as wkt FROM gpx WHERE type_trace = :type';
    $req = $conn->prepare($sql);
    $req->execute(array(
		'type' =>   $type_trace
		));  
}

//Construction du GeoJSON
$geojson = array(
    'type'      => 'FeatureCollection',
    'features'  => array()
);

// boucle pour traiter le résultat de la requete et remplir le geojson
while ($row = $req->fetch(PDO::FETCH_ASSOC)) {
    $properties = $row;
    //On enlève les propriétés des géométries
    unset($properties['wkt']);
    unset($properties['geom']);
    $feature = array(
        'type' => 'Feature',
        'geometry' => json_decode(wkt_to_json($row['wkt'])),
        'properties' => $properties
    );
    // on push la feature
    array_push($geojson['features'], $feature);
}

echo json_encode($geojson);
?>

getTrace.php

Coté client, un code simple avec quelques commentaires :

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8">
  <title>Afficher des lignes sur leaflet</title>
    
	<link rel="stylesheet" href="style_main.css" />
    <link rel="stylesheet" href="leaflet.css" />
	

 <script src="lib/leaflet.js"></script>
 <script src="lib/jquery.js"></script>

<div id="top">
	<label for="type_activite">Type d'activité :</label>
		<SELECT id="type_activite" name="type_activite" onchange="typeChange()">
	        	<OPTION VALUE='*'>Tout</OPTION>
				<OPTION VALUE="Pedestre">Pedestre</OPTION>
				<OPTION VALUE="VTT">VTT</OPTION>
				<OPTION VALUE="Musher">Musher</OPTION>
		</SELECT>
		<label id="nb_traces"> </label>
</div>		
<div id="map" ></div>
   
	
  <script type="text/javascript">
  //map
  var map = L.map('map').setView([ 45.20,5.8 ], 10);
  //url des TMS de fond
    var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',maxZoom: 18});
    var google =  L.tileLayer('http://khms0.googleapis.com/kh?v=145&hl=fr-FR&x={x}&y={y}&z={z}', {maxZoom: 20}) // 
    
     // option pour le selecteur de couche
    var baseMaps = {
        "OSM": osm,
        "Ortho Google":google,
    };
    //on ajoute le fond OSM au départ
    osm.addTo(map)
    //on ajoute le 'control' des fonds de carte
    L.control.layers(baseMaps).addTo(map);
    var FGgpx = L.featureGroup().addTo(map); // feature group ou on va inserer nos traces

// la fonction getTrace() définie plus bas, génère un geojson et l'affiche dans leafLet, on l'execute au départ
    getTrace('*'); // => * permet de tout afficher


//style a appliquer pour changer la couleur selon le type d'activité par exemple
function monStyle(feature) {
        switch (feature.properties.type_trace) {
            case 'VTT': return {color: "green"};
            case 'Pedestre':   return {color: "red"};
            default:  return {color: "black"};
        }
}

//pour chaque trace...
function onEachFeature(feature, layer) {
    //on lui fait afficher une popup lors d'un click
        layer.bindPopup('Nom : ' + feature.properties.nom +'</br> distance : ' +feature.properties.distance );
    //on lui applique le style
        layer.setStyle(monStyle(feature));
    //on ajoute 1 pour compter et on l'affiche
        nb_trace++; 
    // affiche ce nombre
        $("#nb_traces").html('Nombre de traces : '+ nb_trace);
}

 /* --------- On click sur envoyer, les donn饳 sont trait頰ar 'send_to_bdd.php' qui retourne un tableau JSON pour indiqu頣e qu'il s'est pass魭--------*/
function getTrace(type_trace){
 $.ajax({
           type: "POST",
            url: "php/getTrace.php",
			data:{type: type_trace}, // on peut ajouter des paramètres au POST: ici par exemple on peut récupérer le type : $_POST['type'] 
            success: result, // si tout s'es bien pass鬠on execute la fonction "result"
            dataType: "json"
            });

			function result(data){
			    nb_trace = 0; // stok le nombre de traces
			    // on efface la  featureGroup qui contient les traces
			    FGgpx.clearLayers();
			    // on a notre geojson, on l'affiche dans la carte
			    // le geojson est généré a la volé. On peut le voir ici : getTrace.php
			    //on lui passe la fonction onEachFeature pour chaque feature
			    var mon_geojson = L.geoJson(data,{onEachFeature: onEachFeature});
			    mon_geojson.addTo(FGgpx);
			    map.fitBounds(FGgpx.getBounds());
			}
}

//quand le type d'activiter change (menu deroulant)
  function typeChange (){
    // on appelle getTrace en lui donnant le type d'activité en paramètre
      getTrace($('#type_activite').val());
  }
</script>
    </body>
</html>

Le résultat est visible ici

Les sources sont ici

Transformer les données côté clients

Ici le PHP nous sert juste d’intermédiaire entre le client et la base de données. Aucune transformation n’est faite (sauf la transformation du résultat en json).

<?php
// fichier de config où se trouve le mot de passe et les paramètres de connexion à la bdd
include_once('config.php');
$type_trace = $_POST['type']; // on stocke le type dan sune ariable


// le type est null, on affiche toutes les traces
if ($type_trace == '*' ){
    $sql = 'SELECT id,nom,distance,d_plus,d_moins,type_trace, AsText(geom) as wkt FROM gpx';
    $req = $conn->prepare($sql);
    $req->execute();
}

// on filtre selon le type car on a envoyé un paramètre (via le menu déroulant)
else {
    $sql = 'SELECT id,nom,distance,d_plus,d_moins,type_trace, AsText(geom) as wkt FROM gpx WHERE type_trace = :type';
    $req = $conn->prepare($sql);
    $req->execute(array(
		'type' =>   $type_trace
		));  
}

$result = $req->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($result);
?>

On réceptionne donc du WKT du coté du client. Ça donc être à LeafLet de le modifier pour qu’il puisse le lire. Heureusement il existe plusieurs libraires pour Leaflet qui peuvent convertir le WKT. Pour ce test j’ai utilisé l’excellent Omnivore de Mapbox.

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8">
  <title>Afficher des lignes sur leaflet</title>
    
	<link rel="stylesheet" href="style_main.css" />
    <link rel="stylesheet" href="leaflet.css" />
	

 <script src="lib/leaflet.js"></script>
 <script src="lib/jquery.js"></script>
 <script src="lib/leaflet-omnivore.min.js"></script>

<div id="top">
	<label for="type_activite">Type d'activité :</label>
		<SELECT id="type_activite" name="type_activite" onchange="typeChange()">
	        	<OPTION VALUE='*'>Tout</OPTION>
				<OPTION VALUE="Pedestre">Pedestre</OPTION>
				<OPTION VALUE="VTT">VTT</OPTION>
				<OPTION VALUE="Musher">Musher</OPTION>
		</SELECT>
		<label id="nb_traces"> </label>
</div>		
<div id="map" ></div>
   
	
  <script type="text/javascript">
  //map
  var map = L.map('map').setView([ 45.20,5.8 ], 10);
  //url des TMS de fond
    var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',maxZoom: 18});
    var google =  L.tileLayer('http://khms0.googleapis.com/kh?v=145&hl=fr-FR&x={x}&y={y}&z={z}', {maxZoom: 20}) // 
    
     // option pour le selecteur de couche
    var baseMaps = {
        "OSM": osm,
        "Ortho Google":google,
    };
    //on ajoute le fond OSM au départ
    osm.addTo(map)
    //on ajoute le 'control' des fonds de carte
    L.control.layers(baseMaps).addTo(map);
    var FGgpx = L.featureGroup().addTo(map); // feature group ou on va inserer nos traces

// la fonction getTrace() définie plus bas, génère un geojson et l'affiche dans leafLet, on l'execute au départ
    getTrace('*'); // => * permet de tout afficher


//style a appliquer pour changer la couleur selon le type d'activité par exemple
function monStyle(feature) {
        switch (feature.type_trace) {
            case 'VTT': return {color: "green"};
            case 'Pedestre':   return {color: "red"};
            default:  return {color: "black"};
        }
}

        
// fonction qui nous renvois les données avec la géométrie en WKT
function getTrace(type_trace){
 $.ajax({
           type: "POST",
            url: "php/getTrace.php",
			data:{type: type_trace}, // on peut ajouter des paramètres au POST: ici par exemple on peut récupérer le type : $_POST['type'] 
            success: result,
            dataType: "json"
            });

			function result(data){
			   
			    // on efface la  featureGroup qui contient les traces
			    FGgpx.clearLayers();
			   
			 //les données de la requetes sont dans "data". la géometrie est en WKT dans le champs "wkt".
			 //Ainsi pour accéder à la géometrie de la 1ere ligne : data[0].wkt
		
		    //on boucle sur chaque ligne de resultat
			 for (var i = 0; i<data.length;i++){
			    var str_wkt = data[i].wkt; // la chaine WKT
			    var la_feature = omnivore.wkt.parse(str_wkt); // transformation du wkt en format Leaflet avec omnivore
			    
			    //ajout de la popup sur l'objet
			     la_feature.bindPopup('Nom : ' + data[i].nom +'</br> distance : ' +data[i].distance );
			    //on applique le style
			    la_feature.setStyle(monStyle(data[i]));
			    //on l'ajoute à la featureGroup
			    la_feature.addTo(FGgpx);
			 }//fin de la boucle
			 
			 //on indique le nombre de traces avec Jquery
			 $("#nb_traces").html('Nombre de traces : '+ data.length);
			 //on zoom sur l'ensemble des traces affichées
			 map.fitBounds(FGgpx.getBounds());
		
			}
}

//quand le type d'activiter change (menu deroulant)
  function typeChange (){
    // on appelle getTrace en lui donnant le type d'activité en paramètre
      getTrace($('#type_activite').val());
  }
  

</script>
    </body>
</html>

index.html

Le résultat est ici

Les sources ici


Le résultat est exactement le même avec les deux méthodes mais la dernière reste un peu plus efficace en terme de temps de rendu.

Le type GEOMETRY dans Mysql offre comme avantage de pouvoir faire de simples requêtes spatiales ou de pouvoir être directement lu/modifier sur un SIG tel que QGIS. Néanmoins il est impossible de stocker des géométrie en 3D contrairement à Postgis par exemple.

On pourrait également stocker les .gpx dans un champs texte de la table et utiliser Omnivore ou autres pour l’afficher sur la carte ainsi tous les éléments du fichiers seront sauvegardés (altitude, fréquence cardiaque, etc) mais au prix d’un alourdissant considérablement des données transmises. Une autre possibilité est de stocker la géométrie dans un champs de type texte en geojson ou directement en format LeafLet où l’on pourrait intégré l’altitude ([y,x,z])