All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.geom.Angle Maven / Gradle / Ivy

The newest version!
/*
 * 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.*;

/**
 * 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 2419 2014-11-08 04:44:55Z 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 positive 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 or the degrees is negative.
     */
    public static Angle fromDMS(int degrees, int minutes, int seconds)
    {
        if (degrees < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", degrees);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        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(degrees + minutes / 60d + seconds / 3600d);
    }

    /**
     * Obtain an angle from a given number of positive degrees and decimal minutes.
     *
     * @param degrees integer number of degrees, positive.
     * @param minutes double representing the decimal representation of minutes and seconds.
     *
     * @return a new angle whose size in degrees is given by degrees and decimal minutes.
     *
     * @throws IllegalArgumentException if minutes or seconds are outside the 0-60 range or the degrees is negative.
     */
    public static Angle fromDMdS(int degrees, double minutes)
    {
        if (degrees < 0)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", degrees);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }
        if (minutes < 0 || minutes >= 60)
        {
            String message = Logging.getMessage("generic.ArgumentOutOfRange", minutes);
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        return Angle.fromDegrees(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
     * 

* For a string containing both a sign and compass direction, the compass direction will take precedence. * * @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); char prefix = dmsString.charAt(0); if (!Character.isDigit(suffix)) { sign = (suffix == 'S' || suffix == 'W') ? -1 : 1; dmsString = dmsString.substring(0, dmsString.length() - 1); dmsString = dmsString.trim(); // check and trim the prefix if it is erroneously included if (!Character.isDigit(prefix)) { dmsString = dmsString.substring(1, dmsString.length()); dmsString = dmsString.trim(); } } else 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)); } public static double arctanh(double radians) { return 0.5 * Math.log((1 + radians) / (1 - radians)); } /** * 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); } /** * Limits a specified angle to be within a specified minimum and maximum. *

* The returned angle is undefined if min > max. Otherwise, this method's return value is equivalent to the * following: *

*

  • min - If value < min
  • max - If value > max
  • value - If min <= value <= max
* * @param value The angle to clamp. * @param min The minimum angle to clamp to. * @param max The maximum angle to clamp to. * * @return The clamped angle. * * @throws IllegalArgumentException if any argument is null. */ public static Angle clamp(Angle value, Angle min, Angle max) { if (value == null || min == null || max == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return value.degrees < min.degrees ? min : (value.degrees > max.degrees ? max : value); } /** * 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; } public static double normalizedDegrees(double degrees) { double a = degrees % 360; return a > 180 ? a - 360 : a < -180 ? 360 + a : a; } public static double normalizedDegreesLatitude(double degrees) { double lat = degrees % 180; return lat > 90 ? 180 - lat : lat < -90 ? -180 - lat : lat; } public static double normalizedDegreesLongitude(double degrees) { double lon = degrees % 360; return lon > 180 ? lon - 360 : lon < -180 ? 360 + lon : lon; } public static Angle normalizedAngle(Angle unnormalizedAngle) { if (unnormalizedAngle == null) { String msg = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } return Angle.fromDegrees(normalizedDegrees(unnormalizedAngle.degrees)); } 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 normalize() { return normalizedAngle(this); } 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)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy