mirror of https://github.com/apache/lucene.git
SOLR-1302: more distance functions and helpers
git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@881340 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
29109d47ff
commit
2650c93595
|
@ -35,7 +35,7 @@ New Features
|
|||
----------------------
|
||||
|
||||
1. SOLR-1302: Added several new distance based functions, including Great Circle (haversine), Manhattan and Euclidean.
|
||||
Also added deg() and rad() convenience functions. (gsingers)
|
||||
Also added geohash(), deg() and rad() convenience functions. See http://wiki.apache.org/solr/FunctionQuery. (gsingers)
|
||||
|
||||
Optimizations
|
||||
----------------------
|
||||
|
|
|
@ -180,6 +180,29 @@ public abstract class SolrParams implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
/** Returns the Float value of the param, or null if not set */
|
||||
public Double getDouble(String param) {
|
||||
String val = get(param);
|
||||
try {
|
||||
return val==null ? null : Double.valueOf(val);
|
||||
}
|
||||
catch( Exception ex ) {
|
||||
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, ex.getMessage(), ex );
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the float value of the param, or def if not set */
|
||||
public double getDouble(String param, double def) {
|
||||
String val = get(param);
|
||||
try {
|
||||
return val==null ? def : Double.parseDouble(val);
|
||||
}
|
||||
catch( Exception ex ) {
|
||||
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, ex.getMessage(), ex );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Returns the float value of the field param. */
|
||||
public Float getFieldFloat(String field, String param) {
|
||||
String val = getFieldParam(field, param);
|
||||
|
@ -202,6 +225,30 @@ public abstract class SolrParams implements Serializable {
|
|||
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, ex.getMessage(), ex );
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the float value of the field param. */
|
||||
public Double getFieldDouble(String field, String param) {
|
||||
String val = getFieldParam(field, param);
|
||||
try {
|
||||
return val==null ? null : Double.valueOf(val);
|
||||
}
|
||||
catch( Exception ex ) {
|
||||
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, ex.getMessage(), ex );
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the float value of the field param,
|
||||
or the value for param, or def if neither is set. */
|
||||
public double getFieldDouble(String field, String param, double def) {
|
||||
String val = getFieldParam(field, param);
|
||||
try {
|
||||
return val==null ? def : Double.parseDouble(val);
|
||||
}
|
||||
catch( Exception ex ) {
|
||||
throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, ex.getMessage(), ex );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** how to transform a String into a boolean... more flexible than
|
||||
* Boolean.parseBoolean() to enable easier integration with html forms.
|
||||
|
|
|
@ -45,11 +45,14 @@ import org.apache.solr.search.function.SimpleFloatFunction;
|
|||
import org.apache.solr.search.function.SumFloatFunction;
|
||||
import org.apache.solr.search.function.TopValueSource;
|
||||
import org.apache.solr.search.function.ValueSource;
|
||||
import org.apache.solr.search.function.LiteralValueSource;
|
||||
|
||||
import org.apache.solr.search.function.distance.HaversineFunction;
|
||||
|
||||
import org.apache.solr.search.function.distance.SquaredEuclideanFunction;
|
||||
import org.apache.solr.search.function.distance.VectorDistanceFunction;
|
||||
import org.apache.solr.search.function.distance.GeohashHaversineFunction;
|
||||
import org.apache.solr.search.function.distance.GeohashFunction;
|
||||
import org.apache.solr.util.plugin.NamedListInitializedPlugin;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -91,6 +94,15 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
|
|||
public void init(NamedList args) {
|
||||
}
|
||||
|
||||
});
|
||||
standardValueSourceParsers.put("literal", new ValueSourceParser() {
|
||||
public ValueSource parse(FunctionQParser fp) throws ParseException {
|
||||
return new LiteralValueSource(fp.getString());
|
||||
}
|
||||
|
||||
public void init(NamedList args) {
|
||||
}
|
||||
|
||||
});
|
||||
standardValueSourceParsers.put("rord", new ValueSourceParser() {
|
||||
public ValueSource parse(FunctionQParser fp) throws ParseException {
|
||||
|
@ -333,6 +345,36 @@ public abstract class ValueSourceParser implements NamedListInitializedPlugin {
|
|||
|
||||
});
|
||||
|
||||
standardValueSourceParsers.put("ghhsin", new ValueSourceParser() {
|
||||
public ValueSource parse(FunctionQParser fp) throws ParseException {
|
||||
|
||||
ValueSource gh1 = fp.parseValueSource();
|
||||
ValueSource gh2 = fp.parseValueSource();
|
||||
double radius = fp.parseDouble();
|
||||
|
||||
return new GeohashHaversineFunction(gh1, gh2, radius);
|
||||
}
|
||||
|
||||
public void init(NamedList args) {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
standardValueSourceParsers.put("geohash", new ValueSourceParser() {
|
||||
public ValueSource parse(FunctionQParser fp) throws ParseException {
|
||||
|
||||
ValueSource lat = fp.parseValueSource();
|
||||
ValueSource lon = fp.parseValueSource();
|
||||
|
||||
return new GeohashFunction(lat, lon);
|
||||
}
|
||||
|
||||
public void init(NamedList args) {
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
standardValueSourceParsers.put("rad", new ValueSourceParser() {
|
||||
public ValueSource parse(FunctionQParser fp) throws ParseException {
|
||||
return new RadianFunction(fp.parseValueSource());
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package org.apache.solr.search.function.distance;
|
||||
/**
|
||||
* 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.search.function.ValueSource;
|
||||
import org.apache.solr.search.function.DocValues;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.spatial.geohash.GeoHashUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Takes in a latitude and longitude ValueSource and produces a GeoHash.
|
||||
* <p/>
|
||||
* Ex: geohash(lat, lon)
|
||||
*
|
||||
* <p/>
|
||||
* Note, there is no reciprocal function for this.
|
||||
**/
|
||||
public class GeohashFunction extends ValueSource {
|
||||
protected ValueSource lat, lon;
|
||||
|
||||
public GeohashFunction(ValueSource lat, ValueSource lon) {
|
||||
this.lat = lat;
|
||||
this.lon = lon;
|
||||
}
|
||||
|
||||
protected String name() {
|
||||
return "geohash";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocValues getValues(Map context, IndexReader reader) throws IOException {
|
||||
final DocValues latDV = lat.getValues(context, reader);
|
||||
final DocValues lonDV = lon.getValues(context, reader);
|
||||
|
||||
|
||||
return new DocValues() {
|
||||
|
||||
@Override
|
||||
public String strVal(int doc) {
|
||||
return GeoHashUtils.encode(latDV.doubleVal(doc), lonDV.doubleVal(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int doc) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name()).append('(');
|
||||
sb.append(latDV.toString(doc)).append(',').append(lonDV.toString(doc));
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof GeohashFunction)) return false;
|
||||
|
||||
GeohashFunction that = (GeohashFunction) o;
|
||||
|
||||
if (!lat.equals(that.lat)) return false;
|
||||
if (!lon.equals(that.lon)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = lat.hashCode();
|
||||
result = 31 * result + lon.hashCode();
|
||||
result = 31 * name().hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name()).append('(');
|
||||
sb.append(lat).append(',').append(lon);
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package org.apache.solr.search.function.distance;
|
||||
/**
|
||||
* 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.search.function.ValueSource;
|
||||
import org.apache.solr.search.function.DocValues;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.search.Searcher;
|
||||
import org.apache.lucene.spatial.geohash.GeoHashUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the Haversine distance between two geo hash codes.
|
||||
*
|
||||
* <p/>
|
||||
* Ex: ghhsin(ValueSource, ValueSource, radius)
|
||||
* <p/>
|
||||
*
|
||||
* @see org.apache.solr.search.function.distance.HaversineFunction for more details on the implementation
|
||||
*
|
||||
**/
|
||||
public class GeohashHaversineFunction extends ValueSource {
|
||||
|
||||
private ValueSource geoHash1, geoHash2;
|
||||
private double radius;
|
||||
|
||||
public GeohashHaversineFunction(ValueSource geoHash1, ValueSource geoHash2, double radius) {
|
||||
this.geoHash1 = geoHash1;
|
||||
this.geoHash2 = geoHash2;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
protected String name() {
|
||||
return "ghhsin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocValues getValues(Map context, IndexReader reader) throws IOException {
|
||||
final DocValues gh1DV = geoHash1.getValues(context, reader);
|
||||
final DocValues gh2DV = geoHash2.getValues(context, reader);
|
||||
|
||||
return new DocValues() {
|
||||
public float floatVal(int doc) {
|
||||
return (float) doubleVal(doc);
|
||||
}
|
||||
|
||||
public int intVal(int doc) {
|
||||
return (int) doubleVal(doc);
|
||||
}
|
||||
|
||||
public long longVal(int doc) {
|
||||
return (long) doubleVal(doc);
|
||||
}
|
||||
|
||||
public double doubleVal(int doc) {
|
||||
return (double) distance(doc, gh1DV, gh2DV);
|
||||
}
|
||||
|
||||
public String strVal(int doc) {
|
||||
return Double.toString(doubleVal(doc));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(int doc) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name()).append('(');
|
||||
sb.append(gh1DV.toString(doc)).append(',').append(gh2DV.toString(doc));
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected double distance(int doc, DocValues gh1DV, DocValues gh2DV) {
|
||||
double result = 0;
|
||||
String h1 = gh1DV.strVal(doc);
|
||||
String h2 = gh2DV.strVal(doc);
|
||||
if (h1.equals(h2) == false){
|
||||
double[] h1Pair = GeoHashUtils.decode(h1);
|
||||
double[] h2Pair = GeoHashUtils.decode(h2);
|
||||
result = DistanceUtils.haversine(Math.toRadians(h1Pair[0]), Math.toRadians(h1Pair[1]),
|
||||
Math.toRadians(h2Pair[0]), Math.toRadians(h2Pair[1]), radius);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWeight(Map context, Searcher searcher) throws IOException {
|
||||
geoHash1.createWeight(context, searcher);
|
||||
geoHash2.createWeight(context, searcher);
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this.getClass() != o.getClass()) return false;
|
||||
GeohashHaversineFunction other = (GeohashHaversineFunction) o;
|
||||
return this.name().equals(other.name())
|
||||
&& geoHash1.equals(other.geoHash1) &&
|
||||
geoHash2.equals(other.geoHash2) &&
|
||||
radius == other.radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result;
|
||||
long temp;
|
||||
result = geoHash1.hashCode();
|
||||
result = 31 * result + geoHash2.hashCode();
|
||||
result = 31 * result + name().hashCode();
|
||||
temp = radius != +0.0d ? Double.doubleToLongBits(radius) : 0L;
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name()).append('(');
|
||||
sb.append(geoHash1).append(',').append(geoHash2);
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.apache.solr.search.function.distance;
|
|||
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.util.AbstractSolrTestCase;
|
||||
import org.apache.lucene.spatial.geohash.GeoHashUtils;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -39,16 +40,24 @@ public class DistanceFunctionTest extends AbstractSolrTestCase {
|
|||
|
||||
|
||||
public void testHaversine() throws Exception {
|
||||
assertU(adoc("id", "1", "x_td", "0", "y_td", "0"));
|
||||
assertU(adoc("id", "2", "x_td", "0", "y_td", String.valueOf(Math.PI / 2)));
|
||||
assertU(adoc("id", "3", "x_td", String.valueOf(Math.PI / 2), "y_td", String.valueOf(Math.PI / 2)));
|
||||
assertU(adoc("id", "4", "x_td", String.valueOf(Math.PI / 4), "y_td", String.valueOf(Math.PI / 4)));
|
||||
assertU(adoc("id", "1", "x_td", "0", "y_td", "0", "gh_s", GeoHashUtils.encode(32.7693246, -79.9289094)));
|
||||
assertU(adoc("id", "2", "x_td", "0", "y_td", String.valueOf(Math.PI / 2), "gh_s", GeoHashUtils.encode(32.7693246, -78.9289094)));
|
||||
assertU(adoc("id", "3", "x_td", String.valueOf(Math.PI / 2), "y_td", String.valueOf(Math.PI / 2), "gh_s", GeoHashUtils.encode(32.7693246, -80.9289094)));
|
||||
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(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(x_td, y_td, 0, 0, 1)", "fq", "id:1"), "//float[@name='score']='0.0'");
|
||||
assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:2"), "//float[@name='score']='" + (float) (Math.PI / 2) + "'");
|
||||
assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:3"), "//float[@name='score']='" + (float) (Math.PI / 2) + "'");
|
||||
assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:4"), "//float[@name='score']='1.0471976'");
|
||||
|
||||
//Geo Hash Haversine
|
||||
//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(gh_s, \"" + GeoHashUtils.encode(32, -79) +
|
||||
"\"," + Constants.EARTH_RADIUS_KM +
|
||||
")", "fq", "id:1"), "//float[@name='score']='122.30894'");
|
||||
assertQ(req("fl", "*,score", "q", "{!func}ghhsin(gh_s, geohash(32, -79)," + Constants.EARTH_RADIUS_KM +
|
||||
")", "fq", "id:1"), "//float[@name='score']='122.30894'");
|
||||
}
|
||||
|
||||
public void testVector() throws Exception {
|
||||
|
|
Loading…
Reference in New Issue