/** * Returns the <a href="http://en.wikipedia.org/wiki/Rhumb_line">rhumb line</a> bearing from the * current location to the GeoLocation passed in. * * @param location destination location * @return the bearing in degrees */ public double getRhumbLineBearing(GeoLocation location) { double dLon = Math.toRadians(location.getLongitude() - getLongitude()); double dPhi = Math.log( Math.tan(Math.toRadians(location.getLatitude()) / 2 + Math.PI / 4) / Math.tan(Math.toRadians(getLatitude()) / 2 + Math.PI / 4)); if (Math.abs(dLon) > Math.PI) dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); return Math.toDegrees(Math.atan2(dLon, dPhi)); }
/** * An implementation of the {@link java.lang.Object#clone()} method that creates a <a * href="http://en.wikipedia.org/wiki/Object_copy#Deep_copy">deep copy</a> of the object. * <b>Note:</b> If the {@link java.util.TimeZone} in the clone will be changed from the original, * it is critical that {@link net.sourceforge.zmanim.AstronomicalCalendar#getCalendar()}. {@link * java.util.Calendar#setTimeZone(TimeZone) setTimeZone(TimeZone)} is called after cloning in * order for the AstronomicalCalendar to output times in the expected offset. * * @see java.lang.Object#clone() * @since 1.1 */ public Object clone() { GeoLocation clone = null; try { clone = (GeoLocation) super.clone(); } catch (CloneNotSupportedException cnse) { // Required by the compiler. Should never be reached since we implement clone() } clone.timeZone = (TimeZone) getTimeZone().clone(); clone.locationName = getLocationName(); return clone; }
/** * Returns the <a href="http://en.wikipedia.org/wiki/Rhumb_line">rhumb line</a> distance from the * current location to the GeoLocation passed in. * * @param location the destination location * @return the distance in Meters */ public double getRhumbLineDistance(GeoLocation location) { double R = 6371; // earth's mean radius in km double dLat = Math.toRadians(location.getLatitude() - getLatitude()); double dLon = Math.toRadians(Math.abs(location.getLongitude() - getLongitude())); double dPhi = Math.log( Math.tan(Math.toRadians(location.getLongitude()) / 2 + Math.PI / 4) / Math.tan(Math.toRadians(getLatitude()) / 2 + Math.PI / 4)); double q = (Math.abs(dLat) > 1e-10) ? dLat / dPhi : Math.cos(Math.toRadians(getLatitude())); // if dLon over 180° take shorter rhumb across 180° meridian: if (dLon > Math.PI) dLon = 2 * Math.PI - dLon; double d = Math.sqrt(dLat * dLat + q * q * dLon * dLon); return d * R; }
/** * Calculate <a href="http://en.wikipedia.org/wiki/Great-circle_distance">geodesic distance</a> in * Meters between this Object and a second Object passed to this method using <a * href="http://en.wikipedia.org/wiki/Thaddeus_Vincenty">Thaddeus Vincenty's</a> inverse formula * See T Vincenty, "<a href="http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf">Direct and Inverse * Solutions of Geodesics on the Ellipsoid with application of nested equations</a>", Survey * Review, vol XXII no 176, 1975 * * @param location the destination location * @param formula This formula calculates initial bearing ({@link #INITIAL_BEARING}), final * bearing ( {@link #FINAL_BEARING}) and distance ({@link #DISTANCE}). * @return geodesic distance in Meters */ private double vincentyFormula(GeoLocation location, int formula) { double a = 6378137; double b = 6356752.3142; double f = 1 / 298.257223563; // WGS-84 ellipsiod double L = Math.toRadians(location.getLongitude() - getLongitude()); double U1 = Math.atan((1 - f) * Math.tan(Math.toRadians(getLatitude()))); double U2 = Math.atan((1 - f) * Math.tan(Math.toRadians(location.getLatitude()))); double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1); double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2); double lambda = L; double lambdaP = 2 * Math.PI; double iterLimit = 20; double sinLambda = 0; double cosLambda = 0; double sinSigma = 0; double cosSigma = 0; double sigma = 0; double sinAlpha = 0; double cosSqAlpha = 0; double cos2SigmaM = 0; double C; while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0) { sinLambda = Math.sin(lambda); cosLambda = Math.cos(lambda); sinSigma = Math.sqrt( (cosU2 * sinLambda) * (cosU2 * sinLambda) + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); if (sinSigma == 0) return 0; // co-incident points cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda; sigma = Math.atan2(sinSigma, cosSigma); sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; cosSqAlpha = 1 - sinAlpha * sinAlpha; cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha; if (Double.isNaN(cos2SigmaM)) cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6) C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)); lambdaP = lambda; lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM))); } if (iterLimit == 0) return Double.NaN; // formula failed to converge double uSq = cosSqAlpha * (a * a - b * b) / (b * b); double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))); double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))); double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM))); double distance = b * A * (sigma - deltaSigma); // initial bearing double fwdAz = Math.toDegrees(Math.atan2(cosU2 * sinLambda, cosU1 * sinU2 - sinU1 * cosU2 * cosLambda)); // final bearing double revAz = Math.toDegrees(Math.atan2(cosU1 * sinLambda, -sinU1 * cosU2 + cosU1 * sinU2 * cosLambda)); if (formula == DISTANCE) { return distance; } else if (formula == INITIAL_BEARING) { return fwdAz; } else if (formula == FINAL_BEARING) { return revAz; } else { // should never happpen return Double.NaN; } }