거리 데이터 (그래프)에서 이웃 (도둑) 찾기


10

도시의 이웃을 그래프에서 다각형으로 자동 정의하는 방법을 찾고 있습니다.

이웃에 대한 나의 정의는 두 부분으로 구성됩니다.

  1. 블록 : 거리 (가장자리)와 교차로 (노드)의 수가 최소 3 개 (삼각형) 인 여러 거리 사이에있는 영역입니다.
  2. 이웃 : 특정 블록에 대해 해당 블록에 직접 인접한 모든 블록과 블록 자체.

예제는이 그림을 참조하십시오.

여기에 이미지 설명을 입력하십시오

예를 들어 B4 는 7 개의 노드와 6 개의 에지를 연결하는 블록으로 정의됩니다. 여기서 대부분의 예와 같이 다른 블록은 4 개의 노드와 4 개의 에지를 연결하여 정의됩니다. 또한, 이웃B1을 포함 B2 하면서 (또는 그 반대)를 B2가 또한 포함 B3를 .

OSM에서 거리 데이터를 얻기 위해 osmnx 를 사용하고 있습니다.

  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)

관련이있는 이론 :

무 방향 그래프의 모든 사이클 열거


재미있는 문제. 알고리즘 태그를 추가 할 수 있습니다. 블록을 알아 낸 후에는 이웃이 더 쉬운 문제인 것 같습니다. 이웃으로서, 당신이 찾고있는 모든 것은 공유 된 가장자리입니다. 맞습니까? 그리고 각 블록에는 모서리의 목록이있을 것입니다 ... 블록의 경우 노드에서 각 거리 옵션의 기본 방향을 가져 와서 회로를 완료하거나 도달 할 때까지 "오른쪽으로 계속"(또는 왼쪽으로) 이동하는 것이 도움이 될 것입니다 막 다른 골목이나 자신을 되풀이하고 재귀 적으로 추적합니다. 그래도 흥미로운 코너 사례가있는 것처럼 보입니다.
Jeff H

나는 질문이 당신의 문제 no와 매우 유사 하다고 생각 합니다 . 1. 당신이 링크에서 볼 수 있듯이, 나는 약간의 문제에 대해 일했고, 그것은 심하게 나쁜 것입니다 (NP-hard로 밝혀졌습니다). 그러나 내 대답의 휴리스틱은 여전히 ​​충분한 결과를 줄 수 있습니다.
Paul Brodersen

수용 가능한 것으로 간주되는 솔루션이 휴리스틱 일 수 있으므로 각 접근 방식의 유효성을 검사하기 위해 테스트 데이터 세트를 정의하는 것이 좋습니다. 예를 들어 그래프의 경우 이미지의 몇 가지 예만이 아니라 기계가 읽을 수있는 형태로 모든 블록의 주석을 갖는 것이 좋습니다.
Paul Brodersen

답변:


3

그래프를 사용하여 도시 블록을 찾는 것은 놀라운 일이 아닙니다. 기본적으로 이것은 NP- 완전 문제인 가장 작은 링 세트 (SSSR)를 찾는 것입니다. 이 문제 (및 관련 문제)에 대한 검토는 여기 에서 찾을 수 있습니다 . SO에는 여기 에서 해결하는 알고리즘에 대한 설명이 있습니다 . 내가 알 수있는 한, networkx(또는 그 문제에 대한 파이썬 )에는 해당 구현이 없습니다 . 나는이 접근법을 잠깐 시도한 후 버렸다. 오늘날 나의 두뇌는 그런 종류의 작업에 흠집이 없다. 즉 , 나중에이 페이지를 방문하여 Python에서 SSSR을 찾는 알고리즘의 테스트 구현을 게시 할 수있는 사람에게 현상금을 수여합니다.

대신 그래프가 평면임을 보장한다는 사실을 활용하여 다른 접근법을 추구했습니다. 간단히, 이것을 그래프 문제로 취급하는 대신, 이미지 분할 문제로 취급합니다. 먼저 이미지에서 연결된 모든 영역을 찾습니다. 그런 다음 각 영역 주위의 윤곽을 결정하고 이미지 좌표의 윤곽을 경도와 위도로 다시 변환합니다.

다음과 같은 가져 오기 및 함수 정의가 제공됩니다.

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

안녕하세요 소금 다이에 치핑을 위해 SO 감사에 오신 것을 환영합니다. 일을 할 때 nx.Graph(G)나는 많은 정보 (directedness 및 다중 그래프 타입)을 잃고 있어요 나는 새로운 그래프와 관련된 것으로 보인다 어차피 나는, 당신의 답을 확인하는 어려운 시간을 보내고 있어요 그래서 I로를 내 원래 그래프 G.
tmo

원래 그래프에서 기하학적 정보를 보존하는 것은 약간의 작업이 될 것입니다. 나는 이것을 곧 시도 할 것이다.
소금 다이

@tmo 그냥지나 가기 :이 경우 MultiDiGraph 클래스 (Graph를 확장하는)를 사용할 수 있어야합니다
Théo Rubenach

1

코드가 없지만 일단 보도에 있으면 각 모서리에서 오른쪽으로 계속 돌면 블록 가장자리를 순환합니다. 나는 도서관을 모른다. 그래서 나는 단지 여기서 이야기 할 것이다.

  • 당신의 지점에서, 당신이 거리에 도달 할 때까지 북쪽으로 이동
  • 당신이 할 수있는만큼 우회전하고 거리를 걷다
  • 다음 구석에서 모든 Steet을 찾고 오른쪽에서 세는 거리와 가장 작은 각도를 만드는 것을 선택하십시오.
  • 그 거리를 걷다.
  • 우회전 등

실제로 미로를 종료하는 데 사용하는 알고리즘입니다. 오른손을 벽에 대고 걸어보세요. 미로에서 루프가 발생하면 작동하지 않습니다. 그러나 그것은 당신의 문제에 대한 해결책을 제공합니다.


이것은 내가 가진 것보다 훨씬 더 좋은 아이디어입니다. 직관의 구현에 대한 답변을 추가하겠습니다.
Paul Brodersen

0

이것은 Hashemi Emad의 아이디어를 구현 한 입니다. 시작 위치를 선택하면 단단한 원 안에 시계 반대 방향으로 밟을 수있는 방법이 있습니다. 일부지도, 특히지도 외부 주변에서는 불가능합니다. 좋은 시작 위치를 선택하는 방법이나 솔루션을 필터링하는 방법에 대한 아이디어가 없지만 다른 사람이 있습니다.

실제 예 (가장자리로 시작 (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')
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.