Calculating bearing between two CLLocation points in Swift

23,264

Solution 1

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

Solution 2

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.

Share:
23,264
Jeef
Author by

Jeef

Software Dev

Updated on June 08, 2020

Comments

  • Jeef
    Jeef almost 4 years

    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...

  • nr5
    nr5 over 7 years
    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??
  • nr5
    nr5 over 7 years
    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??
  • Martin R
    Martin R over 7 years
    @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.)
  • nr5
    nr5 over 7 years
    ok. But can you help with my other concern, i.e the 0-359 degreee case.
  • Martin R
    Martin R about 7 years
    @Nil: What exactly is the problem? The above code should return a value between -180 and +180 degrees.
  • nr5
    nr5 about 7 years
    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...
  • Martin R
    Martin R about 7 years
    @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.
  • nr5
    nr5 about 7 years
    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?
  • Uma Madhavi
    Uma Madhavi about 7 years
    @Guy Kogus. I am working marker position on map but its not working . Can u guide me how to achieve this.
  • Guy Kogus
    Guy Kogus about 7 years
    Sorry Uma, I've never actually had to use it myself so not sure I can be much help.
  • Abin Baby
    Abin Baby about 6 years
    @Nil Did you find a solution for the issue which you have mentioned in your comment.
  • Abin Baby
    Abin Baby about 6 years
    @MartinR Any suggestions on this issue. I tried the above mentioned code to calculate direction.
  • nr5
    nr5 about 6 years
    @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 degress
  • Borys T
    Borys T over 4 years
    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)
  • William J Bagshaw
    William J Bagshaw almost 4 years
    Scale of long and lat are very different. Say, for example, at the north pole.
  • Desmond Hume
    Desmond Hume almost 4 years
    cos(lat2) is called twice, could be extracted into a precomputed variable.
  • Dave Hubbard
    Dave Hubbard over 2 years
    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.