Filtrer des données

Dans cette dernière partie on va apprendre à filtrer des données, pour cela on va faire une petite interface nous permettant de filtrer les données provenant d’un fichier GEOJSON.

Pour nous allons partir de données GEOJSON avec des points correspondant à des évènements avec pour paramètres :

  • Une date (année)
  • Un type ("AUTRE", "PUBLIC" ou "COMMERCE")

Avec notre interface nous allons filtrer :

  • La date minimale des évènements
  • La date maximale des évènements
  • Les types (3 choix : "AUTRE", "PUBLIC" ou "COMMERCE")

Charger des données GeoJSON

Pour nos données on va utiliser le format GEOJSON qui est le format le plus largement utilisé pour les systèmes de cartographies web.

GeoJSON (de l'anglais Geographic JSON, signifiant littéralement JSON géographique) est un format ouvert d'encodage d'ensemble de données géospatiales simples utilisant la norme JSON (JavaScript Object Notation). - source Wikipédia

On va d’abort initialiser la carte, créer un objet GEOJSON et charger ces données et les ajouter à la carte :

// Initialisation de la carte
let map = L.map('macarte').setView([40.722998, -74.003949], 13);

let selectedTile = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

// Creation de l'objet geojson
let geojsonFeature = {
    "type": "FeatureCollection",
    "features": [{
        "type": "Feature",
        "properties": {
            "shape": "Marker",
            "name": "Unnamed Layer",
            "category": "default",
            "year": 2022,
            "type": "COMMERCE"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [-74.003949, 40.722998]
        },
        "id": "7c587f8f-6ad8-4638-b831-cb368b3a598d"
    }, {
        "type": "Feature",
        "properties": {
            "shape": "Marker",
            "name": "Unnamed Layer",
            "category": "default",
            "year": 2022,
            "type": "PUBLIC"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [-74.002147, 40.719876]
        },
        "id": "2367c632-4ce1-4b4e-b705-1f7698dc70d0"
    }, {
        "type": "Feature",
        "properties": {
            "shape": "Marker",
            "name": "Unnamed Layer",
            "category": "default",
            "year": 2012,
            "type": "AUTRE"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [-73.998027, 40.722413]
        },
        "id": "0aade070-1667-4cb2-b22b-b6db4215453d"
    }, {
        "type": "Feature",
        "properties": {
            "shape": "Marker",
            "name": "Unnamed Layer",
            "category": "default",
            "year": 2015,
            "type": "AUTRE"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [-74.008799, 40.714606]
        },
        "id": "6d726c5b-81a0-4a23-b010-43e8cb5c9163"
    }, {
        "type": "Feature",
        "properties": {
            "shape": "Marker",
            "name": "Unnamed Layer",
            "category": "default",
            "year": 2000,
            "type": "PUBLIC"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [-73.995109, 40.715062]
        },
        "id": "0a354fb5-37e2-43cf-ab6d-71b4ded70d99"
    }, {
        "type": "Feature",
        "properties": {
            "shape": "Marker",
            "name": "Unnamed Layer",
            "category": "default",
            "year": 2000,
            "type": "COMMERCE"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [-73.992877, 40.7216]
        },
        "id": "fd590e75-1098-447e-bccf-7ab618772a64"
    }]
};

// Ajout des données de l'objet GEOJSON à la carte
L.geoJSON(geojsonFeature).addTo(map);
On obtient une carte avec des points issus de l'objet GEOJSON
On obtient une carte avec des points issus de l’objet GEOJSON

Créer l'interface

Maintenant qu’on a nos données on va créer l’interface permettant de les filtrer, voila à quoi elle doit ressembler :

Ce à quoi on veut que ressemble notre interface de filtrage
Ce à quoi on veut que ressemble notre interface de filtrage

Pour cela, on reprend d’abord une interface vide comme dans le premier chapitre :

/*
 * Classe gérant l'interface utilisateur de filtrage
 */
let MyControlClass =  L.Control.extend({

  options: {
    position: 'topright'
  },

  /*
   * Ajout de l'interface à la carte
   */
  onAdd: function(map) {

    this.map = map;

    let div = L.DomUtil.create('div', 'leaflet-bar my-control');

    return div;
  },

  /*
   * Suppression de l'interface
   */
  onRemove: function(map)
  {
  }
});

// Ajout de l'interface utilisateur à la carte
let myControl = new MyControlClass().addTo(map);

Dans le méthode onAdd, on commence par la création du titre : (Création d’un élément HTML h3 avec modification de son contenu HTML)

    let title = L.DomUtil.create('h3', '', div);
    title.innerHTML = "Filter : ";

Il nous faut des éléments permettant de filtrer les dates, pour cela on va ajouter deux div composés de :

  • Un label
  • Un champ input de type number avec une valeur par défaut
    let divMin = L.DomUtil.create('div', '', div);
    let labelMin = L.DomUtil.create('label', '', divMin);
    labelMin.innerHTML = "Année Min : ";
    let inputMin = L.DomUtil.create('input', 'input-number', divMin);
    inputMin.type = "number";
    inputMin.value = 2000;

Pour créer notre sélecteur de type, ont créé une div contenant des checkbox avec chacune un label :

    let divType = L.DomUtil.create('div', '', div);
    divType.innerHTML = "Type : ";

    let divTypeOther = L.DomUtil.create('div', '', divType);
    let inputOther = L.DomUtil.create('input', '', divTypeOther);
    inputOther.type = "checkbox";
    inputOther.checked = true;
    let labelOther = L.DomUtil.create('label', '', divTypeOther);
    labelOther.innerHTML = " AUTRE";

    let divTypePublic = L.DomUtil.create('div', '', divType);
    let inputPublic = L.DomUtil.create('input', '', divTypePublic);
    inputPublic.type = "checkbox";
    inputPublic.checked = true;
    let labelPublic = L.DomUtil.create('label', '', divTypePublic);
    labelPublic.innerHTML = " PUBLIC";

    let divTypeBusiness = L.DomUtil.create('div', '', divType);
    let inputBusiness = L.DomUtil.create('input', '', divTypeBusiness);
    inputBusiness.type = "checkbox";
    inputBusiness.checked = true;
    let labelBusiness = L.DomUtil.create('label', '', divTypeBusiness);
    labelBusiness.innerHTML = " COMMERCE";

    var buttonFilter = L.DomUtil.create('button', '', div);
    buttonFilter.innerHTML = "Filter";

Pour terminer, on gère l’évènement au clic qui appelle de la fonction filter qui effectuera le filtrage des données :

 L.DomEvent.on(buttonFilter, 'click', function() { this.filter(parseInt(inputMin.value), parseInt(inputMax.value), inputOther.checked, inputPublic.checked, inputBusiness.checked); }, this);

Pour que le design soit plus agréable il faut également faire quelques modifications en CSS :

.my-control {
  padding-left : 10px;
  padding-right : 10px;
  padding-bottom : 10px;
  background-color : white;
}

.button-class {
  padding : 2px;
  margin-top : 5px;
  width : 50px;
}

.input-number {
  width : 60px;
}

Code complet de l’interface :

  /*
   * Ajout de l'interface à la carte
   */
  onAdd: function(map) {

    this.map = map;

    let div = L.DomUtil.create('div', 'leaflet-bar my-control');

    let title = L.DomUtil.create('h3', '', div);
    title.innerHTML = "Filter : ";

		// Champ date (année) minimal
    let divMin = L.DomUtil.create('div', '', div);
    let labelMin = L.DomUtil.create('label', '', divMin);
    labelMin.innerHTML = "Année Min : ";
    let inputMin = L.DomUtil.create('input', 'input-number', divMin);
    inputMin.type = "number";
    inputMin.value = 2000;

		// Champ date (année) maximal
    let divMax = L.DomUtil.create('div', '', div);
    let labelMax = L.DomUtil.create('label', '', divMax);
    labelMax.innerHTML = "Année Max : ";
    let inputMax = L.DomUtil.create('input', 'input-number', divMax);
    inputMax.type = "number";
    inputMax.value = 2025;

		// Checkbox des types
    let divType = L.DomUtil.create('div', '', div);
    divType.innerHTML = "Type : ";

    let divTypeOther = L.DomUtil.create('div', '', divType);
    let inputOther = L.DomUtil.create('input', '', divTypeOther);
    inputOther.type = "checkbox";
    inputOther.checked = true;
    let labelOther = L.DomUtil.create('label', '', divTypeOther);
    labelOther.innerHTML = " AUTRE";

    let divTypePublic = L.DomUtil.create('div', '', divType);
    let inputPublic = L.DomUtil.create('input', '', divTypePublic);
    inputPublic.type = "checkbox";
    inputPublic.checked = true;
    let labelPublic = L.DomUtil.create('label', '', divTypePublic);
    labelPublic.innerHTML = " PUBLIC";

    let divTypeBusiness = L.DomUtil.create('div', '', divType);
    let inputBusiness = L.DomUtil.create('input', '', divTypeBusiness);
    inputBusiness.type = "checkbox";
    inputBusiness.checked = true;
    let labelBusiness = L.DomUtil.create('label', '', divTypeBusiness);
    labelBusiness.innerHTML = " COMMERCE";

		// Bouton de lancement de l'action de filtrage
    var buttonFilter = L.DomUtil.create('button', 'button-class', div);
    buttonFilter.innerHTML = "Filter";

    L.DomEvent.on(buttonFilter, 'click', function() { this.filter(parseInt(inputMin.value), parseInt(inputMax.value), inputOther.checked, inputPublic.checked, inputBusiness.checked); }, this);

    return div;
  },

Filtrer les données

On comment par créer la structure de notre fonction de filtrage :

  /*
   * Filtrage les données du GEOJSON
   * @param {number}          startDate                  L'année de début
   * @param {number}          endDate                    L'année de fin
   * @param {boolean}         inputOtherChecked          L'état "coché" de l'input "AUTRE" 
   * @param {boolean}         inputPublicChecked         L'état "coché" de l'input "PUBLIC" 
   * @param {boolean}         inputBusinessChecked       L'état "coché" de l'input "COMMERCE" 
   */
  filter(startDate, endDate, inputOtherChecked, inputPublicChecked, inputBusinessChecked) {

  },

Pour filtrer nos données, il nous faut supprimer nos layers actuellement affichés puis recharger nos données avec la fonction filter qui va nous permettre de sélectionner les données que l’on souhaite récupérer :

    // Retrait des layers de la carte (données issues du GEOJSON)
    map.removeLayer(layers);
    
    // Rechargement des données du GEOJSON
    layers = L.geoJSON(geojsonFeature,
    {
        filter: function (feature) {
            
            // Filtre des données : si return false la donnée n'est pas récupérée

            return true;
        }
    }).addTo(map);

On crée des filtres sur les dates, si des dates ont été remplies dans le formulaire on rejette les éléments dont l’année n’est pas comprise entre la date de début et la date de fin :

if (startDate && endDate) {
    if (endDate) {
        if (!(feature.properties.year >= startDate && feature.properties.year <= endDate))
            return false;
    } else if (feature.properties.year < startDate)
        return false;
}

Enfin on filtre les types, si la case correspondant au type de l’élément n’est cochée, on ne récupère pas cet élément :

if(feature.properties.type == "AUTRE" && !inputOtherChecked) {
 return false;
}
else if(feature.properties.type == "PUBLIC" && !inputPublicChecked) {
 return false;
}
else if(feature.properties.type == "COMMERCE" && !inputBusinessChecked) {
 return false;
}

Code des filtres complet :

  /*
   * Filtrage les données du GEOJSON
   * @param {number}          startDate                  L'année de début
   * @param {number}          endDate                    L'année de fin
   * @param {boolean}         inputOtherChecked          L'état "coché" de l'input "AUTRE" 
   * @param {boolean}         inputPublicChecked         L'état "coché" de l'input "PUBLIC" 
   * @param {boolean}         inputBusinessChecked       L'état "coché" de l'input "COMMERCE" 
   */
  filter(startDate, endDate, inputOtherChecked, inputPublicChecked, inputBusinessChecked) {
    // Retrait des layers de la carte (données issues du GEOJSON)
    map.removeLayer(layers);
    
    // Rechargement des données du GEOJSON
    layers = L.geoJSON(geojsonFeature,
    {
        filter: function (feature) {
            // Filtre des dates
            if (startDate && endDate) {
                if (endDate) {
                    if (!(feature.properties.year >= startDate && feature.properties.year <= endDate))
                        return false;
                } else if (feature.properties.year < startDate)
                    return false;
            }

            // Filtre des types
            if(feature.properties.type == "AUTRE" && !inputOtherChecked) {
             return false;
            }
            else if(feature.properties.type == "PUBLIC" && !inputPublicChecked) {
             return false;
            }
            else if(feature.properties.type == "COMMERCE" && !inputBusinessChecked) {
             return false;
            }

            return true;
        }
    }).addTo(map);
  },

Résultat et code complet

Voici ce le résultat, nous avons notre interface de filtrage fonctionnelle :


Vous savez maintenant comment filtrer des données, le filtrage de données de type Polygon fonctionne de la même manière. Il est également possible de filtrer des données selon d’autres critères notamment leur géométrie pour par exemple exclure des points situés dans une zone cible.