You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by nk...@apache.org on 2016/02/07 05:27:25 UTC

[2/5] lucene-solr git commit: LUCENE-6930: Decouples GeoPointField from NumericType by using a custom GeoPointTokenStream and TermEnum designed for GeoPoint prefix terms

LUCENE-6930: Decouples GeoPointField from NumericType by using a custom GeoPointTokenStream and TermEnum designed for GeoPoint prefix terms


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

Branch: refs/heads/branch_5x
Commit: 74a08c08006941b74eda585b86b57fbe0ff341b2
Parents: aacda06
Author: nknize <nk...@apache.org>
Authored: Mon Feb 1 11:47:11 2016 -0600
Committer: nknize <nk...@apache.org>
Committed: Sat Feb 6 22:07:40 2016 -0600

----------------------------------------------------------------------
 .../lucene/spatial/document/GeoPointField.java  | 179 +++++++++++---
 .../spatial/document/GeoPointTokenStream.java   | 233 ++++++++++++++++++
 .../spatial/search/GeoPointDistanceQuery.java   |  24 +-
 .../search/GeoPointDistanceQueryImpl.java       |  64 ++---
 .../search/GeoPointDistanceRangeQuery.java      |  17 +-
 .../spatial/search/GeoPointInBBoxQuery.java     |  21 +-
 .../spatial/search/GeoPointInBBoxQueryImpl.java |  63 +++--
 .../spatial/search/GeoPointInPolygonQuery.java  | 117 ++++-----
 .../spatial/search/GeoPointMultiTermQuery.java  | 166 +++++++++++++
 .../search/GeoPointNumericTermsEnum.java        | 161 +++++++++++++
 .../spatial/search/GeoPointPrefixTermsEnum.java | 237 +++++++++++++++++++
 .../spatial/search/GeoPointTermQuery.java       | 114 ---------
 .../GeoPointTermQueryConstantScoreWrapper.java  |  11 +-
 .../spatial/search/GeoPointTermsEnum.java       | 199 ++++------------
 .../lucene/spatial/util/GeoEncodingUtils.java   | 157 ++++++++++++
 .../lucene/spatial/util/GeoHashUtils.java       |   8 +-
 .../lucene/spatial/util/GeoRelationUtils.java   |  16 +-
 .../apache/lucene/spatial/util/GeoUtils.java    |  87 +------
 .../spatial/search/TestGeoPointQuery.java       | 117 +++++----
 .../spatial/util/BaseGeoPointTestCase.java      |   4 +-
 .../lucene/spatial/util/TestGeoUtils.java       |  47 ++--
 21 files changed, 1434 insertions(+), 608 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
index ee94522..3cdd6ab 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointField.java
@@ -20,7 +20,9 @@ import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FieldType;
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.IndexOptions;
-import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.spatial.util.GeoEncodingUtils;
 
 /**
  * <p>
@@ -47,39 +49,83 @@ public final class GeoPointField extends Field {
   public static final int PRECISION_STEP = 9;
 
   /**
-   * Type for an GeoPointField that is not stored:
+   * <b>Expert:</b> Optional flag to select term encoding for GeoPointField types
+   */
+  public enum TermEncoding {
+    /**
+     * encodes prefix terms only resulting in a small index and faster queries - use with
+     * {@link GeoPointTokenStream}
+     */
+    PREFIX,
+    /**
+     * @deprecated encodes prefix and full resolution terms - use with
+     * {@link org.apache.lucene.analysis.NumericTokenStream}
+     */
+    @Deprecated
+    NUMERIC
+  }
+
+  /**
+   * @deprecated Type for a GeoPointField that is not stored:
    * normalization factors, frequencies, and positions are omitted.
    */
-  public static final FieldType TYPE_NOT_STORED = new FieldType();
+  @Deprecated
+  public static final FieldType NUMERIC_TYPE_NOT_STORED = new FieldType();
   static {
-    TYPE_NOT_STORED.setTokenized(false);
-    TYPE_NOT_STORED.setOmitNorms(true);
-    TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
-    TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE_NOT_STORED.setNumericType(FieldType.NumericType.LONG);
-    TYPE_NOT_STORED.setNumericPrecisionStep(PRECISION_STEP);
-    TYPE_NOT_STORED.freeze();
+    NUMERIC_TYPE_NOT_STORED.setTokenized(false);
+    NUMERIC_TYPE_NOT_STORED.setOmitNorms(true);
+    NUMERIC_TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
+    NUMERIC_TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    NUMERIC_TYPE_NOT_STORED.setNumericType(FieldType.NumericType.LONG);
+    NUMERIC_TYPE_NOT_STORED.setNumericPrecisionStep(PRECISION_STEP);
+    NUMERIC_TYPE_NOT_STORED.freeze();
+  }
+
+  /**
+   * @deprecated Type for a stored GeoPointField:
+   * normalization factors, frequencies, and positions are omitted.
+   */
+  @Deprecated
+  public static final FieldType NUMERIC_TYPE_STORED = new FieldType();
+  static {
+    NUMERIC_TYPE_STORED.setTokenized(false);
+    NUMERIC_TYPE_STORED.setOmitNorms(true);
+    NUMERIC_TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
+    NUMERIC_TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    NUMERIC_TYPE_STORED.setNumericType(FieldType.NumericType.LONG);
+    NUMERIC_TYPE_STORED.setNumericPrecisionStep(PRECISION_STEP);
+    NUMERIC_TYPE_STORED.setStored(true);
+    NUMERIC_TYPE_STORED.freeze();
+  }
+
+  /**
+   * Type for a GeoPointField that is not stored:
+   * normalization factors, frequencies, and positions are omitted.
+   */
+  public static final FieldType PREFIX_TYPE_NOT_STORED = new FieldType();
+  static {
+    PREFIX_TYPE_NOT_STORED.setTokenized(false);
+    PREFIX_TYPE_NOT_STORED.setOmitNorms(true);
+    PREFIX_TYPE_NOT_STORED.setIndexOptions(IndexOptions.DOCS);
+    PREFIX_TYPE_NOT_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    PREFIX_TYPE_NOT_STORED.freeze();
   }
 
   /**
    * Type for a stored GeoPointField:
    * normalization factors, frequencies, and positions are omitted.
    */
-  public static final FieldType TYPE_STORED = new FieldType();
+  public static final FieldType PREFIX_TYPE_STORED = new FieldType();
   static {
-    TYPE_STORED.setTokenized(false);
-    TYPE_STORED.setOmitNorms(true);
-    TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
-    TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
-    TYPE_STORED.setNumericType(FieldType.NumericType.LONG);
-    TYPE_STORED.setNumericPrecisionStep(PRECISION_STEP);
-    TYPE_STORED.setStored(true);
-    TYPE_STORED.freeze();
+    PREFIX_TYPE_STORED.setTokenized(false);
+    PREFIX_TYPE_STORED.setOmitNorms(true);
+    PREFIX_TYPE_STORED.setIndexOptions(IndexOptions.DOCS);
+    PREFIX_TYPE_STORED.setDocValuesType(DocValuesType.SORTED_NUMERIC);
+    PREFIX_TYPE_STORED.setStored(true);
+    PREFIX_TYPE_STORED.freeze();
   }
 
-  /** Creates a stored or un-stored GeoPointField with the provided value
-   *  and default <code>precisionStep</code> set to 64 to avoid wasteful
-   *  indexing of lower precision terms.
+  /** Creates a stored or un-stored GeoPointField
    *  @param name field name
    *  @param lon longitude double value [-180.0 : 180.0]
    *  @param lat latitude double value [-90.0 : 90.0]
@@ -87,8 +133,20 @@ public final class GeoPointField extends Field {
    *  @throws IllegalArgumentException if the field name is null.
    */
   public GeoPointField(String name, double lon, double lat, Store stored) {
-    super(name, stored == Store.YES ? TYPE_STORED : TYPE_NOT_STORED);
-    fieldsData = GeoUtils.mortonHash(lon, lat);
+    this(name, lon, lat, getFieldType(stored));
+  }
+
+  /** Creates a stored or un-stored GeoPointField using the specified {@link TermEncoding} method
+   *  @param name field name
+   *  @param lon longitude double value [-180.0 : 180.0]
+   *  @param lat latitude double value [-90.0 : 90.0]
+   *  @param termEncoding encoding type to use ({@link TermEncoding#NUMERIC} Terms, or {@link TermEncoding#PREFIX} only Terms)
+   *  @param stored Store.YES if the content should also be stored
+   *  @throws IllegalArgumentException if the field name is null.
+   */
+  @Deprecated
+  public GeoPointField(String name, double lon, double lat, TermEncoding termEncoding, Store stored) {
+    this(name, lon, lat, getFieldType(termEncoding, stored));
   }
 
   /** Expert: allows you to customize the {@link
@@ -103,23 +161,78 @@ public final class GeoPointField extends Field {
    */
   public GeoPointField(String name, double lon, double lat, FieldType type) {
     super(name, type);
-    if (type.numericType() != FieldType.NumericType.LONG) {
-      throw new IllegalArgumentException("type.numericType() must be LONG but got " + type.numericType());
+    // field must be indexed
+    // todo does it make sense here to provide the ability to store a GeoPointField but not index?
+    if (type.indexOptions() == IndexOptions.NONE && type.stored() == false) {
+      throw new IllegalArgumentException("type.indexOptions() is set to NONE but type.stored() is false");
+    } else if (type.indexOptions() == IndexOptions.DOCS) {
+      if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
+        throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
+      }
+      if (type.numericType() != null) {
+        // make sure numericType is a LONG
+        if (type.numericType() != FieldType.NumericType.LONG) {
+          throw new IllegalArgumentException("type.numericType() must be LONG but got " + type.numericType());
+        }
+      }
+    } else {
+      throw new IllegalArgumentException("type.indexOptions() must be one of NONE or DOCS but got " + type.indexOptions());
     }
-    if (type.docValuesType() != DocValuesType.SORTED_NUMERIC) {
-      throw new IllegalArgumentException("type.docValuesType() must be SORTED_NUMERIC but got " + type.docValuesType());
+
+    // set field data
+    fieldsData = GeoEncodingUtils.mortonHash(lon, lat);
+  }
+
+  private static FieldType getFieldType(Store stored) {
+    return getFieldType(TermEncoding.PREFIX, stored);
+  }
+
+  /**
+   * @deprecated
+   * Static helper method for returning a valid FieldType based on termEncoding and stored options
+   */
+  @Deprecated
+  private static FieldType getFieldType(TermEncoding termEncoding, Store stored) {
+    if (stored == Store.YES) {
+      return termEncoding == TermEncoding.PREFIX ? PREFIX_TYPE_STORED : NUMERIC_TYPE_STORED;
+    } else if (stored == Store.NO) {
+      return termEncoding == TermEncoding.PREFIX ? PREFIX_TYPE_NOT_STORED : NUMERIC_TYPE_NOT_STORED;
+    } else {
+      throw new IllegalArgumentException("stored option must be NO or YES but got " + stored);
     }
-    fieldsData = GeoUtils.mortonHash(lon, lat);
+  }
+
+  @Override
+  public TokenStream tokenStream(Analyzer analyzer, TokenStream reuse) {
+    if (fieldType().indexOptions() == IndexOptions.NONE) {
+      // not indexed
+      return null;
+    }
+
+    // if numericType is set
+    if (type.numericType() != null) {
+      // return numeric encoding
+      return super.tokenStream(analyzer, reuse);
+    }
+
+    if (reuse instanceof GeoPointTokenStream == false) {
+      reuse = new GeoPointTokenStream();
+    }
+
+    final GeoPointTokenStream gpts = (GeoPointTokenStream)reuse;
+    gpts.setGeoCode(((Number) fieldsData).longValue());
+
+    return reuse;
   }
 
   /** access longitude value */
   public double getLon() {
-    return GeoUtils.mortonUnhashLon((long) fieldsData);
+    return GeoEncodingUtils.mortonUnhashLon((long) fieldsData);
   }
 
   /** access latitude value */
   public double getLat() {
-    return GeoUtils.mortonUnhashLat((long) fieldsData);
+    return GeoEncodingUtils.mortonUnhashLat((long) fieldsData);
   }
 
   @Override
@@ -128,9 +241,9 @@ public final class GeoPointField extends Field {
       return null;
     }
     StringBuilder sb = new StringBuilder();
-    sb.append(GeoUtils.mortonUnhashLon((long) fieldsData));
+    sb.append(GeoEncodingUtils.mortonUnhashLon((long) fieldsData));
     sb.append(',');
-    sb.append(GeoUtils.mortonUnhashLat((long) fieldsData));
+    sb.append(GeoEncodingUtils.mortonUnhashLat((long) fieldsData));
     return sb.toString();
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointTokenStream.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointTokenStream.java b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointTokenStream.java
new file mode 100644
index 0000000..e22d446
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/document/GeoPointTokenStream.java
@@ -0,0 +1,233 @@
+package org.apache.lucene.spatial.document;
+
+/*
+ * 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.
+ */
+
+import java.util.Objects;
+
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.analysis.tokenattributes.TermToBytesRefAttribute;
+import org.apache.lucene.util.Attribute;
+import org.apache.lucene.util.AttributeFactory;
+import org.apache.lucene.util.AttributeImpl;
+import org.apache.lucene.util.AttributeReflector;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.spatial.util.GeoEncodingUtils;
+
+import static org.apache.lucene.spatial.document.GeoPointField.PRECISION_STEP;
+
+/**
+ * <b>Expert:</b> This class provides a {@link TokenStream} used by {@link GeoPointField}
+ * for encoding {@link GeoPointField.TermEncoding#PREFIX} only GeoPointTerms.
+ *
+ * <p><i>NOTE: This is used as the default encoding unless
+ * {@code GeoPointField.setNumericType(FieldType.LegacyNumericType.LONG)} is set</i></p>
+ *
+ * This class is similar to {@link org.apache.lucene.analysis.LegacyNumericTokenStream} but encodes terms up to a
+ * a maximum of {@link #MAX_SHIFT} using a fixed precision step defined by
+ * {@link GeoPointField#PRECISION_STEP}. This yields a total of 4 terms per GeoPoint
+ * each consisting of 5 bytes (4 prefix bytes + 1 precision byte).
+ *
+ * <p>For best performance use the provided {@link GeoPointField#PREFIX_TYPE_NOT_STORED} or
+ * {@link GeoPointField#PREFIX_TYPE_STORED}</p>
+ *
+ * <p>If prefix terms are used then the default GeoPoint query constructors may be used, but if
+ * {@link org.apache.lucene.analysis.LegacyNumericTokenStream} is used, then be sure to pass
+ * {@link GeoPointField.TermEncoding#NUMERIC} to all GeoPointQuery constructors</p>
+ *
+ * Here's an example usage:
+ *
+ * <pre class="prettyprint">
+ *   // using prefix terms
+ *   GeoPointField geoPointField = new GeoPointField(fieldName1, lon, lat, GeoPointField.PREFIX_TYPE_NOT_STORED);
+ *   document.add(geoPointField);
+ *
+ *   // query by bounding box (default uses TermEncoding.PREFIX)
+ *   Query q = new GeoPointInBBoxQuery(fieldName1, minLon, minLat, maxLon, maxLat);
+ *
+ *   // using numeric terms
+ *   geoPointField = new GeoPointField(fieldName2, lon, lat, GeoPointField.NUMERIC_TYPE_NOT_STORED);
+ *   document.add(geoPointField);
+ *
+ *   // query by distance (requires TermEncoding.NUMERIC)
+ *   q = new GeoPointDistanceQuery(fieldName2, TermEncoding.NUMERIC, centerLon, centerLat, radiusMeters);
+ * </pre>
+ *
+ * @lucene.experimental
+ */
+final class GeoPointTokenStream extends TokenStream {
+  private static final int MAX_SHIFT = PRECISION_STEP * 4;
+
+  private final GeoPointTermAttribute geoPointTermAtt = addAttribute(GeoPointTermAttribute.class);
+  private final PositionIncrementAttribute posIncrAtt = addAttribute(PositionIncrementAttribute.class);
+
+  private boolean isInit = false;
+
+  /**
+   * Expert: Creates a token stream for geo point fields with the specified
+   * <code>precisionStep</code> using the given
+   * {@link org.apache.lucene.util.AttributeFactory}.
+   * The stream is not yet initialized,
+   * before using set a value using the various set<em>???</em>Value() methods.
+   */
+  public GeoPointTokenStream() {
+    super(new GeoPointAttributeFactory(AttributeFactory.DEFAULT_ATTRIBUTE_FACTORY));
+    assert PRECISION_STEP > 0;
+  }
+
+  public GeoPointTokenStream setGeoCode(final long geoCode) {
+    geoPointTermAtt.init(geoCode, MAX_SHIFT-PRECISION_STEP);
+    isInit = true;
+    return this;
+  }
+
+  @Override
+  public void reset() {
+    if (isInit == false) {
+      throw new IllegalStateException("call setGeoCode() before usage");
+    }
+  }
+
+  @Override
+  public boolean incrementToken() {
+    if (isInit == false) {
+      throw new IllegalStateException("call setGeoCode() before usage");
+    }
+
+    // this will only clear all other attributes in this TokenStream
+    clearAttributes();
+
+    final int shift = geoPointTermAtt.incShift();
+    posIncrAtt.setPositionIncrement((shift == MAX_SHIFT) ? 1 : 0);
+    return (shift < 63);
+  }
+
+  /**
+   * Tracks shift values during encoding
+   */
+  public interface GeoPointTermAttribute extends Attribute {
+    /** Returns current shift value, undefined before first token */
+    int getShift();
+
+    /** <em>Don't call this method!</em>
+     * @lucene.internal */
+    void init(long value, int shift);
+
+    /** <em>Don't call this method!</em>
+     * @lucene.internal */
+    int incShift();
+  }
+
+  // just a wrapper to prevent adding CTA
+  private static final class GeoPointAttributeFactory extends AttributeFactory {
+    private final AttributeFactory delegate;
+
+    GeoPointAttributeFactory(AttributeFactory delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public AttributeImpl createAttributeInstance(Class<? extends Attribute> attClass) {
+      if (CharTermAttribute.class.isAssignableFrom(attClass)) {
+        throw new IllegalArgumentException("GeoPointTokenStream does not support CharTermAttribute.");
+      }
+      return delegate.createAttributeInstance(attClass);
+    }
+  }
+
+  public static final class GeoPointTermAttributeImpl extends AttributeImpl implements GeoPointTermAttribute,TermToBytesRefAttribute {
+    private long value = 0L;
+    private int shift = 0;
+    private BytesRefBuilder bytes = new BytesRefBuilder();
+
+    public GeoPointTermAttributeImpl() {
+      this.shift = MAX_SHIFT-PRECISION_STEP;
+    }
+
+    @Override
+    public BytesRef getBytesRef() {
+      GeoEncodingUtils.geoCodedToPrefixCoded(value, shift, bytes);
+      return bytes.get();
+    }
+
+    @Override
+    public void init(long value, int shift) {
+      this.value = value;
+      this.shift = shift;
+    }
+
+    @Override
+    public int getShift() { return shift; }
+
+    @Override
+    public int incShift() {
+      return (shift += PRECISION_STEP);
+    }
+
+    @Override
+    public void clear() {
+      // this attribute has no contents to clear!
+      // we keep it untouched as it's fully controlled by outer class.
+    }
+
+    @Override
+    public void reflectWith(AttributeReflector reflector) {
+      reflector.reflect(TermToBytesRefAttribute.class, "bytes", getBytesRef());
+      reflector.reflect(GeoPointTermAttribute.class, "shift", shift);
+    }
+
+    @Override
+    public void copyTo(AttributeImpl target) {
+      final GeoPointTermAttribute a = (GeoPointTermAttribute) target;
+      a.init(value, shift);
+    }
+
+    @Override
+    public GeoPointTermAttributeImpl clone() {
+      GeoPointTermAttributeImpl t = (GeoPointTermAttributeImpl)super.clone();
+      // Do a deep clone
+      t.bytes = new BytesRefBuilder();
+      t.bytes.copyBytes(getBytesRef());
+      return t;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(shift, value);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      GeoPointTermAttributeImpl other = (GeoPointTermAttributeImpl) obj;
+      if (shift != other.shift) return false;
+      if (value != other.value) return false;
+      return true;
+    }
+  }
+
+  /** override toString because it can throw cryptic "illegal shift value": */
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + "(precisionStep=" + PRECISION_STEP + " shift=" + geoPointTermAtt.getShift() + ")";
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
index 26b0a24..60aadbc 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQuery.java
@@ -22,6 +22,7 @@ import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.GeoDistanceUtils;
 import org.apache.lucene.spatial.util.GeoRect;
 import org.apache.lucene.spatial.util.GeoUtils;
@@ -34,7 +35,7 @@ import org.apache.lucene.spatial.util.GeoUtils;
  * passing this initial filter are then passed to a secondary {@code postFilter} method that verifies whether the
  * decoded lat/lon point fall within the specified query distance (see {@link org.apache.lucene.util.SloppyMath#haversin}.
  * All morton value comparisons are subject to the same precision tolerance defined in
- * {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE} and distance comparisons are subject to the accuracy of the
+ * {@value org.apache.lucene.spatial.util.GeoEncodingUtils#TOLERANCE} and distance comparisons are subject to the accuracy of the
  * haversine formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
  *
  * <p>Note: This query currently uses haversine which is a sloppy distance calculation (see above reference). For large
@@ -55,14 +56,20 @@ public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
    * distance (in meters) from a given point
    **/
   public GeoPointDistanceQuery(final String field, final double centerLon, final double centerLat, final double radiusMeters) {
-    this(field, GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters), centerLon, centerLat, radiusMeters);
+    this(field, TermEncoding.PREFIX, centerLon, centerLat, radiusMeters);
   }
 
-  private GeoPointDistanceQuery(final String field, GeoRect bbox, final double centerLon,
+  public GeoPointDistanceQuery(final String field, final TermEncoding termEncoding, final double centerLon, final double centerLat, final double radiusMeters) {
+    this(field, termEncoding, GeoUtils.circleToBBox(centerLon, centerLat, radiusMeters), centerLon, centerLat, radiusMeters);
+  }
+
+  private GeoPointDistanceQuery(final String field, final TermEncoding termEncoding, final GeoRect bbox, final double centerLon,
                                 final double centerLat, final double radiusMeters) {
-    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+    super(field, termEncoding, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
     {
-      // check longitudinal overlap (limits radius)
+      // check longitudinal overlap (restrict distance to maximum longitudinal radius)
+      // todo this restriction technically shouldn't be needed,
+      // its only purpose is to ensure the bounding box doesn't self overlap.
       final double maxRadius = GeoDistanceUtils.maxRadialDistanceMeters(centerLon, centerLat);
       if (radiusMeters > maxRadius) {
         throw new IllegalArgumentException("radiusMeters " + radiusMeters + " exceeds maxRadius [" + maxRadius
@@ -102,7 +109,7 @@ public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
         // unwrap left
         unwrappedLon += -360.0D;
       }
-      GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
+      GeoPointDistanceQueryImpl left = new GeoPointDistanceQueryImpl(field, termEncoding, this, unwrappedLon,
           new GeoRect(GeoUtils.MIN_LON_INCL, maxLon, minLat, maxLat));
       bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
 
@@ -110,13 +117,14 @@ public class GeoPointDistanceQuery extends GeoPointInBBoxQuery {
         // unwrap right
         unwrappedLon += 360.0D;
       }
-      GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, this, unwrappedLon,
+      GeoPointDistanceQueryImpl right = new GeoPointDistanceQueryImpl(field, termEncoding, this, unwrappedLon,
           new GeoRect(minLon, GeoUtils.MAX_LON_INCL, minLat, maxLat));
       bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
 
       return bqb.build();
     }
-    return new GeoPointDistanceQueryImpl(field, this, centerLon, new GeoRect(this.minLon, this.maxLon, this.minLat, this.maxLat));
+    return new GeoPointDistanceQueryImpl(field, termEncoding, this, centerLon,
+        new GeoRect(this.minLon, this.maxLon, this.minLat, this.maxLat));
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
index 0777799..39fc696 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceQueryImpl.java
@@ -16,13 +16,8 @@
  */
 package org.apache.lucene.spatial.search;
 
-import java.io.IOException;
-
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.util.AttributeSource;
-import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.GeoRect;
 import org.apache.lucene.spatial.util.GeoRelationUtils;
 import org.apache.lucene.util.SloppyMath;
@@ -32,65 +27,46 @@ import org.apache.lucene.util.SloppyMath;
  *    @lucene.experimental
  */
 final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
-  private final GeoPointDistanceQuery query;
+  private final GeoPointDistanceQuery distanceQuery;
   private final double centerLon;
 
-  GeoPointDistanceQueryImpl(final String field, final GeoPointDistanceQuery q, final double centerLonUnwrapped,
-                            final GeoRect bbox) {
-    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
-    query = q;
+  GeoPointDistanceQueryImpl(final String field, final TermEncoding termEncoding, final GeoPointDistanceQuery q,
+                            final double centerLonUnwrapped, final GeoRect bbox) {
+    super(field, termEncoding, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+    distanceQuery = q;
     centerLon = centerLonUnwrapped;
   }
 
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPointRadiusTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
-  }
-
   @Override
   public void setRewriteMethod(MultiTermQuery.RewriteMethod method) {
     throw new UnsupportedOperationException("cannot change rewrite method");
   }
 
-  private final class GeoPointRadiusTermsEnum extends GeoPointTermsEnum {
-    GeoPointRadiusTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                            final double maxLon, final double maxLat) {
-      super(tenum, minLon, minLat, maxLon, maxLat);
-    }
-
-    /**
-     * Computes the maximum shift for the given pointDistanceQuery. This prevents unnecessary depth traversal
-     * given the size of the distance query.
-     */
-    @Override
-    protected short computeMaxShift() {
-      final short shiftFactor;
-
-      if (query.radiusMeters > 1000000) {
-        shiftFactor = 5;
-      } else {
-        shiftFactor = 4;
-      }
+  @Override
+  protected CellComparator newCellComparator() {
+    return new GeoPointRadiusCellComparator(this);
+  }
 
-      return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+  private final class GeoPointRadiusCellComparator extends CellComparator {
+    GeoPointRadiusCellComparator(GeoPointDistanceQueryImpl query) {
+      super(query);
     }
 
     @Override
     protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
       return GeoRelationUtils.rectCrossesCircle(minLon, minLat, maxLon, maxLat,
-          centerLon, query.centerLat, query.radiusMeters, true);
+          centerLon, distanceQuery.centerLat, distanceQuery.radiusMeters, true);
     }
 
     @Override
     protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
       return GeoRelationUtils.rectWithinCircle(minLon, minLat, maxLon, maxLat,
-          centerLon, query.centerLat, query.radiusMeters, true);
+          centerLon, distanceQuery.centerLat, distanceQuery.radiusMeters, true);
     }
 
     @Override
     protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return (cellContains(minLon, minLat, maxLon, maxLat)
-          || cellWithin(minLon, minLat, maxLon, maxLat) || cellCrosses(minLon, minLat, maxLon, maxLat));
+      return cellCrosses(minLon, minLat, maxLon, maxLat);
     }
 
     /**
@@ -101,7 +77,7 @@ final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
      */
     @Override
     protected boolean postFilter(final double lon, final double lat) {
-      return (SloppyMath.haversin(query.centerLat, centerLon, lat, lon) * 1000.0 <= query.radiusMeters);
+      return (SloppyMath.haversin(distanceQuery.centerLat, centerLon, lat, lon) * 1000.0 <= distanceQuery.radiusMeters);
     }
   }
 
@@ -113,7 +89,7 @@ final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
 
     GeoPointDistanceQueryImpl that = (GeoPointDistanceQueryImpl) o;
 
-    if (!query.equals(that.query)) return false;
+    if (!distanceQuery.equals(that.distanceQuery)) return false;
 
     return true;
   }
@@ -121,11 +97,11 @@ final class GeoPointDistanceQueryImpl extends GeoPointInBBoxQueryImpl {
   @Override
   public int hashCode() {
     int result = super.hashCode();
-    result = 31 * result + query.hashCode();
+    result = 31 * result + distanceQuery.hashCode();
     return result;
   }
 
   public double getRadiusMeters() {
-    return query.getRadiusMeters();
+    return distanceQuery.getRadiusMeters();
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
index 2173054..59a2da7 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointDistanceRangeQuery.java
@@ -22,6 +22,7 @@ import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
 
 /** Implements a point distance range query on a GeoPoint field. This is based on
  * {@code org.apache.lucene.spatial.search.GeoPointDistanceQuery} and is implemented using a
@@ -38,8 +39,13 @@ public final class GeoPointDistanceRangeQuery extends GeoPointDistanceQuery {
    * distance (in meters) range from a given point
    */
   public GeoPointDistanceRangeQuery(final String field, final double centerLon, final double centerLat,
+                                    final double minRadiusMeters, final double maxRadiusMeters) {
+    this(field, TermEncoding.PREFIX, centerLon, centerLat, minRadiusMeters, maxRadiusMeters);
+  }
+
+  public GeoPointDistanceRangeQuery(final String field, final TermEncoding termEncoding, final double centerLon, final double centerLat,
                                     final double minRadiusMeters, final double maxRadius) {
-    super(field, centerLon, centerLat, maxRadius);
+    super(field, termEncoding, centerLon, centerLat, maxRadius);
     this.minRadiusMeters = minRadiusMeters;
   }
 
@@ -57,8 +63,13 @@ public final class GeoPointDistanceRangeQuery extends GeoPointDistanceQuery {
     BooleanQuery.Builder bqb = new BooleanQuery.Builder();
 
     // create a new exclusion query
-    GeoPointDistanceQuery exclude = new GeoPointDistanceQuery(field, centerLon, centerLat, minRadiusMeters);
-    bqb.add(new BooleanClause(q, BooleanClause.Occur.MUST));
+    GeoPointDistanceQuery exclude = new GeoPointDistanceQuery(field, termEncoding, centerLon, centerLat, minRadiusMeters);
+    // full map search
+//    if (radiusMeters >= GeoProjectionUtils.SEMIMINOR_AXIS) {
+//      bqb.add(new BooleanClause(new GeoPointInBBoxQuery(this.field, -180.0, -90.0, 180.0, 90.0), BooleanClause.Occur.MUST));
+//    } else {
+      bqb.add(new BooleanClause(q, BooleanClause.Occur.MUST));
+//    }
     bqb.add(new BooleanClause(exclude, BooleanClause.Occur.MUST_NOT));
 
     return bqb.build();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
index a216aa0..b7fcf70 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQuery.java
@@ -24,6 +24,7 @@ import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.FieldValueQuery;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.GeoUtils;
 
 /** Implements a simple bounding box query on a GeoPoint field. This is inspired by
@@ -51,17 +52,23 @@ public class GeoPointInBBoxQuery extends Query {
   protected final double minLat;
   protected final double maxLon;
   protected final double maxLat;
+  protected final TermEncoding termEncoding;
 
   /**
    * Constructs a query for all {@link org.apache.lucene.spatial.document.GeoPointField} types that fall within a
    * defined bounding box
    */
   public GeoPointInBBoxQuery(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    this(field, TermEncoding.PREFIX, minLon, minLat, maxLon, maxLat);
+  }
+
+  public GeoPointInBBoxQuery(final String field, final TermEncoding termEncoding, final double minLon, final double minLat, final double maxLon, final double maxLat) {
     this.field = field;
     this.minLon = minLon;
     this.minLat = minLat;
     this.maxLon = maxLon;
     this.maxLat = maxLat;
+    this.termEncoding = termEncoding;
   }
 
   @Override
@@ -78,15 +85,15 @@ public class GeoPointInBBoxQuery extends Query {
     }
 
     if (maxLon < minLon) {
-      BooleanQuery.Builder bq = new BooleanQuery.Builder();
+      BooleanQuery.Builder bqb = new BooleanQuery.Builder();
 
-      GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, -180.0D, minLat, maxLon, maxLat);
-      bq.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
-      GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, minLon, minLat, 180.0D, maxLat);
-      bq.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
-      return bq.build();
+      GeoPointInBBoxQueryImpl left = new GeoPointInBBoxQueryImpl(field, termEncoding, -180.0D, minLat, maxLon, maxLat);
+      bqb.add(new BooleanClause(left, BooleanClause.Occur.SHOULD));
+      GeoPointInBBoxQueryImpl right = new GeoPointInBBoxQueryImpl(field, termEncoding, minLon, minLat, 180.0D, maxLat);
+      bqb.add(new BooleanClause(right, BooleanClause.Occur.SHOULD));
+      return bqb.build();
     }
-    return new GeoPointInBBoxQueryImpl(field, minLon, minLat, maxLon, maxLat);
+    return new GeoPointInBBoxQueryImpl(field, termEncoding, minLon, minLat, maxLon, maxLat);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
index bd44b1e..8bce0f0 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInBBoxQueryImpl.java
@@ -16,21 +16,17 @@
  */
 package org.apache.lucene.spatial.search;
 
-import java.io.IOException;
-
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.util.AttributeSource;
 import org.apache.lucene.util.SloppyMath;
 import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
 import org.apache.lucene.spatial.util.GeoRelationUtils;
 
 /** Package private implementation for the public facing GeoPointInBBoxQuery delegate class.
  *
  *    @lucene.experimental
  */
-class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
+class GeoPointInBBoxQueryImpl extends GeoPointMultiTermQuery {
   /**
    * Constructs a new GeoBBoxQuery that will match encoded GeoPoint terms that fall within or on the boundary
    * of the bounding box defined by the input parameters
@@ -40,13 +36,8 @@ class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
    * @param maxLon upper longitude (x) value of the bounding box
    * @param maxLat upper latitude (y) value of the bounding box
    */
-  GeoPointInBBoxQueryImpl(final String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    super(field, minLon, minLat, maxLon, maxLat);
-  }
-
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPointInBBoxTermsEnum(terms.iterator(), minLon, minLat, maxLon, maxLat);
+  GeoPointInBBoxQueryImpl(final String field, final TermEncoding termEncoding, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    super(field, termEncoding, minLon, minLat, maxLon, maxLat);
   }
 
   @Override
@@ -54,27 +45,31 @@ class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
     throw new UnsupportedOperationException("cannot change rewrite method");
   }
 
-  protected class GeoPointInBBoxTermsEnum extends GeoPointTermsEnum {
-    protected GeoPointInBBoxTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                            final double maxLon, final double maxLat) {
-      super(tenum, minLon, minLat, maxLon, maxLat);
-    }
+  @Override
+  protected short computeMaxShift() {
+    final short shiftFactor;
 
-    @Override
-    protected short computeMaxShift() {
-      final short shiftFactor;
+    // compute diagonal radius
+    double midLon = (minLon + maxLon) * 0.5;
+    double midLat = (minLat + maxLat) * 0.5;
 
-      // compute diagonal radius
-      double midLon = (minLon + maxLon) * 0.5;
-      double midLat = (minLat + maxLat) * 0.5;
+    if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
+      shiftFactor = 5;
+    } else {
+      shiftFactor = 4;
+    }
 
-      if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
-        shiftFactor = 5;
-      } else {
-        shiftFactor = 4;
-      }
+    return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+  }
+
+  @Override
+  protected CellComparator newCellComparator() {
+    return new GeoPointInBBoxCellComparator(this);
+  }
 
-      return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+  private final class GeoPointInBBoxCellComparator extends CellComparator {
+    GeoPointInBBoxCellComparator(GeoPointMultiTermQuery query) {
+      super(query);
     }
 
     /**
@@ -82,16 +77,16 @@ class GeoPointInBBoxQueryImpl extends GeoPointTermQuery {
      */
     @Override
     protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectCrosses(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
-    }
+      return GeoRelationUtils.rectCrosses(minLon, minLat, maxLon, maxLat, GeoPointInBBoxQueryImpl.this.minLon,
+          GeoPointInBBoxQueryImpl.this.minLat, GeoPointInBBoxQueryImpl.this.maxLon, GeoPointInBBoxQueryImpl.this.maxLat);    }
 
     /**
      * Determine whether quad-cell is within the shape
      */
     @Override
     protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectWithin(minLon, minLat, maxLon, maxLat, this.minLon, this.minLat, this.maxLon, this.maxLat);
-    }
+      return GeoRelationUtils.rectWithin(minLon, minLat, maxLon, maxLat, GeoPointInBBoxQueryImpl.this.minLon,
+          GeoPointInBBoxQueryImpl.this.minLat, GeoPointInBBoxQueryImpl.this.maxLon, GeoPointInBBoxQueryImpl.this.maxLat);    }
 
     @Override
     protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
index b1e864b..a9be21e 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
@@ -22,6 +22,8 @@ import java.util.Arrays;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
+import org.apache.lucene.spatial.util.GeoEncodingUtils;
 import org.apache.lucene.spatial.util.GeoRect;
 import org.apache.lucene.spatial.util.GeoRelationUtils;
 import org.apache.lucene.spatial.util.GeoUtils;
@@ -34,7 +36,11 @@ import org.apache.lucene.spatial.util.GeoUtils;
  * to a secondary filter that verifies whether the decoded lat/lon point falls within
  * (or on the boundary) of the bounding box query. Finally, the remaining candidate
  * term is passed to the final point in polygon check. All value comparisons are subject
+<<<<<<< HEAD:lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointInPolygonQuery.java
  * to the same precision tolerance defined in {@value org.apache.lucene.spatial.util.GeoUtils#TOLERANCE}
+=======
+ * to the same precision tolerance defined in {@value GeoEncodingUtils#TOLERANCE}
+>>>>>>> LUCENE-6930: Decouples GeoPointField from NumericType by using a custom GeoPointTokenStream and TermEnum designed for GeoPoint prefix terms:lucene/sandbox/src/java/org/apache/lucene/search/GeoPointInPolygonQuery.java
  *
  * <p>NOTES:
  *    1.  The polygon coordinates need to be in either clockwise or counter-clockwise order.
@@ -51,17 +57,21 @@ public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
   private final double[] x;
   private final double[] y;
 
+  public GeoPointInPolygonQuery(final String field, final double[] polyLons, final double[] polyLats) {
+    this(field, TermEncoding.PREFIX, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
+  }
+
   /**
    * Constructs a new GeoPolygonQuery that will match encoded {@link org.apache.lucene.spatial.document.GeoPointField} terms
    * that fall within or on the boundary of the polygon defined by the input parameters.
    */
-  public GeoPointInPolygonQuery(final String field, final double[] polyLons, final double[] polyLats) {
-    this(field, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
+  public GeoPointInPolygonQuery(final String field, final TermEncoding termEncoding, final double[] polyLons, final double[] polyLats) {
+    this(field, termEncoding, GeoUtils.polyToBBox(polyLons, polyLats), polyLons, polyLats);
   }
 
   /** Common constructor, used only internally. */
-  private GeoPointInPolygonQuery(final String field, GeoRect bbox, final double[] polyLons, final double[] polyLats) {
-    super(field, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
+  private GeoPointInPolygonQuery(final String field, TermEncoding termEncoding, GeoRect bbox, final double[] polyLons, final double[] polyLats) {
+    super(field, termEncoding, bbox.minLon, bbox.minLat, bbox.maxLon, bbox.maxLat);
     if (polyLats.length != polyLons.length) {
       throw new IllegalArgumentException("polyLats and polyLons must be equal length");
     }
@@ -79,11 +89,6 @@ public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
     this.y = polyLats;
   }
 
-  @Override @SuppressWarnings("unchecked")
-  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
-    return new GeoPolygonTermsEnum(terms.iterator(), this.minLon, this.minLat, this.maxLon, this.maxLat);
-  }
-
   /** throw exception if trying to change rewrite method */
   @Override
   public void setRewriteMethod(RewriteMethod method) {
@@ -91,6 +96,51 @@ public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
   }
 
   @Override
+  protected CellComparator newCellComparator() {
+    return new GeoPolygonCellComparator(this);
+  }
+
+  /**
+   * Custom {@link org.apache.lucene.index.TermsEnum} that computes morton hash ranges based on the defined edges of
+   * the provided polygon.
+   */
+  private final class GeoPolygonCellComparator extends CellComparator {
+    GeoPolygonCellComparator(GeoPointMultiTermQuery query) {
+      super(query);
+    }
+
+    @Override
+    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectCrossesPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
+          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
+    }
+
+    @Override
+    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectWithinPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
+          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
+    }
+
+    @Override
+    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return cellContains(minLon, minLat, maxLon, maxLat) || cellWithin(minLon, minLat, maxLon, maxLat)
+          || cellCrosses(minLon, minLat, maxLon, maxLat);
+    }
+
+    /**
+     * The two-phase query approach. The parent
+     * {@link org.apache.lucene.spatial.search.GeoPointTermsEnum#accept} method is called to match
+     * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
+     * bounding box filter are then compared to the provided polygon using the
+     * {@link org.apache.lucene.spatial.util.GeoRelationUtils#pointInPolygon} method.
+     */
+    @Override
+    protected boolean postFilter(final double lon, final double lat) {
+      return GeoRelationUtils.pointInPolygon(x, y, lat, lon);
+    }
+  }
+
+  @Override
   public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;
@@ -128,57 +178,16 @@ public final class GeoPointInPolygonQuery extends GeoPointInBBoxQueryImpl {
     sb.append(" Points: ");
     for (int i=0; i<x.length; ++i) {
       sb.append("[")
-        .append(x[i])
-        .append(", ")
-        .append(y[i])
-        .append("] ");
+          .append(x[i])
+          .append(", ")
+          .append(y[i])
+          .append("] ");
     }
 
     return sb.toString();
   }
 
   /**
-   * Custom {@link org.apache.lucene.index.TermsEnum} that computes morton hash ranges based on the defined edges of
-   * the provided polygon.
-   */
-  private final class GeoPolygonTermsEnum extends GeoPointTermsEnum {
-    GeoPolygonTermsEnum(final TermsEnum tenum, final double minLon, final double minLat,
-                        final double maxLon, final double maxLat) {
-      super(tenum, minLon, minLat, maxLon, maxLat);
-    }
-
-    @Override
-    protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectCrossesPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
-          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
-    }
-
-    @Override
-    protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return GeoRelationUtils.rectWithinPolyApprox(minLon, minLat, maxLon, maxLat, x, y, GeoPointInPolygonQuery.this.minLon,
-          GeoPointInPolygonQuery.this.minLat, GeoPointInPolygonQuery.this.maxLon, GeoPointInPolygonQuery.this.maxLat);
-    }
-
-    @Override
-    protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat) {
-      return cellContains(minLon, minLat, maxLon, maxLat) || cellWithin(minLon, minLat, maxLon, maxLat)
-          || cellCrosses(minLon, minLat, maxLon, maxLat);
-    }
-
-    /**
-     * The two-phase query approach. The parent
-     * {@link GeoPointTermsEnum#accept} method is called to match
-     * encoded terms that fall within the bounding box of the polygon. Those documents that pass the initial
-     * bounding box filter are then compared to the provided polygon using the
-     * {@link org.apache.lucene.spatial.util.GeoRelationUtils#pointInPolygon} method.
-     */
-    @Override
-    protected boolean postFilter(final double lon, final double lat) {
-      return GeoRelationUtils.pointInPolygon(x, y, lat, lon);
-    }
-  }
-
-  /**
    * API utility method for returning the array of longitudinal values for this GeoPolygon
    * The returned array is not a copy so do not change it!
    */

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointMultiTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointMultiTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointMultiTermQuery.java
new file mode 100644
index 0000000..f11c4a6
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointMultiTermQuery.java
@@ -0,0 +1,166 @@
+package org.apache.lucene.spatial.search;
+
+/*
+ * 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.
+ */
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Terms;
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.search.MultiTermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.AttributeSource;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.document.GeoPointField.TermEncoding;
+import org.apache.lucene.spatial.util.GeoEncodingUtils;
+import org.apache.lucene.spatial.util.GeoRelationUtils;
+import org.apache.lucene.spatial.util.GeoUtils;
+import org.apache.lucene.util.SloppyMath;
+
+/**
+ * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
+ * Geospatial operations
+ *
+ * @lucene.experimental
+ */
+abstract class GeoPointMultiTermQuery extends MultiTermQuery {
+  // simple bounding box optimization - no objects used to avoid dependencies
+  protected final double minLon;
+  protected final double minLat;
+  protected final double maxLon;
+  protected final double maxLat;
+  protected final short maxShift;
+  protected final TermEncoding termEncoding;
+  protected final CellComparator cellComparator;
+
+  /**
+   * Constructs a query matching terms that cannot be represented with a single
+   * Term.
+   */
+  public GeoPointMultiTermQuery(String field, final TermEncoding termEncoding, final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    super(field);
+
+    if (GeoUtils.isValidLon(minLon) == false) {
+      throw new IllegalArgumentException("invalid minLon " + minLon);
+    }
+    if (GeoUtils.isValidLon(maxLon) == false) {
+      throw new IllegalArgumentException("invalid maxLon " + maxLon);
+    }
+    if (GeoUtils.isValidLat(minLat) == false) {
+      throw new IllegalArgumentException("invalid minLat " + minLat);
+    }
+    if (GeoUtils.isValidLat(maxLat) == false) {
+      throw new IllegalArgumentException("invalid maxLat " + maxLat);
+    }
+
+    final long minHash = GeoEncodingUtils.mortonHash(minLon, minLat);
+    final long maxHash = GeoEncodingUtils.mortonHash(maxLon, maxLat);
+    this.minLon = GeoEncodingUtils.mortonUnhashLon(minHash);
+    this.minLat = GeoEncodingUtils.mortonUnhashLat(minHash);
+    this.maxLon = GeoEncodingUtils.mortonUnhashLon(maxHash);
+    this.maxLat = GeoEncodingUtils.mortonUnhashLat(maxHash);
+
+    this.maxShift = computeMaxShift();
+    this.termEncoding = termEncoding;
+    this.cellComparator = newCellComparator();
+
+    this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
+  }
+
+  public static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
+    @Override
+    public Query rewrite(IndexReader reader, MultiTermQuery query) {
+      return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointMultiTermQuery)query);
+    }
+  };
+
+  @Override @SuppressWarnings("unchecked")
+  protected TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException {
+    return GeoPointTermsEnum.newInstance(terms.iterator(), this);
+  }
+
+  /**
+   * Computes the maximum shift based on the diagonal distance of the bounding box
+   */
+  protected short computeMaxShift() {
+    // in this case a factor of 4 brings the detail level to ~0.002/0.001 degrees lon/lat respectively (or ~222m/111m)
+    final short shiftFactor;
+
+    // compute diagonal distance
+    double midLon = (minLon + maxLon) * 0.5;
+    double midLat = (minLat + maxLat) * 0.5;
+
+    if (SloppyMath.haversin(minLat, minLon, midLat, midLon)*1000 > 1000000) {
+      shiftFactor = 5;
+    } else {
+      shiftFactor = 4;
+    }
+
+    return (short)(GeoPointField.PRECISION_STEP * shiftFactor);
+  }
+
+  /**
+   * Abstract method to construct the class that handles all geo point relations
+   * (e.g., GeoPointInPolygon)
+   */
+  abstract protected CellComparator newCellComparator();
+
+  /**
+   * Base class for all geo point relation comparators
+   */
+  static abstract class CellComparator {
+    protected final GeoPointMultiTermQuery geoPointQuery;
+
+    CellComparator(GeoPointMultiTermQuery query) {
+      this.geoPointQuery = query;
+    }
+
+    /**
+     * Primary driver for cells intersecting shape boundaries
+     */
+    protected boolean cellIntersectsMBR(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectIntersects(minLon, minLat, maxLon, maxLat, geoPointQuery.minLon, geoPointQuery.minLat,
+          geoPointQuery.maxLon, geoPointQuery.maxLat);
+    }
+
+    /**
+     * Return whether quad-cell contains the bounding box of this shape
+     */
+    protected boolean cellContains(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+      return GeoRelationUtils.rectWithin(geoPointQuery.minLon, geoPointQuery.minLat, geoPointQuery.maxLon,
+          geoPointQuery.maxLat, minLon, minLat, maxLon, maxLat);
+    }
+
+    /**
+     * Determine whether the quad-cell crosses the shape
+     */
+    abstract protected boolean cellCrosses(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+    /**
+     * Determine whether quad-cell is within the shape
+     */
+    abstract protected boolean cellWithin(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+    /**
+     * Default shape is a rectangle, so this returns the same as {@code cellIntersectsMBR}
+     */
+    abstract protected boolean cellIntersectsShape(final double minLon, final double minLat, final double maxLon, final double maxLat);
+
+    abstract protected boolean postFilter(final double lon, final double lat);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointNumericTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointNumericTermsEnum.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointNumericTermsEnum.java
new file mode 100644
index 0000000..566d917
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointNumericTermsEnum.java
@@ -0,0 +1,161 @@
+package org.apache.lucene.spatial.search;
+
+/*
+ * 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.
+ */
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.util.GeoEncodingUtils;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.util.NumericUtils;
+
+/**
+ * Decomposes a given {@link GeoPointMultiTermQuery} into a set of terms that represent the query criteria using
+ * {@link import org.apache.lucene.document.GeoPointTokenStream.TermEncoding#NUMERIC} method defined by
+ * {@link org.apache.lucene.analysis.NumericTokenStream}. The terms are then enumerated by the
+ * {@link GeoPointTermQueryConstantScoreWrapper} and all docs whose GeoPoint fields match the prefix terms or
+ * pass the {@link GeoPointMultiTermQuery.CellComparator#postFilter} criteria are returned in the resulting DocIdSet.
+ *
+ *  @lucene.experimental
+ */
+@Deprecated
+final class GeoPointNumericTermsEnum extends GeoPointTermsEnum {
+  private final List<Range> rangeBounds = new LinkedList<>();
+
+  // detail level should be a factor of PRECISION_STEP limiting the depth of recursion (and number of ranges)
+  private final short DETAIL_LEVEL;
+
+  GeoPointNumericTermsEnum(final TermsEnum tenum, final GeoPointMultiTermQuery query) {
+    super(tenum, query);
+    DETAIL_LEVEL = (short)(((GeoEncodingUtils.BITS<<1)-this.maxShift)/2);
+    computeRange(0L, (short) (((GeoEncodingUtils.BITS) << 1) - 1));
+    assert rangeBounds.isEmpty() == false;
+    Collections.sort(rangeBounds);
+  }
+
+  /**
+   * entry point for recursively computing ranges
+   */
+  private final void computeRange(long term, final short shift) {
+    final long split = term | (0x1L<<shift);
+    assert shift < 64;
+    final long upperMax;
+    if (shift < 63) {
+      upperMax = term | ((1L << (shift+1))-1);
+    } else {
+      upperMax = 0xffffffffffffffffL;
+    }
+    final long lowerMax = split-1;
+
+    relateAndRecurse(term, lowerMax, shift);
+    relateAndRecurse(split, upperMax, shift);
+  }
+
+  /**
+   * recurse to higher level precision cells to find ranges along the space-filling curve that fall within the
+   * query box
+   *
+   * @param start starting value on the space-filling curve for a cell at a given res
+   * @param end ending value on the space-filling curve for a cell at a given res
+   * @param res spatial res represented as a bit shift (MSB is lower res)
+   */
+  private void relateAndRecurse(final long start, final long end, final short res) {
+    final double minLon = GeoEncodingUtils.mortonUnhashLon(start);
+    final double minLat = GeoEncodingUtils.mortonUnhashLat(start);
+    final double maxLon = GeoEncodingUtils.mortonUnhashLon(end);
+    final double maxLat = GeoEncodingUtils.mortonUnhashLat(end);
+
+    final short level = (short)((GeoEncodingUtils.BITS<<1)-res>>>1);
+
+    // if cell is within and a factor of the precision step, or it crosses the edge of the shape add the range
+    final boolean within = res % GeoPointField.PRECISION_STEP == 0 && relationImpl.cellWithin(minLon, minLat, maxLon, maxLat);
+    if (within || (level == DETAIL_LEVEL && relationImpl.cellIntersectsShape(minLon, minLat, maxLon, maxLat))) {
+      final short nextRes = (short)(res-1);
+      if (nextRes % GeoPointField.PRECISION_STEP == 0) {
+        rangeBounds.add(new Range(start, nextRes, !within));
+        rangeBounds.add(new Range(start|(1L<<nextRes), nextRes, !within));
+      } else {
+        rangeBounds.add(new Range(start, res, !within));
+      }
+    } else if (level < DETAIL_LEVEL && relationImpl.cellIntersectsMBR(minLon, minLat, maxLon, maxLat)) {
+      computeRange(start, (short) (res - 1));
+    }
+  }
+
+  @Override
+  protected final BytesRef peek() {
+    rangeBounds.get(0).fillBytesRef(this.nextSubRangeBRB);
+    return nextSubRangeBRB.get();
+  }
+
+  @Override
+  protected void nextRange() {
+    currentRange = rangeBounds.remove(0);
+    super.nextRange();
+  }
+
+  @Override
+  protected final BytesRef nextSeekTerm(BytesRef term) {
+    while (hasNext()) {
+      if (currentRange == null) {
+        nextRange();
+      }
+      // if the new upper bound is before the term parameter, the sub-range is never a hit
+      if (term != null && term.compareTo(currentCell) > 0) {
+        nextRange();
+        if (!rangeBounds.isEmpty()) {
+          continue;
+        }
+      }
+      // never seek backwards, so use current term if lower bound is smaller
+      return (term != null && term.compareTo(currentCell) > 0) ? term : currentCell;
+    }
+
+    // no more sub-range enums available
+    assert rangeBounds.isEmpty();
+    return null;
+  }
+
+  @Override
+  protected final boolean hasNext() {
+    return rangeBounds.isEmpty() == false;
+  }
+
+  /**
+   * Internal class to represent a range along the space filling curve
+   */
+  protected final class Range extends BaseRange {
+    Range(final long lower, final short shift, boolean boundary) {
+      super(lower, shift, boundary);
+    }
+
+    /**
+     * Encode as a BytesRef using a reusable object. This allows us to lazily create the BytesRef (which is
+     * quite expensive), only when we need it.
+     */
+    @Override
+    protected void fillBytesRef(BytesRefBuilder result) {
+      assert result != null;
+      NumericUtils.longToPrefixCoded(start, shift, result);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointPrefixTermsEnum.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointPrefixTermsEnum.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointPrefixTermsEnum.java
new file mode 100644
index 0000000..239e959
--- /dev/null
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointPrefixTermsEnum.java
@@ -0,0 +1,237 @@
+package org.apache.lucene.spatial.search;
+
+/*
+ * 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.
+ */
+
+import org.apache.lucene.index.TermsEnum;
+import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.BytesRefBuilder;
+import org.apache.lucene.spatial.document.GeoPointField;
+import org.apache.lucene.spatial.util.GeoEncodingUtils;
+
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonHash;
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonUnhashLat;
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonUnhashLon;
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.geoCodedToPrefixCoded;
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.prefixCodedToGeoCoded;
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.getPrefixCodedShift;
+
+/**
+ * Decomposes a given {@link GeoPointMultiTermQuery} into a set of terms that represent the query criteria using
+ * {@link GeoPointField.TermEncoding#PREFIX} method defined by
+ * {@link GeoPointField}. The terms are then enumerated by the
+ * {@link GeoPointTermQueryConstantScoreWrapper} and all docs whose GeoPoint fields match the prefix terms or pass
+ * the {@link GeoPointMultiTermQuery.CellComparator#postFilter} criteria are returned in the
+ * resulting DocIdSet.
+ *
+ *  @lucene.experimental
+ */
+final class GeoPointPrefixTermsEnum extends GeoPointTermsEnum {
+  private final long start;
+
+  private short shift;
+
+  // current range as long
+  private long currStart;
+  private long currEnd;
+
+  private final Range nextRange = new Range(-1, shift, true);
+
+  private boolean hasNext = false;
+
+  private boolean withinOnly = false;
+  private long lastWithin;
+
+  public GeoPointPrefixTermsEnum(final TermsEnum tenum, final GeoPointMultiTermQuery query) {
+    super(tenum, query);
+    this.start = mortonHash(query.minLon, query.minLat);
+    this.currentRange = new Range(0, shift, true);
+    // start shift at maxShift value (from computeMaxShift)
+    this.shift = maxShift;
+    final long mask = (1L << shift) - 1;
+    this.currStart = start & ~mask;
+    this.currEnd = currStart | mask;
+  }
+
+  private boolean within(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    return relationImpl.cellWithin(minLon, minLat, maxLon, maxLat);
+  }
+
+  private boolean boundary(final double minLon, final double minLat, final double maxLon, final double maxLat) {
+    return shift == maxShift && relationImpl.cellIntersectsShape(minLon, minLat, maxLon, maxLat);
+  }
+
+  private boolean nextWithin() {
+    if (withinOnly == false) {
+      return false;
+    }
+    currStart += (1L << shift);
+    setNextRange(false);
+    currentRange.set(nextRange);
+    hasNext = true;
+
+    withinOnly = lastWithin != currStart;
+    if (withinOnly == false) advanceVariables();
+    return true;
+  }
+
+  private void nextRelation() {
+    double minLon = mortonUnhashLon(currStart);
+    double minLat = mortonUnhashLat(currStart);
+    double maxLon;
+    double maxLat;
+    boolean isWithin;
+    do {
+      maxLon = mortonUnhashLon(currEnd);
+      maxLat = mortonUnhashLat(currEnd);
+
+      // within or a boundary
+      if ((isWithin = within(minLon, minLat, maxLon, maxLat) == true) || boundary(minLon, minLat, maxLon, maxLat) == true) {
+        final int m;
+        if (isWithin == false || (m = shift % GeoPointField.PRECISION_STEP) == 0) {
+          setNextRange(isWithin == false);
+          advanceVariables();
+          break;
+        } else if (shift < 54) {
+          withinOnly = true;
+          shift = (short)(shift - m);
+          lastWithin = currEnd & ~((1L << shift) - 1);
+          setNextRange(false);
+          break;
+        }
+      }
+
+      // within cell but not at a depth factor of PRECISION_STEP
+      if (isWithin == true || (relationImpl.cellIntersectsMBR(minLon, minLat, maxLon , maxLat) == true && shift != maxShift)) {
+        // descend: currStart need not change since shift handles end of range
+        currEnd = currStart | (1L<<--shift) - 1;
+      } else {
+        advanceVariables();
+        minLon = mortonUnhashLon(currStart);
+        minLat = mortonUnhashLat(currStart);
+      }
+    } while(shift < 63);
+  }
+
+  private void setNextRange(final boolean boundary) {
+    nextRange.start = currStart;
+    nextRange.shift = shift;
+    nextRange.boundary = boundary;
+  }
+
+  private void advanceVariables() {
+    /** set next variables */
+    long shiftMask = 1L << shift;
+    // pop-up if shift bit is set
+    while ( (currStart & shiftMask) == shiftMask) {
+      shiftMask = 1L << ++shift;
+    }
+    final long shiftMOne = shiftMask - 1;
+    currStart = currStart & ~shiftMOne | shiftMask;
+    currEnd = currStart | shiftMOne;
+  }
+
+  @Override
+  protected final BytesRef peek() {
+    nextRange.fillBytesRef(nextSubRangeBRB);
+    return super.peek();
+  }
+
+  protected void seek(long term, short res) {
+    if (term < currStart && res < maxShift) {
+      throw new IllegalArgumentException("trying to seek backwards");
+    } else if (term == currStart) {
+      return;
+    }
+    shift = res;
+    currStart = term;
+    currEnd = currStart | ((1L<<shift)-1);
+    withinOnly = false;
+  }
+
+  @Override
+  protected void nextRange() {
+    hasNext = false;
+    super.nextRange();
+  }
+
+  @Override
+  protected final boolean hasNext() {
+    if (hasNext == true || nextWithin()) {
+      return true;
+    }
+    nextRelation();
+    if (currentRange.compareTo(nextRange) != 0) {
+      currentRange.set(nextRange);
+      return (hasNext = true);
+    }
+    return false;
+  }
+
+  @Override
+  protected final BytesRef nextSeekTerm(BytesRef term) {
+    while (hasNext()) {
+      nextRange();
+      if (term == null) {
+        return currentCell;
+      }
+
+      final int comparison = term.compareTo(currentCell);
+      if (comparison > 0) {
+        seek(GeoEncodingUtils.prefixCodedToGeoCoded(term), (short)(64-GeoEncodingUtils.getPrefixCodedShift(term)));
+        continue;
+      }
+      return currentCell;
+    }
+
+    // no more sub-range enums available
+    return null;
+  }
+
+  @Override
+  protected AcceptStatus accept(BytesRef term) {
+    // range < term or range is null
+    while (currentCell == null || term.compareTo(currentCell) > 0) {
+      // no more ranges, be gone
+      if (hasNext() == false) {
+        return AcceptStatus.END;
+      }
+
+      // peek next range, if the range > term then seek
+      final int peekCompare = term.compareTo(peek());
+      if (peekCompare < 0) {
+        return AcceptStatus.NO_AND_SEEK;
+      } else if (peekCompare > 0) {
+        seek(prefixCodedToGeoCoded(term), (short)(64 - getPrefixCodedShift(term)));
+      }
+      nextRange();
+    }
+    return AcceptStatus.YES;
+  }
+
+  protected final class Range extends BaseRange {
+    public Range(final long start, final short res, final boolean boundary) {
+      super(start, res, boundary);
+    }
+
+    @Override
+    protected void fillBytesRef(BytesRefBuilder result) {
+      assert result != null;
+      geoCodedToPrefixCoded(start, shift, result);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
deleted file mode 100644
index 894a1e9..0000000
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQuery.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.
- */
-package org.apache.lucene.spatial.search;
-
-import java.io.IOException;
-
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.Terms;
-import org.apache.lucene.index.TermsEnum;
-import org.apache.lucene.search.MultiTermQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.spatial.util.GeoUtils;
-import org.apache.lucene.util.AttributeSource;
-
-/**
- * TermQuery for GeoPointField for overriding {@link org.apache.lucene.search.MultiTermQuery} methods specific to
- * Geospatial operations
- *
- * @lucene.experimental
- */
-abstract class GeoPointTermQuery extends MultiTermQuery {
-  // simple bounding box optimization - no objects used to avoid dependencies
-  /** minimum longitude value (in degrees) */
-  protected final double minLon;
-  /** minimum latitude value (in degrees) */
-  protected final double minLat;
-  /** maximum longitude value (in degrees) */
-  protected final double maxLon;
-  /** maximum latitude value (in degrees) */
-  protected final double maxLat;
-
-  /**
-   * Constructs a query matching terms that cannot be represented with a single
-   * Term.
-   */
-  public GeoPointTermQuery(String field, final double minLon, final double minLat, final double maxLon, final double maxLat) {
-    super(field);
-
-    if (GeoUtils.isValidLon(minLon) == false) {
-      throw new IllegalArgumentException("invalid minLon " + minLon);
-    }
-    if (GeoUtils.isValidLon(maxLon) == false) {
-      throw new IllegalArgumentException("invalid maxLon " + maxLon);
-    }
-    if (GeoUtils.isValidLat(minLat) == false) {
-      throw new IllegalArgumentException("invalid minLat " + minLat);
-    }
-    if (GeoUtils.isValidLat(maxLat) == false) {
-      throw new IllegalArgumentException("invalid maxLat " + maxLat);
-    }
-    this.minLon = minLon;
-    this.minLat = minLat;
-    this.maxLon = maxLon;
-    this.maxLat = maxLat;
-
-    this.rewriteMethod = GEO_CONSTANT_SCORE_REWRITE;
-  }
-
-  private static final RewriteMethod GEO_CONSTANT_SCORE_REWRITE = new RewriteMethod() {
-    @Override
-    public Query rewrite(IndexReader reader, MultiTermQuery query) {
-      return new GeoPointTermQueryConstantScoreWrapper<>((GeoPointTermQuery)query);
-    }
-  };
-
-  /** override package protected method */
-  @Override
-  protected abstract TermsEnum getTermsEnum(final Terms terms, AttributeSource atts) throws IOException;
-
-  /** check if this instance equals another instance */
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-    if (!super.equals(o)) return false;
-
-    GeoPointTermQuery that = (GeoPointTermQuery) o;
-
-    if (Double.compare(that.minLon, minLon) != 0) return false;
-    if (Double.compare(that.minLat, minLat) != 0) return false;
-    if (Double.compare(that.maxLon, maxLon) != 0) return false;
-    return Double.compare(that.maxLat, maxLat) == 0;
-  }
-
-  /** compute hashcode */
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp;
-    temp = Double.doubleToLongBits(minLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(minLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLon);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    temp = Double.doubleToLongBits(maxLat);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    return result;
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/74a08c08/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
----------------------------------------------------------------------
diff --git a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
index 8176aec..1097add 100644
--- a/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
+++ b/lucene/spatial/src/java/org/apache/lucene/spatial/search/GeoPointTermQueryConstantScoreWrapper.java
@@ -33,15 +33,18 @@ import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.DocIdSetBuilder;
-import org.apache.lucene.spatial.util.GeoUtils;
+
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonUnhashLat;
+import static org.apache.lucene.spatial.util.GeoEncodingUtils.mortonUnhashLon;
+
 
 /**
- * Custom ConstantScoreWrapper for {@code GeoPointTermQuery} that cuts over to DocValues
+ * Custom ConstantScoreWrapper for {@code GeoPointMultiTermQuery} that cuts over to DocValues
  * for post filtering boundary ranges. Multi-valued GeoPoint documents are supported.
  *
  * @lucene.experimental
  */
-final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointTermQuery> extends Query {
+final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointMultiTermQuery> extends Query {
   protected final Q query;
 
   protected GeoPointTermQueryConstantScoreWrapper(Q query) {
@@ -95,7 +98,7 @@ final class GeoPointTermQueryConstantScoreWrapper <Q extends GeoPointTermQuery>
               sdv.setDocument(docId);
               for (int i=0; i<sdv.count(); ++i) {
                 hash = sdv.valueAt(i);
-                if (termsEnum.postFilter(GeoUtils.mortonUnhashLon(hash), GeoUtils.mortonUnhashLat(hash))) {
+                if (termsEnum.postFilter(mortonUnhashLon(hash), mortonUnhashLat(hash))) {
                   builder.add(docId);
                   break;
                 }