Menemukan lingkungan (klik-klik) dalam data jalan (grafik)

10

Saya mencari cara untuk secara otomatis mendefinisikan lingkungan di kota-kota sebagai poligon pada grafik.

Definisi saya tentang suatu lingkungan memiliki dua bagian:

  1. Blok : Suatu area yang dilingkupi antara sejumlah jalan, di mana jumlah jalan (tepi) dan persimpangan (simpul) minimal tiga (satu segitiga).
  2. Lingkungan : Untuk setiap blok yang diberikan, semua blok berbatasan langsung dengan blok itu dan blok itu sendiri.

Lihat ilustrasi ini sebagai contoh:

masukkan deskripsi gambar di sini

Misalnya B4 adalah blok yang didefinisikan oleh 7 node dan 6 tepi yang menghubungkannya. Seperti sebagian besar contoh di sini, blok lain didefinisikan oleh 4 node dan 4 edge yang menghubungkannya. Juga, lingkungan dari B1 meliputi B2 (dan sebaliknya) sementara B2 juga termasuk B3 .

Saya menggunakan osmnx untuk mendapatkan data jalan dari OSM.

  1. Menggunakan osmnx dan networkx, bagaimana saya bisa melintasi grafik untuk menemukan node dan tepi yang mendefinisikan setiap blok?
  2. Untuk setiap blok, bagaimana saya bisa menemukan blok yang berdekatan?

Saya bekerja sendiri menuju sepotong kode yang mengambil grafik dan sepasang koordinat (lintang, bujur) sebagai input, mengidentifikasi blok yang relevan dan mengembalikan poligon untuk blok itu dan lingkungan seperti yang didefinisikan di atas.

Berikut adalah kode yang digunakan untuk membuat peta:

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)

dan usaha saya menemukan klik dengan jumlah node dan derajat yang berbeda.

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)

Teori yang mungkin relevan:

Menghitung Semua Siklus dalam Grafik yang Tidak Terarah

tmo
sumber
Masalah menarik. Anda mungkin ingin menambahkan tag algoritme ke sana. Tampaknya lingkungan akan menjadi masalah yang lebih mudah setelah Anda mengetahui bloknya. Sebagai tetangga, semua yang Anda cari adalah keunggulan bersama, benar? Dan setiap blok akan memiliki daftar tepi ... Untuk blok, saya pikir akan sangat membantu untuk mendapatkan arah mata angin dari setiap opsi jalan pada sebuah simpul dan "terus berbelok ke kanan" (atau kiri) sampai Anda menyelesaikan sirkuit, atau mencapai jalan buntu atau loop kembali pada diri sendiri dan mundur secara rekursif. Sepertinya akan ada beberapa kasus sudut yang menarik.
Jeff H
Saya pikir ini pertanyaan yang sangat mirip dengan masalah Anda tidak. 1. Seperti yang dapat Anda lihat di tautan, saya bekerja sedikit untuk masalah ini, dan ini adalah masalah serius (ternyata NP-hard). Heuristik dalam jawaban saya, bagaimanapun, mungkin masih memberi Anda hasil yang cukup baik.
Paul Brodersen
Karena solusi apa pun yang Anda anggap dapat diterima mungkin juga akan menjadi heuristik, mungkin ide yang baik untuk menetapkan set data uji untuk memvalidasi setiap pendekatan. Artinya, untuk grafik contoh Anda, akan lebih baik memiliki anotasi semua blok dalam bentuk yang dapat dibaca mesin - bukan hanya beberapa contoh dalam gambar.
Paul Brodersen

Jawaban:

3

Mengejar blok kota menggunakan grafik sangat mengejutkan. Pada dasarnya, ini sama dengan menemukan set cincin terkecil terkecil (SSSR), yang merupakan masalah NP-complete. Ulasan masalah ini (dan masalah terkait) dapat ditemukan di sini . Pada SO, ada satu deskripsi algoritma untuk menyelesaikannya di sini . Sejauh yang saya tahu, tidak ada implementasi yang sesuai di networkx(atau dalam python dalam hal ini). Saya mencoba pendekatan ini secara singkat dan kemudian meninggalkannya - otak saya tidak siap untuk pekerjaan seperti itu hari ini. Yang sedang berkata, saya akan memberikan hadiah kepada siapa pun yang mungkin mengunjungi halaman ini di kemudian hari dan memposting implementasi teruji dari sebuah algoritma yang menemukan SSSR dalam python.

Saya malah mengejar pendekatan yang berbeda, memanfaatkan fakta bahwa grafik dijamin planar. Secara singkat, alih-alih memperlakukan ini sebagai masalah grafik, kami memperlakukan ini sebagai masalah segmentasi gambar. Pertama, kami menemukan semua wilayah yang terhubung dalam gambar. Kami kemudian menentukan kontur di sekitar setiap wilayah, mengubah kontur dalam koordinat gambar kembali ke garis bujur dan garis lintang.

Diberikan impor dan definisi fungsi berikut:

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

Muat data. Cache impor, jika menguji ini berulang kali - jika tidak akun Anda dapat diblokir. Berbicara dari pengalaman di sini.

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

Pangkas node dan tepi yang tidak bisa menjadi bagian dari siklus. Langkah ini tidak sepenuhnya diperlukan tetapi menghasilkan kontur yang lebih bagus.

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

grafik pangkas

Konversikan plot ke gambar dan temukan wilayah yang terhubung:

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)

plot label wilayah

Untuk setiap wilayah berlabel, temukan kontur dan ubah koordinat piksel kontur kembali ke koordinat data.

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

sebidang kontur yang ditindih pada grafik prun

Tentukan semua titik dalam grafik asli yang termasuk dalam (atau pada) kontur.

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)

plot jaringan dengan node milik blok berwarna merah

Mencari tahu jika dua blok adalah tetangga cukup mudah. Periksa apakah mereka berbagi simpul:

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

Saya tidak sepenuhnya yakin itu cycle_basisakan memberi Anda lingkungan yang Anda cari, tetapi jika ya, itu adalah hal yang sederhana untuk mendapatkan grafik lingkungan dari itu:

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)
garam mati
sumber
Halo, selamat datang ke SO dan terima kasih telah menyumbang. Ketika melakukan nx.Graph(G)saya kehilangan banyak informasi (keteraturan dan tipe multigraf) jadi saya mengalami kesulitan memverifikasi jawaban Anda, karena saya sepertinya tidak bisa menghubungkan grafik baru Ike grafik asli saya G.
tmo
Ini akan menjadi sedikit pekerjaan untuk menjaga informasi geometrik dari grafik asli. Saya akan mencoba ini segera.
Garam mati
@tmo hanya lewat: Anda harus dapat menggunakan kelas MultiDiGraph (yang meluas Grafik) dalam kasus itu
Théo Rubenach
1

Saya tidak punya kode, tapi saya rasa begitu saya berada di trotoar, jika saya terus berbelok ke kanan di setiap sudut, saya akan siklus melalui tepi blok saya. Saya tidak tahu perpustakaan jadi saya hanya akan berbicara algo di sini.

  • dari titik Anda, pergi ke utara sampai Anda mencapai jalan
  • belok kanan sebanyak yang Anda bisa dan berjalan di jalan
  • di sudut berikutnya, temukan semua steet, pilih salah satu yang membuat sudut terkecil dengan penghitungan jalan Anda dari kanan.
  • berjalan di jalan itu.
  • belok kanan, dll.

Sebenarnya ini adalah algoritma yang digunakan untuk keluar dari labirin: jaga tangan kanan Anda di dinding dan berjalan. Ini tidak berfungsi jika loop di labirin, Anda hanya berputar-putar. Tetapi itu memberikan solusi untuk masalah Anda.

Hashemi Emad
sumber
Ini adalah ide yang jauh lebih baik daripada yang saya miliki. Saya akan menambahkan jawaban dengan implementasi intuisi Anda.
Paul Brodersen
0

Ini adalah implementasi dari ide Hashemi Emad . Ini bekerja dengan baik selama posisi awal dipilih sedemikian rupa sehingga ada cara untuk melangkah berlawanan arah dalam lingkaran yang ketat. Untuk beberapa tepi, khususnya di bagian luar peta, ini tidak mungkin. Saya tidak tahu cara memilih posisi awal yang baik, atau cara memfilter solusi - tetapi mungkin orang lain memilikinya.

Contoh kerja (dimulai dengan edge (1204573687, 4555480822)):

masukkan deskripsi gambar di sini

Contoh, di mana pendekatan ini tidak berfungsi (dimulai dengan edge (1286684278, 5818325197)):

masukkan deskripsi gambar di sini

Kode

#!/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')
Paul Brodersen
sumber