在街道数据中查找街区(斜线)(图形)


10

我正在寻找一种自动在城市中将邻域定义为图形上的多边形的方法。

我对邻居的定义包括两个部分:

  1. 街区:在多条街道之间封闭的区域,其中街道(边)和交叉点(节点)的数量最少为3(三角形)。
  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)

可能相关的理论:

枚举无向图中的所有循环


有趣的问题。您可能要向其添加算法标签。弄清楚障碍物之后,似乎邻居将是更容易解决的问题。作为社区,您所寻找的只是共享的优势,对吗?而且每个街区都会有一条边的列表...对于街区,我认为在节点上获取每个街道选项的基本方向并“保持向右转”(或向左转),直到完成电路或到达会有所帮助。死胡同或循环回自己并递归回溯。不过,似乎会有一些有趣的极端情况。
Jeff H

我认为这个问题与您的问题非常相似。1.如您在链接中所看到的,我花了一点时间解决了这个问题,这是一个棘手的问题(结果证明是NP问题)。但是,我的回答中的启发式方法可能仍会给您足够好的结果。
Paul Brodersen

由于您认为可接受的任何解决方案都可能是一种启发式方法,因此定义一个测试数据集来验证每种方法可能是一个好主意。意思是,对于您的示例图,最好以机器可读的形式对所有块进行注释,而不仅仅是图像中的几个示例。
Paul Brodersen

Answers:


3

令人惊讶的是,使用该图查找城市街区并非易事。基本上,这等于找到最小的最小环集(SSSR),这是一个NP完全问题。有关此问题(及相关问题)的评论可以在这里找到。就这么,有一个算法的一个描述来解决它在这里。据我所知,在networkx(或python中)没有相应的实现。我短暂地尝试了这种方法,然后放弃了它-我的大脑现在还不适应这种工作。话虽如此,我将奖励任何以后访问此页面并发布经过测试的算法实现的人,该算法可以在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)

嗨,salt-die,您好,欢迎来到SO,并感谢您参与。这样做时,nx.Graph(G)我会丢失很多信息(方向性和多图类型),因此我很难验证您的答案,因为我似乎无法将新图I与我的原始图G
TMO

从原始图形保留几何信息将需要一点工作。我将尽快尝试。
salt-die

@tmo只是路过:你应该能够使用MultiDiGraph类在这种情况下(扩展图形)
西奥Rubenach

1

我没有代码,但是我想一旦我在人行道上,如果我继续在每个拐角处向右转,我就会在块的边缘循环。我不知道图书馆,所以我这里只讲算法。

  • 从您的角度出发,向北走,直到到达一条街
  • 尽可能右转并在街上行走
  • 在下一个拐角处,找到所有的石ets,然后从右侧开始选择与街道成最小角度的石ets。
  • 走在那条街上。
  • 向右转等

实际上,这是一种用于退出迷宫的算法:将右手放在墙上行走。万一迷宫中有循环,它就不起作用了,而您只是循环而已。但这为您的问题提供了解决方案。


这是一个比我更好的主意。我将为您的直觉实现添加答案。
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')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.