Sokak verilerinde mahalleleri (cliques) bulma (grafik)


10

Şehirlerdeki mahalleleri otomatik olarak bir grafik üzerinde çokgenler olarak tanımlamanın bir yolunu arıyorum.

Bir mahalle tanımımın iki kısmı var:

  1. Bir blok : Sokakların (kenarların) ve kavşakların (düğümlerin) sayısının en az üç (üçgen) olduğu birkaç sokak arasında yer alan bir alan.
  2. Bir mahalle : Herhangi bir blok için, doğrudan o bloğa bitişik tüm bloklar ve bloğun kendisi.

Örnek için bu resme bakın:

resim açıklamasını buraya girin

Örneğin B4 , 7 düğüm ve bunları birleştiren 6 kenar tarafından tanımlanan bloktur. Buradaki örneklerin çoğu gibi, diğer bloklar 4 düğüm ve bunları birleştiren 4 kenar ile tanımlanır. Ayrıca, mahalle arasında B1 içeren B2 ise (ve tersi) B2 da içerir B3 .

OSM'den sokak verileri almak için osmnx kullanıyorum .

  1. Osmnx ve networkx kullanarak, her bloğu tanımlayan düğümleri ve kenarları bulmak için bir grafikte nasıl gezinebilirim?
  2. Her blok için bitişik blokları nasıl bulabilirim?

Kendimi bir grafik ve bir çift koordinat (enlem, boylam) girdi alır, ilgili blok tanımlar ve yukarıda tanımlandığı gibi bu blok ve mahalle için çokgen döndüren bir kod parçası üzerinde çalışıyorum.

Haritayı yapmak için kullanılan kod:

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)

ve farklı sayıda düğüm ve dereceye sahip uçurum bulma girişimim.

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)

İlgili olabilecek teori:

Yönlendirilmemiş Bir Grafikteki Tüm Döngüleri Numaralandırma


İlginç bir sorun. Algoritma etiketini buna eklemek isteyebilirsiniz. Anlaşılan blokları bulduktan sonra mahallelerin daha kolay bir sorun olacağını düşünüyoruz. Mahalleler olarak aradığınız tek şey ortak bir avantaj, değil mi? Ve her bloğun bir kenar listesi olacak ... Bloklar için, bir düğümdeki her sokak seçeneğinin kardinal yönünü bulmanın ve bir devreyi tamamlayana veya çıkmaz sokak ya da kendinize geri döngü ve özyinelemeli olarak geri adım. Yine de bazı ilginç köşe vakaları olacak gibi görünüyor.
Jeff H

Bence bu soru senin problemine çok benziyor hayır. 1. Bağlantıda da görebileceğiniz gibi, ben biraz sorun üzerinde çalıştım ve bu garip bir sorun (NP-zor olduğu ortaya çıkıyor). Cevabımdaki buluşsal yöntem, yine de size yeterince iyi sonuçlar verebilir.
Paul Brodersen

Kabul edilebilir olduğunu düşündüğünüz herhangi bir çözüm muhtemelen sezgisel olacağından, her yaklaşımı doğrulamak için bir test verisi tanımlamak iyi bir fikir olabilir. Yani, örnek grafiğiniz için, bir görüntüdeki sadece birkaç örnek değil, tüm blokların makine tarafından okunabilir biçimde bir açıklamasına sahip olmak iyi olur.
Paul Brodersen

Yanıtlar:


3

Grafiği kullanarak şehir bloklarını bulmak şaşırtıcı değil. Temel olarak, bu, NP-tam bir problem olan en küçük halka grubunu (SSSR) bulmak anlamına gelir. Bu sorunun (ve ilgili sorunların) bir incelemesini burada bulabilirsiniz . Benzerleri, bir algoritma biri açıklama bunu çözmek için orada burada . Anlayabildiğim kadarıyla, networkx(veya bu konuda python) karşılık gelen bir uygulama yoktur . Bu yaklaşımı kısaca denedim ve sonra bıraktım - beynim bugün bu tür işler için çizilmeye hazır değil. Bununla birlikte, bu sayfayı daha sonra ziyaret edebilecek ve SSSR'yi python'da bulan bir algoritmanın test edilmiş bir uygulamasını yayınlayabilecek herkese bir ödül vereceğim.

Bunun yerine farklı bir yaklaşım izledim ve grafiğin düzlemsel olduğu garanti ediliyor. Kısacası, bunu bir grafik problemi olarak ele almak yerine, bunu bir görüntü segmentasyonu problemi olarak ele alıyoruz. İlk olarak, görüntüdeki tüm bağlı bölgeleri buluyoruz. Daha sonra her bölgenin etrafındaki konturu belirleriz, görüntü koordinatlarındaki konturları tekrar boylamlara ve enlemlere dönüştürürüz.

Aşağıdaki içe aktarmalar ve işlev tanımları göz önüne alındığında:

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

Verileri yükleyin. Bunu tekrar tekrar test ediyorsanız, içe aktarma işlemlerini önbelleğe alın - aksi takdirde hesabınız yasaklanabilir. Burada deneyimden konuşma.

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

Bir döngünün parçası olamayan düğümleri ve kenarları budanır. Bu adım kesinlikle gerekli değildir, ancak daha güzel konturlarla sonuçlanır.

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

budanmış grafik

Grafiği resme dönüştürün ve bağlı bölgeleri bulun:

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)

bölge etiketlerinin çizimi

Etiketli her bölge için konturu bulun ve kontur piksel koordinatlarını tekrar veri koordinatlarına dönüştürün.

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

budamış grafik üzerine bindirilmiş kontur çizimi

Orijinal grafikte konturun içine (veya üstüne) düşen tüm noktaları belirleyin.

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)

kırmızı bloğa ait düğümleri olan ağın çizimi

İki bloğun komşu olup olmadığını anlamak oldukça kolaydır. Bir düğümü paylaşıp paylaşmadıklarını kontrol etmeniz yeterlidir:

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

2

cycle_basisSize aradığınız semtleri vereceğinden tam olarak emin değilim , ancak eğer varsa, mahalle grafiğini almak basit bir şey:

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)

Merhaba tuz kalıbı içinde yonga için SO ve teşekkür edilir. Yaparken nx.Graph(G)ben birçok bilgi (yönetme ve ufak matbaa tipi) kaybediyorum ben yeni grafik alakalı görünen olamaz gibi ben cevap doğrulayarak zor günler yaşıyorum öylesine Iiçin benim orijinal grafiğim G.
tmo

Geometrik bilgileri orijinal grafikten korumak biraz uğraşacaktır. Bunu yakında deneyeceğim.
salt-die

@tmo sadece geçerek: bu durumda MultiDiGraph sınıfını (Graph'i genişleten) kullanabilmelisiniz
Théo Rubenach

1

Bir kodum yok, ama kaldırımda olduğumda, her köşede sağa dönmeye devam edersem, bloğumun kenarları arasında dolaşacağımı tahmin ediyorum. Kütüphaneleri tanımıyorum, bu yüzden burada sadece algo ile konuşacağım.

  • andan itibaren sokağa çıkana kadar kuzeye git
  • olabildiğince sağa dön ve sokakta yürü
  • bir sonraki köşede, tüm yönlendirmeleri bulun, sağdan sokak sayımıyla en küçük açıyı yapanı seçin.
  • o sokakta yürü.
  • sağa dönün vs.

Aslında bir labirentten çıkmak için kullanılacak bir algoritma: sağ elinizi duvarda tutun ve yürüyün. Labirentte döngüler olması durumunda işe yaramıyor, etrafta dolaşıyorsunuz. Ancak sorununuza bir çözüm sunar.


Bu benden çok daha iyi bir fikir. Sezginizin bir uygulamasıyla bir cevap ekleyeceğim.
Paul Brodersen

0

Bu Hashemi Emad'ın fikrinin bir uygulamasıdır . Başlangıç ​​konumu seçildiği sürece, sıkı bir daire içinde saat yönünün tersine bir adım atmanın bir yolu olacak şekilde çalışır. Bazı kenarlar için, özellikle haritanın dış tarafında, bu mümkün değildir. İyi başlangıç ​​pozisyonlarının nasıl seçileceği veya çözümlerin nasıl filtreleneceği hakkında bir fikrim yok - belki başka birinin var.

Çalışma örneği (kenardan başlayarak (1204573687, 4555480822)):

resim açıklamasını buraya girin

Bu yaklaşımın işe yaramadığı örnek (kenardan başlayarak (1286684278, 5818325197)):

resim açıklamasını buraya girin

kod

#!/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')
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.