{"spec_id":"map-route-path","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nmap-route-path: Route Path Map\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-21\n\"\"\"\n\nimport base64\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# bokeh.py shadows the installed bokeh package when Python adds this file's\n# directory to sys.path[0]; remove it so imports resolve to the package.\n_here = os.path.dirname(os.path.abspath(__file__))\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != _here]\ndel _here\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper, Title\nfrom bokeh.palettes import Viridis256\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme\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\"\n\n# Data - Simulated hiking trail GPS track (Rocky Mountain National Park)\nnp.random.seed(42)\n\nstart_lon = -105.683\nstart_lat = 40.343\n\nn_points = 200\nt = np.linspace(0, 1, n_points)\n\n# Low-frequency meanders: both lon and lat are monotonically increasing at all t,\n# so the trail never crosses itself (verified: d(lon)/dt >= 0.019, d(lat)/dt >= 0.008)\nlon_progress = 0.038 * t\nlat_progress = 0.027 * t\nlon_meander = 0.006 * np.sin(np.pi * t)\nlat_meander = 0.003 * np.sin(2 * np.pi * t)\n\nlats = start_lat + lat_progress + lat_meander\nlons = start_lon + lon_progress + lon_meander\n\n# Convert lat/lon to Web Mercator for tile compatibility\nk = 20037508.34 / 180\nx_coords = lons * k\ny_coords = np.log(np.tan((90 + lats) * np.pi / 360)) / (np.pi / 180) * k\n\n# Progress percentage (0–100) drives both dot coloring and the colorbar\nt_pct = t * 100\nmapper = LinearColorMapper(palette=Viridis256, low=0, high=100)\n\nsource = ColumnDataSource(data={\"x\": x_coords, \"y\": y_coords, \"t_pct\": t_pct})\n\n# Figure\nW, H = 3200, 1800\np = figure(\n    width=W,\n    height=H,\n    x_axis_type=\"mercator\",\n    y_axis_type=\"mercator\",\n    x_axis_label=\"Longitude\",\n    y_axis_label=\"Latitude\",\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=200,  # extra room for the colorbar + tick labels\n)\n\n# Title\ntitle_obj = Title(\n    text=\"Rocky Mountain Trail · map-route-path · python · bokeh · anyplot.ai\", text_font_size=\"50pt\", text_color=INK\n)\np.add_layout(title_obj, \"above\")\n\n# Theme-appropriate basemap tile\ntile_provider = \"CartoDB Positron\" if THEME == \"light\" else \"CartoDB Dark Matter\"\np.add_tile(tile_provider)\n\n# Route line — Okabe-Ito position 1 (#009E73)\np.line(x=\"x\", y=\"y\", source=source, line_width=5, line_color=\"#009E73\", line_alpha=0.9)\n\n# Waypoints with viridis gradient showing trail progression\np.scatter(\n    x=\"x\",\n    y=\"y\",\n    source=source,\n    size=12,\n    fill_color={\"field\": \"t_pct\", \"transform\": mapper},\n    line_color=PAGE_BG,\n    line_width=1.5,\n    alpha=0.85,\n)\n\n# Start marker — Okabe-Ito #009E73 (large circle)\nstart_src = ColumnDataSource(data={\"x\": [x_coords[0]], \"y\": [y_coords[0]]})\np.scatter(\n    x=\"x\",\n    y=\"y\",\n    source=start_src,\n    size=32,\n    fill_color=\"#009E73\",\n    line_color=PAGE_BG,\n    line_width=3,\n    legend_label=\"Start\",\n)\n\n# End marker — Okabe-Ito #C475FD (large square)\nend_src = ColumnDataSource(data={\"x\": [x_coords[-1]], \"y\": [y_coords[-1]]})\np.scatter(\n    x=\"x\",\n    y=\"y\",\n    source=end_src,\n    size=32,\n    fill_color=\"#C475FD\",\n    line_color=PAGE_BG,\n    line_width=3,\n    marker=\"square\",\n    legend_label=\"End\",\n)\n\n# Colorbar decoding the viridis gradient: purple = trail start, yellow = trail end\ncolor_bar = ColorBar(\n    color_mapper=mapper,\n    label_standoff=16,\n    major_label_text_font_size=\"28pt\",\n    major_label_text_color=INK_SOFT,\n    title=\"Trail Progress (%)\",\n    title_text_font_size=\"30pt\",\n    title_text_color=INK,\n    background_fill_color=PAGE_BG,\n    bar_line_color=INK_SOFT,\n    major_tick_line_color=INK_SOFT,\n    width=40,\n)\np.add_layout(color_bar, \"right\")\n\n# Theme-adaptive chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Slightly more visible grid on the near-black dark basemap\ngrid_alpha = 0.06 if THEME == \"light\" else 0.09\np.xgrid.grid_line_color = INK\np.ygrid.grid_line_color = INK\np.xgrid.grid_line_alpha = grid_alpha\np.ygrid.grid_line_alpha = grid_alpha\n\n# Legend\np.legend.location = \"top_right\"\np.legend.label_text_font_size = \"34pt\"\np.legend.background_fill_color = ELEVATED_BG\np.legend.border_line_color = INK_SOFT\np.legend.label_text_color = INK_SOFT\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome (Selenium 4 / Selenium Manager)\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\n# Capture full page (beyond visible viewport) to handle the ~139px viewport gap\nresult = driver.execute_cdp_cmd(\"Page.captureScreenshot\", {\"format\": \"png\", \"captureBeyondViewport\": True})\nwith open(f\"plot-{THEME}.png\", \"wb\") as fh:\n    fh.write(base64.b64decode(result[\"data\"]))\ndriver.quit()\n"}