You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by kw...@apache.org on 2016/04/05 12:12:34 UTC

[1/2] lucene-solr:branch_6x: LUCENE-7176: Hide GeoPath implementation in a factory/interface.

Repository: lucene-solr
Updated Branches:
  refs/heads/branch_6x 56a4b6af3 -> 784e3e386


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java
new file mode 100755
index 0000000..1546439
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java
@@ -0,0 +1,797 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * GeoShape representing a path across the surface of the globe,
+ * with a specified half-width.  Path is described by a series of points.
+ * Distances are measured from the starting point along the path, and then at right
+ * angles to the path.
+ *
+ * @lucene.internal
+ */
+class GeoStandardPath extends GeoBasePath {
+  /** The cutoff angle (width) */
+  protected final double cutoffAngle;
+
+  /** Sine of cutoff angle */
+  protected final double sinAngle;
+  /** Cosine of cutoff angle */
+  protected final double cosAngle;
+
+  /** The original list of path points */
+  protected final List<GeoPoint> points = new ArrayList<GeoPoint>();
+  
+  /** A list of SegmentEndpoints */
+  protected List<SegmentEndpoint> endPoints;
+  /** A list of PathSegments */
+  protected List<PathSegment> segments;
+
+  /** A point on the edge */
+  protected GeoPoint[] edgePoints;
+
+  /** Set to true if path has been completely constructed */
+  protected boolean isDone = false;
+  
+  /** Constructor.
+   *@param planetModel is the planet model.
+   *@param maxCutoffAngle is the width of the path, measured as an angle.
+   *@param pathPoints are the points in the path.
+   */
+  public GeoStandardPath(final PlanetModel planetModel, final double maxCutoffAngle, final GeoPoint[] pathPoints) {
+    this(planetModel, maxCutoffAngle);
+    Collections.addAll(points, pathPoints);
+    done();
+  }
+  
+  /** Piece-wise constructor.  Use in conjunction with addPoint() and done().
+   *@param planetModel is the planet model.
+   *@param maxCutoffAngle is the width of the path, measured as an angle.
+   */
+  public GeoStandardPath(final PlanetModel planetModel, final double maxCutoffAngle) {
+    super(planetModel);
+    if (maxCutoffAngle <= 0.0 || maxCutoffAngle > Math.PI * 0.5)
+      throw new IllegalArgumentException("Cutoff angle out of bounds");
+    this.cutoffAngle = maxCutoffAngle;
+    this.cosAngle = Math.cos(maxCutoffAngle);
+    this.sinAngle = Math.sin(maxCutoffAngle);
+  }
+
+  /** Add a point to the path.
+   *@param lat is the latitude of the point.
+   *@param lon is the longitude of the point.
+   */
+  public void addPoint(final double lat, final double lon) {
+    if (isDone)
+      throw new IllegalStateException("Can't call addPoint() if done() already called");
+    points.add(new GeoPoint(planetModel, lat, lon));
+  }
+  
+  /** Complete the path.
+   */
+  public void done() {
+    if (isDone)
+      throw new IllegalStateException("Can't call done() twice");
+    if (points.size() == 0)
+      throw new IllegalArgumentException("Path must have at least one point");
+    isDone = true;
+
+    endPoints = new ArrayList<>(points.size());
+    segments = new ArrayList<>(points.size());
+    // Compute an offset to use for all segments.  This will be based on the minimum magnitude of
+    // the entire ellipsoid.
+    final double cutoffOffset = this.sinAngle * planetModel.getMinimumMagnitude();
+    
+    // First, build all segments.  We'll then go back and build corresponding segment endpoints.
+    GeoPoint lastPoint = null;
+    for (final GeoPoint end : points) {
+      if (lastPoint != null) {
+        final Plane normalizedConnectingPlane = new Plane(lastPoint, end);
+        if (normalizedConnectingPlane == null) {
+          continue;
+        }
+        segments.add(new PathSegment(planetModel, lastPoint, end, normalizedConnectingPlane, cutoffOffset));
+      }
+      lastPoint = end;
+    }
+    
+    if (segments.size() == 0) {
+      // Simple circle
+      double lat = points.get(0).getLatitude();
+      double lon = points.get(0).getLongitude();
+      // Compute two points on the circle, with the right angle from the center.  We'll use these
+      // to obtain the perpendicular plane to the circle.
+      double upperLat = lat + cutoffAngle;
+      double upperLon = lon;
+      if (upperLat > Math.PI * 0.5) {
+        upperLon += Math.PI;
+        if (upperLon > Math.PI)
+          upperLon -= 2.0 * Math.PI;
+        upperLat = Math.PI - upperLat;
+      }
+      double lowerLat = lat - cutoffAngle;
+      double lowerLon = lon;
+      if (lowerLat < -Math.PI * 0.5) {
+        lowerLon += Math.PI;
+        if (lowerLon > Math.PI)
+          lowerLon -= 2.0 * Math.PI;
+        lowerLat = -Math.PI - lowerLat;
+      }
+      final GeoPoint upperPoint = new GeoPoint(planetModel, upperLat, upperLon);
+      final GeoPoint lowerPoint = new GeoPoint(planetModel, lowerLat, lowerLon);
+      final GeoPoint point = points.get(0);
+      
+      // Construct normal plane
+      final Plane normalPlane = Plane.constructNormalizedZPlane(upperPoint, lowerPoint, point);
+
+      final SegmentEndpoint onlyEndpoint = new SegmentEndpoint(point, normalPlane, upperPoint, lowerPoint);
+      endPoints.add(onlyEndpoint);
+      this.edgePoints = new GeoPoint[]{onlyEndpoint.circlePlane.getSampleIntersectionPoint(planetModel, normalPlane)};
+      return;
+    }
+    
+    // Create segment endpoints.  Use an appropriate constructor for the start and end of the path.
+    for (int i = 0; i < segments.size(); i++) {
+      final PathSegment currentSegment = segments.get(i);
+      
+      if (i == 0) {
+        // Starting endpoint
+        final SegmentEndpoint startEndpoint = new SegmentEndpoint(currentSegment.start, 
+          currentSegment.startCutoffPlane, currentSegment.ULHC, currentSegment.LLHC);
+        endPoints.add(startEndpoint);
+        this.edgePoints = new GeoPoint[]{currentSegment.ULHC};
+        continue;
+      }
+      
+      // General intersection case
+      final PathSegment prevSegment = segments.get(i-1);
+      // We construct four separate planes, and evaluate which one includes all interior points with least overlap
+      final SidedPlane candidate1 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, prevSegment.URHC, currentSegment.ULHC, currentSegment.LLHC);
+      final SidedPlane candidate2 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, currentSegment.ULHC, currentSegment.LLHC, prevSegment.LRHC);
+      final SidedPlane candidate3 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, currentSegment.LLHC, prevSegment.LRHC, prevSegment.URHC);
+      final SidedPlane candidate4 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, prevSegment.LRHC, prevSegment.URHC, currentSegment.ULHC);
+
+      if (candidate1 == null && candidate2 == null && candidate3 == null && candidate4 == null) {
+        // The planes are identical.  We wouldn't need a circle at all except for the possibility of
+        // backing up, which is hard to detect here.
+        final SegmentEndpoint midEndpoint = new SegmentEndpoint(currentSegment.start, 
+          prevSegment.endCutoffPlane, currentSegment.startCutoffPlane, currentSegment.ULHC, currentSegment.LLHC);
+        //don't need a circle at all.  Special constructor...
+        endPoints.add(midEndpoint);
+      } else {
+        endPoints.add(new SegmentEndpoint(currentSegment.start,
+          prevSegment.endCutoffPlane, currentSegment.startCutoffPlane,
+          prevSegment.URHC, prevSegment.LRHC,
+          currentSegment.ULHC, currentSegment.LLHC,
+          candidate1, candidate2, candidate3, candidate4));
+      }
+    }
+    // Do final endpoint
+    final PathSegment lastSegment = segments.get(segments.size()-1);
+    endPoints.add(new SegmentEndpoint(lastSegment.end,
+      lastSegment.endCutoffPlane, lastSegment.URHC, lastSegment.LRHC));
+
+  }
+
+  @Override
+  protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    // Algorithm:
+    // (1) If the point is within any of the segments along the path, return that value.
+    // (2) If the point is within any of the segment end circles along the path, return that value.
+    double currentDistance = 0.0;
+    for (PathSegment segment : segments) {
+      double distance = segment.pathDistance(planetModel, distanceStyle, x,y,z);
+      if (distance != Double.MAX_VALUE)
+        return currentDistance + distance;
+      currentDistance += segment.fullPathDistance(distanceStyle);
+    }
+
+    int segmentIndex = 0;
+    currentDistance = 0.0;
+    for (SegmentEndpoint endpoint : endPoints) {
+      double distance = endpoint.pathDistance(distanceStyle, x, y, z);
+      if (distance != Double.MAX_VALUE)
+        return currentDistance + distance;
+      if (segmentIndex < segments.size())
+        currentDistance += segments.get(segmentIndex++).fullPathDistance(distanceStyle);
+    }
+
+    return Double.MAX_VALUE;
+  }
+
+  @Override
+  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+    double minDistance = Double.MAX_VALUE;
+    for (final SegmentEndpoint endpoint : endPoints) {
+      final double newDistance = endpoint.outsideDistance(distanceStyle, x,y,z);
+      if (newDistance < minDistance)
+        minDistance = newDistance;
+    }
+    for (final PathSegment segment : segments) {
+      final double newDistance = segment.outsideDistance(planetModel, distanceStyle, x, y, z);
+      if (newDistance < minDistance)
+        minDistance = newDistance;
+    }
+    return minDistance;
+  }
+
+  @Override
+  public boolean isWithin(final double x, final double y, final double z) {
+    for (SegmentEndpoint pathPoint : endPoints) {
+      if (pathPoint.isWithin(x, y, z))
+        return true;
+    }
+    for (PathSegment pathSegment : segments) {
+      if (pathSegment.isWithin(x, y, z))
+        return true;
+    }
+    return false;
+  }
+
+  @Override
+  public GeoPoint[] getEdgePoints() {
+    return edgePoints;
+  }
+
+  @Override
+  public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
+    // We look for an intersection with any of the exterior edges of the path.
+    // We also have to look for intersections with the cones described by the endpoints.
+    // Return "true" if any such intersections are found.
+
+    // For plane intersections, the basic idea is to come up with an equation of the line that is
+    // the intersection (if any).  Then, find the intersections with the unit sphere (if any).  If
+    // any of the intersection points are within the bounds, then we've detected an intersection.
+    // Well, sort of.  We can detect intersections also due to overlap of segments with each other.
+    // But that's an edge case and we won't be optimizing for it.
+    //System.err.println(" Looking for intersection of plane "+plane+" with path "+this);
+    for (final SegmentEndpoint pathPoint : endPoints) {
+      if (pathPoint.intersects(planetModel, plane, notablePoints, bounds)) {
+        return true;
+      }
+    }
+
+    for (final PathSegment pathSegment : segments) {
+      if (pathSegment.intersects(planetModel, plane, notablePoints, bounds)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  @Override
+  public void getBounds(Bounds bounds) {
+    super.getBounds(bounds);
+    // For building bounds, order matters.  We want to traverse
+    // never more than 180 degrees longitude at a pop or we risk having the
+    // bounds object get itself inverted.  So do the edges first.
+    for (PathSegment pathSegment : segments) {
+      pathSegment.getBounds(planetModel, bounds);
+    }
+    for (SegmentEndpoint pathPoint : endPoints) {
+      pathPoint.getBounds(planetModel, bounds);
+    }
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof GeoStandardPath))
+      return false;
+    GeoStandardPath p = (GeoStandardPath) o;
+    if (!super.equals(p))
+      return false;
+    if (cutoffAngle != p.cutoffAngle)
+      return false;
+    return points.equals(p.points);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = super.hashCode();
+    long temp = Double.doubleToLongBits(cutoffAngle);
+    result = 31 * result + (int) (temp ^ (temp >>> 32));
+    result = 31 * result + points.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "GeoStandardPath: {planetmodel=" + planetModel+", width=" + cutoffAngle + "(" + cutoffAngle * 180.0 / Math.PI + "), points={" + points + "}}";
+  }
+
+  /**
+   * This is precalculated data for segment endpoint.
+   * Note well: This is not necessarily a circle.  There are four cases:
+   * (1) The path consists of a single endpoint.  In this case, we build a simple circle with the proper cutoff offset.
+   * (2) This is the end of a path.  The circle plane must be constructed to go through two supplied points and be perpendicular to a connecting plane.
+   * (2.5) Intersection, but the path on both sides is linear.  We generate a circle, but we use the cutoff planes to limit its influence in the straight line case.
+   * (3) This is an intersection in a path.  We are supplied FOUR planes.  If there are intersections within bounds for both upper and lower, then
+   *    we generate no circle at all.  If there is one intersection only, then we generate a plane that includes that intersection, as well as the remaining
+   *    cutoff plane/edge plane points.
+   */
+  public static class SegmentEndpoint {
+    /** The center point of the endpoint */
+    public final GeoPoint point;
+    /** A plane describing the circle */
+    public final SidedPlane circlePlane;
+    /** Pertinent cutoff planes from adjoining segments */
+    public final Membership[] cutoffPlanes;
+    /** Notable points for this segment endpoint */
+    public final GeoPoint[] notablePoints;
+    /** No notable points from the circle itself */
+    public final static GeoPoint[] circlePoints = new GeoPoint[0];
+    /** Null membership */
+    public final static Membership[] NO_MEMBERSHIP = new Membership[0];
+    
+    /** Base case.  Does nothing at all.
+     */
+    public SegmentEndpoint(final GeoPoint point) {
+      this.point = point;
+      this.circlePlane = null;
+      this.cutoffPlanes = null;
+      this.notablePoints = null;
+    }
+    
+    /** Constructor for case (1).
+     * Generate a simple circle cutoff plane.
+     *@param point is the center point.
+     *@param upperPoint is a point that must be on the circle plane.
+     *@param lowerPoint is another point that must be on the circle plane.
+     */
+    public SegmentEndpoint(final GeoPoint point, final Plane normalPlane, final GeoPoint upperPoint, final GeoPoint lowerPoint) {
+      this.point = point;
+      // Construct a sided plane that goes through the two points and whose normal is in the normalPlane.
+      this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, normalPlane, upperPoint, lowerPoint);
+      this.cutoffPlanes = NO_MEMBERSHIP;
+      this.notablePoints = circlePoints;
+    }
+    
+    /** Constructor for case (2).
+     * Generate an endpoint, given a single cutoff plane plus upper and lower edge points.
+     *@param point is the center point.
+     *@param cutoffPlane is the plane from the adjoining path segment marking the boundary between this endpoint and that segment.
+     *@param topEdgePoint is a point on the cutoffPlane that should be also on the circle plane.
+     *@param bottomEdgePoint is another point on the cutoffPlane that should be also on the circle plane.
+     */
+    public SegmentEndpoint(final GeoPoint point,
+      final SidedPlane cutoffPlane, final GeoPoint topEdgePoint, final GeoPoint bottomEdgePoint) {
+      this.point = point;
+      this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane)};
+      this.notablePoints = new GeoPoint[]{topEdgePoint, bottomEdgePoint};
+      // To construct the plane, we now just need D, which is simply the negative of the evaluation of the circle normal vector at one of the points.
+      this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, cutoffPlane, topEdgePoint, bottomEdgePoint);
+    }
+
+    /** Constructor for case (2.5).
+     * Generate an endpoint, given two cutoff planes plus upper and lower edge points.
+     *@param point is the center.
+     *@param cutoffPlane1 is one adjoining path segment cutoff plane.
+     *@param cutoffPlane2 is another adjoining path segment cutoff plane.
+     *@param topEdgePoint is a point on the cutoffPlane that should be also on the circle plane.
+     *@param bottomEdgePoint is another point on the cutoffPlane that should be also on the circle plane.
+     */
+    public SegmentEndpoint(final GeoPoint point,
+      final SidedPlane cutoffPlane1, final SidedPlane cutoffPlane2, final GeoPoint topEdgePoint, final GeoPoint bottomEdgePoint) {
+      this.point = point;
+      this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane1), new SidedPlane(cutoffPlane2)};
+      this.notablePoints = new GeoPoint[]{topEdgePoint, bottomEdgePoint};
+      // To construct the plane, we now just need D, which is simply the negative of the evaluation of the circle normal vector at one of the points.
+      this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, cutoffPlane1, topEdgePoint, bottomEdgePoint);
+    }
+    
+    /** Constructor for case (3).
+     * Generate an endpoint for an intersection, given four points.
+     *@param point is the center.
+     *@param prevCutoffPlane is the previous adjoining segment cutoff plane.
+     *@param nextCutoffPlane is the next path segment cutoff plane.
+     *@param notCand2Point is a point NOT on candidate2.
+     *@param notCand1Point is a point NOT on candidate1.
+     *@param notCand3Point is a point NOT on candidate3.
+     *@param notCand4Point is a point NOT on candidate4.
+     *@param candidate1 one of four candidate circle planes.
+     *@param candidate2 one of four candidate circle planes.
+     *@param candidate3 one of four candidate circle planes.
+     *@param candidate4 one of four candidate circle planes.
+     */
+    public SegmentEndpoint(final GeoPoint point,
+      final SidedPlane prevCutoffPlane, final SidedPlane nextCutoffPlane,
+      final GeoPoint notCand2Point, final GeoPoint notCand1Point,
+      final GeoPoint notCand3Point, final GeoPoint notCand4Point,
+      final SidedPlane candidate1, final SidedPlane candidate2, final SidedPlane candidate3, final SidedPlane candidate4) {
+      // Note: What we really need is a single plane that goes through all four points.
+      // Since that's not possible in the ellipsoid case (because three points determine a plane, not four), we
+      // need an approximation that at least creates a boundary that has no interruptions.
+      // There are three obvious choices for the third point: either (a) one of the two remaining points, or (b) the top or bottom edge
+      // intersection point.  (a) has no guarantee of continuity, while (b) is capable of producing something very far from a circle if
+      // the angle between segments is acute.
+      // The solution is to look for the side (top or bottom) that has an intersection within the shape.  We use the two points from
+      // the opposite side to determine the plane, AND we pick the third to be either of the two points on the intersecting side
+      // PROVIDED that the other point is within the final circle we come up with.
+      this.point = point;
+      
+      // We construct four separate planes, and evaluate which one includes all interior points with least overlap
+      // (Constructed beforehand because we need them for degeneracy check)
+
+      final boolean cand1IsOtherWithin = candidate1!=null?candidate1.isWithin(notCand1Point):false;
+      final boolean cand2IsOtherWithin = candidate2!=null?candidate2.isWithin(notCand2Point):false;
+      final boolean cand3IsOtherWithin = candidate3!=null?candidate3.isWithin(notCand3Point):false;
+      final boolean cand4IsOtherWithin = candidate4!=null?candidate4.isWithin(notCand4Point):false;
+      
+      if (cand1IsOtherWithin && cand2IsOtherWithin && cand3IsOtherWithin && cand4IsOtherWithin) {
+        // The only way we should see both within is if all four points are coplanar.  In that case, we default to the simplest treatment.
+        this.circlePlane = candidate1;  // doesn't matter which
+        this.notablePoints = new GeoPoint[]{notCand2Point, notCand3Point, notCand1Point, notCand4Point};
+        this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane), new SidedPlane(nextCutoffPlane)};
+      } else if (cand1IsOtherWithin) {
+        // Use candidate1, and DON'T include prevCutoffPlane in the cutoff planes list
+        this.circlePlane = candidate1;
+        this.notablePoints = new GeoPoint[]{notCand2Point, notCand3Point, notCand4Point};
+        this.cutoffPlanes = new Membership[]{new SidedPlane(nextCutoffPlane)};
+      } else if (cand2IsOtherWithin) {
+        // Use candidate2
+        this.circlePlane = candidate2;
+        this.notablePoints = new GeoPoint[]{notCand3Point, notCand4Point, notCand1Point};
+        this.cutoffPlanes = new Membership[]{new SidedPlane(nextCutoffPlane)};
+      } else if (cand3IsOtherWithin) {
+        this.circlePlane = candidate3;
+        this.notablePoints = new GeoPoint[]{notCand4Point, notCand1Point, notCand2Point};
+        this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane)};
+      } else if (cand4IsOtherWithin) {
+        this.circlePlane = candidate4;
+        this.notablePoints = new GeoPoint[]{notCand1Point, notCand2Point, notCand3Point};
+        this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane)};
+      } else {
+        // dunno what happened
+        throw new RuntimeException("Couldn't come up with a plane through three points that included the fourth");
+      }
+    }
+
+    /** Check if point is within this endpoint.
+     *@param point is the point.
+     *@return true of within.
+     */
+    public boolean isWithin(final Vector point) {
+      if (circlePlane == null)
+        return false;
+      if (!circlePlane.isWithin(point))
+        return false;
+      for (final Membership m : cutoffPlanes) {
+        if (!m.isWithin(point)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /** Check if point is within this endpoint.
+     *@param x is the point x.
+     *@param y is the point y.
+     *@param z is the point z.
+     *@return true of within.
+     */
+    public boolean isWithin(final double x, final double y, final double z) {
+      if (circlePlane == null)
+        return false;
+      if (!circlePlane.isWithin(x, y, z))
+        return false;
+      for (final Membership m : cutoffPlanes) {
+        if (!m.isWithin(x,y,z)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /** Compute interior path distance.
+     *@param distanceStyle is the distance style.
+     *@param x is the point x.
+     *@param y is the point y.
+     *@param z is the point z.
+     *@return the distance metric.
+     */
+    public double pathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+      if (!isWithin(x,y,z))
+        return Double.MAX_VALUE;
+      return distanceStyle.computeDistance(this.point, x, y, z);
+    }
+
+    /** Compute external distance.
+     *@param distanceStyle is the distance style.
+     *@param x is the point x.
+     *@param y is the point y.
+     *@param z is the point z.
+     *@return the distance metric.
+     */
+    public double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+      return distanceStyle.computeDistance(this.point, x, y, z);
+    }
+
+    /** Determine if this endpoint intersects a specified plane.
+     *@param planetModel is the planet model.
+     *@param p is the plane.
+     *@param notablePoints are the points associated with the plane.
+     *@param bounds are any bounds which the intersection must lie within.
+     *@return true if there is a matching intersection.
+     */
+    public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
+      //System.err.println("  looking for intersection between plane "+p+" and circle "+circlePlane+" on proper side of "+cutoffPlanes+" within "+bounds);
+      if (circlePlane == null)
+        return false;
+      return circlePlane.intersects(planetModel, p, notablePoints, this.notablePoints, bounds, this.cutoffPlanes);
+    }
+
+    /** Get the bounds for a segment endpoint.
+     *@param planetModel is the planet model.
+     *@param bounds are the bounds to be modified.
+     */
+    public void getBounds(final PlanetModel planetModel, Bounds bounds) {
+      bounds.addPoint(point);
+      if (circlePlane == null)
+        return;
+      bounds.addPlane(planetModel, circlePlane);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof SegmentEndpoint))
+        return false;
+      SegmentEndpoint other = (SegmentEndpoint) o;
+      return point.equals(other.point);
+    }
+
+    @Override
+    public int hashCode() {
+      return point.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return point.toString();
+    }
+  }
+
+  /**
+   * This is the pre-calculated data for a path segment.
+   */
+  public static class PathSegment {
+    /** Starting point of the segment */
+    public final GeoPoint start;
+    /** End point of the segment */
+    public final GeoPoint end;
+    /** Place to keep any complete segment distances we've calculated so far */
+    public final Map<DistanceStyle,Double> fullDistanceCache = new HashMap<DistanceStyle,Double>();
+    /** Normalized plane connecting the two points and going through world center */
+    public final Plane normalizedConnectingPlane;
+    /** Cutoff plane parallel to connecting plane representing one side of the path segment */
+    public final SidedPlane upperConnectingPlane;
+    /** Cutoff plane parallel to connecting plane representing the other side of the path segment */
+    public final SidedPlane lowerConnectingPlane;
+    /** Plane going through the center and start point, marking the start edge of the segment */
+    public final SidedPlane startCutoffPlane;
+    /** Plane going through the center and end point, marking the end edge of the segment */
+    public final SidedPlane endCutoffPlane;
+    /** Upper right hand corner of segment */
+    public final GeoPoint URHC;
+    /** Lower right hand corner of segment */
+    public final GeoPoint LRHC;
+    /** Upper left hand corner of segment */
+    public final GeoPoint ULHC;
+    /** Lower left hand corner of segment */
+    public final GeoPoint LLHC;
+    /** Notable points for the upper connecting plane */
+    public final GeoPoint[] upperConnectingPlanePoints;
+    /** Notable points for the lower connecting plane */
+    public final GeoPoint[] lowerConnectingPlanePoints;
+    /** Notable points for the start cutoff plane */
+    public final GeoPoint[] startCutoffPlanePoints;
+    /** Notable points for the end cutoff plane */
+    public final GeoPoint[] endCutoffPlanePoints;
+
+    /** Construct a path segment.
+     *@param planetModel is the planet model.
+     *@param start is the starting point.
+     *@param end is the ending point.
+     *@param normalizedConnectingPlane is the connecting plane.
+     *@param planeBoundingOffset is the linear offset from the connecting plane to either side.
+     */
+    public PathSegment(final PlanetModel planetModel, final GeoPoint start, final GeoPoint end,
+      final Plane normalizedConnectingPlane, final double planeBoundingOffset) {
+      this.start = start;
+      this.end = end;
+      this.normalizedConnectingPlane = normalizedConnectingPlane;
+        
+      // Either start or end should be on the correct side
+      upperConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, -planeBoundingOffset);
+      lowerConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, planeBoundingOffset);
+      // Cutoff planes use opposite endpoints as correct side examples
+      startCutoffPlane = new SidedPlane(end, normalizedConnectingPlane, start);
+      endCutoffPlane = new SidedPlane(start, normalizedConnectingPlane, end);
+      final Membership[] upperSide = new Membership[]{upperConnectingPlane};
+      final Membership[] lowerSide = new Membership[]{lowerConnectingPlane};
+      final Membership[] startSide = new Membership[]{startCutoffPlane};
+      final Membership[] endSide = new Membership[]{endCutoffPlane};
+      GeoPoint[] points;
+      points = upperConnectingPlane.findIntersections(planetModel, startCutoffPlane, lowerSide, endSide);
+      if (points.length == 0) {
+        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
+      }
+      this.ULHC = points[0];
+      points = upperConnectingPlane.findIntersections(planetModel, endCutoffPlane, lowerSide, startSide);
+      if (points.length == 0) {
+        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
+      }
+      this.URHC = points[0];
+      points = lowerConnectingPlane.findIntersections(planetModel, startCutoffPlane, upperSide, endSide);
+      if (points.length == 0) {
+        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
+      }
+      this.LLHC = points[0];
+      points = lowerConnectingPlane.findIntersections(planetModel, endCutoffPlane, upperSide, startSide);
+      if (points.length == 0) {
+        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
+      }
+      this.LRHC = points[0];
+      upperConnectingPlanePoints = new GeoPoint[]{ULHC, URHC};
+      lowerConnectingPlanePoints = new GeoPoint[]{LLHC, LRHC};
+      startCutoffPlanePoints = new GeoPoint[]{ULHC, LLHC};
+      endCutoffPlanePoints = new GeoPoint[]{URHC, LRHC};
+    }
+
+    /** Compute the full distance along this path segment.
+     *@param distanceStyle is the distance style.
+     *@return the distance metric.
+     */
+    public double fullPathDistance(final DistanceStyle distanceStyle) {
+      synchronized (fullDistanceCache) {
+        Double dist = fullDistanceCache.get(distanceStyle);
+        if (dist == null) {
+          dist = new Double(distanceStyle.computeDistance(start, end.x, end.y, end.z));
+          fullDistanceCache.put(distanceStyle, dist);
+        }
+        return dist.doubleValue();
+      }
+    }
+  
+    /** Check if point is within this segment.
+     *@param point is the point.
+     *@return true of within.
+     */
+    public boolean isWithin(final Vector point) {
+      return startCutoffPlane.isWithin(point) &&
+          endCutoffPlane.isWithin(point) &&
+          upperConnectingPlane.isWithin(point) &&
+          lowerConnectingPlane.isWithin(point);
+    }
+
+    /** Check if point is within this segment.
+     *@param x is the point x.
+     *@param y is the point y.
+     *@param z is the point z.
+     *@return true of within.
+     */
+    public boolean isWithin(final double x, final double y, final double z) {
+      return startCutoffPlane.isWithin(x, y, z) &&
+          endCutoffPlane.isWithin(x, y, z) &&
+          upperConnectingPlane.isWithin(x, y, z) &&
+          lowerConnectingPlane.isWithin(x, y, z);
+    }
+
+    /** Compute interior path distance.
+     *@param planetModel is the planet model.
+     *@param distanceStyle is the distance style.
+     *@param x is the point x.
+     *@param y is the point y.
+     *@param z is the point z.
+     *@return the distance metric.
+     */
+    public double pathDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+      if (!isWithin(x,y,z))
+        return Double.MAX_VALUE;
+
+      // (1) Compute normalizedPerpPlane.  If degenerate, then return point distance from start to point.
+      // Want no allocations or expensive operations!  so we do this the hard way
+      final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
+      final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
+      final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
+      final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
+      if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
+        return distanceStyle.computeDistance(start, x,y,z);
+      final double normFactor = 1.0/magnitude;
+      final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
+      
+      // Old computation: too expensive, because it calculates the intersection point twice.
+      //return distanceStyle.computeDistance(planetModel, normalizedConnectingPlane, x, y, z, startCutoffPlane, endCutoffPlane) +
+      //  distanceStyle.computeDistance(planetModel, normalizedPerpPlane, start.x, start.y, start.z, upperConnectingPlane, lowerConnectingPlane);
+
+      final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
+      GeoPoint thePoint;
+      if (intersectionPoints.length == 0)
+        throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
+      else if (intersectionPoints.length == 1)
+        thePoint = intersectionPoints[0];
+      else {
+        if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
+          thePoint = intersectionPoints[0];
+        else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
+          thePoint = intersectionPoints[1];
+        else
+          throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
+      }
+      return distanceStyle.computeDistance(thePoint, x, y, z) + distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z);
+    }
+
+    /** Compute external distance.
+     *@param planetModel is the planet model.
+     *@param distanceStyle is the distance style.
+     *@param x is the point x.
+     *@param y is the point y.
+     *@param z is the point z.
+     *@return the distance metric.
+     */
+    public double outsideDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
+      final double upperDistance = distanceStyle.computeDistance(planetModel, upperConnectingPlane, x,y,z, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
+      final double lowerDistance = distanceStyle.computeDistance(planetModel, lowerConnectingPlane, x,y,z, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
+      final double startDistance = distanceStyle.computeDistance(planetModel, startCutoffPlane, x,y,z, endCutoffPlane, lowerConnectingPlane, upperConnectingPlane);
+      final double endDistance = distanceStyle.computeDistance(planetModel, endCutoffPlane, x,y,z, startCutoffPlane, lowerConnectingPlane, upperConnectingPlane);
+      final double ULHCDistance = distanceStyle.computeDistance(ULHC, x,y,z);
+      final double URHCDistance = distanceStyle.computeDistance(URHC, x,y,z);
+      final double LLHCDistance = distanceStyle.computeDistance(LLHC, x,y,z);
+      final double LRHCDistance = distanceStyle.computeDistance(LRHC, x,y,z);
+      return Math.min(
+        Math.min(
+          Math.min(upperDistance,lowerDistance),
+          Math.min(startDistance,endDistance)),
+        Math.min(
+          Math.min(ULHCDistance, URHCDistance),
+          Math.min(LLHCDistance, LRHCDistance)));
+    }
+
+    /** Determine if this endpoint intersects a specified plane.
+     *@param planetModel is the planet model.
+     *@param p is the plane.
+     *@param notablePoints are the points associated with the plane.
+     *@param bounds are any bounds which the intersection must lie within.
+     *@return true if there is a matching intersection.
+     */
+    public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
+      return upperConnectingPlane.intersects(planetModel, p, notablePoints, upperConnectingPlanePoints, bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane) ||
+          lowerConnectingPlane.intersects(planetModel, p, notablePoints, lowerConnectingPlanePoints, bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
+    }
+
+    /** Get the bounds for a segment endpoint.
+     *@param planetModel is the planet model.
+     *@param bounds are the bounds to be modified.
+     */
+    public void getBounds(final PlanetModel planetModel, Bounds bounds) {
+      // We need to do all bounding planes as well as corner points
+      bounds.addPoint(start).addPoint(end).addPoint(ULHC).addPoint(URHC).addPoint(LRHC).addPoint(LLHC);
+      bounds.addPlane(planetModel, upperConnectingPlane, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
+      bounds.addPlane(planetModel, lowerConnectingPlane, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
+      bounds.addPlane(planetModel, startCutoffPlane, endCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
+      bounds.addPlane(planetModel, endCutoffPlane, startCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
index 398458c..3caf039 100644
--- a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
+++ b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/TestGeo3DPoint.java
@@ -40,7 +40,7 @@ import org.apache.lucene.spatial3d.geom.GeoArea;
 import org.apache.lucene.spatial3d.geom.GeoAreaFactory;
 import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
 import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
-import org.apache.lucene.spatial3d.geom.GeoPath;
+import org.apache.lucene.spatial3d.geom.GeoPathFactory;
 import org.apache.lucene.spatial3d.geom.GeoPoint;
 import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
 import org.apache.lucene.spatial3d.geom.GeoShape;
@@ -625,13 +625,12 @@ public class TestGeo3DPoint extends LuceneTestCase {
         // Paths
         final int pointCount = random().nextInt(5) + 1;
         final double width = toRadians(random().nextInt(89)+1);
+        final GeoPoint[] points = new GeoPoint[pointCount];
+        for (int i = 0; i < pointCount; i++) {
+          points[i] = new GeoPoint(planetModel, toRadians(randomLat()), toRadians(randomLon()));
+        }
         try {
-          final GeoPath path = new GeoPath(planetModel, width);
-          for (int i = 0; i < pointCount; i++) {
-            path.addPoint(toRadians(randomLat()), toRadians(randomLon()));
-          }
-          path.done();
-          return path;
+          return GeoPathFactory.makeGeoPath(planetModel, width, points);
         } catch (IllegalArgumentException e) {
           // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where
           // the exception is thrown incorrectly, we aren't going to be able to do that in this random test.

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/GeoPathTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/GeoPathTest.java b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/GeoPathTest.java
index 3746069..e68e3f4 100755
--- a/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/GeoPathTest.java
+++ b/lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/GeoPathTest.java
@@ -28,9 +28,9 @@ public class GeoPathTest {
   @Test
   public void testPathDistance() {
     // Start with a really simple case
-    GeoPath p;
+    GeoStandardPath p;
     GeoPoint gp;
-    p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     p.addPoint(0.0, 0.0);
     p.addPoint(0.0, 0.1);
     p.addPoint(0.0, 0.2);
@@ -49,7 +49,7 @@ public class GeoPathTest {
     assertEquals(0.0 + 0.05, p.computeDistance(DistanceStyle.ARC,gp), 0.000001);
 
     // Compute path distances now
-    p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     p.addPoint(0.0, 0.0);
     p.addPoint(0.0, 0.1);
     p.addPoint(0.0, 0.2);
@@ -60,7 +60,7 @@ public class GeoPathTest {
     assertEquals(0.12, p.computeDistance(DistanceStyle.ARC,gp), 0.000001);
 
     // Now try a vertical path, and make sure distances are as expected
-    p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     p.addPoint(-Math.PI * 0.25, -0.5);
     p.addPoint(Math.PI * 0.25, -0.5);
     p.done();
@@ -77,9 +77,9 @@ public class GeoPathTest {
   @Test
   public void testPathPointWithin() {
     // Tests whether we can properly detect whether a point is within a path or not
-    GeoPath p;
+    GeoStandardPath p;
     GeoPoint gp;
-    p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     // Build a diagonal path crossing the equator
     p.addPoint(-0.2, -0.2);
     p.addPoint(0.2, 0.2);
@@ -101,7 +101,7 @@ public class GeoPathTest {
     gp = new GeoPoint(PlanetModel.SPHERE, 0.0, Math.PI);
     assertFalse(p.isWithin(gp));
     // Repeat the test, but across the terminator
-    p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     // Build a diagonal path crossing the equator
     p.addPoint(-0.2, Math.PI - 0.2);
     p.addPoint(0.2, -Math.PI + 0.2);
@@ -128,8 +128,8 @@ public class GeoPathTest {
   @Test
   public void testGetRelationship() {
     GeoArea rect;
-    GeoPath p;
-    GeoPath c;
+    GeoStandardPath p;
+    GeoStandardPath c;
     GeoPoint point;
     GeoPoint pointApprox;
     int relationship;
@@ -137,7 +137,7 @@ public class GeoPathTest {
     PlanetModel planetModel;
     
     planetModel = new PlanetModel(1.151145876105594, 0.8488541238944061);
-    c = new GeoPath(planetModel, 0.008726646259971648);
+    c = new GeoStandardPath(planetModel, 0.008726646259971648);
     c.addPoint(-0.6925658899376476, 0.6316613927914589);
     c.addPoint(0.27828548161836364, 0.6785795524104564);
     c.done();
@@ -148,7 +148,7 @@ public class GeoPathTest {
     
     // Start by testing the basic kinds of relationship, increasing in order of difficulty.
 
-    p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     p.addPoint(-0.3, -0.3);
     p.addPoint(0.3, 0.3);
     p.done();
@@ -179,7 +179,7 @@ public class GeoPathTest {
 
   @Test
   public void testPathBounds() {
-    GeoPath c;
+    GeoStandardPath c;
     LatLonBounds b;
     XYZBounds xyzb;
     GeoPoint point;
@@ -188,7 +188,7 @@ public class GeoPathTest {
     PlanetModel planetModel;
     
     planetModel = new PlanetModel(0.751521665790406,1.248478334209594);
-    c = new GeoPath(planetModel, 0.7504915783575618);
+    c = new GeoStandardPath(planetModel, 0.7504915783575618);
     c.addPoint(0.10869761172400265, 0.08895880215465272);
     c.addPoint(0.22467878641991612, 0.10972973084229565);
     c.addPoint(-0.7398772468744732, -0.4465812941383364);
@@ -202,10 +202,10 @@ public class GeoPathTest {
     relationship = area.getRelationship(c);
     assertTrue(relationship == GeoArea.WITHIN || relationship == GeoArea.OVERLAPS);
     assertTrue(area.isWithin(point));
-    // No longer true due to fixed GeoPath waypoints.
+    // No longer true due to fixed GeoStandardPath waypoints.
     //assertTrue(c.isWithin(point));
     
-    c = new GeoPath(PlanetModel.WGS84, 0.6894050545377601);
+    c = new GeoStandardPath(PlanetModel.WGS84, 0.6894050545377601);
     c.addPoint(-0.0788176065762948, 0.9431251741731624);
     c.addPoint(0.510387871458147, 0.5327078872484678);
     c.addPoint(-0.5624521609859962, 1.5398841746888388);
@@ -224,7 +224,7 @@ public class GeoPathTest {
     assertTrue(relationship == GeoArea.WITHIN || relationship == GeoArea.OVERLAPS);
     assertTrue(area.isWithin(point));
     
-    c = new GeoPath(PlanetModel.WGS84, 0.7766715171374766);
+    c = new GeoStandardPath(PlanetModel.WGS84, 0.7766715171374766);
     c.addPoint(-0.2751718361148076, -0.7786721269011477);
     c.addPoint(0.5728375851539309, -1.2700115736820465);
     c.done();
@@ -240,7 +240,7 @@ public class GeoPathTest {
     assertTrue(relationship == GeoArea.WITHIN || relationship == GeoArea.OVERLAPS);
     assertTrue(area.isWithin(point));
 
-    c = new GeoPath(PlanetModel.SPHERE, 0.1);
+    c = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     c.addPoint(-0.3, -0.3);
     c.addPoint(0.3, 0.3);
     c.done();
@@ -260,7 +260,7 @@ public class GeoPathTest {
   @Test
   public void testCoLinear() {
     // p1: (12,-90), p2: (11, -55), (129, -90)
-    GeoPath p = new GeoPath(PlanetModel.SPHERE, 0.1);
+    GeoStandardPath p = new GeoStandardPath(PlanetModel.SPHERE, 0.1);
     p.addPoint(toRadians(-90), toRadians(12));//south pole
     p.addPoint(toRadians(-55), toRadians(11));
     p.addPoint(toRadians(-90), toRadians(129));//south pole again


[2/2] lucene-solr:branch_6x: LUCENE-7176: Hide GeoPath implementation in a factory/interface.

Posted by kw...@apache.org.
LUCENE-7176: Hide GeoPath implementation in a factory/interface.


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

Branch: refs/heads/branch_6x
Commit: 784e3e38631dce6a6f863390ca43e17836de86eb
Parents: 56a4b6a
Author: Karl Wright <Da...@gmail.com>
Authored: Tue Apr 5 03:41:48 2016 -0400
Committer: Karl Wright <Da...@gmail.com>
Committed: Tue Apr 5 06:10:48 2016 -0400

----------------------------------------------------------------------
 .../lucene/spatial/spatial4j/Geo3dRptTest.java  |  26 +-
 .../Geo3dShapeRectRelationTestCase.java         |  15 +-
 .../Geo3dShapeWGS84ModelRectRelationTest.java   |  15 +-
 .../org/apache/lucene/spatial3d/Geo3DPoint.java |   4 +-
 .../lucene/spatial3d/geom/GeoBaseCircle.java    |   2 +-
 .../lucene/spatial3d/geom/GeoBasePath.java      |  34 +
 .../apache/lucene/spatial3d/geom/GeoPath.java   | 776 +-----------------
 .../lucene/spatial3d/geom/GeoPathFactory.java   |  39 +
 .../lucene/spatial3d/geom/GeoStandardPath.java  | 797 +++++++++++++++++++
 .../apache/lucene/spatial3d/TestGeo3DPoint.java |  13 +-
 .../lucene/spatial3d/geom/GeoPathTest.java      |  36 +-
 11 files changed, 928 insertions(+), 829 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java
index aacea2c..004139e 100644
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dRptTest.java
@@ -29,7 +29,7 @@ import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
 import org.apache.lucene.spatial.query.SpatialOperation;
 import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
 import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
-import org.apache.lucene.spatial3d.geom.GeoPath;
+import org.apache.lucene.spatial3d.geom.GeoPathFactory;
 import org.apache.lucene.spatial3d.geom.GeoPoint;
 import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
 import org.apache.lucene.spatial3d.geom.GeoShape;
@@ -95,12 +95,12 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
     points.add(new GeoPoint(PlanetModel.SPHERE, -57 * DEGREES_TO_RADIANS, 146 * DEGREES_TO_RADIANS));
     points.add(new GeoPoint(PlanetModel.SPHERE, 14 * DEGREES_TO_RADIANS, -180 * DEGREES_TO_RADIANS));
     points.add(new GeoPoint(PlanetModel.SPHERE, -15 * DEGREES_TO_RADIANS, 153 * DEGREES_TO_RADIANS));
-    final GeoPath path = new GeoPath(PlanetModel.SPHERE, 29 * DEGREES_TO_RADIANS);
-    path.addPoint(55.0 * DEGREES_TO_RADIANS, -26.0 * DEGREES_TO_RADIANS);
-    path.addPoint(-90.0 * DEGREES_TO_RADIANS, 0.0);
-    path.addPoint(54.0 * DEGREES_TO_RADIANS, 165.0 * DEGREES_TO_RADIANS);
-    path.addPoint(-90.0 * DEGREES_TO_RADIANS, 0.0);
-    path.done();
+    final GeoPoint[] pathPoints = new GeoPoint[] {
+      new GeoPoint(PlanetModel.SPHERE, 55.0 * DEGREES_TO_RADIANS, -26.0 * DEGREES_TO_RADIANS),
+      new GeoPoint(PlanetModel.SPHERE, -90.0 * DEGREES_TO_RADIANS, 0.0),
+      new GeoPoint(PlanetModel.SPHERE, 54.0 * DEGREES_TO_RADIANS, 165.0 * DEGREES_TO_RADIANS),
+      new GeoPoint(PlanetModel.SPHERE, -90.0 * DEGREES_TO_RADIANS, 0.0)};
+    final GeoShape path = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, 29 * DEGREES_TO_RADIANS, pathPoints);
     final Shape shape = new Geo3dShape(path,ctx);
     final Rectangle rect = ctx.makeRectangle(131, 143, 39, 54);
     testOperation(rect,SpatialOperation.Intersects,shape,true);
@@ -199,14 +199,14 @@ public class Geo3dRptTest extends RandomSpatialOpStrategyTestCase {
         // Paths
         final int pointCount = random().nextInt(5) + 1;
         final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
+        final GeoPoint[] points = new GeoPoint[pointCount];
         while (true) {
+          for (int i = 0; i < pointCount; i++) {
+            final Point nextPoint = randomPoint();
+            points[i] = new GeoPoint(PlanetModel.SPHERE, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
+          }
           try {
-            final GeoPath path = new GeoPath(PlanetModel.SPHERE, width);
-            for (int i = 0; i < pointCount; i++) {
-              final Point nextPoint = randomPoint();
-              path.addPoint(nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
-            }
-            path.done();
+            final GeoShape path = GeoPathFactory.makeGeoPath(PlanetModel.SPHERE, width, points);
             return new Geo3dShape(path, ctx);
           } catch (IllegalArgumentException e) {
             // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
index 2c41f79..f8e51d1 100644
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeRectRelationTestCase.java
@@ -30,7 +30,7 @@ import org.apache.lucene.spatial3d.geom.GeoBBox;
 import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
 import org.apache.lucene.spatial3d.geom.GeoCircle;
 import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
-import org.apache.lucene.spatial3d.geom.GeoPath;
+import org.apache.lucene.spatial3d.geom.GeoPathFactory;
 import org.apache.lucene.spatial3d.geom.GeoPoint;
 import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
 import org.apache.lucene.spatial3d.geom.GeoShape;
@@ -221,14 +221,15 @@ public abstract class Geo3dShapeRectRelationTestCase extends RandomizedShapeTest
         final Circle pointZone = ctx.makeCircle(centerPoint, maxDistance);
         final int pointCount = random().nextInt(5) + 1;
         final double width = (random().nextInt(89)+1) * DEGREES_TO_RADIANS;
+        final GeoPoint[] points = new GeoPoint[pointCount];
         while (true) {
+          for (int i = 0; i < pointCount; i++) {
+            final Point nextPoint = randomPointIn(pointZone);
+            points[i] = new GeoPoint(planetModel, nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
+          }
+          
           try {
-            final GeoPath path = new GeoPath(planetModel, width);
-            for (int i = 0; i < pointCount; i++) {
-              final Point nextPoint = randomPointIn(pointZone);
-              path.addPoint(nextPoint.getY() * DEGREES_TO_RADIANS, nextPoint.getX() * DEGREES_TO_RADIANS);
-            }
-            path.done();
+            final GeoShape path = GeoPathFactory.makeGeoPath(planetModel, width, points);
             return new Geo3dShape(planetModel, path, ctx);
           } catch (IllegalArgumentException e) {
             // This is what happens when we create a shape that is invalid.  Although it is conceivable that there are cases where

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
----------------------------------------------------------------------
diff --git a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
index 00a0903..43b88ca 100644
--- a/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
+++ b/lucene/spatial-extras/src/test/org/apache/lucene/spatial/spatial4j/Geo3dShapeWGS84ModelRectRelationTest.java
@@ -21,6 +21,7 @@ import org.apache.lucene.spatial3d.geom.GeoBBox;
 import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
 import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
 import org.apache.lucene.spatial3d.geom.GeoCircle;
+import org.apache.lucene.spatial3d.geom.GeoPathFactory;
 import org.apache.lucene.spatial3d.geom.GeoPath;
 import org.apache.lucene.spatial3d.geom.GeoPoint;
 import org.apache.lucene.spatial3d.geom.PlanetModel;
@@ -36,9 +37,9 @@ public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelation
   public void testFailure1() {
     final GeoBBox rect = GeoBBoxFactory.makeGeoBBox(planetModel, 90 * RADIANS_PER_DEGREE, 74 * RADIANS_PER_DEGREE,
         40 * RADIANS_PER_DEGREE, 60 * RADIANS_PER_DEGREE);
-    final GeoPath path = new GeoPath(planetModel, 4 * RADIANS_PER_DEGREE);
-    path.addPoint(84.4987594274 * RADIANS_PER_DEGREE, -22.8345484402 * RADIANS_PER_DEGREE);
-    path.done();
+    final GeoPoint[] pathPoints = new GeoPoint[] {
+      new GeoPoint(planetModel, 84.4987594274 * RADIANS_PER_DEGREE, -22.8345484402 * RADIANS_PER_DEGREE)};
+    final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, 4 * RADIANS_PER_DEGREE, pathPoints);
     assertTrue(GeoArea.DISJOINT == rect.getRelationship(path));
     // This is what the test failure claimed...
     //assertTrue(GeoArea.CONTAINS == rect.getRelationship(path));
@@ -75,10 +76,10 @@ public class Geo3dShapeWGS84ModelRectRelationTest extends Geo3dShapeRectRelation
   */
     final GeoBBox rect = GeoBBoxFactory.makeGeoBBox(planetModel, 16 * RADIANS_PER_DEGREE, 16 * RADIANS_PER_DEGREE, 4 * RADIANS_PER_DEGREE, 36 * RADIANS_PER_DEGREE);
     final GeoPoint pt = new GeoPoint(planetModel, 16 * RADIANS_PER_DEGREE, 23.81626064835212 * RADIANS_PER_DEGREE);
-    final GeoPath path = new GeoPath(planetModel, 88 * RADIANS_PER_DEGREE);
-    path.addPoint(46.6369060853 * RADIANS_PER_DEGREE, -79.8452213228 * RADIANS_PER_DEGREE);
-    path.addPoint(54.9779334519 * RADIANS_PER_DEGREE, 132.029177424 * RADIANS_PER_DEGREE);
-    path.done();
+    final GeoPoint[] pathPoints = new GeoPoint[]{
+      new GeoPoint(planetModel, 46.6369060853 * RADIANS_PER_DEGREE, -79.8452213228 * RADIANS_PER_DEGREE),
+      new GeoPoint(planetModel, 54.9779334519 * RADIANS_PER_DEGREE, 132.029177424 * RADIANS_PER_DEGREE)};
+    final GeoPath path = GeoPathFactory.makeGeoPath(planetModel, 88 * RADIANS_PER_DEGREE, pathPoints);
     System.out.println("rect=" + rect);
     // Rectangle is within path (this is wrong; it's on the other side.  Should be OVERLAPS)
     assertTrue(GeoArea.OVERLAPS == rect.getRelationship(path));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
index 944ce39..be8990f 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DPoint.java
@@ -31,7 +31,7 @@ import org.apache.lucene.spatial3d.geom.PlanetModel;
 import org.apache.lucene.spatial3d.geom.GeoCircleFactory;
 import org.apache.lucene.spatial3d.geom.GeoBBoxFactory;
 import org.apache.lucene.spatial3d.geom.GeoPolygonFactory;
-import org.apache.lucene.spatial3d.geom.GeoPath;
+import org.apache.lucene.spatial3d.geom.GeoPathFactory;
 import org.apache.lucene.spatial3d.geom.GeoCompositePolygon;
 import org.apache.lucene.spatial3d.geom.GeoPolygon;
 import org.apache.lucene.search.Query;
@@ -172,7 +172,7 @@ public final class Geo3DPoint extends Field {
       GeoUtils.checkLongitude(longitude);
       points[i] = new GeoPoint(PlanetModel.WGS84, fromDegrees(latitude), fromDegrees(longitude));
     }
-    final GeoShape shape = new GeoPath(PlanetModel.WGS84, fromMeters(pathWidthMeters), points);
+    final GeoShape shape = GeoPathFactory.makeGeoPath(PlanetModel.WGS84, fromMeters(pathWidthMeters), points);
     return newShapeQuery(field, shape);
   }
   

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/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
index b34313a..0599c91 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCircle.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCircle.java
@@ -19,7 +19,7 @@ package org.apache.lucene.spatial3d.geom;
 /**
  * GeoCircles have all the characteristics of GeoBaseDistanceShapes, plus GeoSizeable.
  *
- * @lucene.experimental
+ * @lucene.internal
  */
 abstract class GeoBaseCircle extends GeoBaseDistanceShape implements GeoCircle {
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePath.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePath.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePath.java
new file mode 100644
index 0000000..c726b3a
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBasePath.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;
+
+/**
+ * GeoPaths have all the characteristics of GeoBaseDistanceShapes.
+ *
+ * @lucene.internal
+ */
+abstract class GeoBasePath extends GeoBaseDistanceShape implements GeoPath {
+
+  /** Constructor.
+   *@param planetModel is the planet model to use.
+   */
+  public GeoBasePath(final PlanetModel planetModel) {
+    super(planetModel);
+  }
+
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPath.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPath.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPath.java
old mode 100755
new mode 100644
index a5b8b9b..48a613b
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPath.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPath.java
@@ -16,782 +16,10 @@
  */
 package org.apache.lucene.spatial3d.geom;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 /**
- * GeoShape representing a path across the surface of the globe,
- * with a specified half-width.  Path is described by a series of points.
- * Distances are measured from the starting point along the path, and then at right
- * angles to the path.
+ * Interface describing a path.
  *
  * @lucene.experimental
  */
-public class GeoPath extends GeoBaseDistanceShape {
-  /** The cutoff angle (width) */
-  protected final double cutoffAngle;
-
-  /** Sine of cutoff angle */
-  protected final double sinAngle;
-  /** Cosine of cutoff angle */
-  protected final double cosAngle;
-
-  /** The original list of path points */
-  protected final List<GeoPoint> points = new ArrayList<GeoPoint>();
-  
-  /** A list of SegmentEndpoints */
-  protected List<SegmentEndpoint> endPoints;
-  /** A list of PathSegments */
-  protected List<PathSegment> segments;
-
-  /** A point on the edge */
-  protected GeoPoint[] edgePoints;
-
-  /** Set to true if path has been completely constructed */
-  protected boolean isDone = false;
-  
-  /** Constructor.
-   *@param planetModel is the planet model.
-   *@param maxCutoffAngle is the width of the path, measured as an angle.
-   *@param pathPoints are the points in the path.
-   */
-  public GeoPath(final PlanetModel planetModel, final double maxCutoffAngle, final GeoPoint[] pathPoints) {
-    this(planetModel, maxCutoffAngle);
-    Collections.addAll(points, pathPoints);
-    done();
-  }
-  
-  /** Piece-wise constructor.  Use in conjunction with addPoint() and done().
-   *@param planetModel is the planet model.
-   *@param maxCutoffAngle is the width of the path, measured as an angle.
-   */
-  public GeoPath(final PlanetModel planetModel, final double maxCutoffAngle) {
-    super(planetModel);
-    if (maxCutoffAngle <= 0.0 || maxCutoffAngle > Math.PI * 0.5)
-      throw new IllegalArgumentException("Cutoff angle out of bounds");
-    this.cutoffAngle = maxCutoffAngle;
-    this.cosAngle = Math.cos(maxCutoffAngle);
-    this.sinAngle = Math.sin(maxCutoffAngle);
-  }
-
-  /** Add a point to the path.
-   *@param lat is the latitude of the point.
-   *@param lon is the longitude of the point.
-   */
-  public void addPoint(final double lat, final double lon) {
-    if (isDone)
-      throw new IllegalStateException("Can't call addPoint() if done() already called");
-    points.add(new GeoPoint(planetModel, lat, lon));
-  }
-  
-  /** Complete the path.
-   */
-  public void done() {
-    if (isDone)
-      throw new IllegalStateException("Can't call done() twice");
-    if (points.size() == 0)
-      throw new IllegalArgumentException("Path must have at least one point");
-    isDone = true;
-
-    endPoints = new ArrayList<>(points.size());
-    segments = new ArrayList<>(points.size());
-    // Compute an offset to use for all segments.  This will be based on the minimum magnitude of
-    // the entire ellipsoid.
-    final double cutoffOffset = this.sinAngle * planetModel.getMinimumMagnitude();
-    
-    // First, build all segments.  We'll then go back and build corresponding segment endpoints.
-    GeoPoint lastPoint = null;
-    for (final GeoPoint end : points) {
-      if (lastPoint != null) {
-        final Plane normalizedConnectingPlane = new Plane(lastPoint, end);
-        if (normalizedConnectingPlane == null) {
-          continue;
-        }
-        segments.add(new PathSegment(planetModel, lastPoint, end, normalizedConnectingPlane, cutoffOffset));
-      }
-      lastPoint = end;
-    }
-    
-    if (segments.size() == 0) {
-      // Simple circle
-      double lat = points.get(0).getLatitude();
-      double lon = points.get(0).getLongitude();
-      // Compute two points on the circle, with the right angle from the center.  We'll use these
-      // to obtain the perpendicular plane to the circle.
-      double upperLat = lat + cutoffAngle;
-      double upperLon = lon;
-      if (upperLat > Math.PI * 0.5) {
-        upperLon += Math.PI;
-        if (upperLon > Math.PI)
-          upperLon -= 2.0 * Math.PI;
-        upperLat = Math.PI - upperLat;
-      }
-      double lowerLat = lat - cutoffAngle;
-      double lowerLon = lon;
-      if (lowerLat < -Math.PI * 0.5) {
-        lowerLon += Math.PI;
-        if (lowerLon > Math.PI)
-          lowerLon -= 2.0 * Math.PI;
-        lowerLat = -Math.PI - lowerLat;
-      }
-      final GeoPoint upperPoint = new GeoPoint(planetModel, upperLat, upperLon);
-      final GeoPoint lowerPoint = new GeoPoint(planetModel, lowerLat, lowerLon);
-      final GeoPoint point = points.get(0);
-      
-      // Construct normal plane
-      final Plane normalPlane = Plane.constructNormalizedZPlane(upperPoint, lowerPoint, point);
-
-      final SegmentEndpoint onlyEndpoint = new SegmentEndpoint(point, normalPlane, upperPoint, lowerPoint);
-      endPoints.add(onlyEndpoint);
-      this.edgePoints = new GeoPoint[]{onlyEndpoint.circlePlane.getSampleIntersectionPoint(planetModel, normalPlane)};
-      return;
-    }
-    
-    // Create segment endpoints.  Use an appropriate constructor for the start and end of the path.
-    for (int i = 0; i < segments.size(); i++) {
-      final PathSegment currentSegment = segments.get(i);
-      
-      if (i == 0) {
-        // Starting endpoint
-        final SegmentEndpoint startEndpoint = new SegmentEndpoint(currentSegment.start, 
-          currentSegment.startCutoffPlane, currentSegment.ULHC, currentSegment.LLHC);
-        endPoints.add(startEndpoint);
-        this.edgePoints = new GeoPoint[]{currentSegment.ULHC};
-        continue;
-      }
-      
-      // General intersection case
-      final PathSegment prevSegment = segments.get(i-1);
-      // We construct four separate planes, and evaluate which one includes all interior points with least overlap
-      final SidedPlane candidate1 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, prevSegment.URHC, currentSegment.ULHC, currentSegment.LLHC);
-      final SidedPlane candidate2 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, currentSegment.ULHC, currentSegment.LLHC, prevSegment.LRHC);
-      final SidedPlane candidate3 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, currentSegment.LLHC, prevSegment.LRHC, prevSegment.URHC);
-      final SidedPlane candidate4 = SidedPlane.constructNormalizedThreePointSidedPlane(currentSegment.start, prevSegment.LRHC, prevSegment.URHC, currentSegment.ULHC);
-
-      if (candidate1 == null && candidate2 == null && candidate3 == null && candidate4 == null) {
-        // The planes are identical.  We wouldn't need a circle at all except for the possibility of
-        // backing up, which is hard to detect here.
-        final SegmentEndpoint midEndpoint = new SegmentEndpoint(currentSegment.start, 
-          prevSegment.endCutoffPlane, currentSegment.startCutoffPlane, currentSegment.ULHC, currentSegment.LLHC);
-        //don't need a circle at all.  Special constructor...
-        endPoints.add(midEndpoint);
-      } else {
-        endPoints.add(new SegmentEndpoint(currentSegment.start,
-          prevSegment.endCutoffPlane, currentSegment.startCutoffPlane,
-          prevSegment.URHC, prevSegment.LRHC,
-          currentSegment.ULHC, currentSegment.LLHC,
-          candidate1, candidate2, candidate3, candidate4));
-      }
-    }
-    // Do final endpoint
-    final PathSegment lastSegment = segments.get(segments.size()-1);
-    endPoints.add(new SegmentEndpoint(lastSegment.end,
-      lastSegment.endCutoffPlane, lastSegment.URHC, lastSegment.LRHC));
-
-  }
-
-  @Override
-  protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
-    // Algorithm:
-    // (1) If the point is within any of the segments along the path, return that value.
-    // (2) If the point is within any of the segment end circles along the path, return that value.
-    double currentDistance = 0.0;
-    for (PathSegment segment : segments) {
-      double distance = segment.pathDistance(planetModel, distanceStyle, x,y,z);
-      if (distance != Double.MAX_VALUE)
-        return currentDistance + distance;
-      currentDistance += segment.fullPathDistance(distanceStyle);
-    }
-
-    int segmentIndex = 0;
-    currentDistance = 0.0;
-    for (SegmentEndpoint endpoint : endPoints) {
-      double distance = endpoint.pathDistance(distanceStyle, x, y, z);
-      if (distance != Double.MAX_VALUE)
-        return currentDistance + distance;
-      if (segmentIndex < segments.size())
-        currentDistance += segments.get(segmentIndex++).fullPathDistance(distanceStyle);
-    }
-
-    return Double.MAX_VALUE;
-  }
-
-  @Override
-  protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
-    double minDistance = Double.MAX_VALUE;
-    for (final SegmentEndpoint endpoint : endPoints) {
-      final double newDistance = endpoint.outsideDistance(distanceStyle, x,y,z);
-      if (newDistance < minDistance)
-        minDistance = newDistance;
-    }
-    for (final PathSegment segment : segments) {
-      final double newDistance = segment.outsideDistance(planetModel, distanceStyle, x, y, z);
-      if (newDistance < minDistance)
-        minDistance = newDistance;
-    }
-    return minDistance;
-  }
-
-  @Override
-  public boolean isWithin(final double x, final double y, final double z) {
-    for (SegmentEndpoint pathPoint : endPoints) {
-      if (pathPoint.isWithin(x, y, z))
-        return true;
-    }
-    for (PathSegment pathSegment : segments) {
-      if (pathSegment.isWithin(x, y, z))
-        return true;
-    }
-    return false;
-  }
-
-  @Override
-  public GeoPoint[] getEdgePoints() {
-    return edgePoints;
-  }
-
-  @Override
-  public boolean intersects(final Plane plane, final GeoPoint[] notablePoints, final Membership... bounds) {
-    // We look for an intersection with any of the exterior edges of the path.
-    // We also have to look for intersections with the cones described by the endpoints.
-    // Return "true" if any such intersections are found.
-
-    // For plane intersections, the basic idea is to come up with an equation of the line that is
-    // the intersection (if any).  Then, find the intersections with the unit sphere (if any).  If
-    // any of the intersection points are within the bounds, then we've detected an intersection.
-    // Well, sort of.  We can detect intersections also due to overlap of segments with each other.
-    // But that's an edge case and we won't be optimizing for it.
-    //System.err.println(" Looking for intersection of plane "+plane+" with path "+this);
-    for (final SegmentEndpoint pathPoint : endPoints) {
-      if (pathPoint.intersects(planetModel, plane, notablePoints, bounds)) {
-        return true;
-      }
-    }
-
-    for (final PathSegment pathSegment : segments) {
-      if (pathSegment.intersects(planetModel, plane, notablePoints, bounds)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  @Override
-  public void getBounds(Bounds bounds) {
-    super.getBounds(bounds);
-    // For building bounds, order matters.  We want to traverse
-    // never more than 180 degrees longitude at a pop or we risk having the
-    // bounds object get itself inverted.  So do the edges first.
-    for (PathSegment pathSegment : segments) {
-      pathSegment.getBounds(planetModel, bounds);
-    }
-    for (SegmentEndpoint pathPoint : endPoints) {
-      pathPoint.getBounds(planetModel, bounds);
-    }
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (!(o instanceof GeoPath))
-      return false;
-    GeoPath p = (GeoPath) o;
-    if (!super.equals(p))
-      return false;
-    if (cutoffAngle != p.cutoffAngle)
-      return false;
-    return points.equals(p.points);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = super.hashCode();
-    long temp = Double.doubleToLongBits(cutoffAngle);
-    result = 31 * result + (int) (temp ^ (temp >>> 32));
-    result = 31 * result + points.hashCode();
-    return result;
-  }
-
-  @Override
-  public String toString() {
-    return "GeoPath: {planetmodel=" + planetModel+", width=" + cutoffAngle + "(" + cutoffAngle * 180.0 / Math.PI + "), points={" + points + "}}";
-  }
-
-  /**
-   * This is precalculated data for segment endpoint.
-   * Note well: This is not necessarily a circle.  There are four cases:
-   * (1) The path consists of a single endpoint.  In this case, we build a simple circle with the proper cutoff offset.
-   * (2) This is the end of a path.  The circle plane must be constructed to go through two supplied points and be perpendicular to a connecting plane.
-   * (2.5) Intersection, but the path on both sides is linear.  We generate a circle, but we use the cutoff planes to limit its influence in the straight line case.
-   * (3) This is an intersection in a path.  We are supplied FOUR planes.  If there are intersections within bounds for both upper and lower, then
-   *    we generate no circle at all.  If there is one intersection only, then we generate a plane that includes that intersection, as well as the remaining
-   *    cutoff plane/edge plane points.
-   */
-  public static class SegmentEndpoint {
-    /** The center point of the endpoint */
-    public final GeoPoint point;
-    /** A plane describing the circle */
-    public final SidedPlane circlePlane;
-    /** Pertinent cutoff planes from adjoining segments */
-    public final Membership[] cutoffPlanes;
-    /** Notable points for this segment endpoint */
-    public final GeoPoint[] notablePoints;
-    /** No notable points from the circle itself */
-    public final static GeoPoint[] circlePoints = new GeoPoint[0];
-    /** Null membership */
-    public final static Membership[] NO_MEMBERSHIP = new Membership[0];
-    
-    /** Base case.  Does nothing at all.
-     */
-    public SegmentEndpoint(final GeoPoint point) {
-      this.point = point;
-      this.circlePlane = null;
-      this.cutoffPlanes = null;
-      this.notablePoints = null;
-    }
-    
-    /** Constructor for case (1).
-     * Generate a simple circle cutoff plane.
-     *@param point is the center point.
-     *@param upperPoint is a point that must be on the circle plane.
-     *@param lowerPoint is another point that must be on the circle plane.
-     */
-    public SegmentEndpoint(final GeoPoint point, final Plane normalPlane, final GeoPoint upperPoint, final GeoPoint lowerPoint) {
-      this.point = point;
-      // Construct a sided plane that goes through the two points and whose normal is in the normalPlane.
-      this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, normalPlane, upperPoint, lowerPoint);
-      this.cutoffPlanes = NO_MEMBERSHIP;
-      this.notablePoints = circlePoints;
-    }
-    
-    /** Constructor for case (2).
-     * Generate an endpoint, given a single cutoff plane plus upper and lower edge points.
-     *@param point is the center point.
-     *@param cutoffPlane is the plane from the adjoining path segment marking the boundary between this endpoint and that segment.
-     *@param topEdgePoint is a point on the cutoffPlane that should be also on the circle plane.
-     *@param bottomEdgePoint is another point on the cutoffPlane that should be also on the circle plane.
-     */
-    public SegmentEndpoint(final GeoPoint point,
-      final SidedPlane cutoffPlane, final GeoPoint topEdgePoint, final GeoPoint bottomEdgePoint) {
-      this.point = point;
-      this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane)};
-      this.notablePoints = new GeoPoint[]{topEdgePoint, bottomEdgePoint};
-      // To construct the plane, we now just need D, which is simply the negative of the evaluation of the circle normal vector at one of the points.
-      this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, cutoffPlane, topEdgePoint, bottomEdgePoint);
-    }
-
-    /** Constructor for case (2.5).
-     * Generate an endpoint, given two cutoff planes plus upper and lower edge points.
-     *@param point is the center.
-     *@param cutoffPlane1 is one adjoining path segment cutoff plane.
-     *@param cutoffPlane2 is another adjoining path segment cutoff plane.
-     *@param topEdgePoint is a point on the cutoffPlane that should be also on the circle plane.
-     *@param bottomEdgePoint is another point on the cutoffPlane that should be also on the circle plane.
-     */
-    public SegmentEndpoint(final GeoPoint point,
-      final SidedPlane cutoffPlane1, final SidedPlane cutoffPlane2, final GeoPoint topEdgePoint, final GeoPoint bottomEdgePoint) {
-      this.point = point;
-      this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane1), new SidedPlane(cutoffPlane2)};
-      this.notablePoints = new GeoPoint[]{topEdgePoint, bottomEdgePoint};
-      // To construct the plane, we now just need D, which is simply the negative of the evaluation of the circle normal vector at one of the points.
-      this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, cutoffPlane1, topEdgePoint, bottomEdgePoint);
-    }
-    
-    /** Constructor for case (3).
-     * Generate an endpoint for an intersection, given four points.
-     *@param point is the center.
-     *@param prevCutoffPlane is the previous adjoining segment cutoff plane.
-     *@param nextCutoffPlane is the next path segment cutoff plane.
-     *@param notCand2Point is a point NOT on candidate2.
-     *@param notCand1Point is a point NOT on candidate1.
-     *@param notCand3Point is a point NOT on candidate3.
-     *@param notCand4Point is a point NOT on candidate4.
-     *@param candidate1 one of four candidate circle planes.
-     *@param candidate2 one of four candidate circle planes.
-     *@param candidate3 one of four candidate circle planes.
-     *@param candidate4 one of four candidate circle planes.
-     */
-    public SegmentEndpoint(final GeoPoint point,
-      final SidedPlane prevCutoffPlane, final SidedPlane nextCutoffPlane,
-      final GeoPoint notCand2Point, final GeoPoint notCand1Point,
-      final GeoPoint notCand3Point, final GeoPoint notCand4Point,
-      final SidedPlane candidate1, final SidedPlane candidate2, final SidedPlane candidate3, final SidedPlane candidate4) {
-      // Note: What we really need is a single plane that goes through all four points.
-      // Since that's not possible in the ellipsoid case (because three points determine a plane, not four), we
-      // need an approximation that at least creates a boundary that has no interruptions.
-      // There are three obvious choices for the third point: either (a) one of the two remaining points, or (b) the top or bottom edge
-      // intersection point.  (a) has no guarantee of continuity, while (b) is capable of producing something very far from a circle if
-      // the angle between segments is acute.
-      // The solution is to look for the side (top or bottom) that has an intersection within the shape.  We use the two points from
-      // the opposite side to determine the plane, AND we pick the third to be either of the two points on the intersecting side
-      // PROVIDED that the other point is within the final circle we come up with.
-      this.point = point;
-      
-      // We construct four separate planes, and evaluate which one includes all interior points with least overlap
-      // (Constructed beforehand because we need them for degeneracy check)
-
-      final boolean cand1IsOtherWithin = candidate1!=null?candidate1.isWithin(notCand1Point):false;
-      final boolean cand2IsOtherWithin = candidate2!=null?candidate2.isWithin(notCand2Point):false;
-      final boolean cand3IsOtherWithin = candidate3!=null?candidate3.isWithin(notCand3Point):false;
-      final boolean cand4IsOtherWithin = candidate4!=null?candidate4.isWithin(notCand4Point):false;
-      
-      if (cand1IsOtherWithin && cand2IsOtherWithin && cand3IsOtherWithin && cand4IsOtherWithin) {
-        // The only way we should see both within is if all four points are coplanar.  In that case, we default to the simplest treatment.
-        this.circlePlane = candidate1;  // doesn't matter which
-        this.notablePoints = new GeoPoint[]{notCand2Point, notCand3Point, notCand1Point, notCand4Point};
-        this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane), new SidedPlane(nextCutoffPlane)};
-      } else if (cand1IsOtherWithin) {
-        // Use candidate1, and DON'T include prevCutoffPlane in the cutoff planes list
-        this.circlePlane = candidate1;
-        this.notablePoints = new GeoPoint[]{notCand2Point, notCand3Point, notCand4Point};
-        this.cutoffPlanes = new Membership[]{new SidedPlane(nextCutoffPlane)};
-      } else if (cand2IsOtherWithin) {
-        // Use candidate2
-        this.circlePlane = candidate2;
-        this.notablePoints = new GeoPoint[]{notCand3Point, notCand4Point, notCand1Point};
-        this.cutoffPlanes = new Membership[]{new SidedPlane(nextCutoffPlane)};
-      } else if (cand3IsOtherWithin) {
-        this.circlePlane = candidate3;
-        this.notablePoints = new GeoPoint[]{notCand4Point, notCand1Point, notCand2Point};
-        this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane)};
-      } else if (cand4IsOtherWithin) {
-        this.circlePlane = candidate4;
-        this.notablePoints = new GeoPoint[]{notCand1Point, notCand2Point, notCand3Point};
-        this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane)};
-      } else {
-        // dunno what happened
-        throw new RuntimeException("Couldn't come up with a plane through three points that included the fourth");
-      }
-    }
-
-    /** Check if point is within this endpoint.
-     *@param point is the point.
-     *@return true of within.
-     */
-    public boolean isWithin(final Vector point) {
-      if (circlePlane == null)
-        return false;
-      if (!circlePlane.isWithin(point))
-        return false;
-      for (final Membership m : cutoffPlanes) {
-        if (!m.isWithin(point)) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    /** Check if point is within this endpoint.
-     *@param x is the point x.
-     *@param y is the point y.
-     *@param z is the point z.
-     *@return true of within.
-     */
-    public boolean isWithin(final double x, final double y, final double z) {
-      if (circlePlane == null)
-        return false;
-      if (!circlePlane.isWithin(x, y, z))
-        return false;
-      for (final Membership m : cutoffPlanes) {
-        if (!m.isWithin(x,y,z)) {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    /** Compute interior path distance.
-     *@param distanceStyle is the distance style.
-     *@param x is the point x.
-     *@param y is the point y.
-     *@param z is the point z.
-     *@return the distance metric.
-     */
-    public double pathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
-      if (!isWithin(x,y,z))
-        return Double.MAX_VALUE;
-      return distanceStyle.computeDistance(this.point, x, y, z);
-    }
-
-    /** Compute external distance.
-     *@param distanceStyle is the distance style.
-     *@param x is the point x.
-     *@param y is the point y.
-     *@param z is the point z.
-     *@return the distance metric.
-     */
-    public double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
-      return distanceStyle.computeDistance(this.point, x, y, z);
-    }
-
-    /** Determine if this endpoint intersects a specified plane.
-     *@param planetModel is the planet model.
-     *@param p is the plane.
-     *@param notablePoints are the points associated with the plane.
-     *@param bounds are any bounds which the intersection must lie within.
-     *@return true if there is a matching intersection.
-     */
-    public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
-      //System.err.println("  looking for intersection between plane "+p+" and circle "+circlePlane+" on proper side of "+cutoffPlanes+" within "+bounds);
-      if (circlePlane == null)
-        return false;
-      return circlePlane.intersects(planetModel, p, notablePoints, this.notablePoints, bounds, this.cutoffPlanes);
-    }
-
-    /** Get the bounds for a segment endpoint.
-     *@param planetModel is the planet model.
-     *@param bounds are the bounds to be modified.
-     */
-    public void getBounds(final PlanetModel planetModel, Bounds bounds) {
-      bounds.addPoint(point);
-      if (circlePlane == null)
-        return;
-      bounds.addPlane(planetModel, circlePlane);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (!(o instanceof SegmentEndpoint))
-        return false;
-      SegmentEndpoint other = (SegmentEndpoint) o;
-      return point.equals(other.point);
-    }
-
-    @Override
-    public int hashCode() {
-      return point.hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return point.toString();
-    }
-  }
-
-  /**
-   * This is the pre-calculated data for a path segment.
-   */
-  public static class PathSegment {
-    /** Starting point of the segment */
-    public final GeoPoint start;
-    /** End point of the segment */
-    public final GeoPoint end;
-    /** Place to keep any complete segment distances we've calculated so far */
-    public final Map<DistanceStyle,Double> fullDistanceCache = new HashMap<DistanceStyle,Double>();
-    /** Normalized plane connecting the two points and going through world center */
-    public final Plane normalizedConnectingPlane;
-    /** Cutoff plane parallel to connecting plane representing one side of the path segment */
-    public final SidedPlane upperConnectingPlane;
-    /** Cutoff plane parallel to connecting plane representing the other side of the path segment */
-    public final SidedPlane lowerConnectingPlane;
-    /** Plane going through the center and start point, marking the start edge of the segment */
-    public final SidedPlane startCutoffPlane;
-    /** Plane going through the center and end point, marking the end edge of the segment */
-    public final SidedPlane endCutoffPlane;
-    /** Upper right hand corner of segment */
-    public final GeoPoint URHC;
-    /** Lower right hand corner of segment */
-    public final GeoPoint LRHC;
-    /** Upper left hand corner of segment */
-    public final GeoPoint ULHC;
-    /** Lower left hand corner of segment */
-    public final GeoPoint LLHC;
-    /** Notable points for the upper connecting plane */
-    public final GeoPoint[] upperConnectingPlanePoints;
-    /** Notable points for the lower connecting plane */
-    public final GeoPoint[] lowerConnectingPlanePoints;
-    /** Notable points for the start cutoff plane */
-    public final GeoPoint[] startCutoffPlanePoints;
-    /** Notable points for the end cutoff plane */
-    public final GeoPoint[] endCutoffPlanePoints;
-
-    /** Construct a path segment.
-     *@param planetModel is the planet model.
-     *@param start is the starting point.
-     *@param end is the ending point.
-     *@param normalizedConnectingPlane is the connecting plane.
-     *@param planeBoundingOffset is the linear offset from the connecting plane to either side.
-     */
-    public PathSegment(final PlanetModel planetModel, final GeoPoint start, final GeoPoint end,
-      final Plane normalizedConnectingPlane, final double planeBoundingOffset) {
-      this.start = start;
-      this.end = end;
-      this.normalizedConnectingPlane = normalizedConnectingPlane;
-        
-      // Either start or end should be on the correct side
-      upperConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, -planeBoundingOffset);
-      lowerConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, planeBoundingOffset);
-      // Cutoff planes use opposite endpoints as correct side examples
-      startCutoffPlane = new SidedPlane(end, normalizedConnectingPlane, start);
-      endCutoffPlane = new SidedPlane(start, normalizedConnectingPlane, end);
-      final Membership[] upperSide = new Membership[]{upperConnectingPlane};
-      final Membership[] lowerSide = new Membership[]{lowerConnectingPlane};
-      final Membership[] startSide = new Membership[]{startCutoffPlane};
-      final Membership[] endSide = new Membership[]{endCutoffPlane};
-      GeoPoint[] points;
-      points = upperConnectingPlane.findIntersections(planetModel, startCutoffPlane, lowerSide, endSide);
-      if (points.length == 0) {
-        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
-      }
-      this.ULHC = points[0];
-      points = upperConnectingPlane.findIntersections(planetModel, endCutoffPlane, lowerSide, startSide);
-      if (points.length == 0) {
-        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
-      }
-      this.URHC = points[0];
-      points = lowerConnectingPlane.findIntersections(planetModel, startCutoffPlane, upperSide, endSide);
-      if (points.length == 0) {
-        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
-      }
-      this.LLHC = points[0];
-      points = lowerConnectingPlane.findIntersections(planetModel, endCutoffPlane, upperSide, startSide);
-      if (points.length == 0) {
-        throw new IllegalArgumentException("Some segment boundary points are off the ellipsoid; path too wide");
-      }
-      this.LRHC = points[0];
-      upperConnectingPlanePoints = new GeoPoint[]{ULHC, URHC};
-      lowerConnectingPlanePoints = new GeoPoint[]{LLHC, LRHC};
-      startCutoffPlanePoints = new GeoPoint[]{ULHC, LLHC};
-      endCutoffPlanePoints = new GeoPoint[]{URHC, LRHC};
-    }
-
-    /** Compute the full distance along this path segment.
-     *@param distanceStyle is the distance style.
-     *@return the distance metric.
-     */
-    public double fullPathDistance(final DistanceStyle distanceStyle) {
-      synchronized (fullDistanceCache) {
-        Double dist = fullDistanceCache.get(distanceStyle);
-        if (dist == null) {
-          dist = new Double(distanceStyle.computeDistance(start, end.x, end.y, end.z));
-          fullDistanceCache.put(distanceStyle, dist);
-        }
-        return dist.doubleValue();
-      }
-    }
-  
-    /** Check if point is within this segment.
-     *@param point is the point.
-     *@return true of within.
-     */
-    public boolean isWithin(final Vector point) {
-      return startCutoffPlane.isWithin(point) &&
-          endCutoffPlane.isWithin(point) &&
-          upperConnectingPlane.isWithin(point) &&
-          lowerConnectingPlane.isWithin(point);
-    }
-
-    /** Check if point is within this segment.
-     *@param x is the point x.
-     *@param y is the point y.
-     *@param z is the point z.
-     *@return true of within.
-     */
-    public boolean isWithin(final double x, final double y, final double z) {
-      return startCutoffPlane.isWithin(x, y, z) &&
-          endCutoffPlane.isWithin(x, y, z) &&
-          upperConnectingPlane.isWithin(x, y, z) &&
-          lowerConnectingPlane.isWithin(x, y, z);
-    }
-
-    /** Compute interior path distance.
-     *@param planetModel is the planet model.
-     *@param distanceStyle is the distance style.
-     *@param x is the point x.
-     *@param y is the point y.
-     *@param z is the point z.
-     *@return the distance metric.
-     */
-    public double pathDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
-      if (!isWithin(x,y,z))
-        return Double.MAX_VALUE;
-
-      // (1) Compute normalizedPerpPlane.  If degenerate, then return point distance from start to point.
-      // Want no allocations or expensive operations!  so we do this the hard way
-      final double perpX = normalizedConnectingPlane.y * z - normalizedConnectingPlane.z * y;
-      final double perpY = normalizedConnectingPlane.z * x - normalizedConnectingPlane.x * z;
-      final double perpZ = normalizedConnectingPlane.x * y - normalizedConnectingPlane.y * x;
-      final double magnitude = Math.sqrt(perpX * perpX + perpY * perpY + perpZ * perpZ);
-      if (Math.abs(magnitude) < Vector.MINIMUM_RESOLUTION)
-        return distanceStyle.computeDistance(start, x,y,z);
-      final double normFactor = 1.0/magnitude;
-      final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
-      
-      // Old computation: too expensive, because it calculates the intersection point twice.
-      //return distanceStyle.computeDistance(planetModel, normalizedConnectingPlane, x, y, z, startCutoffPlane, endCutoffPlane) +
-      //  distanceStyle.computeDistance(planetModel, normalizedPerpPlane, start.x, start.y, start.z, upperConnectingPlane, lowerConnectingPlane);
-
-      final GeoPoint[] intersectionPoints = normalizedConnectingPlane.findIntersections(planetModel, normalizedPerpPlane);
-      GeoPoint thePoint;
-      if (intersectionPoints.length == 0)
-        throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
-      else if (intersectionPoints.length == 1)
-        thePoint = intersectionPoints[0];
-      else {
-        if (startCutoffPlane.isWithin(intersectionPoints[0]) && endCutoffPlane.isWithin(intersectionPoints[0]))
-          thePoint = intersectionPoints[0];
-        else if (startCutoffPlane.isWithin(intersectionPoints[1]) && endCutoffPlane.isWithin(intersectionPoints[1]))
-          thePoint = intersectionPoints[1];
-        else
-          throw new RuntimeException("Can't find world intersection for point x="+x+" y="+y+" z="+z);
-      }
-      return distanceStyle.computeDistance(thePoint, x, y, z) + distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z);
-    }
-
-    /** Compute external distance.
-     *@param planetModel is the planet model.
-     *@param distanceStyle is the distance style.
-     *@param x is the point x.
-     *@param y is the point y.
-     *@param z is the point z.
-     *@return the distance metric.
-     */
-    public double outsideDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
-      final double upperDistance = distanceStyle.computeDistance(planetModel, upperConnectingPlane, x,y,z, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
-      final double lowerDistance = distanceStyle.computeDistance(planetModel, lowerConnectingPlane, x,y,z, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
-      final double startDistance = distanceStyle.computeDistance(planetModel, startCutoffPlane, x,y,z, endCutoffPlane, lowerConnectingPlane, upperConnectingPlane);
-      final double endDistance = distanceStyle.computeDistance(planetModel, endCutoffPlane, x,y,z, startCutoffPlane, lowerConnectingPlane, upperConnectingPlane);
-      final double ULHCDistance = distanceStyle.computeDistance(ULHC, x,y,z);
-      final double URHCDistance = distanceStyle.computeDistance(URHC, x,y,z);
-      final double LLHCDistance = distanceStyle.computeDistance(LLHC, x,y,z);
-      final double LRHCDistance = distanceStyle.computeDistance(LRHC, x,y,z);
-      return Math.min(
-        Math.min(
-          Math.min(upperDistance,lowerDistance),
-          Math.min(startDistance,endDistance)),
-        Math.min(
-          Math.min(ULHCDistance, URHCDistance),
-          Math.min(LLHCDistance, LRHCDistance)));
-    }
-
-    /** Determine if this endpoint intersects a specified plane.
-     *@param planetModel is the planet model.
-     *@param p is the plane.
-     *@param notablePoints are the points associated with the plane.
-     *@param bounds are any bounds which the intersection must lie within.
-     *@return true if there is a matching intersection.
-     */
-    public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
-      return upperConnectingPlane.intersects(planetModel, p, notablePoints, upperConnectingPlanePoints, bounds, lowerConnectingPlane, startCutoffPlane, endCutoffPlane) ||
-          lowerConnectingPlane.intersects(planetModel, p, notablePoints, lowerConnectingPlanePoints, bounds, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
-    }
-
-    /** Get the bounds for a segment endpoint.
-     *@param planetModel is the planet model.
-     *@param bounds are the bounds to be modified.
-     */
-    public void getBounds(final PlanetModel planetModel, Bounds bounds) {
-      // We need to do all bounding planes as well as corner points
-      bounds.addPoint(start).addPoint(end).addPoint(ULHC).addPoint(URHC).addPoint(LRHC).addPoint(LLHC);
-      bounds.addPlane(planetModel, upperConnectingPlane, lowerConnectingPlane, startCutoffPlane, endCutoffPlane);
-      bounds.addPlane(planetModel, lowerConnectingPlane, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
-      bounds.addPlane(planetModel, startCutoffPlane, endCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
-      bounds.addPlane(planetModel, endCutoffPlane, startCutoffPlane, upperConnectingPlane, lowerConnectingPlane);
-    }
-
-  }
-
+public interface GeoPath extends GeoDistanceShape {
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/784e3e38/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPathFactory.java
----------------------------------------------------------------------
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPathFactory.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPathFactory.java
new file mode 100644
index 0000000..3fd7cf7
--- /dev/null
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoPathFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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 GeoPath representing an arbitrary path.
+ *
+ * @lucene.experimental
+ */
+public class GeoPathFactory {
+  private GeoPathFactory() {
+  }
+
+  /**
+   * Create a GeoPath of the right kind given the specified information.
+   * @param planetModel is the planet model.
+   * @param maxCutoffAngle is the width of the path, measured as an angle.
+   * @param pathPoints are the points in the path.
+   * @return a GeoPath corresponding to what was specified.
+   */
+  public static GeoPath makeGeoPath(final PlanetModel planetModel, final double maxCutoffAngle, final GeoPoint[] pathPoints) {
+    return new GeoStandardPath(planetModel, maxCutoffAngle, pathPoints);
+  }
+
+}