La base de datos NoSQL MongoDB ofrece un conjunto de índices y mecanismos de consulta diferentes, sin embargo hoy vamos halar de aquellos que manejan información geoespacial. Existen soluciones especializadas de base de datos relacionales para modelar coordenadas y figuras geométricas como son PostGIS o extensiones de MySQL. Hay todo un mundo del software especializado en este tipo de Sistemas de Información Geográfica (GIS), sin embargo existen soluciones más sencillas que nos puede proporcionar soluciones a nuestros problemas y una de ellos es los índices geoespaciales de MongoDB.
Un índice en bbdd se trata de una estructura de datos que toma campos particulares de una tabla y almacena una estructura de rápido acceso a éstos datos con el objetivo de realizar consultas por el campo indexado mejorando el rendimiento y la velocidad de respuestas hacia estos. Son útiles emplearlos cuando nuestra base de datos crece significativamente.
Sin embargo la solución no es solución poblar la base de datos con índices en todos los campos ya que estas estructura de datos requieren volumen de la base de datos y puedes perjudicarte en vez de beneficiarte por lo que se busca es que los indíces se apliquen sobre los campos que vayan a ser consultados con mayor frecuencia.
MongoDB ofrece el soporte para poder generar índices llamados geoespaciales contra coordenadas geográficas que permiten realizar consultas sobre éstos. Estas consultas permiten responder consultas como Dame los elementos cercanos a estas coordenadas o Dame los elementos que se encuentren en este área. Esto es lo que se conocen como consultas de proximidad o consultas acotadas.
MongoDB proporciona dos tipo de índices: índices 2d e índices 2d sphericos.
Los índices 2d soportan cálculos en un Plano Eculidiano, es decir en superficies planas. MongoDB recomienda almacenar las localizaciones como pares de cóordenadas en un array, para evitar quellos lenguajes que no soporten array asociativos
loc:[-3.5234,40.434]
Los índices 2d esféricos son índices algo más complejos que soportan consultas de cálculo geométricas en una esfera similar a nusetro planeta, de tal manera que soportan objetos de tipo Point, LineString y Polygon.
Para crear un índice en mongodb es tan facil como decir el campo que tiene el índice y el tipo del índice. Por ejemplo:
db.places.ensureIndex({"loc":"2d"})
Veamos un ejemplo realizado en nuestro Proyecto de Fin de Master donde almacenabamos las localizaciónes de los centros donde se disputaban una serie de eventos para poder realizar consultas para obtener los partidos mas cercanos que se disputarán cercanos de la posición de un usuario en concreto. Para ello al tratarse un proyecto de Symfony y emplear Doctrine como ODM para la persistencia en MongoDB lo primero que realizamos es generar el tipo de documento que contendrá la latitud y la longitud:
<?php namespace MIW\DataAccessBundle\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; /** @MongoDB\EmbeddedDocument */ class Coordinates { /** @MongoDB\Float */ public $x; /** @MongoDB\Float */ public $y; public function getX() { return $this->x; } public function getY() { return $this->y; } public function setX($x) { $this->x = $x; } public function setY($y) { $this->y = $y; } }
Luego la colección que embeba este tipo de documentos son la colección que debe incluir el índice determinado. Como vemos en la siguiente entidad la propiedad coordinates es una referencia embebida contra este documento y el inicio de la clase se define los índices que contendrán los documentos de esta colección.
<?php namespace MIW\DataAccessBundle\Document; use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; /** @MongoDB\EmbeddedDocument @MongoDB\Index(keys={"coordinates"="2d"}) */ class Address { /** * @MongoDB\String */ protected $address; /** * @MongoDB\String */ protected $zipcode; /** * @MongoDB\String */ protected $city; /** * @MongoDB\String */ protected $province; /** * @MongoDB\String */ protected $community; /** @MongoDB\EmbedOne(targetDocument="Coordinates") */ public $coordinates; /** @MongoDB\Distance */ public $distance; public function getCoordinates() { return $this->coordinates; } public function getDistance() { return $this->distance; } public function setCoordinates($coordinates) { $this->coordinates = $coordinates; } public function setDistance($distance) { $this->distance = $distance; } public function __toString() { return $this->address; } }
Una vez modelado nuestras entidades que contienen las coordenadas realizar la query que nos devuelva las posiciones más cercanas quedaría de la siguiente manera con DoctrineODM:
public function findClosestCenters($lat,$long) { return $this->createQueryBuilder() ->field('address.coordinates')->near($lat,$long) ->getQuery() ->execute(); }
Lo que se puede traducir a la siguiente query
db.runCommand( { geoNear: 'addres.coordinates', near: [$lat,$long])} );
Gracias a MongoDB podemos obtener un valor añadido a nuestra base de datos de una forma bastante sencilla. Eso si, esto es una solución para problemas concretos y reducidos. Si se requieren operaciones más complejas en las que se requieran empleo de distancias, rutas se requerirán sistemas más complejos como los sistemas GIS comentados al inicio del artículo o emplear librerías que nos de esta potencia como es Google Maps.