关于LBS坐标系与精度的问题
来源:互联网 发布:eai 数据黑盒 编辑:程序博客网 时间:2024/06/03 12:30
关于LBS坐标系与精度的问题
@(JAVA)[java]
大部分内容来源于:
http://www.jianshu.com/p/f8224779ca63
(一)坐标系问题
App定位遇到的第一个坑是坐标系问题。目前常见的坐标系有三种:地球坐标(WGS84,国际公认坐标),火星坐标(GCJ02,国家标准,适用于高德百度地图大陆+港澳部分、Google地图大陆部分),百度坐标(BD09,适用于百度地图大陆+港澳台部分)。坐标系需要和地图关连才有意义,只有正确匹配地图坐标系的坐标才能在该地图上完美标识位置,否则就会存在偏移。另外对于旅行类App而言,经常需要根据用户当前位置查询周围酒店或者其他POI信息,并且按距离排序,如果坐标系不匹配,就会由于坐标系偏移产生排序问题。
iOS系统上通过定位服务CLLocation相关接口获取定位信息时,获取的经纬度坐标系是WGS84地球坐标,如果直接将该坐标系在iOS系统地图中打点,会发现存在偏移,因为iOS系统地图查看国内时使用的是高德地图数据(这里有另一个坑,详见下文),因此只接受GCJ02火星坐标。如果使用高德或者百度iOS定位SDK中的接口,是可以直接获得火星偏移后的坐标的,由于App Size问题,携程App没有集成第三方SDK,而是通过近似偏移算法直接做偏移(自行Google『transform From WGS To GCJ』)。然而如果在iOS系统地图中获取当前位置,同时在国内,那么获取到的坐标系直接是GCJ02火星坐标系,这点需要小心。
Android系统上通常使用高德或者百度定位SDK获取定位信息。高德SDK没有坐标系参数设定,在大陆和港澳地区获取的坐标系即为GCJ02坐标系,在台湾和海外地区都是WGS84坐标系;百度SDK可以自行设定坐标系参数,即返回WGS84坐标系,还是GCJ02坐标系或者BD09坐标系(注意BD09坐标系只适用于百度地图),如果设定的是GCJ02坐标系,它在大陆+港澳台地区获取的坐标系都是GCJ02坐标系。
海外地图(非大陆和非港澳台地区)是没有火星坐标或者百度坐标之说,都是标准的WGS84地球坐标系。
基本分析结论:【以下结果是最吻合事实的情况,但未完全确认】
1、通过百度抓取到的经纬度都是百度坐标系。
2、ADX发送过来的经纬度是火星坐标系。
因此做法是:将百度抓取过来的数据转为火星坐标系,然后用二者匹配(即数据预处理时多加一步即可)
(二)精度问题
第二个常见的坑是定位精度问题,经常有用户或者Boss反馈,为什么两台一样的手机,获取的当前位置不一样?我明明在这个位置,为什么定位却显示在附件另一个位置,相差那么远?
这类问题的根源是手机不同定位方式导致的,通常手机定位方式有三种:
GPS:根据系统GPS模块获取经纬度,精度10-100米左右,限制是容易受环境影响,在室内几乎不起作用。
基站:根据运营商基站位置计算经纬度,精度1000-3000米左右,限制是定位较慢,精度差。
WIFI:根据周围WIFI路由器位置计算经纬度,精度100-200米左右,限制是受周围WIFI数量和分布影响,需要打开手机WIFI开关。
(三)坐标系变换代码
完整代码请见:https://github.com/lujinhong/lujinhong-commons/blob/master/lujinhong-commons-java/src/main/java/com/lujinhong/commons/java/lbs/LBSTransformer2.java
package com.lujinhong.commons.java.lbs;import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.util.Scanner;/** * AUTHOR: LUJINHONG * CREATED ON: 17/2/21 22:10 * PROJECT NAME: aplus_dmp * DESCRIPTION: 百度坐标系、火星坐标系、地球坐标系之间互相转换。 */public class LBSTransformer2 { private static final double LAT_OFFSET_0(double x, double y) { return -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); } private static final double LAT_OFFSET_1(double x, double y) { return (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; } private static final double LAT_OFFSET_2(double x, double y) { return (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0; } private static final double LAT_OFFSET_3(double x, double y) { return (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0; } private static final double LON_OFFSET_0(double x, double y) { return 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); } private static final double LON_OFFSET_1(double x, double y) { return (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0; } private static final double LON_OFFSET_2(double x, double y) { return (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0; } private static final double LON_OFFSET_3(double x, double y) { return (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0; } private static double RANGE_LON_MAX = 137.8347; private static double RANGE_LON_MIN = 72.004; private static double RANGE_LAT_MAX = 55.8271; private static double RANGE_LAT_MIN = 0.8293; private static double jzA = 6378245.0; private static double jzEE = 0.00669342162296594323; public static double transformLat(double x, double y) { double ret = LAT_OFFSET_0(x, y); ret += LAT_OFFSET_1(x, y); ret += LAT_OFFSET_2(x, y); ret += LAT_OFFSET_3(x, y); return ret; } public static double transformLon(double x, double y) { double ret = LON_OFFSET_0(x, y); ret += LON_OFFSET_1(x, y); ret += LON_OFFSET_2(x, y); ret += LON_OFFSET_3(x, y); return ret; } public static boolean outOfChina(double lat, double lon) { if (lon < RANGE_LON_MIN || lon > RANGE_LON_MAX) return true; if (lat < RANGE_LAT_MIN || lat > RANGE_LAT_MAX) return true; return false; } public static LatLng gcj02Encrypt(double ggLat, double ggLon) { LatLng resPoint = new LatLng(); double mgLat; double mgLon; if (outOfChina(ggLat, ggLon)) { resPoint.latitude = ggLat; resPoint.longitude = ggLon; return resPoint; } double dLat = transformLat(ggLon - 105.0, ggLat - 35.0); double dLon = transformLon(ggLon - 105.0, ggLat - 35.0); double radLat = ggLat / 180.0 * Math.PI; double magic = Math.sin(radLat); magic = 1 - jzEE * magic * magic; double sqrtMagic = Math.sqrt(magic); dLat = (dLat * 180.0) / ((jzA * (1 - jzEE)) / (magic * sqrtMagic) * Math.PI); dLon = (dLon * 180.0) / (jzA / sqrtMagic * Math.cos(radLat) * Math.PI); mgLat = ggLat + dLat; mgLon = ggLon + dLon; resPoint.latitude = mgLat; resPoint.longitude = mgLon; return resPoint; } public static LatLng gcj02Decrypt(double gjLat, double gjLon) { LatLng gPt = gcj02Encrypt(gjLat, gjLon); double dLon = gPt.longitude - gjLon; double dLat = gPt.latitude - gjLat; LatLng pt = new LatLng(); pt.latitude = gjLat - dLat; pt.longitude = gjLon - dLon; return pt; } public static LatLng bd09Decrypt(double bdLat, double bdLon) { LatLng gcjPt = new LatLng(); double x = bdLon - 0.0065, y = bdLat - 0.006; double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * Math.PI); double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * Math.PI); gcjPt.longitude = z * Math.cos(theta); gcjPt.latitude = z * Math.sin(theta); return gcjPt; } public static LatLng bd09Encrypt(double ggLat, double ggLon) { LatLng bdPt = new LatLng(); double x = ggLon, y = ggLat; double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * Math.PI); double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * Math.PI); bdPt.longitude = z * Math.cos(theta) + 0.0065; bdPt.latitude = z * Math.sin(theta) + 0.006; return bdPt; } /** * @param location 世界标准地理坐标(WGS-84) * @return 中国国测局地理坐标(GCJ-02)<火星坐标> * @brief 世界标准地理坐标(WGS-84) 转换成 中国国测局地理坐标(GCJ-02)<火星坐标> * <p> * ####只在中国大陆的范围的坐标有效,以外直接返回世界标准坐标 */ public static LatLng wgs84ToGcj02(LatLng location) { return gcj02Encrypt(location.latitude, location.longitude); } /** * @param location 中国国测局地理坐标(GCJ-02) * @return 世界标准地理坐标(WGS-84) * @brief 中国国测局地理坐标(GCJ-02) 转换成 世界标准地理坐标(WGS-84) * <p> * ####此接口有1-2米左右的误差,需要精确定位情景慎用 */ public static LatLng gcj02ToWgs84(LatLng location) { return gcj02Decrypt(location.latitude, location.longitude); } /** * @param location 世界标准地理坐标(WGS-84) * @return 百度地理坐标(BD-09) * @brief 世界标准地理坐标(WGS-84) 转换成 百度地理坐标(BD-09) */ public static LatLng wgs84ToBd09(LatLng location) { LatLng gcj02Pt = gcj02Encrypt(location.latitude, location.longitude); return bd09Encrypt(gcj02Pt.latitude, gcj02Pt.longitude); } /** * @param location 中国国测局地理坐标(GCJ-02)<火星坐标> * @return 百度地理坐标(BD-09) * @brief 中国国测局地理坐标(GCJ-02)<火星坐标> 转换成 百度地理坐标(BD-09) */ public static LatLng gcj02ToBd09(LatLng location) { return bd09Encrypt(location.latitude, location.longitude); } /** * @param location 百度地理坐标(BD-09) * @return 中国国测局地理坐标(GCJ-02)<火星坐标> * @brief 百度地理坐标(BD-09) 转换成 中国国测局地理坐标(GCJ-02)<火星坐标> */ public static LatLng bd09ToGcj02(LatLng location) { return bd09Decrypt(location.latitude, location.longitude); } /** * @param location 百度地理坐标(BD-09) * @return 世界标准地理坐标(WGS-84) * @brief 百度地理坐标(BD-09) 转换成 世界标准地理坐标(WGS-84) * <p> * ####此接口有1-2米左右的误差,需要精确定位情景慎用 */ public static LatLng bd09ToWgs84(LatLng location) { LatLng gcj02 = bd09ToGcj02(location); return gcj02Decrypt(gcj02.latitude, gcj02.longitude); } public static class LatLng { public double latitude; public double longitude; public LatLng(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; } public LatLng() { } public double getLatitude() { return latitude; } public void setLatitude(double latitude) { this.latitude = latitude; } public double getLongitude() { return longitude; } public void setLongitude(double longitude) { this.longitude = longitude; } } private static final String TAB_SEPERATOR = "\t"; public static void main(String[] args) throws IOException { double lat = 23.117500452966457; double lng = 113.4247365; LatLng ll = null; //假设是世界坐标系 ll = wgs84ToBd09(new LatLng(lat, lng)); System.out.println(ll.getLatitude() + "\t" + ll.getLongitude()); ll = wgs84ToGcj02(new LatLng(lat, lng)); System.out.println(ll.getLatitude() + "\t" + ll.getLongitude()); //假设是火星坐标系 ll = gcj02ToWgs84(new LatLng(lat, lng)); System.out.println(ll.getLatitude() + "\t" + ll.getLongitude()); ll = gcj02ToBd09(new LatLng(lat, lng)); System.out.println(ll.getLatitude() + "\t" + ll.getLongitude()); //假设是百度 ll = bd09ToWgs84(new LatLng(lat, lng)); System.out.println(ll.getLatitude() + "\t" + ll.getLongitude()); ll = bd09ToGcj02(new LatLng(lat, lng)); System.out.println(ll.getLatitude() + "\t" + ll.getLongitude()); }}
- 关于LBS坐标系与精度的问题
- 关于LBS坐标系与精度的数据
- 关于float与double的精度问题
- 关于数据精度的问题
- 关于精度计算的问题
- 关于84坐标系与54坐标系转换问题
- 关于双精度与单精度double,float小数尾数的问题
- 关于Coco2dx中锚点的问题,游戏坐标系,节点坐标系
- 关于坐标系转换与点在坐标系之间的转换
- 关于matlab的单精度与双精度
- 关于ArcGIS中的坐标系的问题
- 关于android定位的坐标系问题
- 关于android定位的坐标系问题
- 关于float和double的精度问题
- 关于float, double的精度问题
- 关于 Redis Double的精度问题
- 关于计算机精度的一个小问题
- 关于js number 的精度问题
- JS数组的map方法
- php不同版本htmlspecialchars函数过滤GBK编码时中文为空
- ThinkPHP自定义错误页面、成功页面及异常页面
- 监听界面左右滑动
- zabbix_server代码分析--多进程创建
- 关于LBS坐标系与精度的问题
- Linux线程的本质
- pl/sql developer 程序编译后的结果没有显示出来
- 博客不再更新
- JSP基础
- 观察者模式的生动理解
- 优化UITableViewCell高度计算的那些事
- System.IO.File.Create 不会自动释放,一定要Dispose
- 每日一则JavaWeb---实验分析Servlet、Filter 和 Listener 调用顺序、生命周期