| using Lucene.Net.Documents; |
| using Lucene.Net.Index; |
| using Lucene.Net.Queries.Function; |
| using Lucene.Net.Search; |
| 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 NUnit.Framework; |
| using Spatial4n.Core.Context; |
| using Spatial4n.Core.Distance; |
| using Spatial4n.Core.Shapes; |
| using System; |
| using System.Globalization; |
| |
| namespace Lucene.Net.Spatial |
| { |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /// <summary> |
| /// This class serves as example code to show how to use the Lucene spatial |
| /// module. |
| /// </summary> |
| public class SpatialExample : LuceneTestCase |
| { |
| // LUCENENET specific: removed this because the test will run |
| // with only an attribute, it doesn't need to worry about a convention |
| ////Note: Test invoked via TestTestFramework.spatialExample() |
| //public static void Main(String[] args) |
| //{ |
| // new SpatialExample().Test(); |
| //} |
| |
| [Test] |
| public virtual void Test() |
| { |
| Init(); |
| IndexPoints(); |
| Search(); |
| } |
| |
| /** |
| * The Spatial4j <see cref="SpatialContext"/> is a sort of global-ish singleton |
| * needed by Lucene spatial. It's a facade to the rest of Spatial4j, acting |
| * as a factory for <see cref="IShape"/>s and provides access to reading and writing |
| * them from Strings. |
| */ |
| private SpatialContext ctx;//"ctx" is the conventional variable name |
| |
| /** |
| * The Lucene spatial <see cref="SpatialStrategy"/> encapsulates an approach to |
| * indexing and searching shapes, and providing distance values for them. |
| * It's a simple API to unify different approaches. You might use more than |
| * one strategy for a shape as each strategy has its strengths and weaknesses. |
| * <p /> |
| * Note that these are initialized with a field name. |
| */ |
| private SpatialStrategy strategy; |
| |
| private Directory directory; |
| |
| protected void Init() |
| { |
| //Typical geospatial context |
| // These can also be constructed from SpatialContextFactory |
| this.ctx = SpatialContext.GEO; |
| |
| int maxLevels = 11;//results in sub-meter precision for geohash |
| //TODO demo lookup by detail distance |
| // This can also be constructed from SpatialPrefixTreeFactory |
| SpatialPrefixTree grid = new GeohashPrefixTree(ctx, maxLevels); |
| |
| this.strategy = new RecursivePrefixTreeStrategy(grid, "myGeoField"); |
| |
| this.directory = new RAMDirectory(); |
| } |
| |
| private void IndexPoints() |
| { |
| IndexWriterConfig iwConfig = new IndexWriterConfig(TEST_VERSION_CURRENT, null); |
| IndexWriter indexWriter = new IndexWriter(directory, iwConfig); |
| |
| //Spatial4j is x-y order for arguments |
| indexWriter.AddDocument(NewSampleDocument( |
| 2, ctx.MakePoint(-80.93, 33.77))); |
| |
| //Spatial4j has a WKT parser which is also "x y" order |
| indexWriter.AddDocument(NewSampleDocument( |
| 4, ctx.ReadShapeFromWkt("POINT(60.9289094 -50.7693246)"))); |
| |
| indexWriter.AddDocument(NewSampleDocument( |
| 20, ctx.MakePoint(0.1, 0.1), ctx.MakePoint(0, 0))); |
| |
| indexWriter.Dispose(); |
| } |
| |
| private Document NewSampleDocument(int id, params IShape[] shapes) |
| { |
| Document doc = new Document(); |
| doc.Add(new Int32Field("id", id, Field.Store.YES)); |
| //Potentially more than one shape in this field is supported by some |
| // strategies; see the javadocs of the SpatialStrategy impl to see. |
| foreach (IShape shape in shapes) |
| { |
| foreach (IIndexableField f in strategy.CreateIndexableFields(shape)) |
| { |
| doc.Add(f); |
| } |
| //store it too; the format is up to you |
| // (assume point in this example) |
| IPoint pt = (IPoint)shape; |
| doc.Add(new StoredField(strategy.FieldName, pt.X.ToString(CultureInfo.InvariantCulture) + " " + pt.Y.ToString(CultureInfo.InvariantCulture))); |
| } |
| |
| return doc; |
| } |
| |
| private void Search() |
| { |
| IndexReader indexReader = DirectoryReader.Open(directory); |
| IndexSearcher indexSearcher = new IndexSearcher(indexReader); |
| Sort idSort = new Sort(new SortField("id", SortFieldType.INT32)); |
| |
| //--Filter by circle (<= distance from a point) |
| { |
| //Search with circle |
| //note: SpatialArgs can be parsed from a string |
| SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, |
| ctx.MakeCircle(-80.0, 33.0, DistanceUtils.Dist2Degrees(200, DistanceUtils.EARTH_MEAN_RADIUS_KM))); |
| Filter filter = strategy.MakeFilter(args); |
| TopDocs docs = indexSearcher.Search(new MatchAllDocsQuery(), filter, 10, idSort); |
| AssertDocMatchedIds(indexSearcher, docs, 2); |
| //Now, lets get the distance for the 1st doc via computing from stored point value: |
| // (this computation is usually not redundant) |
| Document doc1 = indexSearcher.Doc(docs.ScoreDocs[0].Doc); |
| String doc1Str = doc1.GetField(strategy.FieldName).GetStringValue(); |
| //assume doc1Str is "x y" as written in newSampleDocument() |
| int spaceIdx = doc1Str.IndexOf(' '); |
| double x = double.Parse(doc1Str.Substring(0, spaceIdx - 0), CultureInfo.InvariantCulture); |
| double y = double.Parse(doc1Str.Substring(spaceIdx + 1), CultureInfo.InvariantCulture); |
| double doc1DistDEG = ctx.CalcDistance(args.Shape.Center, x, y); |
| assertEquals(121.6d, DistanceUtils.Degrees2Dist(doc1DistDEG, DistanceUtils.EARTH_MEAN_RADIUS_KM), 0.1); |
| //or more simply: |
| assertEquals(121.6d, doc1DistDEG * DistanceUtils.DEG_TO_KM, 0.1); |
| } |
| //--Match all, order by distance ascending |
| { |
| IPoint pt = ctx.MakePoint(60, -50); |
| ValueSource valueSource = strategy.MakeDistanceValueSource(pt, DistanceUtils.DEG_TO_KM);//the distance (in km) |
| Sort distSort = new Sort(valueSource.GetSortField(false)).Rewrite(indexSearcher);//false=asc dist |
| TopDocs docs = indexSearcher.Search(new MatchAllDocsQuery(), 10, distSort); |
| AssertDocMatchedIds(indexSearcher, docs, 4, 20, 2); |
| //To get the distance, we could compute from stored values like earlier. |
| // However in this example we sorted on it, and the distance will get |
| // computed redundantly. If the distance is only needed for the top-X |
| // search results then that's not a big deal. Alternatively, try wrapping |
| // the ValueSource with CachingDoubleValueSource then retrieve the value |
| // from the ValueSource now. See LUCENE-4541 for an example. |
| } |
| //demo arg parsing |
| { |
| SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, |
| ctx.MakeCircle(-80.0, 33.0, 1)); |
| SpatialArgs args2 = new SpatialArgsParser().Parse("Intersects(BUFFER(POINT(-80 33),1))", ctx); |
| assertEquals(args.toString(), args2.toString()); |
| } |
| |
| indexReader.Dispose(); |
| } |
| |
| private void AssertDocMatchedIds(IndexSearcher indexSearcher, TopDocs docs, params int[] ids) |
| { |
| int[] |
| gotIds = new int[docs.TotalHits]; |
| for (int i = 0; i < gotIds.Length; i++) |
| { |
| gotIds[i] = indexSearcher.Doc(docs.ScoreDocs[i].Doc).GetField("id").GetInt32Value().Value; |
| } |
| assertArrayEquals(ids, gotIds); |
| } |
| } |
| } |