Mapa de conexiones en Python con matplotlib

Librería

geopandas

Autor principal

GeoPandas developers

Un mapa de conexiones muestra los enlaces entre lugares, como rutas de vuelo, comercio o migraciones. El mapa base se dibuja con geopandas y las conexiones son líneas entre las coordenadas de los lugares. Los ejemplos muestran algunas rutas de vuelo desde Madrid.

Rutas como líneas rectas

Dibuja el mapa mundial y agrega una línea desde el origen hasta cada destino con plot, usando la longitud como X y la latitud como Y.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
cities = {"Madrid": (-3.7, 40.4), "Nueva York": (-74.0, 40.7), "São Paulo": (-46.6, -23.5),
          "Tokio": (139.7, 35.7), "Sydney": (151.2, -33.9), "Johannesburgo": (28.0, -26.2),
          "Moscú": (37.6, 55.8), "Ciudad de México": (-99.1, 19.4)}

fig, ax = plt.subplots()
world.plot(ax = ax, color = "lightgray", edgecolor = "white", linewidth = 0.3)

origin = cities["Madrid"]
for name, dest in cities.items():
    ax.plot([origin[0], dest[0]], [origin[1], dest[1]], color = "tab:red")
ax.axis("off")
# plt.show()

Mapa de conexiones con líneas rectas en Python

Arcos de círculo máximo

La ruta más corta entre dos puntos de la Tierra es un arco de círculo máximo, no una línea recta sobre el mapa. La siguiente función calcula los puntos de ese arco, de modo que las rutas se parezcan a trayectorias de vuelo reales.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
cities = {"Madrid": (-3.7, 40.4), "Nueva York": (-74.0, 40.7), "São Paulo": (-46.6, -23.5),
          "Tokio": (139.7, 35.7), "Sydney": (151.2, -33.9), "Johannesburgo": (28.0, -26.2),
          "Moscú": (37.6, 55.8), "Ciudad de México": (-99.1, 19.4)}

def great_circle(p1, p2, n = 100):
    """Puntos del círculo máximo entre dos puntos (lon, lat)."""
    lon1, lat1, lon2, lat2 = np.radians([*p1, *p2])
    a = np.array([np.cos(lat1) * np.cos(lon1), np.cos(lat1) * np.sin(lon1), np.sin(lat1)])
    b = np.array([np.cos(lat2) * np.cos(lon2), np.cos(lat2) * np.sin(lon2), np.sin(lat2)])
    omega = np.arccos(np.clip(a @ b, -1, 1))
    t = np.linspace(0, 1, n)[:, None]
    p = (np.sin((1 - t) * omega) * a + np.sin(t * omega) * b) / np.sin(omega)
    return np.degrees(np.arctan2(p[:, 1], p[:, 0])), np.degrees(np.arcsin(p[:, 2]))

fig, ax = plt.subplots()
world.plot(ax = ax, color = "lightgray", edgecolor = "white", linewidth = 0.3)

for name, dest in cities.items():
    if name != "Madrid":
        lon, lat = great_circle(cities["Madrid"], dest)
        ax.plot(lon, lat, color = "tab:red")
ax.axis("off")
# plt.show()

Mapa de conexiones con arcos de círculo máximo en Python

Grosor según una variable

Establece el grosor de cada ruta según una variable, por ejemplo el número de vuelos semanales, y marca las ciudades con puntos.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
cities = {"Madrid": (-3.7, 40.4), "Nueva York": (-74.0, 40.7), "São Paulo": (-46.6, -23.5),
          "Tokio": (139.7, 35.7), "Sydney": (151.2, -33.9), "Johannesburgo": (28.0, -26.2),
          "Moscú": (37.6, 55.8), "Ciudad de México": (-99.1, 19.4)}

def great_circle(p1, p2, n = 100):
    """Puntos del círculo máximo entre dos puntos (lon, lat)."""
    lon1, lat1, lon2, lat2 = np.radians([*p1, *p2])
    a = np.array([np.cos(lat1) * np.cos(lon1), np.cos(lat1) * np.sin(lon1), np.sin(lat1)])
    b = np.array([np.cos(lat2) * np.cos(lon2), np.cos(lat2) * np.sin(lon2), np.sin(lat2)])
    omega = np.arccos(np.clip(a @ b, -1, 1))
    t = np.linspace(0, 1, n)[:, None]
    p = (np.sin((1 - t) * omega) * a + np.sin(t * omega) * b) / np.sin(omega)
    return np.degrees(np.arctan2(p[:, 1], p[:, 0])), np.degrees(np.arcsin(p[:, 2]))

flights = {"Nueva York": 28, "São Paulo": 14, "Tokio": 7, "Sydney": 3,
           "Johannesburgo": 5, "Moscú": 10, "Ciudad de México": 21}

fig, ax = plt.subplots()
world.plot(ax = ax, color = "lightgray", edgecolor = "white", linewidth = 0.3)

for name, n in flights.items():
    lon, lat = great_circle(cities["Madrid"], cities[name])
    ax.plot(lon, lat, color = "tab:red", linewidth = n / 5, alpha = 0.7)
xy = np.array(list(cities.values()))
ax.scatter(xy[:, 0], xy[:, 1], s = 15, color = "black", zorder = 3)
ax.axis("off")
# plt.show()

Mapa de conexiones con el grosor de las rutas según una variable en Python

Estilo del mapa

Un fondo oscuro con rutas brillantes es un aspecto clásico para este tipo de mapas. Agrega los nombres de las ciudades con text.

import numpy as np
import geopandas as gpd
import matplotlib.pyplot as plt

world = gpd.read_file("https://naciscdn.org/naturalearth/110m/cultural/ne_110m_admin_0_countries.zip")
cities = {"Madrid": (-3.7, 40.4), "Nueva York": (-74.0, 40.7), "São Paulo": (-46.6, -23.5),
          "Tokio": (139.7, 35.7), "Sydney": (151.2, -33.9), "Johannesburgo": (28.0, -26.2),
          "Moscú": (37.6, 55.8), "Ciudad de México": (-99.1, 19.4)}

def great_circle(p1, p2, n = 100):
    """Puntos del círculo máximo entre dos puntos (lon, lat)."""
    lon1, lat1, lon2, lat2 = np.radians([*p1, *p2])
    a = np.array([np.cos(lat1) * np.cos(lon1), np.cos(lat1) * np.sin(lon1), np.sin(lat1)])
    b = np.array([np.cos(lat2) * np.cos(lon2), np.cos(lat2) * np.sin(lon2), np.sin(lat2)])
    omega = np.arccos(np.clip(a @ b, -1, 1))
    t = np.linspace(0, 1, n)[:, None]
    p = (np.sin((1 - t) * omega) * a + np.sin(t * omega) * b) / np.sin(omega)
    return np.degrees(np.arctan2(p[:, 1], p[:, 0])), np.degrees(np.arcsin(p[:, 2]))

fig, ax = plt.subplots(facecolor = "#0B1D33")
world.plot(ax = ax, color = "#1F3A5F", edgecolor = "#0B1D33", linewidth = 0.3)

for name, dest in cities.items():
    if name != "Madrid":
        lon, lat = great_circle(cities["Madrid"], dest)
        ax.plot(lon, lat, color = "#FFD166", linewidth = 4, alpha = 0.15)
        ax.plot(lon, lat, color = "#FFD166", linewidth = 1)
for name, (x, y) in cities.items():
    ax.scatter(x, y, color = "white", s = 10, zorder = 3)
    ax.text(x, y - 7, name, color = "white", fontsize = 6, ha = "center")
ax.set_ylim(-60, 85)
ax.axis("off")
# plt.show()

Mapa de conexiones con fondo oscuro en Python

Consulta también cómo crear un mapa estático con geopandas y un mapa de burbujas interactivo en plotly.

Data Sketches

A journey of imagination, exploration, and beautiful data visualizations

Comprar en Amazon

También te puede interesar