From e1634f66bb5b1530cf3fc1af2ec5177c4a43e47e Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 20 Feb 2014 11:15:20 +0100 Subject: [PATCH] Improve `arc` geo-distance accuracy. Close #5192 --- .../java/org/elasticsearch/common/geo/GeoDistance.java | 10 ++++++---- .../java/org/elasticsearch/common/geo/GeoUtils.java | 9 +++++++++ .../java/org/apache/lucene/util/SloppyMathTests.java | 6 ------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/elasticsearch/common/geo/GeoDistance.java b/src/main/java/org/elasticsearch/common/geo/GeoDistance.java index 6bac086a692..bf234c5b27a 100644 --- a/src/main/java/org/elasticsearch/common/geo/GeoDistance.java +++ b/src/main/java/org/elasticsearch/common/geo/GeoDistance.java @@ -81,10 +81,12 @@ public enum GeoDistance { public double calculate(double sourceLatitude, double sourceLongitude, double targetLatitude, double targetLongitude, DistanceUnit unit) { double x1 = sourceLatitude * Math.PI / 180D; double x2 = targetLatitude * Math.PI / 180D; - double h1 = (1D - Math.cos(x1 - x2)) / 2D; - double h2 = (1D - Math.cos((sourceLongitude - targetLongitude) * Math.PI / 180D)) / 2D; - double h = h1 + Math.cos(x1) * Math.cos(x2) * h2; - return unit.fromMeters(GeoUtils.EARTH_MEAN_RADIUS * 2D * Math.asin(Math.min(1, Math.sqrt(h)))); + double h1 = 1D - Math.cos(x1 - x2); + double h2 = 1D - Math.cos((sourceLongitude - targetLongitude) * Math.PI / 180D); + double h = (h1 + Math.cos(x1) * Math.cos(x2) * h2) / 2; + double averageLatitude = (x1 + x2) / 2; + double diameter = GeoUtils.earthDiameter(averageLatitude); + return unit.fromMeters(diameter * Math.asin(Math.min(1, Math.sqrt(h)))); } @Override diff --git a/src/main/java/org/elasticsearch/common/geo/GeoUtils.java b/src/main/java/org/elasticsearch/common/geo/GeoUtils.java index 2efc6eb50d2..998504f8164 100644 --- a/src/main/java/org/elasticsearch/common/geo/GeoUtils.java +++ b/src/main/java/org/elasticsearch/common/geo/GeoUtils.java @@ -21,6 +21,7 @@ package org.elasticsearch.common.geo; import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree; import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree; +import org.apache.lucene.util.SloppyMath; import org.elasticsearch.common.unit.DistanceUnit; /** @@ -45,6 +46,14 @@ public class GeoUtils { /** Earth ellipsoid polar distance in meters */ public static final double EARTH_POLAR_DISTANCE = Math.PI * EARTH_SEMI_MINOR_AXIS; + /** + * Return an approximate value of the diameter of the earth (in meters) at the given latitude (in radians). + */ + public static double earthDiameter(double latitude) { + // SloppyMath impl returns a result in kilometers + return SloppyMath.earthDiameter(latitude) * 1000; + } + /** * Calculate the width (in meters) of geohash cells at a specific level * @param level geohash level must be greater or equal to zero diff --git a/src/test/java/org/apache/lucene/util/SloppyMathTests.java b/src/test/java/org/apache/lucene/util/SloppyMathTests.java index 81a2fb5faaa..2bcd9ed34e1 100644 --- a/src/test/java/org/apache/lucene/util/SloppyMathTests.java +++ b/src/test/java/org/apache/lucene/util/SloppyMathTests.java @@ -44,12 +44,6 @@ public class SloppyMathTests extends ElasticsearchTestCase { @Test public void testSloppyMath() { - assertThat(GeoDistance.SLOPPY_ARC.calculate(-46.645, -171.057, -46.644, -171.058, DistanceUnit.METERS), closeTo(134.87709, maxError(134.87709))); - assertThat(GeoDistance.SLOPPY_ARC.calculate(-77.912, -81.173, -77.912, -81.171, DistanceUnit.METERS), closeTo(46.57161, maxError(46.57161))); - assertThat(GeoDistance.SLOPPY_ARC.calculate(65.75, -20.708, 65.75, -20.709, DistanceUnit.METERS), closeTo(45.66996, maxError(45.66996))); - assertThat(GeoDistance.SLOPPY_ARC.calculate(-86.9, 53.738, -86.9, 53.741, DistanceUnit.METERS), closeTo(18.03998, maxError(18.03998))); - assertThat(GeoDistance.SLOPPY_ARC.calculate(89.041, 115.93, 89.04, 115.946, DistanceUnit.METERS), closeTo(115.11711, maxError(115.11711))); - testSloppyMath(DistanceUnit.METERS, 0.01, 5, 45, 90); testSloppyMath(DistanceUnit.KILOMETERS, 0.01, 5, 45, 90); testSloppyMath(DistanceUnit.INCH, 0.01, 5, 45, 90);