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:
Voir cette illustration pour un exemple:
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.
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:
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)):
Exemple, où cette approche ne fonctionne pas (en commençant par 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')