#

> Leaflet.js – cartes interactives

Leaflet.js est une librairie Javascript de cartographie interactive.

Leaflet propose toute une série de tutos officiels, ainsi qu’une documentation qui détaille toutes les fonctions et leur utilisation.

Installation

La page download propose un zip dans lequel les seuls fichiers utiles sont : leaflet.js, leaflet.css et le dossier images/ (à mettre  à côté de la feuille de style).

Placez les 2 fichiers et le dossier image/ dans le dossier js/ de votre site, et déclarez-les dans l’entête html de votre page :

<link rel="stylesheet" href="js/leaflet.css" />
<script src="js/leaflet.js"></script>

Le dossier d’images contient les marqueurs par défaut, qui ne s’afficheront pas sinon.

Ensuite tout se passe dans votre fichier Javascript, une fois le document chargé.

Créer une carte

Toute la suite se passe dans le fichier Javascript de votre projet, une fois le document chargé.

De la même façon que jQuery donne accès à ses fonctions par le symbole $, Leaflet donne accès à ses fonctions par le symbole L.

var map = L.map('map');

‘map’ correspond côté html à

<div id="map"></div>

qu’il faut ajouter manuellement.

Par défaut la carte n’a pas de hauteur !
Il faut donc indiquer quelque chose en CSS (height:100vh pour une carte pleine page, ou autre).

Leaflet fournit une interface utilisateur pour lire des cartes, mais il ne fournit pas la carte.
Le plus courant est d’utiliser les cartes OpenStreetMap, libres de droit, via l’url ci-dessous :

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
    minZoom: 3,
    maxZoom: 18
}).addTo(map);

Vous trouverez d’autres cartes gratuites sur cette page, ainsi que le code JS à utiliser pour l’intégrer à votre projet.

Centrer la carte

Il faut ensuite « placer » le point de vue utilisateur à un endroit précis du monde.
Pour ça il vous faudra les coordonnées GPS du point à centrer :

map.setView(
    [48.11848409836725, -1.702553629875183], // coords gps
    18 // niveau de zoom souhaité
);

Obtenir des coordonnées GPS

Comme avec jQuery, on peut déclencher du code lors d’événements comme le click, le zoom, etc.
Ici on va écouter le click sur la carte, et afficher dans la console les coordonnées de l’événement :

map.on('click', function(e){
    // e contient les infos relatives au clic
    console.log('['+e.latlng.lat+', '+e.latlng.lng+']'); // prêt à copier-coller !
});

Vous pouvez ensuite utiliser ces coordonnées dans le code Javascript, comme point de départ ou pour y poser un marqueur par exemple.

Vous pouvez aussi afficher le niveau de zoom actuel dans la console avec le code suivant :

map.on('zoomend', function(e){ 
console.log("zoom", map.getZoom());
});

Ajouter des marqueurs

L.marker([51.5, -0.09]).addTo(map); // addTo(map) pour l'afficher sur la carte

Par défaut le marker est cliquable et affiche le même curseur qu’un lien.
Pour désactiver ça, il suffit d’ajouter une option « interactive » :

L.marker([51.5, -0.09], {interactive: false}).addTo(map);

Si vous avez besoin d’accéder au marker par la suite, il faut le stocker dans une variable :

var marker1 = L.marker([51.5, -0.09]).addTo(map);

Message sur un marqueur

Ca fonctionne de la même façon sur un marqueur ou une forme vectorielle :

marker.bindPopup("Un message ! <br> Et c'est du html !");

Le message s’ouvre au clic, mais on peut choisir de l’ouvrir dès le départ :

marker.openPopup();

Et on peut « chainer » les instructions :

L.marker([51.5, -0.09])
  .addTo(map)
  .bindPopup("Un message ! <br> Et c'est du html !")
  .openPopup();

Icônes de marqueurs personnalisées

Pour personnaliser vos marqueurs avec n’importe quelle image (png, jpg, svg, gif), il va falloir créer un « objet d’icône », que vous pourrez réutiliser sur autant de marqueurs que vous le souhaitez.

var mario_icon = L.icon({
    iconUrl: 'mario-icon.png',
    iconSize: [38, 95], // taille de l'icone
    iconAnchor: [22, 94], // coords de la "pointe" de l'icone
    // la suite est optionnelle :
    popupAnchor: [-3, -76], // place de l'infobulle éventuelle PAR RAPPORT à la pointe...
    className: 'mario', // si vous voulez appliquer du css...
    shadowUrl: 'mario-icon-shadow.png', // l'image de l'ombre portée
    shadowSize: [68, 95],
    shadowAnchor: [22, 94]
});
// et on l'utilise sur des markers : L.marker([50.505, 30.57], {icon: mario_icon}).addTo(map); L.marker([49.253, 30.46], {icon: mario_icon}).addTo(map);

Les « shadow… » correspondent à un 2ème fichier image de l’ombre de l’icône.
On peut s’en passer sans problème.

Ajouter un cercle

var circle1 = L.circle([51.508, -0.11], {
radius: 500 // le rayon du cercle, en mètres
opacity: 1,
fill : true, // remplir, ou pas
fillColor: '#f03', // couleur de remplissage
fillOpacity: 0.5,
stroke: true, // un contour, ou pas
weight: // épaisseur du contour
color: 'red', // couleur du contour
}).addTo(map);

Toutes les formes vectorielles ont les options ci-dessus en commun (à part radius).
Retrouvez toutes les options possibles sur cette page.

Ajouter un polygone

var polygon1 = L.polygon([
    [51.509, -0.08], // les coords gps des sommets du polygone
    [51.503, -0.06],
    [51.51, -0.047]
], {
color: 'yellow'
}).addTo(map);

Ajouter une ligne ou une série de lignes

var polyline1 = L.polyline([
    [51.509, -0.08], // les coords gps des sommets à relier
    [51.503, -0.06],
    [51.51, -0.047]
], {
color: 'yellow'
}).addTo(map);

Ajouter un rectangle

var rect1 = L.rectangle([
    [51.509, -0.08], // sommet NO (haut-gauche)
    [51.503, -0.06] // sommet SE (bas-droite)
], {
color: 'yellow'
}).addTo(map);

Overlays en image

Un « imageOverlay » c’est comme un sticker collé sur la carte.
Comme un rectangle, avec une image tendue dessus.
Il ne sera pas découpé en tuiles, mais il va zoomer en même temps que le reste.

L.imageOverlay(
    'img/smb3-map.png', // chemin vers l'image
    [
        [48.11872762243001, -1.7013412714004519], // sommet NO (haut-gauche)
        [48.11767473068865, -1.699463725090027] // sommet SE (bas-droite)
    ]
).addTo(map);

On ne peut malheureusement pas pivoter l’image, et si on place mal les extrémités l’image sera déformée.
Mais on peut toujours modifier l’image dans photoshop avant de l’intégrer sur la carte…
Tous les types d’images web habituels sont supportés : jpg, gif, png, svg

Overlay en vidéo

Même principe que pour l’overlay image, mais avec une vidéo !

L.imageOverlay(
    'video/ma_video.mp4', // chemin vers la vidéo
    [
        [48.11872762243001, -1.7013412714004519], // sommet NO (haut-gauche)
        [48.11767473068865, -1.699463725090027] // sommet SE (bas-droite)
    ]
).addTo(map);

Marqueurs html

Il n’est pas possible par défaut d’afficher du contenu html zoomable dans leaflet…
Mais j’ai un plugin pour faire ça !

Voici la démo de Leaflet HTML Overlay, qui contient des infos quand à son utilisation.

La librairie est téléchargeable ici, et le zip de la démo ici.

Le principe est le même que pour un ImageOverlay, mais avec du code HTML.
Vous allez donc pouvoir intégrer des « stickers » html-css à votre carte Leaflet.

Le plugin ajoute une fonction L.htmlOverlay(), ainsi qu’une mini-librairie jQuery (si jquery est installé) pour sélectionner directement des blocs html dans la page et les injecter dans la carte.

Vous pouvez donc commencer par caler vos contenus HTML-CSS dans votre page, comme d’habitude, avant de les ajouter à la carte.
De quoi faire des sites HTML-CSS zoomables, pas besoin de cartes géographiques pour s’amuser !

Groupes de marqueurs

Les « Marker », « ImageOverlay », « Circle », « Polygon », « HtmlOverlay » sont en réalité tous des « Layer« , c’est à dire des calques Leaflet.
Ils fonctionnent de la même façon, avec les mêmes propriétés de base.

On peut les créer individuellement et les ajouter sur la carte, ou passer par des LayerGroup afin de les regrouper.
Vous pourrez ainsi les piloter par paquet, afin de les afficher/masquer par exemple.

Pour créer un LayerGroup :

var mon_groupe = L.layerGroup().addTo(map);

On peut ajouter plus tard un layer à un groupe existant :

mon_groupe.addLayer(marker12); // ajouter marker12 dans mon_groupe
marker12.addTo(mon_groupe); // même chose, mais écrit dans l'autre sens...

Et on peut aussi retirer un layer du groupe :

mon_groupe.remove(marker12);
marker12.remove(); // même chose, mais écrit dans l'autre sens...

Pour retirer le groupe au complet (et tous les layers qu’il contient du même coup) :

mon_groupe.remove();

A noter : quand un groupe ou un layer est retiré de la carte, il continue d’exister même si on ne le voit plus.
On peut donc le ré-afficher par la suite.

Interfaces de contrôle

Pour gérer l’affichage de vos groupes, Leaflet porpose des éléments d’interface tout faits.
Ils vont permettrent d’avoir un outil pour afficher ou masquer des groupes de layers (ou des layers individuels) :

L.control.layers(
{}, // choix unique, ici on n'utilise pas
{
// cases à cocher
// "Le nom affiché dans l'interface" : variable_du_groupe,
"Palmiers pixel" : pixelGroup, // ici un groupe
"Carte OSM" : carteOSM, // ici un tileLayer
"Palamier unique !" : markerPalm1, // ici un marqueur unique
},
{ // les options du panel
position: "topright", // topleft', 'topright', 'bottomleft' or 'bottomright'
collapsed: false // fermé ou ouvert au démarrage (ici, ouvert au démarrage)
}
).addTo(map); // on l'ajoute à la carte comme un marqueur

Voici un exemple live, et le code source associé.

Vous pouvez aussi réaliser vos propres éléments d’interface, en html-css, en affichant vos blocs par-dessus la carte (absolute + z-index) et en utilisant jQuery.
Voici le code Javascript pour un bouton qui va afficher ou masquer un groupe de layers depuis un bloc html :

$('#toggle_mon_groupe').on('click', function(){
// le groupe est-il affiché sur la carte ?
if(map.hasLayer(mon_groupe){
// déjà affiché, on le retire de la carte
mon_groupe.remove();
} else { // pas affiché, on l'ajoute à la carte
mon_groupe.addTo(map);
}
});

La clé c’est de savoir si le groupe est affiché, et c’est map.hasLayer(mon_groupe) qui contient la réponse… La fonction map.hasLayer() renvoie true ou false.

Contrôler le zoom

Il y a plusieurs façons de déplacer le « point de vue » et le zoom de la carte :

map.setView([48.11872762243001, -1.7013412714004519], 8); // déplacement rapide
map.flyTo([48.11872762243001, -1.7013412714004519], 8); // déplacement animé du zoom et du centre de la carte

On peut aussi piloter le zoom seul, sans changer le centre de la carte :

map.setZoom(8);

Et pour piloter le centre sans toucher au zoom, il y a cette astuce :

map.setView([48.11872762243001, -1.7013412714004519], map.getZoom() );

On peut aussi cadrer un groupe de marqueurs, c’est expliqué plus bas.

Tenir compte du niveau de zoom

En cours de développement il peut être pratique de connaitre le niveau du zoom actuel.
On peut l’afficher facilement dans la console :

map.on('zoomend', function(e){ // déclenché à la fin du changement du zoom
console.log( map.getZoom() );
});

Pour obtenir le zoom à tout moment, il suffit donc de faire :

var z = map.getZoom();

Il est donc possible d’activer ou désactiver des layers suivant le niveau de zoom, d’afficher des messages à certains niveaux seulement, etc :

map.on('zoomend', function(e){ // déclenché à la fin du changement du zoom
var z = map.getZoom();

if(z <= 14){
// en dessous du zoom 14 je masque le groupe
mon_groupe.remove();
} else {
// au dessus du zoom 14 je l'affiche
mon_groupe.addTo(map);
}

if(z==18){
truc_special.addTo(map);
} else {
truc_special.remove();
}
});

Géo-localisation

Leaflet intègre la géolocalisation de manière assez simple, et les demandes d’autorisations à l’utilisateur sont gérées automatiquement quelle que soit la plateforme.
Pratique !

Le process est en 3 étapes :

  • demander la localisation
  • agir quand on l’obtient (un peu plus tard)
  • agir quand l’utilisateur a refusé la localisation

Pour demander la localisation :

map.locate();

Une fois la localisation obtenue (c’est à dire plus tard, après l’accord de l’utilisateur), on peut agir et utiliser les coordonnées obtenues :

map.on('locationfound', function(e){

// la localisation est dans e !
console.log(e.latlng);

// par exemple pour poser un marqueur au point de géo-localisation :
L.marker(e.latlng)
.addTo(map)
.bindPopup("Vous êtes ici !")
.openPopup(); // ouvrir le message
});

Et en cas de refus de l’utilisateur :

map.on('locationerror', function (e) {
console.log("error", e);
alert("Vous n'avez pas autorisé la localisation ! <br>"+ e.message);
});

A noter : on peut centrer la carte automatiquement sur la localisation obtenue en faisant dès le départ :

map.locate({
setView: true, // centrer sur la localisation
maxZoom: 16 // le niveau de zoom souhaité
});

Il y a d’autres options de localisation possible, exposée dans la doc officielle de Leaflet.

Et voici une démo dans le navigateur, et le code source associé.

MapTiler pour des cartes non-géographiques

Pour transformer une grande image en carte zoomable (découpée en tuiles), il faut utiliser le logiciel MapTiler Desktop.

Le détail des opérations à effectuer fait l’objet d’un autre article que vous trouverez ici.

Mode CRS.Simple

Par défaut Leaflet fonctionne en coordonnées GPS, c’est à dire avec des latitudes et longitudes très peu lisibles.
La terre est ronde, aussi ce n’est pas un système orthonormé, impossible de deviner des coordonnées de tête (en tout cas pour moi).

Mais on peut passer en mode « pixels » si on le souhaite !
C’est surtout pertinent pour des projets non-géographiques, ou sur des cartes imaginaires; c’est le mode que MapTiler utilise quand on lui fournit une image à découper, comme sur ces 2 projets :
https://pablolabeque.com/pare/
http://bachelot.valentin.free.fr/cartographiesfilms/leaflet.html

Pour passer manuellement en mode CRS.Simple, ça se passe lors de la création de la carte :

var map = L.map('map', {
// mes autres options puis
crs: L.CRS.Simple
});

Dès lors les coordonnées utilisées et produites par Leaflet seront en pixels (et une carte OpenStreetMap ne va plus fonctionner !).

Empêcher de sortir de la carte

Une carte OpenStreetMap n’a pas de limite (on peut faire le tour du monde), mais ce n’est pas le cas pour des cartes non-géographiques…

On peut donc indiquer des limites à ne pas dépasser, dans le cas d’une utilisaiton de MapTiler par exemple.

var map = L.map('map', {
// des options, dont CRS.Simple, puis
maxBoundsViscosity: 1 // degré de tolérance de la limite, 1 pour un blocage total
});

// MapTiler fournit ces "limites" pour le cadrage initial
var mapBounds = new L.LatLngBounds(
map.unproject([0, 8448], mapMaxZoom),
map.unproject([12032, 0], mapMaxZoom));

// le cadrage initial fourni par MapTiler
map.fitBounds(mapBounds);

// et pour empêcher de sortir de l'image il faut ajouter ça :
map.setMaxBounds(mapBounds);

En gros, 2 lignes de code à ajouter, en utilisant mapBounds qui a été fabriqué par MapTiler.
C’est ce qui est en place sur cet exemple.

Cadrer des markers dans la carte

Plutôt que d’indiquer un centre, vous pouvez aussi demander à cadrer la carte « autour » d’un ensemble de marqueurs.

Au lieu de créer un « LayerGroup », il faudra utiliser un « featureGroup« , qui offre quelques possibilités supplémentaire, comme de fournir les coordonnées qui « contiennent » les layers du groupe.

Un FeatureGroup s’utilise de la même façon qu’un LayerGroup :

// créer le feature group
var ma_collec = L.featureGroup().addTo(map);
// créer un marqueur
var marker = L.marker([48.11939014528798, -1.7044150829315188]);
// ajouter le marqueur au groupe
ma_collec.addLayer(marker);

On peut ensuite indiquer à la carte de « cadrer » le FeatureGroup :

map.fitBounds( ma_collec.getBounds() );