Après une première approche des bases de données NoSQL (Le NoSQL dans le domaine géospatial, approche préliminaire en Python avec SimpleGeo.), nous allons nous intéresser ici à l'une d'entre elles, MongoDB, Open Source et développée par la société 10gen (www.10gen.com/). Cette société offre les supports commerciaux.
Elle est très intéressante pour les sigistes car depuis les dernières versions, elle possède nativement des fonctions d'indexation géospatiale. Il existe aussi une extension pour Quantum GIS, MongoDB Layer (geokoder.com/mongodb-plugin-for-quantum-gis, disponible depuis le menu Extension/Installateur d'extensions Python).
Le souhait de 10gen est, à terme, de positionner MongoDB face à PostGIS en mettant en avant une plus grande facilité d’utilisation (blog.xebia.fr/2010/04/30/nosql-europe-bases-de-donnees-orientees-documents-et-mongodb/).
Après une rapide présentation des principes d'utilisation de MongoDB, nous examinerons ses possibilités géospatiales, la manière d'importer des shapefiles ou de créer des « layers » ArcGIS à l'aide de scripts Python ou l'importation des données dans Quantum GIS. A chacun d'en tirer ses conclusions ...
Présentation
MongoDB est une base de données orientée documents (voir Le NoSQL dans le domaine géospatial, approche préliminaire en Python avec SimpleGeo). Écrite en C++, elle est disponible sur toutes les plateformes. Elle permet de manipuler des objets structurés en BSON (JSON binaire, bsonspec.org/), et d'indexer n'importe quel attribut, sans schema prédéterminé (voir l'article dans Wikipedia). Elle a été conçue pour faciliter les requête avec JavaScript et pour les services REST (Api REST).
Ces fonctionnalités détaillées sont décrites dans www.mongodb.org/display/DOCSFR/Home
(Très) Rapide introduction aux principes
Dans MongoDB, il n'y a ni tables, ni clés primaires ou secondaires, ni jointures et pas de schéma, mais des collections et des documents.
L'élément de base est le document. Il est composé d'une ou d'un ensemble de paires clés-valeurs associées. Il peut être comparé à une ligne d'une table, mais sans structure prédéfinie (champs, etc.), chacun est libre de mettre ce qu'il veut, sans se préoccuper de définitions préalables.
- {"salut":"les copains"}
- {"bonjour:"les copains", "test"=3}
- {"prix": 567.890}
- {"prénom": "Martin", "Nom": "Laloux", "num_télephone": ["+3246", "+3554"]}
sont tous des éléments valides. Chacun de ces documents va être défini par un identifiant unique appelé _id key, créé automatiquement par MongoDB lors de son insertion.
Tous ces documents sont regroupés dans une collection (de documents) qui peut être comparé à une table, toujours sans structure prédéfinie. Une Base de données est ainsi composée de plusieurs collections. Les éléments sont codés en BSON.
La première différence visible avec un système de base de données classique est qu'il ne faut pas créer explicitement une base de données. Dans le Shell Mongo, la simple commande
use mabase
crée, si elle n'existe pas, la base pour nous, avec toutes les caractéristiques.
Comme toutes les bases, elle fonctionne en mode client-serveur. Les données sont servies sur
- mongodb_server = 'localhost'
- mongodb_port = 27017
L'absence de schéma permet donc d'insérer des structures de données différentes dans une collection et/ou de modifier une structure existante de manière transparente sans impact sur les autres entrées (www.fabienpoulard.info/post/2011/05/26/MongoDB-c-est-cool).
Accès aux bases
en ligne de commande
Elles peuvent être accédées nativement avec le Shell Mongo (interpréteur JavaScript, mongo ou mongo.exe, learnmongo.com/videos/administration/mongodb-shell/) et avec la plupart des langages de programmation (www.mongodb.org/display/DOCS/Drivers), dont PHP, Ruby, Python, C# et . NET, Java ou R (et le reste...).
interfaces graphiques
Il existe aussi des interfaces graphiques gratuites ou non (www.mongodb.org/display/DOCS/Admin+UIs) comme JMongoBrowser (multiplateformes) :
ou MongoHub (Mac OS X) :
Interface REST
MongoDB offre une interface REST en version alpha (voir www.mongodb.org/display/DOCS/Http+Interface) visible en local sur http://localhost:28017/
Internet
De très nombreux sites utilisent MongoDB (comme SourceForge qui a remplacé son système PHP - Base de données classique par Python - TurboGears et MongoDB, www.thebitsource.com/programming-software-development/python/sourceforgenet-chooses-python-turbogears-and-mongodb-to-redesign-their-web-site/).
www.mongodb.org/display/DOCS/Production+Deployments répertorie ainsi tous ceux qui utilisent MongoDB comme base de données (Disney, New York Times, SourceForge, Springer, etc.) à l'aide de librairies JavaScript, PHP, Python (frameworks Django, TurboGears, etc.) ou Ruby (Ruby on Rails).
Démarrage
Le guide de démarrage rapide de MongoDB (www.mongodb.org/pages/viewpage.action) permet de commencer très facilement, quelle que soit la plateforme :
- création d'un répertoire par défaut pour les données (avant le premier lancement);
- lancement du serveur avec la commande mongod ou mongod.exe (Shell MongoDB)
et c'est tout.
L'accès se fait ensuite avec les éléments déjà mentionnés, mais pour ceux qui veulent simplement tester MongoDB, un Shell Mongo « test » est disponible à try.mongodb.org/.
Entrée des données, requêtes
avec le Shell Mongo (JavaScript)
Comme le Shell utilise JavaScript, l'entrée des données se fait au format JSON:
Ici, je crée les nouveaux éléments puis je les insère dans la collection macollect. Tout est ensuite fait automatiquement par MongoDB (création, indexation, etc.). Il y a aussi les commandes delete, upgrade,etc. (komunitasweb.com/2011/06/mongodb-basic-operations/)
Retrouver les objets est aussi facile :
On retrouve ici la clé _id créée automatiquement.
Cet exemple simpliste, montré ici à titre de préambule, ne peut illustrer toute la richesse et toutes les possibilités de création et de recherche (notamment avec les expressions régulières) de MongoDB et je vous invite vivement à consulter le tutoriel et les nombreux tutoriels disponibles sur le Net. MongoDB en vaut la peine.
En Python avec PyMongo
En Python, le module le plus simple à utiliser est PyMongo dont la documentation est très complète (api.mongodb.org/python/current/index.html). L'entrée se fait sous forme de dictionnaires Python :
avec PyMongo
from pymongo import Connection connection = Connection()# ou connection = Connection('localhost', 27017) #connexion à mabase db = connection['mabase'] # insertion d'une donnée post = {"auteur": "Martin Laloux", "texte": "Mon papier", "tags": ["mongodb", "python", "pymongo"]} articles = db.articles articles.insert(post) # recherche articles.find_one() {u'text': u'Mon papier', u'_id': ObjectId('4e5b8634c32e37059c000000'), u'auteur': u'Martin Laloux', u'tags': [u'mongodb', u'python', u'pymongo']} # quelles sont les collections dans mabase ? db.collection_names() [u'macollect', u'system.indexes', u'articles']
- 4895 lectures
Encore une fois, pour aller plus loin, je vous invite à suivre le tutoriel de PyMongo (api.mongodb.org/python/current/index.html)
Signalons qu'il existe aussi d'autres types de modules permettant d'accéder à MongoDB (pypi.python.org/pypi). Certains permettent le mapping objet-relationnel (mongoalchemy.org/, hmarr.com/2010/feb/04/introducing-mongoengine/ voir aussi « Python : les bases de données géospatiales - 2) mapping objet-relationnel (ORM, SQLAlchemy, SQLObject, GeoAlchemy, Django-GeoDjango, TurboGears ou MapFish) »), d'autres simplifient la saisie des données mais, PyMongo reste le plus utilisé. Django, TurboGears, Pylons et autres permettent de créer des sites Internet.
Autres solutions
Je vous laisse découvrir les autres solutions avec PHP, Ruby et autres langages ou les interfaces graphiques. Pour ceux que le format JSON rebute, signalons qu'il est aussi possible d'importer divers types de fichiers ou d'exporter les données vers divers types de fichiers : www.mongodb.org/display/DOCS/Import+Export+Tools
Un tutoriel interactif est disponible à mongly.com/tutorial/index
MongoDB géospatial
Comme souligné auparavant, MongoDB permet l’indexation géospatiale nativement (sans extension). Cet aspect a été développé dans un but pratique de géolocalisation : « find me the closest N items to my location » ou « find me the closest N museums to my location » (www.mongodb.org/display/DOCS/Geospatial+Indexing). Ce traitement ne s'adresse donc qu'à des points xy et pas à des lignes ou des polygones.
Insertion de points
Le principe est alors le suivant
- insertion des points dans une base sous forme de tableaux pour les coordonnées x,y ou y,x (attention à la cohérence)
- création de l'index géospatial (sinon, ce n'est pas une élément géospatial). Il est aussi possible de travailler avec des index composés.
Quelques exemples valides de création de points :
La dernière ligne crée l'index spatial pour la clé loc et pas pour location. Tous les éléments avec une clé loc seront valides pour les requêtes spatiales, ceux avec location non.
Par défaut, cette procédure assume que l'on travaille en longitude/latitude et est donc configurée pour un intervalle [-180..180]. Il est possible d'utiliser d'autres systèmes de coordonnées en spécifiant l'intervalle (le système de projection n'est pas pris en compte)
db.lambB.ensureIndex ({loc:"2d"},{min : 130000, max:250000})
Par la suite, il est possible d'entrer les points compris dans l'intervalle défini.
www.snailinaturtleneck.com/blog/2011/06/08/mongo-in-flatland/ fournit une bonne introduction aux principes.
Requêtes géospatiales
Les requêtes géospatiales sont obtenues de deux manières :
- requêtes normales avec la commande find, similaire à la commande non géospatiale, mais en utilisant divers opérateurs conditionnels comme $near
- coordonnées exactes (recherche de x,y)
- near (près de, une distance peut être spécifiée, ainsi que le nombre de résultats obtenus)
- within center
- within box
- within circle
- within polygon
- requêtes avec la commande spécifique geoNear qui permet, entre autres choses, d'obtenir les distances entre les objets dans un espace cartésien plat et/ou dans un espace sphérique.
quelques exemples avec find :
voir aussi blog.mxunit.org/2010/11/simple-geospatial-queries-with-mongodb.html ou blognode.fr/mongodb-et-fonctions-de-geolocalisation-2d/, à titre d'exemple
Les mêmes procédures avec PyMongo (find et geoNear) :
requêtes spatiales avec PyMongo
from pymongo import Connection, GEO2D db = Connection().monspatial # création de l'index spatial db.montest.create_index([("loc", GEO2D)]) # insertion des points (simple) db.montest.insert({"loc": [50.52, 4.42]}) db.montest.insert({"loc": [50.52, 5.42]}) db.montest.insert({"loc": [51.52, 3.42]}) # recherche find near, limitée for doc in db.montest.find({"loc": {"$near": [50.00, 5.00]}}).limit(3): repr(doc) "{u'loc': [50.520000000000003, 5.4199999999999999], u'_id': ObjectId('4e5bc3c5c32e370848000001')}" "{u'loc': [50.520000000000003, 4.4199999999999999], u'_id': ObjectId('4e5bc361c32e370848000000')}" "{u'loc': [51.520000000000003, 3.4199999999999999], u'_id': ObjectId('4e5bc3d2c32e370848000002')}" # recherche find within box for doc in db.montest.find({"loc": {"$within": {"$box": [[50, 4.5], [51, 6.0]]}}}): repr(doc) "{u'loc': [50.520000000000003, 5.4199999999999999], u'_id': ObjectId('4e5bc3c5c32e370848000001')}" # recherche geoNear from bson.son import SON >>> db.command(SON([('geoNear', 'montest'), ('near', [50.52, 4.42])])) {u'ok': 1.0, u'near': u'1100100000001111101011001011000011101100000011001101', u'ns': u'monspatial.montest', u'stats': {u'btreelocs': 2, u'avgDistance': 0.80473870022961835, u'objectsLoaded': 3, u'time': 38, u'maxDistance': 1.4142134780844851, u'nscanned': 3}, u'results': [{u'obj': {u'loc': [50.520000000000003, 4.4199999999999999], u'_id': ObjectId('4e5bc361c32e370848000000')}, u'dis': 0.0}, {u'obj': {u'loc': [50.520000000000003, 5.4199999999999999], u'_id': ObjectId('4e5bc3c5c32e370848000001')}, u'dis': 1.0000026226043701}, {u'obj': {u'loc': [51.520000000000003, 3.4199999999999999], u'_id': ObjectId('4e5bc3d2c32e370848000002')}, u'dis': 1.4142134780844851}]}
- 3160 lectures
Pour la suite et les autres possibilités, je vous invite à analyser les tutoriels avec le Shell Mongo (www.mongodb.org/display/DOCS/Geospatial+Indexing), avec Python et le module PyMongo (api.mongodb.org/python/current/examples/geo.html), et les exemples sur le Net.
A titre d'exemple, openmymind.net/2011/6/20/MongoDB--OpenStreetMap-and-a-Little-Demo montre l'utilisation des données OpenStreetMap avec MongoDB (points seulement). Comme démonstration, il place ensuite les points dans MongoDB sur un fond GoogleMap (pots.mongly.com/). goalfinch.com/blog/2011/03/building-interactive-maps-with-polymaps-tilestache-and-mongodb/ utilise la librairie JavaScript Polymaps pour créer des cartes interactives à partir de MongoDB. Un dernier exemple est fourni par blog.modondo.com/2011/08/using-mongodb-for-location-based-and-other-geotargeting-services/.
Notons que MongoDB ne nécessite pas de serveur cartographique pour servir ces données.
Un tutoriel interactif est disponible à mongly.com/geo
MongoDB et shapefiles
Comme déjà signalé, MongoDB ne peut traiter que les données ponctuelles, mais Paolo Corti a montré sur son blog comment stocker n'importe quel fichier shapefile (points, lignes, polygones) dans MongoDB. Il s'agit d'un script Python avec l'utilisation des modules PyMongo et ogr : www.paolocorti.net/2009/12/06/using-mongodb-to-store-geographic-data/. Malheureusement, les collections résultantes ne sont pas géospatiales. Il est possible de les importer dans MongoDB et/ou de les exporter depuis MongoDB, mais pas de traiter des données MongoDB en tant qu'entités géospatiales. Ainsi, l'extension MongoDB Layer pour QGIS ne les « voit » pas (voir par après). Ce sont de simples entrepôts de shapefiles permettant leur importation et/ou leur exportation.
Dans le cas de shapefiles de points, il est cependant facilement possible de modifier le script de Paolo Corti pour obtenir de véritables collections géospatiales reconnues par l'extension :
collection géospatiale à partir d'un shapefile point - modification du script de Paolo Corti
from pymongo import Connection, GEO2D from osgeo import ogr # lecture du shapefile driver = ogr.GetDriverByName('ESRI Shapefile') shape_path="points.shp" ds = driver.Open(shape_path, 0) lyr = ds.GetLayer() feat = lyr.GetNextFeature() # connexion et création de la collection géospatiale db = Connection().geo_example db.places.create_index([("loc", GEO2D)]) # insertion du premier élément dans MongoDB geom = feat.GetGeometryRef() db.places.insert({"loc": [ geom.GetX(), geom.GetY()]}) # etc. avec la boucle du script de Paolo Corti
- 3377 lectures
MongoDB et ArcGIS
De la même manière que Paolo Corti, gissolved.blogspot.com/2009/05/populating-mongodb-with-pois.html a utilisé le module argisscripting avec PyMongo pour créer directement des « layers » à partir de données contenues dans une base MongoDB.
MongoDB et Quantum GIS
L'extension MongoDB Layer permet de charger les éléments géospatiaux dans QGIS (comme avec PostGIS et SpatiaLite). L'extension est aussi basée sur le module PyMongo.
Conclusions
Et voilà, j'espère vous avoir donné un avant-goût de MongoDB. Ce que j'apprécie vraiment est sa flexibilité, sa facilité d'installation et d'utilisation et sa rapidité.
Sur le plan géospatial MongoDB est encore loin de PostGIS (pas de prise en compte des géométries et des projections). Ce n'est pas le cas avec une autre base de données NoSQL, CouchDB, avec son extension spatiale GeoCouch (basée sur le module Python Shapely, 2010.foss4g.org/presentations/3048.pdf) qui permet beaucoup plus de choses (géométries, projections, gitorious.org/geocouch/pages/GeometryDefinition, skipperkongen.dk/2011/07/20/sticking-bicycle-paths-in-couchdb/) que j'aborderai dans un prochain article.
Comme SimpleGeo, je constate, en pratique, que MongoDB marche très bien pour ce qu'il sait faire, traiter les données ponctuelles xy.
Pour terminer, je vous propose quelques livres sur MongoDB que j'ai consulté :
- MongoDB in Action (2011), Banker, Kyle, Manning, 375p.
- MongoDB: The Definitive Guide (2010), Chodorow, Kristina, Dirolf, Michael, O'Reilly, 216 p.
- The Definitive Guide to MongoDB: The NoSQL Database for Cloud and Desktop Computing (2010), Hawkins, Tim, Plugge, Eelco, Membrey, Peter Apress, 350 p.
- The Little Mongo Book, Seguin, Kar, libre et gratuit openmymind.net/mongodb.pdf
Tous les traitements ont été effectués sur Mac OS X avec MongoDB 1.8.3 et Python 2.6.1
Site officiel : MongoDB
Site officiel : MongoDB (Wikipedia)
Site officiel : PyMongo
Site officiel : CouchDB
Site officiel : GeoCouch
Autres Liens : Le NoSQL dans le domaine géospatial, approche préliminaire en Python avec SimpleGeo.
Autres Liens : Python : les bases de données géospatiales - 2) mapping objet-relationnel (ORM, SQLAlchemy, SQLObject, GeoAlchemy, Django-GeoDjango, TurboGears ou MapFish)
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France
Commentaires
Article fort intéressant. Moi
Article fort intéressant. Moi j’utilise MongoDB en ce moment et j’ai réussi à le manipuler grâce à un tutoriel sur http://www.alphorm.com/tutoriel/formation-en-ligne-mongodb-administration. Cependant, je trouve aussi qu’il y a plusieurs logiques dans NoSQL. Merci pour ce partage.
Première ressource web Francophone 100% dédiée à MongoDB
Bon article de présentation, si certains veulent plus, il y a une nouvelle ressource web http://www.mongotuto.com entièrement gratuite, et le restera :)
Je reformule un peu...
D'accord on parle de clefs et non plus de champs.
Mais ma question sur la recherche porte sur l'intégrité de l'utilisation des clefs.
Avec le SQL, je sais que si je veux remplir le champ "prénom", j'ai bien peu de chances de me tromper (ou alors je me trompe de champ, mais je n'en crée pas un en plus).
Avec le NoSQL, j'ai l'impression que je peux définir par mégarde des clefs, et donc on aura beau indexer la clef "prénom", les valeurs de la clef "prenom" ne sera pas indexée, je me trompe ?
oui, c'est vrai puisque les
oui, c'est vrai puisque les données sont codées en UTF8 :
> adresse = {"prenom": "martin"}
{ "prenom" : "martin" }
> db.collect.insert(adresse)
> adresse = {"prénom": "martin"}
{ "prénom" : "martin" }
> db.collect.insert(adresse)
> db.collect.find()
{ "_id" : ObjectId("4e67911d4d79bcbe06ce5c51"), "prenom" : "martin" }
{ "_id" : ObjectId("4e67912f4d79bcbe06ce5c52"), "prénom" : "martin" }
mais
> db.collect.find({'prenom':'martin'})
{ "_id" : ObjectId("4e67911d4d79bcbe06ce5c51"), "prenom" : "martin" }
> db.collect.find({'prénom':'martin'})
{ "_id" : ObjectId("4e67912f4d79bcbe06ce5c52"), "prénom" : "martin" }
Cela peut causer quelques maux de tête si on n'a pas normalisé l'entrée des données. Il est possible d'utiliser des expressions régulières dans les valeurs mais je n'ai pas trouvé pour les clés.
Mais on peut toujours utiliser les opérateurs logiques:
> db.collect.find({ $or : [{'prenom':'martin'},{'prénom':'martin'}]})
{ "_id" : ObjectId("4e67911d4d79bcbe06ce5c51"), "prenom" : "martin" }
{ "_id" : ObjectId("4e67912f4d79bcbe06ce5c52"), "prénom" : "martin" }
voir www.jonathanhui.com/mongodb-query
ou les index composés (prénom et prenom) jonathanhui.com/mongodb-index
normaliser l'entrée des données ?
> Cela peut causer quelques maux de tête si on n'a pas normalisé l'entrée des données.
Cette "normalisation d'entrée de donnée", est-ce forcément une action externe, ou cela peut-il être une configuration de la base de donnée ?
cela peut être configuré dans
cela peut être configuré dans la base lors de l'entrée de données
Non, le système de champ est
Non, le système de champ est dépendant de la casse {prénom:Martin} et {prénom:martin} sont deux objets différents.
Le mécanisme de recherche des bases NoSQL est généralement basé sur les clés, sur l'indexation (http://www.mongodb.org/display/DOCS/Indexing+Advice+and+FAQ) et sur le mécanisme de MapReduce, avec des fonctions map (subdiviser) et reduce (agréger) empruntés aux langages de programmation fonctionnelle (http://nosql.mypopescu.com/post/543568598/presentation-mapreduce-in-simp...). On est loin du SQL...
Enfin un peu de concret dans le NoSQL
J'y vois un peu plus clair dans le NoSQL désormais, merci pour ce petit article.
Si je comprends bien, avant on avait entre personnes responsables, quelqu'un qui définissait un table "Gens", avec un champ "prénom". Pour rajouter "Martin", l'utilisateur devait aller chercher la table "gens", puis le champ "prénom".
Se tromper de table ou de champ, c'est le faire exprès, ou alors c'est la faute d'une dénomination ou d'une description pas assez précise de la table ou du champ.
Maintenant, avec le NoSQL, que ce soit MongoDB (et pas MonGodB comme je l'ai lu...) ou un autre outil de type NoSQL, il suffit juste de rajouter le tuple {prénom:Martin}, sans aller chercher quoique ce soit d'autres.
Ainsi, entre gens responsables, et sans absolument chercher la petite bête, on aura très facilement les tuples suivants :
{prénom:Martin}
{prenom:Jeannette}
{FirstName:Guillaume}
{pren:Pascal}
{prenon:Kévin}
(je suppose que le système de champ est indépendant de la casse, mais bien sûr, un système sensible à la casse double au mieux les possibilités, si ce n'est beaucoup plus)
Dans l'exemple donné de requête, de recherches par valeurs particulières pour un champ donné (toutes les données dont le "pays" est "Belgique"), quel est le mécanisme qui permet de savoir que la recherche en question me retourne "toutes les données dont le pays est Belgique" ? Comment le NoSQL fait-il pour qu'une requête a un sens (certains diront une sémantique) et pas simplement une expression technique (comme pourrait l'être une requête de type "quels sont les tuples dont le code hexadécimal contient la valeur 0xF510BC" ?).
Poster un nouveau commentaire