Cuando pensamos en geolocalización en nuestras aplicaciones es casi seguro que la mayoría asumimos que estamos hablando exclusivamente de la API del mapa de Google. Y en la mayoría de situaciones estaremos en lo correcto, es por mucho la herramienta de cajón que utilizamos para desplegar un mapa y colocar distintos marcadores en puntos específicos que van acorde a la información que nuestro aplicativo desea transmitir al usuario final.

Uno de los proyectos que he trabajado durante los últimos 3 años hace uso intensivo de la API de JavaScript de Google Maps para desplegar marcadores que representan luces de alumbrado público sobre regiones muy específicas y que a su vez permiten realizar un manejo remoto de las mismas según las necesidades de iluminación de cada punto específico.

Visualización de un área con marcadores que representan el alumbrado público instalado en un lugar determinado.

Detrás de esta visualización existe un backend que retorna un dataset con toda la información relacionada a las lámparas que se deben desplegar en el mapa. Esto incluye por supuesto las coordenadas de geolocalización que se utiliza para ubicar cada una en el mapa. Este approach no tiene nada de malo, y ha funcionado sin mayores inconvenientes por varios años; sin embargo con el pasar del tiempo instalaciones que empezaron con unos cuantos cientos de lámparas crecieron a decenas de miles de lámparas, lo que hace que cargar áreas geográficas con una densidad alta de marcadores haga el proceso de visualizar el mapa una tarea que consume más tiempo y recursos.

Luego de analizar varias soluciones posibles, se optó por cambiar este esquema y dejar que el mapa solicite al backend únicamente las lámparas cuya geolocalización coincida con la visualización actual del mapa. El problema acá era cómo determinar qué lámparas están dentro de un cuadrante específico sin recuperar el dataset completo primero y analizar la geolocalización lámpara por lámpara. Es acá donde entra SQL Server y una función poco conocida pero que facilita muchísimo el poder plantear escenarios tales como el acá descrito.

SQL Server en sus versiones actuales, incluyendo las versiones de Azure, permiten realizar funciones de geografía dentro de los mismos queries y stored procedures. Para el caso muy puntual que nos ocupa acá, era necesario enviar únicamente un polígono de coordenadas que corresponde al área del mapa que se está visualizando y que el backend realice el query que devuelva únicamente las lámparas que intersectan en algún punto el área de dicho polígono. Dibujar el área del polígono no es más que obtener las cuatro esquinas de la visualización del mapa (tema para otro día) y una vez obtenido dicho dato se le envía a la petición del backend. Una vez el backend cuenta con el área que se está evaluando, se arma un query de la siguiente manera:

SELECT 
    *
FROM 
    fixtures
WHERE 
    GEOGRAPHY::STPolyFromText('POLYGON(( -90.57266722642305 14.517044066094487, -90.58609704696062 14.517044066094487, -90.58609704696062 14.50803896274724, -90.57266722642305 14.50803896274724, -90.57266722642305 14.517044066094487 ))', 4326).STIntersects(GEOGRAPHY::Point(latitude, longitude, 4326)) = 1;

Este pequeño bloque de código tiene mucha tela que cortar. Tenemos una hipotética tabla llamada fixtures que contiene todas las lámparas que existen en nuestra base de datos. A esta tabla le estamos haciendo un query donde la condición WHERE arma el área de un polígono mediante la función STPolyFromText. Esta función toma un string que contiene los puntos geográficos a partir de los cuales se arma un polígono (lo mencionado en el párrafo anterior) e inmediatamente luego de armado utilizamos la función STIntersects sobre dicha área. Como parámetro de esta última función utilizamos el tipo de dato Point siempre del tipo GEOGRAPHY a partir de la latitud y longitud de cada lámpara y esta función devolverá 1 ó 0 si su geolocalización se intersecta en algún punto con el área de polígono armado.

Registros que se intersectan en algún punto con el área de un polígono

Estas funciones de geolocalización a nivel de base de datos permiten integrar directamente en nuestros queries escenarios donde ya no es necesario descargar un dataset completo al browser para luego parsear mediante la API de un mapa de Google para determinar si una geolocalización cae dentro de un área o no. De esta manera logramos que nuestro frontend pueda recuperar de manera progresiva y dinámica sólo los marcadores que se deben desplegar en todo momento, liberando recursos de red y de memoria al ya no mantener en todo momento un dataset completo que no se visualiza en todo momento.