src.gov.nasa.worldwind.geom.Angle Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents a geometric angle. Instances of Angle
are immutable. An angle can be obtained through the
* factory methods {@link #fromDegrees} and {@link #fromRadians}.
*
* @author Tom Gaskins
* @version $Id: Angle.java 1690 2013-10-24 19:42:53Z tgaskins $
*/
public class Angle implements Comparable
{
// Angle format
public final static String ANGLE_FORMAT_DD = "gov.nasa.worldwind.Geom.AngleDD";
public final static String ANGLE_FORMAT_DM = "gov.nasa.worldwind.Geom.AngleDM";
public final static String ANGLE_FORMAT_DMS = "gov.nasa.worldwind.Geom.AngleDMS";
/** 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);
/** Represents an angle of negative 360 degrees */
public final static Angle NEG360 = Angle.fromDegrees(-360);
/** Represents an angle of 1 minute */
public final static Angle MINUTE = Angle.fromDegrees(1d / 60d);
/** Represents an angle of 1 second */
public final static Angle SECOND = Angle.fromDegrees(1d / 3600d);
private final static double DEGREES_TO_RADIANS = Math.PI / 180d;
private final static double RADIANS_TO_DEGREES = 180d / Math.PI;
/**
* Obtains an angle from a specified number of degrees.
*
* @param degrees the size in degrees of the angle to be obtained
*
* @return a new angle, whose size in degrees is given by degrees
*/
public static Angle fromDegrees(double degrees)
{
return new Angle(degrees, DEGREES_TO_RADIANS * degrees);
}
/**
* Obtains an angle from a specified number of radians.
*
* @param radians the size in radians of the angle to be obtained.
*
* @return a new angle, whose size in radians is given by radians
.
*/
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 angle from rectangular coordinates.
*
* @param x the abscissa coordinate.
* @param y the ordinate coordinate.
*
* @return a new angle, whose size is determined from x
and y
.
*/
public static Angle fromXY(double x, double y)
{
double radians = Math.atan2(y, x);
return new Angle(RADIANS_TO_DEGREES * radians, radians);
}
/**
* Obtain an angle from a given number of degrees, minutes and seconds.
*
* @param degrees integer number of degrees, positive.
* @param minutes integer number of minutes, positive only between 0 and 60.
* @param seconds integer number of seconds, positive only between 0 and 60.
*
* @return a new angle whose size in degrees is given by degrees
, minutes
and
* seconds
.
*
* @throws IllegalArgumentException if minutes or seconds are outside the 0-60 range.
*/
public static Angle fromDMS(int degrees, int minutes, int seconds)
{
if (minutes < 0 || minutes >= 60)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", minutes);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (seconds < 0 || seconds >= 60)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", seconds);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees(Math.signum(degrees) * (Math.abs(degrees) + minutes / 60d + seconds / 3600d));
}
public static Angle fromDMdS(int degrees, double minutes)
{
if (minutes < 0 || minutes >= 60)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", minutes);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return Angle.fromDegrees(Math.signum(degrees) * (Math.abs(degrees) + minutes / 60d));
}
/**
* Obtain an angle from a degrees, minute and seconds character string.
* eg:
* 123 34 42
* -123* 34' 42" (where * stands for the degree symbol)
* +45* 12' 30" (where * stands for the degree symbol)
* 45 12 30 S
* 45 12 30 N
*
*
* @param dmsString the degrees, minute and second character string.
*
* @return the corresponding angle.
*
* @throws IllegalArgumentException if dmsString is null or not properly formated.
*/
public static Angle fromDMS(String dmsString)
{
if (dmsString == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Check for string format validity
String regex = "([-|\\+]?\\d{1,3}[d|D|\u00B0|\\s](\\s*\\d{1,2}['|\u2019|\\s])?"
+ "(\\s*\\d{1,2}[\"|\u201d|\\s])?\\s*([N|n|S|s|E|e|W|w])?\\s?)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(dmsString + " ");
if (!matcher.matches())
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", dmsString);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Replace degree, min and sec signs with space
dmsString = dmsString.replaceAll("[D|d|\u00B0|'|\u2019|\"|\u201d]", " ");
// Replace multiple spaces with single ones
dmsString = dmsString.replaceAll("\\s+", " ");
dmsString = dmsString.trim();
// Check for sign prefix and suffix
int sign = 1;
char suffix = dmsString.toUpperCase().charAt(dmsString.length() - 1);
if (!Character.isDigit(suffix))
{
sign = (suffix == 'S' || suffix == 'W') ? -1 : 1;
dmsString = dmsString.substring(0, dmsString.length() - 1);
dmsString = dmsString.trim();
}
char prefix = dmsString.charAt(0);
if (!Character.isDigit(prefix))
{
sign *= (prefix == '-') ? -1 : 1;
dmsString = dmsString.substring(1, dmsString.length());
}
// Extract degrees, minutes and seconds
String[] DMS = dmsString.split(" ");
int d = Integer.parseInt(DMS[0]);
int m = DMS.length > 1 ? Integer.parseInt(DMS[1]) : 0;
int s = DMS.length > 2 ? Integer.parseInt(DMS[2]) : 0;
return fromDMS(d, m, s).multiply(sign);
}
public final double degrees;
public final double radians;
public Angle(Angle angle)
{
this.degrees = angle.degrees;
this.radians = angle.radians;
}
private Angle(double degrees, double radians)
{
this.degrees = degrees;
this.radians = radians;
}
/**
* Retrieves the size of this angle in degrees. This method may be faster than first obtaining the radians and then
* converting to degrees.
*
* @return the size of this angle in degrees.
*/
public final double getDegrees()
{
return this.degrees;
}
/**
* Retrieves the size of this angle in radians. This may be useful for java.lang.Math
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 angle in radians.
*/
public final double getRadians()
{
return this.radians;
}
/**
* Obtains the sum of these two angles. Does not accept a null argument. This method is commutative, so
* a.add(b)
and b.add(a)
are equivalent. Neither this angle nor angle is changed, instead
* the result is returned as a new angle.
*
* @param angle the angle to add to this one.
*
* @return an angle whose size is the total of this angles and angles size.
*
* @throws IllegalArgumentException if angle 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 angles. Does not accept a null argument. This method is not commutative.
* Neither this angle nor angle is changed, instead the result is returned as a new angle.
*
* @param angle the angle to subtract from this angle.
*
* @return a new angle corresponding to this angle's size minus angle's size.
*
* @throws IllegalArgumentException if angle 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 angle by multiplier
. This angle remains unchanged. The result is returned as a new
* angle.
*
* @param multiplier a scalar by which this angle is multiplied.
*
* @return a new angle whose size equals this angle's size multiplied by multiplier
.
*/
public final Angle multiply(double multiplier)
{
return Angle.fromDegrees(this.degrees * multiplier);
}
/**
* Divides this angle by another angle. This angle remains unchanged, instead the resulting value in degrees is
* returned.
*
* @param angle the angle by which to divide.
*
* @return this angle's degrees divided by angle's degrees.
*
* @throws IllegalArgumentException if angle 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);
}
if (angle.getDegrees() == 0.0)
{
String message = Logging.getMessage("generic.DivideByZero");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
return this.degrees / angle.degrees;
}
public final Angle addDegrees(double degrees)
{
return Angle.fromDegrees(this.degrees + degrees);
}
public final Angle subtractDegrees(double degrees)
{
return Angle.fromDegrees(this.degrees - degrees);
}
/**
* Divides this angle by divisor
. This angle remains unchanged. The result is returned as a new angle.
* Behaviour is undefined if divisor
equals zero.
*
* @param divisor the number to be divided by.
*
* @return a new angle equivalent to this angle divided by divisor
.
*/
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 angle, as an angle.
*
* @param angle the angle to measure angular distance to.
*
* @return the angular distance between this and value
.
*/
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 angle.
*
* @return the trigonometric sine of this angle.
*/
public final double sin()
{
return Math.sin(this.radians);
}
public final double sinHalfAngle()
{
return Math.sin(0.5 * this.radians);
}
public static Angle asin(double sine)
{
return Angle.fromRadians(Math.asin(sine));
}
/**
* Obtains the cosine of this angle.
*
* @return the trigonometric cosine of this angle.
*/
public final double cos()
{
return Math.cos(this.radians);
}
public final double cosHalfAngle()
{
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 angle.
*
* @return the trigonometric tangent of half of this angle.
*/
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 angles. This method is commutative, so midAngle(m, n)
and
* midAngle(n, m)
are equivalent.
*
* @param a1 the first angle.
* @param a2 the second angle.
*
* @return the average of a1
and a2
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 angles. The order of parameters does not matter.
*
* @param a the first angle.
* @param b the second angle.
*
* @return the average of a1
, a2
and a3
*
* @throws IllegalArgumentException if a
or b
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 angles. The order of parameters does not matter.
*
* @param a the first angle.
* @param b the second angle.
* @param c the third angle.
*
* @return the average of a1
, a2
and a3
.
*
* @throws IllegalArgumentException if a
, b
or c
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 angle.
* @param value2 the second angle.
*
* @return a new angle between value1
and value2
.
*/
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 {@link Angle} with another. Returns a negative integer if this is the smaller angle, a positive
* integer if this is the larger, and zero if both angles are equal.
*
* @param angle the angle to compare against.
*
* @return -1 if this angle is smaller, 0 if both are equal and +1 if this angle is larger.
*
* @throws IllegalArgumentException if angle 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 degrees longitude apart
return (Math.signum(angleA.degrees) != Math.signum(angleB.degrees))
&& (Math.abs(angleA.degrees - angleB.degrees) > 180);
}
public static boolean isValidLatitude(double value)
{
return value >= -90 && value <= 90;
}
public static boolean isValidLongitude(double value)
{
return value >= -180 && value <= 180;
}
public static Angle max(Angle a, Angle b)
{
return a.degrees >= b.degrees ? a : b;
}
public static Angle min(Angle a, Angle b)
{
return a.degrees <= b.degrees ? a : b;
}
/**
* Obtains a String
representation of this angle.
*
* @return the value of this angle in degrees and as a String
.
*/
@Override
public final String toString()
{
return Double.toString(this.degrees) + '\u00B0';
}
/**
* Forms a decimal degrees {@link String} representation of this {@link Angle}.
*
* @param digits the number of digits past the decimal point to include in the string.
*
* @return the value of this angle in decimal degrees as a string with the specified number of digits beyond the
* decimal point. The string is padded with trailing zeros to fill the number of post-decimal point
* positions requested.
*/
public final String toDecimalDegreesString(int digits)
{
if ((digits < 0) || (digits > 15))
{
String msg = Logging.getMessage("generic.ArgumentOutOfRange", digits);
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
return String.format("%." + digits + "f\u00B0", this.degrees);
}
/**
* Obtains a {@link String} representation of this {@link Angle} formatted as degrees, minutes and seconds integer
* values.
*
* @return the value of this angle in degrees, minutes, seconds as a string.
*/
public final String toDMSString()
{
double temp = this.degrees;
int sign = (int) Math.signum(temp);
temp *= sign;
int d = (int) Math.floor(temp);
temp = (temp - d) * 60d;
int m = (int) Math.floor(temp);
temp = (temp - m) * 60d;
int s = (int) Math.round(temp);
if (s == 60)
{
m++;
s = 0;
} // Fix rounding errors
if (m == 60)
{
d++;
m = 0;
}
return (sign == -1 ? "-" : "") + d + '\u00B0' + ' ' + m + '\u2019' + ' ' + s + '\u201d';
}
/**
* Obtains a {@link String} representation of this {@link Angle} formatted as degrees and decimal minutes.
*
* @return the value of this angle in degrees and decimal minutes as a string.
*/
public final String toDMString()
{
double temp = this.degrees;
int sign = (int) Math.signum(temp);
temp *= sign;
int d = (int) Math.floor(temp);
temp = (temp - d) * 60d;
int m = (int) Math.floor(temp);
temp = (temp - m) * 60d;
int s = (int) Math.round(temp);
if (s == 60)
{
m++;
s = 0;
} // Fix rounding errors
if (m == 60)
{
d++;
m = 0;
}
double mf = s == 0 ? m : m + s / 60.0;
return (sign == -1 ? "-" : "") + d + '\u00B0' + ' ' + String.format("%5.2f", mf) + '\u2019';
}
public final String toFormattedDMSString()
{
double temp = this.degrees;
int sign = (int) Math.signum(temp);
temp *= sign;
int d = (int) Math.floor(temp);
temp = (temp - d) * 60d;
int m = (int) Math.floor(temp);
temp = (temp - m) * 60d;
double s = Math.rint(temp * 100) / 100; // keep two decimals for seconds
if (s == 60)
{
m++;
s = 0;
} // Fix rounding errors
if (m == 60)
{
d++;
m = 0;
}
return String.format("%4d\u00B0 %2d\u2019 %5.2f\u201d", sign * d, m, s);
}
public final double[] toDMS()
{
double temp = this.degrees;
int sign = (int) Math.signum(temp);
temp *= sign;
int d = (int) Math.floor(temp);
temp = (temp - d) * 60d;
int m = (int) Math.floor(temp);
temp = (temp - m) * 60d;
double s = Math.rint(temp * 100) / 100; // keep two decimals for seconds
if (s == 60)
{
m++;
s = 0;
} // Fix rounding errors
if (m == 60)
{
d++;
m = 0;
}
return new double[] {sign * d, m, s};
}
/**
* Obtains the amount of memory this {@link Angle} consumes.
*
* @return the memory footprint of this angle in bytes.
*/
public long getSizeInBytes()
{
return Double.SIZE / 8;
}
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));
}
}