package com.github.pfichtner.jrunalyser.base.data;
public final class GeoUtil {
public interface BearingInfo {
Distance getDistance();
double getBearing();
}
public static class DefaultBearingInfo implements BearingInfo {
private final Distance distance;
private final double bearing;
public DefaultBearingInfo(Distance distance, double bearing) {
this.distance = distance;
this.bearing = bearing;
}
public Distance getDistance() {
return this.distance;
}
public double getBearing() {
return this.bearing;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(this.bearing);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result
+ ((this.distance == null) ? 0 : this.distance.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DefaultBearingInfo other = (DefaultBearingInfo) obj;
if (Double.doubleToLongBits(this.bearing) != Double
.doubleToLongBits(other.bearing))
return false;
if (this.distance == null) {
if (other.distance != null)
return false;
} else if (!this.distance.equals(other.distance))
return false;
return true;
}
@Override
public String toString() {
return "DefaultBearingInfo [distance=" + this.distance
+ ", bearing=" + this.bearing + "]";
}
}
/** degrees to radians **/
private static final double D2R = Math.PI / 180;
/** Radius of the earth in m */
private static final Distance EARTH_RADIUS = DefaultDistance.of(6378160,
DistanceUnit.METERS);
/** Radius of the earth in m */
private static final double EARTH_RADIUS_M = EARTH_RADIUS
.getValue(DistanceUnit.METERS);
private GeoUtil() {
super();
}
public static Distance calcDistance(Coordinate coord1, Coordinate coord2) {
return DefaultDistance.of(
calcMeters(coord1.getLatitude(), coord1.getLongitude(),
coord2.getLatitude(), coord2.getLongitude()),
DistanceUnit.METERS);
}
public static double calcMeters(double lat1, double lon1, double lat2,
double lon2) {
double dLat = (lat2 - lat1) * D2R;
double dLon = (lon2 - lon1) * D2R;
double sin = Math.sin(dLat / 2);
double cos = Math.cos(lat1 * D2R);
double a = sin * sin + cos * Math.cos(lat2 * D2R) * Math.sin(dLon / 2)
* Math.sin(dLon / 2);
return EARTH_RADIUS_M
* (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)));
}
public static double bearing(Coordinate coord1, Coordinate coord2) {
return bearing(coord1.getLatitude(), coord1.getLongitude(),
coord2.getLatitude(), coord2.getLongitude());
}
/**
* Computes the bearing in degrees between two points on Earth.
*
* @param lat1
* Latitude of the first point
* @param lon1
* Longitude of the first point
* @param lat2
* Latitude of the second point
* @param lon2
* Longitude of the second point
* @return Bearing between the two points in degrees. A value of 0 means due
* north.
*/
public static double bearing(double lat1, double lon1, double lat2,
double lon2) {
double lat1Rad = Math.toRadians(lat1);
double lat2Rad = Math.toRadians(lat2);
double deltaLonRad = Math.toRadians(lon2 - lon1);
double y = Math.sin(deltaLonRad) * Math.cos(lat2Rad);
double x = Math.cos(lat1Rad) * Math.sin(lat2Rad) - Math.sin(lat1Rad)
* Math.cos(lat2Rad) * Math.cos(deltaLonRad);
return (Math.toDegrees(Math.atan2(y, x)) + 360) % 360;
}
public static BearingInfo bearingInfo(Coordinate coord1, Coordinate coord2) {
return new DefaultBearingInfo(calcDistance(coord1, coord2), bearing(
coord1, coord2));
}
public static Coordinate project(BearingInfo bearing, Coordinate startCoords) {
double[] coords = project(
bearing.getDistance().getValue(DistanceUnit.METERS),
bearing.getBearing(), startCoords.getLatitude(),
startCoords.getLongitude());
return new DefaultCoordinate(coords[0], coords[1]);
}
/**
* Waypoint projection using haversine formula
*
* http://en.wikipedia.org/wiki/Haversine_formula
*
* See discussion here for further information:
* http://www.movable-type.co.uk/scripts/latlong.html
*
*/
private static double[] project(double meters, double bearing,
double startLat, double startLon) {
double distanceRad = meters / EARTH_RADIUS_M;
double bearingRad = Math.toRadians(bearing);
double startLatRad = Math.toRadians(startLat);
double startLonRad = Math.toRadians(startLon);
double endLat = Math.asin(Math.sin(startLatRad) * Math.cos(distanceRad)
+ Math.cos(startLatRad) * Math.sin(distanceRad)
* Math.cos(bearingRad));
double endLon = startLonRad
+ Math.atan2(
Math.sin(bearingRad) * Math.sin(distanceRad)
* Math.cos(startLatRad),
Math.cos(distanceRad) - Math.sin(startLatRad)
* Math.sin(endLat));
// Adjust projections crossing the 180th meridian:
double endLonDeg = Math.toDegrees(endLon);
if (endLonDeg > 180 || endLonDeg < -180) {
endLonDeg = endLonDeg % 360;
if (endLonDeg > 180) {
// Just in case we circle the earth more than once.
endLonDeg = endLonDeg - 360;
} else if (endLonDeg < -180) {
endLonDeg = endLonDeg + 360;
}
}
return new double[] { Math.toDegrees(endLat), endLonDeg };
}
}