/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* JBoss, Home of Professional Open Source
* Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
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 <nicolas.helleringer@novacodex.net>
* @author Mathieu Perez <mathieu.perez@novacodex.net>
*/
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 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.
* @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
* @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Distance haversine formula</a>
*/
public final 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 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();
}
}