NCache macht Lucene verteilt und skalierbar, indem die Lucene-API zusätzlich implementiert wird NCache's Verteilte Architektur. Ab NCache, NCache unterstützt eine weitere Lucene-Funktion: GeoSpatial Indexes. Sehen wir uns an, wie GeoSpatial-Indizes mit Distributed Lucene verwendet werden.
Die Volltextsuche mit Distributed Lucene ist in zwei Phasen unterteilt: Indizierung und Suche. In der Indizierungsphase erstellt ein Analysator Indizes aus Text. Dann verwendet die Suchphase nur diese Indizes.
Da wir mit GeoSpatial-Indizes arbeiten, möchten wir Längen- und Breitengradkoordinaten in unseren Dokumenten indizieren und dann Daten basierend auf diesen gespeicherten Orten durchsuchen.
NCache Details NCache Dokumentation Editionsvergleich
So verwenden Sie GeoSpatial-Indizes mit Distributed Lucene
Um Dokumente mit Geokoordinaten zu indizieren und eine standortbasierte Suche durchzuführen, verwendet Distributed Lucene Räumlich4n, „eine GeoSpatial-Bibliothek für .NET“.
Bevor wir mit unserer Beispielanwendung beginnen, sollten wir dies getan haben NCache installiert und ein bereits erstellter Distributed Lucene-Cache. Um zu erfahren, wie Sie einen verteilten Lucene-Cache konfigurieren, überprüfen Sie Erstellen Sie ein verteiltes Lucene mit Persistenz-Cache.
1. Indexieren Sie einige Sehenswürdigkeiten
Verwenden wir GeoSpatial-Indizes, um unsere bevorzugten Sehenswürdigkeiten von unseren vergangenen Reisen zu speichern.
Lassen Sie uns zunächst eine Konsolenanwendung erstellen und das NuGet-Paket installieren Lucene.Net.Spatial.NCache
.
In dem, Program.cs
Lassen Sie uns einige Sehenswürdigkeiten in der Umgebung von Paris indizieren. Jeder Orientierungspunkt hat einen Namen, einen Längengrad und einen Breitengrad. Etwas wie das,
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); |
Wenn Sie sich auskennen Volltextindizierung mit Distributed Lucene, dann ist das Erstellen von GeoSpatial-Indizes ebenfalls ziemlich ähnlich. Wir müssen ein öffnen NCache Verzeichnis, erstellen Sie einen Writer und fügen Sie dem Writer Dokumente hinzu.
Aber anstatt Analysatoren zum Indexieren von Text zu verwenden, brauchen wir eine SpatialStrategy
. Eine Strategie verwandelt Punkte und Formen in indexierbare Felder.
Um unsere Wahrzeichen zu indizieren, verwendeten wir RecursivePrefixTreeStrategy
. Diese Strategie unterstützt die Suche nach Nicht-Punkt-Formen. Um es zu erstellen, haben wir einen Geohash-basierten Baum und einen Feldnamen verwendet. Wir werden denselben Feldnamen später verwenden, um Dokumente zu erstellen.
Distributed Lucene führt einen neuen Dokumententyp ein: SpatialDocument
. Es ist ein Lucene-Dokument mit einigen angehängten Formen. Wie ein Punkt, ein Rechteck oder ein Kreis.
Dies ist die ToSpatialDocument()
Erweiterungsmethode, in die wir unsere Orientierungspunkte konvertiert haben 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 } }; } } |
Beachten Sie, dass wir den Namen des Orientierungspunkts in einem Zeichenfolgenfeld und eine Zeichenfolgendarstellung des Orientierungspunkts in einem anderen Feld gespeichert haben, das nach dem benannt ist SpatialStrategy
Feldname. Dann haben wir eine erstellt SpatialDocument
mit einem regulären Lucene-Dokument und einem Punkt für den Orientierungspunkt.
Um Punkte zu erstellen, haben wir die verwendet SpatialContext.GEO
Factory anstatt Konstruktoren direkt zu verwenden.
2. Suche nach nächstgelegenen Sehenswürdigkeiten
Nachdem wir unsere Lieblingssehenswürdigkeiten indiziert haben, suchen wir fünf davon, 30 Kilometer um den Pariser Flughafen herum.
Lassen Sie uns die aktualisieren Program.cs
Datei um a . einzuschließen SearchAround()
Verfahren
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}"); } } |
Anstatt nach Dokumenten zu suchen, die einige Schlüsselwörter enthalten, führen wir mit GeoSpatial-Indizes Suchen basierend auf ihren Positionen durch.
Um alle Sehenswürdigkeiten in der Nähe des Flughafens zu finden, gingen wir zum Search()
Methode, ein Filter, eine Zählung und eine Sortierreihenfolge. Wir haben den Filter daraus erstellt SpatialStrategy
die wir vorher benutzt haben. Und wir schrieben a SpatialArg
um alle Dokumente innerhalb eines Kreises abzufragen, der an einem Startpunkt mit einem gegebenen Radius in Kilometern zentriert ist. So was,
1 2 3 |
var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
Dann haben wir aus jedem gefundenen Dokument ein Response-Objekt erstellt und die Entfernung vom Startpunkt berechnet. So was,
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); } } |
Beachten Sie, dass wir unsere Orientierungspunkte analysiert und die verwendet haben CalcDistance()
mit dem Startpunkt und fanden unsere Dokumentenposition.
Dies sind fünf indizierte Orientierungspunkte, die dem Flughafen am nächsten liegen:
NCache Details Herunterladen NCache Volltextindizierung mit NCache Lucene
Zusammenfassung
Um GeoSpatial-Indizes für Distributed Lucene zu implementieren, benötigen wir SpatialDocument
statt einer Ebene Document
.
Da NCache implementiert die Lucene.NET-API, mit der wir unseren Lucene-Code skalieren können NCache indem Sie ein paar Codezeilen ändern und einige Namenskonventionen befolgen.
Um mehr über andere aktuelle zu erfahren NCache Funktionen, prüfen Was ist neu in NCache? Weitere Einzelheiten zu GeoSpatial-Indizes mit Distributed Lucene finden Sie unter Verteilte Lucene Geo-Spatial-API.
Um dem Code zu folgen, den wir in diesem Beitrag geschrieben haben, check my NCache Demo Repository auf GitHub.