/*
Copyright (C) 2001, 2006 United States Government
as represented by the Administrator of the
National Aeronautics and Space Administration.
All Rights Reserved.
*/
package gov.nasa.worldwind.geom;
import gov.nasa.worldwind.util.Logging;
/**
* Represents a geometric angle. Instances of <code>Angle</code> are immutable. An <code>Angle</code> can be obtained
* through the factory methods <code>fromDegrees</code> and <code>fromRadians</code>.
*
* @author Tom Gaskins
* @version $Id: Angle.java 5250 2008-05-01 15:33:31Z dcollins $
*/
public class Angle implements Comparable<Angle>
{
/**
* Represents an angle of zero degrees
*/
public final static Angle ZERO = Angle.fromDegrees(0);
/**
* Represents a right angle of positive 90 degrees
*/
public final static Angle POS90 = Angle.fromDegrees(90);
/**
* Represents a right angle of negative 90 degrees
*/
public final static Angle NEG90 = Angle.fromDegrees(-90);
/**
* Represents an angle of positive 180 degrees
*/
public final static Angle POS180 = Angle.fromDegrees(180);
/**
* Represents an angle of negative 180 degrees
*/
public final static Angle NEG180 = Angle.fromDegrees(-180);
/**
* Represents an angle of positive 360 degrees
*/
public final static Angle POS360 = Angle.fromDegrees(360);
private final static double DEGREES_TO_RADIANS = Math.PI / 180d;
private final static double RADIANS_TO_DEGREES = 180d / Math.PI;
/**
* Obtains an <code>Angle</code> from a specified number of degrees.
*
* @param degrees the size in degrees of the <code>Angle</code> to be obtained
* @return a new <code>Angle</code>, whose size in degrees is given by <code>degrees</code>
*/
public static Angle fromDegrees(double degrees)
{
return new Angle(degrees, DEGREES_TO_RADIANS * degrees);
}
/**
* Obtains an <code>Angle</code> from a specified number of radians.
*
* @param radians the size in radians of the <code>Angle</code> to be obtained
* @return a new <code>Angle</code>, whose size in radians is given by <code>radians</code>
*/
public static Angle fromRadians(double radians)
{
return new Angle(RADIANS_TO_DEGREES * radians, radians);
}
private static final double PIOver2 = Math.PI / 2;
public static Angle fromDegreesLatitude(double degrees)
{
degrees = degrees < -90 ? -90 : degrees > 90 ? 90 : degrees;
double radians = DEGREES_TO_RADIANS * degrees;
radians = radians < -PIOver2 ? -PIOver2 : radians > PIOver2 ? PIOver2 : radians;
return new Angle(degrees, radians);
}
public static Angle fromRadiansLatitude(double radians)
{
radians = radians < -PIOver2 ? -PIOver2 : radians > PIOver2 ? PIOver2 : radians;
double degrees = RADIANS_TO_DEGREES * radians;
degrees = degrees < -90 ? -90 : degrees > 90 ? 90 : degrees;
return new Angle(degrees, radians);
}
public static Angle fromDegreesLongitude(double degrees)
{
degrees = degrees < -180 ? -180 : degrees > 180 ? 180 : degrees;
double radians = DEGREES_TO_RADIANS * degrees;
radians = radians < -Math.PI ? -Math.PI : radians > Math.PI ? Math.PI : radians;
return new Angle(degrees, radians);
}
public static Angle fromRadiansLongitude(double radians)
{
radians = radians < -Math.PI ? -Math.PI : radians > Math.PI ? Math.PI : radians;
double degrees = RADIANS_TO_DEGREES * radians;
degrees = degrees < -180 ? -180 : degrees > 180 ? 180 : degrees;
return new Angle(degrees, radians);
}
/**
* Obtains an <code>Angle</code> from rectangular coordinates.
*
* @param x the abscissa coordinate
* @param y the ordinate coordinate
* @return a new <code>Angle</code>, whose size is determined from <code>x</code> and <code>y</code>
*/
public static Angle fromXY(double x, double y)
{
double radians = Math.atan2(y, x);
return new Angle(RADIANS_TO_DEGREES * radians, radians);
}
public final double degrees;
public final double radians;
public Angle(Angle angle)
{
this.degrees = angle.degrees;
this.radians = angle.radians;
}
//
// private Angle(double degrees)
// {
// this.degrees = degrees;
// this.radians = DEGREES_TO_RADIANS * this.degrees;
// }
private Angle(double degrees, double radians)
{
this.degrees = degrees;
this.radians = radians;
}
/**
* Retrieves the size of this <code>Angle</code> in degrees. This method may be faster than first obtaining the
* radians and then converting to degrees.
*
* @return the size of this <code>Angle</code> in degrees
*/
public final double getDegrees()
{
return this.degrees;
}
/**
* Retrieves the size of this <code>Angle</code> in radians. This may be useful for <code>java.lang.Math</code>
* functions, which generally take radians as trigonometric arguments. This method may be faster that first
* obtaining the degrees and then converting to radians.
*
* @return the size of this <code>Angle</code> in radians.
*/
public final double getRadians()
{
return this.radians;
}
/**
* Obtains the sum of these two <code>Angle</code>s. Does not accept a null argument. This method is commutative, so
* <code>a.add(b)</code> and <code>b.add(a)</code> are equivalent. Neither this <code>Angle</code> nor
* <code>angle</code> is changed, instead the result is returned as a new <code>Angle</code>.
*
* @param angle the <code>Angle</code> to add to this one.
* @return an <code>Angle</code> whose size is the total of this <code>Angle</code>s and <code>angle</code>s size
* @throws IllegalArgumentException if <code>angle</code> is null
*/
public final Angle add(Angle angle)
{
if (angle == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees(this.degrees + angle.degrees);
}
/**
* Obtains the difference of these two <code>Angle</code>s. Does not accept a null argument. This method is not
* commutative. Neither this <code>Angle</code> nor <code>angle</code> is changed, instead the result is returned as
* a new <code>Angle</code>.
*
* @param angle the <code>Angle</code> to subtract from this <code>Angle</code>
* @return a new <code>Angle</code> correpsonding to this <code>Angle</code>'s size minus <code>angle</code>'s size
* @throws IllegalArgumentException if <code>angle</code> is null
*/
public final Angle subtract(Angle angle)
{
if (angle == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees(this.degrees - angle.degrees);
}
/**
* Multiplies this <code>Angle</code> by <code>multiplier</code>. This <code>Angle</code> remains unchanged. The
* result is returned as a new <code>Angle</code>.
*
* @param multiplier a scalar by which this <code>Angle</code> is multiplied
* @return a new <code>Angle</code> whose size equals this <code>Angle</code>'s size multiplied by
* <code>multiplier</code>
*/
public final Angle multiply(double multiplier)
{
return Angle.fromDegrees(this.degrees * multiplier);
}
/**
* Divides this <code>Angle</code> by another angle. This <code>Angle</code> remains unchanged, instead the
* resulting value in degrees is returned.
*
* @param angle the <code>Angle</code> by which to divide
* @return this <code>Angle</code>'s degrees divided by <code>angle</code>'s degrees
* @throws IllegalArgumentException if <code>angle</code> is null
*/
public final double divide(Angle angle)
{
if (angle == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return this.degrees / angle.degrees;
}
public final Angle addDegrees(double degrees)
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Angle.fromDegrees(this.degrees + degrees);
}
public final Angle subtractDegrees(double degrees)
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Angle.fromDegrees(this.degrees - degrees);
}
/**
* Divides this <code>Angle</code> by <code>divisor</code>. This <code>Angle</code> remains unchanged. The result is
* returned as a new <code>Angle</code>. Behaviour is undefined if <code>divisor</code> equals zero.
*
* @param divisor the number to be divided by
* @return a new <code>Angle</code> equivalent to this <code>Angle</code> divided by <code>divisor</code>
*/
public final Angle divide(double divisor)
{
return Angle.fromDegrees(this.degrees / divisor);
}
public final Angle addRadians(double radians)
{
return Angle.fromRadians(this.radians + radians);
}
public final Angle subtractRadians(double radians)
{
return Angle.fromRadians(this.radians - radians);
}
/**
* Computes the shortest distance between this and <code>angle</code>, as an
* <code>Angle</code>.
*
* @param angle the <code>Angle</code> to measure angular distance to.
* @return the angular distance between this and <code>value</code>.
*/
public Angle angularDistanceTo(Angle angle)
{
if (angle == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
double differenceDegrees = angle.subtract(this).degrees;
if (differenceDegrees < -180)
differenceDegrees += 360;
else if (differenceDegrees > 180)
differenceDegrees -= 360;
double absAngle = Math.abs(differenceDegrees);
return Angle.fromDegrees(absAngle);
}
/**
* Obtains the sine of this <code>Angle</code>.
*
* @return the trigonometric sine of this <code>Angle</code>
*/
public final double sin()
{
return Math.sin(this.radians);
}
public final double sinHalfAngle()
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Math.sin(0.5 * this.radians);
}
public static Angle asin(double sine)
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Angle.fromRadians(Math.asin(sine));
}
/**
* Obtains the cosine of this <code>Angle</code>
*
* @return the trigonometric cosine of this <code>Angle</code>
*/
public final double cos()
{
return Math.cos(this.radians);
}
public final double cosHalfAngle()
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Math.cos(0.5 * this.radians);
}
public static Angle acos(double cosine)
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Angle.fromRadians(Math.acos(cosine));
}
/**
* Obtains the tangent of half of this <code>Angle</code>.
*
* @return the trigonometric tangent of half of this <code>Angle</code>
*/
public final double tanHalfAngle()
{
return Math.tan(0.5 * this.radians);
}
public static Angle atan(double tan)
{ //Tom: this method is not used, should we delete it? (13th Dec 06)
return Angle.fromRadians(Math.atan(tan));
}
/**
* Obtains the average of two <code>Angle</code>s. This method is commutative, so <code>midAngle(m, n)</code> and
* <code>midAngle(n, m)</code> are equivalent.
*
* @param a1 the first <code>Angle</code>
* @param a2 the second <code>Angle</code>
* @return the average of <code>a1</code> and <code>a2</code> throws IllegalArgumentException if either angle is
* null
*/
public static Angle midAngle(Angle a1, Angle a2)
{
if (a1 == null || a2 == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees(0.5 * (a1.degrees + a2.degrees));
}
/**
* Obtains the average of three <code>Angle</code>s. The order of parameters does not matter.
*
* @param a the first <code>Angle</code>
* @param b the second <code>Angle</code>
* @return the average of <code>a1</code>, <code>a2</code> and <code>a3</code>
* @throws IllegalArgumentException if <code>a</code> or <code>b</code> is null
*/
public static Angle average(Angle a, Angle b)
{
if (a == null || b == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees(0.5 * (a.degrees + b.degrees));
}
/**
* Obtains the average of three <code>Angle</code>s. The order of parameters does not matter.
*
* @param a the first <code>Angle</code>
* @param b the second <code>Angle</code>
* @param c the third <code>Angle</code>
* @return the average of <code>a1</code>, <code>a2</code> and <code>a3</code>
* @throws IllegalArgumentException if <code>a</code>, <code>b</code> or <code>c</code> is null
*/
public static Angle average(Angle a, Angle b, Angle c)
{
if (a == null || b == null || c == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees((a.degrees + b.degrees + c.degrees) / 3);
}
/**
* Linearly interpolates between two angles.
*
* @param amount the interpolant.
* @param value1 the first <code>Angle</code>.
* @param value2 the second <code>Angle</code>.
* @return a new <code>Angle</code> between <code>value1</code> and <code>value2</code>.
*/
public static Angle mix(double amount, Angle value1, Angle value2)
{
if (value1 == null || value2 == null)
{
String message = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (amount < 0)
return value1;
else if (amount > 1)
return value2;
Quaternion quat = Quaternion.slerp(
amount,
Quaternion.fromAxisAngle(value1, Vec4.UNIT_X),
Quaternion.fromAxisAngle(value2, Vec4.UNIT_X));
Angle angle = quat.getRotationX();
if (Double.isNaN(angle.degrees))
return null;
return angle;
}
/**
* Compares this <code>Angle</code> with <code>angle</code> for order. Returns a negative integer if this is the
* smaller <code>Angle</code>, a positive integer if this is the larger, and zero if both <code>Angle</code>s are
* equal.
*
* @param angle the <code>Angle</code> to compare against
* @return -1 if this <code>Angle</code> is smaller, 0 if both are equal and +1 if this <code>Angle</code> is
* larger.
* @throws IllegalArgumentException if <code>angle</code> is null
*/
public final int compareTo(Angle angle)
{
if (angle == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (this.degrees < angle.degrees)
return -1;
if (this.degrees > angle.degrees)
return 1;
return 0;
}
private static double normalizedDegreesLatitude(double degrees)
{
double lat = degrees % 180;
return lat > 90 ? 180 - lat : lat < -90 ? -180 - lat : lat;
}
private static double normalizedDegreesLongitude(double degrees)
{
double lon = degrees % 360;
return lon > 180 ? lon - 360 : lon < -180 ? 360 + lon : lon;
}
public static Angle normalizedLatitude(Angle unnormalizedAngle)
{
if (unnormalizedAngle == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return Angle.fromDegrees(normalizedDegreesLatitude(unnormalizedAngle.degrees));
}
public static Angle normalizedLongitude(Angle unnormalizedAngle)
{
if (unnormalizedAngle == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return Angle.fromDegrees(normalizedDegreesLongitude(unnormalizedAngle.degrees));
}
public Angle normalizedLatitude()
{
return normalizedLatitude(this);
}
public Angle normalizedLongitude()
{
return normalizedLongitude(this);
}
public static boolean crossesLongitudeBoundary(Angle angleA, Angle angleB)
{
if (angleA == null || angleB == null)
{
String msg = Logging.getMessage("nullValue.AngleIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
// A segment cross the line if end pos have different longitude signs
// and are more than 180 degress longitude apart
return (Math.signum(angleA.degrees) != Math.signum(angleB.degrees))
&& (Math.abs(angleA.degrees - angleB.degrees) > 180);
}
/**
* Obtains a <code>String</code> representation of this <code>Angle</code>.
*
* @return the value of this <code>Angle</code> in degrees and as a <code>String</code>
*/
@Override
public final String toString()
{
return Double.toString(this.degrees) + '\u00B0';
}
/**
* Obtains the amount of memory this <code>Angle</code> consumes.
*
* @return the memory footprint of this <code>Angle</code> in bytes.
*/
public long getSizeInBytes()
{
return Double.SIZE;
}
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Angle angle = (Angle) o;
//noinspection RedundantIfStatement
if (angle.degrees!= this.degrees)
return false;
return true;
}
public int hashCode()
{
long temp = degrees != +0.0d ? Double.doubleToLongBits(degrees) : 0L;
return (int) (temp ^ (temp >>> 32));
}
}