Introdução
Couchbase Pesquisa de texto completo (FTS) é uma ótima ferramenta para indexação e consulta dados geoespaciais. Neste artigo, apresentarei um caso de uso de pesquisa geoespacial e demonstrarei as várias maneiras pelas quais podemos fazer uma pesquisa de dados de localização usando o serviço Couchbase Full Text Search. Usarei o Couchbase Server Enterprise Edition 6.6 (executando localmente no Docker) para criar um índice FTS em meu conjunto de dados geoespaciais de amostra e, em seguida, executar consultas geoespaciais no índice.
O que torna esse caso interessante é que não estamos usando um banco de dados espacial. O que é um banco de dados espacial? Ao contrário do Couchbase Server, que é um banco de dados de documentos NoSQL, os bancos de dados espaciais são especialmente otimizados para dados que descrevem espaços geométricos, como linhas, pontos de interesse ou até mesmo topologia 3D em instâncias avançadas. Como veremos, os recursos de pesquisa de texto completo do Couchbase o tornam tão útil para manipular e consultar dados geoespaciais quanto qualquer coisa que poderíamos esperar de uma solução mais especializada.
Caso de uso
Minha família sempre gostou de visitar e explorar Parque Nacional Great Smoky Mountains (ou GRSM, a abreviação do National Park Service) e, um dia, talvez tenhamos interesse em nos mudar para lá. Mas não é possível morar no parque nacional, portanto, precisamos considerar as várias cidades e vilas próximas ao parque e fazer uma pequena lista das que devem ser avaliadas e possivelmente visitadas.
O objetivo principal é estar próximo ao parque nacional, mas também levaremos em conta outros fatores, como o tamanho (população) das cidades.
Conjunto de dados de amostra
Para apoiar meu caso de uso do GRSM, decidi usar um conjunto de dados públicos do GeoNomes que inclui estados, cidades, vilas e outros pontos de referência de várias nações do mundo. Fiz o download do Arquivo de dados dos Estados Unidos e importou para o Couchbase apenas os dados de "locais preenchidos" (registros com códigos de recursos de "PPL", "PPLA", "PPLA2", "PPLA3′, "PPLA4" e "PPLC") para cidades/bairros com população diferente de zero. O resultado é um bucket do Couchbase "cities" com 30.734 documentos.
O modelo de dados do documento de cada cidade inclui alguns atributos de interesse para meu caso de uso do GRSM: o nome, o estado, a população, a elevação e, o mais importante, a latitude e a longitude. Aqui estão alguns exemplos de documentos JSON:
cidade::4699066
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "geonameid": 4699066, "featureclass": "P", "featurecode": "PPLA2", "name" (nome): "Houston", "estado": "TX", "população": 2296224, "elevação": 12, "geo": { "lat": 29.76328, "longo": -95.36327 } } |
cidade::4649251
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "geonameid": 4649251, "featureclass": "P", "featurecode": "PPL", "name" (nome): "Pigeon Forge", "estado": "TN", "população": 6171, "elevação": 305, "geo": { "lat": 35.78842, "longo": -83.55433 } } |
Criação do índice
Com os dados da cidade carregados no bucket de cidades no Couchbase, podemos criar um índice FTS adequado ao caso de uso "morar perto de GRSM" em questão. Abordarei brevemente os destaques da criação do índice necessário aqui, e a definição completa do índice está abaixo, no apêndice da postagem. (Para obter uma explicação mais detalhada sobre a criação de índices FTS, consulte minha postagem no blog sobre o assunto.)
Criação de índices pontos-chave:
- Nome: city_geo
- Balde: cidades
- Digite o identificador: "Doc ID up to separator" e digite "::" como delimitador (observe as chaves dos documentos de amostra acima)
- Mapeamentos de tipos:
- Desmarque a opção "padrão"
- Crie um mapeamento para documentos do tipo "cidade", indexando apenas esses campos especificados:
- name: usarei o analisador de palavras-chave para esse campo (porque queremos classificar por nome posteriormente) e verificarei index, store, _all, term vectors e docvalues para que, além de pesquisar por esse campo, eu possa testar o índice com destaques e classificar por esse campo.
- estado: Apenas armazene esse campo de texto para que possamos recuperá-lo nos resultados da pesquisa.
- população: Defina o tipo como number e verifique index, store e docvalues para que eu possa classificar os resultados com base na população posteriormente.
- elevação: Defina o tipo como número e marque apenas armazenar para que esse valor seja incluído nos resultados da pesquisa.
- geo: Defina o tipo como geopoint (já que cada documento tem as propriedades "lat" e lon" no subdocumento "geo") e verifique index, store e _all.

Vamos aguardar até que o processo de indexação esteja 100% concluído:

Agora, vamos testar rapidamente o índice na interface do usuário do Couchbase para verificar se o índice está funcionando conforme o esperado. O resultado parece bom!

Pesquisas geoespaciais
Agora que o conjunto de dados está carregado e indexado, posso ir ao cerne do assunto em questão e executar algumas consultas geoespaciais no índice. Para fins de demonstração, consultarei o índice API REST do serviço de pesquisa do Couchbase com cURLmas as consultas de pesquisa também podem ser executadas por meio de qualquer um dos SDKs do Couchbase como parte de seu aplicativo ou serviço. As consultas N1QL também suportam Pesquisa de texto completo com métodos SQL sem a necessidade de codificação.
Formatarei a resposta da API REST para facilitar a leitura usando jqum processador JSON de linha de comando de código aberto.
Método de pesquisa 1: Ponto e raio
Muitas vezes, queremos saber o que há nas proximidades ou em uma distância específica de um ponto específico. No meu caso de uso, gostaria de saber quais cidades e vilas estão próximas ao parque nacional GRSM... talvez dentro de 50 milhas como ponto de partida. Esse primeiro método de pesquisa geoespacial é chamado de "ponto e raio", "ponto e distância" ou "baseado em raio".
Para meu "ponto", escolhi Lacuna recém-descobertaque é uma passagem sobre as Smoky Mountains na fronteira entre o Tennessee e a Carolina do Norte, bem como um ponto de observação popular e um início de trilha para a Appalachian Trail. É um passeio obrigatório para minha família quando visitamos a GRSM. Vamos procurar vilas/cidades a menos de 50 milhas de Newfound Gap.

Aqui está a consulta baseada em raio:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
$ enrolar -s -XPOST -H "Content-Type: application/json" -u Administrador:senha http://localhost:8094/api/index/city_geo/query -d ' { "campos": ["name" (nome),"estado","elevação","população"], "tamanho": 500, "query" (consulta): { "localização": { "longo": -83.4217, "lat": 35.6067 }, "distância": "50mi", "campo": "geo" }, "sort": [ { "por": "geo_distance" (distância geográfica), "campo": "geo", "unidade": "mi", "localização": { "longo": -83.4217, "lat": 35.6067 } } ] }' | jq '("result_count: "+ (.total_hits | tostring)), (.sucessos[]| (.campos.nome + ", " + .campos.estado + " - população: " + (.campos.população | tostring) + ", elevação: " + (.campos.elevação | tostring)))' |
O resultado são 79 cidades, classificadas por distância de Newfound Gap. Incluí os primeiros 15 resultados aqui:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
"result_count: 79" "Gatlinburg, TN - população: 4184, elevação: 394" "Pittman Center, TN - população: 565, elevação: 393" "Cherokee, NC - população: 2138, elevação: 605" "Bryson City, NC - população: 1458, elevação: 528" "Pigeon Forge, TN - população: 6171, elevação: 305" "Dillsboro, NC - população: 240, elevação: 607" "Maggie Valley, NC - população: 1251, elevação: 918" "Townsend, TN - população: 452, elevação: 326" "Sylva, NC - população: 2617, elevação: 623" "Sevierville, TN - população: 16490, elevação: 275" "Fair Garden, TN - população: 529, elevação: 340" "Webster, NC - população: 375, elevação: 656" "Cove Creek, NC - população: 1171, elevação: 767" "Walland, TN - população: 259, altitude: 281" "Cullowhee, NC - população: 6228, elevação: 645" |
Método de pesquisa 2: caixa delimitadora
79 são muitas cidades a serem consideradas, então vamos pensar em outra maneira de ver isso. Com base em minhas visitas ao parque nacional ao longo dos anos, sei que quero morar em algum lugar entre Knoxville, TN, e Waynesville, NC. Considerando esses dois locais, posso consultar meu conjunto de dados GeoNames usando o método de pesquisa geoespacial "bounding box" ou "rectangle-based".
Posso fornecer as coordenadas de locais próximos a Knoxville e Wanyesville como parâmetros para minha pesquisa, e elas serão usadas como os cantos superior esquerdo e inferior direito de um retângulo. Todas as cidades localizadas dentro desse retângulo serão retornadas pela consulta.

Aqui está a consulta baseada em retângulo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ enrolar -s -XPOST -H "Content-Type: application/json" -u Administrador:senha http://localhost:8094/api/index/city_geo/query -d ' { "campos": ["name" (nome),"estado","elevação","população"], "tamanho": 50, "query" (consulta): { "top_left": { "longo": -83.937408, "lat": 36.032024 }, "bottom_right": { "longo": -82.947580, "lat": 35.401835 }, "campo": "geo" }, "sort": [ "name" (nome) ] }' | jq '("result_count: "+ (.total_hits | tostring)), (.sucessos[]| (.campos.nome + ", " + .campos.estado + " - população: " + (.campos.população | tostring) + ", elevação: " + (.campos.elevação | tostring)))' |
O resultado são 21 cidades, classificadas por nome:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
"result_count: 21" "Bryson City, NC - população: 1458, elevação: 528" "Cherokee, NC - população: 2138, elevação: 605" "Cove Creek, NC - população: 1171, elevação: 767" "Dandridge, TN - população: 2924, elevação: 304" "Eagleton Village, TN - população: 5052, elevação: 289" "Fair Garden, TN - população: 529, elevação: 340" "Gatlinburg, TN - população: 4184, elevação: 394" "Hazelwood, NC - população: 1655, elevação: 842" "Knoxville, TN - população: 185291, elevação: 276" "Lake Junaluska, NC - população: 2734, elevação: 778" "Maggie Valley, NC - população: 1251, elevação: 918" "Newport, TN - população: 6834, elevação: 321" "Parrottsville, TN - população: 261, elevação: 363" "Pigeon Forge, TN - população: 6171, elevação: 305" "Pittman Center, TN - população: 565, elevação: 393" "Sevierville, TN - população: 16490, elevação: 275" "Seymour, TN - população: 10919, elevação: 285" "Townsend, TN - população: 452, elevação: 326" "Walland, TN - população: 259, altitude: 281" "Waynesville, NC - população: 9809, elevação: 837" "Wildwood, TN - população: 1098, elevação: 278" |
Método de pesquisa 3: polígono
Após algumas pesquisas adicionais, decidi que preferiria morar no condado de Sevier, mas ao sul da Interstate 40 e ao norte dos limites do parque nacional.

Para fazer isso, precisarei executar uma pesquisa baseada em polígonos no meu índice FTS. Esse terceiro método foi adicionado recentemente no Couchbase Server 6.5.1. As áreas para consultas de pesquisa geoespacial agora podem ser especificadas como polígonos, além de círculos e retângulos. O polígono é expresso como uma série de coordenadas de latitude e longitude, cada uma determinando a localização de um canto do polígono.
No mapa do condado de Sevier acima (a linha vermelha clara é o limite do condado), sobrepus um polígono que corresponde aproximadamente à área em que estou interessado e capturei as coordenadas dos pontos do polígono. Usarei essas coordenadas para formar minha consulta geoespacial baseada em polígonos:
|
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 |
$ enrolar -s -XPOST -H "Content-Type: application/json" -u Administrador:senha http://localhost:8094/api/index/city_geo/query -d ' { "campos": ["name" (nome),"estado","elevação","população"], "tamanho": 50, "query" (consulta): { "campo": "geo", "polygon_points" (pontos de polígono): [ "35.987374, -83.658937", "35.971769, -83.654212", "35.887168, -83.793874", "35.686403, -83.678068", "35.704374, -83.505435", "35.769145, -83.275637", "35.868423, -83.290819", "35.919168, -83.350486", "35.948053, -83.510420", "35.990925, -83.568382" ] }, "sort": [ { "por" : "campo", "campo" : "população", "ausente" : "último", "tipo": "número" } ] }' | jq '("result_count: "+ (.total_hits | tostring)), (.sucessos[]| (.campos.nome + ", " + .campos.estado + " - população: " + (.campos.população | tostring) + ", elevação: " + (.campos.elevação | tostring)))' |
O resultado é uma lista bastante gerenciável de 6 cidades, classificadas por população em ordem crescente:
|
1 2 3 4 5 6 7 |
"result_count: 6" "Fair Garden, TN - população: 529, elevação: 340" "Pittman Center, TN - população: 565, elevação: 393" "Gatlinburg, TN - população: 4184, elevação: 394" "Pigeon Forge, TN - população: 6171, elevação: 305" "Seymour, TN - população: 10919, elevação: 285" "Sevierville, TN - população: 16490, elevação: 275" |
Resumo e próximas etapas
Com esses três métodos de pesquisa, o Couchbase oferece um recurso abrangente de pesquisa geoespacial para você incluir em seus aplicativos. Recomendo que você crie um índice com dados de pontos geográficos e execute algumas consultas baseadas em pontos geoespaciais ou polígonos geoespaciais. Você pode fazer isso facilmente com um de nossos aplicativos Couchbase conjuntos de dados de amostraA amostra de viagem, que tem muitos dados baseados em localização, pode ser usada para essa finalidade.
Dê um passo adiante e visualize os dados JSON como saída em tempo real a partir da solicitação de pesquisa no banco de dados de documentos usando plataformas de tecnologia geoespacial baseadas na Web, como Mapbox ou ESRI. Você se beneficiará do gerenciamento de dados em um sistema de gerenciamento de banco de dados distribuído que também suporta dimensionamento horizontal, armazenamento geral de valores-chave, consistência de dados e muito mais.
A pesquisa geoespacial é apenas um dos recursos da Pesquisa de texto completo no Couchbase. Você também pode testar consultas em matrizes e consultas em linguagem natural com pontuação, facetamento e reforço. Para obter mais informações sobre esse tópico, consulte a documentação do desenvolvedor de aplicativos e os links de treinamento na seção de referência abaixo.
Referências
- Couchbase Pesquisa Recursos: https://www.couchbase.com/products/full-text-search
- O que é um banco de dados espacial?
- Documentação do Couchbase FTS: https://docs.couchbase.com/server/current/fts/full-text-intro.html
- Publicações do blog do Couchbase FTS: https://www.couchbase.com/blog/category/full-text-search/
- Couchbase Pesquisa NoSQL - Treinamento on-line: https://learn.couchbase.com/store/509465-cb121-intro-to-couchbase-full-text-search-fts
- SDK do Couchbase NoSQL - Pesquisa geoespacial - Java, .NET, Python, Node.js, Ir, Scala, Rubi, C
Apêndice
Comando cURL de criação de índice e definição de JSON:
|
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
$ enrolar -XPUT -H "Content-Type: application/json" \ -u <nome de usuário>:<senha> http://localhost:8094/api/index/city_geo -d \ '{ "type": "fulltext-index", "name": "city_geo", "sourceType": "couchbase", "sourceName": "cities", "planParams": { "maxPartitionsPerPIndex": 171, "indexPartitions": 6 }, "params": { "doc_config": { "docid_prefix_delim": "::", "docid_regexp": "", "mode": "docid_prefix", "type_field": "type" }, "mapping": { "análise": {}, "default_analyzer": "standard", "default_datetime_parser": "dateTimeOptional", "default_field": "_all", "default_mapping": { "dynamic": true, "enabled": falso }, "default_type": "_default", "docvalues_dynamic": true, "index_dynamic": true, "store_dynamic": false, "type_field": "_type", "types": { "city": { "dynamic": false, "enabled": true, "properties": { "elevação": { "dynamic": false, "enabled": true, "fields": [ { "include_term_vectors": true, "name": "elevation" (elevação), "store": true, "type": "number" } ] }, "geo": { "dynamic": false, "enabled": true, "fields": [ { "docvalues": true, "include_in_all": true, "include_term_vectors": true, "index": true, "name": "geo", "store": true, "type": "geopoint" } ] }, "name": { "dynamic": false, "enabled": true, "fields": [ { "analisador": "keyword", "docvalues": true, "include_in_all": true, "include_term_vectors": true, "index": true, "name": "name", "store": true, "type": "text" } ] }, "population": { "dynamic": false, "enabled": true, "fields": [ { "docvalues": true, "include_term_vectors": true, "index": true, "name": "population", "store": true, "type": "number" } ] }, "state": { "dynamic": false, "enabled": true, "fields": [ { "name": "state", "store": true, "type": "text" } ] } } } } }, "store": { "indexType": "scorch" } }, "sourceParams": {} }' |
Olá Brian, ótima explicação. Sem nenhum conhecimento prévio sobre o FTS do couchbase, consegui começar a fazer consultas geoespaciais seguindo sua postagem.
Ainda tenho uma dúvida... Existe uma maneira de recuperar a distância real ao usar a consulta Point and Radius? Ou eu mesmo preciso implementar a fórmula haversine?