/**
* Copyright (C) 2014 Cohesive Integrations, LLC (info@cohesiveintegrations.com)
*
* 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 net.di2e.ecdr.commons.util;
/**
* <p>
* Represents a point on the surface of a sphere. (The Earth is almost spherical.)
* </p>
*
* <p>
* To create an instance, call one of the static methods fromDegrees() or fromRadians().
* </p>
*
* <p>
* This code was originally published at <a href="http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates#Java">
* http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates#Java</a>.
* </p>
*
* @author Jan Philip Matuschek
* @version 22 September 2010
*/
final class GeoLocation {
private double radLat; // latitude in radians
private double radLon; // longitude in radians
private double degLat; // latitude in degrees
private double degLon; // longitude in degrees
private static final double MIN_LAT = Math.toRadians( -90d ); // -PI/2
private static final double MAX_LAT = Math.toRadians( 90d ); // PI/2
private static final double MIN_LON = Math.toRadians( -180d ); // -PI
private static final double MAX_LON = Math.toRadians( 180d ); // PI
private static final float EARTH_RADIUS_METERS = 6371010;
private GeoLocation() {
}
/**
* @param latitude
* the latitude, in degrees.
* @param longitude
* the longitude, in degrees.
*/
public static GeoLocation fromDegrees( double latitude, double longitude ) {
GeoLocation result = new GeoLocation();
result.radLat = Math.toRadians( latitude );
result.radLon = Math.toRadians( longitude );
result.degLat = latitude;
result.degLon = longitude;
result.checkBounds();
return result;
}
/**
* @param latitude
* the latitude, in radians.
* @param longitude
* the longitude, in radians.
*/
public static GeoLocation fromRadians( double latitude, double longitude ) {
GeoLocation result = new GeoLocation();
result.radLat = latitude;
result.radLon = longitude;
result.degLat = Math.toDegrees( latitude );
result.degLon = Math.toDegrees( longitude );
result.checkBounds();
return result;
}
private void checkBounds() {
if ( radLat < MIN_LAT || radLat > MAX_LAT || radLon < MIN_LON || radLon > MAX_LON ) {
throw new IllegalArgumentException();
}
}
/**
* @return the latitude, in degrees.
*/
public double getLatitudeInDegrees() {
return degLat;
}
/**
* @return the longitude, in degrees.
*/
public double getLongitudeInDegrees() {
return degLon;
}
/**
* @return the latitude, in radians.
*/
public double getLatitudeInRadians() {
return radLat;
}
/**
* @return the longitude, in radians.
*/
public double getLongitudeInRadians() {
return radLon;
}
@Override
public String toString() {
return "(" + degLat + "\u00B0, " + degLon + "\u00B0) = (" + radLat + " rad, " + radLon + " rad)";
}
/**
* Computes the great circle distance between this GeoLocation instance and the location argument.
*
* @param radius
* the radius of the sphere, e.g. the average radius for a spherical approximation of the figure of the
* Earth is approximately 6371.01 kilometers.
* @return the distance, measured in meters.
*/
public double distanceTo( GeoLocation location ) {
return Math.acos( Math.sin( radLat ) * Math.sin( location.radLat ) + Math.cos( radLat ) * Math.cos( location.radLat ) * Math.cos( radLon - location.radLon ) ) * EARTH_RADIUS_METERS;
}
/**
* <p>
* Computes the bounding coordinates of all points on the surface of a sphere that have a great circle distance to
* the point represented by this GeoLocation instance that is less or equal to the distance argument.
* </p>
* <p>
* For more information about the formulae used in this method visit <a
* href="http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates">
* http://JanMatuschek.de/LatitudeLongitudeBoundingCoordinates</a>.
* </p>
*
* @param distance
* the distance from the point represented by this GeoLocation instance. Must me measured in the same
* unit as the radius argument.
* @param radius
* the radius of the sphere, e.g. the average radius for a spherical approximation of the figure of the
* Earth is approximately 6371.01 kilometers.
* @return an array of two GeoLocation objects such that:
* <ul>
* <li>The latitude of any point within the specified distance is greater or equal to the latitude of the
* first array element and smaller or equal to the latitude of the second array element.</li>
* <li>If the longitude of the first array element is smaller or equal to the longitude of the second
* element, then the longitude of any point within the specified distance is greater or equal to the
* longitude of the first array element and smaller or equal to the longitude of the second array element.</li>
* <li>If the longitude of the first array element is greater than the longitude of the second element (this
* is the case if the 180th meridian is within the distance), then the longitude of any point within the
* specified distance is greater or equal to the longitude of the first array element <strong>or</strong>
* smaller or equal to the longitude of the second array element.</li>
* </ul>
*/
public GeoLocation[] boundingCoordinates( double distance ) {
if ( EARTH_RADIUS_METERS < 0d || distance < 0d ) {
throw new IllegalArgumentException();
}
// angular distance in radians on a great circle
double radDist = distance / EARTH_RADIUS_METERS;
double minLat = radLat - radDist;
double maxLat = radLat + radDist;
double minLon, maxLon;
if ( minLat > MIN_LAT && maxLat < MAX_LAT ) {
double deltaLon = Math.asin( Math.sin( radDist ) / Math.cos( radLat ) );
minLon = radLon - deltaLon;
if ( minLon < MIN_LON ) {
minLon += 2d * Math.PI;
}
maxLon = radLon + deltaLon;
if ( maxLon > MAX_LON ) {
maxLon -= 2d * Math.PI;
}
} else {
// a pole is within the distance
minLat = Math.max( minLat, MIN_LAT );
maxLat = Math.min( maxLat, MAX_LAT );
minLon = MIN_LON;
maxLon = MAX_LON;
}
return new GeoLocation[] { fromRadians( minLat, minLon ), fromRadians( maxLat, maxLon ) };
}
}