mirror of https://github.com/apache/lucene.git
SOLR-6797: spatial distanceUnits=degrees|kilometers|miles
units=degrees is now deprecated. git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1649243 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
c96668e207
commit
8fd247cc0e
|
@ -17,6 +17,9 @@ package org.apache.lucene.spatial.util;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
import com.spatial4j.core.shape.Shape;
|
import com.spatial4j.core.shape.Shape;
|
||||||
import org.apache.lucene.index.LeafReaderContext;
|
import org.apache.lucene.index.LeafReaderContext;
|
||||||
|
@ -26,9 +29,6 @@ import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
|
||||||
import org.apache.lucene.search.Explanation;
|
import org.apache.lucene.search.Explanation;
|
||||||
import org.apache.lucene.search.IndexSearcher;
|
import org.apache.lucene.search.IndexSearcher;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The area of a Shape retrieved from a ValueSource via
|
* The area of a Shape retrieved from a ValueSource via
|
||||||
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
|
* {@link org.apache.lucene.queries.function.FunctionValues#objectVal(int)}.
|
||||||
|
@ -41,11 +41,13 @@ public class ShapeAreaValueSource extends ValueSource {
|
||||||
private final ValueSource shapeValueSource;
|
private final ValueSource shapeValueSource;
|
||||||
private final SpatialContext ctx;//not part of identity; should be associated with shapeValueSource indirectly
|
private final SpatialContext ctx;//not part of identity; should be associated with shapeValueSource indirectly
|
||||||
private final boolean geoArea;
|
private final boolean geoArea;
|
||||||
|
private double multiplier;
|
||||||
|
|
||||||
public ShapeAreaValueSource(ValueSource shapeValueSource, SpatialContext ctx, boolean geoArea) {
|
public ShapeAreaValueSource(ValueSource shapeValueSource, SpatialContext ctx, boolean geoArea, double multiplier) {
|
||||||
this.shapeValueSource = shapeValueSource;
|
this.shapeValueSource = shapeValueSource;
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.geoArea = geoArea;
|
this.geoArea = geoArea;
|
||||||
|
this.multiplier = multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,7 +72,7 @@ public class ShapeAreaValueSource extends ValueSource {
|
||||||
return 0;//or NaN?
|
return 0;//or NaN?
|
||||||
//This part of Spatial4j API is kinda weird. Passing null means 2D area, otherwise geo
|
//This part of Spatial4j API is kinda weird. Passing null means 2D area, otherwise geo
|
||||||
// assuming ctx.isGeo()
|
// assuming ctx.isGeo()
|
||||||
return shape.getArea( geoArea ? ctx : null );
|
return shape.getArea( geoArea ? ctx : null ) * multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,6 +19,13 @@ package org.apache.lucene.spatial.bbox;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.context.SpatialContextFactory;
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
|
import com.spatial4j.core.shape.Shape;
|
||||||
|
import com.spatial4j.core.shape.impl.RectangleImpl;
|
||||||
import org.apache.lucene.document.FieldType;
|
import org.apache.lucene.document.FieldType;
|
||||||
import org.apache.lucene.index.DocValuesType;
|
import org.apache.lucene.index.DocValuesType;
|
||||||
import org.apache.lucene.index.IndexOptions;
|
import org.apache.lucene.index.IndexOptions;
|
||||||
|
@ -30,13 +37,6 @@ import org.apache.lucene.spatial.query.SpatialOperation;
|
||||||
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
|
import org.apache.lucene.spatial.util.ShapeAreaValueSource;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import com.carrotsearch.randomizedtesting.annotations.Repeat;
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
|
||||||
import com.spatial4j.core.context.SpatialContextFactory;
|
|
||||||
import com.spatial4j.core.distance.DistanceUtils;
|
|
||||||
import com.spatial4j.core.shape.Rectangle;
|
|
||||||
import com.spatial4j.core.shape.Shape;
|
|
||||||
import com.spatial4j.core.shape.impl.RectangleImpl;
|
|
||||||
|
|
||||||
public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
|
public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
|
||||||
|
|
||||||
|
@ -301,9 +301,11 @@ public class TestBBoxStrategy extends RandomSpatialOpStrategyTestCase {
|
||||||
adoc("100", ctx.makeRectangle(0, 20, 40, 80));
|
adoc("100", ctx.makeRectangle(0, 20, 40, 80));
|
||||||
adoc("999", (Shape) null);
|
adoc("999", (Shape) null);
|
||||||
commit();
|
commit();
|
||||||
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, false),
|
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, false, 1.0),
|
||||||
new float[]{800f, 0f}, 0f);
|
new float[]{800f, 0f}, 0f);
|
||||||
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, true),//geo
|
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, true, 1.0),//geo
|
||||||
new float[]{391.93f, 0f}, 0.01f);
|
new float[]{391.93f, 0f}, 0.01f);
|
||||||
|
checkValueSource(new ShapeAreaValueSource(bboxStrategy.makeShapeValueSource(), ctx, true, 2.0),
|
||||||
|
new float[]{783.86f, 0f}, 0.01f); // testing with a different multiplier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,13 @@ Upgrading from Solr 4.x
|
||||||
* SolrServer and associated classes have been deprecated. Applications using
|
* SolrServer and associated classes have been deprecated. Applications using
|
||||||
SolrJ should use the equivalent SolrClient classes instead.
|
SolrJ should use the equivalent SolrClient classes instead.
|
||||||
|
|
||||||
|
* Spatial fields originating from Solr 4 (e.g. SpatialRecursivePrefixTreeFieldType, BBoxField)
|
||||||
|
have the 'units' attribute deprecated, now replaced with 'distanceUnits'. If you change it to
|
||||||
|
a unit other than 'degrees' (or if you don't specify it, which will default to kilometers if
|
||||||
|
geo=true), then be sure to update maxDistErr as it's in those units. If you keep units=degrees
|
||||||
|
then it should be backwards compatible but you'll get a deprecation warning on startup. See
|
||||||
|
SOLR-6797.
|
||||||
|
|
||||||
Detailed Change List
|
Detailed Change List
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -271,6 +278,12 @@ New Features
|
||||||
* SOLR-6761: Ability to ignore commit and/or optimize requests from clients when running in
|
* SOLR-6761: Ability to ignore commit and/or optimize requests from clients when running in
|
||||||
SolrCloud mode using the IgnoreCommitOptimizeUpdateProcessorFactory. (Timothy Potter)
|
SolrCloud mode using the IgnoreCommitOptimizeUpdateProcessorFactory. (Timothy Potter)
|
||||||
|
|
||||||
|
* SOLR-6797: Spatial fields that used to require units=degrees like
|
||||||
|
SpatialRecursivePrefixTreeFieldType (RPT) now take distanceUnits=degrees|kilometers|miles
|
||||||
|
instead. It is applied to nearly all distance measurements involving the field: maxDistErr,
|
||||||
|
distErr, d, geodist, score=distance|area|area2d. score now accepts these units as well. It does
|
||||||
|
NOT affect distances embedded in WKT strings like BUFFER(POINT(200 10),0.2)).
|
||||||
|
(Ishan Chattopadhyaya, David Smiley)
|
||||||
|
|
||||||
Bug Fixes
|
Bug Fixes
|
||||||
----------------------
|
----------------------
|
||||||
|
|
|
@ -579,7 +579,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -579,7 +579,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -59,6 +59,7 @@ import org.apache.solr.common.params.SolrParams;
|
||||||
import org.apache.solr.response.TextResponseWriter;
|
import org.apache.solr.response.TextResponseWriter;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.QParser;
|
||||||
import org.apache.solr.search.SpatialOptions;
|
import org.apache.solr.search.SpatialOptions;
|
||||||
|
import org.apache.solr.util.DistanceUnits;
|
||||||
import org.apache.solr.util.MapListener;
|
import org.apache.solr.util.MapListener;
|
||||||
import org.apache.solr.util.SpatialUtils;
|
import org.apache.solr.util.SpatialUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -71,7 +72,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extends FieldType implements SpatialQueryable {
|
public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extends FieldType implements SpatialQueryable {
|
||||||
|
|
||||||
/** A local-param with one of "none" (default), "distance", or "recipDistance". */
|
/** A local-param with one of "none" (default), "distance", "recipDistance" or supported values in ({@link DistanceUnits#getSupportedUnits()}. */
|
||||||
public static final String SCORE_PARAM = "score";
|
public static final String SCORE_PARAM = "score";
|
||||||
/** A local-param boolean that can be set to false to only return the
|
/** A local-param boolean that can be set to false to only return the
|
||||||
* FunctionQuery (score), and thus not do filtering.
|
* FunctionQuery (score), and thus not do filtering.
|
||||||
|
@ -90,6 +91,10 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
|
|
||||||
private final Cache<String, T> fieldStrategyCache = CacheBuilder.newBuilder().build();
|
private final Cache<String, T> fieldStrategyCache = CacheBuilder.newBuilder().build();
|
||||||
|
|
||||||
|
protected DistanceUnits distanceUnits;
|
||||||
|
@Deprecated
|
||||||
|
protected String units; // for back compat; hopefully null
|
||||||
|
|
||||||
protected final Set<String> supportedScoreModes;
|
protected final Set<String> supportedScoreModes;
|
||||||
|
|
||||||
protected AbstractSpatialFieldType() {
|
protected AbstractSpatialFieldType() {
|
||||||
|
@ -101,6 +106,7 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
set.add(NONE);
|
set.add(NONE);
|
||||||
set.add(DISTANCE);
|
set.add(DISTANCE);
|
||||||
set.add(RECIP_DISTANCE);
|
set.add(RECIP_DISTANCE);
|
||||||
|
set.addAll(DistanceUnits.getSupportedUnits());
|
||||||
set.addAll(moreScoreModes);
|
set.addAll(moreScoreModes);
|
||||||
supportedScoreModes = Collections.unmodifiableSet(set);
|
supportedScoreModes = Collections.unmodifiableSet(set);
|
||||||
}
|
}
|
||||||
|
@ -109,11 +115,6 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
protected void init(IndexSchema schema, Map<String, String> args) {
|
protected void init(IndexSchema schema, Map<String, String> args) {
|
||||||
super.init(schema, args);
|
super.init(schema, args);
|
||||||
|
|
||||||
String units = args.remove("units");
|
|
||||||
if (!"degrees".equals(units))
|
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
|
||||||
"Must specify units=\"degrees\" on field types with class "+getClass().getSimpleName());
|
|
||||||
|
|
||||||
//replace legacy rect format with ENVELOPE
|
//replace legacy rect format with ENVELOPE
|
||||||
String wbStr = args.get("worldBounds");
|
String wbStr = args.get("worldBounds");
|
||||||
if (wbStr != null && !wbStr.toUpperCase(Locale.ROOT).startsWith("ENVELOPE")) {
|
if (wbStr != null && !wbStr.toUpperCase(Locale.ROOT).startsWith("ENVELOPE")) {
|
||||||
|
@ -130,9 +131,53 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
ctx = SpatialContextFactory.makeSpatialContext(argsWrap, schema.getResourceLoader().getClassLoader());
|
ctx = SpatialContextFactory.makeSpatialContext(argsWrap, schema.getResourceLoader().getClassLoader());
|
||||||
args.keySet().removeAll(argsWrap.getSeenKeys());
|
args.keySet().removeAll(argsWrap.getSeenKeys());
|
||||||
|
|
||||||
|
final String unitsErrMsg = "units parameter is deprecated, please use distanceUnits instead for field types with class " +
|
||||||
|
getClass().getSimpleName();
|
||||||
|
this.units = args.remove("units");//deprecated
|
||||||
|
if (units != null) {
|
||||||
|
if ("degrees".equals(units)) {
|
||||||
|
log.warn(unitsErrMsg);
|
||||||
|
} else {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, unitsErrMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final String distanceUnitsStr = args.remove("distanceUnits");
|
||||||
|
if (distanceUnitsStr == null) {
|
||||||
|
if (units != null) {
|
||||||
|
this.distanceUnits = DistanceUnits.BACKCOMPAT;
|
||||||
|
} else {
|
||||||
|
this.distanceUnits = ctx.isGeo() ? DistanceUnits.KILOMETERS : DistanceUnits.DEGREES;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If both units and distanceUnits was specified
|
||||||
|
if (units != null) {
|
||||||
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, unitsErrMsg);
|
||||||
|
}
|
||||||
|
this.distanceUnits = parseDistanceUnits(distanceUnitsStr);
|
||||||
|
if (this.distanceUnits == null)
|
||||||
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
|
||||||
|
"Must specify distanceUnits as one of "+ DistanceUnits.getSupportedUnits() +
|
||||||
|
" on field types with class "+getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
argsParser = newSpatialArgsParser();
|
argsParser = newSpatialArgsParser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** if {@code str} is non-null, returns {@link org.apache.solr.util.DistanceUnits#valueOf(String)}
|
||||||
|
* (which will return null if not found),
|
||||||
|
* else returns {@link #distanceUnits} (only null before initialized in {@code init()}.
|
||||||
|
* @param str maybe null
|
||||||
|
* @return maybe null
|
||||||
|
*/
|
||||||
|
public DistanceUnits parseDistanceUnits(String str) {
|
||||||
|
if (str == null) {
|
||||||
|
return this.distanceUnits;
|
||||||
|
} else {
|
||||||
|
return DistanceUnits.valueOf(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected SpatialArgsParser newSpatialArgsParser() {
|
protected SpatialArgsParser newSpatialArgsParser() {
|
||||||
return new SpatialArgsParser() {
|
return new SpatialArgsParser() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -281,7 +326,12 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
|
|
||||||
protected SpatialArgs parseSpatialArgs(QParser parser, String externalVal) {
|
protected SpatialArgs parseSpatialArgs(QParser parser, String externalVal) {
|
||||||
try {
|
try {
|
||||||
return argsParser.parse(externalVal, ctx);
|
SpatialArgs args = argsParser.parse(externalVal, ctx);
|
||||||
|
// Convert parsed args.distErr to degrees (using distanceUnits)
|
||||||
|
if (args.getDistErr() != null) {
|
||||||
|
args.setDistErr(args.getDistErr() * distanceUnits.multiplierFromThisUnitToDegrees());
|
||||||
|
}
|
||||||
|
return args;
|
||||||
} catch (SolrException e) {
|
} catch (SolrException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -315,6 +365,11 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
return new FilteredQuery(functionQuery, filter);
|
return new FilteredQuery(functionQuery, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSphereRadius() {
|
||||||
|
return distanceUnits.getEarthRadius();
|
||||||
|
}
|
||||||
|
|
||||||
/** The set of values supported for the score local-param. Not null. */
|
/** The set of values supported for the score local-param. Not null. */
|
||||||
public Set<String> getSupportedScoreModes() {
|
public Set<String> getSupportedScoreModes() {
|
||||||
return supportedScoreModes;
|
return supportedScoreModes;
|
||||||
|
@ -324,21 +379,31 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
if (score == null) {
|
if (score == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
switch (score) {
|
|
||||||
case NONE:
|
final double multiplier; // default multiplier for degrees
|
||||||
|
|
||||||
|
switch(score) {
|
||||||
case "":
|
case "":
|
||||||
|
case NONE:
|
||||||
return null;
|
return null;
|
||||||
case DISTANCE:
|
|
||||||
double multiplier = 1.0;//TODO support units=kilometers
|
|
||||||
return strategy.makeDistanceValueSource(spatialArgs.getShape().getCenter(), multiplier);
|
|
||||||
case RECIP_DISTANCE:
|
case RECIP_DISTANCE:
|
||||||
return strategy.makeRecipDistanceValueSource(spatialArgs.getShape());
|
return strategy.makeRecipDistanceValueSource(spatialArgs.getShape());
|
||||||
|
case DISTANCE:
|
||||||
|
multiplier = distanceUnits.multiplierFromDegreesToThisUnit();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
DistanceUnits du = parseDistanceUnits(score);
|
||||||
|
if (du != null) {
|
||||||
|
multiplier = du.multiplierFromDegreesToThisUnit();
|
||||||
|
} else {
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
|
||||||
"'score' local-param must be one of " + supportedScoreModes);
|
"'score' local-param must be one of " + supportedScoreModes + ", it was: " + score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return strategy.makeDistanceValueSource(spatialArgs.getShape().getCenter(), multiplier);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the cached strategy for this field, creating it if necessary
|
* Gets the cached strategy for this field, creating it if necessary
|
||||||
* via {@link #newSpatialStrategy(String)}.
|
* via {@link #newSpatialStrategy(String)}.
|
||||||
|
@ -368,6 +433,10 @@ public abstract class AbstractSpatialFieldType<T extends SpatialStrategy> extend
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not supported on SpatialField: " + field.getName()+
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not supported on SpatialField: " + field.getName()+
|
||||||
", instead try sorting by query.");
|
", instead try sorting by query.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DistanceUnits getDistanceUnits() {
|
||||||
|
return this.distanceUnits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,14 @@ package org.apache.solr.schema;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
||||||
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTreeFactory;
|
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTreeFactory;
|
||||||
import org.apache.lucene.spatial.query.SpatialArgsParser;
|
import org.apache.lucene.spatial.query.SpatialArgsParser;
|
||||||
import org.apache.solr.util.MapListener;
|
import org.apache.solr.util.MapListener;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see PrefixTreeStrategy
|
* @see PrefixTreeStrategy
|
||||||
* @lucene.experimental
|
* @lucene.experimental
|
||||||
|
@ -42,6 +42,13 @@ public abstract class AbstractSpatialPrefixTreeFieldType<T extends PrefixTreeStr
|
||||||
protected void init(IndexSchema schema, Map<String, String> args) {
|
protected void init(IndexSchema schema, Map<String, String> args) {
|
||||||
super.init(schema, args);
|
super.init(schema, args);
|
||||||
|
|
||||||
|
// Convert the maxDistErr to degrees (based on distanceUnits) since Lucene spatial layer depends on degrees
|
||||||
|
if(args.containsKey(SpatialPrefixTreeFactory.MAX_DIST_ERR)) {
|
||||||
|
double maxDistErrOriginal = Double.parseDouble(args.get(SpatialPrefixTreeFactory.MAX_DIST_ERR));
|
||||||
|
args.put(SpatialPrefixTreeFactory.MAX_DIST_ERR,
|
||||||
|
Double.toString(maxDistErrOriginal * distanceUnits.multiplierFromThisUnitToDegrees()));
|
||||||
|
}
|
||||||
|
|
||||||
//Solr expects us to remove the parameters we've used.
|
//Solr expects us to remove the parameters we've used.
|
||||||
MapListener<String, String> argsWrap = new MapListener<>(args);
|
MapListener<String, String> argsWrap = new MapListener<>(args);
|
||||||
grid = SpatialPrefixTreeFactory.makeSPT(argsWrap, schema.getResourceLoader().getClassLoader(), ctx);
|
grid = SpatialPrefixTreeFactory.makeSPT(argsWrap, schema.getResourceLoader().getClassLoader(), ctx);
|
||||||
|
|
|
@ -17,6 +17,12 @@ package org.apache.solr.schema;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.spatial4j.core.shape.Rectangle;
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
import org.apache.lucene.index.DocValuesType;
|
import org.apache.lucene.index.DocValuesType;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
|
@ -27,12 +33,6 @@ import org.apache.lucene.spatial.util.ShapeAreaValueSource;
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.QParser;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements SchemaAware {
|
public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements SchemaAware {
|
||||||
private static final String PARAM_QUERY_TARGET_PROPORTION = "queryTargetProportion";
|
private static final String PARAM_QUERY_TARGET_PROPORTION = "queryTargetProportion";
|
||||||
private static final String PARAM_MIN_SIDE_LENGTH = "minSideLength";
|
private static final String PARAM_MIN_SIDE_LENGTH = "minSideLength";
|
||||||
|
@ -140,6 +140,7 @@ public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements
|
||||||
if (scoreParam == null) {
|
if (scoreParam == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (scoreParam) {
|
switch (scoreParam) {
|
||||||
//TODO move these to superclass after LUCENE-5804 ?
|
//TODO move these to superclass after LUCENE-5804 ?
|
||||||
case OVERLAP_RATIO:
|
case OVERLAP_RATIO:
|
||||||
|
@ -160,10 +161,12 @@ public class BBoxField extends AbstractSpatialFieldType<BBoxStrategy> implements
|
||||||
queryTargetProportion, minSideLength);
|
queryTargetProportion, minSideLength);
|
||||||
|
|
||||||
case AREA:
|
case AREA:
|
||||||
return new ShapeAreaValueSource(strategy.makeShapeValueSource(), ctx, ctx.isGeo());
|
return new ShapeAreaValueSource(strategy.makeShapeValueSource(), ctx, ctx.isGeo(),
|
||||||
|
distanceUnits.multiplierFromDegreesToThisUnit() * distanceUnits.multiplierFromDegreesToThisUnit());
|
||||||
|
|
||||||
case AREA2D:
|
case AREA2D:
|
||||||
return new ShapeAreaValueSource(strategy.makeShapeValueSource(), ctx, false);
|
return new ShapeAreaValueSource(strategy.makeShapeValueSource(), ctx, false,
|
||||||
|
distanceUnits.multiplierFromDegreesToThisUnit() * distanceUnits.multiplierFromDegreesToThisUnit());
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return super.getValueSourceFromSpatialArgs(parser, field, spatialArgs, scoreParam, strategy);
|
return super.getValueSourceFromSpatialArgs(parser, field, spatialArgs, scoreParam, strategy);
|
||||||
|
|
|
@ -54,12 +54,7 @@ public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRan
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init(IndexSchema schema, Map<String, String> args) {
|
protected void init(IndexSchema schema, Map<String, String> args) {
|
||||||
super.init(schema, addDegrees(args));
|
super.init(schema, args);
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> addDegrees(Map<String, String> args) {
|
|
||||||
args.put("units", "degrees");//HACK!
|
|
||||||
return args;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,16 +17,18 @@
|
||||||
|
|
||||||
package org.apache.solr.schema;
|
package org.apache.solr.schema;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
|
import com.spatial4j.core.io.GeohashUtils;
|
||||||
|
import com.spatial4j.core.shape.Point;
|
||||||
|
import org.apache.lucene.index.StorableField;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
import org.apache.lucene.queries.function.valuesource.LiteralValueSource;
|
import org.apache.lucene.queries.function.valuesource.LiteralValueSource;
|
||||||
import org.apache.lucene.index.StorableField;
|
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
import org.apache.lucene.search.SortField;
|
import org.apache.lucene.search.SortField;
|
||||||
import org.apache.lucene.uninverting.UninvertingReader.Type;
|
import org.apache.lucene.uninverting.UninvertingReader.Type;
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
|
||||||
import com.spatial4j.core.io.GeohashUtils;
|
|
||||||
import com.spatial4j.core.shape.Point;
|
|
||||||
import org.apache.solr.response.TextResponseWriter;
|
import org.apache.solr.response.TextResponseWriter;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.QParser;
|
||||||
import org.apache.solr.search.SolrConstantScoreQuery;
|
import org.apache.solr.search.SolrConstantScoreQuery;
|
||||||
|
@ -35,9 +37,6 @@ import org.apache.solr.search.function.ValueSourceRangeFilter;
|
||||||
import org.apache.solr.search.function.distance.GeohashHaversineFunction;
|
import org.apache.solr.search.function.distance.GeohashHaversineFunction;
|
||||||
import org.apache.solr.util.SpatialUtils;
|
import org.apache.solr.util.SpatialUtils;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a class that represents a <a
|
* This is a class that represents a <a
|
||||||
* href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> field. The field is
|
* href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> field. The field is
|
||||||
|
@ -94,4 +93,9 @@ public class GeoHashField extends FieldType implements SpatialQueryable {
|
||||||
return new StrFieldSource(field.name);
|
return new StrFieldSource(field.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSphereRadius() {
|
||||||
|
return DistanceUtils.EARTH_MEAN_RADIUS_KM;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,13 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
import com.spatial4j.core.shape.Point;
|
import com.spatial4j.core.shape.Point;
|
||||||
|
import com.spatial4j.core.shape.Rectangle;
|
||||||
import org.apache.lucene.document.FieldType;
|
import org.apache.lucene.document.FieldType;
|
||||||
import org.apache.lucene.index.LeafReaderContext;
|
|
||||||
import org.apache.lucene.index.IndexReader;
|
import org.apache.lucene.index.IndexReader;
|
||||||
|
import org.apache.lucene.index.LeafReaderContext;
|
||||||
import org.apache.lucene.index.StorableField;
|
import org.apache.lucene.index.StorableField;
|
||||||
import org.apache.lucene.queries.function.FunctionValues;
|
import org.apache.lucene.queries.function.FunctionValues;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
|
@ -49,11 +51,6 @@ import org.apache.solr.search.ExtendedQueryBase;
|
||||||
import org.apache.solr.search.PostFilter;
|
import org.apache.solr.search.PostFilter;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.QParser;
|
||||||
import org.apache.solr.search.SpatialOptions;
|
import org.apache.solr.search.SpatialOptions;
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
|
||||||
import com.spatial4j.core.distance.DistanceUtils;
|
|
||||||
import com.spatial4j.core.shape.Rectangle;
|
|
||||||
|
|
||||||
import org.apache.solr.util.SpatialUtils;
|
import org.apache.solr.util.SpatialUtils;
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,6 +253,11 @@ public class LatLonType extends AbstractSubTypeFieldType implements SpatialQuery
|
||||||
throw new UnsupportedOperationException("LatLonType uses multiple fields. field=" + field.getName());
|
throw new UnsupportedOperationException("LatLonType uses multiple fields. field=" + field.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSphereRadius() {
|
||||||
|
return DistanceUtils.EARTH_MEAN_RADIUS_KM;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LatLonValueSource extends VectorValueSource {
|
class LatLonValueSource extends VectorValueSource {
|
||||||
|
|
|
@ -17,10 +17,16 @@
|
||||||
|
|
||||||
package org.apache.solr.schema;
|
package org.apache.solr.schema;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
import org.apache.lucene.document.FieldType;
|
import org.apache.lucene.document.FieldType;
|
||||||
|
import org.apache.lucene.index.StorableField;
|
||||||
import org.apache.lucene.queries.function.ValueSource;
|
import org.apache.lucene.queries.function.ValueSource;
|
||||||
import org.apache.lucene.queries.function.valuesource.VectorValueSource;
|
import org.apache.lucene.queries.function.valuesource.VectorValueSource;
|
||||||
import org.apache.lucene.index.StorableField;
|
|
||||||
import org.apache.lucene.search.BooleanClause;
|
import org.apache.lucene.search.BooleanClause;
|
||||||
import org.apache.lucene.search.BooleanQuery;
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
|
@ -33,11 +39,6 @@ import org.apache.solr.response.TextResponseWriter;
|
||||||
import org.apache.solr.search.QParser;
|
import org.apache.solr.search.QParser;
|
||||||
import org.apache.solr.search.SpatialOptions;
|
import org.apache.solr.search.SpatialOptions;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A point type that indexes a point in an n-dimensional space as separate fields and supports range queries.
|
* A point type that indexes a point in an n-dimensional space as separate fields and supports range queries.
|
||||||
* See {@link LatLonType} for geo-spatial queries.
|
* See {@link LatLonType} for geo-spatial queries.
|
||||||
|
@ -178,6 +179,7 @@ public class PointType extends CoordinateFieldType implements SpatialQueryable {
|
||||||
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
|
||||||
}
|
}
|
||||||
IndexSchema schema = parser.getReq().getSchema();
|
IndexSchema schema = parser.getReq().getSchema();
|
||||||
|
|
||||||
if (dimension == 1){
|
if (dimension == 1){
|
||||||
//TODO: Handle distance measures
|
//TODO: Handle distance measures
|
||||||
String lower = String.valueOf(point[0] - options.distance);
|
String lower = String.valueOf(point[0] - options.distance);
|
||||||
|
@ -273,6 +275,13 @@ public class PointType extends CoordinateFieldType implements SpatialQueryable {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getSphereRadius() {
|
||||||
|
// This won't likely be used. You should probably be using LatLonType instead if you felt the need for this.
|
||||||
|
// This is here just for backward compatibility reasons.
|
||||||
|
return DistanceUtils.EARTH_MEAN_RADIUS_KM;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,4 +31,6 @@ import org.apache.solr.search.SpatialOptions;
|
||||||
public interface SpatialQueryable {
|
public interface SpatialQueryable {
|
||||||
|
|
||||||
public Query createSpatialQuery(QParser parser, SpatialOptions options);
|
public Query createSpatialQuery(QParser parser, SpatialOptions options);
|
||||||
|
|
||||||
|
public double getSphereRadius();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package org.apache.solr.search;
|
||||||
|
|
||||||
|
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
import com.spatial4j.core.distance.DistanceUtils;
|
|
||||||
import org.apache.solr.common.SolrException;
|
import org.apache.solr.common.SolrException;
|
||||||
import org.apache.solr.common.params.SolrParams;
|
import org.apache.solr.common.params.SolrParams;
|
||||||
import org.apache.solr.common.params.SpatialParams;
|
import org.apache.solr.common.params.SpatialParams;
|
||||||
|
@ -76,10 +75,11 @@ public class SpatialFilterQParser extends QParser {
|
||||||
FieldType type = sf.getType();
|
FieldType type = sf.getType();
|
||||||
|
|
||||||
if (type instanceof SpatialQueryable) {
|
if (type instanceof SpatialQueryable) {
|
||||||
double radius = localParams.getDouble(SpatialParams.SPHERE_RADIUS, DistanceUtils.EARTH_MEAN_RADIUS_KM);
|
SpatialQueryable queryable = ((SpatialQueryable)type);
|
||||||
|
double radius = localParams.getDouble(SpatialParams.SPHERE_RADIUS, queryable.getSphereRadius());
|
||||||
SpatialOptions opts = new SpatialOptions(pointStr, dist, sf, measStr, radius);
|
SpatialOptions opts = new SpatialOptions(pointStr, dist, sf, measStr, radius);
|
||||||
opts.bbox = bbox;
|
opts.bbox = bbox;
|
||||||
result = ((SpatialQueryable)type).createSpatialQuery(this, opts);
|
result = queryable.createSpatialQuery(this, opts);
|
||||||
} else {
|
} else {
|
||||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field " + fields[0]
|
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field " + fields[0]
|
||||||
+ " does not support spatial filtering");
|
+ " does not support spatial filtering");
|
||||||
|
|
|
@ -17,6 +17,10 @@ package org.apache.solr.search.function.distance;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
import com.spatial4j.core.distance.DistanceUtils;
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
import com.spatial4j.core.shape.Point;
|
import com.spatial4j.core.shape.Point;
|
||||||
|
@ -33,12 +37,9 @@ import org.apache.solr.schema.SchemaField;
|
||||||
import org.apache.solr.search.FunctionQParser;
|
import org.apache.solr.search.FunctionQParser;
|
||||||
import org.apache.solr.search.SyntaxError;
|
import org.apache.solr.search.SyntaxError;
|
||||||
import org.apache.solr.search.ValueSourceParser;
|
import org.apache.solr.search.ValueSourceParser;
|
||||||
|
import org.apache.solr.util.DistanceUnits;
|
||||||
import org.apache.solr.util.SpatialUtils;
|
import org.apache.solr.util.SpatialUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses "geodist" creating {@link HaversineConstFunction} or {@link HaversineFunction}
|
* Parses "geodist" creating {@link HaversineConstFunction} or {@link HaversineFunction}
|
||||||
* or calling {@link SpatialStrategy#makeDistanceValueSource(com.spatial4j.core.shape.Point,double)}.
|
* or calling {@link SpatialStrategy#makeDistanceValueSource(com.spatial4j.core.shape.Point,double)}.
|
||||||
|
@ -133,8 +134,11 @@ public class GeoDistValueSourceParser extends ValueSourceParser {
|
||||||
" the point must be supplied as constants");
|
" the point must be supplied as constants");
|
||||||
// note: uses Haversine by default but can be changed via distCalc=...
|
// note: uses Haversine by default but can be changed via distCalc=...
|
||||||
SpatialStrategy strategy = ((SpatialStrategyMultiValueSource) mv2).strategy;
|
SpatialStrategy strategy = ((SpatialStrategyMultiValueSource) mv2).strategy;
|
||||||
|
DistanceUnits distanceUnits = ((SpatialStrategyMultiValueSource) mv2).distanceUnits;
|
||||||
Point queryPoint = strategy.getSpatialContext().makePoint(constants[1], constants[0]);
|
Point queryPoint = strategy.getSpatialContext().makePoint(constants[1], constants[0]);
|
||||||
return strategy.makeDistanceValueSource(queryPoint, DistanceUtils.DEG_TO_KM);
|
if (distanceUnits == DistanceUnits.BACKCOMPAT)
|
||||||
|
distanceUnits = DistanceUnits.KILOMETERS;
|
||||||
|
return strategy.makeDistanceValueSource(queryPoint, distanceUnits.multiplierFromDegreesToThisUnit());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constants != null && other instanceof VectorValueSource) {
|
if (constants != null && other instanceof VectorValueSource) {
|
||||||
|
@ -180,7 +184,7 @@ public class GeoDistValueSourceParser extends ValueSourceParser {
|
||||||
FieldType type = sf.getType();
|
FieldType type = sf.getType();
|
||||||
if (type instanceof AbstractSpatialFieldType) {
|
if (type instanceof AbstractSpatialFieldType) {
|
||||||
AbstractSpatialFieldType asft = (AbstractSpatialFieldType) type;
|
AbstractSpatialFieldType asft = (AbstractSpatialFieldType) type;
|
||||||
return new SpatialStrategyMultiValueSource(asft.getStrategy(sfield));
|
return new SpatialStrategyMultiValueSource(asft.getStrategy(sfield), asft.getDistanceUnits());
|
||||||
}
|
}
|
||||||
ValueSource vs = type.getValueSource(sf, fp);
|
ValueSource vs = type.getValueSource(sf, fp);
|
||||||
if (vs instanceof MultiValueSource) {
|
if (vs instanceof MultiValueSource) {
|
||||||
|
@ -194,10 +198,12 @@ public class GeoDistValueSourceParser extends ValueSourceParser {
|
||||||
private static class SpatialStrategyMultiValueSource extends VectorValueSource {
|
private static class SpatialStrategyMultiValueSource extends VectorValueSource {
|
||||||
|
|
||||||
final SpatialStrategy strategy;
|
final SpatialStrategy strategy;
|
||||||
|
final DistanceUnits distanceUnits;
|
||||||
|
|
||||||
public SpatialStrategyMultiValueSource(SpatialStrategy strategy) {
|
public SpatialStrategyMultiValueSource(SpatialStrategy strategy, DistanceUnits distanceUnits) {
|
||||||
super(Collections.EMPTY_LIST);
|
super(Collections.EMPTY_LIST);
|
||||||
this.strategy = strategy;
|
this.strategy = strategy;
|
||||||
|
this.distanceUnits = distanceUnits;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
package org.apache.solr.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
|
import org.apache.solr.schema.AbstractSpatialFieldType;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used with a spatial field type for all distance measurements.
|
||||||
|
*
|
||||||
|
* @see AbstractSpatialFieldType
|
||||||
|
*/
|
||||||
|
public class DistanceUnits {
|
||||||
|
public final static String KILOMETERS_PARAM = "kilometers";
|
||||||
|
public final static String MILES_PARAM = "miles";
|
||||||
|
public final static String DEGREES_PARAM = "degrees";
|
||||||
|
|
||||||
|
// Singleton distance units instances
|
||||||
|
public final static DistanceUnits KILOMETERS = new DistanceUnits(KILOMETERS_PARAM, DistanceUtils.EARTH_MEAN_RADIUS_KM,
|
||||||
|
DistanceUtils.KM_TO_DEG);
|
||||||
|
public final static DistanceUnits MILES = new DistanceUnits(MILES_PARAM, DistanceUtils.EARTH_MEAN_RADIUS_MI,
|
||||||
|
DistanceUtils.MILES_TO_KM * DistanceUtils.KM_TO_DEG);
|
||||||
|
public final static DistanceUnits DEGREES = new DistanceUnits(DEGREES_PARAM, 180.0/Math.PI, 1.0);
|
||||||
|
|
||||||
|
// Previously, distance based filtering was done with km, but scores were based on degrees
|
||||||
|
@Deprecated
|
||||||
|
public final static DistanceUnits BACKCOMPAT = new DistanceUnits("backcompat", DistanceUtils.EARTH_MEAN_RADIUS_KM, 1.0);
|
||||||
|
|
||||||
|
//volatile so other threads see when we replace when copy-on-write
|
||||||
|
private static volatile Map<String, DistanceUnits> instances = ImmutableMap.of(
|
||||||
|
KILOMETERS_PARAM, KILOMETERS,
|
||||||
|
MILES_PARAM, MILES,
|
||||||
|
DEGREES_PARAM, DEGREES);
|
||||||
|
|
||||||
|
private final String stringIdentifier;
|
||||||
|
private final double earthRadius;
|
||||||
|
private final double multiplierThisToDegrees;
|
||||||
|
private final double multiplierDegreesToThis;
|
||||||
|
|
||||||
|
private DistanceUnits(String str, double earthRadius, double multiplierThisToDegrees) {
|
||||||
|
this.stringIdentifier = str;
|
||||||
|
this.earthRadius = earthRadius;
|
||||||
|
this.multiplierThisToDegrees = multiplierThisToDegrees;
|
||||||
|
this.multiplierDegreesToThis = 1.0 / multiplierThisToDegrees;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string representation of distance units and returns its implementing class instance.
|
||||||
|
* Preferred way to parse a DistanceUnits would be to use {@link AbstractSpatialFieldType#parseDistanceUnits(String)},
|
||||||
|
* since it will default to one defined on the field type if the string is null.
|
||||||
|
*
|
||||||
|
* @param str String representation of distance units, e.g. "kilometers", "miles" etc. (null ok)
|
||||||
|
* @return an instance of the concrete DistanceUnits, null if not found.
|
||||||
|
*/
|
||||||
|
public static DistanceUnits valueOf(String str) {
|
||||||
|
return instances.get(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> getSupportedUnits() {
|
||||||
|
return instances.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return Radius of the earth in this distance units
|
||||||
|
*/
|
||||||
|
public double getEarthRadius() {
|
||||||
|
return earthRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return multiplier needed to convert a distance in current units to degrees
|
||||||
|
*/
|
||||||
|
public double multiplierFromThisUnitToDegrees() {
|
||||||
|
return multiplierThisToDegrees;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return multiplier needed to convert a distance in degrees to current units
|
||||||
|
*/
|
||||||
|
public double multiplierFromDegreesToThisUnit() {
|
||||||
|
return multiplierDegreesToThis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the string identifier associated with this units instance
|
||||||
|
*/
|
||||||
|
public String getStringIdentifier() {
|
||||||
|
return stringIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom distance units can be supplied using this method. It's thread-safe.
|
||||||
|
*
|
||||||
|
* @param strId string identifier for the units
|
||||||
|
* @param earthRadius radius of earth in supplied units
|
||||||
|
* @param multiplierThisToDegrees multiplier to convert to degrees
|
||||||
|
*/
|
||||||
|
public static synchronized void addUnits(String strId, double earthRadius, double multiplierThisToDegrees) {
|
||||||
|
//copy-on-write.
|
||||||
|
Map<String, DistanceUnits> map = new HashMap<String, DistanceUnits>(instances);
|
||||||
|
map.put(strId, new DistanceUnits(strId, earthRadius, multiplierThisToDegrees));
|
||||||
|
instances = ImmutableMap.copyOf(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getStringIdentifier();
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,28 +30,28 @@
|
||||||
<fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
|
<fieldType name="string" class="solr.StrField" sortMissingLast="true"/>
|
||||||
|
|
||||||
<fieldType name="srpt_geohash" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="srpt_geohash" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
prefixTree="geohash" units="degrees"
|
prefixTree="geohash" distanceUnits="degrees"
|
||||||
/>
|
/>
|
||||||
<fieldType name="srpt_quad" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="srpt_quad" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
prefixTree="quad" units="degrees"
|
prefixTree="quad" distanceUnits="degrees"
|
||||||
/>
|
/>
|
||||||
<fieldType name="srpt_100km" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="srpt_100km" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
maxDistErr="0.9" units="degrees"
|
maxDistErr="100" distanceUnits="kilometers"
|
||||||
/>
|
/>
|
||||||
<fieldType name="stqpt_geohash" class="solr.SpatialTermQueryPrefixTreeFieldType"
|
<fieldType name="stqpt_geohash" class="solr.SpatialTermQueryPrefixTreeFieldType"
|
||||||
prefixTree="geohash" units="degrees" />
|
prefixTree="geohash" distanceUnits="degrees" />
|
||||||
|
|
||||||
<fieldType name="stqpt_u" class="solr.SpatialTermQueryPrefixTreeFieldType"
|
<fieldType name="stqpt_u" class="solr.SpatialTermQueryPrefixTreeFieldType"
|
||||||
geo="false" distCalculator="cartesian^2" worldBounds="ENVELOPE(0, 1000, 1000, 0)" units="degrees"/>
|
geo="false" distCalculator="cartesian^2" worldBounds="ENVELOPE(0, 1000, 1000, 0)" distanceUnits="degrees"/>
|
||||||
|
|
||||||
<fieldType name="pointvector" class="solr.SpatialPointVectorFieldType"
|
<fieldType name="pointvector" class="solr.SpatialPointVectorFieldType"
|
||||||
numberType="tdouble" units="degrees"/>
|
numberType="tdouble" distanceUnits="degrees"/>
|
||||||
|
|
||||||
<fieldType name="stqpt_u_oldworldbounds" class="solr.SpatialTermQueryPrefixTreeFieldType"
|
<fieldType name="stqpt_u_oldworldbounds" class="solr.SpatialTermQueryPrefixTreeFieldType"
|
||||||
geo="false" distCalculator="cartesian^2" worldBounds="0 0 1000 1000" units="degrees"/>
|
geo="false" distCalculator="cartesian^2" worldBounds="0 0 1000 1000" distanceUnits="degrees"/>
|
||||||
|
|
||||||
<fieldType name="bbox" class="solr.BBoxField"
|
<fieldType name="bbox" class="solr.BBoxField"
|
||||||
numberType="tdoubleDV" units="degrees"/>
|
numberType="tdoubleDV" distanceUnits="degrees"/>
|
||||||
</types>
|
</types>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -392,7 +392,7 @@
|
||||||
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
|
<fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/>
|
||||||
<!-- sub-centimeter accuracy for RPT; distance calcs -->
|
<!-- sub-centimeter accuracy for RPT; distance calcs -->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.00000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.00001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<fieldType name="currency" class="solr.CurrencyField" currencyConfig="currency.xml" multiValued="false" />
|
<fieldType name="currency" class="solr.CurrencyField" currencyConfig="currency.xml" multiValued="false" />
|
||||||
</types>
|
</types>
|
||||||
|
|
|
@ -0,0 +1,275 @@
|
||||||
|
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 java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.solr.core.AbstractBadConfigTestBase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
public class SpatialRPTFieldTypeTest extends AbstractBadConfigTestBase {
|
||||||
|
|
||||||
|
private static File tmpSolrHome;
|
||||||
|
private static File tmpConfDir;
|
||||||
|
|
||||||
|
private static final String collection = "collection1";
|
||||||
|
private static final String confDir = collection + "/conf";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
private void initManagedSchemaCore() throws Exception {
|
||||||
|
tmpSolrHome = createTempDir().toFile();
|
||||||
|
tmpConfDir = new File(tmpSolrHome, confDir);
|
||||||
|
File testHomeConfDir = new File(TEST_HOME(), confDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig-managed-schema.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig-basic.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig.snippet.randomindexconfig.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-one-field-no-dynamic-field.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-one-field-no-dynamic-field-unique-key.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-minimal.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema_codec.xml"), tmpConfDir);
|
||||||
|
FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-bm25.xml"), tmpConfDir);
|
||||||
|
|
||||||
|
// initCore will trigger an upgrade to managed schema, since the solrconfig has
|
||||||
|
// <schemaFactory class="ManagedIndexSchemaFactory" ... />
|
||||||
|
System.setProperty("managed.schema.mutable", "false");
|
||||||
|
System.setProperty("enable.update.log", "false");
|
||||||
|
initCore("solrconfig-managed-schema.xml", "schema-minimal.xml", tmpSolrHome.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
private void afterClass() throws Exception {
|
||||||
|
deleteCore();
|
||||||
|
System.clearProperty("managed.schema.mutable");
|
||||||
|
System.clearProperty("enable.update.log");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String INDEXED_COORDINATES = "25,82";
|
||||||
|
final String QUERY_COORDINATES = "24,81";
|
||||||
|
final String DISTANCE_DEGREES = "1.3520328";
|
||||||
|
final String DISTANCE_KILOMETERS = "150.33939";
|
||||||
|
final String DISTANCE_MILES = "93.416565";
|
||||||
|
|
||||||
|
public void testUnitsDegrees() throws Exception { // test back compat behaviour
|
||||||
|
setupRPTField("degrees", null, "true");
|
||||||
|
|
||||||
|
assertU(adoc("str", "X", "geo", INDEXED_COORDINATES));
|
||||||
|
assertU(commit());
|
||||||
|
String q;
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=distance filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_DEGREES+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=degrees filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_DEGREES+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=kilometers filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_KILOMETERS+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=miles filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_MILES+"']");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUnitsNonDegrees() throws Exception {
|
||||||
|
try {
|
||||||
|
setupRPTField("kilometers", null, "true");
|
||||||
|
fail("Expected exception for deprecated units parameter.");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if(!ex.getMessage().startsWith("units parameter is deprecated"))
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDistanceUnitsDegrees() throws Exception {
|
||||||
|
setupRPTField(null, "degrees", "true");
|
||||||
|
|
||||||
|
assertU(adoc("str", "X", "geo", INDEXED_COORDINATES));
|
||||||
|
assertU(commit());
|
||||||
|
String q;
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=distance filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_DEGREES+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=degrees filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_DEGREES+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=kilometers filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_KILOMETERS+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=miles filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_MILES+"']");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDistanceUnitsKilometers() throws Exception {
|
||||||
|
setupRPTField(null, "kilometers", "true");
|
||||||
|
|
||||||
|
assertU(adoc("str", "X", "geo", INDEXED_COORDINATES));
|
||||||
|
assertU(commit());
|
||||||
|
String q;
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=distance filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_KILOMETERS+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=degrees filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_DEGREES+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=kilometers filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_KILOMETERS+"']");
|
||||||
|
|
||||||
|
q = "geo:{!geofilt score=miles filter=false sfield=geo pt="+QUERY_COORDINATES+" d=1000}";
|
||||||
|
assertQ(req("q", q, "fl", "*,score"), "//result/doc/float[@name='score'][.='"+DISTANCE_MILES+"']");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBothUnitsAndDistanceUnits() throws Exception { // distanceUnits should take precedence
|
||||||
|
try {
|
||||||
|
setupRPTField("degrees", "kilometers", "true");
|
||||||
|
fail("Expected exception for deprecated units parameter.");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if(!ex.getMessage().startsWith("units parameter is deprecated"))
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testJunkValuesForDistanceUnits() throws Exception {
|
||||||
|
try {
|
||||||
|
setupRPTField(null, "rose", "true");
|
||||||
|
fail("Expected exception for bad value of distanceUnits.");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if(!ex.getMessage().startsWith("Must specify distanceUnits as one of"))
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testMaxDistErrConversion() throws Exception {
|
||||||
|
deleteCore();
|
||||||
|
File managedSchemaFile = new File(tmpConfDir, "managed-schema");
|
||||||
|
Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema
|
||||||
|
System.setProperty("managed.schema.mutable", "true");
|
||||||
|
initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath());
|
||||||
|
|
||||||
|
String fieldName = "new_text_field";
|
||||||
|
assertNull("Field '" + fieldName + "' is present in the schema",
|
||||||
|
h.getCore().getLatestSchema().getFieldOrNull(fieldName));
|
||||||
|
|
||||||
|
IndexSchema oldSchema = h.getCore().getLatestSchema();
|
||||||
|
|
||||||
|
SpatialRecursivePrefixTreeFieldType rptFieldType = new SpatialRecursivePrefixTreeFieldType();
|
||||||
|
Map<String, String> rptMap = new HashMap<String,String>();
|
||||||
|
|
||||||
|
rptFieldType.setTypeName("location_rpt");
|
||||||
|
rptMap.put("geo", "true");
|
||||||
|
|
||||||
|
// test km
|
||||||
|
rptMap.put("distanceUnits", "kilometers");
|
||||||
|
rptMap.put("maxDistErr", "0.001"); // 1 meter
|
||||||
|
rptFieldType.init(oldSchema, rptMap);
|
||||||
|
assertEquals(11, rptFieldType.grid.getMaxLevels());
|
||||||
|
|
||||||
|
// test miles
|
||||||
|
rptMap.put("distanceUnits", "miles");
|
||||||
|
rptMap.put("maxDistErr", "0.001");
|
||||||
|
rptFieldType.init(oldSchema, rptMap);
|
||||||
|
assertEquals(10, rptFieldType.grid.getMaxLevels());
|
||||||
|
|
||||||
|
// test degrees
|
||||||
|
rptMap.put("distanceUnits", "degrees");
|
||||||
|
rptMap.put("maxDistErr", "0.001");
|
||||||
|
rptFieldType.init(oldSchema, rptMap);
|
||||||
|
assertEquals(8, rptFieldType.grid.getMaxLevels());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGeoDistanceFunctionWithBackCompat() throws Exception {
|
||||||
|
setupRPTField("degrees", null, "true");
|
||||||
|
|
||||||
|
assertU(adoc("str", "X", "geo", "1,2"));
|
||||||
|
assertU(commit());
|
||||||
|
|
||||||
|
// geodist() should return in km
|
||||||
|
assertJQ(req("defType","func",
|
||||||
|
"q","geodist(3,4)",
|
||||||
|
"sfield","geo",
|
||||||
|
"fl","score")
|
||||||
|
, 1e-5
|
||||||
|
,"/response/docs/[0]/score==314.4033"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGeoDistanceFunctionWithKilometers() throws Exception {
|
||||||
|
setupRPTField(null, "kilometers", "true");
|
||||||
|
|
||||||
|
assertU(adoc("str", "X", "geo", "1,2"));
|
||||||
|
assertU(commit());
|
||||||
|
|
||||||
|
assertJQ(req("defType","func",
|
||||||
|
"q","geodist(3,4)",
|
||||||
|
"sfield","geo",
|
||||||
|
"fl","score")
|
||||||
|
, 1e-5
|
||||||
|
,"/response/docs/[0]/score==314.4033"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGeoDistanceFunctionWithMiles() throws Exception {
|
||||||
|
setupRPTField(null, "miles", "true");
|
||||||
|
|
||||||
|
assertU(adoc("str", "X", "geo", "1,2"));
|
||||||
|
assertU(commit());
|
||||||
|
|
||||||
|
assertJQ(req("defType","func",
|
||||||
|
"q","geodist(3,4)",
|
||||||
|
"sfield","geo",
|
||||||
|
"fl","score")
|
||||||
|
, 1e-5
|
||||||
|
,"/response/docs/[0]/score==195.36115"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRPTField(String units, String distanceUnits, String geo) throws Exception {
|
||||||
|
deleteCore();
|
||||||
|
File managedSchemaFile = new File(tmpConfDir, "managed-schema");
|
||||||
|
Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema
|
||||||
|
System.setProperty("managed.schema.mutable", "true");
|
||||||
|
initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath());
|
||||||
|
|
||||||
|
String fieldName = "new_text_field";
|
||||||
|
assertNull("Field '" + fieldName + "' is present in the schema",
|
||||||
|
h.getCore().getLatestSchema().getFieldOrNull(fieldName));
|
||||||
|
|
||||||
|
IndexSchema oldSchema = h.getCore().getLatestSchema();
|
||||||
|
|
||||||
|
SpatialRecursivePrefixTreeFieldType rptFieldType = new SpatialRecursivePrefixTreeFieldType();
|
||||||
|
Map<String, String> rptMap = new HashMap<String,String>();
|
||||||
|
if(units!=null)
|
||||||
|
rptMap.put("units", units);
|
||||||
|
if(distanceUnits!=null)
|
||||||
|
rptMap.put("distanceUnits", distanceUnits);
|
||||||
|
if(geo!=null)
|
||||||
|
rptMap.put("geo", geo);
|
||||||
|
rptFieldType.init(oldSchema, rptMap);
|
||||||
|
rptFieldType.setTypeName("location_rpt");
|
||||||
|
SchemaField newField = new SchemaField("geo", rptFieldType, SchemaField.STORED | SchemaField.INDEXED, null);
|
||||||
|
IndexSchema newSchema = oldSchema.addField(newField);
|
||||||
|
|
||||||
|
h.getCore().setLatestSchema(newSchema);
|
||||||
|
|
||||||
|
assertU(delQ("*:*"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,9 @@ package org.apache.solr.search;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.RandomizedTest;
|
import com.carrotsearch.randomizedtesting.RandomizedTest;
|
||||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||||
import com.spatial4j.core.context.SpatialContext;
|
import com.spatial4j.core.context.SpatialContext;
|
||||||
|
@ -33,9 +36,6 @@ import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test Solr 4's new spatial capabilities from the new Lucene spatial module. Don't thoroughly test it here because
|
* Test Solr 4's new spatial capabilities from the new Lucene spatial module. Don't thoroughly test it here because
|
||||||
* Lucene spatial has its own tests. Some of these tests were ported from Solr 3 spatial tests.
|
* Lucene spatial has its own tests. Some of these tests were ported from Solr 3 spatial tests.
|
||||||
|
@ -110,19 +110,20 @@ public class TestSolr4Spatial extends SolrTestCaseJ4 {
|
||||||
@Test
|
@Test
|
||||||
public void testIntersectFilter() throws Exception {
|
public void testIntersectFilter() throws Exception {
|
||||||
setupDocs();
|
setupDocs();
|
||||||
|
|
||||||
//Try some edge cases
|
//Try some edge cases
|
||||||
checkHits(fieldName, "1,1", 175, 3, 5, 6, 7);
|
checkHits(fieldName, "1,1", 175, DistanceUtils.EARTH_MEAN_RADIUS_KM, 3, 5, 6, 7);
|
||||||
checkHits(fieldName, "0,179.8", 200, 2, 8, 9);
|
checkHits(fieldName, "0,179.8", 200, DistanceUtils.EARTH_MEAN_RADIUS_KM, 2, 8, 9);
|
||||||
checkHits(fieldName, "89.8, 50", 200, 2, 10, 11);//this goes over the north pole
|
checkHits(fieldName, "89.8, 50", 200, DistanceUtils.EARTH_MEAN_RADIUS_KM, 2, 10, 11);//this goes over the north pole
|
||||||
checkHits(fieldName, "-89.8, 50", 200, 2, 12, 13);//this goes over the south pole
|
checkHits(fieldName, "-89.8, 50", 200, DistanceUtils.EARTH_MEAN_RADIUS_KM, 2, 12, 13);//this goes over the south pole
|
||||||
//try some normal cases
|
//try some normal cases
|
||||||
checkHits(fieldName, "33.0,-80.0", 300, 2);
|
checkHits(fieldName, "33.0,-80.0", 300, DistanceUtils.EARTH_MEAN_RADIUS_KM, 2);
|
||||||
//large distance
|
//large distance
|
||||||
checkHits(fieldName, "1,1", 5000, 3, 5, 6, 7);
|
checkHits(fieldName, "1,1", 5000, DistanceUtils.EARTH_MEAN_RADIUS_KM, 3, 5, 6, 7);
|
||||||
//Because we are generating a box based on the west/east longitudes and the south/north latitudes, which then
|
//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,
|
//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.
|
//it will be included, b/c of the box calculation.
|
||||||
checkHits(fieldName, false, "0.1,0.1", 15, 2, 5, 6);
|
checkHits(fieldName, false, "0.1,0.1", 15, DistanceUtils.EARTH_MEAN_RADIUS_KM, 2, 5, 6);
|
||||||
|
|
||||||
//try some more
|
//try some more
|
||||||
clearIndex();
|
clearIndex();
|
||||||
|
@ -133,18 +134,18 @@ public class TestSolr4Spatial extends SolrTestCaseJ4 {
|
||||||
assertU(adoc("id", "17", fieldName, "44.043900,-95.436643"));
|
assertU(adoc("id", "17", fieldName, "44.043900,-95.436643"));
|
||||||
assertU(commit());
|
assertU(commit());
|
||||||
|
|
||||||
checkHits(fieldName, "0,0", 1000, 1, 14);
|
checkHits(fieldName, "0,0", 1000, DistanceUtils.EARTH_MEAN_RADIUS_KM, 1, 14);
|
||||||
checkHits(fieldName, "0,0", 2000, 2, 14, 15);
|
checkHits(fieldName, "0,0", 2000, DistanceUtils.EARTH_MEAN_RADIUS_KM, 2, 14, 15);
|
||||||
checkHits(fieldName, false, "0,0", 3000, 3, 14, 15, 16);
|
checkHits(fieldName, false, "0,0", 3000, DistanceUtils.EARTH_MEAN_RADIUS_KM, 3, 14, 15, 16);
|
||||||
checkHits(fieldName, "0,0", 3001, 3, 14, 15, 16);
|
checkHits(fieldName, "0,0", 3001, DistanceUtils.EARTH_MEAN_RADIUS_KM, 3, 14, 15, 16);
|
||||||
checkHits(fieldName, "0,0", 3000.1, 3, 14, 15, 16);
|
checkHits(fieldName, "0,0", 3000.1, DistanceUtils.EARTH_MEAN_RADIUS_KM, 3, 14, 15, 16);
|
||||||
|
|
||||||
//really fine grained distance and reflects some of the vagaries of how we are calculating the box
|
//really fine grained distance and reflects some of the vagaries of how we are calculating the box
|
||||||
checkHits(fieldName, "43.517030,-96.789603", 109, 0);
|
checkHits(fieldName, "43.517030,-96.789603", 109, DistanceUtils.EARTH_MEAN_RADIUS_KM, 0);
|
||||||
|
|
||||||
//falls outside of the real distance, but inside the bounding box
|
//falls outside of the real distance, but inside the bounding box
|
||||||
checkHits(fieldName, true, "43.517030,-96.789603", 110, 0);
|
checkHits(fieldName, true, "43.517030,-96.789603", 110, DistanceUtils.EARTH_MEAN_RADIUS_KM, 0);
|
||||||
checkHits(fieldName, false, "43.517030,-96.789603", 110, 1, 17);
|
checkHits(fieldName, false, "43.517030,-96.789603", 110, DistanceUtils.EARTH_MEAN_RADIUS_KM, 1, 17);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -164,14 +165,14 @@ public class TestSolr4Spatial extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkQueryEmptyIndex() throws ParseException {
|
public void checkQueryEmptyIndex() throws ParseException {
|
||||||
checkHits(fieldName, "0,0", 100, 0);//doesn't error
|
checkHits(fieldName, "0,0", 100, DistanceUtils.EARTH_MEAN_RADIUS_KM, 0);//doesn't error
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkHits(String fieldName, String pt, double distKM, int count, int ... docIds) throws ParseException {
|
private void checkHits(String fieldName, String pt, double distKM, double sphereRadius, int count, int ... docIds) throws ParseException {
|
||||||
checkHits(fieldName, true, pt, distKM, count, docIds);
|
checkHits(fieldName, true, pt, distKM, sphereRadius, count, docIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkHits(String fieldName, boolean exact, String ptStr, double distKM, int count, int ... docIds) throws ParseException {
|
private void checkHits(String fieldName, boolean exact, String ptStr, double distKM, double sphereRadius, int count, int ... docIds) throws ParseException {
|
||||||
if (exact && fieldName.equalsIgnoreCase("bbox")) {
|
if (exact && fieldName.equalsIgnoreCase("bbox")) {
|
||||||
return; // bbox field only supports rectangular query
|
return; // bbox field only supports rectangular query
|
||||||
}
|
}
|
||||||
|
@ -217,7 +218,7 @@ public class TestSolr4Spatial extends SolrTestCaseJ4 {
|
||||||
{
|
{
|
||||||
assertQ(req(
|
assertQ(req(
|
||||||
"fl", "id", "q", "*:*", "rows", "1000",
|
"fl", "id", "q", "*:*", "rows", "1000",
|
||||||
"fq", "{!" + (exact ? "geofilt" : "bbox") + " sfield=" + fieldName + " pt='" + ptStr + "' d=" + distKM + "}"),
|
"fq", "{!" + (exact ? "geofilt" : "bbox") + " sfield=" + fieldName + " pt='" + ptStr + "' d=" + distKM + " sphere_radius=" + sphereRadius + "}"),
|
||||||
tests);
|
tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +333,7 @@ public class TestSolr4Spatial extends SolrTestCaseJ4 {
|
||||||
"sfield=" + fieldName + " "
|
"sfield=" + fieldName + " "
|
||||||
+ (score != null ? "score="+score : "") + " "
|
+ (score != null ? "score="+score : "") + " "
|
||||||
+ (filter != null ? "filter="+filter : "") + " "
|
+ (filter != null ? "filter="+filter : "") + " "
|
||||||
+ "pt=" + lat + "," + lon + " d=" + (dDEG * DistanceUtils.DEG_TO_KM) + "}";
|
+ "pt=" + lat + "," + lon + " d=" + (dDEG /* DistanceUtils.DEG_TO_KM*/) + "}";
|
||||||
} else {
|
} else {
|
||||||
return "{! "
|
return "{! "
|
||||||
+ (score != null ? "score="+score : "") + " "
|
+ (score != null ? "score="+score : "") + " "
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.apache.solr.util;
|
||||||
|
|
||||||
|
import com.spatial4j.core.distance.DistanceUtils;
|
||||||
|
import org.apache.lucene.util.LuceneTestCase;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class DistanceUnitsTest extends LuceneTestCase {
|
||||||
|
|
||||||
|
public void testAddNewUnits() throws Exception {
|
||||||
|
DistanceUnits.addUnits("lightyears", 6.73430542e-12, 9.4605284e12 * DistanceUtils.KM_TO_DEG);
|
||||||
|
assertTrue(DistanceUnits.getSupportedUnits().contains("lightyears"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -697,7 +697,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -616,7 +616,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -647,7 +647,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -697,7 +697,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -528,7 +528,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -504,7 +504,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has
|
<!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has
|
||||||
special relevancy modes: score=overlapRatio|area|area2D (local-param to the query). DocValues is recommended for
|
special relevancy modes: score=overlapRatio|area|area2D (local-param to the query). DocValues is recommended for
|
||||||
|
|
|
@ -635,7 +635,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
<!-- Money/currency field type. See http://wiki.apache.org/solr/MoneyFieldType
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
|
@ -707,7 +707,7 @@
|
||||||
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
http://wiki.apache.org/solr/SolrAdaptersForLuceneSpatial4
|
||||||
-->
|
-->
|
||||||
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
<fieldType name="location_rpt" class="solr.SpatialRecursivePrefixTreeFieldType"
|
||||||
geo="true" distErrPct="0.025" maxDistErr="0.000009" units="degrees" />
|
geo="true" distErrPct="0.025" maxDistErr="0.001" distanceUnits="kilometers" />
|
||||||
|
|
||||||
<!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has
|
<!-- Spatial rectangle (bounding box) field. It supports most spatial predicates, and has
|
||||||
special relevancy modes: score=overlapRatio|area|area2D (local-param to the query). DocValues is recommended for
|
special relevancy modes: score=overlapRatio|area|area2D (local-param to the query). DocValues is recommended for
|
||||||
|
|
Loading…
Reference in New Issue