การค้นหาละแวกใกล้เคียง (cliques) ในข้อมูลถนน (กราฟ)


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)

ทฤษฎีที่อาจเกี่ยวข้อง:

แจกแจงทุกรอบในกราฟที่ไม่ได้บอกทิศทาง


ปัญหาที่น่าสนใจ คุณอาจต้องการเพิ่มแท็กอัลกอริทึม ดูเหมือนว่าละแวกใกล้เคียงจะเป็นปัญหาได้ง่ายขึ้นหลังจากที่คุณได้รับการคำนวณบล็อก ในฐานะที่เป็นละแวกใกล้เคียงสิ่งที่คุณกำลังมองหาคือการแบ่งปันที่ถูกต้องใช่ไหม? และแต่ละบล็อคจะมีรายการขอบ ... สำหรับบล็อกฉันคิดว่ามันจะมีประโยชน์ในการรับทิศทางที่สำคัญของแต่ละตัวเลือกถนนที่โหนดและ "เลี้ยวขวา" (หรือเลี้ยวซ้าย) จนกว่าคุณจะครบวงจรหรือไปถึง ปลายตายหรือย้อนกลับไปที่ตัวคุณเองและย้อนรอยวนซ้ำ ดูเหมือนว่าจะมีบางกรณีมุมที่น่าสนใจแม้ว่า
Jeff H

ผมคิดว่านี้คำถามคล้ายกันมากกับปัญหาของคุณ 1. อย่างที่คุณเห็นในลิงค์ฉันทำงานเกี่ยวกับปัญหานิดหน่อยและมันก็เป็นปัญหาที่น่ากลัว (กลายเป็น NP-hard) อย่างไรก็ตามฮิวริสติกในคำตอบของฉันอาจยังให้ผลลัพธ์ที่ดีพอ
Paul Brodersen

วิธีแก้ปัญหาใดก็ตามที่คุณเห็นว่ายอมรับได้อาจเป็นวิธีแก้ปัญหาด้วยเช่นกันคุณควรกำหนดชุดข้อมูลทดสอบเพื่อตรวจสอบความถูกต้องของแต่ละวิธี ความหมายสำหรับกราฟตัวอย่างของคุณมันจะเป็นการดีถ้ามีหมายเหตุประกอบของบล็อคทั้งหมดในรูปแบบที่เครื่องอ่านได้ - ไม่ใช่แค่ตัวอย่างเล็ก ๆ น้อย ๆ ในรูปภาพ
Paul Brodersen

คำตอบ:


3

การค้นหาบล็อกของเมืองโดยใช้กราฟนั้นไม่น่าแปลกใจเลย โดยทั่วไปจำนวนนี้จะหาชุดที่เล็กที่สุดของวงที่เล็กที่สุด (SSSR) ซึ่งเป็นปัญหาที่สมบูรณ์แบบของ NP ความคิดเห็นของปัญหานี้ (และปัญหาที่เกี่ยวข้อง) สามารถพบได้ที่นี่ ในดังนั้นมีคำอธิบายหนึ่งในขั้นตอนวิธีการแก้ปัญหามันนี่ เท่าที่ฉันสามารถบอกได้ไม่มีการใช้งานที่สอดคล้องกันในnetworkx(หรือในหลามสำหรับเรื่องนั้น) ฉันลองวิธีนี้ในเวลาสั้น ๆ แล้วก็ละทิ้งมันไป - สมองของฉันไม่ได้เป็นรอยขีดข่วนสำหรับงานประเภทนี้ในวันนี้ ที่ถูกกล่าวว่าฉันจะให้รางวัลแก่ทุกคนที่อาจเข้าชมหน้านี้ในภายหลังและโพสต์การดำเนินการทดสอบอัลกอริทึมที่พบ 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)

สวัสดีเกลือตายยินดีมากและขอบคุณสำหรับการบิ่นใน. เมื่อทำnx.Graph(G)ฉันกำลังสูญเสียข้อมูลจำนวนมาก (directedness และประเภท multigraph) เพื่อให้ฉันมีช่วงเวลาที่ยากในการตรวจสอบคำตอบของคุณเป็นฉันลาดเทดูเหมือนจะเกี่ยวข้องกับรูปแบบของกราฟใหม่Iเพื่อ Gกราฟเดิมของฉัน
tmo

มันจะเป็นการทำงานเล็กน้อยเพื่อรักษาข้อมูลทางเรขาคณิตจากกราฟดั้งเดิม ฉันจะลองเร็ว ๆ นี้
salt-die

@tmo เพิ่งผ่านไป: คุณควรจะใช้คลาส MultiDiGraph (ที่ขยายกราฟ) ในกรณีนั้น
Théo Rubenach

1

ฉันไม่มีรหัส แต่ฉันเดาว่าเมื่อฉันอยู่บนทางเท้าถ้าฉันหันไปทางขวาที่แต่ละมุมฉันจะวนไปตามขอบของบล็อก ฉันไม่รู้ห้องสมุดดังนั้นฉันจะคุยเรื่องอัลโกที่นี่

  • จากจุดของคุณไปทางทิศเหนือจนกว่าคุณจะถึงถนน
  • เลี้ยวขวาให้มากที่สุดแล้วเดินไปตามถนน
  • ในมุมถัดไปค้นหา steets ทั้งหมดเลือกมุมที่ทำให้มุมที่เล็กที่สุดที่มีการนับถนนของคุณจากด้านขวา
  • เดินไปตามถนนสายนั้น
  • เลี้ยวขวาเป็นต้น

จริงๆแล้วมันเป็นอัลกอริธึมที่ใช้เพื่อออกจากเขาวงกต: วางมือขวาไว้บนกำแพงแล้วเดิน มันไม่ทำงานในกรณีที่ลูปในเขาวงกตคุณแค่วนรอบ แต่มันให้ทางออกสำหรับปัญหาของคุณ


นี่เป็นความคิดที่ดีกว่าที่ฉันมี ฉันจะเพิ่มคำตอบด้วยการใช้สัญชาตญาณของคุณ
Paul Brodersen

0

นี่คือการดำเนินการของความคิด Hashemi Emad ของ มันทำงานได้ดีตราบใดที่ตำแหน่งเริ่มต้นถูกเลือกเช่นนั้นจะมีวิธีก้าวทวนเข็มนาฬิกาในวงกลมที่แน่น สำหรับขอบบางโดยเฉพาะบริเวณด้านนอกของแผนที่เป็นไปไม่ได้ ฉันไม่ทราบวิธีเลือกตำแหน่งเริ่มต้นที่ดีหรือวิธีกรองโซลูชัน - แต่อาจมีบางคนมี

ตัวอย่างการทำงาน (เริ่มต้นด้วย edge (1204573687, 4555480822)):

ป้อนคำอธิบายรูปภาพที่นี่

ตัวอย่างที่วิธีนี้ใช้ไม่ได้ (เริ่มต้นด้วย edge (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.