As consultas SQL++ podem acessar os dados armazenados no cluster do Couchbase de várias maneiras. Há situações em que ter a lógica de negócios como parte de suas consultas de dados também pode ser benéfico. O SQL++ oferece suporte a isso com as Funções Definidas pelo Usuário (UDFs) que estão disponíveis desde Couchbase 7.0.
Nesta postagem do blog, criamos uma UDF em JavaScript que consulta pontos a partir da localização de um usuário, dinamicamente, usando SQL++. Além disso, realizamos os mesmos cálculos de distância com uma UDF em Python dentro do Serviço de análise.
Nosso caso de uso de consulta geoespacial
Nosso aplicativo gerará pontos de interesse geográfico a partir do nosso banco de dados que estão próximos à localização GPS do usuário, semelhante a serviços como o Google Maps, mostrado na captura de tela abaixo. Para este exemplo, usaremos o conjunto de dados de amostra de viagens que está disponível em um bucket de amostra fornecido pelo Couchbase.
Em particular, estamos interessados em ver o marcos históricos e aeroportos em torno da localização atual do usuário. Isso não pode ser feito usando uma consulta SQL++ diretamente, pois o cálculo da distância é baseado na localização geográfica em tempo real do usuário. O SQL++ suporta a definição de UDFs em JavaScript para executar a lógica personalizada nas consultas.

Cálculo de distâncias a partir de coordenadas de GPS
Há muitas maneiras de calcular a distância entre dois conjuntos de coordenadas de GPS. Neste exemplo, calcularemos a distância usando a função Fórmula Haversine. Ele fornece a distância aproximada entre duas coordenadas de GPS, considerando o caminho como uma esfera em vez de uma distância em linha reta.
O código JavaScript para calcular distâncias geográficas é mostrado neste exemplo:
|
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 duas funções JavaScript - uma que realiza a conversão entre graus e radianos e outra que calcula a distância em quilômetros entre as coordenadas de GPS da outra função.
Importação de UDFs para o Couchbase
Essas funções JavaScript agora podem ser importadas para o Couchbase usando o API RESTcomo mostrado abaixo, com o enrolar comando:
|
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; }' |
Após essa etapa, o UDF pode ser definido na seção 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" |
Aqui, o matemática referência é para o JavaScript biblioteca que criamos para avaliar o UDF.
O UDF agora pode ser testado com coordenadas de GPS de amostra no SQL++ usando o Executar Função conforme mostrado abaixo
|
1 |
EXECUTE FUNCTION distanceInKmBetweenEarthCoordinates(51.5, 0, 38.8, -77.1) |
Podemos observar que a função funciona como pretendido quando fornecemos as coordenadas de GPS manualmente para a função.
Conectando o UDF aos dados do Couchbase
No amostra de viagem conjunto de dados, temos as coordenadas de GPS do marcos históricos e aeroportos juntamente com outros locais de interesse, como hotéis.
Podemos integrá-los em nossas consultas, como a que está abaixo:
|
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; |
Essa consulta retorna uma lista dos dez aeroportos mais próximos da localização do usuário (51.509865, -0.118092). Fornecemos a latitude (a.geo.lat) e longitude (a.geo.lon) que estão incorporados nos documentos usando o poder do 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 } ] |
Da mesma forma, podemos calcular os pontos de interesse ao redor do usuário usando a função marco histórico em uma distância cada vez maior do usuário:
|
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 da consulta mostrando pontos de referência próximos:
|
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 } ] |
Se executarmos essas consultas para vários usuários ao mesmo tempo, poderemos ter problemas de desempenho, pois estamos usando os recursos de computação que fazem parte do cluster do Couchbase.
Nesses cenários, o Couchbase Analytics pode reduzir o impacto em seu cluster. O Couchbase Analytics foi projetado para executar com eficiência consultas complexas em muitos registros. As consultas complexas podem incluir grandes operações ad hoc de junção, conjunto, agregação e agrupamento - qualquer uma delas pode resultar em consultas de longa duração, alto uso da CPU, alto consumo de memória ou latência excessiva da rede devido à busca de dados e à coordenação entre nós.
Funções definidas pelo usuário com o Couchbase Analytics
O Couchbase Analytics nos permite definir e usar Funções definidas pelo usuário em Python mas, no momento da redação deste documento, exige que você ativar o Developer Preview no Couchbase Server. Isso pode ser feito com o seguinte comando:
|
1 2 |
$ /opt/couchbase/bin/couchbase-cli enable-developer-preview \ --enable -c localhost:8091 -u <username> -p <password> |
A próxima etapa é criar um pacote Python, localmente, no ambiente de desenvolvimento com o Python UDF. Nesse caso, o UDF é um método para calcular a distância entre duas coordenadas de 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 |
Aqui, calculamos o distância geodésica (a distância mais curta entre pontos ao longo de um caminho curvo) entre as duas coordenadas de GPS com a ajuda de uma biblioteca, geopia.
Empacotamento do UDF
Para empacotar a biblioteca, usamos um pacote shiv que pode empacotar o código junto com seus requisitos para qualquer plataforma. Aqui, estamos usando o Linux, pois o Couchbase Server está sendo executado em um ambiente Linux dentro do Docker.
|
1 |
shiv -o distance.pyz --site-packages . --platform manylinux2010_x86_64 --python-version 39 --only-binary=:all: geopy |
Para fazer upload desse pacote binário com o UDF para o Couchbase, precisamos usar o comando API REST para o serviço do Analytics.
|
1 |
curl -X POST -u <username>:<password> -F "type=python" -F "data=@./distance.pyz" localhost:8095/analytics/library/Default/pylib |
Isso faz o upload do UDF empacotado para o pylib na biblioteca padrão escopo (anteriormente universo de dados) do ambiente do Analytics. Agora podemos definir o UDF na seção Workbench de análise.
Essa declaração de definição indica que estamos definindo um UDF chamado distância_em_km que pode ser chamado a partir da função Python calculate_distance definido na classe cálculo de distância dentro do módulo Python distância.
|
1 2 |
CREATE ANALYTICS FUNCTION distance_in_km(lat1, lon1, lat2, lon2) AS "distance", "distance_calculation.calculate_distance" AT pylib; |
Agora podemos usar o UDF em nossas consultas do Analytics da mesma forma que usamos o UDF em nossas consultas do 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 } ] |
Mapeamento de dados do Data Service no Analytics Service
Para consultar os dados no Data Service a partir do Analytics Service, precisamos mapa o amostra de viagem coleções de dados no Analytics que cria uma cópia sombra em tempo real dos dados no Data Service no Analytics. Para este exemplo, precisamos mapear as coleções com dados geográficos, ou seja, o ponto de referência, aeroporto e hotel coleções do escopo do inventário no amostra de viagem balde.
Execução do UDF do Analytics nos dados do Couchbase
Agora, podemos executar as mesmas consultas que estávamos executando antes no SQL++, mas com o Analytics Service. Apenas o nome do UDF foi alterado. O restante da interface é semelhante ao que tínhamos com as consultas do SQL++. Os resultados também serão semelhantes aos resultados 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; |
Essa abordagem é adequada quando queremos executar essas consultas sem afetar o Serviço de dados que é comumente usado para nossos dados transacionais. Os dados são sincronizados entre o Dados e o Serviço de análise internamente em tempo real no Couchbase.
Resumo
Nesta postagem do blog, você aprendeu a criar uma função definida pelo usuário (UDF) em JavaScript que calcula as distâncias entre duas coordenadas de GPS. Você viu como importar a UDF para o Couchbase e, em seguida, integrá-la a uma consulta SQL++ para alimentar aplicativos que poderiam fornecer pontos de interesse em torno de um usuário. Também mostramos como você pode executar o mesmo cálculo de distância em um UDF baseado em Python usando o serviço Analytics para reduzir o impacto no cluster transacional do Couchbase.
Para leitura e referência adicionais, consulte os seguintes recursos: