Skip to content

Instantly share code, notes, and snippets.

@beyoung
Created July 19, 2026 08:46
Show Gist options
  • Select an option

  • Save beyoung/1bd0da1617784b60b9c94114f4205307 to your computer and use it in GitHub Desktop.

Select an option

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
Display the source blob
Display the rendered blob
Raw
{"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]]}}]}
#!/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