7
03 nov 2010

Introduction


La personnalisation des couches vecteurs, dans OpenLayers, peut intervenir à différents niveaux. En effet, il est possible de le faire aussi bien au niveau de l'entité elle-même qu'à un niveau plus général ou encore de définir un style spécifique en fonction d'un attribut. C'est pourquoi, nous essayerons, dans ce tutoriel, de vous faire découvrir toutes les subtilités de cette classe.

Style par défaut


Commençons immédiatement à découvrir nos données. Celles-ci proviennent d'une extraction des villes de plus de 10 000 habitants définies dans la base OpenStreetMap (ShapeFile de GeoFabrik) sur la zone Midi-Pyrénées. Les données ShapeFile ont ensuite été transformées, grâce à QGIS, au format GML afin d'être utilisées dans OpenLayers. De plus, aux quatre champs déjà présents (osm_id, name, type, population) nous en avons ajouté un supplémentaire (color) pour les besoins du tutoriel.

Ce dernier, lors du chargement d'une couche de type vecteur et si aucun style n'a été spécifié, lui en attribut un par défaut. Notre couche apparaîtra alors en orangé avec une légère transparence.

default_styleAM.png

Le code utilisé est présenté ci-dessous. Nous partirons de celui-ci que nous améliorerons ensuite au fur et à mesure.


/* MAP */
var map = new OpenLayers.Map('map');

/* LAYERS */
var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS","http://labs.metacarta.com/wms/vmap0?", {layers: 'basic'});
var city = new OpenLayers.Layer.GML('city','./data/osm_city_mp_color_sup10000.gml');
map.addLayers([ol_wms, city]);

/* POSITION */
map.setCenter(new OpenLayers.LonLat(9.66,49.21));
map.addControl(new OpenLayers.Control.MousePosition());
map.zoomTo(4);

Modification du style général


Maintenant, imaginons que nous souhaitons modifier le style par défaut de la couche en affichant des ronds noirs avec un contour vert. Il n'existe pas une manière unique de le faire. En effet, vous pouvez aussi bien utiliser la classe de base Util que la classe style ou encore la classe StyleMap. Néanmoins, dans ce tutoriel, nous utiliserons la manière la plus usuelle et surtout la plus propre, à savoir la création d'un objet de type StyleMap auquel nous affecterons différents styles.

Afin de bien comprendre les lignes de codes qui vont suivre, il est nécessaire de savoir que StyleMap est en quelques sortes, une classe composite. Celle-ci va en fonction de l'état de la couche (default, select, temporary, delete) activer un style particulier.

Par exemple, si nous souhaitons obtenir des ronds noirs entourés de vert, nous allons devoir modifier la propriété default de l'objet StyleMap. Cela se passe de la manière suivante :


var style = new OpenLayers.Style({
strokeColor: "green",
strokeWidth: 2,
strokeOpacity: 0.5,
fillColor : "black",
pointRadius : 10
});

var StyleMap = new OpenLayers.StyleMap({'default': style});
var city = new OpenLayers.Layer.GML('city','./data/osm_city_mp_color_sup10000.gml',{styleMap: StyleMap});

Voici la carte que vous devriez maintenant avoir :

green_black_style.png

Ce n'est pas le cas dans cet exemple mais imaginons que vous souhaitiez ajouter un style particulier lorsqu'un objet de la couche est sélectionné. Pour cela, il suffit de déclarer un nouveau style et de l'affecter à la propriété select. Cela fonctionne également de la même façon pour les propriétés temporary et delete.

Modification du style en fonction d'un attribut de la couche


Bien, nous savons maintenant modifier le style de nos couches. Mais cela n'est pas un peu fade tout ces ronds de la même taille et de la même couleur ? Ne serait-il pas plus agréable de pouvoir faire varier tout cela en fonction d'un attribut de la couche ? Sachez que c'est tout à fait possible et cela se fait grâce à la propriété context de l'objet style. En fait, dans mon objet style, je vais faire référence à une fonction déclarée dans context. Bon cela n'est pas très clair, passons au code immédiatement cela sera plus simple.


var style = new OpenLayers.Style(
{
strokeColor: "green",
strokeWidth: 2,
strokeOpacity: 0.5,
fillColor : "${COLOR}",
pointRadius : "${radius}"
},{
context: {
radius: function(feature) {
var minV = 73000;
var maxV = 143953092;
var minRayon = 1;
var maxRayon = 9;
surf = Math.round(minRayon+( (maxRayon-minRayon)*( (feature.attributes.population-minV)/(maxV-minV) )));
surf = surf* Math.pow(2,map.getZoom()-1);
return surf;
}
}
});

var StyleMap = new OpenLayers.StyleMap({'default': style});

var city = new OpenLayers.Layer.GML('city','./data/osm_city_mp_color_sup10000.gml',{styleMap:StyleMap} );

Comme vous pouvez le constater, sur l'image ci-dessous, la taille des points varie en fonction de l'attribut "population". Pour cela ma fonction "radius" possède en paramètre l'attribut "feature". Ensuite, à chaque création d'un objet j'accède à la valeur de celui-ci en naviguant dans mon objet jusqu'à "feature.attributes.population". Ensuite, nous lui appliquons quelques formules mathématiques "Math.round(..)" afin de créer nos cercles proportionnels.

Concernant la couleur, celle-ci change en fonction de la valeur de l'attribut COLOR. Rappelez-vous nous avions ajouté ce champ dans notre fichier de départ. Ensuite, nous utilisons "${...}" qui agit comme un raccourci vers la valeur de n'importe quel attribut de la couche. Néanmoins, spécifier le code couleur de l'objet au sein même de notre fichier n'est pas très propre. Regardons immédiatement comment améliorer cela.


green_attribute.png

Bien que cette solution puisse être utilisée pour réaliser des analyses thématiques elle implique que les informations soient fournies dans le fichier de données. De plus, il n'est pas possible de réaliser des filtres conditionnels (ex. : si la population est supérieure à X faire ça sinon faire ceci). Cela impose donc certaines contraintes. Voyons comment nous pouvons contourner cela.

Jouer avec la classe Rule


Dans cette partie, nous allons faire exactement la même chose que notre exemple précédent mais de manière complétement interactive. Pour cela, nous allons nous appuyer sur la classe Rule qui permet, en fonction des données de la couche, de définir un comportement ou des attributs de style particulier.

Nous allons donc définir trois règles :

  • Si la population est inférieure à 20 000, la couleur du cercle sera jaune pâle.
  • Si la population est supérieure à 20 000 mais inférieure à 56 000, la couleur du cercle sera orangée.
  • Si la population est supérieure à 50 000, la couleur du cercle sera rouge.

Regardons immédiatement ce que cela donne au niveau du code :


var style = new OpenLayers.Style(
{
strokeColor: "green",
strokeWidth: 2,
strokeOpacity: 0.5,
pointRadius : "${radius}"
},{
context: {
radius: function(feature) {
var minV = 10000;
var maxV = 400000;
var minRayon = 1;
var maxRayon = 8;
surf = Math.round(minRayon+( (maxRayon-minRayon)*( (feature.attributes.population-minV)/(maxV-minV) )));
surf = surf*Math.pow(2,map.getZoom()-5);
return surf;
}
}
});

var less_than_20000 = new OpenLayers.Rule({
filter: new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.LESS_THAN,
property: "population",
value: 20000
}),
symbolizer: {'fillColor': '#FFB87F'}
});

var between_20000_56000 = new OpenLayers.Rule({
filter: new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.BETWEEN,
property: "population",
lowerBoundary: 20000,
upperBoundary: 56000
}),
symbolizer: {'fillColor': '#FF6E1F'}
});

var more_than_20000 = new OpenLayers.Rule({
filter: new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.GREATER_THAN,
property: "population",
value: 56000
}),
symbolizer: {'fillColor': '#EF1D25'}
});

style.addRules([less_than_20000, between_20000_56000, more_than_20000]);

var StyleMap = new OpenLayers.StyleMap({'default': style});

var city = new OpenLayers.Layer.GML('city','./data/osm_city_mp_color_sup10000.gml',{styleMap:StyleMap} );

Comme vous pouvez le constater, nous avons créé trois règles : less_than_20000, between_20000_56000 et more_than_20000. Celles-ci s'appuient sur, un critère de comparaison et sur l'attribut sur lequel il s'appliquera. Les critères de comparaison disponibles sont : EQUAL_TO, NOT_EQUAL_TO, LESS_THAN, GREATER_THAN, LESS_THAN_OR_EQUAL_TO, GREATER_THAN_OR_EQUAL_TO, BETWEEN, LIKE. Mis à part le dernier, tous ces critères s'appliquent à des données de type numérique. Le critère "LIKE" s'applique à des chaînes de caractères.

Regardons immédiatement le résultat de nos critères :


style_rule.png

Conclusion


Il est facile avec OpenLayers de personnaliser ses données. La puissance de cette librairie apporte une réelle souplesse au développement d'interface cartographique. Mais, la logique qui se cache parfois derrière telle ou telle classe peu parfois être difficile à comprendre. J'espère qu'au travers de ce tutoriel, avoir réussi à vous faire découvrir un peu mieux les subtilités de la classe style.

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

Clair net et précis ... pourrait-on avoir un apperçu du fichier GML sous jacent ? merci

Merci pour votre remarque sur l'article. Le fichier GML est téléchargeable ci-dessous :
http://www.geotribu.net/applications/tutoriaux/openlayers/style/data/osm...

Bonjour,

Tout d'abord, je vous félicite pour ce site formidable!!
Ma question concerne l'arbre de couches qu'on peut créer avec GeoExt, Est-ce qu'on peut rajouter ces styles dans les "children" du "OverlayContainer" dans le "TreePanel" et permettre ainsi de n'afficher que les qu'on souhaite afficher: par exemple dans le cas de votre Exemple, avoir le choix de n'afficher que les éléments satisfaisant à la première règle de style (population moins de 20000 Hab.) ? J'espère que ma question n'est pas trop farfelue... :) Merci d'avance pour vos réponses...

Anas

Salut,

Oui ce tuto (comme les autres est très bien fait).
J'ai une petite question : Peut on récupérer la légende via new GeoExt.LegendPanel?

A plus


Bonjour,
Ce n'est pas le premier tuto que je suis, du coup je tiens à vous féliciter et à vous encourager à continuer !
Je vais m'éloigner un petit peu du sujet pour aborder le problème que posent GeoServer et ces styles. GeoServer propose un certain nombre de styles variés, mais plus intéressant encore, il permet de créer soi-même ses styles sous format SLD. Le truc, c'est que cela va bien plus loin que les styles proposés par OpenLayers (notamment pour les labels) d'où mon grand intérêt pour cet outil. GeoServer permet de prévisualiser les couches renseignées via OpenLayers, et celui-ci accepte les styles. Hors, si je travaille depuis OpenLayers et charge mes couches (WMS, WFS), les styles ne sont pas utilisés !
Deux questions :
Comment indiquer un style configuré en format sld ?
Ou mieux encore: comment faire accepter directement par OpenLayers les styles entrés dans GeoServer ?
J'ai grand besoin de savoir comment régler cette question. Je dois publier un Atlas de cartes, je peux difficilement me limiter à ce que propose OpenLayers.
Merci beaucoup pour votre aide.


Bonjour,

Je vous remercie pour vos encouragements. C'est toujours agréable et très motivant.
Concernant votre question. Effectivement GeoServer permet la création de styles au format Styled Layer Descripor. Par contre, je pense (mais à vérifier) qu'il n'est possible de l'utiliser directement qu'en WMS. Pour du WFS tu devras définir ton style toi même dans OpenLayers. Si tu le souhaites tu peux toujours parser ton SLD et le rendre ainsi directement utilisable dans OL.

Arnaud

Salut, juste un petit commentaire pour indiquer que l'ensemble des images insérées dans ce billet renvoient maintenant à des liens morts :/
Vous serait-il possible d'actualiser ces liens ? Ce tuto est une excellente ressource, même s'il se pourrait que certains éléments ne fonctionnent plus (il me semble que le style a changé).