Geoespacial agora é totalmente compatível com o Couchbase Server 5.5. Confira a Anúncio do Couchbase Server 5.5e Faça o download da versão para desenvolvedores gratuitamente agora mesmo.
Neste post, vou demonstrar os recursos geoespaciais do Couchbase Full Text Search criando uma interface de usuário baseada na Web que realiza pesquisas. Sempre que penso em pesquisas geoespaciais, penso no Yelp, que é excelente para me ajudar a encontrar restaurantes em uma área específica.
Portanto, vou me divertir um pouco e criar uma versão bem básica do Yelp, mas apenas para hotéis. Além disso, leia O que são dados geoespaciais? se isso for novo para você.
Se você quiser acompanhar, o O código-fonte completo está disponível no Github.
Preparação
Aqui estão as etapas que segui para criar um novo projeto antes de começar a escrever o código.
- Na linha de comando:
dotnet new aurelia
. Isso pressupõe que você tenha .NET Core instalado. Observe que o Geospatial Search não é um recurso exclusivo do .NET: você pode usá-lo com o outros SDKs do Couchbase como Node.js, Java, etc. Ele também pressupõe que você tenha instalado um Modelo de SPA para o Aurelia. Você também pode usar o Angular ou o React, se quiser, mas eu gosto muito do Auréliae acho que você deveria dar uma chance a ele. - O comando acima criará um shell de um projeto ASP.NET Core. Nesta postagem do blog, não usarei o Razor. Estou usando apenas o ASP.NET como backend para pontos de extremidade da API REST.
npm install aurelia-google-maps
. Você não precisa usar isso, mas o Plug-in aurelia-google-maps facilitará minha interação com o Google Maps em meu aplicativo.- Abri esse projeto no Visual Studio 2017. Adicionei Couchbase.Extensions.DependencyInjection com o NuGet. Você não precisa usar essa extensão, mas ela facilita as coisas.
- Instalei o Couchbase Server 5.5, incluindo o Serviço de pesquisa de texto completo. Configurei o balde para amostras de viagem. I criou um usuário "matt" com acesso total a esse balde.
Criar um índice geoespacial
Antes de criar o backend do ASP.NET, precisamos criar um índice geoespacial no Couchbase Server. Depois de fazer login, clique em "Search" (Pesquisa) no menu (em "Workbench"). Clique em "Add Index" (Adicionar índice) para começar.
Chamei meu índice de "mygeoindex". Selecionei travel-sample como o bucket a ser indexado.
Em "Type Mappings", eu desmarco o padrão. Adiciono um novo mapeamento de tipo com um nome de tipo "hotel". Todo documento de hotel em "travel-sample" tem um tipo com o valor "hotel". Marque a caixa "only index specified fields" (indexar somente campos especificados).
Vou adicionar dois campos secundários. Um deles é "geo", que contém as coordenadas geoespaciais em um documento de hotel. Certifique-se de selecionar "geopoint" como o tipo. O outro é "name", que será o nome do hotel. Optei por "armazenar" cada um deles: isso tornará o índice maior, mas posso evitar uma pesquisa secundária se armazenar as informações no índice.
Observação importante: Há um bug (NCBC-1651) na versão atual do .NET SDK que causará um erro se você tentar ler de um campo de geoponto. Nos exemplos de código, criei uma solução alternativa: Na verdade, não obtenho os campos geo e name do índice de pesquisa. Em vez disso, uso a chave do documento retornada pela pesquisa para fazer uma chamada "get" secundária e obter o documento completo. Lembre-se de que essa ainda é uma técnica que você pode considerar se quiser manter o tamanho do seu índice baixo. Esse bug já foi corrigido e estará em uma versão futura. Esse é o perigo de estar na vanguarda!
Isso é tudo o que há para fazer. Clique em "Create Index" (Criar índice). Observe o "progresso da indexação" na próxima tela até chegar a 100% (não deve demorar muito, supondo que você tenha se lembrado de desmarcar a opção "default").
Pontos de extremidade REST do ASP.NET Core
Em seguida, vamos passar para o ASP.NET. Criarei dois endpoints. Um endpoint demonstrará o caixa delimitadora e o outro demonstrará o método de pesquisa distância método de pesquisa.
Precisarei de um objeto bucket do Couchbase para executar as consultas. Siga as instruções exemplos em minha postagem no blog sobre injeção de dependência ou consulte o código-fonte no Github se você nunca fez isso antes.
Caixa delimitadora
Uma pesquisa de "caixa delimitadora" significa que você define uma caixa em um mapa e deseja pesquisar pontos de interesse que estejam dentro dessa caixa. Você só precisa de dois pontos para definir uma caixa: as coordenadas do canto superior direito e as coordenadas do canto inferior esquerdo. (As coordenadas são a latitude e a longitude).
1 2 3 4 5 6 7 |
público classe BoxSearch { público duplo LatitudeTopLeft { obter; definir; } público duplo LongitudeTopLeft { obter; definir; } público duplo LatitudeBottomRight { obter; definir; } público duplo LongitudeBottomRight { obter; definir; } } |
Para criar uma consulta geoespacial de caixa delimitadora, use a opção GeoBoundingBoxQuery
disponível no SDK do .NET. Farei isso dentro de um método POST com o código acima BoxSearch
como parâmetro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Rota("api/Box")] [HttpPost] público IActionResult Caixa([DeCorpo] BoxSearch caixa) { var consulta = novo GeoBoundingBoxQuery(); consulta.Superior esquerdo(caixa.LongitudeTopLeft, caixa.LatitudeTopLeft); consulta.BottomRight(caixa.LongitudeBottomRight, caixa.LatitudeBottomRight); var searchParams = novo SearchParams() // .Fields("geo", "name") // omitido por causa do bug NCBC-1651 .Limite(10) .Tempo limite(Tempo.FromMilliseconds(10000)); var pesquisa = novo Pesquisa { Consulta = consulta, Índice = "mygeoindex", SearchParams = searchParams }; var resultados = _bucket.Consulta(pesquisa); // ... snip ... |
Tudo o que preciso retornar desse endpoint é uma lista dos resultados: as coordenadas de cada hotel e o nome e a localização do hotel. Eu criei um Resultado da pesquisa geográfica
classe para isso.
1 2 3 4 5 6 7 8 9 10 11 |
público classe Resultado da pesquisa geográfica { público duplo Latitude { obter; definir; } público duplo Longitude { obter; definir; } público Janela de informações Janela de informações { obter; definir; } } público classe Janela de informações { público string Conteúdo { obter; definir; } } |
Criei essa classe para corresponder ao plug-in do Google Maps que usarei posteriormente.
Por fim, usarei essa classe para retornar alguns resultados do ponto de extremidade.
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 |
// ... snip ... var lista = novo Lista<Resultado da pesquisa geográfica>(); antes de (var atingido em resultados.Acertos) { *** esta parte não deveria ser necessária // a localização geográfica e o nome devem vir com os resultados da pesquisa // mas há um bug NCBC-1651 no SDK var doc = _bucket.Obter<dinâmico>(atingido.Id).Valor; // **************** lista.Adicionar(novo Resultado da pesquisa geográfica { Latitude = doc.geo.lat, Longitude = doc.geo.solitário, Janela de informações = novo Janela de informações { Conteúdo = doc.nome + "<br \/>" + doc.cidade + ", " + doc.estado + " " + doc.país } }); } retorno Ok(lista); } |
Pesquisa de distância
Uma pesquisa de "distância" é outra maneira de realizar consultas geoespaciais. Desta vez, em vez de uma caixa, ela será mais parecida com um círculo. Você fornece uma única coordenada e uma distância. A distância será o raio a partir desse ponto.
1 2 3 4 5 6 7 8 |
público classe PointSearch { público duplo Latitude { obter; definir; } público duplo Longitude { obter; definir; } público int Distância { obter; definir; } // milhas estão sendo assumidas como a unidade público string DistanceWithUnits (distância com unidades) => Distância + "mi"; } |
Estou usando milhas como padrão, mas certamente você pode usar quilômetros em vez disso ou apresentar a opção na interface do usuário.
O ponto final será muito semelhante ao ponto final da caixa delimitadora, exceto pelo fato de usar Consulta de distância geográfica
.
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 |
[Rota("api/Point")] [HttpPost] público IActionResult Ponto([DeCorpo] PointSearch ponto) { var consulta = novo Consulta de distância geográfica(); consulta.Latitude(ponto.Latitude); consulta.Longitude(ponto.Longitude); consulta.Distância(ponto.DistanceWithUnits (distância com unidades)); var searchParams = novo SearchParams() // .Fields("geo", "name") // omitido por causa do bug NCBC-1651 .Limite(10) .Tempo limite(Tempo.FromMilliseconds(10000)); var pesquisa = novo Pesquisa { Consulta = consulta, Índice = "mygeoindex", SearchParams = searchParams }; var resultados = _bucket.Consulta(pesquisa); var lista = novo Lista<Resultado da pesquisa geográfica>(); antes de (var atingido em resultados.Acertos) { *** esta parte não deveria ser necessária // a localização geográfica e o nome devem vir com os resultados da pesquisa // mas há um bug NCBC-1651 no SDK var doc = _bucket.Obter<dinâmico>(atingido.Id).Valor; // **************** lista.Adicionar(novo Resultado da pesquisa geográfica { Latitude = doc.geo.lat, Longitude = doc.geo.solitário, Janela de informações = novo Janela de informações { Conteúdo = doc.nome + "<br \/>" + doc.cidade + ", " + doc.estado + " " + doc.país } }); } retorno Ok(lista); } |
Neste ponto, você pode começar a testar esses pontos de extremidade com o Postman ou o Fiddler, se desejar. Mas será muito bom ver isso em um mapa.
Auerlia e Google Maps
No Aurelia, criei dois componentes: geosearchbox e geosearchpoint.
Cada um deles terá um componente do Google Maps com o qual o usuário pode interagir. Esses mapas serão centralizados em São Francisco, porque é lá que estão localizados muitos dos hotéis da "amostra de viagem".
Componente de pesquisa Bounding Box
O google-map`
tem um map-click.delegate que será acionado sempre que o usuário clicar no mapa. Em geosearchbox.html:
1 2 3 4 5 6 7 8 |
<google-mapa se.vincular="marcadores" mapa-clique.delegado="clickMap($event)" latitude="37.780986253433895" longitude="-122.45291600632277" zoom="12" marcadores.vincular="marcadores"> </google-mapa> |
marcadores
é simplesmente uma matriz que contém as coordenadas dos resultados da pesquisa que devem aparecer no mapa. Inicialmente, ele estará vazio.
Quando o usuário clicar no mapa pela primeira vez, isso definirá a primeira coordenada (canto superior esquerdo) no formulário. Em geosearchbox.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público clickMap(evento : qualquer) { var latLng = evento.detalhes.latLng, lat = latLng.lat(), lng = latLng.lng(); // atualizar o canto superior esquerdo somente se ele ainda não tiver sido definido // ou se o canto inferior direito já estiver definido se (!este.longitudeTopLeft || este.longitudeBottomRight) { este.longitudeTopLeft = lng; este.latitudeTopLeft = lat; este.longitudeBottomRight = nulo; este.latitudeBottomRight = nulo; } mais { este.longitudeBottomRight = lng; este.latitudeBottomRight = lat; } } |
Em seguida, clique em outro ponto no mapa. Isso definirá a segunda coordenada (canto inferior direito).
Minha implementação é bem simples. Sem gráficos sofisticados e sem validação de que a segunda coordenada está no canto inferior direito da primeira. Os campos em um formulário serão simplesmente preenchidos com a latitude e a longitude. Em geosearchbox.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<p> Vinculação caixa pesquisa: <br /> Latitude (superior esquerda): <entrada tipo="texto" valor="${ latitudeTopLeft }" /> Longitude (superior esquerda): <entrada tipo="texto" valor="${ longitudeTopLeft }" /> <br /> Latitude (fundo direito): <entrada tipo="texto" valor="${ latitudeBottomRight }" /> Longitude (fundo direito): <entrada tipo="texto" valor="${ longitudeBottomRight }" /> <br /> <entrada se.vincular="latitudeTopLeft && latitudeBottomRight" clique.gatilho="searchClick()" tipo="botão" nome="search" (pesquisa) valor="Pesquisar" /> </p> |
Depois de selecionar duas coordenadas, aparecerá um botão de pesquisa. Clique nele para postar essas coordenadas no endpoint criado anteriormente, e isso acionará a função searchClick()
conforme visto em geosearchbox.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
público searchClick() { deixar boxSearch = { latitudeTopLeft: este.latitudeTopLeft, longitudeTopLeft: este.longitudeTopLeft, latitudeBottomRight: este.latitudeBottomRight, longitudeBottomRight: este.longitudeBottomRight }; console.registro("POSTing to api/Box: " + JSON.stringify(boxSearch)); este.http.buscar('api/Box', { método: "POST", corpo: json(boxSearch) }) .então(resultado => resultado.json() como Promessa<qualquer[]>) .então(dados => { este.marcadores = dados; }); } |
Quando o Aurelia, o Google Maps, o ASP.NET Core e o Couchbase trabalham juntos, o resultado é o seguinte:
Pesquisa de distância
A implementação da consulta geoestatística "distância" será semelhante à IU da caixa delimitadora. Desta vez, você só precisa clicar em um único ponto no mapa. No entanto, será necessário digitar uma distância (em milhas).
O google-map
terá a mesma aparência. O componente clickMap
é diferente:
1 2 3 4 5 6 7 8 |
público clickMap(evento: qualquer) { var latLng = evento.detalhes.latLng, lat = latLng.lat(), lng = latLng.lng(); este.longitude = lng; este.latitude = lat; } |
Especifique uma distância (em milhas) e, em seguida, clique em "search" (pesquisar) para fazer uma solicitação POST para o endpoint que escrevemos anteriormente.
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 |
caixa de pesquisa geográfica.html: <p> Distância pesquisa: <br /> Latitude: <entrada tipo="texto" valor="${ latitude }" /> Longitude: <entrada tipo="texto" valor="${ longitude }" /> <br /> Distância (milhas): <entrada tipo="texto" valor="${ distância }" /> <br /> <entrada se.vincular="latitude" clique.gatilho="searchClick()" tipo="botão" nome="search" (pesquisa) valor="Pesquisar" /> </p> caixa de pesquisa geográfica.ts: público searchClick() { deixar pointSearch = { latitude: este.latitude, longitude: este.longitude, distância: este.distância }; console.registro("POSTing to api/Point: " + JSON.stringify(pointSearch)); este.http.buscar('api/Point', { método: "POST", corpo: json(pointSearch) }) .então(resultado => resultado.json() como Promessa<qualquer[]>) .então(dados => { este.marcadores = dados; }); } } |
Abaixo está um clipe da pesquisa em movimento. Observe como os resultados mudam à medida que eu movo a coordenada.
Resumo
Com o recurso integrado do Couchbase indexação geoespacial e o recurso de pesquisa, toda a matemática e a pesquisa são delegadas à plataforma de dados Couchbase. Assim, você pode se concentrar na criação de uma interface de usuário incrível (melhor do que a minha, de qualquer forma) e de uma lógica comercial sólida como uma rocha.
Certifique-se de Confira a documentação para obter uma visão geral completa dos recursos geoespaciais do Couchbase. Leia esta postagem do blog para saber mais sobre bancos de dados espaciais.
Se precisar de ajuda ou tiver dúvidas, consulte o Fóruns do Couchbase Servere, se você tiver alguma dúvida sobre o SDK do Couchbase .NET, consulte o Fóruns do SDK do .NET.
Se quiser entrar em contato comigo, deixe um comentário ou encontre-me em Twitter @mgroves.