mirror of https://github.com/apache/lucene.git
LUCENE-7212: Structural changes necessary to support distance-limited bounds.
This commit is contained in:
parent
02f1dacc3d
commit
2342290816
|
@ -64,4 +64,5 @@ class Geo3DUtil {
|
|||
return Math.nextDown((x+1) * DECODE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -56,4 +56,5 @@ public interface GeoDistance extends Membership {
|
|||
*/
|
||||
public double computeDistance(final DistanceStyle distanceStyle, final double x, final double y, final double z);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue