org.apache.lucene.spatial.geometry.shape.Ellipse Maven / Gradle / Ivy
/**
* 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.spatial.geometry.shape;
/**
* Ellipse shape. From C++ gl.
*
* NOTE: This API is still in
* flux and might change in incompatible ways in the next
* release.
*/
public class Ellipse implements Geometry2D {
private Point2D center;
/**
* Half length of major axis
*/
private double a;
/**
* Half length of minor axis
*/
private double b;
private double k1, k2, k3;
/**
* sin of rotation angle
*/
private double s;
/**
* cos of rotation angle
*/
private double c;
public Ellipse() {
center = new Point2D(0, 0);
}
private double SQR(double d) {
return d * d;
}
/**
* Constructor given bounding rectangle and a rotation.
*/
public Ellipse(Point2D p1, Point2D p2, double angle) {
center = new Point2D();
// Set the center
center.x((p1.x() + p2.x()) * 0.5f);
center.y((p1.y() + p2.y()) * 0.5f);
// Find sin and cos of the angle
double angleRad = Math.toRadians(angle);
c = Math.cos(angleRad);
s = Math.sin(angleRad);
// Find the half lengths of the semi-major and semi-minor axes
double dx = Math.abs(p2.x() - p1.x()) * 0.5;
double dy = Math.abs(p2.y() - p1.y()) * 0.5;
if (dx >= dy) {
a = dx;
b = dy;
} else {
a = dy;
b = dx;
}
// Find k1, k2, k3 - define when a point x,y is on the ellipse
k1 = SQR(c / a) + SQR(s / b);
k2 = 2 * s * c * ((1 / SQR(a)) - (1 / SQR(b)));
k3 = SQR(s / a) + SQR(c / b);
}
/**
* Determines if a line segment intersects the ellipse and if so finds the
* point(s) of intersection.
*
* @param seg
* Line segment to test for intersection
* @param pt0
* OUT - intersection point (if it exists)
* @param pt1
* OUT - second intersection point (if it exists)
*
* @return Returns the number of intersection points (0, 1, or 2).
*/
public int intersect(LineSegment seg, Point2D pt0, Point2D pt1) {
if (pt0 == null)
pt0 = new Point2D();
if (pt1 == null)
pt1 = new Point2D();
// Solution is found by parameterizing the line segment and
// substituting those values into the ellipse equation.
// Results in a quadratic equation.
double x1 = center.x();
double y1 = center.y();
double u1 = seg.A.x();
double v1 = seg.A.y();
double u2 = seg.B.x();
double v2 = seg.B.y();
double dx = u2 - u1;
double dy = v2 - v1;
double q0 = k1 * SQR(u1 - x1) + k2 * (u1 - x1) * (v1 - y1) + k3
* SQR(v1 - y1) - 1;
double q1 = (2 * k1 * dx * (u1 - x1)) + (k2 * dx * (v1 - y1))
+ (k2 * dy * (u1 - x1)) + (2 * k3 * dy * (v1 - y1));
double q2 = (k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy));
// Compare q1^2 to 4*q0*q2 to see how quadratic solves
double d = SQR(q1) - (4 * q0 * q2);
if (d < 0) {
// Roots are complex valued. Line containing the segment does
// not intersect the ellipse
return 0;
}
if (d == 0) {
// One real-valued root - line is tangent to the ellipse
double t = -q1 / (2 * q2);
if (0 <= t && t <= 1) {
// Intersection occurs along line segment
pt0.x(u1 + t * dx);
pt0.y(v1 + t * dy);
return 1;
} else
return 0;
} else {
// Two distinct real-valued roots. Solve for the roots and see if
// they fall along the line segment
int n = 0;
double q = Math.sqrt(d);
double t = (-q1 - q) / (2 * q2);
if (0 <= t && t <= 1) {
// Intersection occurs along line segment
pt0.x(u1 + t * dx);
pt0.y(v1 + t * dy);
n++;
}
// 2nd root
t = (-q1 + q) / (2 * q2);
if (0 <= t && t <= 1) {
if (n == 0) {
pt0.x(u1 + t * dx);
pt0.y(v1 + t * dy);
n++;
} else {
pt1.x(u1 + t * dx);
pt1.y(v1 + t * dy);
n++;
}
}
return n;
}
}
public IntersectCase intersect(Rectangle r) {
// Test if all 4 corners of the rectangle are inside the ellipse
Point2D ul = new Point2D(r.MinPt().x(), r.MaxPt().y());
Point2D ur = new Point2D(r.MaxPt().x(), r.MaxPt().y());
Point2D ll = new Point2D(r.MinPt().x(), r.MinPt().y());
Point2D lr = new Point2D(r.MaxPt().x(), r.MinPt().y());
if (contains(ul) && contains(ur) && contains(ll) && contains(lr))
return IntersectCase.CONTAINS;
// Test if any of the rectangle edges intersect
Point2D pt0 = new Point2D(), pt1 = new Point2D();
LineSegment bottom = new LineSegment(ll, lr);
if (intersect(bottom, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
LineSegment top = new LineSegment(ul, ur);
if (intersect(top, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
LineSegment left = new LineSegment(ll, ul);
if (intersect(left, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
LineSegment right = new LineSegment(lr, ur);
if (intersect(right, pt0, pt1) > 0)
return IntersectCase.INTERSECTS;
// Ellipse does not intersect any edge : since the case for the ellipse
// containing the rectangle was considered above then if the center
// is inside the ellipse is fully inside and if center is outside
// the ellipse is fully outside
return (r.contains(center)) ? IntersectCase.WITHIN
: IntersectCase.OUTSIDE;
}
public double area() {
throw new UnsupportedOperationException();
}
public Point2D centroid() {
throw new UnsupportedOperationException();
}
public boolean contains(Point2D pt) {
// Plug in equation for ellipse, If evaluates to <= 0 then the
// point is in or on the ellipse.
double dx = pt.x() - center.x();
double dy = pt.y() - center.y();
double eq=(((k1 * SQR(dx)) + (k2 * dx * dy) + (k3 * SQR(dy)) - 1));
return eq<=0;
}
public void translate(Vector2D v) {
throw new UnsupportedOperationException();
}
}