├── .gitignore ├── LICENSE ├── README.md ├── coordTransform_utils.py ├── coord_converter.py └── test_input.csv /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | .idea 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 WangMing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 坐标转换模块 2 | 3 | 此模块用于百度坐标系(bd-09)、火星坐标系(国测局坐标系、gcj02)、WGS84坐标系的相互转换,并提供中文地址到坐标的转换功能,仅使用Python标准模块,无其他依赖。 4 | 5 | 中文地址到坐标转换使用高德地图API,需要[申请](http://lbs.amap.com/)API KEY。 6 | 7 | 需要js版本可以移步[coordtransform](https://github.com/wandergis/coordtransform) 8 | 9 | ## 使用说明(coordTransform_utils.py) 10 | 11 | ### 方法说明 12 | 13 | ```bash 14 | # 方法说明 15 | gcj02_to_bd09(lng, lat) # 火星坐标系->百度坐标系 16 | bd09_to_gcj02(lng, lat) # 百度坐标系->火星坐标系 17 | wgs84_to_gcj02(lng, lat) # WGS84坐标系->火星坐标系 18 | gcj02_to_wgs84(lng, lat) # 火星坐标系->WGS84坐标系 19 | bd09_to_wgs84(lng, lat) # 百度坐标系->WGS84坐标系 20 | wgs84_to_bd09(lng, lat) # WGS84坐标系->百度坐标系 21 | 22 | # 中文地址到火星坐标系, 需要高德地图API Key 23 | g = Geocoding('API_KEY') # 这里填写你的高德Api_Key 24 | g.geocode('北京市朝阳区朝阳公园') 25 | ``` 26 | 27 | ### 测试 28 | 29 | ```bash 30 | # 测试(转换坐标 128.543,37.065 ) 31 | $ python coordTransform_utils.py 32 | [128.54944656269413, 37.07113427883019] [128.5365893261212, 37.058754503281534] [128.54820547949757, 37.065651049489816] [128.53779452050244, 37.06434895051018] [128.53136876750008, 37.0580926428705] [128.55468192918485, 37.07168344938498] None 33 | ``` 34 | 35 | ## 批量转换csv文件使用说明(coord_converter.py) 36 | 37 | ### 使用说明: 38 | 39 | ```bash 40 | # 查看使用帮助 41 | $ python coord_converter.py -h 42 | 43 | usage: coord_converter.py [-h] -i INPUT -o OUTPUT -t TYPE [-n LNG_COLUMN] [-a LAT_COLUMN] [-s SKIP_INVALID_ROW] 44 | 45 | Convert coordinates in csv files. 46 | 47 | optional arguments: 48 | -h, --help show this help message and exit 49 | 50 | arguments: 51 | -i , --input Location of input file 52 | -o , --output Location of output file 53 | -t , --type Convert type, must be one of: g2b, b2g, w2g, g2w, b2w, 54 | w2b 55 | -n , --lng_column Column name for longitude (default: lng) 56 | -a , --lat_column Column name for latitude (default: lat) 57 | -s , --skip_invalid_row 58 | Whether to skip invalid row (default: False) 59 | ``` 60 | 61 | ### 示例 62 | 63 | ```bash 64 | # 不指定经纬度列名(默认为'lng', 'lat') 65 | $ python coord_converter.py -i test_input.csv -o test_output.csv -t b2g 66 | 67 | # 指定经纬度列名 68 | $ python coord_converter.py -i test_input.csv -o test_output.csv -t b2g -n 经度 -a 纬度 69 | 70 | # 跳过无效经纬度的行(默认不跳过) 71 | $ python coord_converter.py -i test_input.csv -o test_output.csv -t b2g -n 经度 -a 纬度 -s True 72 | ``` 73 | -------------------------------------------------------------------------------- /coordTransform_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import urllib 4 | import math 5 | 6 | x_pi = 3.14159265358979324 * 3000.0 / 180.0 7 | pi = 3.1415926535897932384626 # π 8 | a = 6378245.0 # 长半轴 9 | ee = 0.00669342162296594323 # 偏心率平方 10 | 11 | 12 | class Geocoding: 13 | def __init__(self, api_key): 14 | self.api_key = api_key 15 | 16 | def geocode(self, address): 17 | """ 18 | 利用高德geocoding服务解析地址获取位置坐标 19 | :param address:需要解析的地址 20 | :return: 21 | """ 22 | geocoding = {'s': 'rsv3', 23 | 'key': self.api_key, 24 | 'city': '全国', 25 | 'address': address} 26 | geocoding = urllib.urlencode(geocoding) 27 | ret = urllib.urlopen("%s?%s" % ("http://restapi.amap.com/v3/geocode/geo", geocoding)) 28 | 29 | if ret.getcode() == 200: 30 | res = ret.read() 31 | json_obj = json.loads(res) 32 | if json_obj['status'] == '1' and int(json_obj['count']) >= 1: 33 | geocodes = json_obj['geocodes'][0] 34 | lng = float(geocodes.get('location').split(',')[0]) 35 | lat = float(geocodes.get('location').split(',')[1]) 36 | return [lng, lat] 37 | else: 38 | return None 39 | else: 40 | return None 41 | 42 | 43 | def gcj02_to_bd09(lng, lat): 44 | """ 45 | 火星坐标系(GCJ-02)转百度坐标系(BD-09) 46 | 谷歌、高德——>百度 47 | :param lng:火星坐标经度 48 | :param lat:火星坐标纬度 49 | :return: 50 | """ 51 | z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi) 52 | theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi) 53 | bd_lng = z * math.cos(theta) + 0.0065 54 | bd_lat = z * math.sin(theta) + 0.006 55 | return [bd_lng, bd_lat] 56 | 57 | 58 | def bd09_to_gcj02(bd_lon, bd_lat): 59 | """ 60 | 百度坐标系(BD-09)转火星坐标系(GCJ-02) 61 | 百度——>谷歌、高德 62 | :param bd_lat:百度坐标纬度 63 | :param bd_lon:百度坐标经度 64 | :return:转换后的坐标列表形式 65 | """ 66 | x = bd_lon - 0.0065 67 | y = bd_lat - 0.006 68 | z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi) 69 | theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi) 70 | gg_lng = z * math.cos(theta) 71 | gg_lat = z * math.sin(theta) 72 | return [gg_lng, gg_lat] 73 | 74 | 75 | def wgs84_to_gcj02(lng, lat): 76 | """ 77 | WGS84转GCJ02(火星坐标系) 78 | :param lng:WGS84坐标系的经度 79 | :param lat:WGS84坐标系的纬度 80 | :return: 81 | """ 82 | if out_of_china(lng, lat): # 判断是否在国内 83 | return [lng, lat] 84 | dlat = _transformlat(lng - 105.0, lat - 35.0) 85 | dlng = _transformlng(lng - 105.0, lat - 35.0) 86 | radlat = lat / 180.0 * pi 87 | magic = math.sin(radlat) 88 | magic = 1 - ee * magic * magic 89 | sqrtmagic = math.sqrt(magic) 90 | dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) 91 | dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) 92 | mglat = lat + dlat 93 | mglng = lng + dlng 94 | return [mglng, mglat] 95 | 96 | 97 | def gcj02_to_wgs84(lng, lat): 98 | """ 99 | GCJ02(火星坐标系)转GPS84 100 | :param lng:火星坐标系的经度 101 | :param lat:火星坐标系纬度 102 | :return: 103 | """ 104 | if out_of_china(lng, lat): 105 | return [lng, lat] 106 | dlat = _transformlat(lng - 105.0, lat - 35.0) 107 | dlng = _transformlng(lng - 105.0, lat - 35.0) 108 | radlat = lat / 180.0 * pi 109 | magic = math.sin(radlat) 110 | magic = 1 - ee * magic * magic 111 | sqrtmagic = math.sqrt(magic) 112 | dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) 113 | dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) 114 | mglat = lat + dlat 115 | mglng = lng + dlng 116 | return [lng * 2 - mglng, lat * 2 - mglat] 117 | 118 | 119 | def bd09_to_wgs84(bd_lon, bd_lat): 120 | lon, lat = bd09_to_gcj02(bd_lon, bd_lat) 121 | return gcj02_to_wgs84(lon, lat) 122 | 123 | 124 | def wgs84_to_bd09(lon, lat): 125 | lon, lat = wgs84_to_gcj02(lon, lat) 126 | return gcj02_to_bd09(lon, lat) 127 | 128 | 129 | def _transformlat(lng, lat): 130 | ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \ 131 | 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng)) 132 | ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * 133 | math.sin(2.0 * lng * pi)) * 2.0 / 3.0 134 | ret += (20.0 * math.sin(lat * pi) + 40.0 * 135 | math.sin(lat / 3.0 * pi)) * 2.0 / 3.0 136 | ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 * 137 | math.sin(lat * pi / 30.0)) * 2.0 / 3.0 138 | return ret 139 | 140 | 141 | def _transformlng(lng, lat): 142 | ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \ 143 | 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng)) 144 | ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * 145 | math.sin(2.0 * lng * pi)) * 2.0 / 3.0 146 | ret += (20.0 * math.sin(lng * pi) + 40.0 * 147 | math.sin(lng / 3.0 * pi)) * 2.0 / 3.0 148 | ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * 149 | math.sin(lng / 30.0 * pi)) * 2.0 / 3.0 150 | return ret 151 | 152 | 153 | def out_of_china(lng, lat): 154 | """ 155 | 判断是否在国内,不在国内不做偏移 156 | :param lng: 157 | :param lat: 158 | :return: 159 | """ 160 | return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55) 161 | 162 | 163 | if __name__ == '__main__': 164 | lng = 128.543 165 | lat = 37.065 166 | result1 = gcj02_to_bd09(lng, lat) 167 | result2 = bd09_to_gcj02(lng, lat) 168 | result3 = wgs84_to_gcj02(lng, lat) 169 | result4 = gcj02_to_wgs84(lng, lat) 170 | result5 = bd09_to_wgs84(lng, lat) 171 | result6 = wgs84_to_bd09(lng, lat) 172 | 173 | g = Geocoding('API_KEY') # 这里填写你的高德api的key 174 | result7 = g.geocode('北京市朝阳区朝阳公园') 175 | print(result1, result2, result3, result4, result5, result6, result7) 176 | -------------------------------------------------------------------------------- /coord_converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import csv 5 | import sys 6 | import argparse 7 | from coordTransform_utils import gcj02_to_bd09 8 | from coordTransform_utils import bd09_to_gcj02 9 | from coordTransform_utils import wgs84_to_gcj02 10 | from coordTransform_utils import gcj02_to_wgs84 11 | from coordTransform_utils import bd09_to_wgs84 12 | from coordTransform_utils import wgs84_to_bd09 13 | 14 | # Configuration 15 | # Input file name 16 | INPUT = '' 17 | # Output file name 18 | OUTPUT = '' 19 | # Convert type: g2b, b2g, w2g, g2w, b2w, w2b 20 | TYPE = '' 21 | # lng column name 22 | LNG_COLUMN = '' 23 | # lat column name 24 | LAT_COLUMN = '' 25 | # Skip invalid row 26 | SKIP_INVALID_ROW = False 27 | 28 | def convert(): 29 | with open(INPUT, 'r') as input_file: 30 | input_file_reader = csv.reader(input_file) 31 | headers = next(input_file_reader) 32 | lng_index, lat_index = get_lng_lat_index(headers) 33 | results = [] 34 | 35 | for index, row in enumerate(input_file_reader): 36 | result = [] 37 | try: 38 | result = convert_by_type(float(row[lng_index]), float(row[lat_index]), TYPE) 39 | except ValueError: 40 | # Deal with ValueError(invalid lng or lat) 41 | # print(index + 2, row[lng_index], row[lat_index]) # '+ 2' is due to zero-based index and first row is header 42 | result = row[lng_index], row[lat_index] 43 | results.append(result) 44 | 45 | with open(OUTPUT, 'w') as output_file: 46 | output_file_writer = csv.writer(output_file, lineterminator='\n') 47 | 48 | with open(INPUT, 'r') as input_file: 49 | input_file_reader = csv.reader(input_file) 50 | headers = next(input_file_reader) 51 | lng_index, lat_index = get_lng_lat_index(headers) 52 | 53 | output_file_writer.writerow(headers) 54 | for index, row in enumerate(input_file_reader): 55 | row[lng_index] = results[index][0] 56 | row[lat_index] = results[index][1] 57 | if type(row[lng_index]) is not float or type(row[lat_index]) is not float: 58 | # Data is invalid 59 | if SKIP_INVALID_ROW: 60 | # Skip invalid row 61 | pass 62 | else: 63 | # Reserve invalid row 64 | output_file_writer.writerow(row) 65 | else: 66 | # Data is valid 67 | output_file_writer.writerow(row) 68 | 69 | def get_lng_lat_index(headers): 70 | try: 71 | if LNG_COLUMN == '' and LAT_COLUMN == '': 72 | return [headers.index('lng'), headers.index('lat')] 73 | else: 74 | return [headers.index(LNG_COLUMN), headers.index(LAT_COLUMN)] 75 | except ValueError as error: 76 | print('Error: ' + str(error).split('is', 1)[0] + 'is missing from csv header. Or use -n or -a to specify custom column name for lng or lat.') 77 | sys.exit() 78 | 79 | def convert_by_type(lng, lat, type): 80 | if type == 'g2b': 81 | return gcj02_to_bd09(lng, lat) 82 | elif type == 'b2g': 83 | return bd09_to_gcj02(lng, lat) 84 | elif type == 'w2g': 85 | return wgs84_to_gcj02(lng, lat) 86 | elif type == 'g2w': 87 | return gcj02_to_wgs84(lng, lat) 88 | elif type == 'b2w': 89 | return bd09_to_wgs84(lng, lat) 90 | elif type == 'w2b': 91 | return wgs84_to_bd09(lng, lat) 92 | else: 93 | print('Usage: type must be in one of g2b, b2g, w2g, g2w, b2w, w2b') 94 | sys.exit() 95 | 96 | if __name__ == '__main__': 97 | parser = argparse.ArgumentParser(description='Convert coordinates in csv files.', usage='%(prog)s [-h] -i INPUT -o OUTPUT -t TYPE [-n LNG_COLUMN] [-a LAT_COLUMN] [-s SKIP_INVALID_ROW]', formatter_class=argparse.ArgumentDefaultsHelpFormatter) 98 | 99 | group = parser.add_argument_group('arguments') 100 | 101 | group.add_argument('-i', '--input', help='Location of input file', default=argparse.SUPPRESS, metavar='') 102 | group.add_argument('-o', '--output', help='Location of output file', default=argparse.SUPPRESS, metavar='') 103 | group.add_argument('-t', '--type', help='Convert type, must be one of: g2b, b2g, w2g, g2w, b2w, w2b', default=argparse.SUPPRESS, metavar='') 104 | group.add_argument('-n', '--lng_column', help='Column name for longitude', default='lng', metavar='') 105 | group.add_argument('-a', '--lat_column', help='Column name for latitude', default='lat', metavar='') 106 | group.add_argument('-s', '--skip_invalid_row', help='Whether to skip invalid row', default=False, type=bool, metavar='') 107 | 108 | args = parser.parse_args() 109 | # print('\nArguments you provide are:') 110 | # for arg in vars(args): 111 | # print '{0:20} {1}'.format(arg, str(getattr(args, arg))) 112 | 113 | # Get arguments 114 | if not args.input or not args.output or not args.type: 115 | parser.print_help() 116 | else: 117 | INPUT = args.input 118 | OUTPUT = args.output 119 | TYPE = args.type 120 | 121 | if args.lng_column and args.lat_column: 122 | LNG_COLUMN, LAT_COLUMN = args.lng_column, args.lat_column 123 | 124 | if args.skip_invalid_row: 125 | SKIP_INVALID_ROW = args.skip_invalid_row 126 | 127 | convert() 128 | -------------------------------------------------------------------------------- /test_input.csv: -------------------------------------------------------------------------------- 1 | lng,lat,column1 2 | 116.4172,39.93889,value1 3 | 102.72569,25.0444,value2 4 | --------------------------------------------------------------------------------