/* * Licensed to GraphHopper GmbH under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. * * GraphHopper GmbH 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 com.graphhopper.geohash; import com.graphhopper.util.BitUtil; import com.graphhopper.util.DistanceCalcEarth; import com.graphhopper.util.shapes.GHPoint; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Peter Karich */ public class SpatialKeyAlgoTest { @Test public void testEncode() { SpatialKeyAlgo algo = new SpatialKeyAlgo(32); long val = algo.encode(-24.235345f, 47.234234f); assertEquals("01100110101000111100000110010100", BitUtil.BIG.toLastBitString(val, 32)); } @Test public void testEncode3BytesPrecision() { // 3 bytes => c / 1^12 = ~10km int bits = 3 * 8; SpatialKeyAlgo algo = new SpatialKeyAlgo(bits); float lat = 24.235345f; float lon = 47.234234f; long val = algo.encode(lat, lon); assertEquals("00000000" + "110011000000100101101011", BitUtil.BIG.toLastBitString(val, 32)); GHPoint fl = new GHPoint(); algo.decode(val, fl); // normally 10km are expected here we have only 100meters ... (?) assertEquals(lat, fl.lat, .1); assertEquals(lon, fl.lon, .1); double expectedDist = ((float) DistanceCalcEarth.C / (1 << bits / 2)); double d = new DistanceCalcEarth().calcDist(lat, lon, fl.lat, fl.lon); assertTrue("Returned point shouldn't be more far away than " + expectedDist + " -> It was " + d, d < expectedDist); } @Test public void testEncode4BytesPrecision() { int bits = 4 * 8; SpatialKeyAlgo algo = new SpatialKeyAlgo(bits); float lat = 24.235345f; float lon = 47.234234f; long val = algo.encode(lat, lon); assertEquals("11001100000010010110101100111110", BitUtil.BIG.toLastBitString(val, bits)); GHPoint fl = new GHPoint(); algo.decode(val, fl); assertEquals(lat, fl.lat, 1e-2); assertEquals(lon, fl.lon, 1e-2); double expectedDist = ((float) DistanceCalcEarth.C / (1 << bits / 2)); double d = new DistanceCalcEarth().calcDist(lat, lon, fl.lat, fl.lon); assertTrue("Returned point shouldn't be more far away than " + expectedDist + " -> It was " + d, d < expectedDist); } @Test public void testEncode6BytesPrecision() { int bits = 6 * 8; SpatialKeyAlgo algo = new SpatialKeyAlgo(bits); float lat = 24.235345f; float lon = 47.234234f; long val = algo.encode(lat, lon); assertEquals("11001100000010010110101100111110" + "11100111" + "01000110", BitUtil.BIG.toLastBitString(val, bits)); GHPoint fl = new GHPoint(); algo.decode(val, fl); assertEquals(lat, fl.lat, 1e-4); assertEquals(lon, fl.lon, 1e-4); double expectedDist = ((float) DistanceCalcEarth.C / (1 << bits / 2)); double d = new DistanceCalcEarth().calcDist(lat, lon, fl.lat, fl.lon); assertTrue("Returned point shouldn't be more far away than " + expectedDist + " -> It was " + d, d < expectedDist); } @Test public void testBijectionBug2() { for (long i = 4; i <= 64; i += 4) { SpatialKeyAlgo algo = new SpatialKeyAlgo((int) i); long keyX = algo.encode(1, 1); GHPoint coord = new GHPoint(); algo.decode(keyX, coord); long keyY = algo.encode(coord.lat, coord.lon); GHPoint coord2 = new GHPoint(); algo.decode(keyY, coord2); double dist = new DistanceCalcEarth().calcDist(coord.lat, coord.lon, coord2.lat, coord2.lon); assertEquals(0, dist, 1e-5); // System.out.println("\n\n##" + i + "\nkeyX:" + BitUtil.BIG.toBitString(keyX)); // System.out.println("keyY:" + BitUtil.BIG.toBitString(keyY)); // System.out.println("distanceX:" + dist + " precision:" + precision + " difference:" + (dist - precision) + " factor:" + dist / precision); } } @Test public void testBijection() { // fix bijection precision problem! // // the latitude encoding "10" would result in 1.0 but a rounding error could lead to e.g. 0.99 // this new float decodes to an entire different latitude "01" which again could result in // another different value like 0.49 and so on. // To avoid this unstable rounding we need to add the half of the next interval in the last // iteration in decode: else { ... break; } // Now the latitude encoding "10" results in 1.25 and a minor rounding error such as 1.24 // results in the same encoding "10"! // // 0 .5 1.0 1.5 2.0 // |--- ^ ---|--- ^ ---| testBijection(6 * 8); testBijection(7 * 8); testBijection(8 * 8); } public void testBijection(int bits) { SpatialKeyAlgo algo = new SpatialKeyAlgo(bits); GHPoint coord11 = new GHPoint(); long key = algo.encode(1, 1); algo.decode(key, coord11); long resKey = algo.encode(coord11.lat, coord11.lon); GHPoint coord2 = new GHPoint(); algo.decode(resKey, coord2); assertEquals(key, resKey); GHPoint coord = new GHPoint(50.022846, 9.2123575); key = algo.encode(coord); algo.decode(key, coord2); assertEquals(key, algo.encode(coord2)); double dist = new DistanceCalcEarth().calcDist(coord.lat, coord.lon, coord2.lat, coord2.lon); // and ensure small distance assertTrue(dist + "", dist < 5); long queriedKey = 246557819640268L; long storedKey = 246557819640269L; algo.decode(queriedKey, coord); algo.decode(storedKey, coord2); // 2. fix bijection precision problem assertEquals(storedKey, algo.encode(coord2)); dist = new DistanceCalcEarth().calcDist(coord.lat, coord.lon, coord2.lat, coord2.lon); // and ensure small distance assertTrue(dist + "", dist < 5); coord = new GHPoint(50.0606072, 9.6277542); key = algo.encode(coord); algo.decode(key, coord2); assertEquals(key, algo.encode(coord2)); dist = new DistanceCalcEarth().calcDist(coord.lat, coord.lon, coord2.lat, coord2.lon); // and ensure small distance assertTrue(dist + "", dist < 5); coord = new GHPoint(0.01, 0.01); key = algo.encode(coord); algo.decode(key, coord2); assertEquals(key, algo.encode(coord2)); dist = new DistanceCalcEarth().calcDist(coord.lat, coord.lon, coord2.lat, coord2.lon); // and ensure small distance assertTrue(dist + "", dist < 5); } @Test public void testNoFurtherIterationIfBitsIs1() { SpatialKeyAlgo algo = new SpatialKeyAlgo(4).setBounds(0, 5, 0, 5); // 1001 GHPoint coord = new GHPoint(); algo.decode(9, coord); assertEquals(3.125, coord.lat, 1e-4); assertEquals(1.875, coord.lon, 1e-4); } @Test public void testOddBits() { GHPoint coord = new GHPoint(); SpatialKeyAlgo algo = new SpatialKeyAlgo(8); long key = algo.encode(5, 30); assertEquals("11000001", BitUtil.BIG.toLastBitString(key, 8)); algo.decode(key, coord); assertEquals(5.63, coord.lat, 1e-2); assertEquals(33.75, coord.lon, 1e-2); algo = new SpatialKeyAlgo(7); key = algo.encode(11.11, 40.66); assertEquals("01100000", BitUtil.BIG.toLastBitString(key, 8)); assertEquals(5.63, coord.lat, 1e-2); assertEquals(33.75, coord.lon, 1e-2); } @Test public void testDifferentInitialBounds() { SpatialKeyAlgo algo = new SpatialKeyAlgo(8).setBounds(0, 5, 0, 5); assertEquals(1, algo.encode(0, 0.5)); assertEquals(5, algo.encode(0, 1)); GHPoint coord = new GHPoint(); algo.decode(5, coord); assertEquals(5, algo.encode(coord)); algo.decode(1, coord); assertEquals(1, algo.encode(coord)); } @Test public void testEdgeCases() { double minLon = -1, maxLon = 1.6; double minLat = -1, maxLat = 0.5; int parts = 4; int bits = (int) (Math.log(parts * parts) / Math.log(2)); final KeyAlgo keyAlgo = new SpatialKeyAlgo(bits).setBounds(minLon, maxLon, minLat, maxLat); // lat border 0.125 assertEquals(11, keyAlgo.encode(0.125, -0.2)); assertEquals(9, keyAlgo.encode(0.124, -0.2)); // lon border -0.35 assertEquals(11, keyAlgo.encode(0.2, -0.35)); assertEquals(10, keyAlgo.encode(0.2, -0.351)); } }