NCache hace que Lucene sea distribuido y escalable mediante la implementación de la API de Lucene además de NCacheLa arquitectura distribuida. Empezando desde NCache, NCache tiene soporte para otra función de Lucene: índices geoespaciales. Veamos cómo usar los índices geoespaciales con Distributed Lucene.
La búsqueda de texto completo con Distributed Lucene se divide en dos fases: indexación y búsqueda. En la fase de indexación, un analizador crea índices a partir de algún texto. Luego, la fase de búsqueda solo utiliza esos índices.
Dado que estamos trabajando con índices geoespaciales, queremos indexar las coordenadas de longitud y latitud en nuestros documentos y luego buscar datos en función de esas ubicaciones almacenadas.
NCache Detalles NCache Documentación Comparación de ediciones
Cómo usar índices geoespaciales con Distributed Lucene
Para indexar documentos con coordenadas geoespaciales y realizar búsquedas basadas en la ubicación, Distributed Lucene utiliza Espacial4n, "una biblioteca geoespacial para .NET".
Antes de comenzar nuestra aplicación de muestra, deberíamos haber NCache instalado y un caché Distributed Lucene ya creado. Para obtener información sobre cómo configurar un caché de Lucene distribuido, consulte Crear un Lucene distribuido con caché de persistencia.
1. Indexar algunos puntos de referencia
Usemos índices geoespaciales para almacenar nuestros puntos de referencia favoritos de nuestros viajes anteriores.
Primero, creemos una aplicación de Consola e instalemos el paquete NuGet Lucene.Net.Spatial.NCache
.
En el, Program.cs
archivo, vamos a indexar algunos puntos de referencia alrededor de París. Cada hito tiene un nombre, longitud y latitud. Algo como esto,
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); |
Si estás familiarizado con Indexación de texto completo con Distributed Lucene, la creación de índices geoespaciales también es bastante similar. Tenemos que abrir un NCache directorio, cree un escritor y agregue documentos al escritor.
Pero, en lugar de usar analizadores para indexar texto, necesitamos un SpatialStrategy
. Una estrategia convierte puntos y formas en campos indexables.
Para indexar nuestros puntos de referencia, usamos RecursivePrefixTreeStrategy
. Esta estrategia admite la búsqueda de formas no puntuales. Para crearlo, usamos un árbol basado en Geohash y un nombre de campo. Usaremos el mismo nombre de campo más adelante para crear documentos.
Distributed Lucene introduce un nuevo tipo de documento: SpatialDocument
. Es un documento de Lucene con algunas formas adjuntas. Como un punto, un rectángulo o un círculo.
Este es el ToSpatialDocument()
método de extensión que usamos para convertir nuestros puntos de referencia en 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 almacenamos el nombre del punto de referencia en un campo de cadena y una representación de cadena de la ubicación del punto de referencia en otro campo con el nombre del SpatialStrategy
nombre del campo. Entonces, creamos un SpatialDocument
con un documento regular de Lucene y un punto para la ubicación del hito.
Para crear puntos, usamos el SpatialContext.GEO
factory en lugar de usar constructores directamente.
2. Buscar puntos de referencia más cercanos
Ahora que hemos indexado nuestros puntos de referencia favoritos, encontremos cinco de ellos, 30 kilómetros alrededor del aeropuerto de París.
Actualicemos el Program.cs
archivo para incluir un 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}"); } } |
En lugar de buscar documentos que contengan algunas palabras clave, con los Índices Geoespaciales realizamos búsquedas en función de sus posiciones.
Para encontrar todos los puntos de referencia cerca del aeropuerto, pasamos al Search()
método, un filtro, un conteo y un orden de clasificación. Creamos el filtro a partir del mismo SpatialStrategy
que usábamos antes. Y escribimos un SpatialArg
para consultar todos los documentos dentro de un círculo, centrado en un punto de partida con un radio dado en kilómetros. Como esto,
1 2 3 |
var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
Luego, creamos un objeto de respuesta a partir de cada documento encontrado y calculamos la distancia desde el punto de partida. Como esto,
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); } } |
Tenga en cuenta que analizamos nuestras ubicaciones de puntos de referencia y usamos el CalcDistance()
con el punto de partida y encontramos la posición de nuestro documento.
Estos son cinco puntos de referencia indexados más cercanos al aeropuerto:
NCache Detalles Descargar NCache Indexación de texto completo con NCache Lucene
Conclusión
Para implementar índices geoespaciales para Distributed Lucene, necesitamos SpatialDocument
en lugar de una llanura Document
.
Como NCache implementa la API Lucene.NET, podemos escalar nuestro código Lucene con NCache cambiando algunas líneas de código y siguiendo algunas convenciones de nomenclatura.
Para conocer otros recientes NCache caracteristicas, comprobar Lo nuevo en NCache? Para obtener más detalles sobre los índices geoespaciales con Distributed Lucene, consulte API geoespacial distribuida de Lucene.
Para seguir el código que escribimos en esta publicación, consulte mi NCache De demostración repositorio en GitHub.