Пошук мікрорайонів (кліків) у вуличних даних (графік)


10

Я шукаю спосіб автоматичного визначення мікрорайонів у містах як полігони на графіку.

Моє визначення мікрорайону має дві частини:

  1. Блок : площа, розміщена між кількома вулицями, де кількість вулиць (країв) та перехресть (вузли) становить мінімум три (трикутник).
  2. Район : Для будь-якого даного блоку всі блоки, безпосередньо прилеглі до цього блоку та самого блоку.

Дивіться приклад для прикладу:

введіть тут опис зображення

Напр. B4 - це блок, визначений 7 вузлами та 6 ребрами, що з'єднують їх. Як і більшість прикладів тут, інші блоки визначені 4 вузлами та 4 країми, що з'єднують їх. Крім того , сусідство з B1 включає B2 (і навпаки) , а B2 також включає в себе B3 .

Я використовую osmnx для отримання даних про вулицю від OSM.

  1. Як користуватися osmnx та networkx, як я можу пройти графік, щоб знайти вузли та ребра, які визначають кожен блок?
  2. Як я можу знайти сусідні блоки для кожного блоку?

Я працюю над фрагментом коду, який приймає графік і пару координат (широту, довготу) як вхідні дані, ідентифікує відповідний блок і повертає полігон для цього блоку та околиці, як визначено вище.

Ось код, який використовується для складання карти:

import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt

G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
                          network_type='all', 
                          distance=500)

і моя спроба знайти кліки з різною кількістю вузлів та ступенів.

def plot_cliques(graph, number_of_nodes, degree):
    ug = ox.save_load.get_undirected(graph)
    cliques = nx.find_cliques(ug)
    cliques_nodes = [clq for clq in cliques if len(clq) >= number_of_nodes]
    print("{} cliques with more than {} nodes.".format(len(cliques_nodes), number_of_nodes))
    nodes = set(n for clq in cliques_nodes for n in clq)
    h = ug.subgraph(nodes)
    deg = nx.degree(h)
    nodes_degree = [n for n in nodes if deg[n] >= degree]
    k = h.subgraph(nodes_degree)
    nx.draw(k, node_size=5)

Теорія, яка може бути актуальною:

Перерахування всіх циклів у непрямому графіку


Цікава проблема. Ви можете додати до нього тег алгоритму. Здається, що квартали будуть легшою проблемою після того, як ви зрозумієте блоки. Що стосується районів, все, що ви шукаєте, - це спільний край, правильно? І кожен блок матиме список ребер ... Щодо блоків, я думаю, буде корисним отримати кардинальний напрямок кожної вуличної опції на вузлі та "продовжувати поворот праворуч" (або ліворуч), поки ви не завершите контур або не досягнете глухий кут або петля назад на собі і відступати рекурсивно. Схоже, були б цікаві кутові випадки.
Джефф Н

Я думаю, це питання дуже схоже на вашу проблему немає. 1. Як ви бачите за посиланням, я трохи працював над проблемою, і вона є загальною (виявляється, що це NP). Однак моя евристична відповідь може все-таки дати вам досить хороші результати.
Пол Бродерсен

Оскільки будь-яке рішення, яке ви вважаєте прийнятним, ймовірно, також буде евристичним, може бути хорошою ідеєю визначити набір тестових даних для перевірки кожного підходу. Значить, для вашого прикладу графік було б добре мати анотацію всіх блоків у машиночитаному вигляді - не лише кілька прикладів зображення.
Пол Бродерсен

Відповіді:


3

Пошук міських блоків за допомогою графіка напрочуд нетривіальний. В основному, це означає пошук найменшого набору найменших кілець (SSSR), що є повною проблемою. Огляд цієї проблеми (та пов'язаних з цим проблем) можна знайти тут . На SO є один опис алгоритму для його вирішення тут . Наскільки я можу сказати, немає відповідної реалізації networkx(або в python для цього питання). Я спробував цей підхід коротко, а потім відмовився від нього - моєму мозку сьогодні не подряпини за таку роботу. Зважаючи на це, я присуджую нагороду кожному, хто пізніше зможе завітати на цю сторінку і опублікую перевірену реалізацію алгоритму, що знаходить SSSR в python.

Натомість я застосував інший підхід, використовуючи той факт, що графік гарантовано буде планарним. Коротко, замість того, щоб розглядати це як проблему графіків, ми трактуємо це як проблему сегментації зображення. По-перше, ми знаходимо всі пов'язані області на зображенні. Потім ми визначаємо контур навколо кожної області, перетворюємо контури в координатах зображень назад у довготи та широти.

З огляду на наступні визначення імпорту та функцій:

#!/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]

Завантажте дані. Кешуйте імпорт, якщо це тестувати повторно - інакше ваш рахунок може бути заборонений. Якщо говорити з досвіду тут.

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')

Чорносливі вузли та краї, які не можуть бути частиною циклу. Цей крок не є строго необхідним, але приводить до вишуканіших контурів.

H = k_core(G, 2)
fig1, ax1 = ox.plot_graph(H, node_size=0, edge_color='k', edge_linewidth=1)

обрізаний графік

Перетворіть сюжет у зображення та знайдіть пов’язані регіони:

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)

сюжет етикетки регіону

Знайдіть контур для кожної міченої області та перетворіть контури піксельних координат назад у координати даних.

# 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)

сюжет контуру, накладений на обрізаний графік

Визначте всі точки оригінального графіка, які потрапляють всередину (або на) контуру.

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)

Діаграма мережі з вузлами, що належать блоку червоного кольору

З'ясувати, чи є два блоки сусідами, досить легко. Просто перевірте, чи ділиться ними вузол:

if set(nodes_inside_block_1) & set(nodes_inside_block_2): # empty set evaluates to False
    print("Blocks are neighbors.")

2

Я не зовсім впевнений, що cycle_basisце дасть вам райони, до яких ви шукаєте, але якщо це станеться, отримати просту дію з нього:

import osmnx as ox
import networkx as nx
import matplotlib.pyplot as plt

G = ox.graph_from_address('Nørrebrogade 20, Copenhagen Municipality',
                          network_type='all',
                          distance=500)

H = nx.Graph(G) # make a simple undirected graph from G

cycles = nx.cycles.cycle_basis(H) # I think a cycle basis should get all the neighborhoods, except
                                  # we'll need to filter the cycles that are too small.
cycles = [set(cycle) for cycle in cycles if len(cycle) > 2] # Turn the lists into sets for next loop.

# We can create a new graph where the nodes are neighborhoods and two neighborhoods are connected if
# they are adjacent:

I = nx.Graph()
for i, n in enumerate(cycles):
    for j, m in enumerate(cycles[i + 1:], start=i + 1):
        if not n.isdisjoint(m):
            I.add_edge(i, j)

Вітаю, солі-гине, ласкаво просимо до ДУ і дякуємо за чіпінг. Коли nx.Graph(G)я роблю, я втрачаю багато інформації (спрямованість та тип мультиграфування), тому мені важко перевірити Вашу відповідь, оскільки я не можу пов’язати новий графік Iз мій оригінальний графік G.
tmo

Потрібно трохи попрацювати над збереженням геометричної інформації з початкового графіка. Я спробую це скоро.
соляно-помре

@tmo просто проходячи повз: у цьому випадку ви маєте можливість використовувати клас MultiDiGraph (який розширює графік)
Тео Рубенах

1

У мене немає коду, але я гадаю, що як тільки я переходжу на тротуар, якщо я продовжую повертати праворуч у кожному куті, я пройдуся по краях блоку. Я не знаю бібліотек, тому я просто поговорю тут про альго.

  • від своєї точки, їдьте на північ, поки не досягнете вулиці
  • поверніть праворуч, наскільки можете, і пройдіться вулицею
  • на наступному куті знайдіть усі крути, виберіть той, який робить найменший кут, коли ваша вулиця рахується праворуч.
  • ходити по тій вулиці.
  • повернути праворуч тощо.

Це фактично алгоритм, який потрібно використовувати для виходу з лабіринту: тримайте праву руку на стіні і ходіть. Це не працює, якщо в лабіринті є петлі, ви просто обведіть їх. Але це дає вирішення вашої проблеми.


Це набагато краща ідея, ніж я. Я додам відповідь із реалізацією вашої інтуїції.
Пол Бродерсен

0

Це втілення ідеї Хашемі Емада . Це добре працює до тих пір, поки вихідне положення вибране таким чином, що існує спосіб крокувати проти годинникової стрілки по тугому колу. Для деяких країв, зокрема навколо зовнішньої сторони карти, це неможливо. Я не маю уявлення, як вибрати хороші вихідні позиції чи як фільтрувати рішення - але, можливо, хтось інший має.

Робочий приклад (починаючи з краю (1204573687, 4555480822)):

введіть тут опис зображення

Приклад, коли такий підхід не працює (починаючи з краю (1286684278, 5818325197)):

введіть тут опис зображення

Код

#!/usr/bin/env python
# coding: utf-8

"""
Find house blocks in osmnx graphs.
"""

import numpy as np
import networkx as nx
import osmnx as ox

import matplotlib.pyplot as plt; plt.ion()

from matplotlib.path import Path
from matplotlib.patches import PathPatch


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 get_vector(G, n1, n2):
    dx = np.diff([G.nodes.data()[n]['x'] for n in (n1, n2)])
    dy = np.diff([G.nodes.data()[n]['y'] for n in (n1, n2)])
    return np.array([dx, dy])


def angle_between(v1, v2):
    # https://stackoverflow.com/a/31735642/2912349
    ang1 = np.arctan2(*v1[::-1])
    ang2 = np.arctan2(*v2[::-1])
    return (ang1 - ang2) % (2 * np.pi)


def step_counterclockwise(G, edge, path):
    start, stop = edge
    v1 = get_vector(G, stop, start)
    neighbors = set(G.neighbors(stop))
    candidates = list(set(neighbors) - set([start]))
    if not candidates:
        raise Exception("Ran into a dead end!")
    else:
        angles = np.zeros_like(candidates, dtype=float)
        for ii, neighbor in enumerate(candidates):
            v2 = get_vector(G, stop, neighbor)
            angles[ii] = angle_between(v1, v2)
        next_node = candidates[np.argmin(angles)]
        if next_node in path:
            # next_node might not be the same as the first node in path;
            # therefor, we backtrack until we end back at next_node
            closed_path = [next_node]
            for node in path[::-1]:
                closed_path.append(node)
                if node == next_node:
                    break
            return closed_path[::-1] # reverse to have counterclockwise path
        else:
            path.append(next_node)
            return step_counterclockwise(G, (stop, next_node), path)


def get_city_block_patch(G, boundary_nodes, *args, **kwargs):
    xy = []
    for node in boundary_nodes:
        x = G.nodes.data()[node]['x']
        y = G.nodes.data()[node]['y']
        xy.append((x, y))
    path = Path(xy, closed=True)
    return PathPatch(path, *args, **kwargs)


if __name__ == '__main__':

    # --------------------------------------------------------------------------------
    # load data

    # # DO CACHE RESULTS -- otherwise you can get banned for repeatedly querying the same address
    # 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')

    # --------------------------------------------------------------------------------
    # prune nodes and edges that should/can not be part of a cycle;
    # this also reduces the chance of running into a dead end when stepping counterclockwise

    H = k_core(G, 2)

    # --------------------------------------------------------------------------------
    # pick an edge and step counterclockwise until you complete a circle

    # random edge
    total_edges = len(H.edges)
    idx = np.random.choice(total_edges)
    start, stop, _ = list(H.edges)[idx]

    # good edge
    # start, stop = 1204573687, 4555480822

    # bad edge
    # start, stop = 1286684278, 5818325197

    steps = step_counterclockwise(H, (start, stop), [start, stop])

    # --------------------------------------------------------------------------------
    # plot

    patch = get_city_block_patch(G, steps, facecolor='red', edgecolor='red', zorder=-1)

    node_size = [100 if node in steps else 20 for node in G.nodes]
    node_color = ['crimson' if node in steps else 'black' for node in G.nodes]
    fig1, ax1 = ox.plot_graph(G, node_size=node_size, node_color=node_color, edge_color='k', edge_linewidth=1)
    ax1.add_patch(patch)
    fig1.savefig('city_block.png')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.