Created
July 19, 2026 08:46
-
-
Save beyoung/1bd0da1617784b60b9c94114f4205307 to your computer and use it in GitHub Desktop.
Python + Cartopy: Render Trans-Siberian Railway & Pan-American Highway on a dark-theme world map
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| {"type": "FeatureCollection", "features": [{"type": "Feature", "properties": {"name": "Pan-American Highway", "description": "Prudhoe Bay \u2192 Ushuaia \u2014 ~30,000 km"}, "geometry": {"type": "LineString", "coordinates": [[-148.8782, 70.2568], [-148.6891, 69.6284], [-148.5, 69.0], [-148.25, 68.25], [-148.0, 67.5], [-147.8615, 66.16890000000001], [-147.723, 64.8378], [-148.1115, 64.16890000000001], [-148.5, 63.5], [-149.0, 62.75], [-149.5, 62.0], [-149.684, 61.609049999999996], [-149.868, 61.2181], [-149.934, 60.859049999999996], [-150.0, 60.5], [-149.5, 60.0], [-149.0, 59.5], [-148.25, 59.0], [-147.5, 58.5], [-146.5, 58.0], [-145.5, 57.5], [-144.25, 57.0], [-143.0, 56.5], [-142.0, 56.0], [-141.0, 55.5], [-140.0, 55.0], [-139.0, 54.5], [-138.0, 54.0], [-137.0, 53.5], [-136.02814999999998, 57.110600000000005], [-135.0563, 60.7212], [-136.52814999999998, 61.360600000000005], [-138.0, 62.0], [-137.0, 61.5], [-136.0, 61.0], [-135.52814999999998, 60.860600000000005], [-135.0563, 60.7212], [-134.02814999999998, 60.360600000000005], [-133.0, 60.0], [-132.0, 59.5], [-131.0, 59.0], [-130.0, 58.75], [-129.0, 58.5], [-128.0, 58.25], [-127.0, 58.0], [-126.0, 57.5], [-125.0, 57.0], [-124.0, 56.5], [-123.0, 56.0], [-122.8493, 57.402699999999996], [-122.6986, 58.8054], [-122.3493, 57.902699999999996], [-122.0, 57.0], [-121.5, 56.5], [-121.0, 56.0], [-120.61869999999999, 55.87975], [-120.2374, 55.7595], [-121.4915, 54.838300000000004], [-122.7456, 53.9171], [-122.9336, 51.5999], [-123.1216, 49.2827], [-122.72685, 48.44445], [-122.3321, 47.6062], [-122.50524999999999, 46.5607], [-122.6784, 45.5152], [-122.5892, 44.2576], [-122.5, 43.0], [-122.4597, 40.38745], [-122.4194, 37.7749], [-121.7097, 36.88745], [-121.0, 36.0], [-120.0, 35.75], [-119.0, 35.5], [-118.62185, 34.7761], [-118.2437, 34.0522], [-117.70240000000001, 33.38395], [-117.1611, 32.7157], [-117.09055000000001, 32.62435], [-117.02, 32.533], [-116.25999999999999, 31.7665], [-115.5, 31.0], [-114.25, 30.5], [-113.0, 30.0], [-111.98859999999999, 29.53785], [-110.9772, 29.0757], [-109.98859999999999, 28.03785], [-109.0, 27.0], [-108.0, 26.0], [-107.0, 25.0], [-106.705, 24.10835], [-106.41, 23.2167], [-105.705, 22.60835], [-105.0, 22.0], [-104.175, 21.33835], [-103.35, 20.6767], [-102.175, 20.33835], [-101.0, 20.0], [-100.0666, 19.7163], [-99.1332, 19.4326], [-98.0666, 18.9663], [-97.0, 18.5], [-96.8632, 17.7866], [-96.7264, 17.0732], [-95.8632, 16.7866], [-95.0, 16.5], [-94.05625, 16.62765], [-93.1125, 16.7553], [-92.30625, 16.12765], [-91.5, 15.5], [-91.00755000000001, 15.0622], [-90.5151, 14.6244], [-90.00755000000001, 14.3122], [-89.5, 14.0], [-89.3592, 13.84645], [-89.2184, 13.6929], [-88.6092, 13.34645], [-88.0, 13.0], [-87.25, 12.75], [-86.5, 12.5], [-86.38669999999999, 12.30745], [-86.2734, 12.1149], [-85.63669999999999, 11.55745], [-85.0, 11.0], [-84.54165, 10.46665], [-84.0833, 9.9333], [-83.29165, 9.71665], [-82.5, 9.5], [-81.5, 9.25], [-80.5, 9.0], [-80.01665, 8.99165], [-79.5333, 8.9833], [-78.76665, 8.49165], [-78.0, 8.0], [-77.25, 7.25], [-76.5, 6.5], [-76.03790000000001, 6.358499999999999], [-75.5758, 6.217], [-75.03790000000001, 5.858499999999999], [-74.5, 5.5], [-74.28605, 5.1055], [-74.0721, 4.711], [-75.03605, 3.8555], [-76.0, 3.0], [-77.0, 2.25], [-78.0, 1.5], [-78.25, 0.75], [-78.5, 0.0], [-78.5, -0.11495], [-78.5, -0.2299], [-78.75, -1.1149499999999999], [-79.0, -2.0], [-79.25, -2.75], [-79.5, -3.5], [-79.75, -4.25], [-80.0, -5.0], [-80.0, -5.75], [-80.0, -6.5], [-79.5, -7.25], [-79.0, -8.0], [-78.5, -8.75], [-78.0, -9.5], [-77.75, -10.25], [-77.5, -11.0], [-77.26665, -11.525], [-77.0333, -12.05], [-76.76665, -12.775], [-76.5, -13.5], [-76.0, -14.25], [-75.5, -15.0], [-75.0, -15.5], [-74.5, -16.0], [-74.0, -16.25], [-73.5, -16.5], [-72.75, -16.5], [-72.0, -16.5], [-71.76875, -16.44945], [-71.5375, -16.3989], [-70.76875, -17.19945], [-70.0, -18.0], [-69.75, -19.0], [-69.5, -20.0], [-69.5, -21.0], [-69.5, -22.0], [-69.5, -23.0], [-69.5, -24.0], [-69.75, -25.0], [-70.0, -26.0], [-70.25, -27.0], [-70.5, -28.0], [-70.5, -29.0], [-70.5, -30.0], [-70.57415, -31.72845], [-70.6483, -33.4569], [-70.32415, -34.228449999999995], [-70.0, -35.0], [-69.75, -36.0], [-69.5, -37.0], [-69.25, -38.0], [-69.0, -39.0], [-69.25, -40.0], [-69.5, -41.0], [-70.405, -41.06675], [-71.31, -41.1335], [-70.655, -42.06675], [-70.0, -43.0], [-69.25, -43.75], [-68.5, -44.5], [-67.99000000000001, -45.183350000000004], [-67.48, -45.8667], [-67.74000000000001, -46.933350000000004], [-68.0, -48.0], [-68.5, -49.0], [-69.0, -50.0], [-69.5, -51.0], [-70.0, -52.0], [-70.46635, -52.583349999999996], [-70.9327, -53.1667], [-70.21635, -53.583349999999996], [-69.5, -54.0], [-68.9015, -54.40345], [-68.303, -54.8069]]}}]} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Render the World's Longest Railway & Highway Routes | |
| =================================================== | |
| Visualize the Trans-Siberian Railway and Pan-American Highway | |
| using Python, Cartopy, and Matplotlib. | |
| Requirements: pip install matplotlib cartopy numpy | |
| """ | |
| import json | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| import cartopy.crs as ccrs | |
| import cartopy.feature as cfeature | |
| from matplotlib.lines import Line2D | |
| # ──────────────────────────────────────────── | |
| # DATA: Waypoints along each route | |
| # ──────────────────────────────────────────── | |
| TRANS_SIBERIAN_WAYPOINTS = [ | |
| # European Russia | |
| [37.6176, 55.7558], # Moscow | |
| [40.3960, 56.1365], # Vladimir | |
| [44.0020, 56.3269], # Nizhny Novgorod | |
| [49.6600, 58.5966], # Kirov | |
| [56.2293, 58.0105], # Perm | |
| [60.5975, 56.8389], # Yekaterinburg | |
| # Siberia | |
| [65.5344, 57.1530], # Tyumen | |
| [73.3703, 54.9893], # Omsk | |
| [82.9346, 55.0302], # Novosibirsk | |
| [92.8526, 56.0090], # Krasnoyarsk | |
| [104.2968, 52.2864], # Irkutsk | |
| [107.6100, 51.8333], # Ulan-Ude | |
| # Far East | |
| [113.5007, 52.0330], # Chita | |
| [132.9250, 48.7925], # Birobidzhan | |
| [135.0852, 48.4802], # Khabarovsk | |
| [131.8735, 43.1056], # Vladivostok | |
| ] | |
| PAN_AMERICAN_WAYPOINTS = [ | |
| [-148.8782, 70.2568], # Prudhoe Bay | |
| [-147.7230, 64.8378], # Fairbanks | |
| [-149.8680, 61.2181], # Anchorage | |
| [-135.0563, 60.7212], # Whitehorse | |
| [-123.1216, 49.2827], # Vancouver | |
| [-122.3321, 47.6062], # Seattle | |
| [-122.6784, 45.5152], # Portland | |
| [-122.4194, 37.7749], # San Francisco | |
| [-118.2437, 34.0522], # Los Angeles | |
| [-117.1611, 32.7157], # San Diego | |
| [-117.0200, 32.5330], # Tijuana | |
| [-110.9772, 29.0757], # Hermosillo | |
| [-106.4100, 23.2167], # Mazatlán | |
| [-103.3500, 20.6767], # Guadalajara | |
| [-99.1332, 19.4326], # Mexico City | |
| [-96.7264, 17.0732], # Oaxaca | |
| [-93.1125, 16.7553], # Tuxtla Gutiérrez | |
| [-90.5151, 14.6244], # Guatemala City | |
| [-89.2184, 13.6929], # San Salvador | |
| [-86.2734, 12.1149], # Managua | |
| [-84.0833, 9.9333], # San José | |
| [-79.5333, 8.9833], # Panama City | |
| [-75.5758, 6.2170], # Medellín | |
| [-74.0721, 4.7110], # Bogotá | |
| [-78.5000, -0.2299], # Quito | |
| [-77.0333, -12.0500], # Lima | |
| [-71.5375, -16.3989], # Arequipa | |
| [-70.6483, -33.4569], # Santiago | |
| [-67.4800, -45.8667], # Comodoro Rivadavia | |
| [-70.9327, -53.1667], # Punta Arenas | |
| [-68.3030, -54.8069], # Ushuaia | |
| ] | |
| def smooth_path(coords, segments=2): | |
| """Add interpolated points between each waypoint for smoother curves.""" | |
| result = [] | |
| for i in range(len(coords) - 1): | |
| result.append(coords[i]) | |
| for s in range(1, segments): | |
| t = s / segments | |
| result.append([ | |
| coords[i][0] + (coords[i+1][0] - coords[i][0]) * t, | |
| coords[i][1] + (coords[i+1][1] - coords[i][1]) * t | |
| ]) | |
| result.append(coords[-1]) | |
| return result | |
| def save_geojson(waypoints, name, filename): | |
| """Save waypoints as a GeoJSON LineString.""" | |
| coords = smooth_path(waypoints) | |
| geojson = { | |
| "type": "FeatureCollection", | |
| "features": [{ | |
| "type": "Feature", | |
| "properties": {"name": name}, | |
| "geometry": { | |
| "type": "LineString", | |
| "coordinates": coords | |
| } | |
| }] | |
| } | |
| with open(filename, 'w') as f: | |
| json.dump(geojson, f) | |
| print(f" GeoJSON saved: {filename} ({len(coords)} points)") | |
| def draw_map(rail_waypoints, road_waypoints, output='railway_road_map.png'): | |
| """Render both routes on a dark-theme world map.""" | |
| rail_smooth = smooth_path(rail_waypoints) | |
| road_smooth = smooth_path(road_waypoints) | |
| rail_lons = [c[0] for c in rail_smooth] | |
| rail_lats = [c[1] for c in rail_smooth] | |
| road_lons = [c[0] for c in road_smooth] | |
| road_lats = [c[1] for c in road_smooth] | |
| fig = plt.figure(figsize=(18, 9), facecolor='#0d1117') | |
| ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) | |
| ax.set_facecolor('#0d1117') | |
| ax.set_extent([-170, 180, -58, 73], crs=ccrs.PlateCarree()) | |
| # Base layers | |
| ax.add_feature(cfeature.LAND, facecolor='#161b22', edgecolor='#1a2332', linewidth=0.3) | |
| ax.add_feature(cfeature.OCEAN, facecolor='#0d1117') | |
| ax.add_feature(cfeature.COASTLINE, edgecolor='#1a2332', linewidth=0.4) | |
| ax.add_feature(cfeature.BORDERS, edgecolor='#1f2a3a', linewidth=0.2, alpha=0.5) | |
| # Gridlines | |
| gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, | |
| linewidth=0.5, color='#1a2332', alpha=0.7) | |
| gl.top_labels = False | |
| gl.right_labels = False | |
| gl.xlocator = plt.FixedLocator(range(-180, 181, 30)) | |
| gl.ylocator = plt.FixedLocator(range(-90, 91, 30)) | |
| # Routes | |
| rail_line, = ax.plot(rail_lons, rail_lats, color='#f0883e', linewidth=2.2, | |
| alpha=0.9, solid_capstyle='round', zorder=5, | |
| transform=ccrs.PlateCarree()) | |
| road_line, = ax.plot(road_lons, road_lats, color='#58a6ff', linewidth=2.2, | |
| alpha=0.9, solid_capstyle='round', zorder=5, | |
| transform=ccrs.PlateCarree()) | |
| # Start/end markers | |
| ax.plot(rail_lons[0], rail_lats[0], 'o', color='#f0883e', markersize=7, zorder=6, | |
| transform=ccrs.PlateCarree()) | |
| ax.plot(rail_lons[-1], rail_lats[-1], 's', color='#f0883e', markersize=7, zorder=6, | |
| transform=ccrs.PlateCarree()) | |
| ax.plot(road_lons[0], road_lats[0], 'o', color='#58a6ff', markersize=7, zorder=6, | |
| transform=ccrs.PlateCarree()) | |
| ax.plot(road_lons[-1], road_lats[-1], 's', color='#58a6ff', markersize=7, zorder=6, | |
| transform=ccrs.PlateCarree()) | |
| # Title | |
| ax.text(0.5, 0.97, "World's Longest Railway & Highway Routes", | |
| transform=ax.transAxes, color='#e6edf3', fontsize=15, | |
| fontweight='bold', ha='center', va='top', fontfamily='monospace') | |
| ax.text(0.5, 0.93, 'Trans-Siberian Railway · Pan-American Highway', | |
| transform=ax.transAxes, color='#8b949e', fontsize=9, | |
| ha='center', va='top', fontfamily='monospace') | |
| # Legend | |
| legend = ax.legend( | |
| handles=[ | |
| Line2D([0], [0], color='#f0883e', linewidth=2.5, | |
| label='Trans-Siberian Railway (9,289 km)'), | |
| Line2D([0], [0], color='#58a6ff', linewidth=2.5, | |
| label='Pan-American Highway (~30,000 km)'), | |
| ], | |
| loc='lower left', framealpha=0.85, facecolor='#161b22', | |
| edgecolor='#2d3a4a', labelcolor='#c9d1d9', fontsize=8 | |
| ) | |
| plt.savefig(output, dpi=180, bbox_inches='tight', facecolor=fig.get_facecolor()) | |
| plt.close() | |
| print(f" Map saved: {output}") | |
| if __name__ == '__main__': | |
| print("Rendering World's Longest Routes...") | |
| print() | |
| print("Step 1: Saving GeoJSON files") | |
| save_geojson(TRANS_SIBERIAN_WAYPOINTS, "Trans-Siberian Railway", | |
| "trans_siberian_railway.geojson") | |
| save_geojson(PAN_AMERICAN_WAYPOINTS, "Pan-American Highway", | |
| "pan_american_highway.geojson") | |
| print() | |
| print("Step 2: Rendering map") | |
| draw_map(TRANS_SIBERIAN_WAYPOINTS, PAN_AMERICAN_WAYPOINTS) | |
| print() | |
| print("Done! Files created:") | |
| print(" - trans_siberian_railway.geojson") | |
| print(" - pan_american_highway.geojson") | |
| print(" - railway_road_map.png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment