You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2016/11/25 11:07:20 UTC
[15/26] lucenenet git commit: Added missing Spatial classes,
ported Spatial tests, and fixed several bugs
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj b/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj
new file mode 100644
index 0000000..d74a255
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Lucene.Net.Tests.Spatial.csproj
@@ -0,0 +1,130 @@
+\ufeff<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{31F52F5C-A08F-4363-8003-23D6F7D6EB3A}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Lucene.Net.Tests.Spatial</RootNamespace>
+ <AssemblyName>Lucene.Net.Tests.Spatial</AssemblyName>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="Spatial4n.Core.NTS">
+ <HintPath>..\..\packages\Spatial4n.Core.NTS.0.4.1\lib\net40\Spatial4n.Core.NTS.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="GeoAPI, Version=1.7.4.0, Culture=neutral, PublicKeyToken=a1a0da7def465678, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\GeoAPI.1.7.4\lib\net45\GeoAPI.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="NetTopologySuite, Version=1.14.0.0, Culture=neutral, PublicKeyToken=f580a05016ebada1, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\NetTopologySuite.1.14\lib\net45\NetTopologySuite.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="PowerCollections, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2573bf8a1bdddcd5, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\NetTopologySuite.1.14\lib\net45\PowerCollections.dll</HintPath>
+ <Private>True</Private>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="DistanceStrategyTest.cs" />
+ <Compile Include="PortedSolr3Test.cs" />
+ <Compile Include="Prefix\NtsPolygonTest.cs" />
+ <Compile Include="Prefix\SpatialOpRecursivePrefixTreeTest.cs" />
+ <Compile Include="Prefix\TestRecursivePrefixTreeStrategy.cs" />
+ <Compile Include="Prefix\TestTermQueryPrefixGridStrategy.cs" />
+ <Compile Include="Prefix\Tree\SpatialPrefixTreeTest.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="QueryEqualsHashCodeTest.cs" />
+ <Compile Include="Query\SpatialArgsParserTest.cs" />
+ <Compile Include="Serialized\SerializedStrategyTest.cs" />
+ <Compile Include="SpatialArgsTest.cs" />
+ <Compile Include="SpatialExample.cs" />
+ <Compile Include="SpatialMatchConcern.cs" />
+ <Compile Include="SpatialTestCase.cs" />
+ <Compile Include="SpatialTestData.cs" />
+ <Compile Include="SpatialTestQuery.cs" />
+ <Compile Include="StrategyTestCase.cs" />
+ <Compile Include="TestTestFramework.cs" />
+ <Compile Include="Vector\TestPointVectorStrategy.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Lucene.Net.Core\Lucene.Net.csproj">
+ <Project>{5d4ad9be-1ffb-41ab-9943-25737971bf57}</Project>
+ <Name>Lucene.Net</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Lucene.Net.Queries\Lucene.Net.Queries.csproj">
+ <Project>{69D7956C-C2CC-4708-B399-A188FEC384C4}</Project>
+ <Name>Lucene.Net.Queries</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Lucene.Net.Spatial\Lucene.Net.Spatial.csproj">
+ <Project>{35c347f4-24b2-4be5-8117-a0e3001551ce}</Project>
+ <Name>Lucene.Net.Spatial</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Lucene.Net.TestFramework\Lucene.Net.TestFramework.csproj">
+ <Project>{B2C0D749-CE34-4F62-A15E-00CB2FF5DDB3}</Project>
+ <Name>Lucene.Net.TestFramework</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Test-Files\cities-Intersects-BBox.txt" />
+ <EmbeddedResource Include="Test-Files\Data\countries-bbox.txt" />
+ <EmbeddedResource Include="Test-Files\Data\countries-poly.txt" />
+ <EmbeddedResource Include="Test-Files\Data\geonames-IE.txt" />
+ <EmbeddedResource Include="Test-Files\Data\LUCENE-4464.txt" />
+ <EmbeddedResource Include="Test-Files\Data\simple-bbox.txt" />
+ <EmbeddedResource Include="Test-Files\Data\states-bbox.txt" />
+ <EmbeddedResource Include="Test-Files\Data\states-poly.txt" />
+ <EmbeddedResource Include="Test-Files\Data\world-cities-points.txt" />
+ <EmbeddedResource Include="Test-Files\simple-Queries-BBox.txt" />
+ <EmbeddedResource Include="Test-Files\states-Intersects-BBox.txt" />
+ <EmbeddedResource Include="Test-Files\states-IsWithin-BBox.txt" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs b/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs
new file mode 100644
index 0000000..e120b0b
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/PortedSolr3Test.cs
@@ -0,0 +1,201 @@
+\ufeffusing Lucene.Net.Randomized.Generators;
+using Lucene.Net.Search;
+using Lucene.Net.Spatial.Prefix;
+using Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Queries;
+using Lucene.Net.Spatial.Vector;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Distance;
+using Spatial4n.Core.Shapes;
+using System;
+using System.Collections.Generic;
+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>
+ /// Based off of Solr 3's SpatialFilterTest.
+ /// </summary>
+ public class PortedSolr3Test : StrategyTestCase
+ {
+ //@ParametersFactory
+ public static IList<Object[]> Parameters()
+ {
+ List<Object[]> ctorArgs = new List<object[]>();
+
+ SpatialContext ctx = SpatialContext.GEO;
+ SpatialPrefixTree grid;
+ SpatialStrategy strategy;
+
+ grid = new GeohashPrefixTree(ctx, 12);
+ strategy = new RecursivePrefixTreeStrategy(grid, "recursive_geohash");
+ ctorArgs.Add(new Object[] { new Param(strategy) });
+
+ grid = new QuadPrefixTree(ctx, 25);
+ strategy = new RecursivePrefixTreeStrategy(grid, "recursive_quad");
+ ctorArgs.Add(new Object[] { new Param(strategy) });
+
+ grid = new GeohashPrefixTree(ctx, 12);
+ strategy = new TermQueryPrefixTreeStrategy(grid, "termquery_geohash");
+ ctorArgs.Add(new Object[] { new Param(strategy) });
+
+ strategy = new PointVectorStrategy(ctx, "pointvector");
+ ctorArgs.Add(new Object[] { new Param(strategy) });
+
+ return ctorArgs;
+ }
+
+ // this is a hack for clover! (otherwise strategy.toString() used as file name)
+ public class Param
+ {
+ internal SpatialStrategy strategy;
+
+ internal Param(SpatialStrategy strategy) { this.strategy = strategy; }
+
+
+ public override String ToString() { return strategy.FieldName; }
+ }
+
+ // private String fieldName;
+
+ //public PortedSolr3Test(Param param)
+ //{
+ // SpatialStrategy strategy = param.strategy;
+ // this.ctx = strategy.SpatialContext;
+ // this.strategy = strategy;
+ //}
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ SpatialStrategy strategy = ((Param)(RandomInts.RandomFrom(Random(), Parameters()))[0]).strategy;
+ this.ctx = strategy.SpatialContext;
+ this.strategy = strategy;
+ }
+
+ private void setupDocs()
+ {
+ base.DeleteAll();
+ adoc("1", ctx.MakePoint(-79.9289094, 32.7693246));
+ adoc("2", ctx.MakePoint(-80.9289094, 33.7693246));
+ adoc("3", ctx.MakePoint(50.9289094, -32.7693246));
+ adoc("4", ctx.MakePoint(60.9289094, -50.7693246));
+ adoc("5", ctx.MakePoint(0, 0));
+ adoc("6", ctx.MakePoint(0.1, 0.1));
+ adoc("7", ctx.MakePoint(-0.1, -0.1));
+ adoc("8", ctx.MakePoint(179.9, 0));
+ adoc("9", ctx.MakePoint(-179.9, 0));
+ adoc("10", ctx.MakePoint(50, 89.9));
+ adoc("11", ctx.MakePoint(-130, 89.9));
+ adoc("12", ctx.MakePoint(50, -89.9));
+ adoc("13", ctx.MakePoint(-130, -89.9));
+ Commit();
+ }
+
+
+ [Test]
+ public virtual void TestIntersections()
+ {
+ setupDocs();
+ //Try some edge cases
+ //NOTE: 2nd arg is distance in kilometers
+ CheckHitsCircle(ctx.MakePoint(1, 1), 175, 3, 5, 6, 7);
+ CheckHitsCircle(ctx.MakePoint(179.8, 0), 200, 2, 8, 9);
+ CheckHitsCircle(ctx.MakePoint(50, 89.8), 200, 2, 10, 11);//this goes over the north pole
+ CheckHitsCircle(ctx.MakePoint(50, -89.8), 200, 2, 12, 13);//this goes over the south pole
+ //try some normal cases
+ CheckHitsCircle(ctx.MakePoint(-80.0, 33.0), 300, 2);
+ //large distance
+ CheckHitsCircle(ctx.MakePoint(1, 1), 5000, 3, 5, 6, 7);
+ //Because we are generating a box based on the west/east longitudes and the south/north latitudes, which then
+ //translates to a range query, which is slightly more inclusive. Thus, even though 0.0 is 15.725 kms away,
+ //it will be included, b/c of the box calculation.
+ CheckHitsBBox(ctx.MakePoint(0.1, 0.1), 15, 2, 5, 6);
+ //try some more
+ DeleteAll();
+ adoc("14", ctx.MakePoint(5, 0));
+ adoc("15", ctx.MakePoint(15, 0));
+ //3000KM from 0,0, see http://www.movable-type.co.uk/scripts/latlong.html
+ adoc("16", ctx.MakePoint(19.79750, 18.71111));
+ adoc("17", ctx.MakePoint(-95.436643, 44.043900));
+ Commit();
+
+ CheckHitsCircle(ctx.MakePoint(0, 0), 1000, 1, 14);
+ CheckHitsCircle(ctx.MakePoint(0, 0), 2000, 2, 14, 15);
+ CheckHitsBBox(ctx.MakePoint(0, 0), 3000, 3, 14, 15, 16);
+ CheckHitsCircle(ctx.MakePoint(0, 0), 3001, 3, 14, 15, 16);
+ CheckHitsCircle(ctx.MakePoint(0, 0), 3000.1, 3, 14, 15, 16);
+
+ //really fine grained distance and reflects some of the vagaries of how we are calculating the box
+ CheckHitsCircle(ctx.MakePoint(-96.789603, 43.517030), 109, 0);
+
+ // falls outside of the real distance, but inside the bounding box
+ CheckHitsCircle(ctx.MakePoint(-96.789603, 43.517030), 110, 0);
+ CheckHitsBBox(ctx.MakePoint(-96.789603, 43.517030), 110, 1, 17);
+ }
+
+ //---- these are similar to Solr test methods
+
+ private void CheckHitsCircle(IPoint pt, double distKM, int assertNumFound, params int[] assertIds)
+ {
+ _CheckHits(false, pt, distKM, assertNumFound, assertIds);
+ }
+ private void CheckHitsBBox(IPoint pt, double distKM, int assertNumFound, params int[] assertIds)
+ {
+ _CheckHits(true, pt, distKM, assertNumFound, assertIds);
+ }
+
+ private void _CheckHits(bool bbox, IPoint pt, double distKM, int assertNumFound, params int[] assertIds)
+ {
+ SpatialOperation op = SpatialOperation.Intersects;
+ double distDEG = DistanceUtils.Dist2Degrees(distKM, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+ IShape shape = ctx.MakeCircle(pt, distDEG);
+ if (bbox)
+ shape = shape.BoundingBox;
+
+ SpatialArgs args = new SpatialArgs(op, shape);
+ //args.setDistPrecision(0.025);
+ Query query;
+ if (Random().nextBoolean())
+ {
+ query = strategy.MakeQuery(args);
+ }
+ else
+ {
+ query = new FilteredQuery(new MatchAllDocsQuery(), strategy.MakeFilter(args));
+ }
+ SearchResults results = executeQuery(query, 100);
+ assertEquals("" + shape, assertNumFound, results.numFound);
+ if (assertIds != null)
+ {
+ ISet<int?> resultIds = new HashSet<int?>();
+ foreach (SearchResult result in results.results)
+ {
+ resultIds.add(int.Parse(result.document.Get("id"), CultureInfo.InvariantCulture));
+ }
+ foreach (int assertId in assertIds)
+ {
+ assertTrue("has " + assertId, resultIds.contains(assertId));
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs b/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs
new file mode 100644
index 0000000..7f4338d
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/NtsPolygonTest.cs
@@ -0,0 +1,118 @@
+\ufeffusing Lucene.Net.Documents;
+using Lucene.Net.Search;
+using Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Queries;
+using Lucene.Net.Support;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Context.Nts;
+using Spatial4n.Core.Shapes;
+using System;
+using System.Collections.Generic;
+
+namespace Lucene.Net.Spatial.Prefix
+{
+ /*
+ * 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.
+ */
+
+ public class NtsPolygonTest : StrategyTestCase
+ {
+ private static readonly double LUCENE_4464_distErrPct = SpatialArgs.DEFAULT_DISTERRPCT;//DEFAULT 2.5%
+
+ public NtsPolygonTest()
+ {
+ try
+ {
+ IDictionary<string, string> args = new Dictionary<string, string>();
+ args.Put("spatialContextFactory",
+ typeof(NtsSpatialContextFactory).AssemblyQualifiedName);
+ ctx = SpatialContextFactory.MakeSpatialContext(args /*, getClass().getClassLoader()*/);
+ }
+ catch (TypeLoadException e) //LUCENENET TODO: Does this match NoClassDefFoundError ??
+ {
+ AssumeTrue("This test requires JTS jar: " + e, false);
+ }
+
+ GeohashPrefixTree grid = new GeohashPrefixTree(ctx, 11);//< 1 meter == 11 maxLevels
+ this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
+ ((RecursivePrefixTreeStrategy)this.strategy).DistErrPct = (LUCENE_4464_distErrPct);//1% radius (small!)
+ }
+
+ [Test]
+ /** LUCENE-4464 */
+ public virtual void TestCloseButNoMatch()
+ {
+ getAddAndVerifyIndexedDocuments("LUCENE-4464.txt");
+ SpatialArgs args = q(
+ "POLYGON((-93.18100824442227 45.25676372469945," +
+ "-93.23182001200654 45.21421290799412," +
+ "-93.16315546122038 45.23742639412364," +
+ "-93.18100824442227 45.25676372469945))",
+ LUCENE_4464_distErrPct);
+ SearchResults got = executeQuery(strategy.MakeQuery(args), 100);
+ assertEquals(1, got.numFound);
+ assertEquals("poly2", got.results[0].document.Get("id"));
+ //did not find poly 1 !
+ }
+
+ private SpatialArgs q(String shapeStr, double distErrPct)
+ {
+ IShape shape = ctx.ReadShapeFromWkt(shapeStr);
+ SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);
+ args.DistErrPct = (distErrPct);
+ return args;
+ }
+
+ /**
+ * A PrefixTree pruning optimization gone bad.
+ * See <a href="https://issues.apache.org/jira/browse/LUCENE-4770>LUCENE-4770</a>.
+ */
+ [Test]
+ public virtual void TestBadPrefixTreePrune()
+ {
+
+ IShape area = ctx.ReadShapeFromWkt("POLYGON((-122.83 48.57, -122.77 48.56, -122.79 48.53, -122.83 48.57))");
+
+ SpatialPrefixTree trie = new QuadPrefixTree(ctx, 12);
+ TermQueryPrefixTreeStrategy strategy = new TermQueryPrefixTreeStrategy(trie, "geo");
+ Document doc = new Document();
+ doc.Add(new TextField("id", "1", Field.Store.YES)); // LUCENENET TODO: Change API to Store.Yes
+
+ Field[] fields = strategy.CreateIndexableFields(area, 0.025);
+ foreach (Field field in fields)
+ {
+ doc.Add(field);
+ }
+ AddDocument(doc);
+
+ IPoint upperleft = ctx.MakePoint(-122.88, 48.54);
+ IPoint lowerright = ctx.MakePoint(-122.82, 48.62);
+
+ Query query = strategy.MakeQuery(new SpatialArgs(SpatialOperation.Intersects, ctx.MakeRectangle(upperleft, lowerright)));
+ Commit();
+
+ TopDocs search = indexSearcher.Search(query, 10);
+ ScoreDoc[] scoreDocs = search.ScoreDocs;
+ foreach (ScoreDoc scoreDoc in scoreDocs)
+ {
+ Console.WriteLine(indexSearcher.Doc(scoreDoc.Doc));
+ }
+
+ assertEquals(1, search.TotalHits);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs b/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs
new file mode 100644
index 0000000..d7d3fb7
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/SpatialOpRecursivePrefixTreeTest.cs
@@ -0,0 +1,485 @@
+\ufeffusing Lucene.Net.Documents;
+using Lucene.Net.Search;
+using Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Queries;
+using Lucene.Net.Support;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Shapes;
+using Spatial4n.Core.Shapes.Impl;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.Spatial.Prefix
+{
+ /*
+ * 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.
+ */
+
+ public class SpatialOpRecursivePrefixTreeTest : StrategyTestCase
+ {
+ const int ITERATIONS = 1;//Test Iterations
+
+ private SpatialPrefixTree grid;
+
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ DeleteAll();
+ }
+
+ public virtual void SetupGrid(int maxLevels)
+ {
+ if (Random().nextBoolean())
+ SetupQuadGrid(maxLevels);
+ else
+ SetupGeohashGrid(maxLevels);
+ //((PrefixTreeStrategy) strategy).setDistErrPct(0);//fully precise to grid
+
+ Console.WriteLine("Strategy: " + strategy.toString());
+ }
+
+ private void SetupQuadGrid(int maxLevels)
+ {
+ //non-geospatial makes this test a little easier (in gridSnap), and using boundary values 2^X raises
+ // the prospect of edge conditions we want to test, plus makes for simpler numbers (no decimals).
+ SpatialContextFactory factory = new SpatialContextFactory();
+ factory.geo = false;
+ factory.worldBounds = new Rectangle(0, 256, -128, 128, null);
+ this.ctx = factory.NewSpatialContext();
+ //A fairly shallow grid, and default 2.5% distErrPct
+ if (maxLevels == -1)
+ maxLevels = randomIntBetween(1, 8);//max 64k cells (4^8), also 256*256
+ this.grid = new QuadPrefixTree(ctx, maxLevels);
+ this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
+ }
+
+ public virtual void SetupGeohashGrid(int maxLevels)
+ {
+ this.ctx = SpatialContext.GEO;
+ //A fairly shallow grid, and default 2.5% distErrPct
+ if (maxLevels == -1)
+ maxLevels = randomIntBetween(1, 3);//max 16k cells (32^3)
+ this.grid = new GeohashPrefixTree(ctx, maxLevels);
+ this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
+ }
+
+ [Test, Repeat(ITERATIONS)]
+ public virtual void TestIntersects()
+ {
+ SetupGrid(-1);
+ doTest(SpatialOperation.Intersects);
+ }
+
+ [Test, Repeat(ITERATIONS)]
+ public virtual void TestWithin()
+ {
+ SetupGrid(-1);
+ doTest(SpatialOperation.IsWithin);
+ }
+
+ [Test, Repeat(ITERATIONS)]
+ public virtual void TestContains()
+ {
+ SetupGrid(-1);
+ doTest(SpatialOperation.Contains);
+ }
+
+ [Test, Repeat(ITERATIONS)]
+ public virtual void TestDisjoint()
+ {
+ SetupGrid(-1);
+ doTest(SpatialOperation.IsDisjointTo);
+ }
+
+ /** See LUCENE-5062, {@link ContainsPrefixTreeFilter#multiOverlappingIndexedShapes}. */
+ [Test, Repeat(ITERATIONS)]
+ public virtual void TestContainsPairOverlap()
+ {
+ SetupQuadGrid(3);
+ adoc("0", new ShapePair(ctx.MakeRectangle(0, 33, -128, 128), ctx.MakeRectangle(33, 128, -128, 128), true, ctx));
+ Commit();
+ Query query = strategy.MakeQuery(new SpatialArgs(SpatialOperation.Contains,
+ ctx.MakeRectangle(0, 128, -16, 128)));
+ SearchResults searchResults = executeQuery(query, 1);
+ assertEquals(1, searchResults.numFound);
+ }
+
+ [Test]
+ public virtual void TestWithinDisjointParts()
+ {
+ SetupQuadGrid(7);
+ //one shape comprised of two parts, quite separated apart
+ adoc("0", new ShapePair(ctx.MakeRectangle(0, 10, -120, -100), ctx.MakeRectangle(220, 240, 110, 125), false, ctx));
+ Commit();
+ //query surrounds only the second part of the indexed shape
+ Query query = strategy.MakeQuery(new SpatialArgs(SpatialOperation.IsWithin,
+ ctx.MakeRectangle(210, 245, 105, 128)));
+ SearchResults searchResults = executeQuery(query, 1);
+ //we shouldn't find it because it's not completely within
+ assertTrue(searchResults.numFound == 0);
+ }
+
+ [Test] /** LUCENE-4916 */
+ public virtual void TestWithinLeafApproxRule()
+ {
+ SetupQuadGrid(2);//4x4 grid
+ //indexed shape will simplify to entire right half (2 top cells)
+ adoc("0", ctx.MakeRectangle(192, 204, -128, 128));
+ Commit();
+
+ ((RecursivePrefixTreeStrategy)strategy).PrefixGridScanLevel = (Random().nextInt(2 + 1));
+
+ //query does NOT contain it; both indexed cells are leaves to the query, and
+ // when expanded to the full grid cells, the top one's top row is disjoint
+ // from the query and thus not a match.
+ assertTrue(executeQuery(strategy.MakeQuery(
+ new SpatialArgs(SpatialOperation.IsWithin, ctx.MakeRectangle(38, 192, -72, 56))
+ ), 1).numFound == 0);//no-match
+
+ //this time the rect is a little bigger and is considered a match. It's a
+ // an acceptable false-positive because of the grid approximation.
+ assertTrue(executeQuery(strategy.MakeQuery(
+ new SpatialArgs(SpatialOperation.IsWithin, ctx.MakeRectangle(38, 192, -72, 80))
+ ), 1).numFound == 1);//match
+ }
+
+ //Override so we can index parts of a pair separately, resulting in the detailLevel
+ // being independent for each shape vs the whole thing
+ protected override Document newDoc(String id, IShape shape)
+ {
+ Document doc = new Document();
+ doc.Add(new StringField("id", id, Field.Store.YES));
+ if (shape != null)
+ {
+ IList<IShape> shapes;
+ if (shape is ShapePair)
+ {
+ shapes = new List<IShape>(2);
+ shapes.Add(((ShapePair)shape).shape1);
+ shapes.Add(((ShapePair)shape).shape2);
+ }
+ else
+ {
+ shapes = new List<IShape>(new IShape[] { shape });//Collections.Singleton(shape);
+ }
+ foreach (IShape shapei in shapes)
+ {
+ foreach (Field f in strategy.CreateIndexableFields(shapei))
+ {
+ doc.Add(f);
+ }
+ }
+ if (storeShape)//just for diagnostics
+ doc.Add(new StoredField(strategy.FieldName, shape.toString()));
+ }
+ return doc;
+ }
+
+ private void doTest(SpatialOperation operation)
+ {
+ //first show that when there's no data, a query will result in no results
+ {
+ Query query = strategy.MakeQuery(new SpatialArgs(operation, randomRectangle()));
+ SearchResults searchResults = executeQuery(query, 1);
+ assertEquals(0, searchResults.numFound);
+ }
+
+ bool biasContains = (operation == SpatialOperation.Contains);
+
+ //Main index loop:
+ IDictionary<String, IShape> indexedShapes = new LinkedHashMap<String, IShape>();
+ IDictionary<String, IShape> indexedShapesGS = new LinkedHashMap<String, IShape>();//grid snapped
+ int numIndexedShapes = randomIntBetween(1, 6);
+ bool indexedAtLeastOneShapePair = false;
+ for (int i = 0; i < numIndexedShapes; i++)
+ {
+ String id = "" + i;
+ IShape indexedShape;
+ int R = Random().nextInt(12);
+ if (R == 0)
+ {//1 in 12
+ indexedShape = null;
+ }
+ else if (R == 1)
+ {//1 in 12
+ indexedShape = randomPoint();//just one point
+ }
+ else if (R <= 4)
+ {//3 in 12
+ //comprised of more than one shape
+ indexedShape = randomShapePairRect(biasContains);
+ indexedAtLeastOneShapePair = true;
+ }
+ else
+ {
+ indexedShape = randomRectangle();//just one rect
+ }
+
+ indexedShapes.Put(id, indexedShape);
+ indexedShapesGS.Put(id, gridSnap(indexedShape));
+
+ adoc(id, indexedShape);
+
+ if (Random().nextInt(10) == 0)
+ Commit();//intermediate commit, produces extra segments
+
+ }
+ //delete some documents randomly
+ IEnumerator<String> idIter = indexedShapes.Keys.ToList().GetEnumerator();
+ while (idIter.MoveNext())
+ {
+ String id = idIter.Current;
+ if (Random().nextInt(10) == 0)
+ {
+ DeleteDoc(id);
+ //idIter.Remove();
+ indexedShapes.Remove(id); // LUCENENET TODO: Verify this works.
+ indexedShapesGS.Remove(id);
+ }
+ }
+
+ Commit();
+
+ //Main query loop:
+ int numQueryShapes = AtLeast(20);
+ for (int i = 0; i < numQueryShapes; i++)
+ {
+ int scanLevel = randomInt(grid.MaxLevels);
+ ((RecursivePrefixTreeStrategy)strategy).PrefixGridScanLevel = (scanLevel);
+
+ IShape queryShape;
+ switch (randomInt(10))
+ {
+ case 0: queryShape = randomPoint(); break;
+ // LUCENE-5549
+ //TODO debug: -Dtests.method=testWithin -Dtests.multiplier=3 -Dtests.seed=5F5294CE2E075A3E:AAD2F0F79288CA64
+ // case 1:case 2:case 3:
+ // if (!indexedAtLeastOneShapePair) { // avoids ShapePair.relate(ShapePair), which isn't reliable
+ // queryShape = randomShapePairRect(!biasContains);//invert biasContains for query side
+ // break;
+ // }
+ default: queryShape = randomRectangle(); break;
+ }
+ IShape queryShapeGS = gridSnap(queryShape);
+
+ bool opIsDisjoint = operation == SpatialOperation.IsDisjointTo;
+
+ //Generate truth via brute force:
+ // We ensure true-positive matches (if the predicate on the raw shapes match
+ // then the search should find those same matches).
+ // approximations, false-positive matches
+ ISet<String> expectedIds = new /* LinkedHashSet<string>*/ HashSet<string>();//true-positives
+ ISet<String> secondaryIds = new /* LinkedHashSet<string>*/ HashSet<string>();//false-positives (unless disjoint)
+ foreach (var entry in indexedShapes)
+ {
+ String id = entry.Key;
+ IShape indexedShapeCompare = entry.Value;
+ if (indexedShapeCompare == null)
+ continue;
+ IShape queryShapeCompare = queryShape;
+
+ if (operation.Evaluate(indexedShapeCompare, queryShapeCompare))
+ {
+ expectedIds.add(id);
+ if (opIsDisjoint)
+ {
+ //if no longer intersect after buffering them, for disjoint, remember this
+ indexedShapeCompare = indexedShapesGS[id];
+ queryShapeCompare = queryShapeGS;
+ if (!operation.Evaluate(indexedShapeCompare, queryShapeCompare))
+ secondaryIds.add(id);
+ }
+ }
+ else if (!opIsDisjoint)
+ {
+ //buffer either the indexed or query shape (via gridSnap) and try again
+ if (operation == SpatialOperation.Intersects)
+ {
+ indexedShapeCompare = indexedShapesGS[id];
+ queryShapeCompare = queryShapeGS;
+ //TODO Unfortunately, grid-snapping both can result in intersections that otherwise
+ // wouldn't happen when the grids are adjacent. Not a big deal but our test is just a
+ // bit more lenient.
+ }
+ else if (operation == SpatialOperation.Contains)
+ {
+ indexedShapeCompare = indexedShapesGS[id];
+ }
+ else if (operation == SpatialOperation.IsWithin)
+ {
+ queryShapeCompare = queryShapeGS;
+ }
+ if (operation.Evaluate(indexedShapeCompare, queryShapeCompare))
+ secondaryIds.add(id);
+ }
+ }
+
+ //Search and verify results
+ SpatialArgs args = new SpatialArgs(operation, queryShape);
+ if (queryShape is ShapePair)
+ args.DistErrPct = (0.0);//a hack; we want to be more detailed than gridSnap(queryShape)
+ Query query = strategy.MakeQuery(args);
+ SearchResults got = executeQuery(query, 100);
+ ISet<String> remainingExpectedIds = new /* LinkedHashSet<string>*/ HashSet<string>(expectedIds);
+ foreach (SearchResult result in got.results)
+ {
+ String id = result.GetId();
+ bool removed = remainingExpectedIds.remove(id);
+ if (!removed && (!opIsDisjoint && !secondaryIds.contains(id)))
+ {
+ fail("Shouldn't match", id, indexedShapes, indexedShapesGS, queryShape);
+ }
+ }
+ if (opIsDisjoint)
+ remainingExpectedIds.removeAll(secondaryIds);
+ if (remainingExpectedIds.Any())
+ {
+ var iter = remainingExpectedIds.GetEnumerator();
+ iter.MoveNext();
+ String id = iter.Current;
+ fail("Should have matched", id, indexedShapes, indexedShapesGS, queryShape);
+ }
+ }
+ }
+
+ private IShape randomShapePairRect(bool biasContains)
+ {
+ IRectangle shape1 = randomRectangle();
+ IRectangle shape2 = randomRectangle();
+ return new ShapePair(shape1, shape2, biasContains, ctx);
+ }
+
+ private void fail(String label, String id, IDictionary<String, IShape> indexedShapes, IDictionary<String, IShape> indexedShapesGS, IShape queryShape)
+ {
+ Console.WriteLine("Ig:" + indexedShapesGS[id] + " Qg:" + gridSnap(queryShape));
+ fail(label + " I#" + id + ":" + indexedShapes[id] + " Q:" + queryShape);
+ }
+
+ // private Rectangle inset(Rectangle r) {
+ // //typically inset by 1 (whole numbers are easy to read)
+ // double d = Math.min(1.0, grid.getDistanceForLevel(grid.getMaxLevels()) / 4);
+ // return ctx.makeRectangle(r.getMinX() + d, r.getMaxX() - d, r.getMinY() + d, r.getMaxY() - d);
+ // }
+
+ protected IShape gridSnap(IShape snapMe)
+ {
+ if (snapMe == null)
+ return null;
+ if (snapMe is ShapePair)
+ {
+ ShapePair me = (ShapePair)snapMe;
+ return new ShapePair(gridSnap(me.shape1), gridSnap(me.shape2), me.biasContainsThenWithin, ctx);
+ }
+ if (snapMe is IPoint)
+ {
+ snapMe = snapMe.BoundingBox;
+ }
+ //The next 4 lines mimic PrefixTreeStrategy.createIndexableFields()
+ double distErrPct = ((PrefixTreeStrategy)strategy).DistErrPct;
+ double distErr = SpatialArgs.CalcDistanceFromErrPct(snapMe, distErrPct, ctx);
+ int detailLevel = grid.GetLevelForDistance(distErr);
+ IList<Cell> cells = grid.GetCells(snapMe, detailLevel, false, true);
+
+ //calc bounding box of cells.
+ List<IShape> cellShapes = new List<IShape>(cells.size());
+ foreach (Cell cell in cells)
+ {
+ cellShapes.Add(cell.GetShape());
+ }
+ return new ShapeCollection(cellShapes, ctx).BoundingBox;
+ }
+
+ /**
+ * An aggregate of 2 shapes. Unfortunately we can't simply use a ShapeCollection because:
+ * (a) ambiguity between CONTAINS & WITHIN for equal shapes, and
+ * (b) adjacent pairs could as a whole contain the input shape.
+ * The tests here are sensitive to these matters, although in practice ShapeCollection
+ * is fine.
+ */
+ private class ShapePair : ShapeCollection /*<Shape>*/
+ {
+
+ private readonly SpatialContext ctx;
+ internal IShape shape1, shape2;
+ internal bool biasContainsThenWithin;//a hack
+
+ public ShapePair(IShape shape1, IShape shape2, bool containsThenWithin, SpatialContext ctx)
+ : base(Arrays.AsList(shape1, shape2), ctx)
+ {
+
+ this.shape1 = shape1;
+ this.shape2 = shape2;
+ biasContainsThenWithin = containsThenWithin;
+ }
+
+ public override SpatialRelation Relate(IShape other)
+ {
+ SpatialRelation r = relateApprox(other);
+ if (r == SpatialRelation.CONTAINS)
+ return r;
+ if (r == SpatialRelation.DISJOINT)
+ return r;
+ if (r == SpatialRelation.WITHIN && !biasContainsThenWithin)
+ return r;
+
+ //See if the correct answer is actually Contains, when the indexed shapes are adjacent,
+ // creating a larger shape that contains the input shape.
+ bool pairTouches = shape1.Relate(shape2).Intersects();
+ if (!pairTouches)
+ return r;
+ //test all 4 corners
+ IRectangle oRect = (IRectangle)other;
+ if (Relate(ctx.MakePoint(oRect.MinX, oRect.MinY)) == SpatialRelation.CONTAINS
+ && Relate(ctx.MakePoint(oRect.MinX, oRect.MaxY)) == SpatialRelation.CONTAINS
+ && Relate(ctx.MakePoint(oRect.MaxX, oRect.MinY)) == SpatialRelation.CONTAINS
+ && Relate(ctx.MakePoint(oRect.MaxX, oRect.MaxY)) == SpatialRelation.CONTAINS)
+ return SpatialRelation.CONTAINS;
+ return r;
+ }
+
+ private SpatialRelation relateApprox(IShape other)
+ {
+ if (biasContainsThenWithin)
+ {
+ if (shape1.Relate(other) == SpatialRelation.CONTAINS || shape1.equals(other)
+ || shape2.Relate(other) == SpatialRelation.CONTAINS || shape2.equals(other)) return SpatialRelation.CONTAINS;
+
+ if (shape1.Relate(other) == SpatialRelation.WITHIN && shape2.Relate(other) == SpatialRelation.WITHIN) return SpatialRelation.WITHIN;
+
+ }
+ else
+ {
+ if ((shape1.Relate(other) == SpatialRelation.WITHIN || shape1.equals(other))
+ && (shape2.Relate(other) == SpatialRelation.WITHIN || shape2.equals(other))) return SpatialRelation.WITHIN;
+
+ if (shape1.Relate(other) == SpatialRelation.CONTAINS || shape2.Relate(other) == SpatialRelation.CONTAINS) return SpatialRelation.CONTAINS;
+ }
+
+ if (shape1.Relate(other).Intersects() || shape2.Relate(other).Intersects())
+ return SpatialRelation.INTERSECTS;//might actually be 'CONTAINS' if the pair are adjacent but we handle that later
+ return SpatialRelation.DISJOINT;
+ }
+
+ public override String ToString()
+ {
+ return "ShapePair(" + shape1 + " , " + shape2 + ")";
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs b/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs
new file mode 100644
index 0000000..f55b189
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/TestRecursivePrefixTreeStrategy.cs
@@ -0,0 +1,127 @@
+\ufeffusing Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Queries;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Distance;
+using Spatial4n.Core.Shapes;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Lucene.Net.Spatial.Prefix
+{
+ /*
+ * 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.
+ */
+
+ public class TestRecursivePrefixTreeStrategy : StrategyTestCase
+ {
+ private int maxLength;
+
+ //Tests should call this first.
+ private void init(int maxLength)
+ {
+ this.maxLength = maxLength;
+ this.ctx = SpatialContext.GEO;
+ GeohashPrefixTree grid = new GeohashPrefixTree(ctx, maxLength);
+ this.strategy = new RecursivePrefixTreeStrategy(grid, GetType().Name);
+ }
+
+ [Test]
+ public virtual void TestFilterWithVariableScanLevel()
+ {
+ init(GeohashPrefixTree.MaxLevelsPossible);
+ getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
+
+ //execute queries for each prefix grid scan level
+ for (int i = 0; i <= maxLength; i++)
+ {
+ ((RecursivePrefixTreeStrategy)strategy).PrefixGridScanLevel = (i);
+ executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_Intersects_BBox);
+ }
+ }
+
+ [Test]
+ public virtual void TestOneMeterPrecision()
+ {
+ init(GeohashPrefixTree.MaxLevelsPossible);
+ GeohashPrefixTree grid = (GeohashPrefixTree)((RecursivePrefixTreeStrategy)strategy).Grid;
+ //DWS: I know this to be true. 11 is needed for one meter
+ double degrees = DistanceUtils.Dist2Degrees(0.001, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+ assertEquals(11, grid.GetLevelForDistance(degrees));
+ }
+
+ [Test]
+ public virtual void TestPrecision()
+ {
+ init(GeohashPrefixTree.MaxLevelsPossible);
+
+ Spatial4n.Core.Shapes.IPoint iPt = ctx.MakePoint(2.8028712999999925, 48.3708044);//lon, lat
+ AddDocument(newDoc("iPt", iPt));
+ Commit();
+
+ Spatial4n.Core.Shapes.IPoint qPt = ctx.MakePoint(2.4632387000000335, 48.6003516);
+
+ double KM2DEG = DistanceUtils.Dist2Degrees(1, DistanceUtils.EARTH_MEAN_RADIUS_KM);
+ double DEG2KM = 1 / KM2DEG;
+
+ double DIST = 35.75;//35.7499...
+ assertEquals(DIST, ctx.DistCalc.Distance(iPt, qPt) * DEG2KM, 0.001);
+
+ //distErrPct will affect the query shape precision. The indexed precision
+ // was set to nearly zilch via init(GeohashPrefixTree.getMaxLevelsPossible());
+ double distErrPct = 0.025; //the suggested default, by the way
+ double distMult = 1 + distErrPct;
+
+ assertTrue(35.74 * distMult >= DIST);
+ checkHits(q(qPt, 35.74 * KM2DEG, distErrPct), 1, null);
+
+ assertTrue(30 * distMult < DIST);
+ checkHits(q(qPt, 30 * KM2DEG, distErrPct), 0, null);
+
+ assertTrue(33 * distMult < DIST);
+ checkHits(q(qPt, 33 * KM2DEG, distErrPct), 0, null);
+
+ assertTrue(34 * distMult < DIST);
+ checkHits(q(qPt, 34 * KM2DEG, distErrPct), 0, null);
+ }
+
+ private SpatialArgs q(Spatial4n.Core.Shapes.IPoint pt, double distDEG, double distErrPct)
+ {
+ IShape shape = ctx.MakeCircle(pt, distDEG);
+ SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);
+ args.DistErrPct = (distErrPct);
+ return args;
+ }
+
+ private void checkHits(SpatialArgs args, int assertNumFound, int[] assertIds)
+ {
+ SearchResults got = executeQuery(strategy.MakeQuery(args), 100);
+ assertEquals("" + args, assertNumFound, got.numFound);
+ if (assertIds != null)
+ {
+ ISet<int?> gotIds = new HashSet<int?>();
+ foreach (SearchResult result in got.results)
+ {
+ gotIds.add(int.Parse(result.document.Get("id"), CultureInfo.InvariantCulture));
+ }
+ foreach (int assertId in assertIds)
+ {
+ assertTrue("has " + assertId, gotIds.contains(assertId));
+ }
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs b/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs
new file mode 100644
index 0000000..02afd84
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/TestTermQueryPrefixGridStrategy.cs
@@ -0,0 +1,60 @@
+\ufeffusing Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Queries;
+using Lucene.Net.Support;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+
+namespace Lucene.Net.Spatial.Prefix
+{
+ /*
+ * 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.
+ */
+
+ public class TestTermQueryPrefixGridStrategy : SpatialTestCase
+ {
+ [Test]
+ public virtual void TestNGramPrefixGridLosAngeles()
+ {
+ SpatialContext ctx = SpatialContext.GEO;
+ TermQueryPrefixTreeStrategy prefixGridStrategy = new TermQueryPrefixTreeStrategy(new QuadPrefixTree(ctx), "geo");
+
+ Spatial4n.Core.Shapes.IShape point = ctx.MakePoint(-118.243680, 34.052230);
+
+ Document losAngeles = new Document();
+ losAngeles.Add(new StringField("name", "Los Angeles", Field.Store.YES));
+ foreach (IndexableField field in prefixGridStrategy.CreateIndexableFields(point))
+ {
+ losAngeles.Add(field);
+ }
+ losAngeles.Add(new StoredField(prefixGridStrategy.FieldName, point.toString()));//just for diagnostics
+
+ addDocumentsAndCommit(Arrays.AsList(losAngeles));
+
+ // This won't work with simple spatial context...
+ SpatialArgsParser spatialArgsParser = new SpatialArgsParser();
+ // TODO... use a non polygon query
+ // SpatialArgs spatialArgs = spatialArgsParser.parse(
+ // "Intersects(POLYGON((-127.00390625 39.8125,-112.765625 39.98828125,-111.53515625 31.375,-125.94921875 30.14453125,-127.00390625 39.8125)))",
+ // new SimpleSpatialContext());
+
+ // Query query = prefixGridStrategy.makeQuery(spatialArgs, fieldInfo);
+ // SearchResults searchResults = executeQuery(query, 1);
+ // assertEquals(1, searchResults.numFound);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs b/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs
new file mode 100644
index 0000000..581b2f6
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Prefix/Tree/SpatialPrefixTreeTest.cs
@@ -0,0 +1,105 @@
+\ufeffusing Lucene.Net.Documents;
+using Lucene.Net.Search;
+using Lucene.Net.Spatial.Queries;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Shapes;
+using System;
+
+namespace Lucene.Net.Spatial.Prefix.Tree
+{
+ /*
+ * 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.
+ */
+
+ public class SpatialPrefixTreeTest : SpatialTestCase
+ {
+ //TODO plug in others and test them
+ new private SpatialContext ctx;
+ private SpatialPrefixTree trie;
+
+ public override void SetUp()
+ {
+ base.SetUp();
+ ctx = SpatialContext.GEO;
+ }
+
+ [Test]
+ public virtual void TestCellTraverse()
+ {
+ trie = new GeohashPrefixTree(ctx, 4);
+
+ Cell prevC = null;
+ Cell c = trie.WorldCell;
+ assertEquals(0, c.Level);
+ assertEquals(ctx.WorldBounds, c.GetShape());
+ while (c.Level < trie.MaxLevels)
+ {
+ prevC = c;
+
+ var iter = c.GetSubCells().GetEnumerator();
+ iter.MoveNext();
+ c = iter.Current;
+ //c = c.GetSubCells().GetEnumerator().next();//TODO random which one?
+
+ assertEquals(prevC.Level + 1, c.Level);
+ IRectangle prevNShape = (IRectangle)prevC.GetShape();
+ IShape s = c.GetShape();
+ IRectangle sbox = s.BoundingBox;
+ assertTrue(prevNShape.Width > sbox.Width);
+ assertTrue(prevNShape.Height > sbox.Height);
+ }
+ }
+ /**
+ * A PrefixTree pruning optimization gone bad.
+ * See <a href="https://issues.apache.org/jira/browse/LUCENE-4770>LUCENE-4770</a>.
+ */
+ [Test]
+ public virtual void TestBadPrefixTreePrune()
+ {
+
+ trie = new QuadPrefixTree(ctx, 12);
+ TermQueryPrefixTreeStrategy strategy = new TermQueryPrefixTreeStrategy(trie, "geo");
+ Document doc = new Document();
+ doc.Add(new TextField("id", "1", Field.Store.YES));
+
+ IShape area = ctx.MakeRectangle(-122.82, -122.78, 48.54, 48.56);
+
+ Field[] fields = strategy.CreateIndexableFields(area, 0.025);
+ foreach (Field field in fields)
+ {
+ doc.Add(field);
+ }
+ AddDocument(doc);
+
+ IPoint upperleft = ctx.MakePoint(-122.88, 48.54);
+ IPoint lowerright = ctx.MakePoint(-122.82, 48.62);
+
+ Query query = strategy.MakeQuery(new SpatialArgs(SpatialOperation.Intersects, ctx.MakeRectangle(upperleft, lowerright)));
+
+ Commit();
+
+ TopDocs search = indexSearcher.Search(query, 10);
+ ScoreDoc[] scoreDocs = search.ScoreDocs;
+ foreach (ScoreDoc scoreDoc in scoreDocs)
+ {
+ Console.WriteLine(indexSearcher.Doc(scoreDoc.Doc));
+ }
+
+ assertEquals(1, search.TotalHits);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Properties/AssemblyInfo.cs b/src/Lucene.Net.Tests.Spatial/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..3e1f0a5
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+\ufeffusing System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Lucene.Net.Tests.Spatial")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Lucene.Net.Tests.Spatial")]
+[assembly: AssemblyCopyright("Copyright � 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("31f52f5c-a08f-4363-8003-23d6f7d6eb3a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs b/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs
new file mode 100644
index 0000000..ee2f9a2
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Query/SpatialArgsParserTest.cs
@@ -0,0 +1,69 @@
+\ufeffusing Lucene.Net.Util;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Shapes;
+using System;
+
+namespace Lucene.Net.Spatial.Queries
+{
+ /*
+ * 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.
+ */
+
+ public class SpatialArgsParserTest : LuceneTestCase
+ {
+ private SpatialContext ctx = SpatialContext.GEO;
+
+ //The args parser is only dependent on the ctx for IO so I don't care to test
+ // with other implementations.
+
+ [Test]
+ public virtual void TestArgsParser()
+ {
+ SpatialArgsParser parser = new SpatialArgsParser();
+
+ String arg = SpatialOperation.IsWithin + "(Envelope(-10, 10, 20, -20))";
+ SpatialArgs @out = parser.Parse(arg, ctx);
+ assertEquals(SpatialOperation.IsWithin, @out.Operation);
+ IRectangle bounds = (IRectangle)@out.Shape;
+ assertEquals(-10.0, bounds.MinX, 0D);
+ assertEquals(10.0, bounds.MaxX, 0D);
+
+ // Disjoint should not be scored
+ arg = SpatialOperation.IsDisjointTo + " (Envelope(-10,-20,20,10))";
+ @out = parser.Parse(arg, ctx);
+ assertEquals(SpatialOperation.IsDisjointTo, @out.Operation);
+
+ try
+ {
+ parser.Parse(SpatialOperation.IsDisjointTo + "[ ]", ctx);
+ fail("spatial operations need args");
+ }
+ catch (Exception ex)
+ {//expected
+ }
+
+ try
+ {
+ parser.Parse("XXXX(Envelope(-10, 10, 20, -20))", ctx);
+ fail("unknown operation!");
+ }
+ catch (Exception ex)
+ {//expected
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs b/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs
new file mode 100644
index 0000000..25db926
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/QueryEqualsHashCodeTest.cs
@@ -0,0 +1,169 @@
+\ufeffusing Lucene.Net.Spatial.Prefix;
+using Lucene.Net.Spatial.Prefix.Tree;
+using Lucene.Net.Spatial.Queries;
+using Lucene.Net.Spatial.Serialized;
+using Lucene.Net.Spatial.Vector;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Shapes;
+using System;
+using System.Collections.Generic;
+
+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.
+ */
+
+ public class QueryEqualsHashCodeTest : LuceneTestCase
+ {
+ private readonly SpatialContext ctx = SpatialContext.GEO;
+
+ [Test]
+ public virtual void TestEqualsHashCode()
+ {
+
+ SpatialPrefixTree gridQuad = new QuadPrefixTree(ctx, 10);
+ SpatialPrefixTree gridGeohash = new GeohashPrefixTree(ctx, 10);
+
+ List<SpatialStrategy> strategies = new List<SpatialStrategy>();
+ strategies.Add(new RecursivePrefixTreeStrategy(gridGeohash, "recursive_geohash"));
+ strategies.Add(new TermQueryPrefixTreeStrategy(gridQuad, "termquery_quad"));
+ strategies.Add(new PointVectorStrategy(ctx, "pointvector"));
+ //strategies.Add(new BBoxStrategy(ctx, "bbox"));
+ strategies.Add(new SerializedDVStrategy(ctx, "serialized"));
+ foreach (SpatialStrategy strategy in strategies)
+ {
+ TestEqualsHashcode(strategy);
+ }
+ }
+
+ private class ObjGeneratorQueryAnonymousHelper : ObjGenerator
+ {
+ private readonly SpatialStrategy strategy;
+
+ public ObjGeneratorQueryAnonymousHelper(SpatialStrategy strategy)
+ {
+ this.strategy = strategy;
+ }
+
+ public object gen(SpatialArgs args)
+ {
+ return strategy.MakeQuery(args);
+ }
+ }
+
+ private class ObjGeneratorFilterAnonymousHelper : ObjGenerator
+ {
+ private readonly SpatialStrategy strategy;
+
+ public ObjGeneratorFilterAnonymousHelper(SpatialStrategy strategy)
+ {
+ this.strategy = strategy;
+ }
+
+ public object gen(SpatialArgs args)
+ {
+ return strategy.MakeFilter(args);
+ }
+ }
+
+ private class ObjGeneratorDistanceValueSourceAnonymousHelper : ObjGenerator
+ {
+ private readonly SpatialStrategy strategy;
+
+ public ObjGeneratorDistanceValueSourceAnonymousHelper(SpatialStrategy strategy)
+ {
+ this.strategy = strategy;
+ }
+
+ public object gen(SpatialArgs args)
+ {
+ return strategy.MakeDistanceValueSource(args.Shape.Center);
+ }
+ }
+
+ private void TestEqualsHashcode(SpatialStrategy strategy)
+ {
+ SpatialArgs args1 = MakeArgs1();
+ SpatialArgs args2 = MakeArgs2();
+ TestEqualsHashcode(args1, args2, new ObjGeneratorQueryAnonymousHelper(strategy));
+ // testEqualsHashcode(args1, args2, new ObjGenerator() {
+ // @Override
+ // public Object gen(SpatialArgs args)
+ // {
+ // return strategy.makeQuery(args);
+ // }
+ //});
+ TestEqualsHashcode(args1, args2, new ObjGeneratorFilterAnonymousHelper(strategy));
+ // testEqualsHashcode(args1, args2, new ObjGenerator()
+ // {
+ // @Override
+ // public Object gen(SpatialArgs args)
+ // {
+ // return strategy.makeFilter(args);
+ // }
+ //});
+ TestEqualsHashcode(args1, args2, new ObjGeneratorDistanceValueSourceAnonymousHelper(strategy));
+ // testEqualsHashcode(args1, args2, new ObjGenerator()
+ //{
+ // @Override
+ // public Object gen(SpatialArgs args)
+ //{
+ // return strategy.makeDistanceValueSource(args.getShape().getCenter());
+ //}
+ // });
+ }
+
+ private void TestEqualsHashcode(SpatialArgs args1, SpatialArgs args2, ObjGenerator generator)
+ {
+ Object first;
+ try
+ {
+ first = generator.gen(args1);
+ }
+ catch (NotSupportedException e)
+ {
+ return;
+ }
+ if (first == null)
+ return;//unsupported op?
+ Object second = generator.gen(args1);//should be the same
+ assertEquals(first, second);
+ assertEquals(first.GetHashCode(), second.GetHashCode());
+ second = generator.gen(args2);//now should be different
+ assertNotSame(args1, args2);
+ }
+
+ private SpatialArgs MakeArgs1()
+ {
+ IShape shape1 = ctx.MakeRectangle(0, 0, 10, 10);
+ return new SpatialArgs(SpatialOperation.Intersects, shape1);
+ }
+
+ private SpatialArgs MakeArgs2()
+ {
+ IShape shape2 = ctx.MakeRectangle(0, 0, 20, 20);
+ return new SpatialArgs(SpatialOperation.Intersects, shape2);
+ }
+
+ interface ObjGenerator
+ {
+ Object gen(SpatialArgs args);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs b/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs
new file mode 100644
index 0000000..b4d00a6
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/Serialized/SerializedStrategyTest.cs
@@ -0,0 +1,73 @@
+\ufeffusing Lucene.Net.Search;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+
+namespace Lucene.Net.Spatial.Serialized
+{
+ /*
+ * 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.
+ */
+
+ public class SerializedStrategyTest : StrategyTestCase
+ {
+ public override void SetUp()
+ {
+ base.SetUp();
+ this.ctx = SpatialContext.GEO;
+ this.strategy = new SerializedDVStrategy(ctx, "serialized");
+ }
+
+
+ protected override bool NeedsDocValues()
+ {
+ return true;
+ }
+
+ //called by StrategyTestCase; we can't let it call our makeQuery which will UOE ex.
+ protected override Query makeQuery(SpatialTestQuery q)
+ {
+ return new FilteredQuery(new MatchAllDocsQuery(), strategy.MakeFilter(q.args),
+ FilteredQuery.QUERY_FIRST_FILTER_STRATEGY);
+ }
+
+ [Test]
+ public virtual void TestBasicOperaions()
+ {
+ getAddAndVerifyIndexedDocuments(DATA_SIMPLE_BBOX);
+
+ executeQueries(SpatialMatchConcern.EXACT, QTEST_Simple_Queries_BBox);
+ }
+
+ [Test]
+ public virtual void TestStatesBBox()
+ {
+ getAddAndVerifyIndexedDocuments(DATA_STATES_BBOX);
+
+ executeQueries(SpatialMatchConcern.FILTER, QTEST_States_IsWithin_BBox);
+ executeQueries(SpatialMatchConcern.FILTER, QTEST_States_Intersects_BBox);
+ }
+
+ [Test]
+ public virtual void TestCitiesIntersectsBBox()
+ {
+ getAddAndVerifyIndexedDocuments(DATA_WORLD_CITIES_POINTS);
+
+ executeQueries(SpatialMatchConcern.FILTER, QTEST_Cities_Intersects_BBox);
+ }
+
+ //sorting is tested in DistanceStrategyTest
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs b/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs
new file mode 100644
index 0000000..afac670
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/SpatialArgsTest.cs
@@ -0,0 +1,52 @@
+\ufeffusing Lucene.Net.Spatial.Queries;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using Spatial4n.Core.Context;
+using Spatial4n.Core.Shapes;
+
+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.
+ */
+
+ public class SpatialArgsTest : LuceneTestCase
+ {
+ [Test]
+ public void CalcDistanceFromErrPct()
+ {
+ SpatialContext ctx = SpatialContext.GEO;
+ double DEP = 0.5;//distErrPct
+
+ //the result is the diagonal distance from the center to the closest corner,
+ // times distErrPct
+
+ IShape superwide = ctx.MakeRectangle(-180, 180, 0, 0);
+ //0 distErrPct means 0 distance always
+ assertEquals(0, SpatialArgs.CalcDistanceFromErrPct(superwide, 0, ctx), 0);
+ assertEquals(180 * DEP, SpatialArgs.CalcDistanceFromErrPct(superwide, DEP, ctx), 0);
+
+ IShape supertall = ctx.MakeRectangle(0, 0, -90, 90);
+ assertEquals(90 * DEP, SpatialArgs.CalcDistanceFromErrPct(supertall, DEP, ctx), 0);
+
+ IShape upperhalf = ctx.MakeRectangle(-180, 180, 0, 90);
+ assertEquals(45 * DEP, SpatialArgs.CalcDistanceFromErrPct(upperhalf, DEP, ctx), 0.0001);
+
+ IShape midCircle = ctx.MakeCircle(0, 0, 45);
+ assertEquals(60 * DEP, SpatialArgs.CalcDistanceFromErrPct(midCircle, DEP, ctx), 0.0001);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/SpatialExample.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialExample.cs b/src/Lucene.Net.Tests.Spatial/SpatialExample.cs
new file mode 100644
index 0000000..7e0f7f7
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/SpatialExample.cs
@@ -0,0 +1,196 @@
+\ufeffusing 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
+ {
+ //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 {@link 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 {@link Shape}s and provides access to reading and writing
+ * them from Strings.
+ */
+ private SpatialContext ctx;//"ctx" is the conventional variable name
+
+ /**
+ * The Lucene spatial {@link 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 IntField("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 (IndexableField 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 + " " + pt.Y));
+ }
+
+ return doc;
+ }
+
+ private void Search()
+ {
+ IndexReader indexReader = DirectoryReader.Open(directory);
+ IndexSearcher indexSearcher = new IndexSearcher(indexReader);
+ Sort idSort = new Sort(new SortField("id", SortField.Type_e.INT));
+
+ //--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).StringValue;
+ //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] = Convert.ToInt32(indexSearcher.Doc(docs.ScoreDocs[i].Doc).GetField("id").NumericValue, CultureInfo.InvariantCulture);
+ }
+ assertArrayEquals(ids, gotIds);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/d8c73530/src/Lucene.Net.Tests.Spatial/SpatialMatchConcern.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Spatial/SpatialMatchConcern.cs b/src/Lucene.Net.Tests.Spatial/SpatialMatchConcern.cs
new file mode 100644
index 0000000..035f279
--- /dev/null
+++ b/src/Lucene.Net.Tests.Spatial/SpatialMatchConcern.cs
@@ -0,0 +1,35 @@
+\ufeffnamespace 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.
+ */
+
+ public class SpatialMatchConcern
+ {
+ public readonly bool orderIsImportant;
+ public readonly bool resultsAreSuperset; // if the strategy can not give exact answers, but used to limit results
+
+ private SpatialMatchConcern(bool order, bool superset)
+ {
+ this.orderIsImportant = order;
+ this.resultsAreSuperset = superset;
+ }
+
+ public static readonly SpatialMatchConcern EXACT = new SpatialMatchConcern(true, false);
+ public static readonly SpatialMatchConcern FILTER = new SpatialMatchConcern(false, false);
+ public static readonly SpatialMatchConcern SUPERSET = new SpatialMatchConcern(false, true);
+ }
+}