Introduction
MapQuery a récemment sortie sa première version officielle. Similaire par son objectif à GeoExt (OpenLayers + ExtJS), MapQuery permet d'associer les deux librairies que sont OpenLayers et Jquery (+ Jquery UI). Le résultat est la possibilité de créer rapidement et facilement des interfaces cartographiques poussées. Découvrons ensemble les potentialités de ce nouveau projet.
Téléchargement & gabarit
Bien évidemment, avant de nous amuser, il est nécessaire de télécharger les différentes librairies. Pour les linuxiens, le plus simple est de lancer le script getdeps.sh. Celui-ci se chargera automatiquement de télécharger les dépendances nécessaires. Pour les personnes ayant comme système d'exploitation Windows, vous devrez télécharger et décompresser manuellement les librairies suivantes : Jquery UI, OpenLayers et enfin Jquery Tmpl.
Maintenant, nous allons créer le gabarit de notre page. C'est à partir de celui-ci que se baseront tous les exemples. Il contient les appels vers les bibliothèques ainsi que les éléments du Dom nécessaires à notre script. Pour cela, il vous suffit de copier dans votre éditeur de texte les lignes ci-dessous. Bien évidemment, vous devrez modifier les chemins vers les librairies en fonction de votre configuration. Dans notre cas, nous avons créé un dossier exemples à la racine de MapQuery. Ne vous étonnez pas de voir des dollars ($) fleurir partout dans notre code. Ce n'est pas que GeoTribu soit devenu avide d'argent, mais c'est la manière pour Jquery de pointer vers des éléments.
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0;"> <meta name="apple-mobile-web-app-capable" content="yes"> <title>MapQuery hello world!</title> <link rel='StyleSheet' href='../demo/style/style.css' type='text/css' /> <script src="../lib/openlayers/OpenLayers.js" type="text/javascript"></script> <script src="../lib/jquery/jquery-1.4.4.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.core.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function() { //Futur emplacement du code }); </script> </head> <body> <div id="map" class="map"></div> </body> </html>
Et voilà, nous avons maintenant tous les éléments nécessaires. Nous pouvons commencer à créer notre 1ère application.
Créer sa 1er carte
La création d'une carte, avec MapQuery, se fait très simplement avec à peine quelques lignes de code. Vous pouvez, si vous le souhaitez, définir l'ensemble des caractéristiques de votre carte directement lors de la construction de l'objet map. C'est ce qui est fait dans les lignes ci-dessous (exemple 1) :
$(document).ready(function() { $('#map').mapQuery({ //Construction de l'objet map layers:[{ //Ajout des layers type:'osm' //Ajout d'une couche de type OpenStreetMap }] }); });
Même si cette manière de faire est tout fait correcte, j'avoue lui préférer celle présentée ci-dessous. Elle est certes plus verbeuse mais je la trouve plus compréhensible (exemple 2) :
$(document).ready(function() { var map = $('#map').mapQuery(); //Construction de l'objet map var mapObj = map.data('mapQuery'); //On récupère l'objet map mapObj.layers({type:'osm'}); //On ajoute la couche OSM });
Le coeur de l'API est l'objet map. Néanmoins, je suis assez surpris car j'ai tenté d'utiliser des fonctions d'OpenLayers et cela me retourne une erreur. Par exemple, si je souhaite zoomer sur un endroit de la carte, la propriété à appeler est center et non pas setCenter comme c'est le cas avec OL. Contrairement à GeoExt qui fait le lien entre les deux librairies tout en gardant les objets intacts, MapQuery semble ré-écrire tous les appels. De ce fait l'objet map produit par MapQuery ne semble pas valide au sens d'OpenLayers. Un mystère à résoudre ! Mais si c'est bien le cas comme je le suppose, cette librairie perd un peu de sa souplesse. Cela nécessiterait d'apprendre une nouvelle API et aussi d'attendre que les nouveautés apportées par les nouvelles versions d'OpenLayers soient implémentées dans MapQuery.
Ajouter des couches de données
Dans le code précédent, nous avons déjà rapidement abordé comment ajouter des couches de données à votre carte. Regardons plus en détail les options proposées. Dans la version que nous utilisons (0.1), sept types de données sont disponibles. Celles-ci sont : Bing, Google, Vecteur, Json, OSM, WMS et WMTS.
L'ajout d'une couche se fait via la méthode "layers()" de l'objet map. Celle-ci prend comme principal argument l'un des sept types que nous avons listés. Par exemple pour ajouter une couche OpenStreetMap (OSM), il suffit de spécifier les lignes suivantes :
mapObj.layers({type:'osm'});Pour certaines couches il est nécessaire d'ajouter des arguments complémentaires comme votre clé d'enregistrement (Bing), la couche désirée (Bing, Google) ou encore l'adresse de votre Web Service (WMS) (exemple 3) :
//Bing
mapObj.layers({ type : 'bing',
view : 'satellite',
key : 'your_api_key' });
//Google
mapObj.layers({ type : 'google',
view : 'terrain' });
//WMS
mapObj.layers({ type : 'wms',
layers : 'bluemarble',
url : 'http://wms.jpl.nasa.gov/wms.cgi?',
format : 'image/png' });
//OSM
mapObj.layers({ type : 'osm' });Enfin, il existe également des méthoodes (up, down, opacity, etc) relatives à l'objet layer. Néanmoins, chose étrange, il n'existe pas de pointeur vers les couches de données. En effet, comme il n'est pas possible de spécifier un identifiant, vous devrez vous balader dans votre tableau de couche afin de récupérer celle désirée. Par exemple, pour changer l'opacité de la couche OpenStreetMap (le chiffre 0 étant la position de la couche OSM dans le tableau) cela se passe de la manière suivante :
mapObj.layers()[0].opacity(0.5);
Comme vous avez certainement dû le remarquer, les couches se superposent. Plutôt gênant non ? Ce petit désagrément va nous permettre d'aborder concrètement, les apports de MapQuery.
Les Widgets
MapQuery dispose d'un certain nombre de widgets. Comme cela est expliqué dans la documentation, les widgets, contrairement aux plugins, permettent de modifier l'apparence de l'application. Il est ainsi possible d'ajouter, un contrôleur de couches, une mini-carte ou encore un gestionnaire de zoom.
Afin de pouvoir "jouer" avec nos couches ajoutons immédiatement un contrôleur. Néanmoins, nous allons devoir modifier notre page html de départ en ajoutant des bibliothèques MapQuery supplémentaires ainsi qu'un nouvel élément 'div'. En effet, un widget se construit en prenant tout d'abord l'identifiant du div de la page, puis le widget lui même. Pour cette raison, nous afficherons la totalité du code (exemple 4) :
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0;"> <meta name="apple-mobile-web-app-capable" content="yes"> <title>MapQuery hello world!</title> <link rel='StyleSheet' href='../demo/style/style.css' type='text/css' /> <link rel="stylesheet" href="../lib/jquery/themes/base/jquery-ui.css" type="text/css" media="all" /> <script src="http://maps.google.com/maps/api/js?v=3.5&sensor=false" type="text/javascript"></script> <script src="../lib/openlayers/OpenLayers.js" type="text/javascript"></script> <script src="../lib/jquery/jquery-1.4.4.js" type="text/javascript"></script> <script src="../lib/jquery/jquery.tmpl.js" type="text/javascript"></script> <script src="../lib/jquery/ui/jquery-ui.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.core.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.mqLayerManager.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.legend.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function() { var map = $('#map').mapQuery(); var mapObj = map.data('mapQuery'); //Bing mapObj.layers({ type : 'bing', label : 'Bing', legend : {url:'./img/bing.jpg'}, view : 'satellite', key : 'Av48uG1dlRVRDWGbpmSYo4DlXGCuFOyEPNeEmfTVtLcv1olpEM1LvGkAplbo0-qk' }); //OSM mapObj.layers({ type : 'osm', label : 'OpenStreetMap', legend : {url:'http://www.openstreetmap.org/images/osm_logo.png'} }); //Google mapObj.layers({ type : 'google', label : 'Google', legend : {url:'./img/google.png'}, view : 'terrain' }); $('#layermanager').mqLayerManager({map:'#map'}); }); </script> </head> <body> <div id="map" class="map"></div> <div id="layermanager" class="mq-widget"></div> </body> </html>
Ajoutons maintenant une mini-carte de localisation. Là encore faite bien attention à ajouter le script JS supplémentaire ainsi que l'élément 'overview' dans la partie Body de la page (exemple 5) :
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0;"> <meta name="apple-mobile-web-app-capable" content="yes"> <title>MapQuery hello world!</title> <link rel='StyleSheet' href='../demo/style/style.css' type='text/css' /> <link rel="stylesheet" href="../lib/jquery/themes/base/jquery-ui.css" type="text/css" media="all" /> <script src="http://maps.google.com/maps/api/js?v=3.5&sensor=false" type="text/javascript"></script> <script src="../lib/openlayers/OpenLayers.js" type="text/javascript"></script> <script src="../lib/jquery/jquery-1.4.4.js" type="text/javascript"></script> <script src="../lib/jquery/jquery.tmpl.js" type="text/javascript"></script> <script src="../lib/jquery/ui/jquery-ui.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.core.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.mqLayerManager.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.legend.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.mqOverviewMap.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function() { var map = $('#map').mapQuery(); var mapObj = map.data('mapQuery'); //Bing mapObj.layers({ type : 'bing', label : 'Bing', legend : {url:'./img/bing.jpg'}, view : 'satellite', key : 'Av48uG1dlRVRDWGbpmSYo4DlXGCuFOyEPNeEmfTVtLcv1olpEM1LvGkAplbo0-qk' }); //OSM mapObj.layers({ type : 'osm', label : 'OpenStreetMap', legend : {url:'http://www.openstreetmap.org/images/osm_logo.png'} }); //Google mapObj.layers({ type : 'google', label : 'Google', legend : {url:'./img/google.png'}, view : 'terrain' }); $('#layermanager').mqLayerManager({map:'#map'}); $('#overview').mqOverviewMap({ map: '#map' }); }); </script> </head> <body> <div id="map" class="map"></div> <div id="layermanager" class="mq-widget"></div> <div id="overview" class="mq-widget"></div> </body> </html>
Finissons avec un dernier widgets. Nous allons ajouter à notre interface un outil de zoom (exemple 6):
<!doctype html> <html> <head> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0;"> <meta name="apple-mobile-web-app-capable" content="yes"> <title>MapQuery hello world!</title> <link rel='StyleSheet' href='../demo/style/style.css' type='text/css' /> <link rel="stylesheet" href="../lib/jquery/themes/base/jquery-ui.css" type="text/css" media="all" /> <script src="http://maps.google.com/maps/api/js?v=3.5&sensor=false" type="text/javascript"></script> <script src="../lib/openlayers/OpenLayers.js" type="text/javascript"></script> <script src="../lib/jquery/jquery-1.4.4.js" type="text/javascript"></script> <script src="../lib/jquery/jquery.tmpl.js" type="text/javascript"></script> <script src="../lib/jquery/ui/jquery-ui.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.core.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.mqLayerManager.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.legend.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.mqOverviewMap.js" type="text/javascript"></script> <script src="../src/jquery.mapquery.mqZoomSlider.js" type="text/javascript"></script> <script type="text/javascript"> $(document).ready(function() { var map = $('#map').mapQuery(); var mapObj = map.data('mapQuery'); //Bing mapObj.layers({ type : 'bing', label : 'Bing', legend : {url:'./img/bing.jpg'}, view : 'satellite', key : 'Av48uG1dlRVRDWGbpmSYo4DlXGCuFOyEPNeEmfTVtLcv1olpEM1LvGkAplbo0-qk' }); //OSM mapObj.layers({ type : 'osm', label : 'OpenStreetMap', legend : {url:'http://www.openstreetmap.org/images/osm_logo.png'} }); //Google mapObj.layers({ type : 'google', label : 'Google', legend : {url:'./img/google.png'}, view : 'terrain' }); $('#layermanager').mqLayerManager({map:'#map'}); $('#overview').mqOverviewMap({ map: '#map' }); $('#zoomslider').mqZoomSlider({ map:'#map' }); }); </script> </head> <body> <div id="map" class="map"> <div id="zoomslider" class="mq-widget"></div> </div> <div id="layermanager" class="mq-widget"></div> <div id="overview" class="mq-widget"></div> </body> </html>
Bon je m'arrête là avec les widgets. Je pense que vous aurez compris la philosophie générale de ces composants. Sachez qu'il en existe actuellement près d'une dizaine tels qu'un gestionnaire de légende, de zoom ou encore de popup.
Conclusion
Il n'est jamais agréable de devoir critiquer le travail réalisé par autrui d'autant plus qu'il est mis librement à disposition. Néanmoins, je dois avouer être un peu déçu. Si le résultat final est conforme à mes attentes, c'est du point de vue de l'architecture et de la construction de l'API que je suis plus partagé. En effet, il semblerait que MapQuery, à la construction de l'objet Map, ne retourne pas un objet compatible avec Openlayers. Peut-être que cela provient d'une mauvaise interprétation de ma part. Dans ce cas n'hésitez pas à laisser des commentaires. Mais si c'est le cas, cela soulève plusieurs problèmes. Tout d'abord, cela signifie que les développeurs devront implémenter une à une les différentes méthodes d'OpenLayers. Ce travail de double écriture est dommageable en terme de temps et de suivi des versions. De plus, pour les personnes habitués à OpenLayers, l'utilisation de MapQuery signifie alors l'apprentissage d'une nouvelle librairie.
A ce problème d'objet Map, s'ajoute également celui des méthodes d'accès. Je m'explique, si vous souhaitez pointer vers une couche il n'existe aucun moyen spécifique permettant d'identifier cette dite couche. Nous pourrions également souligner la difficulté à placer les différents éléments. En effet, contrairement à GeoExt, il n'existe aucun objet View Port et aucun concept de régions. Quand je regarde les exemples disponibles sur la page de Jquery UI j'ai plus l'impression d'avoir à ma disposition une boite à outils plutôt qu'un véritable framework que je pourrai utiliser pour le développement d'application.
J’arrêterai là mes critiques qui doivent être vu avant tout comme des remarques. Nous ne sommes qu'à la version 0.1 et les difficultés rencontrées sont celles d'une jeune librairie. Je suis certain que le jour de la sortie de la version 1.0, ces défauts seront du passé. De plus, concernant mes remarques sur l'interface, les exemples prouvent qu'il est tout à fait possible de faire de jolies choses. Sur ce sujet, n'hésitez pas à consulter les réalisations de la société 3liz et de leur application Osmtransport.

Hi (sorry for my english, my
Hi (sorry for my english, my french isn't good enough to respond in french)
Thanks for diving into MapQuery. It is a good read and it is nice to see a critical outside view on the project. You are right that the MapQuery rewrites the calls. The philosophy is that OpenLayers is deemed to be complex for many users. We try to provide a simple API without all the legacy code of OpenLayers. You are right that MapQuery will always be behind OpenLayers and that you loose some flexibility. We aim at those develoeprs who know jQuery but don't know OpenLayers as such they shouldn't need to learn OpenLayers to get a map working. If however you are used to OpenLayers, MapQuery is indeed a bit wierd and GeoExt is probably a more logical choide.
The layer story I couldn't completely grasp, but mapObj.layers()[0].opacity(0.5); shouldn't be necessary. In general the idea is that you always have the layer object, this is a hard concept to grasp, since everyone seems to be used to .getLayerById('id'). However, to get this 'id' you need to ask the layer for its 'id' and if you ask the layer for its 'id', you already have the layer.
Would you be so kind to send your views to the mailing list as well? It is nice to have this discussion in a broader setting.
regards,
Steven
Hi, thanks for the review,
Hi,
thanks for the review, it's the best I've seen so far.
Just a small addition to what Steven said. It is true that MapQuery wraps the OpenLayers API, but the original OpenLayers API is still accessible for MapQuery plugin authors. The goal is to make a nice API to the end user, but have the full API available to plugin developers who than expose such a nice API. For the OpenLayers map object it is $('#map').mapQuery().data('mapQuery').olMap, for the layer (e.g. the first one) it is $('#map').mapQuery().data('mapQuery').layers()[0].olLayer. As previously said, whenever you need those, you should create a higher level API to expose them in a nice (jQuery-ish) way.
Cheers,
Volker