You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2013/06/09 17:57:04 UTC
svn commit: r1491231 - in /sis/branches/JDK7/storage/sis-storage/src:
main/java/org/apache/sis/index/GeoHashCoder.java
main/java/org/apache/sis/index/package-info.java
test/java/org/apache/sis/index/GeoHashCoderTest.java
Author: desruisseaux
Date: Sun Jun 9 15:57:03 2013
New Revision: 1491231
URL: http://svn.apache.org/r1491231
Log:
Avoid usage of arrays in implementation since they were not strictly needed,
and update the API to use DirectPosition.
Added:
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java (with props)
Modified:
sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/GeoHashCoder.java
sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/index/GeoHashCoderTest.java
Modified: sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/GeoHashCoder.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/GeoHashCoder.java?rev=1491231&r1=1491230&r2=1491231&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/GeoHashCoder.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/GeoHashCoder.java [UTF-8] Sun Jun 9 15:57:03 2013
@@ -16,13 +16,19 @@
*/
package org.apache.sis.index;
+import java.io.Serializable;
import java.text.ParseException;
+import org.opengis.geometry.DirectPosition;
+import org.apache.sis.geometry.DirectPosition2D;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
/**
- * Utilities for encoding and decoding geohashes.
+ * Encodes geographic coordinates as <cite>geohashes</cite> strings, and decode such strings back to coordinates.
+ * The current implementation is restricted to two-dimensional geographic coordinates, in the (<var>longitude</var>,
+ * <var>latitude</var>) order with the longitudes ranging from -180° to 180° and the latitudes ranging from -90° to
+ * 90°. The datum is unspecified.
*
* @author Chris Mattmann (JPL)
* @since 0.1
@@ -31,7 +37,12 @@ import org.apache.sis.util.resources.Err
*
* @see <a href="http://en.wikipedia.org/wiki/Geohash">Wikipedia: Geohash</a>
*/
-public class GeoHashCoder {
+public class GeoHashCoder implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 9162259764027168776L;
+
/**
* The encoding format used by {@link GeoHashCoder}.
*/
@@ -41,13 +52,20 @@ public class GeoHashCoder {
* and lower-case letters {@code 'b'} to {@code 'z'} excluding {@code 'i'}, {@code 'l'} and {@code 'o'}.
* Decoding is case-insensitive.
*/
- BASE32(new byte[] {
+ BASE32(16, new byte[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm',
'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
});
/**
+ * A single one-bit in the position of the highest-order ("leftmost") one-bit of the value
+ * represented by a letter or digit. This value can be computed as {@code 1 << (numBits-1)}
+ * where {@code numBits} is the number of bits needed for representing a letter or digit.
+ */
+ final int highestOneBit;
+
+ /**
* Mapping from a numerical value to its symbol. The length of this array is the base of the encoding,
* e.g. 32 for {@link #BASE32}.
*/
@@ -68,9 +86,11 @@ public class GeoHashCoder {
* Creates a new format for the given {@coe encoding} mapping.
* This constructor computes the {@code decoding} arrays from the {@code encoding} one.
*
+ * @param highestOneBit The leftmost one-bit of the value represented by a letter or digit.
* @param encoding The mapping from numerical values to symbols.
*/
- private Format(final byte[] encoding) {
+ private Format(final int highestOneBit, final byte[] encoding) {
+ this.highestOneBit = highestOneBit;
this.encoding = encoding;
final byte[] decoding = new byte[26];
for (byte i=10; i<encoding.length; i++) {
@@ -101,10 +121,8 @@ public class GeoHashCoder {
*/
private transient char[] buffer;
- private static final byte[] BITS = { 16, 8, 4, 2, 1 };
-
/**
- * Creates a new geohash coder/decoder initialized to the default precision.
+ * Creates a new geohash coder/decoder initialized to the default precision for {@link Format#BASE32}.
*/
public GeoHashCoder() {
format = Format.BASE32;
@@ -113,6 +131,7 @@ public class GeoHashCoder {
/**
* Returns the current encoding/decoding format.
+ * The default value is {@link Format#BASE32}.
*
* @return The current format.
*/
@@ -132,6 +151,7 @@ public class GeoHashCoder {
/**
* Returns the length of geohashes strings to be encoded by the {@link #encode(double, double)} method.
+ * The default value is 12.
*
* @return The length of geohashes strings.
*/
@@ -147,58 +167,63 @@ public class GeoHashCoder {
public void setPrecision(final int precision) {
ArgumentChecks.ensureBetween("precision", 1, 255, precision);
this.precision = (byte) precision;
+ buffer = null; // Will recreate a new buffer when first needed.
}
/**
- * Encodes the given latitude and longitude into a geohash.
+ * Encodes the given longitude and latitude into a geohash.
*
- * @param latitude
- * Latitude to encode
- * @param longitude
- * Longitude to encode
- * @return Geohash encoding of the longitude and latitude
+ * @param longitude Longitude to encode, as decimal degrees in the [-180 … 180]° range.
+ * @param latitude Latitude to encode, as decimal degrees in the [-90 … 90]° range.
+ * @return Geohash encoding of the given longitude and latitude.
*/
- public String encode(final double latitude, final double longitude) {
+ public String encode(final double longitude, final double latitude) {
final byte[] encoding = format.encoding;
- final double[] latInterval = { -90.0, 90.0 };
- final double[] lngInterval = { -180.0, 180.0 };
-
+ final int highestOneBit = format.highestOneBit;
char[] geohash = buffer;
if (geohash == null) {
buffer = geohash = new char[precision & 0xFF];
}
+ /*
+ * The current implementation assumes a two-dimensional coordinates. The 'isEven' boolean takes
+ * the 'true' value for longitude, and 'false' for latitude. We could extend this algorithm to
+ * the multi-dimensional case by replacing 'isEven' by a counter over the ordinate dimension.
+ */
boolean isEven = true;
-
- int bit = 0;
+ double xmin = -180, ymin = -90;
+ double xmax = 180, ymax = 90;
+ /*
+ * 'ch' is the index of the character to be added in the geohash. The actual character will be
+ * given by the 'encoding' array. 'bit' shall have a single one-bit, rotating from 10000 in the
+ * BASE32 case to 00001 (binary representation).
+ */
int ch = 0;
-
+ int bit = highestOneBit;
for (int i=0; i<geohash.length;) {
- double mid;
if (isEven) {
- mid = (lngInterval[0] + lngInterval[1]) / 2D;
+ final double mid = (xmin + xmax) / 2;
if (longitude > mid) {
- ch |= BITS[bit];
- lngInterval[0] = mid;
+ ch |= bit;
+ xmin = mid;
} else {
- lngInterval[1] = mid;
+ xmax = mid;
}
} else {
- mid = (latInterval[0] + latInterval[1]) / 2D;
+ final double mid = (ymin + ymax) / 2;
if (latitude > mid) {
- ch |= BITS[bit];
- latInterval[0] = mid;
+ ch |= bit;
+ ymin = mid;
} else {
- latInterval[1] = mid;
+ ymax = mid;
}
}
isEven = !isEven;
- if (bit < 4) {
- bit++;
- } else {
+ bit >>>= 1;
+ if (bit == 0) {
geohash[i++] = (char) encoding[ch];
- bit = 0;
+ bit = highestOneBit;
ch = 0;
}
}
@@ -206,22 +231,42 @@ public class GeoHashCoder {
}
/**
- * Decodes the given geohash into a latitude and longitude
+ * Encodes the given position into a geohash. The current implementation takes the first ordinate value as the
+ * longitude, the second ordinate value as the latitude, then delegates to {@link #encode(double, double)}.
+ *
+ * <p>The current implementation does not verify the Coordinate Reference System of the given position.
+ * However this may change in future SIS versions.</p>
*
- * @param geohash
- * Geohash to decode
- * @return Array with the latitude at index 0, and longitude at index 1
- * @throws ParseException
+ * @param position The coordinate to encode.
+ * @return Geohash encoding of the given position.
*/
- public double[] decode(final String geohash) throws ParseException {
+ public String encode(final DirectPosition position) {
+ ArgumentChecks.ensureDimensionMatches("position", 2, position);
+ return encode(position.getOrdinate(0), position.getOrdinate(1));
+ }
+
+ /**
+ * Decodes the given geohash into a longitude and a latitude.
+ *
+ * @param geohash Geohash string to decode.
+ * @return A new position with the longitude at ordinate 0 and latitude at ordinate 1.
+ * @throws ParseException If an error occurred while parsing the given string.
+ */
+ public DirectPosition decode(final String geohash) throws ParseException {
final int length = geohash.length();
+ final int highestOneBit = format.highestOneBit;
final byte[] decodingLowerCase = format.decodingLowerCase;
final byte[] decodingUpperCase = format.decodingUpperCase;
- final double[] latInterval = { -90.0, 90.0 };
- final double[] lngInterval = { -180.0, 180.0 };
+ /*
+ * The current implementation assumes a two-dimensional coordinates. The 'isEven' boolean takes
+ * the 'true' value for longitude, and 'false' for latitude. We could extend this algorithm to
+ * the multi-dimensional case by replacing 'isEven' by a counter over the ordinate dimension.
+ */
boolean isEven = true;
+ double xmin = -180, ymin = -90;
+ double xmax = 180, ymax = 90;
- int nc;
+ int nc; // Number of characters for the 'c' code point.
for (int i=0; i<length; i+=nc) {
int c = geohash.codePointAt(i);
nc = Character.charCount(c);
@@ -240,26 +285,27 @@ public class GeoHashCoder {
"GeoHash",geohash, geohash.substring(i, i+nc)), i);
}
}
- for (int mask : BITS) {
+ int mask = highestOneBit;
+ do {
if (isEven) {
+ final double mid = (xmin + xmax) / 2;
if ((c & mask) != 0) {
- lngInterval[0] = (lngInterval[0] + lngInterval[1]) / 2D;
+ xmin = mid;
} else {
- lngInterval[1] = (lngInterval[0] + lngInterval[1]) / 2D;
+ xmax = mid;
}
} else {
+ final double mid = (ymin + ymax) / 2;
if ((c & mask) != 0) {
- latInterval[0] = (latInterval[0] + latInterval[1]) / 2D;
+ ymin = mid;
} else {
- latInterval[1] = (latInterval[0] + latInterval[1]) / 2D;
+ ymax = mid;
}
}
isEven = !isEven;
- }
+ } while ((mask >>>= 1) != 0);
}
- double latitude = (latInterval[0] + latInterval[1]) / 2D;
- double longitude = (lngInterval[0] + lngInterval[1]) / 2D;
-
- return new double[] { latitude, longitude };
+ return new DirectPosition2D((xmin + xmax) / 2,
+ (ymin + ymax) / 2);
}
}
Added: sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java?rev=1491231&view=auto
==============================================================================
--- sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java (added)
+++ sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java [UTF-8] Sun Jun 9 15:57:03 2013
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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.
+ */
+
+
+/**
+ * Computes geohash strings from geographic coordinates, and conversely.
+ */
+package org.apache.sis.index;
Propchange: sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sis/branches/JDK7/storage/sis-storage/src/main/java/org/apache/sis/index/package-info.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified: sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/index/GeoHashCoderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/index/GeoHashCoderTest.java?rev=1491231&r1=1491230&r2=1491231&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/index/GeoHashCoderTest.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-storage/src/test/java/org/apache/sis/index/GeoHashCoderTest.java [UTF-8] Sun Jun 9 15:57:03 2013
@@ -16,9 +16,8 @@
*/
package org.apache.sis.index;
-import java.util.Map;
-import java.util.HashMap;
import java.text.ParseException;
+import org.opengis.geometry.DirectPosition;
import org.apache.sis.test.TestCase;
import org.junit.Test;
@@ -43,11 +42,13 @@ public final strictfp class GeoHashCoder
* A geographic coordinates together with the expected geohash.
*/
private static final class Place {
- final double latitude;
+ final String name;
final double longitude;
+ final double latitude;
final String geohash;
- Place(final double latitude, final double longitude, final String geohash) {
+ Place(final String name, final double longitude, final double latitude, final String geohash) {
+ this.name = name;
this.latitude = latitude;
this.longitude = longitude;
this.geohash = geohash;
@@ -55,19 +56,17 @@ public final strictfp class GeoHashCoder
}
/**
- * Returns a map of places with their expected geohash.
+ * A list o places with their expected geohash.
*/
- private Map<String, Place> places() {
- final Map<String, Place> places = new HashMap<>(12);
- places.put("Empire State Building", new Place(40.748433, -73.985656, "dr5ru6j2c62q"));
- places.put("Statue Of Liberty", new Place(40.689167, -74.044444, "dr5r7p4rx6kz"));
- places.put("The White House", new Place(38.897669, -77.036550, "dqcjqcpeq70c"));
- places.put("Hoover Dam", new Place(36.015556, -114.737778, "9qqkvh6mzfpz"));
- places.put("Golden Gate Bridge", new Place(37.819722, -122.478611, "9q8zhuvgce0m"));
- places.put("Mount Rushmore", new Place(43.878947, -103.459825, "9xy3teyv7ke4"));
- places.put("Space Needle", new Place(47.620400, -122.349100, "c22yzvh0gmfy"));
- return places;
- }
+ private static Place[] PLACES = new Place[] {
+ new Place("Empire State Building", -73.985656, 40.748433, "dr5ru6j2c62q"),
+ new Place("Statue Of Liberty", -74.044444, 40.689167, "dr5r7p4rx6kz"),
+ new Place("The White House", -77.036550, 38.897669, "dqcjqcpeq70c"),
+ new Place("Hoover Dam", -114.737778, 36.015556, "9qqkvh6mzfpz"),
+ new Place("Golden Gate Bridge", -122.478611, 37.819722, "9q8zhuvgce0m"),
+ new Place("Mount Rushmore", -103.459825, 43.878947, "9xy3teyv7ke4"),
+ new Place("Space Needle", -122.349100, 47.620400, "c22yzvh0gmfy")
+ };
/**
* Tests the {@link GeoHashCoder#encode(double, double)} method.
@@ -75,10 +74,8 @@ public final strictfp class GeoHashCoder
@Test
public void testEncode() {
final GeoHashCoder coder = new GeoHashCoder();
- for (final Map.Entry<String, Place> entry : places().entrySet()) {
- final Place place = entry.getValue();
- assertEquals(entry.getKey(), place.geohash,
- coder.encode(place.latitude, place.longitude));
+ for (final Place place : PLACES) {
+ assertEquals(place.name, place.geohash, coder.encode(place.longitude, place.latitude));
}
}
@@ -90,12 +87,10 @@ public final strictfp class GeoHashCoder
@Test
public void testDecode() throws ParseException {
final GeoHashCoder coder = new GeoHashCoder();
- for (final Map.Entry<String, Place> entry : places().entrySet()) {
- final String name = entry.getKey();
- final Place place = entry.getValue();
- final double[] result = coder.decode(place.geohash);
- assertEquals(name, place.latitude, result[0], TOLERANCE);
- assertEquals(name, place.longitude, result[1], TOLERANCE);
+ for (final Place place : PLACES) {
+ final DirectPosition result = coder.decode(place.geohash);
+ assertEquals(place.name, place.longitude, result.getOrdinate(0), TOLERANCE);
+ assertEquals(place.name, place.latitude, result.getOrdinate(1), TOLERANCE);
}
}
}