最近有一个项目和第三方厂家对接的时候,反馈我方的点位数据在对方的地图上存在偏移情况,这时想到了可能双方的坐标系不一致导致的,沟通后了解对方使用的腾讯的地图,即火星坐标系,而我方使用的是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"
}