无题
import networkx as nx
import osmnx as ox
import pandas as pd
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
ox.__version__
'1.1.1'
基本展示¶
city = ox.geocode_to_gdf('Qinhuai,Nanjing, China')
ax = ox.project_gdf(city).plot()
#_ = ax.axis('off')
# G = ox.graph_from_place('Qinhuai, Nanjing, China', network_type='all')
streets=gpd.read_file("basic/road21Final.shp")
streets.head()
FID_road21 | type | name | lengthQGIS | ID | geometry | |
---|---|---|---|---|---|---|
0 | 13 | lblue | 干线公路 | 746.941 | 0 | LINESTRING Z (683092.608 3454982.000 0.000, 68... |
1 | 12 | lblue | 干线公路 | 684.967 | 1 | LINESTRING Z (683649.578 3455380.702 0.000, 68... |
2 | 33 | red | 高速公路 | 2212.137 | 2 | LINESTRING Z (651690.838 3456004.237 0.000, 65... |
3 | 0 | red | 高速公路 | 2494.064 | 3 | LINESTRING Z (687071.291 3456735.217 0.000, 68... |
4 | 11 | lblue | 干线公路 | 1826.529 | 4 | LINESTRING Z (684644.129 3456912.720 0.000, 68... |
for i in range(len(streets)):
streets.geometry[i]=shapely.ops.transform(lambda x, y, z=None: (x, y), streets.geometry[i]) # 'LINESTRING (2 3, 4 5)'
graph = momepy.gdf_to_nx(streets)
nodes, edges = momepy.nx_to_gdf(graph)
nodes.head()
nodeID | geometry | |
---|---|---|
0 | 0 | POINT (683092.608 3454982.000) |
1 | 1 | POINT (682886.751 3454263.987) |
2 | 2 | POINT (683649.578 3455380.702) |
3 | 3 | POINT (651690.838 3456004.237) |
4 | 4 | POINT (652171.081 3453844.858) |
edges.head()
FID_road21 | type | name | lengthQGIS | ID | geometry | mm_len | node_start | node_end | |
---|---|---|---|---|---|---|---|---|---|
0 | 13 | lblue | 干线公路 | 746.941 | 0 | LINESTRING (683092.608 3454982.000, 682886.751... | 746.940674 | 0 | 1 |
1 | 12 | lblue | 干线公路 | 684.967 | 1 | LINESTRING (683649.578 3455380.702, 683092.608... | 684.966555 | 0 | 2 |
2 | 11 | lblue | 干线公路 | 1826.529 | 4 | LINESTRING (684644.129 3456912.720, 683649.578... | 1826.528798 | 2 | 7 |
3 | 33 | red | 高速公路 | 2212.137 | 2 | LINESTRING (651690.838 3456004.237, 652171.081... | 2212.136803 | 3 | 4 |
4 | 32 | red | 高速公路 | 5939.964 | 35 | LINESTRING (648142.369 3460767.801, 651690.838... | 5939.963801 | 3 | 37 |
nodes["osmid"]=nodes["nodeID"]
nodes["x"]=nodes.geometry.x
nodes["y"]=nodes.geometry.y
nodes=nodes[['osmid','y','x','nodeID','geometry']]
edges["u"]=edges["node_start"]
edges["v"]=edges["node_end"]
edges["osmid"]=edges["ID"]
edges["key"]=0
edges['length']=edges['mm_len']
edges=edges[['u','v','key','osmid','FID_road21','type','name','lengthQGIS','ID','geometry','mm_len','length','node_start','node_end']]
u | v | key | osmid | FID_road21 | type | name | lengthQGIS | ID | geometry | mm_len | length | node_start | node_end | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
u | v | key | ||||||||||||||
0 | 1 | 0 | 0 | 1 | 0 | 0 | 13 | lblue | 干线公路 | 746.941 | 0 | LINESTRING (683092.608 3454982.000, 682886.751... | 746.940674 | 746.940674 | 0 | 1 |
2 | 0 | 0 | 2 | 0 | 1 | 12 | lblue | 干线公路 | 684.967 | 1 | LINESTRING (683649.578 3455380.702, 683092.608... | 684.966555 | 684.966555 | 0 | 2 | |
2 | 7 | 0 | 2 | 7 | 0 | 4 | 11 | lblue | 干线公路 | 1826.529 | 4 | LINESTRING (684644.129 3456912.720, 683649.578... | 1826.528798 | 1826.528798 | 2 | 7 |
3 | 4 | 0 | 3 | 4 | 0 | 2 | 33 | red | 高速公路 | 2212.137 | 2 | LINESTRING (651690.838 3456004.237, 652171.081... | 2212.136803 | 2212.136803 | 3 | 4 |
37 | 0 | 3 | 37 | 0 | 35 | 32 | red | 高速公路 | 5939.964 | 35 | LINESTRING (648142.369 3460767.801, 651690.838... | 5939.963801 | 5939.963801 | 3 | 37 |
edges.set_index(['u','v','key'],drop=True,inplace=True)
edges.head()
osmid | FID_road21 | type | name | lengthQGIS | ID | geometry | mm_len | length | node_start | node_end | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
u | v | key | |||||||||||
0 | 1 | 0 | 0 | 13 | lblue | 干线公路 | 746.941 | 0 | LINESTRING (683092.608 3454982.000, 682886.751... | 746.940674 | 746.940674 | 0 | 1 |
2 | 0 | 1 | 12 | lblue | 干线公路 | 684.967 | 1 | LINESTRING (683649.578 3455380.702, 683092.608... | 684.966555 | 684.966555 | 0 | 2 | |
2 | 7 | 0 | 4 | 11 | lblue | 干线公路 | 1826.529 | 4 | LINESTRING (684644.129 3456912.720, 683649.578... | 1826.528798 | 1826.528798 | 2 | 7 |
3 | 4 | 0 | 2 | 33 | red | 高速公路 | 2212.137 | 2 | LINESTRING (651690.838 3456004.237, 652171.081... | 2212.136803 | 2212.136803 | 3 | 4 |
37 | 0 | 35 | 32 | red | 高速公路 | 5939.964 | 35 | LINESTRING (648142.369 3460767.801, 651690.838... | 5939.963801 | 5939.963801 | 3 | 37 |
# 展示某一列大于另外一列的所有行
tmp=edges.loc[edges['node_start']>edges['node_end']]
tmp
osmid | FID_road21 | type | name | lengthQGIS | ID | geometry | mm_len | length | node_start | node_end | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
u | v | key |
# 构建节点的street_count列
t1=pd.DataFrame(edges['node_start'].value_counts())
t2=pd.DataFrame(edges['node_end'].value_counts())
t1.columns=['street_count']
t2.columns=['street_count']
tt=t1.add(t2,fill_value=0).astype(int)
tt
street_count | |
---|---|
0 | 2 |
1 | 1 |
2 | 2 |
3 | 2 |
4 | 1 |
... | ... |
2565 | 1 |
2566 | 1 |
2567 | 1 |
2568 | 1 |
2569 | 1 |
2570 rows × 1 columns
len(tt)==len(nodes)
True
nodes.set_index(['osmid'],drop=True,inplace=True)
nodes.head()
y | x | nodeID | geometry | |
---|---|---|---|---|
osmid | ||||
0 | 3.454982e+06 | 683092.608372 | 0 | POINT (683092.608 3454982.000) |
1 | 3.454264e+06 | 682886.751173 | 1 | POINT (682886.751 3454263.987) |
2 | 3.455381e+06 | 683649.578442 | 2 | POINT (683649.578 3455380.702) |
3 | 3.456004e+06 | 651690.837729 | 3 | POINT (651690.838 3456004.237) |
4 | 3.453845e+06 | 652171.081126 | 4 | POINT (652171.081 3453844.858) |
nodesnew=pd.merge(nodes,tt,how='left',left_index=True,right_index=True)
nodesnew.head(10)
y | x | nodeID | geometry | street_count | |
---|---|---|---|---|---|
osmid | |||||
0 | 3.454982e+06 | 683092.608372 | 0 | POINT (683092.608 3454982.000) | 2 |
1 | 3.454264e+06 | 682886.751173 | 1 | POINT (682886.751 3454263.987) | 1 |
2 | 3.455381e+06 | 683649.578442 | 2 | POINT (683649.578 3455380.702) | 2 |
3 | 3.456004e+06 | 651690.837729 | 3 | POINT (651690.838 3456004.237) | 2 |
4 | 3.453845e+06 | 652171.081126 | 4 | POINT (652171.081 3453844.858) | 1 |
5 | 3.456735e+06 | 687071.291070 | 5 | POINT (687071.291 3456735.217) | 2 |
6 | 3.454341e+06 | 686371.578992 | 6 | POINT (686371.579 3454341.316) | 1 |
7 | 3.456913e+06 | 684644.128657 | 7 | POINT (684644.129 3456912.720) | 2 |
8 | 3.457126e+06 | 699989.079878 | 8 | POINT (699989.080 3457125.993) | 2 |
9 | 3.454558e+06 | 703019.672811 | 9 | POINT (703019.673 3454558.207) | 1 |
tt.head(10)
street_count | |
---|---|
0 | 2 |
1 | 1 |
2 | 2 |
3 | 2 |
4 | 1 |
5 | 2 |
6 | 1 |
7 | 2 |
8 | 2 |
9 | 1 |
G = ox.graph_from_gdfs(nodesnew, edges)
fig, ax = ox.plot_graph(G)
G.graph
{'crs': <Projected CRS: EPSG:32650> Name: WGS 84 / UTM zone 50N Axis Info [cartesian]: - E[east]: Easting (metre) - N[north]: Northing (metre) Area of Use: - name: Between 114°E and 120°E, northern hemisphere between equator and 84°N, onshore and offshore. Brunei. China. Hong Kong. Indonesia. Malaysia - East Malaysia - Sarawak. Mongolia. Philippines. Russian Federation. Taiwan. - bounds: (114.0, 0.0, 120.0, 84.0) Coordinate Operation: - name: UTM zone 50N - method: Transverse Mercator Datum: World Geodetic System 1984 - Ellipsoid: WGS 84 - Prime Meridian: Greenwich}
area_m=nodes.unary_union.convex_hull.area
ox.basic_stats(G,area=area_m,clean_int_tol=15)
{'n': 2570, 'm': 3568, 'k_avg': 2.7766536964980544, 'edge_length_total': 6067344.658434196, 'edge_length_avg': 1700.4889737764004, 'streets_per_node_avg': 2.7766536964980544, 'streets_per_node_counts': {0: 0, 1: 78, 2: 1294, 3: 360, 4: 804, 5: 30, 6: 4}, 'streets_per_node_proportions': {0: 0.0, 1: 0.030350194552529183, 2: 0.5035019455252918, 3: 0.14007782101167315, 4: 0.31284046692607004, 5: 0.011673151750972763, 6: 0.0015564202334630351}, 'intersection_count': 2492, 'street_length_total': 6067344.658434196, 'street_segment_count': 3568, 'street_length_avg': 1700.4889737764004, 'circuity_avg': 0.9999999999999983, 'self_loop_proportion': 0.0, 'clean_intersection_count': 2492, 'node_density_km': 0.13292351481358414, 'intersection_density_km': 0.12888926027838588, 'edge_density_km': 313.8104192935792, 'street_density_km': 313.8104192935792, 'clean_intersection_density_km': 0.12888926027838588}
# 下载数据的时候的代码,因为南京太大了,熟悉OSMnx的时候,就用了秦淮区的一个范围 fig, ax = ox.plot_graph(G)
下载数据的时候找的保存为shapefile数据的代码
# 下载数据的时候的代码
ox.settings.all_oneway = True
ox.settings.log_console = True
G2 = ox.graph_from_place('Nanjing, China', network_type='all',simplify=True)
Gfile = "./nanjing2022"
ox.save_graphml(G2, Gfile)
# 相对应的加载就是 load_graphml
#G = ox.load_graphml(Gfile)
import os
path = "./"
filep_nodes = os.path.join(path, "nodes.shp")
filep_edges = os.path.join(path, "edges.shp")
def stringify_nonnumeric_cols(gdf): # 转换数据类型
for col in (c for c in gdf.columns if not c == "geometry"): # 除 geometry 外的所有列
if not pd.api.types.is_numeric_dtype(gdf[col]): # 如果不是数字类型
gdf[col] = gdf[col].fillna("").astype(str) # 全部转换为字符串
return gdf
gdf_nodes, gdf_edges = ox.graph_to_gdfs(G)
gdf_nodes = stringify_nonnumeric_cols(gdf_nodes)
gdf_edges = stringify_nonnumeric_cols(gdf_edges)
gdf_edges["fid"] = np.arange(0, gdf_edges.shape[0], dtype='int') # 增加 fid 列
gdf_nodes.to_file(filep_nodes, encoding="utf-8")
gdf_edges.to_file(filep_edges, encoding="utf-8")
D:\LenovoSoftstore\Install\anaconda\envs\osmnx_env\lib\site-packages\ipykernel_launcher.py:19: UserWarning: Column names longer than 10 characters will be truncated when saved to ESRI Shapefile.
获取到的数据可以转换为networkx里的multigraph,digraph这些,和networkx交互; 也可以转换为geopandas里的格式
# you can convert your graph to node and edge GeoPandas GeoDataFrames
gdf_nodes, gdf_edges = ox.graph_to_gdfs(graph)
gdf_nodes.head()
gdf_edges.head()
osmid | oneway | name | highway | length | geometry | bridge | lanes | maxspeed | ref | tunnel | access | width | junction | service | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
u | v | key | |||||||||||||||
304815373 | 7232707226 | 0 | 105242650 | True | 内环东线 | trunk | 7.907 | LINESTRING (118.79551 32.02452, 118.79553 32.0... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3999186863 | 0 | 61822454 | True | 象房村路 | residential | 160.252 | LINESTRING (118.79551 32.02452, 118.79688 32.0... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
304815377 | 770095460 | 0 | [254471398, 254471399] | True | 龙蟠南路 | primary | 267.154 | LINESTRING (118.79258 32.01546, 118.79208 32.0... | yes | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2602977729 | 0 | 835800514 | True | NaN | trunk_link | 361.196 | LINESTRING (118.79258 32.01546, 118.79234 32.0... | yes | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | |
305194317 | 3302177260 | 0 | 142491926 | True | 中华路 | secondary | 59.662 | LINESTRING (118.77634 32.01394, 118.77694 32.0... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
⚡ 可以采用的一个措施:
将节点和边那种的shp导入进来,通过geopandas类似的进行处理,形成geodataframes类似的格式,然后在转换成multidigraph;
也可以往上面两个结构靠,就是保证gdf_nodes是用omsid编码的,gdf_edges是用u,v,key来多重索引的;
You can create a graph from node/edge GeoDataFrames, as long as gdf_nodes is indexed by osmid and gdf_edges is multi-indexed by u, v, key (following normal MultiDiGraph structure). This allows you to load graph node/edge shapefiles or GeoPackage layers as GeoDataFrames then convert to a MultiDiGraph for graph analytics
G[304815377][770095460] # 其实就是nexworkx里数据访问的方式
AtlasView({0: {'osmid': [254471398, 254471399], 'bridge': 'yes', 'oneway': True, 'name': '龙蟠南路', 'highway': 'primary', 'length': 267.154, 'geometry': <shapely.geometry.linestring.LineString object at 0x000002EF6DA94248>}})
# convert node/edge GeoPandas GeoDataFrames to a NetworkX MultiDiGraph
G2 = ox.graph_from_gdfs(gdf_nodes, gdf_edges, graph_attrs=G.graph)
G.graph
{'created_date': '2023-01-20 09:20:55', 'created_with': 'OSMnx 1.0.1', 'crs': 'epsg:4326', 'simplified': True}
# If to_crs is None, project the graph to the UTM CRS for the UTM zone in which the graph’s centroid lies. Otherwise, project the graph to the CRS defined by to_crs
G_proj = ox.project_graph(G)
G_proj
<networkx.classes.multidigraph.MultiDiGraph at 0x1b5fbe35908>
nodes_proj = ox.graph_to_gdfs(G_proj, edges=False)
nodes_proj # 已经实现了投影,可以看x和y
y | x | street_count | lon | lat | highway | ref | geometry | |
---|---|---|---|---|---|---|---|---|
osmid | ||||||||
304815373 | 3.544563e+06 | 669563.070442 | 3 | 118.795515 | 32.024524 | NaN | NaN | POINT (669563.070 3544563.165) |
7232707226 | 3.544571e+06 | 669564.566721 | 3 | 118.795532 | 32.024594 | NaN | NaN | POINT (669564.567 3544570.909) |
3999186863 | 3.544563e+06 | 669723.638818 | 3 | 118.797215 | 32.024502 | NaN | NaN | POINT (669723.639 3544563.429) |
304815377 | 3.543553e+06 | 669302.813527 | 3 | 118.792583 | 32.015456 | NaN | NaN | POINT (669302.814 3543553.281) |
770095460 | 3.543299e+06 | 669225.275707 | 3 | 118.791718 | 32.013173 | NaN | NaN | POINT (669225.276 3543298.761) |
... | ... | ... | ... | ... | ... | ... | ... | ... |
10303904934 | 3.543303e+06 | 668114.891707 | 3 | 118.779967 | 32.013375 | NaN | NaN | POINT (668114.892 3543302.843) |
10303904966 | 3.543465e+06 | 668468.251299 | 1 | 118.783735 | 32.014786 | NaN | NaN | POINT (668468.251 3543465.101) |
10303904989 | 3.543423e+06 | 668458.820824 | 3 | 118.783628 | 32.014406 | NaN | NaN | POINT (668458.821 3543422.794) |
10303904974 | 3.543432e+06 | 668171.975837 | 1 | 118.780593 | 32.014533 | NaN | NaN | POINT (668171.976 3543432.157) |
10303904990 | 3.543433e+06 | 668418.461737 | 1 | 118.783202 | 32.014502 | NaN | NaN | POINT (668418.462 3543432.762) |
1929 rows × 8 columns
G_proj.graph['crs']
<Projected CRS: +proj=utm +zone=50 +ellps=WGS84 +datum=WGS84 +unit ...> Name: unknown Axis Info [cartesian]: - E[east]: Easting (metre) - N[north]: Northing (metre) Area of Use: - undefined Coordinate Operation: - name: UTM zone 50N - method: Transverse Mercator Datum: World Geodetic System 1984 - Ellipsoid: WGS 84 - Prime Meridian: Greenwich
graph_area_m = nodes_proj.unary_union.convex_hull.area
graph_area_m
54649971.6096648
nodes_proj.unary_union # 包含所有地理实体的集合体,是geopandas包里的
nodes_proj.unary_union.convex_hull # 对应的凸包
# show some basic stats about the network
ox.basic_stats(G_proj, area=graph_area_m, clean_int_tol=15) # 如果提供面积,可以计算density指标,如果提供clean_int_tol可以计算clean intersection
{'n': 1929, 'm': 4945, 'k_avg': 5.127008812856403, 'edge_length_total': 774358.6850000017, 'edge_length_avg': 156.59427401415604, 'streets_per_node_avg': 3.0031104199066876, 'streets_per_node_counts': {0: 0, 1: 223, 2: 12, 3: 1246, 4: 432, 5: 16}, 'streets_per_node_proportions': {0: 0.0, 1: 0.11560393986521514, 2: 0.006220839813374806, 3: 0.6459305339554173, 4: 0.223950233281493, 5: 0.00829445308449974}, 'intersection_count': 1706, 'street_length_total': 486759.9220000005, 'street_segment_count': 2836, 'street_length_avg': 171.636079689704, 'circuity_avg': 1.0441095736027672, 'self_loop_proportion': 0.0007052186177715092, 'clean_intersection_count': 1273, 'node_density_km': 35.29736508882025, 'intersection_density_km': 31.21685061769173, 'edge_density_km': 14169.425201733446, 'street_density_km': 8906.86504792104, 'clean_intersection_density_km': 23.29369920065743}
'n': 1929,节点的数量
'm': 4945, 边的数量
'k_avg': 5.127008812856403, 平均节点度(包括了入度和出度)
'intersection_count': 1706, 交叉点数量
'streets_per_node_avg': 3.0031104199066876,无向图中,每个节点连接的街道数目(边)的平均值
'streets_per_node_counts': {0: 0, 1: 223, 2: 12, 3: 1246, 4: 432, 5: 16},无向图中,连接到一个节点的街道数目:这个数字下的街道数目一共有多少
'streets_per_node_proportion': {0: 0.0,
1: 0.11560393986521514,
2: 0.006220839813374806,
3: 0.6459305339554173,
4: 0.223950233281493,
5: 0.00829445308449974},无向图中,对应的改成了占比
'edge_length_total': 774358.6850000017,米制的所有边长度的总和
'edge_length_avg': 156.59427401415604,上面的均值
'street_length_total': 486759.9220000005,在无向图中,米制的所有边长度的总和
'street_length_avg': 171.636079689704,上面的均值
'street_segments_count': 2836,无向图中,边的数量
'node_density_km': 35.29736508882025,平方千米下的,面积除以n
'intersection_density_km': 31.21685061769173,平方千米下的,面积除以intersection_count
'edge_density_km': 14169.425201733446,km下的,edge_length_total divided by area
'street_density_km': 8906.86504792104,km下的,street_length_total divided by area
'circuity_avg': 2.0995288875627176e-05,每个边的节点之间最大圆环距离,除以,边的总长度
'self_loop_proportion': 0.0008088978766430738,表示proportion of edges that have a single node as its endpoints (ie, the edge links nodes u and v, and u==v)
'clean_intersection_count': 8906.86504792104,表示number of intersections in street network, merging complex ones into single points
'clean_intersection_density_km': 23.29369920065743,km下的,clean_intersection_count divided by area
下面配的图针对的就是实验的2021年的¶
# unpack dicts into individiual keys:values
stats = ox.basic_stats(G, area=area_m,clean_int_tol=15)
for k, count in stats["streets_per_node_counts"].items():
stats["{}way_int_count".format(k)] = count
for k, proportion in stats["streets_per_node_proportions"].items():
stats["{}way_int_prop".format(k)] = proportion
# delete the no longer needed dict elements
del stats["streets_per_node_counts"]
del stats["streets_per_node_proportions"]
# load as a pandas dataframe
stats2021=pd.DataFrame(pd.Series(stats, name="value")).round(3)
stats2021
value | |
---|---|
n | 2570.000 |
m | 3568.000 |
k_avg | 2.777 |
edge_length_total | 6067344.658 |
edge_length_avg | 1700.489 |
streets_per_node_avg | 2.777 |
intersection_count | 2492.000 |
street_length_total | 6067344.658 |
street_segment_count | 3568.000 |
street_length_avg | 1700.489 |
circuity_avg | 1.000 |
self_loop_proportion | 0.000 |
clean_intersection_count | 2492.000 |
node_density_km | 0.133 |
intersection_density_km | 0.129 |
edge_density_km | 313.810 |
street_density_km | 313.810 |
clean_intersection_density_km | 0.129 |
0way_int_count | 0.000 |
1way_int_count | 78.000 |
2way_int_count | 1294.000 |
3way_int_count | 360.000 |
4way_int_count | 804.000 |
5way_int_count | 30.000 |
6way_int_count | 4.000 |
0way_int_prop | 0.000 |
1way_int_prop | 0.030 |
2way_int_prop | 0.504 |
3way_int_prop | 0.140 |
4way_int_prop | 0.313 |
5way_int_prop | 0.012 |
6way_int_prop | 0.002 |
# 作为geopackage或者graphml把graph存入磁盘
ox.save_graph_geopackage(G,filepath="mynetwork.gpkg")
ox.save_graphml(G,filepath="mynetwork.graphml")
⚡可视化街道中心性,就是利用relative closeness centrality来进行可视化,这里能计算closeness_centrality,那也能计算其他参数,那么这些参数的计算公式,和空间句法里计算参数的区别,这些东西都可以进一步学习
edge_centrality=nx.closeness_centrality(nx.line_graph(G))
nx.set_edge_attributes(G,edge_centrality,"edge_centrality")
ec=ox.plot.get_edge_colors_by_attr(G,"edge_centrality",cmap="inferno")
fig,ax=ox.plot_graph(G,edge_color=ec,edge_linewidth=2,node_size=0)
# 计算双向图(没有平行边)的betweenness
bc=nx.betweenness_centrality(ox.get_digraph(G),weight="length")
max_node,max_bc=max(bc.items(),key=lambda x:x[1]) # 是按照第1个(从0开始)的大小来选的最大的item
max_node,max_bc #比例是44%,说明有这个节点有~44%的最短路径是通过它的
(2262867596, 0.44300260478332937)
bc.items()
dict_items([(41542771, 0.01941747572815534), (41542772, 0.0013260715131423158), (41542778, 0.0), (41544344, 0.16123608808903622), (41544346, 0.06331991475254559), (41577408, 0.04525219038598153), (41597889, 0.007080274686242008), (41597891, 0.0), (41612013, 0.020577788302154867), (41612044, 0.053563817191569975), (41612048, 0.05441629173573289), (41612050, 0.00970873786407767), (41615126, 0.4426710869050438), (41615128, 0.03840871418422922), (41659040, 0.020672507695950745), (41672313, 0.050532796590101826), (41672314, 0.10348093772199858), (41672318, 0.23720104191333174), (41672321, 0.03805351645749467), (41717386, 0.0026521430262846316), (41756544, 0.15027231825716317), (41756546, 0.03840871418422922), (41794465, 0.011674165285342174), (41794468, 0.0), (41849849, 0.010253374378403977), (41849851, 0.006180440445181151), (41849858, 0.01948851527350225), (41864246, 0.004735969689793985), (41941829, 0.0), (41979079, 0.04593890599100166), (42238826, 0.028913094956192282), (42397551, 0.047643855079327495), (42449925, 0.0), (42452350, 0.11200568316362776), (42500019, 0.027918541321335543), (42666429, 0.0), (42708110, 0.0), (371833008, 0.14956192280369407), (371839652, 0.10982713710632253), (371839813, 0.21498934406819797), (371848315, 0.23594600994553636), (371852313, 0.054534690977977744), (2262867586, 0.0), (2262867591, 0.08555529244612835), (2262867596, 0.44300260478332937), (2262867612, 0.00961401847028179), (2262867613, 0.07624911200568317), (2262867619, 0.00961401847028179), (2262867623, 4.735969689793985e-05), (2262867639, 4.735969689793985e-05), (2262867640, 0.34004262372720817), (2262867643, 0.00970873786407767), (2262867644, 0.0), (3545571404, 0.0), (3640359571, 0.0), (3640359581, 0.08190859578498698), (3640359590, 0.02903149419843713), (3851123454, 0.004617570447549135), (3851123455, 0.005138527113426474), (3851123456, 4.735969689793985e-05), (4848535844, 0.0191333175467677), (4848535849, 0.00966137816717973), (4848535860, 0.0), (4848535864, 0.00970873786407767), (4848557987, 0.25884442339569025), (4848557990, 0.0), (4848557991, 0.03443049964480227), (4848557992, 0.08756807956429079), (4848557994, 0.04840161022969453), (4848558000, 0.07705422685294815), (4848558134, 0.08974662562159602), (4848558135, 0.0), (4848558138, 0.010466493014444708), (4848558153, 0.09372484016102298), (4848558155, 0.02178546057305233), (4848558156, 0.0852000947193938), (4848558222, 0.01932275633435946), (4848558238, 0.02884205541084537), (4848558243, 0.0030310206014681506), (4848558245, 0.005588444233956903), (4848558330, 0.01690741179256453), (4848558331, 0.006819796353303339), (4848558347, 0.004546530902202226), (4848558353, 0.06526166232536111), (4848558355, 0.04584418659720578), (4848558356, 0.01922803694056358), (4848558360, 0.027752782382192753), (4848558363, 0.08827847501775989), (4848558379, 0.01847028179019654), (4848558388, 0.027184466019417475), (4848558405, 0.024153445417949324), (4848558407, 0.05574236324887521), (4848558409, 0.052569263556713236), (4850315857, 0.11835188254795169), (4850315858, 0.023679848448969927), (4850315859, 0.0), (4850315861, 0.00970873786407767), (4850315873, 0.17830925882074355), (4850315874, 0.1939379587970637), (4850315875, 0.3254321572341937), (5052915276, 0.04825953113900071), (5052915279, 0.00966137816717973), (5052915280, 0.0), (5052915281, 0.0), (5701320606, 0.057518351882547954), (5701320609, 0.062396400663035756), (5701320612, 0.39985792090930616), (6058659222, 0.12848685768411083), (6058659224, 0.00028415818138763913), (6058659225, 0.050343357802510064), (6058659226, 0.00970873786407767), (6058659227, 0.00966137816717973), (6058659228, 0.1499171205304286), (6902845091, 0.0), (6902845092, 0.01932275633435946), (6902879136, 0.0191333175467677), (6902879139, 0.05607388112716079), (6902879140, 0.1775515036703765), (6902879143, 0.14326308311626806), (6902879145, 0.4123608808903623), (6902879146, 0.0096850580156287), (6902879148, 0.004854368932038835), (6902879151, 0.0096850580156287), (6902879152, 0.38233483305706845), (7045852575, 0.08420554108453705), (7045852579, 0.00970873786407767), (7045852581, 0.0), (7045856798, 0.01932275633435946), (7046571485, 0.00014207909069381957), (7046571497, 0.047643855079327495), (7048949334, 0.11565237982476913), (7048949336, 0.0382666350935354), (7048949340, 0.07501775988633673), (7048949342, 0.00970873786407767), (7048949343, 0.06644565474780961), (7130067150, 0.17210513852711343), (7130067152, 0.0), (7655326298, 0.004854368932038835), (7655335686, 0.23059436419606916), (7655335691, 0.33817191569973953), (7655352447, 0.037130002367984846), (7655352462, 0.0), (7737996633, 0.01922803694056358), (7737996634, 0.0), (7737996635, 0.0), (7737996668, 4.735969689793985e-05), (7737996669, 0.0), (7738026875, 0.0), (7738026884, 0.0), (7738026886, 0.0193701160312574), (7738026888, 0.0), (7738026893, 0.11702581103480937), (7738026908, 0.3151314231588918), (7738026909, 0.0), (7738026910, 0.15216670613308075), (7738026912, 0.3024390243902439), (7738026923, 0.06649301444470755), (7738026926, 0.0), (7738026928, 0.12071986739284869), (7738026929, 0.0), (7738026931, 0.0193701160312574), (7738026932, 0.0), (7738026933, 0.03840871418422922), (7738026934, 0.0), (7738026957, 0.0), (7738026958, 0.00970873786407767), (7738026983, 0.046317783566185176), (7738026984, 0.0), (7738026989, 0.0), (7738026990, 0.13634856736916884), (7738026994, 0.0), (7738027004, 0.01929907648591049), (7738027005, 0.04458915462941037), (7738027006, 0.01929907648591049), (7738027009, 0.05332701870708027), (7738027028, 0.0), (7738027032, 0.15964953824295525), (7738027067, 0.09793985318493961), (7738027068, 0.06852948141131897), (7738027074, 0.07103954534690977), (7738027095, 0.0), (7738027116, 0.0193701160312574), (7738027120, 0.0), (7738027121, 0.2259057542031731), (7738027122, 0.0), (7738027128, 0.0), (7738027130, 0.09727681742836845), (7738027131, 0.0), (7738027135, 0.11915699739521667), (7738027137, 0.15415581340279422), (7738027141, 0.0), (7738027164, 0.00970873786407767), (7738027165, 0.00966137816717973), (7738027166, 0.0), (7738027167, 4.735969689793985e-05), (7738027184, 0.00956665877338385), (7738027187, 9.47193937958797e-05), (9527190857, 0.20170494908832584), (9939284239, 0.06410134975136159), (9939284242, 0.047004499171205305), (9939284243, 0.0), (9939284244, 0.0004262372720814587), (10263950598, 0.024437603599336964), (10263950599, 0.019725313757991948), (10263950600, 0.01982003315178783), (10288549305, 0.11139000710395454), (10559494634, 0.01932275633435946)])
nc = ["r" if node == max_node else "w" for node in G.nodes]
ns = [80 if node == max_node else 15 for node in G.nodes]
fig, ax = ox.plot_graph(G, node_size=ns, node_color=nc, node_zorder=2)
# add the betweenness centraliy values as new node attributes, then plot
nx.set_node_attributes(G, bc, "bc")
nc = ox.plot.get_node_colors_by_attr(G, "bc", cmap="plasma")
fig, ax = ox.plot_graph(
G,
node_color=nc,
node_size=30,
node_zorder=2,
edge_linewidth=0.2,
edge_color="w",
)
routing
G=ox.speed.add_edge_speeds(G)
G=ox.speed.add_edge_travel_times(G)
orig=ox.distance.nearest_nodes(G,Y=32.0306,X=118.7854)
dest=ox.distance.nearest_nodes(G,Y=32.0367,X=118.8082)
route=ox.shortest_path(G,orig,dest,weight="travel_time")
fig,ax=ox.plot_graph_route(G,route,node_size=0)
# 求这条最短路径的长度
edge_lengths=ox.utils_graph.get_route_edge_attributes(G,route,"length")# 因为这个route的标注,使得只有在route上的才有length值,所以下面可以累加再round
round(sum(edge_lengths))
2926
# 环形距离
orig_x=G.nodes[orig]["x"]
orig_y=G.nodes[orig]["y"]
dest_x=G.nodes[dest]["x"]
dest_y=G.nodes[dest]["y"]
dest
1038950909
round(ox.distance.great_circle_vec(orig_y,orig_x,dest_y,dest_y))
7918205
利用google API或者是遥感影像文件,获取得到路网节点对应的高程数据,再赋予色彩进行可视化
try:
except ImportError:
获取网络的其他方式
place={"city":"Nanjing","state":"Qixia","country":"China"}
G=ox.graph_from_place(place,network_type="drive",truncate_by_edge=True)
fig,ax=ox.plot_graph(G,figsize=(10,10),node_size=0,edge_color="y",edge_linewidth=0.2)
G=ox.graph_from_place("Qixia, Nanjing, China", network_type="all")
fig,ax=ox.plot_graph(G,node_size=0,edge_linewidth=0.5)
wurster_hall=(37,112)
one_mile=1609 # menter
G=ox.graph_from_point(wurster_hall,dist=one_mile,network_type="drive")
fig,ax=ox.plot_graph(G,node_size=0)
# 获取网络上的其他基础设施数据
G=ox.graph_from_place("Qinhuai, Nanjing, China",retain_all=False,truncate_by_edge=True,simplify=True,custom_filter='["railway"~"subway"]')
fig,ax=ox.plot_graph(G,node_size=0,edge_color="w",edge_linewidth=0.2)
# 获取其他数据,包括建筑,是通过设置ox.geometries_from_place里的参数来实现的
查询,简化,可视化,保存¶
# 获取边界
ox.geocode_to_gdf
ox.project_gdf
().plot()
# 获取多个边界
place_names={"","",""}
ox.geocode_to_gdf
().to_file("",driver="GPKG")
ox.project_gdf
().plot()
# 如果知道地点对应的OSM ID,可以用这个来查询
ox.geocode_to_gdf(["",""],by_osmid=True)
# 对街道网络进行下载和建模
north, south, east, west=37.1,37,112,112.1
ox.graph_from_bbox
ox.graph_from_point(center_point,dist=500,) # 500meter, 此外这五百米以内的道路,是不管单向还是双向,都包括的
ox.graph_from_address
ox.graph_from_place #包括了一个和多个地址,从这些地址获取到网络数据
ox.save_graph_geopackage
# 输入一个矩形,从这个里面下载
ox.graph_from_polygon
# 从.osm xml数据中获取
ox.graph_from_xml
⚡:简化的效果挺好,思考下能不能把2011或者2021的数据套进来
# 简化
location_point = (33.299896, -111.831638)
G = ox.graph_from_point(location_point, network_type="drive_service", dist=500, simplify=False)
nc=["r" if ox.simplification._is_endpoint(G,node) else "y" for node in G.nodes()]
fig,ax=ox.plot_graph(G,node_color=nc)
G=ox.simplify_graph(G)
fig,ax=ox.plot_graph(G,node_color="r")
# 高亮所有multiline
ec=["gray" if k==0 or u==v else "r" for u,v,k in G.edges(keys=True)]
fig,ax=ox.plot_graph(G,node_color="w",node_edgecolor="k",node_size=50,edge_color=ec,edge_linewidth=3)
# 高亮所有单向边
ec = ["r" if data["oneway"] else "w" for u, v, key, data in G2.edges(keys=True, data=True)]
fig, ax = ox.plot_graph(G2, node_size=0, edge_color=ec, edge_linewidth=1.5, edge_alpha=0.7)
stats = ox.basic_stats(G)
stats["circuity_avg"]
1.1358685392173113
routing, speed imputation and travel times¶
np.random.seed(0)
Gp=ox.project_graph(G)
# 获取最近的节点或者边
# 随机采样n个点
points=ox.utils_geo.sample_points(ox.get_undirected(Gp),n=100)
X=points.x.values
Y=points.y.values
X0=X.mean()
Y0=Y.mean()
# 获取距离一组点最近的点,以及距离
nodes, dists=ox.nearest_nodes(Gp,X,Y,return_dist=True)
dists
[13.02141728799831, 134.19332912976063, 73.72831276320169, 1.6346128584053425, 31.051578351764224, 3.5092931187607337, 87.64878994034457, 45.24829443936839, 21.675178504056582, 1.9023468458797488, 99.26566791577433, 48.04498166737192, 31.751374377487615, 2.959880615277566, 20.51153786015448, 73.97941904897311, 39.17673517978925, 51.19340120660089, 26.292049431254576, 48.73309618738953, 19.68170187060116, 110.61499421077518, 2.07086297197591, 79.14645854179912, 103.63040736078571, 18.294585058685378, 3.1758686606072075, 74.5396610113333, 89.77840652642139, 35.60307440223833, 64.69173210012778, 51.769136898939884, 14.959001661483766, 31.514877405422276, 80.07542594380753, 113.91072204216687, 118.51794337137711, 96.5571174517652, 85.98895825266781, 74.43497754359082, 38.135585234599276, 79.2778038957343, 16.545247470930708, 11.00946274600901, 51.210083147405726, 78.29977045162616, 18.127620581777, 3.7319266397329214, 56.175853888514595, 0.42502303011911563, 15.789889605670393, 29.724675648960957, 30.74656359807148, 72.58172722111378, 22.394805722575228, 18.393164301227127, 14.209634163942912, 61.163073088422514, 48.11301482510856, 15.228321340803829, 36.6686502821069, 99.101673081213, 73.4806591657184, 11.305126802576373, 2.6208123714609033, 53.15459623552805, 1.1616710308975458, 67.26829825223594, 33.01123945419404, 32.40340313009194, 34.31105211554059, 6.804884421846613, 14.326963767036661, 5.156922180681355, 102.53175736335805, 30.47406260073624, 39.79955632535689, 18.026335309299743, 5.634745347636121, 7.980388514743713, 13.493124386913825, 47.25059828852815, 20.60957777585811, 41.04494671981354, 2.617842381746908, 65.06686540652039, 102.23000946268759, 77.42432758636345, 28.513828742105808, 77.10283794168458, 32.625603970107015, 55.89264138909181, 15.753421513281532, 27.64679165312897, 14.877395834709823, 54.76616717587069, 50.14486761670674, 55.70394320916568, 7.094620558333998, 23.985772284941927]
# 也可以是针对一个点,发现最近的点
node=ox.nearest_nodes(Gp,X0,Y0)
node
371839652
# 对边同理
ox.nearest_edges
# 用距离作为权重,找两个点之间的最短路径
orig = list(G)[0]
dest = list(G)[12]
route = ox.shortest_path(G, orig, dest, weight="length")
fig, ax = ox.plot_graph_route(G, route, route_color="y", route_linewidth=6, node_size=0)
# 或者是k条最短路径
routes = ox.k_shortest_paths(G, orig, dest, k=3, weight="length")
fig, ax = ox.plot_graph_routes(G, list(routes), route_colors="y", route_linewidth=4, node_size=0)
⚡这一部分可以看看要不要用,可不可以用
Imputes free-flow travel speeds for all edges based on mean maxspeed
value of edges, per highway type. This mean-imputation can obviously be imprecise, and the caller can override it by passing in hwy_speeds
and/or fallback
arguments that correspond to local speed limit standards.
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
edges = ox.graph_to_gdfs(G, nodes=False)
edges["highway"] = edges["highway"].astype(str)
edges.groupby("highway")[["length", "speed_kph", "travel_time"]].mean().round(1)
length | speed_kph | travel_time | |
---|---|---|---|
highway | |||
['residential', 'service'] | 219.8 | 40.2 | 19.7 |
['residential', 'tertiary'] | 238.2 | 40.2 | 21.3 |
['residential', 'unclassified'] | 265.9 | 40.2 | 23.8 |
['unclassified', 'residential'] | 265.9 | 40.2 | 23.8 |
residential | 115.0 | 40.2 | 10.3 |
service | 67.6 | 40.2 | 6.1 |
tertiary | 72.4 | 40.2 | 6.5 |
unclassified | 239.2 | 40.2 | 21.4 |
G.nodes(data=True)
NodeDataView({41542771: {'y': 33.2963878, 'x': -111.8302054, 'street_count': 3}, 41542772: {'y': 33.2963871, 'x': -111.8296124, 'street_count': 3}, 41542778: {'y': 33.2963861, 'x': -111.8284497, 'street_count': 3}, 41544344: {'y': 33.303419, 'x': -111.8328293, 'street_count': 4}, 41577408: {'y': 33.2978321, 'x': -111.829308, 'street_count': 3}, 41597889: {'y': 33.2995713, 'x': -111.8267331, 'street_count': 3}, 41597891: {'y': 33.2995642, 'x': -111.8288445, 'street_count': 3}, 41612013: {'y': 33.2970891, 'x': -111.8284497, 'street_count': 3}, 41612044: {'y': 33.2989257, 'x': -111.8288359, 'street_count': 3}, 41612048: {'y': 33.301034, 'x': -111.8287095, 'street_count': 3}, 41612050: {'y': 33.302161, 'x': -111.8287501, 'street_count': 3}, 41615126: {'y': 33.3013315, 'x': -111.8328592, 'street_count': 3}, 41615128: {'y': 33.3013334, 'x': -111.833994, 'highway': 'turning_circle', 'street_count': 1}, 41659040: {'y': 33.3038903, 'x': -111.8299606, 'street_count': 3}, 41672313: {'y': 33.2989108, 'x': -111.8365753, 'street_count': 3}, 41672314: {'y': 33.2989147, 'x': -111.835408, 'street_count': 3}, 41672318: {'y': 33.2989291, 'x': -111.8303403, 'street_count': 3}, 41672321: {'y': 33.2989318, 'x': -111.8267393, 'street_count': 4}, 41717386: {'y': 33.2970891, 'x': -111.8279519, 'street_count': 3}, 41756544: {'y': 33.302656, 'x': -111.8328442, 'street_count': 3}, 41756546: {'y': 33.3025514, 'x': -111.8340428, 'highway': 'turning_circle', 'street_count': 1}, 41794465: {'y': 33.2984307, 'x': -111.827145, 'street_count': 3}, 41794468: {'y': 33.2979385, 'x': -111.82706, 'highway': 'turning_circle', 'street_count': 1}, 41849849: {'y': 33.3010551, 'x': -111.8267189, 'street_count': 3}, 41849851: {'y': 33.3003102, 'x': -111.8267245, 'street_count': 3}, 41864246: {'y': 33.3039416, 'x': -111.8280492, 'street_count': 3}, 41941829: {'y': 33.29861, 'x': -111.8297457, 'highway': 'turning_circle', 'street_count': 1}, 41979079: {'y': 33.3039357, 'x': -111.8318553, 'street_count': 3}, 42238826: {'y': 33.2970963, 'x': -111.8289732, 'street_count': 3}, 42397551: {'y': 33.3033933, 'x': -111.836549, 'street_count': 3}, 42449935: {'y': 33.2957046, 'x': -111.8266043, 'street_count': 2}, 42452350: {'y': 33.2978352, 'x': -111.830192, 'street_count': 3}, 42500019: {'y': 33.3010504, 'x': -111.8277324, 'street_count': 3}, 42666429: {'y': 33.3021037, 'x': -111.8289389, 'highway': 'turning_circle', 'street_count': 1}, 42708110: {'y': 33.3013576, 'x': -111.8277287, 'highway': 'turning_circle', 'street_count': 1}, 371834252: {'y': 33.296917, 'x': -111.8355479, 'street_count': 2}, 371839652: {'y': 33.2992775, 'x': -111.8304364, 'street_count': 3}, 371839813: {'y': 33.2989311, 'x': -111.8301766, 'street_count': 3}, 371845086: {'y': 33.2965224, 'x': -111.8264498, 'street_count': 2}, 371845424: {'y': 33.2971896, 'x': -111.8267846, 'street_count': 2}, 371848315: {'y': 33.2989196, 'x': -111.8339511, 'street_count': 3}, 2262867640: {'y': 33.3016904, 'x': -111.8328566, 'street_count': 3}, 2262867644: {'y': 33.3017162, 'x': -111.8298663, 'street_count': 1}, 3372780078: {'y': 33.2989113, 'x': -111.836983, 'street_count': 2}, 3372780204: {'y': 33.3033917, 'x': -111.837007, 'street_count': 2}, 3545571404: {'y': 33.3032242, 'x': -111.8346051, 'street_count': 1}, 3640359590: {'y': 33.3034511, 'x': -111.829964, 'street_count': 3}, 3640364793: {'y': 33.3035253, 'x': -111.8271125, 'street_count': 2}, 3823450766: {'y': 33.2989347, 'x': -111.8263069, 'street_count': 2}, 3851123455: {'y': 33.3043135, 'x': -111.8299519, 'street_count': 4}, 4848535844: {'y': 33.2956364, 'x': -111.8340035, 'street_count': 3}, 4848535845: {'y': 33.295633, 'x': -111.8338967, 'street_count': 1}, 4848535848: {'y': 33.2954493, 'x': -111.8338983, 'street_count': 1}, 4848535849: {'y': 33.2954492, 'x': -111.8340067, 'street_count': 3}, 4848557987: {'y': 33.2989237, 'x': -111.832886, 'highway': 'stop', 'street_count': 3}, 4848557992: {'y': 33.2978655, 'x': -111.8339717, 'street_count': 3}, 4848557993: {'y': 33.2978666, 'x': -111.8338489, 'street_count': 1}, 4848557999: {'y': 33.2986504, 'x': -111.8338515, 'street_count': 1}, 4848558000: {'y': 33.2986537, 'x': -111.8339561, 'street_count': 3}, 4850315862: {'y': 33.2985951, 'x': -111.8320715, 'street_count': 1}, 4850315869: {'y': 33.2980148, 'x': -111.8320688, 'street_count': 1}, 4850315874: {'y': 33.2986516, 'x': -111.8318473, 'street_count': 3}, 4850315875: {'y': 33.2989193, 'x': -111.831842, 'street_count': 3}, 5701320609: {'y': 33.2993325, 'x': -111.831753, 'street_count': 3}, 5701320612: {'y': 33.2993428, 'x': -111.8328754, 'street_count': 3}, 6058659222: {'y': 33.2980071, 'x': -111.8301879, 'street_count': 3}, 6058659224: {'y': 33.2973234, 'x': -111.8303825, 'street_count': 1}, 6058659225: {'y': 33.29732, 'x': -111.830192, 'street_count': 3}, 6058659228: {'y': 33.2985058, 'x': -111.8301761, 'street_count': 3}, 7046571485: {'y': 33.2954046, 'x': -111.834007, 'street_count': 2}, 7655352447: {'y': 33.2964013, 'x': -111.8302054, 'street_count': 3}, 7655352448: {'y': 33.2963997, 'x': -111.8303308, 'street_count': 1}, 7738027128: {'y': 33.2998071, 'x': -111.8349029, 'street_count': 1}, 7738027130: {'y': 33.2989146, 'x': -111.835433, 'street_count': 3}, 9527190857: {'y': 33.2989248, 'x': -111.8305289, 'street_count': 3}, 9939284239: {'y': 33.3034439, 'x': -111.8315241, 'street_count': 3}, 9939284242: {'y': 33.3034474, 'x': -111.8307561, 'street_count': 3}, 9939284243: {'y': 33.3032476, 'x': -111.8315291, 'street_count': 3}, 9939284244: {'y': 33.303254, 'x': -111.8307597, 'street_count': 3}, 10263950590: {'y': 33.3039436, 'x': -111.8267341, 'street_count': 2}, 10263950598: {'y': 33.3039351, 'x': -111.8300117, 'street_count': 3}, 10263950599: {'y': 33.3039376, 'x': -111.8299024, 'street_count': 3}, 10263950600: {'y': 33.3039816, 'x': -111.8299574, 'street_count': 3}, 10288549305: {'y': 33.3034148, 'x': -111.8346065, 'street_count': 3}})
# 这里自己填充相应的数值 (km/hour)
# to fill in edges with missing `maxspeed` from OSM
hwy_speeds = {"residential": 35, "secondary": 50, "tertiary": 60}
G = ox.add_edge_speeds(G, hwy_speeds)
G = ox.add_edge_travel_times(G)
# calculate two routes by minimizing travel distance vs travel time
orig = list(G)[1]
dest = list(G)[12]
route1 = ox.shortest_path(G, orig, dest, weight="length")
route2 = ox.shortest_path(G, orig, dest, weight="travel_time")
# plot the routes
fig, ax = ox.plot_graph_routes(
G, routes=[route1, route2], route_colors=["r", "y"], route_linewidth=6, node_size=0
)
# compare the two routes
route1_length = int(sum(ox.utils_graph.get_route_edge_attributes(G, route1, "length")))
route2_length = int(sum(ox.utils_graph.get_route_edge_attributes(G, route2, "length")))
route1_time = int(sum(ox.utils_graph.get_route_edge_attributes(G, route1, "travel_time")))
route2_time = int(sum(ox.utils_graph.get_route_edge_attributes(G, route2, "travel_time")))
print("Route 1 is", route1_length, "meters and takes", route1_time, "seconds.")
print("Route 2 is", route2_length, "meters and takes", route2_time, "seconds.")
Route 1 is 962 meters and takes 73 seconds. Route 2 is 962 meters and takes 73 seconds.
# multiprocessing
# calculate 100,000 shortest-path routes using random origin-destination pairs
n = 100000
origs = np.random.choice(G.nodes, size=n, replace=True)
dests = np.random.choice(G.nodes, size=n, replace=True)
%%time
# it takes 3.2 seconds to solve all the routes using all the cores on my computer
# I have a 24-thread AMD 5900x: performance will depend on your specific CPU
routes = ox.shortest_path(G, origs, dests, weight="travel_time", cpus=None)
%%time
# it takes 43 seconds to solve all the routes using just 1 core on my computer
routes = ox.shortest_path(G, origs, dests, weight="travel_time", cpus=1)
# how many total results did we get
print(len(routes))
# and how many were solvable paths
# some will be unsolvable due to directed graph perimeter effects
routes_valid = [r for r in routes if r is not None]
print(len(routes_valid))
在导航的时候,正确处理了单项的街道;此外,在节点之间如果有平行的边,OSMNX会选择最abs的边来绘制
graph place queries¶
# 在单次查询中获取到多个地点
gdf = ox.geocode_to_gdf(["United Kingdom", "Ireland"])
# or optionally buffer them
places = [
"Berkeley, California, USA",
"Oakland, California, USA",
"Piedmont, California, USA",
"Emeryville, California, USA",
"Alameda, Alameda County, CA, USA",
]
gdf = ox.geocode_to_gdf(places, buffer_dist=500)
交叉点处理、简化并构建拓扑¶
# get a street network and plot it with all edge intersections
point = 37.858495, -122.267468
G = ox.graph_from_point(point, network_type="drive", dist=500)
fig,ax=ox.plot_graph(G,node_color="r")
# get a GeoSeries of consolidated intersections
G_proj = ox.project_graph(G)
ints = ox.consolidate_intersections(G_proj, rebuild_graph=False, tolerance=15, dead_ends=False)
len(ints)
66
len(G) # 比较节点数
109
上面的这种cleaned up intersections得到了更准确的交点数量和密度,但不改变或者整合网络中的拓扑,而是需要我们重建图
# consolidate intersections and rebuild graph topology
# this reconnects edge geometries to the new consolidated nodes
G2 = ox.consolidate_intersections(G_proj, rebuild_graph=True, tolerance=15, dead_ends=False)
len(G2)
66
fig, ax = ox.plot_graph(G2, node_color="r")
通过对比发现,有的边没了,不过节点确实得到了简化;而且那种四个点的也没了;
设置rebuild_graph=False知识把节点缓冲区内的点合并;设置为True在合并之前还检查了下拓扑
# 简化图_节点
ox.simplification._is_endpoint
ox.simplify_graph
# 自环边
loops = [edge[0] for edge in nx.selfloop_edges(G2)]
nc = ["r" if node in loops else "y" for node in G2.nodes()]
fig, ax = ox.plot_graph(G2, node_color=nc)
# turn off strict mode and see what nodes we'd remove
nc = ["r" if ox.simplification._is_endpoint(G, node, strict=False) else "y" for node in G.nodes()]
fig, ax = ox.plot_graph(G, node_color=nc)
# simplify network with strict mode turned off
G3 = ox.simplify_graph(G.copy(), strict=False)
fig, ax = ox.plot_graph(G3, node_color="r")
获取网络相关方法中的clean_periphery参数默认是True,表示执行清除外围;具体操作包括:
- 对于请求的区域做一个0.5km的缓冲区,获取包含缓冲区的大区域内的路网;
- 简化拓扑,仅存留表示街道交叉口的节点
- 计算大的网络下的每个节点的无向的度;
- 裁剪网络到请求的区域大小
- 保存节点度的数值作为图的属性
# get some bbox
bbox = ox.utils_geo.bbox_from_point((45.518698, -122.679964), dist=300)
north, south, east, west = bbox
G = ox.graph_from_bbox(north, south, east, west, network_type="drive", clean_periphery=False)
fig, ax = ox.plot_graph(G, node_color="r")
# 节点度的分布有很多假的死胡同
k = dict(G.degree())
{n: list(k.values()).count(n) for n in range(max(k.values()) + 1)}
{0: 0, 1: 30, 2: 2, 3: 6, 4: 52}
list(k.values()).count(4)
52
G = ox.graph_from_bbox(north, south, east, west, network_type="drive")
fig, ax = ox.plot_graph(G, node_color="r")
相对上面的图,这里清除了stray peripheral edges;在左下角的有两个interstitial nodes被保留了,因为在大的网络中,这两个其实是intersections,而OSMnx也正确的保留了这些节点,在intersection density的求解中也被考虑了进来
# the streets per node distribution for this cleaned up graph is more accurate
# dict keys = count of streets emanating from the node (ie, intersections and dead-ends)
# dict vals = number of nodes with that count
k = nx.get_node_attributes(G, "street_count")
{n: list(k.values()).count(n) for n in range(max(k.values()) + 1)}
{0: 0, 1: 0, 2: 0, 3: 3, 4: 57}
location_point = (33.299896, -111.831638)
G = ox.graph_from_point(location_point, dist=500, simplify=True)
fig, ax = ox.plot_graph(G, node_color="r")
保存和加载¶
# ox.save_graph_geopackage()
# ox.save_graphml()
# ox.load_graphml()
# 保存到svg
fig,ax=ox.plot_graph(G,show=False,save=True,close=True,filepath="piedont.svg")
# 保存兴趣点和建筑物足迹数据
# get all "amenities" and save as a geopackage via geopandas
gdf = ox.geometries_from_place(place, tags={"amenity": True})
gdf = gdf.apply(lambda c: c.astype(str) if c.name != "geometry" else c, axis=0)
gdf.to_file("pois.gpkg", driver="GPKG")
# get all building footprints and save as a geopackage via geopandas
gdf = ox.geometries_from_place(place, tags={"building": True})
gdf = gdf.apply(lambda c: c.astype(str) if c.name != "geometry" else c, axis=0)
gdf.to_file(".building_footprints.gpkg", driver="GPKG")
# 保存和加载.osm xml文件
ox.settings.all_oneway = True
ox.settings.log_console = True
G = ox.graph_from_place("Piedmont, California, USA", network_type="drive")
ox.save_graph_xml(G, filepath="piedmont.osm")
street network over place shape¶
from descartes import PolygonPatch
from shapely.geometry import MultiPolygon
from shapely.geometry import Polygon
G=ox.load_graphml("mynetwork.graphml")
# plot the network, but do not show it or close it yet
fig, ax = ox.plot_graph(
G,
show=False,
close=False,
bgcolor="#333333",
edge_color="w",
edge_linewidth=0.3,
node_size=0,
)
gdf=ox.geocode_to_gdf("Qinhuai, Nanjing, China")
gdf
# to this matplotlib axis, add the place shape as descartes polygon patches
for geometry in gdf["geometry"].tolist():
if isinstance(geometry, (Polygon, MultiPolygon)):
if isinstance(geometry, Polygon):
geometry = MultiPolygon([geometry])
for polygon in geometry.geoms:
patch = PolygonPatch(polygon, fc="k", ec="#666666", lw=1, alpha=1, zorder=-1)
ax.add_patch(patch)
# optionally set up the axes extents
margin = 0.02
west, south, east, north = gdf.unary_union.bounds
margin_ns = (north - south) * margin
margin_ew = (east - west) * margin
ax.set_ylim((south - margin_ns, north + margin_ns))
ax.set_xlim((west - margin_ew, east + margin_ew))
plt.show()
这个市政边界是一个行政边界,而不是一个物理边界,所以它代表的是管辖边界,而不是个别的物理特征,如岛屿。要获得单个岛屿的几何图形,使用“geometries”模块:
islands = ox.geometries_from_place("Qinhuai, Nanjing, China", tags={"place": ["island", "islet"]})
islands.shape
(0, 1)
自定义过滤条件¶
place = {"city": "Berkeley", "state": "California"}
# only get motorway ways
cf = '["highway"~"motorway"]'
G = ox.graph_from_place(place, network_type="drive", custom_filter=cf)
print(len(G), "motorway")
# only get primary ways
cf = '["highway"~"primary"]'
G = ox.graph_from_place(place, network_type="drive", custom_filter=cf)
print(len(G), "primary")
# use the pipe (|) as 'or' operator
cf = '["highway"~"motorway|primary"]'
G = ox.graph_from_place(place, network_type="drive", custom_filter=cf)
print(len(G), "motorway + primary")
G=ox.graph_from_place(place,custom_filter='["waterway"~"canal"]')
如果下载的数据量太大,OSMnx会把请求分割成多个服务器请求来下载所有数据,然后分配到网络图上
%%time
# get only motorways, trunks, and their links in all of Belgium
# takes a couple minutes to do all the downloading and processing
# OSMnx automatically divides up the query into multiple requests to not overload server
cf = '["highway"~"motorway|motorway_link|trunk|trunk_link"]'
G = ox.graph_from_place("Belgium", network_type="drive", custom_filter=cf)
fig, ax = ox.plot_graph(G, node_size=0)
# get NY subway rail network
# note this is rail *infrastructure* and thus includes crossovers, sidings, spurs, yards, etc
# for station-based rail network, you should download a station adjacency matrix elsewhere
ox.settings.useful_tags_way += ["railway"]
G = ox.graph_from_place(
"New York, New York, USA",
retain_all=False,
truncate_by_edge=True,
simplify=True,
custom_filter='["railway"~"subway"]',
)
fig, ax = ox.plot_graph(G, node_size=0, edge_color="w", edge_linewidth=0.2)
绘图¶
from IPython.display import Image
# configure the inline image display
img_folder = "images"
extension = "png"
size = 240
dpi = 40
place = "sf"
point = (37.793897, -122.402189)
fp = f"./{img_folder}/{place}.{extension}"
fig, ax = ox.plot_figure_ground(
point=point, filepath=fp, dpi=dpi, save=True, show=False, close=True
)
Image(fp, height=size, width=size)
place = "Atlanta, Georgia, USA"
fp = f"./{img_folder}/{place}.{extension}"
fig, ax = ox.plot_figure_ground(
address=place,
network_type="drive",
filepath=fp,
dpi=dpi,
save=True,
show=False,
close=True,
)
Image(fp, height=size, width=size)
街道宽度:默认是根据highway属性来进行可视化,也可以进行自定义,没有被自定义的就还是使用默认宽度
street_widths = {
"footway": 0.5,
"steps": 0.5,
"pedestrian": 0.5,
"path": 0.5,
"track": 0.5,
"service": 2,
"residential": 3,
"primary": 5,
"motorway": 6,
}
place = "sf-custom"
point = (37.793897, -122.402189)
fp = f"./{img_folder}/{place}.{extension}"
fig, ax = ox.plot_figure_ground(
point=point,
filepath=fp,
network_type="all",
street_widths=street_widths,
dpi=dpi,
save=True,
show=False,
close=True,
)
Image(fp, height=size, width=size)
下载建筑足迹数据,并作为图底进行可视化
# configure the inline image display
img_folder = "images"
extension = "png"
size = 240
# specify that we're retrieving building footprint geometries
tags = {"building": True}
gdf = ox.geometries_from_place("Piedmont, California, USA", tags)
gdf_proj = ox.project_gdf(gdf)
fp = f"./{img_folder}/piedmont_bldgs.{extension}"
fig, ax = ox.plot_footprints(gdf_proj, filepath=fp, dpi=400, save=True, show=False, close=True)
Image(fp, height=size, width=size)
# 保存为shapefile
gdf_save = gdf.applymap(lambda x: str(x) if isinstance(x, list) else x)
gdf_save.drop(labels="nodes", axis=1).to_file("./data/piedmont_bldgs.gpkg", driver="GPKG")
# 分析建筑足迹数据
# calculate the area in projected units (meters) of each building footprint, then display first five
areas = gdf_proj.area
areas.head()
# total area (sq m) covered by building footprints
sum(areas)
# get the total area within Piedmont's admin boundary in sq meters
place = ox.geocode_to_gdf("Piedmont, California, USA")
place_proj = ox.project_gdf(place)
place_proj.area.iloc[0]
# what proportion of piedmont is covered by building footprints?
sum(areas) / place_proj.area.iloc[0]
# 构造一个函数来简化制图
# helper funcion to get one-square-mile street networks, building footprints, and plot them
def make_plot(
place,
point,
network_type="drive",
dpi=40,
dist=805,
default_width=4,
street_widths=None,
):
fp = f"./{img_folder}/{place}.{extension}"
gdf = ox.geometries_from_point(point, tags, dist=dist)
fig, ax = ox.plot_figure_ground(
point=point,
dist=dist,
network_type=network_type,
default_width=default_width,
street_widths=street_widths,
save=False,
show=False,
close=True,
)
fig, ax = ox.plot_footprints(
gdf, ax=ax, filepath=fp, dpi=dpi, save=True, show=False, close=True
)
place = "portland_buildings"
point = (45.517309, -122.682138)
make_plot(place, point, network_type="all", default_width=1, street_widths={"secondary": 3})
# 用folium创造一个leaflet网络地图
from IPython.display import IFrame
# plot the street network with folium
m1 = ox.plot_graph_folium(G, popup_attribute="name", weight=2, color="#8b0000")n
# save as html file then display map as an iframe
m1.save(filepath)
IFrame(filepath, width=600, height=500)
# use networkx to calculate the shortest path between two nodes
origin_node = list(G.nodes())[0]
destination_node = list(G.nodes())[-1]
route = nx.shortest_path(G, origin_node, destination_node)
# plot the route with folium
# like above, you can pass keyword args along to folium PolyLine to style the lines
m2 = ox.plot_route_folium(G, route, weight=10)
# save as html file then display map as an iframe
filepath = "route.html"
m2.save(filepath)
IFrame(filepath, width=600, height=500)
# 把两个组合
# plot the route with folium on top of the previously created graph_map
m3 = ox.plot_route_folium(G, route, route_map=m1, popup_attribute="length", weight=7)
# save as html file then display map as an iframe
m3.save(filepath)
IFrame(filepath, width=600, height=500)
节点高程和边等级¶
osmnx可以使用elevation模块来自行给节点添加高程属性,或者加载本地栅格数据或谷歌高程api作为高程数据源,使用谷歌api的话需要密钥。
有了高程数据后,osmnx可以自动计算边的等级
import sys
# osmnx可以加载一个或一组栅格数据来使用
address = "600 Montgomery St, San Francisco, California, USA"
G = ox.graph_from_address(address=address, dist=500, dist_type="bbox", network_type="bike")
# add node elevations from a single raster file
# some nodes will be null because the single file does not cover the graph's extents
raster_path = "./input_data/elevation1.tif"
G = ox.elevation.add_node_elevations_raster(G, raster_path, cpus=1)
# add node elevations from multiple raster files
# no nulls should remain
raster_paths = ["./input_data/elevation1.tif", "./input_data/elevation2.tif"]
G = ox.elevation.add_node_elevations_raster(G, raster_paths)
assert not np.isnan(np.array(G.nodes(data="elevation"))[:, 1]).any()
# add edge grades and their absolute values
G = ox.elevation.add_edge_grades(G, add_absolute=True)
使用谷歌api的案例省略
一些指标的统计:
使用网络的无向图方式,这样就不会重复计算双向的街道。对于街道等级,使用绝对值,这样就关注于了街道的阶数而不是它的方向
import osmnx as ox
import pandas as pd
import geopandas as gpd
import networkx as nx
# calculate the edges' absolute grades (and drop any infinite/null values)
grades = pd.Series([d["grade_abs"] for _, _, d in ox.get_undirected(G).edges(data=True)])
grades = grades.replace([np.inf, -np.inf], np.nan).dropna()
avg_grade = np.mean(grades)
print("Average street grade in {} is {:.1f}%".format(address, avg_grade * 100))
med_grade = np.median(grades)
print("Median street grade in {} is {:.1f}%".format(address, med_grade * 100))
Average street grade in 600 Montgomery St, San Francisco, California, USA is 13.9% Median street grade in 600 Montgomery St, San Francisco, California, USA is 7.0%
# 利用高程差异绘制节点
# get one color for each node, by elevation, then plot the network
nc = ox.plot.get_node_colors_by_attr(G, "elevation", cmap="plasma")
fig, ax = ox.plot_graph(G, node_color=nc, node_size=5, edge_color="#333333", bgcolor="k")
# 利用级别差异绘制边
# get a color for each edge, by grade, then plot the network
ec = ox.plot.get_edge_colors_by_attr(G, "grade_abs", cmap="plasma", num_bins=5, equal_size=True)
fig, ax = ox.plot_graph(G, edge_color=ec, edge_linewidth=0.5, node_size=0, bgcolor="k")
# 考虑等级差异绘制最短边
# select an origin and destination node and a bounding box around them
origin = ox.distance.nearest_nodes(G, -122.426, 37.77)
destination = ox.distance.nearest_nodes(G, -122.441, 37.773)
bbox = ox.utils_geo.bbox_from_point((37.772, -122.434), dist=1500)
# define some edge impedance function here
def impedance(length, grade):
penalty = grade**2
return length * penalty
# add impedance and elevation rise values to each edge in the projected graph
# use absolute value of grade in impedance function if you want to avoid uphill and downhill
for _, _, _, data in G.edges(keys=True, data=True):
data["impedance"] = impedance(data["length"], data["grade_abs"])
data["rise"] = data["length"] * data["grade"]
# 发现最小化旅行距离的最短路径
route_by_length = ox.shortest_path(G, origin, destination, weight="length")
fig, ax = ox.plot_graph_route(G, route_by_length, bbox=bbox, node_size=1)
# 发现最小化阻力impedance的最短路径
route_by_impedance = ox.shortest_path(G, origin, destination, weight="impedance")
fig, ax = ox.plot_graph_route(G, route_by_impedance, bbox=bbox, node_size=0)
# 两路径对比
def print_route_stats(route):
route_grades = ox.utils_graph.get_route_edge_attributes(G, route, "grade_abs")
msg = "The average grade is {:.1f}% and the max is {:.1f}%"
print(msg.format(np.mean(route_grades) * 100, np.max(route_grades) * 100))
route_rises = ox.utils_graph.get_route_edge_attributes(G, route, "rise")
ascent = np.sum([rise for rise in route_rises if rise >= 0])
descent = np.sum([rise for rise in route_rises if rise < 0])
msg = "Total elevation change is {:.1f} meters: {:.0f} meter ascent and {:.0f} meter descent"
print(msg.format(np.sum(route_rises), ascent, abs(descent)))
route_lengths = ox.utils_graph.get_route_edge_attributes(G, route, "length")
print("Total trip distance: {:,.0f} meters".format(np.sum(route_lengths)))
# stats of route minimizing length
print_route_stats(route_by_length)
The average grade is 8.2% and the max is 14.9% Total elevation change is 11.0 meters: 11 meter ascent and 0 meter descent Total trip distance: 132 meters
# stats of route minimizing impedance (function of length and grade)
print_route_stats(route_by_impedance)
The average grade is 8.2% and the max is 14.9% Total elevation change is 11.0 meters: 11 meter ascent and 0 meter descent Total trip distance: 132 meters
绘制等值线图isochrone map¶
from descartes import PolygonPatch
from shapely.geometry import LineString
from shapely.geometry import Point
from shapely.geometry import Polygon
# configure the place, network type, trip times, and travel speed
place = {"city": "Berkeley", "state": "California"}
network_type = "walk"
trip_times = [5, 10, 15, 20, 25] # in minutes
travel_speed = 4.5 # walking speed in km/hour
# download the street network
G = ox.graph_from_place(place, network_type=network_type)
# find the centermost node and then project the graph to UTM
gdf_nodes = ox.graph_to_gdfs(G, edges=False)
x, y = gdf_nodes["geometry"].unary_union.centroid.xy
center_node = ox.distance.nearest_nodes(G, x[0], y[0])
G = ox.project_graph(G)
# add an edge attribute for time in minutes required to traverse each edge
meters_per_minute = travel_speed * 1000 / 60 # km per hour to m per minute
for _, _, _, data in G.edges(data=True, keys=True):
data["time"] = data["length"] / meters_per_minute
绘制步行5,10,15,20等时间内行走的距离
# get one color for each isochrone
iso_colors = ox.plot.get_colors(n=len(trip_times), cmap="plasma", start=0, return_hex=True)
iso_colors
['#0d0887', '#7e03a8', '#cc4778', '#f89540', '#f0f921']
# color the nodes according to isochrone then plot the street network
node_colors = {}
for trip_time, color in zip(sorted(trip_times, reverse=True), iso_colors):
subgraph = nx.ego_graph(G, center_node, radius=trip_time, distance="time")
for node in subgraph.nodes():
node_colors[node] = color
nc = [node_colors[node] if node in node_colors else "none" for node in G.nodes()]
ns = [15 if node in node_colors else 0 for node in G.nodes()]
fig, ax = ox.plot_graph(
G,
node_color=nc,
node_size=ns,
node_alpha=0.8,
edge_linewidth=0.2,
edge_color="#999999",
)
绘制凸包表示convex hull
# make the isochrone polygons
isochrone_polys = []
for trip_time in sorted(trip_times, reverse=True):
subgraph = nx.ego_graph(G, center_node, radius=trip_time, distance="time")
node_points = [Point((data["x"], data["y"])) for node, data in subgraph.nodes(data=True)]
bounding_poly = gpd.GeoSeries(node_points).unary_union.convex_hull
isochrone_polys.append(bounding_poly)
# plot the network then add isochrones as colored descartes polygon patches
fig, ax = ox.plot_graph(
G, show=False, close=False, edge_color="#999999", edge_alpha=0.2, node_size=0
)
for polygon, fc in zip(isochrone_polys, iso_colors):
patch = PolygonPatch(polygon, fc=fc, ec="none", alpha=0.6, zorder=-1)
ax.add_patch(patch)
plt.show()
绘制等值线作为缓冲区,得到比凸包更准确的等值区域
def make_iso_polys(G, edge_buff=25, node_buff=50, infill=False):
isochrone_polys = []
for trip_time in sorted(trip_times, reverse=True):
subgraph = nx.ego_graph(G, center_node, radius=trip_time, distance="time")
node_points = [Point((data["x"], data["y"])) for node, data in subgraph.nodes(data=True)]
nodes_gdf = gpd.GeoDataFrame({"id": list(subgraph.nodes)}, geometry=node_points)
nodes_gdf = nodes_gdf.set_index("id")
edge_lines = []
for n_fr, n_to in subgraph.edges():
f = nodes_gdf.loc[n_fr].geometry
t = nodes_gdf.loc[n_to].geometry
edge_lookup = G.get_edge_data(n_fr, n_to)[0].get("geometry", LineString([f, t]))
edge_lines.append(edge_lookup)
n = nodes_gdf.buffer(node_buff).geometry
e = gpd.GeoSeries(edge_lines).buffer(edge_buff).geometry
all_gs = list(n) + list(e)
new_iso = gpd.GeoSeries(all_gs).unary_union
# try to fill in surrounded areas so shapes will appear solid and
# blocks without white space inside them
if infill:
new_iso = Polygon(new_iso.exterior)
isochrone_polys.append(new_iso)
return isochrone_polys
isochrone_polys = make_iso_polys(G, edge_buff=25, node_buff=0, infill=True)
fig, ax = ox.plot_graph(
G, show=False, close=False, edge_color="#999999", edge_alpha=0.2, node_size=0
)
for polygon, fc in zip(isochrone_polys, iso_colors):
patch = PolygonPatch(polygon, fc=fc, ec="none", alpha=0.7, zorder=-1)
ax.add_patch(patch)
plt.show()
OSMnx结合igraph¶
networkX是纯python写的,所以虽然使用简单,但面对大型网络的处理分析略显吃力,这里可以转向别的工具,像igraph, graph-tool or cugraph for analysis.
import operator
import igraph as ig
print(ig.__version__)
weight = "length"
0.10.2
G_nx=G
osmids = list(G_nx.nodes)
G_nx = nx.relabel.convert_node_labels_to_integers(G_nx)
# give each node its original osmid as attribute since we relabeled them
osmid_values = {k: v for k, v in zip(G_nx.nodes, osmids)}
nx.set_node_attributes(G_nx, osmid_values, "osmid")
G_nx.nodes(data=True)
%%time
# convert networkx graph to igraph
G_ig = ig.Graph(directed=True)
G_ig.add_vertices(G_nx.nodes)
G_ig.add_edges(G_nx.edges())
G_ig.vs["osmid"] = osmids
G_ig.es[weight] = list(nx.get_edge_attributes(G_nx, weight).values())
Wall time: 32 ms
assert len(G_nx.nodes()) == G_ig.vcount()
assert len(G_nx.edges()) == G_ig.ecount()
# 最短路径
source = list(G_nx.nodes())[26]
target = list(G_nx.nodes())[37]
%%time
path1 = G_ig.get_shortest_paths(v=source, to=target, weights=weight)[0]
Wall time: 0 ns
%%time
path2 = nx.shortest_path(G_nx, source, target, weight=weight)
Wall time: 0 ns
# 照理,面对大数据,igraph是更快的
%%time
closeness1 = G_ig.closeness(vertices=None, mode="ALL", cutoff=None, weights=weight, normalized=True)
max_closeness1 = np.argmax(closeness1)
Wall time: 7.6 s
%%time
closeness2 = nx.closeness_centrality(G_nx, distance=weight, wf_improved=True)
max_closeness2 = max(closeness2.items(), key=operator.itemgetter(1))[0]
Wall time: 2min 22s
# confirm same node has max closeness in both graphs
print(G_nx.nodes[max_closeness2]["osmid"] == G_ig.vs[max_closeness1]["osmid"])
False
高阶绘图¶
使用plot模块
# get n evenly-spaced colors from some matplotlib colormap
ox.plot.get_colors(n=5, cmap="plasma", return_hex=True)
['#0d0887', '#7e03a8', '#cc4778', '#f89540', '#f0f921']
# get node colors by linearly mapping an attribute's values to a colormap
nc = ox.plot.get_node_colors_by_attr(G, attr="y", cmap="plasma")
fig, ax = ox.plot_graph(G, node_color=nc, edge_linewidth=0.3)
# when num_bins is not None, bin the nodes/edges then assign one color to each bin
# also set equal_size=True for equal-sized quantiles (requires unique bin edges!)
ec = ox.plot.get_edge_colors_by_attr(G, attr="length", num_bins=5)
# otherwise, when num_bins is None (default), linearly map one color to each node/edge by value
ec = ox.plot.get_edge_colors_by_attr(G, attr="length")
# plot the graph with colored edges
fig, ax = ox.plot_graph(G, node_size=5, edge_color=ec, bgcolor="k")
fig, ax = ox.plot_graph(
G,
ax=None, # optionally draw on pre-existing axis
figsize=(8, 8), # figure size to create if ax is None
bgcolor="#111111", # background color of the plot
node_color="w", # color of the nodes
node_size=15, # size of the nodes: if 0, skip plotting them
node_alpha=None, # opacity of the nodes
node_edgecolor="none", # color of the nodes' markers' borders
node_zorder=1, # zorder to plot nodes: edges are always 1
edge_color="#999999", # color of the edges
edge_linewidth=1, # width of the edges: if 0, skip plotting them
edge_alpha=None, # opacity of the edges
show=True, # if True, call pyplot.show() to show the figure
close=False, # if True, call pyplot.close() to close the figure
save=False, # if True, save figure to disk at filepath
filepath=None, # if save is True, the path to the file
dpi=300, # if save is True, the resolution of saved file
bbox=None, # bounding box to constrain plot
)
可以用bbox把绘图限制在图的某些精确的部分。例如,想要查看,是否完成了consolidate nearby intersections to clean-up node clusters
place = "Piedmont, California, USA"
G = ox.graph_from_place(place, network_type="drive")
Gc = ox.consolidate_intersections(ox.project_graph(G), dead_ends=True)
c = ox.graph_to_gdfs(G, edges=False).unary_union.centroid
bbox = ox.utils_geo.bbox_from_point(point=(c.y, c.x), dist=200, project_utm=True)
fig, ax = ox.plot_graph(
Gc,
figsize=(5, 5),
bbox=bbox,
edge_linewidth=2,
edge_color="c",
node_size=80,
node_color="#222222",
node_edgecolor="c",
)
# or save a figure to disk instead of showing it
fig, ax = ox.plot_graph(G, filepath="./images/image.png", save=True, show=False, close=True)
# 绘制路线
# impute missing edge speeds and calculate free-flow travel times
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
# calculate 3 shortest paths, minimizing travel time
w = "travel_time"
orig, dest = list(G)[10], list(G)[-1]
route1 = ox.shortest_path(G, orig, dest, weight=w)
orig, dest = list(G)[0], list(G)[-1]
route2 = ox.shortest_path(G, orig, dest, weight=w)
orig, dest = list(G)[2], list(G)[9]
route3 = ox.shortest_path(G, orig, dest, weight=w)
route3
[53018399, 53078914, 53085009, 682821166, 53119160, 53076382, 53064331, 53119162, 53081990, 53119164, 53041443, 53042707, 53119166, 53119168, 53085388, 53085389, 53073689, 53025060, 53082627, 53073691, 53033660, 53040495, 53085375, 53085377, 53027454, 53085380, 53021750, 53021742, 53021743]
fig, ax = ox.plot_graph_route(G, route1, orig_dest_size=0, node_size=0)
# you can also pass any ox.plot_graph parameters as additional keyword args
fig, ax = ox.plot_graph_route(G, route1, save=True, show=False, close=True)
# 绘制多重路线
routes = [route1, route2, route3]
rc = ["r", "y", "c"]
fig, ax = ox.plot_graph_routes(G, routes, route_colors=rc, route_linewidth=6, node_size=0)
标注街道段名字
G2=ox.get_undirected(Gc)
fig, ax = ox.plot_graph(G2, edge_linewidth=3, node_size=0, show=False, close=False)
for _, edge in ox.graph_to_gdfs(G2, nodes=False).fillna("").iterrows():
text = edge["name"]
c = edge["geometry"].centroid
ax.annotate(text, (c.x, c.y), c="y")
plt.show()
# 绘制地理实体,plot_gootprints
# get all the building footprints in a city
gdf = ox.geometries_from_place("Piedmont, California, USA", {"building": True})
gdf.shape
fig, ax = ox.plot_footprints(gdf)
# or plot street network and the entities' footprints together
fig, ax = ox.plot_footprints(gdf, alpha=0.4, show=False)
fig, ax = ox.plot_graph(G, ax=ax, node_size=0, edge_color="w", edge_linewidth=0.7)
下载地理实体¶
可以使用geometries模块来下载实体,例如grocery stores, transit stops, POI or building footprints,并转变为GeoDataFrame
# get all building footprints in some neighborhood
# `True` means retrieve any object with this tag, regardless of value
place = "Bunker Hill, Los Angeles, California"
tags = {"building": True}
gdf = ox.geometries_from_place(place, tags)
gdf.shape
fig, ax = ox.plot_footprints(gdf, figsize=(3, 3))
# get all the parks in some neighborhood
# constrain acceptable `leisure` tag values to `park`
tags = {"leisure": "park"}
gdf = ox.geometries_from_place(place, tags)
gdf.shape
# get everything tagged amenity,
# and everything tagged landuse = retail or commercial,
# and everything tagged highway = bus_stop
tags = {"amenity": True, "landuse": ["retail", "commercial"], "highway": "bus_stop"}
gdf = ox.geometries_from_place("Piedmont, California, USA", tags)
gdf.shape
# view just the banks
gdf[gdf["amenity"] == "bank"].dropna(axis=1, how="any")
# view just the bus stops
gdf[gdf["highway"] == "bus_stop"].dropna(axis=1, how="any").head()
街道网络的方向¶
# define the study sites as label : query
places = {
# 'Atlanta' : 'Atlanta, Georgia, USA',
# 'Boston' : 'Boston, MA, USA',
"Buffalo": "Buffalo, NY, USA",
# 'Charlotte' : 'Charlotte, NC, USA',
# 'Chicago' : 'Chicago, IL, USA',
"Cleveland": "Cleveland, OH, USA",
# 'Dallas' : 'Dallas, TX, USA',
# 'Houston' : 'Houston, TX, USA',
# 'Denver' : 'Denver, CO, USA',
# 'Detroit' : 'Detroit, MI, USA',
# 'Las Vegas' : 'Las Vegas, NV, USA',
# 'Los Angeles' : {'city':'Los Angeles', 'state':'CA', 'country':'USA'},
# 'Manhattan' : 'Manhattan, NYC, NY, USA',
"Miami": "Miami, FL, USA",
"Minneapolis": "Minneapolis, MN, USA",
# 'Orlando' : 'Orlando, FL, USA',
# 'Philadelphia' : 'Philadelphia, PA, USA',
# 'Phoenix' : 'Phoenix, AZ, USA',
# 'Portland' : 'Portland, OR, USA',
# 'Sacramento' : 'Sacramento, CA, USA',
"San Francisco": {"city": "San Francisco", "state": "CA", "country": "USA"},
# 'Seattle' : 'Seattle, WA, USA',
# 'St Louis' : 'St. Louis, MO, USA',
# 'Tampa' : 'Tampa, FL, USA',
"Washington": "District of Columbia, USA",
}
# verify OSMnx geocodes each query to what you expect (i.e., a [multi]polygon geometry)
gdf = ox.geocode_to_gdf(list(places.values()))
gdf
geometry | bbox_north | bbox_south | bbox_east | bbox_west | place_id | osm_type | osm_id | lat | lon | display_name | class | type | importance | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | POLYGON ((-78.92236 42.94914, -78.91945 42.947... | 42.966449 | 42.826006 | -78.795121 | -78.922357 | 307947630 | relation | 175031 | 42.886717 | -78.878392 | Buffalo, Erie County, New York, United States | boundary | administrative | 0.732931 |
1 | POLYGON ((-81.87909 41.39641, -81.87906 41.395... | 41.604436 | 41.390628 | -81.532744 | -81.879094 | 307598444 | relation | 182130 | 41.499657 | -81.693677 | Cleveland, Cuyahoga County, Ohio, United States | boundary | administrative | 0.856081 |
2 | POLYGON ((-80.31976 25.76249, -80.31968 25.762... | 25.855783 | 25.709052 | -80.139157 | -80.319760 | 307919581 | relation | 1216769 | 25.774173 | -80.193620 | Miami, Miami-Dade County, Florida, United States | boundary | administrative | 0.885449 |
3 | POLYGON ((-93.32913 44.92030, -93.32911 44.920... | 45.051250 | 44.890150 | -93.193859 | -93.329127 | 307870876 | relation | 136712 | 44.977300 | -93.265469 | Minneapolis, Hennepin County, Minnesota, Unite... | boundary | administrative | 0.747169 |
4 | MULTIPOLYGON (((-123.17382 37.77573, -123.1737... | 37.929811 | 37.640314 | -122.281479 | -123.173825 | 307775847 | relation | 111968 | 37.779026 | -122.419906 | San Francisco, California, United States | boundary | administrative | 1.025131 |
5 | POLYGON ((-77.11979 38.93435, -77.11977 38.934... | 38.995968 | 38.791630 | -76.909366 | -77.119795 | 307700086 | relation | 162069 | 38.893847 | -76.988043 | District of Columbia, United States | boundary | administrative | 0.712066 |
# create figure and axes
n = len(places)
ncols = int(np.ceil(np.sqrt(n)))
nrows = int(np.ceil(n / ncols))
figsize = (ncols * 5, nrows * 5)
fig, axes = plt.subplots(nrows, ncols, figsize=figsize, subplot_kw={"projection": "polar"})
# plot each city's polar histogram
for ax, place in zip(axes.flat, sorted(places.keys())):
print(ox.utils.ts(), place)
# get undirected graphs with edge bearing attributes
G = ox.graph_from_place(place, network_type="drive")
Gu = ox.add_edge_bearings(ox.get_undirected(G))
fig, ax = ox.bearing.plot_orientation(Gu, ax=ax, title=place, area=True)
# add figure title and save image
suptitle_font = {
"family": "DejaVu Sans",
"fontsize": 60,
"fontweight": "normal",
"y": 1,
}
fig.suptitle("City Street Network Orientation", **suptitle_font)
fig.tight_layout()
fig.subplots_adjust(hspace=0.35)
fig.savefig("images/street-orientations.png", facecolor="w", dpi=100, bbox_inches="tight")
plt.close()
2023-01-28 09:36:11 Buffalo 2023-01-28 09:37:29 Cleveland 2023-01-28 09:38:59 Miami
也可以通过ox.bearing.orientation_entropy函数来计算空间图的orientation entropy