LUCENE-1512 -- adding GeoHash implementaion

git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@732400 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Ryan McKinley 2009-01-07 17:10:58 +00:00
parent d666ae5eda
commit 977c9acc71
7 changed files with 805 additions and 229 deletions

View File

@ -0,0 +1,246 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.geohash;
import java.io.IOException;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.spatial.tier.DistanceFilter;
import org.apache.lucene.spatial.tier.DistanceUtils;
import org.apache.lucene.spatial.tier.DistanceHandler.Precision;
import org.apache.lucene.spatial.NumberUtils;
public class GeoHashDistanceFilter extends DistanceFilter {
/**
*
*/
private static final long serialVersionUID = 1L;
private double distance;
private double lat;
private double lng;
private String geoHashField;
private Logger log = Logger.getLogger(getClass().getName());
private Map<Integer,Double> distances = null;
private Precision precise = null;
/**
* Provide a distance filter based from a center point with a radius
* in miles
* @param lat
* @param lng
* @param miles
* @param latField
* @param lngField
*/
public GeoHashDistanceFilter(double lat, double lng, double miles, String geoHashField){
distance = miles;
this.lat = lat;
this.lng = lng;
this.geoHashField = geoHashField;
}
public Map<Integer,Double> getDistances(){
return distances;
}
public Double getDistance(int docid){
return distances.get(docid);
}
@Override
public BitSet bits(IndexReader reader) throws IOException {
/* Create a BitSet to store the result */
int maxdocs = reader.numDocs();
BitSet bits = new BitSet(maxdocs);
setPrecision(maxdocs);
// create an intermediate cache to avoid recomputing
// distances for the same point
// TODO: Why is this a WeakHashMap?
WeakHashMap<String,Double> cdistance = new WeakHashMap<String,Double>(maxdocs);
String[] geoHashCache = FieldCache.DEFAULT.getStrings(reader, geoHashField);
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>(maxdocs);
for (int i = 0 ; i < maxdocs; i++) {
String geoHash = geoHashCache[i];
double[] coords = GeoHashUtils.decode(geoHash);
double x = coords[0];
double y = coords[1];
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
Double cachedDistance = cdistance.get(geoHash);
double d;
if(cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
cdistance.put(geoHash, d);
}
distances.put(i, d);
if (d < distance){
bits.set(i);
}
}
return bits;
}
@Override
public BitSet bits(IndexReader reader, BitSet bits) throws Exception {
/* Create a BitSet to store the result */
int size = bits.cardinality();
BitSet result = new BitSet(size);
/* create an intermediate cache to avoid recomputing
distances for the same point */
HashMap<String,Double> cdistance = new HashMap<String,Double>(size);
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>(size);
long start = System.currentTimeMillis();
String[] geoHashCache = FieldCache.DEFAULT.getStrings(reader, geoHashField);
/* loop over all set bits (hits from the boundary box filters) */
int i = bits.nextSetBit(0);
while (i >= 0){
// if we have a completed
// filter chain, lat / lngs can be retrived from
// memory rather than document base.
String geoHash = geoHashCache[i];
double[] coords = GeoHashUtils.decode(geoHash);
double x = coords[0];
double y = coords[1];
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
Double cachedDistance = cdistance.get(geoHash);
double d;
if(cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
//d = DistanceUtils.getLLMDistance(lat, lng, x, y);
cdistance.put(geoHash, d);
}
distances.put(i, d);
if (d < distance){
result.set(i);
}
i = bits.nextSetBit(i+1);
}
long end = System.currentTimeMillis();
log.fine("Time taken : "+ (end - start) +
", results : "+ distances.size() +
", cached : "+ cdistance.size() +
", incoming size: "+ size);
cdistance = null;
return result;
}
/** Returns true if <code>o</code> is equal to this. */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof GeoHashDistanceFilter)) return false;
GeoHashDistanceFilter other = (GeoHashDistanceFilter) o;
if (this.distance != other.distance ||
this.lat != other.lat ||
this.lng != other.lng ||
!this.geoHashField.equals(other.geoHashField) ) {
return false;
}
return true;
}
/** Returns a hash code value for this object.*/
@Override
public int hashCode() {
int h = new Double(distance).hashCode();
h ^= new Double(lat).hashCode();
h ^= new Double(lng).hashCode();
h ^= geoHashField.hashCode();
return h;
}
private void setPrecision(int maxDocs) {
precise = Precision.EXACT;
if (maxDocs > 1000 && distance > 10) {
precise = Precision.TWENTYFEET;
}
if (maxDocs > 10000 && distance > 10){
precise = Precision.TWOHUNDREDFEET;
}
}
public void setDistances(Map<Integer, Double> distances) {
this.distances = distances;
}
}

View File

@ -0,0 +1,170 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.geohash;
import java.util.HashMap;
import java.util.Map;
/**
* Based on http://en.wikipedia.org/wiki/Geohash
*/
public class GeoHashUtils {
// geohash's char map
// no a's i's l's o's
// old MacDonal wouldn't be happy
private static char[] _base32 = {'0','1','2','3','4','5','6','7','8','9',
'b','c','d','e','f','g','h','j','k','m',
'n','p','q','r','s','t','u','v','w','x',
'y','z'} ;
private final static Map<Character, Integer> _decodemap = new HashMap<Character, Integer>();
static {
int sz = _base32.length;
for (int i = 0; i < sz; i++ ){
_decodemap.put(_base32[i], i);
}
}
private static int precision = 12;
private static int[] bits = {16, 8, 4, 2, 1};
public static void main(String[] args) {
GeoHashUtils ghf = new GeoHashUtils();
String gc1 = ghf.encode(30, -90.0);
String gc2 = ghf.encode(51.4797, -0.0124);
System.out.println(gc1);
System.out.println(gc2);
double [] gd1 = ghf.decode(gc1);
double [] gd2 = ghf.decode(gc2);
System.out.println(gd1[0]+ ", "+ gd1[1]);
System.out.println(gd2[0]+ ", "+ gd2[1]);
}
public static String encode(double latitude, double longitude){
double[] lat_interval = {-90.0 , 90.0};
double[] lon_interval = {-180.0, 180.0};
StringBuilder geohash = new StringBuilder();
boolean is_even = true;
int bit = 0, ch = 0;
while(geohash.length() < precision){
double mid = 0.0;
if(is_even){
mid = (lon_interval[0] + lon_interval[1]) / 2;
if (longitude > mid){
ch |= bits[bit];
lon_interval[0] = mid;
} else {
lon_interval[1] = mid;
}
} else {
mid = (lat_interval[0] + lat_interval[1]) / 2;
if(latitude > mid){
ch |= bits[bit];
lat_interval[0] = mid;
} else {
lat_interval[1] = mid;
}
}
is_even = is_even ? false : true;
if (bit < 4){
bit ++;
} else {
geohash.append(_base32[ch]);
bit =0;
ch = 0;
}
}
return geohash.toString();
}
public static double[] decode(String geohash) {
double[] ge = decode_exactly(geohash);
double lat, lon, lat_err, lon_err;
lat = ge[0];
lon = ge[1];
lat_err = ge[2];
lon_err = ge[3];
double lat_precision = Math.max(1, Math.round(- Math.log10(lat_err))) - 1;
double lon_precision = Math.max(1, Math.round(- Math.log10(lon_err))) - 1;
lat = getPrecision(lat, lat_precision);
lon = getPrecision(lon, lon_precision);
return new double[] {lat, lon};
}
public static double[] decode_exactly (String geohash){
double[] lat_interval = {-90.0 , 90.0};
double[] lon_interval = {-180.0, 180.0};
double lat_err = 90.0;
double lon_err = 180.0;
boolean is_even = true;
int sz = geohash.length();
int bsz = bits.length;
double latitude, longitude;
for (int i = 0; i < sz; i++){
int cd = _decodemap.get(geohash.charAt(i));
for (int z = 0; z< bsz; z++){
int mask = bits[z];
if (is_even){
lon_err /= 2;
if ((cd & mask) != 0){
lon_interval[0] = (lon_interval[0]+lon_interval[1])/2;
} else {
lon_interval[1] = (lon_interval[0]+lon_interval[1])/2;
}
} else {
lat_err /=2;
if ( (cd & mask) != 0){
lat_interval[0] = (lat_interval[0]+lat_interval[1])/2;
} else {
lat_interval[1] = (lat_interval[0]+lat_interval[1])/2;
}
}
is_even = is_even ? false : true;
}
}
latitude = (lat_interval[0] + lat_interval[1]) / 2;
longitude = (lon_interval[0] + lon_interval[1]) / 2;
return new double []{latitude, longitude, lat_err, lon_err};
}
static double getPrecision(double x, double precision) {
double base = Math.pow(10,- precision);
double diff = x % base;
return x - diff;
}
}

View File

@ -1,5 +1,4 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
/** 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
@ -19,230 +18,36 @@ package org.apache.lucene.spatial.tier;
import java.io.IOException;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.spatial.ISerialChainFilter;
import org.apache.lucene.spatial.tier.DistanceHandler.Precision;
import org.apache.lucene.spatial.NumberUtils;
public abstract class DistanceFilter extends ISerialChainFilter {
public class DistanceFilter extends ISerialChainFilter {
/**
*
*/
private static final long serialVersionUID = 1L;
private double distance;
private double lat;
private double lng;
private String latField;
private String lngField;
private Logger log = Logger.getLogger(getClass().getName());
private Map<Integer,Double> distances = null;
private Precision precise = null;
/**
* Provide a distance filter based from a center point with a radius
* in miles
* @param lat
* @param lng
* @param miles
* @param latField
* @param lngField
*/
public DistanceFilter(double lat, double lng, double miles, String latField, String lngField){
distance = miles;
this.lat = lat;
this.lng = lng;
this.latField = latField;
this.lngField = lngField;
public DistanceFilter() {
super();
}
public abstract Map<Integer,Double> getDistances();
public Map<Integer,Double> getDistances(){
return distances;
}
public Double getDistance(int docid){
return distances.get(docid);
}
public abstract Double getDistance(int docid);
@Override
public BitSet bits(IndexReader reader) throws IOException {
/* Create a BitSet to store the result */
int maxdocs = reader.numDocs();
BitSet bits = new BitSet(maxdocs);
setPrecision(maxdocs);
// create an intermediate cache to avoid recomputing
// distances for the same point
// TODO: Why is this a WeakHashMap?
WeakHashMap<String,Double> cdistance = new WeakHashMap<String,Double>(maxdocs);
String[] latIndex = FieldCache.DEFAULT.getStrings(reader, latField);
String[] lngIndex = FieldCache.DEFAULT.getStrings(reader, lngField);
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>(maxdocs);
for (int i = 0 ; i < maxdocs; i++) {
String sx = latIndex[i];
String sy = lngIndex[i];
double x = NumberUtils.SortableStr2double(sx);
double y = NumberUtils.SortableStr2double(sy);
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
String ck = new Double(x).toString()+","+new Double(y).toString();
Double cachedDistance = cdistance.get(ck);
double d;
if(cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
cdistance.put(ck, d);
}
distances.put(i, d);
if (d < distance){
bits.set(i);
}
}
return bits;
}
public abstract BitSet bits(IndexReader reader) throws IOException;
@Override
public BitSet bits(IndexReader reader, BitSet bits) throws Exception {
/* Create a BitSet to store the result */
int size = bits.cardinality();
BitSet result = new BitSet(size);
/* create an intermediate cache to avoid recomputing
distances for the same point */
HashMap<String,Double> cdistance = new HashMap<String,Double>(size);
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>(size);
long start = System.currentTimeMillis();
String[] latIndex = FieldCache.DEFAULT.getStrings(reader, latField);
String[] lngIndex = FieldCache.DEFAULT.getStrings(reader, lngField);
/* loop over all set bits (hits from the boundary box filters) */
int i = bits.nextSetBit(0);
while (i >= 0){
double x,y;
// if we have a completed
// filter chain, lat / lngs can be retrived from
// memory rather than document base.
String sx = latIndex[i];
String sy = lngIndex[i];
x = NumberUtils.SortableStr2double(sx);
y = NumberUtils.SortableStr2double(sy);
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
String ck = new Double(x).toString()+","+new Double(y).toString();
Double cachedDistance = cdistance.get(ck);
double d;
if(cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
//d = DistanceUtils.getLLMDistance(lat, lng, x, y);
cdistance.put(ck, d);
}
distances.put(i, d);
if (d < distance){
result.set(i);
}
i = bits.nextSetBit(i+1);
}
long end = System.currentTimeMillis();
log.fine("Time taken : "+ (end - start) +
", results : "+ distances.size() +
", cached : "+ cdistance.size() +
", incoming size: "+ size);
cdistance = null;
return result;
}
public abstract BitSet bits(IndexReader reader, BitSet bits) throws Exception;
/** Returns true if <code>o</code> is equal to this. */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DistanceFilter)) return false;
DistanceFilter other = (DistanceFilter) o;
if (this.distance != other.distance ||
this.lat != other.lat ||
this.lng != other.lng ||
!this.latField.equals(other.latField) ||
!this.lngField.equals(other.lngField)) {
return false;
}
return true;
}
public abstract boolean equals(Object o);
/** Returns a hash code value for this object.*/
@Override
public int hashCode() {
int h = new Double(distance).hashCode();
h ^= new Double(lat).hashCode();
h ^= new Double(lng).hashCode();
h ^= latField.hashCode();
h ^= lngField.hashCode();
return h;
}
public abstract int hashCode();
private void setPrecision(int maxDocs) {
precise = Precision.EXACT;
public abstract void setDistances(Map<Integer, Double> distances);
if (maxDocs > 1000 && distance > 10) {
precise = Precision.TWENTYFEET;
}
if (maxDocs > 10000 && distance > 10){
precise = Precision.TWOHUNDREDFEET;
}
}
public void setDistances(Map<Integer, Double> distances) {
this.distances = distances;
}
}

View File

@ -19,9 +19,11 @@ package org.apache.lucene.spatial.tier;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.spatial.ISerialChainFilter;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.spatial.SerialChainFilter;
import org.apache.lucene.spatial.geohash.GeoHashDistanceFilter;
public class DistanceQueryBuilder {
@ -48,7 +50,7 @@ public class DistanceQueryBuilder {
* @param miles
*/
public DistanceQueryBuilder (double lat, double lng, double miles,
String latField, String lngField, String tierFieldPrefix, boolean needPrecise){
String latField, String lngField, String tierFieldPrefix,boolean needPrecise){
this.lat = lat;
this.lng = lng;
@ -60,10 +62,38 @@ public class DistanceQueryBuilder {
/* create precise distance filter */
if( needPrecise)
distanceFilter = new DistanceFilter(lat, lng, miles, latField, lngField);
distanceFilter = new LatLongDistanceFilter(lat, lng, miles, latField, lngField);
}
/**
* Create a distance query using
* a boundary box wrapper around a more precise
* DistanceFilter.
*
* @see SerialChainFilter
* @param lat
* @param lng
* @param miles
*/
public DistanceQueryBuilder (double lat, double lng, double miles,
String geoHashFieldPrefix, String tierFieldPrefix,boolean needPrecise){
this.lat = lat;
this.lng = lng;
this.miles = miles;
CartesianPolyFilterBuilder cpf = new CartesianPolyFilterBuilder(tierFieldPrefix);
cartesianFilter = cpf.getBoundingArea(lat, lng, (int)miles);
/* create precise distance filter */
if( needPrecise)
distanceFilter = new GeoHashDistanceFilter(lat, lng, miles, geoHashFieldPrefix);
}
/**
* Create a distance query using
* a boundary box wrapper around a more precise

View File

@ -0,0 +1,249 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.spatial.tier;
import java.io.IOException;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.spatial.NumberUtils;
import org.apache.lucene.spatial.tier.DistanceHandler.Precision;
public class LatLongDistanceFilter extends DistanceFilter {
/**
*
*/
private static final long serialVersionUID = 1L;
double distance;
double lat;
double lng;
String latField;
String lngField;
Logger log = Logger.getLogger(getClass().getName());
Map<Integer,Double> distances = null;
private Precision precise = null;
/**
* Provide a distance filter based from a center point with a radius
* in miles
* @param lat
* @param lng
* @param miles
* @param latField
* @param lngField
*/
public LatLongDistanceFilter(double lat, double lng, double miles, String latField, String lngField){
distance = miles;
this.lat = lat;
this.lng = lng;
this.latField = latField;
this.lngField = lngField;
}
public Map<Integer,Double> getDistances(){
return distances;
}
public Double getDistance(int docid){
return distances.get(docid);
}
@Override
public BitSet bits(IndexReader reader) throws IOException {
/* Create a BitSet to store the result */
int maxdocs = reader.numDocs();
BitSet bits = new BitSet(maxdocs);
setPrecision(maxdocs);
// create an intermediate cache to avoid recomputing
// distances for the same point
// TODO: Why is this a WeakHashMap?
WeakHashMap<String,Double> cdistance = new WeakHashMap<String,Double>(maxdocs);
String[] latIndex = FieldCache.DEFAULT.getStrings(reader, latField);
String[] lngIndex = FieldCache.DEFAULT.getStrings(reader, lngField);
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>(maxdocs);
for (int i = 0 ; i < maxdocs; i++) {
String sx = latIndex[i];
String sy = lngIndex[i];
double x = NumberUtils.SortableStr2double(sx);
double y = NumberUtils.SortableStr2double(sy);
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
String ck = new Double(x).toString()+","+new Double(y).toString();
Double cachedDistance = cdistance.get(ck);
double d;
if(cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
cdistance.put(ck, d);
}
distances.put(i, d);
if (d < distance){
bits.set(i);
}
}
return bits;
}
@Override
public BitSet bits(IndexReader reader, BitSet bits) throws Exception {
/* Create a BitSet to store the result */
int size = bits.cardinality();
BitSet result = new BitSet(size);
/* create an intermediate cache to avoid recomputing
distances for the same point */
HashMap<String,Double> cdistance = new HashMap<String,Double>(size);
/* store calculated distances for reuse by other components */
distances = new HashMap<Integer,Double>(size);
long start = System.currentTimeMillis();
String[] latIndex = FieldCache.DEFAULT.getStrings(reader, latField);
String[] lngIndex = FieldCache.DEFAULT.getStrings(reader, lngField);
/* loop over all set bits (hits from the boundary box filters) */
int i = bits.nextSetBit(0);
while (i >= 0){
double x,y;
// if we have a completed
// filter chain, lat / lngs can be retrived from
// memory rather than document base.
String sx = latIndex[i];
String sy = lngIndex[i];
x = NumberUtils.SortableStr2double(sx);
y = NumberUtils.SortableStr2double(sy);
// round off lat / longs if necessary
// x = DistanceHandler.getPrecision(x, precise);
// y = DistanceHandler.getPrecision(y, precise);
String ck = new Double(x).toString()+","+new Double(y).toString();
Double cachedDistance = cdistance.get(ck);
double d;
if(cachedDistance != null){
d = cachedDistance.doubleValue();
} else {
d = DistanceUtils.getInstance().getDistanceMi(lat, lng, x, y);
//d = DistanceUtils.getLLMDistance(lat, lng, x, y);
cdistance.put(ck, d);
}
distances.put(i, d);
if (d < distance){
result.set(i);
}
i = bits.nextSetBit(i+1);
}
long end = System.currentTimeMillis();
log.fine("Time taken : "+ (end - start) +
", results : "+ distances.size() +
", cached : "+ cdistance.size() +
", incoming size: "+ size);
cdistance = null;
return result;
}
/** Returns true if <code>o</code> is equal to this. */
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof LatLongDistanceFilter)) return false;
LatLongDistanceFilter other = (LatLongDistanceFilter) o;
if (this.distance != other.distance ||
this.lat != other.lat ||
this.lng != other.lng ||
!this.latField.equals(other.latField) ||
!this.lngField.equals(other.lngField)) {
return false;
}
return true;
}
/** Returns a hash code value for this object.*/
@Override
public int hashCode() {
int h = new Double(distance).hashCode();
h ^= new Double(lat).hashCode();
h ^= new Double(lng).hashCode();
h ^= latField.hashCode();
h ^= lngField.hashCode();
return h;
}
public void setDistances(Map<Integer, Double> distances) {
this.distances = distances;
}
void setPrecision(int maxDocs) {
precise = Precision.EXACT;
if (maxDocs > 1000 && distance > 10) {
precise = Precision.TWENTYFEET;
}
if (maxDocs > 10000 && distance > 10){
precise = Precision.TWOHUNDREDFEET;
}
}
}

View File

@ -35,6 +35,7 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.spatial.geohash.GeoHashUtils;
import org.apache.lucene.spatial.tier.DistanceQueryBuilder;
import org.apache.lucene.spatial.tier.DistanceSortSource;
import org.apache.lucene.spatial.tier.DistanceUtils;
@ -67,12 +68,12 @@ public class TestCartesian extends TestCase{
private String latField = "lat";
private String lngField = "lng";
private List<CartesianTierPlotter> ctps = new LinkedList<CartesianTierPlotter>();
private String geoHashPrefix = "_geoHash_";
private IProjector project = new SinusoidalProjector();
@Override
protected void setUp() throws IOException {
directory = new RAMDirectory();
@ -113,6 +114,10 @@ public class TestCartesian extends TestCase{
NumberUtils.double2sortableStr(ctp.getTierBoxId(lat,lng)),
Field.Store.YES,
Field.Index.NO_NORMS));
doc.add(new Field(geoHashPrefix, GeoHashUtils.encode(lat,lng),
Field.Store.YES,
Field.Index.NO_NORMS));
}
writer.addDocument(doc);
@ -223,4 +228,86 @@ public class TestCartesian extends TestCase{
}
}
public void testGeoHashRange() throws IOException, InvalidGeoException {
searcher = new IndexSearcher(directory);
final double miles = 6.0;
// create a distance query
final DistanceQueryBuilder dq = new DistanceQueryBuilder(lat, lng, miles,
geoHashPrefix, CartesianTierPlotter.DEFALT_FIELD_PREFIX, true);
System.out.println(dq);
//create a term query to search against all documents
Query tq = new TermQuery(new Term("metafile", "doc"));
FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
CustomScoreQuery customScore = new CustomScoreQuery(tq,fsQuery){
@Override
public float customScore(int doc, float subQueryScore, float valSrcScore){
//System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
return 0;
double distance = dq.distanceFilter.getDistance(doc);
// boost score shouldn't exceed 1
if (distance < 1.0d)
distance = 1.0d;
//boost by distance is invertly proportional to
// to distance from center point to location
float score = new Float((miles - distance) / miles ).floatValue();
return score * subQueryScore;
}
};
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
//
DistanceSortSource dsort = new DistanceSortSource(dq.distanceFilter);
Sort sort = new Sort(new SortField("foo", dsort));
// Perform the search, using the term query, the serial chain filter, and the
// distance sort
Hits hits = searcher.search(customScore, dq.getFilter()); //,sort);
int results = hits.length();
// Get a list of distances
Map<Integer,Double> distances = dq.distanceFilter.getDistances();
// distances calculated from filter first pass must be less than total
// docs, from the above test of 20 items, 12 will come from the boundary box
// filter, but only 5 are actually in the radius of the results.
// Note Boundary Box filtering, is not accurate enough for most systems.
System.out.println("Distance Filter filtered: " + distances.size());
System.out.println("Results: " + results);
System.out.println("=============================");
System.out.println("Distances should be 14 "+ distances.size());
System.out.println("Results should be 7 "+ results);
assertEquals(14, distances.size());
assertEquals(7, results);
for(int i =0 ; i < results; i++){
Document d = hits.doc(i);
String name = d.get("name");
double rsLat = NumberUtils.SortableStr2double(d.get(latField));
double rsLng = NumberUtils.SortableStr2double(d.get(lngField));
Double geo_distance = distances.get(hits.id(i));
double distance = DistanceUtils.getInstance().getDistanceMi(lat, lng, rsLat, rsLng);
double llm = DistanceUtils.getInstance().getLLMDistance(lat, lng, rsLat, rsLng);
System.out.println("Name: "+ name +", Distance (res, ortho, harvesine):"+ distance +" |"+ geo_distance +"|"+ llm +" | score "+ hits.score(i));
assertTrue(Math.abs((distance - llm)) < 1);
assertTrue((distance < miles ));
}
}
}

View File

@ -17,7 +17,6 @@
package org.apache.lucene.spatial.tier;
import java.io.IOException;
import java.util.Map;
import junit.framework.TestCase;
@ -25,19 +24,9 @@ import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.spatial.tier.DistanceQueryBuilder;
import org.apache.lucene.spatial.tier.DistanceSortSource;
import org.apache.lucene.spatial.tier.DistanceUtils;
import org.apache.lucene.spatial.tier.InvalidGeoException;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.spatial.NumberUtils;
import org.apache.lucene.store.RAMDirectory;
/**