From 132175856fc9148113bd938fd24b620b58281a0d Mon Sep 17 00:00:00 2001 From: David Wayne Smiley Date: Mon, 2 Jul 2012 06:47:49 +0000 Subject: [PATCH] LUCENE-4157 Ported the Solr 3 spatial test to Lucene spatial. Did a few minor other things too. git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1356081 13f79535-47bb-0310-9956-ffa450edef68 --- .../spatial/query/SpatialOperation.java | 5 + .../spatial/vector/DistanceValueSource.java | 8 +- .../lucene/spatial/PortedSolr3Test.java | 220 ++++++++++++++++++ .../prefix/TestSpatialPrefixField.java | 69 ------ 4 files changed, 228 insertions(+), 74 deletions(-) create mode 100644 lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java delete mode 100644 lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestSpatialPrefixField.java diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialOperation.java b/lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialOperation.java index 4b2837d0836..ae02c27a1d8 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialOperation.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/query/SpatialOperation.java @@ -25,6 +25,11 @@ import java.util.*; /** * A clause that compares a stored geometry to a supplied geometry. * + * @see + * ESRI's docs on spatial relations + * @see + * GeoServer ECQL Spatial Predicates + * * @lucene.experimental */ public class SpatialOperation implements Serializable { diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java b/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java index bf17e287197..5043eb3438d 100644 --- a/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java +++ b/lucene/spatial/src/java/org/apache/lucene/spatial/vector/DistanceValueSource.java @@ -1,3 +1,5 @@ +package org.apache.lucene.spatial.vector; + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -15,11 +17,8 @@ * limitations under the License. */ -package org.apache.lucene.spatial.vector; - import com.spatial4j.core.distance.DistanceCalculator; import com.spatial4j.core.shape.Point; -import com.spatial4j.core.shape.simple.PointImpl; import org.apache.lucene.index.AtomicReader; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.queries.function.FunctionValues; @@ -84,8 +83,7 @@ public class DistanceValueSource extends ValueSource { public double doubleVal(int doc) { // make sure it has minX and area if (validX.get(doc) && validY.get(doc)) { - PointImpl pt = new PointImpl( ptX[doc], ptY[doc] ); - return calculator.distance(from, pt); + return calculator.distance(from, ptX[doc], ptY[doc]); } return 0; } diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java b/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java new file mode 100644 index 00000000000..90e9d3c77ea --- /dev/null +++ b/lucene/spatial/src/test/org/apache/lucene/spatial/PortedSolr3Test.java @@ -0,0 +1,220 @@ +package org.apache.lucene.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. + */ + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import com.spatial4j.core.context.SpatialContext; +import com.spatial4j.core.context.simple.SimpleSpatialContext; +import com.spatial4j.core.shape.Point; +import com.spatial4j.core.shape.Shape; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.StringField; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.search.FilteredQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy; +import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy; +import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; +import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; +import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree; +import org.apache.lucene.spatial.query.SpatialArgs; +import org.apache.lucene.spatial.query.SpatialOperation; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Based off of Solr 3's SpatialFilterTest. + * @author dsmiley + */ +public class PortedSolr3Test extends StrategyTestCase { + + @ParametersFactory + public static Iterable parameters() { + List ctorArgs = new ArrayList(); + + SpatialContext ctx = SimpleSpatialContext.GEO_KM; + SpatialPrefixTree grid; + SpatialStrategy strategy; + + grid = new GeohashPrefixTree(ctx,12); + strategy = new RecursivePrefixTreeStrategy(grid); + ctorArgs.add(new Object[]{"recursive_geohash",strategy}); + + grid = new QuadPrefixTree(ctx,25); + strategy = new RecursivePrefixTreeStrategy(grid); + ctorArgs.add(new Object[]{"recursive_quad",strategy}); + + grid = new GeohashPrefixTree(ctx,12); + strategy = new TermQueryPrefixTreeStrategy(grid); + ctorArgs.add(new Object[]{"termquery_geohash",strategy}); + + return ctorArgs; + } + +// private String fieldName; + + public PortedSolr3Test(String fieldName, SpatialStrategy strategy) { + ctx = strategy.getSpatialContext(); + this.strategy = strategy; +// this.fieldName = fieldName; + fieldInfo = new SimpleSpatialFieldInfo( fieldName ); + } + + private void setupDocs() throws IOException { + super.deleteAll(); + adoc("1", "32.7693246, -79.9289094"); + adoc("2", "33.7693246, -80.9289094"); + adoc("3", "-32.7693246, 50.9289094"); + adoc("4", "-50.7693246, 60.9289094"); + adoc("5", "0,0"); + adoc("6", "0.1,0.1"); + adoc("7", "-0.1,-0.1"); + adoc("8", "0,179.9"); + adoc("9", "0,-179.9"); + adoc("10", "89.9,50"); + adoc("11", "89.9,-130"); + adoc("12", "-89.9,50"); + adoc("13", "-89.9,-130"); + commit(); + } + + + @Test + public void testIntersections() throws Exception { + setupDocs(); + //Try some edge cases + checkHitsCircle("1,1", 175, 3, 5, 6, 7); + checkHitsCircle("0,179.8", 200, 2, 8, 9); + checkHitsCircle("89.8, 50", 200, 2, 10, 11);//this goes over the north pole + checkHitsCircle("-89.8, 50", 200, 2, 12, 13);//this goes over the south pole + //try some normal cases + checkHitsCircle("33.0,-80.0", 300, 2); + //large distance + checkHitsCircle("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("0.1,0.1", 15, 2, 5, 6); + //try some more + deleteAll(); + adoc("14", "0,5"); + adoc("15", "0,15"); + //3000KM from 0,0, see http://www.movable-type.co.uk/scripts/latlong.html + adoc("16", "18.71111,19.79750"); + adoc("17", "44.043900,-95.436643"); + commit(); + + checkHitsCircle("0,0", 1000, 1, 14); + checkHitsCircle("0,0", 2000, 2, 14, 15); + checkHitsBBox("0,0", 3000, 3, 14, 15, 16); + checkHitsCircle("0,0", 3001, 3, 14, 15, 16); + checkHitsCircle("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("43.517030,-96.789603", 109, 0); + + // falls outside of the real distance, but inside the bounding box + checkHitsCircle("43.517030,-96.789603", 110, 0); + checkHitsBBox("43.517030,-96.789603", 110, 1, 17); + } + + /** + * This test is similar to a Solr 3 spatial test. + */ + @Test + public void testDistanceOrder() throws IOException { + adoc("100","1,2"); + adoc("101","4,-1"); + commit(); + + //query closer to #100 + checkHitsOrdered("Intersects(Circle(3,4 d=1000))", "101", "100"); + //query closer to #101 + checkHitsOrdered("Intersects(Circle(4,0 d=1000))", "100", "101"); + } + + private void checkHitsOrdered(String spatialQ, String... ids) { + SpatialArgs args = this.argsParser.parse(spatialQ,ctx); + Query query = strategy.makeQuery(args, fieldInfo); + SearchResults results = executeQuery(query, 100); + String[] resultIds = new String[results.numFound]; + int i = 0; + for (SearchResult result : results.results) { + resultIds[i++] = result.document.get("id"); + } + assertArrayEquals("order matters",ids, resultIds); + } + + //---- these are similar to Solr test methods + + private void adoc(String idStr, String shapeStr) throws IOException { + Shape shape = ctx.readShape(shapeStr); + addDocument(newDoc(idStr,shape)); + } + + @SuppressWarnings("unchecked") + private Document newDoc(String id, Shape shape) { + Document doc = new Document(); + doc.add(new StringField("id", id, Field.Store.YES)); + for (IndexableField f : strategy.createFields(fieldInfo, shape, true, storeShape)) { + doc.add(f); + } + return doc; + } + + private void checkHitsCircle(String ptStr, double dist, int assertNumFound, int... assertIds) { + _checkHits(SpatialOperation.Intersects, ptStr, dist, assertNumFound, assertIds); + } + private void checkHitsBBox(String ptStr, double dist, int assertNumFound, int... assertIds) { + _checkHits(SpatialOperation.BBoxIntersects, ptStr, dist, assertNumFound, assertIds); + } + + @SuppressWarnings("unchecked") + private void _checkHits(SpatialOperation op, String ptStr, double dist, int assertNumFound, int... assertIds) { + Point pt = (Point) ctx.readShape(ptStr); + Shape shape = ctx.makeCircle(pt,dist); + + SpatialArgs args = new SpatialArgs(op,shape); + //args.setDistPrecision(0.025); + Query query; + if (random().nextBoolean()) { + query = strategy.makeQuery(args, fieldInfo); + } else { + query = new FilteredQuery(new MatchAllDocsQuery(),strategy.makeFilter(args, fieldInfo)); + } + SearchResults results = executeQuery(query, 100); + assertEquals(""+shape,assertNumFound,results.numFound); + if (assertIds != null) { + Set resultIds = new HashSet(); + for (SearchResult result : results.results) { + resultIds.add(Integer.valueOf(result.document.get("id"))); + } + for (int assertId : assertIds) { + assertTrue("has " + assertId, resultIds.contains(assertId)); + } + } + } + +} diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestSpatialPrefixField.java b/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestSpatialPrefixField.java deleted file mode 100644 index cbeae7b488e..00000000000 --- a/lucene/spatial/src/test/org/apache/lucene/spatial/prefix/TestSpatialPrefixField.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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. - */ - -package org.apache.lucene.spatial.prefix; - -import org.apache.lucene.util.LuceneTestCase; -import org.junit.Test; - -import java.util.Arrays; -import java.util.List; - - -/** - * This is just a quick idea for *simple* tests - */ -public class TestSpatialPrefixField extends LuceneTestCase { - - @Test - public void testRawTokens() { - // Ignoring geometry for now, and focus on what tokens need to match - - List docA = Arrays.asList( - "AAAAAA*", - "AAAAAB+" - ); - - List docB = Arrays.asList( - "A*", - "BB*" - ); - - // Assumptions: - checkQuery("AAAAA", "docA", "docB"); - checkQuery("AAAAA*", "docA", "docB"); // for now * and + are essentially identical - checkQuery("AAAAA+", "docA", "docB"); // down the road, there may be a difference between 'covers' and an edge - - checkQuery("AA*", "docB", "docA"); // Bigger input query - - checkQuery("AAAAAAAAAAAA*", "docA", "docB"); // small - - checkQuery("BC"); // nothing - checkQuery("XX"); // nothing - - // match only B - checkQuery("B", "docB"); - checkQuery("BBBB", "docB"); - checkQuery("B*", "docB"); - checkQuery("BBBB*", "docB"); - } - - void checkQuery(String query, String... expect) { - // TODO, check that the query returns the docs in order - } - -}