我写的客户端获取到的经纬度是底层蓝牙传上来的,其实是地球坐标,也就是GPS卫星测量到的实实在在的原始坐标。而高德地图采用的坐标是火星坐标,是经天朝测绘局加密后的坐标。我直接拿原始坐标去定位,自然会出现很大的偏差。
但是对于国内地图而言,使用LocationManager定位所获得经纬度,是有一段较大距离的偏移的。
原来国内地图使用的坐标系统是GCJ-02而iOS SDK中所用到的是国际标准的坐标系统WGS-84。
因为国内使用的是加密后的坐标系GCJ-02就是网络上叫的火星坐标。
LocationManager就是因为得到的是火星坐标偏移后的经纬度,所以导致在MapView上有很大的偏差,而在MKMapView上通过定位自己位置所获得的经纬度有是准确,因为Apple已经对国内地图做了偏移优化。
SDK | 坐标系 | 名称 |
---|---|---|
iOS SDK | WGS-84 | 地球坐标 |
高德 SDK | BD-09 | 火星坐标 |
百度 SDK | GCJ-02 | 百度坐标 |
WGS84TOGCJ02.h
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
@interface WGS84TOGCJ02 : NSObject
// 判断是否已经超出中国范围
+ (BOOL)isLocationOutOfChina:(CLLocationCoordinate2D)location;
// 将WGS-84转为GCJ-02(火星坐标)
// 转GCJ-02
+ (CLLocationCoordinate2D)transformFromWGSToGCJ:(CLLocationCoordinate2D)wgsLoc;
@end
WGS84TOGCJ02.m
#import "WGS84TOGCJ02.h"
const double a = 6378245.0;
const double ee = 0.00669342162296594323;
const double pi = 3.14159265358979324;
@implementation WGS84TOGCJ02
// 我们通过判断isLocationOutOfChina 然后调用transformLatWithX方法就能获取转换后的
+ (CLLocationCoordinate2D)transformFromWGSToGCJ:(CLLocationCoordinate2D)wgsLoc
{
CLLocationCoordinate2D adjustLoc;
if([self isLocationOutOfChina:wgsLoc]){
adjustLoc = wgsLoc;
}
else{
double adjustLat = [self transformLatWithX:wgsLoc.longitude - 105.0 withY:wgsLoc.latitude - 35.0];
double adjustLon = [self transformLonWithX:wgsLoc.longitude - 105.0 withY:wgsLoc.latitude - 35.0];
double radLat = wgsLoc.latitude / 180.0 * pi;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
adjustLat = (adjustLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
adjustLon = (adjustLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi);
adjustLoc.latitude = wgsLoc.latitude + adjustLat;
adjustLoc.longitude = wgsLoc.longitude + adjustLon;
}
return adjustLoc;
}
+ (BOOL)isLocationOutOfChina:(CLLocationCoordinate2D)location
{
if (location.longitude < 72.004 || location.longitude > 137.8347 || location.latitude < 0.8293 || location.latitude > 55.8271)
return YES;
return NO;
}
+ (double)transformLatWithX:(double)x withY:(double)y
{
double lat = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(abs(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;
// 320 写成 3320了
// lat += (160.0 * sin(y / 12.0 * pi) + 3320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
lat += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
return lat;
}
+ (double)transformLonWithX:(double)x withY:(double)y
{
double lon = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(abs(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;
}
@end
测试结果:
latitude | longitude | address |
---|---|---|
25.048367 | 102.701019 | 中国云南省昆明市五华区华山街道中和巷1号 |
25.063974 | 102.701012 | 中国云南省昆明市五华区莲华街道民院路 |
25.063915 | 102.701027 | 中国云南省昆明市五华区莲华街道民院路 |
25.063770 | 102.701141 | 中国云南省昆明市五华区莲华街道民院路13号 |
25.064100 | 102.701020 | 云南省昆明市五华区莲华街道教场中路社区 |
中国云南省昆明市五华区教场东路9号(在教场中路社区附近) |
| 25.064100| 102.701020 | 云南省昆明市五华区莲华街道教场中路社区 |
推荐的解决方案:
- 既然是在国内,存储一律用火星坐标,这样在使用国内地图显示时最方便(用百度地图显示时可以一次转换取得)
- CLLocationManager 拿到的 CLLocation 转为火星坐标,MKMapView 不用处理
- 使用地图 API 进行 地址解析/逆地址解析(Geocoding) 时注意相应使用相应地图商的坐标系
- 部分地图商支持多个坐标系输入,如高德支持地球、火星坐标(这个一直有变动,具体只能参考厂商最新文档了)