सड़क डेटा में पड़ोस (क्लोन) ढूँढना (एक ग्राफ)


10

मैं शहरों में पड़ोस को स्वचालित रूप से एक ग्राफ पर बहुभुज के रूप में परिभाषित करने का एक रास्ता खोज रहा हूं।

पड़ोस की मेरी परिभाषा के दो भाग हैं:

  1. एक ब्लॉक : सड़कों की एक संख्या के बीच एक क्षेत्र, जहां सड़कों की संख्या (किनारों) और चौराहों (नोड्स) की संख्या न्यूनतम तीन (एक त्रिकोण) है।
  2. एक पड़ोस : किसी भी दिए गए ब्लॉक के लिए, सभी ब्लॉक सीधे उस ब्लॉक और ब्लॉक के समीप आते हैं।

इस उदाहरण को उदाहरण के लिए देखें:

यहाँ छवि विवरण दर्ज करें

ईजी बी 4 को 7 नोड्स और उन्हें जोड़ने वाले 6 किनारों द्वारा परिभाषित किया गया है। यहाँ के अधिकांश उदाहरणों के रूप में, अन्य खंडों को 4 नोड्स द्वारा परिभाषित किया गया है और उन्हें जोड़ने वाले 4 किनारे हैं। इसके अलावा, पड़ोस की बी 1 शामिल हैं बी 2 (और इसके विपरीत), जबकि बी 2 भी शामिल बी 3

ओएसएम से सड़क डेटा प्राप्त करने के लिए मैं ऑस्मेनक्स का उपयोग कर रहा हूं ।

  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)

प्रासंगिक हो सकता है कि सिद्धांत:

एक अप्रत्यक्ष ग्राफ़ में सभी चक्रों की गणना


दिलचस्प समस्या है। आप इसमें एल्गोरिथ्म टैग जोड़ना चाह सकते हैं। लगता है कि आपके द्वारा ब्लॉक का पता लगाने के बाद पड़ोस आसान समस्या होगी। पड़ोस के रूप में, आप जिस चीज की तलाश कर रहे हैं वह साझा बढ़त है, सही है? और प्रत्येक ब्लॉक में किनारों की एक सूची होगी ... ब्लॉक के लिए, मुझे लगता है कि यह एक नोड पर प्रत्येक सड़क के विकल्प की कार्डिनल दिशा प्राप्त करने में सहायक होगा और जब तक आप एक सर्किट को पूरा नहीं करते, तब तक "या दाएं मुड़ें" या "पहुंचते रहें"। अपने आप पर एक मृत अंत या पाश वापस और पुनरावर्ती रूप से पीछे। लगता है जैसे कुछ दिलचस्प कोने के मामले होंगे।
जेफ एच

मुझे लगता है कि यह सवाल आपकी समस्या नहीं के समान है। 1. जैसा कि आप लिंक में देख सकते हैं, मैंने थोड़ी सी समस्या पर काम किया है, और यह एक बहुत ही कठोर है (एनपी-हार्ड हो जाता है)। मेरे जवाब में अनुमानी, फिर भी, आपको अभी भी काफी अच्छे परिणाम दे सकता है।
पॉल ब्रोडर्सन

जो भी समाधान आपको स्वीकार्य हो सकता है, वह संभवतः एक अनुमानी भी होगा, प्रत्येक दृष्टिकोण को मान्य करने के लिए परीक्षण डेटा सेट को परिभाषित करना एक अच्छा विचार हो सकता है। मतलब, आपके उदाहरण के ग्राफ के लिए, मशीन पठनीय रूप में सभी ब्लॉकों का एक एनोटेशन करना अच्छा होगा - केवल एक छवि में कुछ उदाहरण नहीं।
पॉल ब्रोडर्सन

जवाबों:


3

ग्राफ़ का उपयोग करके शहर के ब्लॉक ढूंढना आश्चर्यजनक रूप से गैर-तुच्छ है। मूल रूप से, यह सबसे छोटी रिंगों (एसएसएसआर) के सबसे छोटे सेट को खोजने के लिए है, जो एनपी-पूर्ण समस्या है। इस समस्या (और संबंधित समस्याओं) की समीक्षा यहां मिल सकती है । एसओ पर, इसे हल करने के लिए एक एल्गोरिथ्म का एक विवरण है । जहां तक ​​मैं बता सकता हूं, 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

यह मूल ग्राफ से ज्यामितीय जानकारी को संरक्षित करने के लिए थोड़ा काम होगा। मैं जल्द ही इसका प्रयास करूंगा।
नमक

@tmo अभी-अभी गुजर रहा है: आपको उस मामले में मल्टीडीग्राफ क्लास (जो कि ग्राफ बढ़ाता है) का उपयोग करने में सक्षम होना चाहिए
Théo Rubenach

1

मेरे पास एक कोड नहीं है, लेकिन मुझे लगता है कि एक बार मैं फुटपाथ पर हूं, अगर मैं प्रत्येक कोने पर दाईं ओर मुड़ता हूं, तो मैं अपने ब्लॉक के किनारों के माध्यम से साइकिल चलाऊंगा। मैं पुस्तकालयों को नहीं जानता, इसलिए मैं यहां सिर्फ एलो बात करूंगा।

  • अपनी बात से, जब तक आप एक सड़क तक नहीं पहुंच जाते, तब तक उत्तर की ओर जाएं
  • जितना हो सके, दाएं मुड़ें और सड़क पर चलें
  • अगले कोने पर, सभी क़दमों को खोजें, एक को चुना जो आपकी सड़क की दाईं ओर से गिनती के साथ सबसे छोटा कोण बनाता है।
  • उस सड़क पर चलो।
  • दाएँ मुड़ना, आदि।

यह वास्तव में एक भूलभुलैया से बाहर निकलने के लिए उपयोग करने के लिए एक एल्गोरिथ्म है: दीवार पर अपना दाहिना हाथ रखें और चलें। यह भूलभुलैया में छोरों के मामले में काम नहीं करता है, आप बस चारों ओर लूप करते हैं। लेकिन यह आपकी समस्या का समाधान देता है।


यह एक बेहतर विचार है जो मैंने किया था। मैं आपके अंतर्ज्ञान के कार्यान्वयन के साथ एक उत्तर जोड़ूंगा।
पॉल ब्रोडर्सन

0

यह हाशमी इमाद के विचार का कार्यान्वयन है । यह तब तक अच्छी तरह से काम करता है जब तक कि शुरुआती स्थिति को चुना नहीं जाता है कि एक तंग सर्कल में वामावर्त को स्थानांतरित करने का एक तरीका मौजूद है। कुछ किनारों के लिए, विशेष रूप से नक्शे के बाहर के आसपास, यह संभव नहीं है। मुझे इस बात का अंदाजा नहीं है कि अच्छे शुरुआती पदों का चयन कैसे किया जाए, या समाधानों को कैसे फ़िल्टर किया जाए - लेकिन हो सकता है कि किसी और के पास कोई हो।

काम करने का उदाहरण (बढ़त के साथ शुरू (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')
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.