/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.spatial.impl; import org.hibernate.search.spatial.Coordinates; /** * Normalized latitude,longitude holder (in [-90;90],[-180,180]) with distance and destination computations methods * * @author Nicolas Helleringer * @author Mathieu Perez */ public final class Point implements Coordinates { private final double latitude; private final double longitude; /** * @param latitude in degrees * @param longitude in degrees * @return a point with coordinates given in degrees */ public static Point fromDegrees(double latitude, double longitude) { return new Point( normalizeLatitude( latitude ), normalizeLongitude( longitude ) ); } /** * @param center the coordinates for the Point to be created * @return a Point from given Coordinates. Same instance when given a Point. */ public static Point fromCoordinates(Coordinates center) { if ( center instanceof Point ) { return (Point) center; } else { return Point.fromDegrees( center.getLatitude(), center.getLongitude() ); } } /** * @param latitude in degrees * @param longitude in degrees * @return a point with coordinates given in degrees */ public static Point fromDegreesInclusive(double latitude, double longitude) { return new Point( normalizeLatitude( latitude ), normalizeLongitudeInclusive( longitude ) ); } /** * @param longitude in degrees * @return longitude normalized in ]-180;+180] */ public static double normalizeLongitude(double longitude) { if ( longitude == ( -GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ) ) { return GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ; } else { return normalizeLongitudeInclusive( longitude ); } } /** * @param longitude in degrees * @return longitude normalized in [-180;+180] */ public static double normalizeLongitudeInclusive(double longitude) { if ( (longitude < -( GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ) ) || (longitude > ( GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ) ) ) { double _longitude; // shift 180 and normalize full circle turn _longitude = ( ( longitude + ( GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ) ) % GeometricConstants.WHOLE_CIRCLE_DEGREE_RANGE ); // as Java % is not a math modulus we may have negative numbers so the unshift is sign dependant if ( _longitude < 0 ) { _longitude = _longitude + ( GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ); } else { _longitude = _longitude - ( GeometricConstants.LONGITUDE_DEGREE_RANGE / 2 ); } return _longitude; } else { return longitude; } } /** * @param latitude in degrees * @return latitude normalized in [-90;+90] */ public static double normalizeLatitude(double latitude) { if ( latitude > GeometricConstants.LATITUDE_DEGREE_MAX || latitude < GeometricConstants.LATITUDE_DEGREE_MIN ) { // shift 90, normalize full circle turn and 'symmetry' on the lat axis with abs double _latitude = Math.abs( ( latitude + ( GeometricConstants.LATITUDE_DEGREE_RANGE / 2 ) ) % ( GeometricConstants.WHOLE_CIRCLE_DEGREE_RANGE ) ); // Push 2nd and 3rd quadran in 1st and 4th by 'symmetry' if ( _latitude > GeometricConstants.LATITUDE_DEGREE_RANGE ) { _latitude = GeometricConstants.WHOLE_CIRCLE_DEGREE_RANGE - _latitude; } // unshift _latitude = _latitude - ( GeometricConstants.LATITUDE_DEGREE_RANGE / 2 ); return _latitude; } else { return latitude; } } /** * @param latitude in radians * @param longitude in radians * @return a point with coordinates given in radians */ public static Point fromRadians(double latitude, double longitude) { return fromDegrees( latitude * GeometricConstants.TO_DEGREES_RATIO, longitude * GeometricConstants.TO_DEGREES_RATIO ); } /** * @param latitude in degrees * @param longitude in degrees */ private Point(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; } /** * Calculate end of travel point * * @param distance to travel * @param heading of travel in decimal degree * @return arrival point * @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Compute destination</a> */ public Point computeDestination(double distance, double heading) { double headingRadian = heading * GeometricConstants.TO_RADIANS_RATIO; double destinationLatitudeRadian = Math.asin( Math.sin( getLatitudeRad() ) * Math.cos( distance / GeometricConstants.EARTH_MEAN_RADIUS_KM ) + Math.cos( getLatitudeRad() ) * Math.sin( distance / GeometricConstants.EARTH_MEAN_RADIUS_KM ) * Math.cos( headingRadian ) ); double destinationLongitudeRadian = getLongitudeRad() + Math.atan2( Math.sin( headingRadian ) * Math.sin( distance / GeometricConstants.EARTH_MEAN_RADIUS_KM ) * Math.cos( getLatitudeRad() ), Math.cos( distance / GeometricConstants.EARTH_MEAN_RADIUS_KM ) - Math.sin( getLatitudeRad() ) * Math.sin( destinationLatitudeRadian ) ); return fromRadians( destinationLatitudeRadian, destinationLongitudeRadian ); } /** * Compute distance between two points * * @param other a {@link org.hibernate.search.spatial.impl.Point} object. * @return the distance between points * @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Distance haversine formula</a> */ public double getDistanceTo(Point other) { return getDistanceTo( other.getLatitude(), other.getLongitude() ); } /** * Compute distance point and other location given by its latitude and longitude in decimal degrees * * @param latitude in decimal degrees * @param longitude in decimal degrees * @return the distance between the points * @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Distance haversine formula</a> */ public double getDistanceTo(final double latitude,final double longitude) { double destinationLatitudeRadians = normalizeLatitude( latitude ) * GeometricConstants.TO_RADIANS_RATIO; double destinationLongitudeRadians = normalizeLongitude( longitude ) * GeometricConstants.TO_RADIANS_RATIO; final double dLat = ( destinationLatitudeRadians - getLatitudeRad() ) / 2.0d; final double dLon = ( destinationLongitudeRadians - getLongitudeRad() ) / 2.0d; final double a = Math.pow( Math.sin( dLat ), 2 ) + Math.pow( Math.sin( dLon ), 2 ) * Math.cos( getLatitudeRad() ) * Math.cos( destinationLatitudeRadians ); final double c = 2.0d * Math.atan2( Math.sqrt( a ), Math.sqrt( 1.0d - a ) ); return c * GeometricConstants.EARTH_MEAN_RADIUS_KM; } @Override public Double getLatitude() { return latitude; } @Override public Double getLongitude() { return longitude; } public double getLatitudeRad() { return latitude * GeometricConstants.TO_RADIANS_RATIO; } public double getLongitudeRad() { return longitude * GeometricConstants.TO_RADIANS_RATIO; } @Override public int hashCode() { return 31 * Double.hashCode( latitude ) + Double.hashCode( longitude ); } @Override public boolean equals(Object obj) { if ( obj == this ) { return true; } if ( obj instanceof Point ) { Point other = (Point) obj; return latitude == other.latitude && longitude == other.longitude; } return false; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append( "Point" ); sb.append( "{latitude=" ).append( latitude ); sb.append( ", longitude=" ).append( longitude ); sb.append( '}' ); return sb.toString(); } }