Con Azure Cosmos DB podemos realizar búsquedas geoespaciales, lo permite cuando en nuestra entidad/documento tenemos sobre una propiedad la ubicación de algún punto de interés. El punto (latitud y longitud) debe estar representado de acuerdo a la especificación GeoJSON.

El API expone funciones ya establecidas para hacer búsquedas geoespaciales de forma muy fácil.

Vamos a realizar un ejemplo completo desde la consulta hacia una colección de Azure Cosmos DB hasta la representación de estos puntos sobre un mapa sobre Azure Maps.

Escenario

Tendremos una colección con 117,969 entidades que definen una propiedad (casa, local o departamento) sobre la Ciudad de México. Estas propiedades se mostrarán como pines sobre un mapa de Azure Maps de acuerdo a su latitud y longitud.

Adicional tendremos un pin/marcador que podremos arrastrar sobre el mapa y basado en la ubicación del este pin se filtraran las propiedades mas cercanas a un radio de 500 metros del pin/marcador.

Aquí un ejemplo de lo que haremos:

Lo haremos sobre ASP .NET Core 2.0.0 la libreria de Nuget para Cosmos DB Microsoft.Azure.DocumentDB.Core.

El código completo de esta solución en GitHub.

Preparando Indice

Antes de poder ocupar el tipo de datos GeoJSON sobre nuestro documento para hacer búsquedas. Debemos indicar a nuestro índice/colección que debe considerar este tipo para la indexación. Esto es porque de forma predeterminada el índice/colección no tiene habilitada esta política.

Una vez creada la base y la colección debemos habilitar la política para que considere los datos espaciales:

Validamos si ya existe la política, si no existe iniciamos el proceso para aplicar la política sobre la colección, esto lo podemos realizar en cualquier momento, aun cuando ya existan documentos sobre la colección. El proceso se ejecuta de forma asíncrona y monitoreamos el progreso con el método ReadDocumentCollectionAsync. Mientras dura el proceso la colección puede seguir habilitada para seguir insertando/escribiendo, no hay afectación.

El objeto que establece el tipo de política es new SpatialIndex(DataType.Point), que en nuestro caso es DataType.Point porque vamos a guardar nada mas un punto (latitud y longitud), existen mas para diferentes escenarios.

Si no ejecutamos este procedimiento al momento de ejecutar filtros sobre la propiedad con este tipo de dato no funcionan.

El documento se ve asi:

Búsquedas

Ya que tenemos la colección lista podemos hacer búsquedas. Dentro de nuestra solución tenemos un controlador que recibe la ubicación del pin que se puede arrastrar sobre el mapa, a partir de esta ubicación realizamos una búsqueda que nos filtre todas las propiedades que se encuentran a una distancia menor a 500 metros:

Veamos le expresión lambda del filtro como la propiedad Location del tipo Property expone un método Distance que recibe un objeto Point con el punto con el que calculara la distancia. Internamente eso se traduce en algo asi “SELECT *
FROM f WHERE ST_DISTANCE(f.location, {‘type’: ‘Point’, ‘coordinates’:[0.0, 0.0]}) < 500″ cuando ocupamos SQL API de Cosmos DB.

Es importante que nuestra clase Property tenga definida la propiedad Location con el tipo Point:

La propiedad Location es del tipo Microsoft.Azure.Documents.Spatial.Point.

Veamos la definición del método DocumentDBRepository<Property>.GetItemsAsync:

Para mas detalle de las consultas geoespaciales aquí.

Mapa

El mapa que usaremos es el de Azure Maps. Primero debemos agregar los scripts para CSS y JS:

Debemos recuperar la llave de suscripción del servicio de Azure Maps. Esto lo hacemos desde el portal de Azure en el dashboard del servicio después de crearlo.

Después inicializamos el mapa:

En la propiedad “subscription-key”: mapAccountKey se establece la llave que recuperamos del servicio.

Al momento de que el mapa carga agregamos el pin que permitirá arrastrarlo sobre el mapa con la función addCenterPin:

Al momento que el pin termina de ser arrastrado ejecutamos la búsqueda basado en la ubicación del pin, esto ocurre en el evento centerPin.onmouseup invocando la función search(). Veamos la definición de la búsqueda donde invocamos la acción del controlador que ya habíamos visto antes:

Realizamos la búsqueda por medio de ajax y en la respuesta iteramos sobre el array, y por cada propiedad devuelta en el filtro la agregamos al mapa con la función addPropertyPin:

Aquí generamos el pin, y adicional generamos el control Popup que permite mostrar un poco de detalle al momento de dar clic sobre un pin. El método con el que agregamos los pines es map.addHtml(propertyPin, property.location.coordinates) que recibe un objeto DOM y las coordenadas (latitud y longitud).

Aquí mas documentación sobre Azure Maps.

Y el código completo de esta solución en GitHub.