Afficher des géometries de MySQL sur LeafLet

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])

 

 

9 pensées sur “Afficher des géometries de MySQL sur LeafLet

  • 14 décembre 2014 à 11 h 58 min
    Permalink

    bojour,
    merci pour le tuto … Malheureusement si pour la première partie (article d’import dans bdd) tout s’était bien passé, là je bloque sur l’affichage des traces. J’ai beau essayer ( je ne suis surement pas très doué), j’ai toujours le même erreur :PHP Fatal error: Call to a member function prepare() on a non-object in /home/vttplusn/public_html/leaflet/bdd/php/getTrace.php on line 12 qui correspond à cette ligne (la 12) $req = $conn->prepare($sql);
    Pour l’instant je laisse un peu de côté.
    bien cordialement

    Répondre
      • 29 décembre 2014 à 10 h 20 min
        Permalink

        bonjour,
        après renseignements pris auprès de mon hébergeur, PDO est bien activé … et le fichier config.php est opérationnel puisque je peux « remplir » la table gpx. En fait mon souci est que je n’arrive pas à adapter votre exemple à mon cas. En fait je veux juste pouvoir me servir de la base pour afficher sur une carte avec leaflet le tracé enregistré en geometry et quelques informations sur le tracé. Pour que vous compreniez bien, j’ai mis ici « http://vttplus.net/essai5.zip » un exemple basique de ce que j’aimerai (fichier .php) … il manque juste l’affichage du tracé que je n’arrive pas à reproduire. Pour voir ce que donne cet exemple , c’est ici « http://vttplus.net/leaflet/essai5.php » les données s’affichent mais pas le tracé … j’ai mis dans la requête mysql le « AsText(geom) as wkt » mais je n’arrive pas à m’en servir.

        Bien cordialement jacques
        (je comprendrai bien sur que vous n’ayez pas forcément le temps de vous occuper de mon cas. )
        .

        Répondre
        • 5 janvier 2015 à 12 h 26 min
          Permalink

          Bonjour,
          Je n’ai pas l’habitude d’utiliser du PHP directement dans l’html, je l’appelle en principe en AJAX.

          Dans votre cas, vous transformez la géométrie en WKT, il faut donc le convertir pour leaflet et l’afficher.
          L’extension Omnivore de Mapbox le fait très bien (cf partie 2 de l’article).
          Il faut bien sur inclure cette bibliothèque javascript :

          Dans votre partie PHP, ferais comme cela :
          http://pastebin.com/dTDYRAL6

          Je n’ai pas encore testé mais ça devrait fonctionner.

          EDIT : Testé et corrigé : http://pastebin.com/PWsCq9Eq

          Répondre
          • 7 janvier 2015 à 8 h 19 min
            Permalink

            Bonjour,
            merci pour le retour … et bravo ça fonctionne très bien (ici: http://vttplus.net/leaflet/essai5.php). J’ai encore plein d’autres questions sur comment modifier le style par exemple, mais je vais d’abord chercher … et améliorer l’affichage de mon essai.
            Encore merci pour l’aide, la patience et le temps consacré à me répondre (c’est suffisamment rare pour le souligner).
            bien cordialement

  • 24 février 2015 à 12 h 58 min
    Permalink

    Bonjour,
    A aucun moment vous parlez de la base des donnée. C’est quoi ce que vous tirez? Mysql spatial extension?

    Répondre
    • 24 février 2015 à 14 h 02 min
      Permalink

      Bonjour,

      C’est précisé, ou au moins sous entendu:

      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 :

      De plus il y a la référence à un article précédent ou on a construit la base.

      Il y a également « AsText(geom) » dans les requêtes. Donc oui la géométrie est stocké dans un champ de type « geometry » de Mysql qui possède nativement une toute petite extension spatiale (rien a voir avec postgis par exemple)

      Mais il est a noter que le la géométrie est convertie en WKT (qui est du texte), donc il est tout a fait possible de la stocker directement dans un champs texte directement en WKT.

      Répondre
  • 12 mai 2017 à 13 h 51 min
    Permalink

    Salut !
    Je dois faire, pour un projet, une carte interactive leaflet qui affiche des marqueurs selon des données récupérer sur une BDD mysql
    Je voudrais savoir si votre code était approprié pour mon cas
    Merci

    Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *