Mise en place de polyline pour tracer des itinéraires
Maintenant que vous avez ajouté une carte Google maps à votre application Flutter et êtes capable de lui ajouter des marqueurs, la suite va être de tracer un itinéraire entre ceux-ci.
La première étape va être de configurer votre clé API dans Google Cloud, ajouter des autorisations nécessaires pour Android et iOS, et intégrer le package flutter_polyline_points à votre application Flutter pour générer l’itinéraire.
Configurer vote clé API dans Google Cloud
Avant de pouvoir interagir avec l’API Directions et générer un itinéraire, vous devez configurer correctement votre clé API dans la Google Cloud Console. Pour commencer, ajoutez les services Directions API et Routes API.
Puis, restreignez votre clé API à cette liste de services et copiez là :
- Maps SDK for Android
- Places API
- Maps JavaScript API
- Maps Embed API
- Maps SDK for iOS
- Directions API
- Routes API
- Roads API
- Geocoding API
- Geolocation API
- Street View Static API
- Distance Matrix API
Ces API sont essentielles pour permettre à votre application d’accéder à des fonctionnalités de cartographie, de géolocalisation et de création d’itinéraires.
Configurer Android
Pour Android, vous devrez ajouter quelques autorisations pour permettre l’accès à la localisation de l’utilisateur. La seule chose que vous aurez à faire est de mettre à jour le fichier AndroidManifest.xml en ajoutant ces lignes si elles ne sont pas déjà présentes :
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>Configurer IOS
Pour IOS, vous n’aurez pas besoin de demander des permissions, mais vous devrez spécifier les raisons pour lesquelles votre application nécessite l’accès à la localisation.
Pour cela, rendez vous dans le fichier Info.plist de votre application et ajoutez les permissions qui suivent :
<key>NSLocationWhenInUseUsageDescription</key>
<string>Cet app nécessite l'accès à la localisation lorsque l'application est ouverte.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Cet app nécessite l'accès à la localisation en permanence et lorsque l'application est ouverte.</string>
<key>NSLocationUsageDescription</key>
<string>Les anciens appareils nécessitent l'accès à la localisation.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Cet app nécessite l'accès à la localisation en arrière-plan.</string>Puis, pensez aussi à configurer correctement Google Maps dans le fichier AppDelegate.swift en ajoutant l’importation et la configuration de la clé API comme suit :
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("VOTRE_CLÉ_API") // Remplacez cette valeur par votre clé
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}Intégration du package flutter polyline points
Enfin, la dernière étape va être d’installer et configurer le package flutter_polyline_points pour générer l’itinéraire entre deux marqueurs.
Dans votre fichier pubspec.yaml, ajoutez la dernière version (2.1.0) du package :
dependencies:
flutter:
sdk: flutter
google_maps_flutter: ^2.1.0
flutter_polyline_points: ^2.1.0Ou utilisez cette commande dans le terminal :
flutter pub add flutter_polyline_pointsPuis importez le package dans votre fichier Dart où vous souhaitez gérer l’itinéraire :
import 'package:flutter_polyline_points/flutter_polyline_points.dart';Créer un itinéraire avec Google Maps dans Flutter
Maintenant que la mise en place est terminée, vous allez pouvoir créer des itinéraires en plaçant des marqueurs sur votre carte. Voici un code d’exemple qui reprend les fonctionnalités de base, c’est à dire :
- Placer et supprimer des marqueurs sur la carte.
- Trouver la route la plus rapide entre ces marqueurs.
- Adapter le trajet, selon les marqueurs ajoutés ou supprimés.
- Changer le mode de transport (Voiture, marche, ou transports en commun).
- Le temps de voyage estimé.
- Recentrer la carte autour de l’itinéraire.
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: MapScreen(),
);
}
}
class MapScreen extends StatefulWidget {
const MapScreen({Key? key}) : super(key: key);
@override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
late GoogleMapController mapController;
Set<Marker> _markers = {};
Set<Polyline> _polylines = {};
List<LatLng> _routePoints = [];
PolylinePoints polylinePoints = PolylinePoints();
String googleApiKey = "VOTRE_CLÉ_API";
String _travelTime = "Calcul en cours...";
TravelMode _selectedMode = TravelMode.driving;
@override
void initState() {
super.initState();
checkAndRequestPermission();
}
Future<void> checkAndRequestPermission() async {
PermissionStatus status = await Permission.location.status;
if (!status.isGranted) {
await Permission.location.request();
}
}
void _onMapTap(LatLng position) {
setState(() {
String markerId = "marker_${_markers.length}";
_markers.add(
Marker(
markerId: MarkerId(markerId),
position: position,
infoWindow: InfoWindow(title: "Point ${_markers.length + 1}"),
onTap: () {
setState(() {
_markers
.removeWhere((marker) => marker.markerId.value == markerId);
_updateRoute();
});
},
),
);
_updateRoute();
});
}
void _updateRoute() async {
if (_markers.length < 2) return;
_routePoints.clear();
List<Marker> markerList = _markers.toList();
for (int i = 0; i < markerList.length - 1; i++) {
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleApiKey: googleApiKey,
request: PolylineRequest(
origin: PointLatLng(
markerList[i].position.latitude,
markerList[i].position.longitude,
),
destination: PointLatLng(
markerList[i + 1].position.latitude,
markerList[i + 1].position.longitude,
),
mode: _selectedMode,
),
);
if (result.points.isNotEmpty) {
_routePoints
.addAll(result.points.map((p) => LatLng(p.latitude, p.longitude)));
}
}
setState(() {
_polylines.clear();
_polylines.add(
Polyline(
polylineId: const PolylineId("route"),
color: Colors.blue,
width: 5,
points: _routePoints,
),
);
_calculateTravelTime();
_recenterMap(); // Recentrer la carte
});
}
void _calculateTravelTime() {
setState(() {
_travelTime = "~ ${(_routePoints.length / 5).toStringAsFixed(1)} min";
});
}
// Recentrer la carte pour afficher tous les points
void _recenterMap() {
if (_markers.isEmpty) return;
double minLat = _routePoints.first.latitude;
double maxLat = _routePoints.first.latitude;
double minLon = _routePoints.first.longitude;
double maxLon = _routePoints.first.longitude;
for (LatLng point in _routePoints) {
if (point.latitude < minLat) minLat = point.latitude;
if (point.latitude > maxLat) maxLat = point.latitude;
if (point.longitude < minLon) minLon = point.longitude;
if (point.longitude > maxLon) maxLon = point.longitude;
}
LatLngBounds bounds = LatLngBounds(
southwest: LatLng(minLat, minLon),
northeast: LatLng(maxLat, maxLon),
);
mapController.animateCamera(CameraUpdate.newLatLngBounds(bounds, 50));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Planificateur d'itinéraire"),
actions: [
DropdownButton<TravelMode>(
value: _selectedMode,
onChanged: (TravelMode? mode) {
setState(() {
_selectedMode = mode!;
_updateRoute();
});
},
items: const [
DropdownMenuItem(value: TravelMode.driving, child: Text("Voiture")),
DropdownMenuItem(value: TravelMode.walking, child: Text("Marche")),
DropdownMenuItem(value: TravelMode.transit, child: Text("Transport")),
DropdownMenuItem(value: TravelMode.bicycling, child: Text("Vélo")),
],
),
],
),
body: Stack(
children: [
GoogleMap(
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: const CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 12.0,
),
markers: _markers,
polylines: _polylines,
onTap: _onMapTap,
),
Positioned(
top: 20,
left: 20,
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 5)],
),
child: Text(
"Temps estimé: $_travelTime",
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_markers.clear();
_polylines.clear();
_routePoints.clear();
_travelTime = "Calcul en cours...";
});
},
child: const Icon(Icons.delete),
),
);
}
}Avec ce code d’exemple que vous pouvez adapter à vos besoins, vous pouvez facilement créer des itinéraires et estimer le temps de trajet selon le mode de déplacement.
Ajouter et supprimer des marqueurs
Ajouter des marqueurs sur la carte est la première étape pour créer un itinéraire. Pour ajouter un marqueur, on utilise la méthode onTap de GoogleMap, qui permet de récupérer la position cliquée et d’ajouter un widget de type Marker à cet endroit :
//Fonction lancée, lorsque l'utilisateur clique quelque part sur la carte
void _onMapTap(LatLng position) {
setState(() {
// Génère un identifiant unique pour le marqueur
String markerId = "marker_\${_markers.length}";
// Ajoute un marqueur à l'endroit cliqué
_markers.add(
Marker(
markerId: MarkerId(markerId),
position: position,
infoWindow: InfoWindow(title: "Point \${_markers.length + 1}"),
// Ajoute une action de suppression lorsqu'on clique sur le marqueur
onTap: () {
setState(() {
_markers.removeWhere((marker) => marker.markerId.value == markerId);
_updateRoute(); // Met à jour l'itinéraire
});
},
),
);
_updateRoute(); // Appel la fonction qui met à jour l'itinéraire après ajout du marqueur
});
}Je vous invite à lire mon guide, si vous souhaitez en apprendre plus sur comment ajouter des marqueurs et les personnaliser selon vos besoins.
Récupérer les marqueurs et tracer un itinéraire
Une fois plusieurs marqueurs ajoutés, l’itinéraire entre eux peut être tracé à l’aide du package flutter_polyline_points, qui utilise l’API Google Directions pour calculer le meilleur chemin.
void _updateRoute() async {
if (_markers.length < 2) return; // Pas assez de points pour tracer un itinéraire
_routePoints.clear(); // Efface l'itinéraire actuel
List<Marker> markerList = _markers.toList();
for (int i = 0; i < markerList.length - 1; i++) {
// Appel à l'API Google Directions pour obtenir l'itinéraire entre deux points. L'étape se répète jusqu'à ce que tous les points soient reliés.
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleApiKey: googleApiKey,
request: PolylineRequest(
origin: PointLatLng(
markerList[i].position.latitude,
markerList[i].position.longitude,
),
destination: PointLatLng(
markerList[i + 1].position.latitude,
markerList[i + 1].position.longitude,
),
mode: _selectedMode, // Mode de transport sélectionné
),
);
// Ajoute les points de l'itinéraire à la liste
if (result.points.isNotEmpty) {
_routePoints.addAll(result.points.map((p) => LatLng(p.latitude, p.longitude)));
}
}
setState(() {
_polylines.clear(); // Efface les anciennes lignes
_polylines.add(
Polyline(
polylineId: const PolylineId("route"),
color: Colors.blue,
width: 5,
points: _routePoints,
),
);
_calculateTravelTime(); // Calcule le temps de trajet selon le mode de transport selectionné
});
}La fonction se compose de 3 étapes.
- Récupérer les marqueurs : L’application garde une liste des marqueurs ajoutés, stockée dans la variable
_markers. Chaque marqueur représente une étape de l’itinéraire. - Appel à l’API Google Directions : Pour chaque paire de points adjacents (c’est-à-dire deux marqueurs qui se suivent dans la liste
_markers), l’application appelle l’API Google Directions via le package flutter_polyline_points pour obtenir la route entre ces deux points. L’API renvoie une série de points qui décrivent la trajectoire de la route entre ces deux points. Cette opération est répétée jusqu’à ce que tous les marqueurs soient reliés par une route. - Tracer l’itinéraire sur la carte : Les points obtenus à chaque appel sont ajoutés à une liste (_routePoints) et utilisés pour dessiner une polyline sur la carte, qui représente visuellement l’itinéraire.
- L’ancienne polyligne est effacée et puis une fois toutes les paires de points tracées, est mise à jour avec celles-ci.
Ainsi, l’itinéraire est tracé entre les marqueurs, et la carte est mise à jour pour refléter visuellement le trajet entre chaque point.
Adapter le tracé lorsque des marqueurs sont retirés
Lorsque l’utilisateur supprime un marqueur, la route doit se recalculer. Ceci est fait directement dans la fonction _onMapTap qui se relance lorsqu’on supprime un point du tracé et qui utilise la fonction _updateRoute() :
onTap: () {
setState(() {
_markers.removeWhere((marker) => marker.markerId.value == markerId); // Supprime le marqueur
_updateRoute(); // Recalcule l'itinéraire
});
}Le tracé est supprimé, pour être redessiné à partir des points restants.
Changer le mode de transport
Google Maps permet de calculer des itinéraires selon différents modes de déplacement. On peut ainsi utiliser la propriété TravelMode pour spécifier l’option souhaitée.
Dans mon exemple, j’utilise un DropdownButton pour changer dynamiquement le mode de déplacement et recalculer l’itinéraire :
DropdownButton<TravelMode>(
value: _selectedMode, // Mode de transport actuel
onChanged: (TravelMode? mode) {
setState(() {
_selectedMode = mode!; // Met à jour le mode de transport
_updateRoute(); // Recalcule l'itinéraire avec le nouveau mode
});
},
items: const [
DropdownMenuItem(value: TravelMode.driving, child: Text("Voiture")),
DropdownMenuItem(value: TravelMode.walking, child: Text("Marche")),
DropdownMenuItem(value: TravelMode.transit, child: Text("Transport")),
],
)Calculer le temps de trajet
Une estimation du temps de trajet peut être calculée en fonction du nombre de points de l’itinéraire et le mode de transport.
void _calculateTravelTime() {
setState(() {
_travelTime = "~ ${(_routePoints.length / 5).toStringAsFixed(1)} min";
});
}Ici, l’estimation du temps de trajet repose sur le nombre de points qui définissent l’itinéraire. Ces points sont générés par l’API de Google Maps lorsque vous calculez la route entre deux endroits (marqueurs).
Chaque fois que vous ajoutez des marqueurs sur la carte, l’application calcule la route entre chaque paire de marqueurs et génère une série de points (coordonnées GPS) représentant cette route. Ces points sont stockés dans la variable _routePoints.
Pour estimer le temps de trajet, l’application divise simplement le nombre de points par 5. Cette division est une approximation, car l’idée est que environ 5 points correspondent à 1 minute de trajet, en fonction de la densité de la route.
Le nombre de points générés par l’API varie en fonction du mode de transport choisi (voiture, marche, vélo, etc.). Par exemple, pour un trajet en voiture, l’itinéraire sera plus direct, avec moins de points, tandis que pour la marche, l’itinéraire sera plus sinueux et aura plus de points.
Recentrer la carte autour de l’itinéraire
Lorsque des points sont ajoutés ou supprimés sur la carte et que l’itinéraire est recalculé, il est nécessaire de s’assurer que toute la route est visible pour l’utilisateur.
void _recenterMap() {
if (_markers.isEmpty) return;
double minLat = _routePoints.first.latitude;
double maxLat = _routePoints.first.latitude;
double minLon = _routePoints.first.longitude;
double maxLon = _routePoints.first.longitude;
for (LatLng point in _routePoints) {
if (point.latitude < minLat) minLat = point.latitude;
if (point.latitude > maxLat) maxLat = point.latitude;
if (point.longitude < minLon) minLon = point.longitude;
if (point.longitude > maxLon) maxLon = point.longitude;
}
LatLngBounds bounds = LatLngBounds(
southwest: LatLng(minLat, minLon),
northeast: LatLng(maxLat, maxLon),
);
mapController.animateCamera(CameraUpdate.newLatLngBounds(bounds, 50));
}Voici comment cette fonctionnalité fonctionne :
- Mise à jour des points de la route : Dès qu’un utilisateur ajoute ou enlève un marqueur sur la carte, cela déclenche la mise à jour de l’itinéraire. Le calcul de l’itinéraire est effectué avec la méthode
_updateRoute(). - Calcul des coordonnées extrêmes : Une fois les points de l’itinéraire calculés, on parcoure toutes les coordonnées des points pour déterminer les limites nord, sud, est et ouest (latitude et longitude minimale et maximale).
- Création des limites de la carte : Avec ces coordonnées extrêmes, on peut ensuite créer un
LatLngBoundsqui délimite la zone géographique à afficher sur la carte. - Recentrage de la carte : Enfin, la méthode
animateCamerava permettre d’appliquer un zoom et centrer la carte afin qu’elle affiche l’ensemble de l’itinéraire dans la vue.
Cela permet à l’utilisateur de toujours voir l’itinéraire complet sur la carte, même s’il ajoute ou retire des points.
Personnaliser le visuel de son itinéraire
Plusieurs options peuvent vous permettre de personnaliser l’apparence de l’itinéraire sur la carte. Voici les principales options qui vous sont proposées.
Changer la couleur de la polyline
La couleur de la polyline est l’un des aspects les plus simples à personnaliser. Vous pouvez définir la couleur de votre polyline (le chemin qui relie les points) pour qu’elle corresponde à la charte graphique de votre application ou bien pour la rendre plus visible.
_polylines.add(
Polyline(
polylineId: PolylineId("route"),
color: Colors.blue, // Définir la couleur de la polyline
width: 5,
points: _routePoints,
),
);Changer l’épaisseur de la Polyline
L’épaisseur de la polyline peut être modifiée pour la rendre plus ou moins visible. Vous pouvez ajuster l’épaisseur en jouant avec la propriété width.
_polylines.add(
Polyline(
polylineId: PolylineId("route"),
color: Colors.blue,
width: 10, // Modifier l'épaisseur de la polyline
points: _routePoints,
),
);Changer le style de la Polyline (Dotted ou Dashé)
Google Maps vous permet de modifier le style des polylines, en ajoutant des segments ou des pointillés à votre itinéraire.
_polylines.add(
Polyline(
polylineId: PolylineId("route"),
color: Colors.blue,
width: 5,
patterns: [PatternItem.dash(30.0), PatternItem.gap(20.0)], // Style dashé
points: _routePoints,
),
);Prix de l’API Google Directions
Avant de commencer à utiliser l’API Google Directions, sachez que celle-ci suit un modèle de facturation à l’usage. Il est donc important de comprendre combien celles-ci coutent, ainsi que les différentes options disponibles.
Modèle de facturation
L’API Directions utilise un modèle de facturation basé sur le nombre de requêtes effectuées. Chaque requête envoyée au service Directions (via API ou SDK) génère un coût, et ce coût dépend du type de requête effectuée. Il existe deux types principaux de SKU pour cette API : Directions et Directions Advanced.
Tarifs pour les requêtes standard (Directions)
Pour les requêtes standard qui ne nécessitent pas d’informations supplémentaires (comme le trafic en temps réel ou plus de 10 points de cheminement), le tarif est le suivant :
- De 0 à 100 000 requêtes par mois : 0,005 USD par requête
- De 100 001 à 500 000 requêtes par mois : 0,004 USD par requête
- Au-delà de 500 000 requêtes par mois : Contactez Google pour obtenir un prix sur mesure
Les requêtes standard sont généralement utilisées pour des itinéraires simples, sans demandes complexes comme les informations de trafic.
Tarifs pour les requêtes avancées (Directions Advanced)
Les requêtes avancées englobent des fonctionnalités supplémentaires telles que les informations sur le trafic en temps réel. La facturation de ce service est la suivante :
- De 0 à 100 000 requêtes par mois : 0,01 USD par requête
- De 100 001 à 500 000 requêtes par mois : 0,008 USD par requête
- Au-delà de 500 000 requêtes par mois : Contactez Google pour obtenir un prix sur mesure
Les requêtes avancées incluent des fonctionnalités telles que :
- Informations sur le trafic : Calculs d’itinéraires prenant en compte le trafic en temps réel, si le paramètre
departure_timeest défini. - Optimisation des points de cheminement : Lorsque vous définissez
optimize:truepour réorganiser les points de cheminement dans l’ordre le plus efficace. - Modificateurs d’emplacement : Utilisation de
side_of_roadouheadingpour influencer l’itinéraire.

