グラフを使用して街区を見つけることは驚くほど簡単ではありません。基本的に、これはNP完全問題である最小リングの最小セット(SSSR)を見つけることになります。この問題(および関連する問題)のレビューはこちらにあります。SOでは、ここでそれを解決するアルゴリズムの1つの説明があります。私の知る限り、対応する実装はありませんnetworkx
(または、Pythonでは)。私はこのアプローチを手短に試してから、それを放棄しました。今日、その種の仕事のために私の脳はスクラッチに耐えられません。そうは言っても、後日このページにアクセスして、PythonでSSSRを検出するアルゴリズムのテスト済み実装を投稿する可能性がある人には、賞金を授与します。
代わりに、グラフが平面であることが保証されているという事実を利用して、別のアプローチを追求しました。簡単に言うと、これをグラフの問題として扱うのではなく、画像のセグメンテーションの問題として扱います。まず、画像内のすべての接続された領域を見つけます。次に、各領域の輪郭を決定し、画像座標の輪郭を経度と緯度に戻します。
次のインポートと関数定義があるとします。
#!/usr/bin/env python
# coding: utf-8
"""
Find house blocks in osmnx graphs.
"""
import numpy as np
import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from skimage.measure import label, find_contours, points_in_poly
from skimage.color import label2rgb
ox.config(log_console=True, use_cache=True)
def k_core(G, k):
H = nx.Graph(G, as_view=True)
H.remove_edges_from(nx.selfloop_edges(H))
core_nodes = nx.k_core(H, k)
H = H.subgraph(core_nodes)
return G.subgraph(core_nodes)
def plot2img(fig):
# remove margins
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
# convert to image
# https://stackoverflow.com/a/35362787/2912349
# https://stackoverflow.com/a/54334430/2912349
canvas = FigureCanvas(fig)
canvas.draw()
img_as_string, (width, height) = canvas.print_to_buffer()
as_rgba = np.fromstring(img_as_string, dtype='uint8').reshape((height, width, 4))
return as_rgba[:,:,:3]
データをロードします。これを繰り返しテストする場合は、インポートをキャッシュしてください。そうしないと、アカウントが禁止される可能性があります。ここでの経験から言えば。
G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
network_type='all', distance=500)
G_projected = ox.project_graph(G)
ox.save_graphml(G_projected, filename='network.graphml')
# G = ox.load_graphml('network.graphml')
サイクルの一部にできないノードとエッジを削除します。このステップは厳密には必要ありませんが、より良い輪郭になります。
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
プロットを画像に変換し、接続された領域を見つける:
img = plot2img(fig1)
label_image = label(img > 128)
image_label_overlay = label2rgb(label_image[:,:,0], image=img[:,:,0])
fig, ax = plt.subplots(1,1)
ax.imshow(image_label_overlay)
ラベル付けされた領域ごとに、輪郭を見つけ、輪郭のピクセル座標をデータ座標に変換します。
# using a large region here as an example;
# however we could also loop over all unique labels, i.e.
# for ii in np.unique(labels.ravel()):
ii = np.argsort(np.bincount(label_image.ravel()))[-5]
mask = (label_image[:,:,0] == ii)
contours = find_contours(mask.astype(np.float), 0.5)
# Select the largest contiguous contour
contour = sorted(contours, key=lambda x: len(x))[-1]
# display the image and plot the contour;
# this allows us to transform the contour coordinates back to the original data cordinates
fig2, ax2 = plt.subplots()
ax2.imshow(mask, interpolation='nearest', cmap='gray')
ax2.autoscale(enable=False)
ax2.step(contour.T[1], contour.T[0], linewidth=2, c='r')
plt.close(fig2)
# first column indexes rows in images, second column indexes columns;
# therefor we need to swap contour array to get xy values
contour = np.fliplr(contour)
pixel_to_data = ax2.transData + ax2.transAxes.inverted() + ax1.transAxes + ax1.transData.inverted()
transformed_contour = pixel_to_data.transform(contour)
transformed_contour_path = Path(transformed_contour, closed=True)
patch = PathPatch(transformed_contour_path, facecolor='red')
ax1.add_patch(patch)
等高線の内側(または上)にある元のグラフのすべての点を特定します。
x = G.nodes.data('x')
y = G.nodes.data('y')
xy = np.array([(x[node], y[node]) for node in G.nodes])
eps = (xy.max(axis=0) - xy.min(axis=0)).mean() / 100
is_inside = transformed_contour_path.contains_points(xy, radius=-eps)
nodes_inside_block = [node for node, flag in zip(G.nodes, is_inside) if flag]
node_size = [50 if node in nodes_inside_block else 0 for node in G.nodes]
node_color = ['r' if node in nodes_inside_block else 'k' for node in G.nodes]
fig3, ax3 = ox.plot_graph(G, node_color=node_color, node_size=node_size)
2つのブロックが隣接しているかどうかを判断するのは非常に簡単です。彼らがノードを共有しているかどうかを確認してください:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")