web-dev-qa-db-fra.com

Recherche de quartiers (cliques) dans les données de rue (un graphique)

Je recherche un moyen de définir automatiquement les quartiers des villes sous forme de polygones sur un graphique.

Ma définition d'un quartier comporte deux parties:

  1. Un bloc : Une zone comprise entre un certain nombre de rues, où le nombre de rues (bords) et d'intersections (nœuds) est au minimum de trois (a Triangle).
  2. Un voisinage : Pour un bloc donné, tous les blocs directement adjacents à ce bloc et le bloc lui-même.

Voir cette illustration pour un exemple:

enter image description here

Par exemple. B4 est un bloc défini par 7 nœuds et 6 arêtes les reliant. Comme la plupart des exemples ici, les autres blocs sont définis par 4 nœuds et 4 arêtes les reliant. De plus, le voisinage de B1 inclut B2 (et vice versa) tandis que B2 inclut aussi B3 .

J'utilise osmnx pour obtenir des données de rue d'OSM.

  1. En utilisant osmnx et networkx, comment puis-je parcourir un graphique pour trouver les nœuds et les arêtes qui définissent chaque bloc?
  2. Pour chaque bloc, comment puis-je trouver les blocs adjacents?

Je travaille moi-même vers un morceau de code qui prend un graphique et une paire de coordonnées (latitude, longitude) comme entrée, identifie le bloc pertinent et renvoie le polygone pour ce bloc et le voisinage comme défini ci-dessus.

Voici le code utilisé pour créer la carte:

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)

et ma tentative de trouver des cliques avec un nombre différent de nœuds et de degrés.

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)

Théorie qui pourrait être pertinente:

Énumération de tous les cycles dans un graphique non dirigé

10
tmo

Ceci est une implémentation de idée de Hashemi Emad . Cela fonctionne bien tant que la position de départ est choisie de telle sorte qu'il existe un moyen de faire un pas dans le sens antihoraire dans un cercle serré. Pour certains bords, en particulier autour de l'extérieur de la carte, cela n'est pas possible. Je ne sais pas comment sélectionner les bonnes positions de départ, ni comment filtrer les solutions - mais peut-être que quelqu'un d'autre en a une.

Exemple de travail (commençant par Edge (1204573687, 4555480822)):

enter image description here

Exemple, où cette approche ne fonctionne pas (en commençant par Edge (1286684278, 5818325197)):

enter image description here

Code

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