Archives pour la catégorie SIG

petiteReine, une visualisation des parkings vélo avec OpenStreetMap

Un besoin

En octobre 2014 a commencé une collaboration entre le Collectif cycliste 37 et Tour(s) plus pour répertorier les parkings vélo de plusieurs communes de l’agglomération tourangelle. Ceci répondait aux besoins des deux parties : la collectivité n’avait pas de bases de données de ces équipements et l’association cherchait à diagnostiquer les besoins en stationnement sur l’agglomération. Culture libre et créations collectives s’est associée à cette initiative, d’où ma propre implication.

Quatre jours de cartopartie ont permis d’identifier 859 parkings sur les communes de Tours et Joué-les-Tours, mais comment visualiser ces données dans un outil grand public sur la toile ? comment tirer du sens d’un nuage de points sur une carte ?

La carte cyclable du site openstreetmap.org présente des carrés bleus pour les parkings vélo, les parkings de grande capacité ont un marqueur différent (un C dans un carré, accompagné de la capacité). Ce rendu est très sommaire car il n’informe du nombre de places que pour une petite minorité de stationnement, qu’il ne différencie par les équipements ouverts au public de ceux qui sont privés, et qu’il affiche identiquement les arceaux et les pince-roues, quand bien même les cyclistes préfèrent largement les premiers aux seconds (pour des raisons de sécurité). Le service de calcul d’itinéraires Géovélo affiche lui aussi les parkings vélo sur la carte, aux niveaux de zooms les plus hauts, mais le but est de montrer une information complémentaire (des stationnements situés dans les environs) à des cyclistes qui viennent calculer un itinéraire.

J’ai donc décidé de développer une petite application qui permettrait la visualisation des parkings vélo, l’analyse des données et le contrôle qualité.

Voir la carte.

Consultation détaillée

Le premier objectif est d’afficher les parkings vélos avec toutes les informations utiles aux cyclistes, dans une info-bulle qui s’affiche quand on passe la souris. Chacune de ces informations vient d’un attribut des objets décrits dans OpenStreetMap

  • Quel est le type ? On a seulement trois types sur notre territoire, les arceaux, les pince-roues et les abris (clé bicycle_parking)
  • Combien de vélos peut-on garer ? (clé capacity)
  • Est-ce que l’emplacement est couvert ? (clé covered)
  • Le lieu est-il accessible au public ? réservé aux clients ou usagers ? ou bien privé ? (clé access)
  • Quand des restrictions d’accès s’appliquent, à qui appartient le stationnement (clé operator) ? Cela peut être une entreprise qui pose des emplacements pour ses salariés, ou un commerce qui fournit ce service à sa clientèle.

Analyse de données

L’application petiteReine est avant tout un outil d’analyse des données sur les parkings vélo, qui peut servir aux associations cyclistes, aux collectivés, aux citoyens. Elle permet la visualisation des parkings vélo et le diagnostic sur la couverture du territoire en stationnements.

L’analyse se fait selon trois dimensions.

L’espace (où sont les parkings ?)

La prise en compte de l’espace est centrale dans un outil cartographique. Il est pris en compte de différentes manières à différents niveaux :

  • À petite échelle, on peut voir la localisation la plus exacte possible des stationnements vélo
  • À une échelle plus grande, les parkings sont regroupés en grappes ; on peut voir l’étendue de chaque grappe en passant la souris sur le symbole circulaire
  • Au niveau des divisions administratives, on a la possibilité de filtrer les données par commune
  • On peut également constater la répartition physique des parkings vélo sur le territoire avec la couche « Couverture en parkings vélo », qui affiche en vert les zones bien couvertes en stationnements (à moins de 100 mètres d’un parking vélo), et montre des zones plus éloignées (jaune entre 100 et 200m , orange de 200 à 400m). Ceci permet notamment de mettre en avant les zones où la couverture est dense, et les zones blanches.
  • Au niveau d’un point quelconque de la carte, un clic permet de déterminer la distance à vol d’oiseau jusqu’au parking le plus proche

La quantité (est-ce qu’il y en a assez ?)

Afficher des points sur une carte permet de vérifier la présence ou l’absence de parkings à un endroit. Il est intéressant de connaitre le nombre de places, au niveau d’un parking, mais aussi à plus grande échelle. C’est pour cela que les regroupements de parkings affichent le nombre total de places dans la zone du regroupement.

Dans le bloc des statistiques, on reprend le nombre de parkings et de places au niveaux des communes actuellement affichées.

Au delà de la maille de la commune, on peut déterminer une zone (quartier, voie de circulation) dans laquelle, on fait un comptage des parkings et places de stationnement. Le tracé de zones peut se faire par rectangle ou par polygone.

La typologie des parkings

La consultation individuelle d’un parking vélo met avant tout en valeur son type parmi les trois types identifiés sur Tours : arceaux, pince-roue et abri

Les statistiques proposées par l’outil permettent de compter les stationnements et places disponibles sur une zone donnée, mais on s’intéresse également au type des parkings en affichant des sous-totaux par type.

Veiller à la qualité

Puisqu’on récupère l’ensemble des parkings vélo sur une zone pour présenter des statistiques, on peut sans peine effectuer une validation simple et automatique des données présentes (mais pas deviner la présence sur le terrain de parkings inconnus dans OpenStreetMap). Alors qu’OpenStreetMap laisse la liberté de décrire un objet géographique, même de manière très incomplète, nous pensons que pour chaque parking, on doit au moins connaître le type de stationnement (arceau, pince-roue,…) et le nombre de places.

Un couche cartographique présente l’ensemble des parkings qui présentent au moins un de ces défauts :

  • Nombre de places inconnu
  • Type inconnu
  • Doublon géographique (deux parkings aux mêmes coordonnées, même si deux parkings voisins existent, ils doivent être distincts dans l’espace)

Le fait de présenter des données à corriger est une motivation pour des contributeurs d’OpenStreetMap, qui vont chercher à faire disparaitre ces signalements en corrigeant les données. Les marqueurs sont accompagnés d’un lien permettant de modifier ces données dans JOSM (éditeur de données OpenStreetMap). Si des parkings manquant d’informations sont ajoutés par la suite, ils seront aussi signalés.

La rapidité de mise à jour fait partie de la culture d’OpenStreetMap, les contributeurs souhaitent voir apparaitre les résultats de leurs modification sans attendre un processus de validation ou une synchronisation qui se produit rarement. La mise à jour des données de la carte se fait donc toutes les heures. Un traitement planifié récupère l’ensemble des données en parkings vélo de la zone, grâce à un service nommé Overpass API, qui extrait des données d’OpenStreetMap à la demande. Lors d’une cartopartie, on peut donc voir rapidement les résultats de la collecte, mais si d’autres utilisateurs d’OpenStreetMap ajoutent des données inexactes ou incomplètes cela sera visible rapidement.

Essaimer

Sur d’autres territoires

Après quelques dizaines d’heures de développement, j’ai réalisé qu’il serait dommage d’avoir passé tant de temps pour un outil qui ne servirait que pour un territoire, ses citoyens et ses collectivités.

Il était assez logique de publier le logiciel sous forme de logiciel libre (licence AGPL), sur la plateforme GitHub. Et non seulement faut-il mettre le logiciel à disposition, mais il faut le rendre accessible ; une documentation d’installation est disponible, pour guider toute personne ayant des compétences en informatique qui voudrait déployer l’application sur son propre serveur et sur le territoire de son choix. Le but de cette documentation est de rendre la carte installable rapidement, sur un hébergement peu cher et sans nécessiter de compétences très pointues.

Actuellement, les outils requis sont PHP et PostGIS. Il n’est pas possible à ma connaissance de disposer de PostGIS sur des hébergements mutualisés, mais on peut très bien se baser sur des serveurs dédiés ou des serveurs privés virtuels à faible coût (ex : VPS à partir de 2€ HT par mois chez OVH).

Pour maximiser les chances de réutilisation à l’échelle internationale, la documentation est écrite en anglais (enfin, en cours d’écriture). L’application offre également le support de plusieurs langues : anglais et français sont disponibles pour le moment, mais l’ajout d’autres langues peut se faire assez facilement. Les textes affichés à l’écran font partie de la configuration ; si par exemple des réutilisateurs francophones reprennent l’outil et veulent simplement changer des libellés ou la terminologie (exemple : Communes en Arrondissements si on fait une carte sur Paris), ceci nécessite simplement de modifier un fichier de configuration, sans avoir de compétences techniques.

Vers un outil plus global ?

Si le besoin émerge d’afficher des parkings vélo à une échelle plus grande (pays, monde), l’évolution est envisageable, mais nécessitera des moyens supérieurs en termes d’infrastructure.

La magie du regroupement dans OpenLayers

Quand on affiche tout un tas de points d’intérêts sur une carte, leur répartition n’est pas toujours uniforme, ce qui occasionne parfois un agglutinement des points d’intérêt qui s’empilent dans une mêlée confuse.

Pour gérer proprement des points d’intérêt nombreux et qui ont tendance à se confondre, on dispose dans OpenLayers d’un mécanisme de regroupement, qu’on désigne en anglais sous le nom de clustering. Plutôt que dessiner tous les points tel quel, la bibliothèque va se charger de regrouper les points très rapprochés en un seul point d’intérêt. Ce système, bien que puissant est un peu compliqué, comme bien d’autres choses dans OpenLayers.

Voyons point par point comment mettre en œuvre le regroupement sur une couche de points, gérer l’apparence des points regroupés et prendre en compte les événements sur une telle couche.

Propriétés de la couche

Le regroupement dans OpenLayers est implémenté comme une stratégie, c’est à dire un objet qui change de comportement d’une couche. Voici la définition de notre couche de point d’intérêt, un objet OpenLayers.Strategy.Cluster() est fourni dans la propriété strategies.

var layerPOI = new OpenLayers.Layer.Vector("Points d'intérêt", {
	projection: new OpenLayers.Projection("EPSG:4326"),
	strategies: [
		new OpenLayers.Strategy.Cluster()
	],
	styleMap:new OpenLayers.StyleMap({
           "default": clusterStyle
	})
});

Nota: Nous avons référencé un style nommé clusterStyle, que nous expliciterons plus tard.

Manipuler les groupes

Il est nécessaire ici d’adapter votre code concernant la gestion des événements liés à la couche. Quand on implémente un contrôle, comme SelectFeature pour intercepter le clic sur un objet, sans regroupement d’objets, on reçoit un objet OpenLayers.Vector, dont les propriétés sont:

  • attributes:  tableau des attributs de l’objets
  • geometry: une géométrie

Quand on ajoute à cela le regroupement, on reçoit plus des OpenLayers.Vector, mais un objet avec deux attributs:

  • count: nombre de points d’intérêt dans le groupe
  • cluster: tableau des objets du groupe, ce sont des OpenLayers.Vector

Re-définir le style

Pour la couche, on doit définir un style avec la classe OpenLayers.Style. Comme dans toutes les déclarations de style dans OpenLayers, on y définit les valeurs de différents attributs comme le label, la grosseur d’un point ou l’adresse d’un pictogramme. Mais comme on ne souhaite pas forcément rendre un point d’intérêt seul de la même manière que le regroupement de dix points d’intérêt, on peut calculer des propriétés de style en fonction du groupe de points d’intérêt. Là où on donne souvent des valeurs constantes pour une propriété, on peut déclarer dans les attributs des « variables ». Par exemple, dans la propriété label, on utilise la déclaration ${nombre}, qui désigne une valeur à calculer lors du rendu de la couche. Les variables sont calculées dans des méthodes du même nom, qui font partie de l’objet context.

Dans cet exemple, on affiche comme label le nombre d’éléments d’un groupe d’au moins deux éléments, avec la variable ${nombre}.

var clusterStyle = new OpenLayers.Style({
    label:"${nombre}",
    graphicWidth: 20,
    graphicHeight: 20
}, {
    context: {
         nombre: function(feature) {
             if(feature.attributes.count>=2)
            	 return feature.attributes.count;
             else
            	 return "";
         }
    }
});

Critères de regroupement

L’algorithme de regroupement d’OpenLayers est relativement simple: on regroupe tous les objets qui, à l’écran, sont à moins de 40 pixels de distance.

On peut vouloir personnaliser ce comportement, pour prendre en compte d’autres règles de gestion. Dans ce cas, après avoir instancié la stratégie de regroupement, on doit re-définir sa méthode shouldCluster. Partant d’un groupe existant, nommé cluster et d’un objet feature qu’on pourrait y ajouter, cette méthode décide de la possibilité de fusionner le groupe avec un élément, en retournant un booléen. Dans cet exemple, on a modifié la stratégie pour ne regrouper que les objets qui ont la même valeur pour l’attribut type et qui sont distant d’au plus 35 pixels.

var clusterCat = new OpenLayers.Strategy.Cluster();
clusterCat.shouldCluster = function(cluster,feature)
{
	if(cluster.cluster[0].attributes.type != feature.attributes.type)
	{
		return false;
	}
	var cc = cluster.geometry.getBounds().getCenterLonLat();
        var fc = feature.geometry.getBounds().getCenterLonLat();
        var distance = (Math.sqrt(Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2)) / this.resolution);
        return (distance <= 35);
}

Transformations de coordonnées avec GDAL

Cette petite note technique traite d’une des activités les plus fréquentes quand on travaille avec des données géographiques: la transformation de coordonnées entre différentes projections. L’incontournable PostGIS est pourvu de la fonction st_transform(the_geom,srid), qui passe n’importe quelle géométrie dans le système de coordonnées voulu. Or, il n’est pas toujours possible pour des contraintes techniques ou autres soucis de performances de solliciter PostGIS.

La bibliothèque GDAL est un outil très adapté dans les cas où vous voulez manipuler des données géographiques en C++ ou Python. Celle ci a la mérite d’être bien développée, même s’il n’y a pas vraiment une grande communauté et beaucoup de tutoriel sur ses fonctionnalités. Pour preuve, le site de GDAL n’est autre qu’une documentation générée par Doxygen.

Il n’est pourtant pas très compliqué, en C++ ou Python, de transformer des coordonnées avec GDAL, supposant qu’on utilise le langage C++:

  • Installer la bibliothèque  GDAL, sur des systèmes de type Debian ou Ubuntu:
sudo  apt-get install libgdal1-dev
  • Il est nécessaire de faire les inclusions suivantes:
#include <gdal/ogr_geometry.h>
#include <gdal/ogr_spatialref.h>
#include <gdal/ogr_srs_api.h>s
  • Pour effectuer des transformations d’un système à l’autre, on déclare d’abord deux objets représentant des systèmes de coordonnées, avec la classe OGRSpatialReference, puis l’objet destiné à transformer des coordonnées (classe OGRCoordinateTransformation). Typiquement, on utilise le code EPSG, pour représenter un système de coordonnées. Ici, on instancie la projection 4326 (latitude et longitude) et la projection 25572, qu’on appelle aussi Lambert II, et qui a l’avantage en France de représenter fidèlement les distances en mètres. On cherche alors à transformer les coordonnées de Lambert II vers longitude/latitude
OGRSpatialReference refLonLat;
refLonLat.importFromEPSG(4326);
OGRSpatialReference refLambert;
refLambert.importFromEPSG(27572);
OGRCoordinateTransformation * lambertToLonLat = OGRCreateCoordinateTransformation(&refLambert,&refLonLat);
  • Lorsqu’on veut convertir un vecteur de deux coordonnées, avec les variables x et y, on utilise l’objet de transformation avec la méthode Transform
double x = ...;
double y = ...;
lambertToLonLat->Transform(1,&x,&y);

A noter, les coordonnées sont converties sur place, c’est à dire que les variables (ou tables) qui lui sont données sont modifiées. Si le premier argument vaut 1, on peut lui donner de simples valeurs de type double, ou bien des tableaux de double dans le cas contraire…

Pour convertir des coordonnées en Python, un code Python équivalent serait:

try:
from osgeo import ogr
except:
import ogr
try:
from osgeo import osr
except:
import osr
refLambert = osr.SpatialReference()
refLambert.ImportFromEPSG(27572)
refLonLat = osr.SpatialReference()
refLonLat.ImportFromEPSG(4326)
coordTrans = osr.CoordinateTransformation(refLambert,refLonLat)
point = ogr.Geometry(ogr.wkbPoint)
x = ...
y = ...
point.AddPoint(x,y)
point.Transform(coordTrans)