blob: c6f7c4572e33319bf3b865710969324f0bca5508 [file] [log] [blame]
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);
}
}
}