Author: gsingers
Date: Tue Nov 17 15:36:25 2009
New Revision: 881340
URL: http://svn.apache.org/viewvc?rev=881340&view=rev
Log:
SOLR-1302: more distance functions and helpers
Added:
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashFunction.java
(with props)
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java
(with props)
Modified:
lucene/solr/trunk/CHANGES.txt
lucene/solr/trunk/src/common/org/apache/solr/common/params/SolrParams.java
lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java
lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
Modified: lucene/solr/trunk/CHANGES.txt
URL:
http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=881340&r1=881339&r2=881340&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Tue Nov 17 15:36:25 2009
@@ -35,7 +35,7 @@
----------------------
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
----------------------
Modified:
lucene/solr/trunk/src/common/org/apache/solr/common/params/SolrParams.java
URL:
http://svn.apache.org/viewvc/lucene/solr/trunk/src/common/org/apache/solr/common/params/SolrParams.java?rev=881340&r1=881339&r2=881340&view=diff
==============================================================================
--- lucene/solr/trunk/src/common/org/apache/solr/common/params/SolrParams.java
(original)
+++ lucene/solr/trunk/src/common/org/apache/solr/common/params/SolrParams.java
Tue Nov 17 15:36:25 2009
@@ -180,6 +180,29 @@
}
}
+ /** 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 @@
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.
Modified:
lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java
URL:
http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java?rev=881340&r1=881339&r2=881340&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java
(original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java
Tue Nov 17 15:36:25 2009
@@ -45,11 +45,14 @@
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;
@@ -92,6 +95,15 @@
}
});
+ 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 {
String field = fp.parseId();
@@ -333,6 +345,36 @@
});
+ 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());
Added:
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashFunction.java
URL:
http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashFunction.java?rev=881340&view=auto
==============================================================================
---
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashFunction.java
(added)
+++
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashFunction.java
Tue Nov 17 15:36:25 2009
@@ -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();
+ }
+}
Propchange:
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashFunction.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java
URL:
http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java?rev=881340&view=auto
==============================================================================
---
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java
(added)
+++
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java
Tue Nov 17 15:36:25 2009
@@ -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();
+ }
+}
Propchange:
lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/GeohashHaversineFunction.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified:
lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
URL:
http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java?rev=881340&r1=881339&r2=881340&view=diff
==============================================================================
---
lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
(original)
+++
lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
Tue Nov 17 15:36:25 2009
@@ -18,6 +18,7 @@
import org.apache.solr.common.SolrException;
import org.apache.solr.util.AbstractSolrTestCase;
+import org.apache.lucene.spatial.geohash.GeoHashUtils;
/**
@@ -39,16 +40,24 @@
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"), "//flo...@name='score']='0.0'");
assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)",
"fq", "id:2"), "//flo...@name='score']='" + (float) (Math.PI / 2) + "'");
assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)",
"fq", "id:3"), "//flo...@name='score']='" + (float) (Math.PI / 2) + "'");
assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)",
"fq", "id:4"), "//flo...@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"), "//flo...@name='score']='122.30894'");
+ assertQ(req("fl", "*,score", "q", "{!func}ghhsin(gh_s, geohash(32, -79),"
+ Constants.EARTH_RADIUS_KM +
+ ")", "fq", "id:1"), "//flo...@name='score']='122.30894'");
}
public void testVector() throws Exception {