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:
Grant Ingersoll 2009-11-17 15:36:25 +00:00
parent 29109d47ff
commit 2650c93595
6 changed files with 342 additions and 5 deletions

View File

@ -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
----------------------

View File

@ -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.

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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 {