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 |
function degreesToRadians(degrees) { return degrees * Math.PI / 180; } function distanceInKmBetweenEarthCoordinates(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 = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return 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 |
curl -v -X POST https://localhost:8093/evaluator/v1/libraries/math -u <user>:<password> -d 'function degreesToRadians(degrees) { return degrees * Math.PI / 180; } function distanceInKmBetweenEarthCoordinates(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 = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return earthRadiusKm * c; }' |
Después de este paso, la UDF se puede definir en la consola web de Editor de consultas:
|
1 2 3 |
CREATE FUNCTION degreesToRadians(a) LANGUAGE JAVASCRIPT AS "degreesToRadians" AT "math" CREATE FUNCTION distanceInKmBetweenEarthCoordinates(lat1, lon1, lat2, lon2) LANGUAGE JAVASCRIPT AS "distanceInKmBetweenEarthCoordinates" AT "math" |
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 |
EXECUTE FUNCTION distanceInKmBetweenEarthCoordinates(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 |
SELECT distanceInKmBetweenEarthCoordinates(a.geo.lat, a.geo.lon, 51.509865, -0.118092) AS distance, a.airportname, a.city FROM `travel-sample`.inventory.airport a ORDER BY distance ASC LIMIT 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 |
[ { "airportname": "All Airports", "city": "London", "distance": 0.6998675034052988 }, { "airportname": "Waterloo International", "city": "London", "distance": 0.7880158040048914 }, { "airportname": "London St Pancras", "city": "London", "distance": 2.289359875405007 }, { "airportname": "Euston Station", "city": "London", "distance": 2.30782110865356 }, { "airportname": "St Pancras Railway Station", "city": "London", "distance": 2.582290289682563 }, { "airportname": "Paddington Station", "city": "London", "distance": 4.069442660124984 }, { "airportname": "London Heliport", "city": "London", "distance": 6.062824964656381 }, { "airportname": "Elstree", "city": "Elstree", "distance": 8.735152174563803 }, { "airportname": "City", "city": "London", "distance": 12.009592036043564 }, { "airportname": "London - Kings Cross", "city": "London", "distance": 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 |
SELECT distanceInKmBetweenEarthCoordinates(l.geo.lat, l.geo.lon, 51.509865, -0.118092) AS distance, l.activity, l.city, l.content FROM `travel-sample`.inventory.landmark l ORDER BY distance ASC LIMIT 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 |
[ { "activity": "see", "city": "London", "content": "Somerset House is home to three art galleries: The exceptional '''Courtauld Institute''' displays a collection of 19th and 20th century art, including well-known works by Degas, Matisse and Kandinsky; The Gilbert Collection presents a collection of decorative art; and The Hermitage Rooms, the most recent addition to Somerset House, hosts temporary exhibitions of works on loan from the Hermitage Museum in Saint Petersburg. The central courtyard is filled with fountains in the Summer, but in the Winter, an ice rink is installed, it is very popular, so visitors should book in advance, or expect to wait a long time to skate.", "distance": 0.10940067520415872 }, { "activity": "see", "city": "London", "content": "Cleopatra's Needle originated in the ancient [[Egypt]]ian city of [[Cairo/Heliopolis|Heliopolis]], in the Temple of Atum, but the Romans moved it to [[Alexandria]] in 12 BC. In 1819, viceroy Mehemet Ali presented Cleopatra's Needle to the British, commemorating military victories in Egypt, but it remained in Alexandria until 1877 when transportation was arranged to bring it to London. On the voyage, the ship capsized in a storm, killing six crewmembers. Cleopatra's Needle was thought to be lost, but Spanish trawlers found it afloat a few days later, and after some repairs, it arrived in London on 21 Jan 1878. The obelisk is flanked by two faux-sphinxes, which show the effects of bombings of London during World War II. Today, Cleopatra's Needle shows some wear from exposure to London's damp weather.", "distance": 0.2153782246329736 }, { "activity": "buy", "city": "London", "content": "Daily second-hand book sale near the bank of the Thames. A nice place to just browse for books (classic and modern), maps and prints.", "distance": 0.329776385402355 }, { "activity": "see", "city": "London", "content": "[[London]] (in [[London/Covent Garden|Covent Garden]])", "distance": 0.34889537479151833 }, { "activity": "see", "city": "London", "content": "London's most famous and popular skateboarding area, situated partly underneath Queen Elizabeth Hall along Queen's Walk and the Thames. Also popular with graffiti artists, BMXers and so forth.", "distance": 0.36487940944981834 }, { "activity": "see", "city": "London", "content": "Tucked under Waterloo Bridge, BFI Southbank, formerly known as the National Film Theatre, pitches itself as the home of film and has three screens showing classic (including silent), foreign language and less mainstream films. Recently redeveloped, it now has a new entrance on Theatre Alley, a shop dedicated to film, an interactive exhibition space and an excellent bar/restaurant at the back. Visitors can also access the Mediatheque - wind your way through the BFI's extensive film and TV archive for free. Cool bar and restaurant. Tickets are generally available on the spur of the moment.", "distance": 0.378692262237853 }, { "activity": "see", "city": "London", "content": "Music venue hosting daily performances. | image=Queen Elizabeth Hall.jpg", "distance": 0.3859430181613397 }, { "activity": "drink", "city": "London", "content": "The antidote to gay bars: a pub-like atmosphere and great music. | image=The Retro Bar.jpg", "distance": 0.39732030942983415 }, { "activity": "see", "city": "London", "content": "Three large auditoriums, the Olivier, the Lyttelton and the Cottesloe. The Olivier theatre is the largest with an open stage and a fan shaped auditorium around it. This ensures that all seats provide a good view. Most of the more 'popular' productions are put on here as the space provided is much larger than most theatres. The Lyttelton theatre is more traditional with a procenium arc with good views from most seats. The Cottesloe is a small studio theatre, seating around 400. Some tickets will be available on the day, either day seats (arrive before 09:30 and queue) or standby (arrive before 6PM and queue), or you can buy online. Popular shows, especially those around Christmas in the Olivier sell out months in advance. Tickets to The National Theatre are generally better value than most other theatres. There is also the '£10 Travelex' season in the summer that provides a large number (over 100,000 seats a year) at £10. Booking in advance is required for these. There is also an exhibition space in the Lyttelton theatre foyer that frequently holds popular photographic exhibitions. Free jazz is often played in the evening in the ground floor foyer. During summer there is a free daily outdoor festival of performance, music, comedy and cabaret known as Watch This Space. Deckchairs (and artificial grass) are provided to watch on. Free exhibitions. Backstage tours £5. (https://www.nationaltheatrelondon.com/tickets/)", "distance": 0.42625112040817054 }, { "activity": "drink", "city": "London", "content": "Free nightly music events. The best place to sample underground electro, indie, dub-step and more.", "distance": 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 |
$ /opt/couchbase/bin/couchbase-cli enable-developer-preview \ --enable -c localhost:8091 -u <username> -p <password> |
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 |
# distance.py from geopy import distance class distance_calculation: def calculate_distance(self, lat1, lon1, lat2, lon2) -> float: """Calculate Distance using geodesic distance""" return distance.distance((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 distance.pyz --site-packages . --platform manylinux2010_x86_64 --python-version 39 --only-binary=:all: geopy |
Para subir este paquete binario con el UDF a Couchbase, tenemos que utilizar el comando API REST para el servicio Analytics.
|
1 |
curl -X POST -u <username>:<password> -F "type=python" -F "data=@./distance.pyz" localhost:8095/analytics/library/Default/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 |
CREATE ANALYTICS FUNCTION distance_in_km(lat1, lon1, lat2, lon2) AS "distance", "distance_calculation.calculate_distance" AT 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 |
SELECT distance_in_km(51.5, 0, 38.8, -77.1) AS "distance" RESULTS: [ { "distance": 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 |
SELECT distance_in_km(a.geo.lat, a.geo.lon, 51.509865, -0.118092) AS distance, a.airportname, a.city FROM `travel-sample`.inventory.airport a ORDER BY distance ASC LIMIT 10; SELECT distance_in_km(l.geo.lat, l.geo.lon, 51.509865, -0.118092) AS distance, l.activity, l.city, l.content FROM `travel-sample`.inventory.landmark l ORDER BY distance ASC LIMIT 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: