NCache rende Lucene distribuito e scalabile implementando l'API Lucene in aggiunta NCacheArchitettura distribuita. A partire da NCache, NCache supporta un'altra funzionalità di Lucene: gli indici geospaziali. Vediamo come utilizzare gli indici geospaziali con Lucene distribuita.
La ricerca full-text con Lucene distribuita è suddivisa in due fasi: indicizzazione e ricerca. Nella fase di indicizzazione, un analizzatore crea indici da del testo. Quindi, la fase di ricerca utilizza solo quegli indici.
Poiché stiamo lavorando con gli indici geospaziali, vogliamo indicizzare le coordinate di longitudine e latitudine nei nostri documenti e quindi cercare i dati in base a tali posizioni memorizzate.
NCache Dettagli NCache Documentazione Confronto edizione
Come utilizzare gli indici geospaziali con Lucene distribuita
Per indicizzare documenti con coordinate geospaziali ed eseguire ricerche basate sulla posizione, Distributed Lucene utilizza spaziale4n, "una libreria geospaziale per .NET".
Prima di iniziare la nostra applicazione di esempio, avremmo dovuto NCache installato e una cache Lucene distribuita già creata. Per informazioni su come configurare una cache Lucene distribuita, controlla Crea un Lucene distribuito con Persistence Cache.
1. Indicizza alcuni punti di riferimento
Usiamo gli indici geospaziali per memorizzare i nostri punti di riferimento preferiti dei nostri viaggi passati.
Innanzitutto, creiamo un'applicazione Console e installiamo il pacchetto NuGet Lucene.Net.Spatial.NCache
.
Nel, Program.cs
file, indiciamo alcuni punti di riferimento intorno a Parigi. Ogni punto di riferimento ha un nome, longitudine e latitudine. Qualcosa come questo,
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 hai familiarità con Indicizzazione full-text con Lucene distribuita, anche la creazione di indici geospaziali è abbastanza simile. Dobbiamo aprire un NCache directory, creare uno scrittore e aggiungere documenti allo scrittore.
Ma, invece di usare gli analizzatori per indicizzare il testo, abbiamo bisogno di a SpatialStrategy
. Una strategia trasforma punti e forme in campi indicizzabili.
Per indicizzare i nostri punti di riferimento, abbiamo usato RecursivePrefixTreeStrategy
. Questa strategia supporta la ricerca di forme non puntiformi. Per crearlo, abbiamo utilizzato un albero basato su Geohash e un nome di campo. Useremo lo stesso nome di campo in seguito per creare documenti.
Distributed Lucene introduce un nuovo tipo di documento: SpatialDocument
. È un documento Lucene con alcune forme allegate. Come un punto, un rettangolo o un cerchio.
Questa è la ToSpatialDocument()
metodo di estensione in cui abbiamo convertito i nostri punti di riferimento 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 } }; } } |
Si noti che abbiamo memorizzato il nome del punto di riferimento in un campo stringa e una rappresentazione di stringa della posizione del punto di riferimento in un altro campo chiamato dopo il SpatialStrategy
nome del campo. Quindi, abbiamo creato un SpatialDocument
con un regolare documento Lucene e un punto per la posizione del punto di riferimento.
Per creare punti, abbiamo usato il SpatialContext.GEO
fabbrica invece di utilizzare direttamente i costruttori.
2. Cerca i punti di riferimento più vicini
Ora che abbiamo indicizzato i nostri punti di riferimento preferiti, troviamone cinque, a 30 chilometri dall'aeroporto di Parigi.
Aggiorniamo il file Program.cs
file da includere a SearchAround()
Metodo,
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}"); } } |
Invece di cercare documenti che contengono alcune parole chiave, con gli indici geospaziali eseguiamo ricerche in base alle loro posizioni.
Per trovare tutti i punti di riferimento vicino all'aeroporto, siamo passati al Search()
metodo, un filtro, un conteggio e un ordinamento. Abbiamo creato il filtro dallo stesso SpatialStrategy
che abbiamo usato prima. E abbiamo scritto a SpatialArg
per interrogare tutti i documenti all'interno di un cerchio, centrato in un punto iniziale con un dato raggio in chilometri. Come questo,
1 2 3 |
var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
Quindi, abbiamo creato un oggetto risposta da ogni documento trovato e calcolato la distanza dal punto di partenza. Come questo,
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); } } |
Si noti che abbiamo analizzato le nostre posizioni di riferimento e utilizzato il CalcDistance()
con il punto di partenza e abbiamo trovato la nostra posizione del documento.
Questi sono cinque punti di riferimento indicizzati più vicini all'aeroporto:
NCache Dettagli Scaricare NCache Indicizzazione full-text con NCache Luceno
Conclusione
Per implementare gli indici GeoSpatial per Distributed Lucene, abbiamo bisogno SpatialDocument
invece di una pianura Document
.
Dal NCache implementa l'API Lucene.NET, possiamo ridimensionare il nostro codice Lucene con NCache modificando alcune righe di codice e seguendo alcune convenzioni di denominazione.
Per conoscere altri recenti NCache caratteristiche, verifica Novità in NCache? Per maggiori dettagli sugli indici geospaziali con Lucene distribuita, controllare API distribuita di Lucene geospaziale.
Per seguire il codice, abbiamo scritto in questo post, controlla il mio NCache Dimo repository su GitHub.