14
08 oct 2008

Introduction


L'une des grandes nouveautés d'OpenLayers 2.7 est la possibilité de définir un comportement particulier pour une couche vecteur (WFS, GML...) lors de la réception du flux de données. Cela se passe au moyen de la class "strategy". Celle-ci doit être utilisée conjointement à l'une des quatres sous"-classes" suivantes :

  • BBOX - Seuls les objets vecteur contenus dans l'extention géographique de la vue sont affichés
  • Cluster - Les objets vecteur spatialement proche sont regroupés afin d'apporter une meilleure visibilité
  • Fixed - La requête éffectuée vers le serveur ne se fait qu'une seule fois. Il n'y a pas de rechargement de la couche après, par exemple, un déplacement, zoom...
  • Paging - Les objets vecteur ne sont pas tous affichés mais disponible sous la forme d'un slideshow.

Comme à son habitude, les créateurs de cette librairie ont pensé à tout et ont également fourni un certains nombes d'exemples :

Ce tutoriel est une mise en application de cette classe strategy. Nous utiliserons pour cela deux flux de données, un GML et l'autre WFS.

Mise en application de strategy.Cluster pour une couche GML


L'exemple ci-dessous s'appuit sur un fichier de points générés aléatoireement puis exporter au format GML et SHP depuis GRASS. Nous allons à partir de là, utiliser la classe strategy.Cluster pour automatiquement regrouper les points adjacents et former un cercle de rayon plus important.

Bien entendu nous allons nous appuyer sur la classe strategy.Cluster pour y arriver, mais l'astuce, pour faire varier la taille des cercles, consiste à utiliser l'attibut pointRadius de la classe Style. Dans le cas ci-dessous nous allons simplement utiliser un fichier GML qui contient l'ensemble de nos points :


var GMLstyle = new OpenLayers.Style({
pointRadius: "${radius}",
fillColor: "#ffcc66",
fillOpacity: 0.8,
strokeColor: "#ff0000",
strokeWidth: 2,
strokeOpacity: 0.3
}, {
context: {
radius: function(feature) {
return Math.min(feature.attributes.count,7) + 4;
}
}
});

GML = new OpenLayers.Layer.Vector("GML",
{
strategies:[
new OpenLayers.Strategy.Fixed(),
new OpenLayers.Strategy.Cluster()
],
protocol: new OpenLayers.Protocol.HTTP({
url: "../data/poi_random/gml/random_poi.gml",
format: new OpenLayers.Format.GML()
}),
styleMap:new OpenLayers.StyleMap({
"default": GMLstyle, // Utilisation du style GMLstyle
"select": { // Style particulier lors de la selection
fillColor: "#8aeeef",
strokeColor: "#32a8a9"
}
})
}
);

Et voilà, sous vos yeux ébahis devrait alors s'afficher vos points automatiquement regroupés en fonction de leur distance (en pixel) avec les points voisins. Vous pouvez, évidemment, faire varier la distance maximale de recherche de points voisins (par défaut 20 px). Cela se passe grace à la propriété distance :


strategyCluster = new OpenLayers.Strategy.Cluster();
strategyCluster.distance = 50;

Mise en application de strategy.Cluster pour une couche WFS


Je pensais au départ pouvoir utiliser strategy.Cluster directement depuis la classe standards layers WFS d'Openlayers qui hérite pourtant de la classe layer vector. Malheureusement cela semble impossible. Il faut pour cela "redescendre d'un niveau" et utiliser directement la classe layer vector en spécifiant le protocole corrspondant. Cela se fait de la manière suivante :


var WFSstyle = new OpenLayers.Style({
pointRadius: "${radius}",
fillColor: "#71FF00",
fillOpacity: 0.5,
strokeColor: "#000",
strokeWidth: 2,
strokeOpacity: 0.3
}, {
context: {
radius: function(feature) {
return Math.min(feature.attributes.count,7) + 4;
}
}
});

wfs = new OpenLayers.Layer.Vector("WFS",
{
strategies: [
new OpenLayers.Strategy.Fixed(),
new OpenLayers.Strategy.Cluster()
],
protocol: new OpenLayers.Protocol.HTTP({
url: "http://localhost/cgi-bin/mapserv?map=/var/www/html/data/random_poi.map",
params: {
format: "WFS",
service: "WFS",
request: "GetFeature",
srs: "EPSG:4326",
VERSION : "1.0.0",
typename : 'Random_POI'
},
format: new OpenLayers.Format.GML()
}),
styleMap:new OpenLayers.StyleMap({
"default": WFSstyle,
"select": {
fillColor: "#8aeeef",
strokeColor: "#32a8a9"
}
})
},
{
extractAttributes:true,
displayInLayerSwitcher: true
});

Voici un exemple concret de ce qu'il est possible de réaliser :

A propos de l'auteur: 
Arnaud Vandecasteele

Fervent défenseur de l'Open Source, Arnaud s'est spécialisé dans le développement d'application cartographiques web. OpenLayers, PostGIS ou encore Django sont autant d'outils qu'il manipule au quotidien.
S'il n'est pas en face de son ordinateur, vous le retrouverez un GPS à la main en train de cartographier pour OpenStreetMap, de faire voler son drone ou sur un tatami !

Commentaires

Waouh
ça déchire ça !
Faut vraiment que je m'y colle à OpenLayers, ça devient plus puissant que Google Maps.

Je pense que les deux se valent. GMap du fait qu'il ne propose pas (peu) d'incorporer ses propres sources de données a pu dévelloper une API plus puissante au niveau des fonctionnalités orientées utilisateur. Les mashups par exemple une superbe réalisation et un formidable outil.

Néanmoins, je pense qu'il existe deux visions de la géographie entre ces deux applications.

En effet, GMap est beaucoup plus orienté néogéographie néocartographie (Géographie 2.0) qui pour moi n'est pas vraiment de la géographie mais plutôt une autre vision de la représentation, de l'accés à l'information de l'univers du Web. La géographie n'est pas seulement une position sur carte...

OpenLayers, par la liberté qu'il propose permet d'offrir à celui qui l'utilise (le programme) un champ d'exploration plus vaste.

Arnaud

Bonjour Arnaud,

Effectivement, ce Strategy pour OL est assez impressionnant. C'est visuellement agréable de clustériser (regrouper) les données ponctuelles pour plus de clarté.
Ma question, porte cependant sur la difficulté d'afficher la donnée attributaire d'un GML que l'on "clusterise" dans un GridPanelFeature.
En effet, cette clusterisation m'a "détourné" le flux de données vers Strategy, et ainsi mon gridPanelFeature devient vide...

Comment puis-je le réalimenter?
A première vue, le passage de
Projects = new OpenLayers.Layer.GML(... Issu du tuto sur le GridPanelfeature
à
Projects = new OpenLayers.Layer.Vector(... Issu du tuto Strategy

serait l'origine?

Merci d'avance
Arno

Hum, ton besoin est particulier.
Je ne pense pas qu'il existe dans GeoExt un objet pré-construit permettant cela.
Mais pourquoi ne pas construire les deux indépendamment. Ton tableau serait rempli à partir des données contenues dans feature.attributes.

Arnaud

Ce genre de manip serait-il possible avec un Openlayers.Layer.Text (v. http://wiki.openstreetmap.org/index.php/Openlayers_POI_layer_example) ? Je peine un peu à trouver le bon style de "popup" avec des données contenues dans un fichier texte (Openlayers.Layer.Text ne permet pas l'utilisation d'un FramedCloud, par exemple) et, pour me connecter à votre article, j'aimerais bien pouvoir moduler la taille d'un symbole en fonction du nombre d'éléments.

Pour une application avec "popup", ça prendrait quelque chose comme j'ai déjà vu dans Google Maps : un gros symbole s'éclate en plusieurs petits symboles tous reliés au symbole parent par une ligne et chacun de ces symboles devient cliquable. Ainsi, la présentation sur la carte devient plus simple et, surtout, il n'est plus nécessaire de faire une gymnastique de translation forcée (ex. pour que deux symboles apparaissent au même endroit, il faut faire un delta x et y dans la BD pour avoir un affichage qui montre l'importance relative des objets dans un secteur donné).

Merci.

Je n'ai jamais eu à travailler avec des données de type OpenLayers.Layer.Text, je n'oserai donc pas m'avancer sur ce sujet. Peut-être l'objet d'un prochain tuto. Ce qui m'étonne tout de même c'est que l'on ne puisse pas utiliser les popup de type FramedCloud, puisque les objets popUp et layer sont différents...

Concernant la problématique de regroupement de point en fonction du niveau d'échelle, c'est justement ce que permet l'objet strategy.Cluster. L'avantage étant que l'on a pas besoin de jouer dans la base de données et que ce regroupement est recalculé à chaque changement d'échelle. Pour faire varier la taille du symbole, il suffit ensuite de jouer avec la propriété radius de l'objet Style.

Arnaud

Bonjour,

Est-ce que la strategy Cluster (ou autre) permet au client un chargement plus rapide si le nombre de features est important? J'ai par exemple 1000 features (rectangles en GeoJSON) et je veux eviter la mise en place d'un serveur WFS (car je comprends rien a GeoServer par exemple)... alors si je peux m'en passer.
Mais j'ai peur que le traitement requis par la strategy soit encore plus gourmand que le gain a l'affichage...

Merci

Bonjour Pascal,

Effectivement la stratégie de clusterisation permet de faire gagner au client un gain important de temps.
Le nombre d'informations à traiter reste le même, mais les traitements à effectuer lors de l'affichage sont moins nombreux.
Même si parser un fichier KML, GML etc ralentit le client, c'est surtout l'affichage de plusieurs centaines d'objets vecteurs qui est consommatrice de ressources.

Après si vous disposez d'un trop grand nombre de données pourquoi ne pas passer tout d'abord par du WMS puis quand l'extent d'affichage diminue afficher les données sous forme de vecteur avec par exemple une strategy BBOX ?

Arnaud

Après si vous disposez d'un trop grand nombre de données pourquoi ne pas passer tout d'abord par du WMS
WFS tu veux dire? car je vois pas le lien avec WMS
puis quand l'extent d'affichage diminue afficher les données sous forme de vecteur avec par exemple une strategy BBOX ?
Puisque je suis pas dans une architecture client-serveur (je suis pas contre mais j'ai pas trouver un serveur WFS suffisament simple pour moi), je pense que la strategie BBO ne s'applique pas si? je ne fais pas de requete, j'ouvre directement le fichier GML...

Merci

Bonjour,

Non je parlais bien de WMS. L'idée étant de n'afficher que la couche WMS jusqu'à un certain niveau de zoom. Une fois ce dernier suffisant, la couche WMS change pour devenir WFS.

Néanmoins, si vous ne disposez d'aucune infrastructure carto (serveur WMS/WFS, tilecache...) cela est plus problématique. Dans ce cas j'opterai plus vers une strategy de clusterisation ainsi que BBOX.

Néanmoins, faites attention car OpenLayers déconseille l'affichage d'un trop grand nombre de données (Marker, WFS...). En effet pour chacun d'entre eux, un div dans le DOM est créé ce qui contribue à alourdir considérablement le chargement. De plus la plupart des navigateurs ralentissent considérablement une fois qu'ils ont plus d'une centaine d'éléments DOM à charger

=> Browsers can't handle moving around a DOM with more than a few hundred elements at once

=> 100-200 Features (Since each geometry is rendered as a separate DOM object, dragging and the like get seriously slowed down the more features you have on the map.)

Bonjour.
D'abord merci pour cet article. Mais, parce qu'évidemment, il y a un "mais"...
J'ai voulu utiliser strategy.cluster avec des données vectorielle en Lambert93 (EPSG 2154). La clusterisation ne fonctionnait pas alors que si j'utilise des données en WGS84 (EPSG 4326) tout marche bien.
J'ai fini par trouver que dans cette classe, notamment dans la méthode "shouldCluster", en gros la distance entre les points est calculée dans l'unité du système de projection puis divisée par la résolution. Or cette résolution est tirée de la layer passée en paramètre et celle-ci est différente de la résolution de la carte. Si cette différence est minime lorsqu'on travaille en WGS84, elle est flagrante en Lambert93. La variable "this.resolution" utilisée dans cette méthode est définie dans la méthode "cluster". J'ai donc changé
var resolution = this.layer.getResolution();
par var resolution = this.layer.map.getResolution();
et cela a résolu mon problème.
Je ne suis pas sûr que ce soit la meilleure solution, je travaille avec Openlayers depuis peu de temps, mais je n'ai pas trouvé d'info pour clusteriser mes données en Lambert93.
Ou bien cela vient de mes données, de mon serveur WFS ?
Une autre solution pour obtenir un resultat en Lambert93 similaire au WGS84 pour des données sur la France entière, sans aller bidouiller le code:
wfs = new OpenLayers.Layer.Vector("WFS",
{
strategies: [
new OpenLayers.Strategy.Fixed(),
new OpenLayers.Strategy.Cluster({distance: 50000})

Voila. Si vous avez d'autres idées, n'hésitez pas.
Un peu long, pas forcément clair, mais cela pourra peut-être servir à quelqu'un.
Et merci encore pour l'article.
Emmanuel

Merci beaucoup pour l'astuce.

Habitant l'île de La Réunion je n'avais jamais eu ce problème. Je n'utilise en effet que très rarement voir jamais des projections de type Lambert.

Merci à nouveau, je pense que cela sera d'une grande utilité pour tous les utilisateurs.

Bonjour Arnaud,
Comme tu habites La Reunion, Est ce que tu peux m'aider en me donnant l'extent de La Reunion dans la projection Piton de Neige et RGR92.
Qu'est ce qui est mieux entre les deux projections ? Et est ce que ces deux projections sont déjà implementés dans PROJ4 ?

Merci d'avance de tes réponses.

Richard

Bonjour Richard,

La projection Gauss Laborde Réunion ne doit plus être utilisée pour la Réunion. Elle est approximative et peut fiable. En effet elle introduit à la fois une translation+une rotation...

Pourquoi ne pas vous orientez vers de l'UTMS 40S en WGS 84. Qui à l'avantage d'être mondial.

Sinon vous pouvez toujours jeter un coup d'oeil ici :

> spatialreference.org Piton des neiges
> spatialreference.org RGR92 UTM 40S