NCache torna o Lucene distribuído e escalável ao implementar a API do Lucene em cima de NCache's Arquitetura distribuída. Começando de NCache, NCache tem suporte para outro recurso do Lucene: Índices Geoespaciais. Vamos ver como usar Índices Geoespaciais com Lucene Distribuído.
A pesquisa de texto completo com o Lucene distribuído é dividida em duas fases: indexação e pesquisa. Na fase de indexação, um analisador cria índices a partir de algum texto. Então, a fase de busca usa apenas esses índices.
Como estamos trabalhando com índices geoespaciais, queremos indexar as coordenadas de longitude e latitude em nossos documentos e, em seguida, pesquisar dados com base nesses locais armazenados.
NCache Adicionar ao carrinho NCache Documentação Comparação de edições
Como usar índices geoespaciais com Lucene distribuído
Para indexar documentos com coordenadas geoespaciais e realizar buscas baseadas em localização, o Distributed Lucene usa Espacial4n, “uma biblioteca geoespacial para .NET”.
Antes de iniciar nosso aplicativo de exemplo, devemos ter NCache instalado e um cache Distributed Lucene já criado. Para saber como configurar um cache distribuído do Lucene, verifique Criar um Lucene distribuído com cache de persistência.
1. Indexe alguns pontos de referência
Vamos usar os índices geoespaciais para armazenar nossos pontos de referência favoritos de nossas viagens anteriores.
Primeiro, vamos criar um aplicativo de console e instalar o pacote NuGet Lucene.Net.Spatial.NCache
.
No, Program.cs
arquivo, vamos indexar alguns pontos de referência em Paris. Cada ponto de referência tem um nome, longitude e latitude. Algo assim,
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 |
using DistributedLucene.Net.Spatial; using Landmarks; using Lucene.Net.Analysis.Standard; using Lucene.Net.Index; using Lucene.Net.Spatial.Prefix; using Lucene.Net.Spatial.Prefix.Tree; using Lucene.Net.Store; using Lucene.Net.Util; using Spatial4n.Core.Context; const LuceneVersion LuceneVersion = LuceneVersion.LUCENE_48; const string CacheName = "DemoLuceneCache"; const string IndexName = "landmarks"; const string NameFieldName = "name"; const string LocationFieldName = "landmarkLocation"; // 1. Let's index some locations around Paris var favoriteLandmarks = new Landmark[] { new Landmark("Eiffel Tower", new Location(48.858093, 2.294694)), new Landmark("Sacre Coeur", new Location(48.886452, 2.343121)), new Landmark("Louvre Museum", new Location(48.860294, 2.338629)), new Landmark("Palace of Versailles", new Location(48.804722, 2.121782)), new Landmark("Disneyland Paris", new Location(48.867374, 2.784018)), new Landmark("Arc de Triomphe", new Location(48.873756, 2.294946)) }; IndexLandmarks(favoriteLandmarks); // Later, we will search all landmarks close to a reference point here... static void IndexLandmarks(IEnumerable landmarks) { // Create an SpatialStrategy var context = SpatialContext.GEO; var strategy = new RecursivePrefixTreeStrategy( new GeohashPrefixTree(context, maxLevels: 11), fieldName: LocationFieldName); // Open a directory using var indexDirectory = NCacheDirectory.Open(CacheName, IndexName); var config = new IndexWriterConfig(LuceneVersion, new StandardAnalyzer(LuceneVersion)) { OpenMode = OpenMode.CREATE }; // Create a writer using var writer = new IndexWriter(indexDirectory, config); foreach (var landmark in landmarks) { // Create a SpatialDocument from our Landmark var document = landmark.ToSpatialDocument(strategy); // Add a document writer.AddDocument(document, strategy); } // Write all documents writer.Commit(); } public record Location(double Latitude, double Longitude); public record Landmark(string Name, Location Position); |
Se você estiver familiarizado com Indexação de texto completo com Lucene distribuído, a criação de Índices Geoespaciais também é bastante semelhante. Temos que abrir um NCache diretório, crie um gravador e adicione documentos ao gravador.
Mas, em vez de usar analisadores para indexar texto, precisamos de um SpatialStrategy
. Uma estratégia transforma pontos e formas em campos indexáveis.
Para indexar nossos pontos de referência, usamos RecursivePrefixTreeStrategy
. Essa estratégia oferece suporte à pesquisa de formas não pontuais. Para criá-lo, usamos uma árvore baseada em Geohash e um nome de campo. Usaremos o mesmo nome de campo posteriormente para criar documentos.
Distributed Lucene apresenta um novo tipo de documento: SpatialDocument
. É um documento do Lucene com algumas formas anexadas. Tipo, um ponto, retângulo ou círculo.
Este é o ToSpatialDocument()
método de extensão que usamos para converter nossos pontos de referência em SpatialDocument
,
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 |
using Lucene.Net.Documents; using Lucene.Net.Spatial; using Spatial4n.Core.Context; using static System.FormattableString; using Document = Lucene.Net.Documents.Document; namespace Landmarks; public static class LocationExtensions { public static SpatialDocument ToSpatialDocument(this Landmark landmark, SpatialStrategy strategy) { var document = new Document { new StringField("name", landmark.Name, Field.Store.YES) }; var point = SpatialContext.GEO.MakePoint(landmark.Position.Longitude, landmark.Position.Latitude); document.Add(new StoredField(strategy.FieldName, Invariant($"{point.X} {point.Y}"))); return new SpatialDocument { Document = document, Shapes = new[] { point } }; } } |
Observe que armazenamos o nome do ponto de referência em um campo de string e uma representação de string do local do ponto de referência em outro campo com o nome do SpatialStrategy
nome do campo. Em seguida, criamos um SpatialDocument
com um documento regular do Lucene e um ponto para a localização do marco.
Para criar pontos, usamos o SpatialContext.GEO
factory em vez de usar construtores diretamente.
2. Pesquise os pontos de referência mais próximos
Agora que indexamos nossos pontos de referência favoritos, vamos encontrar cinco deles, 30 quilômetros ao redor do aeroporto de Paris.
Vamos atualizar o Program.cs
arquivo para incluir um SearchAround()
método
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 |
using DistributedLucene.Net.Spatial; using Landmarks; using Lucene.Net.Analysis.Standard; using Lucene.Net.Index; using Lucene.Net.Search; using Lucene.Net.Spatial; using Lucene.Net.Spatial.Prefix; using Lucene.Net.Spatial.Prefix.Tree; using Lucene.Net.Spatial.Queries; using Lucene.Net.Store; using Lucene.Net.Util; using Spatial4n.Core.Context; using Spatial4n.Core.Distance; const LuceneVersion LuceneVersion = LuceneVersion.LUCENE_48; const string CacheName = "DemoLuceneCache"; const string IndexName = "landmarks"; const string NameFieldName = "name"; const string LocationFieldName = "landmarkLocation"; // 2. Let's find five landmarks, 30Km around the airport // and print them var airport = new Landmark("Charles de Gaulle Airport", new Location(49.009724, 2.547778)); SearchAround(5, airport, 30); static void SearchAround(int landmarkCount, Landmark referenceLandmark, int distanceInKm) { // Create a reader using var indexDirectory = NCacheDirectory.Open(CacheName, IndexName); using var reader = DirectoryReader.Open(indexDirectory); var searcher = new IndexSearcher(reader); var startingPoint = referenceLandmark.ToPoint(); var context = SpatialContext.GEO; var sortByName = new Sort(new SortField(NameFieldName, SortFieldType.STRING)); // Create a circle of 30km around a reference point var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); var strategy = new RecursivePrefixTreeStrategy( new GeohashPrefixTree(context, maxLevels: 11), fieldName: LocationFieldName); // Create a filter using the same strategy var filter = strategy.MakeFilter(spatialArgs); // Search documents var documents = searcher.Search(new MatchAllDocsQuery(), filter, landmarkCount, sortByName); foreach (var scoreDoc in documents.ScoreDocs) { var document = searcher.Doc(scoreDoc.Doc); // Create a result tuple var (name, awayInKm) = document.ToResponse(startingPoint); Console.WriteLine($"Name: {name}"); Console.WriteLine($"Distance: {awayInKm}"); } } |
Em vez de pesquisar documentos que contenham algumas palavras-chave, com os Índices Geoespaciais, realizamos pesquisas com base em suas posições.
Para encontrar todos os pontos de referência próximos ao aeroporto, passamos pela Search()
método, um filtro, contagem e ordem de classificação. Criamos o filtro a partir do mesmo SpatialStrategy
que usávamos antes. E escrevemos um SpatialArg
para consultar todos os documentos dentro de um círculo, centrado em um ponto inicial com um determinado raio em quilômetros. Assim,
1 2 3 |
var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
Em seguida, criamos um objeto de resposta de cada documento encontrado e calculamos a distância do ponto de partida. Assim,
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 |
using Spatial4n.Core.Context; using Spatial4n.Core.Distance; using Spatial4n.Core.Shapes; using System.Globalization; using Document = Lucene.Net.Documents.Document; namespace Landmarks; public static class LocationExtensions { public static (string Name, double DistanceInKm) ToResponse(this Document document, IPoint startingPoint) { var name = document.GetField("name").GetStringValue(); var location = document.GetField("landmarkLocation").GetStringValue(); var positions = location.Split(' '); var x = double.Parse(positions[0], CultureInfo.InvariantCulture); var y = double.Parse(positions[1], CultureInfo.InvariantCulture); var distanceInDeg = SpatialContext.GEO.CalcDistance(startingPoint, x, y); var distanceInKm = distanceInDeg * DistanceUtils.DEG_TO_KM; return (name, distanceInKm); } } |
Observe que analisamos nossos locais de referência e usamos o CalcDistance()
com o ponto de partida e encontramos nossa posição de documento.
Estes são cinco pontos de referência indexados mais próximos do aeroporto:
NCache Adicionar ao carrinho Baixar NCache Indexação de texto completo com NCache lucene
Conclusão
Para implementar índices GeoSpatial para Lucene Distribuído, precisamos SpatialDocument
em vez de uma planície Document
.
Como NCache implementa a API Lucene.NET, podemos dimensionar nosso código Lucene com NCache alterando algumas linhas de código e seguindo algumas convenções de nomenclatura.
Para conhecer outros recentes NCache características, verifique O que há de novo em NCache? Para mais detalhes sobre Índices Geoespaciais com Lucene Distribuído, consulte API geoespacial distribuída do Lucene.
Para acompanhar o código que escrevemos neste post, confira meu NCache Demo repositório no GitHub.
NCache Adicionar ao carrinho Baixar NCache Comparação de edições