Znalezienie bloków miast za pomocą wykresu jest zaskakująco nietrywialne. Zasadniczo sprowadza się to do znalezienia najmniejszego zestawu najmniejszych pierścieni (SSSR), co stanowi problem NP-zupełny. Przegląd tego problemu (i powiązanych problemów) można znaleźć tutaj . Na tak, to jest jeden opis algorytmu rozwiązać go tutaj . O ile mi wiadomo, nie ma odpowiedniej implementacji w networkx
(lub w Pythonie). Próbowałem krótko to podejście, a potem je porzuciłem - mój mózg nie jest dziś w stanie sprostać tego rodzaju pracy. Biorąc to pod uwagę, przyznam nagrodę każdemu, kto może odwiedzić tę stronę w późniejszym terminie i opublikować przetestowaną implementację algorytmu, który znajdzie SSSR w pythonie.
Zamiast tego wybrałem inne podejście, wykorzystując fakt, że wykres ma zagwarantowaną płaskość. W skrócie, zamiast traktować to jako problem z grafem, traktujemy to jako problem z segmentacją obrazu. Najpierw znajdujemy wszystkie połączone regiony na obrazie. Następnie określamy kontur wokół każdego regionu, przekształcamy kontury we współrzędnych obrazu z powrotem na długości i szerokości geograficzne.
Biorąc pod uwagę następujące definicje importu i funkcji:
#!/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]
Załaduj dane. Wykonuj buforowanie importu, jeśli testujesz to wielokrotnie - w przeciwnym razie twoje konto może zostać zbanowane. Mówiąc z doświadczenia tutaj.
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')
Przycinaj węzły i krawędzie, które nie mogą być częścią cyklu. Ten krok nie jest absolutnie konieczny, ale powoduje ładniejsze kontury.
H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)
Konwertuj wykres na obraz i znajdź połączone regiony:
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)
Dla każdego oznaczonego regionu znajdź kontur i przekonwertuj współrzędne piksela konturu z powrotem na współrzędne danych.
# 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)
Określ wszystkie punkty na oryginalnym wykresie, które mieszczą się w (lub na) konturze.
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)
Ustalenie, czy dwa bloki są sąsiadami, jest dość łatwe. Wystarczy sprawdzić, czy współużytkują węzeł:
if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
print("Blocks are neighbors.")