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
- User entry – In a notebook the user runs
from anymap_ts import Map(exported fromanymap_ts/__init__.py). - Python side –
__init__.pyimports the concrete map class fromanymap_ts/base.py, which creates ananywidget.AnyWidgetinstance using the type definitions insrc/types/anywidget. The widget’s model stores map options (center, zoom, basemap list). - Front‑end boot – When the widget is displayed, anywidget loads the compiled JavaScript bundle produced by Vite. The bundle’s entry point (
src/main.tsfor the selected library) reads the model, instantiates the corresponding renderer (src/maplibre/MapLibreRenderer.tsfor MapLibre,src/deckgl/DeckGLRenderer.tsfor DeckGL, etc.). - Renderer dispatch –
MapLibreRendererimports the high‑fan‑out adapterDeckLayerAdapter.ts. This file is the hub module (30 incoming imports, zero outgoing), meaning changes here ripple to many layers. - 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. - Bidirectional sync – Anywidget’s messaging channel propagates user interactions (draw, click) back to Python via callbacks defined in
anymap_ts/utils.py. Broadexcept: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 commitpackage-lock.json(orpnpm-lock.yaml) to guarantee reproducible builds. - MEDIUM – Workflows lack explicit
permissions:forGITHUB_TOKEN; add least‑privilege scopes. - MEDIUM – No dependency‑vulnerability scan in CI; integrate
dependency-review-actionorosv-scanner. - MEDIUM – Checkout step keeps the token; set
persist-credentials: falseand 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.