package org.opensha2.geo;
import static org.opensha2.data.Data.checkInRange;
import org.opensha2.util.Maths;
import com.google.common.collect.Range;
/**
* Constants and utility methods pertaining to geographic coordinates.
*
* @author Peter Powers
*/
public class Coordinates {
/**
* The Authalic mean radius (A<subscript>r</subscript>) of the earth:
* {@code 6371.0072 km}.
*
* @see <a href="http://en.wikipedia.org/wiki/Earth_radius#Authalic_radius"
* target="_blank">Wikipedia</a>
*/
public static final double EARTH_RADIUS_MEAN = 6371.0072;
/**
* The equatorial radius of the earth as derived from the WGS-84 ellipsoid:
* {@code 6378.1370 km}.
*
* @see <a href="http://en.wikipedia.org/wiki/Earth_radius#Equatorial_radius"
* target="_blank">Wikipedia</a>
*/
public static final double EARTH_RADIUS_EQUATORIAL = 6378.1370;
/**
* The polar radius of the earth as derived from the WGS-84 ellipsoid:
* {@code 6356.7523 km]}.
*
* @see <a href="http://en.wikipedia.org/wiki/Earth_radius#Polar_radius"
* target="_blank">Wikipedia</a>
*/
public static final double EARTH_RADIUS_POLAR = 6356.7523;
/** Supported latitudes: {@code [-90..90]°}. */
public static final Range<Double> LAT_RANGE = Range.closed(-90.0, 90.0);
/** Supported longitudes: {@code (-360..360)°}. */
public static final Range<Double> LON_RANGE = Range.open(-360.0, 360.0);
/** Constant for arcminutes per degree: {@code 60″}. */
public static final double MINUTES_PER_DEGREE = 60;
/** Constant for arcseconds per degree: {@code 3600′}. */
public static final double SECONDS_PER_DEGREE = 3600;
/**
* Ensure that {@code -90° ≤ latitude ≤ 90°}.
*
* @param latitude to validate
* @return the validated latitude
* @throws IllegalArgumentException if {@code latitude} is outside the range
* {@code [-90..90]°}
*/
public static double checkLatitude(double latitude) {
return checkInRange(LAT_RANGE, "Latitude", latitude);
}
/**
* Ensure that {@code -360° < longitude < 360°}.
*
* @param longitude to validate
* @return the validated longitude
* @throws IllegalArgumentException if {@code longitude} is outside the range
* {@code (-360..360)°}
*/
public static double checkLongitude(double longitude) {
return checkInRange(LON_RANGE, "Longitude", longitude);
}
/**
* Return the radius of the earth at the latitude of the supplied
* <code>Location</code> (see <a
* href="http://en.wikipedia.org/wiki/Earth_radius#Authalic_radius"
* target="_blank">Wikipedia</a> for source).
*
* @param location at which to compute the earth's radius
* @return the earth's radius at the supplied {@code location}
*/
public static double radiusAtLocation(Location location) {
double cosL = Math.cos(location.latRad());
double sinL = Math.sin(location.latRad());
double C1 = cosL * EARTH_RADIUS_EQUATORIAL;
double C2 = C1 * EARTH_RADIUS_EQUATORIAL;
double C3 = sinL * EARTH_RADIUS_POLAR;
double C4 = C3 * EARTH_RADIUS_POLAR;
return Math.sqrt((C2 * C2 + C4 * C4) / (C1 * C1 + C3 * C3));
}
/**
* Return a conversion factor for the number of degrees of latitude per km at
* a given {@code location}. This can be used to convert between km-based and
* degree-based grid spacing. The calculation takes into account the shape of
* the earth (oblate spheroid) and scales the conversion accordingly.
*
* @param location at which to compute conversion value
* @return the number of decimal degrees latitude per km at the supplied
* {@code location}
* @see #radiusAtLocation(Location)
*/
public static double degreesLatPerKm(Location location) {
return Maths.TO_DEG / radiusAtLocation(location);
}
/**
* Return a conversion factor for the number of degrees of longitude per km at
* a given {@code location}. This can be used to convert between km-based and
* degree-based grid spacing. The calculation scales the degrees longitude per
* km at the equator by the cosine of the supplied latitude. (<i>Note</i>: The
* values returned are not based on the radius of curvature of the earth at
* the supplied location.)
*
* @param location at which to compute conversion value
* @return the number of decimal degrees longitude per km at the supplied
* {@code location}
*/
public static double degreesLonPerKm(Location location) {
return Maths.TO_DEG / (EARTH_RADIUS_EQUATORIAL * Math.cos(location.latRad()));
}
/**
* Convert arc{@code seconds} to decimal degrees.
*
* @param seconds to convert
* @return the equivalent number of decimal degrees
*/
public static double secondsToDeg(double seconds) {
return seconds / SECONDS_PER_DEGREE;
}
/**
* Convert arc{@code minutes} to decimal degrees.
*
* @param minutes to convert
* @return the equivalent number of decimal degrees
*/
public static double minutesToDeg(double minutes) {
return minutes / MINUTES_PER_DEGREE;
}
/**
* Convert {@code degrees} and decimal {@code minutes} to decimal degrees.
*
* @param degrees to convert
* @param minutes to convert
* @return the equivalent number of decimal degrees
*/
public static double toDecimalDegrees(double degrees, double minutes) {
return (degrees < 0) ? (degrees - minutesToDeg(minutes))
: (degrees + minutesToDeg(minutes));
}
}