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); }