Calculating bearing between two CLLocation points in Swift [duplicate] - ios

This question already has answers here:
CLLocation Category for Calculating Bearing w/ Haversine function
(8 answers)
Closed 8 years ago.
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...

Here is an Objective-C solution
CLLocation Category for Calculating Bearing w/ Haversine function
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).

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.

Related

can't point to right location in compass Swift ios

I am developing a compass that can point from my current location to other location, but after implementing it, I noticed that it always points to the same direction every time of choosing a new target location to go or to point.
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let heading = newHeading.magneticHeading
let headingR = Measurement(value: newHeading.trueHeading, unit: UnitAngle.degrees).converted(to: .radians).value
// To know if the Data that our device is correct, The "horizontalAccuracy" value should be grater then ZERO for valid GPS coordinate Data
if heading < 0 {return}
// Get the heading(direction)
if(heading == -1){
print("no location")
}
self.directionAngel = Double(self.getBearingBetweenTwoPoints1(latDestination: self.desiredLatit,lonDestination: self.desiredLongit))
let north = -1 * heading.degreesToRadians
UIView.animate(withDuration: 1) {
self.imageView.transform = CGAffineTransform(rotationAngle: CGFloat(north))
self.directionArrowView.transform = CGAffineTransform(rotationAngle: CGFloat(self.directionAngel.degreesToRadians - headingR))
}
self.updateHeading(heading: heading)
}
private func getBearingBetweenTwoPoints1(latDestination:Double,lonDestination:Double) -> Double {
let lat1 = getMyLocation().latitude.degreesToRadians
let lon1 = getMyLocation().longitude.degreesToRadians
let lat2 = latDestination.degreesToRadians
let lon2 = lonDestination.degreesToRadians
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 * Double.pi
}
return radiansBearing
}
let locationManager: CLLocationManager = {
$0.requestAlwaysAuthorization()
$0.requestWhenInUseAuthorization()
$0.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
$0.startUpdatingLocation()
$0.startUpdatingHeading()
return $0
}(CLLocationManager())

Converting two coordinate points to distance in west-east-facing and north-south-facing direction

I'm trying to find out how to calculate the "west-east-facing" distance and the "north-south-facing" distance between two points respectively given the coordinates of the two points (with latitude and longitude), in order to find out the degree in which the line differs from the northern direction.
In short I need to calculate x and y in meters in order to get the degree of alpha, when x and y are given by longitude and latitude.
I know CLLocation has a function to calculate the distance between two points:
distance(from location: CLLocation) -> CLLocationDistance
and I tried to open the source code of that function in order to figure out how to separate the two components but I did not find out how to open that code.
You can use this
func getDistanceAndAngle(positionA: CLLocation, positionB: CLLocation) -> (Float, Float, Float){
let distanceInMeters = Float(positionA.distance(from: positionB)) // result is in meters
print(distanceInMeters)
//search for the degree
let angle = bearingFromLocation(fromLocation: positionA.coordinate, toLocation: positionB.coordinate)
print("ANGLE", angle)
let xDistance = abs(distanceInMeters * cos(DegreesToRadians(degrees: angle)))
let yDistance = abs(distanceInMeters * sin(DegreesToRadians(degrees: angle)))
return (xDistance,yDistance,angle)
}
func bearingFromLocation(fromLocation:CLLocationCoordinate2D, toLocation: CLLocationCoordinate2D)-> Float{
let lat1 = DegreesToRadians(degrees: Float(fromLocation.latitude))
let lon1 = DegreesToRadians(degrees: Float(fromLocation.longitude))
let lat2 = DegreesToRadians(degrees: Float(toLocation.latitude))
let lon2 = DegreesToRadians(degrees: Float(toLocation.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)
print("radian", radiansBearing)
let degreesBearing = RadiansToDegrees(radians: radiansBearing)
print("deg", degreesBearing)
if (degreesBearing >= 0) {
return degreesBearing
} else {
return degreesBearing + 360.0
}
}
func DegreesToRadians(degrees: Float)->Float {return degrees * Float.pi / 180.0}
func RadiansToDegrees(radians: Float)->Float {return radians * 180.0/Float.pi}
notes:
the angle is from north to east
so north is 0 degree and east is 90 degree.
the X and Y is always positive. So if you want to make it negative to the left and down, you can try to put use degree to make it right.
You can convert point1 and point2 to MKMapPoint. It's gives you coordinates which is projected map in 2D plane. Then you can eaisily get difference of x's and y's.. And using simple math, you can calculate the alpha value. You can initialize MKMapPoint with CLLocationCoordinate2D.
https://developer.apple.com/documentation/mapkit/mkmappoint
Assuming we have:
let locationA = CLLocation(..)
let locationB = CLLocation(..)
I would probably still want to use Apple's provided functions and create new locations that only differ in latitude or longitude. Something like:
let latitudeA = CLLocation(latitude: locationA.latitude, longitude: locationA.longitude)
// notice I use latitude from B but KEEP longitude from A to keep them parallel)
let latitudeB = CLLocation(latitude: locationB.latitude, longitude: locationA.longitude)
let northSouthDistance = latitudeA.distance(from: latitudeB)
etc

Calculate new coordinates with starting position and distance

I am trying to get the coordinates of a point, that is on a set distance from a starting position, but the end result is wrong.
First, I calculate the angle between the starting position and the desired destination:
private func calculateAngleBetweenLocations(currentLocation: CLLocationCoordinate2D, targetLocation: CLLocationCoordinate2D) -> Double {
let fLat = self.degreesToRadians(currentLocation.latitude);
let fLng = self.degreesToRadians(currentLocation.longitude);
let tLat = self.degreesToRadians(targetLocation.latitude);
let tLng = self.degreesToRadians(targetLocation.longitude);
let deltaLng = tLng - fLng
let y = sin(deltaLng) * cos(tLat)
let x = cos(fLat) * sin(tLat) - sin(fLat) * cos(tLat) * cos(deltaLng)
let bearing = atan2(y, x)
return self.radiansToDegrees(bearing)
}
Then, I calculate the new point, given a distance:
private func coordinatesForMovement(endLocation: CLLocationCoordinate2D, distance: Double) -> CLLocationCoordinate2D {
let angle = self.calculateAngleBetweenLocations(self.currentLocation, targetLocation: endLocation)
let x = self.currentLocation.latitude + distance * cos(angle)
let y = self.currentLocation.longitude + distance * sin(angle)
return CLLocationCoordinate2D(latitude: x, longitude: y)
}
And this is the result (the feet are the starting position, the blue marker is the destination and the red marker is where the new calculated point is). I've tried passing the distance in meters and kilometers and every other floating point position, but never got the correct result. Any ideas?
Ok, after some more digging, I found this answer, which resolves my problem. Here is my complete solution in swift:
internal func moveToLocation(location: CLLocationCoordinate2D, distance: Double) {
let angle = self.calculateAngleBetweenLocations(self.currentLocation, targetLocation: location)
let newLocation = self.coordinates(self.currentLocation, atDistance: distance, atAngle: angle)
self.moveUser(newLocation: newLocation)
}
private func coordinates(startingCoordinates: CLLocationCoordinate2D, atDistance: Double, atAngle: Double) -> CLLocationCoordinate2D {
let distanceRadians = atDistance / 6371
let bearingRadians = self.degreesToRadians(atAngle)
let fromLatRadians = self.degreesToRadians(startingCoordinates.latitude)
let fromLonRadians = self.degreesToRadians(startingCoordinates.longitude)
let toLatRadians = asin(sin(fromLatRadians) * cos(distanceRadians) + cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians))
var toLonRadians = fromLonRadians + atan2(sin(bearingRadians) * sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians) - sin(fromLatRadians) * sin(toLatRadians));
toLonRadians = fmod((toLonRadians + 3 * M_PI), (2 * M_PI)) - M_PI
let lat = self.radiansToDegrees(toLatRadians)
let lon = self.radiansToDegrees(toLonRadians)
return CLLocationCoordinate2D(latitude: lat, longitude: lon)
}
private func calculateAngleBetweenLocations(currentLocation: CLLocationCoordinate2D, targetLocation: CLLocationCoordinate2D) -> Double {
let fLat = self.degreesToRadians(currentLocation.latitude);
let fLng = self.degreesToRadians(currentLocation.longitude);
let tLat = self.degreesToRadians(targetLocation.latitude);
let tLng = self.degreesToRadians(targetLocation.longitude);
let deltaLng = tLng - fLng
let y = sin(deltaLng) * cos(tLat)
let x = cos(fLat) * sin(tLat) - sin(fLat) * cos(tLat) * cos(deltaLng)
let bearing = atan2(y, x)
return self.radiansToDegrees(bearing)
}
private func degreesToRadians(x: Double) -> Double {
return M_PI * x / 180.0
}
private func radiansToDegrees(x: Double) -> Double {
return x * 180.0 / M_PI
}

Display lateral view same like google map in my application

I am developing one navigation based application in which i used google map and google direction api for route navigation but i stuck at one point, i need to show same google map lateral view which is open when we start the navigation.
I have R&D lots off on it but not able to do exactly same like google map lateral view.
I try with following
GMSCameraPosition *camera =[GMSCameraPosition cameraWithTarget:marker.position zoom:50.0 bearing:120 viewingAngle:90];
[[self getMapView] animateToCameraPosition:camera];
not succeed to display same.
I need to show same google map as following when i start navigation.Can anybody help me in this point.Thanks in advance
You can achieve using proper bearing between 2 location. (present and target location)
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)
}
let bearingPoint = self.getBearingBetweenTwoPoints1(point1: location!, point2: self.featureLocation)
let camera = GMSCameraPosition.camera(withLatitude: (location?.coordinate.latitude)!, longitude: (location?.coordinate.longitude)!, zoom: 50, bearing: bearingPoint, viewingAngle: 45)
self.mapView?.animate(to: camera)

How can I convert from degrees to radians?

I am trying to convert this Obj-C code to Swift code but I don't know what the equivalent of this code should be ?
#define DEGREES_TO_RADIANS(degrees)((M_PI * degrees)/180)
I googled and found this
But I don't understand how to convert that in Swift in my case?
Xcode 11 • Swift 5.1 or later
extension BinaryInteger {
var degreesToRadians: CGFloat { CGFloat(self) * .pi / 180 }
}
extension FloatingPoint {
var degreesToRadians: Self { self * .pi / 180 }
var radiansToDegrees: Self { self * 180 / .pi }
}
Playground
45.degreesToRadians // 0.7853981633974483
Int(45).degreesToRadians // 0.7853981633974483
Int8(45).degreesToRadians // 0.7853981633974483
Int16(45).degreesToRadians // 0.7853981633974483
Int32(45).degreesToRadians // 0.7853981633974483
Int64(45).degreesToRadians // 0.7853981633974483
UInt(45).degreesToRadians // 0.7853981633974483
UInt8(45).degreesToRadians // 0.7853981633974483
UInt16(45).degreesToRadians // 0.7853981633974483
UInt32(45).degreesToRadians // 0.7853981633974483
UInt64(45).degreesToRadians // 0.7853981633974483
Double(45).degreesToRadians // 0.7853981633974483
CGFloat(45).degreesToRadians // 0.7853981633974483
Float(45).degreesToRadians // 0.7853981
Float80(45).degreesToRadians // 0.78539816339744830963
If you would like to make the binary integer return a floating point type instead of always returning a CGFloat you can make a generic method instead of a computed property:
extension BinaryInteger {
func degreesToRadians<F: FloatingPoint>() -> F { F(self) * .pi / 180 }
}
let radiansDouble: Double = 45.degreesToRadians() // 0.7853981633974483
let radiansCGFloat: CGFloat = 45.degreesToRadians() // 0.7853981633974483
let radiansFloat: Float = 45.degreesToRadians() // 0.7853981
let radiansFloat80: Float80 = 45.degreesToRadians() // 0.78539816339744830963
This is not identically what you asked, but in Swift 3 / iOS 10, you can use the Measurement type and do the conversion without knowing the formula!
let result = Measurement(value: 45, unit: UnitAngle.degrees)
.converted(to: .radians).value
Apple provides these GLKit functions for conversion:
func GLKMathDegreesToRadians(_ degrees: Float) -> Float
func GLKMathRadiansToDegrees(_ radians: Float) -> Float
let angle = 45° // angle will be in radians, 45 is in degrees
Compiles under Swift 3. Still keep all values, do all calculations in radians with CGFloats..., but make the code more readable with the constants in degrees. For example: 90°
The ° sign will magically do the degrees to radians conversion.
How to set this up:
Define and use a postfix operator for the ° sign. This operator will do the conversion from degrees to radians.
This example is for Ints, extend these also for the Float types if you have the need.
postfix operator °
protocol IntegerInitializable: ExpressibleByIntegerLiteral {
init (_: Int)
}
extension Int: IntegerInitializable {
postfix public static func °(lhs: Int) -> CGFloat {
return CGFloat(lhs) * .pi / 180
}
}
Some examples of usage:
let angle = 45°
contentView.transform = CGAffineTransform(rotationAngle: 45°)
let angle = 45
contentView.transform = CGAffineTransform(rotationAngle: angle°)
Warning!
It is too easy to use this conversion twice (on a value already in radians by mistake), you will get a very small number as the result, and seemingly the resulting angle will be always zero... DO NOT use ° on the same value twice (do not convert twice)!!:
// OBVIOUSLY WRONG!
let angle = 45°° // ° used twice here
// WRONG! BUT EASY TO MISS
let angle = 45° // ° used here
contentView.transform = CGAffineTransform(rotationAngle: angle°) // ° also used here
Also, to convert fron radians to degrees (if anyone stumbles upon this on google):
var degrees = radians * (180.0 / M_PI)
The most efficient and accurate way to convert a Double between degrees and radians:
import Foundation
extension Double {
var radians: Double { return Measurement(value: self, unit: UnitAngle.degrees).converted(to: UnitAngle.radians).value }
var degrees: Double { return Measurement(value: self, unit: UnitAngle.radians).converted(to: UnitAngle.degrees).value }
}
The Foundation team at Apple addressed this problem long ago by developing a solution that surpasses all others:
Measurements and Units - WWDC 2016, Session 238
You're no longer limited to ASCII characters when creating variable names, so how about this using π (alt-p):
typealias RadianAngle = CGFloat
let π = RadianAngle(M_PI)
let π_x_2 = RadianAngle(M_PI * 2)
let π_2 = RadianAngle(M_PI_2)
let π_4 = RadianAngle(M_PI_4)
extension RadianAngle {
var degrees: CGFloat {
return self * 180 / π
}
init(degrees: Int) {
self = CGFloat(degrees) * π / 180
}
}
Example usage:
let quarterCircle = RadianAngle(degrees: 90)
print("quarter circle = \(quarterCircle) radians")
// quarter circle = 1.5707963267949 radians
let halfCircle = π
print("half circle = \(halfCircle.degrees) degrees")
// half circle = 180.0 degrees
Swift 4.2
You can write global generic functions:
public func radians<T: FloatingPoint>(degrees: T) -> T {
return .pi * degrees / 180
}
public func degrees<T: FloatingPoint>(radians: T) -> T {
return radians * 180 / .pi
}
Example:
let cgFloat: CGFloat = 23.0
let double: Double = 23.0
let float: Float = 23.0
let int: Int = 23
let cgf = radians(degrees: cgFloat) // 0.4014 CGFloat
let d = radians(degrees: double) // 0.4014 Double
let f = radians(degrees: float) // 0.4014 Float
let i = radians(degrees: int) // compile error
In case when you don't want to extension all your floating types like this
extension FloatingPoint { ... }
I'd use the same principle #t1ser mentioned above, but create an extension of CGFloat to make it easier to use decimals for the degree as well (so you could have a 23.4 degrees, for example):
extension CGFloat {
func degrees() -> CGFloat {
return self * .pi / 180
}
init(degrees: CGFloat) {
self = degrees.degrees()
}
}
Using it would be pretty easy as well (mainly because I personally didn't know how to type ° 😜 - in case you didn't either, it's option+shift+8 btw):
let degreesToConvert: CGFloat = 45.7
.
.
.
let convertedDegrees = degreesToConvert.degrees()
Or to use the initialiser:
let convertedDegrees = CGFloat(degrees: 45.3)
If it is ok to import SwiftUI or if you are already using it you could use the Angle type to convert between angle units, eg.:
import SwiftUI
let radians = Angle(degrees: 45).radians
This will convert from Double to Double.

Resources