第19章 - Python 控制台、Notebook 与 Jupyter 集成
GeoLibre 与 Python 的关系有三个方向,非常容易混淆:
- Python 在应用里:Python Console,用 Pyodide 在浏览器里跑 CPython,直接驱动你眼前的地图;
- Notebook 在应用里:Notebook 面板,网页版是 JupyterLite,桌面版是真 JupyterLab 服务器;
- 应用在 Notebook 里:
geolibrePython 包,把整站 GeoLibre 嵌进一个单元格,状态双向同步。
一、二方向是”从我这里操作地图”,三方向是”从 notebook 操作地图”。三者的 API 表面刻意做成一致——控制台里学会的调用,搬到 notebook 里基本能直接用。
说明:本章基于 GeoLibre 3.4。方法名、参数名、环境变量、文件路径保留英文原文,叙述用中文。事实来源为仓库
docs/user-guide/python-console.md、docs/notebook.md、docs/python.md、docs/r.md、python/src/geolibre/geolibre.py、backend/geolibre_server/notebook_client.py、backend/geolibre_server/jupyter_server_config.py。
19.1 三种运行位置,一张对照表
| Python Console | Notebook 面板 | geolibre 包 |
|
|---|---|---|---|
| 入口 | Processing → Python Console | Processing → Jupyter Notebook | pip install geolibre |
| 运行时 | Pyodide(浏览器内 WebAssembly) | 网页版 JupyterLite / 桌面版 JupyterLab | 你环境里的 CPython |
| 面板形态 | 底部可调高度面板 | 底部 iframe 面板 | 单元格里的 anywidget |
| 地图在哪 | 主地图(同一页面) | 左/上主地图,右/下 notebook | iframe 内的整站 GeoLibre |
| 数据可达性 | 受 CSP 与沙箱限制 | 桌面版可读本地文件;网页版不能 | 完整 CPython:geopandas、rasterio、GDAL |
| 状态持久 | 应用开着就保留变量 | kernel 存活期 | kernel 存活期 |
| 典型用途 | 临时改地图、试 API | 边写代码边看地图 | 分析、批处理、出图、可重复研究 |
提示:先想清楚”数据在哪一侧”。数据在桌面磁盘上,用桌面版的 Notebook 面板或
geolibre包;数据是公开 URL,三者都行;只想快速把图层改一改,Console 最快。
19.2 Python Console:首次运行与界面
19.2.1 打开与首次运行
Processing → Python Console,面板停靠在窗口底部,拖上边缘改高度,✕ 关闭。它不阻塞应用其余部分。
Python 通过 Pyodide(编译成 WebAssembly 的 CPython)运行,所以网页版和桌面版都能用,什么都不用装。第一次打开会下载运行时(几 MB,头部显示进度);之后保持”热”状态,变量在应用开着期间一直存在——即使你关掉再打开面板。
19.2.2 键盘
| 键 | 动作 |
|---|---|
| Ctrl/Cmd + Enter | 运行当前代码 |
| Enter | 换行(多行编辑) |
| ↑ / ↓ | 上一条 / 下一条命令(光标在首行/末行时) |
| Tab 或 Ctrl + Space | 补全光标处的名字或属性 |
补全会内省实时运行时:输入 geolibre. 会列出它真正的方法,你自己定义的变量和导入的模块也能补全。多个候选时出现列表,↑/↓ 选择,Enter/Tab 接受,Esc 关闭。
geolibre.get_center() # 当前视图的 [lng, lat]
geolibre.get_bounds() # [west, south, east, north]
geolibre.fly_to(-122.4, 37.8, zoom=10)
19.2.3 脚本编辑器
要写多行、要留存的脚本,点头部的面板图标(Show editor)在控制台旁打开脚本编辑器——相当于 QGIS 的 Python 编辑器。拖分隔条改宽度,再点图标收起。
编辑器与控制台共用同一个解释器,所以脚本里定义的变量或函数立刻能在控制台用,反之亦然。
| 功能 | 行为 |
|---|---|
| New / Open / Save / Save As | 操作 .py 文件;桌面版写回打开的文件,浏览器版下载(或用 File System Access API) |
| 未保存标记 | 文件名旁显示 • |
| Run(或 Ctrl/Cmd+Enter) | 跑整个脚本,有选中时只跑选中行;输出与错误显示在右侧控制台 |
| Tab | 缩进 |
| Ctrl+Space | 补全 |
| Ctrl/Cmd+S | 保存 |
提示:把常用的十行”初始化”(加底图、设视图、装包)存成一个
.py,每次开应用跑一遍,比记在脑子里可靠。
19.3 geolibre 对象:控制台的唯一入口
控制台暴露一个对象 geolibre,它的方法镜像 Python 包,所以学一次两边通用。
19.3.1 相机与视图
| 方法 | 说明 |
|---|---|
get_view() |
实时相机:{center, zoom, bearing, pitch, bbox} |
get_center() |
[lng, lat] |
get_bounds() |
[west, south, east, north] |
fly_to(lng, lat, zoom=, bearing=, pitch=, duration=) |
动画移动;只改给出的字段 |
fit_bounds([w, s, e, n]) |
适配到范围 |
set_basemap(url) |
设置底图样式(http(s) 或根相对 URL) |
19.3.2 图层
| 方法 | 说明 |
|---|---|
add_geojson(data, name=, **style) |
从 GeoJSON 字典 / 几何 / 带 __geo_interface__ 的对象加图层,可内联样式覆盖;返回图层 id |
await load_geojson(url, name=, **style) |
抓一个 GeoJSON URL 再加,同样支持样式覆盖 |
layers |
Layer 列表,按绘制顺序 |
get_layer(layer_id) |
取某个 Layer(不存在会抛错) |
remove_layer(layer_id) |
按 id 删除 |
identify(lng, lat, layer_id=None) |
查询某点的已渲染要素(等价于点击) |
Layer 的成员:
| 成员 | 说明 |
|---|---|
id、name、type |
身份(只读) |
visible |
读/写可见性 |
opacity |
读/写不透明度(0–1) |
set_style(**style) |
合并样式覆盖(例如 fillColor="#ff0000") |
get_features() |
要素列表,元素是 Feature |
zoom_to() |
缩放到图层范围 |
remove() |
从地图移除 |
Feature 本身就是一个普通 dict,所以能直接序列化、能喂给 geopandas.GeoDataFrame.from_features;它另带 .geometry、.properties、.id 与 __geo_interface__ 四个便利访问器。
19.3.3 处理与图像
| 方法 | 说明 |
|---|---|
list_algorithms() |
可用算法(id、name、group、parameters) |
await run_algorithm(id, parameters=None) |
跑算法,结果图层加进地图,返回 {logs, resultLayerIds} |
to_image() |
把当前地图截成 PNG 字节 |
19.3.4 异步与装包
控制台支持顶层 await。少数操作是异步的,必须 await:
# 抓远程 GeoJSON 并加图层
await geolibre.load_geojson("https://example.com/data.geojson", name="Data")
# 跑处理算法;结果图层自动加进地图
geolibre.list_algorithms() # 发现 id 与参数
result = await geolibre.run_algorithm("buffer", {"layer": layer_id, "distance": 1000})
print(result["logs"], result["resultLayerIds"])
为了启动快,初始只加载基础运行时,其余按需装:
await geolibre.load_package("numpy")
import numpy as np
np.array([1, 2, 3]).mean()
await geolibre.load_package("geopandas") # 会连带装 shapely / pyproj / pandas
Pyodide 发行版里的包都能这样装。
19.4 控制台的限制
| 限制 | 原因 | 影响 |
|---|---|---|
| 跑在主线程 | 设计使然——Python 要能同步驱动实时地图 | 长任务会短暂卡住界面;避免紧循环,重活走 await run_algorithm(...) |
| 沙箱 | Pyodide 读不到本机文件系统,也够不到桌面版的本地工具 | 不能 open("C:/data.shp");网络访问受应用 CSP 约束 |
to_image() 返回字节 |
写不了文件 | 要落盘得自己走浏览器下载,或用桌面版脚本编辑器 |
注意:同一个 API 在 notebook 里是
Map对象的方法(见 19.12),在控制台里是geolibre模块函数。函数名一致,调用形态略有差别——控制台代码粘到 notebook 时通常只需把geolibre.换成m.。
19.5 Notebook 面板:两种后端,同一个 iframe
Processing → Jupyter Notebook 在地图旁打开一个可缩放、可折叠的 notebook 面板。同一个 <iframe> 背后,网页版与桌面版是不同的 Python 运行时:
| 构建 | 后端 | 特征 |
|---|---|---|
| 网页版 | JupyterLite | 整个 Jupyter UI 跑在浏览器里,Pyodide(WebAssembly)内核,无服务器;可用 %pip install geopandas 按需装包 |
| 桌面版 | 真 JupyterLab 服务器 | 应用从 uv 管理的 Python 环境里启动 jupyter lab(与 FastAPI sidecar 同一机制),监听 127.0.0.1:8766,token 认证,然后嵌入;完整 CPython 加原生地理栈(geopandas、rasterio、GDAL) |
| Mac App Store 版 | JupyterLite | 例外:它无法 spawn 服务器,所以像网页版一样嵌 JupyterLite 站点 |
提示:这与
geolibrePython 包正好相反:包是把整站应用嵌进 notebook,面板是把 notebook 嵌进应用。两者可以同时开,互不干扰。
19.6 从单元格驱动地图
19.6.1 客户端是预装的
一个轻量 geolibre 客户端已经预装进内核——网页版打包在 JupyterLite 的文件系统里,不需要 pip install;桌面版放在内核的 import 路径上。直接 import:
import geolibre
m = geolibre.connect() # 或 geolibre.Map()
m.fly_to(-122.4, 37.8, zoom=11) # 让左侧的实时地图飞过去
layer_id = m.add_geojson(
gdf,
name="My layer",
fillColor="#facc15",
strokeColor="#d97706",
) # 接受 GeoDataFrame、dict 或 JSON 字符串;桌面版返回 id
m.get_layer(layer_id)
m.list_layers()
m.fit_bounds([-123, 37, -122, 38])
m.set_basemap("https://…/style.json")
19.6.2 “即发即忘”与读回语义
大多数变更调用是即发即忘的:发出去就行,不等回复。桌面版上 add_geojson 走中继的”带关联的请求/应答”路径,会返回新图层 id。同一条路径还提供:
list_layers():每个实时图层一个 dict,含id、name、type、visible、opacity;get_layer(layer_id):返回匹配的图层,或抛ValueError。
拿到的 id 可以直接传给 set_visibility、set_opacity、set_style、remove_layer、zoom_to_layer。
19.6.3 多窗口与返回值陷阱
所有命令广播给连到同一个 Jupyter 服务器的每一个 GeoLibre 窗口。对 add_geojson、list_layers、get_layer,第一个关联回复返回给 notebook,后续回复被忽略。由于每个窗口维护自己的地图状态,在把返回的图层 id 串进后续读回调用时,只用一个已连接窗口。只有在你把多个窗口挂到同一个服务器时才会看到这个差异。
add_geojson 在两种情况下返回 None,且从不直接失败:
| 情况 | 行为 |
|---|---|
| 没有任何窗口收到命令(没连接,或中继本身不可达) | 与其它变更一致:图层经显示通道发出,并给一个 GeoLibreNotConnectedWarning |
窗口收到了但 5 秒内没应答(大的 FeatureCollection 会这样) |
给 GeoLibreTimeoutWarning;图层仍在添加中,所以刻意不重发(重发会加两次)——用 list_layers() 去找 |
读回调用在这两种情况下无值可返,于是抛异常:GeoLibreNotConnectedError / GeoLibreTimeoutError(都是 RuntimeError)。
注意:同步读回只在桌面版可用。JupyterLite 走浏览器的
postMessage;阻塞它的 Python 调用会同时阻塞必须投递结果的浏览器事件循环。规范客户端源码在backend/geolibre_server/notebook_client.py。
19.7 外部客户端与中继(relay)
19.7.1 用你自己的编辑器
桌面版的 JupyterLab 服务器是一个普通、token 认证的 Jupyter 服务器,所以你可以挂任何 Jupyter 客户端上去,继续用自己的编辑器,而上面那些地图命令照样能用:
- 打开一次 Notebook 面板(这一步才启动服务器);
- 点面板头部的链接按钮复制连接 URL;
- 粘进你的客户端(VS Code 里是 Jupyter: Specify Jupyter Server for Connections → Existing)。
19.7.2 为什么能这样:中继而不是显示通道
命令走 Jupyter 服务器上的中继,而不是依赖 notebook 被”怎样显示”:
| 组件 | 作用 |
|---|---|
backend/geolibre_server/geolibre_server/jupyter_relay.py |
一个 Jupyter Server 扩展(由 jupyter_server_config.py 启用),暴露 POST …/geolibre/relay/command、…/geolibre/relay/socket(WebSocket)、GET …/geolibre/relay/status |
| 环境变量注入 | 加载时把 GEOLIBRE_RELAY_URL 与 GEOLIBRE_RELAY_TOKEN 发布到服务器环境,内核继承——这就是 import geolibre 零配置找到地图的原因 |
useJupyterRelay |
应用整个生命周期订阅那个 socket,带退避重连,把每条命令交给与 embed 相同的 createScriptingHandlers |
useNotebookBridge |
仍然是嵌入式面板与网页版(JupyterLite)的 postMessage 路径——那里 notebook 页面确实是应用的 iframe |
POST 会回答”多少个应用窗口收到了命令”,所以断连会被报告而不是静默无事发生:
geolibre.is_connected() # False 表示没有 GeoLibre 窗口在监听
命令投递不了时客户端抛 GeoLibreNotConnectedWarning 并指向你自己的那一行。要把它升级为错误:
import warnings, geolibre
warnings.simplefilter("error", geolibre.GeoLibreNotConnectedWarning)
只有桌面版服务器有中继:网页版(JupyterLite)没有服务器,也就没有可挂的外部客户端。
19.8 主题同步与 JupyterLite 站点构建
19.8.1 主题
| 构建 | 行为 |
|---|---|
| 网页版 | notebook 实时跟随应用明暗主题(无需刷新)。JupyterLite 用 exposeAppInBrowser 构建(见 apps/geolibre-desktop/jupyterlite/jupyter-lite.json),把 app 对象放到 window.jupyterapp;iframe 同源,所以 useNotebookThemeSync 能伸进去,在主题变化时执行内置的 apputils:change-theme 命令 |
| 桌面版 | JupyterLab 服务器是另一个来源,这个跨源调用成为 no-op,notebook 保持自己的主题(要同步得写一个小 JupyterLab 扩展,属未来增强) |
19.8.2 构建网页版 JupyterLite 站点
网页版把自托管的 JupyterLite 站点嵌在 apps/geolibre-desktop/public/jupyterlite/ 下。它不进版本库(约 70 MB),由一个尽力而为的预构建步骤生成,需要 jupyter lite CLI:
pip install -r apps/geolibre-desktop/jupyterlite/requirements.txt
npm run build:jupyterlite
npm run dev 与 npm run build 都会自动跑它:
| 命令 | 行为 |
|---|---|
npm run dev |
首次经 predev 钩子构建一次(--if-missing),之后秒开 |
npm run build |
每次经 prebuild 重建,以便客户端/配置改动被带走 |
两者在 jupyter lite 未安装时跳过并给警告,所以纯 Node 构建仍能成功。但要清楚这样产出的后果:面板不会显示”未构建”提示,它的 iframe 请求 /jupyterlite/lab/index.html,而任何”未知路径返回 index.html”的东西——Tauri 的资源解析器,或带 SPA 回退(try_files $uri /index.html)的静态主机——会把第二份 GeoLibre交给面板(GeoLibre#1851、GeoLibre#1658)。装上依赖重构建即可。
有两种构建把”CLI 缺失”当致命而不是可跳过,因为它们要对外提供站点、不能降级:Mac App Store 构建,以及任何设置了 GEOLIBRE_JUPYTERLITE_REQUIRED=1 的构建(Docker 镜像就是)。
Docker 镜像会构建并提供该站点,并给 /jupyterlite/ 单独一段 nginx 配置:
- 自己的 CSP:JupyterLab 靠内联
<script>引导,应用策略禁止内联,套用应用策略会出现”HTML 加载了但永远不启动”; - 无 SPA 回退(
try_files $uri $uri/ =404),所以上面那个失败在这个镜像里表现为404,而不是重复的应用; - 给内容哈希资源长缓存,否则前缀匹配会把它们的长缓存策略拿走。
构建配置在 apps/geolibre-desktop/jupyterlite/(jupyter_lite_config.json、jupyter-lite.json、构建用 requirements.txt)。构建会把内核客户端与起始 Welcome.ipynb 从 backend/geolibre_server/ 下的规范副本(notebook_client.py、notebook_examples/Welcome.ipynb)拷进它的 files/。生成的目录被排除在 PWA 预缓存之外(见 apps/geolibre-desktop/vite.config.ts 的 pwaPlugin)。
19.8.3 桌面服务器
| 项 | 位置 / 值 |
|---|---|
| 后端 extra | backend/geolibre_server/pyproject.toml 里的 notebook(jupyterlab、jupyter-server),同步到它自己的 uv 项目环境,绝不打扰 sidecar 的环境 |
| 启动/停止 | apps/geolibre-desktop/src-tauri/src/lib.rs 的 start_jupyter_server / stop_jupyter_server Tauri 命令(与 sidecar 启动器同构),TS 包装在 apps/geolibre-desktop/src/lib/jupyter.ts |
| 嵌入授权 | backend/geolibre_server/jupyter_server_config.py 把 Content-Security-Policy: frame-ancestors 放宽到 Tauri webview / 回环来源;Tauri 的 CSP(tauri.conf.json)把回环来源加进 frame-src/child-src |
| 命令中继 | 同一个配置启用 geolibre_server.jupyter_relay 扩展;它的 WebSocket 只接受应用自己的来源,每个端点都要求服务器每次启动生成的 token,所以命令只能来自已经拥有该服务器内核执行权的东西 |
19.9 geolibre Python 包:安装与快速开始
19.9.1 安装
pip install geolibre
或从 conda-forge:
conda install -c conda-forge geolibre
可选 extras 提供对 GeoDataFrame 与本地矢量文件的 GeoPandas/Shapely 支持,以及对内存栅格的 xarray/rioxarray/rasterio/rio-tiler 支持:
pip install "geolibre[all]" # 两者都装
pip install "geolibre[vector]" # 只装 GeoPandas/Shapely
pip install "geolibre[raster]" # 只装 xarray/rioxarray/rasterio/rio-tiler
[all] 是两者的并集,所以现在会顺带装上 rasterio(以及 GDAL);只做矢量分析时用 [vector] 可以让环境保持轻量。
其它 extras:
| extra | 提供 |
|---|---|
geolibre[spaceborne] |
h5py 加 NumPy,用于 add_icesat2 / add_gedi 读 ICESat-2 与 GEDI 的 HDF5 granule |
geolibre[mcp] |
MCP SDK,跑 geolibre-mcp |
geolibre[pointcloud] |
点云预标注与整文件回写 |
geolibre[dash] |
from geolibre import DashMap,嵌进 Dash 应用 |
注意:
[all]只在 pip 上有效;用 conda 装完之后,要在同一环境里再pip install "geolibre[all]"。
19.9.2 最小可运行示例
from geolibre import Map
m = Map(center=(-100, 40), zoom=4)
m.add_geojson("https://example.com/data.geojson", name="Data")
m
最后那个裸露的 m 很关键:完整 GeoLibre UI 在单元格里渲染。继续加数据、继续驱动视图:
m.add_tile_layer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
name="OpenStreetMap",
attribution="(c) OpenStreetMap contributors",
)
m.add_cog("https://example.com/dem.tif", name="DEM", colormap="terrain")
m.add_basemap("dark")
m.set_center(-120, 47, zoom=8)
19.9.3 Earth Engine(可选)
需要 pip install earthengine-api 加凭据(ee.Authenticate() 一次,再指定 Google Cloud 项目):
import ee
ee.Authenticate() # 每台机器一次
ee.Initialize(project="your-google-cloud-project")
m.add_ee_layer(ee.Image("USGS/SRTMGL1_003"), {"min": 0, "max": 3000}, name="SRTM")
add_ee_layer 在内核里求值 Earth Engine 对象,把得到的瓦片 URL 作为栅格图层加上去(ImageCollection 会被镶嵌;矢量对象会被样式化成栅格瓦片——那种情况下 vis_params 用 ee.FeatureCollection.style() 的键,例如 color、fillColor、width、pointSize,不是影像键)。
该 URL 绑定到一个会过期的 Earth Engine map id,所以保存的工程重开时可能需要重新生成这一层。结果是普通栅格瓦片图层,不是应用自己的 Earth Engine 面板管理的那种实时图层。
19.10 常用图层方法速查
| 方法 | 说明 |
|---|---|
add_geojson(data, name=, **style) |
GeoJSON 字典、文件路径、URL、JSON 字符串或 GeoDataFrame |
add_gdf(gdf, name=, column=None, **style) |
GeoDataFrame,可给 column 直接做分级设色 |
add_csv(data, x="longitude", y="latitude", name=, **style) / add_xy_data(...) |
从 CSV 路径、URL、文本、DataFrame 或行映射加点 |
add_vector(data, name=, render_mode=, data_format=, source_layer=, **style) |
远程矢量(GeoParquet、FlatGeobuf、ZIP Shapefile、GeoJSON…)或本地文件(经 GeoPandas 读入并内联) |
add_geoparquet / add_flatgeobuf / add_shp / add_kml / add_gpkg |
各格式的便捷封装 |
add_polyline(polyline, name="Polyline", precision=5, unescape=False, **style) |
编码折线图层(精度 5 或 6) |
add_vector_tiles(url, name=, source_layers=, source_layer=, **style) |
从 TileJSON 端点加矢量瓦片 |
add_pmtiles(url, name=, tile_type=, source_layers=, **style) |
PMTiles 档案(矢量或栅格) |
add_tile_layer(url, name=, tile_size=, attribution=, bounds=, **style) |
栅格 XYZ 瓦片 |
add_wms(endpoint, layers, name=, styles=, image_format=, transparent=, tile_size=, version=, crs=, bounds=, **style) |
WMS(GetMap,按瓦片栅格);crs 默认 EPSG:3857 |
add_wmts(url, name=, tile_size=, bounds=, **style) |
WMTS 瓦片模板 |
add_wfs(endpoint, type_name, name=, version=, output_format=, srs_name=, max_features=, **style) |
WFS(GetFeature GeoJSON,抓取后内联) |
add_cog(url, name=, bands=, colormap=, rescale=, **style) |
COG(URL 或内核侧本地 GeoTIFF 路径) |
add_raster(source, name=, url=, bands=, colormap=, rescale=, array_args=, **style) |
COG/GeoTIFF URL 或路径,或 xarray 对象(需 geolibre[raster]) |
add_lidar(url, name=None, **style) |
LAS / LAZ / COPC / EPT 点云(COPC/EPT 按细节层次流式) |
add_3d_tiles(url=None, name=, ion_asset_id=, altitude_offset=, request_headers=, **style) |
3D Tiles tileset.json,或 Cesium Ion tileset(仅球面) |
add_czml / add_cesium_kml |
CZML 动态场景 / 原生球面 KML(仅球面) |
add_video(urls, coordinates, name=, **style) |
四角点定位的地理参考视频 |
add_basemap(basemap) / set_basemap(basemap) |
设置底图 |
关于本地栅格:add_raster / add_cog 也接受内核主机上的本地 GeoTIFF 路径,文件由随包启动的 localhost 服务器提供给应用读。这在本地 Jupyter 与 VS Code 里直接可用;在 Google Colab 里,因为内核代理不保留浏览器 COG 渲染所需的字节范围语义,GeoLibre 改在内核里把它渲染成 PNG XYZ 瓦片;JupyterHub 在装了 jupyter-server-proxy 时可以让 COG 走内核端口。只有静态服务器扩展的部署无法暴露内核文件,那里请传托管 URL。
警告:这个服务出的 URL 是会话级的,所以保存了本地栅格的工程以后再打开不会恢复它。要做可重开的工程,就传托管 URL。
19.10.1 内存栅格(xarray)
装了 geolibre[raster] 才能可视化内存里的 xarray 对象。空间维叫 lon/lat 或 longitude/latitude 时默认按 EPSG:4326;否则要显式给 CRS 与维度名。Dataset 的变量默认成为波段,除非你选一个:
m.add_raster(data_array, name="Temperature", colormap="viridis")
m.add_raster(
dataset,
name="Temperature",
array_args={"variable": "temperature", "isel": {"time": 0}},
)
支撑一个 xarray 图层的临时 COG 在组件关闭时删除,所以 xarray 图层与本地文件有同样的”会话级”限制。本地时浏览器直接读那个 COG;在 Colab 里 rio-tiler 把它渲染成普通 PNG XYZ 瓦片,以避开 Colab 不兼容的字节范围代理行为。用完调 m.close() 可以立刻删;否则在 Map 被垃圾回收或内核正常退出时删。被强杀的内核会把文件留在系统临时目录里。
19.11 双向同步与工程文件
19.11.1 同步是双向的
因为工程双向同步,你可以在 UI 里平移缩放,再从 Python 把实时状态读回来:
proj = m.to_project()
proj["mapView"]["center"] # 反映 UI 里的实时视图
[layer["name"] for layer in proj["layers"]]
保存与重开工程,与桌面版、网页版完全互通:
m.save_project("my-map.geolibre.json")
m2 = Map()
m2.load_project("my-map.geolibre.json")
m2
19.11.2 凭据自动脱敏
to_project()、save_project()、to_html() 会脱敏凭据:API key、token、带认证的请求头、密钥类环境变量(每一行没有标 "secret": false 的)、地理编码 key,以及 URL 里的凭据参数。所以任何你序列化、提交或分享的东西默认是安全的。
对”必须不重新输入凭据才能继续工作”的本地私密文件,给 keep_credentials=True:
m.save_project("private.geolibre.json", keep_credentials=True)
注意:
project这个 trait 本身不脱敏,所以与组件的实时双向同步仍能让需要认证的图层正常渲染。这是有意为之:”落盘/导出”要安全,”实时渲染”要能用。
19.12 Map 选项、交互查询、事件与处理
19.12.1 构造参数
Map(
center=(-100, 40), # [lng, lat]
zoom=4,
basemap="dark", # 底图名或 MapLibre style URL
renderer="maplibre", # maplibre / mapbox / cesium / arcgis
height="800px",
layout="embed", # "embed"(紧凑 UI)、"full"(桌面 UI)或 "maponly"
theme="light", # "light" 或 "dark"
server_proxy=False, # 远程服务器时设 True,见 19.15
)
19.12.2 交互查询与事件
除了加数据,组件还能查询实时应用并对它作出反应——与应用内 Python Console 是同一个表面。这些调用会往运行中的地图往返,所以地图必须先显示(在一个单元格里输出 m),然后在后续单元格里查询。
m.get_center() # 实时 [lng, lat],反映 UI 的平移缩放
m.get_bounds() # [west, south, east, north]
m.fly_to(-122.4, 37.8, zoom=10)
m.identify(-122.4, 37.8) # 某点上的要素,等价于点击地图
# Layer 对象:读写图层、读要素
for layer in m.layers:
layer.opacity = 0.6
features = m.layers[0].get_features() # GeoJSON Feature 列表
# 跑处理算法;结果图层加进地图
m.list_algorithms()
m.run_algorithm("buffer", {"layer": layer_id, "distance": 1000})
# 读回用户在图上选中或绘制的东西
m.get_selected_features() # 被点选的要素
m.get_drawn_features() # 用 Geo Editor 画的要素
m.user_rois # 画出的 ROI,GeoJSON FeatureCollection
m.get_drawn_features(as_gdf=True) # 同一个,GeoDataFrame(需 GeoPandas)
png = m.to_image() # PNG 字节(或 m.to_image("map.png"))
m.to_html("map.html") # 独立 HTML,内嵌实时工程
事件回调:
m.on_click(lambda e: print("clicked", e["lngLat"]))
m.on_selection_change(lambda e: print("selected", e))
m.on_layer_change(lambda e: print("layers", e["layerIds"]))
注意:交互查询会阻塞内核直到应用回复(通过
jupyter_ui_poll,自动安装)。慢调用请传timeout=,例如m.run_algorithm(..., timeout=300)。
19.12.3 处理与 Whitebox
| 方法 | 说明 |
|---|---|
list_algorithms() |
可用处理算法(id、parameters 等) |
run_algorithm(id, parameters=None, timeout=) |
跑算法,返回 {logs, resultLayerIds} |
run_model_builder(graph, timeout=) |
在已显示的 app 里跑一个序列化后的 Model Builder 图 |
list_whitebox_tools() / run_whitebox_tool(tool_id, parameters=None, timeout=) |
列出/在浏览器里用 WASM 跑内置 Whitebox 工具;后者返回 {logs, resultLayerIds} |
19.12.4 其它常用方法(选摘)
| 方法 | 说明 |
|---|---|
set_center(lng, lat, zoom=None) / set_zoom / set_bearing / set_pitch / fit_project_bounds |
不需要显示组件也能持久化相机改动 |
rename_layer / move_layer / duplicate_layer / show_layer / hide_layer |
按 id、名字或 Layer 句柄管理图层 |
layer_properties(layer) / column_values(layer, column) / describe() |
无浏览器往返地检查内联数据、汇总工程 |
set_layer_filter(layer, expression) |
用布尔 MapLibre 表达式隐藏不匹配要素;None 清除 |
set_labels(layer, field=None, expression=None, enabled=None, **options) |
按属性或文本表达式标注;选项含 placement、size、color、halo_color、min_zoom、max_zoom、anchor、offset_x/y、rotation、number_format 以及各类 *_expression |
set_identify(layer="all") |
让点击弹出识别框:一个图层、一组图层、所有可见图层("all")、或关闭(None) |
show_control(name, visible=True) / hide_control(name) |
显示/隐藏工具面板(bookmark、search、measure、minimap、print)或内置控件(navigation、fullscreen、compass、geolocate、globe、scale、attribution、logo) |
set_projection(projection) / projection |
"globe"(默认)或 "mercator",随工程保存 |
set_story_map(**settings) / add_story_chapter(...) |
故事地图与章节(见第 20 章) |
add_bookmark / remove_bookmark |
书签面板条目 |
set_plugin_state(plugin_id, state=None, position=, activate=True, allow_unknown=False, clear=False) |
保存插件的工程状态 |
set_layer_metadata(layer, merge=False, **fields) / clear_layer_metadata(layer) |
编目元数据(应用 Metadata 对话框、STAC Item 导出与 GeoParquet 导出元数据都用它);邮箱、ISO 8601 日期、链接 URL 会被校验 |
样式关键字参数(fillColor、strokeColor、strokeWidth、circleRadius 等)直接映射到 GeoLibre 的图层样式字段。
19.13 图例、色带与卷帘
# 应用的地图图例面板(Controls > Legend),由每个图层的样式生成
m.set_map_legend("Population", position="bottom-right")
# 内置土地覆盖图例,或自己的 {label: color} 字典
m.add_legend(builtin="nlcd")
m.add_legend(legend_dict={"Water": "#0000ff", "Land": "#00ff00"})
# 连续栅格的色带
m.add_colorbar(colormap="terrain", vmin=0, vmax=4000, label="Elevation", units="m")
# 用卷帘滑块对比两个图层(或一个图层与底图)
before = m.add_cog("https://example.com/before.tif", name="Before")
after = m.add_cog("https://example.com/after.tif", name="After")
m.split_map(before, after)
| 方法 | 要点 |
|---|---|
set_map_legend(title=None, position=, group_by_layer=, visible=, collapsed=) |
一个地图只有一个;再调即更新 |
add_legend(title=None, legend_dict=, labels=, colors=, builtin=, position=, shape=) |
builtin 可用 "nlcd"、"esa_worldcover" |
add_colorbar(colormap=, vmin=, vmax=, label=, units=, colors=, orientation=, position=) |
可用命名色带或自定义 colors |
add_colormap(colormap, vmin=, vmax=, label=, **kwargs) |
add_colorbar 的 leafmap 风格别名 |
split_map(left_layers=None, right_layers=None, orientation=, position=, control_position=) |
卷帘(分屏对比) |
提示:
set_map_legend与add_legend的区别是”自动 vs 手写”。前者从图层符号化生成,改样式它跟着变;后者是你给的一组固定条目,适合叠加说明(例如”图中灰线为行政区界”)。
19.14 标记、弹窗与尺寸
19.14.1 两种点渲染模式
add_marker 与 add_markers 把常见点符号参数做成命名参数,不用记底层样式键:
m.add_markers(points, color="#e11d48", radius=8, stroke_color="#ffffff", stroke_width=2)
m.add_markers(points, shape="pin", color="#e11d48", size=32)
m.add_markers(points, icon='<svg viewBox="0 0 24 24">…</svg>', size=28)
一个点图层有两种画法:
| 模式 | 触发 | 尺寸参数 |
|---|---|---|
| 圆(circle) | 默认 | radius(MapLibre 圆半径) |
| 标记精灵(sprite) | 给了 shape、size 或 icon |
size;shape 取 circle、square、triangle、diamond、star、cross、pin、custom(需 icon,原始 SVG 或 data URL) |
两种模式的参数不通用,混用会报错而不是静默无效:精灵图层会整体替换圆图层并自带白色光晕,所以 opacity、radius、stroke_color、stroke_width 是圆专用,给了 shape/size/icon 时会被拒绝。精灵的 color 必须是十六进制,因为精灵烘焙器只接受这种,否则会静默回退到默认蓝。
命名参数与底层样式键同时给出时(add_markers(pts, radius=8, circleRadius=20)),原始样式键胜——它是底层逃生口。
19.14.2 弹窗字段类型
不给任何配置时,识别工具开启后点要素会显示图层名与全部可见属性,且没有悬停提示。每个接受样式覆盖的 add_* 方法都接受 popup= 与 tooltip=(例外是 add_ee_layer,签名固定),set_popup / set_tooltip / clear_popup 改已有图层。
m.add_markers(
points,
popup={
"title": "name",
"fields": [
{"field": "name", "label": "Site"},
{"field": "photo", "kind": "image", "label": "Photo"},
{"field": "url", "kind": "link", "link_label": "Read more"},
{"field": "pop", "kind": "number", "thousands": True, "suffix": " people"},
{"field": "surveyed", "kind": "date", "date_format": "datetime"},
],
},
tooltip="name",
)
m.set_popup("Sites", ["name", "pop"], title="name") # 替换配置
m.set_tooltip("Sites", ["name"]) # 加悬停提示
m.set_popup("Sites", click=False) # 点击不弹
m.clear_popup("Sites") # 回到默认
字段的 kind 决定渲染方式:
kind |
渲染为 |
|---|---|
auto(默认) |
文本;内联 base64 栅格 data URL 会变成缩略图 |
text |
文本,应用 prefix/suffix |
number |
本地化数字;decimals、thousands、prefix、suffix |
date |
本地化日期;date_format 取 date、datetime、time、iso、year |
link |
http(s) 值变链接,标签用 link_label |
image |
http(s) 值或内联 base64 栅格 data URL 变缩略图 |
19.14.3 尺寸
弹窗默认 520 px 宽(带图片时 420 px,可拖宽),内部图片最高画到 min(50vh, 420px)。max_width 与 image_height 以 CSS 像素改这两个值:
m.set_popup("Sites", max_width=640, image_height=420, merge=True)
# 或作为任何 add_* 的顶层参数
m.add_markers(sites, popup=["name", "photo"], popup_max_width=640, popup_image_height=420)
| 参数 | 允许范围 |
|---|---|
max_width |
288–1200(视口仍会压它) |
image_height |
40–1200 |
缩略图保持长宽比,所以横版照片要同时把宽度放大才能用上多出来的高度。超出应用渲染范围的尺寸会报错,而不是在图上被静默截断;点图片仍会在灯箱里全尺寸打开。
19.14.4 两条容易踩的规则
- 属性里的原始 HTML 不会被当作标签渲染,Markdown 也不会。 从 GeoJSON 来的弹窗值是不可信内容,所以按文本写出。要图片和链接就用
kind="image"与kind="link";要拼一句话就用body_expression(MapLibre 表达式,JSON 文本),例如body_expression='["concat", ["get", "name"], " — ", ["get", "county"], " County"]'。唯一的例外是 KML 的description:它的已知标记会被清洗后渲染,所以转换来的 KML 能保留描述卡片。 - 悬停提示需要被标记的字段,而标记字段会收窄点击弹窗。 两者共用一张字段表:提示显示标了 hover 的条目,点击弹窗显示全部条目——但只有在字段表为空时才回退到”全部可见属性”。所以
m.add_markers(points, tooltip="name")会让点击弹窗只显示 name。要保留完整点击弹窗,就把点击要看的字段也列出来:
m.add_markers(points, popup=["name", "pop", "county"], tooltip="name")
一个永远显示不出东西的提示会报错而不是静默不出现——包括只标了一个 image 字段的情况(图片不会进悬停提示,因为它的值是 URL,会把提示变成一整串地址)。点击弹窗仍然显示图片。
19.15 marimo 与远程环境
19.15.1 marimo
marimo 能渲染 GeoLibre 的 anywidget,但它的浏览器可能够不到 GeoLibre 通常服务应用的随机 127.0.0.1 端口,症状是 iframe 显示 127.0.0.1 refused to connect。显示之前把组件指向托管版应用:
from geolibre import Map
m = Map(center=(-100, 40), zoom=4)
m._app_url = "https://web.geolibre.app/"
m.add_basemap("dark")
m.add_vector(
"https://data.source.coop/giswqs/opengeos/world_cities.geojson",
name="World cities",
)
m
这用的还是同一套工程同步桥,只是应用的位置变了。要在单元格返回 m 之前设 _app_url,这样 iframe 首次渲染就用托管 URL。
示例用 add_vector() 让托管版浏览器直接去抓远程 GeoJSON;add_geojson(url) 则是用 Python 下载并内联,遇到拒绝 Python HTTP 客户端的数据主机就会失败。
因为托管版访问不到内核临时 localhost 服务器暴露的文件,栅格与其它”浏览器直接加载”的源要用托管 URL。本地 GeoJSON、CSV 与矢量文件仍可用——它们在 Python 里读入并内联进工程。_app_url 目前是内部兼容手段,不是公开构造参数。
警告:组件会把同步后的工程(含任何内联的本地数据)通过
window.postMessage发给_app_url指定的来源。处理敏感数据时只使用可信的 app URL,或自托管 GeoLibre。
19.15.2 支持的环境
| 环境 | 行为 |
|---|---|
| 本地 Jupyter / VS Code | 应用直接从 localhost 提供 |
| Google Colab | 自动走 Colab 内置端口代理(google.colab.kernel.proxyPort) |
JupyterHub(含托管/共享 hub,运行时按 JUPYTERHUB_SERVICE_PREFIX 识别) |
前端探测两条同源路由,取活着的那条,所以主机只需其中一条 |
| 其它远程服务器(Binder、SSH/网络上的远程 JupyterLab) | 传 Map(server_proxy=True) 走同一套双路由远程路径 |
| marimo | 用 19.15.1 的托管 app URL;Jupyter 的代理与服务器扩展路由在 marimo 里不可用 |
JupyterHub 的两条路由:
- 随
geolibre一起装的 Jupyter Server 扩展,挂在{base_url}geolibre/app/(notebook 服务器自己的来源)。pip install geolibre时自动启用,不需要jupyter-server-proxy、不需要额外端口,所以能在禁止裸端口代理的受限 hub 上工作——但它从启动配置 drop-in 加载,要重启 Jupyter 服务器才注册。 jupyter-server-proxy的{base_url}proxy/{port}/,在任何装了它的地方都能到内核 localhost 的包,正在运行的服务器无需重启。
设 Map(server_proxy=False) 可以强制走直连 localhost。hub 上加载失败时,要么装 jupyter-server-proxy,要么确认扩展已启用(jupyter server extension list 看 geolibre;没有就跑 jupyter server extension enable geolibre)并重启 Jupyter 服务器。
19.15.3 URL 抓取的安全提醒
add_geojson(url)、add_csv(url) / add_xy_data(url)、add_wfs() 是从内核抓 URL 的,会跟随重定向,所以 notebook 能到达内核能到的任何主机。每一跳都会检查,解析到非公网地址(私有、回环、链路本地——包括 169.254.169.254 这类云元数据端点)的 URL 会被拒绝;响应上限 50 MB。瓦片与服务图层则由浏览器抓取,所以那些仍可指向本地服务器。不要在共享/多租户内核上加载不可信的 .geolibre.json 工程或 URL。
19.16 MCP 与源码构建
19.16.1 同一个包里的 MCP 服务器
pip install "geolibre[mcp]"
geolibre-mcp --root ~/maps
它用与本包相同的 builder 造工程,所以它写的东西在组件、桌面版、网页版里都能原样打开。工具清单与客户端配置见 第18章 AI 助手、MCP 与智能体技能。
19.16.2 从源码构建
包在仓库的 python/ 目录。两个内置前端独立从 monorepo 根构建:
npm install
npm run build:embed # 构建并暂存 Jupyter/web 应用
npm run build:mcp-app # 构建并暂存独立 MCP App
python -m build python # 构建 wheel 与 sdist
python -m twine upload python/dist/* # 上传 PyPI
pip install -e python # 开发用可编辑安装
打包钩子只跑那些暂存产物缺失的前端构建。要在一个完整 checkout 上刷新两者,设 GEOLIBRE_FORCE_JS_BUILD=1;预构建的 sdist 既不需要 Node 也不需要 monorepo 源码。
| 改了什么 | 怎么生效 |
|---|---|
| Python 代码 | 重启内核 |
| Jupyter 应用 | 重跑 npm run build:embed 并重启内核 |
| MCP 预览 | 跑 npm run build:mcp-app 并重启 MCP 客户端 |
19.17 渲染引擎与混排
m = Map(renderer="cesium", center=(-100, 40), zoom=4)
m.set_map_layout(1, 2, view_kinds=["cesium", "maplibre"], sync_view=True)
pane_id = m.project["secondaryMapViews"][0]["id"]
m.set_renderer("cesium", pane_id=pane_id)
assert m.get_renderer() == "cesium"
| 项 | 规则 |
|---|---|
| 渲染器 | "maplibre"、"mapbox"、"cesium"、"arcgis" |
| 目标 | 省略 pane_id 改主图 |
| 网格维度 | 1–4;view_kinds 每格一个渲染器,主视图在前 |
| 调整布局 | 既有 pane id、相机与可见性覆盖会保留 |
| 保存 | 正常保存工程即可保留 primaryRenderer 与各分屏的 viewKind |
| Dash | DashMap(renderer="cesium") 设同一初始渲染器;回调可经 project 改这些字段 |
19.18 R 包与 Shiny / Quarto / R Markdown
geolibre 的 R 包通过 htmlwidgets 把完整 GeoLibre 地图嵌进 RStudio、Quarto、R Markdown 与 Shiny。它创建标准的 .geolibre.json 工程,所以地图可以在 R 与 GeoLibre 网页版/桌面版之间来回。
19.18.1 安装
install.packages("geolibre") # CRAN
install.packages("pak")
pak::pak("opengeos/geolibre-r") # 开发版
19.18.2 快速开始
library(geolibre)
places <- list(
type = "FeatureCollection",
features = list(list(
type = "Feature",
properties = list(name = "Washington, DC"),
geometry = list(
type = "Point",
coordinates = c(-77.0369, 38.9072)
)
))
)
geolibre(map_only = TRUE) |>
add_geojson(
places,
name = "Places",
style = list(fillColor = "#dc2626", circleRadius = 8)
) |>
set_view(center = c(-77.0369, 38.9072), zoom = 10)
组件在 RStudio Viewer 与渲染出的 HTML 文档里自适应。它的地图加载托管版 GeoLibre 应用,所以查看需要联网——除非 app_url 指向自托管部署。
19.18.3 sf、栅格与工程文件
library(sf)
nc <- st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE)
geolibre() |>
add_sf(nc, name = "North Carolina counties")
add_sf() 接受 sf 对象并在把 GeoJSON 交给浏览器之前转换到 WGS 84。sf 包是可选的;Linux 上安装它可能需要 GDAL、GEOS、PROJ、UDUNITS 等系统库。
geolibre() |>
add_raster(
"https://example.org/visual.tif",
name = "Satellite image",
bands = c(1, 2, 3)
)
GeoLibre 在浏览器里直接读远程 COG;托管它的服务器必须支持 CORS 与 HTTP range 请求。
map <- geolibre() |> add_geojson(places)
save_project(map, "example.geolibre.json")
restored <- geolibre(load_project("example.geolibre.json"))
保存的工程也能在 GeoLibre Web 或 Desktop 打开。在 Shiny 应用里,浏览器侧的改动可从 input$<outputId>_project 读到。
19.18.4 Shiny
用 geolibreOutput() 与 renderGeolibre() 渲染地图;用 proxy 更新实时组件而不重建输出:
library(shiny)
library(geolibre)
ui <- fluidPage(
geolibreOutput("map", height = "80vh"),
actionButton("reset", "Reset view")
)
server <- function(input, output, session) {
map <- reactiveVal(geolibre(map_only = TRUE))
output$map <- renderGeolibre(map())
observeEvent(input$reset, {
next_map <- set_view(map(), center = c(0, 20), zoom = 2)
map(next_map)
update_geolibre(geolibre_proxy("map"), next_map)
})
}
shinyApp(ui, server)
19.18.5 自托管
给单个组件或整个 R 会话设自定义应用 URL:
geolibre(app_url = "https://gis.example.org/")
options(geolibre.app_url = "https://gis.example.org/")
部署必须同时提供 GeoLibre web 应用与它的 ?embed=1 工程桥。完整 API 见 https://r.geolibre.app/reference/。
19.19 架构:整站是怎么被嵌进一个单元格的
19.19.1 运行原理
wheel 打包了 GeoLibre 的 web 构建。import 时包启动一个小的 localhost 静态服务器来提供这个打包好的应用;组件把该应用渲染在一个 iframe 里,并通过 window.postMessage 交换工程。
- 从 Python 加数据 = 改写同步中的工程并推给应用;
- UI 里的编辑 = 以同一路径回流。
19.19.2 这个设计的四个后果
| 后果 | 解释 |
|---|---|
| 需要连到那个 localhost 端口 | 所以 Colab/JupyterHub 要走端口代理;marimo 干脆用托管 URL |
| 数据在两侧都可能存在 | 内联进工程的数据随工程走;只留引用的数据(本地文件路径、会话级栅格 URL)只在出图那台机器有效 |
| 版本必须匹配 | 组件的 JS 与 Python 来自同一个 wheel,混用不同版本的 app URL 会出现协议不匹配 |
| 同步是”整份工程” | 不是增量补丁;大工程在每次改动时都要序列化一次,这也是”别把上百 MB 内联进工程”的实际理由 |
提示:要”可分享、可重开”的成果,内联小矢量、托管大栅格(COG/PMTiles/GeoParquet),这与 第05章 数据添加与格式支持 里的建议一致。
19.20 常见问题
| 症状 | 原因 | 处理 |
|---|---|---|
marimo 里 iframe 显示 127.0.0.1 refused to connect |
marimo 的浏览器够不到随机回环端口 | m._app_url = "https://web.geolibre.app/"(见 19.15.1) |
add_geojson 返回 None,图层却出现了 |
没窗口连接,或 5 秒内没应答 | 看是否收到 GeoLibreNotConnectedWarning / GeoLibreTimeoutWarning;用 list_layers() 找图层 |
list_layers() 抛 GeoLibreTimeoutError |
大 FeatureCollection 来不及应答 | 加大超时或减小内联数据量;桌面版才有同步读回 |
| notebook 里改了地图,地图没反应 | 用 JupyterLite(网页版)时只有 postMessage 路径,且必须已显示 m |
先在一个单元格输出 m,再在后续单元格操作 |
| 外部客户端连上了但命令无效 | 中继未启动 | 先在应用里打开一次 Processing → Jupyter Notebook |
| Notebook 面板显示的是另一个 GeoLibre | JupyterLite 站点未构建,SPA 回退返回了 index.html | 装 apps/geolibre-desktop/jupyterlite/requirements.txt 后 npm run build:jupyterlite |
| 桌面版 Notebook 面板起不来 | notebook extra 缺失或端口占用 |
装后端 [notebook] extra;检查 8766 端口 |
| 本地栅格重开工程后不见了 | 服务 URL 是会话级 | 换托管 URL(COG) |
| 图片弹窗高度调了却没变大 | 缩略图保持长宽比 | 同时调大 popup_max_width |
| 悬停提示出现后点击弹窗只剩一个字段 | 字段表被 hover 标记收窄 | 用 popup=[...] 显式列出点击字段 |
| JupyterHub 上组件空白 | 双路由都不通 | 装 jupyter-server-proxy,或启用 geolibre 服务器扩展并重启 |
| 保存的工程里凭据丢了 | save_project 默认脱敏 |
私密本地文件用 keep_credentials=True |
19.21 本章小结
- 三个方向别混:Console 在应用里跑 Pyodide;Notebook 面板是”应用里的 notebook”(网页版 JupyterLite、桌面版真 JupyterLab);
geolibre包是”notebook 里的应用”。 - API 是同一套:
geolibre.的方法与Map的方法同名同义,控制台代码搬到 notebook 通常只需改前缀。 - 控制台的三条限制:主线程、沙箱、
to_image()返回字节;长任务走await run_algorithm(...)。 - Notebook 面板的关键差异是后端:JupyterLite 无服务器、包要按需装;桌面版有完整 CPython 与中继,因此才有同步读回与外部客户端。
- 中继才是”外部客户端能驱动地图”的原因:
jupyter_relay把GEOLIBRE_RELAY_URL/TOKEN注入内核环境,geolibre.is_connected()可自检。 - 返回值语义要背下来:变更即发即忘;
add_geojson在两种情况下返回None且不重发;读回调用直接抛异常。 - 凭据默认脱敏,
projecttrait 例外(为了实时渲染),落盘要保留凭据才用keep_credentials=True。 - 弹窗有两套尺寸与一条字段表规则:圆与精灵不能混用;标记 hover 字段会收窄点击弹窗。
- 远程环境的正确姿势:Colab 自动代理、JupyterHub 双路由、其它远程
server_proxy=True、marimo 用托管 URL。
19.22 实践练习
练习 1(控制台)
在 Python Console 里:读取当前中心与范围、飞到一个城市、用 identify 查询某点要素,并把结果打印成”图层名 + 属性键”。再用 await geolibre.load_package("geopandas") 装包,把 get_features() 的结果转成 GeoDataFrame 并报告行数。
练习 2(脚本编辑器)
写一个 20 行的 .py 脚本:加 OSM 底图、加一个远程 GeoJSON、按某个数值字段做分级设色、缩放到图层、导出 PNG 字节并说明为什么不落盘。保存、全跑与”只跑选中行”各试一次。
练习 3(Notebook 面板)
分别用网页版与桌面版打开 Notebook 面板,执行同一段 import geolibre; m = geolibre.connect() 代码。记录两者在 add_geojson 返回值、list_layers() 是否可用、能否用 VS Code 连上服务器这三点的差异,并解释原因。
练习 4(工程往返)
用 Map 建一个含三个图层(一个内联 GeoJSON、一个托管 COG、一个 XYZ 底图)的工程,save_project 后 load_project 到新组件。检查哪些图层恢复、哪些丢失,并说明”内联 vs 引用”的区别。
练习 5(弹窗与图标)
同一组点做两个图层:一个用 radius 画圆、一个用 shape="pin" 画精灵。给两个图层都配 popup(含 image 与 link 字段)与 tooltip。故意混用一次圆与精灵参数,记录报错信息;再验证”只给 tooltip 会收窄点击弹窗”这条规则。
练习 6(R 与 Shiny)
用 R 包做一个含 sf 图层的地图,save_project 后在 GeoLibre 网页版打开。再写一个最小 Shiny 应用,用 geolibre_proxy 在按钮点击时改视图,并读出 input$<outputId>_project 里的中心坐标。