org.xmlcml.euclid.Angle Maven / Gradle / Ivy
Show all versions of euclid Show documentation
/**
* Copyright 2011 Peter Murray-Rust
*
* Licensed 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.xmlcml.euclid;
import org.apache.log4j.Logger;
/**
* Angle object
*
* Angle represents an angle The reason for the class is to help remember about
* radian/degree problems, to keep the angle in the right range (0, 2*PI) or
* (-PI, PI) as required, and to format output.
*
* To construct an angle the user must consciously use RADIANS
*
* The angle returned is always in RADIANS (except if getDegrees() is used)
*
*
* If SIGNED is used, the angle is in the range -180 to 180 (-pi to pi)
* If UNSIGNED is used, the angle is in the range 0 to 360 (0 to 2*pi)
* Default is SIGNED
*
* Default value of Angle is 0.0; Constructions of invalid angles should throw
* exceptions rather than try to make invalid angles.
*
* @author (C) P. Murray-Rust, 1996
*/
public class Angle {
private final static Logger LOG = Logger.getLogger(Angle.class);
/** units */
public enum Units {
/** */
DEGREES,
/** */
RADIANS;
}
/** range */
public enum Range {
/**
* any value.
*/
UNLIMITED,
/**
* 0 to 2*PI.
*/
UNSIGNED,
/**
* -PI to PI.
*/
SIGNED;
}
/**
* default is UNLIMITED
*/
Range range = Range.UNLIMITED;
/**
* default is RADIANS
*/
Units type = Units.RADIANS;
/** */
public final static double DEGREES_IN_RADIAN = 180.0 / Math.PI;
/**
* ALWAYS held as radians internally
*/
double angle = 0.0;
/**
* create default Angle default is (0.0)
*/
public Angle() {
}
/**
* create an angle IN RADIANS
*
* @param a radian value
*/
public Angle(double a) {
angle = a;
}
/**
* construct using degrees or radians
*
* @param a value of angle
* @param units unit of angle. It can either be in DEGREES or RADIANS
*/
public Angle(double a, Units units) {
angle = (units == Units.RADIANS) ? a : a / DEGREES_IN_RADIAN;
}
/**
* from X and Y components (uses atan2)
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
*/
public Angle(double y, double x) {
angle = Math.atan2(y, x);
}
/**
* copy constructor
*
* @param a Angle object that needs to be copied
*/
public Angle(Angle a) {
angle = a.angle;
range = a.range;
type = a.type;
}
/**
* shallowCopy
*
* @param a Angle object that needs to be copied
*/
public void shallowCopy(Angle a) {
range = a.range;
type = a.type;
angle = a.angle;
}
/**
* add two angles
*
* @param a2 the specified angle to be added to this angle
* @return new angle
*/
public Angle plus(Angle a2) {
Angle temp = new Angle(angle + a2.angle);
return temp;
}
/**
* subtract two angles
*
* @param a2 the specified angle to be subtracted from this angle
* @return new angle
*/
public Angle subtract(Angle a2) {
Angle temp = new Angle(angle - a2.angle);
return temp;
}
/**
* multiply an angle by a scalar
*
* @param f a floating point scalar value
* @return new angle
*/
public Angle multiplyBy(double f) {
Angle temp = new Angle(angle * f);
return temp;
}
/**
* trigonometric functions
*
* @return cosine of angle
*/
public double cos() {
return Math.cos(angle);
}
/**
* sin of angle
*
* @return sine
*/
public double sin() {
return Math.sin(angle);
}
/**
* tan.
*
* @return the tan
*/
public double tan() {
return Math.tan(angle);
}
/**
* normalise angle. to range 0 -{@literal >} 2*PI
*
* @param angle the angle to be normalised
* @return normalised angle
*/
public static double normalise(double angle) {
while (angle > 2 * Math.PI) {
angle -= 2 * Math.PI;
}
while (angle < 0.0) {
angle += 2 * Math.PI;
}
LOG.trace(angle);
return angle;
}
/** normalizes angle to be in range - Math.PI -{@literal >} Math.PI.
*
*/
public void normalizeToPlusMinusPI() {
angle = Angle.normalise(angle);
if (angle > Math.PI) {
angle -= 2 * Math.PI;
}
}
/**
* Normalises angle to be in range 0 -> Math.PI * 2.
*/
public void normalizeTo2Pi() {
angle = Angle.normalise(angle);
}
/**
* Tests whether this is a right angle.
*
* @param eps tolerance
* @return 1 for PI/2, -1 for -PI/2 else 0
*/
public Integer getRightAngle(Angle eps) {
if (eps == null) return null;
double absEps = Math.abs(eps.getRadian());
normalizeToPlusMinusPI();
Integer rt = 0;
if (Math.abs(Math.PI / 2. - this.getRadian()) < absEps) {
rt = 1;
} else if (Math.abs(-Math.PI / 2. - this.getRadian()) < absEps) {
rt = -1;
}
return rt;
}
/**
* relational operators normalise the angles internally before comparison
*/
/**
* are two normalised angles equal.
*
* @param a the specified angle to which this angle is compared
* @return boolean
* @deprecated // use epsilon method
*
*/
public boolean isEqualTo(double a) {
return Real.isEqual(Angle.normalise(angle), Angle.normalise(a));
}
/** compare angles allowing for epsilon
*
* @param a the specified angle to which this angle is compared to be equal to
* @param epsilon tolerance limit for equality
* @return boolean
*/
public boolean isEqualTo(double a, double epsilon) {
return Real.isEqual(Angle.normalise(angle), Angle.normalise(a), epsilon);
}
/**
* is one angle greater than another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be greater
* @return greater than
*/
public boolean greaterThan(double a) {
return Angle.normalise(angle) > Angle.normalise(a);
}
/**
* is one angle greater than or equal to another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be greater than or equal to
* @return greater than or equals
*/
public boolean greaterThanOrEquals(double a) {
return Angle.normalise(angle) >= Angle.normalise(a);
}
/**
* is one angle less than another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be lesser
* @return {@literal <}
*/
public boolean lessThan(double a) {
return Angle.normalise(angle) < Angle.normalise(a);
}
/**
* is one angle less than or equal to another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be lesser than or equal to
* @return {@literal <}=
*/
public boolean lessThanOrEquals(double a) {
return Angle.normalise(angle) <= Angle.normalise(a);
}
/**
* are two angles equal
*
* @param a the specified angle to which this angle is compared to be equal to
* @return ==
*/
public boolean isEqualTo(Angle a) {
return isEqualTo(a.angle);
}
/**
* are two angles equal
*
* @param a the specified angle to which this angle is compared to be equal to
* @param eps epsilon tolerance limit for equality
* @return ==
*/
public boolean isEqualTo(Angle a, double eps) {
return a != null && Real.isEqual(a.getRadian(), this.getRadian(), eps);
}
/**
* is one angle greater than another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be greater (after normalisation)
* @return {@literal >}
*/
public boolean greaterThan(Angle a) {
return greaterThan(a.angle);
}
/**
* is one angle greater than or equal to another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be greater than or equal to (after normalisation)
* @return {@literal >}=
*/
public boolean greaterThanOrEquals(Angle a) {
return greaterThanOrEquals(a.angle);
}
/**
* is one angle less than another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be lesser (after normalisation)
* @return {@literal <}
*/
public boolean lessThan(Angle a) {
return lessThan(a.angle);
}
/**
* is one angle less than or equal to another (after normalisation)
*
* @param a the specified angle to which this angle is compared to be lesser than or equal to (after normalisation)
* @return {@literal <}=
*/
public boolean lessThanOrEquals(Angle a) {
return lessThanOrEquals(a.angle);
}
/**
* get angle in radians
*
* @return angle
*/
public double getAngle() {
return adjust(angle);
}
/**
* get angle in radians
*
* @return angle
*/
public double getRadian() {
return adjust(angle);
}
/**
* get angle in degrees
*
* @return angle
*/
public double getDegrees() {
return adjust(angle) * DEGREES_IN_RADIAN;
}
/**
* @param a angle in degrees
*/
public void putDegrees(double a) {
angle = a / DEGREES_IN_RADIAN;
}
/**
* set type of range
*
* @param range type of range
*/
public void setRange(Range range) {
this.range = range;
}
/**
* set angle to correct range
*/
private double adjust(double a) {
if (range == Range.UNLIMITED)
return a;
double temp = normalise(a);
if (range == Range.UNSIGNED) {
return temp;
}
if (temp > Math.PI) {
temp -= 2 * Math.PI;
} else if (temp < -Math.PI) {
temp += 2 * Math.PI;
}
return temp;
}
/**
* to string.
*
* @return string
*/
public String toString() {
StringBuffer s = new StringBuffer();
double temp = adjust(angle);
if (type == Units.DEGREES) {
s.append(temp).append(" degrees");
} else {
s.append(temp);
}
return s.toString();
}
}