使用 CLLocationManager 取得座標時,經緯度與實際位置有偏移 (特別在中國大陸地區)。
CLLocation 回傳的是 WGS-84 原始座標,直接在地圖上標註會偏移
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
// 過濾精度差的座標 (水平精度 > 1000m 視為無效)
if (newLocation.horizontalAccuracy > 1000) {
NSLog(@"座標精度不足: %.0fm", newLocation.horizontalAccuracy);
return;
}
// 精度較好才使用
if (newLocation.horizontalAccuracy < 100) {
self.currentLocation = newLocation;
[self updateMapWithLocation:newLocation];
}
}
將 WGS-84 (GPS 原始) 轉換為 GCJ-02 (火星坐標) 以匹配國內地圖:
// WGS-84 轉 GCJ-02 (火星坐標系)
+ (CLLocationCoordinate2D)wgs84ToGcj02:(CLLocationCoordinate2D)wgs84 {
const double a = 6378245.0; // 長半軸
const double ee = 0.00669342162296594323; // 扁率平方
double lat = wgs84.latitude;
double lon = wgs84.longitude;
if (outOfChina(lat, lon)) return wgs84; // 國外無偏移
double dLat = transformLat(lon - 105.0, lat - 35.0);
double dLon = transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * M_PI;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * M_PI);
dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * M_PI);
return CLLocationCoordinate2DMake(lat + dLat, lon + dLon);
}
static bool outOfChina(double lat, double lon) {
return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}
static double transformLat(double x, double y) {
double ret = -100.0 + 2.0*x + 3.0*y + 0.2*y*y + 0.1*x*y + 0.2*sqrt(fabs(x));
ret += (20.0*sin(6.0*x*M_PI) + 20.0*sin(2.0*x*M_PI)) * 2.0/3.0;
ret += (20.0*sin(y*M_PI) + 40.0*sin(y/3.0*M_PI)) * 2.0/3.0;
ret += (150.0*sin(y/12.0*M_PI) + 300.0*sin(y/30.0*M_PI)) * 2.0/3.0;
return ret;
}
static double transformLon(double x, double y) {
double ret = 300.0 + x + 2.0*y + 0.1*x*x + 0.1*x*y + 0.1*sqrt(fabs(x));
ret += (20.0*sin(6.0*x*M_PI) + 20.0*sin(2.0*x*M_PI)) * 2.0/3.0;
ret += (20.0*sin(x*M_PI) + 40.0*sin(x/3.0*M_PI)) * 2.0/3.0;
ret += (150.0*sin(x/12.0*M_PI) + 300.0*sin(x/30.0*M_PI)) * 2.0/3.0;
return ret;
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
CLLocation *loc = locations.lastObject;
if (loc.horizontalAccuracy < 100) {
// 轉換為 GCJ-02 供國內地圖使用
CLLocationCoordinate2D gcj02 = [self.class wgs84ToGcj02:loc.coordinate];
CLLocation *correctedLoc = [[CLLocation alloc] initWithCoordinate:gcj02
altitude:loc.altitude horizontalAccuracy:loc.horizontalAccuracy
verticalAccuracy:loc.verticalAccuracy timestamp:loc.timestamp];
self.currentLocation = correctedLoc;
[self updateMapWithLocation:correctedLoc];
}
}
// 棄用的委派方法 (iOS 6 前)
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation;
// 推薦使用 (iOS 6+)
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
horizontalAccuracy > 100~200m 的座標didUpdateLocations: 替代棄用方法