Trouver des blocs de villes à l'aide du graphique est étonnamment non trivial. Fondamentalement, cela revient à trouver le plus petit ensemble de plus petits anneaux (SSSR), qui est un problème NP-complet. Un examen de ce problème (et des problèmes connexes) peut être trouvé ici . Sur SO, il existe une description d'un algorithme pour le résoudre ici . Pour autant que je sache, il n'y a pas d'implémentation correspondante dans networkx
(ou en python d'ailleurs). J'ai essayé brièvement cette approche, puis je l'ai abandonnée - mon cerveau n'est pas à la hauteur pour ce genre de travail aujourd'hui. Cela étant dit, je remettrai une prime à quiconque pourrait visiter cette page à une date ultérieure et publier une implémentation testée d'un algorithme qui trouve le SSSR en python.
Au lieu de cela, j'ai poursuivi une approche différente, en tirant parti du fait que le graphique est garanti d'être plan. En bref, au lieu de traiter cela comme un problème de graphe, nous le traitons comme un problème de segmentation d'image. Tout d'abord, nous trouvons toutes les régions connectées dans l'image. Nous déterminons ensuite le contour autour de chaque région, transformons les contours en coordonnées image en longitudes et latitudes.
Étant donné les importations et les définitions de fonction suivantes:
#!/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]
Chargez les données. Mettez en cache les importations, si vous testez cela à plusieurs reprises - sinon votre compte peut être banni. Parlant d'expérience ici.
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')
Taillez les nœuds et les arêtes qui ne peuvent pas faire partie d'un cycle. Cette étape n'est pas strictement nécessaire mais donne de plus beaux contours.
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
Convertissez le tracé en image et trouvez les régions connectées:
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)
Pour chaque région étiquetée, recherchez le contour et reconvertissez les coordonnées des pixels du contour en coordonnées de données.
# 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)
Déterminez tous les points du graphique d'origine qui se trouvent à l'intérieur (ou sur) le contour.
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)
Il est assez facile de déterminer si deux blocs sont voisins. Vérifiez simplement s'ils partagent un nœud:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")