package org.apache.lucene.spatial3d.geom;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class GeoStandardPath extends GeoBasePath {
protected final double cutoffAngle;
protected final double sinAngle;
protected final double cosAngle;
protected final List<GeoPoint> points = new ArrayList<GeoPoint>();
protected List<SegmentEndpoint> endPoints;
protected List<PathSegment> segments;
protected GeoPoint[] edgePoints;
protected boolean isDone = false;
public GeoStandardPath(final PlanetModel planetModel, final double maxCutoffAngle, final GeoPoint[] pathPoints) {
this(planetModel, maxCutoffAngle);
Collections.addAll(points, pathPoints);
done();
}
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);
}
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));
}
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());
final double cutoffOffset = this.sinAngle * planetModel.getMinimumMagnitude();
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) {
double lat = points.get(0).getLatitude();
double lon = points.get(0).getLongitude();
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);
final Plane normalPlane = Plane.constructNormalizedZPlane(upperPoint, lowerPoint, point);
final CircleSegmentEndpoint onlyEndpoint = new CircleSegmentEndpoint(point, normalPlane, upperPoint, lowerPoint);
endPoints.add(onlyEndpoint);
this.edgePoints = new GeoPoint[]{onlyEndpoint.circlePlane.getSampleIntersectionPoint(planetModel, normalPlane)};
return;
}
for (int i = 0; i < segments.size(); i++) {
final PathSegment currentSegment = segments.get(i);
if (i == 0) {
final SegmentEndpoint startEndpoint = new CutoffSingleCircleSegmentEndpoint(currentSegment.start,
currentSegment.startCutoffPlane, currentSegment.ULHC, currentSegment.LLHC);
endPoints.add(startEndpoint);
this.edgePoints = new GeoPoint[]{currentSegment.ULHC};
continue;
}
final PathSegment prevSegment = segments.get(i-1);
if (prevSegment.endCutoffPlane.isWithin(currentSegment.ULHC) && prevSegment.endCutoffPlane.isWithin(currentSegment.LLHC) &&
currentSegment.startCutoffPlane.isWithin(prevSegment.URHC) && currentSegment.startCutoffPlane.isWithin(prevSegment.LRHC)) {
final SegmentEndpoint midEndpoint = new CutoffSingleCircleSegmentEndpoint(currentSegment.start,
prevSegment.endCutoffPlane, currentSegment.startCutoffPlane, currentSegment.ULHC, currentSegment.LLHC);
endPoints.add(midEndpoint);
} else {
endPoints.add(new CutoffDualCircleSegmentEndpoint(currentSegment.start,
prevSegment.endCutoffPlane, currentSegment.startCutoffPlane,
prevSegment.URHC, prevSegment.LRHC,
currentSegment.ULHC, currentSegment.LLHC));
}
}
final PathSegment lastSegment = segments.get(segments.size()-1);
endPoints.add(new CutoffSingleCircleSegmentEndpoint(lastSegment.end,
lastSegment.endCutoffPlane, lastSegment.URHC, lastSegment.LRHC));
}
public GeoStandardPath(final PlanetModel planetModel, final InputStream inputStream) throws IOException {
this(planetModel,
SerializableObject.readDouble(inputStream),
SerializableObject.readPointArray(planetModel, inputStream));
}
@Override
public void write(final OutputStream outputStream) throws IOException {
SerializableObject.writeDouble(outputStream, cutoffAngle);
SerializableObject.writePointArray(outputStream, points);
}
@Override
public double computePathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double closestDistance = Double.POSITIVE_INFINITY;
for (PathSegment segment : segments) {
final double segmentDistance = segment.pathCenterDistance(planetModel, distanceStyle, x, y, z);
if (segmentDistance < closestDistance) {
closestDistance = segmentDistance;
}
}
for (SegmentEndpoint endpoint : endPoints) {
final double endpointDistance = endpoint.pathCenterDistance(distanceStyle, x, y, z);
if (endpointDistance < closestDistance) {
closestDistance = endpointDistance;
}
}
return closestDistance;
}
@Override
public double computeNearestDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double currentDistance = 0.0;
double minPathCenterDistance = Double.POSITIVE_INFINITY;
double bestDistance = Double.POSITIVE_INFINITY;
int segmentIndex = 0;
for (final SegmentEndpoint endpoint : endPoints) {
final double endpointPathCenterDistance = endpoint.pathCenterDistance(distanceStyle, x, y, z);
if (endpointPathCenterDistance < minPathCenterDistance) {
minPathCenterDistance = endpointPathCenterDistance;
bestDistance = currentDistance;
}
if (segmentIndex < segments.size()) {
final PathSegment segment = segments.get(segmentIndex++);
final double segmentPathCenterDistance = segment.pathCenterDistance(planetModel, distanceStyle, x, y, z);
if (segmentPathCenterDistance < minPathCenterDistance) {
minPathCenterDistance = segmentPathCenterDistance;
bestDistance = distanceStyle.aggregateDistances(currentDistance, segment.nearestPathDistance(planetModel, distanceStyle, x, y, z));
}
currentDistance = distanceStyle.aggregateDistances(currentDistance, segment.fullPathDistance(distanceStyle));
}
}
return bestDistance;
}
@Override
protected double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double bestDistance = Double.POSITIVE_INFINITY;
double currentDistance = 0.0;
for (final PathSegment segment : segments) {
double distance = segment.pathDistance(planetModel, distanceStyle, x,y,z);
if (distance != Double.POSITIVE_INFINITY) {
final double thisDistance = distanceStyle.fromAggregationForm(distanceStyle.aggregateDistances(currentDistance, distance));
if (thisDistance < bestDistance) {
bestDistance = thisDistance;
}
}
currentDistance = distanceStyle.aggregateDistances(currentDistance, segment.fullPathDistance(distanceStyle));
}
int segmentIndex = 0;
currentDistance = 0.0;
for (final SegmentEndpoint endpoint : endPoints) {
double distance = endpoint.pathDistance(distanceStyle, x, y, z);
if (distance != Double.POSITIVE_INFINITY) {
final double thisDistance = distanceStyle.fromAggregationForm(distanceStyle.aggregateDistances(currentDistance, distance));
if (thisDistance < bestDistance) {
bestDistance = thisDistance;
}
}
if (segmentIndex < segments.size())
currentDistance = distanceStyle.aggregateDistances(currentDistance, segments.get(segmentIndex++).fullPathDistance(distanceStyle));
}
return bestDistance;
}
@Override
protected double deltaDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double bestDistance = Double.POSITIVE_INFINITY;
for (final PathSegment segment : segments) {
final double distance = segment.pathDeltaDistance(planetModel, distanceStyle, x, y, z);
if (distance != Double.POSITIVE_INFINITY) {
final double thisDistance = distanceStyle.fromAggregationForm(distance);
if (thisDistance < bestDistance) {
bestDistance = thisDistance;
}
}
}
for (final SegmentEndpoint endpoint : endPoints) {
final double distance = endpoint.pathDeltaDistance(distanceStyle, x, y, z);
if (distance != Double.POSITIVE_INFINITY) {
final double thisDistance = distanceStyle.fromAggregationForm(distance);
if (thisDistance < bestDistance) {
bestDistance = thisDistance;
}
}
}
return bestDistance;
}
@Override
protected void distanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) {
getBounds(bounds);
}
@Override
protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double minDistance = Double.POSITIVE_INFINITY;
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) {
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 boolean intersects(GeoShape geoShape) {
for (final SegmentEndpoint pathPoint : endPoints) {
if (pathPoint.intersects(geoShape)) {
return true;
}
}
for (final PathSegment pathSegment : segments) {
if (pathSegment.intersects(geoShape)) {
return true;
}
}
return false;
}
@Override
public void getBounds(Bounds bounds) {
super.getBounds(bounds);
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 + "}}";
}
private interface SegmentEndpoint {
boolean isWithin(final Vector point);
boolean isWithin(final double x, final double y, final double z);
double pathDeltaDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
double pathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
double nearestPathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
double pathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds);
boolean intersects(final GeoShape geoShape);
void getBounds(final PlanetModel planetModel, Bounds bounds);
}
private static class BaseSegmentEndpoint implements SegmentEndpoint {
protected final GeoPoint point;
protected final static Membership[] NO_MEMBERSHIP = new Membership[0];
public BaseSegmentEndpoint(final GeoPoint point) {
this.point = point;
}
@Override
public boolean isWithin(final Vector point) {
return false;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return false;
}
@Override
public double pathDeltaDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!isWithin(x,y,z))
return Double.POSITIVE_INFINITY;
final double theDistance = distanceStyle.toAggregationForm(distanceStyle.computeDistance(this.point, x, y, z));
return distanceStyle.aggregateDistances(theDistance, theDistance);
}
@Override
public double pathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!isWithin(x,y,z))
return Double.POSITIVE_INFINITY;
return distanceStyle.toAggregationForm(distanceStyle.computeDistance(this.point, x, y, z));
}
@Override
public double nearestPathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
return distanceStyle.toAggregationForm(0.0);
}
@Override
public double pathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
return distanceStyle.computeDistance(this.point, x, y, z);
}
@Override
public double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
return distanceStyle.computeDistance(this.point, x, y, z);
}
@Override
public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return false;
}
@Override
public boolean intersects(final GeoShape geoShape) {
return false;
}
@Override
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
bounds.addPoint(point);
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof BaseSegmentEndpoint))
return false;
final BaseSegmentEndpoint other = (BaseSegmentEndpoint) o;
return point.equals(other.point);
}
@Override
public int hashCode() {
return point.hashCode();
}
@Override
public String toString() {
return point.toString();
}
}
private static class DegenerateSegmentEndpoint extends BaseSegmentEndpoint {
public DegenerateSegmentEndpoint(final GeoPoint point) {
super(point);
}
}
private static class CircleSegmentEndpoint extends BaseSegmentEndpoint {
protected final SidedPlane circlePlane;
protected final static GeoPoint[] circlePoints = new GeoPoint[0];
public CircleSegmentEndpoint(final GeoPoint point, final Plane normalPlane, final GeoPoint upperPoint, final GeoPoint lowerPoint) {
super(point);
this.circlePlane = SidedPlane.constructNormalizedPerpendicularSidedPlane(point, normalPlane, upperPoint, lowerPoint);
}
protected CircleSegmentEndpoint(final GeoPoint point, final SidedPlane circlePlane) {
super(point);
this.circlePlane = circlePlane;
}
@Override
public boolean isWithin(final Vector point) {
return circlePlane.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
return circlePlane.isWithin(x, y, z);
}
@Override
public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return circlePlane.intersects(planetModel, p, notablePoints, circlePoints, bounds);
}
@Override
public boolean intersects(final GeoShape geoShape) {
return geoShape.intersects(circlePlane, circlePoints, NO_MEMBERSHIP);
}
@Override
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
super.getBounds(planetModel, bounds);
bounds.addPlane(planetModel, circlePlane);
}
}
private static class CutoffSingleCircleSegmentEndpoint extends CircleSegmentEndpoint {
protected final Membership[] cutoffPlanes;
private final GeoPoint[] notablePoints;
public CutoffSingleCircleSegmentEndpoint(final GeoPoint point,
final SidedPlane cutoffPlane, final GeoPoint topEdgePoint, final GeoPoint bottomEdgePoint) {
super(point, cutoffPlane, topEdgePoint, bottomEdgePoint);
this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane)};
this.notablePoints = new GeoPoint[]{topEdgePoint, bottomEdgePoint};
}
public CutoffSingleCircleSegmentEndpoint(final GeoPoint point,
final SidedPlane cutoffPlane1, final SidedPlane cutoffPlane2, final GeoPoint topEdgePoint, final GeoPoint bottomEdgePoint) {
super(point, cutoffPlane1, topEdgePoint, bottomEdgePoint);
this.cutoffPlanes = new Membership[]{new SidedPlane(cutoffPlane1), new SidedPlane(cutoffPlane2)};
this.notablePoints = new GeoPoint[]{topEdgePoint, bottomEdgePoint};
}
@Override
public boolean isWithin(final Vector point) {
if (!super.isWithin(point)) {
return false;
}
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(point)) {
return false;
}
}
return true;
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
if (!super.isWithin(x, y, z)) {
return false;
}
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return false;
}
}
return true;
}
@Override
public double nearestPathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return super.nearestPathDistance(distanceStyle, x, y, z);
}
@Override
public double pathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return super.pathCenterDistance(distanceStyle, x, y, z);
}
@Override
public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return circlePlane.intersects(planetModel, p, notablePoints, this.notablePoints, bounds, this.cutoffPlanes);
}
@Override
public boolean intersects(final GeoShape geoShape) {
return geoShape.intersects(circlePlane, this.notablePoints, this.cutoffPlanes);
}
}
private static class CutoffDualCircleSegmentEndpoint extends BaseSegmentEndpoint {
protected final SidedPlane circlePlane1;
protected final SidedPlane circlePlane2;
protected final GeoPoint[] notablePoints1;
protected final GeoPoint[] notablePoints2;
protected final Membership[] cutoffPlanes;
public CutoffDualCircleSegmentEndpoint(final GeoPoint point,
final SidedPlane prevCutoffPlane, final SidedPlane nextCutoffPlane,
final GeoPoint prevURHC, final GeoPoint prevLRHC,
final GeoPoint currentULHC, final GeoPoint currentLLHC) {
super(point);
if (!prevCutoffPlane.isWithin(currentULHC)) {
circlePlane1 = SidedPlane.constructNormalizedThreePointSidedPlane(point, prevURHC, prevLRHC, currentULHC);
notablePoints1 = new GeoPoint[]{prevURHC, prevLRHC, currentULHC};
} else if (!prevCutoffPlane.isWithin(currentLLHC)) {
circlePlane1 = SidedPlane.constructNormalizedThreePointSidedPlane(point, prevURHC, prevLRHC, currentLLHC);
notablePoints1 = new GeoPoint[]{prevURHC, prevLRHC, currentLLHC};
} else {
throw new IllegalArgumentException("Constructing CutoffDualCircleSegmentEndpoint with colinear segments");
}
if (!nextCutoffPlane.isWithin(prevURHC)) {
circlePlane2 = SidedPlane.constructNormalizedThreePointSidedPlane(point, currentULHC, currentLLHC, prevURHC);
notablePoints2 = new GeoPoint[]{currentULHC, currentLLHC, prevURHC};
} else if (!nextCutoffPlane.isWithin(prevLRHC)) {
circlePlane2 = SidedPlane.constructNormalizedThreePointSidedPlane(point, currentULHC, currentLLHC, prevLRHC);
notablePoints2 = new GeoPoint[]{currentULHC, currentLLHC, prevLRHC};
} else {
throw new IllegalArgumentException("Constructing CutoffDualCircleSegmentEndpoint with colinear segments");
}
this.cutoffPlanes = new Membership[]{new SidedPlane(prevCutoffPlane), new SidedPlane(nextCutoffPlane)};
}
@Override
public boolean isWithin(final Vector point) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(point)) {
return false;
}
}
return circlePlane1.isWithin(point) || circlePlane2.isWithin(point);
}
@Override
public boolean isWithin(final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return false;
}
}
return circlePlane1.isWithin(x, y, z) || circlePlane2.isWithin(x, y, z);
}
@Override
public double nearestPathDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return super.nearestPathDistance(distanceStyle, x, y, z);
}
@Override
public double pathCenterDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
for (final Membership m : cutoffPlanes) {
if (!m.isWithin(x,y,z)) {
return Double.POSITIVE_INFINITY;
}
}
return super.pathCenterDistance(distanceStyle, x, y, z);
}
@Override
public boolean intersects(final PlanetModel planetModel, final Plane p, final GeoPoint[] notablePoints, final Membership[] bounds) {
return circlePlane1.intersects(planetModel, p, notablePoints, this.notablePoints1, bounds, this.cutoffPlanes) ||
circlePlane2.intersects(planetModel, p, notablePoints, this.notablePoints2, bounds, this.cutoffPlanes);
}
@Override
public boolean intersects(final GeoShape geoShape) {
return geoShape.intersects(circlePlane1, this.notablePoints1, this.cutoffPlanes) ||
geoShape.intersects(circlePlane2, this.notablePoints2, this.cutoffPlanes);
}
@Override
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
super.getBounds(planetModel, bounds);
bounds.addPlane(planetModel, circlePlane1);
bounds.addPlane(planetModel, circlePlane2);
}
}
private static class PathSegment {
public final GeoPoint start;
public final GeoPoint end;
public final Map<DistanceStyle,Double> fullDistanceCache = new HashMap<DistanceStyle,Double>();
public final Plane normalizedConnectingPlane;
public final SidedPlane upperConnectingPlane;
public final SidedPlane lowerConnectingPlane;
public final SidedPlane startCutoffPlane;
public final SidedPlane endCutoffPlane;
public final GeoPoint URHC;
public final GeoPoint LRHC;
public final GeoPoint ULHC;
public final GeoPoint LLHC;
public final GeoPoint[] upperConnectingPlanePoints;
public final GeoPoint[] lowerConnectingPlanePoints;
public final GeoPoint[] startCutoffPlanePoints;
public final GeoPoint[] endCutoffPlanePoints;
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;
upperConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, -planeBoundingOffset);
lowerConnectingPlane = new SidedPlane(start, normalizedConnectingPlane, planeBoundingOffset);
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");
}
if (points.length > 1) {
throw new IllegalArgumentException("Ambiguous boundary points; path too short");
}
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");
}
if (points.length > 1) {
throw new IllegalArgumentException("Ambiguous boundary points; path too short");
}
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");
}
if (points.length > 1) {
throw new IllegalArgumentException("Ambiguous boundary points; path too short");
}
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");
}
if (points.length > 1) {
throw new IllegalArgumentException("Ambiguous boundary points; path too short");
}
this.LRHC = points[0];
upperConnectingPlanePoints = new GeoPoint[]{ULHC, URHC};
lowerConnectingPlanePoints = new GeoPoint[]{LLHC, LRHC};
startCutoffPlanePoints = new GeoPoint[]{ULHC, LLHC};
endCutoffPlanePoints = new GeoPoint[]{URHC, LRHC};
}
public double fullPathDistance(final DistanceStyle distanceStyle) {
synchronized (fullDistanceCache) {
Double dist = fullDistanceCache.get(distanceStyle);
if (dist == null) {
dist = distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, end.x, end.y, end.z));
fullDistanceCache.put(distanceStyle, dist);
}
return dist.doubleValue();
}
}
public boolean isWithin(final Vector point) {
return startCutoffPlane.isWithin(point) &&
endCutoffPlane.isWithin(point) &&
upperConnectingPlane.isWithin(point) &&
lowerConnectingPlane.isWithin(point);
}
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);
}
public double pathCenterDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!startCutoffPlane.isWithin(x, y, z) || !endCutoffPlane.isWithin(x, y, z)) {
return Double.POSITIVE_INFINITY;
}
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);
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);
}
public double nearestPathDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!startCutoffPlane.isWithin(x, y, z) || !endCutoffPlane.isWithin(x, y, z)) {
return Double.POSITIVE_INFINITY;
}
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.toAggregationForm(0.0);
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
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.toAggregationForm(distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z));
}
public double pathDeltaDistance(final PlanetModel planetModel, final DistanceStyle distanceStyle, final double x, final double y, final double z) {
if (!isWithin(x,y,z))
return Double.POSITIVE_INFINITY;
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) {
final double theDistance = distanceStyle.computeDistance(start, x,y,z);
return distanceStyle.aggregateDistances(theDistance, theDistance);
}
final double normFactor = 1.0/magnitude;
final Plane normalizedPerpPlane = new Plane(perpX * normFactor, perpY * normFactor, perpZ * normFactor, 0.0);
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);
}
final double theDistance = distanceStyle.toAggregationForm(distanceStyle.computeDistance(thePoint, x, y, z));
return distanceStyle.aggregateDistances(theDistance, theDistance);
}
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.POSITIVE_INFINITY;
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.toAggregationForm(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);
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.aggregateDistances(distanceStyle.toAggregationForm(distanceStyle.computeDistance(thePoint, x, y, z)),
distanceStyle.toAggregationForm(distanceStyle.computeDistance(start, thePoint.x, thePoint.y, thePoint.z)));
}
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)));
}
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);
}
public boolean intersects(final GeoShape geoShape) {
return geoShape.intersects(upperConnectingPlane, upperConnectingPlanePoints, lowerConnectingPlane, startCutoffPlane, endCutoffPlane) ||
geoShape.intersects(lowerConnectingPlane, lowerConnectingPlanePoints, upperConnectingPlane, startCutoffPlane, endCutoffPlane);
}
public void getBounds(final PlanetModel planetModel, Bounds bounds) {
bounds.addPoint(start).addPoint(end)
.addPoint(ULHC).addPoint(URHC).addPoint(LRHC).addPoint(LLHC)
.addPlane(planetModel, upperConnectingPlane, lowerConnectingPlane, startCutoffPlane, endCutoffPlane)
.addPlane(planetModel, lowerConnectingPlane, upperConnectingPlane, startCutoffPlane, endCutoffPlane)
.addPlane(planetModel, startCutoffPlane, endCutoffPlane, upperConnectingPlane, lowerConnectingPlane)
.addPlane(planetModel, endCutoffPlane, startCutoffPlane, upperConnectingPlane, lowerConnectingPlane)
.addIntersection(planetModel, upperConnectingPlane, startCutoffPlane, lowerConnectingPlane, endCutoffPlane)
.addIntersection(planetModel, startCutoffPlane, lowerConnectingPlane, endCutoffPlane, upperConnectingPlane)
.addIntersection(planetModel, lowerConnectingPlane, endCutoffPlane, upperConnectingPlane, startCutoffPlane)
.addIntersection(planetModel, endCutoffPlane, upperConnectingPlane, startCutoffPlane, lowerConnectingPlane);
}
}
}