Sommaire
N'ayant pas trouvé d'alternative libre à Polarstep, la solution retenue se base sur les briques logicielles libres suivantes :
- un moteur de blog static : pelican (AGPL v3.0)
- des thèmes pour le blog
- des bibliothèques cartographiques : leaflet (BSD 2)
1 - Préparation de pelican
Pelican propose d'écrire chaque billet de blogs dans un fichier texte indépendant (au format markdown ou reStructuredText).
Pelican les convertit en html et l'organisation du site ainsi généré (catégories, mots-clefs, archivage) se fait par le biais de gabarits (qui sont dans un sous-répertoire templates
)
a) Le moteur
L'installation ne sera pas développée ici, pelican étant disponible dans de nombreuses distributions.
Il faut créer la structure de travail (dans le répertoire personnel de notre choix) :
pelican-quickstart
b) Installation du thème graphique
En allant sur le dépôt des thèmes de pelican, il est possible de trouver le style graphique qui nous convient le mieux.
Nous allons utiliser le thème pelican-blue (sous licence MIT 2.0), qui a l'avantage d'être simple, et commençons son installation :
- création du répertoire
theme
dans notre structure de travail
- décompression de l'archive du thème dans le répertoire « theme »
- modification du fichier
pelicanconf.py
pour configurer notre site. Il faut adapter quelques variables :
SITENAME = 'Mon blog'
SITEDESCRIPTION = 'Mes souvenirs de vacances'
THEME = "./theme/pelican-blue"
STATIC_PATHS = ['images', 'gpx']
- modifications propres au thème. Souvent l'auteur d'un thème propose de le personnaliser à partir de variables déclarées dans le fichier de configuration.
c) Écriture du premier billet
On va créer notre premier billet
Title: Première sortie
Date: 2025-05-01
Modified: 2025-05-01
Category: Lieux
Slug: depart
Tags: bonjour, balade
Bonjour tout le monde ! Quelle chouette sortie j'ai faite.
d) Génération de notre site
On lance la première compilation :
make clean
make html
On peut voir le résultat :
- soit en ouvrant directement le fichier index.html (présent dans le répertoire
output
)
- soit en lançant un mini serveur web (
make serve
) et lancer son navigateur web à l'adresse http://localhost:8000/
Pour plus de renseignements sur pelican, je vous invite à vous rendre sur la documentation du projet.
2 - Peaufinage de base
On va maintenant nettoyer le code des gabarits, en supprimant les choses que l'on trouve inutiles ou qui nous déplaisent. Tout se passe dans le répertoire templates
de notre thème.
- il y a les fichiers analytics.html et disqus.html
- une recherche par mot nous informe des éventuelles références à Google, Twitter, Facebook
On supprime les parties qui ne nous conviennent pas.
3 - Gestion cartographique
Nous attaquons désormais notre objectif : rendre visibles sur des cartes des fichiers de trace.
a) Gestion des cartes
On va maintenant configurer la gestion des cartes, par l'intermédiaire de leaflet. Comme l'indique sa page wikipédia, leaflet est très largement utilisé et très pratique.
On va donc
- le télécharger,
- le décompresser dans le répertoire
static
de notre thème
- modifier les entêtes de nos gabarits (cela se fait le plus souvent dans le fichier
base.html
) pour y ajouter au niveau <head>
les références à leaflet :
<link rel="stylesheet" href="{{ SITEURL }}/theme/leaflet/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="{{ SITEURL }}/theme/leaflet/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
Comme on a récupéré en local les fichiers, on met des chemins propres à notre arborescence (via {{ SITEURL }}/theme/
).
b) Gestion des fichiers de trace (gpx)
Elle va se faire par l’intermédiaire d'un module supplémentaire https://github.com/mpetazzoni/leaflet-gpx (BSD 2).
De la même manière qu'on a intégré dans nos entêtes l'intégration de leaflet, nous allons ajouter une ligne pour faire référence à leaflet-gpx (bien vérifier le nom du fichier javascript) :
<script src="{{ SITEURL }}/theme/leaflet-gpx/gpx.js"></script>
Par rapport à la documentation officielle, on retire l'attribut defer (puisque nous utilisons les fichiers locaux et non distants).
Pour tester notre environnement, on va déposer dans notre répertoire gpx
un fichier de trace, puis on va ajouter dans notre billet les éléments de cartographie de notre voyage :
<div id="map" style="width: 600px; height: 400px;"></div>
<script>
var map = L.map('map');
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Carte et données : <a href="http://www.osm.org">OpenStreetMap</a>'
}).addTo(map);
var gpx = '/gpx/FICHIER.gpx';
new L.GPX(gpx, {async: true}).on('loaded', function(e) {
map.fitBounds(e.target.getBounds());
}).addTo(map);
</script>
On regénère notre site web, et on peut visualiser notre billet

Globalement, ça fait le boulot.
Mais on peut améliorer la chose : on peut par exemple cacher les marques de début et de fin d'itinéraire en insérant la ligne suivante après le async: true
markers: {startIcon: null, endIcon: null, }
Mais surtout, nous souhaitons que pelican génère automatiquement la partie consacrée au fichier de trace (alors que dans notre test, nous avons dû l'ajouter nous-même) !
c) Modification des gabarits
Si l'on veut simplement ajouter notre fichier de trace et que notre gabarit le traite, on va ajouter cette information dans les entêtes de notre fichier markdown ! En effet pelican permet de créer des variables qui seront utilisables dans nos gabarits.
Nous allons donc créer et utiliser une variable (qui s'appellerait… Gpx
par exemple), qui stockera le nom du fichier gpx à afficher (les chemins sont relatifs à notre site web)
Title: Première sortie
Date: 2025-05-01
Modified: 2025-05-01
Category: Lieux
Gpx: /gpx/monfichier.gpx
Slug: depart
Tags: bonjour, balade
Nous modifions ensuite notre gabarit article.html
pour qu'il génère la carte à partir de notre variable.
Pelican est très souple : basé sur Jinja2, il permet les boucles, les conditions et les variables.
Tous les éléments qu'il utilise sont insérés dans des accolades. Le fonctionnement est facilement lisible et compréhensible.
On va donc conditonner (avec if
) l'insertion de leaflet.
{% if article.gpx %}
<div id="map" style="width: 600px; height: 400px;"></div>
<script>
var map = L.map('map');
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Carte et données : <a href="http://www.osm.org">OpenStreetMap</a>'
}).addTo(map);
var gpx = '{{ article.gpx }}';
new L.GPX(gpx, {async: true,
markers: {startIcon: null, endIcon: null, }
}).on('loaded', function(e) {
map.fitBounds(e.target.getBounds());
}).addTo(map);
</script>
{% endif %}
Bien entendu, nous supprimons ces références du fichier markdown correspondant à notre billet de test.
On regénère notre site web, et on peut visualiser notre billet… qui n'a pas changé : tout fonctionne. Pour chacune de nos sorties, il suffit donc d'indiquer le fichier de trace dans les entêtes pour que la carte soit insérée automatiquement dans notre billet.
Passons maintenant à l'intégration de nos photos.
4 - Gestion des photographies associées à notre cartographie
Nous avons besoin :
- d'une image
- de ses coordonnées géographiques (latitude et longitude)
Pour cela, nous allons procéder de la même manière que pour le fichier trace : nous allons créer et utiliser des variables dans les entêtes des fichiers markdown.
a) Fichier des billets
Nous modifions encore une fois les entêtes en ajoutant autant d'informations (image, latitude et longitude) que de photos à afficher en miniatures.
Title: Première sortie
Date: 2025-05-01
Modified: 2025-05-01
Category: Lieux
Gpx: /gpx/monfichier.gpx
Slug: depart
Img: /images/image1.jpg
Lat: 49.895517
Lon: 2.295983
Img: /images/image2.jpg
Lat: 49.89443
Lon: 2.30137
Tags: bonjour, balade
On remarque ici que l'on a mis plusieurs images avec les mêmes noms de variables.
b) Modification des gabarits
Nous allons ensuite modifier les gabarits de pelican pour qu'ils positionnent des miniatures des photos sur notre trajet.
Nous allons à nouveau modifier notre fichier article.html
, en y ajoutant (à la suite de notre précédente modification, dans la condition {% if article.gpx %}
) le code suivant :
Nous commençons par indiquer l'icône qui s'affichera sur la carte à chaque photo mise en valeur
var MonIcone = L.icon({
iconUrl: '/images/app-photo.png',
iconSize: [36, 36]
});
Puis nous codons l'affichage du marqueur (qui sera géré par leaflet).
{% if article.img %}
{% if article.img is string %}
imageTxt = 'Description';
L.marker([{{ article.lat }}, {{ article.lon }}], {icon: MonIcone}).bindPopup(imageTxt + '<br><img src="{{ article.img }}" width="200px"><a href="#bal5">plus de détail</a>').addTo(map);
{% else %}
{% for n in range(article.img| length) %}
imageTxt = 'Description';
L.marker([{{ article.lat[n] }}, {{ article.lon[n] }}], {icon: MonIcone}).bindPopup(imageTxt + '<br><img src="{{ article.img[n] }}" width="200px"><a href="#bal5">plus de détail</a>').addTo(map);
{% endfor %}
{% endif %}
La difficulté réside dans la gestion des éléments répétitifs :
- s'ils sont plusieurs, on peut utiliser les méthodes python des listes.
- s'il n'y en a qu'un seul, cette méthode renvoie toutes les lettres de notre variable ! Il a donc fallu tester si celle-ci est une chaine de caractères ou une liste.
Les choix sont ici purement personnels ou démonstatifs :
- on a laissé une variable imageTxt en dur, elle pourrait être passée dans les entêtes de nos fichiers markdown
- le texte du popup peut être adapté (on pourrait y ajouter un lien direct vers notre image par exemple)
- le lien (ancre) est à créer dans notre fichier markdown
- la taille de l'image du popup est en dur (on peut passer par une feuille de style css)
On regénère notre site web, et on peut visualiser notre billet :

Et lorsqu'on clique sur une icône d'appareil photo, on voit bien notre popup :

c) Gestion des photographies
Comme indiqué plus haut, la taille des miniatures affichées peut se gérer :
- par CSS
- ou créer des miniatures (avec
imagemagick
) pour diminuer la charge de notre serveur (afficher une photo de 3000 pixels à 200 pixels n'est pas optimal). Dans ce cas, il suffira d'adapter notre gabarit pour lui indiquer où aller chercher les petites images (/images/miniatures/
par exemple)
Par contre, le point le plus compliqué est la gestion des coordonnées des photographies : il faut les rentrer à la main !
Pour les photographies qui n'intègrent pas les coordonnées dans leurs métadonnées, il n'y a pas d'autre solution que d'aller chercher sur une carte (openstreetmap par exemple) et de trouver le lieu de la prise de vue et de repérer les coordonnées.
Pour les photographies qui contiennent leurs coordonnées géographiques, on peut utiliser l'outil exiftool
pour les récupérer. On peut éventuellement faire un script bash qui affiche les lignes d'entête pour notre billet (on n'a plus qu'à les recopier ou les rediriger vers un fichier texte) :
for photo in $(ls ./content/images);
do
echo ""
echo "Img: /images/"$photo
LAT=$(exiftool -n -s3 -gpslatitude ./content/images/$photo)
echo "Lat: "$LAT
LONG=$(exiftool -n -s3 -gpslongitude ./content/images/$photo)
echo "Lon: "$LONG
done
Nous avons utilisé les options -n
qui affichent les valeurs numériques au format décimal (celui utilisé par openstreetmap pour les coordonnées) et -s3
pour avoir la valeur du champ sans le nom de son attribut.
5) Dernières modifications
Nous venons de voir les différentes techniques qui permettent d'avoir le rendu que nous souhaitions. Et le résultat est déjà agréable à regarder.
Nous pourrions nous arrêter ici, mais vous voulons que la carte reste en permanence dans le menu latéral. La solution est de la mettre dans une balise <aside>
.
a) Modifier les gabarits
Notre thème comporte déjà une telle balise : elle est dans le fichier base.html
… ce qui signifie qu'il ne peut pas voir les informations sur les articles (donc nos entêtes) !
La solution va donc consister à déplacer, à l'intérieur du fichier article.html
, tout notre code dans une section (que nous appellerons mamap :
{% block mamap %}
Mettre ici tout le code sur notre gestion cartographique
{% endblock %}
Et dans le fichier base.html
, on va insérer à l'intérieur des balises <aside>
son appel (qui ne tient que sur deux lignes) :
{% block mamap %}
{% endblock %}
b) Ajuster les feuilles de style
Il faut surcharger le comportement de la carte gérée par leaflet :
.leaflet-container {
width: 400px;
height: 300px;
max-width: 100%;
max-height: 100%;
margin: auto;
}
Et vérifier que les largeurs de la carte, et de <aside>
soient compatibles.
Le résultat avec nos dernières modifications est désormais le suivant

6) Conclusion
Il est temps de finir cette dépêche, dans laquelle nous avons pu découvrir la souplesse et la richesse des gabarits gérés avec jinja2, ainsi que la facilité d'utilisation de leaflet.
Désormais, dans notre flux de travail, nos répertoires sont organisé ainsi :
content
+ gpx : les fichiers de trace
+ images : les photos que l'on veut afficher sur notre blog
fichierXX.md : les billets
output : notre site web (généré par pelican)
theme
+ pelican-blue : le thème choisi
+ static
+ css
+ leaflet
+ leaflet-gpx
+ templates
Et la rédaction de nos billets consiste à :
- ajouter le fichier gpx de notre trace dans les entêtes
- ajouter les informations sur chaque photo que l'on veut voir (toujours dans les entêtes)
- écrire notre billet normalement (en y ajoutant éventuellement d'autres photos ou des ancres de navigation)
Cette dépêche démontre qu'il est possible d'avoir, avec les outils actuels, un rendu intéressant pour partager ses sorties. Et totalement utilisable en auto-hébergement.
Les outils utilisés sont très personnalisables et je vous invite à lire leurs documentations ou à parcourir leurs extensions respectives et de vous les approprier selon votre usage.
Malheureusement, la solution présentée ne conviendra qu'à une minorité d'utilisateurs. En effet, elle se base sur des éléments qui sont le plus souvent rendus invisibles (site web, transfert de fichiers, métadonnées) et elle est inutilisable sur téléphone.