/** * 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 ) }; } }