Trovare blocchi di città usando il grafico è sorprendentemente non banale. Fondamentalmente, questo equivale a trovare il set più piccolo di anelli più piccoli (SSSR), che è un problema NP-completo. Una recensione di questo problema (e dei relativi problemi) può essere trovata qui . Su SO, c'è una descrizione di un algoritmo per risolverlo qui . Per quanto ne so, non esiste un'implementazione corrispondente in networkx
(o in Python per quella materia). Ho provato brevemente questo approccio e poi l'ho abbandonato - il mio cervello non è all'altezza per quel tipo di lavoro oggi. Detto questo, assegnerò una taglia a chiunque possa visitare questa pagina in un secondo momento e pubblicherò un'implementazione testata di un algoritmo che trova l'SSSR in Python.
Ho invece seguito un approccio diverso, sfruttando il fatto che il grafico è garantito per essere planare. In breve, invece di considerare questo come un problema grafico, lo trattiamo come un problema di segmentazione dell'immagine. Innanzitutto, troviamo tutte le regioni connesse nell'immagine. Determiniamo quindi il contorno attorno a ciascuna regione, trasformiamo i contorni in coordinate dell'immagine in lunghezze e latitudini.
Date le seguenti definizioni di importazione e funzione:
#!/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]
Carica i dati. Memorizza nella cache le importazioni, se la verifica ripetutamente, altrimenti il tuo account potrebbe essere bannato. Parlando per esperienza qui.
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')
Elimina nodi e bordi che non possono far parte di un ciclo. Questo passaggio non è strettamente necessario ma comporta contorni più piacevoli.
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
Converti trama in immagine e trova regioni collegate:
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)
Per ogni regione etichettata, trova il contorno e converti le coordinate dei pixel del contorno in coordinate dei dati.
# 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)
Determina tutti i punti nel grafico originale che rientrano (o accedono) al 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)
Capire se due blocchi sono vicini è abbastanza facile. Controlla se condividono un nodo:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")