Skip to Content

Le NoSQL dans le domaine géospatial : MongoDB avec JavaScript ou Python, ArcGIS et Quantum Gis


python

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 :

  1. création d'un répertoire par défaut pour les données (avant le premier lancement);
  2. 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

  1. from pymongo import Connection
  2. connection = Connection()# ou connection = Connection('localhost', 27017)
  3.  
  4. #connexion à mabase
  5. db = connection['mabase']
  6.  
  7. # insertion d'une donnée
  8. post = {"auteur": "Martin Laloux",
  9. "texte": "Mon papier",
  10. "tags": ["mongodb", "python", "pymongo"]}
  11.  
  12. articles = db.articles
  13. articles.insert(post)
  14.  
  15. # recherche
  16. articles.find_one()
  17. {u'text': u'Mon papier', u'_id': ObjectId('4e5b8634c32e37059c000000'), u'auteur': u'Martin Laloux', u'tags': [u'mongodb', u'python', u'pymongo']}
  18.  
  19. # quelles sont les collections dans mabase ?
  20. db.collection_names()
  21. [u'macollect', u'system.indexes', u'articles']
  22.  
  23.  

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

  1. insertion des points dans une base sous forme de tableaux  pour les coordonnées x,y ou y,x (attention à la cohérence)
  2. 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

  1. from pymongo import Connection, GEO2D
  2. db = Connection().monspatial
  3.  
  4. # création de l'index spatial
  5. db.montest.create_index([("loc", GEO2D)])
  6.  
  7. # insertion des points (simple)
  8. db.montest.insert({"loc": [50.52, 4.42]})
  9. db.montest.insert({"loc": [50.52, 5.42]})
  10. db.montest.insert({"loc": [51.52, 3.42]})
  11.  
  12. # recherche find near, limitée
  13. for doc in db.montest.find({"loc": {"$near": [50.00, 5.00]}}).limit(3):
  14. repr(doc)
  15. "{u'loc': [50.520000000000003, 5.4199999999999999], u'_id': ObjectId('4e5bc3c5c32e370848000001')}"
  16. "{u'loc': [50.520000000000003, 4.4199999999999999], u'_id': ObjectId('4e5bc361c32e370848000000')}"
  17. "{u'loc': [51.520000000000003, 3.4199999999999999], u'_id': ObjectId('4e5bc3d2c32e370848000002')}"
  18.  
  19.  
  20. # recherche find within box
  21. for doc in db.montest.find({"loc": {"$within": {"$box": [[50, 4.5], [51, 6.0]]}}}):
  22. repr(doc)
  23. "{u'loc': [50.520000000000003, 5.4199999999999999], u'_id': ObjectId('4e5bc3c5c32e370848000001')}"
  24.  
  25. # recherche geoNear
  26. from bson.son import SON
  27. >>> db.command(SON([('geoNear', 'montest'), ('near', [50.52, 4.42])]))
  28. {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}]}

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

  1. from pymongo import Connection, GEO2D
  2. from osgeo import ogr
  3.  
  4. # lecture du shapefile
  5. driver = ogr.GetDriverByName('ESRI Shapefile')
  6. shape_path="points.shp"
  7. ds = driver.Open(shape_path, 0)
  8. lyr = ds.GetLayer()
  9. feat = lyr.GetNextFeature()
  10.  
  11. # connexion et création de la collection géospatiale
  12. db = Connection().geo_example
  13. db.places.create_index([("loc", GEO2D)])
  14.  
  15. # insertion du premier élément dans MongoDB
  16. geom = feat.GetGeometryRef()
  17. db.places.insert({"loc": [ geom.GetX(), geom.GetY()]})
  18.  
  19. # etc. avec la boucle du script de Paolo Corti

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)


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

Commentaires

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

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.