【问题标题】:CLLocation Category for Calculating Bearing w/ Haversine functionCLLocation 类别,用于计算带半正弦函数的轴承
【发布时间】:2011-04-24 23:11:52
【问题描述】:

我正在尝试为 CLLocation 编写一个类别以将方位返回到另一个 CLLocation。

我认为我的公式做错了(精打细算不是我的强项)。返回的轴承始终处于关闭状态。

我一直在查看这个问题并尝试应用被接受为正确答案的更改及其引用的网页:

Calculating bearing between two CLLocationCoordinate2Ds

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

感谢您的任何指点。我已经尝试整合来自其他问题的反馈,但我仍然没有得到任何东西。

谢谢

这是我的类别 -

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

【问题讨论】:

  • 为什么要将纬度和经度值从度数转换为弧度? Haversine 函数是否需要这种转换?
  • 回答我自己的问题,是的。 Haversine 函数需要该转换,如下所示:movable-type.co.uk/scripts/latlong.html

标签: iphone mapkit core-location cllocation haversine


【解决方案1】:

Swift 5 中实现了这一点。重点是准确性,而不是速度,但它实时运行 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
}

我发现与 CLLocation 的 distance 方法相比,Haversine 确定了距离,但没有提供可与 CL 一起使用的轴承。所以我没有将它用于轴承。这给出了我从我尝试过的所有数学中遇到的最准确的测量结果。 translateCoord 方法还将根据原点、以米为单位的距离和以度为单位的方位精确绘制一个新点。

【讨论】:

    【解决方案2】:

    值得一提的是,如果您使用的是 Google 地图 GMSMapView,则可以使用 GMSGeometryHeading method 的开箱即用解决方案:

    GMSGeometryHeading(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D)

    在 from of 处返回初始航向(北顺时针度数) 到的最短路径。

    【讨论】:

      【解决方案3】:

      这是另一个 CLLocation 扩展,可用于 Swift 3Swift 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)
          }
      
      }
      

      【讨论】:

        【解决方案4】:

        使用 Swift 3 和 4

        尝试了这么多版本,这个终于给出了正确的值!

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

        用法:

        let degrees = location1.bearingDegreesTo(location: location2)
        

        【讨论】:

          【解决方案5】:

          这是对开头的 Category 在 Swift 中的一个移植:

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

          【讨论】:

            【解决方案6】:

            我在 Swift 中使用余弦定律。它比 Haversine 运行得更快,其结果非常相似。远距离变化 1 米。

            我为什么使用余弦定理:

            • 跑得快(因为没有sqrt函数)
            • 足够精确,除非你会做一些天文学
            • 非常适合后台任务
            
            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
            
            }
            

            【讨论】:

              【解决方案7】:

              这是另一个实现

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

              【讨论】:

              • 我不明白这些算法如何将 sin 应用于以度数而不是弧度为单位的坐标。
              • williams.best.vwh.net/avform.htm#Crs 我相信它在航空领域的做法。
              • 我的问题是我选择的任何算法都会给出不同的结果。在底部是上述类别的 Swift 移植。
              • 事实上它首先将度数转换为弧度。然而,即使直接攻读学位也能得出类似的结果。
              • 每一个算法都会给你不同的结果——因为试图在椭球体上做线性几何的性质不同。所有这些算法都只是近似值并使用不同的投影。余弦校正适用于小距离进行 xy 近似等。但其他算法更适合不同的距离。
              【解决方案8】:

              您的代码对我来说似乎很好。算计没有错。你没有指定你的结果有多远,但你可以尝试调整你的弧度/度数转换器:

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

              如果您得到负方位角,请将2*M_PI 添加到弧度轴承的最终结果中(如果在转换为度数后添加,则添加 360)。 atan2 返回-M_PIM_PI(-180 到 180 度)范围内的结果,因此您可能希望使用类似于以下代码的内容将其转换为罗盘方位

              if(radiansBearing < 0.0)
                  radiansBearing += 2*M_PI;
              

              【讨论】:

              • 非常感谢!我应该给出一些预期和实际的结果,但你还是发现了这个问题。我只是没有处理负度数并转换为指南针度数。好点也将 180 指定为浮点数。现在一切正常。
              猜你喜欢
              • 2014-03-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-06-16
              • 1970-01-01
              • 2022-10-21
              相关资源
              最近更新 更多