From d371619dd88e1b16f718216ee01344ae8c37ec72 Mon Sep 17 00:00:00 2001 From: Shay Banon Date: Fri, 19 Aug 2011 03:21:46 +0300 Subject: [PATCH] add an option to just compute distance factor --- .../common/unit/DistanceUnit.java | 38 ++++++++++--- .../mapper/geo/GeoPointDocFieldData.java | 12 +++++ .../index/mapper/geo/GeoPointFieldData.java | 8 +++ .../index/query/GeoDistanceFilterParser.java | 1 + .../query/GeoDistanceRangeFilterParser.java | 2 + .../index/search/geo/GeoDistance.java | 53 +++++++++++++++---- .../search/geo/GeoDistanceTests.java | 13 +++++ 7 files changed, 112 insertions(+), 15 deletions(-) diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/common/unit/DistanceUnit.java b/modules/elasticsearch/src/main/java/org/elasticsearch/common/unit/DistanceUnit.java index b7e015908de..818a5be639a 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/common/unit/DistanceUnit.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/common/unit/DistanceUnit.java @@ -32,25 +32,37 @@ public enum DistanceUnit { MILES(3959, 24902) { @Override public String toString() { return "miles"; - }@Override public double toMiles(double distance) { + } + + @Override public double toMiles(double distance) { return distance; - }@Override public double toKilometers(double distance) { + } + + @Override public double toKilometers(double distance) { return distance * MILES_KILOMETRES_RATIO; } + @Override public String toString(double distance) { return distance + "mi"; - }}, + } + }, KILOMETERS(6371, 40076) { @Override public String toString() { return "km"; - }@Override public double toMiles(double distance) { + } + + @Override public double toMiles(double distance) { return distance / MILES_KILOMETRES_RATIO; - }@Override public double toKilometers(double distance) { + } + + @Override public double toKilometers(double distance) { return distance; } + @Override public String toString(double distance) { return distance + "km"; - }}; + } + }; static final double MILES_KILOMETRES_RATIO = 1.609344; @@ -95,10 +107,24 @@ public enum DistanceUnit { protected final double earthCircumference; protected final double earthRadius; + protected final double distancePerDegree; DistanceUnit(double earthRadius, double earthCircumference) { this.earthCircumference = earthCircumference; this.earthRadius = earthRadius; + this.distancePerDegree = earthCircumference / 360; + } + + public double getEarthCircumference() { + return earthCircumference; + } + + public double getEarthRadius() { + return earthRadius; + } + + public double getDistancePerDegree() { + return distancePerDegree; } public abstract double toMiles(double distance); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointDocFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointDocFieldData.java index 0c612bc9d14..6f02c33bb87 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointDocFieldData.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointDocFieldData.java @@ -39,6 +39,18 @@ public class GeoPointDocFieldData extends DocFieldData { return fieldData.values(docId); } + public double factorDistance(double lat, double lon) { + return fieldData.factorDistance(docId, DistanceUnit.MILES, lat, lon); + } + + public double arcDistance(double lat, double lon) { + return fieldData.arcDistance(docId, DistanceUnit.MILES, lat, lon); + } + + public double arcDistanceInKm(double lat, double lon) { + return fieldData.arcDistance(docId, DistanceUnit.KILOMETERS, lat, lon); + } + public double distance(double lat, double lon) { return fieldData.distance(docId, DistanceUnit.MILES, lat, lon); } diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldData.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldData.java index 705a1c94910..3e1d59003de 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldData.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/mapper/geo/GeoPointFieldData.java @@ -82,6 +82,14 @@ public abstract class GeoPointFieldData extends FieldData return GeoDistance.PLANE.calculate(latValue(docId), lonValue(docId), lat, lon, unit); } + public double arcDistance(int docId, DistanceUnit unit, double lat, double lon) { + return GeoDistance.ARC.calculate(latValue(docId), lonValue(docId), lat, lon, unit); + } + + public double factorDistance(int docId, DistanceUnit unit, double lat, double lon) { + return GeoDistance.FACTOR.calculate(latValue(docId), lonValue(docId), lat, lon, unit); + } + public double distanceGeohash(int docId, DistanceUnit unit, String geoHash) { GeoPointHash geoPointHash = geoHashCache.get().get(); if (geoPointHash.geoHash != geoHash) { diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceFilterParser.java index dce345e136b..ee3ea4311e6 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceFilterParser.java @@ -154,6 +154,7 @@ public class GeoDistanceFilterParser implements FilterParser { } else { distance = DistanceUnit.parse((String) vDistance, unit, DistanceUnit.MILES); } + distance = geoDistance.normalize(distance, DistanceUnit.MILES); MapperService mapperService = parseContext.mapperService(); FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceRangeFilterParser.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceRangeFilterParser.java index 573a944a2bb..a4a9540576b 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceRangeFilterParser.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/query/GeoDistanceRangeFilterParser.java @@ -205,11 +205,13 @@ public class GeoDistanceRangeFilterParser implements FilterParser { } else { from = DistanceUnit.parse((String) vFrom, unit, DistanceUnit.MILES); } + from = geoDistance.normalize(from, DistanceUnit.MILES); if (vTo instanceof Number) { to = unit.toMiles(((Number) vTo).doubleValue()); } else { to = DistanceUnit.parse((String) vTo, unit, DistanceUnit.MILES); } + to = geoDistance.normalize(to, DistanceUnit.MILES); MapperService mapperService = parseContext.mapperService(); FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName); diff --git a/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/geo/GeoDistance.java b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/geo/GeoDistance.java index 9fb521d1289..e5bd18fde95 100644 --- a/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/geo/GeoDistance.java +++ b/modules/elasticsearch/src/main/java/org/elasticsearch/index/search/geo/GeoDistance.java @@ -32,14 +32,30 @@ public enum GeoDistance { * Calculates distance as points on a plane. Faster, but less accurate than {@link #ARC}. */ PLANE() { - private final static double EARTH_CIRCUMFERENCE_MILES = 24901; - private final static double DISTANCE_PER_DEGREE = EARTH_CIRCUMFERENCE_MILES / 360; - @Override public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { double px = targetLongitude - sourceLongitude; double py = targetLatitude - sourceLatitude; - double distanceMiles = Math.sqrt(px * px + py * py) * DISTANCE_PER_DEGREE; - return DistanceUnit.convert(distanceMiles, DistanceUnit.MILES, unit); + return Math.sqrt(px * px + py * py) * unit.getDistancePerDegree(); + } + + @Override public double normalize(double distance, DistanceUnit unit) { + return distance; + } + }, + /** + * Calculates distance factor. + */ + FACTOR() { + @Override public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { + // TODO: we might want to normalize longitude as we did in LatLng... + double longitudeDifference = targetLongitude - sourceLongitude; + double a = Math.toRadians(90D - sourceLatitude); + double c = Math.toRadians(90D - targetLatitude); + return (Math.cos(a) * Math.cos(c)) + (Math.sin(a) * Math.sin(c) * Math.cos(Math.toRadians(longitudeDifference))); + } + + @Override public double normalize(double distance, DistanceUnit unit) { + return Math.cos(distance / unit.getEarthRadius()); } }, /** @@ -47,10 +63,27 @@ public enum GeoDistance { */ ARC() { @Override public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { - LatLng sourcePoint = new LatLng(sourceLatitude, sourceLongitude); - LatLng targetPoint = new LatLng(targetLatitude, targetLongitude); - return DistanceUnit.convert(sourcePoint.arcDistance(targetPoint, DistanceUnit.MILES), DistanceUnit.MILES, unit); - }}; + // TODO: we might want to normalize longitude as we did in LatLng... + double longitudeDifference = targetLongitude - sourceLongitude; + double a = Math.toRadians(90D - sourceLatitude); + double c = Math.toRadians(90D - targetLatitude); + double factor = (Math.cos(a) * Math.cos(c)) + (Math.sin(a) * Math.sin(c) * Math.cos(Math.toRadians(longitudeDifference))); + + if (factor < -1D) { + return Math.PI * unit.getEarthRadius(); + } else if (factor >= 1D) { + return 0; + } else { + return Math.acos(factor) * unit.getEarthRadius(); + } + } + + @Override public double normalize(double distance, DistanceUnit unit) { + return distance; + } + }; + + public abstract double normalize(double distance, DistanceUnit unit); public abstract double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit); @@ -59,6 +92,8 @@ public enum GeoDistance { return PLANE; } else if ("arc".equals(s)) { return ARC; + } else if ("factor".equals(s)) { + return FACTOR; } throw new ElasticSearchIllegalArgumentException("No geo distance for [" + s + "]"); } diff --git a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java index ce00945cd8a..eefe52dda1e 100644 --- a/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java +++ b/modules/test/integration/src/test/java/org/elasticsearch/test/integration/search/geo/GeoDistanceTests.java @@ -22,6 +22,7 @@ package org.elasticsearch.test.integration.search.geo; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.index.search.geo.GeoDistance; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; @@ -122,6 +123,18 @@ public class GeoDistanceTests extends AbstractNodesTests { assertThat(hit.id(), anyOf(equalTo("1"), equalTo("3"), equalTo("4"), equalTo("5"), equalTo("6"))); } + // now with a PLANE type + searchResponse = client.prepareSearch() // from NY + .setQuery(filteredQuery(matchAllQuery(), geoDistanceFilter("location").distance("3km").geoDistance(GeoDistance.PLANE).point(40.7143528, -74.0059731))) + .execute().actionGet(); + assertThat(searchResponse.hits().getTotalHits(), equalTo(5l)); + assertThat(searchResponse.hits().hits().length, equalTo(5)); + for (SearchHit hit : searchResponse.hits()) { + assertThat(hit.id(), anyOf(equalTo("1"), equalTo("3"), equalTo("4"), equalTo("5"), equalTo("6"))); + } + + // factor type is really too small for this resolution + searchResponse = client.prepareSearch() // from NY .setQuery(filteredQuery(matchAllQuery(), geoDistanceFilter("location").distance("2km").point(40.7143528, -74.0059731))) .execute().actionGet();