You are viewing a plain text version of this content. The canonical link for it is here.
Posted to solr-commits@lucene.apache.org by gs...@apache.org on 2009/12/29 03:58:47 UTC

svn commit: r894301 - in /lucene/solr/trunk: ./ example/solr/conf/ src/java/org/apache/solr/schema/ src/java/org/apache/solr/search/function/distance/ src/test/org/apache/solr/schema/ src/test/org/apache/solr/search/function/distance/ src/test/test-fil...

Author: gsingers
Date: Tue Dec 29 02:58:46 2009
New Revision: 894301

URL: http://svn.apache.org/viewvc?rev=894301&view=rev
Log:
SOLR-1586: added GeoHashField and SpatialTileField.  Refactored CoordinateFieldType a bit for sharing with SpatialTileField

Added:
    lucene/solr/trunk/src/java/org/apache/solr/schema/AbstractSubTypeFieldType.java   (with props)
    lucene/solr/trunk/src/java/org/apache/solr/schema/GeoHashField.java   (with props)
    lucene/solr/trunk/src/java/org/apache/solr/schema/SpatialTileField.java   (with props)
Modified:
    lucene/solr/trunk/CHANGES.txt
    lucene/solr/trunk/example/solr/conf/schema.xml
    lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java
    lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java
    lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java
    lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java
    lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
    lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml
    lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml

Modified: lucene/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Tue Dec 29 02:58:46 2009
@@ -77,6 +77,8 @@
 * SOLR-1131: FieldTypes can now output multiple Fields per Type and still be searched.  This can be handy for hiding the details of a particular
   implementation such as in the spatial case. (Chris Mattmann, shalin, noble, gsingers, yonik)
 
+* SOLR-1586: Add support for Geohash and Spatial Tile FieldType (Chris Mattmann, gsingers)  
+
 Optimizations
 ----------------------
 

Modified: lucene/solr/trunk/example/solr/conf/schema.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/solr/conf/schema.xml?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/example/solr/conf/schema.xml (original)
+++ lucene/solr/trunk/example/solr/conf/schema.xml Tue Dec 29 02:58:46 2009
@@ -409,6 +409,23 @@
      -->
     <fieldType name="location" class="solr.PointType" dimension="2" subFieldSuffix="_d"/>
 
+   <!--
+    A Geohash is a compact representation of a latitude longitude pair in a single field.
+
+    See http://wiki.apache.org/solr/SpatialSearch
+   -->
+    <fieldtype name="geohash" class="solr.GeoHashField"/>
+
+    <!--
+      A SpatialTielField is like a set of zoom levels on an interactive map (i.e. Google Maps or MapQuest).  It takes a lat/lon
+      field and indexes it into (end - start) different fields, each representing a different zoom level.
+      This can then be leveraged to quickly narrow the search space by creating a filter, at an appropriate tier level,
+      that only has to enumerate a minimum number of terms.
+
+      See http://wiki.apache.org/solr/SpatialSearch
+     -->
+    <fieldType name="tile" class="solr.SpatialTileField" start="4" end="15" subFieldSuffix="_tiled"/>
+    
  </types>
 
 
@@ -454,7 +471,8 @@
    <field name="inStock" type="boolean" indexed="true" stored="true" />
 
    <field name="store" type="location" indexed="true" stored="true"/>
-
+   <field name="store_hash" type="geohash" indexed="true" stored="false"/>
+   <field name="store_tiles" type="tile" indexed="true" stored="false"/>
 
    <!-- Common metadata fields, named specifically to match up with
      SolrCell metadata when parsing rich documents such as Word, PDF.
@@ -509,6 +527,9 @@
    <dynamicField name="*_b"  type="boolean" indexed="true"  stored="true"/>
    <dynamicField name="*_f"  type="float"  indexed="true"  stored="true"/>
    <dynamicField name="*_d"  type="double" indexed="true"  stored="true"/>
+
+   <dynamicField name="*_tiled"  type="double" indexed="true"  stored="false"/>
+
    <dynamicField name="*_dt" type="date"    indexed="true"  stored="true"/>
    <dynamicField name="*_p"  type="location" indexed="true" stored="true"/>
 
@@ -550,6 +571,8 @@
         or to add multiple fields to the same field for easier/faster searching.  -->
 
    <copyField source="cat" dest="text"/>
+   <copyField source="store" dest="store_hash"/>
+   <copyField source="store" dest="store_tiles"/>
    <copyField source="name" dest="text"/>
    <copyField source="manu" dest="text"/>
    <copyField source="features" dest="text"/>

Added: lucene/solr/trunk/src/java/org/apache/solr/schema/AbstractSubTypeFieldType.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/AbstractSubTypeFieldType.java?rev=894301&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/AbstractSubTypeFieldType.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/AbstractSubTypeFieldType.java Tue Dec 29 02:58:46 2009
@@ -0,0 +1,120 @@
+package org.apache.solr.schema;
+/**
+ * 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.solr.common.SolrException;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.search.QParser;
+import org.apache.lucene.search.Query;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * An abstract base class for FieldTypes that delegate work to another {@link org.apache.solr.schema.FieldType}.
+ * The sub type can be obtained by either specifying the subFieldType attribute or the subFieldSuffix.  In the former
+ * case, a new dynamic field will be injected into the schema automatically with the name of {@link #POLY_FIELD_SEPARATOR}.
+ * In the latter case, it will use an existing dynamic field definition to get the type.  See the example schema and the
+ * use of the {@link org.apache.solr.schema.PointType} for more details.
+ *
+ **/
+public abstract class AbstractSubTypeFieldType extends FieldType implements SchemaAware {
+  protected FieldType subType;
+  public static final String SUB_FIELD_SUFFIX = "subFieldSuffix";
+  public static final String SUB_FIELD_TYPE = "subFieldType";
+  protected String suffix;
+  protected int dynFieldProps;
+  protected String[] suffixes;
+  protected IndexSchema schema;   // needed for retrieving SchemaFields
+
+  public FieldType getSubType() {
+    return subType;
+  }
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+    this.schema = schema;
+    //it's not a first class citizen for the IndexSchema
+    SolrParams p = new MapSolrParams(args);
+    String subFT = p.get(SUB_FIELD_TYPE);
+    String subSuffix = p.get(SUB_FIELD_SUFFIX);
+    if (subFT != null) {
+      args.remove(SUB_FIELD_TYPE);
+      subType = schema.getFieldTypeByName(subFT.trim());
+      suffix = POLY_FIELD_SEPARATOR + subType.typeName;
+    } else if (subSuffix != null) {
+      args.remove(SUB_FIELD_SUFFIX);
+      suffix = subSuffix;
+    } else {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field type: " + typeName
+              + " must specify the " +
+              SUB_FIELD_TYPE + " attribute or the " + SUB_FIELD_SUFFIX + " attribute.");
+    }
+
+  }
+
+  /**
+   * Helper method for creating a dynamic field SchemaField prototype.  Returns a {@link SchemaField} with
+   * the {@link FieldType} given and a name of "*" + {@link FieldType#POLY_FIELD_SEPARATOR} + {@link FieldType#typeName}
+   * and props of indexed=true, stored=false.
+   *
+   * @param schema the IndexSchema
+   * @param type   The {@link FieldType} of the prototype.
+   * @return The {@link SchemaField}
+   */
+
+  static SchemaField registerPolyFieldDynamicPrototype(IndexSchema schema, FieldType type) {
+    String name = "*" + FieldType.POLY_FIELD_SEPARATOR + type.typeName;
+    Map<String, String> props = new HashMap<String, String>();
+    //Just set these, delegate everything else to the field type
+    props.put("indexed", "true");
+    props.put("stored", "false");
+    int p = SchemaField.calcProps(name, type, props);
+    SchemaField proto = SchemaField.create(name,
+            type, p, null);
+    schema.registerDynamicField(proto);
+    return proto;
+  }
+
+  public void inform(IndexSchema schema) {
+    //Can't do this until here b/c the Dynamic Fields are not initialized until here.
+    if (subType != null) {
+      SchemaField proto = registerPolyFieldDynamicPrototype(schema, subType);
+      dynFieldProps = proto.getProperties();
+    }
+  }
+
+  /**
+   * Throws UnsupportedOperationException()
+   */
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    throw new UnsupportedOperationException();
+  }
+
+  protected void createSuffixCache(int size) {
+    suffixes = new String[size];
+    for (int i=0; i<size; i++) {
+      suffixes[i] = "_" + i + suffix;
+    }
+  }
+
+  protected SchemaField subField(SchemaField base, int i) {
+    return schema.getField(base.getName() + suffixes[i]);
+  }
+}

Propchange: lucene/solr/trunk/src/java/org/apache/solr/schema/AbstractSubTypeFieldType.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java Tue Dec 29 02:58:46 2009
@@ -47,84 +47,19 @@
  * NOTE: There can only be one sub Field Type.
  *
  */
-public abstract class CoordinateFieldType extends FieldType implements SchemaAware  {
+public abstract class CoordinateFieldType extends AbstractSubTypeFieldType {
   /**
    * The dimension of the coordinate system
    */
   protected int dimension;
-  protected FieldType subType;
-  public static final String SUB_FIELD_SUFFIX = "subFieldSuffix";
-  public static final String SUB_FIELD_TYPE = "subFieldType";
-  protected String suffix;
-  protected int dynFieldProps;
-
-  public int getDimension() {
-    return dimension;
-  }
-
-  public FieldType getSubType() {
-    return subType;
-  }
-
-  @Override
-  protected void init(IndexSchema schema, Map<String, String> args) {
-
-    //it's not a first class citizen for the IndexSchema
-    SolrParams p = new MapSolrParams(args);
-    String subFT = p.get(SUB_FIELD_TYPE);
-    String subSuffix = p.get(SUB_FIELD_SUFFIX);
-    if (subFT != null) {
-      args.remove(SUB_FIELD_TYPE);
-      subType = schema.getFieldTypeByName(subFT.trim());
-      suffix = POLY_FIELD_SEPARATOR + subType.typeName;      
-    } else if (subSuffix != null) {
-      args.remove(SUB_FIELD_SUFFIX);
-      suffix = subSuffix;
-    }else {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field type: " + typeName
-              + " must specify the " +
-      SUB_FIELD_TYPE + " attribute or the " + SUB_FIELD_SUFFIX + " attribute.");
-    }
-
-    super.init(schema, args);
-  }
-
-  public void inform(IndexSchema schema) {
-    //Can't do this until here b/c the Dynamic Fields are not initialized until here.
-    if (subType != null) {
-      SchemaField proto = registerPolyFieldDynamicPrototype(schema, subType);
-      dynFieldProps = proto.getProperties();
-    }
-  }
-
   /**
-   * Helper method for creating a dynamic field SchemaField prototype.  Returns a {@link org.apache.solr.schema.SchemaField} with
-   * the {@link org.apache.solr.schema.FieldType} given and a name of "*" + {@link org.apache.solr.schema.FieldType#POLY_FIELD_SEPARATOR} + {@link org.apache.solr.schema.FieldType#typeName}
-   * and props of indexed=true, stored=false.
-   * @param schema the IndexSchema
-   * @param type The {@link org.apache.solr.schema.FieldType} of the prototype.
-   * @return The {@link org.apache.solr.schema.SchemaField}
+   * 2 dimensional by default
    */
-
-  static SchemaField registerPolyFieldDynamicPrototype(IndexSchema schema, FieldType type){
-    String name = "*" + FieldType.POLY_FIELD_SEPARATOR + type.typeName;
-    Map<String, String> props = new HashMap<String, String>();
-    //Just set these, delegate everything else to the field type
-    props.put("indexed", "true");
-    props.put("stored", "false");
-    int p = SchemaField.calcProps(name, type, props);
-    SchemaField proto = SchemaField.create(name,
-            type, p, null);
-    schema.registerDynamicField(proto);
-    return proto;
-  }
+  public static final int DEFAULT_DIMENSION = 2;
+  public static final String DIMENSION = "dimension";
 
 
-  /**
-   * Throws UnsupportedOperationException()
-   */
-  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
-    throw new UnsupportedOperationException();
+  public int getDimension() {
+    return dimension;
   }
-
 }

Added: lucene/solr/trunk/src/java/org/apache/solr/schema/GeoHashField.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/GeoHashField.java?rev=894301&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/GeoHashField.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/GeoHashField.java Tue Dec 29 02:58:46 2009
@@ -0,0 +1,82 @@
+/**
+ * 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.solr.schema;
+
+import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.spatial.geohash.GeoHashUtils;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.request.TextResponseWriter;
+import org.apache.solr.request.XMLWriter;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.function.ValueSource;
+import org.apache.solr.search.function.distance.DistanceUtils;
+
+import java.io.IOException;
+
+/**
+ * This is a class that represents a <a
+ * href="http://en.wikipedia.org/wiki/Geohash">Geohash</a> field. The field is
+ * provided as a lat lon pair and is internally represented as a string
+ *
+ */
+public class GeoHashField extends FieldType {
+
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    return getStringSort(field, top);
+  }
+
+  @Override
+  public void write(XMLWriter xmlWriter, String name, Fieldable f)
+          throws IOException {
+    xmlWriter.writeStr(name, toExternal(f));
+  }
+
+  @Override
+  public void write(TextResponseWriter writer, String name, Fieldable f)
+          throws IOException {
+    writer.writeStr(name, toExternal(f), false);
+  }
+
+
+  @Override
+  public String toExternal(Fieldable f) {
+    double[] latLon = GeoHashUtils.decode(f.stringValue());
+    return latLon[0] + "," + latLon[1];
+  }
+
+
+  @Override
+  public String toInternal(String val) {
+    // validate that the string is of the form
+    // latitude, longitude
+    double[] latLon = DistanceUtils.parseLatitudeLongitude(null, val);
+    return GeoHashUtils.encode(latLon[0], latLon[1]);
+  }
+
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser parser) {
+    return new StrFieldSource(field.name);
+  }
+
+  
+
+}

Propchange: lucene/solr/trunk/src/java/org/apache/solr/schema/GeoHashField.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java Tue Dec 29 02:58:46 2009
@@ -46,14 +46,6 @@
  * NOTE: There can only be one sub type
  */
 public class PointType extends CoordinateFieldType {
-  /**
-   * 2 dimensional by default
-   */
-  public static final int DEFAULT_DIMENSION = 2;
-  public static final String DIMENSION = "dimension";
-
-  protected IndexSchema schema;   // needed for retrieving SchemaFields
-  protected String[] suffixes;
 
   @Override
   protected void init(IndexSchema schema, Map<String, String> args) {
@@ -68,14 +60,7 @@
     super.init(schema, args);
 
     // cache suffixes
-    suffixes = new String[dimension];
-    for (int i=0; i<dimension; i++) {
-      suffixes[i] = "_" + i + suffix;
-    }
-  }
-
-  protected SchemaField subField(SchemaField base, int i) {
-    return schema.getField(base.getName() + suffixes[i]);
+    createSuffixCache(dimension);
   }
 
 
@@ -109,7 +94,7 @@
 
   @Override
   public ValueSource getValueSource(SchemaField field, QParser parser) {
-    ArrayList<ValueSource> vs = new ArrayList(dimension);
+    ArrayList<ValueSource> vs = new ArrayList<ValueSource>(dimension);
     for (int i=0; i<dimension; i++) {
       SchemaField sub = subField(field, i);
       vs.add(sub.getType().getValueSource(sub, parser));
@@ -136,7 +121,7 @@
 
   @Override
   public SortField getSortField(SchemaField field, boolean top) {
-    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not suported on DualPointType " + field.getName());
+    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not suported on PointType " + field.getName());
   }
 
   @Override

Added: lucene/solr/trunk/src/java/org/apache/solr/schema/SpatialTileField.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/SpatialTileField.java?rev=894301&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/SpatialTileField.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/SpatialTileField.java Tue Dec 29 02:58:46 2009
@@ -0,0 +1,187 @@
+package org.apache.solr.schema;
+/**
+ * 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.document.Field;
+import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.spatial.tier.projections.CartesianTierPlotter;
+import org.apache.lucene.spatial.tier.projections.IProjector;
+import org.apache.lucene.spatial.tier.projections.SinusoidalProjector;
+import org.apache.solr.common.ResourceLoader;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.request.TextResponseWriter;
+import org.apache.solr.request.XMLWriter;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.function.ValueSource;
+import org.apache.solr.search.function.distance.DistanceUtils;
+import org.apache.solr.util.plugin.ResourceLoaderAware;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+
+
+/**
+ * Represents a Tiling system for spatial data representation (lat/lon).  A Tile is like a zoom level on an
+ * interactive map.
+ * <p/>
+ * Specify a lower and upper tile, and this will create tiles for all the levels in between, inclusive of the upper tile.
+ * <p/>
+ * Querying directly against this field is probably not all that useful unless you specifically know the box id
+ * <p/>
+ *
+ * See http://wiki.apache.org/solr/SpatialSearch
+ */
+public class SpatialTileField extends AbstractSubTypeFieldType implements ResourceLoaderAware {
+
+  public static final String START_LEVEL = "start";
+  public static final String END_LEVEL = "end";
+  public static final String PROJECTOR_CLASS = "projector";
+
+  private static final int DEFAULT_END_LEVEL = 15;
+
+  private static final int DEFAULT_START_LEVEL = 4;
+
+  private int start = DEFAULT_START_LEVEL, end = DEFAULT_END_LEVEL;
+  private int tileDiff = DEFAULT_END_LEVEL - DEFAULT_START_LEVEL;//we're going to need this over and over, so cache it.
+  private String projectorName;
+  protected List<CartesianTierPlotter> plotters;
+
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+    SolrParams p = new MapSolrParams(args);
+    start = p.getInt(START_LEVEL, DEFAULT_START_LEVEL);
+    end = p.getInt(END_LEVEL, DEFAULT_END_LEVEL);
+    if (end < start) {
+      //flip them around
+      int tmp = start;
+      start = end;
+      end = tmp;
+    }
+    args.remove(START_LEVEL);
+    args.remove(END_LEVEL);
+    projectorName = p.get(PROJECTOR_CLASS, SinusoidalProjector.class.getName());
+    super.init(schema, args);
+    tileDiff = (end - start) + 1;//add one since we are inclusive of the upper tier
+    createSuffixCache(tileDiff);
+
+
+  }
+
+  public void inform(ResourceLoader loader) {
+    IProjector projector = (IProjector) loader.newInstance(projectorName);
+    if (projector != null) {
+      plotters = new ArrayList<CartesianTierPlotter>(tileDiff);
+      for (int i = start; i <= end; i++) {
+        plotters.add(new CartesianTierPlotter(i, projector, ""));
+      }
+    } else {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Could not instantiate a Projector Instance for: "
+              + projectorName + ". Make sure the " + PROJECTOR_CLASS + " attribute is set properly in the schema");
+    }
+
+  }
+
+  @Override
+  public Fieldable[] createFields(SchemaField field, String externalVal, float boost) {
+    Fieldable[] f = new Fieldable[(field.indexed() ? tileDiff : 0) + (field.stored() ? 1 : 0)];
+    if (field.indexed()) {
+      int i = 0;
+      double[] latLon = DistanceUtils.parseLatitudeLongitude(null, externalVal);
+      for (CartesianTierPlotter plotter : plotters) {
+        double boxId = plotter.getTierBoxId(latLon[0], latLon[1]);
+        f[i] = subField(field, i).createField(String.valueOf(boxId), boost);
+        i++;
+      }
+    }
+
+    if (field.stored()) {
+      String storedVal = externalVal;  // normalize or not?
+      f[f.length - 1] = createField(field.getName(), storedVal,
+              getFieldStore(field, storedVal), Field.Index.NO, Field.TermVector.NO,
+              false, false, boost);
+    }
+    return f;
+  }
+
+  //The externalVal here is a box id, as it doesn't make sense to pick a specific tile since that requires a distance
+  //so, just OR together a search against all the tile
+  @Override
+  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive,
+                             boolean maxInclusive) {
+    BooleanQuery bq = new BooleanQuery(true);
+    for (int i = 0; i < tileDiff; i++) {
+      SchemaField sf = subField(field, i);
+      Query tq = sf.getType().getRangeQuery(parser, sf, part1, part2, minInclusive, maxInclusive);
+      bq.add(tq, BooleanClause.Occur.SHOULD);
+    }
+    return bq;
+  }
+
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    //The externalVal here is a box id, as it doesn't make sense to pick a specific tile since that requires a distance
+    //so, just OR together a search against all the tiles
+    BooleanQuery bq = new BooleanQuery(true);
+    for (int i = 0; i < tileDiff; i++) {
+      SchemaField sf = subField(field, i);
+      Query tq = sf.getType().getFieldQuery(parser, sf, externalVal);
+      bq.add(tq, BooleanClause.Occur.SHOULD);
+    }
+    return bq;
+  }
+
+  @Override
+  public boolean isPolyField() {
+    return true;
+  }
+
+  @Override
+  public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException {
+    xmlWriter.writeStr(name, f.stringValue());
+  }
+
+  @Override
+  public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException {
+    writer.writeStr(name, f.stringValue(), false);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not supported on SpatialTileField " + field.getName());
+  }
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser parser) {
+    //TODO: Should this really throw UOE?  What does it mean for a function to use the values of a tier?  Let's leave it unsupported for now
+    throw new UnsupportedOperationException("SpatialTileField uses multiple fields and does not support ValueSource");
+  }
+
+  //It never makes sense to create a single field, so make it impossible to happen
+  @Override
+  public Field createField(SchemaField field, String externalVal, float boost) {
+    throw new UnsupportedOperationException("SpatialTileField uses multiple fields.  field=" + field.getName());
+  }
+}

Propchange: lucene/solr/trunk/src/java/org/apache/solr/schema/SpatialTileField.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java Tue Dec 29 02:58:46 2009
@@ -92,4 +92,36 @@
     }
     return out;
   }
+
+  /**
+   * extract (by calling {@link #parsePoint(String[], String, int)} and validate the latitude and longitude contained
+   * in the String by making sure the latitude is between 90 & -90 and longitude is between -180 and 180
+   * @param latLon A preallocated array to hold the result
+   * @param latLonStr The string to parse
+   * @return The lat long
+   */
+  public static final double[] parseLatitudeLongitude(double [] latLon, String latLonStr) {
+    if (latLon == null){
+      latLon = new double[2];
+    }
+    String[] toks = DistanceUtils.parsePoint(null, latLonStr, 2);
+    latLon[0] = Double.valueOf(toks[0]);
+
+    if (latLon[0] < -90.0 || latLon[0] > 90.0) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+              "Invalid latitude: latitudes are range -90 to 90: provided lat: ["
+                      + latLon[0] + "]");
+    }
+
+    latLon[1] = Double.valueOf(toks[1]);
+
+    if (latLon[1] < -180.0 || latLon[1] > 180.0) {
+
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+              "Invalid longitude: longitudes are range -180 to 180: provided lon: ["
+                      + latLon[1] + "]");
+    }
+
+    return latLon;
+  }
 }

Modified: lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java (original)
+++ lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java Tue Dec 29 02:58:46 2009
@@ -24,6 +24,8 @@
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.spatial.tier.CartesianPolyFilterBuilder;
+import org.apache.lucene.spatial.tier.Shape;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.util.AbstractSolrTestCase;
 import org.apache.solr.common.SolrException;
@@ -31,6 +33,7 @@
 
 import java.util.Map;
 import java.util.Random;
+import java.util.List;
 
 
 /**
@@ -123,10 +126,10 @@
     //
     SchemaField s1 = schema.getField("test_p");
     SchemaField s2 = schema.getField("test_p");
-    ValueSource v1 = s1.getType().getValueSource(s1,null);
-    ValueSource v2 = s2.getType().getValueSource(s2,null);
-    assertEquals(v1,v2);
-    assertEquals(v1.hashCode(),v2.hashCode());
+    ValueSource v1 = s1.getType().getValueSource(s1, null);
+    ValueSource v2 = s2.getType().getValueSource(s2, null);
+    assertEquals(v1, v2);
+    assertEquals(v1.hashCode(), v2.hashCode());
   }
 
   public void testSearching() throws Exception {
@@ -156,12 +159,11 @@
             "homed:[1,1000 TO 2000,35000]"),
             "\"//*[@numFound='2']\"");
     //bad
-    
+
     assertQEx("Query should throw an exception due to incorrect dimensions", req("fl", "*,score", "q",
             "homed:[1 TO 2000]"), SolrException.ErrorCode.BAD_REQUEST);
   }
 
-  
 
   public void testSearchDetails() throws Exception {
     SolrCore core = h.getCore();
@@ -184,4 +186,40 @@
     assertEquals(clauses.length, 2);
 
   }
+
+
+  public void testCartesian() throws Exception {
+    for (int i = 40; i < 50; i++) {
+      for (int j = -85; j < -79; j++) {
+        assertU(adoc("id", "" + i, "home_tier",
+                i + "," + j));
+      }
+    }
+    assertU(commit());
+    CartesianPolyFilterBuilder cpfb = new CartesianPolyFilterBuilder("");
+    //Get the box based on this point and our distance
+    final Shape shape = cpfb.getBoxShape(45, -80, 10);//There's a bit of a bug in here that requires a small tier filter here.
+    final List<Double> boxIds = shape.getArea();
+    //do a box id search
+    StringBuilder qry = new StringBuilder();
+    boolean first = true;
+    for (Double boxId : boxIds) {
+      if (first == true){
+        first = false;
+      } else {
+        qry.append(" OR ");
+      }
+      qry.append("home_tier:");
+      if (boxId < 0) {
+        qry.append('\\').append(boxId);
+      } else {
+        qry.append(boxId);
+      }
+    }
+
+    assertQ(req("fl", "*,score", "q", qry.toString()),
+            "//*[@numFound='1']");
+
+  }
+
 }
\ No newline at end of file

Modified: lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java (original)
+++ lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java Tue Dec 29 02:58:46 2009
@@ -46,6 +46,8 @@
     assertU(adoc("id", "4", "x_td", String.valueOf(Math.PI / 4), "y_td", String.valueOf(Math.PI / 4), "gh_s", GeoHashUtils.encode(32.7693246, -81.9289094)));
     assertU(adoc("id", "5", "x_td", "45.0", "y_td", "45.0",
             "gh_s", GeoHashUtils.encode(32.7693246, -81.9289094)));
+    assertU(adoc("id", "6", "point_hash", "32.5, -79.0"));
+    assertU(adoc("id", "7", "point_hash", "32.6, -78.0"));
     assertU(commit());
     //Get the haversine distance between the point 0,0 and the docs above assuming a radius of 1
     assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0)", "fq", "id:1"), "//float[@name='score']='0.0'");
@@ -58,6 +60,14 @@
     //Can verify here: http://www.movable-type.co.uk/scripts/latlong.html, but they use a slightly different radius for the earth, so just be close
     assertQ(req("fl", "*,score", "q", "{!func}ghhsin(" + Constants.EARTH_RADIUS_KM + ", gh_s, \"" + GeoHashUtils.encode(32, -79) +
             "\",)", "fq", "id:1"), "//float[@name='score']='122.30894'");
+
+    assertQ(req("fl", "id,point_hash,score", "q", "{!func}recip(ghhsin(" + Constants.EARTH_RADIUS_KM + ", point_hash, \"" + GeoHashUtils.encode(32, -79) + "\"), 1, 1, 0)"),
+            "//*[@numFound='7']", 
+            "//result/doc[1]/float[@name='id'][.='6.0']",
+            "//result/doc[2]/float[@name='id'][.='7.0']"//all the rest don't matter
+            );
+
+
     assertQ(req("fl", "*,score", "q", "{!func}ghhsin(" + Constants.EARTH_RADIUS_KM + ", gh_s, geohash(32, -79))", "fq", "id:1"), "//float[@name='score']='122.30894'");
   }
 

Modified: lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml (original)
+++ lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml Tue Dec 29 02:58:46 2009
@@ -373,7 +373,9 @@
   <fieldType name="tenD" class="solr.PointType" dimension="10" subFieldType="double"/>
     <!-- Use the sub field suffix -->
   <fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/>
-  
+
+  <fieldType name="tier" class="solr.SpatialTileField" start="4" end="15" subFieldType="double"/>
+
  </types>
 
 
@@ -405,6 +407,8 @@
    <field name="home_ns" type="xy" indexed="true" stored="false" multiValued="false"/>
    <field name="work" type="xy" indexed="true" stored="true" multiValued="false"/>
 
+   <field name="home_tier" type="tier" indexed="true" stored="true" multiValued="false"/>
+
    <field name="point10" type="tenD" indexed="true" stored="true" multiValued="false"/>
 
 

Modified: lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml?rev=894301&r1=894300&r2=894301&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml (original)
+++ lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml Tue Dec 29 02:58:46 2009
@@ -255,6 +255,8 @@
     <fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/>
     <fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/>
 
+    <fieldtype name="geohash" class="solr.GeoHashField"/>
+
  </types>
 
 
@@ -285,6 +287,8 @@
    <field name="point" type="xy" indexed="true" stored="true" multiValued="false"/>
    <field name="pointD" type="xyd" indexed="true" stored="true" multiValued="false"/>
 
+   <field name="point_hash" type="geohash" indexed="true" stored="true" multiValued="false"/>
+
    <!-- Dynamic field definitions.  If a field name is not found, dynamicFields
         will be used if the name matches any of the patterns.
         RESTRICTION: the glob-like pattern in the name attribute must have