{"spec_id":"map-connection-lines","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nmap-connection-lines: Connection Lines Map (Origin-Destination)\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-05-28\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import (this file shares its name with the plotnine library)\n_here = os.path.abspath(os.path.dirname(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _here]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_cartesian,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_path,\n    geom_point,\n    geom_polygon,\n    ggplot,\n    labs,\n    scale_color_gradient,\n    scale_size_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Imprint palette position 1\nLAND_FILL = \"#E0DED4\" if THEME == \"light\" else \"#282824\"\nLAND_BORDER = \"#BCBBB1\" if THEME == \"light\" else \"#3A3A30\"\n\nTITLE = \"Global Flight Routes · map-connection-lines · python · plotnine · anyplot.ai\"\n_n = len(TITLE)\nTITLE_SIZE = max(8, round(12 * 67 / _n)) if _n > 67 else 12\n\nnp.random.seed(42)\n\n# Major international airports: code → (city, lat, lon)\nairports = {\n    \"JFK\": (\"New York\", 40.64, -73.78),\n    \"LAX\": (\"Los Angeles\", 33.94, -118.41),\n    \"LHR\": (\"London\", 51.47, -0.46),\n    \"CDG\": (\"Paris\", 49.01, 2.55),\n    \"DXB\": (\"Dubai\", 25.25, 55.36),\n    \"HND\": (\"Tokyo\", 35.55, 139.78),\n    \"SIN\": (\"Singapore\", 1.36, 103.99),\n    \"SYD\": (\"Sydney\", -33.95, 151.18),\n    \"GRU\": (\"São Paulo\", -23.43, -46.47),\n    \"JNB\": (\"Johannesburg\", -26.14, 28.25),\n    \"FRA\": (\"Frankfurt\", 50.03, 8.57),\n    \"HKG\": (\"Hong Kong\", 22.31, 113.92),\n    \"PEK\": (\"Beijing\", 40.08, 116.58),\n    \"ORD\": (\"Chicago\", 41.97, -87.91),\n    \"MIA\": (\"Miami\", 25.79, -80.29),\n}\n\n# Flight routes: (origin, destination, annual passengers in thousands)\nroutes = [\n    (\"JFK\", \"LHR\", 4200),\n    (\"JFK\", \"CDG\", 2800),\n    (\"LAX\", \"HND\", 3500),\n    (\"LAX\", \"SYD\", 1800),\n    (\"LHR\", \"DXB\", 3100),\n    (\"LHR\", \"SIN\", 2600),\n    (\"LHR\", \"HKG\", 2400),\n    (\"CDG\", \"JFK\", 2900),\n    (\"DXB\", \"SIN\", 2200),\n    (\"DXB\", \"LHR\", 3000),\n    (\"HND\", \"SIN\", 1900),\n    (\"SIN\", \"SYD\", 2100),\n    (\"GRU\", \"MIA\", 1500),\n    (\"GRU\", \"LHR\", 1700),\n    (\"JNB\", \"LHR\", 1400),\n    (\"JNB\", \"DXB\", 1600),\n    (\"FRA\", \"JFK\", 2300),\n    (\"FRA\", \"DXB\", 1800),\n    (\"HKG\", \"LAX\", 2000),\n    (\"HKG\", \"SIN\", 2500),\n    (\"PEK\", \"LAX\", 2200),\n    (\"PEK\", \"LHR\", 1900),\n    (\"ORD\", \"LHR\", 2100),\n    (\"ORD\", \"FRA\", 1700),\n    (\"MIA\", \"GRU\", 1400),\n]\n\n# Build flight path dataframe — great circle arcs computed inline\nflight_paths = []\nN_PTS = 50\n\nfor route_i, (origin, dest, volume) in enumerate(routes):\n    _, olat, olon = airports[origin]\n    _, dlat, dlon = airports[dest]\n\n    lon1_r, lat1_r = np.radians(olon), np.radians(olat)\n    lon2_r, lat2_r = np.radians(dlon), np.radians(dlat)\n    d = np.arccos(\n        np.clip(np.sin(lat1_r) * np.sin(lat2_r) + np.cos(lat1_r) * np.cos(lat2_r) * np.cos(lon2_r - lon1_r), -1.0, 1.0)\n    )\n    if d < 1e-10:\n        arc_lons = np.array([olon, dlon])\n        arc_lats = np.array([olat, dlat])\n    else:\n        t = np.linspace(0, 1, N_PTS)\n        a_c = np.sin((1 - t) * d) / np.sin(d)\n        b_c = np.sin(t * d) / np.sin(d)\n        x = a_c * np.cos(lat1_r) * np.cos(lon1_r) + b_c * np.cos(lat2_r) * np.cos(lon2_r)\n        y = a_c * np.cos(lat1_r) * np.sin(lon1_r) + b_c * np.cos(lat2_r) * np.sin(lon2_r)\n        z = a_c * np.sin(lat1_r) + b_c * np.sin(lat2_r)\n        arc_lats = np.degrees(np.arctan2(z, np.sqrt(x**2 + y**2)))\n        arc_lons = np.degrees(np.arctan2(y, x))\n\n    for step, (lon, lat) in enumerate(zip(arc_lons, arc_lats, strict=True)):\n        flight_paths.append({\"route_id\": route_i, \"step\": step, \"lon\": lon, \"lat\": lat, \"volume\": volume})\n\ndf_flights = pd.DataFrame(flight_paths)\n\n# Airport endpoint markers\ndf_airports = pd.DataFrame([{\"code\": code, \"lat\": lat, \"lon\": lon} for code, (_, lat, lon) in airports.items()])\n\n# Simplified continent polygons for basemap context\ncontinents = []\n\nfor i, (lo, la) in enumerate(\n    zip(\n        [\n            -170,\n            -168,\n            -140,\n            -125,\n            -124,\n            -117,\n            -105,\n            -97,\n            -82,\n            -77,\n            -68,\n            -55,\n            -52,\n            -80,\n            -87,\n            -97,\n            -105,\n            -125,\n            -145,\n            -165,\n            -170,\n        ],\n        [60, 65, 70, 55, 48, 33, 25, 26, 25, 35, 45, 48, 45, 27, 30, 20, 22, 50, 60, 55, 60],\n        strict=True,\n    )\n):\n    continents.append({\"continent\": \"N. America\", \"order\": i, \"lon\": lo, \"lat\": la})\n\nfor i, (lo, la) in enumerate(\n    zip(\n        [-80, -68, -60, -50, -35, -40, -50, -55, -68, -72, -75, -80, -82, -80],\n        [10, 12, 5, 0, -5, -22, -35, -52, -55, -18, -5, 0, 8, 10],\n        strict=True,\n    )\n):\n    continents.append({\"continent\": \"S. America\", \"order\": i, \"lon\": lo, \"lat\": la})\n\nfor i, (lo, la) in enumerate(\n    zip(\n        [-10, 0, 10, 20, 30, 40, 50, 60, 50, 35, 25, 20, 10, 0, -10, -10],\n        [35, 37, 36, 35, 35, 40, 45, 55, 70, 70, 70, 65, 60, 50, 40, 35],\n        strict=True,\n    )\n):\n    continents.append({\"continent\": \"Europe\", \"order\": i, \"lon\": lo, \"lat\": la})\n\nfor i, (lo, la) in enumerate(\n    zip(\n        [-17, -5, 10, 35, 50, 52, 43, 35, 30, 15, 0, -17, -17],\n        [15, 37, 37, 32, 12, 0, -25, -35, -35, -25, 5, 20, 15],\n        strict=True,\n    )\n):\n    continents.append({\"continent\": \"Africa\", \"order\": i, \"lon\": lo, \"lat\": la})\n\nfor i, (lo, la) in enumerate(\n    zip(\n        [60, 80, 100, 120, 140, 145, 140, 130, 105, 100, 80, 60, 45, 30, 25, 30, 35, 50, 60],\n        [55, 70, 75, 70, 55, 45, 35, 30, 0, 5, 10, 25, 30, 35, 42, 55, 70, 70, 55],\n        strict=True,\n    )\n):\n    continents.append({\"continent\": \"Asia\", \"order\": i, \"lon\": lo, \"lat\": la})\n\nfor i, (lo, la) in enumerate(\n    zip(\n        [113, 125, 135, 145, 152, 150, 140, 130, 115, 113],\n        [-22, -15, -12, -15, -25, -38, -38, -33, -35, -22],\n        strict=True,\n    )\n):\n    continents.append({\"continent\": \"Australia\", \"order\": i, \"lon\": lo, \"lat\": la})\n\ndf_continents = pd.DataFrame(continents)\n\n# Plot\nplot = (\n    ggplot()\n    + geom_polygon(\n        aes(x=\"lon\", y=\"lat\", group=\"continent\"),\n        data=df_continents,\n        fill=LAND_FILL,\n        color=LAND_BORDER,\n        size=0.3,\n        alpha=0.95,\n    )\n    + geom_path(\n        aes(x=\"lon\", y=\"lat\", group=\"route_id\", color=\"volume\", size=\"volume\"),\n        data=df_flights,\n        alpha=0.55,\n        lineend=\"round\",\n    )\n    + geom_point(\n        aes(x=\"lon\", y=\"lat\"), data=df_airports, color=INK_SOFT, fill=BRAND, size=3.5, shape=\"o\", stroke=0.8, alpha=0.95\n    )\n    + scale_color_gradient(low=\"#009E73\", high=\"#4467A3\", name=\"Passengers\\n(thousands/yr)\")\n    + scale_size_continuous(range=(0.5, 2.0), guide=None)\n    + coord_cartesian(xlim=(-180, 180), ylim=(-60, 80))\n    + labs(title=TITLE, x=\"Longitude (°)\", y=\"Latitude (°)\")\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK, size=0.2, alpha=0.12),\n        panel_grid_minor=element_blank(),\n        panel_border=element_blank(),\n        plot_title=element_text(size=TITLE_SIZE, color=INK, weight=\"bold\"),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=8, color=INK),\n        legend_position=\"right\",\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}