CLLocation Category for Calculating Bearing w/ Haversine function
Asked Answered
C

8

31

I'm trying to write a category for CLLocation to return the bearing to another CLLocation.

I believe I'm doing something wrong with the formula (calculous is not my strong suit). The returned bearing is always off.

I've been looking at this question and tried applying the changes that were accepted as a correct answer and the webpage it references:

Calculating bearing between two CLLocationCoordinate2Ds

http://www.movable-type.co.uk/scripts/latlong.html

Thanks for any pointers. I've tried incorporating the feedback from that other question and I'm still just not getting something.

Thanks

Here's my category -

----- CLLocation+Bearing.h

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>


@interface CLLocation (Bearing)

-(double) bearingToLocation:(CLLocation *) destinationLocation;
-(NSString *) compassOrdinalToLocation:(CLLocation *) nwEndPoint;

@end

---------CLLocation+Bearing.m

#import "CLLocation+Bearing.h"

double DegreesToRadians(double degrees) {return degrees * M_PI / 180;};
double RadiansToDegrees(double radians) {return radians * 180/M_PI;};


@implementation CLLocation (Bearing)

-(double) bearingToLocation:(CLLocation *) destinationLocation {

 double lat1 = DegreesToRadians(self.coordinate.latitude);
 double lon1 = DegreesToRadians(self.coordinate.longitude);

 double lat2 = DegreesToRadians(destinationLocation.coordinate.latitude);
 double lon2 = DegreesToRadians(destinationLocation.coordinate.longitude);

 double dLon = lon2 - lon1;

 double y = sin(dLon) * cos(lat2);
 double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
 double radiansBearing = atan2(y, x);

 return RadiansToDegrees(radiansBearing);
}
Coaler answered 13/10, 2010 at 16:38 Comment(2)
Why are you converting the lat and lon values from degrees to radians? Does the Haversine function require that conversion?Painful
To answer my own question, Yes. The Haversine function reqqires that conversion as shown here: movable-type.co.uk/scripts/latlong.htmlPainful
B
25

Your code seems fine to me. Nothing wrong with the calculous. You don't specify how far off your results are, but you might try tweaking your radian/degrees converters to this:

double DegreesToRadians(double degrees) {return degrees * M_PI / 180.0;};
double RadiansToDegrees(double radians) {return radians * 180.0/M_PI;};

If you are getting negative bearings, add 2*M_PI to the final result in radiansBearing (or 360 if you do it after converting to degrees). atan2 returns the result in the range -M_PI to M_PI (-180 to 180 degrees), so you might want to convert it to compass bearings, using something like the following code

if(radiansBearing < 0.0)
    radiansBearing += 2*M_PI;
Bifacial answered 13/10, 2010 at 19:17 Comment(1)
Thank you so much! I should have given some expected and actual results but you spotted the issue anyway. I was just not handling the negative degrees and converting to compass degrees. Good point specifying the 180's as floats as well. Everything working perfectly now.Coaler
P
8

This is a porting in Swift of the Category at the beginning:

import Foundation
import CoreLocation
public extension CLLocation{

    func DegreesToRadians(_ degrees: Double ) -> Double {
        return degrees * M_PI / 180
    }

    func RadiansToDegrees(_ radians: Double) -> Double {
        return radians * 180 / M_PI
    }


    func bearingToLocationRadian(_ destinationLocation:CLLocation) -> Double {

        let lat1 = DegreesToRadians(self.coordinate.latitude)
        let lon1 = DegreesToRadians(self.coordinate.longitude)

        let lat2 = DegreesToRadians(destinationLocation.coordinate.latitude);
        let lon2 = DegreesToRadians(destinationLocation.coordinate.longitude);

        let dLon = lon2 - lon1

        let y = sin(dLon) * cos(lat2);
        let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
        let radiansBearing = atan2(y, x)

        return radiansBearing
    }

    func bearingToLocationDegrees(destinationLocation:CLLocation) -> Double{
        return   RadiansToDegrees(bearingToLocationRadian(destinationLocation))
    }
}
Penland answered 4/8, 2015 at 19:17 Comment(0)
A
4

Here is another implementation

public func bearingBetweenTwoPoints(#lat1 : Double, #lon1 : Double, #lat2 : Double, #lon2: Double) -> Double {

func DegreesToRadians (value:Double) -> Double {
    return value * M_PI / 180.0
}

func RadiansToDegrees (value:Double) -> Double {
    return value * 180.0 / M_PI
}

let y = sin(lon2-lon1) * cos(lat2)
let x = (cos(lat1) * sin(lat2)) - (sin(lat1) * cos(lat2) * cos(lat2-lon1))

let degrees = RadiansToDegrees(atan2(y,x))

let ret = (degrees + 360) % 360

return ret;

}
Arvind answered 1/5, 2015 at 9:51 Comment(5)
I do not understand it these algorithms how may sin be applied to a coordinate in degrees rather than in radians.Penland
williams.best.vwh.net/avform.htm#Crs its the way its done in aviation i believe.Arvind
My problem is that whatever algorithm I choose gives a different result. At the bottom a Swift porting of the afore written category.Penland
And in fact it first translate the degrees to radians. Yet I am in the black how even taking directly the degrees gives similar results.Penland
Every single algorithm is going to give you a different result - due to the different nature of trying to do linear geometry on an elipsoid. All these algorithms are just approximations and use different projections. Cosine correction is good for small distances to do an xy approximation etc. But other algorithms are better for different distances.Arvind
S
3

This is an another CLLocation extension can be used in Swift 3 and Swift 4

public extension CLLocation {

    func degreesToRadians(degrees: Double) -> Double {
        return degrees * .pi / 180.0
    }

    func radiansToDegrees(radians: Double) -> Double {
        return radians * 180.0 / .pi
    }

    func getBearingBetweenTwoPoints(point1: CLLocation, point2: CLLocation) -> Double {
        let lat1 = degreesToRadians(degrees: point1.coordinate.latitude)
        let lon1 = degreesToRadians(degrees: point1.coordinate.longitude)

        let lat2 = degreesToRadians(degrees: point2.coordinate.latitude)
        let lon2 = degreesToRadians(degrees: point2.coordinate.longitude)

        let dLon = lon2 - lon1

        let y = sin(dLon) * cos(lat2)
        let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
        let radiansBearing = atan2(y, x)

        return radiansToDegrees(radians: radiansBearing)
    }

}
Seddon answered 11/5, 2017 at 11:48 Comment(1)
I tried this out and I'm "in the neighborhood", but the degrees are off by like 3 or 4, which is pretty hefty. I think the math on this might be off a bit.Gerkman
S
3

Working Swift 3 and 4

Tried so many versions and this one finally gives correct values!

extension CLLocation {


    func getRadiansFrom(degrees: Double ) -> Double {

        return degrees * .pi / 180

    }

    func getDegreesFrom(radians: Double) -> Double {

        return radians * 180 / .pi

    }


    func bearingRadianTo(location: CLLocation) -> Double {

        let lat1 = self.getRadiansFrom(degrees: self.coordinate.latitude)
        let lon1 = self.getRadiansFrom(degrees: self.coordinate.longitude)

        let lat2 = self.getRadiansFrom(degrees: location.coordinate.latitude)
        let lon2 = self.getRadiansFrom(degrees: location.coordinate.longitude)

        let dLon = lon2 - lon1

        let y = sin(dLon) * cos(lat2)
        let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)

        var radiansBearing = atan2(y, x)

        if radiansBearing < 0.0 {

            radiansBearing += 2 * .pi

        }


        return radiansBearing
    }

    func bearingDegreesTo(location: CLLocation) -> Double {

        return self.getDegreesFrom(radians: self.bearingRadianTo(location: location))

    }


}

Usage:

let degrees = location1.bearingDegreesTo(location: location2)
Supererogate answered 30/6, 2017 at 18:13 Comment(0)
A
1

I use the Law of Cosines in Swift. It runs faster than Haversine and its result is extremely similar. Variation of 1 metre on huge distances.

Why do I use the Law of Cosines:

  • Run fast (because there is no sqrt functions)
  • Precise enough unless you do some astronomy
  • Perfect for a background task

func calculateDistance(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) -> Double {

    let π = M_PI
    let degToRad: Double = π/180
    let earthRadius: Double = 6372797.560856

    // Law of Cosines formula
    // d = r . arc cos (sin 𝜑A sin 𝜑B + cos 𝜑A cos 𝜑B cos(𝜆B - 𝜆A) )

    let 𝜑A = from.latitude * degToRad
    let 𝜑B = to.latitude * degToRad
    let 𝜆A = from.longitude * degToRad
    let 𝜆B = to.longitude * degToRad

    let angularDistance = acos(sin(𝜑A) * sin(𝜑B) + cos(𝜑A) * cos(𝜑B) * cos(𝜆B - 𝜆A) )
    let distance = earthRadius * angularDistance

    return distance

}
Alternate answered 20/2, 2016 at 6:13 Comment(0)
P
1

Worth mentioning that if you are using Google map GMSMapView, there's an out-of-the-box solution using the GMSGeometryHeading method:

GMSGeometryHeading(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D)

Returns the initial heading (degrees clockwise of North) at from of the shortest path to to.

Perpend answered 20/4, 2020 at 16:31 Comment(0)
C
1

Implemented this in Swift 5. Focus is on accuracy, not speed, but it runs in real time np.

let earthRadius: Double = 6372456.7
let degToRad: Double = .pi / 180.0
let radToDeg: Double = 180.0 / .pi

func calcOffset(_ coord0: CLLocationCoordinate2D,
                _ coord1: CLLocationCoordinate2D) -> (Double, Double) {
    let lat0: Double = coord0.latitude * degToRad
    let lat1: Double = coord1.latitude * degToRad
    let lon0: Double = coord0.longitude * degToRad
    let lon1: Double = coord1.longitude * degToRad
    let dLat: Double = lat1 - lat0
    let dLon: Double = lon1 - lon0
    let y: Double = cos(lat1) * sin(dLon)
    let x: Double = cos(lat0) * sin(lat1) - sin(lat0) * cos(lat1) * cos(dLon)
    let t: Double = atan2(y, x)
    let bearing: Double = t * radToDeg

    let a: Double = pow(sin(dLat * 0.5), 2.0) + cos(lat0) * cos(lat1) * pow(sin(dLon * 0.5), 2.0)
    let c: Double = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
    let distance: Double = c * earthRadius

    return (distance, bearing)
}

func translateCoord(_ coord: CLLocationCoordinate2D,
                    _ distance: Double,
                    _ bearing: Double) -> CLLocationCoordinate2D {
    let d: Double = distance / earthRadius
    let t: Double = bearing * degToRad

    let lat0: Double = coord.latitude * degToRad
    let lon0: Double = coord.longitude * degToRad
    let lat1: Double = asin(sin(lat0) * cos(d) + cos(lat0) * sin(d) * cos(t))
    let lon1: Double = lon0 + atan2(sin(t) * sin(d) * cos(lat0), cos(d) - sin(lat0) * sin(lat1))

    let lat: Double = lat1 * radToDeg
    let lon: Double = lon1 * radToDeg

    let c: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: lat,
                                                           longitude: lon)
    return c
}

I found that Haversine nailed the distance versus CLLocation's distance method, but didn't provide a bearing ready-to-use with CL. So I'm not using it for the bearing. This gives the most accurate measurement I've encountered from all the math I've tried. The translateCoord method will also precisely plot a new point given an origin, distance in meters, and a bearing in degrees.

Chemurgy answered 23/4, 2020 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.