NCache rend Lucene distribué et évolutif en implémentant l'API Lucene en plus de NCacheL'architecture distribuée. Commençant par NCache, NCache prend en charge une autre fonctionnalité de Lucene : les index géospatiaux. Voyons comment utiliser les index géospatiaux avec Lucene distribué.
La recherche en texte intégral avec Distributed Lucene est divisée en deux phases : l'indexation et la recherche. Dans la phase d'indexation, un analyseur crée des index à partir de certains textes. Ensuite, la phase de recherche n'utilise que ces index.
Étant donné que nous travaillons avec des index géospatiaux, nous souhaitons indexer les coordonnées de longitude et de latitude dans nos documents, puis rechercher des données en fonction de ces emplacements stockés.
NCache Détails NCache Documentation Comparaison Des Éditions
Comment utiliser les index géospatiaux avec Lucene distribué
Pour indexer des documents avec des coordonnées géospatiales et effectuer une recherche basée sur la localisation, Distributed Lucene utilise Spatial4n, "une bibliothèque géospatiale pour .NET".
Avant de commencer notre exemple d'application, nous devrions avoir NCache installé et un cache Lucene distribué déjà créé. Pour savoir comment configurer un cache Lucene distribué, consultez Créer un Lucene distribué avec un cache de persistance.
1. Indexer certains points de repère
Utilisons les index géospatiaux pour stocker nos points de repère préférés de nos voyages passés.
Tout d'abord, créons une application console et installons le package NuGet Lucene.Net.Spatial.NCache
.
Dans le, Program.cs
fichier, indexons quelques repères autour de Paris. Chaque point de repère a un nom, une longitude et une latitude. Quelque chose comme ça,
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 vous connaissez Indexation de texte intégral avec Lucene distribué, la création d'index géospatiaux est également assez similaire. Nous devons ouvrir un NCache répertoire, créez un écrivain et ajoutez des documents à l'écrivain.
Mais, au lieu d'utiliser des analyseurs pour indexer le texte, nous avons besoin d'un SpatialStrategy
. Une stratégie transforme les points et les formes en champs indexables.
Pour indexer nos repères, nous avons utilisé RecursivePrefixTreeStrategy
. Cette stratégie prend en charge la recherche de formes non ponctuelles. Pour le créer, nous avons utilisé un arbre basé sur Geohash et un nom de champ. Nous utiliserons le même nom de champ plus tard pour créer des documents.
Distributed Lucene introduit un nouveau type de document : SpatialDocument
. C'est un document Lucene avec quelques formes attachées. Comme un point, un rectangle ou un cercle.
Il s'agit de la ToSpatialDocument()
méthode d'extension que nous avons utilisée pour convertir nos points de repère 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 } }; } } |
Notez que nous avons stocké le nom du point de repère dans un champ de chaîne et une représentation sous forme de chaîne de l'emplacement du point de repère dans un autre champ nommé d'après le SpatialStrategy
nom de domaine. Ensuite, nous avons créé un SpatialDocument
avec un document Lucene régulier et un point pour l'emplacement du point de repère.
Pour créer des points, nous avons utilisé le SpatialContext.GEO
factory au lieu d'utiliser directement les constructeurs.
2. Rechercher les points de repère les plus proches
Maintenant que nous avons répertorié nos repères préférés, découvrons-en cinq, à 30 kilomètres autour de l'aéroport de Paris.
Mettons à jour le Program.cs
fichier pour inclure un SearchAround()
Procédé,
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}"); } } |
Au lieu de rechercher des documents contenant des mots-clés, avec les index géospatiaux, nous effectuons des recherches en fonction de leurs positions.
Pour trouver tous les points de repère proches de l'aéroport, nous sommes passés au Search()
méthode, un filtre, un décompte et un ordre de tri. Nous avons créé le filtre à partir du même SpatialStrategy
que nous utilisions auparavant. Et nous avons écrit un SpatialArg
pour interroger tous les documents à l'intérieur d'un cercle, centré sur un point de départ avec un rayon donné en kilomètres. Comme ça,
1 2 3 |
var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
Ensuite, nous avons créé un objet de réponse à partir de chaque document trouvé et calculé la distance depuis le point de départ. Comme ça,
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); } } |
Notez que nous avons analysé nos points de repère et utilisé le CalcDistance()
avec le point de départ et trouvé notre position de document.
Voici cinq points de repère indexés les plus proches de l'aéroport :
NCache Détails Télécharger NCache Indexation de texte intégral avec NCache Lucene
Conclusion
Pour implémenter les index GeoSpatial pour Distributed Lucene, nous avons besoin SpatialDocument
au lieu d'une plaine Document
.
Depuis que NCache implémente l'API Lucene.NET, nous pouvons faire évoluer notre code Lucene avec NCache en changeant quelques lignes de code et en suivant quelques conventions de nommage.
Pour en savoir plus sur d'autres NCache fonctionnalités, vérifier Quoi de neuf dans NCache? Pour plus de détails sur les index géospatiaux avec Lucene distribué, consultez API distribuée Lucene Geo-Spatial.
Pour suivre le code, nous avons écrit dans ce post, vérifiez mon NCache Démo dépôt sur GitHub.