Workshop OpenLayers (2017, part 2)

7. Contrôles et interactions

OpenLayers offre un ensemble de fonctionnalités permettant d'interagir avec la carte. Par défaut, les interactions de zoom et pan sont actives mais il en existe d'autres (ol.control). En plus de la gestion événementielle offerte par ces contrôleurs, OpenLayers permet d'enregistrer des écouteurs sur de nombreux événements. Maintenant 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 la carte, ses couches et les objets géographiques représentés.

Ex7A : découverte de la gestion événementielle cartographique

Cet exemple démontre qu'il est nécessaire qu'un gestionnaire s'occupe spécifiquement d'évenements cartographiques comme ceux en lien avec la vie d'un ol.Map (voir ol.MapBrowserEvents) ou ceux relatifs à la vie d'une ol.layer.Vector, d'une ol.source.Vector, etc.

On reprend ici l'exemple 5A :

<html>
<head>
<title>ol3 - Ex7A - ol.ObjectEvent</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map, vectorLayer;
$(document).ready(function () {
map = new ol.Map({
view: new ol.View({
center: [0, 0],
zoom: 2
}),
target: 'map',
layers: [
new ol.layer.Tile({
opacity: 0.5, // !!
source: new ol.source.OSM()
})
]
});
vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson",
format: new ol.format.GeoJSON()
})
});
map.addLayer(vectorLayer);
$(document).keypress(function () {
var fts = vectorLayer.getSource().getFeatures();
$("#info").html("");
for (var i = 0; i < fts.length; i++) {
$("#info").append($("<p>").html(fts[i].get("title")));
}
});
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
#info {
position: absolute;
top: 15px;
left: 60px;
background-color: #fff;
padding: 5px;
font-size: small;
}
p {
margin: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="info"></div>
</body>
</html>

TODO

On souhaite supprimer l'interaction imposée à l'utilisateur (keypress) pour afficher la liste des descriptions. Comment faire ? Qu'est-ce qui ne marche justement pas ?

Remplacer le callback keypress par ce qui suit :

// Set listener on addfeature (ol.source.VectorEvent)
vectorLayer.getSource().on("addfeature", function (e) {
$("#info").append($("<p>").html(e.feature.get("title")));
});

De nombreuses propriétés des classes OpenLayers sont “observables”, comme celles de la classe ol.View. Ajouter à l'exemple ce qui suit :

// Set listener on change:center (ol.ObjectEvent)
map.getView().on('change:center', function (e) {
$("#info").html("Center of the map: " + e.target.getCenter());
});

Adapter l'exemple pour que soit automatiquement utilisé comme baselayer une couche Bingmaps dès que le niveau de zoom passe à une échelle plus grande que le niveau 6 (à la http://urbangene.heig-vd.ch).

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

Le gestionnaire d'événements cartographiques va notamment servir à déployer la dernière fonctionnalité de base d'une application de webmapping, celle qui consiste à interroger les objets de la carte.

Cet exemple illustre l'interrogation des objets de la carte par le simple clic d'un pixel sur la carte. On enregistre un écouteur sur la map à l'aide de la méthode ol.Map.on afin d'exécuter du code lors d'un click sur la map. On utilise ensuite la méthode ol.source.ImageWMS.getGetFeatureInfoUrl pour construire l'URL qui permet d'interroger le WMS. Enfin, la fonction getFeatureInfo(url) permet d'envoyer une requête au serveur WMS à l'aide de l'URL précédemment construite.

<html>
<head>
<title>ol3 - Ex7B - interaction with WMS GetFeatureInfo control</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map, wmsLayer;
$(document).ready(function () {
map = new ol.Map({
target: 'map',
view: new ol.View({
center: ol.proj.transform([6.7, 46.7], 'EPSG:4326', 'EPSG:3857'),
zoom: 4
})
});
var wmsLayer = new ol.layer.Tile({
source: new ol.source.TileWMS({
url: myOWS,
params: {
VERSION: "1.0.0",
LAYERS: "worldadm",
FORMAT: "image/png"
}
})
});
map.addLayer(wmsLayer);
map.on('singleclick', function (evt) {
var wmsSource = wmsLayer.getSource();
var url = wmsSource.getGetFeatureInfoUrl(
evt.coordinate,
map.getView().getResolution(),
"EPSG:3857",
{
INFO_FORMAT: "text/html"
}
);
if (url) getFeatureInfo(url);
});
function getFeatureInfo(url) {
var url2 = encodeURIComponent(url);
var request = $.ajax({
url: "php/proxy2.php?url=" + url2, // oui, on a besoin d'un proxy !
dataType: "text"
});
request.done(function (data) {
$("#info").html(data);
});
request.fail(function (jqXHR, textStatus) {
alert("Request failed: " + textStatus);
});
}
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
#info {
position: absolute;
top: 20px;
left: 60px;
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

Modifier encore une fois INFO_FORMAT comme suit :

INFO_FORMAT: "application/json",

que se passe-t-il à l'interrogation de la carte ? Regarder le flux client/serveur ... et exploitez-le pour n'afficher que le nom de pays.

Remplacer la fonction getFeatureInfo par celle-ci :

function getFeatureInfo(url) {
var url2 = encodeURIComponent(url);
var request = $.ajax({
url: "php/proxy2.php?url=" + url2,
dataType: "text"
});
request.done(function (data) {
decoder = new ol.format.GeoJSON();
features = decoder.readFeatures(data, {
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857"
});
//console.log(features);
if (features.length > 0) {
sldUrl = "http://www.mediamaps.ch/oltuto/php/GetWorldSLD.php?ID=" + features[0].get("GMI_CNTRY");
//console.log(sldUrl);
wmsLayer.getSource().updateParams({
VERSION: "1.1.1",
FORMAT: 'image/png',
SLD: sldUrl
});
$("#info").html(features[0].get("NAME"));
}
});
request.fail(function (jqXHR, textStatus) {
alert("Request failed: " + textStatus);
});
}

Encore une autre solution pour faire un “highlight” de l'entité, en deux étapes :

ajouter une couche vecteur :

vectors = new ol.layer.Vector({
source: new ol.source.Vector()
});
map.addLayer(vectors);

remplaçer le callback request.done comme suit

request.done(function (data) {
decoder = new ol.format.GeoJSON();
features = decoder.readFeatures(data, {
dataProjection: "EPSG:4326",
featureProjection: "EPSG:3857"
});
//console.log(features);
if (features.length > 0) {
vectors.getSource().clear();
vectors.getSource().addFeatures(features);
$("#info").html(features[0].get("NAME"));
}
});

Ex7C : Interaction sur entités vectors

Après les utilisations possibles pour interagir avec les entités représentées par les services cartographiques, voyons comme interagir directement avec les entités vectors “vivantes” client-side. On tulise pour cela le concept d'ol.interaction, notamment ol.interaction.Select.

<html>
<head>
<title>ol3 - Ex7C - Interaction sur entités vectors</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function () {
map = new ol.Map({
view: new ol.View({
center: [738600, 5840171],
zoom: 3
}),
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
]
});
vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson",
format: new ol.format.GeoJSON()
})
});
map.addLayer(vectorLayer);
var selectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
layers: [vectorLayer] // the layers on which the selection is possible
});
map.addInteraction(selectInteraction);
selectInteraction.on('select', function (e) {
if (e.target.getFeatures().getLength() > 0) {
//console.log(e.target.getFeatures().item(0).getKeys());
var title = e.target.getFeatures().item(0).get("title");
$("#info").html(title);
}
});
});
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
#info {
position: absolute;
top: 20px;
left: 60px;
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">Select on the map to get feature info</div>
</body>
</html>

TODO

Utiliser l'ol.events.condition qui va bien pour avoir le comportement type “on mouse over”. Sans oublier de mettre l'IHM en cohérence.

Configurer l'interaction de sorte que l'utilisateur puisse aussi cliquer sur la bulle d'information pour aller à la page des détails du séisme (cf propriété url, ex. http://earthquake.usgs.gov/earthquakes/eventpage/nc72722700)

Ex7D : stratégies de chargement vector

Le chargement en mode vector d'entités géographiques ouvre des possibilités intéressantes en terme d'interaction utilisateur au point d'être tenté de toujours tout charger dans le navigateur. Une telle approche peut s'avérer piégeuse et 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. Des stratégies de chargement peuvent alors s'avérer utiles afin d'éviter des écueils.

Avant d'aller plus loin, charger le script proxy1.php dans le dossier php du projet : https://github.com/camel113/geoinf15-ol3/blob/tutoOL3.9.0/php/proxy1.php.

L'exemple ci-dessous utilise la stratégie ol.loadingstrategy.all qui charge simplement tous les objets d'une couche donnée (autrement dit c'est la stratégie “no strategy” !).

<html>
<head>
<title>Ex7D - Vector overlay loading strategies</title>
<script type="text/javascript" src="js/config.js"></script>
<script type="text/javascript">
var map;
$(document).ready(function () {
map = new ol.Map({
target: 'map',
view: new ol.View({
center: ol.proj.transform([8, 47], "EPSG:4326", "EPSG:3857"),
zoom: 5
})
});
var osmLayer = new ol.layer.Tile({
source: new ol.source.OSM(),
opacity: 0.3
});
map.addLayer(osmLayer);
var vectorSource = new ol.source.Vector({
loader: function (extent, resolution, projection) {
// Web Feature Service parameters
url = unWFS + "?" + $.param({
service: 'WFS',
version: '1.1.0',
request: 'GetFeature',
typename: 'Cities_esri',
srsname: 'EPSG:3857'
});
// Pass it through the proxy
$.ajax({
url: "php/proxy1.php?url=" + url,
dataType: 'xml',
success: function (response) {
var wfsFormat = new ol.format.WFS();
vectorSource.addFeatures(wfsFormat.readFeatures(response));
},
error: function (jqXHR, textStatus, errorThrown) {
alert("The request has failed: " + textStatus + errorThrown);
}
});
},
strategy: ol.loadingstrategy.all,
projection: "EPSG:3857"
});
var lyr = new ol.layer.Vector({
source: vectorSource
});
map.addLayer(lyr);
})
</script>
<style type="text/css">
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="info"></div>
</body>
</html>

TODO

Scruter le flux client/serveur : compter les appels au serveur géographique et vérifier le(s) volume(s) des entités géographiques transférées

Modifier la stratégie comme suit :

ol.loadingstrategy.bbox

ajouter le paramètre suivant aux paramètres Web Feature Service

bbox: extent.join(',') + ',EPSG:3857'

Tout en naviguant dans la carte, rescruter le flux client/serveur.

Pour aller plus loin ...

Quelques autres stratégies qui cherchent à contrôler le flux client/serveur et l'expérience utilisateur selon les besoins d'interactivité de l'interface homme-machine :

Scalable interactions : https://www.mapbox.com/help/an-open-platform/#scalable-interactions (tester avec : http://bit.ly/1HNgHrp)

Vector tiles : https://en.wikipedia.org/wiki/Vector_tiles