Workshop OpenLayers v2

Dans OpenLayers, une carte est un ensemble de couches associée à des contrôles pour gérer l'interaction avec l'utilisateur. Une carte se construit avec trois ingrédients de base : du balisage HTML, des déclarations de style CSS, et du code JavaScript d'initialisation.

1. Les bases

Pour ce workshop, téléchargez le matériel de départ ol2014-workshop.zip

Retenez ceci qui pourra être très utile : OpenLayers API docs, OpenLayers examples

Ex1a : créer une carte, un premier exemple pour comprendre

<html>
<head>
<title>Ex1a - create a first map with a WMS layer</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
map = new OpenLayers.Map('map');
wms = new OpenLayers.Layer.WMS(
"World boundaries",
myWMS,
{
version: "1.1.1",
layers: 'ogo:world_simple',
format: 'image/png'
},
{
singleTile: true
}
);
map.addLayer(wms);
map.zoomToMaxExtent();
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

Balisage HTML et déclarations CSS

Le bloc <div> d'ID map est la cible de visualisation (la vue cartographique)

C'est la position et la taille de cette div qui déterminent comment la vue s'intègre dans l'interface

OpenLayers est accompagné de nombreuses prédéfinitions de déclarations CSS

TODO

Où sont-elles décrites ?

Trouvez celles qui habillent les boutons zoomin/zoomout !

Ajoutez le code HTML ci-dessous avant la div ainsi que le CSS (voir OpenLayers.Control.Zoom)

<a href="#" id="olZoomInLink"><img src="images/zoomin.png" class="zoomHover"></a>
<a href="#" id="olZoomOutLink"><img src="images/zoomout.png" class="zoomHover"></a>

.zoomHover:hover {
background: lightskyblue;
}

Initialisation de la carte

L'objet OpenLayers.Map est associé à l'ID de la cible de visualisation

Un objet OpenLayers.Layer est défini pour chaque couche à ajouter à la carte

L'objet OpenLayers.Layer.WMS permet de définir les paramètres d'une requête WMS pour obtenir une couche servie par un serveur cartographique conforme au standard OGC WMS (http://www.opengeospatial.org/standards/wms)

La méthode zoomToMaxExtent termine l'initialisation en définissant l'enveloppe géographique de visualisation (par défaut tout le globe)

TODO

Quelle différence en modifiant comme suit l'option singleTile de la définition OpenLayers.Layer.WMS ?

//singleTile: true
singleTile: false

Rendre visible la grille régulière des tuiles en plaçant l'instruction CSS ci-dessous au bon endroit

border: 1px solid red;

Ajouter le paramètre WMS suivant dans la définition OpenLayers.Layer.WMS

styles: "green"

Remplacer map.zoomToMaxExtent comme suit (voir zoomToExtent)

//map.zoomToMaxExtent();
map.zoomToExtent([6,46,9,49]);
console.log(map.getProjection());

Rechercher et tester d'autres couches de serveurs cartographiques WMS (par exemple de celui-ci : http://maps.opengeo.org/geowebcache/service/wms?request=GetCapabilities)

2. Cache de tuiles

Avec singleTile à false, le client contrôle la grille de tuiles qu'il peut garder en cache. Néanmoins, à chaque requête initiale de tuile, tout le processus de rendu cartographique se fait. Mais puisqu'une couche de tuiles fait des requêtes d'images selon une grille régulière, il est possible pour le serveur de préparer un cache de ces images.

Le service cartographique WMS permettant une grande flexibilité en terme de ce que le client peut demander. Côté serveur, cela rend difficile la mise en cache. A l'extrême opposé, un service peut aussi offrir des tuiles pour un ensemble prédéfini de niveaux de zoom et pour une grille régulière prédéfinie. On parle de couches de tuiles avec une source XYZ - avec X et Y indiquant la colonne et la rangée de la grille et Z le niveau de zoom. Il faut voir cela comme une pyramide de tuiles.

Ex2a : OpenLayers.Layer.OSM

<html>
<head>
<title>Ex2a - webmap with tiled layer</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM Map");
map.addLayer(osm);
map.zoomToMaxExtent();
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Modifier le centrage de la carte comme suit :

//map.zoomToMaxExtent();
map.setCenter(new OpenLayers.LonLat(6.15,46.2).transform("EPSG:4326","EPSG:3857"), 10);

Notez bien : on centre selon un point LonLat transformé de sa définition en coordonnées géographiques EPSG:4326 vers sa définition en coordonnées Mercator EPSG:3857

Insérer en fin de code les logs ci-dessous :

console.log("Map projection: " + map.getProjection());
console.log("Map extent: " + map.getExtent());
console.log("LonLat Mercator: " + new OpenLayers.LonLat(6.15,46.2).transform("EPSG:4326","EPSG:3857"));
console.log("LonLat Google: " + new OpenLayers.LonLat(6.15,46.2).transform("EPSG:4326","EPSG:900913"));

Remplacer la définition de la couche OpenLayers.Layer.OSM comme suit :

osm = new OpenLayers.Layer.OSM("UrbanGene Custom OSM",
"http://urbangene.heig-vd.ch/tilecache/${z}/${x}/${y}.png");

Remplacer la définition de la couche comme suit :

osm = new OpenLayers.Layer.OSM("OSM tiles restyled by MapBox",
"http://api.tiles.mapbox.com/v3/oertz.map-i2ak2ozc/${z}/${x}/${y}.png",
{
attribution: "Based on <a href='www.openstreetmap.org'>OpenStreetMap</a> / MapBox restyled by oertz :: HEIG-VD"
}
);

et ajouter l'instruction CSS suivante :

.olControlAttribution {
background-color: lightgray;
opacity: 0.6;
position: relative !important;
left: 50px;
top: 0px;
}

Ex2b : OpenLayers.Layer.Google

L'utilisation d'un cache serveur de tuiles a largement été popularisé par des services propriétaires comme Google Maps. OpenLayers étant indépendant de toute source de données, l'API offre également un type de couche pour dialoguer avec de tels services.

TODO

Reprendre le code précédent en adaptant le code JavaScript comme suit :

<script type="text/javascript" src="js/config.js"></script>
<script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
map = new OpenLayers.Map('map', {
projection: new OpenLayers.Projection("EPSG:3857"),
maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508),
});
goog = new OpenLayers.Layer.Google("Google layer",
{
type: google.maps.MapTypeId.TERRAIN,
sphericalMercator: true
}
);
map.addLayer(goog);
map.setCenter(new OpenLayers.LonLat(8, 47).transform("EPSG:4326", "EPSG:900913"), 6);
});
</script>

Modifier le paramètre de type de couche selon la documentation Google Maps API (contrairement à un service WMS, le service de Google Maps se sait pas s'auto-décrire).

Ex2c : OpenLayers.Layer.WMTS

Swisstopo offre un service de tuiles basé sur le standard WMTS et que l'on peut exploiter comme suit.

<html>
<head>
<title>Ex2c - use of Geoadmin WMTS</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript" src="js/jquery/jquery-1.9.1.js"></script>
<script type="text/javascript" src="js/proj4js/proj4js-compressed.js"></script>
<script type="text/javascript" src="js/proj4js/defs/EPSG21781.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:21781",
units: "m",
resolutions: [650, 500, 250, 100, 50, 20, 10, 5, 2.5, 2, 1.5],
});
pixelkartefarbe = new OpenLayers.Layer.WMTS({
name: "Swisstopo PK",
url: ["http://wmts0.geo.admin.ch/", "http://wmts1.geo.admin.ch/", "http://wmts.geo.admin.ch/"],
layer: "ch.swisstopo.pixelkarte-farbe",
requestEncoding: "REST",
formatSuffix: "jpeg",
matrixSet: "21781",
format: "image/jpeg",
serverResolutions: [4000, 3750, 3500, 3250, 3000, 2750, 2500, 2250, 2000, 1750, 1500, 1250, 1000, 750, 650, 500, 250, 100, 50, 20, 10, 5, 2.5, 2, 1.5, 1, 0.5],
style: "default",
maxExtent: new OpenLayers.Bounds(420000, 30000, 900000, 350000),
isBaseLayer: true,
opacity: 1.0,
dimensions: ['TIME'],
params: {'time': 20130213}
});
map.addLayer(pixelkartefarbe);
map.zoomToMaxExtent();
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Modifier le centrage comme suit :

map.setCenter(new OpenLayers.LonLat(7.658, 45.98).transform("EPSG:4326", "EPSG:21781"), 6);

Modifier le centrage comme suit :

map.setCenter(new OpenLayers.LonLat(600000, 200000), 8);

Modifier les paramètres suivant de la définition OpenLayers.Layer.WMTS :

layer: "ch.swisstopo.hiks-dufour",
...
formatSuffix: "png",
...
format: "image/png",
...
params: {'time': 18450101}

3. Style de couche "image"

Pour le moment, c'est le serveur qui fait tourner son moteur cartographique pour créer un représentation image des données géographiques. Le client peut dire son mot et le piloter “à distance”.

Ex3a : Styling avec WMS

Le standard WMS offre beaucoup de libertés, parfois au détriment de la performance. Comme il permet de demander ce que l'on veut en termes d'enveloppe géographique (paramètre bbox) et de style parmis un ensemble de styles pré-configurer, il est possible d'envoyer au serveur cartographique des instructions personnalisées de style.

<html>
<head>
<title>Ex3a - use of a custom SLD style</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
map = new OpenLayers.Map('map');
wms = new OpenLayers.Layer.WMS(
"World boundaries",
myWMS,
{
layers: 'world_simple',
format: 'image/png'
}
);
map.addLayer(wms);
map.zoomToMaxExtent();
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Créer une nouvelle application avec le code ci-dessus

Ajouter le paramètre WMS de style comme suit :

styles: "giant_polygon"

Modifier la définition OpenLayers.Layer.WMS comme suit :

wms = new OpenLayers.Layer.WMS(
"World boundaries",
myWMS,
{
format: 'image/png',
SLD: remoteSLD + '/pinkWorld.sld.xml'
}
);

Avec Firefox RESTClient (ou autre équivalent), lancer la requête POST suivante sur le serveur WMS :

<?xml version="1.0" encoding="UTF-8"?>
<DescribeFeatureType version="1.1.0" service="WFS"
xmlns="http://www.opengis.net/wfs"
xmlns:ogo="http://mediamaps.ch/ogo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<TypeName>ogo:world_simple</TypeName>
</DescribeFeatureType>

puis lancer

<?xml version="1.0" encoding="UTF-8"?>
<GetFeature version="1.1.0" service="WFS"
xmlns="http://www.opengis.net/wfs"
xmlns:ogo="http://mediamaps.ch/ogo"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<Query typeName="ogo:world_simple">
<PropertyName>ogo:REGION</PropertyName>
</Query>
</GetFeature>

Modifier pinkWorld.sld.xml en insérant l'instruction Filter suivante en début de Rule

...
<Rule>
<Filter xmlns="http://www.opengis.net/ogc">
<PropertyIsEqualTo>
<PropertyName>REGION</PropertyName>
<Literal>NorthAfrica</Literal>
</PropertyIsEqualTo>
</Filter>
<PolygonSymbolizer>
...

Ajouter la Rule suivante après la première :

...
</Rule>
<Rule>
<ElseFilter/>
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">#DDDDDD</CssParameter>
<CssParameter name="fill-opacity">1.0</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">#000000</CssParameter>
<CssParameter name="stroke-width">1</CssParameter>
</Stroke>
</PolygonSymbolizer>
</Rule>
</FeatureTypeStyle>
...

Modifier le style pour que la Suisse soit représentée comme ci-dessous.

voir la spécification Styled Layer Descriptor

et le tutoriel SLD

Ex3b : Google Styled Maps

L'API Google Maps permet de personnaliser le style des couches habituelles par des manipulations de couleurs (hue, saturation, ...) et de façon programmatique (code JavaScript).

Voir Style syntax, Feature types.

<html>
<head>
<title>Ex3b - Google Maps with StyledMapType</title>
<script type="text/javascript" src="js/config.js"></script>
<script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
// définition de notre ID de MapTypeId (comme google.maps.MapTypeId.TERRAIN)
var NIGHT_MAPTYPE_ID = 'ogo.gmaps.nightview';
// Création d'une instance de google.maps.StyledMapType basé sur une description de style "mode nuit"
var nightStyle = [
{
featureType: "road",
elementType: "geometry",
stylers: [{hue: "#00ff00"}, {saturation: 100}]
},
{
featureType: "landscape",
elementType: "geometry",
stylers: [{hue: "#000000"}, {saturation: 75}, {lightness: -100}]
}
];
var nightStyledMapType = new google.maps.StyledMapType(nightStyle);
// Création d'une OpenLayers.Layer.Google lié à notre MapTypeId
var nightGmap = new OpenLayers.Layer.Google("Google Night", {type: NIGHT_MAPTYPE_ID});
// Création de la map et ajout de notre couche nightGmap
map = new OpenLayers.Map('map');
map.addLayer(nightGmap);
// le mapObject étant instancié par map.addLayer,
// on peut associer notre description de style à notre couche nightGmap, le MapTypeId servant de lien
nightGmap.mapObject.mapTypeId = NIGHT_MAPTYPE_ID;
nightGmap.mapObject.mapTypes.set(NIGHT_MAPTYPE_ID, nightStyledMapType);
// on centre la map sur un point longitude/latitude
// on veille à le transformer vers la projection EPSG:900913 qu'utilise Google Maps v3
map.setCenter(new OpenLayers.LonLat(6, 46).transform("EPSG:4326","EPSG:900913"), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Modifier le style à votre goût afin que ce soit plus lisible

A lire : http://gis.stackexchange.com/questions/28089/how-are-google-serving-up-their-styled-maps

4. Composition de couches

Un intérêt d'une carte en ligne est de pouvoir la composer à partir de plusieurs couches.

Ex4a : Composition "server-side"

La composition peut se faire par un assemblage côté serveur, éventuellement sur demande comme ci-dessous.

<html>
<head>
<title>Ex4a - one WMS request for two layers</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
map = new OpenLayers.Map('map');
wms = new OpenLayers.Layer.WMS(
"ogo:world_simple + ogo:cities",
myWMS,
{
// les couches à assembler dans une image cartographique
layers: 'ogo:world_simple,ogo:cities',
// les styles à appliquer respectivement aux couches listées
styles: 'giant_polygon,capitals',
format: 'image/png'
}
);
map.addLayer(wms);
map.setCenter(new OpenLayers.LonLat(7, 47), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

Notez bien : plus il y a de couches disponibles sur le serveur cartographique, plus il y a de combinaisons.

TODO

Apporter la preuve que l'assemblage s'est fait côté serveur

Trouver un tel type d'assemblage parmi les Basic Map Types de l'API Google Maps,

Ex4b : Composition "client-side"

Un cadre applicatif de cartographie en ligne se doit de fournir la capacité de gérer la composition côté client. C'est le cas d'OpenLayers comme ci-dessous :

<html>
<head>
<title>Ex4b - two WMS base layers</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
map = new OpenLayers.Map('map');
map.addControl(new OpenLayers.Control.LayerSwitcher());
world = new OpenLayers.Layer.WMS(
"World admin boundaries",
myWMS,
{
layers: 'ogo:world_simple',
styles: 'giant_polygon',
format: 'image/png'
}
);
map.addLayer(world);
cities = new OpenLayers.Layer.WMS(
"World cities",
myWMS,
{
layers: 'ogo:cities',
styles: 'capitals',
format: 'image/png'
}
);
map.addLayer(cities);
map.setCenter(new OpenLayers.LonLat(7, 47), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Comment faire pour les assembler/superposer côté client ?

Modifier la définition WMS de cities avec les paramètres suivants :

{
SLD: remoteSLD + "/capitals.sld.xml",
format: 'image/png',
transparent: true
}

Exercice

TODO

Créer une application de webmapping avec la composition suivante :

une couche de base Google (ex. TERRAIN)

une superposition basée sur la couche ch.swisstopo.pixelkarte-pk25.metadata-kartenblatt servie par le serveur WMS http://wms.geo.admin.ch de Swisstopo
Illustration du résultat attendu :

Créer une autre application avec une composition différente selon une couhe de base de votre choix et une autre couche de ce serveur WMS, ou d'un autre.

5. Couche "vector"

Dans les sections précédentes nous avons inséré des couches cartographique sous la forme d'images construites par le serveur avec son moteur cartographique. Un serveur cartographique était donc nécessaire.

A l'opposé, pour une couche OpenLayers.Layer.Vector c'est le client qui joue le rôle de moteur cartographique pour créer la visualisation de données géographiques.

Ex5a : Source de données OpenLayers.Format

Une couche OpenLayers.Layer.Vector peut être alimentée par une source de données au format OpenLayers.Format.GeoJSON (voir aussi http://geojson.org). Il existe de nombreux autres formats : OpenLayers.Format.GPX, OpenLayers.Format.GML, OpenLayers.Format.KML, ...)

<html>
<head>
<title>Ex5a - GeoJSON vector overlay</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
vectorLyr = new OpenLayers.Layer.Vector("Vector layer from GeoJSON", {
protocol: new OpenLayers.Protocol.HTTP({
url: "data/4capitals.json",
format: new OpenLayers.Format.GeoJSON({ignoreExtraDims: true})
}),
strategies: [new OpenLayers.Strategy.Fixed()],
projection: new OpenLayers.Projection("EPSG:4326")
});
map.addLayer(vectorLyr);
map.setCenter(new OpenLayers.LonLat(738600,5840171),5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Apporter la preuve que les données sont bien transformées en objets graphiques !

Remplacer l'URL source par

http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson

Pourquoi cela ne fonctionne pas ? ...

Dans une div HTML, afficher la liste des descriptions des séismes

Modifier le paramètre protocol comme suit :

protocol: new OpenLayers.Protocol.HTTP({
url: "http://cafleman.phpnet.org/caf/gpx/480.gpx",
format: new OpenLayers.Format.GPX()
}),

et recentrer par ici :

map.setCenter(new OpenLayers.LonLat(750000,5840171),12);

Ex5b : Service OpenLayers.Protocol.WFS

En général, un service WMS est associé à un service WFS (Web Feature Service). Ce dernier permet d'interroger côté serveur une base de données géographiques de manière standardisée. On peut utiliser un tel service comme source de données répondant à des requêtes d'interrogation (“à la SQL”).

<html>
<head>
<title>Ex5b - WFS vector overlay</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
OpenLayers.ProxyHost = myProxy;
map = new OpenLayers.Map('map');
world = new OpenLayers.Layer.WMS(
"World admin boundaries",
myWMS,
{
layers: 'ogo:world_simple',
styles: 'giant_polygon',
format: 'image/png'
}
);
map.addLayer(world);
capitals = new OpenLayers.Layer.Vector("WFS - cities (capitals)", {
protocol: new OpenLayers.Protocol.WFS({
version: "1.1.0",
url: myWFS,
featureType: "cities",
featurePrefix: "ogo",
featureNS: "http://mediamaps.ch/ogo"
}),
strategies: [new OpenLayers.Strategy.Fixed()],
projection: new OpenLayers.Projection("EPSG:4326")
});
map.addLayer(capitals);
map.setCenter(new OpenLayers.LonLat(6,40), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Analyser le dialogue client/serveur ! Quel est le format d'échange utilisé pour la couche WFS capitals ?

Remplacer la couche WMS par une couche OSM comme suit :

osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);

et comme le SRS de cette couche est en projection Mercator (EPSG:900913), ajustez le centrage comme suit :

map.setCenter(new OpenLayers.LonLat(6,40).transform("EPSG:4326","EPSG:900913"), 5);

Quelle est le SRS des coordonnées reçues du serveur WFS ?

Remplacer la couche WFS par la définition suivante :

capitals = new OpenLayers.Layer.Vector("WFS - cities (capitals)", {
protocol: new OpenLayers.Protocol.WFS({
version: "1.1.0",
url: "http://ogo.heig-vd.ch/geoserver/wfs",
featureType: "cities",
featurePrefix: "ogo",
featureNS: "http://mediamaps.ch/ogo",
srsName: "EPSG:900913"
}),
strategies: [new OpenLayers.Strategy.Fixed()],
projection: new OpenLayers.Projection("EPSG:900913")
});

Quelle est la différence ?

Remplacer la définition strategies comme suit :

strategies: [new OpenLayers.Strategy.BBOX()],

Analysez le trafic client/serveur. Qu'en déduisez-vous par rapport à précédemment ?

Avant l'instanciation OpenLayers.Layer.Vector, préparer le filtre d'interrogation :

filter = new OpenLayers.Filter.Comparison({
type: OpenLayers.Filter.Comparison.EQUAL_TO,
property: "geo_region",
value: "Africa"
});

et ajouter ce filtre à la définition OpenLayers.Protocol.WFS avec le paramètre suivant :

defaultFilter: filter

Ex5c : OpenLayer.Layer.Vector sous la loupe

L'exemple ci-dessous décrit en détail ce qu'est une couche OpenLayer.Layer.Vector en montrant comment la créer et l'alimenter à partir d'un format de source de données “fait maison”.

En résumé, une couche OpenLayer.Layer.Vector est composée d'entités géographiques (OpenLayers.Feature.Vector) chacune composée d'une géométrie (ex. OpenLayers.Geometry.Point) et d'une liste d'attributs.

La source de données est un fichier texte formaté comme suit:

2.3332999999999999 48.866700000000002 Paris,7.4333 46.950000000000003 Bern,12.5 41.883299999999998 Rome,-3.71 40.409999999999997 Madrid

La structure de formatage est simple : c'est une suite de triplets “Longitude Latitude Nom” dont le séparateur interne est l'espace. Le séparateur entre triplets est la virgule. Chaque triplet correspond à une entité géographique.

Le code ci-dessous déchiffre cette structure et transforme les triplets en OpenLayers.Feature.Vector.

<html>
<head>
<title>Ex5c - custom format for vector overlay</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
$.ajax({
type: "GET",
url: "data/4capitals.txt",
dataType: "text",
success: createCustomOverlay
});
map.setCenter(new OpenLayers.LonLat(6, 46).transform("EPSG:4326", "EPSG:3857"), 5);
});
function createCustomOverlay(response) {
// Créer/ajouter une nouvelle couche vide
vectors = new OpenLayers.Layer.Vector("Custom vector overlay");
map.addLayer(vectors);
// Préparer un tableau pour accueillir des entités géographiques (des "features")
features = new Array();
// On analyse et décompose notre flux géographique encodé "maison"
tabCapitals = response.split(",");
for(i = 0; i < tabCapitals.length; i++){
tabCapital = tabCapitals[i].split(" ");
// on construit et ajoute au tableau une feature composé d'une géométrie (ici Point)
ptGeom = new OpenLayers.Geometry.Point(tabCapital[0],tabCapital[1]);
ptGeom = ptGeom.transform("EPSG:4326", "EPSG:900913");
// que le constructeur utilise pour alimenter la propriété feature.geometry
features[i] = new OpenLayers.Feature.Vector(ptGeom);
// et d'un tableau asssociatif feature.attributes pour chaque attribut à y associer (ici un seul, nom)
features[i].attributes["nom"] = tabCapital[2];
}
// le tableau est ajouté à la couche pour afficher les entités
vectors.addFeatures(features);
}
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Vérifier bien que l'attribut nom est en mémoire pour chaque entité !

Adapter le code pour une source de données similaire contenant un attribut de plus comme suit :

2.3332999999999999 48.866700000000002 Paris France,7.4333 46.950000000000003 Bern Germany,12.5 41.883299999999998 Rome Italy,-3.71 40.409999999999997 Madrid Spain

et revérifier que tout est en mémoire.

6. Style "vector"

OpenLayers met à disposition un Styling Framework permettant de configurer le moteur de rendu cartographique à appliquer sur une couche OpenLayers.Layer.Vector. Il offre de nombreux paramètres de style (voir Symbolizer properties).

Ex6a : Style d'entité

OpenLayers permet d'associer directement un style à une entité géographique OpenLayers.Feature.Vector.

<html>
<head>
<title>Ex6a - One feature, one style</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
vectorLyr = new OpenLayers.Layer.Vector("Vector layer");
map.addLayer(vectorLyr);
feature = new OpenLayers.Feature.Vector();
feature.geometry = new OpenLayers.Geometry.Point(738600, 5840171);
feature.attributes = {
name: "The precious is here!",
author: "Gollum"
};
feature.style = {
graphicName: 'cross',
pointRadius: 10,
fillColor: '#ff0000',
fillOpacity: 0.8,
strokeColor: '#000000',
strokeWidth: 1
};
vectorLyr.addFeatures([feature]);
map.setCenter(new OpenLayers.LonLat(738600, 5840171), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Remplacer le style avec les propriétés suivantes :

{
externalGraphic: "http://www.cretasolaris.gr/gfx/marker.png",
graphicWidth: 36,
graphicHeight: 33,
graphicOpacity: 1
}

Ex6b : Style de couche

L'approche la plus répandue consiste à associer un style à une couche. Ce style est alors composé de règle cartographique et il est alors possible de configurer la représentation en contexte des données attributaires.

<html>
<head>
<title>Ex6b - One layer, one style</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
ptSymbolizer = new OpenLayers.Symbolizer.Point({
externalGraphic: "http://www.cretasolaris.gr/gfx/marker.png",
graphicWidth: 36,
graphicHeight: 33,
graphicOpacity: 1
});
vectorLyr = new OpenLayers.Layer.Vector("Vector layer", {
styleMap: new OpenLayers.Style(ptSymbolizer)
});
map.addLayer(vectorLyr);
feature = new OpenLayers.Feature.Vector();
feature.geometry = new OpenLayers.Geometry.Point(738600, 5840171);
feature.attributes = {
name: "The precious is here!",
author: "Gollum"
};
vectorLyr.addFeatures([feature]);
map.setCenter(new OpenLayers.LonLat(738600, 5840171), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Ajouter les propriétés de style suivantes (elles exploitent le contexte par une syntaxe de remplacement attributaire) :

label: "${name}, ${author}",
labelYOffset: -30,
fontColor: "red",
fontSize: "20px"

Ajouter une entité géographique de plus comme suit :

feature = new OpenLayers.Feature.Vector();
feature.geometry = new OpenLayers.Geometry.Point(1938600, 6840171);
feature.attributes = {
name: "The hobbits are far away...",
author: "Gandalf"
};
vectorLyr.addFeatures([feature]);

Ex6c : Style avancé : fonction de contexte

OpenLayers offre un outil puissant de rendu capable d'adapter une valeur de propriété de style selon le contexte de l'entité (géographique et attributaire). Il se base sur la syntaxe de remplacement attributaire vu ci-dessus.

<html>
<head>
<title>Ex6c - styling context function</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
OpenLayers.ProxyHost = myProxy;
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
ctxCategorize = {
getColor: function(feature) {
switch (feature.attributes["geo_region"]) {
case "Europe": noClass = 0; break;
case "Africa": noClass = 1; break;
default: noClass = 2;
}
colors = ["#ff0000", "#00ff00", "#0000ff"];
return colors[noClass];
}
};
ptSymbolizer = new OpenLayers.Symbolizer.Point({
graphicName: 'circle',
pointRadius: 5,
fillColor: '${getColor}'
});
capitals = new OpenLayers.Layer.Vector("WFS - cities (capitals)", {
protocol: new OpenLayers.Protocol.WFS({
url: myWFS,
featureType: "cities",
featurePrefix: "ogo",
featureNS: "http://mediamaps.ch/ogo",
srsName: "EPSG:900913"
}),
strategies: [new OpenLayers.Strategy.Fixed()],
styleMap: new OpenLayers.Style(ptSymbolizer, {context: ctxCategorize})
});
map.addLayer(capitals);
map.setCenter(new OpenLayers.LonLat(0, 0), 3);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Créer une application similaire exploitant l'attribut capit_1_0 pour avoir une représentation cartographique des villes permettant de distinguer les capitales (valeur=1) des autre villes selon un style à votre goût.

Créer une application similaire qui représente les villes de l'hémisphère Sud différemment de celles de l'hémisphère Nord.

Exercices "de style"

TODO

Créer une application exploitant le flux http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson avec un style basé sur une fonction de contexte. Par exemple, une représentation par classification de l'intensité des séismes (attribut mag).

7. Interactions et contrôles

OpenLayers offre un ensemble de fonctionnalités permettant d'interagir avec la carte. Par défaut, les interactions de zoom et pan sont actives, et nous avons entre aperçu le contrôle OpenLayers.Control.LayerSwitcher. Il existe de nombreux autres OpenLayers.Control (voir aussi http://docs.openlayers.org/library/controls.html).

En plus de la gestion événementielle offerte par ces contrôleurs, OpenLayers permet d'enregistrer des écouteurs sur de nombreux événements, comme ceux en lien avec la vie d'un OpenLayers.Map (voir OpenLayers.Map.events, OpenLayers.Layer.Vector.events).

Voir aussi un autre workshop sur le sujet : http://softlibre.gloobe.org/openlayers/workshop/introduction/module3.

Ex7a : un premier exemple pour piloter ces outils d'interactions.

<html>
<head>
<title>Ex7a - controlling controls</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
var map = new OpenLayers.Map('map', {controls: []});
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
if (!map.getCenter()) map.setCenter(new OpenLayers.LonLat(8, 47).transform("EPSG:4326", "EPSG:900913"), 6);
});
</script>
<style type="text/css">
#map {
width: 1000px;
height: 800px;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Que constatez-vous avec ce code ?

Ajouter après l'instanciation de OpenLayers.Map les contrôles suivants :

map.addControl(new OpenLayers.Control.Zoom());
map.addControl(new OpenLayers.Control.Navigation());

Ajouter le contrôle ci-dessous :

map.addControl(new OpenLayers.Control.Permalink());

Ajouter le contrôle ci-dessous :

map.addControl(new OpenLayers.Control.Permalink({
div: document.getElementById("permalink")
}));

et le HTML suivant :

<div id="permalink"></div>

Expérimenter encore les contrôles : ScaleLine, OverviewMap et PanZoomBar

Ex7b : mise en place d'un OpenLayers.Control.Panel

OpenLayers offre des facilités pour créer ses propres boutons de contrôles permettant par exemple de contrôler l'activation/désactivation de fonctionnalités.

Il existe trois types de Button :

OpenLayers.Control.TYPE_BUTTON : un simple bouton avec fonction de trigger

OpenLayers.Control.TYPE_TOGGLE : équivalent au checkbox HTML avec les events activate/deactivate

OpenLayers.Control.TYPE_TOOL : équivalent au radio button HTML avec les events activate/deactivate (l'activation de l'un désactive l'autre)

L'exemple ci-dessous contrôle l'activation de OpenLayers.Control.MousePosition.

<html>
<head>
<title>Ex7b - button control activation </title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
map.setCenter(new OpenLayers.LonLat(8, 47).transform("EPSG:4326", "EPSG:900913"), 6);
pnl = document.getElementById("pnl");
mp = new OpenLayers.Control.MousePosition({div: coord, prefix: "Current mouse Position: "});
map.addControl(mp);
mp.activate();
btn1 = new OpenLayers.Control.Button({
displayClass: 'MyButton1',
type: OpenLayers.Control.TYPE_TOGGLE,
eventListeners: {
activate: function() { mp.activate(); },
deactivate: function() { mp.deactivate(); }
}
});
pnl = document.getElementById("pnl");
panel = new OpenLayers.Control.Panel({div: pnl, defaultControl: btn1});
panel.addControls([btn1]);
map.addControl(panel);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
.MyButton1ItemInactive, .MyButton1ItemActive {
background-image: url("http://img855.imageshack.us/img855/7876/dhit.jpg");
border: 1px solid black;
width: 27px; height: 27px;
}
.MyButton1ItemActive {
border-color: red;
}
#coord {
font-size: small;
}
</style>
</head>
<body>
<div id="pnl"></div>
<div id="coord"></div>
<div id="map"></div>
</body>
</html>

TODO

Ajouter un bouton de type OpenLayers.Control.BUTTON comme ci-dessous :

btn2 = new OpenLayers.Control.Button({
displayClass: 'MyButton2',
type: OpenLayers.Control.BUTTON,
trigger: function(){
alert("Centre de la carte sont en " + map.getProjection() + " : " + map.getCenter());
}
});

en l'accompagnant de son bout de CSS suivant :

.MyButton2ItemInactive {
background-image: url("http://www.geomreze.rgz.gov.rs/gmm/fugue_sm_readcord.png");
width: 32px; height: 32px;
}

Créer une application exploitant un panel avec deux boutons de type OpenLayers.Control.TOOL qui permettent de switcher entre la vue Google TERRAIN et la vue Google SATELLITE (utilisez deux icônes appropriées).

Ex7c : contrôle d'interrogation en mode image (WMS)

Les deux exemples suivants illustrent l'interrogation des objets de la carte par une interaction utilisateur, par exemple au clic. D'abord, on illustre l'interrogation d'un pixel de la carte avec OpenLayers.Control.WMSGetFeatureInfo.

<html>
<head>
<title>Ex7c - interaction with WMS GetFeatureInfo control</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function() {
OpenLayers.ProxyHost = myProxy;
map = new OpenLayers.Map('map');
wms = new OpenLayers.Layer.WMS("OGO WMS",
myWMS,
{
layers: 'ogo:world_simple',
format: 'image/png'
}
);
map.addLayer(wms);
map.zoomToMaxExtent();
info = new OpenLayers.Control.WMSGetFeatureInfo({
url: myWMS,
title: 'Identify features by clicking',
queryVisible: true,
infoFormat: 'text/html'
});
map.addControl(info);
info.events.register('getfeatureinfo', info, onGetFeatureInfo);
info.activate();
});
function onGetFeatureInfo(event) {
for (i = 0; i < map.popups.length; i++) map.removePopup(map.popups[i]);
map.addPopup(new OpenLayers.Popup.FramedCloud("popup",
map.getLonLatFromPixel(event.xy),
null, event.text, null, true
)
);
}
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Modidier le paramètre layers de OpenLayers.Layer.WMS comme suit :

layers: 'ogo:world_simple,ogo:cities',

Ex7d : contrôle d'interrogation en mode vector

Le contrôle OpenLayers.Control.SelectFeature offre des facilités pour interroger un objet d'une couche OpenLayers.Layer.Vector.

<html>
<head>
<title>Ex7d - vector overlay interaction with popup</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
OpenLayers.ProxyHost = myProxy;
map = new OpenLayers.Map('map');
map.addControl(new OpenLayers.Control.LayerSwitcher());
world = new OpenLayers.Layer.WMS("World admin boundaries", myWMS,
{
layers: 'ogo:world_simple',
styles: 'giant_polygon',
format: 'image/png'
}
);
map.addLayer(world);
cities = new OpenLayers.Layer.WMS("Cities", myWMS,
{
layers: 'ogo:cities',
format: 'image/png',
transparent: true
}
);
map.addLayer(cities);
var defaultPoint = new OpenLayers.Symbolizer.Point({
graphicName: 'square',
pointRadius: 6,
fillColor: '#ffff00',
fillOpacity: 0,
stroke: 0
});
var selectPoint = defaultPoint.clone();
selectPoint.fillOpacity = 0.8;
capitals = new OpenLayers.Layer.Vector("WFS - cities (capitals)", {
strategies: [new OpenLayers.Strategy.BBOX()],
protocol: new OpenLayers.Protocol.WFS({
url: myWFS,
featureType: "cities",
featurePrefix: "ogo",
featureNS: "http://mediamaps.ch/ogo"
}),
styleMap: new OpenLayers.StyleMap({
"default": new OpenLayers.Style(defaultPoint),
"select": new OpenLayers.Style(selectPoint)
})
});
map.addLayer(capitals);
selectControl = new OpenLayers.Control.SelectFeature(capitals, {hover:true});
map.addControl(selectControl);
selectControl.activate();
capitals.events.register("featureselected", capitals, onFeatureSelect);
capitals.events.register("featureunselected", capitals, onFeatureUnselect);
map.setCenter(new OpenLayers.LonLat(7, 47), 5);
});
function onPopupClose(evt) {
// 'this' is the popup.
selectControl.unselect(this.feature);
}
function onFeatureSelect(evt) {
feature = evt.feature;
popup = new OpenLayers.Popup.FramedCloud("featurePopup",
feature.geometry.getBounds().getCenterLonLat(),
new OpenLayers.Size(100,100),
"<h2>" + feature.attributes.wup_aggl + "</h2>" +
feature.attributes.cntry_name,
null,
true,
onPopupClose
);
feature.popup = popup;
popup.feature = feature;
map.addPopup(popup);
}
function onFeatureUnselect(evt) {
feature = evt.feature;
if (feature.popup) {
popup.feature = null;
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}
}
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

A quoi sert la Layer.Vector si son style rend ses objets invisibles (fillOpacity: 0, stroke: 0) ?

Ajouter les deux paramètres suivant au style defaultPoint :

label: "${wup_aggl}",
labelYOffset: 12

Enlever ces deux derniers paramètres; mettre en commentaire l'instruction Filter de la description de style capitals.sld.xml

\\; ajouter le paramètre suivant à la définition de la layer WMS cities :

SLD: remoteSLD + "/capitals.sld.xml",

Ex7e : des outils de dessin

OpenLayers.Control.DrawFeature est un contrôle fort utile pour les WebGIS et les applications de PPGIS (crowdsourcing) permettant à l'utilisateur de créer un objet géographique sur la carte.

L'exemple ci-dessous illustre cela pour la saisie d'une ligne (OpenLayers.Handler.Path), mais il est bien sûr possible de créer des points, des polygones, des polygones réguliers.

<html>
<head>
<title>Ex7e - draw line feature</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function(){
var map = new OpenLayers.Map('map');
osm = new OpenLayers.Layer.OSM("Simple OSM layer");
map.addLayer(osm);
lnSymbolizerDef = new OpenLayers.Symbolizer.Line({
strokeWidth: 3,
strokeColor: "#ff0000"
});
lnSymbolizerTmp = lnSymbolizerDef.clone();
lnSymbolizerTmp.strokeDashstyle = "dash";
drawLayer = new OpenLayers.Layer.Vector("Draw layer", {
styleMap: new OpenLayers.StyleMap({
"default": new OpenLayers.Style(lnSymbolizerDef),
"temporary": new OpenLayers.Style(lnSymbolizerTmp)
})
});
map.addLayer(drawLayer);
drawControl = new OpenLayers.Control.DrawFeature(drawLayer, OpenLayers.Handler.Path);
map.addControl(drawControl);
drawControl.activate();
map.setCenter(new OpenLayers.LonLat(738600,5880171),10); // vers le Lausanne
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Ajouter après l'activation du contrôle ce qui suit :

drawControl.events.register('featureadded', drawControl, function(evt){
alert("Vraiment une bien belle ligne !");
});

Remplacer le message d'alert par ceci :

nbPts = evt.feature.geometry.getVertices().length;
alert("Une belle ligne composée de " + nbPts + " points !");

Créer une application permettant de calculer la surface d'une zone dessinée par l'utilisateur.