Encontrar blocos urbanos usando o gráfico é surpreendentemente não trivial. Basicamente, isso equivale a encontrar o menor conjunto de anéis menores (SSSR), que é um problema completo do NP. Uma revisão deste problema (e problemas relacionados) pode ser encontrada aqui . No SO, há uma descrição de um algoritmo para resolvê-lo aqui . Até onde eu sei, não existe uma implementação correspondente em networkx
(ou em python). Tentei essa abordagem brevemente e depois a abandonei - meu cérebro não está preparado para esse tipo de trabalho hoje. Dito isto, concederei uma recompensa a qualquer pessoa que possa visitar esta página posteriormente e publicarei uma implementação testada de um algoritmo que encontra o SSSR em python.
Em vez disso, segui uma abordagem diferente, aproveitando o fato de que o gráfico é garantido como plano. Resumidamente, em vez de tratar isso como um problema gráfico, tratamos isso como um problema de segmentação de imagem. Primeiro, encontramos todas as regiões conectadas na imagem. Em seguida, determinamos o contorno em torno de cada região, transformamos os contornos das coordenadas da imagem em longitudes e latitudes.
Dadas as seguintes importações e definições de função:
#!/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]
Carregue os dados. Faça o cache das importações, se estiver testando isso repetidamente - caso contrário, sua conta poderá ser banida. Falando por experiência aqui.
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')
Remova nós e arestas que não podem fazer parte de um ciclo. Esta etapa não é estritamente necessária, mas resulta em contornos mais agradáveis.
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
Converta plotagem em imagem e encontre regiões conectadas:
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)
Para cada região rotulada, localize o contorno e converta as coordenadas de pixel de contorno novamente em coordenadas de dados.
# 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)
Determine todos os pontos no gráfico original que caem dentro (ou sobre) o contorno.
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)
Descobrir se dois quarteirões são vizinhos é bastante fácil. Basta verificar se eles compartilham um nó:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")