Maintenant que nous savons afficher des cartes, on va essayer d'aller un peu plus loin ; aujourd'hui, nous allons calculer et afficher un itinéraire entre 2 points.

Le serveur de carte que nous avons installé précédemment ne va pas pouvoir nous aider, nous allons devoir utiliser un autre service pour ça. Il en existe plusieurs, j'ai décidé d'utiliser Itinero, qui est une librairie .NET, et qui propose aussi une api déployable directement.

Vous pouvez lire la documentation complète d'Itinero pour entrer plus en détail dans le sujet.

Création du fichier RouteDb

Nous avons déjà du effectuer une conversion des données OpenStreetMap vers un format vectoriel, le principe ici est le même, puisqu'Itinero ne va pas travailler non plus sur les données brutes, mais sur des données ne contenant que le nécessaires pour calculer un itinéraire (uniquement les routes, inutiles de s'encombrer des batiments ou des cours d'eau).

Les données OpenStreetMap peuvent toujours être récupérées par région depuis le site Geofabrik, et ensuite Itinero Data Processor permets d'effectuer la conversion en ligne de commande.

Tout ce que permets IDP est faisable par code grâce à un packge Nuget, et c'est ce que j'ai choisi de faire, afin d'automatiser complètement la récupération des données OSM, la conversion, et l'envoi du résultat vers un stockage cloud.

Pour cela, j'ai créé une application console, dans laquelle j'ai référencé les packages Nuget Itinero et Itinero.IO.Osm.
Puis quelques lignes de code pour récupérer les données de Midi-Pyrénées, les convertir, et sauvegarder le résultat.

var routerDb = new RouterDb();
var client = new HttpClient();

var url = "http://download.geofabrik.de/europe/france/midi-pyrenees-latest.osm.pbf";
using (var response = await client.GetAsync(url))
{
    routerDb.LoadOsmData(response.Content.ReadAsStream(), Vehicle.Car, Vehicle.Bicycle);
}

using (var stream = new FileInfo(@"midi-pyrenees.routerdb").Open(FileMode.Create))
{
    routerDb.Serialize(stream);
}

Deux choses importantes à voir dans ce code :

  • routerDb.LoadOsmData : cette méthode lis le fichier OpenStreetMap, et récupère les informations nécessaires au calcul d'itinéraire.
    Une liste de Vehicle est passé en paramètre, car selon le type d'itinéraire qu'on veut calculer, les données à utiliser ne seront pas les mêmes.
  • routerDb.Serialize : cette méthode permets d'enregistrer nos données dans un fichier RouterDb, ensuite nous n'utiliserons que ce fichier pour tous nos calculs d'itinéraires.

On a une liste de profils de véhicules prédéfinis (voiture, vélo, piéton ou camion…), et on peut définir d'autres profils personnalisés.

Vous pouvez lancer cette application, et la laisser tourner quelques minutes, ça sera plus ou moins long selon la taille de la zone à convertir, et le nombre de profils de véhicules que vous souhaitez gérer.

Calcul d'un itinéraire

Maintenant qu'on a un RouterDb, on va pouvoir l'utiliser pour calculer nos itinéraires.

On commence par charger les données depuis le fichier, puis on crée un Router utilisant ces données

RouterDb routerDb;
await using (Stream routerDbFile = await container.GetFile("midi-pyrenees.routerdb"))
{
    routerDb = RouterDb.Deserialize(routerDbFile);
}

var router = new Router(routerDb);

Il nous faut ensuite créer un Profile : avec quel type de véhicule on souhaite calculer l'itinéraire, et si on veut le plus rapide ou le plus court.

Là aussi, je reste sur les profils prédéfinis :

var profile = Vehicle.Car.Fastest();            // Trajet le plus rapide en voiture
var profile2 = Vehicle.SmallTruck.Shortest();   // Trajet le plus court en camionnette

Puis on choisit le point de départ et d'arrivée avec leurs coordonnées GPS, et on peut calculer l'itinéraire

var start = router.Resolve(profile, 43.60442f, 1.44403f);
var end = router.Resolve(profile, 43.23370f, 1.57595f);

var route = router.Calculate(profile, start, end);

L'objet Route contient toutes les informations qui nous intéressent, notamment :

  • Shape et ShapeMeta : les coordonnées de tous les points représentant la route, et les métadonnées associées
  • TotalDistance et TotalTime : la distance totale et la durée estimée du trajet

Ces informations suffisent maintenant à connaitre toutes les informations du trajet. Pour le dessiner sur une carte, on peut par exemple dessiner un trait entre chaque point du tableau Shape.

Cependant, plutôt que de faire ça manuellement, on préfèrera certainement utiliser le format GeoJson pour envoyer les données à une librairie compatible. Il suffit d'appeler la méthode ToGeoJson()

// Envoi du résultat depuis une API ASP.NET
return Ok(route.ToGeoJson());

Il suffit ensuite de récupérer les données en JavaScript et de les passer à la librairie MapLibre pour pouvoir l'afficher :

// On fait un appel à l'API pour recevoir les informations de l'itinéraire
const response = await fetch('/Itineraire');
const data = await response.json();

const map = initMap(); // voir les articles précédents pour l'initialisation de la carte

// On crée une source de données avec les informations geojson reçues
map.addSource('route', {
    type: 'geojson',
    data: data
});

// On affiche les données de la source avec l'id "route"
map.addLayer({
    'id': 'route',
    'type': 'line',
    'source': 'route',
    'layout': {
        'line-join': 'round',
        'line-cap': 'round'
    },
    'paint': {
        'line-color': '#888',
        'line-width': 8
    }
});

Démonstration

Voilà donc le résultat : j'ai repris l'affichage de la carte existant, et on peut afficher par dessus le trajet entre Toulouse et Saverdun.