NetworkX是一个很强大的python网络处理工具包,内置了常用的图和复杂网络分析方法。一般网络处理不关心节点的坐标,因为网络分析通常只关心节点的连通关系和边的权值属性,一般并不涉及坐标值。但是在对路网进行模拟的时候,大多需要将网络直观地呈现出来,这是个简单但是网络上的文章鲜有涉及的问题。针对网络绘制,NetworkX提供了一个简单的方法,此方法依赖于matplotlib中的pyplot函数,于是在开头需要导入以下两个工具包:
import networkx as nx
import matplotlib.pyplot as plt
NetworkX和matplotlib的安装和配置请查阅网络教程,假设以下是路网,中间褐色的点是村庄,绿色的方块是城市,路网的节点由村庄、城市和交叉节点组成。
在图2中,笔者将村庄编码为1-29的29个点,交叉点编码为1021-1024共24个点,为了使图更为紧凑,还调整了原图,添加了两个交叉点1025和1026,城市节点编码为'a-e'。准备工作做好以后,开始使用networkx新建网络。
roads=nx.Graph()#新建一个无向图
将附带人口属性的城市节点添加到网络中
#城市的人口数据
rcapacity=[24115, 15092, 16194, 22563, 19311]
rnlist=['a','b','c','d','e'] #城市节点列表
#将城市添加到网络中
for i in range(0,5):
roads.add_node(rnlist[i],capacity=rcapacity[i])
将附带人口属性的村庄节点添加到网络中:
npop=[637, 1517, 4683, 1860, 1102, 4684, 1233, 4421, 2286, 529, 4489, 3071, 4268,\
2542, 3465, 4751, 2284, 2220, 4488, 4483, 3804, 2455, 4895, 1608, 2718, 1459,\
2221, 3162]
#村庄节点
for i in range(1,29):
nnlist.append(i)
roads.add_node(i,population=npop[i-1])
将交叉点加入网络
for j in range(1001,1025):
vnlist.append(j)
roads.add_nodes_from(vnlist)
#与村庄相关的边
nedges=[(1,2),[1,1008],(2,1),(2,3),(3,4),(3,2),(4,3),(4,5),(4,1001),(5,4),(5,1003),\
(6,1005),(6,'a'),(7,'a'),(7,1011),(8,1004),(9,1004),(9,'b'),\
(10,13),(10,1009),(11,'c'),(11,1010),(12,13),(12,1010),(13,10),(13,12),\
(13,14),(13,1007),(14,15),(14,13),(15,1014),(15,1012),(16,1011),(16,1012)\
,(17,18),(17,1018),(17,1014),(18,19),(18,1010),(18,1015),(20,1017),(20,1018),\
(21,1017),(21,22),(22,1016),(22,23),(24,1019),(24,1018),(25,26),(26,25)\
,(26,1019),(26,1013),(27,1022),(27,1024),(28,1021),(28,1023)]
#与交叉点相关的边
vedges=[(1001,4),(1001,1004),(1001,1002),(1002,1003),(1002,'a'),(1003,1002),(1003,1005),\
(1004,9),(1004,1001),(1004,8),(1005,4),(1005,6),(1005,1003),(1005,1006),\
(1006,1007),(1006,1005),(1007,13),(1007,1006),(1008,1009),\
(1008,1),(1009,'c'),(1009,10),(1010,18),(1010,11),(1010,12),(1011,16),\
(1011,7),(1012,16),(1012,1013),(1012,15),(1013,1014),(1013,26),(1013,1012),(1014,15),\
(1014,17),(1015,'c'),(1015,18),(1015,'d'),(1016,'d'),(1016,22),(1017,20),(1017,19),\
(1017,'d'),(1017,21),(1018,24),(1018,17),(1018,20),(1019,24),(1019,'e'),(1020,'e'),\
(1020,1023),(1021,'e'),(1021,28),(1021,1022),(1022,8),(1022,27),(1023,28),(1023,1024),\
(1024,27),(1024,'b')]
#对原图进行了一些调整,便于显示,添加了几个节点
roads.add_nodes_from([1025,1026])
tedges=[(1025,1006),(1025,1007),(1025,1011),(1026,23),(1026,1019),(1026,'e'),\
(1026,1020)]
上面的代码是边的列表,注意因为是无向图,nedges和vedges中有的边是重复的,笔者为免遗漏,统计了每个点的关联边,无向图会忽略重复的边。下面的代码将这些边加入到网络:
#边的集合
edges=nedges+vedges+tedges;
for e in edges:
roads.add_edge(e[0],e[1],length=CalLength(e[0],e[1],pos))
这样的话,路网就建立完成了,我们调用绘制语句,会得到下面的结果:
nx.draw(roads)
plt.show()
图3 绘制图
import numpy as np
nodes=np.loadtxt('E:/data0730/nodes.txt',dtype=np.int32) #转移单元节点坐标
vnodes=np.loadtxt('E:/data0730/vnodes.txt',dtype=np.int32) #交叉节点坐标
receivers=np.loadtxt('E:/data0730/receivers.txt',dtype=np.int32) #城市节点坐标
def draw_networkx_nodes(G, pos,
nodelist=None,
node_size=300,
node_color='r',
node_shape='o',
alpha=1.0,
cmap=None,
vmin=None,
vmax=None,
ax=None,
linewidths=None,
label=None,
**kwds):
#获取节点与坐标之间的映射关系,用字典表示
rpos=dict(zip(rnlist,receivers))
npos=dict(zip(nnlist,nodes))
vpos=dict(zip(vnlist,vnodes))
#将所有的节点坐标合并到一个列表
pos={}
pos=rpos.copy()
pos.update(npos)
pos.update(vpos)
#额外添加的节点也要加入字典
tdict={1025:(286,126),1026:(260,319)}
pos.update(tdict)
draw_networkx_edges(G, pos, edgelist=None, width=1.0, edge_color='k', style='solid', alpha=1.0, edge_cmap=None, edge_vmin=None, edge_vmax=None, ax=None, arrows=True, label=None, **kwds)
draw_networkx_labels(G, pos, labels=None, font_size=12, font_color='k', font_family='sans-serif', font_weight='normal', alpha=1.0, bbox=None, ax=None, **kwds)
#分布绘制村庄、交叉点、城市
nx.draw_networkx_nodes(roads,rpos,rnlist,node_size=50,node_color='r',with_labels=True)
nx.draw_networkx_nodes(roads,npos,nnlist,node_size=20,node_color='g',with_labels=True)
nx.draw_networkx_nodes(roads,vpos,vnlist,node_size=10,node_color='b',with_labels=True)
#绘制边
nx.draw_networkx_edges(roads,pos,nedges)
nx.draw_networkx_edges(roads,pos,vedges)
nx.draw_networkx_edges(roads,pos,tedges)
#标志字典,构建节点和标识点之间的映射关系,这里直接用节点名作为标识
nlabels=dict(zip(nnlist,nnlist))
rlabels=dict(zip(rnlist,rnlist))
#个性化标志
nx.draw_networkx_labels(roads,rpos,rlabels,font_size=20)
nx.draw_networkx_labels(roads,npos,nlabels,font_size=10)
plt.show()
其结果如下图:
从本文可以看出networkx提供的绘制方法还是很完善的,可以任意定制节点和边的大小、颜色和形状,以及其标识的属性和形状。本来笔者做实验都是使用的matlab,深感于networkx在网络分析方面的强大,因此投入到python的阵营。如果需要进行网络处理和呈现,networkx会是一个很好的备选工具。