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