Swift_系统定位CLLocationManager

前言

  • 苹果产品只允许接入了苹果定位CoreLocation,第三方地图(百度/高德/腾讯)只是在苹果定位服务基础上二次封装。

  • 定位的方式有三种,基站定位,WiFi定位,GPS定位。其实无论哪种定位,都是根据已知点位置信息来定位当前位置,原理都是一样的,只是精度、定位速度和耗电的差别。目前苹果使用的是GPS定位。

  • 执行定位的代理方法的时候,会发现定位代理方法一般会执行3~4次。先给返回一个大致的位置,之后再进行校正,所以后面返回的坐标精度比前面的高。

  • 地图定位的原理
    地图定位是三角测量定位,已知三个点的坐标,和未知点到这三个点的距离,求未知点的坐标。
    Trilateration三边测量定位算法的原理:已知(x1,y1),(x2,y2),(x3,y3),和三个圆的半径,求(x0,y0)。但实际情况是三个圆往往不能相交于一个点,因为三个圆的半径有误差,所以最后得到的是一个圆,而不是点,那个圆的半径其实就是精度。为了增加精度,也就是缩小圆的半径,我们需要更多的已知点和未知点到已知点的距离,来相互校验。

    三角测量定位

坐标系

  • WGS-84坐标系 (原始坐标系)
    用国际GPS纪录仪记录下来的经纬度,通过GPS定位拿到的原始经纬度。
  • GCJ-02坐标系 (火星坐标系/中国坐标系)
    是我国独创的坐标体系。由WGS-84加密而成,在国内,必须至少使用GCJ-02坐标系或使用基于GCJ-02加密的坐标系。
  • bd-09坐标系(百度坐标系)
    百度坐标系是在GCJ-02坐标系的基础上再次加密偏移后形成的坐标系,只适用于百度地图。

高德地图,谷歌地图和苹果地图在国外都是使用的WGS-84坐标系坐标系,在国内使用的是GCJ-02坐标系。百度地图使用的是bd-09坐标系

听闻是为了国家安全搞的加密措施,使用的是非线性的偏移值,想得到真实的数据,得向GCJ申请,才能得到解密方法。

那么就可以解释以下问题了。

  1. CLLocationManager定位坐标不准确问题?
    因为CLLocationManager定位出来的结果是基于WGS-84坐标系的,却拿来用在GCJ-02坐标系上使用。肯定是有一定误差的。
  2. 为什么在国内的地图还是定位挺准确的?
    虽然这些地图的定位都是使用的CLLocationManager来定位的,但是地图根据定位的信息根据算法进行了转化。
    1.China Map Deviation as a Regression Problem
    2.The Deviation of China Map as a Regression Problem

不同坐标系下的转换

  • 判断是否在中国范围
    /**
     *  判断是否在中国范围
     *
     *  @param lat 纬度
     *  @param lon 经度
     *
     *  @return bool
     */
    private static func inTheRangeChina(latitude: Double, longitude: Double) -> Bool {
        
        if (longitude < 72.004 || longitude > 137.8347) {
        return false
        }
        if (latitude < 0.8293 || latitude > 55.8271) {
        return false
        }
        return true
    }
  • 火星坐标系 -> 原始坐标系
    /**
     *  中国坐标 -> 标准坐标
     *
     *  @param latitude 标准坐标的维度
     *
     *  @param longitude 标准坐标的经度
     *
     *  @return 中国坐标
     */
    public static func transformFromGCJToWGS(latitude: Double, longitude: Double) -> (latitude:Double,longitude:Double) {
        
        let gcLoc     : (latitude: Double, longitude: Double) = (latitude, longitude)
        var wgLoc     : (latitude: Double, longitude: Double) = (latitude, longitude)
        var currGcLoc : (latitude: Double, longitude: Double) = (0,0)
        var dLoc      : (latitude: Double, longitude: Double) = (0,0)

        
        
        while true {
            currGcLoc = transformFromWGSToGCJ(latitude: latitude, longitude: longitude)
            
            dLoc.latitude = gcLoc.latitude - currGcLoc.latitude;
            dLoc.longitude = gcLoc.longitude - currGcLoc.longitude;
            if (fabs(dLoc.latitude) < 1e-7 && fabs(dLoc.longitude) < 1e-7) {  // 1e-7 ~ centimeter level accuracy
                // Result of experiment:
                //   Most of the time 2 iterations would be enough for an 1e-8 accuracy (milimeter level).
                //
                return wgLoc;
            }
            wgLoc.latitude += dLoc.latitude;
            wgLoc.longitude += dLoc.longitude
        }
        
        return wgLoc
    }

    private static func transformLatitude(x:Double, y:Double) -> Double {
        let pi : Double = Double.pi

        var lat: Double = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(x > 0 ? x:-x);
        lat += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
        lat += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
        lat += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
        return lat
    }
    
    private static func transformLongitude(x:Double, y:Double) -> Double {
        let pi : Double = Double.pi

        var lon : Double = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(x > 0 ? x:-x);
        lon += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
        lon += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
        lon += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
        return lon;
    }

  • 中国坐标 -> 标准坐标
    /**
     *  中国坐标 -> 标准坐标
     *
     *  @param latitude 标准坐标的维度
     *
     *  @param longitude 标准坐标的经度
     *
     *  @return 中国坐标
     */
    public static func transformFromGCJToWGS(latitude: Double, longitude: Double) -> (latitude:Double,longitude:Double) {
        
        let gcLoc     : (latitude: Double, longitude: Double) = (latitude, longitude)
        var wgLoc     : (latitude: Double, longitude: Double) = (latitude, longitude)
        var currGcLoc : (latitude: Double, longitude: Double) = (0,0)
        var dLoc      : (latitude: Double, longitude: Double) = (0,0)

        
        
        while true {
            currGcLoc = transformFromWGSToGCJ(latitude: latitude, longitude: longitude)
            
            dLoc.latitude = gcLoc.latitude - currGcLoc.latitude;
            dLoc.longitude = gcLoc.longitude - currGcLoc.longitude;
            if (fabs(dLoc.latitude) < 1e-7 && fabs(dLoc.longitude) < 1e-7) {  // 1e-7 ~ centimeter level accuracy
                // Result of experiment:
                //   Most of the time 2 iterations would be enough for an 1e-8 accuracy (milimeter level).
                //
                return wgLoc;
            }
            wgLoc.latitude += dLoc.latitude;
            wgLoc.longitude += dLoc.longitude
        }
        
        return wgLoc
    }
  • GCJ-02 to BD-09 (标准坐标系 -> 百度坐标系)
    /**
     * GCJ-02 to BD-09 (标准坐标系 -> 百度坐标系)
     *
     *  @param latitude 标准坐标的维度
     *
     *  @param longitude 标准坐标的经度
     *
     *  @return 百度坐标
     */
    public static func transformFromGCJToBD(latitude: Double, longitude: Double) -> (latitude:Double,longitude:Double) {
        return (latitude + 0.006, longitude + 0.0065)
    }

  • BD-09 to GCJ-02 (百度坐标系 -> 标准坐标系)
    /**
     * BD-09 to GCJ-02 (百度坐标系 -> 标准坐标系)
     *
     *  @param latitude 百度坐标的维度
     *
     *  @param longitude 百度坐标的经度
     *
     *  @return 标准坐标
     */
    public static func transformFromBDToGCJ(latitude: Double, longitude: Double) -> (latitude:Double,longitude:Double) {
        return (latitude - 0.006, longitude - 0.0065)
    }

封装的定位功能

代码地址

如何使用 ?
import UIKit
import MCComponentFunction

class ViewController: UIViewController {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        MCLocation.shared.didUpdateLocation(self)
    }
}


extension ViewController: MCLocationProtocol {
    func mc_locationManager(latitude: Double, longitude: Double) {
        // latitude 经度
        // longitude 维度
    }
}
实现逻辑
import Foundation

import CoreLocation


@objc public protocol MCLocationProtocol {
    func mc_locationManager(latitude: Double, longitude: Double)
}


public class MCLocation: NSObject {
    
    public weak var delegate : MCLocationProtocol?
    
    public static let shared = MCLocation.init()
    
    private var locationManager : CLLocationManager?
    private var viewController : UIViewController?      // 承接外部传过来的视图控制器,做弹框处理
    
    
    // 外部初始化的对象调用,执行定位处理。
    public func didUpdateLocation(_ vc:UIViewController) {
        
        self.viewController = vc
        self.delegate = vc as? MCLocationProtocol
        if (self.locationManager != nil) && (CLLocationManager.authorizationStatus() == .denied) {
            // 定位提示
            self.alter(viewController: viewController!)
        } else {
            self.requestLocationServicesAuthorization()
        }
    }
    
    
    // 初始化定位
    private func requestLocationServicesAuthorization() {
        
        if (self.locationManager == nil) {
            self.locationManager = CLLocationManager()
            self.locationManager?.delegate = self
        }
        
        self.locationManager?.requestWhenInUseAuthorization()
        self.locationManager?.startUpdatingLocation()
        
        if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.notDetermined) {
            locationManager?.requestWhenInUseAuthorization()
        }
        
        if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse) {
            locationManager?.desiredAccuracy = kCLLocationAccuracyBest
            let distance : CLLocationDistance = 100.0
            locationManager?.distanceFilter = distance
            locationManager?.startUpdatingLocation()
        }
    }
    
    
    // 获取定位代理返回状态进行处理
    private func reportLocationServicesAuthorizationStatus(status:CLAuthorizationStatus) {
        
        if status == .notDetermined {
            // 未决定,继续请求授权
            requestLocationServicesAuthorization()
        } else if (status == .restricted) {
            // 受限制,尝试提示然后进入设置页面进行处理
            alter(viewController: viewController!)
        } else if (status == .denied) {
            // 受限制,尝试提示然后进入设置页面进行处理
            alter(viewController: viewController!)
        }
    }
    
    
    private func alter(viewController:UIViewController) {
        let alter = UIAlertController.init(title: "定位服务未开启,是否前往开启?", message: "", preferredStyle: UIAlertController.Style.alert)
        let cancle = UIAlertAction.init(title: "暂不开启", style: UIAlertAction.Style.cancel) { (a) in
        }
        let confirm = UIAlertAction.init(title: "前往开启", style: UIAlertAction.Style.default) { (b) in
            // 跳转到开启定位服务页面
            let url = NSURL.init(string: UIApplication.openSettingsURLString)
            if(UIApplication.shared.canOpenURL(url! as URL)) {
                UIApplication.shared.openURL(url! as URL)
            }
        }
        alter.addAction(cancle)
        alter.addAction(confirm)
        viewController.present(alter, animated: true, completion: nil)
    }
}


extension MCLocation:  CLLocationManagerDelegate {
    
    public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        self.locationManager?.stopUpdatingLocation()
        
        let location = locations.last ?? CLLocation.init()
        let coordinate = location.coordinate
        
        let latitude : Double = coordinate.latitude
        let longitude : Double = coordinate.longitude
        
        
        let transformLocation = MCLocationHelper.transformFromWGSToGCJ(latitude: latitude, longitude: longitude)
        
        
        delegate?.mc_locationManager(latitude: transformLocation.latitude, longitude: transformLocation.longitude)
    }
    
    public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        reportLocationServicesAuthorizationStatus(status: status)
    }
    
    public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        self.locationManager?.stopUpdatingLocation()
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容