Workshop OpenLayers v2 ... la suite

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 et la section 8).

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.

7. Feature access and controls

A présent que les techniques de visualisation cartographique sont maîtrisées, tant côté serveur que client, il s'agit de découvrir comment interagir avec les objets géographiques représentés.

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

Cet exemple illustre l'interrogation des objets de la carte par le simple clic d'un pixel sur la carte. On utilise le contrôle OpenLayers.Control.WMSGetFeatureInfo qui permet d'enregistrer un écouteur sur l'événement onGetFeatureInfo.

<html>
<head>
<title>Ex7A - 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("World boundaries", myWMS, {
layers: 'public.world_simple',
format: 'image/png'
}, { singleTile: true }
);
map.addLayer(wms);
map.zoomToMaxExtent();
info = new OpenLayers.Control.WMSGetFeatureInfo({
url: myWMS,
infoFormat: 'text/plain'
});
map.addControl(info);
info.events.register('getfeatureinfo', info, onGetFeatureInfo);
info.activate();
});
function onGetFeatureInfo(event) {
$("#info").html(event.text.replace(":", "<br>"));
}
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
#info {
position: absolute;
top: 20px;
left: 50px;
background-color: white;
border: solid gray 1px;
padding: 5px;
font-size: smaller;
z-index: 1000000000;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="info">Click on the map to get feature info</div>
</body>
</html>

TODO

Analyser l'échange client/serveur (ex. avec Firebug). Comment est-ce possible ?

Ajouter des paramètres dits “vendor” comme ci-dessous. Quel est l'effet ?

info = new OpenLayers.Control.WMSGetFeatureInfo({
url: myWMS,
infoFormat: 'text/plain',
vendorParams: { radius: 5 }
});

Ex7B : un contrôle d'interrogation image "fait maison"

Dans Ex7A, c'est une version standardisé du contrôle d'interrogation lié au standard OGC WMS. L'exemple suivant montre comment mettre cela en oeuvre par nous-même. On s'assure ainsi de maîtriser tout le processus client/serveur.

Côté client :

<html>
<head>
<title>Ex7B - homemade GetFeatureInfo</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: 'public.world_simple',
format: 'image/png'
}, { singleTile: true }
);
map.addLayer(wms);
map.zoomToMaxExtent();
map.events.register('click', map, getFeatureInfo);
});
function getFeatureInfo(e) {
// Conversion du graphique (Pixel) au géographique (LonLat)
px = new OpenLayers.Pixel(e.xy.x,e.xy.y);
xy = map.getLonLatFromPixel(e.xy);
// On forme et lance la requête en veillant à enregistrer la function de callback
getInfo = "GetCountriesByXY.php?x=" + xy.lon + "&y=" + xy.lat;
$.ajax({
type: "GET",
url: getInfo,
success: function(response){
$("#info").html(response.features[0].properties.name);
}
});
}
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
#info {
position: absolute;
top: 20px;
left: 50px;
background-color: white;
border: solid gray 1px;
padding: 5px;
font-size: smaller;
z-index: 1000000000;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="info">Click on the map to get feature info</div>
</body>
</html>

Côté serveur : GetCountriesByXY.php

<?php
require_once 'GeoManager.php';
$XY = $_GET["x"] . " " . $_GET["y"];
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$api_key = "2e957be5f1e9c0793ca1725aab08274a795f2c3f";
$q = "SELECT name, ST_AsGeoJSON(the_geom) AS geom FROM world_simple"
. " WHERE ST_Intersects(the_geom, ST_GeomFromText('POINT(" . $XY . ")', 4326))";
$url = "http://oertz.cartodb.com/api/v2/sql?api_key=" . $api_key. "&q=" . urlencode($q);
curl_setopt($ch, CURLOPT_URL, $url);
$result = curl_exec($ch);
$rows = json_decode($result)->rows;
header("Content-type: application/json");
$i = 0;
$fc = new FeatureCollection();
foreach ($rows as $row) {
$fc->addFeature(new Feature($i++, json_decode($row->geom), array("name" => $row->name)));
}
echo json_encode($fc);
?>

TODO

Côté client, ajouter la ligne ci-dessous au bon endroit :

$("#info").html("Loading... please wait...");

Ajouter en fin de fonction ready une couche OpenLayers.Layer.Vector comme suit :

vectors = new OpenLayers.Layer.Vector("FeatureInfo overlay");
map.addLayer(vectors);

et remplacer l'appel AJAX comme suit :

$.ajax({
type: "GET",
url: getInfo,
dataType: "text",
success: function(response){
json = new OpenLayers.Format.GeoJSON();
fc = json.read(response);
$("#info").html(fc[0].attributes.name);
vectors.addFeatures(fc);
}
});

Ajouter en fin de fonction ready le contrôle suivant avec son écouteur :

selectControl = new OpenLayers.Control.SelectFeature(vectors, {hover:true});
map.addControl(selectControl);
selectControl.activate();
vectors.events.register("featureselected", vectors, function(e){
$("#info").html(e.feature.attributes["name"]);
});

Enregistrer un écouteur sur le bon événement pour vider la <div> info quand le curseur sort d'un feature.

Ex7C : stratégies de chargement en mode vector

Le chargement d'entités géographiques peut s'avérer néfaste en terme d'expérience utilisateur du fait d'une latence provoquée (1) par le volume important de données à charger pour une bande passante donnée et (2) par les performances et l'aptitude du moteur de rendu cartographique à digérer ces données.

L'exemple suivant montre déjà des limites avec la stratégie OpenLayers.Strategy.Fixed() qui charge simplement tous les objets dans l'enveloppe de visualisation. Il s'agit alors d'opter pour tes stratégies pour éviter ces écueils.

<html>
<head>
<title>Ex7C - Vector overlay loading strategies</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map, lyr;
$(document).ready(function() {
OpenLayers.ProxyHost = myProxy;
map = new OpenLayers.Map('map');
map.addControl(new OpenLayers.Control.LayerSwitcher());
osm = new OpenLayers.Layer.OSM("Simple OSM Map");
map.addLayer(osm);
lyr = new OpenLayers.Layer.Vector("WFS - cities (capitals)", {
protocol: new OpenLayers.Protocol.WFS({
version: "1.0.0",
url: myWFS,
featureType: "cities"
}),
strategies: [new OpenLayers.Strategy.Fixed()],
projection: new OpenLayers.Projection("EPSG:4326")
});
map.addLayer(lyr);
map.setCenter(new OpenLayers.LonLat(8, 47).transform("EPSG:4326", "EPSG:3857"), 5);
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>

TODO

Compter les appels au serveur géographique et vérifier le(s) volume(s) des entités géographiques transférées.

Faire de même avec le chargement de la couche world_simple en modifiant le paramètre ci-dessous :

featureType: "world_simple"

Modifier la stratégie comme suit :

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

et tout en naviguant dans la carte, compter les appels au serveur géographique et revérifier le(s) volume(s) des entités géographiques transférées.

Rendre la couche vector invisible au chargement de l'application en insérant l'instruction comme indiqué ci-dessous :

...
lyr.setVisibility(false);
map.addLayer(lyr);
...

de plus, ajouter les instructions ci-dessous en fin de fonction ready :

...
map.myZoom = map.zoomTo;
map.zoomTo = function(zoom, xy) {
lyr.setVisibility(zoom >= 6 ? true : false);
map.myZoom(zoom, xy);
};

Quelles autres stratégies peut-on imaginer pour garder une bonne expérience utilisateur ?

Ex7D : contrôles en mode vector

Définir les symbolizers defaultPoint et selectPoint suivant :

defaultPoint = new OpenLayers.Symbolizer.Point({
graphicName: 'circle',
pointRadius: 6,
fillColor: '#0000ff',
fillOpacity: 0.8
});
selectPoint = defaultPoint.clone();
selectPoint.fillColor = '#ff0000';

puis les connecter en ajoutant un styleMap sur la couche Vector comme suit :

...
styleMap: new OpenLayers.StyleMap({
"default": new OpenLayers.Style(defaultPoint),
"select": new OpenLayers.Style(selectPoint)
})
...

enfin ajouter et activer un contrôle SelectFeature sur cette couche Vector comme suit :

selectControl = new OpenLayers.Control.SelectFeature(lyr, {hover:true});
map.addControl(selectControl);
selectControl.activate();

Remarque : dans l'exemple sur les outils d'édition (dessin) on découvre la 3e clé de style temporary qui s'applique le temps de l'édition d'un objet.

Autres interactions et contrôles

Un autre exemple pour piloter ces outils d'interactions

Mise en place d'un OpenLayers.Control.Panel

Des outils de dessin