Calculating bearing between two CLLocation points in Swift [duplicate]
Asked Answered
R

2

34

I'm trying to calculate a bearing between two CLLocation points in swift-only code. I've run into some difficulty and was assuming this is a pretty simple function. Stack overflow didn't seem to have anything listed.

func d2r(degrees : Double) -> Double {
    return degrees * M_PI / 180.0
}

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


func getBearing(fromLoc : CLLocation, toLoc : CLLocation) {

    let fLat = d2r(fromLoc.coordinate.latitude)
    let fLng = d2r(fromLoc.coordinate.longitude)
    let tLat = d2r(toLoc.coordinate.latitude)
    let tLng = d2r(toLoc.coordinate.longitude)

    var a = CGFloat(sin(fLng-tLng)*cos(tLat));
    var b = CGFloat(cos(fLat)*sin(tLat)-sin(fLat)*cos(tLat)*cos(fLng-tLng))

    return atan2(a,b)
}

I'm getting an error with my atan2 call about lvalue cgfloat or something...

Rainier answered 18/11, 2014 at 15:34 Comment(0)
B
61

Here is an Objective-C solution

which can easily be translated to Swift:

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

func getBearingBetweenTwoPoints1(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)
}

The result type is Double because that is how all location coordinates are stored (CLLocationDegrees is a type alias for Double).

Balder answered 18/11, 2014 at 21:31 Comment(10)
Why can't we calculate this by subtracting the course value on CLLocation object? But even if I try this, just imagine a case where user goes from 0 to 359 degrees, in that case, what if the actual angle of change was 1 degree not 359 degrees? How can I eliminate this??Neldanelia
@Nil: The course value is the direction in which the device is currently moving. This code is about the direction from one given point to another point and unrelated to the devices location or moving. (So the parameters could better be CLLocationCoordinate2D instead of CLLocation. I'll update that later.)Balder
ok. But can you help with my other concern, i.e the 0-359 degreee case.Neldanelia
@Nil: What exactly is the problem? The above code should return a value between -180 and +180 degrees.Balder
Lets say the user is driving on a 4 lane road. In 1st lane he is travelling in the direction of 0 degree towards north. Now the road gets a small curve towards left and user steers the vehicle so that he is now travelling to 345 degree towards north. Now i need the angle of that curve which should be 15 degrees and not 345 degrees. Simple subtraction will not help me here...Neldanelia
@Nil: That is quite unrelated to this Q&A. But you can always normalize the difference by adding or subtracting 360 to be in the range -180...+180.Balder
I agree. But consider these two data points. In this case the bearing is just around 1 degrees which is correctly found by your logic. But consider a round about where you take a 3th exit. So you completed an angle of 270 degrees. But your answer will say 90 degrees. Am I correct?Neldanelia
@MartinR Any suggestions on this issue. I tried the above mentioned code to calculate direction.Freaky
Is there a reason why this test will fail? let location1 = CLLocation(latitude: 50, longitude: -120.1) let location2 = CLLocation(latitude: 50, longitude: -120) let expectedDirection: CLLocationDirection = 90 let direction = LocationService.getDirectionFromTwoPoints(point1: location1, point2: location2) XCTAssertEqual(expectedDirection, direction)Evita
cos(lat2) is called twice, could be extracted into a precomputed variable.Laurielaurier
D
6

This isn't exactly accurate, but you're probably looking for something along the lines of:

func XXRadiansToDegrees(radians: Double) -> Double {
    return radians * 180.0 / M_PI
}

func getBearingBetweenTwoPoints(point1 : CLLocation, point2 : CLLocation) -> Double {
    // Returns a float with the angle between the two points
    let x = point1.coordinate.longitude - point2.coordinate.longitude
    let y = point1.coordinate.latitude - point2.coordinate.latitude

    return fmod(XXRadiansToDegrees(atan2(y, x)), 360.0) + 90.0
}

I appropriated the code from this NSHipster article that goes into more detail about what's wrong with it. The basic issue is that it's using the coordinates as though the world is flat (which it isn't, right?). Mattt's article can show you how to get the real directions using MKMapPoints instead of CLLocations.

Dorm answered 18/11, 2014 at 15:53 Comment(7)
Why can't we calculate this by subtracting the course value on CLLocation object? But even if I try this, just imagine a case where user goes from 0 to 359 degrees, in that case, what if the actual angle of change was 1 degree not 359 degrees? How can I eliminate this??Neldanelia
@Guy Kogus. I am working marker position on map but its not working . Can u guide me how to achieve this.Terrific
Sorry Uma, I've never actually had to use it myself so not sure I can be much help.Dorm
@Nil Did you find a solution for the issue which you have mentioned in your comment.Freaky
@AbinBaby In my case I was calculating the angle of difference between the consecutive location coordinates. So I calculated bothways and used the smaller one assuming in just 2 corrdinates, user can't change 359 degressNeldanelia
Scale of long and lat are very different. Say, for example, at the north pole.Locale
You probably want to use the 'haversine' algorithm. which accounts for the earth being a sphere. You get two bearings, the initial one, and the final one (yes it changes). For short distance the initial bearing works fine. See movable-type.co.uk/scripts/latlong.html for an excellent discussion. I wrote it in Java, and now will be converting to Swift.Bottomry

© 2022 - 2024 — McMap. All rights reserved.