LUCENE-7212: Structural changes necessary to support distance-limited bounds.

This commit is contained in:
Karl Wright 2016-04-19 10:20:00 -04:00
parent 02f1dacc3d
commit 2342290816
15 changed files with 281 additions and 0 deletions

View File

@ -64,4 +64,5 @@ class Geo3DUtil {
return Math.nextDown((x+1) * DECODE);
}
}
}

View File

@ -51,6 +51,21 @@ public class ArcDistance implements DistanceStyle {
return plane.arcDistance(planetModel, x,y,z, bounds);
}
@Override
public GeoPoint[] findDistancePoints(final PlanetModel planetModel, final double distanceValue, final GeoPoint startPoint, final Plane plane, final Membership... bounds) {
return plane.findArcDistancePoints(planetModel, distanceValue, startPoint, bounds);
}
@Override
public double findMinimumArcDistance(final PlanetModel planetModel, final double distanceValue) {
return distanceValue;
}
@Override
public double findMaximumArcDistance(final PlanetModel planetModel, final double distanceValue) {
return distanceValue;
}
}

View File

@ -78,6 +78,34 @@ public interface DistanceStyle {
*/
public double computeDistance(final PlanetModel planetModel, final Plane plane, final double x, final double y, final double z, final Membership... bounds);
// The following methods are used to go from a distance value back to something
// that can be used to construct a constrained shape.
/** Find a GeoPoint, at a specified distance from a starting point, within the
* specified bounds. The GeoPoint must be in the specified plane.
* @param planetModel is the planet model.
* @param distanceValue is the distance to set the new point at, measured from point1 and on the way to point2.
* @param startPoint is the starting point.
* @param plane is the plane that the point must be in.
* @param bounds are the constraints on where the point can be found.
* @return zero, one, or two points at the proper distance from startPoint.
*/
public GeoPoint[] findDistancePoints(final PlanetModel planetModel, final double distanceValue, final GeoPoint startPoint, final Plane plane, final Membership... bounds);
/** Given a distance metric, find the minimum arc distance represented by that distance metric.
* @param planetModel is the planet model.
* @param distanceValue is the distance metric.
* @return the minimum arc distance that that distance value can represent given the planet model.
*/
public double findMinimumArcDistance(final PlanetModel planetModel, final double distanceValue);
/** Given a distance metric, find the maximum arc distance represented by the distance metric.
* @param planetModel is the planet model.
* @param distanceValue is the distance metric.
* @return the maximum arc distance that that distance value can represent given the planet model.
*/
public double findMaximumArcDistance(final PlanetModel planetModel, final double distanceValue);
}

View File

@ -52,5 +52,17 @@ public abstract class GeoBaseDistanceShape extends GeoBaseMembershipShape implem
/** Called by a {@code computeDistance} method if X/Y/Z is not within this shape. */
protected abstract double distance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
@Override
public void getDistanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) {
if (distanceValue == Double.MAX_VALUE) {
getBounds(bounds);
return;
}
distanceBounds(bounds, distanceStyle, distanceValue);
}
/** Called by a {@code getDistanceBounds} method if distanceValue is not Double.MAX_VALUE. */
protected abstract void distanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue);
}

View File

@ -131,5 +131,11 @@ class GeoDegeneratePoint extends GeoPoint implements GeoBBox, GeoCircle {
return 0.0;
return Double.MAX_VALUE;
}
@Override
public void getDistanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) {
getBounds(bounds);
}
}

View File

@ -56,4 +56,5 @@ public interface GeoDistance extends Membership {
*/
public double computeDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
}

View File

@ -24,4 +24,16 @@ package org.apache.lucene.spatial3d.geom;
*/
public interface GeoDistanceShape extends GeoMembershipShape, GeoDistance {
/**
* Compute a bound based on a provided distance measure.
* This method takes an input distance and distance metric and provides bounds on the
* shape if reduced to match that distance. The method is allowed to return
* bounds that are larger than the distance would indicate, but never smaller.
* @param bounds is the bounds object to update.
* @param distanceStyle describes the type of distance metric provided.
* @param distanceValue is the distance metric to use. It is presumed that the distance metric
* was produced with the same distance style as is provided to this method.
*/
public void getDistanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue);
}

View File

@ -106,6 +106,12 @@ class GeoStandardCircle extends GeoBaseCircle {
return distanceStyle.computeDistance(this.center, x, y, z);
}
@Override
protected void distanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) {
// TBD: Compute actual bounds based on distance
getBounds(bounds);
}
@Override
protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
return distanceStyle.computeDistance(planetModel, circlePlane, x, y, z);

View File

@ -219,6 +219,12 @@ class GeoStandardPath extends GeoBasePath {
return Double.MAX_VALUE;
}
@Override
protected void distanceBounds(final Bounds bounds, final DistanceStyle distanceStyle, final double distanceValue) {
// TBD: Compute actual bounds based on distance
getBounds(bounds);
}
@Override
protected double outsideDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z) {
double minDistance = Double.MAX_VALUE;

View File

@ -51,6 +51,21 @@ public class LinearDistance implements DistanceStyle {
return plane.linearDistance(planetModel, x,y,z, bounds);
}
@Override
public GeoPoint[] findDistancePoints(final PlanetModel planetModel, final double distanceValue, final GeoPoint startPoint, final Plane plane, final Membership... bounds) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMinimumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMaximumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
}

View File

@ -51,6 +51,21 @@ public class LinearSquaredDistance implements DistanceStyle {
return plane.linearDistanceSquared(planetModel, x,y,z, bounds);
}
@Override
public GeoPoint[] findDistancePoints(final PlanetModel planetModel, final double distanceValue, final GeoPoint startPoint, final Plane plane, final Membership... bounds) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMinimumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMaximumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
}

View File

@ -51,6 +51,21 @@ public class NormalDistance implements DistanceStyle {
return plane.normalDistance(x,y,z, bounds);
}
@Override
public GeoPoint[] findDistancePoints(final PlanetModel planetModel, final double distanceValue, final GeoPoint startPoint, final Plane plane, final Membership... bounds) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMinimumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMaximumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
}

View File

@ -51,6 +51,21 @@ public class NormalSquaredDistance implements DistanceStyle {
return plane.normalDistanceSquared(x,y,z, bounds);
}
@Override
public GeoPoint[] findDistancePoints(final PlanetModel planetModel, final double distanceValue, final GeoPoint startPoint, final Plane plane, final Membership... bounds) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMinimumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
@Override
public double findMaximumArcDistance(final PlanetModel planetModel, final double distanceValue) {
throw new IllegalStateException("Reverse mapping not implemented for this distance metric");
}
}

View File

@ -1557,6 +1557,120 @@ public class Plane extends Vector {
return evaluateIsZero(-p.x * p.D * denom, -p.y * p.D * denom, -p.z * p.D * denom);
}
/**
* Locate a point that is within the specified bounds and on the specified plane, that has an arcDistance as
* specified from the startPoint.
* @param planetModel is the planet model.
* @param arcDistanceValue is the arc distance.
* @param startPoint is the starting point.
* @param bounds are the bounds.
* @return zero, one, or two points.
*/
public GeoPoint[] findArcDistancePoints(final PlanetModel planetModel, final double arcDistanceValue, final GeoPoint startPoint, final Membership... bounds) {
if (Math.abs(D) >= MINIMUM_RESOLUTION) {
throw new IllegalStateException("Can't find arc distance using plane that doesn't go through origin");
}
if (!evaluateIsZero(startPoint)) {
throw new IllegalArgumentException("Start point is not on plane");
}
assert Math.abs(x*x + y*y + z*z - 1.0) < MINIMUM_RESOLUTION_SQUARED : "Plane needs to be normalized";
// The first step is to rotate coordinates for the point so that the plane lies on the x-y plane.
// To acheive this, there will need to be three rotations:
// (1) rotate the plane in x-y so that the y axis lies in it.
// (2) rotate the plane in x-z so that the plane lies on the x-y plane.
// (3) rotate in x-y so that the starting vector points to (1,0,0).
// This presumes a normalized plane!!
final double azimuthMagnitude = Math.sqrt(this.x * this.x + this.y * this.y);
final double cosPlaneAltitude = this.z;
final double sinPlaneAltitude = azimuthMagnitude;
final double cosPlaneAzimuth = this.x / azimuthMagnitude;
final double sinPlaneAzimuth = this.y / azimuthMagnitude;
assert Math.abs(sinPlaneAltitude * sinPlaneAltitude + cosPlaneAltitude * cosPlaneAltitude - 1.0) < MINIMUM_RESOLUTION : "Improper sin/cos of altitude: "+(sinPlaneAltitude * sinPlaneAltitude + cosPlaneAltitude * cosPlaneAltitude);
assert Math.abs(sinPlaneAzimuth * sinPlaneAzimuth + cosPlaneAzimuth * cosPlaneAzimuth - 1.0) < MINIMUM_RESOLUTION : "Improper sin/cos of azimuth: "+(sinPlaneAzimuth * sinPlaneAzimuth + cosPlaneAzimuth * cosPlaneAzimuth);
// Coordinate rotation formula:
// xT = xS cos T - yS sin T
// yT = xS sin T + yS cos T
// But we're rotating backwards, so use:
// sin (-T) = -sin (T)
// cos (-T) = cos (T)
// Now, rotate startpoint in x-y
final double x0 = startPoint.x;
final double y0 = startPoint.y;
final double z0 = startPoint.z;
final double x1 = x0 * cosPlaneAzimuth + y0 * sinPlaneAzimuth;
final double y1 = -x0 * sinPlaneAzimuth + y0 * cosPlaneAzimuth;
final double z1 = z0;
// Rotate now in x-z
final double x2 = x1 * cosPlaneAltitude - z1 * sinPlaneAltitude;
final double y2 = y1;
final double z2 = +x1 * sinPlaneAltitude + z1 * cosPlaneAltitude;
assert Math.abs(z2) < MINIMUM_RESOLUTION : "Rotation should have put startpoint on x-y plane, instead has value "+z2;
// Ok, we have the start point on the x-y plane. To apply the arc distance, we
// next need to convert to an angle (in radians).
final double startAngle = Math.atan2(y2, x2);
// To apply the arc distance, just add to startAngle.
final double point1Angle = startAngle + arcDistanceValue;
final double point2Angle = startAngle - arcDistanceValue;
// Convert each point to x-y
final double point1x2 = Math.cos(point1Angle);
final double point1y2 = Math.sin(point1Angle);
final double point1z2 = 0.0;
final double point2x2 = Math.cos(point2Angle);
final double point2y2 = Math.sin(point2Angle);
final double point2z2 = 0.0;
// Now, do the reverse rotations for both points
// Altitude...
final double point1x1 = point1x2 * cosPlaneAltitude + point1z2 * sinPlaneAltitude;
final double point1y1 = point1y2;
final double point1z1 = -point1x2 * sinPlaneAltitude + point1z2 * cosPlaneAltitude;
final double point2x1 = point2x2 * cosPlaneAltitude + point2z2 * sinPlaneAltitude;
final double point2y1 = point2y2;
final double point2z1 = -point2x2 * sinPlaneAltitude + point2z2 * cosPlaneAltitude;
// Azimuth...
final double point1x0 = point1x1 * cosPlaneAzimuth - point1y1 * sinPlaneAzimuth;
final double point1y0 = point1x1 * sinPlaneAzimuth + point1y1 * cosPlaneAzimuth;
final double point1z0 = point1z1;
final double point2x0 = point2x1 * cosPlaneAzimuth - point2y1 * sinPlaneAzimuth;
final double point2y0 = point2x1 * sinPlaneAzimuth + point2y1 * cosPlaneAzimuth;
final double point2z0 = point2z1;
final GeoPoint point1 = planetModel.createSurfacePoint(point1x0, point1y0, point1z0);
final GeoPoint point2 = planetModel.createSurfacePoint(point2x0, point2y0, point2z0);
// Figure out what to return
boolean isPoint1Inside = meetsAllBounds(point1, bounds);
boolean isPoint2Inside = meetsAllBounds(point2, bounds);
if (isPoint1Inside) {
if (isPoint2Inside) {
return new GeoPoint[]{point1, point2};
} else {
return new GeoPoint[]{point1};
}
} else {
if (isPoint2Inside) {
return new GeoPoint[]{point2};
} else {
return new GeoPoint[0];
}
}
}
/**
* Check if a vector meets the provided bounds.
* @param p is the vector.

View File

@ -20,6 +20,7 @@ import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
/**
* Test basic plane functionality.
@ -60,5 +61,24 @@ public class PlaneTest {
assertTrue(p.evaluateIsZero(point));
}
}
@Test
public void testFindArcPoints() {
// Create two points
final GeoPoint p1 = new GeoPoint(PlanetModel.WGS84, 0.123, -0.456);
final GeoPoint p2 = new GeoPoint(PlanetModel.WGS84, -0.368, 0.888);
// Create a plane that links them.
final Plane plane = new Plane(p1, p2);
// Now, use that plane to find points that are a certain distance from the original
final GeoPoint[] newPoints = plane.findArcDistancePoints(PlanetModel.WGS84, 0.20, p1);
assertTrue(newPoints.length == 2);
assertTrue(plane.evaluateIsZero(newPoints[0]));
assertTrue(plane.evaluateIsZero(newPoints[1]));
assertTrue(PlanetModel.WGS84.pointOnSurface(newPoints[0]));
assertTrue(PlanetModel.WGS84.pointOnSurface(newPoints[1]));
assertEquals(0.20, p1.arcDistance(newPoints[0]), 1e-6);
assertEquals(0.20, p1.arcDistance(newPoints[1]), 1e-6);
}
}