文档章节

GPS各坐标系间的坐标值转换

城邑耕夫
 城邑耕夫
发布于 2016/11/21 20:49
字数 2185
阅读 505
收藏 2

1、地球坐标系:是国际上通用的地理坐标系wgs84,也就是我们常说的经度纬度多少多少。比如08年汶川大地震报的经纬度就是基于这个坐标系。而我们常见的一些设备比如gps也是以这个坐标系为参考的。

2、火星坐标系:主要是因为国家安全的原因为了保护一些比较敏感的坐标位置,在大天朝所有获取地图数据的第一个关卡就是国家测绘局,在他们出产的所有地图在wgs84的基础上进行一次加密,也就是所谓的按照一定的坐标偏移算法到另外一个坐标系gcj-02,这个坐标系是特有的且是变化的,大家习惯性的把他称为火星坐标系。正是因为火星坐标系的存在我们直接拿gps采集的数据在地图上显示始终都有偏差,原因就是因为两个坐标系都不一样造成的。

3、百度坐标系:百度坐标在火星坐标系gcj-02基础上,进行了BD-09二次加密措施,更加保护了个人隐私。百度对外接口的坐标系bd-09并不是GPS采集的真实经纬度,需要通过坐标转换接口进行转换。

package com.cdthgk.utils;

import java.util.List;

import com.cdthgk.utils.vo.ZuoBiao;

/**
 * <p>
 * MyGPSUtil類主要用於-GPS相关数据处理.
 * <ul>
 * 经纬度坐标系
 * <li>WGS84坐标系:即地球坐标系,是为GPS全球定位系统使用而建立的坐标系统,国际上通用的坐标系。如Google Earth(不含中国)</li>
 * <li>GCJ02坐标系:又称火星坐标系,中国国家测绘局制订的地理信息系统的坐标系统,WGS84坐标系经加密(加入随机的偏差)后的坐标系。如Gogole中国、搜搜、阿里云、高德</li>
 * <li>BD09坐标系:即百度坐标系,在GCJ02坐标系再次加密后的坐标系。如百度</li>
 * <li>CGCS2000坐标系:国家大地坐标系,是我国当前最新的国家大地坐标系,之前有北京54、西安80,同WGS84的原点、尺度、定向及定向演变的定义都是相同的,参考椭球也非常相近,仅扁率f有微小差异。如天地图</li>
 * <li>国内其他坐标系:应国家要求均是在GCJ02坐标系上加密后的坐标系。如搜狗、图吧</li>
 * </ul>
 * </p>
 * <p>
 * 創建時間 2016-11-21 - 下午5:59:47
 * </p>
 * <p>
 * copyright cdthgk 2010-2018, all rights reserved.
 * </p>
 * @author 城邑耕夫
 * @since 1.0
 * @version 1.0
 */
public class MyGPSUtil {

	private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MyGPSUtil.class);

	/**地球半径6378.137,单位为千米 */
	private static double EARTH_RADIUS = 6378.137;
	/**圆周率PI */
	private static double PI = Math.PI;
	/**卫星椭球坐标投影到平面地图坐标系的投影因子*/
	private static double AXIS = 6378245.0;
	/**椭球的偏心率(a^2 - b^2) / a^2 */
	private static double OFFSET = 0.00669342162296594323;
	/**圆周率转换量*/
	private static double X_PI = PI * 3000.0 / 180.0;

	/**
	 * <p>
	 * getMiles方法主要用于-从一系列坐标轨迹中计算行驶距离.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:00:16
	 * </p>
	 * @param zbList 坐标集合(至少两个坐标,且为有效坐标点)
	 * @return double 单位 KM
	 */
	public static double getMiles(List<ZuoBiao> zbList) {
		double miles = 0;
		if (zbList != null && zbList.size() > 1) {
			log.debug("计算开始,坐标量:" + zbList.size());
			ZuoBiao zb1 = zbList.get(0);
			for (int i = 1, len = zbList.size(); i < len; i++) {
				ZuoBiao zb2 = zbList.get(i);
				miles += getDistance1(zb1.getLat(), zb1.getLng(), zb2.getLat(), zb2.getLng());
				zb1 = zb2;
			}
			log.debug("计算完毕");
		}
		return miles;
	}

	/**
	 * 计算两坐标点间的距离
	 * @param lat1 坐标1维度
	 * @param lng1 坐标1经度
	 * @param lat2 坐标2维度
	 * @param lng2 坐标2经度
	 * @return double 单位 KM
	 */
	public static double getDistance1(double lat1, double lng1, double lat2, double lng2) {
		double radLat1 = rad(lat1);
		double radLat2 = rad(lat2);
		double a = radLat1 - radLat2;
		double b = rad(lng1) - rad(lng2);
		double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
		s = s * EARTH_RADIUS;
		// s = Math.round(s * 10000) / 10000;
		// s = Math.round(s * 10000d) / 10000d;

		return s;
	}
	
	/**
	 * <p>
	 * distance方法主要用于-计算两坐标点间的距离.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:35:16
	 * </p>
	 * @param latA A点维度
	 * @param logA A点经度
	 * @param latB B点维度
	 * @param logB B点经度
	 * @return double 单位??
	 */
	public static double getDistance2(double latA, double logA, double latB, double logB) {
		int earthR = 6371000;
		double x = Math.cos(latA * Math.PI / 180) * Math.cos(latB * Math.PI / 180) * Math.cos((logA - logB) * Math.PI / 180);
		double y = Math.sin(latA * Math.PI / 180) * Math.sin(latB * Math.PI / 180);
		double s = x + y;
		if (s > 1) {
			s = 1;
		}
		if (s < -1) {
			s = -1;
		}
		double alpha = Math.acos(s);
		double distance = alpha * earthR;
		return distance;
	}

	private static double rad(double d) {
		return d * PI / 180.0;
	}

	/**
	 * <p>
	 * gcj02_bd09方法主要用于-GCJ02坐标转换为百度09坐标.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:06:44
	 * </p>
	 * @param lat gcj02维度
	 * @param lng gcj02经度
	 * @return latlng[] 经纬数组(bd09)
	 */
	public static double[] gcj02_bd09(double lat, double lng) {
		double x = lng;
		double y = lat;
		double[] latlng = new double[2];
		double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * X_PI);
		double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * X_PI);
		latlng[0] = z * Math.sin(theta) + 0.006;
		latlng[1] = z * Math.cos(theta) + 0.0065;
		return latlng;
	}

	/**
	 * <p>
	 * bd09_gcj02方法主要用于-百度09坐标转转换为GCJ02坐标.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:19:02
	 * </p>
	 * @param lat bd09维度
	 * @param lng bd09经度
	 * @return latlng[] 维经数组(gcj02)
	 */
	public static double[] bd09_gcj02(double lat, double lng) {
		double x = lng - 0.0065;
		double y = lat - 0.006;
		double[] latlng = new double[2];
		double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI);
		double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI);
		latlng[0] = z * Math.sin(theta);
		latlng[1] = z * Math.cos(theta);
		return latlng;
	}

	/**
	 * <p>
	 * bd09_wgs84方法主要用于-BD09坐标转为WGS84(地球坐标系).
	 * <br>转换过程:bd09->gcj02->wgs84.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:23:15
	 * </p>
	 * @param lat 维度 (bd09)
	 * @param lng 经度(bd09)
	 * @return latlng[] 维经数组(wgs84)
	 */
	public static double[] bd09_wgs84(double lat, double lng) {
		double[] latlng = bd09_gcj02(lat, lng);
		return gcj02_wgs84_1(latlng[0], latlng[1]);
	}

	/**
	 * <p>
	 * wgs84_bd09方法主要用于-wgs84地球坐标换转为百度09坐标.
	 * <br>转换过程:wgs84->gcj02->bd09.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:29:08
	 * </p>
	 * @param lat 维度(wgs84)
	 * @param lng 经度(wgs84)
	 * @return latlng[] 维经数组(bd09)
	 */
	public static double[] wgs84_bd09(double lat, double lng) {
		double[] latlon = wgs84_gcj02(lat, lng);
		return gcj02_bd09(latlon[0], latlon[1]);
	}

	/**
	 * <p>
	 * wgs84_gcj02方法主要用于-wgs84地球坐标转换为gcj02.
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:29:45
	 * </p>
	 * @param lat 维度(wgs84)
	 * @param lng 经度(wgs84)
	 * @return latlng[] 维经数组(gcj-02)
	 */
	public static double[] wgs84_gcj02(double lat, double lng) {
		double[] latlon = new double[2];
		if (outOfChina(lat, lng)) {
			latlon[0] = lat;
			latlon[1] = lng;
			return latlon;
		}
		double[] deltaD = transform(lat, lng);
		latlon[0] = lat + deltaD[0];
		latlon[1] = lng + deltaD[1];
		return latlon;
	}

	/**
	 * <p>
	 * gcj02_wgs84_1方法主要用于-gcj02坐标转为地球坐标wgs84(粗放).
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:30:20
	 * </p>
	 * @param lat 维度(gcj02)
	 * @param lng 经度(gcj02)
	 * @return latlng[] 维经数组(wgs84)
	 */
	public static double[] gcj02_wgs84_1(double lat, double lng) {
		double[] latlon = new double[2];
		if (outOfChina(lat, lng)) {
			latlon[0] = lat;
			latlon[1] = lng;
			return latlon;
		}
		double[] deltaD = transform(lat, lng);
		latlon[0] = lat - deltaD[0];
		latlon[1] = lng - deltaD[1];
		return latlon;
	}

	/**
	 * <p>
	 * gcj02_wgs84_2方法主要用于-gcj02坐标转为地球坐标wgs84(精确).
	 * </p>
	 * <p>
	 * 城邑耕夫 2016-11-21 - 下午6:30:51
	 * </p>
	 * @param lat 维度(gcj02)
	 * @param lng 经度(gcj02)
	 * @return latlng[] 维经数组(wgs84)
	 */
	public static double[] gcj02_wgs84_2(double lat, double lng) {
		double initDelta = 0.01;
		double threshold = 0.000000001;
		double dLat = initDelta, dLon = initDelta;
		double mLat = lat - dLat, mLon = lng - dLon;
		double pLat = lat + dLat, pLon = lng + dLon;
		double wgsLat, wgsLon, i = 0;
		while (true) {
			wgsLat = (mLat + pLat) / 2;
			wgsLon = (mLon + pLon) / 2;
			double[] tmp = wgs84_gcj02(wgsLat, wgsLon);
			dLat = tmp[0] - lat;
			dLon = tmp[1] - lng;
			if ((Math.abs(dLat) < threshold) && (Math.abs(dLon) < threshold)) {
				break;
			}
			if (dLat > 0) {
				pLat = wgsLat;
			} else {
				mLat = wgsLat;
			}
			if (dLon > 0) {
				pLon = wgsLon;
			} else {
				mLon = wgsLon;
			}
			if (++i > 10000) {
				break;
			}
		}
		double[] latlon = new double[2];
		latlon[0] = wgsLat;
		latlon[1] = wgsLon;
		return latlon;
	}

	/**
	 * <p>
	 * transform方法主要用于-wgs84与gcj02的坐标转换.
	 * </p>
	 * <p>
	 * 山河戀夢 2016-11-21 - 下午8:06:56
	 * </p>
	 * @param lat 维度
	 * @param lng 经度
	 * @return double[] 两坐标系间的偏移
	 */
	public static double[] transform(double lat, double lng) {
		double[] latlng = new double[2];
		double dLat = transformLat(lng - 105.0, lat - 35.0);
		double dLon = transformLng(lng - 105.0, lat - 35.0);
		double radLat = lat / 180.0 * PI;
		double magic = Math.sin(radLat);
		magic = 1 - OFFSET * magic * magic;
		double sqrtMagic = Math.sqrt(magic);
		dLat = (dLat * 180.0) / ((AXIS * (1 - OFFSET)) / (magic * sqrtMagic) * PI);
		dLon = (dLon * 180.0) / (AXIS / sqrtMagic * Math.cos(radLat) * PI);
		latlng[0] = dLat;
		latlng[1] = dLon;
		return latlng;
	}

	public 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 * Math.sqrt(Math.abs(x));
		ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
		ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0;
		ret += (160.0 * Math.sin(y / 12.0 * PI) + 320 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0;
		return ret;
	}

	public static double transformLng(double x, double y) {
		double 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 * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0;
		ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0;
		ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0;
		return ret;
	}
	
	public static boolean outOfChina(double lat, double lon) {
		if (lon < 72.004 || lon > 137.8347) {
			return true;
		}
		if (lat < 0.8293 || lat > 55.8271) {
			return true;
		}
		return false;
	}

	public static void main(String[] args) {
		double dis = getDistance1(31.868278, 106.757043, 31.868245, 106.757106);
		System.out.println(dis);
	}
}

 

© 著作权归作者所有

城邑耕夫
粉丝 92
博文 137
码字总数 104811
作品 0
成都
程序员
私信 提问
GPS位置地图坐标系:WGS-84(GPS)、GCJ-02(Google地图)、BD-09(百度地图),OpenGIS

国家规定,中国大陆所有公开地理数据都需要至少用GCJ-02进行加密,也就是说我们从国内公司的产品中得到的数据,一定是经过了加密的。绝大部分国内互联网地图提供商都是使用GCJ-02坐标系,包括...

desaco
01/29
0
0
Android LBS地图开发:地球地理GPS坐标系经纬度偏移偏差

本文作者:ZhangPhil 欢迎各位转载!但请注明转载出处: http://blog.csdn.net/zhangphil/article/details/48024831 Android LBS地图开发基础知识之地球地理GPS坐标系经纬度偏移偏差 通常...

开开心心过
2015/08/27
0
0
iOS开发中的火星坐标系及各种坐标系转换算法

其原理是这样的:保密局开发了一个系统,能将实际的坐标转换成虚拟的坐标。所有在中国销售的数字地图必须使用这个系统进行坐标转换之后方可上市。这是生产环节,这种电子地图被称为火星地图。...

刀客445
2016/02/23
1K
1
DLA新增函数发布:身份证、星座、拼音、Json数组、经纬度城市查询、互联网坐标系经纬度转换等

概述 本月,Data Lake Analytics(https://et.aliyun.com/bdad/datalake)发布了一批新增的内置函数,涉及身份证地域查询、星座查询、中文拼音转换、Json数组内容字段提取、经纬度城市查询、...

javainthinking
06/21
0
0
【高德地图API】从零开始学高德JS API(六)坐标转换

摘要:如何从GPS转到谷歌?如何从百度转到高德?这些都是小case。我们还提供,如何将基站cell_id转换为GPS坐标? ----------------------------------------------------------------------...

酸奶小妹GIS
2014/06/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
5
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
昨天
6
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
7
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部