SOLR-6183: Spatial BBoxField using BBoxSpatialStrategy

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1608998 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
David Wayne Smiley 2014-07-09 03:42:09 +00:00
parent d1d8101289
commit 7cffcc7422
6 changed files with 242 additions and 7 deletions

View File

@ -130,6 +130,10 @@ New Features
* SOLR-5768: Add a distrib.singlePass parameter to make EXECUTE_QUERY phase fetch all fields
and skip GET_FIELDS. (Gregg Donovan, shalin)
* SOLR-6183: New spatial BBoxField for indexing rectangles with search support for most predicates.
It includes extra score relevancy modes in addition to distance: score=overlapRatio|area|area2D.
(David Smiley, Ryan McKinley)
Bug Fixes
----------------------

View File

@ -0,0 +1,133 @@
package org.apache.solr.schema;
/*
* 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.spatial4j.core.shape.Rectangle;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.spatial.bbox.BBoxOverlapRatioValueSource;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
import org.apache.solr.search.QParser;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements SchemaAware {
private static final String PARAM_QUERY_TARGET_PROPORTION = "queryTargetProportion";
private static final String PARAM_MIN_SIDE_LENGTH = "minSideLength";
private String numberFieldName = "tdouble";
private String booleanFieldName = "boolean";
private IndexSchema schema;
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
super.init(schema, args);
String v = args.remove("numberType");
if (v != null) {
numberFieldName = v;
}
v = args.remove("booleanType");
if (v != null) {
booleanFieldName = v;
}
}
@Override
public void inform(IndexSchema schema) {
this.schema = schema;
FieldType numberType = schema.getFieldTypeByName(numberFieldName);
FieldType booleanType = schema.getFieldTypeByName(booleanFieldName);
if (numberType == null) {
throw new RuntimeException("Cannot find number fieldType: " + numberFieldName);
}
if (booleanType == null) {
throw new RuntimeException("Cannot find boolean fieldType: " + booleanFieldName);
}
if (!(booleanType instanceof BoolField)) {
throw new RuntimeException("Must be a BoolField: " + booleanType);
}
if (!(numberType instanceof TrieDoubleField)) { // TODO support TrieField (any trie) once BBoxStrategy does
throw new RuntimeException("Must be TrieDoubleField: " + numberType);
}
List<SchemaField> fields = new ArrayList<>(schema.getFields().values());//copy, because we modify during iteration
for (SchemaField sf : fields) {
if (sf.getType() == this) {
String name = sf.getName();
register(schema, name + BBoxStrategy.SUFFIX_MINX, numberType);
register(schema, name + BBoxStrategy.SUFFIX_MAXX, numberType);
register(schema, name + BBoxStrategy.SUFFIX_MINY, numberType);
register(schema, name + BBoxStrategy.SUFFIX_MAXY, numberType);
register(schema, name + BBoxStrategy.SUFFIX_XDL, booleanType);
}
}
}
private void register(IndexSchema schema, String name, FieldType fieldType) {
SchemaField sf = new SchemaField(name, fieldType);
schema.getFields().put(sf.getName(), sf);
}
@Override
protected BBoxStrategy newSpatialStrategy(String s) {
BBoxStrategy strategy = new BBoxStrategy(ctx, s);
//Solr's FieldType ought to expose Lucene FieldType. Instead as a hack we create a field with a dummy value.
SchemaField field = schema.getField(strategy.getFieldName() + BBoxStrategy.SUFFIX_MINX);
strategy.setFieldType((org.apache.lucene.document.FieldType) field.createField(0.0, 1.0f).fieldType());
return strategy;
}
@Override
protected ValueSource getValueSourceFromSpatialArgs(QParser parser, SchemaField field, SpatialArgs spatialArgs, String scoreParam, BBoxStrategy strategy) {
switch (scoreParam) {
//TODO move these to superclass after LUCENE-5804 ?
case "overlapRatio":
double queryTargetProportion = 0.25;//Suggested default; weights towards target area
String v = parser.getParam(PARAM_QUERY_TARGET_PROPORTION);
if (v != null)
queryTargetProportion = Double.parseDouble(v);
double minSideLength = 0.0;
v = parser.getParam(PARAM_MIN_SIDE_LENGTH);
if (v != null)
minSideLength = Double.parseDouble(v);
return new BBoxOverlapRatioValueSource(
strategy.makeShapeValueSource(), ctx.isGeo(),
(Rectangle) spatialArgs.getShape(),
queryTargetProportion, minSideLength);
case "area":
return new ShapeAreaValueSource(strategy.makeShapeValueSource(), ctx, ctx.isGeo());
case "area2D":
return new ShapeAreaValueSource(strategy.makeShapeValueSource(), ctx, false);
default:
return super.getValueSourceFromSpatialArgs(parser, field, spatialArgs, scoreParam, strategy);
}
}
}

View File

@ -19,10 +19,13 @@
<schema name="test" version="1.4">
<types>
<fieldType name="tint" class="solr.TrieIntField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tlong" class="solr.TrieLongField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="tint" class="solr.TrieIntField" precisionStep="8"/>
<fieldType name="tfloat" class="solr.TrieFloatField" precisionStep="8"/>
<fieldType name="tlong" class="solr.TrieLongField" precisionStep="8"/>
<fieldType name="tdouble" class="solr.TrieDoubleField" precisionStep="8"/>
<fieldType name="tdoubleDV" class="solr.TrieDoubleField" precisionStep="8" docValues="true"/>
<fieldType name="boolean" class="solr.BoolField"/>
<fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
@ -46,6 +49,9 @@
<fieldType name="stqpt_u_oldworldbounds" class="solr.SpatialTermQueryPrefixTreeFieldType"
geo="false" distCalculator="cartesian^2" worldBounds="0 0 1000 1000" units="degrees"/>
<fieldType name="bbox" class="solr.BBoxField"
numberType="tdoubleDV" units="degrees"/>
</types>
@ -57,6 +63,7 @@
<field name="srpt_quad" type="srpt_quad" multiValued="true" />
<field name="stqpt_geohash" type="stqpt_geohash" multiValued="true" />
<field name="pointvector" type="pointvector" />
<field name="bbox" type="bbox" />
</fields>

View File

@ -24,12 +24,9 @@ import com.spatial4j.core.distance.DistanceUtils;
import com.spatial4j.core.shape.Point;
import com.spatial4j.core.shape.Rectangle;
import com.spatial4j.core.shape.impl.RectangleImpl;
import org.apache.lucene.spatial.SpatialStrategy;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException;
import org.apache.solr.core.SolrCore;
import org.apache.solr.schema.AbstractSpatialFieldType;
import org.apache.solr.schema.FieldType;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.util.SpatialUtils;
import org.junit.Before;

View File

@ -0,0 +1,89 @@
package org.apache.solr.search;
/*
* 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 org.apache.solr.SolrTestCaseJ4;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
//Unlike TestSolr4Spatial, not parameterized / not generic.
public class TestSolr4Spatial2 extends SolrTestCaseJ4 {
@BeforeClass
public static void beforeClass() throws Exception {
initCore("solrconfig-basic.xml", "schema-spatial.xml");
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
clearIndex();
}
@Test
public void testBBox() throws Exception {
String fieldName = "bbox";
assertU(adoc("id", "0"));//nothing
assertU(adoc("id", "1", fieldName, "ENVELOPE(-10, 20, 15, 10)"));
assertU(adoc("id", "2", fieldName, "ENVELOPE(22, 22, 10, 10)"));//pt
assertU(commit());
assertJQ(req("q", "{!field f="+fieldName+" filter=false score=overlapRatio " +
"queryTargetProportion=0.25}" +
"Intersects(ENVELOPE(10,25,12,10))",
"fl", "id,score",
"debug", "results"),//explain info
"/response/docs/[0]/id=='2'",
"/response/docs/[0]/score==0.75]",
"/response/docs/[1]/id=='1'",
"/response/docs/[1]/score==0.26666668]",
"/response/docs/[2]/id=='0'",
"/response/docs/[2]/score==0.0"
);
//minSideLength with point query
assertJQ(req("q", "{!field f="+fieldName+" filter=false score=overlapRatio " +
"queryTargetProportion=0.5 minSideLength=1}" +
"Intersects(ENVELOPE(0,0,12,12))",//pt
"fl", "id,score",
"debug", "results"),//explain info
"/response/docs/[0]/id=='1'",
"/response/docs/[0]/score==0.50333333]"//just over 0.5
);
//area2D
assertJQ(req("q", "{!field f="+fieldName+" filter=false score=area2D}" +
"Intersects(ENVELOPE(0,0,12,12))",//pt
"fl", "id,score",
"debug", "results"),//explain info
"/response/docs/[0]/id=='1'" ,
"/response/docs/[0]/score==" + (30f * 5f) + "]"//150
);
//area (not 2D)
assertJQ(req("q", "{!field f="+fieldName+" filter=false score=area}" +
"Intersects(ENVELOPE(0,0,12,12))",//pt
"fl", "id,score",
"debug", "results"),//explain info
"/response/docs/[0]/id=='1'" ,
"/response/docs/[0]/score==" + 146.39793f + "]"//a bit less than 150
);
}
}

View File

@ -708,6 +708,11 @@
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
<!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has
special relevancy modes: score=overlapRatio|area|area2D (local-param to the query)-->
<fieldType name="bbox" class="solr.BBoxField"
geo="true" units="degrees" numberType="tdouble" />
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
Parameters:
defaultCurrency: Specifies the default currency if none specified. Defaults to "USD"