/*
* 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.commons.math.geometry.euclidean.twod;
import java.awt.geom.AffineTransform;
import org.apache.commons.math.exception.MathIllegalArgumentException;
import org.apache.commons.math.exception.util.LocalizedFormats;
import org.apache.commons.math.geometry.Vector;
import org.apache.commons.math.geometry.euclidean.oned.Euclidean1D;
import org.apache.commons.math.geometry.euclidean.oned.IntervalsSet;
import org.apache.commons.math.geometry.euclidean.oned.OrientedPoint;
import org.apache.commons.math.geometry.euclidean.oned.Vector1D;
import org.apache.commons.math.geometry.partitioning.Embedding;
import org.apache.commons.math.geometry.partitioning.Hyperplane;
import org.apache.commons.math.geometry.partitioning.SubHyperplane;
import org.apache.commons.math.geometry.partitioning.Transform;
import org.apache.commons.math.util.FastMath;
import org.apache.commons.math.util.MathUtils;
/** This class represents an oriented line in the 2D plane.
* <p>An oriented line can be defined either by prolongating a line
* segment between two points past these points, or by one point and
* an angular direction (in trigonometric orientation).</p>
* <p>Since it is oriented the two half planes at its two sides are
* unambiguously identified as a left half plane and a right half
* plane. This can be used to identify the interior and the exterior
* in a simple way by local properties only when part of a line is
* used to define part of a polygon boundary.</p>
* <p>A line can also be used to completely define a reference frame
* in the plane. It is sufficient to select one specific point in the
* line (the orthogonal projection of the original reference frame on
* the line) and to use the unit vector in the line direction and the
* orthogonal vector oriented from left half plane to right half
* plane. We define two coordinates by the process, the
* <em>abscissa</em> along the line, and the <em>offset</em> across
* the line. All points of the plane are uniquely identified by these
* two coordinates. The line is the set of points at zero offset, the
* left half plane is the set of points with negative offsets and the
* right half plane is the set of points with positive offsets.</p>
* @version $Id: Line.java 1131153 2011-06-03 19:23:56Z luc $
* @since 3.0
*/
public class Line implements Hyperplane<Euclidean2D>, Embedding<Euclidean2D, Euclidean1D> {
/** Angle with respect to the abscissa axis. */
private double angle;
/** Cosine of the line angle. */
private double cos;
/** Sine of the line angle. */
private double sin;
/** Offset of the frame origin. */
private double originOffset;
/** Build a line from two points.
* <p>The line is oriented from p1 to p2</p>
* @param p1 first point
* @param p2 second point
*/
public Line(final Vector2D p1, final Vector2D p2) {
reset(p1, p2);
}
/** Build a line from a point and an angle.
* @param p point belonging to the line
* @param angle angle of the line with respect to abscissa axis
*/
public Line(final Vector2D p, final double angle) {
reset(p, angle);
}
/** Build a line from its internal characteristics.
* @param angle angle of the line with respect to abscissa axis
* @param cos cosine of the angle
* @param sin sine of the angle
* @param originOffset offset of the origin
*/
private Line(final double angle, final double cos, final double sin, final double originOffset) {
this.angle = angle;
this.cos = cos;
this.sin = sin;
this.originOffset = originOffset;
}
/** Copy constructor.
* <p>The created instance is completely independant from the
* original instance, it is a deep copy.</p>
* @param line line to copy
*/
public Line(final Line line) {
angle = MathUtils.normalizeAngle(line.angle, FastMath.PI);
cos = FastMath.cos(angle);
sin = FastMath.sin(angle);
originOffset = line.originOffset;
}
/** {@inheritDoc} */
public Line copySelf() {
return new Line(this);
}
/** Reset the instance as if built from two points.
* <p>The line is oriented from p1 to p2</p>
* @param p1 first point
* @param p2 second point
*/
public void reset(final Vector2D p1, final Vector2D p2) {
final double dx = p2.getX() - p1.getX();
final double dy = p2.getY() - p1.getY();
final double d = FastMath.hypot(dx, dy);
if (d == 0.0) {
angle = 0.0;
cos = 1.0;
sin = 0.0;
originOffset = p1.getY();
} else {
angle = FastMath.PI + FastMath.atan2(-dy, -dx);
cos = FastMath.cos(angle);
sin = FastMath.sin(angle);
originOffset = (p2.getX() * p1.getY() - p1.getX() * p2.getY()) / d;
}
}
/** Reset the instance as if built from a line and an angle.
* @param p point belonging to the line
* @param alpha angle of the line with respect to abscissa axis
*/
public void reset(final Vector2D p, final double alpha) {
this.angle = MathUtils.normalizeAngle(alpha, FastMath.PI);
cos = FastMath.cos(this.angle);
sin = FastMath.sin(this.angle);
originOffset = cos * p.getY() - sin * p.getX();
}
/** Revert the instance.
*/
public void revertSelf() {
if (angle < FastMath.PI) {
angle += FastMath.PI;
} else {
angle -= FastMath.PI;
}
cos = -cos;
sin = -sin;
originOffset = -originOffset;
}
/** Get the reverse of the instance.
* <p>Get a line with reversed orientation with respect to the
* instance. A new object is built, the instance is untouched.</p>
* @return a new line, with orientation opposite to the instance orientation
*/
public Line getReverse() {
return new Line((angle < FastMath.PI) ? (angle + FastMath.PI) : (angle - FastMath.PI),
-cos, -sin, -originOffset);
}
/** {@inheritDoc} */
public Vector1D toSubSpace(final Vector<Euclidean2D> point) {
Vector2D p2 = (Vector2D) point;
return new Vector1D(cos * p2.getX() + sin * p2.getY());
}
/** {@inheritDoc} */
public Vector2D toSpace(final Vector<Euclidean1D> point) {
final double abscissa = ((Vector1D) point).getX();
return new Vector2D(abscissa * cos - originOffset * sin,
abscissa * sin + originOffset * cos);
}
/** Get the intersection point of the instance and another line.
* @param other other line
* @return intersection point of the instance and the other line
* (really a {@link Vector2D Vector2D} instance)
*/
public Vector2D intersection(final Hyperplane<Euclidean2D> other) {
final Line otherL = (Line) other;
final double d = sin * otherL.cos - otherL.sin * cos;
if (FastMath.abs(d) < 1.0e-10) {
return null;
}
return new Vector2D((cos * otherL.originOffset - otherL.cos * originOffset) / d,
(sin * otherL.originOffset - otherL.sin * originOffset) / d);
}
/** {@inheritDoc} */
public SubLine wholeHyperplane() {
return new SubLine(this, new IntervalsSet());
}
/** Build a region covering the whole space.
* @return a region containing the instance (really a {@link
* PolygonsSet PolygonsSet} instance)
*/
public PolygonsSet wholeSpace() {
return new PolygonsSet();
}
/** Get the offset (oriented distance) of a parallel line.
* <p>This method should be called only for parallel lines otherwise
* the result is not meaningful.</p>
* <p>The offset is 0 if both lines are the same, it is
* positive if the line is on the right side of the instance and
* negative if it is on the left side, according to its natural
* orientation.</p>
* @param line line to check
* @return offset of the line
*/
public double getOffset(final Line line) {
return originOffset +
((cos * line.cos + sin * line.sin > 0) ? -line.originOffset : line.originOffset);
}
/** {@inheritDoc} */
public double getOffset(final Vector<Euclidean2D> point) {
Vector2D p2 = (Vector2D) point;
return sin * p2.getX() - cos * p2.getY() + originOffset;
}
/** {@inheritDoc} */
public boolean sameOrientationAs(final Hyperplane<Euclidean2D> other) {
final Line otherL = (Line) other;
return (sin * otherL.sin + cos * otherL.cos) >= 0.0;
}
/** Get one point from the plane.
* @param abscissa desired abscissa for the point
* @param offset desired offset for the point
* @return one point in the plane, with given abscissa and offset
* relative to the line
*/
public Vector2D getPointAt(final Vector1D abscissa, final double offset) {
final double x = abscissa.getX();
final double dOffset = offset - originOffset;
return new Vector2D(x * cos + dOffset * sin, x * sin - dOffset * cos);
}
/** Check if the line contains a point.
* @param p point to check
* @return true if p belongs to the line
*/
public boolean contains(final Vector2D p) {
return FastMath.abs(getOffset(p)) < 1.0e-10;
}
/** Check the instance is parallel to another line.
* @param line other line to check
* @return true if the instance is parallel to the other line
* (they can have either the same or opposite orientations)
*/
public boolean isParallelTo(final Line line) {
return FastMath.abs(sin * line.cos - cos * line.sin) < 1.0e-10;
}
/** Translate the line to force it passing by a point.
* @param p point by which the line should pass
*/
public void translateToPoint(final Vector2D p) {
originOffset = cos * p.getY() - sin * p.getX();
}
/** Get the angle of the line.
* @return the angle of the line with respect to the abscissa axis
*/
public double getAngle() {
return MathUtils.normalizeAngle(angle, FastMath.PI);
}
/** Set the angle of the line.
* @param angle new angle of the line with respect to the abscissa axis
*/
public void setAngle(final double angle) {
this.angle = MathUtils.normalizeAngle(angle, FastMath.PI);
cos = FastMath.cos(this.angle);
sin = FastMath.sin(this.angle);
}
/** Get the offset of the origin.
* @return the offset of the origin
*/
public double getOriginOffset() {
return originOffset;
}
/** Set the offset of the origin.
* @param offset offset of the origin
*/
public void setOriginOffset(final double offset) {
originOffset = offset;
}
/** Get a {@link org.apache.commons.math.geometry.partitioning.Transform
* Transform} embedding an affine transform.
* @param transform affine transform to embed (must be inversible
* otherwise the {@link
* org.apache.commons.math.geometry.partitioning.Transform#apply(Hyperplane)
* apply(Hyperplane)} method would work only for some lines, and
* fail for other ones)
* @return a new transform that can be applied to either {@link
* Vector2D Vector2D}, {@link Line Line} or {@link
* org.apache.commons.math.geometry.partitioning.SubHyperplane
* SubHyperplane} instances
* @exception MathIllegalArgumentException if the transform is non invertible
*/
public static Transform<Euclidean2D, Euclidean1D> getTransform(final AffineTransform transform)
throws MathIllegalArgumentException {
return new LineTransform(transform);
}
/** Class embedding an affine transform.
* <p>This class is used in order to apply an affine transform to a
* line. Using a specific object allow to perform some computations
* on the transform only once even if the same transform is to be
* applied to a large number of lines (for example to a large
* polygon)./<p>
*/
private static class LineTransform implements Transform<Euclidean2D, Euclidean1D> {
// CHECKSTYLE: stop JavadocVariable check
private double cXX;
private double cXY;
private double cX1;
private double cYX;
private double cYY;
private double cY1;
private double c1Y;
private double c1X;
private double c11;
// CHECKSTYLE: resume JavadocVariable check
/** Build an affine line transform from a n {@code AffineTransform}.
* @param transform transform to use (must be invertible otherwise
* the {@link LineTransform#apply(Hyperplane)} method would work
* only for some lines, and fail for other ones)
* @exception MathIllegalArgumentException if the transform is non invertible
*/
public LineTransform(final AffineTransform transform) throws MathIllegalArgumentException {
final double[] m = new double[6];
transform.getMatrix(m);
cXX = m[0];
cXY = m[2];
cX1 = m[4];
cYX = m[1];
cYY = m[3];
cY1 = m[5];
c1Y = cXY * cY1 - cYY * cX1;
c1X = cXX * cY1 - cYX * cX1;
c11 = cXX * cYY - cYX * cXY;
if (FastMath.abs(c11) < 1.0e-20) {
throw new MathIllegalArgumentException(LocalizedFormats.NON_INVERTIBLE_TRANSFORM);
}
}
/** {@inheritDoc} */
public Vector2D apply(final Vector<Euclidean2D> point) {
final Vector2D p2D = (Vector2D) point;
final double x = p2D.getX();
final double y = p2D.getY();
return new Vector2D(cXX * x + cXY * y + cX1,
cYX * x + cYY * y + cY1);
}
/** {@inheritDoc} */
public Line apply(final Hyperplane<Euclidean2D> hyperplane) {
final Line line = (Line) hyperplane;
final double rOffset = c1X * line.cos + c1Y * line.sin + c11 * line.originOffset;
final double rCos = cXX * line.cos + cXY * line.sin;
final double rSin = cYX * line.cos + cYY * line.sin;
final double inv = 1.0 / FastMath.sqrt(rSin * rSin + rCos * rCos);
return new Line(FastMath.PI + FastMath.atan2(-rSin, -rCos),
inv * rCos, inv * rSin,
inv * rOffset);
}
/** {@inheritDoc} */
public SubHyperplane<Euclidean1D> apply(final SubHyperplane<Euclidean1D> sub,
final Hyperplane<Euclidean2D> original,
final Hyperplane<Euclidean2D> transformed) {
final OrientedPoint op = (OrientedPoint) sub.getHyperplane();
final Line originalLine = (Line) original;
final Line transformedLine = (Line) transformed;
final Vector1D newLoc =
transformedLine.toSubSpace(apply(originalLine.toSpace(op.getLocation())));
return new OrientedPoint(newLoc, op.isDirect()).wholeHyperplane();
}
}
}