Une carte interactive dans Ghost

Comment intégrer un tracé de randonnée dans un billet de blog ?

Une carte interactive dans Ghost
Fond de carte Géologique et IGN superposée sur Geoportail

C'est super de faire randonnée et de faire saliver les lecteurs avec de belles images et des descriptions au petits oignons, mais ça serait quand même top avec des belles cartes pour accompagner ça !! Tu crois pas?


Les cartes sont, en plus d'être un élément de sécurité que chacun devrait avoir avec lui en randonnées, très pratique pour mettre en valeur un coin et efficace pour représenter une activité de plein air. Grâce au travail de beaucoup de personnes, on dispose aujourd'hui de nombreuses technologie et cartes a notre disposition gratuitement notamment Géoportail et OpenStreetMap pour les fonds de cartes et Leaflet pour l'intégration dans les sites web.

Dans cet billet je vais détailler comment j'ai mis sur pied la carte la carte qui suit.

  • Difficulté: Moyen (E3/T3/R3)
  • Dénivelé: Inconnu m
  • Distance: Inconnu km
  • Durée de marche: Inconnu
  • Altitude mini : Inconnu m
  • Altitude maxi: Inconnu m

Nous aurons donc besoin:

  • d'un fond de carte pour pouvoir affichier le tracé de notre activité
  • d'un outil permettant d'intégrer la carte et le tracé
  • d'un fichier GPX contenant les données de notre randonnée

Les fonds de carte

Il existe de très nombreux fonds de carte. Ils permettent chacun de mettre en avant certaines informations clés comme le dénivelé pour les carte topographiques, la nature des sols pour les cartes géologique et même la façon dont l'homme se représentait son environnement dans le passé grâce au cartes de Cassini.

La première contrainte rencontrée est que je souhaite mettre en avant des outils que j'utilise presque quotidiennement. Ces outils sont gratuits et dispose d'une communauté forte qui les améliores régulièrement. Il s'agit de Géoportail développé et entretenu par l'Institut Géographique National aussi connu sous le nom d'IGN et de OpenStreetMap, un outil Open-source et utilisé dans la plupart des cas d'intégration de carte dans les sites internet.

L'intégration web

L'intégration web se fait par Leaflet, un outil Open-source, très répandu dans l'intégration de cartes, mis à jour régulièrement et disposant de nombreux plugin et extensions.

Le fichier GPX

C'est ce fichier qui possède les informations clés de notre parcours comme l'altitude, le temps de trajet et les différents points GPS.

J'enregistre mon parcours avec ma montre connectée Suunto 9 Baro et depuis l'application j'exporte le fichier GPX.

Les fichiers GPX sont des fichiers XML  permettant l'échange de coordonnées GPS. Ce format permet de décrire une collection de points utilisables sous forme de points de cheminement (waypoints), traces (tracks) ou itinéraires (routes).

Ils ressemblent a peu près à ça:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<gpx
     xmlns="http://www.topografix.com/GPX/1/1"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     creator="Suunto app"
     version="1.1"
     xsi:schemaLocation="http://www.topografix.com/GPX/1/1">
    <metadata>
        <name>suuntoapp-Hiking-2022-06-18T04-39-58Z</name>
        <desc>Lac de Palluel</desc>
        <author><name>Louison SARLIN--MAGNUS</name></author>
    </metadata>
    <trk>
        <name>suuntoapp-Hiking-2022-06-18T04-39-58Z</name>
        <trkseg>
            <trkpt lat="44.732962" lon="6.44885">
                <ele>1455.8</ele>
                <time>2022-06-18T04:41:45Z</time>
            </trkpt>
            <trkpt lat="44.732995" lon="6.448837">
                <ele>1455.8</ele>
                <time>2022-06-18T04:41:46Z</time>
            </trkpt>
            ...
            <trkpt lat="44.733733" lon="6.45029">
				<ele>1458.2</ele>
				<time>2022-06-18T13:46:24Z</time>
			</trkpt>
		</trkseg>
	</trk>
</gpx>

A partir de toutes ces données, en mettant les informations contenues dans les balises <trkpt> bout à bout on peut récréer précisement tout le tracé de la randonnée.

Les essais

Intégrer Géoportail

Ci-dessous on trouve un fond de carte tiré de Géoportail, intégré par iFrame.

Pour obtenir le résultat juste au dessus il suffit d'ajouter une "HTML card" et y coller le code suivant ou utiliser le générateur fourni dans Géoportail en connaissant le fond de carte et le centre de la carte souhaité:

<iframe 
        width="100%" height="400px"
        frameborder="0" scrolling="no"
        marginheight="0" marginwidth="0"
        sandbox="allow-forms allow-scripts allow-same-origin"
        src="https://www.geoportail.gouv.fr/embed/visu.html?c=6.128586050750606,44.547428356609345&z=13&l0=GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN25TOUR.CV::GEOPORTAIL:OGC:WMTS(1)&permalink=yes"
        allowfullscreen>
</iframe>

L'avantage d'une intégration en utilisant iFrame est la facilité, cependant on dispose de beaucoup moins de flexibilité pour effectuer des modifications sur la carte comme ajouter un tracé GPX.

Intégrer OpenStreetMap

Ci-dessous on trouve un fond de carte fournie par OpenStreetMap et intégré par Leaflet:

L'avantage de cette méthode est que Leaflet est un outil disposant de beaucoup de fonctionnalités, réglages et plugin. Le principal inconvénient est la complexité et le besoin de compétence rudimentaire en HTML, CSS et JS. Pour intégrer la carte simple comme au dessus il faut ajouter le code suivant dans une "HTML card"

<div id = "map" style="width: 100%; height: 400px;"></div>
<script>
    // Creating map options
    var mapOptions = {
        zoom: 13,
        center: [44.54742, 6.12858],
        scrollWheelZoom: false
    }

    // Creating a map object
    var map = new L.map('map', mapOptions);

    // Creating a Layer object
    var layer = new L.TileLayer('https://a.tile.opentopomap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenTopoMap'});

    // Adding layer to the map
    map.addLayer(layer);
    L.control.scale().addTo(map); 
</script>

Mais aussi d'injecter le code suivant dans le "Post header {{ghost_head}}"

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"
	integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
	crossorigin=""/>
<link rel = "stylesheet" href = "https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"/>
 <!-- Make sure you put this AFTER Leaflet's CSS -->
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.7.0/gpx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-gpx/1.7.0/gpx.min.js"></script>

Une fois cette carte en place on peut se lancer dans l'ajout de choses comme un overlay affichant le nom de la randonnée ou un sélécteur de fond de carte.

Pour ajouter un selecteur de carte il faut ajouter:

Les variables détaillant les fonds de carte:

var osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap'
});
var otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenTopoMap'
});

var baseMaps = {
    "OpenTopoMap": otm,
    "OpenStreetMap": osm
};

Ajouter à la carte le fond que l'on souhaite par défaut:

var mapOptions = {
    center: [44.5612, 6.08207],
    scrollWheelZoom: false,
    layers: otm
}

Et enfin ajouter les fonds de carte à l'objet "map":

L.control.layers(baseMaps).addTo(map);

Adoption de Leaflet

Leaflet est un outil rédigé en JS permettant d'intégrer des cartes et d'utiliser des plugin. L'objectif étant de pouvoir intégrer un parcours tiré d'un fichier GPX, Leaflet et leaflet-gpx me permette facilement d'intégrer un fond de carte puis d'ajouter un tracé par dessus.

Pour ajouter un tracé GPX par dessus un fond de carte, il faut se baser sur l'exemple détaillé au-dessus et ajouter:

La variable JS menant au fichier GPX

var gpxfile = 'https://blog.lsarlinmagnus.fr/content/files/ascension.gpx';

La variable JS contenant le tracé GPX

var gpx = new L.GPX(gpxfile, {
    polyline_options: { color: 'blue'},
    async: true,
    marker_options: {
        startIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-depart.png',
        endIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-arrive.png',
        shadowUrl: 'https://blog.lsarlinmagnus.fr/content/images/empty.png'
    }
}).on('loaded', function(e) {
    map.fitBounds(e.target.getBounds());
}).addTo(map);

Ajout de personnalisations

Ajoutons les informations calculées a partir du GPX par dessus la carte:

<div>
    <div id = "map" style="width: 100%; height: 400px; z-index: 1;"></div>
    <div id = "content" style ="background-color:  rgba(255, 255, 255, 0.75); border-radius: 8px; width: max-content; float: right; margin-top: -160px; margin-right: 5px; z-index: 2;position: relative;">
        <ul style="list-style: none; margin: 0; padding: 10px;font-size: smaller;">
            <li style="margin-top: 0;">
                Difficulté:
                <span id = "difficulte">
                    Moyen (E3/T3/R3)
                </span>
            </li>
            <li style="margin-top: 0;">
                Dénivelé:
                <span id = "denivele">
                    Inconnu
                </span>
                m
            </li>
            <li style="margin-top: 0;">
                Distance:
                <span id = "distance">
                    Inconnu
                </span>
                km
            </li>
            <li style="margin-top: 0;">
                Durée de marche:
                <span id = "duree">
                    Inconnu
                </span>
            </li>
            <li style="margin-top: 0;">
                Altitude mini :
                <span id = "atl_min">
                    Inconnu
                </span>
                m
            </li>
            <li style="margin-top: 0;">
                Altitude maxi:
                <span id = "atl_max">
                    Inconnu 
                </span>
                m
            </li>
        </ul>
    </div>
</div>

Ajoutons les valeurs dans le HTML:

var gpx = new L.GPX(gpxfile, {
    async: true,
    marker_options: {
        startIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-depart.png',
        endIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-arrive.png',
        shadowUrl: 'https://blog.lsarlinmagnus.fr/content/images/empty.png'
    }
}).on('loaded', function(e) {
    map.fitBounds(e.target.getBounds());
    document.getElementById("denivele").innerHTML = gpx.get_elevation_gain().toLocaleString();
    document.getElementById("distance").innerHTML = (gpx.get_distance()/1000).toFixed(2);
    document.getElementById("duree").innerHTML = gpx.get_duration_string_iso(gpx.get_moving_time());
    document.getElementById("atl_min").innerHTML = gpx.get_elevation_min().toFixed();
    document.getElementById("atl_max").innerHTML = gpx.get_elevation_max().toFixed();
}).addTo(map);

Puis changeons la couleur du tracé:

var gpx = new L.GPX(gpxfile, {
    polyline_options: { color: 'blue'},
    async: true,
    marker_options: {
        startIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-depart.png',
        endIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-arrive.png',
        shadowUrl: 'https://blog.lsarlinmagnus.fr/content/images/empty.png'
    }
}).on('loaded', function(e) {
    map.fitBounds(e.target.getBounds());
}).addTo(map);

Conclusion

Afin d'obtenir le résultat suivant:

  • Difficulté: Moyen (E3/T3/R3)
  • Dénivelé: Inconnu m
  • Distance: Inconnu km
  • Durée de marche: Inconnu
  • Altitude mini : Inconnu m
  • Altitude maxi: Inconnu m

Il suffit d'ajouter dans une "HTML card" le code suivant:

<div>
    <div id = "conclusion_map" style="width: 100%; height: 400px; z-index: 1;"></div>
    <div id = "content" style ="background-color:  rgba(255, 255, 255, 0.75); border-radius: 8px;
                                width: max-content; float: right; margin-top: -160px;
                                margin-right: 5px; z-index: 2;position: relative;">
        <ul style="list-style: none; margin: 0; padding: 10px;font-size: smaller;">
            <li style="margin-top: 0;">
                Difficulté:
                <span id = "conclusion_difficulte">
                    Moyen (E3/T3/R3)
                </span>
            </li>
            <li style="margin-top: 0;">
                Dénivelé:
                <span id = "conclusion_denivele">
                    Inconnu
                </span>
                m
            </li>
            <li style="margin-top: 0;">
                Distance:
                <span id = "conclusion_distance">
                    Inconnu
                </span>
                km
            </li>
            <li style="margin-top: 0;">
                Durée de marche:
                <span id = "conclusion_duree">
                    Inconnu
                </span>
            </li>
            <li style="margin-top: 0;">
                Altitude mini :
                <span id = "conclusion_atl_min">
                    Inconnu
                </span>
                m
            </li>
            <li style="margin-top: 0;">
                Altitude maxi:
                <span id = "conclusion_atl_max">
                    Inconnu 
                </span>
                m
            </li>
        </ul>
    </div>
</div>
<script>
    //Creating map vars
    var conclusion_osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenStreetMap'
    });
	var conclusion_otm = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
        attribution: '© OpenTopoMap'
    });
    
    var conclusion_baseMaps = {
        "OpenTopoMap": conclusion_otm,
        "OpenStreetMap": conclusion_osm
    };
    
    // Creating map options
    var conclusion_mapOptions = {
        center: [44.5612, 6.08207],
        scrollWheelZoom: false,
        layers: conclusion_otm
    }

    // Creating a map object
    var conclusion_map = new L.map('conclusion_map', conclusion_mapOptions);

    // Adding GPX trace to Layer
    var conclusion_gpxfile = 'https://blog.lsarlinmagnus.fr/content/files/ascension.gpx';

    // GPX processing
    var conclusion_gpx = new L.GPX(conclusion_gpxfile, {
        polyline_options: { color: 'blue'},
        async: true,
        marker_options: {
            startIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-depart.png',
            endIconUrl: 'https://blog.lsarlinmagnus.fr/content/images/map-marker-arrive.png',
            shadowUrl: 'https://blog.lsarlinmagnus.fr/content/images/empty.png'
        }
    }).on('loaded', function(e) {
        conclusion_map.fitBounds(e.target.getBounds());
        document.getElementById("conclusion_denivele").innerHTML = gpx.get_elevation_gain().toLocaleString();
        document.getElementById("conclusion_distance").innerHTML = (gpx.get_distance()/1000).toFixed(2);
        document.getElementById("conclusion_duree").innerHTML = gpx.get_duration_string_iso(gpx.get_moving_time());
        document.getElementById("conclusion_atl_min").innerHTML = gpx.get_elevation_min().toFixed();
        document.getElementById("conclusion_atl_max").innerHTML = gpx.get_elevation_max().toFixed();
    }).addTo(conclusion_map);
    
    // Adding layer to the map
	L.control.layers(conclusion_baseMaps).addTo(conclusion_map);
    L.control.scale().addTo(conclusion_map); 

</script>