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.
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()

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()

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()

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()

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