前言
-
苹果产品只允许接入了苹果定位
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申请,才能得到解密方法。
那么就可以解释以下问题了。
- CLLocationManager定位坐标不准确问题?
因为CLLocationManager
定位出来的结果是基于WGS-84坐标系
的,却拿来用在GCJ-02坐标系
上使用。肯定是有一定误差的。 - 为什么在国内的地图还是定位挺准确的?
虽然这些地图的定位都是使用的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()
}
}
网友评论