Faire des recherches avec OpenStreetmap

Par Nouvelle-Techno.fr le 14 février 2020 - Catégories : CSS HTML Javascript PHP Tutoriel Live-Coding

Lire l'article sur le site d'origine

Lorsqu'on génère une carte, il est parfois nécessaire de créer un formulaire permettant de faire des recherches et de filtrer les marqueurs affichés.

Dans l'exemple ci-dessous, nous allons mettre en place un formulaire contenant 2 informations :

Fonctionnement

Lors de la saisie de la ville, nous allons récupérer ses coordonnées GPS avec Nominatim, les stocker et enfin centrer la carte sur la ville.

Lors du choix de la distance, nous allons tracer un cercle sur la carte, rechercher tous les marqueurs se trouvant dans la zone en question et les afficher sur la carte.

Le code HTML et CSS

Nous allons définir le code HTML de la page, contenant la carte et les champs de formulaire.

Ce code a déjà été traité dans d'autres tutoriels, et sera le suivant

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>

        <!-- Fichiers CSS -->
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <!--Carte-->
        <div id="map"></div>

        <!--Champs de recherche-->
        <p>
            <label for="champ-ville">Ville : </label>
            <input type="text" id="champ-ville">
        </p>
        <p>
            <label for="champ-distance">Distance : </label>
            <input type="range" min="1" max="200" id="champ-distance">
        </p>
        <p id="valeur-distance"></p>
        
        <!-- Fichiers JS -->
        <script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" crossorigin=""></script>
        <script src="js/scripts.js"></script>
    </body>
</html>

Au niveau du CSS, nous devons définir une hauteur pour la carte, le CSS sera donc

#map{
    height: 600px;
}

Initialiser la carte et les variables

Il est maintenant nécessaire d'initialiser la carte et les variables dont nous aurons besoin.

Nous allons ajouter le code suivant dans "scripts.js"

// Variables globales
let ville = distance = ""

window.onload = () => {
    // On initialise la carte et on la centre sur Paris
    let carte = L.map('map').setView([48.852969, 2.349903], 13);
                
    // On charge les "tuiles"
    L.tileLayer('https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png', {
        // Il est toujours bien de laisser le lien vers la source des données
        attribution: 'données © <a href="//osm.org/copyright">OpenStreetMap</a>/ODbL - rendu <a href="//openstreetmap.fr">OSM France</a>',
        minZoom: 1,
        maxZoom: 20,
        name: 'tiles' // permettra de ne pas supprimer cette couche
    }).addTo(carte);
}

Gestion des champs

Nous allons maintenant gérer les modifications que l'utilisateur peut apporter à nos champs, en saisissant une ville et une distance.

Pour ce faire, nous allons mettre en place des écouteurs d'évènements "change" avec le code suivant

// On récupère les champs de la page
let champVille = document.getElementById('champ-ville')
let champDistance = document.getElementById('champ-distance')
let valeurDistance = document.getElementById('valeur-distance')


// On écoute l'évènement "change" sur le champ ville
champVille.addEventListener("change", function(){
    // Ici nous chercherons les coordonnées GPS de la ville saisie
})

champDistance.addEventListener("change", function(){
    // On récupère la distance choisie
    distance = this.value

    // On écrit cette valeur sur la page
    valeurDistance.innerText = distance + " km"

    // Ici nous chercherons les agences correspondant à la localisation souhaitée
})

Création d'une fonction "Ajax"

Afin de trouver les coordonnées GPS, nous utiliserons des requêtes Ajax. Pour optimiser le code, nous allons créer une fonction qui pourra être utilisée autant de fois que nécessaire.

Cette fonction contiendra le code suivant

/**
 * Cette fonction effectue un appel Ajax vers une url et retourne une promesse
 * @param {string} url 
 */
function ajaxGet(url){
    return new Promise(function(resolve, reject){
        // Nous allons gérer la promesse
        let xmlhttp = new XMLHttpRequest();

        xmlhttp.onreadystatechange = function(){
            // Si le traitement est terminé
            if(xmlhttp.readyState == 4){
                // Si le traitement est un succès
                if(xmlhttp.status == 200){
                    // On résoud la promesse et on renvoie la réponse
                    resolve(xmlhttp.responseText);
                }else{
                    // On résoud la promesse et on envoie l'erreur
                    reject(xmlhttp);
                }
            }
        }

        // Si une erreur est survenue
        xmlhttp.onerror = function(error){
            // On résoud la promesse et on envoie l'erreur
            reject(error);
        }

        // On ouvre la requête
        xmlhttp.open('GET', url, true);

        // On envoie la requête
        xmlhttp.send(null);
    })
}

Recherche des coordonnées GPS de la ville

Quand l'utilisateur aura saisi une ville, nous allons utiliser Nominatim pour trouver ses coordonnées GPS.

ATTENTION : ceci est la version "simple", vous devez mettre en place une vérification en cas de doublon de ville.

On modifiera donc l'écouteur d'évènements comme suit

champVille.addEventListener("change", function(){
    // On envoie la requête ajax vers nominatim et on traite la réponse
    ajaxGet(`https://nominatim.openstreetmap.org/search?q=${this.value}&format=json&addressdetails=1&limit=1&polygon_svg=1`).then(reponse => {
        // On convertit la réponse en objet Javascript
        let data = JSON.parse(reponse)

        // On stocke la latitude et la longitude dans la variable ville
        ville = [data[0].lat, data[0].lon]

        // On centre la carte sur la ville
        carte.panTo(ville)
    })
})

A partir de ce moment, notre variable "ville" contient un tableau avec la latitude et la longitude de la ville.

Extraction des données de la base de données

Nous allons utiliser une base de données contenant les différentes agences que nous souhaitons afficher sur la carte.

Cette base de données, volontairement simple, contiendra une seule table appelée "agences". Elle contiendra les données ci-dessous (données exemple)

La requête SQL

Pour effectuer une recherche géographique, nous allons devoir utiliser une requête SQL faisant appel à des formules mathématiques qui permettront de définir des limites circulaires.

Cette requête ressemblera à ceci, en partant des valeurs exemple suivantes :

SELECT id, nom, lat, lon, ( 6371 * acos( cos( radians(48.85296900) ) * cos( radians( lat ) ) * cos( radians( lon ) - radians(2.34990300) ) + sin( radians(48.85296900) ) * sin( radians( lat ) ) ) ) AS distance FROM `agences` HAVING distance < 50 ORDER BY distance

Le code PHP

Le fichier PHP, que nous appellerons "chargeAgences.php" contiendra donc

<?php
// On autorise les requêtes Ajax pour toutes les sources
header('Access-Control-Allow-Origin: *');

// On vérifie qu'on utilise la méthode GET
if($_SERVER['REQUEST_METHOD'] == 'GET'){
    // Ici on utilise la méthode GET
    // On se connecte à la base
    require_once('connect.php');

    // // On récupère les données dans la base
    
    $sql = "SELECT id, nom, lat, lon, ( 6371 * acos( cos( radians(:lat) ) * cos( radians( lat ) ) * cos( radians( lon ) - radians(:lon) ) + sin( radians(:lat) ) * sin( radians( lat ) ) ) ) AS distance FROM `agences` HAVING distance < :distance ORDER BY distance";

    $query = $db->prepare($sql);

    $query->bindValue(':lat', $_GET['lat'], PDO::PARAM_STR);
    $query->bindValue(':lon', $_GET['lon'], PDO::PARAM_STR);
    $query->bindValue(':distance', $_GET['distance'], PDO::PARAM_INT);
    $query->execute();
    $result = $query->fetchAll();

    // // On envoie le code de confirmation
    http_response_code(200);

    // // On envoie les données en json
    echo json_encode($result);

    // On se déconnecte de la base
    require_once('close.php');
}else{
    http_response_code(405);
    echo 'La méthode n\'est pas autorisée';
}

Charger les marqueurs et les afficher

Nous allons maintenant revenir dans le javascript et charger les marqueurs correspondant à la recherche de l'utilisateur.

Cette recherche fonctionnera comme suit :

Tout ceci s'effectuera dans l'écouteur d'évènements placé sur le choix de la distance.

Le code sera le suivant

champDistance.addEventListener("change", function(){
    // On récupère la distance choisie
    distance = this.value

    // On écrit cette valeur sur la page
    valeurDistance.innerText = distance + " km"

    // On vérifie si une ville a été saisie
    if(ville != ""){
        // On envoie les données au serveur
        ajaxGet(`http://agences-osm.test/chargeAgences.php?lat=${ville[0]}&lon=${ville[1]}&distance=${distance}`).then(reponse => {
            // On efface toutes les couches de la carte sauf les tuiles
            carte.eachLayer(function (layer) {
                if(layer.options.name != "tiles") carte.removeLayer(layer);
            });

            // On trace le cercle de rayon "distance"
            let circle = L.circle(ville, {
                color: '#4471C4',
                fillColor: '#4471C4',
                fillOpacity: 0.3,
                radius: distance * 1000,
            }).addTo(carte);

            // On convertit la réponse en objet Javascript
            let donnees = JSON.parse(reponse)
                    
            // On boucle sur les données (ES8)
            Object.entries(donnees).forEach(agence => {
                // Ici j'ai une seule agence
                // On crée un marqueur pour l'agence
                let marker = L.marker([agence[1].lat, agence[1].lon]).addTo(carte)
                marker.bindPopup(agence[1].nom)
            })

            // On centre et on zoome sur le cercle
            bounds = circle.getBounds();
            carte.fitBounds(bounds);
        })
    }
})

Et voilà, la recherche est fonctionnelle

Obtenir de l'aide

Pour obtenir de l'aide, vous pouvez accéder au serveur Discord pour une entraide par chat

#Tutoriel #Javascript #MySQL #Openstreetmap #PHP #Live-Coding #css #html5 #ES6