From 8f0ecbcc0b0148c011b66332d42791eefc9e580b Mon Sep 17 00:00:00 2001 From: Olivier Favre Date: Tue, 10 Jan 2012 17:21:54 +0100 Subject: [PATCH] Improve latitude and longitude normalization --- .../index/search/geo/GeoUtils.java | 36 ++-- .../unit/index/search/geo/GeoUtilsTests.java | 177 ++++++++++++++++++ 2 files changed, 189 insertions(+), 24 deletions(-) create mode 100644 src/test/java/org/elasticsearch/test/unit/index/search/geo/GeoUtilsTests.java diff --git a/src/main/java/org/elasticsearch/index/search/geo/GeoUtils.java b/src/main/java/org/elasticsearch/index/search/geo/GeoUtils.java index 69d560dcc7e..493943f65b2 100644 --- a/src/main/java/org/elasticsearch/index/search/geo/GeoUtils.java +++ b/src/main/java/org/elasticsearch/index/search/geo/GeoUtils.java @@ -24,32 +24,20 @@ package org.elasticsearch.index.search.geo; public class GeoUtils { public static double normalizeLon(double lon) { - double delta = 0; - if (lon < 0) { - delta = 360; - } else if (lon >= 0) { - delta = -360; - } - - double newLng = lon; - while (newLng < -180 || newLng > 180) { - newLng += delta; - } - return newLng; + return centeredModulus(lon, 360); } public static double normalizeLat(double lat) { - double delta = 0; - if (lat < 0) { - delta = 180; - } else if (lat >= 0) { - delta = -180; - } - - double newLat = lat; - while (newLat < -90 || newLat > 90) { - newLat += delta; - } - return newLat; + return centeredModulus(lat, 180); } + + private static double centeredModulus(double dividend, double divisor) { + double rtn = dividend % divisor; + if (rtn <= 0) + rtn += divisor; + if (rtn > divisor/2) + rtn -= divisor; + return rtn; + } + } diff --git a/src/test/java/org/elasticsearch/test/unit/index/search/geo/GeoUtilsTests.java b/src/test/java/org/elasticsearch/test/unit/index/search/geo/GeoUtilsTests.java new file mode 100644 index 00000000000..430142712d8 --- /dev/null +++ b/src/test/java/org/elasticsearch/test/unit/index/search/geo/GeoUtilsTests.java @@ -0,0 +1,177 @@ +/* + * Licensed to ElasticSearch and Shay Banon under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.unit.index.search.geo; + +import org.elasticsearch.index.search.geo.GeoUtils; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; + +/** + * + */ +public class GeoUtilsTests { + + /** + * Test special values like inf, NaN and -0.0. + */ + @Test + public void testSpecials() { + assertThat(GeoUtils.normalizeLon(Double.POSITIVE_INFINITY), equalTo(Double.NaN)); + assertThat(GeoUtils.normalizeLat(Double.POSITIVE_INFINITY), equalTo(Double.NaN)); + assertThat(GeoUtils.normalizeLon(Double.NEGATIVE_INFINITY), equalTo(Double.NaN)); + assertThat(GeoUtils.normalizeLat(Double.NEGATIVE_INFINITY), equalTo(Double.NaN)); + assertThat(GeoUtils.normalizeLon(Double.NaN), equalTo(Double.NaN)); + assertThat(GeoUtils.normalizeLat(Double.NaN), equalTo(Double.NaN)); + assertThat(0.0, not(equalTo(-0.0))); + assertThat(GeoUtils.normalizeLon(-0.0), equalTo(0.0)); + assertThat(GeoUtils.normalizeLat(-0.0), equalTo(0.0)); + assertThat(GeoUtils.normalizeLon( 0.0), equalTo(0.0)); + assertThat(GeoUtils.normalizeLat( 0.0), equalTo(0.0)); + } + + /** + * Test bounding values. + */ + @Test + public void testBounds() { + assertThat(GeoUtils.normalizeLon(-360.0), equalTo(0.0)); + assertThat(GeoUtils.normalizeLat(-180.0), equalTo(0.0)); + assertThat(GeoUtils.normalizeLon( 360.0), equalTo(0.0)); + assertThat(GeoUtils.normalizeLat( 180.0), equalTo(0.0)); + // and halves + assertThat(GeoUtils.normalizeLon(-180.0), equalTo(180.0)); + assertThat(GeoUtils.normalizeLat(- 90.0), equalTo( 90.0)); + assertThat(GeoUtils.normalizeLon( 180.0), equalTo(180.0)); + assertThat(GeoUtils.normalizeLat( 90.0), equalTo( 90.0)); + } + + /** + * Test normal values. + */ + @Test + public void testNormal() { + // Near bounds + assertThat(GeoUtils.normalizeLon(-360.5), equalTo(-0.5)); + assertThat(GeoUtils.normalizeLat(-180.5), equalTo(-0.5)); + assertThat(GeoUtils.normalizeLon( 360.5), equalTo( 0.5)); + assertThat(GeoUtils.normalizeLat( 180.5), equalTo( 0.5)); + // and near halves + assertThat(GeoUtils.normalizeLon(-180.5), equalTo( 179.5)); + assertThat(GeoUtils.normalizeLat(- 90.5), equalTo( 89.5)); + assertThat(GeoUtils.normalizeLon( 180.5), equalTo(-179.5)); + assertThat(GeoUtils.normalizeLat( 90.5), equalTo(- 89.5)); + // Every 10-units, multiple full turns + for (int shift = -20 ; shift <= 20 ; ++shift) { + assertThat(GeoUtils.normalizeLon(shift*360.0+ 0.0), equalTo( 0.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 10.0), equalTo( 10.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 20.0), equalTo( 20.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 30.0), equalTo( 30.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 40.0), equalTo( 40.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 50.0), equalTo( 50.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 60.0), equalTo( 60.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 70.0), equalTo( 70.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 80.0), equalTo( 80.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 90.0), equalTo( 90.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 100.0), equalTo( 100.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 110.0), equalTo( 110.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 120.0), equalTo( 120.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 130.0), equalTo( 130.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 140.0), equalTo( 140.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 150.0), equalTo( 150.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 160.0), equalTo( 160.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 170.0), equalTo( 170.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 180.0), equalTo( 180.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 190.0), equalTo(-170.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 200.0), equalTo(-160.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 210.0), equalTo(-150.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 220.0), equalTo(-140.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 230.0), equalTo(-130.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 240.0), equalTo(-120.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 250.0), equalTo(-110.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 260.0), equalTo(-100.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 270.0), equalTo(- 90.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 280.0), equalTo(- 80.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 290.0), equalTo(- 70.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 300.0), equalTo(- 60.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 310.0), equalTo(- 50.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 320.0), equalTo(- 40.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 330.0), equalTo(- 30.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 340.0), equalTo(- 20.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 350.0), equalTo(- 10.0)); + assertThat(GeoUtils.normalizeLon(shift*360.0+ 360.0), equalTo( 0.0)); + } + for (int shift = -20 ; shift <= 20 ; ++shift) { + assertThat(GeoUtils.normalizeLat(shift*180.0+ 0.0), equalTo( 0.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 10.0), equalTo( 10.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 20.0), equalTo( 20.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 30.0), equalTo( 30.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 40.0), equalTo( 40.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 50.0), equalTo( 50.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 60.0), equalTo( 60.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 70.0), equalTo( 70.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 80.0), equalTo( 80.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 90.0), equalTo( 90.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 100.0), equalTo(-80.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 110.0), equalTo(-70.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 120.0), equalTo(-60.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 130.0), equalTo(-50.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 140.0), equalTo(-40.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 150.0), equalTo(-30.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 160.0), equalTo(-20.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 170.0), equalTo(-10.0)); + assertThat(GeoUtils.normalizeLat(shift*180.0+ 180.0), equalTo( 0.0)); + } + } + + /** + * Test huge values. + */ + @Test + public void testHuge() { + assertThat(GeoUtils.normalizeLon(-36000000000181.0), equalTo(GeoUtils.normalizeLon(-181.0))); + assertThat(GeoUtils.normalizeLon(-36000000000180.0), equalTo(GeoUtils.normalizeLon(-180.0))); + assertThat(GeoUtils.normalizeLon(-36000000000179.0), equalTo(GeoUtils.normalizeLon(-179.0))); + assertThat(GeoUtils.normalizeLon(-36000000000178.0), equalTo(GeoUtils.normalizeLon(-178.0))); + assertThat(GeoUtils.normalizeLon(-36000000000001.0), equalTo(GeoUtils.normalizeLon(-001.0))); + assertThat(GeoUtils.normalizeLon(+36000000000000.0), equalTo(GeoUtils.normalizeLon(+000.0))); + assertThat(GeoUtils.normalizeLon(+36000000000001.0), equalTo(GeoUtils.normalizeLon(+001.0))); + assertThat(GeoUtils.normalizeLon(+36000000000002.0), equalTo(GeoUtils.normalizeLon(+002.0))); + assertThat(GeoUtils.normalizeLon(+36000000000178.0), equalTo(GeoUtils.normalizeLon(+178.0))); + assertThat(GeoUtils.normalizeLon(+36000000000179.0), equalTo(GeoUtils.normalizeLon(+179.0))); + assertThat(GeoUtils.normalizeLon(+36000000000180.0), equalTo(GeoUtils.normalizeLon(+180.0))); + assertThat(GeoUtils.normalizeLon(+36000000000181.0), equalTo(GeoUtils.normalizeLon(+181.0))); + assertThat(GeoUtils.normalizeLat(-18000000000091.0), equalTo(GeoUtils.normalizeLat(-091.0))); + assertThat(GeoUtils.normalizeLat(-18000000000090.0), equalTo(GeoUtils.normalizeLat(-090.0))); + assertThat(GeoUtils.normalizeLat(-18000000000089.0), equalTo(GeoUtils.normalizeLat(-089.0))); + assertThat(GeoUtils.normalizeLat(-18000000000088.0), equalTo(GeoUtils.normalizeLat(-088.0))); + assertThat(GeoUtils.normalizeLat(-18000000000001.0), equalTo(GeoUtils.normalizeLat(-001.0))); + assertThat(GeoUtils.normalizeLat(+18000000000000.0), equalTo(GeoUtils.normalizeLat(+000.0))); + assertThat(GeoUtils.normalizeLat(+18000000000001.0), equalTo(GeoUtils.normalizeLat(+001.0))); + assertThat(GeoUtils.normalizeLat(+18000000000002.0), equalTo(GeoUtils.normalizeLat(+002.0))); + assertThat(GeoUtils.normalizeLat(+18000000000088.0), equalTo(GeoUtils.normalizeLat(+088.0))); + assertThat(GeoUtils.normalizeLat(+18000000000089.0), equalTo(GeoUtils.normalizeLat(+089.0))); + assertThat(GeoUtils.normalizeLat(+18000000000090.0), equalTo(GeoUtils.normalizeLat(+090.0))); + assertThat(GeoUtils.normalizeLat(+18000000000091.0), equalTo(GeoUtils.normalizeLat(+091.0))); + } + +}