mirror of https://github.com/apache/lucene.git
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:
parent
d1d8101289
commit
7cffcc7422
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue