Recherche de coordonnées GPS

En réalisant un module todd_weather de prévisions métgéorologiques, j'ai eu besoin de pouvoir accéder aux coordonnées GPS d'un lieu. Comment récupérer ces coordonnées GPS pour une ville connaissant son nom ?

Voici deux solutions, une en back end via PHP, une autre en front-end via JS.

Open Meteo Geocoding (PHP)

Utilisation de l'API open-meteo-geocoding. On peut interroger la base pour n'importe quelle ville au monde mais pas de précision pour les petite villes en France.

require_once __DIR__ . '/../vendor/autoload.php';
use Flibidi67\OpenMeteoGeocoding\Service\GeocodingService;

function getCoordinates($city, $code)
{
    $latitude = '';
    $longitude = '';

    try {
        $api = new GeocodingService();
        $response = $api->get($city);

        // 1. On vérifie que la réponse n'est pas vide et qu'elle contient bien le premier index
        if (!empty($response) && isset($response[0])) {

            // 2. On isole le premier résultat (la ville principale)
            $firstResult = $response[0];

            // 3. On extrait les coordonnées exactes
            $latitude = $firstResult['latitude'];
            $longitude = $firstResult['longitude'];

            // 4. Réponse
            return ['latitude' => $latitude, 'longitude' => $longitude];

        } else {
            return;
        }

    } catch (Exception $e) {
        echo "<div style='color: white; background: red; padding: 10px;'>";
        echo "<strong>Erreur :</strong> " . $e->getMessage();
        echo "</div>";
    }
}

geo.api.gouv.fr (JS)

On utilise une API développée par l'État Français rescensant toutes le communes de France avec leurs codes postaux, leurs coordonnées,etc.

Voici le code JavaScript permettant de :

  1. créer une suggestion dans un champ de saisi texte basée sur des résultats d'interrogation de l'API
  2. remplir les champs cachés d'un formulaire par les valeurs récupérées lors du clic sur un nom de la liste.
document.addEventListener('DOMContentLoaded', function () {
    const input = document.getElementById('weather_city_input');
    // Empêche la soumission du formulaire si on tape sur Entrée dans le champ de recherche
    input.addEventListener('keydown', function (e) {
        if (e.key === 'Enter') {
            e.preventDefault();
        }
    });
    const suggestionsList = document.getElementById('weather_suggestions');
    const hiddenName = document.getElementById('weather_city_name');
    const hiddenCode = document.getElementById('weather_postal_code');
    const hiddenLat = document.getElementById('weather_lat');
    const hiddenLon = document.getElementById('weather_lon');

    let timeoutId;

    input.addEventListener('input', function () {
        clearTimeout(timeoutId);
        const query = this.value.trim();

        // On ne cherche qu'à partir de 2 caractères
        if (query.length < 2) {
            suggestionsList.style.display = 'none';
            return;
        }

        // Debounce : on attend 300ms après la dernière frappe pour ne pas spammer l'API
        timeoutId = setTimeout(() => {

            // Détection si c'est un code postal (que des chiffres) ou un nom
            const isPostalCode = /^\d+$/.test(query);
            const param = isPostalCode ? `codePostal=${query}` : `nom=${query}`;

            // Appel à l'API Geo Gouv avec une limite fixée à 5 résultats
            fetch(`https://geo.api.gouv.fr/communes?${param}&fields=nom,code,codesPostaux,centre&format=json&geometry=centre&limit=5`)
                .then(response => response.json())
                .then(data => {
                    suggestionsList.innerHTML = '';

                    if (data.length > 0) {
                        data.forEach(city => {
                            const li = document.createElement('li');
                            // Format d'affichage : "Toulouse (31000)"
                            li.textContent = `${city.nom} (${city.codesPostaux[0]})`;

                            // Au clic sur une suggestion
                            li.addEventListener('click', () => {
                                // 1. On remplit l'input visible
                                input.value = li.textContent;

                                // 2. On remplit les champs cachés pour la sauvegarde en BDD
                                hiddenName.value = city.nom;
                                hiddenCode.value = city.codesPostaux[0];
                                // L'API gouv renvoie [longitude, latitude]
                                hiddenLon.value = city.centre.coordinates[0];
                                hiddenLat.value = city.centre.coordinates[1];

                                // 3. On cache la liste
                                suggestionsList.style.display = 'none';

                                console.log('Nom : ' + hiddenName.value + ' Code postal : ' + hiddenCode.value + ' Latitude : ' + hiddenLat.value + ' Longitude : ' + hiddenLon.value);
                            });

                            suggestionsList.appendChild(li);
                        });
                        suggestionsList.style.display = 'block';
                    } else {
                        suggestionsList.style.display = 'none';
                    }
                })
                .catch(err => console.error("Erreur API Geo:", err));
        }, 300);
    });

    // Cacher la liste si on clique ailleurs sur la page
    document.addEventListener('click', function (e) {
        if (!e.target.closest('.todd-autocomplete')) {
            suggestionsList.style.display = 'none';
        }
    });
});

Le formulaire qui va avec ce code :

<form method="POST" action="" id="weather_form">
    <label for="weather_city_input">Rechercher une ville ou un code postal :</label>
    <input type="text" id="weather_city_input" class="form-control" placeholder="Ex: 31000 ou Toulouse"
        autocomplete="off">
    <input type="hidden" id="weather_city_name" name="city_name">
    <input type="hidden" id="weather_postal_code" name="postal_code">
    <input type="hidden" id="weather_lat" name="latitude">
    <input type="hidden" id="weather_lon" name="longitude">
    <ul id="weather_suggestions" class="suggestions-list" style="display: none;"></ul>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>