Las consultas SQL++ pueden acceder a los datos almacenados en tu cluster Couchbase de varias maneras. Hay situaciones en las que tener la lógica de negocio como parte de tus consultas de datos también puede ser beneficioso. SQL++ soporta esto con Funciones Definidas por el Usuario (UDFs) que han estado disponibles desde Couchbase 7.0.
En esta entrada de blog, creamos una UDF en JavaScript que consulta puntos a partir de la ubicación de un usuario, de forma dinámica, utilizando SQL++. Además, realizamos los mismos cálculos de distancia con una UDF en Python dentro de la aplicación Servicio de análisis.
Nuestro caso de consulta geoespacial
Nuestra aplicación generará puntos geográficos de interés a partir de nuestra base de datos que estén cerca de la ubicación GPS de un usuario, de forma similar a servicios como Google Maps, que se muestra en la captura de pantalla siguiente. Para este ejemplo, utilizaremos la función conjunto de datos de muestras de viajes que está disponible en un bucket de ejemplo proporcionado por Couchbase.
En particular, nos interesa ver la hitos y aeropuertos alrededor de la ubicación actual del usuario. Esto no puede conseguirse utilizando directamente una consulta SQL++, ya que el cálculo de la distancia se basa en la ubicación geográfica en tiempo real del usuario. SQL++ admite la definición de UDF en JavaScript para realizar lógica personalizada en las consultas.
Cálculo de distancias a partir de coordenadas GPS
Hay muchas formas de calcular la distancia entre dos conjuntos de coordenadas GPS. En este ejemplo, calcularemos la distancia utilizando la función Fórmula Haversine. Proporciona la distancia aproximada entre dos coordenadas GPS considerando la trayectoria como una esfera en lugar de una distancia en línea recta.
En este ejemplo se muestra código JavaScript para calcular distancias geográficas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
función degreesToRadians(grados) { devolver grados * Matemáticas.PI / 180; } función distanciaEnKmEntreCoordenadasTierra(lat1, lon1, lat2, lon2) { var earthRadiusKm = 6371; var dLat = degreesToRadians(lat2-lat1); var dLon = degreesToRadians(lon2-lon1); lat1 = degreesToRadians(lat1); lat2 = degreesToRadians(lat2); var a = Matemáticas.sin(dLat/2) * Matemáticas.sin(dLat/2) + Matemáticas.sin(dLon/2) * Matemáticas.sin(dLon/2) * Matemáticas.cos(lat1) * Matemáticas.cos(lat2); var c = 2 * Matemáticas.atan2(Matemáticas.sqrt(a), Matemáticas.sqrt(1-a)); devolver earthRadiusKm * c; } |
Definimos dos funciones JavaScript: una que realiza la conversión entre grados y radianes y otra que calcula la distancia en kilómetros entre las coordenadas GPS de la otra función.
Importación de UDFs a Couchbase
Estas funciones JavaScript pueden importarse ahora a Couchbase utilizando la función API RESTcomo se muestra a continuación con el rizo mando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
rizo -v -X POST http://localhost:8093/evaluador/v1/bibliotecas/matemáticas -u <usuario>:<contraseña> -d 'función degreesToRadians(grados) { devolver grados * Matemáticas.PI / 180; } función distanciaEnKmEntreCoordenadasTierra(lat1, lon1, lat2, lon2) { var earthRadiusKm = 6371; var dLat = degreesToRadians(lat2-lat1); var dLon = degreesToRadians(lon2-lon1); lat1 = degreesToRadians(lat1); lat2 = degreesToRadians(lat2); var a = Matemáticas.sin(dLat/2) * Matemáticas.sin(dLat/2) + Matemáticas.sin(dLon/2) * Matemáticas.sin(dLon/2) * Matemáticas.cos(lat1) * Matemáticas.cos(lat2); var c = 2 * Matemáticas.atan2(Matemáticas.sqrt(a), Matemáticas.sqrt(1-a)); devolver earthRadiusKm * c; }' |
Después de este paso, la UDF se puede definir en la consola web de Editor de consultas:
1 2 3 |
CREAR FUNCIÓN degreesToRadians(a) IDIOMA JAVASCRIPT AS "degreesToRadians" EN "matemáticas" CREAR FUNCIÓN distanciaEnKmEntreCoordenadasTierra(lat1, lon1, lat2, lon2) IDIOMA JAVASCRIPT AS "distanciaEnKmEntreCoordenadasTierra" EN "matemáticas" |
Aquí, el matemáticas hace referencia al código JavaScript biblioteca que creamos para evaluar la UDF.
La UDF puede probarse ahora con coordenadas GPS de muestra en SQL++ utilizando la función Ejecute Función como se muestra a continuación
1 |
EJECUTAR FUNCIÓN distanciaEnKmEntreCoordenadasTierra(51.5, 0, 38.8, -77.1) |
Podemos observar que la función funciona según lo previsto cuando proporcionamos manualmente las coordenadas GPS a la función.
Conexión de la UDF a los datos de Couchbase
En el viaje-muestra de datos, tenemos las coordenadas GPS del hitos y aeropuertos junto con otros lugares de interés como hoteles.
Podemos integrarlas en nuestras consultas como la siguiente:
1 2 3 4 5 6 |
SELECCIONE distanciaEnKmEntreCoordenadasTierra(a.geo.lat, a.geo.lon, 51.509865, -0.118092) AS distancia, a.nombre del aeropuerto, a.ciudad DESDE `viaje-muestra`.inventario.aeropuerto a PEDIR POR distancia ASC LÍMITE 10; |
Esta consulta devuelve una lista de los diez aeropuertos más cercanos a la ubicación del usuario (51.509865, -0.118092). Proporcionamos la latitud (a.geo.lat) y la longitud (a.geo.lon) campos incrustados en los documentos utilizando la potencia de SQL++.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
[ { "nombredeaeropuerto": "Todos los aeropuertos, "ciudad": "Londres", "distancia": 0.6998675034052988 }, { "nombredeaeropuerto": "Waterloo Internacional", "ciudad": "Londres", "distancia": 0.7880158040048914 }, { "nombredeaeropuerto": "Londres St Pancras", "ciudad": "Londres", "distancia": 2.289359875405007 }, { "nombredeaeropuerto": "Estación de Euston", "ciudad": "Londres", "distancia": 2.30782110865356 }, { "nombredeaeropuerto": "Estación de tren de St Pancras", "ciudad": "Londres", "distancia": 2.582290289682563 }, { "nombredeaeropuerto": "Estación Paddington", "ciudad": "Londres", "distancia": 4.069442660124984 }, { "nombredeaeropuerto": "Helipuerto de Londres", "ciudad": "Londres", "distancia": 6.062824964656381 }, { "nombredeaeropuerto": "Elstree", "ciudad": "Elstree", "distancia": 8.735152174563803 }, { "nombredeaeropuerto": "Ciudad", "ciudad": "Londres", "distancia": 12.009592036043564 }, { "nombredeaeropuerto": "Londres - Kings Cross", "ciudad": "Londres", "distancia": 16.891716659500467 } ] |
Del mismo modo, podemos calcular los puntos de interés alrededor del usuario utilizando la función hito recogida a una distancia cada vez mayor del usuario:
1 2 3 4 5 6 7 |
SELECCIONE distanciaEnKmEntreCoordenadasTierra(l.geo.lat, l.geo.lon, 51.509865, -0.118092) AS distancia, l.actividad, l.ciudad, l.contenido DESDE `viaje-muestra`.inventario.hito l PEDIR POR distancia ASC LÍMITE 10; |
Resultados de la consulta mostrando puntos de referencia cercanos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
[ { "actividad": "ver", "ciudad": "Londres", "contenido": "Somerset House alberga tres galerías de arte: El excepcional '''Instituto Courtauld''' exhibe una colección de arte de los siglos XIX y XX, que incluye conocidas obras de Degas, Matisse y Kandinsky; La Colección Gilbert presenta una colección de arte decorativo; y Las Salas del Hermitage, la más reciente incorporación a Somerset House, acoge exposiciones temporales de obras prestadas por el Museo del Hermitage de San Petersburgo. El patio central se llena de fuentes en verano, pero en invierno se instala una pista de hielo, muy popular, por lo que los visitantes deben reservar con antelación, o esperar mucho tiempo para patinar"., "distancia": 0.10940067520415872 }, { "actividad": "ver", "ciudad": "Londres", "contenido": "La Aguja de Cleopatra tiene su origen en la antigua ciudad [[egipcia]]ia de [[El Cairo/Heliópolis|Heliópolis]], en el templo de Atum, pero los romanos la trasladaron a [[Alejandría]] en el año 12 a.C.". En 1819, el virrey Mehemet Ali regaló la Aguja de Cleopatra a los británicos, en conmemoración de las victorias militares en Egipto, pero permaneció en Alejandría hasta 1877, cuando se organizó el transporte para llevarla a Londres. En el viaje, el barco zozobró en una tormenta, matando a seis miembros de la tripulación. Se creyó que la Aguja de Cleopatra se había perdido, pero unos arrastreros españoles la encontraron a flote unos días después y, tras algunas reparaciones, llegó a Londres el 21 de enero de 1878. El obelisco está flanqueado por dos falsas esfinges, que muestran los efectos de los bombardeos de Londres durante la II Guerra Mundial. Hoy, la Aguja de Cleopatra muestra cierto desgaste por la exposición al clima húmedo de Londres"., "distancia": 0.2153782246329736 }, { "actividad": "comprar", "ciudad": "Londres", "contenido": "Venta diaria de libros de segunda mano cerca de la orilla del Támesis. Un lugar agradable para curiosear libros (clásicos y modernos), mapas y grabados"., "distancia": 0.329776385402355 }, { "actividad": "ver", "ciudad": "Londres", "contenido": "[[Londres]] (en [[London/Covent Garden|Covent Garden]])", "distancia": 0.34889537479151833 }, { "actividad": "ver", "ciudad": "Londres", "contenido": "La zona de skate más famosa y popular de Londres, situada en parte bajo el Queen Elizabeth Hall, a lo largo de Queen's Walk y el Támesis. También es popular entre grafiteros, BMXers y demás"., "distancia": 0.36487940944981834 }, { "actividad": "ver", "ciudad": "Londres", "contenido": "Escondido bajo el puente de Waterloo, el BFI Southbank, antes conocido como National Film Theatre, se presenta como el hogar del cine y cuenta con tres pantallas en las que se proyectan películas clásicas (incluso mudas), extranjeras y menos convencionales. Recientemente remodelado, cuenta con una nueva entrada por Theatre Alley, una tienda dedicada al cine, un espacio de exposiciones interactivas y un excelente bar-restaurante en la parte trasera. Los visitantes también pueden acceder a la Mediateca, donde podrán recorrer gratuitamente el extenso archivo de cine y televisión del BFI. Bar y restaurante geniales. Las entradas suelen estar disponibles en el momento"., "distancia": 0.378692262237853 }, { "actividad": "ver", "ciudad": "Londres", "contenido": "Sala de conciertos con actuaciones diarias. | image=Sala Queen Elizabeth.jpg", "distancia": 0.3859430181613397 }, { "actividad": "beber", "ciudad": "Londres", "contenido": "El antídoto a los bares gay: ambiente de pub y buena música. | image=El Bar Retro.jpg", "distancia": 0.39732030942983415 }, { "actividad": "ver", "ciudad": "Londres", "contenido": "Tres grandes auditorios, el Olivier, el Lyttelton y el Cottesloe. El teatro Olivier es el más grande, con un escenario abierto y un auditorio en forma de abanico a su alrededor. De este modo, todos los asientos ofrecen una buena visión. Aquí se representan la mayoría de las producciones más "populares", ya que el espacio es mucho mayor que en la mayoría de los teatros. El teatro Lyttelton es más tradicional, con un arco procenium con buenas vistas desde la mayoría de los asientos. El Cottesloe es un pequeño estudio con capacidad para unas 400 personas. Algunas entradas estarán disponibles el mismo día de la función, ya sean localidades de día (hay que llegar antes de las 9.30 y hacer cola) o de espera (hay que llegar antes de las 18.00 y hacer cola), o se pueden comprar por Internet. Los espectáculos más populares, especialmente los que se representan en Navidad en el Olivier, se agotan con meses de antelación. Las entradas para el National Theatre suelen ser más baratas que para la mayoría de los teatros. También existe la temporada "£10 Travelex" en verano, que ofrece un gran número de entradas (más de 100.000 al año) a 10 libras. Es necesario reservar con antelación. En el vestíbulo del teatro Lyttelton también hay un espacio de exposiciones que suele albergar populares muestras fotográficas. En el vestíbulo de la planta baja se suele tocar jazz gratis por la noche. En verano hay un festival diario gratuito al aire libre de actuaciones, música, comedia y cabaret conocido como Watch This Space. Hay tumbonas (y césped artificial) a disposición de los espectadores. Exposiciones gratuitas. Las visitas entre bastidores cuestan 5 libras (http://www.nationaltheatrelondon.com/tickets/)"., "distancia": 0.42625112040817054 }, { "actividad": "beber", "ciudad": "Londres", "contenido": "Eventos musicales nocturnos gratuitos. El mejor lugar para probar electro underground, indie, dub-step y mucho más"., "distancia": 0.4323026974543284 } ] |
Si ejecutamos estas consultas para varios usuarios al mismo tiempo, podríamos encontrarnos con problemas de rendimiento, ya que estamos utilizando los recursos informáticos que forman parte del clúster de Couchbase.
En tales escenarios, Couchbase Analytics podría reducir el impacto en tu cluster. Couchbase Analytics está diseñado para ejecutar eficientemente consultas complejas sobre muchos registros. Las consultas complejas pueden incluir grandes operaciones ad hoc de unión, conjunto, agregación y agrupación, cualquiera de las cuales puede dar lugar a consultas de larga duración, alto uso de CPU, alto consumo de memoria o latencia de red excesiva debido a la obtención de datos y la coordinación entre nodos.
Funciones definidas por el usuario con Couchbase Analytics
Couchbase Analytics nos permite definir y utilizar Funciones definidas por el usuario en Python pero en el momento de redactar este documento requiere que activar la vista previa para desarrolladores en Couchbase Server. Esto se puede hacer con el siguiente comando:
1 2 |
$ /op/couchbase/papelera/couchbase-cli active-desarrollador-previsualizar \ --active -c localhost:8091 -u <nombre de usuario> -p <contraseña> |
El siguiente paso es crear un paquete Python, localmente, en el entorno de desarrollo con el UDF de Python. En este caso, el UDF es un método para calcular la distancia entre dos coordenadas GPS.
1 2 3 4 5 6 7 |
# distancia.py de geopy importar distancia clase cálculo_distancia: def calcular_distancia(auto, lat1, lon1, lat2, lon2) -> float: """Calcular distancia usando distancia geodésica"""" devolver distancia.distancia((lat1, lon1), (lat2, lon2)).km |
Aquí calculamos el distancia geodésica (la distancia más corta entre puntos a lo largo de una trayectoria curva) entre las dos coordenadas GPS con la ayuda de una biblioteca, geopy.
Empaquetado de la UDF
Para empaquetar la biblioteca, utilizamos un paquete shiv que puede empaquetar el código junto con sus requisitos para cualquier plataforma. Aquí, estamos utilizando Linux como Couchbase Server se ejecuta en un entorno Linux dentro de Docker.
1 |
shiv -o distancia.pyz --sitio-paquetes . --plataforma manylinux2010_x86_64 --python-versión 39 --sólo-binario=:todos: geopy |
Para subir este paquete binario con el UDF a Couchbase, tenemos que utilizar el comando API REST para el servicio Analytics.
1 |
rizo -X POST -u <nombre de usuario>:<contraseña> -F "type=python" -F "data=@./distance.pyz" localhost:8095/análisis/biblioteca/Por defecto/pylib |
Esto carga el UDF empaquetado en el archivo pylib biblioteca en el por defecto alcance (anteriormente universo de datos) del entorno Analytics. Ahora podemos definir la UDF en el Workbench de análisis.
Esta definición indica que estamos definiendo una UDF llamada distancia_en_km que puede llamarse desde la función Python calcular_distancia definido en la clase cálculo_distancia dentro del módulo Python distancia.
1 2 |
CREAR ANÁLISIS FUNCIÓN distancia_en_km(lat1, lon1, lat2, lon2) AS "distancia", "cálculo_distancia.calcular_distancia" EN pylib; |
Ahora podemos utilizar la UDF en nuestras consultas de Analytics del mismo modo que utilizamos la UDF en nuestras consultas de SQL++.
1 2 3 4 5 6 7 8 |
SELECCIONE distancia_en_km(51.5, 0, 38.8, -77.1) AS "distancia" RESULTADOS: [ { "distancia": 5933.5299530300545 } ] |
Asignación de datos del servicio de datos al servicio de análisis
Para consultar los datos en el Servicio de Datos desde el Servicio de Análisis, necesitamos mapa el viaje-muestra en Analytics que crea una copia sombra en tiempo real de los datos en el servicio de datos de Analytics. Para este ejemplo, necesitamos asignar las colecciones con datos geográficos, a saber, el hito, aeropuerto y hotel colecciones del ámbito del inventario en el viaje-muestra cubo.
Ejecución de la UDF de análisis con datos de Couchbase
Ahora, podemos ejecutar las mismas consultas que ejecutábamos antes en SQL++ pero con el Servicio Analytics. Sólo ha cambiado el nombre de la UDF. El resto de la interfaz es similar a la que teníamos con las consultas SQL++. Los resultados también serán similares a los anteriores.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECCIONE distancia_en_km(a.geo.lat, a.geo.lon, 51.509865, -0.118092) AS distancia, a.nombre del aeropuerto, a.ciudad DESDE `viaje-muestra`.inventario.aeropuerto a PEDIR POR distancia ASC LÍMITE 10; SELECCIONE distancia_en_km(l.geo.lat, l.geo.lon, 51.509865, -0.118092) AS distancia, l.actividad, l.ciudad, l.contenido DESDE `viaje-muestra`.inventario.hito l PEDIR POR distancia ASC LÍMITE 10; |
Este enfoque es adecuado cuando queremos ejecutar estas consultas sin afectar al Servicio de datos que se utiliza habitualmente para nuestros datos transaccionales. Los datos se sincronizan entre Datos y el Servicio de análisis internamente en tiempo real en Couchbase.
Resumen
En esta entrada de blog, has aprendido a crear una Función Definida por el Usuario (UDF) en JavaScript que calcula distancias entre dos coordenadas GPS. Has visto cómo importar la UDF a Couchbase y luego integrarla en una consulta SQL++ para potenciar aplicaciones que podrían proporcionar puntos de interés alrededor de un usuario. También vimos cómo puedes realizar el mismo cálculo de distancia en un UDF basado en Python usando el servicio Analytics para reducir el impacto en tu cluster transaccional de Couchbase.
Para más información y referencias, consulte los siguientes recursos: