The Problem

Creating interactive, browser‑based maps inside Jupyter notebooks normally requires juggling separate Python wrappers, JavaScript builds, and manual widget plumbing. Users end up with fragmented code bases, duplicated layer logic, and brittle builds that break when front‑end dependencies shift.

What This Does

anymap-ts bundles a Python API with a TypeScript front‑end, all served through anywidget.

  • The Python package lives in anymap_ts/ (e.g. anymap_ts/maplibre.py, anymap_ts/leaflet.py). Each module defines a class that subclasses the anywidget base and forwards configuration to the browser.
  • Front‑end code resides under src/ (src/maplibre/MapLibreRenderer.ts, src/maplibre/adapters/DeckLayerAdapter.ts, etc.) and is built with Vite (vite.config.ts).
  • Example notebooks and HTML entry points (examples/cesium/main.ts, examples/maplibre/index.html) demonstrate the full stack.

The package supports multiple libraries—MapLibre, Mapbox, Leaflet, OpenLayers, DeckGL, Cesium, KeplerGL, and Potree—by reusing a common widget skeleton (anymap_ts/base.py) and delegating rendering to the appropriate TypeScript renderer.

How It Is Wired

  1. User entry – In a notebook the user runs from anymap_ts import Map (exported from anymap_ts/__init__.py).
  2. Python side – __init__.py imports the concrete map class from anymap_ts/base.py, which creates an anywidget.AnyWidget instance using the type definitions in src/types/anywidget. The widget’s model stores map options (center, zoom, basemap list).
  3. Front‑end boot – When the widget is displayed, anywidget loads the compiled JavaScript bundle produced by Vite. The bundle’s entry point (src/main.ts for the selected library) reads the model, instantiates the corresponding renderer (src/maplibre/MapLibreRenderer.ts for MapLibre, src/deckgl/DeckGLRenderer.ts for DeckGL, etc.).
  4. Renderer dispatch – MapLibreRenderer imports the high‑fan‑out adapter DeckLayerAdapter.ts. This file is the hub module (30 incoming imports, zero outgoing), meaning changes here ripple to many layers.
  5. Layer creation – Renderers call adapters (e.g., DeckLayerAdapter) to translate Python‑side layer specifications into MapLibre‑compatible objects. The adapters do not import other modules, keeping the dependency direction clean.
  6. Bidirectional sync – Anywidget’s messaging channel propagates user interactions (draw, click) back to Python via callbacks defined in anymap_ts/utils.py. Broad except: blocks in these utilities swallow errors, as flagged in the health audit.

No circular imports were detected; the call graph is a directed acyclic structure with 98 internal modules and 109 edges.

How To Use It

# Clone the repo (use the exact URL given)
git clone https://github.com/moses-y/anymap-ts
cd anymap-ts

# Install Python package in editable mode with dev extras
pip install -e ".[dev]"

# Install front‑end dependencies (npm is implied by package.json)
npm install
npm run build   # Vite builds the TS bundles

# Example: launch a MapLibre map in a notebook
python - <<'PY'
from anymap_ts import Map
m = Map(center=[-122.4, 37.8], zoom=10)
m.add_basemap("OpenStreetMap")
m
PY

Optional: Set MAPBOX_TOKEN in the environment for Mapbox maps, as shown in the README.

Real‑World Use

A data‑science team can embed a MapLibre view inside a notebook to explore a large GeoJSON dataset, then call m.add_draw_control() to let analysts sketch polygons. The drawn geometry is instantly available as a Python shapely object via the anywidget callback, enabling downstream spatial analysis without leaving the notebook.

Code Health & Issues

  • HIGH – GitHub Actions use tag versions (astral-sh/setup-uv@v7, nwtgck/actions-netlify@v3.0). Pin to commit SHA to avoid supply‑chain drift.
  • HIGH – No lockfile for npm (package.json). Generate and commit package-lock.json (or pnpm-lock.yaml) to guarantee reproducible builds.
  • MEDIUM – Workflows lack explicit permissions: for GITHUB_TOKEN; add least‑privilege scopes.
  • MEDIUM – No dependency‑vulnerability scan in CI; integrate dependency-review-action or osv-scanner.
  • MEDIUM – Checkout step keeps the token; set persist-credentials: false and pass a scoped token only where needed.
  • LOW – CI jobs have no timeout-minutes; define sensible limits to prevent overlapping runs.

Additional observations: tests exist (tests/), CI is present, a license file is included, but a lockfile is missing and several large, highly‑connected TypeScript files (DeckLayerAdapter.ts, MapLibreRenderer.ts, anymap_ts/maplibre.py) exhibit high cognitive load and duplicated code blocks.

The Bottom Line

anymap-ts delivers a functional Python‑to‑JavaScript map widget ecosystem with extensive library support, but the front‑end codebase contains several heavyweight modules that are hard to modify safely. Immediate remediation should focus on stabilizing the hub adapters, adding lockfiles, and tightening CI permissions. It is a solid foundation for notebook‑based GIS work, provided the team is comfortable managing the TypeScript side or is willing to invest in refactoring the identified hotspots.