You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2016/04/07 22:29:57 UTC

[25/50] [abbrv] lucene-solr:apiv2: LUCENE-7166: fix quantization bugs in LatLonPoint and GeoPointField, remove test leniency

LUCENE-7166: fix quantization bugs in LatLonPoint and GeoPointField, remove test leniency

Squashed commit of the following:

commit 83c0f9b6158495b8b3d7108059a23bdf38e0f7f3
Author: Robert Muir <rm...@apache.org>
Date:   Fri Apr 1 23:33:29 2016 -0400

    fix geopoint

commit 97ebd2de516e61c236542fb2fb28e71cf6bdc403
Author: Robert Muir <rm...@apache.org>
Date:   Fri Apr 1 23:06:05 2016 -0400

    fix test and LatLonPoint encoding/quantization/box queries


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/f8ea8b85
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/f8ea8b85
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/f8ea8b85

Branch: refs/heads/apiv2
Commit: f8ea8b855e43fc0a2fa434ab8c366de810047c8f
Parents: 0166171
Author: Robert Muir <rm...@apache.org>
Authored: Sat Apr 2 07:16:55 2016 -0400
Committer: Robert Muir <rm...@apache.org>
Committed: Sat Apr 2 07:16:55 2016 -0400

----------------------------------------------------------------------
 lucene/CHANGES.txt                              |   3 +
 .../org/apache/lucene/document/LatLonPoint.java |  56 ++++++--
 .../apache/lucene/document/TestLatLonPoint.java | 136 +++++++++++++++++--
 .../geopoint/search/GeoPointMultiTermQuery.java |  12 +-
 .../org/apache/lucene/spatial/util/GeoRect.java |  12 +-
 .../spatial/util/BaseGeoPointTestCase.java      |  33 ++++-
 .../lucene/spatial/util/TestGeoUtils.java       |  15 --
 7 files changed, 212 insertions(+), 55 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/CHANGES.txt
----------------------------------------------------------------------
diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 8e95950..8dccacd 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -57,6 +57,9 @@ Bug Fixes
 
 * LUCENE-7127: Fix corner case bugs in GeoPointDistanceQuery. (Robert Muir)
 
+* LUCENE-7166: Fix corner case bugs in LatLonPoint/GeoPointField bounding box
+  queries. (Robert Muir)
+
 ======================= Lucene 6.0.0 =======================
 
 System Requirements

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
index af014cb..507f543 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/document/LatLonPoint.java
@@ -97,10 +97,10 @@ public class LatLonPoint extends Field {
   }
 
   private static final int BITS = 32;
-  private static final double LONGITUDE_ENCODE = (0x1L<<BITS)/360.0D;
-  private static final double LONGITUDE_DECODE = 1/LONGITUDE_ENCODE;
-  private static final double LATITUDE_ENCODE  = (0x1L<<BITS)/180.0D;
-  private static final double LATITUDE_DECODE  =  1/LATITUDE_ENCODE;
+  private static final double LONGITUDE_MUL = (0x1L<<BITS)/360.0D;
+  private static final double LONGITUDE_DECODE = 1/LONGITUDE_MUL;
+  private static final double LATITUDE_MUL  = (0x1L<<BITS)/180.0D;
+  private static final double LATITUDE_DECODE  = 1/LATITUDE_MUL;
   
   @Override
   public String toString() {
@@ -132,7 +132,7 @@ public class LatLonPoint extends Field {
   // public helper methods (e.g. for queries)
 
   /** 
-   * Quantizes double (64 bit) latitude into 32 bits 
+   * Quantizes double (64 bit) latitude into 32 bits (rounding down: in the direction of -90)
    * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
    * @return encoded value as a 32-bit {@code int}
    * @throws IllegalArgumentException if latitude is out of bounds
@@ -143,11 +143,26 @@ public class LatLonPoint extends Field {
     if (latitude == 90.0D) {
       latitude = Math.nextDown(latitude);
     }
-    return (int) Math.floor(latitude * LATITUDE_ENCODE);
+    return (int) Math.floor(latitude / LATITUDE_DECODE);
+  }
+  
+  /** 
+   * Quantizes double (64 bit) latitude into 32 bits (rounding up: in the direction of +90)
+   * @param latitude latitude value: must be within standard +/-90 coordinate bounds.
+   * @return encoded value as a 32-bit {@code int}
+   * @throws IllegalArgumentException if latitude is out of bounds
+   */
+  public static int encodeLatitudeCeil(double latitude) {
+    GeoUtils.checkLatitude(latitude);
+    // the maximum possible value cannot be encoded without overflow
+    if (latitude == 90.0D) {
+      latitude = Math.nextDown(latitude);
+    }
+    return (int) Math.ceil(latitude / LATITUDE_DECODE);
   }
 
   /** 
-   * Quantizes double (64 bit) longitude into 32 bits 
+   * Quantizes double (64 bit) longitude into 32 bits (rounding down: in the direction of -180)
    * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
    * @return encoded value as a 32-bit {@code int}
    * @throws IllegalArgumentException if longitude is out of bounds
@@ -158,7 +173,22 @@ public class LatLonPoint extends Field {
     if (longitude == 180.0D) {
       longitude = Math.nextDown(longitude);
     }
-    return (int) Math.floor(longitude * LONGITUDE_ENCODE);
+    return (int) Math.floor(longitude / LONGITUDE_DECODE);
+  }
+  
+  /** 
+   * Quantizes double (64 bit) longitude into 32 bits (rounding up: in the direction of +180)
+   * @param longitude longitude value: must be within standard +/-180 coordinate bounds.
+   * @return encoded value as a 32-bit {@code int}
+   * @throws IllegalArgumentException if longitude is out of bounds
+   */
+  public static int encodeLongitudeCeil(double longitude) {
+    GeoUtils.checkLongitude(longitude);
+    // the maximum possible value cannot be encoded without overflow
+    if (longitude == 180.0D) {
+      longitude = Math.nextDown(longitude);
+    }
+    return (int) Math.ceil(longitude / LONGITUDE_DECODE);
   }
 
   /** 
@@ -210,6 +240,14 @@ public class LatLonPoint extends Field {
     NumericUtils.intToSortableBytes(encodeLongitude(longitude), bytes, Integer.BYTES);
     return bytes;
   }
+  
+  /** sugar encodes a single point as a byte array, rounding values up */
+  private static byte[] encodeCeil(double latitude, double longitude) {
+    byte[] bytes = new byte[2 * Integer.BYTES];
+    NumericUtils.intToSortableBytes(encodeLatitudeCeil(latitude), bytes, 0);
+    NumericUtils.intToSortableBytes(encodeLongitudeCeil(longitude), bytes, Integer.BYTES);
+    return bytes;
+  }
 
   /** helper: checks a fieldinfo and throws exception if its definitely not a LatLonPoint */
   static void checkCompatible(FieldInfo fieldInfo) {
@@ -246,7 +284,7 @@ public class LatLonPoint extends Field {
    * @throws IllegalArgumentException if {@code field} is null, or the box has invalid coordinates.
    */
   public static Query newBoxQuery(String field, double minLatitude, double maxLatitude, double minLongitude, double maxLongitude) {
-    byte[] lower = encode(minLatitude, minLongitude);
+    byte[] lower = encodeCeil(minLatitude, minLongitude);
     byte[] upper = encode(maxLatitude, maxLongitude);
     // Crosses date line: we just rewrite into OR of two bboxes, with longitude as an open range:
     if (maxLongitude < minLongitude) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
----------------------------------------------------------------------
diff --git a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
index 045f8ba..73a3683 100644
--- a/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
+++ b/lucene/sandbox/src/test/org/apache/lucene/document/TestLatLonPoint.java
@@ -16,6 +16,7 @@
  */
 package org.apache.lucene.document;
 
+import org.apache.lucene.spatial.util.GeoTestUtil;
 import org.apache.lucene.util.LuceneTestCase;
 
 /** Simple tests for {@link LatLonPoint} */
@@ -26,7 +27,7 @@ public class TestLatLonPoint extends LuceneTestCase {
     assertEquals("LatLonPoint <field:18.313693958334625,-65.22744401358068>",(new LatLonPoint("field", 18.313694, -65.227444)).toString());
     
     // looks crazy due to lossiness
-    assertEquals("field:[17.99999997485429 TO 18.999999999068677],[-66.00000000558794 TO -65.00000006519258]", LatLonPoint.newBoxQuery("field", 18, 19, -66, -65).toString());
+    assertEquals("field:[18.000000016763806 TO 18.999999999068677],[-65.9999999217689 TO -65.00000006519258]", LatLonPoint.newBoxQuery("field", 18, 19, -66, -65).toString());
     
     // distance query does not quantize inputs
     assertEquals("field:18.0,19.0 +/- 25.0 meters", LatLonPoint.newDistanceQuery("field", 18, 19, 25).toString());
@@ -41,11 +42,11 @@ public class TestLatLonPoint extends LuceneTestCase {
 
     int iters = atLeast(10000);
     for(int iter=0;iter<iters;iter++) {
-      double lat = -90 + 180.0 * random().nextDouble();
+      double lat = GeoTestUtil.nextLatitude();
       double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(lat));
       assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, ENCODING_TOLERANCE);
 
-      double lon = -180 + 360.0 * random().nextDouble();
+      double lon = GeoTestUtil.nextLongitude();
       double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lon));
       assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, ENCODING_TOLERANCE);
     }
@@ -59,6 +60,31 @@ public class TestLatLonPoint extends LuceneTestCase {
     assertEquals(180.0, LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(180.0)), ENCODING_TOLERANCE);
     assertEquals(-180.0, LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(-180.0)), ENCODING_TOLERANCE);
   }
+  
+  public void testEncodeDecodeCeil() throws Exception {
+    // just for testing quantization error
+    final double ENCODING_TOLERANCE = 1e-7;
+
+    int iters = atLeast(10000);
+    for(int iter=0;iter<iters;iter++) {
+      double lat = GeoTestUtil.nextLatitude();
+      double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(lat));
+      assertEquals("lat=" + lat + " latEnc=" + latEnc + " diff=" + (lat - latEnc), lat, latEnc, ENCODING_TOLERANCE);
+
+      double lon = GeoTestUtil.nextLongitude();
+      double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(lon));
+      assertEquals("lon=" + lon + " lonEnc=" + lonEnc + " diff=" + (lon - lonEnc), lon, lonEnc, ENCODING_TOLERANCE);
+    }
+
+    // check edge/interesting cases explicitly
+    assertEquals(0.0, LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(0.0)), ENCODING_TOLERANCE);
+    assertEquals(90.0, LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(90.0)), ENCODING_TOLERANCE);
+    assertEquals(-90.0, LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(-90.0)), ENCODING_TOLERANCE);
+
+    assertEquals(0.0, LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(0.0)), ENCODING_TOLERANCE);
+    assertEquals(180.0, LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(180.0)), ENCODING_TOLERANCE);
+    assertEquals(-180.0, LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(-180.0)), ENCODING_TOLERANCE);
+  }
 
   public void testEncodeDecodeExtremeValues() throws Exception {
     assertEquals(Integer.MIN_VALUE, LatLonPoint.encodeLatitude(-90.0));
@@ -69,12 +95,22 @@ public class TestLatLonPoint extends LuceneTestCase {
     assertEquals(0, LatLonPoint.encodeLatitude(0.0));
     assertEquals(Integer.MAX_VALUE, LatLonPoint.encodeLongitude(180.0));
   }
+  
+  public void testEncodeDecodeExtremeValuesCeil() throws Exception {
+    assertEquals(Integer.MIN_VALUE, LatLonPoint.encodeLatitudeCeil(-90.0));
+    assertEquals(0, LatLonPoint.encodeLatitudeCeil(0.0));
+    assertEquals(Integer.MAX_VALUE, LatLonPoint.encodeLatitudeCeil(90.0));
+
+    assertEquals(Integer.MIN_VALUE, LatLonPoint.encodeLongitudeCeil(-180.0));
+    assertEquals(0, LatLonPoint.encodeLatitudeCeil(0.0));
+    assertEquals(Integer.MAX_VALUE, LatLonPoint.encodeLongitudeCeil(180.0));
+  }
 
   public void testEncodeDecodeIsStable() throws Exception {
     int iters = atLeast(1000);
     for(int iter=0;iter<iters;iter++) {
-      double lat = -90 + 180.0 * random().nextDouble();
-      double lon = -180 + 360.0 * random().nextDouble();
+      double lat = GeoTestUtil.nextLatitude();
+      double lon = GeoTestUtil.nextLongitude();
 
       double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(lat));
       double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lon));
@@ -86,16 +122,92 @@ public class TestLatLonPoint extends LuceneTestCase {
     }
   }
   
+  public void testEncodeDecodeCeilIsStable() throws Exception {
+    int iters = atLeast(1000);
+    for(int iter=0;iter<iters;iter++) {
+      double lat = GeoTestUtil.nextLatitude();
+      double lon = GeoTestUtil.nextLongitude();
+
+      double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(lat));
+      double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(lon));
+
+      double latEnc2 = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(latEnc));
+      double lonEnc2 = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(lonEnc));
+      assertEquals(latEnc, latEnc2, 0.0);
+      assertEquals(lonEnc, lonEnc2, 0.0);
+    }
+  }
+  
   /** make sure values always go down: this is important for edge case consistency */
   public void testEncodeDecodeRoundsDown() throws Exception {
-    int iters = atLeast(1000);
+    int iters = atLeast(10000);
     for(int iter=0;iter<iters;iter++) {
-      double lat = -90 + 180.0 * random().nextDouble();
-      double lon = -180 + 360.0 * random().nextDouble();
-      double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(lat));
-      double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lon));
-      assertTrue(latEnc <= lat);
-      assertTrue(lonEnc <= lon);
+      final double latBase = GeoTestUtil.nextLatitude();
+      final double lonBase = GeoTestUtil.nextLongitude();
+
+      // test above the value
+      double lat = latBase;
+      double lon = lonBase;
+      for (int i = 0; i < 1000; i++) {
+        lat = Math.min(90, Math.nextUp(lat));
+        lon = Math.min(180, Math.nextUp(lon));
+        double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(lat));
+        double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lon));
+        assertTrue(latEnc <= lat);
+        assertTrue(lonEnc <= lon);
+      }
+
+      // test below the value
+      lat = latBase;
+      lon = lonBase;
+      for (int i = 0; i < 1000; i++) {
+        lat = Math.max(-90, Math.nextDown(lat));
+        lon = Math.max(-180, Math.nextDown(lon));
+        double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitude(lat));
+        double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(lon));
+        assertTrue(latEnc <= lat);
+        assertTrue(lonEnc <= lon);
+      }
+    }
+  }
+
+  /** bug in previous encoding! */
+  public void testSpecialBuggyValue() throws Exception {
+    double special = 124.40717171877621;
+    double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitude(special));
+    assertTrue(lonEnc <= special);
+  }
+  
+  /** make sure values can go up if we need */
+  public void testEncodeDecodeCeilRoundsUp() throws Exception {
+    int iters = atLeast(10000);
+    for(int iter=0;iter<iters;iter++) {
+      final double latBase = GeoTestUtil.nextLatitude();
+      final double lonBase = GeoTestUtil.nextLongitude();
+
+      // test above the value
+      double lat = latBase;
+      double lon = lonBase;
+      for (int i = 0; i < 1000; i++) {
+        lat = Math.min(90, Math.nextUp(lat));
+        lon = Math.min(180, Math.nextUp(lon));
+        double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(lat));
+        double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(lon));
+        assertTrue(latEnc >= lat);
+        assertTrue(lonEnc >= lon);
+      }
+
+      // test below the value
+      lat = latBase;
+      lon = lonBase;
+      for (int i = 0; i < 1000; i++) {
+        lat = Math.max(-90, Math.nextDown(lat));
+        lon = Math.max(-180, Math.nextDown(lon));
+        double latEnc = LatLonPoint.decodeLatitude(LatLonPoint.encodeLatitudeCeil(lat));
+        double lonEnc = LatLonPoint.decodeLongitude(LatLonPoint.encodeLongitudeCeil(lon));
+        assertTrue(latEnc >= lat);
+        assertTrue(lonEnc >= lon);
+      }
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
index f1f53e3..20026a1 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/geopoint/search/GeoPointMultiTermQuery.java
@@ -27,7 +27,6 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.util.AttributeSource;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField;
 import org.apache.lucene.spatial.geopoint.document.GeoPointField.TermEncoding;
-import org.apache.lucene.spatial.util.GeoEncodingUtils;
 import org.apache.lucene.spatial.util.GeoRelationUtils;
 import org.apache.lucene.geo.GeoUtils;
 import org.apache.lucene.util.SloppyMath;
@@ -44,6 +43,7 @@ abstract class GeoPointMultiTermQuery extends MultiTermQuery {
   protected final double minLat;
   protected final double maxLon;
   protected final double maxLat;
+
   protected final short maxShift;
   protected final TermEncoding termEncoding;
   protected final CellComparator cellComparator;
@@ -60,12 +60,10 @@ abstract class GeoPointMultiTermQuery extends MultiTermQuery {
     GeoUtils.checkLongitude(minLon);
     GeoUtils.checkLongitude(maxLon);
 
-    final long minHash = GeoEncodingUtils.mortonHash(minLat, minLon);
-    final long maxHash = GeoEncodingUtils.mortonHash(maxLat, maxLon);
-    this.minLat = GeoEncodingUtils.mortonUnhashLat(minHash);
-    this.maxLat = GeoEncodingUtils.mortonUnhashLat(maxHash);
-    this.minLon = GeoEncodingUtils.mortonUnhashLon(minHash);
-    this.maxLon = GeoEncodingUtils.mortonUnhashLon(maxHash);
+    this.minLat = minLat;
+    this.maxLat = maxLat;
+    this.minLon = minLon;
+    this.maxLon = maxLon;
 
     this.maxShift = computeMaxShift();
     this.termEncoding = termEncoding;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
index 391f6d7..fdde3bf 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/util/GeoRect.java
@@ -68,17 +68,17 @@ public class GeoRect {
   @Override
   public String toString() {
     StringBuilder b = new StringBuilder();
-    b.append("GeoRect(lon=");
+    b.append("GeoRect(lat=");
+    b.append(minLat);
+    b.append(" TO ");
+    b.append(maxLat);
+    b.append(" lon=");
     b.append(minLon);
     b.append(" TO ");
     b.append(maxLon);
     if (maxLon < minLon) {
-      b.append(" (crosses dateline!)");
+      b.append(" [crosses dateline!]");
     }
-    b.append(" lat=");
-    b.append(minLat);
-    b.append(" TO ");
-    b.append(maxLat);
     b.append(")");
 
     return b.toString();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
index 93b0b1c..182ec6f 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/BaseGeoPointTestCase.java
@@ -538,8 +538,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     int iters = atLeast(25);
     for (int iter=0;iter<iters;iter++) {
       GeoRect rect = randomRect(small);
-      // TODO: why does this test need this quantization leniency? something is not right
-      rect = new GeoRect(quantizeLat(rect.minLat), quantizeLat(rect.maxLat), quantizeLon(rect.minLon), quantizeLon(rect.maxLon));
 
       if (VERBOSE) {
         System.out.println("\nTEST: iter=" + iter + " rect=" + rect);
@@ -823,8 +821,6 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
       }
       
       GeoRect rect = randomRect(small);
-      // TODO: why does this test need this quantization leniency? something is not right
-      rect = new GeoRect(quantizeLat(rect.minLat), quantizeLat(rect.maxLat), quantizeLon(rect.minLon), quantizeLon(rect.maxLon));
 
       Query query = newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon);
 
@@ -1185,6 +1181,8 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
         break;
       }
     }
+    // this test works in quantized space: for testing inclusiveness of exact edges it must be aware of index-time quantization!
+    rect = new GeoRect(quantizeLat(rect.minLat), quantizeLat(rect.maxLat), quantizeLon(rect.minLon), quantizeLon(rect.maxLon));
     Directory dir = newDirectory();
     IndexWriterConfig iwc = newIndexWriterConfig();
     // Else seeds may not reproduce:
@@ -1219,7 +1217,29 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
     }
     IndexReader r = w.getReader();
     IndexSearcher s = newSearcher(r, false);
+    // exact edge cases
     assertEquals(8, s.count(newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, rect.maxLon)));
+    
+    // expand 1 ulp in each direction
+    assumeFalse("can't expand box, its too big already", rect.minLat ==  -90);
+    assumeFalse("can't expand box, its too big already", rect.maxLat ==   90);
+    assumeFalse("can't expand box, its too big already", rect.minLon == -180);
+    assumeFalse("can't expand box, its too big already", rect.maxLon ==  180);
+    assertEquals(8, s.count(newRectQuery(FIELD_NAME, Math.nextDown(rect.minLat), rect.maxLat, rect.minLon, rect.maxLon)));
+    assertEquals(8, s.count(newRectQuery(FIELD_NAME, rect.minLat, Math.nextUp(rect.maxLat), rect.minLon, rect.maxLon)));
+    assertEquals(8, s.count(newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, Math.nextDown(rect.minLon), rect.maxLon)));
+    assertEquals(8, s.count(newRectQuery(FIELD_NAME, rect.minLat, rect.maxLat, rect.minLon, Math.nextUp(rect.maxLon))));
+    
+    // now shrink 1 ulp in each direction: it should not include bogus stuff
+    assumeFalse("can't shrink box, its too small already", rect.minLat ==   90);
+    assumeFalse("can't shrink box, its too small already", rect.maxLat ==  -90);
+    assumeFalse("can't shrink box, its too small already", rect.minLon ==  180);
+    assumeFalse("can't shrink box, its too small already", rect.maxLon == -180);
+    // note we put points on "sides" not just "corners" so we just shrink all 4 at once for now: it should exclude all points!
+    assertEquals(0, s.count(newRectQuery(FIELD_NAME, Math.nextUp(rect.minLat), 
+                                                     Math.nextDown(rect.maxLat), 
+                                                     Math.nextUp(rect.minLon), 
+                                                     Math.nextDown(rect.maxLon))));
     r.close();
     w.close();
     dir.close();
@@ -1356,6 +1376,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   /** return topdocs over a small set of points in field "point" */
   private TopDocs searchSmallSet(Query query, int size) throws Exception {
     // this is a simple systematic test, indexing these points
+    // TODO: fragile: does not understand quantization in any way yet uses extremely high precision!
     double[][] pts = new double[][] {
         { 32.763420,          -96.774             },
         { 32.7559529921407,   -96.7759895324707   },
@@ -1417,7 +1438,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
   
   public void testSmallSetRect() throws Exception {
-    TopDocs td = searchSmallSet(newRectQuery("point", 32.778650, 32.778950, -96.7772, -96.77690000), 5);
+    TopDocs td = searchSmallSet(newRectQuery("point", 32.778, 32.779, -96.778, -96.777), 5);
     assertEquals(4, td.totalHits);
   }
 
@@ -1427,7 +1448,7 @@ public abstract class BaseGeoPointTestCase extends LuceneTestCase {
   }
 
   public void testSmallSetMultiValued() throws Exception {
-    TopDocs td = searchSmallSet(newRectQuery("point", 32.7559529921407, 32.7756745755423, -96.4538113027811, -96.7706036567688), 20);
+    TopDocs td = searchSmallSet(newRectQuery("point", 32.755, 32.776, -96.454, -96.770), 20);
     // 3 single valued docs + 2 multi-valued docs
     assertEquals(5, td.totalHits);
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f8ea8b85/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
index 06ef48c..28b885a 100644
--- a/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
+++ b/lucene/spatial/src/test/org/apache/lucene/spatial/util/TestGeoUtils.java
@@ -101,21 +101,6 @@ public class TestGeoUtils extends LuceneTestCase {
     }
   }
   
-  /** make sure values always go down: this is important for edge case consistency */
-  public void testEncodeDecodeRoundsDown() throws Exception {
-    int iters = atLeast(1000);
-    for(int iter=0;iter<iters;iter++) {
-      double lat = -90 + 180.0 * random().nextDouble();
-      double lon = -180 + 360.0 * random().nextDouble();
-      
-      long enc = GeoEncodingUtils.mortonHash(lat, lon);
-      double latEnc = GeoEncodingUtils.mortonUnhashLat(enc);
-      double lonEnc = GeoEncodingUtils.mortonUnhashLon(enc);
-      assertTrue(latEnc <= lat);
-      assertTrue(lonEnc <= lon);
-    }
-  }
-
   public void testScaleUnscaleIsStable() throws Exception {
     int iters = atLeast(1000);
     boolean small = random().nextBoolean();