Tìm các vùng lân cận (cụm) trong dữ liệu đường phố (biểu đồ)


10

Tôi đang tìm cách để tự động xác định các vùng lân cận trong thành phố dưới dạng đa giác trên biểu đồ.

Định nghĩa của tôi về một khu phố có hai phần:

  1. Một khối : Một khu vực bao gồm giữa một số đường phố, trong đó số lượng đường (cạnh) và giao lộ (nút) tối thiểu là ba (một hình tam giác).
  2. Một vùng lân cận : Đối với bất kỳ khối nào, tất cả các khối liền kề với khối đó và chính khối đó.

Xem ví dụ này cho một ví dụ:

nhập mô tả hình ảnh ở đây

Ví dụ B4 là khối được xác định bởi 7 nút và 6 cạnh kết nối chúng. Như hầu hết các ví dụ ở đây, các khối khác được xác định bởi 4 nút và 4 cạnh kết nối chúng. Ngoài ra, vùng lân cận của B1 bao gồm B2 (và ngược lại) trong khi B2 cũng bao gồm B3 .

Tôi đang sử dụng osmnx để lấy dữ liệu đường phố từ OSM.

  1. Sử dụng osmnx và networkx, làm cách nào tôi có thể duyệt qua biểu đồ để tìm các nút và cạnh xác định từng khối?
  2. Đối với mỗi khối, làm thế nào tôi có thể tìm thấy các khối liền kề?

Tôi đang làm việc với một đoạn mã lấy biểu đồ và một cặp tọa độ (vĩ độ, kinh độ) làm đầu vào, xác định khối có liên quan và trả về đa giác cho khối đó và vùng lân cận như được xác định ở trên.

Đây là mã được sử dụng để tạo bản đồ:

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)

và nỗ lực của tôi trong việc tìm kiếm các cửa hàng với số lượng nút và độ khác nhau.

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)

Lý thuyết có thể có liên quan:

Liệt kê tất cả các chu kỳ trong một đồ thị vô hướng


Vấn đề thú vị. Bạn có thể muốn thêm thẻ thuật toán vào nó. Có vẻ như các khu phố sẽ là vấn đề dễ dàng hơn sau khi bạn nhận ra các khối. Là hàng xóm, tất cả những gì bạn đang tìm kiếm là một lợi thế chung, đúng không? Và mỗi khối sẽ có một danh sách các cạnh ... Đối với các khối, tôi nghĩ sẽ hữu ích khi lấy hướng chính của từng tùy chọn đường phố tại một nút và "tiếp tục rẽ phải" (hoặc sang trái) cho đến khi bạn hoàn thành một mạch hoặc đạt một ngõ cụt hoặc lặp lại chính mình và quay lui đệ quy. Có vẻ như sẽ có một số trường hợp góc thú vị, mặc dù.
Jeff H

Tôi nghĩ rằng đây câu hỏi là rất giống với vấn đề của bạn không. 1. Như bạn có thể thấy trong liên kết, tôi đã giải quyết vấn đề một chút và đó là một vấn đề rắc rối (hóa ra là NP-hard). Các heuristic trong câu trả lời của tôi, tuy nhiên, vẫn có thể cho bạn kết quả đủ tốt.
Paul Broderen

Vì bất kỳ giải pháp nào bạn cho là có thể chấp nhận có lẽ cũng sẽ là một heuristic, có thể là một ý tưởng tốt để xác định một bộ dữ liệu thử nghiệm để xác nhận mỗi phương pháp. Có nghĩa là, đối với biểu đồ ví dụ của bạn, sẽ rất tốt nếu có một chú thích của tất cả các khối ở dạng có thể đọc được bằng máy - không chỉ là một vài ví dụ trong ảnh.
Paul Broderen

Câu trả lời:


3

Tìm các khối thành phố bằng cách sử dụng biểu đồ là không đáng ngạc nhiên. Về cơ bản, số tiền này để tìm ra tập hợp các vòng nhỏ nhất (SSSR) nhỏ nhất, đây là một vấn đề hoàn chỉnh NP. Một đánh giá về vấn đề này (và các vấn đề liên quan) có thể được tìm thấy ở đây . Trên SO, có một mô tả về một thuật toán để giải quyết nó ở đây . Theo như tôi có thể nói, không có triển khai tương ứng trong networkx(hoặc trong python cho vấn đề đó). Tôi đã thử cách tiếp cận này một thời gian ngắn và sau đó từ bỏ nó - bộ não của tôi không thể trầy xước cho loại công việc đó ngày hôm nay. Điều đó đang được nói, tôi sẽ trao tiền thưởng cho bất kỳ ai có thể truy cập trang này vào một ngày sau đó và đăng một triển khai thử nghiệm của một thuật toán tìm thấy SSSR trong python.

Thay vào đó, tôi đã theo đuổi một cách tiếp cận khác, tận dụng thực tế là đồ thị được đảm bảo là phẳng. Tóm lại, thay vì coi đây là vấn đề đồ thị, chúng tôi coi đây là vấn đề phân đoạn hình ảnh. Đầu tiên, chúng tôi tìm thấy tất cả các khu vực kết nối trong hình ảnh. Sau đó, chúng tôi xác định đường viền xung quanh từng vùng, biến đổi các đường viền trong tọa độ hình ảnh trở lại kinh độ và vĩ độ.

Đưa ra các định nghĩa nhập khẩu và hàm sau:

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

Tải dữ liệu. Làm bộ nhớ cache khi nhập, nếu kiểm tra điều này nhiều lần - nếu không tài khoản của bạn có thể bị cấm. Nói từ kinh nghiệm ở đây.

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

Nút tỉa và các cạnh không thể là một phần của một chu kỳ. Bước này không thực sự cần thiết nhưng dẫn đến đường viền đẹp hơn.

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

đồ thị cắt tỉa

Chuyển đổi cốt truyện thành hình ảnh và tìm các khu vực được kết nối:

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)

lô nhãn khu vực

Đối với mỗi vùng được gắn nhãn, hãy tìm đường viền và chuyển đổi tọa độ pixel đường viền trở lại tọa độ dữ liệu.

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

biểu đồ đường viền chồng lên biểu đồ được cắt tỉa

Xác định tất cả các điểm trong biểu đồ ban đầu nằm bên trong (hoặc trên) đường viền.

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)

sơ đồ mạng với các nút thuộc khối màu đỏ

Tìm ra nếu hai khối là hàng xóm là khá dễ dàng. Chỉ cần kiểm tra nếu họ chia sẻ một nút:

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

2

Tôi không hoàn toàn chắc chắn rằng cycle_basissẽ cung cấp cho bạn các vùng lân cận mà bạn tìm kiếm, nhưng nếu có, việc lấy biểu đồ vùng lân cận từ đó là một điều đơn giản:

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)

Hi muối-chết, chào mừng bạn đến SO và cảm ơn cho sứt mẻ. Khi làm nx.Graph(G)tôi mất rất nhiều thông tin (directedness và loại multigraph) vì vậy tôi đang có một thời gian khó xác minh câu trả lời của bạn, như tôi không thể dường như mối liên hệ giữa đồ thị mới Iđến đồ thị ban đầu của tôi G.
tmo

Sẽ là một chút công việc để lưu giữ thông tin hình học từ biểu đồ ban đầu. Tôi sẽ thử điều này sớm thôi.
muối chết

@tmo chỉ cần đi ngang qua: bạn sẽ có thể sử dụng lớp MultiDiGraph (mở rộng Biểu đồ) trong trường hợp đó
Théo Rubenach

1

Tôi không có mã, nhưng tôi đoán rằng một khi tôi đang ở trên vỉa hè, nếu tôi tiếp tục rẽ sang phải ở mỗi góc, tôi sẽ đạp xe qua các cạnh của khối nhà. Tôi không biết các thư viện nên tôi sẽ chỉ nói chuyện ở đây.

  • từ điểm của bạn, đi về phía bắc cho đến khi bạn đến một đường phố
  • rẽ phải nhiều nhất có thể và đi bộ trên đường
  • ở góc tiếp theo, tìm tất cả các steets, chọn một góc tạo ra góc nhỏ nhất với đường phố của bạn đếm từ bên phải.
  • đi trên con đường đó.
  • rẽ phải, v.v.

Đó thực sự là một thuật toán được sử dụng để thoát khỏi mê cung: giữ tay phải của bạn trên tường và đi bộ. Nó không hoạt động trong trường hợp các vòng lặp trong mê cung, bạn chỉ cần lặp đi lặp lại. Nhưng nó đưa ra một giải pháp cho vấn đề của bạn.


Đây là một ý tưởng tốt hơn nhiều so với tôi đã có. Tôi sẽ thêm một câu trả lời với việc thực hiện trực giác của bạn.
Paul Broderen

0

Đây là một triển khai ý tưởng của Hashemi Emad . Nó hoạt động tốt miễn là vị trí bắt đầu được chọn sao cho tồn tại một cách để bước ngược chiều kim đồng hồ trong một vòng tròn chặt chẽ. Đối với một số cạnh, đặc biệt là xung quanh bên ngoài bản đồ, điều này là không thể. Tôi không có ý tưởng làm thế nào để chọn vị trí bắt đầu tốt hoặc cách lọc giải pháp - nhưng có lẽ ai đó có một vị trí.

Ví dụ làm việc (bắt đầu bằng cạnh (1204563687, 4555480822)):

nhập mô tả hình ảnh ở đây

Ví dụ, trong đó phương pháp này không hoạt động (bắt đầu bằng cạnh (1286684278, 5818325197)):

nhập mô tả hình ảnh ở đây

#!/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')
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.