I have a CLLocation defined, and I\'d like to move that point x meters to the east and y meters to the south. How may I achieve that?
Swift 4.2 as a CGPoint extension
Derived from Peter O.'s solution
FloatingPoint extension: thanks to https://stackoverflow.com/a/29179878/2500428
extension FloatingPoint
{
var degreesToRadians: Self { return self * .pi / 180 }
var radiansToDegrees: Self { return self * 180 / .pi }
}
extension CGPoint
{
// NOTE: bearing is in radians
func locationWithBearing(bearing: Double, distanceMeters: Double) -> CGPoint
{
let distRadians = distanceMeters / (6372797.6) // earth radius in meters
let origLat = Double(self.y.degreesToRadians)
let origLon = Double(self.x.degreesToRadians)
let newLat = asin(sin(origLat) * cos(distRadians) + cos(origLat) * sin(distRadians) * cos(bearing))
let newLon = origLon + atan2(sin(bearing) * sin(distRadians) * cos(origLat), cos(distRadians) - sin(origLat) * sin(newLat))
return CGPoint(x: newLon.radiansToDegrees, y: newLat.radiansToDegrees)
}
}
Usage:
let loc = CGPoint(x: lon, y: lat)
let newLoc = loc.locationWithBearing(bearing: 90.degreesToRadians, distanceMeters: 500.0)
A conversion to Swift, taken from this answer:
func locationWithBearing(bearingRadians:Double, distanceMeters:Double, origin:CLLocationCoordinate2D) -> CLLocationCoordinate2D { let distRadians = distanceMeters / (6372797.6) // earth radius in meters let lat1 = origin.latitude * M_PI / 180 let lon1 = origin.longitude * M_PI / 180 let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(bearingRadians)) let lon2 = lon1 + atan2(sin(bearingRadians) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2)) return CLLocationCoordinate2D(latitude: lat2 * 180 / M_PI, longitude: lon2 * 180 / M_PI) }
Morgan Chen wrote this:
All of the math in this method is done in radians. At the start of the method, lon1 and lat1 are converted to radians for this purpose as well. Bearing is in radians too. Keep in mind this method takes into account the curvature of the Earth, which you don't really need to do for small distances.
Improved swift solution to Peters answer. Only correction is the bearing should be radian while calculation has been made.
func locationWithBearing(bearing:Double, distanceMeters:Double, origin:CLLocationCoordinate2D) -> CLLocationCoordinate2D {
let distRadians = distanceMeters / (6372797.6)
var rbearing = bearing * M_PI / 180.0
let lat1 = origin.latitude * M_PI / 180
let lon1 = origin.longitude * M_PI / 180
let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(rbearing))
let lon2 = lon1 + atan2(sin(rbearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))
return CLLocationCoordinate2D(latitude: lat2 * 180 / M_PI, longitude: lon2 * 180 / M_PI)
}
Swift 4
extension CLLocationCoordinate2D {
/// Get coordinate moved from current to `distanceMeters` meters with azimuth `azimuth` [0, Double.pi)
///
/// - Parameters:
/// - distanceMeters: the distance in meters
/// - azimuth: the azimuth (bearing)
/// - Returns: new coordinate
func shift(byDistance distanceMeters: Double, azimuth: Double) -> CLLocationCoordinate2D {
let bearing = azimuth
let origin = self
let distRadians = distanceMeters / (6372797.6) // earth radius in meters
let lat1 = origin.latitude * Double.pi / 180
let lon1 = origin.longitude * Double.pi / 180
let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(bearing))
let lon2 = lon1 + atan2(sin(bearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))
return CLLocationCoordinate2D(latitude: lat2 * 180 / Double.pi, longitude: lon2 * 180 / Double.pi)
}
}
Usage
let point: CLLocationCoordinate2D!
let north100 = point.shift(byDistance: 100, azimuth: 0) // 100m to North
let south100 = point.shift(byDistance: 100, azimuth: Double.pi) // 100m to South
Great post, here's the Obj-C wrapper for those who love copy/paste:
- (CLLocationCoordinate2D) locationWithBearing:(float)bearing distance:(float)distanceMeters fromLocation:(CLLocationCoordinate2D)origin {
CLLocationCoordinate2D target;
const double distRadians = distanceMeters / (6372797.6); // earth radius in meters
float lat1 = origin.latitude * M_PI / 180;
float lon1 = origin.longitude * M_PI / 180;
float lat2 = asin( sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(bearing));
float lon2 = lon1 + atan2( sin(bearing) * sin(distRadians) * cos(lat1),
cos(distRadians) - sin(lat1) * sin(lat2) );
target.latitude = lat2 * 180 / M_PI;
target.longitude = lon2 * 180 / M_PI; // no need to normalize a heading in degrees to be within -179.999999° to 180.00000°
return target;
}