最近有一个项目和第三方厂家对接的时候,反馈我方的点位数据在对方的地图上存在偏移情况,这时想到了可能双方的坐标系不一致导致的,沟通后了解对方使用的腾讯的地图,即火星坐标系,而我方使用的是wgs84坐标系。于是打算写一个坐标系转换接口,验证转换后坐标是否还存在偏移。
网上有一些坐标系转换的算法存在偏移比较大的情况,后来参考了红领巾1994的文章:https://www.cnblogs.com/giserjobs/p/12291291.html里的算法,经过测试,wgs84转换gcj-02基本没有偏移,gcj-02转换bd09偏移也很小。
整个接口逻辑简单,核心功能就是坐标系转换算法,于是使用flask单例模式来构建,代码都写在一个文件里,代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Yujichang
from flask import Flask, request, jsonify
from flask_cors import CORS
import math
app = Flask(__name__)
CORS(app)
class ChangeCoordinate(object):
"""坐标转换函数"""
def __init__(self):
self.PI = 3.1415926535897932384626
self.x_PI = 3.14159265358979324 * 3000.0 / 180.0
self.a = 6378245.0
self.ee = 0.00669342162296594323
def trans_form_lat(self, lng, lat):
ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(abs(lng))
ret += (20.0 * math.sin(6.0 * lng * self.PI) + 20.0 * math.sin(2.0 * lng * self.PI)) * 2.0 / 3.0
ret += (20.0 * math.sin(lat * self.PI) + 40.0 * math.sin(lat / 3.0 * self.PI)) * 2.0 / 3.0
ret += (160.0 * math.sin(lat / 12.0 * self.PI) + 320 * math.sin(lat * self.PI / 30.0)) * 2.0 / 3.0
return ret
def trans_form_lng(self, lng, lat):
ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(abs(lng))
ret += (20.0 * math.sin(6.0 * lng * self.PI) + 20.0 * math.sin(2.0 * lng * self.PI)) * 2.0 / 3.0
ret += (20.0 * math.sin(lng * self.PI) + 40.0 * math.sin(lng / 3.0 * self.PI)) * 2.0 / 3.0
ret += (150.0 * math.sin(lng / 12.0 * self.PI) + 300.0 * math.sin(lng / 30.0 * self.PI)) * 2.0 / 3.0
return ret
def trans_form_wgs84_gcj02(self, lng, lat):
dlat = self.trans_form_lat(lng - 105.0, lat - 35.0)
dlng = self.trans_form_lng(lng - 105.0, lat - 35.0)
radlat = lat / 180.0 * self.PI
magic = math.sin(radlat)
magic = 1 - self.ee * magic * magic
sqrt_magic = math.sqrt(magic)
dlat = (dlat * 180.0) / ((self.a * (1 - self.ee)) / (magic * sqrt_magic) * self.PI)
dlng = (dlng * 180.0) / (self.a / sqrt_magic * math.cos(radlat) * self.PI)
mg_lat = lat + dlat
mg_lng = lng + dlng
return mg_lng, mg_lat
def bd09_to_gcj02(self, bd_lng, bd_lat):
"""
百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
百度转谷歌、高德、腾讯
:param bd_lng: 经度
:param bd_lat: 纬度
:return: 转换后经纬度
"""
x = bd_lng - 0.0065
y = bd_lat - 0.006
z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * self.x_PI)
theta = math.atan2(y, x) - 0.000003 * math.cos(x * self.x_PI)
gg_lng = z * math.cos(theta)
gg_lat = z * math.sin(theta)
return gg_lng, gg_lat
def gcj02_to_bd09(self, gg_lng, gg_lat):
"""
火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
谷歌、高德、腾讯转百度
:param gg_lng: 经度
:param gg_lat: 纬度
:return: 转换后经纬度
"""
x = gg_lng
y = gg_lat
z = math.sqrt(x * x + y * y) + 0.00002 * math.sin(y * self.x_PI)
theta = math.atan2(y, x) + 0.000003 * math.cos(x * self.x_PI)
bd_lon = z * math.cos(theta) + 0.0065
bd_lat = z * math.sin(theta) + 0.006
return bd_lon, bd_lat
def wgs84_to_gcj02(self, lng, lat):
"""
wgs84坐标系 与 火星坐标系 (GCJ-02)的转换
:param lng: 经度
:param lat: 纬度
:return: 转换后经纬度
"""
gg_lng, gg_lat = self.trans_form_wgs84_gcj02(lng, lat)
return gg_lng, gg_lat
def gcj02_to_wgs84(self, gg_lng, gg_lat):
"""
火星坐标系 (GCJ-02) 与 wgs84坐标系的转换
:param gg_lng:经度
:param gg_lat:纬度
:return:转换后经纬度
"""
lng, lat = self.trans_form_wgs84_gcj02(gg_lng, gg_lat)
return (gg_lng * 2 - lng), (gg_lat * 2 - lat)
def bd09_to_wgs84(self, bd_lng, bd_lat):
"""
百度坐标系 (BD-09) 与 wgs84坐标系的转换
:param bd_lng: 经度
:param bd_lat: 纬度
:return: 转换后经纬度
"""
gg_lng, gg_lat = self.bd09_to_gcj02(bd_lng, bd_lat)
lng, lat = self.gcj02_to_wgs84(gg_lng, gg_lat)
return lng, lat
def wgs84_to_bd09(self, lng, lat):
"""
wgs84坐标系 与 百度坐标系 (BD-09)的转换
:param lng: 经度
:param lat: 纬度
:return: 转换后经纬度
"""
gg_lng, gg_lat = self.wgs84_to_gcj02(lng, lat)
bd_lng, bd_lat = self.gcj02_to_bd09(gg_lng, gg_lat)
return bd_lng, bd_lat
cc = ChangeCoordinate()
coordinate_type = {
"0": "wgs84",
"1": "gcj02",
"2": "bd09"
}
methods = {
"01": cc.wgs84_to_gcj02,
"02": cc.wgs84_to_bd09,
"10": cc.gcj02_to_wgs84,
"12": cc.gcj02_to_bd09,
"20": cc.bd09_to_wgs84,
"21": cc.bd09_to_gcj02
}
@app.route('/api/v1/changes', methods=['GET'])
def change_coordinate():
src = request.args.get('from', default='-1')
tar = request.args.get('to', default='-1')
lng = request.args.get('longitude')
lat = request.args.get('latitude')
# 经纬度不能为空,否则返回错误
if lng is None or lat is None:
res = {'code': 0, 'msg': 'Invalid latitude and longitude', 'data': None}
return jsonify(res)
# 获取坐标转换函数对应的方法,执行转换
_method = methods.get(src + tar)
if _method:
geo = _method(float(lng), float(lat))
resp_data = {
'coordinate': coordinate_type[tar],
'longitude': geo[0],
'latitude': geo[1]
}
code = 1
msg = 'Conversion coordinate system successful'
else:
code = 0
msg = 'No conversion function found'
resp_data = None
res = {'code': code, 'msg': msg, 'data': resp_data}
return jsonify(res)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
接口提供了一个GET方法,提供经纬度、原坐标系和转换坐标系4个参数,如:
curl "http://192.168.100.1:8080/api/v1/changes?longitude=83.9155&latitude=46.6025&from=0&to=1"
{
"code":1,
"data":
{
"coordinate":"gcj02",
"latitude":46.6030717506088,
"longitude":83.91832849509086
},
"msg":"Conversion coordinate system successful"
}