NCache 通过在上面实现 Lucene API 使 Lucene 分布式和可扩展 NCache的分布式架构。 从...开始 NCache, NCache 支持另一个 Lucene 功能:地理空间索引。 让我们看看如何在分布式 Lucene 中使用地理空间索引。
使用分布式 Lucene 进行全文搜索分为两个阶段:索引和搜索。 在索引阶段,分析器从一些文本创建索引。 然后,搜索阶段仅使用这些索引。
由于我们正在使用地理空间索引,我们希望在我们的文档中索引经度和纬度坐标,然后根据这些存储的位置搜索数据。
如何在分布式 Lucene 中使用地理空间索引
为了使用地理空间坐标索引文档并执行基于位置的搜索,分布式 Lucene 使用 空间4n, “.NET 的地理空间库”。
在开始我们的示例应用程序之前,我们应该有 NCache 已安装,并且已创建分布式 Lucene 缓存。 要了解如何配置分布式 Lucene 缓存,请查看 使用持久缓存创建分布式 Lucene.
1.索引一些地标
让我们使用地理空间索引来存储我们过去旅行中最喜欢的地标。
首先,让我们创建一个控制台应用程序并安装 NuGet 包 Lucene.Net.Spatial.NCache
.
在里面, Program.cs
文件,让我们索引巴黎周围的一些地标。 每个地标都有名称、经度和纬度。 像这样的东西,
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); |
如果你熟悉的话 使用分布式 Lucene 进行全文索引,那么创建 GeoSpatial Indexes 也非常相似。 我们必须开一个 NCache 目录,创建一个 writer,并将文档添加到 writer。
但是,我们不需要使用分析器来索引文本,而是需要一个 SpatialStrategy
. 策略将点和形状转换为可索引字段。
为了索引我们的地标,我们使用 RecursivePrefixTreeStrategy
. 该策略支持搜索非点形状。 为了创建它,我们使用了基于 Geohash 的树和字段名称。 稍后我们将使用相同的字段名称来创建文档。
分布式 Lucene 引入了一种新型文档: SpatialDocument
. 这是一个附有一些形状的 Lucene 文档。 比如,一个点、矩形或圆形。
这是 ToSpatialDocument()
我们用来将地标转换为的扩展方法 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 } }; } } |
请注意,我们将地标名称存储在一个字符串字段中,并将地标位置的字符串表示形式存储在另一个以 SpatialStrategy
字段名称。 然后,我们创建了一个 SpatialDocument
带有一个常规的 Lucene 文档和一个地标位置的点。
为了创建点,我们使用 SpatialContext.GEO
factory 而不是直接使用构造函数。
2. 搜索最近的地标
现在我们已经索引了我们最喜欢的地标,让我们在巴黎机场周围 30 公里处找到其中的五个。
让我们更新 Program.cs
文件以包含一个 SearchAround()
方法,
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}"); } } |
我们不是使用地理空间索引搜索包含某些关键字的文档,而是根据它们的位置执行搜索。
为了找到机场附近的所有地标,我们去了 Search()
方法、过滤器、计数和排序顺序。 我们使用相同的方法创建了过滤器 SpatialStrategy
我们以前用过的。 我们写了一个 SpatialArg
查询一个圆内的所有文档,该圆以给定半径(以公里为单位)为中心。 像这样,
1 2 3 |
var spatialArgs = new SpatialArgs( SpatialOperation.Intersects, context.MakeCircle(startingPoint, DistanceUtils.Dist2Degrees(distanceInKm, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
然后,我们从每个找到的文档中创建一个响应对象,并计算到起点的距离。 像这样,
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); } } |
请注意,我们解析了地标位置并使用了 CalcDistance()
有了起点,找到了我们的文档位置。
以下是距离机场最近的五个索引地标:
NCache 更多信息 下载 NCache 全文索引 NCache Lucene
结论
为了实现分布式 Lucene 的地理空间索引,我们需要 SpatialDocument
而不是一个平原 Document
.
自 NCache 实现了 Lucene.NET API,我们可以扩展我们的 Lucene 代码 NCache 通过更改几行代码并遵循一些命名约定。
了解其他近期 NCache 功能,检查 什么是新的 NCache? 有关使用分布式 Lucene 的地理空间索引的更多详细信息,请查看 分布式 Lucene 地理空间 API.
为了遵循我们在这篇文章中写的代码,请查看我的 NCache 演示 GitHub 上的存储库。