开发者

Moving a CLLocation by x meters

开发者 https://www.devze.com 2023-04-01 05:26 出处:网络
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?A conversion to Swift, taken from this answer:

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?


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.

My comments (Mar. 25, 2021):

The calculation used in this method is called solving the "direct geodesic problem", and this is discussed in C.F.F. Karney's article "Algorithms for geodesics", 2012. The code given above uses a technique that is less accurate than the algorithms presented in Karney's article.


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)
}


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;
}


There is a C function that is close to what you are asking but it takes a bearing and distance. It's in my UtilitiesGeo class on github. You would pass the latitude and longitude from your CLLocation to it and then create a new CLLocation from the resulting lat2 and lon2 that it returns:

/*-------------------------------------------------------------------------
* Given a starting lat/lon point on earth, distance (in meters)
* and bearing, calculates destination coordinates lat2/lon2.
*
* all params in degrees
*-------------------------------------------------------------------------*/
void destCoordsInDegrees(double lat1, double lon1,
                         double distanceMeters, double bearing,
                         double* lat2, double* lon2);

If you can't use that, take a look at the algorithms that it was derived from here and here and perhaps you can modify it or those sites might have something closer to your needs.


Strange that nobody thought of using MKCoordinateRegion from MapKit to calculate that automatically.

import MapKit

extension CLLocation {
    func movedBy(latitudinalMeters: CLLocationDistance, longitudinalMeters: CLLocationDistance) -> CLLocation {
        let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: abs(latitudinalMeters), longitudinalMeters: abs(longitudinalMeters))

        let latitudeDelta = region.span.latitudeDelta
        let longitudeDelta = region.span.longitudeDelta

        let latitudialSign = CLLocationDistance(latitudinalMeters.sign == .minus ? -1 : 1)
        let longitudialSign = CLLocationDistance(longitudinalMeters.sign == .minus ? -1 : 1)

        let newLatitude = coordinate.latitude + latitudialSign * latitudeDelta
        let newLongitude = coordinate.longitude + longitudialSign * longitudeDelta

        let newCoordinate = CLLocationCoordinate2D(latitude: newLatitude, longitude: newLongitude)

        let newLocation = CLLocation(coordinate: newCoordinate, altitude: altitude, horizontalAccuracy: horizontalAccuracy, verticalAccuracy: verticalAccuracy, course: course, speed: speed, timestamp: Date())

        return newLocation
    }
}


Slight adjustment to @CocoaChris answer: now a category on CLLocation, and using the built-in units.

#import <CoreLocation/CoreLocation.h>


@interface CLLocation (Movement)

- (CLLocation *)locationByMovingDistance:(double)distanceMeters withBearing:(CLLocationDirection)bearingDegrees;

@end


@implementation CLLocation (Movement)

- (CLLocation *)locationByMovingDistance:(double)distanceMeters withBearing:(CLLocationDirection)bearingDegrees
{
    const double distanceRadians = distanceMeters / (6372797.6); // earth radius in meters
    const double bearingRadians = bearingDegrees * M_PI / 180;

    float lat1 = self.coordinate.latitude * M_PI / 180;
    float lon1 = self.coordinate.longitude * M_PI / 180;

    float lat2 = asin(sin(lat1) * cos(distanceRadians) + cos(lat1) * sin(distanceRadians) * cos(bearingRadians));
    float lon2 = lon1 + atan2(sin(bearingRadians) * sin(distanceRadians) * cos(lat1),
                              cos(distanceRadians) - sin(lat1) * sin(lat2) );

    return [[CLLocation alloc] initWithLatitude:lat2 * 180 / M_PI
                                      longitude:lon2 * 180 / M_PI];
}

@end


Swift implementation using Measurement struct to do the conversions between degrees and radians.

class GPSLocation {

public class func degreesToRadians(degrees: Double) -> Double {
        return Measurement(value: degrees, unit: UnitAngle.degrees).converted(to: .radians).value
    }

    public class func radiansToDegrees(radians: Double) -> Double {
        return Measurement(value: radians, unit: UnitAngle.radians).converted(to: .degrees).value
    }

    public class func location(location: CLLocation, byMovingDistance distance: Double, withBearing bearingDegrees:CLLocationDirection) -> CLLocation {
        let distanceRadians: Double = distance / 6372797.6
        let bearingRadians: Double = GPSLocation.degreesToRadians(degrees: bearingDegrees)

        let lat1 = GPSLocation.degreesToRadians(degrees: location.coordinate.latitude)
        let lon1 = GPSLocation.degreesToRadians(degrees: location.coordinate.longitude)

        let lat2 = GPSLocation.radiansToDegrees(radians:asin(sin(lat1) * cos(distanceRadians) + cos(lat1) * sin(distanceRadians) * cos(bearingRadians)))
        let lon2 = GPSLocation.radiansToDegrees(radians:lon1 + atan2(sin(bearingRadians) * sin(distanceRadians * cos(lat1)), cos(distanceRadians) - sin(lat1) * sin(lat2)))

        return CLLocation(latitude: lat2, longitude: lon2)
    }

}


A simpler solution is to use MKMapPoints.

Convert your original coordinates, and any offset distances you need to MKMapPoints using this:

let coordinatesInMapPoints = MKMapPointForCoordinate(CLLocationCoordinate2D)
let distancesInMapPoints = yourDistanceInMeters * MKMapPointsPerMeterAtLatitude(CLLocationDegrees) // Do this for both x and y directions if needed.

Then make a new MKMapPoint by simply adding your offset distances to your original coordinates:

let newCoordinatesInMapPoints = MKMapPointMake(coordinatesInMapPoints.x + distancesInMapPoints, coordinatesInMapPoints.y)

Finally, convert the new coordinates from a MKMapPoint back to CLLocationCoordinate2D:

let newCoordinate = MKCoordinateForMapPoint(newCoordinatesInMapPoints)

No complex conversion calculations needed.


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)


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


I posted an updated answer to a measurement question, which includes an answer to this plotting one. Here: CLLocation Category for Calculating Bearing w/ Haversine function

0

精彩评论

暂无评论...
验证码 换一张
取 消