Encontrar bloques de ciudades usando el gráfico es sorprendentemente no trivial. Básicamente, esto equivale a encontrar el conjunto más pequeño de anillos más pequeños (SSSR), que es un problema NP-completo. Una revisión de este problema (y problemas relacionados) se puede encontrar aquí . En SO, hay una descripción de un algoritmo para resolverlo aquí . Por lo que puedo decir, no hay una implementación correspondiente en networkx
(o en Python para el caso). Probé este enfoque brevemente y luego lo abandoné: mi cerebro no está preparado para ese tipo de trabajo hoy. Dicho esto, otorgaré una recompensa a cualquiera que visite esta página más adelante y publique una implementación probada de un algoritmo que encuentre el SSSR en Python.
En cambio, he seguido un enfoque diferente, aprovechando el hecho de que se garantiza que el gráfico sea plano. Brevemente, en lugar de tratar esto como un problema gráfico, tratamos esto como un problema de segmentación de imagen. Primero, encontramos todas las regiones conectadas en la imagen. Luego determinamos el contorno alrededor de cada región, transformamos los contornos en las coordenadas de la imagen a longitudes y latitudes.
Dadas las siguientes importaciones y definiciones de funciones:
#!/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]
Cargar los datos. Guarde en caché las importaciones, si lo prueba repetidamente, de lo contrario su cuenta puede ser bloqueada. Hablando por experiencia aquí.
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')
Pode nodos y bordes que no pueden ser parte de un ciclo. Este paso no es estrictamente necesario pero da como resultado contornos más bonitos.
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
Convierta la trama en imagen y encuentre regiones 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ón etiquetada, encuentre el contorno y convierta las coordenadas del píxel del contorno nuevamente en coordenadas de datos.
# 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 los puntos en el gráfico original que caen dentro (o sobre) el 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)
Averiguar si dos bloques son vecinos es bastante fácil. Solo verifique si comparten un nodo:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")