王新阳

wangxinyang

分别用js、php实现GS84与GCJ02两种坐标系的互转

高德地图: 使用 GCJ-02 坐标系,也称为火星坐标系。它是基于国际标准 WGS-84 坐标系进行加密处理得到的。
百度地图: 采用 BD-09 坐标系,这是在 GCJ-02 基础上进行二次加密而来的,具有更高的安全性和隐私保护。
天地图: 使用 CGCS2000 坐标系,该坐标系与 WGS-84 存在微小偏差,在要求不高的情况下可以直接与 WGS-84 互换使用。

js

/**
 * WGS84转GCJ02(火星坐标系)
 * @param {number} wgsLon WGS84坐标系的经度
 * @param {number} wgsLat WGS84坐标系的纬度
 * @returns {Array} GCJ02坐标 [经度, 纬度]
 */
function wgs84ToGcj02(wgsLon, wgsLat) {
    const PI = 3.14159265358979324;
    const a = 6378245.0;
    const ee = 0.00669342162296594323;
    
    if (outOfChina(wgsLat, wgsLon)) {
        return [wgsLon, wgsLat];
    }
    
    let dLat = transformLat(wgsLon - 105.0, wgsLat - 35.0);
    let dLon = transformLon(wgsLon - 105.0, wgsLat - 35.0);
    
    const radLat = wgsLat / 180.0 * PI;
    let magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    const sqrtMagic = Math.sqrt(magic);
    
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
    dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
    
    const gcjLat = wgsLat + dLat;
    const gcjLon = wgsLon + dLon;
    
    return [gcjLon, gcjLat];
}

/**
 * GCJ02(火星坐标系)转WGS84
 * @param {number} gcjLon GCJ02坐标系的经度
 * @param {number} gcjLat GCJ02坐标系的纬度
 * @returns {Array} WGS84坐标 [经度, 纬度]
 */
function gcj02ToWgs84(gcjLon, gcjLat) {
    const PI = 3.14159265358979324;
    const a = 6378245.0;
    const ee = 0.00669342162296594323;
    
    if (outOfChina(gcjLat, gcjLon)) {
        return [gcjLon, gcjLat];
    }
    
    let dLat = transformLat(gcjLon - 105.0, gcjLat - 35.0);
    let dLon = transformLon(gcjLon - 105.0, gcjLat - 35.0);
    
    const radLat = gcjLat / 180.0 * PI;
    let magic = Math.sin(radLat);
    magic = 1 - ee * magic * magic;
    const sqrtMagic = Math.sqrt(magic);
    
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * PI);
    dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * PI);
    
    const wgsLat = gcjLat - dLat;
    const wgsLon = gcjLon - dLon;
    
    return [wgsLon, wgsLat];
}

function transformLat(x, y) {
    let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
    ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
    return ret;
}

function transformLon(x, y) {
    let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
    ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
    ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
    return ret;
}

function outOfChina(lat, lon) {
    return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271;
}

// 使用示例
// let gcjCoord = wgs84ToGcj02(116.404, 39.915); // WGS84转GCJ02
// let wgsCoord = gcj02ToWgs84(116.404, 39.915); // GCJ02转WGS84

PHP

/**
 * WGS84转GCJ02(火星坐标系)
 * @param float $wgsLon WGS84坐标系的经度
 * @param float $wgsLat WGS84坐标系的纬度
 * @return array GCJ02坐标 [经度, 纬度]
 */
function wgs84ToGcj02($wgsLon, $wgsLat) {
    $PI = 3.14159265358979324;
    $a = 6378245.0;
    $ee = 0.00669342162296594323;
    
    if (outOfChina($wgsLat, $wgsLon)) {
        return array($wgsLon, $wgsLat);
    }
    
    $dLat = transformLat($wgsLon - 105.0, $wgsLat - 35.0);
    $dLon = transformLon($wgsLon - 105.0, $wgsLat - 35.0);
    
    $radLat = $wgsLat / 180.0 * $PI;
    $magic = sin($radLat);
    $magic = 1 - $ee * $magic * $magic;
    $sqrtMagic = sqrt($magic);
    
    $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * $PI);
    $dLon = ($dLon * 180.0) / ($a / $sqrtMagic * cos($radLat) * $PI);
    
    $gcjLat = $wgsLat + $dLat;
    $gcjLon = $wgsLon + $dLon;
    
    return array($gcjLon, $gcjLat);
}

/**
 * GCJ02(火星坐标系)转WGS84
 * @param float $gcjLon GCJ02坐标系的经度
 * @param float $gcjLat GCJ02坐标系的纬度
 * @return array WGS84坐标 [经度, 纬度]
 */
function gcj02ToWgs84($gcjLon, $gcjLat) {
    $PI = 3.14159265358979324;
    $a = 6378245.0;
    $ee = 0.00669342162296594323;
    
    if (outOfChina($gcjLat, $gcjLon)) {
        return array($gcjLon, $gcjLat);
    }
    
    $dLat = transformLat($gcjLon - 105.0, $gcjLat - 35.0);
    $dLon = transformLon($gcjLon - 105.0, $gcjLat - 35.0);
    
    $radLat = $gcjLat / 180.0 * $PI;
    $magic = sin($radLat);
    $magic = 1 - $ee * $magic * $magic;
    $sqrtMagic = sqrt($magic);
    
    $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * $PI);
    $dLon = ($dLon * 180.0) / ($a / $sqrtMagic * cos($radLat) * $PI);
    
    $wgsLat = $gcjLat - $dLat;
    $wgsLon = $gcjLon - $dLon;
    
    return array($wgsLon, $wgsLat);
}

function transformLat($x, $y) {
    $ret = -100.0 + 2.0 * $x + 3.0 * $y + 0.2 * $y * $y + 0.1 * $x * $y + 0.2 * sqrt(abs($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 += (160.0 * sin($y / 12.0 * M_PI) + 320 * sin($y * M_PI / 30.0)) * 2.0 / 3.0;
    return $ret;
}

function transformLon($x, $y) {
    $ret = 300.0 + $x + 2.0 * $y + 0.1 * $x * $x + 0.1 * $x * $y + 0.1 * sqrt(abs($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;
}

function outOfChina($lat, $lon) {
    return $lon < 72.004 || $lon > 137.8347 || $lat < 0.8293 || $lat > 55.8271;
}

// 使用示例
// $gcjCoord = wgs84ToGcj02(116.404, 39.915); // WGS84转GCJ02
// $wgsCoord = gcj02ToWgs84(116.404, 39.915); // GCJ02转WGS84

百度地图API在CodeIgniter中的实现:
地理编码、全球逆地理编码、坐标系转换

/**
 * 百度地图开放平台接口
 */
class Baidumap extends MY_Controller{
	public function __construct(){
		parent::__construct();
		
		//百度地图配置
		define('BAIDUMAP_CONFIG', array(
			'ak' => '百度地图AK',
			'sk' => '百度地图SK',
			'host' => 'https://api.map.baidu.com',
		));
	}
	
	//计算请求验证的SN值
	private function caculateAKSN($sk, $uri, $param, $method = 'GET'){
		if($method === 'POST'){
			ksort($param);
		}
		$querystring = http_build_query($param);
		return md5(urlencode($uri.'?'.$querystring.$sk));
	}

	/**
	 * 地理编码
	 * 为保证高德、腾讯、百度地图通用,所以获取的是国测局坐标gcj02ll
	 * https://lbsyun.baidu.com/faq/api?title=webapi/guide/webservice-geocoding-base
	 */
	public function geocoder(){
		$address=trim(G('address'));
		if(empty($address))return json(101,'参数address不能为空');
		$city=trim(G('city'));
		$city OR $city='济南市';
		
		$coord_type=G('coordtype', 'gcj02ll'); //或百度坐标bd09ll
		
		$uri = '/geocoding/v3/';
		//构造请求串数组
		$param = array(
			'address' => $address,
			'city' => $city,
			'ret_coordtype' => $coord_type,
			'output' => 'json',
			'ak' => BAIDUMAP_CONFIG['ak'],
		);

		//调用sn计算函数,默认get请求
		$param['sn'] = $this->caculateAKSN(BAIDUMAP_CONFIG['sk'], $uri, $param);
		$res=my_curl(BAIDUMAP_CONFIG['host'].$uri,'GET',$param);
		$res=json_decode($res,true);
		$res['result']['location']['lng']=(string)$res['result']['location']['lng'];
		if(!is_array($res))return json(102,'请求失败,请重试');
		if($res['status']==0){
			$data=$res['result'];
			$data['lon_lat']=$data['location']['lng'].','.$data['location']['lat'];
			$data['lat_lon']=$data['location']['lat'].','.$data['location']['lng'];
			unset($data['location']);
			
			return json(0,'',$data);
		}else{
			return json($res['status'],$res['message']);
		}
	}

	/**
	 * 坐标转换
	 * 把国测局坐标gcj02ll转为百度坐标
	 * https://lbsyun.baidu.com/faq/api?title=webapi/guide/changeposition-base
	 * coord坐标经度在前
$model 转换方式可选值:
1:amap/tencent to bd09ll
2:gps to bd09ll
3:bd09ll to bd09mc
4:bd09mc to bd09ll
5:bd09ll to amap/tencent
6:bd09mc to amap/tencent
	 */
	public function getconv(){
		$coord=str_replace(',',',',trim(G('coord')));
		if(!preg_match('/^\d+(\.\d+)?,\d+(\.\d+)?$/',$coord))return json(101,'坐标格式错误');
		$model=G('model','1');
		$model=is_id($model) ? (int)$model : 1;
		
		$uri = '/geoconv/v2/';
		
		//需转换的源坐标,多组坐标以";"分隔
		$param=array(
			'coords' => $coord,
			'model' => $model,
			'output' => 'json',
			'ak' => BAIDUMAP_CONFIG['ak'],
		);
		$param['sn'] = $this->caculateAKSN(BAIDUMAP_CONFIG['sk'], $uri, $param);

		$res = my_curl(BAIDUMAP_CONFIG['host'].$uri, 'GET', $param);
		//echo $res;
		$res = json_decode($res,true);
		if(!is_array($res))return json(102,'请求失败,请重试');
		
		if($res['status']!=0){
			return json($res['status'], $res['message']);
		}
		$data=array(
			'lon_lat' => $res['result'][0]['x'].','.$res['result'][0]['y'],
			'lat_lon' => $res['result'][0]['y'].','.$res['result'][0]['x'],
		);
		return json(0,'',$data);
	}

	/**
	 * 全球逆地理编码
	 * https://lbsyun.baidu.com/faq/api?title=webapi/guide/webservice-geocoding-abroad-base
	 * coord坐标纬度在前,格式:纬度,经度
	 */
	public function reverse_geocoder(){
		$coord=str_replace(',',',',trim(G('coord')));
		if(empty($coord))return json(101,'参数coord不能为空');
		if(!preg_match('/^\d+(\.\d+)?,\d+(\.\d+)?$/',$coord))return json(102,'坐标格式错误');
		
		$coordtype=G('coordtype', 'gcj02ll'); //或百度坐标bd09ll
		
		$uri = '/reverse_geocoding/v3';
		
		$param=array(
			'location' => $coord,
			'coordtype' => $coordtype,
			'extensions_poi' => '1',
			'radius' => 50, //poi半径:0-3000米
			'region_data_source' => 1, //行政区划数据的来源:1统计局(把开发区作为行政区划返回),2民政部
			'entire_poi' => 1,
			'sort_strategy' => 'distance',
			//'poi_types' => '交通设施|公交线路|铁路',
			'output' => 'json',
			'ak' => BAIDUMAP_CONFIG['ak'],
		);
		//printr($param);
		$param['sn'] = $this->caculateAKSN(BAIDUMAP_CONFIG['sk'], $uri, $param);
		
		$res = my_curl(BAIDUMAP_CONFIG['host'].$uri, 'GET', $param);
		$res = json_decode($res,true);
		
		if($res['status']!=0){
			return json($res['status'], $res['message']);
		}
		$result=$res['result'];
		$data=array(
			'formatted_address' => $result['formatted_address'],
			'formatted_address_poi' => $result['formatted_address_poi'],
			'addressComponent' => $result['addressComponent'],
		);
		json(0,'',$data);
	}
}
2025-08-22
2026-01-14 星期三 农历冬月二十六