OpenStreetMap logo OpenStreetMap

Isócronas en Python

Posted by kaxtillo on 26 June 2025 in Spanish (Español). Last updated on 27 June 2025.

Resaltar áreas transitables en Python

Isócronas Popayán En las ciencias geoespaciales y la inteligencia de ubicación, las isócronas representan áreas geográficas accesibles en un tiempo determinado desde un punto específico. Por ejemplo, en el contexto de las distancias a pie, las isócronas son herramientas útiles para profesionales como los urbanistas que buscan comprender la accesibilidad y la conectividad dentro de un área determinada.

Al visualizar las isócronas, la ciencia de datos puede proporcionar una herramienta rápida y fácil de usar para ayudar a obtener información sobre el nivel de conectividad y accesibilidad a pie de los vecindarios, ayudar a identificar áreas que están bien conectadas y señalar áreas potenciales de mejoras de infraestructura.

En este artículo, explico cómo generar isócronas de distancia a pie mediante los paquetes de Python NetworkX (diseñado para análisis de grafos) y OSMNnx (que combina OpenStreetMap y NetworkX). Tomamos como ejemplo Perímetro urbano de la ciudad de Popayán. Primero, descargamos la red vial del área, seleccionamos un nodo aleatorio (una intersección aleatoria) y dibujamos las isócronas de distancia a pie de 5, 10, 20 y 30 minutos a su alrededor.

1. Adquisición de datos

Primero, importaremos OSMnx y lo usaremos para descargar los límites administrativos del área objetivo. Puede reemplazarlo por el nombre de cualquier lugar que prefiera, que puede consultar fácilmente en OpenStreetMap. A continuación, usaremos OSMnx para descargar la red vial del área objetivo y mostrar el tamaño de esta red de nodos en términos de número de intersecciones y segmentos viales. Además, debemos especificar el tipo de red , que ahora está configurado como “walk” para obtener solo las partes transitables de la red vial.

# Importing osmnx
import osmnx as ox

# Configurar Matplotlib para visualización si es necesario
# import matplotlib.pyplot as plt

# Definir el polígono para la ciudad de Popayán, Cauca, Colombia
# Es importante ser específico para que OSMnx identifique correctamente el área.
# Puedes probar con diferentes niveles de especificidad si la primera no funciona,
# por ejemplo: 'Popayán, Colombia' o 'Popayán'.
# 'Perímetro urbano de Popayán, Cauca, Colombia' es un buen punto de partida.
print("Geocodificando la ciudad de Popayán...")
try:
    admin_district = ox.geocode_to_gdf('Perímetro urbano de Popayán, Cauca, Colombia')
    print("Geocodificación exitosa.")
except Exception as e:
    print(f"Error al geocodificar Popayán: {e}")
    print("Asegúrate de que la cadena de ubicación sea correcta y haya conexión a internet.")
    # Puedes probar con una cadena menos específica si da error, como 'Popayán, Colombia' o 'Popayán'
    admin_district = ox.geocode_to_gdf('Popayán, Colombia') # Intento alternativo

# Muestra el polígono en un gráfico (opcional, para verificar que es correcto)
print("Mostrando el polígono geocodificado...")
admin_district.plot()
# Si estás en un entorno interactivo como Jupyter, la trama se mostrará automáticamente.
# Si estás en un script .py, puedes añadir plt.show() al final para que la ventana del gráfico aparezca.
# plt.show()

# Extraer la geometría del polígono de la primera fila del GeoDataFrame
# Asumimos que el primer resultado es el correcto para la ciudad de Popayán.
# Puedes inspeccionar admin_district si hay múltiples geometrías.
admin_poly = admin_district.geometry.values[0]

# Guardar imagen del mapa
png_path = os.path.join(output_dir, "map.png")
plt.savefig(png_path, dpi=300, bbox_inches='tight')

# Cargar el grafo de la red de calles dentro de ese polígono
print("Cargando el grafo de la red de calles de Popayán...")
G = ox.graph_from_polygon(admin_poly, network_type='walk')
print("Grafo cargado exitosamente.")

print('\nAnálisis de la red de calles de Popayán:')
print('Número de intersecciones (nodos): ', G.number_of_nodes())
print('Número de segmentos de carretera (aristas):',  G.number_of_edges())

# Opcional: Visualizar el grafo
# print("\nVisualizando el grafo...")
# fig, ax = ox.plot_graph(G, figsize=(10, 10), node_size=0, edge_linewidth=0.5, show=False, close=False)
# plt.title("Red Peatonal de Popayán")
# plt.show()

2. Polígonos de alcanzabilidad

Ahora, construyamos los polígonos isócronos de transitabilidad. Para ello, primero, asumimos una velocidad promedio de caminata de 5 km/h (1,39 m/s) y, con esta velocidad, asignamos un tiempo de caminata a cada borde de la red vial. Luego, seleccionamos un nodo aleatorio para comenzar y definimos la lista de rangos de transitabilidad: 5, 10, 20 y 30 minutos.

Para crear los polígonos de accesibilidad, primero usamos la función integrada de NetworkX llamada _ego graph , que devuelve un subgrafo específico de la red vial original que contiene todos los nodos dentro de un radio determinado, según las métricas de distancia que elijamos. Si seleccionamos _travel time , podemos devolver el subgrafo accesible en un tiempo determinado y almacenarlo como un atributo de borde llamado _travel time .

Luego, necesitamos extraer todas las coordenadas de los nodos de este subgrafo, que almacenaremos en la lista _node points . Finalmente, las convertimos en un polígono, lo graficamos y lo guardamos para la siguiente sección.

El resultado: todos los polígonos isócronos de transitabilidad, para 5, 10, 20 y 30 minutos, respectivamente

# Define the walking speed (5 km/h -> 1.39 m/s)
walking_speed = 1.39  # in meters per second

# Calculate travel time for each edge
for u, v, data in G.edges(data=True):
    # Calculate travel time in seconds
    data['travel_time'] = data['length'] / walking_speed

# Pick a center node
center_node = list(G.nodes())[30]  # starting point

# Generate isochrones
isochrone_times = [5, 10, 20, 30]  # isochrones in minutes

# Library imports
import networkx as nx 
from shapely.geometry import Point, Polygon  
import geopandas as gpd 

isochrone_polys = []

for time in isochrone_times:
    subgraph = nx.ego_graph(G, 
                            center_node, 
                            radius=time*60, 
                            distance='travel_time')

    node_points = [Point((data['x'], data['y'])) 
                   for node, data in subgraph.nodes(data=True)]

    polygon = Polygon(gpd.GeoSeries(node_points).unary_union.convex_hull)
    gpd.GeoDataFrame([polygon], columns = ['geometry']).plot()
    isochrone_polys.append(gpd.GeoSeries([polygon]))

3. Visualizando las isócronas

Por último, utilicemos Matplotlib y la coloración de rojo a verde para trazar y visualizar las isócronas sobre la red de carreteras.

import os

# Crear carpeta de salida (si no existe)
output_dir = "isochrones_popayan"
os.makedirs(output_dir, exist_ok=True)

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import osmnx as ox
import networkx as nx
import geopandas as gpd


ox.settings.use_cache = True
ox.settings.log_console = True

# 1. Obtener geometría de Popayán
admin_district = ox.geocode_to_gdf('Perímetro urbano de Popayán, Cauca, Colombia')
polygon = admin_district.geometry.iloc[0]

# 2. Descargar red caminable
G = ox.graph_from_polygon(polygon, network_type='walk', simplify=True)

# 3. Proyectar grafo a CRS métrico
G_proj = ox.project_graph(G)
nodes_proj = ox.graph_to_gdfs(G_proj, nodes=True, edges=False)

# 4. Nodo de inicio: centroide proyectado

# Coordenadas del punto inicial (WGS84)
lat, lon = 2.441901, -76.606439

# Crear GeoSeries y proyectar al CRS del grafo
point_wgs = gpd.GeoSeries([gpd.points_from_xy([lon], [lat])[0]], crs="EPSG:4326")
point_proj = point_wgs.to_crs(G_proj.graph['crs'])
x, y = point_proj.geometry.iloc[0].x, point_proj.geometry.iloc[0].y

# Obtener el nodo más cercano a esas coordenadas proyectadas
center_node = ox.distance.nearest_nodes(G_proj, X=x, Y=y)



# 5. Definir tiempos y distancias
trip_times = [5, 10, 15, 20]  # minutos
meters_per_minute = 80  # m/min
travel_distances = [t * meters_per_minute for t in trip_times]

# 6. Generar polígonos de isócrona
def make_iso_polys(G, center_node, distances):
    polys = []
    for dist in distances:
        subgraph = nx.ego_graph(G, center_node, radius=dist, distance='length')
        nodes_gdf = ox.graph_to_gdfs(subgraph, nodes=True, edges=False)
        if not nodes_gdf.empty:
            union = nodes_gdf.unary_union
            buffered = gpd.GeoSeries([union]).buffer(100)  # Aumentar buffer para visibilidad
            polys.append(buffered)
        else:
            polys.append(gpd.GeoSeries())
    return polys

isochrone_polys = make_iso_polys(G_proj, center_node, travel_distances)

# 7. Visualización
fig, ax = plt.subplots(figsize=(10, 10))
colors = ['red', 'orange', 'yellow', 'green']

# Dibujar isócronas
for idx, (poly, time) in enumerate(reversed(list(zip(isochrone_polys, trip_times)))):
    if not poly.empty and not poly.is_empty.all():
        poly.plot(ax=ax, color=colors[idx], alpha=0.4)
        poly.boundary.plot(ax=ax, color=colors[idx], linewidth=2)

# Leyenda manual
handles = [
    mpatches.Patch(color=colors[idx], alpha=0.5, label=f'{time} min')
    for idx, time in enumerate(reversed(trip_times))
]

ox.plot_graph(G_proj, ax=ax, node_size=0, edge_linewidth=0.5, show=False)
ax.set_title("Isócronas desde el centro de Popayán, Cauca", fontsize=15)
plt.legend(handles=handles)
plt.tight_layout()

# Guardar imagen del mapa
png_path = os.path.join(output_dir, "isochrones_map.png")
plt.savefig(png_path, dpi=300, bbox_inches='tight')


plt.show()

Repositorio

Location: El Centro, Comuna 4, Perímetro Urbano Popayán, Popayán, Centro, Cauca, RAP Pacífico, 190003, Colombia
Email icon Bluesky Icon Facebook Icon LinkedIn Icon Mastodon Icon Telegram Icon X Icon

Discussion

Log in to leave a comment