Panel de Notebook
Meratyn puede ejecutar un Jupyter notebook junto al mapa, en un panel redimensionable y plegable (ábralo desde Processing → Jupyter Notebook). Funciona tanto en la build web como en la de escritorio, con un runtime de Python distinto detrás del mismo panel <iframe>:
- Build web → JupyterLite. Una UI completa
de Jupyter ejecutándose íntegramente en el navegador sobre un kernel
Pyodide (WebAssembly) — sin servidor. Instale paquetes extra a demanda desde una celda con
%pip install geopandas. - Build de escritorio → un servidor JupyterLab real. La app lanza
jupyter labdesde un entorno Python gestionado con uv (el mismo mecanismo que el sidecar FastAPI) en127.0.0.1:8766, autenticado por token, y lo incrusta. Esto da CPython completo con el stack geoespacial nativo (geopandas, rasterio, GDAL, …).
Esto es distinto del paquete Python meratyn, que hace lo
inverso: incrustar la app completa de Meratyn dentro de una celda de notebook.
Scripting del mapa desde una celda
Un cliente ligero meratyn está precargado en el kernel del notebook — en web
va incluido en el sistema de archivos de JupyterLite, así que no hace falta ningún
pip install; en escritorio se coloca en la ruta de importación del kernel. Solo tiene que importarlo:
import meratyn
m = meratyn.connect() # or meratyn.Map()
m.fly_to(-74.1982, 11.2408, zoom=11) # animate the live map in the left pane
m.add_geojson(gdf, name="My layer") # GeoDataFrame, dict, or JSON string
m.fit_bounds([-123, 37, -122, 38])
m.set_basemap("https://…/style.json")
m.set_visibility(layer_id, False)
m.remove_layer(layer_id)
La mayoría de las llamadas son fire-and-forget: publican un comando en la app anfitriona a través del protocolo de scripting compartido (la misma superficie createScriptingHandlers que usan la consola Python integrada y el widget de Jupyter) y devuelven el control de inmediato, por lo que se comportan igual en el kernel en el navegador y en un servidor real. Fuente canónica del cliente: services/server/notebook_client.py.
El cliente de escritorio incluido también usa llamadas acotadas de relay correlacionadas para estas operaciones cuando está conectado al servidor Jupyter de Meratyn Desktop:
m.add_geojson(...)devuelve el id de la nueva capa como cadena. DevuelveNoneen JupyterLite o cuando debe recurrir a la entrega fire-and-forget; un timeout del relay igualmente devuelveNonecon una advertencia, porque la capa puede haberse creado.m.list_layers()devuelve diccionarios públicos de resumen de capas.m.get_layer(layer_id)devuelve los diccionarios de features GeoJSON de esa capa, excluyendo rutas de source y metadatos.
Otras consultas de lectura (por ejemplo get_center) no están expuestas por el cliente incluido.
Manejar el mapa desde un cliente externo (VS Code, …)
El servidor JupyterLab de la app de escritorio es un servidor Jupyter normal autenticado por token, así que puede conectar cualquier cliente Jupyter a él y conservar su propio editor — y los comandos de mapa anteriores siguen funcionando. Abra el panel de Notebook una vez (eso es lo que arranca el servidor) y use su botón de enlace en la cabecera del panel para copiar la URL de conexión y pegarla en su cliente (en VS Code: Jupyter: Specify Jupyter Server for Connections → Existing).
Esto funciona porque los comandos viajan por un relay en el servidor Jupyter en lugar de depender de cómo se esté mostrando el notebook:
services/server/src/sidecar/jupyter_relay.pyes una extensión del Jupyter Server (activada desdejupyter_server_config.py) que exponePOST …/mappview/relay/command, un WebSocket…/mappview/relay/socketyGET …/mappview/relay/status. Al cargarse publicaMAPPVIEW_RELAY_URLyMAPPVIEW_RELAY_TOKENen el entorno del servidor, que los kernels heredan — así es comoimport meratynencuentra el mapa sin ninguna configuración.- La app se suscribe a ese socket durante toda su vida útil
(
integrations/jupyter/relay/runtime.ts), reconecta con backoff y ejecuta cada comando contra la misma superficiecreateScriptingHandlers. integrations/jupyter/notebook/bridge.tssigue siendo la vía postMessage para el panel incrustado y para web (JupyterLite), donde la página del notebook realmente es el iframe de la app.
El POST responde con cuántas ventanas de la app recibieron el comando, de modo que una sesión desconectada se informa en lugar de no hacer nada silenciosamente:
meratyn.is_connected() # False -> no Meratyn window is listening
El cliente incluido envía comandos fire-and-forget. Otros invocadores de confianza del relay pueden incluir un id de solicitud para una respuesta correlacionada: el servidor difunde un id de despacho fresco a cada ventana viva de la app, devuelve la primera respuesta válida bajo el id original del invocador e ignora las respuestas tardías. Como cada ventana tiene su propio estado de mapa, use una única ventana conectada cuando encadene un id de capa devuelto en una posterior solicitud de lectura.
Cuando un comando no puede entregarse, el cliente lanza una
MappViewNotConnectedWarning apuntando a su propia línea. Conviértala en error con:
import warnings, meratyn
warnings.simplefilter("error", meratyn.MappViewNotConnectedWarning)
Solo el servidor de escritorio tiene el relay: en web (JupyterLite) no hay servidor al que adjuntar un cliente externo.
Tema
En la build web el notebook sigue el tema claro/oscuro de la app en vivo (sin recargar). JupyterLite se compila con exposeAppInBrowser (véase
apps/mappview/jupyterlite/jupyter-lite.json), que expone su objeto app en window.jupyterapp; como el iframe de JupyterLite es del mismo origen,
integrations/jupyter/notebook/theme.ts entra y ejecuta el comando integrado apputils:change-theme
cada vez que cambia el tema de la app. En la build de escritorio el servidor
JupyterLab es de otro origen, así que esta llamada entre orígenes no hace nada y el
notebook conserva su propio tema (sincronizarlo allí requeriría una pequeña extensión
de JupyterLab — una mejora futura).
Construir el sitio JupyterLite web
La build web incrusta un sitio JupyterLite autoalojado bajo
apps/mappview/public/jupyterlite/. No se versiona en git (≈70 MB) y
lo genera un paso prebuild de mejor esfuerzo que necesita la CLI jupyter lite:
pip install -r apps/mappview/jupyterlite/requirements.txt
bun run build:jupyterlite
Tanto bun run dev como bun run build lo ejecutan automáticamente:
bun run devlo compila una vez en la primera ejecución mediante un hookpredev(--if-missing), y luego es instantáneo en las siguientes.bun run buildsiempre lo recompila (víaprebuild) para recoger cambios en el cliente/configuración.
Ambos se omiten con elegancia cuando jupyter lite no está instalado — una build solo Node igualmente triunfa y el panel de Notebook web muestra un mensaje de “not built” hasta que se genere el sitio (instale las dependencias anteriores y vuelva a ejecutar). Las rutas de desarrollo y build de escritorio (Tauri) lo omiten por completo (usan el servidor JupyterLab real), de modo que el sitio estático nunca infla el instalador.
La imagen Docker de producción es diferente: instala estas dependencias Python solo para la compilación y marca JupyterLite como obligatorio. La build del contenedor falla si el sitio no se puede generar, y nginx devuelve un 404 acotado en lugar de la app de respaldo si llega a faltar un recurso del notebook.
La configuración de build vive en apps/mappview/jupyterlite/ (un
jupyter_lite_config.json, el requirements.txt de build y un
files/Welcome.ipynb inicial). El directorio generado queda excluido del precache
de la PWA (véase pwaPlugin en apps/mappview/vite.config.ts).
Servidor de escritorio
- Extra del backend:
notebookenservices/server/pyproject.toml(jupyterlab,jupyter-server), sincronizado en su propio entorno de proyecto uv para que nunca perturbe el entorno del sidecar. - Lanzador / ciclo de vida: comandos Tauri
start_jupyter_server/stop_jupyter_serverenapps/mappview/src-tauri/src/lib.rs(espeja el lanzador del sidecar), con el wrapper TS enapps/mappview/src/integrations/jupyter/relay/server.ts. - Enmarcado:
services/server/jupyter_server_config.pyrelajaContent-Security-Policy: frame-ancestorsa los orígenes del webview Tauri / loopback para que la app pueda incrustar el servidor; la CSP de Tauri (tauri.conf.json) añade los orígenes de loopback aframe-src/child-src. - Relay de comandos de mapa: la misma configuración activa la extensión de servidor
sidecar.jupyter_relay(véase más arriba). Su WebSocket acepta solo los orígenes propios de la app y cada endpoint exige el token por lanzamiento del servidor, de modo que un comando solo puede provenir de algo que ya tenga derechos de ejecución de kernel allí.