All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphhopper.geohash.SpatialKeyAlgo Maven / Gradle / Ivy

There is a newer version: 0.8.0
Show newest version
/*
 *  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.shapes.BBox;
import com.graphhopper.util.shapes.GHPoint;

/**
 * This class implements the idea of a geohash but in 'binary form' - to avoid confusion this is
 * called 'spatial key'. The idea of mixing the latitude and longitude is also taken to allow
 * removing the insignificant (right side) bits to make a geo-query or the coordinate less precise.
 * E.g. for a 3 bit precision the spatial key would need 6 bits and look like:
 * 

* lat0 lon0 | lat1 lon1 | lat2 lon2 *

* This works similar to how BIG endianess works for bytes to int packing. Detailed information is * available in this blog post: * http://karussell.wordpress.com/2012/05/23/spatial-keys-memory-efficient-geohashes/ *

* The bits are usable as key for hash tables like our SpatialKeyHashtable or for a spatial tree * like QuadTreeSimple. Also the binary form makes it relative simple for implementations using this * encoding scheme to expand to arbitrary dimension (e.g. shifting n-times if n would be the * dimension). *

* A 32 bit representation has a precision of approx 600 meters = 40000/2^16 *

* There are different possibilities how to handle different precision and order of bits. Either: *

* lat0 lon0 | lat1 lon1 | lat2 lon2 *

* 0 0 | lat0 lon0 | lat1 lon1 *

* as it is done now. Advantage: A single shift is only necessary to make it less precise. Or: *

* lat2 lon2 | lat1 lon1 | lat0 lon0 *

* 0 0 | lat1 lon1 | lat0 lon0 *

* Advantage: the bit mask to get lat0 lon0 is simple: 000..0011 and independent of the precision! * But when stored e.g. as int one would need to (left) shift several times if precision is only * 3bits. *

* @author Peter Karich */ // A 2 bit precision spatial key could look like // // |----|----|----|----| // |1010|1011|1110|1111| // |----|----|----|----| lat0 == 1 // |1000|1001|1100|1101| // -|----|----|----|----|------ // |0010|0011|0110|0111| // |----|----|----|----| lat0 == 0 // |0000|0001|0100|0101| // |----|----|----|----| // | // lon0 == 0 | lon0 == 1 public class SpatialKeyAlgo implements KeyAlgo { private BBox bbox; private int allBits; private long initialBits; /** * @param allBits how many bits should be used for the spatial key when encoding/decoding */ public SpatialKeyAlgo( int allBits ) { myinit(allBits); } private void myinit( int allBits ) { if (allBits > 64) throw new IllegalStateException("allBits is too big and does not fit into 8 bytes"); if (allBits <= 0) throw new IllegalStateException("allBits must be positive"); // if ((allBits & 0x1) == 1) // throw new IllegalStateException("allBits needs to be even to use the same amount for lat and lon"); this.allBits = allBits; initialBits = 1L << (allBits - 1); setWorldBounds(); } /** * @return the number of involved bits */ public int getBits() { return allBits; } public int getExactPrecision() { // 360 / 2^(allBits/2) = 1/precision int p = (int) (Math.pow(2, allBits) / 360); // no rounding error p++; return (int) Math.log10(p); } public SpatialKeyAlgo bounds( BBox box ) { bbox = box.clone(); return this; } @Override public SpatialKeyAlgo setBounds( double minLonInit, double maxLonInit, double minLatInit, double maxLatInit ) { bounds(new BBox(minLonInit, maxLonInit, minLatInit, maxLatInit)); return this; } protected void setWorldBounds() { setBounds(-180, 180, -90, 90); } @Override public long encode( GHPoint coord ) { return encode(coord.lat, coord.lon); } /** * Take latitude and longitude as input. *

* @return the spatial key */ @Override public final long encode( double lat, double lon ) { // PERFORMANCE: int operations would be faster than double (for further comparison etc) // but we would need 'long' because 'int factorForPrecision' is not enough (problem: coord!=decode(encode(coord)) see testBijection) // and 'long'-ops are more expensive than double (at least on 32bit systems) long hash = 0; double minLatTmp = bbox.minLat; double maxLatTmp = bbox.maxLat; double minLonTmp = bbox.minLon; double maxLonTmp = bbox.maxLon; int i = 0; while (true) { if (minLatTmp < maxLatTmp) { double midLat = (minLatTmp + maxLatTmp) / 2; if (lat < midLat) { maxLatTmp = midLat; } else { hash |= 1; minLatTmp = midLat; } } i++; if (i < allBits) hash <<= 1; else // if allBits is an odd number break; if (minLonTmp < maxLonTmp) { double midLon = (minLonTmp + maxLonTmp) / 2; if (lon < midLon) { maxLonTmp = midLon; } else { hash |= 1; minLonTmp = midLon; } } i++; if (i < allBits) hash <<= 1; else break; } return hash; } /** * This method returns latitude and longitude via latLon - calculated from specified spatialKey *

* @param spatialKey is the input */ @Override public final void decode( long spatialKey, GHPoint latLon ) { // Performance: calculating 'midLon' and 'midLat' on the fly is not slower than using // precalculated values from arrays and for 'bits' a precalculated array is even slightly slower! // Use the value in the middle => start from "min" use "max" as initial step-size double midLat = (bbox.maxLat - bbox.minLat) / 2; double midLon = (bbox.maxLon - bbox.minLon) / 2; double lat = bbox.minLat; double lon = bbox.minLon; long bits = initialBits; while (true) { if ((spatialKey & bits) != 0) { lat += midLat; } midLat /= 2; bits >>>= 1; if ((spatialKey & bits) != 0) { lon += midLon; } midLon /= 2; if (bits > 1) { bits >>>= 1; } else { break; } } // stable rounding - see testBijection lat += midLat; lon += midLon; latLon.lat = lat; latLon.lon = lon; } @Override public String toString() { return "bits:" + allBits + ", bounds:" + bbox; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy