├── Get_Isochrone_from_gaode.py ├── Get_Isochrone_from_mapbox.py ├── Readme.md ├── converter.py ├── plot_isochrone_in_python.ipynb ├── python_isochrone.png ├── time.csv ├── 公众号文章[Python]Mapbox等时圈绘制零基础拿来主义.md └── 公众号文章[Python]等时圈绘制第二弹_高德等时圈绘制.md /Get_Isochrone_from_gaode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Author: Bardbo 3 | # @Date: 2020-10-24 21:29:09 4 | # @Last Modified by: Bardbo 5 | # @Last Modified time: 2022-04-03 10:19:38 6 | 7 | import requests 8 | import json 9 | import pandas as pd 10 | import converter # 坐标转换的脚本,来自于GitHub https://github.com/gaussic/geo_convert 11 | from tqdm import tqdm 12 | 13 | 14 | def generateCoor(left_lng, left_lat, right_lng, right_lat, m, n): 15 | """[1.根据传入的左下坐标和右上坐标, 将围成的矩形划分为m行n列, 返回网格交点的坐标; 16 | 2.此处使用遍历方式产生,读者也可以使用小旭学长视频中的传入小区id的方式产生; 17 | 3.左下、右上的坐标可以使用坐标拾取器拾取,如高德坐标拾取器] 18 | 19 | Args: 20 | left_lng ([float]): [矩形空间范围左下角的经度坐标] 21 | left_lat ([float]): [矩形空间范围左下角的纬度坐标] 22 | right_lng ([float]): [矩形空间范围右上角的经度坐标] 23 | right_lat ([float]): [矩形空间范围右上角的纬度坐标] 24 | m ([int]): [行数] 25 | n ([int]): [列数] 26 | 27 | Returns: 28 | [list]: [列表中为元组形式的经纬度坐标,矩形网格交点的坐标] 29 | """ 30 | coor_ls = [] 31 | for i in range(m + 1): 32 | lat = left_lat + i * (right_lat - left_lat) / m 33 | lat = round(lat, 6) 34 | for j in range(n + 1): 35 | lng = left_lng + j * (right_lng - left_lng) / n 36 | lng = round(lng, 6) 37 | coor_ls.append((lng, lat)) 38 | return coor_ls 39 | 40 | 41 | class GetTripTime: 42 | def __init__(self, 43 | method, 44 | center_coor, 45 | r=0.18, 46 | m=5, 47 | n=5, 48 | key='47e9cf07fca900b056abf416d6e3b155'): 49 | """[未使用批量请求接口,未使用多线程,建议更换自己的高德开放平台webAPI key] 50 | 51 | Args: 52 | method ([str]): [出行方式,不同出行方式的请求网址不同,walking、driving、transit、bicycling] 53 | center_coor ([list or tuple]): [等时圈中心点的经纬度坐标, GCJ02 高德坐标系] 54 | r (float, optional): [矩形范围的半径,单位为度数]. Defaults to 0.18. 55 | m (int, optional): [矩形范围划分成m行]. Defaults to 20. 56 | n (int, optional): [矩形范围划分成n列]. Defaults to 20. 57 | key (str, optional): [高德webAPIkey,需要注册开发者账号然后申请,建议使用自己的key] 58 | """ 59 | print('**正在初始化**') 60 | self.method = method 61 | self.center_coor = center_coor 62 | self.r = r 63 | self.m = m 64 | self.n = n 65 | self.key = key 66 | 67 | self.get_coor_ls() 68 | if self.method == 'walking': 69 | self.get_time = self.get_walking_time 70 | elif self.method == 'transit': 71 | self.get_time = self.get_transit_time 72 | elif self.method == 'driving': 73 | self.get_time = self.get_driving_time 74 | elif self.method == 'bicycling': 75 | self.get_time = self.get_bicycling_time 76 | else: 77 | raise '不支持该种出行方式!请从walking、driving、transit、bicycling中选择' 78 | 79 | def get_coor_ls(self): 80 | left = (self.center_coor[0] - self.r, self.center_coor[1] - self.r) 81 | right = (self.center_coor[0] + self.r, self.center_coor[1] + self.r) 82 | self.coor_ls = generateCoor(left[0], left[1], right[0], right[1], 83 | self.m, self.n) 84 | 85 | # 如下分别为步行,公交,驾车的行程时间获取函数,相关参数含义请参考高德开放平台路径规划API文档 86 | # 传送门:https://lbs.amap.com/api/webservice/guide/api/direction 87 | 88 | def get_walking_time(self, origin, destination): 89 | url = f'https://restapi.amap.com/v3/direction/walking?origin={origin}&destination={destination}&key={self.key}' 90 | try: 91 | r = requests.get(url) 92 | rt = json.loads(r.text) 93 | time = rt['route']['paths'][0]['duration'] 94 | except: 95 | time = 0 96 | return time 97 | 98 | # 注意此时的默认范围为湖南省长沙市 99 | def get_transit_time(self, 100 | origin, 101 | destination, 102 | city='长沙', 103 | cityd='长沙', 104 | extensions='base', 105 | strategy='0', 106 | nightflag='0', 107 | date=None, 108 | time=None): 109 | url = f'https://restapi.amap.com/v3/direction/transit/integrated?origin={origin}&destination={destination}&key={self.key}' + \ 110 | f'&city={city}&cityd={cityd}&extensions={extensions}&strategy={strategy}&nightflag={nightflag}' 111 | if date: 112 | url += f'&date={date}' 113 | if time: 114 | url += f'&time={time}' 115 | try: 116 | r = requests.get(url) 117 | rt = json.loads(r.text) 118 | time = rt['route']['transits'][0]['duration'] 119 | except: 120 | time = 0 121 | return time 122 | 123 | # 此处可传递其余参数,详见高德文档 124 | def get_driving_time(self, origin, destination): 125 | url = f'https://restapi.amap.com/v3/direction/driving?origin={origin}&destination={destination}&key={self.key}' 126 | try: 127 | r = requests.get(url) 128 | rt = json.loads(r.text) 129 | time = rt['route']['paths'][0]['duration'] 130 | print(time) 131 | except: 132 | time = 0 133 | return time 134 | 135 | def get_bicycling_time(self, origin, destination): 136 | url = f'https://restapi.amap.com/v4/direction/bicycling?origin={origin}&destination={destination}&key={self.key}' 137 | try: 138 | r = requests.get(url) 139 | rt = json.loads(r.text) 140 | time = rt['data']['paths'][0]['duration'] 141 | print(time) 142 | except: 143 | time = 0 144 | return time 145 | 146 | def main(self): 147 | result = [] 148 | origin = str(self.center_coor[0]) + ',' + str(self.center_coor[1]) 149 | print('**正在获取行程时间数据**') 150 | for coor in tqdm(self.coor_ls): 151 | destination = str(coor[0]) + ',' + str(coor[1]) 152 | time = self.get_time(origin=origin, destination=destination) 153 | coor_wgs84 = converter.gcj02_to_wgs84(coor[0], coor[1]) 154 | result.append((coor_wgs84[0], coor_wgs84[1], time)) 155 | print('**正在保存数据**') 156 | data = pd.DataFrame(result) 157 | data.columns = ['lng', 'lat', 'time'] 158 | data['time'] = pd.to_numeric(data['time']) 159 | data = data[~(data['time'] == 0)] 160 | center_x_wgs84, center_y_wgs84 = converter.gcj02_to_wgs84( 161 | self.center_coor[0], self.center_coor[1]) 162 | data.loc['center'] = [center_x_wgs84, center_y_wgs84, 0] 163 | data.to_csv('time.csv', encoding='utf_8_sig', index=False) 164 | print(f'**数据保存完毕,共采集到{len(data) - 1}个有效点**') 165 | 166 | 167 | if __name__ == '__main__': 168 | method = 'transit' 169 | center_coor = (113.063964, 28.279842) 170 | gtt = GetTripTime(method, center_coor) 171 | gtt.main() -------------------------------------------------------------------------------- /Get_Isochrone_from_mapbox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Author: Bardbo 3 | # @Date: 2020-10-15 13:41:08 4 | # @Last Modified by: Bardbo 5 | # @Last Modified time: 2020-10-15 21:00:01 6 | 7 | import requests 8 | import json 9 | import geopandas as gpd 10 | from shapely.geometry import Point, Polygon 11 | import matplotlib.pyplot as plt 12 | import argparse 13 | 14 | 15 | def get_isochrone_from_mapbox( 16 | coordinates, 17 | profile='driving', 18 | contours_minutes='5,10,15', 19 | access_token='pk.eyJ1IjoiYmFyZGJvIiwiYSI6ImNrZzkxbnlsZzA5M3gzMnF4NDgwOTV2YjEifQ.WqJJ6FosmvYhPj828tPUDw' 20 | ): 21 | """[使用request发起获取等时圈数据的请求,返回响应结果并保存至r.json中; 22 | mapbox官方文档:https://docs.mapbox.com/api/navigation/#isochrone] 23 | 24 | Args: 25 | coordinates ([str]): [坐标, 如 "-118.22258,33.99038", 坐标系为WGS84] 26 | profile (str, optional): [可设置为"driving", "walking", "cycling"]. Defaults to 'driving'. 27 | contours_minutes (str, optional): [等时圈时间间距,单位为分钟,可以使用多个]. Defaults to '5,10,15'. 28 | access_token (str, optional): [mapbox的token,需注册申请]. Defaults to 'pk.eyJ1IjoiYmFyZGJvIiwiYSI6ImNrZzkxbnlsZzA5M3gzMnF4NDgwOTV2YjEifQ.WqJJ6FosmvYhPj828tPUDw'. 29 | 30 | Returns: 31 | [dict]: [请求结果] 32 | """ 33 | 34 | # 发起请求 35 | r = requests.get( 36 | f"https://api.mapbox.com/isochrone/v1/mapbox/{profile}/" + \ 37 | f"{coordinates}?contours_minutes={contours_minutes}" + \ 38 | f"&polygons=true&access_token={access_token}" 39 | ) 40 | 41 | # 判断请求是否成功 42 | if r.status_code == requests.codes.ok: 43 | r = json.loads(r.text) 44 | # 保存响应的参数结果 45 | with open('r.json', 'w') as f: 46 | print("**请求成功, 正在保存请求结果**") 47 | json.dump(r, f) 48 | return r 49 | else: 50 | r.raise_for_status() 51 | 52 | 53 | def save_to_shp(coordinates, r, filename='r'): 54 | """[处理mapbox的响应结果,返回gdf和配色列表,并保存结果文件] 55 | 56 | Args: 57 | coordinates ([str]): [坐标, 如 "-118.22258,33.99038", 坐标系为WGS84] 58 | r ([dict]): [mapbox的等时圈响应结果字典] 59 | filename (str, optional): [gdf文件存储的名字]. Defaults to 'r'. 60 | 61 | Returns: 62 | [GeoDataFrame]: [存储了Polygon的gdf,拥有time, polygon_id, geometry三列] 63 | [List]: [存储了gdf中geometry的配色] 64 | """ 65 | 66 | # 创建空的列表容器 67 | time = [] 68 | polygon_id = [] 69 | geometry = [] 70 | color = [] 71 | # 第一层遍历:遍历不同的时间取值 72 | for i in range(len(r['features'])): 73 | # 第二层遍历: 遍历同时间不同的polygon坐标,此处仅返回一个最大范围的polygon,因而无意义 74 | for j in range(len(r['features'][i]['geometry']['coordinates'])): 75 | time.append(r['features'][i]['properties']['contour']) 76 | polygon_id.append(j) 77 | geometry.append( 78 | # 构建面层 79 | Polygon(r['features'][i]['geometry']['coordinates'][j])) 80 | color.append(r['features'][i]['properties']['fillColor']) 81 | 82 | gdf = gpd.GeoDataFrame() 83 | gdf['time'] = time 84 | gdf['polygon_id'] = polygon_id 85 | gdf.geometry = geometry 86 | # 保存 87 | print("**正在保存处理结果文件**") 88 | gdf.to_file(filename, crs="EPSG:4326") 89 | return gdf, color 90 | 91 | 92 | def plot_and_save(gdf, color, fig_name='r'): 93 | """[绘制等时圈,并保存图片] 94 | 95 | Args: 96 | gdf ([GeoDataFrame]): [存储了Polygon的gdf,拥有time, polygon_id, geometry三列] 97 | color ([List]): [存储了gdf中geometry的配色] 98 | fig_name (str, optional): [保存图片的名称]. Defaults to 'r'. 99 | """ 100 | gdf.plot(color=color) 101 | print("**正在保存等时圈图片**") 102 | plt.savefig(fig_name + '.png', dpi=300) 103 | plt.show() 104 | 105 | 106 | def main( 107 | coordinates, 108 | profile='driving', 109 | contours_minutes='5,10,15', 110 | access_token='pk.eyJ1IjoiYmFyZGJvIiwiYSI6ImNrZzkxbnlsZzA5M3gzMnF4NDgwOTV2YjEifQ.WqJJ6FosmvYhPj828tPUDw', 111 | filename='r', 112 | center_name='center_point', 113 | fig_name='r'): 114 | print("**正在请求网页**") 115 | r = get_isochrone_from_mapbox(coordinates, profile, contours_minutes, 116 | access_token) 117 | print("**正在保存中心点文件**") 118 | point_gdf = gpd.GeoDataFrame() 119 | point = coordinates.split(',') 120 | point_geometry = [Point(float(point[0]), float(point[1]))] 121 | point_gdf.geometry = point_geometry 122 | point_gdf.to_file(center_name, crs="EPSG:4326") 123 | print("**正在处理请求结果**") 124 | gdf, color = save_to_shp(coordinates, r, filename) 125 | gdf = gdf.append( 126 | { 127 | 'time': -1, 128 | 'polygon_id': -1, 129 | 'geometry': point_geometry[0] 130 | }, 131 | ignore_index=True) 132 | color.append('black') 133 | print("**正在绘制等时圈图片**") 134 | plot_and_save(gdf, color, fig_name) 135 | 136 | 137 | if __name__ == "__main__": 138 | parser = argparse.ArgumentParser() 139 | parser.add_argument( 140 | '--C', 141 | type=str, 142 | required=True, 143 | help= 144 | '输入WGS84坐标, 如 "-118.22258,33.99038", 请使用等号传递,引号无所谓 如 --C=-118.22258,33.99038' 145 | ) 146 | parser.add_argument( 147 | '--P', 148 | type=str, 149 | default="driving", 150 | help='输入出行方式, 可以为"driving", "walking", "cycling"中的任一种, 默认为 "driving"') 151 | parser.add_argument( 152 | '--M', 153 | type=str, 154 | default='5,10,15', 155 | help="输入等时圈时间范围,单位是分钟, 默认为'5,10,15', 可传递单一数字,传递多个数字时请使用等号传递") 156 | parser.add_argument( 157 | '--T', 158 | type=str, 159 | default= 160 | 'pk.eyJ1IjoiYmFyZGJvIiwiYSI6ImNrZzkxbnlsZzA5M3gzMnF4NDgwOTV2YjEifQ.WqJJ6FosmvYhPj828tPUDw', 161 | help='输入mapbox的token') 162 | parser.add_argument('--F', 163 | type=str, 164 | default='r', 165 | help='输入GeoPandas文件的保存名字, 默认为r') 166 | parser.add_argument('--Center', 167 | type=str, 168 | default='center_point', 169 | help='输入坐标点文件的保存名字, 默认为center_point') 170 | parser.add_argument('--Fig', 171 | type=str, 172 | default='r', 173 | help='输入等时圈图片的保存名字, 默认为r') 174 | 175 | args = parser.parse_args() 176 | 177 | coordinates = args.C 178 | profile = args.P 179 | contours_minutes = args.M 180 | access_token = args.T 181 | filename = args.F 182 | center_name = args.Center 183 | fig_name = args.Fig 184 | 185 | main(coordinates, profile, contours_minutes, access_token, filename, 186 | center_name, fig_name) 187 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ## 1. 项目说明 2 | 3 | 基于地图平台的API接口绘制等时圈,支持Mapbox和高德地图。 4 | 5 | ## 2. 使用方式 6 | 7 | 1. 下载或克隆本项目 8 | 2. 基于Mapbox的等时圈绘制 9 | 10 | 最简单的绘制方法为,在命令行中输入 `python 你的路径\Get_Isochrone_from_mapbox.py --C=113.013714,28.193858` 11 | 12 | 在命令行中输入 `python 你的路径\Get_Isochrone_from_mapbox.py -h`可以查看帮助文档(写的不好..也不知道我说清楚了没) 13 | 14 | 3. 基于高德地图的等时圈绘制 15 | 16 | 最简单的绘制方法为,在命令行中输入`python 你的路径\Get_Isochrone_from_gaode.py`,获取得到time.csv文件,然后运行plot_isochrone_in_python.ipynb文件绘制等时圈 17 | 18 | ## 3. 项目文件介绍 19 | 20 | 1. Get_Isochrone_from_mapbox.py,用于存放基于Mapbox绘制等时圈的脚本 21 | 2. Get_Isochrone_from_gaode.py,用于获取基于高德地图的出行时间数据的脚本 22 | 3. converter.py,将高德坐标系转为wgs84的脚本,来源于GitHub https://github.com/gaussic/geo_convert 23 | 4. plot_isochrone_in_python.ipynb, 基于出行时间数据绘制等时圈的代码 24 | 5. time.csv, Get_Isochrone_from_gaode.py文件默认获取的出行时间数据 25 | 6. python_isochrone.png,基于默认出行数据绘制的python等时圈图 26 | 27 | ## 4. ... 28 | 29 | 感谢这些厉害的地图平台,感谢GitHub的开源者 30 | 31 | -------------------------------------------------------------------------------- /converter.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """ 4 | Geolocataion conversion between WGS84, BD09 and GCJ02. 5 | WGS84 / BD09 / GCJ02 / MapBar 经纬度坐标互转。 6 | 7 | - WGS84: GPS coordinates for Google Earth (GPS坐标,谷歌地球使用) 8 | - GCJ02: national coordinate system developed by China (国测局坐标,谷歌中国地图、腾讯地图、高德地图使用) 9 | - BD09: Baidu coordinates (百度坐标系,百度地图使用) 10 | - MapBar: MapBar coordinates (图吧坐标系,图吧地图使用) 11 | 12 | Test website: http://gpsspg.com/maps.htm 13 | 14 | Author: Gaussic 15 | Date: 2019-05-09 16 | """ 17 | 18 | import math 19 | 20 | PI = math.pi 21 | PIX = math.pi * 3000 / 180 22 | EE = 0.00669342162296594323 23 | A = 6378245.0 24 | 25 | 26 | def bd09_to_gcj02(lng, lat): 27 | """BD09 -> GCJ02""" 28 | x, y = lng - 0.0065, lat - 0.006 29 | z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * PIX) 30 | theta = math.atan2(y, x) - 0.000003 * math.cos(x * PIX) 31 | lng, lat = z * math.cos(theta), z * math.sin(theta) 32 | return lng, lat 33 | 34 | 35 | def gcj02_to_bd09(lng, lat): 36 | """GCJ02 -> BD09""" 37 | z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * PIX) 38 | theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * PIX) 39 | lng, lat = z * math.cos(theta) + 0.0065, z * math.sin(theta) + 0.006 40 | return lng, lat 41 | 42 | 43 | def gcj02_to_wgs84(lng, lat): 44 | """GCJ02 -> WGS84""" 45 | if out_of_china(lng, lat): 46 | return lng, lat 47 | dlat = transform_lat(lng - 105.0, lat - 35.0) 48 | dlng = transform_lng(lng - 105.0, lat - 35.0) 49 | radlat = lat / 180.0 * PI 50 | magic = math.sin(radlat) 51 | magic = 1 - EE * magic * magic 52 | sqrtmagic = math.sqrt(magic) 53 | dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI) 54 | dlng = (dlng * 180.0) / (A / sqrtmagic * math.cos(radlat) * PI) 55 | lng, lat = lng - dlng, lat - dlat 56 | return lng, lat 57 | 58 | 59 | def wgs84_to_gcj02(lng, lat): 60 | """WGS84 -> GCJ02""" 61 | if out_of_china(lng, lat): 62 | return lng, lat 63 | dlat = transform_lat(lng - 105.0, lat - 35.0) 64 | dlng = transform_lng(lng - 105.0, lat - 35.0) 65 | radlat = lat / 180.0 * PI 66 | magic = math.sin(radlat) 67 | magic = 1 - EE * magic * magic 68 | sqrtmagic = math.sqrt(magic) 69 | dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI) 70 | dlng = (dlng * 180.0) / (A / sqrtmagic * math.cos(radlat) * PI) 71 | lng, lat = lng + dlng, lat + dlat 72 | return lng, lat 73 | 74 | 75 | def mapbar_to_wgs84(lng, lat): 76 | """MapBar -> WGS84""" 77 | lng = lng * 100000.0 % 36000000 78 | lat = lat * 100000.0 % 36000000 79 | lng1 = int(lng - math.cos(lat / 100000.0) * lng / 18000.0 - math.sin(lng / 100000.0) * lat / 9000.0) 80 | lat1 = int(lat - math.sin(lat / 100000.0) * lng / 18000.0 - math.cos(lng / 100000.0) * lat / 9000.0) 81 | lng2 = int(lng - math.cos(lat1 / 100000.0) * lng1 / 18000.0 - math.sin(lng1 / 100000.0) * lat1 / 9000.0 + (1 if lng > 0 else -1)) 82 | lat2 = int(lat - math.sin(lat1 / 100000.0) * lng1 / 18000.0 - math.cos(lng1 / 100000.0) * lat1 / 9000.0 + (1 if lat > 0 else -1)) 83 | lng, lat = lng2 / 100000.0, lat2 / 100000.0 84 | return lng, lat 85 | 86 | 87 | def transform_lat(lng, lat): 88 | """GCJ02 latitude transformation""" 89 | ret = -100 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng)) 90 | ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * math.sin(2.0 * lng * PI)) * 2.0 / 3.0 91 | ret += (20.0 * math.sin(lat * PI) + 40.0 * math.sin(lat / 3.0 * PI)) * 2.0 / 3.0 92 | ret += (160.0 * math.sin(lat / 12.0 * PI) + 320.0 * math.sin(lat * PI / 30.0)) * 2.0 / 3.0 93 | return ret 94 | 95 | 96 | def transform_lng(lng, lat): 97 | """GCJ02 longtitude transformation""" 98 | ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng)) 99 | ret += (20.0 * math.sin(6.0 * lng * PI) + 20.0 * math.sin(2.0 * lng * PI)) * 2.0 / 3.0 100 | ret += (20.0 * math.sin(lng * PI) + 40.0 * math.sin(lng / 3.0 * PI)) * 2.0 / 3.0 101 | ret += (150.0 * math.sin(lng / 12.0 * PI) + 300.0 * math.sin(lng / 30.0 * PI)) * 2.0 / 3.0 102 | return ret 103 | 104 | 105 | def out_of_china(lng, lat): 106 | """No offset when coordinate out of China.""" 107 | if lng < 72.004 or lng > 137.8437: 108 | return True 109 | if lat < 0.8293 or lat > 55.8271: 110 | return True 111 | return False 112 | 113 | 114 | def bd09_to_wgs84(lng, lat): 115 | """BD09 -> WGS84""" 116 | lng, lat = bd09_to_gcj02(lng, lat) 117 | lng, lat = gcj02_to_wgs84(lng, lat) 118 | return lng, lat 119 | 120 | 121 | def wgs84_to_bd09(lng, lat): 122 | """WGS84 -> BD09""" 123 | lng, lat = wgs84_to_gcj02(lng, lat) 124 | lng, lat = gcj02_to_bd09(lng, lat) 125 | return lng, lat 126 | 127 | 128 | def mapbar_to_gcj02(lng, lat): 129 | """MapBar -> GCJ02""" 130 | lng, lat = mapbar_to_wgs84(lng, lat) 131 | lng, lat = wgs84_to_gcj02(lng, lat) 132 | return lng, lat 133 | 134 | 135 | def mapbar_to_bd09(lng, lat): 136 | """MapBar -> BD09""" 137 | lng, lat = mapbar_to_wgs84(lng, lat) 138 | lng, lat = wgs84_to_bd09(lng, lat) 139 | return lng, lat 140 | 141 | 142 | if __name__ == '__main__': 143 | blng, blat = 121.4681891220,31.1526609317 144 | print('BD09:', (blng, blat)) 145 | print('BD09 -> GCJ02:', bd09_to_gcj02(blng, blat)) 146 | print('BD09 -> WGS84:',bd09_to_wgs84(blng, blat)) 147 | wlng, wlat = 121.45718237717077, 31.14846209914084 148 | print('WGS84:', (wlng, wlat)) 149 | print('WGS84 -> GCJ02:', wgs84_to_gcj02(wlng, wlat)) 150 | print('WGS84 -> BD09:', wgs84_to_bd09(wlng, wlat)) 151 | mblng, mblat = 121.4667323772, 31.1450420991 152 | print('MapBar:', (mblng, mblat)) 153 | print('MapBar -> WGS84:', mapbar_to_wgs84(mblng, mblat)) 154 | print('MapBar -> GCJ02:', mapbar_to_gcj02(mblng, mblat)) 155 | print('MapBar -> BD09:', mapbar_to_bd09(mblng, mblat)) 156 | -------------------------------------------------------------------------------- /python_isochrone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bardbo/Plot_Isochrone/06eddc4474146f7629e1c54f8dfda726cb80cc5d/python_isochrone.png -------------------------------------------------------------------------------- /time.csv: -------------------------------------------------------------------------------- 1 | lng,lat,time 2 | 112.87856842454507,28.10336601143071,5157.0 3 | 112.95048104670671,28.103308471905727,7252.0 4 | 113.02222922218712,28.103104393605324,3208.0 5 | 113.09409203702577,28.102999927320305,9277.0 6 | 113.16617806302388,28.103090053825962,7295.0 7 | 113.23827196675796,28.10318263259909,10417.0 8 | 112.87856277215508,28.17537195910735,5526.0 9 | 112.95047533060739,28.17531441550935,3360.0 10 | 113.02222333241083,28.175110334675875,1847.0 11 | 113.09408605023334,28.17500586481098,7276.0 12 | 113.1661721284795,28.175095985691843,5353.0 13 | 113.23826608972959,28.17518855881441,7046.0 14 | 112.87855710366283,28.24737328903623,8580.0 15 | 112.95046959818238,28.247315741366332,4121.0 16 | 113.02221742567825,28.24711165800346,1759.0 17 | 113.09408004613786,28.247007184560623,1827.0 18 | 113.166166176838,28.24709729981521,3933.0 19 | 113.23826019582951,28.247189867285684,6867.0 20 | 112.87855141901456,28.3193689713866,5474.0 21 | 112.95046384937723,28.3193114196459,3668.0 22 | 113.02221150193306,28.3191073337573,4304.0 23 | 113.094074024682,28.319002856738454,4951.0 24 | 113.16616020804263,28.319092966365282,5759.0 25 | 113.23825428500163,28.319185528182143,7655.0 26 | 112.87854571815618,28.391358198511362,7079.0 27 | 112.95045808413721,28.39130064270097,9272.0 28 | 113.0222055611187,28.3910965542903,6450.0 29 | 113.09406798580815,28.390992073697358,5973.0 30 | 113.16615422203634,28.39108217769496,8810.0 31 | 113.23824835718953,28.39117473385669,10926.0 32 | 112.87854000103336,28.46334042946177,7756.0 33 | 112.95045230240733,28.463282869582777,10968.0 34 | 113.02219960317831,28.46307877865368,9000.0 35 | 113.09406192945843,28.46297429448856,7631.0 36 | 113.16614821876183,28.46306439285547,6038.0 37 | 113.23824241233656,28.463156943360566,8470.0 38 | 113.05811589210612,28.283031925529347,0.0 39 | -------------------------------------------------------------------------------- /公众号文章[Python]Mapbox等时圈绘制零基础拿来主义.md: -------------------------------------------------------------------------------- 1 | 本次分享的是基于Mapbox地图的等时圈绘制Python脚本,可以直接拿来用,不需要了解代码,下面首先会讲如何使用,然后才会对代码进行解析(不想要看代码的可以不看代码解析的部分,这也是公众号的初心之一,**无套路的贯彻拿来主义**,尽可能的降低学习成本,以后有关技术向的文章尽量写成一个黑盒子,只要给出输入就可以得到输出,而不需要关注中间的过程,人的学习精力是有限的~,当然由于喵喵君本身技术有限,所以可能写的不是很好,**欢迎各位大佬来投稿分享哦**,技术向文章的原则为**免费开源,开袋即食**,其余有趣的文章无要求(目前木有稿费!!) 2 | 3 | ## 1. 前提条件 4 | 5 | 说是拿来就用,但前提是装了Python和相关库,不然运行不了啊.. 6 | 7 | 1. 安装好Python,建议按照公众号推文安装Anaconda,不然后面装第三方库更麻烦,传送门https://mp.weixin.qq.com/s/VyO3FfKXlXsPZpXIko5Y1A 8 | 9 | 2. 安装GeoPandas库,建议按照公众号推文来安装,传送门https://mp.weixin.qq.com/s/pOnJrTCpvl4xIErtq3iOZA 10 | 3. 下载本文提供的脚本文件,GitHub地址 https://github.com/Bardbo/Plot_Isochrone 11 | 12 | ## 2. 开袋即食 13 | 14 | 前提一定要做,不然运行不了!! 15 | 16 | 进入前文第三点中下载文件的目录,会有一个文件为 Get_Isochrone_from_mapbox.py,这就是本次的主角,**正文开始**,原则上仅需两步即可(可只看步骤1和3): 17 | 18 | 1. 在该文件目录下打开命令行或Powershell,(Shift+右键) 19 | 2. 输入命令(建议复制),`python .\Get_Isochrone_from_mapbox.py -h `,回车运行,该命令运行会显示脚本的帮助文档,如下图 20 | 21 | ![1help](https://i.loli.net/2020/10/15/do5AsFCQ81h2kU6.png) 22 | 23 | 如--C,--P等均为脚本所支持的参数,除了--C参数是必须传入的,其余均为可选,参数此处不做详细介绍 24 | 25 | 3. 使用帮助文档中所提供的坐标绘制等时圈 26 | 27 | 继续在命令行中输入命令(建议复制)`python .\Get_Isochrone_from_mapbox.py --C=-118.22258,33.99038`,回车运行,即获得该坐标位置的等时圈图,不出意外的话应该和下图一样 28 | 29 | ![2示例图片](https://i.loli.net/2020/10/15/kS4GAgEtfiabZ3J.png) 30 | 31 | 4. 至此就绘制完了等时圈,可发现目录下多出了两个文件夹,一个json文件和一张图片。其中center_point文件夹内存储了坐标点的shp文件,是一种矢量文件,可在ArcGIS中进行编辑;r文件夹内存储了等时圈面层的shp文件,可自行编辑,如更换颜色等;r.json为mapbox网页响应的结果;r.png则是等时圈图片。 32 | 33 | ## 3. 详解 34 | 35 | ### 参数详解 36 | 37 | 所有参数的传入均与前文步骤3相似,--C表示坐标点(等时圈的起点),--P表示可选则的等时圈时间计算方式,也即三种出行方式,驾车、步行和骑行,--M表示等时圈绘制的时间范围,**--T为Mapbox网站注册申请的token(建议是自己去申请一个,不然容易出问题)**,--F是矢量等时圈面层shp保存文件的名称,--Center是坐标点文件shp的保存名称,--Fig是等时圈图片的保存名称。 38 | 39 | **举例:**如果需要获取长沙火车站的10,20,30分钟的步行等时圈,将面层的文件名改为walk,将点层文件名改为train_station,图片名称改为t,应该在py文件目录的命令行中输入`python .\Get_Isochrone_from_mapbox.py --C=113.013714,28.193858 --P walking --M=10,20,30 --F walk --Center train_station --Fig t` 40 | 41 | 此处的坐标通过高德坐标拾取器获得 https://lbs.amap.com/console/show/picker ,需指出的是高德地图所使用坐标系为GCJ-02,Mapbox地图所使用的坐标系为WGS84,因此会存在十米到百米不等的偏差,虽然对于长时间的等时圈来说影响不大,但实践中仍**建议直接使用WGS84坐标**,如OSM和Mapbox本身 42 | 43 | ### 原理交流 44 | 45 | 1. 等时圈绘制原理 46 | 47 | 实际上等时圈的绘制是基于路径规划的,只是Mapbox自身提供了等时圈API接口,而高德地图等没有提供(基于高德地图的路径规划API的等时圈绘制下次找时间整理一下推送给大家) 48 | 49 | 举个例子来讲吧:假如要获取长沙火车站的15分钟步行等时圈,我们可以打开高德地图,设置长沙火车站为起点(红点),然后在火车站的周边选一些点,比如周边的茶颜悦色(绿点)和网吧(蓝点),利用高德的导航得出相应点位的步行时间,假设刚好砸偶到茶颜悦色的时间为10分钟,走到网吧的时间为20分钟,把他们连起来就成了20分钟和10分钟的等时圈,然后按照这些来做插值,就可以得出15分钟的这个等时圈(红色),选的点越多则插值与真实值越相似,但是也相对来说没那么平滑(灵魂画手的草图,如与事实雷同,纯属巧合)。 50 | 51 | ![3.例子](https://i.loli.net/2020/10/15/q58joHytZkGLRXx.png) 52 | 53 | 高德需要自己去通过路径规划API接口获得点的时间,然后再手动去做插值(比如利用ArcGIS软件,用Python也行)才能得出等时圈,而Mapbox的等时圈API就帮我们把这个过程给做了,只要发起请求就可以获得等时圈了。但是,高德地图提供了公交、货运等的路径规划,因此可以做公交和货运等的等时圈,而Mapbox就仅支持驾车、步行和骑行了。 54 | 55 | 2. Python调用API接口 56 | 57 | 首先提供API接口的网站都把如何去调用写的很详细了,至少Mapbox的等时圈接口写的很详细,Mapbox官方文档传送门:https://docs.mapbox.com/api/navigation/#isochrone 58 | 59 | 实际上调用API接口就像是访问网页,只是官方文档告诉了我们按它的来更改网页的URL地址(更改其中的参数)可以进入不同页面,不同的页面即不同的返回结果。 60 | 61 | 比如官方文档提供了这样一个URL地址:https://api.mapbox.com/isochrone/v1/mapbox/driving/-118.22258,33.99038?contours_minutes=5,10,15&contours_colors=6706ce,04e813,4286f4&polygons=true&access_token=pk.eyJ1IjoiYmFyZGJvIiwiYSI6ImNrZzkxbnlsZzA5M3gzMnF4NDgwOTV2YjEifQ.WqJJ6FosmvYhPj828tPUDw 我们可以将其复制到浏览器中看看会打开一个什么页面,里面的contours_minutes等就是官方文档给我们提供的参数,可以更改等号后面的数值来看看有什么不同(当然这里看不出来什么不同,哈哈哈,因为返回的页面是等时圈面层的边界坐标,全是数字) 62 | 63 | 本文提供的Python脚本就是使用requests库模拟浏览器的访问,获取返回的页面数据,然后再对获取到的等时圈面层坐标数据进行处理和可视化。 64 | 65 | 别的其它的API调用原理也是这样的,就是访问官方提供的URL地址,然后我们照它的做它就会给我们返回数据,更改URL地址的参数就会返回不同的数据,只是我们可以使用程序来帮助我们访问网页,然后处理网页返回的数据。(此外,一般网站都会有一些隐藏的API接口) 66 | 67 | ### 代码简单介绍 68 | 69 | 首先是导入库,没啥好说的,如果按照之前的推文进行下载的话都没问题,所用到的库功能写在下面的注释里 70 | 71 | ```python 72 | import requests # 用来模拟访问网页,发起请求,获得数据 73 | import json # 用来加载网页返回的数据,保存json格式的数据 74 | import geopandas as gpd # 用来存储几何形状数据,自带了基于matplolib库的绘图 75 | from shapely.geometry import Point, Polygon # 用来创建点和面,通过坐标对创建 76 | import matplotlib.pyplot as plt # 用来绘图和保存图片 77 | import argparse # 一个传入命令行参数的库,解析命令行参数 78 | ``` 79 | 80 | 然后py文件中除了`main`函数外,有`get_isochrone_from_mapbox`、`save_to_shp`、`plot_and_save`这三个函数,函数的输入和输出等都写在注释里了。 81 | 82 | 1. `get_isochrone_from_mapbox`函数是使用`request`库的`get`方式发起请求,请求成功则保存相应的返回结果至json文件 83 | 2. `save_to_shp`函数,使用`for`循环取出返回结果中的等时圈面层坐标,并使用`shapely`库的`Polygon`函数创建面层,并使用`GeoPandas`库设置投影和保存 84 | 3. `plot_and_save`函数,则是绘制图片和保存图片 85 | 4. `main`函数则是将这三个函数整合在了一起,然后添加了保存坐标点的几行代码 86 | 87 | 此次的代码相对较简单,写了较多的注释,具体的可以看下源码哈~ 88 | 89 | ### ArcGISPro 90 | 91 | 运行py文件获得的两个文件夹内的shp文件可以使用ArcGIS打开,进行编辑和可视化,择日再写有关ArcGIS的教程。分享ArcGISPro2.5版 百度云https://pan.baidu.com/s/1Y0J3SkBt8p-9ERCor-g9ng 提取码:p14h(侵删,来源于地信网,有条件建议支持正版哈哈) 92 | 93 | 下次见! -------------------------------------------------------------------------------- /公众号文章[Python]等时圈绘制第二弹_高德等时圈绘制.md: -------------------------------------------------------------------------------- 1 | ## 1. 唠嗑 2 | 3 | 最近需要参见面试 然后自己大论文的pyflink代码又还没有弄好~ 所以就断更了几天咯哈哈 4 | 5 | 抽空补上之前欠下来的等时圈绘制第二弹,基于高德的等时圈绘制。本次将从高德获取行程时间数据,然后在ArcGISPro中基于该数据绘制等时圈。相关原理不再赘述,可以先看看之前的第一弹 6 | 7 | 代码下载地址仍为之前mapbox等时圈绘制的项目地址:https://github.com/Bardbo/Plot_Isochrone 8 | 9 | ## 2. 快速使用 10 | 11 | 在代码目录进入命令行,输入: 12 | 13 | ``` 14 | python .\Get_Isochrone_from_gaode.py 15 | ``` 16 | 17 | 即可获得一个行程时间的csv文件:time.csv 18 | 19 | **如何将其转化为一个等时圈呢?**下面提供两种不同的方式,ArcGISPro和Python。 20 | 21 | ### 2.1 ArcGISPro绘制 22 | 23 | 1. 打开ArcGISPro,新建地图 (没有安装的同学可以在mapbox那篇推文底部获取安装包,推文传送门: https://mp.weixin.qq.com/s/GPj_hHhvl34_SvsntfitcA) 24 | 2. 在目录中**添加文件夹连接**(右下角目录——文件夹右键——添加文件夹连接——连接到time.csv所在文件夹 25 | 3. 更换底图(在界面左上方点击地图——底图——天地图-矢量(球面墨卡托)) 26 | 4. 将连接文件夹内的csv拖入ArcGISPro中间加载 27 | 5. 在界面左侧内容栏中右键刚加载入的time.csv——**显示XY数据**——在界面右侧出现的地理处理栏中设置X字段为lng,Y字段为lat,Z字段为time——运行 28 | 6. 地理处理栏上方点击后退箭头——搜索栏中搜索 **插值**——出现很多结果,选择**径向基函数(RBF)插值法** 29 | 30 | 7. 设置输入要素为time_XYTableToPoint(点层),设置Z字段为time,输出地统计图层空着,输出栅格随便输个名字(如sg),——运行(这一步就得到了栅格的等时圈了,如下图) 31 | 32 | ![栅格等时圈](https://i.loli.net/2020/10/24/78ZrdAWcQSf6RP5.png) 33 | 34 | 7. 地理处理栏上方点击后退箭头——搜索 **等值线** ——点击第一个搜索结果——输入栅格设置为sg(上一步生成的结果栅格)——等值线间距设置为600(**十分钟**)——运行即可得到等值线了(如果设置等值线类型为**等值线面**,就可以得到等值线面,生成后可以自己换颜色,下一步演示) 35 | 8. 设置等值线类型为**等值线面**——运行——在左侧内容栏中右键生成的等值线面层——符号系统——在右侧符号系统栏中,设置主符号系统为**分级色彩**,色带可以选择大家喜欢的黄色或绿色之类的(滑稽 你不对劲),按各自的审美来弄即可,如图一个奇奇怪怪的矢量等时圈就诞生了 36 | 37 | ![矢量等时圈](https://i.loli.net/2020/10/24/ejCu5OSmcrEYVBz.png) 38 | 39 | ### 2.2 当然啦 我们也可以使用Python进行绘制啦 40 | 41 | 绘制的代码为plot_isochrone_in_python.ipynb文件,需要在jupyter中打开(当然,ArcGISPro也是支持jupyter的) 42 | 43 | 主要是用到scipy中的插值函数,和matplotlib的绘图功能,绘图的代码比较简单,就不进行详细解释了,自己看看就行,结果如图: 44 | 45 | ![python_isochrone](https://i.loli.net/2020/10/25/b3ftKSjQ1a4zIhE.png) 46 | 47 | ## 3. 源码解释 48 | 49 | 前面快速使用中获取的是长沙一个点的公交出行(含地铁)等时圈,默认将该中心点的周边矩形范围划分为5行5列,也就是36个交点。这些我们都可以自己更改,但是本次代码仅写了三种出行方式,高德地图支持的另外几种方式没有写(但是原理是一样的)。 50 | 51 | 使用默认配置,我们至少需要更改两个参数,一个是**method参数**,表示不同的出行方式,可以是walking、driving、transit,其中transit表示公交出行;另一个参数是**等时圈的起点坐标center_coor**,需要是高德坐标系,因此建议使用高德坐标拾取器拾取(传送门:https://lbs.amap.com/console/show/picker) 52 | 53 | 源代码总共由两部分组成:generateCoor函数,这是根据左下角、右上角坐标返回矩形范围交叉点坐标的函数,可以不用管他;**GetTripTime类**,这个是核心,得介绍一下。 54 | 55 | 1. 初始化参数 56 | 57 | method,center_coor前面介绍过了,然后**r参数**,表示矩形范围1/2边长,这个取得越大矩形范围就越大,等时圈的范围就越大,单位是度数,即经纬度;**参数m和n**,分别表示矩形的划分行数和列数,最后会获取(m+1)×(n+1)个点坐标的行程时间,这里取得越大则等时圈数据越精细,边界越离散,同时也越耗时;**key参数**,这个就是高德开放平台的webAPIkey了,还是**希望各位大佬自己注册申请哦** 58 | 59 | 2. 接下来会生成交点的坐标 60 | 3. 然后就是使用request来请求接口,获得数据了,三个不同的函数分别对应三种不同的出行方式,值得一提的是,公交出行的参数有一些不同的地方,大家可以对照高德的官方文档进行设置,那里有详细的解释,传送门:https://lbs.amap.com/api/webservice/guide/api/direction,此外驾车出行的参数也有一些别的,代码中未写出来,大家可以自己根据自己的需要进行更改 61 | 4. 然后写了一个main函数,作用就是循环遍历各个交点,获得中心点到达各个交点的时间,然后使用pandas将其存储为csv格式。 62 | 63 | 本次的分享就到这里啦,实际上其余接口的调用也是同样的道理,比如基于高德地图获取POI数据,获取公交线路站点数据等等,网上有相当多的教程,这类接口的调用还是挺简单的,大家有时间可以对代码进行优化哦,比如加入多线程之类的 64 | 65 | 到时候有时间我在GitHub上补一下POI数据、公交线路站点数据等获取的代码,但是可能就不会写文章来进行详细介绍了哈~有什么问题可以公众号留言给我 --------------------------------------------------------------------------------