Playing - Les types de voies à Paris

Nous combinons Leaflet et D3JS dans leur dernière version avec quelques changements à réaliser pour la bonne intégration de ces deux librairies
d3js7.x leaflet1.7.1
Sources :

Introduction

Cette visualisation permet d'explorer les différents types de voies de la ville de Paris. Au total, elle possède plus de 40 types de voies et 6513 polygones les représentants. L'ensemble est parfaitement fluide malgré la manipulation de 3500 éléments (pour les rues) en même temps. C'est aussi une occasion pour nous de mettre à jour l'intégration de D3JS sur une carte Leaflet avec les dernières versions de ces librairies. Les données proviennent du site Open Data Paris et se présentent sous la forme d'un fichier geoJSON. Sur la carte vous pouvez utiliser les fonctionnalités de zoom, passer votre souris sur toutes les voies de Paris pour connaitre leur nom et faire apparaitre toutes les voies d'un même type grâce au panneau de commande situé à droite.

Initialisation

Nous avons choisi un fond de carte neutre et sans libellé pour donner toute sa place aux interactions. Avec la version 1.7 de Leaflet l'ajout de l'overlay pour D3JS n'a pas changé, il faut récupérer le div prévu à cet effet par Leaflet. On initialise une fonction de transformation qui permet de passer des latitudes/longitudes de notre fichier geoJSON à un emplacement X,Y en pixel correspondant avec Leaflet. Les propriétés passées à la fonction addCommandControl sont visibles dans le code source et contiennent la liste des types de voies.

var CartoDB_DarkMatterNoLabels = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png', {
    attribution: '© OpenStreetMap contributors © CARTO',
    subdomains: 'abcd',
    maxZoom: 20
});

let map = L.map('map').setView([48.85341, 2.3488], 13);
map.addLayer(CartoDB_DarkMatterNoLabels);

let svg = d3.select(map.getPanes().overlayPane).append("svg"),
    voies = svg.append("g").attr("class", "leaflet-zoom-hide");
    
let transform = d3.geoTransform({point: projectPoint}),
    path = d3.geoPath().projection(transform);

let tooltip = d3.select("body").append("div")
    .attr("class", "map-tooltip")
    .style("opacity", 0);

addCommandControl(map, props);

Fonction de projection projectPoint

Cette fonction demande à Leaflet de projeter les coordonnées de notre fichier geoJSON sur la carte.

function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}

Construction du panneau de commande

Le panneau est construit à partir des données de voies. Si nous utilisons au départ les fonctions de Leaflet pour construire le premier div, le reste est géré avec l'innerHTML. L'ajout des évènements mouseover et mouseout est fait avec D3JS mais pourrait être fait avec toute autre librairie JS. Nous utilisons le nom de la class pour sélectionner toutes les voies du même type. Ce nom est défini à la construction des polygones juste en dessous.

function addCommandControl(map, props) {
    var command = L.control({position: 'topright'});
    command.onAdd = function (map) {
        var div = L.DomUtil.create('div', 'command');
        let innerHTML = '<div class="title"><span>Les types de voies</br> à Paris</div>';
        innerHTML += '<div class="subtitle"><span>(Sélectionner un type de voie)</div>';
        innerHTML += '<ul class="legend">'
        for (let [key, value] of props) {
            innerHTML += '<li name="' + key + '">' + (value.label ? value.label : "Les portiques d'Orléans") + ' (' + value.count + ')</li>';
        }
        innerHTML += '</ul>';
        div.innerHTML = innerHTML;
        return div;
    };
    command.addTo(map);

    Array.from(document.getElementsByTagName('li')).forEach(e => {
        d3.select(e)
            .on("mouseover", function(d) {
                let className = e.getAttribute('name');
                d3.selectAll('.' + className).style('opacity', 1);
            })
            .on("mouseout", function(d) {
                let className = e.getAttribute('name');
                d3.selectAll('.' + className).style('opacity', 0);
            });
    });
}

Ajout des voies sur la carte et gestion des interactions

Cette dernière partie du code contient des éléments essentiels au bon fonctionnement de l'interaction :

  • .style("pointer-events", "auto") permet de rétablir les évènements de souris sur les polygones représentant les voies. Par défaut Leaflet les désactive
  • map.on("zoomend", reset); indique sur quel évènement il faut recalculer le path des polygones. Depuis la version 1.0 de Leaflet, il ne faut plus utiliser viewreset mais zoomend sinon cela ne fonctionne pas. Pour la petite histoire, c'est lié à l'introduction de la fonctionnalité flyTo
d3.json('voie.geojson').then(function(geojson) {
    var features = voies
        .selectAll("path")
        .data(geojson.features)
        .enter()
        .append("path")
        .attr("class", d => d.properties.c_desi)
        .style("pointer-events", "auto")
        .style("fill", "none")
        .style("stroke", d => props.get(d.properties.c_desi) ? props.get(d.properties.c_desi).color : "white")
        .style("stroke-width", "2")
        .style("opacity", 0)
        .on("mouseover", function(event) {
            d3.select(this).style('opacity', 1);
            tooltip.html(d3.select(this).data()[0].properties.l_longmin)
				.style('opacity', 1)
				.style('color', props.get(d3.select(this).attr('class')) ? props.get(d3.select(this).attr('class')).color : "white")
                .style('left', (event.clientX + 30) + 'px')     
                .style('top', (event.clientY - 30) + 'px')
        })
        .on("mouseout", function(event) {
            d3.select(this).style('opacity', 0);
            tooltip.style('opacity', 0)
        });

    map.on("zoomend", reset);
    reset();

    function reset() {
        var bounds = path.bounds(geojson),
            topLeft = bounds[0],
            bottomRight = bounds[1];

        svg.attr("width", bottomRight[0] - topLeft[0])
            .attr("height", bottomRight[1] - topLeft[1])
            .style("left", topLeft[0] + "px")
            .style("top", topLeft[1] + "px");

        voies.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");

        features.attr("d", path);
    }
});

Conclusion

Nous terminons ici notre tutoriel. N'hésitez pas à regarder le code source pour voir la partie CSS qui gère l'affichage du tooltip, le panneau de contrôle et sa scrollbar.

COMMENTAIRES