/** * This uses the ‘haversine’ formula to calculate the great-circle distance between two * locations, more calculation but more accurate * * @param that * The other point to compute the distance to * @return The haversine distance * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Distance haversineDistanceTo(final Location that) { // convert to radians final double lat1 = this.getLatitude().asRadians(); final double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); final double deltaLat = lat2 - lat1; final double deltaLon = lon2 - lon1; final double hav = Math.pow(Math.sin(deltaLat / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(deltaLon / 2), 2); final double result = 2 * Math.atan2(Math.sqrt(hav), Math.sqrt(1 - hav)); return Distance.AVERAGE_EARTH_RADIUS.scaleBy(result); }
/** * The half-way point along a great-circle path between this and that point * * @param that * The other point to compute the midpoint between * @return The {@link Location} of the midpoint * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Location midPoint(final Location that) { // Convert to Radians final double lat1 = this.getLatitude().asRadians(); final double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); final double longitudeDelta = lon2 - lon1; final double xBearing = Math.cos(lat2) * Math.cos(longitudeDelta); final double yBearing = Math.cos(lat2) * Math.sin(longitudeDelta); final double pheta = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt( (Math.cos(lat1) + xBearing) * (Math.cos(lat1) + xBearing) + yBearing * yBearing)); double lambda = lon1 + Math.atan2(yBearing, Math.cos(lat1) + xBearing); // Normalize to -180/180 lambda = (lambda + FACTOR_OF_3 * Math.PI) % (2 * Math.PI) - Math.PI; return new Location(Latitude.radians(pheta), Longitude.radians(lambda)); }
/** * @return The approximate surface area of this polygon if it were projected onto the Earth. Not * valid if the polygon self-intersects, and/or overlaps itself. Uses "Some Algorithms * for Polygons on a Sphere" paper as reference. * @see "https://trs.jpl.nasa.gov/bitstream/handle/2014/41271/07-0286.pdf" */ @Override public Surface surfaceOnSphere() { double dm7 = 0L; final List<Location> locations = Lists.newArrayList(this.closedLoop()); if (locations.size() > 2) { double radians = 0L; for (int index = 0; index < locations.size() - 1; index++) { radians += (locations.get(index + 1).getLongitude().asRadians() - locations.get(index).getLongitude().asRadians()) * (2 + Math.sin(locations.get(index).getLatitude().asRadians()) + Math.sin(locations.get(index + 1).getLatitude().asRadians())); } radians = Math.abs(radians / 2.0); // Calculations are in Radians, convert to Degrees. dm7 = radians * ((double) Angle.DM7_PER_RADIAN * (double) Angle.DM7_PER_RADIAN); } return Surface.forDm7Squared(Math.round(dm7)); }
/** * This computes the initial heading (heading at the start point) of the segment on the surface * of earth between two locations * * @see "http://www.movable-type.co.uk/scripts/latlong.html" * @param that * The other point to compute the heading to * @return The heading between two points */ public Heading headingTo(final Location that) { if (this.equals(that)) { throw new CoreException("Cannot compute some heading when two points are the same."); } // convert to radians final double lat1 = this.getLatitude().asRadians(); final double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); final double deltaLon = lon2 - lon1; final double yAxis = Math.sin(deltaLon) * Math.cos(lat2); final double xAxis = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(deltaLon); return Heading.radians(Math.atan2(yAxis, xAxis)); }
/** * Midpoint along a Rhumb line between this point and that point * * @param that * The other point to compute the midpoint between * @return The {@link Location} of the loxodromic midpoint * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Location loxodromicMidPoint(final Location that) { // Convert to Radians final double lat1 = this.getLatitude().asRadians(); double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); // Crossing anti-meridian if (Math.abs(lon2 - lon1) > Math.PI) { lon1 += 2 * Math.PI; } final double pheta = (lat1 + lat2) / 2; final double phi1 = Math.tan(Math.PI / 4 + lat1 / 2); final double phi2 = Math.tan(Math.PI / 4 + lat2 / 2); final double phi3 = Math.tan(Math.PI / 4 + pheta / 2); double lambda = ((lon2 - lon1) * Math.log(phi3) + lon1 * Math.log(phi2) - lon2 * Math.log(phi1)) / Math.log(phi2 / phi1); // Normalize to -180/180 lambda = (lambda + FACTOR_OF_3 * Math.PI) % (2 * Math.PI) - Math.PI; return new Location(Latitude.radians(pheta), Longitude.radians(lambda)); }
/** * An equirectangular approximation distance between two locations, better performance but less * accurate. It is especially not able to handle distances that cross the antimeridian. It would * compute the distance all the way around the world instead. * * @param that * The other point to compute the distance to * @return The equirectangular distance * @see "http://www.movable-type.co.uk/scripts/latlong.html" */ public Distance equirectangularDistanceTo(final Location that) { // convert to radians final double lat1 = this.getLatitude().asRadians(); final double lon1 = this.getLongitude().asRadians(); final double lat2 = that.getLatitude().asRadians(); final double lon2 = that.getLongitude().asRadians(); final double xAxis = (lon2 - lon1) * Math.cos((lat1 + lat2) / 2); final double yAxis = lat2 - lat1; return Distance.AVERAGE_EARTH_RADIUS.scaleBy(Math.sqrt(xAxis * xAxis + yAxis * yAxis)); }
final double latitude1 = this.getLatitude().asRadians(); final double longitude1 = this.getLongitude().asRadians(); final double bearing = initialHeading.asRadians();