title

Algorithmes OSMnx pour visualiser et analyser le réseau routier des principales villes françaises en modélisant les réseaux de transport, en calculant les statistiques et l'orientation moyenne avec les données d'OpenStreetMap et de l'API GoogleMaps.

87% des trajets s’effectuent sur les routes. Élément essentiel du maillage territorial, pour cette raison, nous étudierons le nouveaux algorithmes comme OSMnx nous présentent une série d'indicateurs qui aident à définir la logique spatiale et l'ordre urbain, révélant l'ordre et le désordre des rues, ainsi que les meilleures stratégies pour la dynamique du transport d'une ville. Dans cette opportunité, nous explorerons la ville de Paris en utilisant les données d'OpenStreetMap et OSMnx dans une première occasion de connaître le réseau routier et d'exécuter une modélisation des itinéraires basée sur les distances et l'impédance et enfin d'appliquer l'indicateur d'orientation des villes afin de quantifier la façon dont les réseaux routiers dans les villes françaises suivent une logique d’ordonnancement géométrique d’une grille unique ou son schéma est hétérogène.

Les ressources:

1.- Données de l'API OpenStreetMap pour télécharger des données depuis le réseau routier.

2.- Données Google Maps API pour télécharger les données d'altitude et les associer au réseau routier, pour développer notre modèle d'impédances, défini par la différence d'altitude des itinéraires.

3.- Appliquer l'algorithme OSMnx pour développer les modèles et définir les directions cardinales des axes routiers.

Sources

In [1]:
from api import APIKey 
import networkx as nx
import numpy as np
import osmnx as ox
import matplotlib.pyplot as plt
import pandas as pd
%matplotlib inline
import datetime
import pandas as pd
weight_by_length = False
ox.__version__
ox.config(log_console=True, use_cache=True)
ox.__version__
Out[1]:
'0.11.4'
In [2]:
# Nous extrayons les données associées à Paris
place = 'Paris'
place_query = {'city':'Paris', 'state':'Paris', 'country':'France'}
G = ox.graph_from_place(place_query, network_type='drive')
In [3]:
# Nous consultons les données de chaque nœud routier extrait à l'aide des données de l'API Google Elevation
G = ox.add_node_elevations(G, api_key=APIKey)
G = ox.add_edge_grades(G)

Nous calculons les variations de la pente du réseau routier

Nous utilisons les nœuds pour définir le réseau et nous prenons uniquement les valeurs uniques pour éviter de dupliquer les données et enfin calculer les valeurs de pourcentage de pente.

In [4]:
edge_grades = [data['grade_abs'] for u, v, k, data in ox.get_undirected(G).edges(keys=True, data=True)]
In [5]:
avg_grade = np.mean(edge_grades)
print('Pente moyenne du réseau routier à Paris  {} is {:.1f}%'.format(place, avg_grade*100))

med_grade = np.median(edge_grades)
print('Pente médiane du réseau routier à Paris {} is {:.1f}%'.format(place, med_grade*100))
Pente moyenne du réseau routier à Paris  Paris is 1.5%
Pente médiane du réseau routier à Paris Paris is 1.0%

Nous voyons qu'en moyenne la ville de Paris a une condition idéale pour le développement d'une infrastructure pour Ciclovia, maintenant nous allons visualiser le réseau développant un modèle d'impédance en raison de l'état des distances et de la différence d'altitude.

Visualiser les nœuds routiers et leur altitude

Ce sont les valeurs du violet de basse altitude et celles du jaune de plus haute altitude.

In [6]:
G_proj = ox.project_graph(G)
In [7]:
nc = ox.get_node_colors_by_attr(G_proj, 'elevation', cmap='plasma', num_bins=20)
fig, ax = ox.plot_graph(G_proj, fig_height=6, node_color=nc, node_size=12, node_zorder=2, edge_color='#dddddd')

Les valeurs d'altitude sont intégrées au réseau routier français pour visualiser notre modèle d'impédance

In [8]:
# Nous cartographions le réseau routier avec les valeurs d'altitude téléchargées
ec = ox.get_edge_colors_by_attr(G_proj, 'grade_abs', cmap='plasma', num_bins=100)
fig, ax = ox.plot_graph(G_proj, fig_height=6, edge_color=ec, edge_linewidth=0.8, node_size=0)

Nous exécutons le modèle d'impédance définissant un itinéraire pour calculer l'itinéraire le plus court et l'itinéraire avec la pente la plus basse

In [9]:
# sélectionnez un nœud d'origine et de destination et un cadre de sélection autour d'eux
origin = ox.get_nearest_node(G, (48.826955,2.366481))
destination = ox.get_nearest_node(G, (48.890483,2.337470))
bbox = ox.bbox_from_point((48.859661,2.333608), distance=5000, project_utm=True)
In [10]:
# Nous définissons notre modèle en fonction des variations de distance et de pente
def impedance(length, grade):
    penalty = grade ** 2
    return length * penalty


for u, v, k, data in G_proj.edges(keys=True, data=True):
    data['impedance'] = impedance(data['length'], data['grade_abs'])
    data['rise'] = data['length'] * data['grade']

Itinéraire le plus court:

In [14]:
route_by_length = nx.shortest_path(G_proj, source=origin, target=destination, weight='length')
fig, ax = ox.plot_graph_route(G_proj, route_by_length, bbox=bbox, node_size=0)

Route avec moins de pente:

In [15]:
route_by_impedance = nx.shortest_path(G_proj, source=origin, target=destination, weight='impedance')
fig, ax = ox.plot_graph_route(G_proj, route_by_impedance, bbox=bbox, node_size=0)

Calculons les statistiques pour les deux routes:

In [16]:
def print_route_stats(route):
    route_grades = ox.get_route_edge_attributes(G_proj, route, 'grade_abs')
    msg = 'la pente moyenne est {:.1f}% et le max est {:.1f}%'
    print(msg.format(np.mean(route_grades)*100, np.max(route_grades)*100))

    route_rises = ox.get_route_edge_attributes(G_proj, route, 'rise')
    ascent = np.sum([rise for rise in route_rises if rise >= 0])
    descent = np.sum([rise for rise in route_rises if rise < 0])
    msg = 'Le changement altitudinal total est {:.0f} mètres: {:.0f} mètres dans ascension et {:.0f} mètres descente'
    print(msg.format(np.sum(route_rises), ascent, abs(descent)))

    route_lengths = ox.get_route_edge_attributes(G_proj, route, 'length')
    print('Distance totale du trajet: {:,.0f} mètres'.format(np.sum(route_lengths)))

Statistiques sur les itinéraires les plus courts

In [17]:
print_route_stats(route_by_length)
la pente moyenne est 2.0% et le max est 21.3%
Le changement altitudinal total est 19 mètres: 83 mètres dans ascension et 64 mètres descente
Distance totale du trajet: 8,963 mètres

Statistiques pour l'itinéraire avec la pente la plus basse

In [18]:
print_route_stats(route_by_impedance)
la pente moyenne est 0.9% et le max est 6.0%
Le changement altitudinal total est 19 mètres: 63 mètres dans ascension et 43 mètres descente
Distance totale du trajet: 14,642 mètres

Dans notre modèle de pente la plus raide, nous avons réduit la pente moyenne le long du parcours d'une pente de 21% à une pente de 6%, diminuant la montée totale de 83 mètres à 63 mètres (mais la distance de déplacement augmente de 8,9 km à 14,6 km).

L'ordre spatial urbain: orientation du réseau routier dans les villes françaises

Cette étude examine l'orientation du réseau routier dans les villes françaises à l'aide des données OpenStreetMap et OSMnx. Il mesure l'entropie des relèvements des rues dans les modèles de réseau pondérés et non pondérés, ainsi que la longueur typique des segments de rue de chaque ville, la circuité moyenne, le degré moyen des nœuds et les proportions du réseau d'intersections à quatre voies et d'impasses. Il développe également un nouvel indicateur d’ordre d’orientation qui quantifie la façon dont le réseau routier d’une ville suit la logique d’ordre géométrique d’une grille unique.

Villes françaises

In [2]:
places = {'Paris'       : 'Paris, France',
         'Rennes'        : 'Rennes, France',
         'Lyon'       : 'Lyon, France',
         'Reims'     : 'Reims, France',
         'Le Havre'       : 'Le Havre, France',
         'Marseille'     : 'Marseille, France',
         'Saint-Étienne'        : 'Saint-Étienne, France',
         'Toulon'       : 'Toulon, France',
         'Toulouse'        : 'Toulouse, France',
         'Grenoble'       : 'Grenoble, France',
         'Nice'     : 'Nice, France',
         'Dijon'     : 'Dijon, France',
         'Nantes'         : 'Nantes, France',
         'Strasbourg'   : 'Strasbourg, France',
         'Nîmes'       : 'Nîmes, France',
         'Angers'  : 'Angers, France',
         'Villeurbanne'       : 'Villeurbanne, France',
         'Le Mans'      : 'Le Mans, France',
         'Saint-Denis'    : 'Saint-Denis, France',
         'Montpellier' : {'city':'Montpellier', 'state':'Occitanie', 'country':'France'},
         'Aix-en-Provence'       : 'Aix-en-Provence, France',
         'Clermont-Ferrand'      : 'Clermont-Ferrand, France',
         'Brest'         : 'Brest, France',
         'Lille'    : 'Lille, France'}
In [3]:
gdf = ox.gdf_from_places(places.values())
gdf
Out[3]:
geometry place_name bbox_north bbox_south bbox_east bbox_west
0 POLYGON ((2.22412 48.85420, 2.22412 48.85402, ... Paris, Ile-de-France, Metropolitan France, France 48.902156 48.815576 2.469760 2.224122
1 POLYGON ((-1.75259 48.08765, -1.75254 48.08740... Rennes, Ille-et-Vilaine, Brittany, Metropolita... 48.154970 48.076915 -1.624405 -1.752588
2 POLYGON ((4.77181 45.75103, 4.77182 45.75102, ... Lyon, Métropole de Lyon, Departemental constit... 45.808263 45.707367 4.898377 4.771813
3 POLYGON ((3.98582 49.29335, 3.98924 49.29150, ... Reims, Marne, Grand Est, Metropolitan France, ... 49.303187 49.203935 4.129696 3.985821
4 POLYGON ((0.06680 49.51855, 0.06965 49.51801, ... Le Havre, Seine-Maritime, Normandy, Metropolit... 49.540146 49.451670 0.195556 0.066799
5 MULTIPOLYGON (((5.22863 43.19774, 5.22864 43.1... Marseille, Provence-Alpes-Côte d'Azur, Metropo... 43.391033 43.169623 5.532476 5.228631
6 MULTIPOLYGON (((4.24430 45.43680, 4.24432 45.4... Saint-Étienne, Loire, Auvergne-Rhône-Alpes, Me... 45.476708 45.371539 4.488858 4.244297
7 MULTIPOLYGON (((5.87950 43.13422, 5.87963 43.1... Toulon, Var, Provence-Alpes-Côte d'Azur, Metro... 43.171494 43.102041 5.987384 5.879503
8 POLYGON ((1.35040 43.60427, 1.35164 43.60390, ... Toulouse, Haute-Garonne, Occitania, Metropolit... 43.668708 43.532654 1.515380 1.350396
9 POLYGON ((5.67761 45.21287, 5.68481 45.20761, ... Grenoble, Isère, Auvergne-Rhône-Alpes, Metropo... 45.214076 45.154144 5.753118 5.677606
10 POLYGON ((7.18195 43.73746, 7.18202 43.73625, ... Nice, Maritime Alps, Provence-Alpes-Côte d'Azu... 43.760764 43.645419 7.323912 7.181953
11 POLYGON ((4.96246 47.32499, 4.96258 47.32494, ... Dijon, Côte-d'Or, Bourgogne-Franche-Comté, Met... 47.377486 47.286247 5.102060 4.962459
12 POLYGON ((-1.64181 47.19116, -1.64174 47.19086... Nantes, Loire-Atlantique, Pays de la Loire, Me... 47.295858 47.180586 -1.478844 -1.641811
13 POLYGON ((7.68814 48.59947, 7.68826 48.59812, ... Strasbourg, Bas-Rhin, Grand Est, Metropolitan ... 48.646190 48.491861 7.836065 7.688137
14 POLYGON ((4.23567 43.86511, 4.23787 43.86022, ... Nimes, Nîmes, Gard, Occitania, Metropolitan Fr... 43.923018 43.741266 4.449661 4.235671
15 POLYGON ((-0.61769 47.46008, -0.61769 47.46005... Angers, Maine-et-Loire, Pays de la Loire, Metr... 47.526299 47.437355 -0.508563 -0.617693
16 POLYGON ((4.85836 45.77243, 4.86034 45.77197, ... Villeurbanne, Lyon, Métropole de Lyon, Departe... 45.795588 45.748452 4.921261 4.858362
17 POLYGON ((0.13628 48.01766, 0.13629 48.01764, ... Le Mans, Sarthe, Pays de la Loire, Metropolita... 48.035873 47.927977 0.255094 0.136283
18 POLYGON ((2.33322 48.92317, 2.33681 48.92109, ... Saint-Denis, Seine-Saint-Denis, Ile-de-France,... 48.952135 48.901493 2.398118 2.333219
19 POLYGON ((3.80706 43.63465, 3.80711 43.63450, ... Montpellier, Hérault, Occitania, Metropolitan ... 43.653358 43.566709 3.941321 3.807060
20 POLYGON ((5.26947 43.49617, 5.26952 43.49567, ... Aix-en-Provence, Provence-Alpes-Côte d'Azur, M... 43.625922 43.446106 5.506301 5.269475
21 POLYGON ((3.05326 45.78583, 3.05331 45.78577, ... Clermont-Ferrand, Puy-de-Dôme, Auvergne-Rhône-... 45.818384 45.755694 3.172176 3.053256
22 POLYGON ((-4.56892 48.39279, -4.56876 48.39266... Brest, Finistère, Brittany, Metropolitan Franc... 48.459552 48.357297 -4.427831 -4.568917
23 POLYGON ((2.96797 50.63509, 2.96960 50.63359, ... Lille, Nord, Hauts-de-France, Metropolitan Fra... 50.661260 50.600826 3.125725 2.967968

Obtenez les réseaux routiers et leurs repères

In [4]:
def reverse_bearing(x):
    return x + 180 if x < 180 else x - 180
In [5]:
bearings = {}
for place in sorted(places.keys()):
    print(datetime.datetime.now(), place)
    

    query = places[place]
    G = ox.graph_from_place(query, network_type='drive')
    

    Gu = ox.add_edge_bearings(ox.get_undirected(G))
    
    if weight_by_length:

        city_bearings = []
        for u, v, k, d in Gu.edges(keys=True, data=True):
            city_bearings.extend([d['bearing']] * int(d['length']))
        b = pd.Series(city_bearings)
        bearings[place] = pd.concat([b, b.map(reverse_bearing)]).reset_index(drop='True')
    else:

        b = pd.Series([d['bearing'] for u, v, k, d in Gu.edges(keys=True, data=True)])
        bearings[place] = pd.concat([b, b.map(reverse_bearing)]).reset_index(drop='True')
2020-04-20 23:57:31.518052 Aix-en-Provence
2020-04-20 23:57:58.126289 Angers
2020-04-20 23:58:22.102838 Brest
2020-04-20 23:58:47.026596 Clermont-Ferrand
2020-04-20 23:59:07.330450 Dijon
2020-04-20 23:59:25.086831 Grenoble
2020-04-20 23:59:40.238911 Le Havre
2020-04-20 23:59:57.789443 Le Mans
2020-04-21 00:00:23.089686 Lille
2020-04-21 00:00:40.293983 Lyon
2020-04-21 00:00:57.492135 Marseille
2020-04-21 00:01:49.848270 Montpellier
2020-04-21 00:02:16.559246 Nantes
2020-04-21 00:02:46.913758 Nice
2020-04-21 00:03:08.325133 Nîmes
2020-04-21 00:03:33.603939 Paris
2020-04-21 00:04:05.102661 Reims
2020-04-21 00:04:29.666054 Rennes
2020-04-21 00:05:06.525323 Saint-Denis
2020-04-21 00:05:16.503753 Saint-Étienne
2020-04-21 00:05:36.314843 Strasbourg
2020-04-21 00:05:52.595683 Toulon
2020-04-21 00:06:12.757298 Toulouse
2020-04-21 00:06:48.196127 Villeurbanne

Représentez graphiquement les résultats

Nous créons un histogramme polaire pour chaque ville où chaque barre représentera la direction des rues par rapport aux points cardinaux (Nord, Sud, Est et Ouest) et sa longueur représentera le nombre de rues dans une direction spécifique.

In [6]:
def count_and_merge(n, bearings):
    n = n * 2
    bins = np.arange(n + 1) * 360 / n
    count, _ = np.histogram(bearings, bins=bins)
    count = np.roll(count, 1)
    return count[::2] + count[1::2]
In [7]:
def polar_plot(ax, bearings, n=36, title=''):

    bins = np.arange(n + 1) * 360 / n
    count = count_and_merge(n, bearings)
    _, division = np.histogram(bearings, bins=bins)
    frequency = count / count.sum()
    division = division[0:-1]
    width =  2 * np.pi / n

    ax.set_theta_zero_location('N')
    ax.set_theta_direction('clockwise')

    x = division * np.pi / 180
    bars = ax.bar(x, height=frequency, width=width, align='center', bottom=0, zorder=2,
                  color='#003366', edgecolor='k', linewidth=0.5, alpha=0.7)
    
    ax.set_ylim(top=frequency.max())
    
    title_font = {'family':'Century Gothic', 'size':24, 'weight':'bold'}
    xtick_font = {'family':'Century Gothic', 'size':10, 'weight':'bold', 'alpha':1.0, 'zorder':3}
    ytick_font = {'family':'Century Gothic', 'size': 9, 'weight':'bold', 'alpha':0.2, 'zorder':3}
    
    ax.set_title(title.upper(), y=1.05, fontdict=title_font)
    
    ax.set_yticks(np.linspace(0, max(ax.get_ylim()), 5))
    yticklabels = ['{:.2f}'.format(y) for y in ax.get_yticks()]
    yticklabels[0] = ''
    ax.set_yticklabels(labels=yticklabels, fontdict=ytick_font)
    
    xticklabels = ['N', '', 'E', '', 'S', '', 'W', '']
    ax.set_xticklabels(labels=xticklabels, fontdict=xtick_font)
    ax.tick_params(axis='x', which='major', pad=-2)
In [10]:
n = len(places)
ncols = int(np.ceil(np.sqrt(n)))
nrows = int(np.ceil(n / ncols))
figsize = (ncols * 5, nrows * 5)
fig, axes = plt.subplots(nrows, ncols, figsize=figsize, subplot_kw={'projection':'polar'})

for ax, place in zip(axes.flat, sorted(places.keys())):
    polar_plot(ax, bearings[place].dropna(), title=place)

suptitle_font = {'family':'Century Gothic', 'fontsize':40, 'fontweight':'normal', 'y':1.07}
fig.suptitle('Orientation du réseau routier des principales Villes Françaises.', **suptitle_font)
fig.tight_layout()
fig.subplots_adjust(hspace=0.35)
plt.show()

Cette analyse nous permet d'explorer les similitudes et les différences entre les villes françaises. Notez qu'il existe des relations statistiques importantes entre l'ordre d'orientation de la ville, par exemple, en moyenne, les villes ont un schéma hétérogène de leurs orientations, bien que des villes comme Villeurbanne, Rennes, Saint-Denis et Clermont-Ferrand soient beaucoup plus quadrillées que les autres.

Conclusions

Le réseau routier organise et limite la dynamique des transports dans la ville selon une certaine logique spatiale, planifiée ou non, ordonnée ou désordonnée.

Cette étude a présenté un modèle basé sur les algorithmes OSMnx pour l'optimisation des itinéraires cyclables potentiels en fonction de la longueur et de l'altitude du réseau routier en utilisant les données d'OpenStreetMap et de l'API GoogleMaps.

Enfin, le histogramme polaire des orientations a été visualisé pour découvrir pour la première fois la logique spatiale de la structure routière des principales Villes Françaises, montrant que les modèles sont une technique viable pour mesurer empiriquement et visualiser la complexité de l'ordre spatial, illustrant les modèles d'urbanisation et de transport.

Pour un affichage dynamique de l'algorithme OSMnx, il est possible de le trouver ici.