SOLR-1586: added GeoHashField and SpatialTileField. Refactored CoordinateFieldType a bit for sharing with SpatialTileField

git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@894301 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Grant Ingersoll 2009-12-29 02:58:46 +00:00
parent a9b50cb5e4
commit ed5949c38c
12 changed files with 520 additions and 98 deletions

View File

@ -77,6 +77,8 @@ New Features
* SOLR-1131: FieldTypes can now output multiple Fields per Type and still be searched. This can be handy for hiding the details of a particular
implementation such as in the spatial case. (Chris Mattmann, shalin, noble, gsingers, yonik)
* SOLR-1586: Add support for Geohash and Spatial Tile FieldType (Chris Mattmann, gsingers)
Optimizations
----------------------

View File

@ -409,6 +409,23 @@
-->
<fieldType name="location" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
<!--
A Geohash is a compact representation of a latitude longitude pair in a single field.
See http://wiki.apache.org/solr/SpatialSearch
-->
<fieldtype name="geohash" class="solr.GeoHashField"/>
<!--
A SpatialTielField is like a set of zoom levels on an interactive map (i.e. Google Maps or MapQuest). It takes a lat/lon
field and indexes it into (end - start) different fields, each representing a different zoom level.
This can then be leveraged to quickly narrow the search space by creating a filter, at an appropriate tier level,
that only has to enumerate a minimum number of terms.
See http://wiki.apache.org/solr/SpatialSearch
-->
<fieldType name="tile" class="solr.SpatialTileField" start="4" end="15" subFieldSuffix="_tiled"/>
</types>
@ -454,7 +471,8 @@
<field name="inStock" type="boolean" indexed="true" stored="true" />
<field name="store" type="location" indexed="true" stored="true"/>
<field name="store_hash" type="geohash" indexed="true" stored="false"/>
<field name="store_tiles" type="tile" indexed="true" stored="false"/>
<!-- Common metadata fields, named specifically to match up with
SolrCell metadata when parsing rich documents such as Word, PDF.
@ -509,6 +527,9 @@
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
<dynamicField name="*_f" type="float" indexed="true" stored="true"/>
<dynamicField name="*_d" type="double" indexed="true" stored="true"/>
<dynamicField name="*_tiled" type="double" indexed="true" stored="false"/>
<dynamicField name="*_dt" type="date" indexed="true" stored="true"/>
<dynamicField name="*_p" type="location" indexed="true" stored="true"/>
@ -550,6 +571,8 @@
or to add multiple fields to the same field for easier/faster searching. -->
<copyField source="cat" dest="text"/>
<copyField source="store" dest="store_hash"/>
<copyField source="store" dest="store_tiles"/>
<copyField source="name" dest="text"/>
<copyField source="manu" dest="text"/>
<copyField source="features" dest="text"/>

View File

@ -0,0 +1,120 @@
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 org.apache.solr.common.SolrException;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.search.QParser;
import org.apache.lucene.search.Query;
import java.util.HashMap;
import java.util.Map;
/**
* An abstract base class for FieldTypes that delegate work to another {@link org.apache.solr.schema.FieldType}.
* The sub type can be obtained by either specifying the subFieldType attribute or the subFieldSuffix. In the former
* case, a new dynamic field will be injected into the schema automatically with the name of {@link #POLY_FIELD_SEPARATOR}.
* In the latter case, it will use an existing dynamic field definition to get the type. See the example schema and the
* use of the {@link org.apache.solr.schema.PointType} for more details.
*
**/
public abstract class AbstractSubTypeFieldType extends FieldType implements SchemaAware {
protected FieldType subType;
public static final String SUB_FIELD_SUFFIX = "subFieldSuffix";
public static final String SUB_FIELD_TYPE = "subFieldType";
protected String suffix;
protected int dynFieldProps;
protected String[] suffixes;
protected IndexSchema schema; // needed for retrieving SchemaFields
public FieldType getSubType() {
return subType;
}
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
this.schema = schema;
//it's not a first class citizen for the IndexSchema
SolrParams p = new MapSolrParams(args);
String subFT = p.get(SUB_FIELD_TYPE);
String subSuffix = p.get(SUB_FIELD_SUFFIX);
if (subFT != null) {
args.remove(SUB_FIELD_TYPE);
subType = schema.getFieldTypeByName(subFT.trim());
suffix = POLY_FIELD_SEPARATOR + subType.typeName;
} else if (subSuffix != null) {
args.remove(SUB_FIELD_SUFFIX);
suffix = subSuffix;
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field type: " + typeName
+ " must specify the " +
SUB_FIELD_TYPE + " attribute or the " + SUB_FIELD_SUFFIX + " attribute.");
}
}
/**
* Helper method for creating a dynamic field SchemaField prototype. Returns a {@link SchemaField} with
* the {@link FieldType} given and a name of "*" + {@link FieldType#POLY_FIELD_SEPARATOR} + {@link FieldType#typeName}
* and props of indexed=true, stored=false.
*
* @param schema the IndexSchema
* @param type The {@link FieldType} of the prototype.
* @return The {@link SchemaField}
*/
static SchemaField registerPolyFieldDynamicPrototype(IndexSchema schema, FieldType type) {
String name = "*" + FieldType.POLY_FIELD_SEPARATOR + type.typeName;
Map<String, String> props = new HashMap<String, String>();
//Just set these, delegate everything else to the field type
props.put("indexed", "true");
props.put("stored", "false");
int p = SchemaField.calcProps(name, type, props);
SchemaField proto = SchemaField.create(name,
type, p, null);
schema.registerDynamicField(proto);
return proto;
}
public void inform(IndexSchema schema) {
//Can't do this until here b/c the Dynamic Fields are not initialized until here.
if (subType != null) {
SchemaField proto = registerPolyFieldDynamicPrototype(schema, subType);
dynFieldProps = proto.getProperties();
}
}
/**
* Throws UnsupportedOperationException()
*/
public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
throw new UnsupportedOperationException();
}
protected void createSuffixCache(int size) {
suffixes = new String[size];
for (int i=0; i<size; i++) {
suffixes[i] = "_" + i + suffix;
}
}
protected SchemaField subField(SchemaField base, int i) {
return schema.getField(base.getName() + suffixes[i]);
}
}

View File

@ -47,84 +47,19 @@ import java.util.HashMap;
* NOTE: There can only be one sub Field Type.
*
*/
public abstract class CoordinateFieldType extends FieldType implements SchemaAware {
public abstract class CoordinateFieldType extends AbstractSubTypeFieldType {
/**
* The dimension of the coordinate system
*/
protected int dimension;
protected FieldType subType;
public static final String SUB_FIELD_SUFFIX = "subFieldSuffix";
public static final String SUB_FIELD_TYPE = "subFieldType";
protected String suffix;
protected int dynFieldProps;
/**
* 2 dimensional by default
*/
public static final int DEFAULT_DIMENSION = 2;
public static final String DIMENSION = "dimension";
public int getDimension() {
return dimension;
}
public FieldType getSubType() {
return subType;
}
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
//it's not a first class citizen for the IndexSchema
SolrParams p = new MapSolrParams(args);
String subFT = p.get(SUB_FIELD_TYPE);
String subSuffix = p.get(SUB_FIELD_SUFFIX);
if (subFT != null) {
args.remove(SUB_FIELD_TYPE);
subType = schema.getFieldTypeByName(subFT.trim());
suffix = POLY_FIELD_SEPARATOR + subType.typeName;
} else if (subSuffix != null) {
args.remove(SUB_FIELD_SUFFIX);
suffix = subSuffix;
}else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field type: " + typeName
+ " must specify the " +
SUB_FIELD_TYPE + " attribute or the " + SUB_FIELD_SUFFIX + " attribute.");
}
super.init(schema, args);
}
public void inform(IndexSchema schema) {
//Can't do this until here b/c the Dynamic Fields are not initialized until here.
if (subType != null) {
SchemaField proto = registerPolyFieldDynamicPrototype(schema, subType);
dynFieldProps = proto.getProperties();
}
}
/**
* Helper method for creating a dynamic field SchemaField prototype. Returns a {@link org.apache.solr.schema.SchemaField} with
* the {@link org.apache.solr.schema.FieldType} given and a name of "*" + {@link org.apache.solr.schema.FieldType#POLY_FIELD_SEPARATOR} + {@link org.apache.solr.schema.FieldType#typeName}
* and props of indexed=true, stored=false.
* @param schema the IndexSchema
* @param type The {@link org.apache.solr.schema.FieldType} of the prototype.
* @return The {@link org.apache.solr.schema.SchemaField}
*/
static SchemaField registerPolyFieldDynamicPrototype(IndexSchema schema, FieldType type){
String name = "*" + FieldType.POLY_FIELD_SEPARATOR + type.typeName;
Map<String, String> props = new HashMap<String, String>();
//Just set these, delegate everything else to the field type
props.put("indexed", "true");
props.put("stored", "false");
int p = SchemaField.calcProps(name, type, props);
SchemaField proto = SchemaField.create(name,
type, p, null);
schema.registerDynamicField(proto);
return proto;
}
/**
* Throws UnsupportedOperationException()
*/
public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,82 @@
/**
* 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.solr.schema;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial.geohash.GeoHashUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.request.TextResponseWriter;
import org.apache.solr.request.XMLWriter;
import org.apache.solr.search.QParser;
import org.apache.solr.search.function.ValueSource;
import org.apache.solr.search.function.distance.DistanceUtils;
import java.io.IOException;
/**
* This is a class that represents a <a
* href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> field. The field is
* provided as a lat lon pair and is internally represented as a string
*
*/
public class GeoHashField extends FieldType {
@Override
public SortField getSortField(SchemaField field, boolean top) {
return getStringSort(field, top);
}
@Override
public void write(XMLWriter xmlWriter, String name, Fieldable f)
throws IOException {
xmlWriter.writeStr(name, toExternal(f));
}
@Override
public void write(TextResponseWriter writer, String name, Fieldable f)
throws IOException {
writer.writeStr(name, toExternal(f), false);
}
@Override
public String toExternal(Fieldable f) {
double[] latLon = GeoHashUtils.decode(f.stringValue());
return latLon[0] + "," + latLon[1];
}
@Override
public String toInternal(String val) {
// validate that the string is of the form
// latitude, longitude
double[] latLon = DistanceUtils.parseLatitudeLongitude(null, val);
return GeoHashUtils.encode(latLon[0], latLon[1]);
}
@Override
public ValueSource getValueSource(SchemaField field, QParser parser) {
return new StrFieldSource(field.name);
}
}

View File

@ -46,14 +46,6 @@ import java.util.ArrayList;
* NOTE: There can only be one sub type
*/
public class PointType extends CoordinateFieldType {
/**
* 2 dimensional by default
*/
public static final int DEFAULT_DIMENSION = 2;
public static final String DIMENSION = "dimension";
protected IndexSchema schema; // needed for retrieving SchemaFields
protected String[] suffixes;
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
@ -68,14 +60,7 @@ public class PointType extends CoordinateFieldType {
super.init(schema, args);
// cache suffixes
suffixes = new String[dimension];
for (int i=0; i<dimension; i++) {
suffixes[i] = "_" + i + suffix;
}
}
protected SchemaField subField(SchemaField base, int i) {
return schema.getField(base.getName() + suffixes[i]);
createSuffixCache(dimension);
}
@ -109,7 +94,7 @@ public class PointType extends CoordinateFieldType {
@Override
public ValueSource getValueSource(SchemaField field, QParser parser) {
ArrayList<ValueSource> vs = new ArrayList(dimension);
ArrayList<ValueSource> vs = new ArrayList<ValueSource>(dimension);
for (int i=0; i<dimension; i++) {
SchemaField sub = subField(field, i);
vs.add(sub.getType().getValueSource(sub, parser));
@ -136,7 +121,7 @@ public class PointType extends CoordinateFieldType {
@Override
public SortField getSortField(SchemaField field, boolean top) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not suported on DualPointType " + field.getName());
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not suported on PointType " + field.getName());
}
@Override

View File

@ -0,0 +1,187 @@
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 org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.SortField;
import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter;
import org.apache.lucene.spatial.tier.projections.IProjector;
import org.apache.lucene.spatial.tier.projections.SinusoidalProjector;
import org.apache.solr.common.ResourceLoader;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.TextResponseWriter;
import org.apache.solr.request.XMLWriter;
import org.apache.solr.search.QParser;
import org.apache.solr.search.function.ValueSource;
import org.apache.solr.search.function.distance.DistanceUtils;
import org.apache.solr.util.plugin.ResourceLoaderAware;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
/**
* Represents a Tiling system for spatial data representation (lat/lon). A Tile is like a zoom level on an
* interactive map.
* <p/>
* Specify a lower and upper tile, and this will create tiles for all the levels in between, inclusive of the upper tile.
* <p/>
* Querying directly against this field is probably not all that useful unless you specifically know the box id
* <p/>
*
* See http://wiki.apache.org/solr/SpatialSearch
*/
public class SpatialTileField extends AbstractSubTypeFieldType implements ResourceLoaderAware {
public static final String START_LEVEL = "start";
public static final String END_LEVEL = "end";
public static final String PROJECTOR_CLASS = "projector";
private static final int DEFAULT_END_LEVEL = 15;
private static final int DEFAULT_START_LEVEL = 4;
private int start = DEFAULT_START_LEVEL, end = DEFAULT_END_LEVEL;
private int tileDiff = DEFAULT_END_LEVEL - DEFAULT_START_LEVEL;//we're going to need this over and over, so cache it.
private String projectorName;
protected List<CartesianTierPlotter> plotters;
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
SolrParams p = new MapSolrParams(args);
start = p.getInt(START_LEVEL, DEFAULT_START_LEVEL);
end = p.getInt(END_LEVEL, DEFAULT_END_LEVEL);
if (end < start) {
//flip them around
int tmp = start;
start = end;
end = tmp;
}
args.remove(START_LEVEL);
args.remove(END_LEVEL);
projectorName = p.get(PROJECTOR_CLASS, SinusoidalProjector.class.getName());
super.init(schema, args);
tileDiff = (end - start) + 1;//add one since we are inclusive of the upper tier
createSuffixCache(tileDiff);
}
public void inform(ResourceLoader loader) {
IProjector projector = (IProjector) loader.newInstance(projectorName);
if (projector != null) {
plotters = new ArrayList<CartesianTierPlotter>(tileDiff);
for (int i = start; i <= end; i++) {
plotters.add(new CartesianTierPlotter(i, projector, ""));
}
} else {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not instantiate a Projector Instance for: "
+ projectorName + ". Make sure the " + PROJECTOR_CLASS + " attribute is set properly in the schema");
}
}
@Override
public Fieldable[] createFields(SchemaField field, String externalVal, float boost) {
Fieldable[] f = new Fieldable[(field.indexed() ? tileDiff : 0) + (field.stored() ? 1 : 0)];
if (field.indexed()) {
int i = 0;
double[] latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal);
for (CartesianTierPlotter plotter : plotters) {
double boxId = plotter.getTierBoxId(latLon[0], latLon[1]);
f[i] = subField(field, i).createField(String.valueOf(boxId), boost);
i++;
}
}
if (field.stored()) {
String storedVal = externalVal; // normalize or not?
f[f.length - 1] = createField(field.getName(), storedVal,
getFieldStore(field, storedVal), Field.Index.NO, Field.TermVector.NO,
false, false, boost);
}
return f;
}
//The externalVal here is a box id, as it doesn't make sense to pick a specific tile since that requires a distance
//so, just OR together a search against all the tile
@Override
public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive,
boolean maxInclusive) {
BooleanQuery bq = new BooleanQuery(true);
for (int i = 0; i < tileDiff; i++) {
SchemaField sf = subField(field, i);
Query tq = sf.getType().getRangeQuery(parser, sf, part1, part2, minInclusive, maxInclusive);
bq.add(tq, BooleanClause.Occur.SHOULD);
}
return bq;
}
@Override
public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
//The externalVal here is a box id, as it doesn't make sense to pick a specific tile since that requires a distance
//so, just OR together a search against all the tiles
BooleanQuery bq = new BooleanQuery(true);
for (int i = 0; i < tileDiff; i++) {
SchemaField sf = subField(field, i);
Query tq = sf.getType().getFieldQuery(parser, sf, externalVal);
bq.add(tq, BooleanClause.Occur.SHOULD);
}
return bq;
}
@Override
public boolean isPolyField() {
return true;
}
@Override
public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException {
xmlWriter.writeStr(name, f.stringValue());
}
@Override
public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException {
writer.writeStr(name, f.stringValue(), false);
}
@Override
public SortField getSortField(SchemaField field, boolean top) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not supported on SpatialTileField " + field.getName());
}
@Override
public ValueSource getValueSource(SchemaField field, QParser parser) {
//TODO: Should this really throw UOE? What does it mean for a function to use the values of a tier? Let's leave it unsupported for now
throw new UnsupportedOperationException("SpatialTileField uses multiple fields and does not support ValueSource");
}
//It never makes sense to create a single field, so make it impossible to happen
@Override
public Field createField(SchemaField field, String externalVal, float boost) {
throw new UnsupportedOperationException("SpatialTileField uses multiple fields. field=" + field.getName());
}
}

View File

@ -92,4 +92,36 @@ public class DistanceUtils {
}
return out;
}
/**
* extract (by calling {@link #parsePoint(String[], String, int)} and validate the latitude and longitude contained
* in the String by making sure the latitude is between 90 & -90 and longitude is between -180 and 180
* @param latLon A preallocated array to hold the result
* @param latLonStr The string to parse
* @return The lat long
*/
public static final double[] parseLatitudeLongitude(double [] latLon, String latLonStr) {
if (latLon == null){
latLon = new double[2];
}
String[] toks = DistanceUtils.parsePoint(null, latLonStr, 2);
latLon[0] = Double.valueOf(toks[0]);
if (latLon[0] < -90.0 || latLon[0] > 90.0) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Invalid latitude: latitudes are range -90 to 90: provided lat: ["
+ latLon[0] + "]");
}
latLon[1] = Double.valueOf(toks[1]);
if (latLon[1] < -180.0 || latLon[1] > 180.0) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Invalid longitude: longitudes are range -180 to 180: provided lon: ["
+ latLon[1] + "]");
}
return latLon;
}
}

View File

@ -24,6 +24,8 @@ import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.spatial.tier.CartesianPolyFilterBuilder;
import org.apache.lucene.spatial.tier.Shape;
import org.apache.solr.core.SolrCore;
import org.apache.solr.util.AbstractSolrTestCase;
import org.apache.solr.common.SolrException;
@ -31,6 +33,7 @@ import org.apache.solr.search.function.ValueSource;
import java.util.Map;
import java.util.Random;
import java.util.List;
/**
@ -123,10 +126,10 @@ public class PolyFieldTest extends AbstractSolrTestCase {
//
SchemaField s1 = schema.getField("test_p");
SchemaField s2 = schema.getField("test_p");
ValueSource v1 = s1.getType().getValueSource(s1,null);
ValueSource v2 = s2.getType().getValueSource(s2,null);
assertEquals(v1,v2);
assertEquals(v1.hashCode(),v2.hashCode());
ValueSource v1 = s1.getType().getValueSource(s1, null);
ValueSource v2 = s2.getType().getValueSource(s2, null);
assertEquals(v1, v2);
assertEquals(v1.hashCode(), v2.hashCode());
}
public void testSearching() throws Exception {
@ -156,12 +159,11 @@ public class PolyFieldTest extends AbstractSolrTestCase {
"homed:[1,1000 TO 2000,35000]"),
"\"//*[@numFound='2']\"");
//bad
assertQEx("Query should throw an exception due to incorrect dimensions", req("fl", "*,score", "q",
"homed:[1 TO 2000]"), SolrException.ErrorCode.BAD_REQUEST);
}
public void testSearchDetails() throws Exception {
SolrCore core = h.getCore();
@ -184,4 +186,40 @@ public class PolyFieldTest extends AbstractSolrTestCase {
assertEquals(clauses.length, 2);
}
public void testCartesian() throws Exception {
for (int i = 40; i < 50; i++) {
for (int j = -85; j < -79; j++) {
assertU(adoc("id", "" + i, "home_tier",
i + "," + j));
}
}
assertU(commit());
CartesianPolyFilterBuilder cpfb = new CartesianPolyFilterBuilder("");
//Get the box based on this point and our distance
final Shape shape = cpfb.getBoxShape(45, -80, 10);//There's a bit of a bug in here that requires a small tier filter here.
final List<Double> boxIds = shape.getArea();
//do a box id search
StringBuilder qry = new StringBuilder();
boolean first = true;
for (Double boxId : boxIds) {
if (first == true){
first = false;
} else {
qry.append(" OR ");
}
qry.append("home_tier:");
if (boxId < 0) {
qry.append('\\').append(boxId);
} else {
qry.append(boxId);
}
}
assertQ(req("fl", "*,score", "q", qry.toString()),
"//*[@numFound='1']");
}
}

View File

@ -46,6 +46,8 @@ public class DistanceFunctionTest extends AbstractSolrTestCase {
assertU(adoc("id", "4", "x_td", String.valueOf(Math.PI / 4), "y_td", String.valueOf(Math.PI / 4), "gh_s", GeoHashUtils.encode(32.7693246, -81.9289094)));
assertU(adoc("id", "5", "x_td", "45.0", "y_td", "45.0",
"gh_s", GeoHashUtils.encode(32.7693246, -81.9289094)));
assertU(adoc("id", "6", "point_hash", "32.5, -79.0"));
assertU(adoc("id", "7", "point_hash", "32.6, -78.0"));
assertU(commit());
//Get the haversine distance between the point 0,0 and the docs above assuming a radius of 1
assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0)", "fq", "id:1"), "//float[@name='score']='0.0'");
@ -58,6 +60,14 @@ public class DistanceFunctionTest extends AbstractSolrTestCase {
//Can verify here: http://www.movable-type.co.uk/scripts/latlong.html, but they use a slightly different radius for the earth, so just be close
assertQ(req("fl", "*,score", "q", "{!func}ghhsin(" + Constants.EARTH_RADIUS_KM + ", gh_s, \"" + GeoHashUtils.encode(32, -79) +
"\",)", "fq", "id:1"), "//float[@name='score']='122.30894'");
assertQ(req("fl", "id,point_hash,score", "q", "{!func}recip(ghhsin(" + Constants.EARTH_RADIUS_KM + ", point_hash, \"" + GeoHashUtils.encode(32, -79) + "\"), 1, 1, 0)"),
"//*[@numFound='7']",
"//result/doc[1]/float[@name='id'][.='6.0']",
"//result/doc[2]/float[@name='id'][.='7.0']"//all the rest don't matter
);
assertQ(req("fl", "*,score", "q", "{!func}ghhsin(" + Constants.EARTH_RADIUS_KM + ", gh_s, geohash(32, -79))", "fq", "id:1"), "//float[@name='score']='122.30894'");
}

View File

@ -373,7 +373,9 @@
<fieldType name="tenD" class="solr.PointType" dimension="10" subFieldType="double"/>
<!-- Use the sub field suffix -->
<fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/>
<fieldType name="tier" class="solr.SpatialTileField" start="4" end="15" subFieldType="double"/>
</types>
@ -405,6 +407,8 @@
<field name="home_ns" type="xy" indexed="true" stored="false" multiValued="false"/>
<field name="work" type="xy" indexed="true" stored="true" multiValued="false"/>
<field name="home_tier" type="tier" indexed="true" stored="true" multiValued="false"/>
<field name="point10" type="tenD" indexed="true" stored="true" multiValued="false"/>

View File

@ -255,6 +255,8 @@
<fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/>
<fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/>
<fieldtype name="geohash" class="solr.GeoHashField"/>
</types>
@ -285,6 +287,8 @@
<field name="point" type="xy" indexed="true" stored="true" multiValued="false"/>
<field name="pointD" type="xyd" indexed="true" stored="true" multiValued="false"/>
<field name="point_hash" type="geohash" indexed="true" stored="true" multiValued="false"/>
<!-- Dynamic field definitions. If a field name is not found, dynamicFields
will be used if the name matches any of the patterns.
RESTRICTION: the glob-like pattern in the name attribute must have