L’objectif est d’insérer la trace d’un fichier GPX dans une base de données (ici MySQL mais le principe est le même pour PostGIS ou Oracle).
Le résultat final est ici. Le code complet est là
Les ingrédients pour cette recette
- Une grosse dose de Leaflet
- Une petitesextension de Leaflet pour lire les traces GPX
- Une pincé d’une autreextension de Leafletafin de transformer la géométrie en WKT
Vous allez également avoir besoin de quelques ustensiles :
- Du coté Javascript on utilisera du Jquerry pour simplifier l’Ajax
- Du PHP pour traiter les requêtes
- Et bien sûr, une base de données pour stocker tout ça au frais
Commençons par la création de la table qui recevra les données, voici la requête SQL :
CREATE TABLE `gpx` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nom` varchar(200) DEFAULT NULL,
`distance` int(12) DEFAULT NULL,
`d_plus` int(12) DEFAULT NULL,
`d_moins` int(12) DEFAULT NULL,
`type_trace` varchar(100) DEFAULT NULL,
`origine` varchar(100) NOT NULL,
`date_trace` date NOT NULL,
`descriptionn` text,
`geom` geometry NOT NULL,
PRIMARY KEY (`id`)
)
Création de la table
Nous avons donc notre table ‘gpx’ contenant 11 champs.
Le champ ‘id’ est la clé primaire, le champ ‘geom’ recevra la géométrie, les champs ‘distance’, ‘d_plus’, ‘d_moins’ (dénivelé positif et négatif) et nom seront déduis automatiquement à partir du fichier GPX. Le reste des champs seront remplis par l’utilisateur.
Tout est prêt, entrons donc dans le vive du sujet.
Commençons par créer un petit fichier php qui va contenir les paramètres de configuration de l’application (connexion à la bdd, mot de passe de l’application)
<?php
$password_attendu = 'demo';
$cfg_db_host = 'localhost';
$cfg_db_db = 'db_gpx';
$cfg_db_user = 'root';
$cfg_db_password = '';
?>
config.php
On va créer une toute petite page d’authentification pour que tout le monde ne puisse pas accéder à l’application. Il n’y aura qu’un champ pour entrer le mot de passe et un bouton pour valider. Ce sera la page de démarrage, nous allons donc l’appelé ‘index.html’.
<!DOCTYPE html>
<html>
<head>
<title>Importer les GPX</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<link rel="stylesheet" href="/index.css" />
</head>
<body>
<div id = "main" >
<div id ="connexion"> <h1>Importer des GPX</h1> </div>
<form name="formulaire" action="php/login.php" method="post">
<p>Mot de passe : <input type="password" name="password" value = 'dada'/></p>
<input id="btn" type="submit" value="Connexion">
</form>
</div>
</body>
</html>
index.html
C’est la page ‘ login.php’ qui va traiter la requête, voici le code commenté :
<?php
include 'config.php';
// on teste si nos variables sont définies
if ( isset($_POST['password'])) {
// on vérifie les informations du formulaire (ici que le mot de passe est le bon)
if ($password_attendu == $_POST['password']) {
// dans ce cas, tout est ok, on peut démarrer notre session
session_start ();
// on enregistre mot de passe comme variables de session
$_SESSION['password'] = $_POST['password'];
// on redirige notre visiteur vers la page principale
header ('location: ../main.php');
}
else {
// Le mot de passe est incorrect. On utilise alors un petit javascript lui signalant ce fait
echo '<body onLoad="alert(\'Mauvais mot de passe\')">';
// puis on le redirige vers la page d'accueil
echo '<meta http-equiv="refresh" content="0;URL=../index.html">';
}
}
else {
echo 'Les variables du formulaire ne sont pas déclarées.';
}
?>
login.php
Passons maintenant à la page principale.
Elle va être constitué d’une carte sur la droite et d’une barre verticale à gauche permettant d’ouvrir le GPX de renseigner les données et de valider tout ça pour l’envoyer dans la BDD
<?php
include 'php/config.php';
session_start ();
if ($_SESSION['password'] != $password_attendu ){ // si le mot de passe stocké dans la session est le bon, on continue sur cette page, sinon on le reirige vers la page d'accueil
header("location:index.html");
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Importer les GPX</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=1024, user-scalable=no">
<link rel="stylesheet" href="/style_main.css" />
<link rel="stylesheet" href="/leaflet.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.ie.css" />
<![endif]-->
<script src="/lib/leaflet.js"></script>
<script src="/lib/gpx.js"></script>
<script src="/lib/LeafletToWKT.js"></script>
<script src="/lib/jquery.js"></script>
<div id="bloc_page">
<div id="barre_droite">
<!-- le bouton parcourir -->
<input id="file" type="file" onchange="load_file();" />
<!-- les formulaires-->
<form name="formulaire">
<label for="nom_fichier">Nom du fichier :</label>
<input type="text" name="nom_fichier" value="" disabled>
<label for="distance">Distance :</label>
<input type="text" name="distance" value="">
<label for="D+">D+ :</label>
<input type="text" name="D+" value="">
<label for="D-">D- :</label>
<input type="text" name="D-" value="">
<label for="decription">Description :</label>
<input type="text" name="description" value="">
<label for="origine">Origine du fichier :</label>
<input type="text" name="origine" value="">
<label for="date_trace">Date de la trace :</label>
<input type="date" name="date_trace" value="">
<label for="type_activite">Type d'activité :</label>
<SELECT id="type_activite" name="type_activite">
<OPTION VALUE="Pedestre">Pedestre</OPTION>
<OPTION VALUE="VTT">VTT</OPTION>
<OPTION VALUE="Equestre">Equestre</OPTION>
<OPTION VALUE="Chemin d acces">Chemin d'accès</OPTION>
<OPTION VALUE="Trail">Trail</OPTION>
<OPTION VALUE="Raquette">Raquette</OPTION>
<OPTION VALUE="Ski de fond">Ski de fond</OPTION>
<OPTION VALUE="Musher">Musher</OPTION>
</SELECT>
</form>
<!-- Le bouton envoyer qui va valider les données et les traités -->
<button id = 'btn_envoyer' onclick="envoyer_bdd()">Envoyer</button>
</div>
<div id="map" ></div>
<script type="text/javascript">
/* --------- initialisation des variable----------*/
data_obj = new Object();
feat_gpx = '';
wkt = "";
m = "";
lat_start = 44.8;
long_start = 5.2;
zoom_start = 8;
/* --------- création de la carte Leaflet----------*/
var mapQuestAttr = 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> — ';
var osmDataAttr = 'Map data © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';
var mopt = {
url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpeg',
options: {attribution:mapQuestAttr + osmDataAttr, subdomains:'1234'}
};
var mq=L.tileLayer(mopt.url,mopt.options);
m = L.map('map', {
center: new L.LatLng(lat_start, long_start), // centre de la carte en WGS84
zoom: zoom_start, // zoom de départ
layers: [mq]
});
/* --------- fonction qui reset les variable (vu carto, reset des formulaires, etc ----------*/
function reset(){
m.removeLayer(feat_gpx);
feat_gpx = '';
data_obj = new Object(); // objet ou seront stocké les données en vu de les envoyer vers la bdd
document["formulaire"].elements["distance"].value= "";
document["formulaire"].elements["D+"].value= "";
document["formulaire"].elements["D-"].value= "";
document["formulaire"].elements["nom_fichier"].value= "";
document["formulaire"].elements["origine"].value="";
document["formulaire"].elements["date_trace"].value="";
document["formulaire"].elements["description"].value="";
m.setView(new L.LatLng(lat_start, long_start), zoom_start);
}
/* --------- chargement du fichier----------*/
fileInput =$('#file')[0];
function load_file() {
reset();
var reader = new FileReader();
reader.onload = function() {
data_obj['gpx_full'] = reader.result;
data_obj['nom'] = fileInput.files[0].name;
document["formulaire"].elements["nom_fichier"].value= data_obj['nom'];
exec_gpx(m,data_obj.gpx_full);
};
reader.readAsText(fileInput.files[0]);
};
/* --------- Le GPX est chargé, on execute cette fonction pour ajouter la trace à la carte, pré remplir les formulaires et stocké la géométrie----------*/
feat_gpx = new L.featureGroup(); // ou va etre stocké la trace gpx ( de type L.featureGroup)
function exec_gpx(_map,_gpx) {
new L.GPX(_gpx, {
async: true,
marker_options: {
startIconUrl: 'image/pin-icon-start.png', // icone du départ
endIconUrl: 'image/pin-icon-end.png', // icone de l'arrivé
shadowUrl: 'image/pin-shadow.png', // ombre des icones
},
}).on('loaded', function(e) {
feat_gpx = e.target; // on stock la géométrie de la trace dans notre variable
//console.log( feat_gpx); // pour debug
_map.fitBounds(feat_gpx.getBounds()); // On zoom sur l'emprise de la trace
data_obj['distance'] = feat_gpx.get_distance().toFixed(0);
data_obj['dev_cum_pos'] = feat_gpx.get_elevation_gain().toFixed(0);
data_obj['dev_cum_neg'] = feat_gpx.get_elevation_loss().toFixed(0);
document["formulaire"].elements["distance"].value= data_obj.distance;
document["formulaire"].elements["D+"].value= data_obj.dev_cum_pos;
document["formulaire"].elements["D-"].value= data_obj.dev_cum_neg;
// conversion en WKT
var feature = feat_gpx.getLayers();
feature = feature[0].getLayers()[0];
data_obj['wkt'] = toWKT(feature); // on stocke le WKT dans l'objet final
}).addTo(_map);
}
/* --------- On click sur envoyer, les données sont traité par 'send_to_bdd.php' qui retourne un tableau JSON pour indiqué ce qu'il s'est passé----------*/
function envoyer_bdd(){
$.ajax({
type: "POST",
url: "php/send_to_bdd.php",
data:
"&nom="+ data_obj.nom +
"&distance=" + document["formulaire"].elements["distance"].value +
"&d_plus=" + document["formulaire"].elements["D+"].value +
"&d_moins=" +document["formulaire"].elements["D-"].value +
"&type_activite=" +document["formulaire"].elements["type_activite"].value+
"&origine=" +document["formulaire"].elements["origine"].value+
"&date_trace=" +document["formulaire"].elements["date_trace"].value+
"&description=" +document["formulaire"].elements["description"].value+
"&wkt=" +data_obj.wkt ,
success: result, // si tout s'es bien passé, on execute la fonction "result"
dataType: "json"
});
function result(data){
if (data.statut == 1) { // tout s'est bien passé, on l'annonce à l'utiliateur et on reset tout
alert(data.retour);
reset();
clear_file();
}
if (data.statut ==2 ||data.statut ==3 || data.statut ==4 || data.statut ==5 ){ // là y'a un problème...
alert(data.retour);
}
}
}
// pour reinitialiser le bouton parcourir, mais ne fonctionne pas sur IE...
function clear_file(){
fileInput = document.getElementById("file");
fileInput.value = "";
fileInput.focus();
}
</script>
</div>
</body>
</html>
main.php
Et voici le code commenté de la page de traitement de l’envoi des données :
<?php
// fichier de config où se trouve le mot de passe et les paramètres de connexion à la bdd
include '../php/config.php';
// récupération des variables
$nom = $_POST['nom'];
$distance = $_POST['distance'];
$d_plus = $_POST['d_plus'];
$d_moins = $_POST['d_moins'];
$wkt = $_POST['wkt'];
$type_activite = $_POST['type_activite'];
$date_trace = $_POST['date_trace'];
$origine = $_POST['origine'];
$description = $_POST['description'];
//regexp pour verifier que la géométrie en wkt est bien une linestring
if (preg_match("/^LINESTRING\(/", $wkt)){ // c'est un WKT linestring
//si c'est le cas on tente de pousser les données dans la base
push_to_bdd();
}
else{
//sinon on indique à l'utilisateur que la géométrie pose problème
echo json_encode(array("retour"=>'aucun tracé chargé ou la géométrie pose problème',"statut"=>2));
die();
}
// la fonction principale qui tente de pousser les données dans la base
function push_to_bdd(){
// les variables distantes deviennent globales pour être accessible dans la fonction
global $nom,$distance,$d_plus,$d_moins,$wkt,$type_activite,$origine,$date_trace,$description ;
global $cfg_db_db,$cfg_db_user,$cfg_db_password, $cfg_db_host;
// on tente de se connecter à la base
try{
$bdd = new PDO('mysql:host='.$cfg_db_host.';dbname='.$cfg_db_db, $cfg_db_user , $cfg_db_password);
}
catch (Exception $e){
echo json_encode(array("retour"=>'Erreur de connexion à la bdd',"statut"=>3, "e" => $e ));
die();
}
// on débute la transaction de la requete paramétrée
try {
$bdd->beginTransaction();
// on prepare la requete (transformation de la date(en string) en type date et de la géometrie en WKT en type geométrie
$req = $bdd->prepare("INSERT INTO gpx(nom, distance, d_plus, d_moins, type_trace, origine, date_trace, descriptionn, geom) VALUES(:nom, :distance, :d_plus, :d_moins,:type_activite,:origine,STR_TO_DATE(:date_trace,'%Y-%m-%d'),:description, GeomFromText(:geom,4326))");
// on execute la requete avec les paramètres que l'on défini
$req->execute(array(
'nom' => utf8_decode( $nom),
'distance' => $distance,
'd_plus' => $d_plus,
'd_moins' => $d_moins,
'type_activite' => utf8_decode($type_activite),
'origine' => utf8_decode($origine),
'date_trace' => $date_trace,
'description' => utf8_decode($description),
'geom' => $wkt
));
// on récupère l'id du dernier enregistrement
$last_insert = $bdd->lastInsertId();
// si ce dernier est = à 0, il y a un problème
if ($last_insert == 0){
echo json_encode(array("retour"=>"Une erreur lors de l'insert s'est produite","statut"=>5));
}
//sinon tout va bien
else {
$bdd->commit();
echo json_encode(array("retour"=>"Import de ".$nom. " réussi","statut"=>1));
}
}
// si on a une erreur dans la transaction
catch(PDOExecption $e) {
echo json_encode(array("retour"=>"Erreur lors de l'execution de la requete","statut"=>4));
$bdd->rollback();
die();
}
} //EOF la fonction push to bdd
?>
send_to_bdd.php
Et voilà.
Pour consulter les traces, nous pouvons nous connecter directement à la base (en lecture seule) via QGIS par exemple.
Le prochain tuto permettra d’afficher ces GPX via très certainement Openlayers 3 histoire de varier les plaisirs.