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);
         }
     }
 }