You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ds...@apache.org on 2016/03/08 02:15:09 UTC

[09/16] lucene-solr git commit: LUCENE-7056: Geo3D package re-org

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBBoxFactory.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBBoxFactory.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBBoxFactory.java
new file mode 100755
index 0000000..de7493e
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBBoxFactory.java
@@ -0,0 +1,111 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Factory for {@link GeoBBox}.
+ *
+ * @lucene.experimental
+ */
+public class GeoBBoxFactory {
+  private GeoBBoxFactory() {
+  }
+
+  /**
+   * Create a geobbox of the right kind given the specified bounds.
+   *
+   * @param planetModel is the planet model
+   * @param topLat    is the top latitude
+   * @param bottomLat is the bottom latitude
+   * @param leftLon   is the left longitude
+   * @param rightLon  is the right longitude
+   * @return a GeoBBox corresponding to what was specified.
+   */
+  public static GeoBBox makeGeoBBox(final PlanetModel planetModel, double topLat, double bottomLat, double leftLon, double rightLon) {
+    //System.err.println("Making rectangle for topLat="+topLat*180.0/Math.PI+", bottomLat="+bottomLat*180.0/Math.PI+", leftLon="+leftLon*180.0/Math.PI+", rightlon="+rightLon*180.0/Math.PI);
+    if (topLat > Math.PI * 0.5)
+      topLat = Math.PI * 0.5;
+    if (bottomLat < -Math.PI * 0.5)
+      bottomLat = -Math.PI * 0.5;
+    if (leftLon < -Math.PI)
+      leftLon = -Math.PI;
+    if (rightLon > Math.PI)
+      rightLon = Math.PI;
+    if (Math.abs(leftLon + Math.PI) < Vector.MINIMUM_RESOLUTION && Math.abs(rightLon - Math.PI) < Vector.MINIMUM_RESOLUTION) {
+      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION && Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
+        return new GeoWorld(planetModel);
+      if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION) {
+        if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION || Math.abs(topLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
+          return new GeoDegeneratePoint(planetModel, topLat, 0.0);
+        return new GeoDegenerateLatitudeZone(planetModel, topLat);
+      }
+      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
+        return new GeoNorthLatitudeZone(planetModel, bottomLat);
+      else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION)
+        return new GeoSouthLatitudeZone(planetModel, topLat);
+      return new GeoLatitudeZone(planetModel, topLat, bottomLat);
+    }
+    //System.err.println(" not latitude zone");
+    double extent = rightLon - leftLon;
+    if (extent < 0.0)
+      extent += Math.PI * 2.0;
+    if (topLat == Math.PI * 0.5 && bottomLat == -Math.PI * 0.5) {
+      if (Math.abs(leftLon - rightLon) < Vector.MINIMUM_RESOLUTION)
+        return new GeoDegenerateLongitudeSlice(planetModel, leftLon);
+
+      if (extent >= Math.PI)
+        return new GeoWideLongitudeSlice(planetModel, leftLon, rightLon);
+
+      return new GeoLongitudeSlice(planetModel, leftLon, rightLon);
+    }
+    //System.err.println(" not longitude slice");
+    if (Math.abs(leftLon - rightLon) < Vector.MINIMUM_RESOLUTION) {
+      if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION)
+        return new GeoDegeneratePoint(planetModel, topLat, leftLon);
+      return new GeoDegenerateVerticalLine(planetModel, topLat, bottomLat, leftLon);
+    }
+    //System.err.println(" not vertical line");
+    if (extent >= Math.PI) {
+      if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION) {
+        //System.err.println(" wide degenerate line");
+        return new GeoWideDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon);
+      }
+      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
+        return new GeoWideNorthRectangle(planetModel, bottomLat, leftLon, rightLon);
+      } else if (Math.abs(bottomLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
+        return new GeoWideSouthRectangle(planetModel, topLat, leftLon, rightLon);
+      }
+      //System.err.println(" wide rect");
+      return new GeoWideRectangle(planetModel, topLat, bottomLat, leftLon, rightLon);
+    }
+    if (Math.abs(topLat - bottomLat) < Vector.MINIMUM_RESOLUTION) {
+      if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION || Math.abs(topLat + Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
+        return new GeoDegeneratePoint(planetModel, topLat, 0.0);
+      }
+      //System.err.println(" horizontal line");
+      return new GeoDegenerateHorizontalLine(planetModel, topLat, leftLon, rightLon);
+    }
+    if (Math.abs(topLat - Math.PI * 0.5) < Vector.MINIMUM_RESOLUTION) {
+      return new GeoNorthRectangle(planetModel, bottomLat, leftLon, rightLon);
+    } else if (Math.abs(bottomLat + Math.PI * 0.5) <  Vector.MINIMUM_RESOLUTION) {
+      return new GeoSouthRectangle(planetModel, topLat, leftLon, rightLon);
+    }
+    //System.err.println(" rectangle");
+    return new GeoRectangle(planetModel, topLat, bottomLat, leftLon, rightLon);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseBBox.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseBBox.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseBBox.java
new file mode 100644
index 0000000..7190cdc
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseBBox.java
@@ -0,0 +1,72 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * All bounding box shapes can derive from this base class, which furnishes
+ * some common code
+ *
+ * @lucene.internal
+ */
+public abstract class GeoBaseBBox extends GeoBaseMembershipShape implements GeoBBox {
+
+  /** Construct, given planet model.
+   *@param planetModel is the planet model.
+   */
+  public GeoBaseBBox(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+  // Signals for relationship of edge points to shape
+  
+  /** All edgepoints inside shape */
+  protected final static int ALL_INSIDE = 0;
+  /** Some edgepoints inside shape */
+  protected final static int SOME_INSIDE = 1;
+  /** No edgepoints inside shape */
+  protected final static int NONE_INSIDE = 2;
+
+  /** Determine the relationship between this BBox and the provided
+   * shape's edgepoints.
+   *@param path is the shape.
+   *@return the relationship.
+   */
+  protected int isShapeInsideBBox(final GeoShape path) {
+    final GeoPoint[] pathPoints = path.getEdgePoints();
+    boolean foundOutside = false;
+    boolean foundInside = false;
+    for (GeoPoint p : pathPoints) {
+      if (isWithin(p)) {
+        foundInside = true;
+      } else {
+        foundOutside = true;
+      }
+      if (foundInside && foundOutside) {
+        return SOME_INSIDE;
+      }
+    }
+    if (!foundInside && !foundOutside)
+      return NONE_INSIDE;
+    if (foundInside && !foundOutside)
+      return ALL_INSIDE;
+    if (foundOutside && !foundInside)
+      return NONE_INSIDE;
+    return SOME_INSIDE;
+  }
+
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCircle.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCircle.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCircle.java
new file mode 100644
index 0000000..75219fd
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCircle.java
@@ -0,0 +1,34 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * GeoCircles have all the characteristics of GeoBaseDistanceShapes, plus GeoSizeable.
+ *
+ * @lucene.experimental
+ */
+public abstract class GeoBaseCircle extends GeoBaseDistanceShape implements GeoCircle {
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   */
+  public GeoBaseCircle(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseDistanceShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseDistanceShape.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseDistanceShape.java
new file mode 100644
index 0000000..39dcf96
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseDistanceShape.java
@@ -0,0 +1,56 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Distance shapes have capabilities of both geohashing and distance
+ * computation (which also includes point membership determination).
+ *
+ * @lucene.experimental
+ */
+public abstract class GeoBaseDistanceShape extends GeoBaseMembershipShape implements GeoDistanceShape {
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   */
+  public GeoBaseDistanceShape(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+  @Override
+  public boolean isWithin(Vector point) {
+    return isWithin(point.x, point.y, point.z);
+  }
+
+  @Override
+  public double computeDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
+    return computeDistance(distanceStyle, point.x, point.y, point.z);
+  }
+
+  @Override
+  public double computeDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    if (!isWithin(x,y,z)) {
+      return Double.MAX_VALUE;
+    }
+    return distance(distanceStyle, x, y, z);
+  }
+
+  /** Called by a {@code computeDistance} method if X/Y/Z is not within this shape. */
+  protected abstract double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
+
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseMembershipShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseMembershipShape.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseMembershipShape.java
new file mode 100644
index 0000000..831a7c6
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseMembershipShape.java
@@ -0,0 +1,56 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Membership shapes have capabilities of both geohashing and membership
+ * determination.  This is a useful baseclass for them.
+ *
+ * @lucene.experimental
+ */
+public abstract class GeoBaseMembershipShape extends GeoBaseShape implements GeoMembershipShape {
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   */
+  public GeoBaseMembershipShape(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+  @Override
+  public boolean isWithin(Vector point) {
+    return isWithin(point.x, point.y, point.z);
+  }
+
+  @Override
+  public double computeOutsideDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
+    return computeOutsideDistance(distanceStyle, point.x, point.y, point.z);
+  }
+
+  @Override
+  public double computeOutsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    if (isWithin(x,y,z)) {
+      return 0.0;
+    }
+    return outsideDistance(distanceStyle, x,y,z);
+  }
+
+  /** Called by a {@code computeOutsideDistance} method if X/Y/Z is not within this shape. */
+  protected abstract double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
+
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePolygon.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePolygon.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePolygon.java
new file mode 100644
index 0000000..ba221ae
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePolygon.java
@@ -0,0 +1,34 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * GeoBasePolygon objects are the base class of most GeoPolygon objects.
+ *
+ * @lucene.experimental
+ */
+public abstract class GeoBasePolygon extends GeoBaseMembershipShape implements GeoPolygon {
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   */
+  public GeoBasePolygon(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+}
+  

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseShape.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseShape.java
new file mode 100644
index 0000000..54896fc
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseShape.java
@@ -0,0 +1,59 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Base extended shape object.
+ *
+ * @lucene.internal
+ */
+public abstract class GeoBaseShape extends BasePlanetObject implements GeoShape {
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   */
+  public GeoBaseShape(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    if (isWithin(planetModel.NORTH_POLE)) {
+      bounds.noTopLatitudeBound().noLongitudeBound()
+        .addPoint(planetModel.NORTH_POLE);
+    }
+    if (isWithin(planetModel.SOUTH_POLE)) {
+      bounds.noBottomLatitudeBound().noLongitudeBound()
+        .addPoint(planetModel.SOUTH_POLE);
+    }
+    if (isWithin(planetModel.MIN_X_POLE)) {
+      bounds.addPoint(planetModel.MIN_X_POLE);
+    }
+    if (isWithin(planetModel.MAX_X_POLE)) {
+      bounds.addPoint(planetModel.MAX_X_POLE);
+    }
+    if (isWithin(planetModel.MIN_Y_POLE)) {
+      bounds.addPoint(planetModel.MIN_Y_POLE);
+    }
+    if (isWithin(planetModel.MAX_Y_POLE)) {
+      bounds.addPoint(planetModel.MAX_Y_POLE);
+    }
+  }
+
+}
+
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircle.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircle.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircle.java
new file mode 100755
index 0000000..b05dff6
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircle.java
@@ -0,0 +1,25 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Interface describing circular area with a center and radius.
+ *
+ * @lucene.experimental
+ */
+public interface GeoCircle extends GeoDistanceShape, GeoSizeable {
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircleFactory.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircleFactory.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircleFactory.java
new file mode 100644
index 0000000..ee75179
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCircleFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Class which constructs a GeoCircle representing an arbitrary circle.
+ *
+ * @lucene.experimental
+ */
+public class GeoCircleFactory {
+  private GeoCircleFactory() {
+  }
+
+  /**
+   * Create a GeoCircle of the right kind given the specified bounds.
+   * @param planetModel is the planet model.
+   * @param latitude is the center latitude.
+   * @param longitude is the center longitude.
+   * @param radius is the radius angle.
+   * @return a GeoCircle corresponding to what was specified.
+   */
+  public static GeoCircle makeGeoCircle(final PlanetModel planetModel, final double latitude, final double longitude, final double radius) {
+    if (radius < Vector.MINIMUM_RESOLUTION) {
+      return new GeoDegeneratePoint(planetModel, latitude, longitude);
+    }
+    return new GeoStandardCircle(planetModel, latitude, longitude, radius);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositeMembershipShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositeMembershipShape.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositeMembershipShape.java
new file mode 100755
index 0000000..9747eda
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositeMembershipShape.java
@@ -0,0 +1,117 @@
+/*
+ * 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.spatial3d.geom;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GeoComposite is a set of GeoMembershipShape's, treated as a unit.
+ *
+ * @lucene.experimental
+ */
+public class GeoCompositeMembershipShape implements GeoMembershipShape {
+  /** The list of shapes. */
+  protected final List<GeoMembershipShape> shapes = new ArrayList<GeoMembershipShape>();
+
+  /** Constructor.
+   */
+  public GeoCompositeMembershipShape() {
+  }
+
+  /**
+   * Add a shape to the composite.
+   *@param shape is the shape to add.
+   */
+  public void addShape(final GeoMembershipShape shape) {
+    shapes.add(shape);
+  }
+
+  @Override
+  public boolean isWithin(final Vector point) {
+    return isWithin(point.x, point.y, point.z);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    for (GeoMembershipShape shape : shapes) {
+      if (shape.isWithin(x, y, z))
+        return true;
+    }
+    return false;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return shapes.get(0).getEdgePoints();
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    for (GeoMembershipShape shape : shapes) {
+      if (shape.intersects(p, notablePoints, bounds))
+        return true;
+    }
+    return false;
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    for (GeoMembershipShape shape : shapes) {
+      shape.getBounds(bounds);
+    }
+  }
+
+  @Override
+  public double computeOutsideDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
+    return computeOutsideDistance(distanceStyle, point.x, point.y, point.z);
+  }
+
+  @Override
+  public double computeOutsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    if (isWithin(x,y,z))
+      return 0.0;
+    double distance = Double.MAX_VALUE;
+    for (GeoMembershipShape shape : shapes) {
+      final double normalDistance = shape.computeOutsideDistance(distanceStyle, x, y, z);
+      if (normalDistance < distance) {
+        distance = normalDistance;
+      }
+    }
+    return distance;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoCompositeMembershipShape))
+      return false;
+    GeoCompositeMembershipShape other = (GeoCompositeMembershipShape) o;
+
+    return super.equals(o) && shapes.equals(other.shapes);
+  }
+
+  @Override
+  public int hashCode() {
+    return super.hashCode() * 31 + shapes.hashCode();//TODO cache
+  }
+
+  @Override
+  public String toString() {
+    return "GeoCompositeMembershipShape: {" + shapes + '}';
+  }
+}
+  

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositePolygon.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositePolygon.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositePolygon.java
new file mode 100644
index 0000000..920d3fb
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoCompositePolygon.java
@@ -0,0 +1,31 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * GeoCompositePolygon is a specific implementation of GeoMembershipShape, which implements GeoPolygon explicitly.
+ *
+ * @lucene.experimental
+ */
+public class GeoCompositePolygon extends GeoCompositeMembershipShape implements GeoPolygon {
+  /** Constructor.
+   */
+  public GeoCompositePolygon() {
+  }
+
+}
+  

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoConvexPolygon.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoConvexPolygon.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoConvexPolygon.java
new file mode 100755
index 0000000..fb024b6
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoConvexPolygon.java
@@ -0,0 +1,288 @@
+/*
+ * 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.spatial3d.geom;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * GeoConvexPolygon objects are generic building blocks of more complex structures.
+ * The only restrictions on these objects are: (1) they must be convex; (2) they must have
+ * a maximum extent no larger than PI.  Violating either one of these limits will
+ * cause the logic to fail.
+ *
+ * @lucene.experimental
+ */
+public class GeoConvexPolygon extends GeoBasePolygon {
+  /** The list of polygon points */
+  protected final List<GeoPoint> points;
+  /** A bitset describing, for each edge, whether it is internal or not */
+  protected final BitSet isInternalEdges;
+
+  /** A list of edges */
+  protected SidedPlane[] edges = null;
+  /** The set of notable points for each edge */
+  protected GeoPoint[][] notableEdgePoints = null;
+  /** A point which is on the boundary of the polygon */
+  protected GeoPoint[] edgePoints = null;
+  /** Tracking the maximum distance we go at any one time, so to be sure it's legal */
+  protected double fullDistance = 0.0;
+  /** Set to true when the polygon is complete */
+  protected boolean isDone = false;
+  
+  /**
+   * Create a convex polygon from a list of points.  The first point must be on the
+   * external edge.
+   *@param planetModel is the planet model.
+   *@param pointList is the list of points to create the polygon from.
+   */
+  public GeoConvexPolygon(final PlanetModel planetModel, final List<GeoPoint> pointList) {
+    super(planetModel);
+    this.points = pointList;
+    this.isInternalEdges = new BitSet();
+    done(false);
+  }
+
+  /**
+   * Create a convex polygon from a list of points, keeping track of which boundaries
+   * are internal.  This is used when creating a polygon as a building block for another shape.
+   *@param planetModel is the planet model.
+   *@param pointList is the set of points to create the polygon from.
+   *@param internalEdgeFlags is a bitset describing whether each edge is internal or not.
+   *@param returnEdgeInternal is true when the final return edge is an internal one.
+   */
+  public GeoConvexPolygon(final PlanetModel planetModel, final List<GeoPoint> pointList, final BitSet internalEdgeFlags,
+                          final boolean returnEdgeInternal) {
+    super(planetModel);
+    this.points = pointList;
+    this.isInternalEdges = internalEdgeFlags;
+    done(returnEdgeInternal);
+  }
+
+  /**
+   * Create a convex polygon, with a starting latitude and longitude.
+   * Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
+   *@param planetModel is the planet model.
+   *@param startLatitude is the latitude of the first point.
+   *@param startLongitude is the longitude of the first point.
+   */
+  public GeoConvexPolygon(final PlanetModel planetModel, final double startLatitude, final double startLongitude) {
+    super(planetModel);
+    points = new ArrayList<>();
+    isInternalEdges = new BitSet();
+    points.add(new GeoPoint(planetModel, startLatitude, startLongitude));
+  }
+
+  /**
+   * Add a point to the polygon.
+   * Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
+   *
+   * @param latitude       is the latitude of the next point.
+   * @param longitude      is the longitude of the next point.
+   * @param isInternalEdge is true if the edge just added with this point should be considered "internal", and not
+   *                       intersected as part of the intersects() operation.
+   */
+  public void addPoint(final double latitude, final double longitude, final boolean isInternalEdge) {
+    if (isDone)
+      throw new IllegalStateException("Can't call addPoint() if done() already called");
+    if (isInternalEdge)
+      isInternalEdges.set(points.size() - 1);
+    points.add(new GeoPoint(planetModel, latitude, longitude));
+  }
+
+  /**
+   * Finish the polygon, by connecting the last added point with the starting point.
+   *@param isInternalReturnEdge is true if the return edge (back to start) is an internal one.
+   */
+  public void done(final boolean isInternalReturnEdge) {
+    if (isDone)
+      throw new IllegalStateException("Can't call done() more than once");
+    // If fewer than 3 points, can't do it.
+    if (points.size() < 3)
+      throw new IllegalArgumentException("Polygon needs at least three points.");
+
+    if (isInternalReturnEdge)
+      isInternalEdges.set(points.size() - 1);
+
+    isDone = true;
+    
+    // Time to construct the planes.  If the polygon is truly convex, then any adjacent point
+    // to a segment can provide an interior measurement.
+    edges = new SidedPlane[points.size()];
+    notableEdgePoints = new GeoPoint[points.size()][];
+
+    for (int i = 0; i < points.size(); i++) {
+      final GeoPoint start = points.get(i);
+      final GeoPoint end = points.get(legalIndex(i + 1));
+      final double distance = start.arcDistance(end);
+      if (distance > fullDistance)
+        fullDistance = distance;
+      final GeoPoint check = points.get(legalIndex(i + 2));
+      final SidedPlane sp = new SidedPlane(check, start, end);
+      //System.out.println("Created edge "+sp+" using start="+start+" end="+end+" check="+check);
+      edges[i] = sp;
+      notableEdgePoints[i] = new GeoPoint[]{start, end};
+    }
+    createCenterPoint();
+  }
+
+  /** Compute a reasonable center point.
+   */
+  protected void createCenterPoint() {
+    // In order to naively confirm that the polygon is convex, I would need to
+    // check every edge, and verify that every point (other than the edge endpoints)
+    // is within the edge's sided plane.  This is an order n^2 operation.  That's still
+    // not wrong, though, because everything else about polygons has a similar cost.
+    for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
+      final SidedPlane edge = edges[edgeIndex];
+      for (int pointIndex = 0; pointIndex < points.size(); pointIndex++) {
+        if (pointIndex != edgeIndex && pointIndex != legalIndex(edgeIndex + 1)) {
+          if (!edge.isWithin(points.get(pointIndex)))
+            throw new IllegalArgumentException("Polygon is not convex: Point " + points.get(pointIndex) + " Edge " + edge);
+        }
+      }
+    }
+    edgePoints = new GeoPoint[]{points.get(0)};
+  }
+
+  /** Compute a legal point index from a possibly illegal one, that may have wrapped.
+   *@param index is the index.
+   *@return the normalized index.
+   */
+  protected int legalIndex(int index) {
+    while (index >= points.size())
+      index -= points.size();
+    return index;
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    for (final SidedPlane edge : edges) {
+      if (!edge.isWithin(x, y, z))
+        return false;
+    }
+    return true;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    //System.err.println("Checking for polygon intersection with plane "+p+"...");
+    for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
+      final SidedPlane edge = edges[edgeIndex];
+      final GeoPoint[] points = this.notableEdgePoints[edgeIndex];
+      if (!isInternalEdges.get(edgeIndex)) {
+        //System.err.println(" non-internal edge "+edge);
+        // Edges flagged as 'internal only' are excluded from the matching
+        // Construct boundaries
+        final Membership[] membershipBounds = new Membership[edges.length - 1];
+        int count = 0;
+        for (int otherIndex = 0; otherIndex < edges.length; otherIndex++) {
+          if (otherIndex != edgeIndex) {
+            membershipBounds[count++] = edges[otherIndex];
+          }
+        }
+        if (edge.intersects(planetModel, p, notablePoints, points, bounds, membershipBounds)) {
+          //System.err.println(" intersects!");
+          return true;
+        }
+      }
+    }
+    //System.err.println(" no intersection");
+    return false;
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+
+    // Add all the points
+    for (final GeoPoint point : points) {
+      bounds.addPoint(point);
+    }
+
+    // Add planes with membership.
+    for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
+      final SidedPlane edge = edges[edgeIndex];
+      // Construct boundaries
+      final Membership[] membershipBounds = new Membership[edges.length - 1];
+      int count = 0;
+      for (int otherIndex = 0; otherIndex < edges.length; otherIndex++) {
+        if (otherIndex != edgeIndex) {
+          membershipBounds[count++] = edges[otherIndex];
+        }
+      }
+      bounds.addPlane(planetModel, edge, membershipBounds);
+    }
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    double minimumDistance = Double.MAX_VALUE;
+    for (final GeoPoint edgePoint : points) {
+      final double newDist = distanceStyle.computeDistance(edgePoint, x,y,z);
+      if (newDist < minimumDistance) {
+        minimumDistance = newDist;
+      }
+    }
+    for (int edgeIndex = 0; edgeIndex < edges.length; edgeIndex++) {
+      final Plane edgePlane = edges[edgeIndex];
+      final Membership[] membershipBounds = new Membership[edges.length - 1];
+      int count = 0;
+      for (int otherIndex = 0; otherIndex < edges.length; otherIndex++) {
+        if (otherIndex != edgeIndex) {
+          membershipBounds[count++] = edges[otherIndex];
+        }
+      }
+      final double newDist = distanceStyle.computeDistance(planetModel, edgePlane, x, y, z, membershipBounds);
+      if (newDist < minimumDistance) {
+        minimumDistance = newDist;
+      }
+    }
+    return minimumDistance;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoConvexPolygon))
+      return false;
+    GeoConvexPolygon other = (GeoConvexPolygon) o;
+    if (!super.equals(other))
+      return false;
+    if (!other.isInternalEdges.equals(isInternalEdges))
+      return false;
+    return (other.points.equals(points));
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + points.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoConvexPolygon: {planetmodel=" + planetModel + ", points=" + points + "}";
+  }
+}
+  

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateHorizontalLine.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateHorizontalLine.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateHorizontalLine.java
new file mode 100644
index 0000000..b7de0c2
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateHorizontalLine.java
@@ -0,0 +1,215 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Degenerate bounding box limited on two sides (left lon, right lon).
+ * The left-right maximum extent for this shape is PI; for anything larger, use
+ * GeoWideDegenerateHorizontalLine.
+ *
+ * @lucene.internal
+ */
+public class GeoDegenerateHorizontalLine extends GeoBaseBBox {
+  /** Latitude of horizontal line */
+  protected final double latitude;
+  /** Left bounding longitude of line */
+  protected final double leftLon;
+  /** Right bounding longitude of line */
+  protected final double rightLon;
+
+  /** Left hand endpoint of line */
+  protected final GeoPoint LHC;
+  /** Right hand endpoint of line */
+  protected final GeoPoint RHC;
+
+  /** The plane describing the line */
+  protected final Plane plane;
+  /** The left side end plane */
+  protected final SidedPlane leftPlane;
+  /** The right side end plane */
+  protected final SidedPlane rightPlane;
+
+  /** Notable points for the line */
+  protected final GeoPoint[] planePoints;
+
+  /** Center of line */
+  protected final GeoPoint centerPoint;
+  /** A point that's on the line */
+  protected final GeoPoint[] edgePoints;
+
+  /**
+   * Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, lon: {@code -PI -> PI}
+   *@param planetModel is the planet model.
+   *@param latitude is the latitude of the line.
+   *@param leftLon is the left end longitude.
+   *@param rightLon is the right end longitude.
+   */
+  public GeoDegenerateHorizontalLine(final PlanetModel planetModel, final double latitude, final double leftLon, double rightLon) {
+    super(planetModel);
+    // Argument checking
+    if (latitude > Math.PI * 0.5 || latitude < -Math.PI * 0.5)
+      throw new IllegalArgumentException("Latitude out of range");
+    if (leftLon < -Math.PI || leftLon > Math.PI)
+      throw new IllegalArgumentException("Left longitude out of range");
+    if (rightLon < -Math.PI || rightLon > Math.PI)
+      throw new IllegalArgumentException("Right longitude out of range");
+    double extent = rightLon - leftLon;
+    if (extent < 0.0) {
+      extent += 2.0 * Math.PI;
+    }
+    if (extent > Math.PI)
+      throw new IllegalArgumentException("Width of rectangle too great");
+
+    this.latitude = latitude;
+    this.leftLon = leftLon;
+    this.rightLon = rightLon;
+
+    final double sinLatitude = Math.sin(latitude);
+    final double cosLatitude = Math.cos(latitude);
+    final double sinLeftLon = Math.sin(leftLon);
+    final double cosLeftLon = Math.cos(leftLon);
+    final double sinRightLon = Math.sin(rightLon);
+    final double cosRightLon = Math.cos(rightLon);
+
+    // Now build the two points
+    this.LHC = new GeoPoint(planetModel, sinLatitude, sinLeftLon, cosLatitude, cosLeftLon, latitude, leftLon);
+    this.RHC = new GeoPoint(planetModel, sinLatitude, sinRightLon, cosLatitude, cosRightLon, latitude, rightLon);
+
+    this.plane = new Plane(planetModel, sinLatitude);
+
+    // Normalize
+    while (leftLon > rightLon) {
+      rightLon += Math.PI * 2.0;
+    }
+    final double middleLon = (leftLon + rightLon) * 0.5;
+    final double sinMiddleLon = Math.sin(middleLon);
+    final double cosMiddleLon = Math.cos(middleLon);
+
+    this.centerPoint = new GeoPoint(planetModel, sinLatitude, sinMiddleLon, cosLatitude, cosMiddleLon);
+    this.leftPlane = new SidedPlane(centerPoint, cosLeftLon, sinLeftLon);
+    this.rightPlane = new SidedPlane(centerPoint, cosRightLon, sinRightLon);
+
+    this.planePoints = new GeoPoint[]{LHC, RHC};
+
+    this.edgePoints = new GeoPoint[]{centerPoint};
+  }
+
+  @Override
+  public GeoBBox expand(final double angle) {
+    double newTopLat = latitude + angle;
+    double newBottomLat = latitude - angle;
+    // Figuring out when we escalate to a special case requires some prefiguring
+    double currentLonSpan = rightLon - leftLon;
+    if (currentLonSpan < 0.0)
+      currentLonSpan += Math.PI * 2.0;
+    double newLeftLon = leftLon - angle;
+    double newRightLon = rightLon + angle;
+    if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
+      newLeftLon = -Math.PI;
+      newRightLon = Math.PI;
+    }
+    return GeoBBoxFactory.makeGeoBBox(planetModel, newTopLat, newBottomLat, newLeftLon, newRightLon);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    return plane.evaluateIsZero(x, y, z) &&
+        leftPlane.isWithin(x, y, z) &&
+        rightPlane.isWithin(x, y, z);
+  }
+
+  @Override
+  public double getRadius() {
+    double topAngle = centerPoint.arcDistance(RHC);
+    double bottomAngle = centerPoint.arcDistance(LHC);
+    return Math.max(topAngle, bottomAngle);
+  }
+
+  @Override
+  public GeoPoint getCenter() {
+    return centerPoint;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    return p.intersects(planetModel, plane, notablePoints, planePoints, bounds, leftPlane, rightPlane);
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+    bounds.addHorizontalPlane(planetModel, latitude, plane, leftPlane, rightPlane)
+      .addPoint(LHC).addPoint(RHC);
+  }
+
+  @Override
+  public int getRelationship(final GeoShape path) {
+    //System.err.println("getting relationship between "+this+" and "+path);
+    if (path.intersects(plane, planePoints, leftPlane, rightPlane)) {
+      //System.err.println(" overlaps");
+      return OVERLAPS;
+    }
+
+    if (path.isWithin(centerPoint)) {
+      //System.err.println(" contains");
+      return CONTAINS;
+    }
+
+    //System.err.println(" disjoint");
+    return DISJOINT;
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    final double distance = distanceStyle.computeDistance(planetModel, plane, x,y,z, leftPlane, rightPlane);
+    
+    final double LHCDistance = distanceStyle.computeDistance(LHC, x,y,z);
+    final double RHCDistance = distanceStyle.computeDistance(RHC, x,y,z);
+    
+    return Math.min(
+      distance,
+      Math.min(LHCDistance, RHCDistance));
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoDegenerateHorizontalLine))
+      return false;
+    GeoDegenerateHorizontalLine other = (GeoDegenerateHorizontalLine) o;
+    return super.equals(other) && other.LHC.equals(LHC) && other.RHC.equals(RHC);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + LHC.hashCode();
+    result = 31 * result + RHC.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoDegenerateHorizontalLine: {planetmodel="+planetModel+", latitude=" + latitude + "(" + latitude * 180.0 / Math.PI + "), leftlon=" + leftLon + "(" + leftLon * 180.0 / Math.PI + "), rightLon=" + rightLon + "(" + rightLon * 180.0 / Math.PI + ")}";
+  }
+}
+  
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLatitudeZone.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLatitudeZone.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLatitudeZone.java
new file mode 100644
index 0000000..e794123
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLatitudeZone.java
@@ -0,0 +1,138 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * This GeoBBox represents an area rectangle of one specific latitude with
+ * no longitude bounds.
+ *
+ * @lucene.internal
+ */
+public class GeoDegenerateLatitudeZone extends GeoBaseBBox {
+  /** The latitude */
+  protected final double latitude;
+  /** Sine of the latitude */
+  protected final double sinLatitude;
+  /** Plane describing the latitude zone */
+  protected final Plane plane;
+  /** A point on the world that's also on the zone */
+  protected final GeoPoint interiorPoint;
+  /** An array consisting of the interiorPoint */
+  protected final GeoPoint[] edgePoints;
+  /** No notable points */
+  protected final static GeoPoint[] planePoints = new GeoPoint[0];
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   *@param latitude is the latitude of the latitude zone.
+   */
+  public GeoDegenerateLatitudeZone(final PlanetModel planetModel, final double latitude) {
+    super(planetModel);
+    this.latitude = latitude;
+
+    this.sinLatitude = Math.sin(latitude);
+    double cosLatitude = Math.cos(latitude);
+    this.plane = new Plane(planetModel, sinLatitude);
+    // Compute an interior point.
+    interiorPoint = new GeoPoint(planetModel, sinLatitude, 0.0, cosLatitude, 1.0);
+    edgePoints = new GeoPoint[]{interiorPoint};
+  }
+
+  @Override
+  public GeoBBox expand(final double angle) {
+    double newTopLat = latitude + angle;
+    double newBottomLat = latitude - angle;
+    return GeoBBoxFactory.makeGeoBBox(planetModel, newTopLat, newBottomLat, -Math.PI, Math.PI);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    return Math.abs(z - this.sinLatitude) < 1e-10;
+  }
+
+  @Override
+  public double getRadius() {
+    return Math.PI;
+  }
+
+  @Override
+  public GeoPoint getCenter() {
+    // Totally arbitrary
+    return interiorPoint;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    return p.intersects(planetModel, plane, notablePoints, planePoints, bounds);
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+    bounds.noLongitudeBound()
+      .addHorizontalPlane(planetModel, latitude, plane);
+  }
+
+  @Override
+  public int getRelationship(final GeoShape path) {
+    // Second, the shortcut of seeing whether endpoints are in/out is not going to
+    // work with no area endpoints.  So we rely entirely on intersections.
+    //System.out.println("Got here! latitude="+latitude+" path="+path);
+
+    if (path.intersects(plane, planePoints)) {
+      return OVERLAPS;
+    }
+
+    if (path.isWithin(interiorPoint)) {
+      return CONTAINS;
+    }
+
+    return DISJOINT;
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    return distanceStyle.computeDistance(planetModel, plane, x,y,z);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoDegenerateLatitudeZone))
+      return false;
+    GeoDegenerateLatitudeZone other = (GeoDegenerateLatitudeZone) o;
+    return super.equals(other) && other.latitude == latitude;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp = Double.doubleToLongBits(latitude);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoDegenerateLatitudeZone: {planetmodel="+planetModel+", lat=" + latitude + "(" + latitude * 180.0 / Math.PI + ")}";
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLongitudeSlice.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLongitudeSlice.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLongitudeSlice.java
new file mode 100644
index 0000000..0bb7b90
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateLongitudeSlice.java
@@ -0,0 +1,153 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Degenerate longitude slice.
+ *
+ * @lucene.internal
+ */
+public class GeoDegenerateLongitudeSlice extends GeoBaseBBox {
+  /** The longitude of the slice */
+  protected final double longitude;
+
+  /** The bounding plane for the slice (through both poles, perpendicular to the slice) */
+  protected final SidedPlane boundingPlane;
+  /** The plane of the slice */
+  protected final Plane plane;
+  /** A point on the slice */
+  protected final GeoPoint interiorPoint;
+  /** An array consisting of the one point chosen on the slice */
+  protected final GeoPoint[] edgePoints;
+  /** Notable points for the slice (north and south poles) */
+  protected final GeoPoint[] planePoints;
+
+  /**
+   * Accepts only values in the following ranges: lon: {@code -PI -> PI}
+   */
+  public GeoDegenerateLongitudeSlice(final PlanetModel planetModel, final double longitude) {
+    super(planetModel);
+    // Argument checking
+    if (longitude < -Math.PI || longitude > Math.PI)
+      throw new IllegalArgumentException("Longitude out of range");
+    this.longitude = longitude;
+
+    final double sinLongitude = Math.sin(longitude);
+    final double cosLongitude = Math.cos(longitude);
+
+    this.plane = new Plane(cosLongitude, sinLongitude);
+    // We need a bounding plane too, which is perpendicular to the longitude plane and sided so that the point (0.0, longitude) is inside.
+    this.interiorPoint = new GeoPoint(planetModel, 0.0, sinLongitude, 1.0, cosLongitude);
+    this.boundingPlane = new SidedPlane(interiorPoint, -sinLongitude, cosLongitude);
+    this.edgePoints = new GeoPoint[]{interiorPoint};
+    this.planePoints = new GeoPoint[]{planetModel.NORTH_POLE, planetModel.SOUTH_POLE};
+  }
+
+  @Override
+  public GeoBBox expand(final double angle) {
+    // Figuring out when we escalate to a special case requires some prefiguring
+    double newLeftLon = longitude - angle;
+    double newRightLon = longitude + angle;
+    double currentLonSpan = 2.0 * angle;
+    if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
+      newLeftLon = -Math.PI;
+      newRightLon = Math.PI;
+    }
+    return GeoBBoxFactory.makeGeoBBox(planetModel, Math.PI * 0.5, -Math.PI * 0.5, newLeftLon, newRightLon);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    return plane.evaluateIsZero(x, y, z) &&
+        boundingPlane.isWithin(x, y, z);
+  }
+
+  @Override
+  public double getRadius() {
+    return Math.PI * 0.5;
+  }
+
+  @Override
+  public GeoPoint getCenter() {
+    return interiorPoint;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    return p.intersects(planetModel, plane, notablePoints, planePoints, bounds, boundingPlane);
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+    bounds
+      .addVerticalPlane(planetModel, longitude, plane, boundingPlane)
+      .addPoint(planetModel.NORTH_POLE).addPoint(planetModel.SOUTH_POLE);
+  }
+
+  @Override
+  public int getRelationship(final GeoShape path) {
+    // Look for intersections.
+    if (path.intersects(plane, planePoints, boundingPlane))
+      return OVERLAPS;
+
+    if (path.isWithin(interiorPoint))
+      return CONTAINS;
+
+    return DISJOINT;
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    final double distance = distanceStyle.computeDistance(planetModel, plane, x,y,z, boundingPlane);
+    
+    final double northDistance = distanceStyle.computeDistance(planetModel.NORTH_POLE, x,y,z);
+    final double southDistance = distanceStyle.computeDistance(planetModel.SOUTH_POLE, x,y,z);
+    
+    return Math.min(
+      distance,
+      Math.min(northDistance, southDistance));
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoDegenerateLongitudeSlice))
+      return false;
+    GeoDegenerateLongitudeSlice other = (GeoDegenerateLongitudeSlice) o;
+    return super.equals(other) && other.longitude == longitude;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp = Double.doubleToLongBits(longitude);
+    result = result * 31 + (int) (temp ^ (temp >>> 32));
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoDegenerateLongitudeSlice: {planetmodel="+planetModel+", longitude=" + longitude + "(" + longitude * 180.0 / Math.PI + ")}";
+  }
+}
+  
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java
new file mode 100644
index 0000000..fcd2037
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegeneratePoint.java
@@ -0,0 +1,135 @@
+/*
+ * 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.spatial3d.geom;
+    
+/**
+ * This class represents a degenerate point bounding box.
+ * It is not a simple GeoPoint because we must have the latitude and longitude.
+ *
+ * @lucene.internal
+ */
+public class GeoDegeneratePoint extends GeoPoint implements GeoBBox, GeoCircle {
+  /** Current planet model, since we don't extend BasePlanetObject */
+  protected final PlanetModel planetModel;
+  /** Edge point is an area containing just this */
+  protected final GeoPoint[] edgePoints;
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   *@param lat is the latitude.
+   *@param lon is the longitude.
+   */
+  public GeoDegeneratePoint(final PlanetModel planetModel, final double lat, final double lon) {
+    super(planetModel, lat, lon);
+    this.planetModel = planetModel;
+    this.edgePoints = new GeoPoint[]{this};
+  }
+
+  @Override
+  public GeoBBox expand(final double angle) {
+    final double newTopLat = latitude + angle;
+    final double newBottomLat = latitude - angle;
+    final double newLeftLon = longitude - angle;
+    final double newRightLon = longitude + angle;
+    return GeoBBoxFactory.makeGeoBBox(planetModel, newTopLat, newBottomLat, newLeftLon, newRightLon);
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
+    // If not on the plane, no intersection
+    if (!plane.evaluateIsZero(this))
+      return false;
+
+    for (Membership m : bounds) {
+      if (!m.isWithin(this))
+        return false;
+    }
+    return true;
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    bounds.addPoint(this);
+  }
+
+  @Override
+  public double computeOutsideDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
+    return distanceStyle.computeDistance(this, point);
+  }
+
+  @Override
+  public double computeOutsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    return distanceStyle.computeDistance(this, x,y,z);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoDegeneratePoint))
+      return false;
+    GeoDegeneratePoint other = (GeoDegeneratePoint) o;
+    return super.equals(other) && other.latitude == latitude && other.longitude == longitude;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoDegeneratePoint: {planetmodel="+planetModel+", lat=" + latitude + "(" + latitude * 180.0 / Math.PI + "), lon=" + longitude + "(" + longitude * 180.0 / Math.PI + ")}";
+  }
+
+  @Override
+  public boolean isWithin(final Vector point) {
+    return isWithin(point.x, point.y, point.z);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    return x == this.x && y == this.y && z == this.z;
+  }
+
+  @Override
+  public double getRadius() {
+    return 0.0;
+  }
+
+  @Override
+  public GeoPoint getCenter() {
+    return this;
+  }
+
+  @Override
+  public int getRelationship(final GeoShape shape) {
+    if (shape.isWithin(this)) {
+      //System.err.println("Degenerate point "+this+" is WITHIN shape "+shape);
+      return CONTAINS;
+    }
+
+    //System.err.println("Degenerate point "+this+" is NOT within shape "+shape);
+    return DISJOINT;
+  }
+
+  @Override
+  public double computeDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    if (isWithin(x,y,z))
+      return 0.0;
+    return Double.MAX_VALUE;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateVerticalLine.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateVerticalLine.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateVerticalLine.java
new file mode 100644
index 0000000..dff53b4
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDegenerateVerticalLine.java
@@ -0,0 +1,205 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Degenerate bounding box limited on two sides (top lat, bottom lat).
+ *
+ * @lucene.internal
+ */
+public class GeoDegenerateVerticalLine extends GeoBaseBBox {
+  /** Top latitude of the vertical line */
+  protected final double topLat;
+  /** Bottom latitude of the vertical line */
+  protected final double bottomLat;
+  /** Longitude of the vertical line */
+  protected final double longitude;
+
+  /** Point at the upper end of the vertical line */
+  protected final GeoPoint UHC;
+  /** Point at the lower end of the vertical line */
+  protected final GeoPoint LHC;
+
+  /** Top end cutoff plane */
+  protected final SidedPlane topPlane;
+  /** Bottom end cutoff plane */
+  protected final SidedPlane bottomPlane;
+  /** Back-side cutoff plane */
+  protected final SidedPlane boundingPlane;
+  /** The vertical line plane */
+  protected final Plane plane;
+  /** Notable points for the line (end points) */
+  protected final GeoPoint[] planePoints;
+  /** A computed center point for the line */
+  protected final GeoPoint centerPoint;
+  /** A point that's on the line */
+  protected final GeoPoint[] edgePoints;
+
+  /**
+   * Accepts only values in the following ranges: lat: {@code -PI/2 -> PI/2}, longitude: {@code -PI -> PI}
+   */
+  public GeoDegenerateVerticalLine(final PlanetModel planetModel, final double topLat, final double bottomLat, final double longitude) {
+    super(planetModel);
+    // Argument checking
+    if (topLat > Math.PI * 0.5 || topLat < -Math.PI * 0.5)
+      throw new IllegalArgumentException("Top latitude out of range");
+    if (bottomLat > Math.PI * 0.5 || bottomLat < -Math.PI * 0.5)
+      throw new IllegalArgumentException("Bottom latitude out of range");
+    if (topLat < bottomLat)
+      throw new IllegalArgumentException("Top latitude less than bottom latitude");
+    if (longitude < -Math.PI || longitude > Math.PI)
+      throw new IllegalArgumentException("Longitude out of range");
+
+    this.topLat = topLat;
+    this.bottomLat = bottomLat;
+    this.longitude = longitude;
+
+    final double sinTopLat = Math.sin(topLat);
+    final double cosTopLat = Math.cos(topLat);
+    final double sinBottomLat = Math.sin(bottomLat);
+    final double cosBottomLat = Math.cos(bottomLat);
+    final double sinLongitude = Math.sin(longitude);
+    final double cosLongitude = Math.cos(longitude);
+
+    // Now build the two points
+    this.UHC = new GeoPoint(planetModel, sinTopLat, sinLongitude, cosTopLat, cosLongitude, topLat, longitude);
+    this.LHC = new GeoPoint(planetModel, sinBottomLat, sinLongitude, cosBottomLat, cosLongitude, bottomLat, longitude);
+
+    this.plane = new Plane(cosLongitude, sinLongitude);
+
+    final double middleLat = (topLat + bottomLat) * 0.5;
+    final double sinMiddleLat = Math.sin(middleLat);
+    final double cosMiddleLat = Math.cos(middleLat);
+
+    this.centerPoint = new GeoPoint(planetModel, sinMiddleLat, sinLongitude, cosMiddleLat, cosLongitude);
+
+    this.topPlane = new SidedPlane(centerPoint, planetModel, sinTopLat);
+    this.bottomPlane = new SidedPlane(centerPoint, planetModel, sinBottomLat);
+
+    this.boundingPlane = new SidedPlane(centerPoint, -sinLongitude, cosLongitude);
+
+    this.planePoints = new GeoPoint[]{UHC, LHC};
+
+    this.edgePoints = new GeoPoint[]{centerPoint};
+  }
+
+  @Override
+  public GeoBBox expand(final double angle) {
+    final double newTopLat = topLat + angle;
+    final double newBottomLat = bottomLat - angle;
+    double newLeftLon = longitude - angle;
+    double newRightLon = longitude + angle;
+    double currentLonSpan = 2.0 * angle;
+    if (currentLonSpan + 2.0 * angle >= Math.PI * 2.0) {
+      newLeftLon = -Math.PI;
+      newRightLon = Math.PI;
+    }
+    return GeoBBoxFactory.makeGeoBBox(planetModel, newTopLat, newBottomLat, newLeftLon, newRightLon);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    return plane.evaluateIsZero(x, y, z) &&
+        boundingPlane.isWithin(x, y, z) &&
+        topPlane.isWithin(x, y, z) &&
+        bottomPlane.isWithin(x, y, z);
+  }
+
+  @Override
+  public double getRadius() {
+    // Here we compute the distance from the middle point to one of the corners.  However, we need to be careful
+    // to use the longest of three distances: the distance to a corner on the top; the distnace to a corner on the bottom, and
+    // the distance to the right or left edge from the center.
+    final double topAngle = centerPoint.arcDistance(UHC);
+    final double bottomAngle = centerPoint.arcDistance(LHC);
+    return Math.max(topAngle, bottomAngle);
+  }
+
+  @Override
+  public GeoPoint getCenter() {
+    return centerPoint;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    return p.intersects(planetModel, plane, notablePoints, planePoints, bounds, boundingPlane, topPlane, bottomPlane);
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+    bounds.addVerticalPlane(planetModel, longitude, plane, boundingPlane, topPlane, bottomPlane)
+      .addPoint(UHC).addPoint(LHC);
+  }
+
+  @Override
+  public int getRelationship(final GeoShape path) {
+    //System.err.println(this+" relationship to "+path);
+    if (path.intersects(plane, planePoints, boundingPlane, topPlane, bottomPlane)) {
+      //System.err.println(" overlaps");
+      return OVERLAPS;
+    }
+
+    if (path.isWithin(centerPoint)) {
+      //System.err.println(" contains");
+      return CONTAINS;
+    }
+
+    //System.err.println(" disjoint");
+    return DISJOINT;
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    final double distance = distanceStyle.computeDistance(planetModel, plane, x,y,z, topPlane, bottomPlane, boundingPlane);
+    
+    final double UHCDistance = distanceStyle.computeDistance(UHC, x,y,z);
+    final double LHCDistance = distanceStyle.computeDistance(LHC, x,y,z);
+    
+    return Math.min(
+      distance,
+      Math.min(UHCDistance, LHCDistance));
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoDegenerateVerticalLine))
+      return false;
+    GeoDegenerateVerticalLine other = (GeoDegenerateVerticalLine) o;
+    return super.equals(other) && other.UHC.equals(UHC) && other.LHC.equals(LHC);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + UHC.hashCode();
+    result = 31 * result + LHC.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoDegenerateVerticalLine: {longitude=" + longitude + "(" + longitude * 180.0 / Math.PI + "), toplat=" + topLat + "(" + topLat * 180.0 / Math.PI + "), bottomlat=" + bottomLat + "(" + bottomLat * 180.0 / Math.PI + ")}";
+  }
+}
+  
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistance.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistance.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistance.java
new file mode 100755
index 0000000..d41dd51
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistance.java
@@ -0,0 +1,59 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * An implementer of this interface is capable of computing the described "distance" values,
+ * which are meant to provide both actual distance values, as well as
+ * distance estimates that can be computed more cheaply.
+ *
+ * @lucene.experimental
+ */
+public interface GeoDistance extends Membership {
+  
+  // The following methods compute distances from the shape to a point
+  // expected to be INSIDE the shape.  Typically a value of Double.MAX_VALUE
+  // is returned for points that happen to be outside the shape.
+
+  /**
+   * Compute this shape's <em>internal</em> "distance" to the GeoPoint.
+   * Implementations should clarify how this is computed when it's non-obvious.
+   * A return value of Double.MAX_VALUE should be returned for
+   * points outside of the shape.
+   *
+   * @param distanceStyle is the distance style.
+   * @param point is the point to compute the distance to.
+   * @return the distance.
+   */
+  public default double computeDistance(final DistanceStyle distanceStyle, final GeoPoint point) {
+    return computeDistance(distanceStyle, point.x, point.y, point.z);
+  }
+
+  /**
+   * Compute this shape's <em>internal</em> "distance" to the GeoPoint.
+   * Implementations should clarify how this is computed when it's non-obvious.
+   * A return value of Double.MAX_VALUE should be returned for
+   * points outside of the shape.
+   *
+   * @param x is the point's unit x coordinate (using U.S. convention).
+   * @param y is the point's unit y coordinate (using U.S. convention).
+   * @param z is the point's unit z coordinate (using U.S. convention).
+   * @return the distance.
+   */
+  public double computeDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistanceShape.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistanceShape.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistanceShape.java
new file mode 100755
index 0000000..e7b0348
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoDistanceShape.java
@@ -0,0 +1,27 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * Distance shapes have capabilities of both geohashing and distance
+ * computation (which also includes point membership determination).
+ *
+ * @lucene.experimental
+ */
+public interface GeoDistanceShape extends GeoMembershipShape, GeoDistance {
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/f7f81c32/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoLatitudeZone.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoLatitudeZone.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoLatitudeZone.java
new file mode 100755
index 0000000..912ca32
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoLatitudeZone.java
@@ -0,0 +1,198 @@
+/*
+ * 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.spatial3d.geom;
+
+/**
+ * This GeoBBox represents an area rectangle limited only in latitude.
+ *
+ * @lucene.internal
+ */
+public class GeoLatitudeZone extends GeoBaseBBox {
+  /** The top latitude of the zone */
+  protected final double topLat;
+  /** The bottom latitude of the zone */
+  protected final double bottomLat;
+  /** Cosine of the top lat */
+  protected final double cosTopLat;
+  /** Cosine of the bottom lat */
+  protected final double cosBottomLat;
+  /** The top plane */
+  protected final SidedPlane topPlane;
+  /** The bottom plane */
+  protected final SidedPlane bottomPlane;
+  /** An interior point */
+  protected final GeoPoint interiorPoint;
+  /** Notable points (none) */
+  protected final static GeoPoint[] planePoints = new GeoPoint[0];
+
+  // We need two additional points because a latitude zone's boundaries don't intersect.  This is a very
+  // special case that most GeoBBox's do not have.
+  
+  /** Top boundary point */
+  protected final GeoPoint topBoundaryPoint;
+  /** Bottom boundary point */
+  protected final GeoPoint bottomBoundaryPoint;
+  /** A point on each distinct edge */
+  protected final GeoPoint[] edgePoints;
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   *@param topLat is the top latitude.
+   *@param bottomLat is the bottom latitude.
+   */
+  public GeoLatitudeZone(final PlanetModel planetModel, final double topLat, final double bottomLat) {
+    super(planetModel);
+    this.topLat = topLat;
+    this.bottomLat = bottomLat;
+
+    final double sinTopLat = Math.sin(topLat);
+    final double sinBottomLat = Math.sin(bottomLat);
+    this.cosTopLat = Math.cos(topLat);
+    this.cosBottomLat = Math.cos(bottomLat);
+
+    // Compute an interior point.  Pick one whose lat is between top and bottom.
+    final double middleLat = (topLat + bottomLat) * 0.5;
+    final double sinMiddleLat = Math.sin(middleLat);
+    this.interiorPoint = new GeoPoint(planetModel, sinMiddleLat, 0.0, Math.sqrt(1.0 - sinMiddleLat * sinMiddleLat), 1.0);
+    this.topBoundaryPoint = new GeoPoint(planetModel, sinTopLat, 0.0, Math.sqrt(1.0 - sinTopLat * sinTopLat), 1.0);
+    this.bottomBoundaryPoint = new GeoPoint(planetModel, sinBottomLat, 0.0, Math.sqrt(1.0 - sinBottomLat * sinBottomLat), 1.0);
+
+    this.topPlane = new SidedPlane(interiorPoint, planetModel, sinTopLat);
+    this.bottomPlane = new SidedPlane(interiorPoint, planetModel, sinBottomLat);
+
+    this.edgePoints = new GeoPoint[]{topBoundaryPoint, bottomBoundaryPoint};
+  }
+
+  @Override
+  public GeoBBox expand(final double angle) {
+    final double newTopLat = topLat + angle;
+    final double newBottomLat = bottomLat - angle;
+    return GeoBBoxFactory.makeGeoBBox(planetModel, newTopLat, newBottomLat, -Math.PI, Math.PI);
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    return topPlane.isWithin(x, y, z) &&
+        bottomPlane.isWithin(x, y, z);
+  }
+
+  @Override
+  public double getRadius() {
+    // This is a bit tricky.  I guess we should interpret this as meaning the angle of a circle that
+    // would contain all the bounding box points, when starting in the "center".
+    if (topLat > 0.0 && bottomLat < 0.0)
+      return Math.PI;
+    double maxCosLat = cosTopLat;
+    if (maxCosLat < cosBottomLat)
+      maxCosLat = cosBottomLat;
+    return maxCosLat * Math.PI;
+  }
+
+  @Override
+  public GeoPoint getCenter() {
+    // This is totally arbitrary and only a cartesian could agree with it.
+    return interiorPoint;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane p, final GeoPoint[] notablePoints, final Membership... bounds) {
+    return p.intersects(planetModel, topPlane, notablePoints, planePoints, bounds, bottomPlane) ||
+        p.intersects(planetModel, bottomPlane, notablePoints, planePoints, bounds, topPlane);
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+    bounds.noLongitudeBound()
+      .addHorizontalPlane(planetModel, topLat, topPlane)
+      .addHorizontalPlane(planetModel, bottomLat, bottomPlane);
+  }
+
+  @Override
+  public int getRelationship(final GeoShape path) {
+    final int insideRectangle = isShapeInsideBBox(path);
+    if (insideRectangle == SOME_INSIDE)
+      return OVERLAPS;
+
+    final boolean topBoundaryInsideShape = path.isWithin(topBoundaryPoint);
+    final boolean bottomBoundaryInsideShape = path.isWithin(bottomBoundaryPoint);
+
+    if (topBoundaryInsideShape && !bottomBoundaryInsideShape ||
+        !topBoundaryInsideShape && bottomBoundaryInsideShape)
+      return OVERLAPS;
+
+    final boolean insideShape = topBoundaryInsideShape && bottomBoundaryInsideShape;
+
+    if (insideRectangle == ALL_INSIDE && insideShape)
+      return OVERLAPS;
+
+    // Second, the shortcut of seeing whether endpoints are in/out is not going to
+    // work with no area endpoints.  So we rely entirely on intersections.
+
+    if (path.intersects(topPlane, planePoints, bottomPlane) ||
+        path.intersects(bottomPlane, planePoints, topPlane))
+      return OVERLAPS;
+
+    // There is another case for latitude zones only.  This is when the boundaries of the shape all fit
+    // within the zone, but the shape includes areas outside the zone crossing a pole.
+    // In this case, the above "overlaps" check is insufficient.  We also need to check a point on either boundary
+    // whether it is within the shape.  If both such points are within, then CONTAINS is the right answer.  If
+    // one such point is within, then OVERLAPS is the right answer.
+
+    if (insideShape)
+      return CONTAINS;
+
+    if (insideRectangle == ALL_INSIDE)
+      return WITHIN;
+
+    return DISJOINT;
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    final double topDistance = distanceStyle.computeDistance(planetModel, topPlane, x,y,z, bottomPlane);
+    final double bottomDistance = distanceStyle.computeDistance(planetModel, bottomPlane, x,y,z, topPlane);
+
+    return Math.min(topDistance, bottomDistance);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoLatitudeZone))
+      return false;
+    GeoLatitudeZone other = (GeoLatitudeZone) o;
+    return super.equals(other) && other.topBoundaryPoint.equals(topBoundaryPoint) && other.bottomBoundaryPoint.equals(bottomBoundaryPoint);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    result = 31 * result + topBoundaryPoint.hashCode();
+    result = 31 * result + bottomBoundaryPoint.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoLatitudeZone: {planetmodel="+planetModel+", toplat=" + topLat + "(" + topLat * 180.0 / Math.PI + "), bottomlat=" + bottomLat + "(" + bottomLat * 180.0 / Math.PI + ")}";
+  }
+}