第11章 - STAC Browser 与目录浏览
前四章把数据、目录、服务都搭好了,但用户看到的第一屏仍然是一个 JSON 文件。STAC 的价值只有在“人能看懂、能点开、能看到图”之后才兑现。STAC Browser 就是这一层:一个纯前端应用,读 STAC API 或静态目录,渲染成可浏览的目录树、地图、资产列表与预览。
它不是一个需要长期维护的服务——构建产物是静态文件,可以扔到任何 Web 服务器、对象存储或 CDN 上。真正需要决策的只有三件事:指向哪个目录、用哪种路由模式、部署在哪个路径。
11.1 它支持什么
当前稳定版是 5.2.0,支持 STAC 0.6.0 到 1.1.0 之间的所有版本。这个跨度很重要:老目录(0.9、1.0.0-rc)和新目录(1.1.0)可以混着浏览,不需要先做数据迁移。
它同时支持两种数据源:
| 数据源 | 识别方式 | 行为 |
|---|---|---|
| 静态目录 | 根 JSON 的 type 是 Catalog 或 Collection,且有 child/item 链接 | 逐层拉 JSON |
| STAC API | 根文档有 conformsTo 与 data/search 链接 | 用 /collections 与 /search |
对 API,它还能额外显示集合列表、全局检索框、分页结果。对静态目录,只能逐层浏览,没有检索。
浏览器不做的事:不缓存瓦片、不做服务端渲染、不做鉴权代理。需要登录的 API 要自己在前面加一层。
11.2 三种部署方式
11.2.1 源码构建
git clone https://github.com/radiantearth/stac-browser.git
cd stac-browser
npm install
# 指向一个 STAC API
SB_catalogUrl="https://earth-search.aws.element84.com/v1/" npm start
# 开发服务器在 http://localhost:8080
构建产物:
SB_catalogUrl="https://api.example.com/stac" \
SB_historyMode="hash" \
SB_pathPrefix="/browser/" \
npm run build
ls dist/
# index.html assets/ config.js runtime-config.js
注意 historyMode 与 pathPrefix 是 build-only 选项——它们在构建时被编译进产物,运行时改不了。这一点与 catalogUrl 不同,后者可以在运行时用 SB_* 环境变量覆盖。
11.2.2 Docker
docker run -p 8080:8080 \
-e SB_catalogUrl="https://api.example.com/stac" \
-e SB_catalogTitle="示例影像目录" \
ghcr.io/radiantearth/stac-browser:latest
镜像内部是多阶段构建:第一阶段 npm run build 生成 dist,第二阶段 nginx 提供静态文件。启动时 entrypoint 会把 SB_pathPrefix 渲染进 nginx 配置,并生成 runtime-config.js。
所以 Docker 方式下 pathPrefix 可以在启动时给:
docker run -p 8080:8080 \
-e SB_catalogUrl="https://api.example.com/stac" \
-e SB_pathPrefix="/browser/" \
ghcr.io/radiantearth/stac-browser:latest
# 访问 http://localhost:8080/browser/
historyMode 仍然要在构建时定,除非用自定义镜像加 –build-arg historyMode=hash。
11.2.3 静态托管
构建出的 dist 是纯静态文件,可以直接放对象存储或 CDN:
aws s3 sync ./dist s3://my-bucket/browser \
--delete \
--cache-control "public, max-age=3600"
# index.html 不缓存,避免发布后用户拿到旧壳
aws s3 cp ./dist/index.html s3://my-bucket/browser/index.html \
--cache-control "no-cache" \
--content-type "text/html"
缓存策略值得单独说:assets/ 下的文件名带哈希,可以设一年;index.html 与 runtime-config.js 必须短缓存,否则改了 catalogUrl 用户要等很久才生效。
11.3 配置项
11.3.1 catalogUrl
最重要的一项。规则很硬:
提供的 URL 必须与响应体里 self 链接的 href 完全一致。
也就是说,如果根目录的 JSON 里写的是 “self”: “https://cdn.example.com/stac/catalog.json”,那 catalogUrl 就必须是 https://cdn.example.com/stac/catalog.json,不能是 https://cdn.example.com/stac/ 或者带查询串的版本。不一致的表现是首页空白或报“无法加载目录”。
这条约束直接决定了第 7 章的 catalog_type 选择:用 ABSOLUTE_PUBLISHED 时 self 链接是写死的绝对 URL,与 catalogUrl 天然一致;用 SELF_CONTAINED 时根本没有 self 链接,Browser 会退化成“用传入的 URL 当基准”,行为不稳定。
11.3.2 其他常用项
| 配置 | 环境变量 | 默认 | 作用 |
|---|---|---|---|
| catalogUrl | SB_catalogUrl | 空 | 根目录 URL;为空时显示 STAC Index 选择页 |
| catalogTitle | SB_catalogTitle | 从根读 | 标题兜底 |
| catalogImage | SB_catalogImage | 无 | 头部 logo |
| historyMode | SB_historyMode | history | 路由模式,build-only |
| pathPrefix | SB_pathPrefix | / | 子路径部署,build-only |
| apiCatalogPriority | SB_apiCatalogPriority | null | collections 或 childs |
| allowExternalAccess | SB_allowExternalAccess | false | 是否允许浏览外部数据 |
| searchResultsPerPage | SB_searchResultsPerPage | 12 | 检索每页条数 |
| displayGeoTiffByDefault | SB_displayGeoTiffByDefault | false | 是否用 GeoTIFF 渲染器显示 COG |
| preprocessSTAC | 仅配置文件 | 无 | 加载前改写 STAC 对象 |
apiCatalogPriority 解决一个具体问题:STAC API 既可能通过 /collections 暴露集合,也可能通过 child 链接暴露。两者都开时 Browser 会去重,但去重不一定准。如果你的 API 只有 /collections 没有 child 链接,设成 collections 更干净。
11.3.3 配置文件
环境变量适合少量覆盖,复杂配置(比如自定义字段渲染)要写 config.js:
// config.js
export default {
catalogUrl: "https://api.example.com/stac",
catalogTitle: "示例影像目录",
historyMode: "hash",
pathPrefix: "/browser/",
apiCatalogPriority: "collections",
searchResultsPerPage: 24,
displayGeoTiffByDefault: true,
footerLinks: [
{ label: "使用条款", url: "https://example.com/terms" },
{ label: "联系我们", url: "https://example.com/contact" },
],
};
也可以用一个外部配置文件覆盖,不重新构建:
SB_CONFIG=./config.local.mjs npm start
11.3.4 私有查询参数
需要传 API Key 时,Browser 支持以 ~ 开头的查询参数:它们会被存进内存、从地址栏移除,然后附加到后续所有 STAC 请求上。
https://example.com/browser/?~API_KEY=123
如果不想让托管 Browser 的服务器看到这个参数(比如走 history 模式时参数会进访问日志),可以放到 hash 里:
https://example.com/browser/#?~API_KEY=123
这个机制是实验性的,且只对同域或 allowedDomains 里列出的域生效。
11.4 与静态目录的配合
11.4.1 目录结构要求
Browser 从根 JSON 开始,靠 child 与 item 链接往下走。它对目录结构没有要求,但对链接有:
- 根必须有 self(否则 catalogUrl 校验不过);
- 每个子节点最好有 parent,用于面包屑;
- Item 的 href 要能直接取到。
第 7 章的 pystac 生成流程默认满足这些条件。检查方法:
# 根节点应该有 self
curl -s https://cdn.example.com/stac/catalog.json | python -m json.tool | head -20
# 子节点应该有 parent
curl -s https://cdn.example.com/stac/demo/collection.json \
| python -c "import json,sys; d=json.load(sys.stdin); print([l['rel'] for l in d['links']])"
# ['self', 'root', 'parent', 'item', 'item', ...]
11.4.2 同源部署避开 CORS
静态托管(尤其是 GitHub Pages、S3 静态网站)通常不能自定义响应头,也就配不了 CORS。此时把 Browser 与 STAC 目录放在同一个域名下就能绕开:
https://example.github.io/imagery/
├── index.html ← STAC Browser 的 dist
├── assets/
├── config.js
└── stac/
├── catalog.json
├── demo/collection.json
└── demo/item-001/item-001.json
catalogUrl 写 https://example.github.io/imagery/stac/catalog.json,同源,不需要 CORS。
11.4.3 GitHub Pages 的两个限制
不支持 URL 重写。 history 模式下刷新 /browser/collections/demo 会 404,因为 Pages 只认实际存在的文件。必须用 hash 模式:
SB_historyMode="hash" \
SB_catalogUrl="https://example.github.io/imagery/stac/catalog.json" \
npm run build
不支持自定义响应头。 所以 Content-Type 由文件扩展名决定,.json 会被当成 application/json——这对 Browser 没问题,但某些严格校验 STAC 媒体类型的客户端可能不认。
11.4.4 子路径部署
部署在 /browser/ 而不是根路径时,pathPrefix 必须设:
SB_pathPrefix="/browser/" npm run build
漏设的表现是页面能打开但所有 assets 404——因为 index.html 里引用的是 /assets/xxx 而不是 /browser/assets/xxx。
Docker 部署时还有一层:nginx 配置里的 location 也要跟着改。镜像的 entrypoint 会自动处理,前提是设了 SB_pathPrefix。
11.5 与 API 的配合
11.5.1 指向 stac-fastapi
SB_catalogUrl="https://api.example.com/stac" npm run build
Browser 会先请求根文档,发现 conformsTo 里有 item-search,就启用检索框。检索走 POST /search,带 bbox、datetime、collections 与分页。
一个常见问题:Browser 期望根文档的 self 链接与 catalogUrl 一致。stac-fastapi 生成的 self 链接来自请求的 Host 头,反向代理没传 X-Forwarded-Host 时会变成内部地址。
location /stac/ {
proxy_pass http://api:8082/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
stac-fastapi 的 ProxyHeaderMiddleware 会读这些头来构造链接。漏了就会看到 self 指向 http://api:8082。
11.5.2 事务扩展的界面
5.1 起 Browser 支持 STAC API 的 Transactions 扩展,可以在界面上新建、编辑、删除 Item。默认关闭,要在配置里显式打开:
export default {
catalogUrl: "https://api.example.com/stac",
transactions: true,
transactionsRequireLogin: true,
};
打开之前想清楚:Browser 是纯前端,所有请求都从浏览器发出。要鉴权就得让 API 支持 CORS 携带凭据,或者用前面说的私有查询参数传 token。生产环境建议 transactionsRequireLogin 保持 true。
11.5.3 缩略图与预览
Browser 会优先展示带 thumbnail 角色的 asset,其次是有 visual 角色的 COG(如果 displayGeoTiffByDefault 为 true)。
COG 的预览有两条路径:
- 直接读 COG 的前几个块渲染缩略图(依赖服务端 CORS 与 Range,见第 6 章);
- 走 TiTiler 的预览端点。
第 1 条对静态托管友好但慢;第 2 条需要额外部署 TiTiler。Browser 本身不代理请求,它只是发起请求。
11.6 自定义
11.6.1 字段渲染
自定义扩展的字段默认显示为原始 JSON。想让它好看,用 stac-fields 的注册机制:
// fields.config.js
import { Registry } from "@radiantearth/stac-fields";
Registry.addExtension("radiant", "Radiant Earth");
Registry.addMetadataField("radiant:public_access", {
label: "数据访问",
formatter: (value) => (value ? "公开" : "受限"),
});
这比改源码稳——升级 Browser 时配置不会丢。
11.6.2 加载前改写
preprocessSTAC 可以在渲染前修改 STAC 对象,适合补字段、改链接:
export default {
catalogUrl: "https://api.example.com/stac",
preprocessSTAC: (stac) => {
// 给所有 Item 补一个默认缩略图
if (stac.type === "Feature" && !stac.assets?.thumbnail) {
stac.assets = stac.assets || {};
stac.assets.thumbnail = {
href: "https://cdn.example.com/placeholder.png",
type: "image/png",
roles: ["thumbnail"],
};
}
return stac;
},
};
这个钩子只在配置文件里可用,不能通过环境变量传(函数没法序列化)。
11.7 排错
| 现象 | 原因 | 修法 |
|---|---|---|
| 首页空白,控制台报 catalogUrl 不匹配 | catalogUrl 与 self 链接不一致 | 逐字符比对,注意末尾斜杠 |
| 刷新页面 404 | history 模式 + 服务器不支持重写 | 改 hash 模式 |
| 所有 assets 404 | pathPrefix 没设 | 重新构建并设 SB_pathPrefix |
| 地图不出图,控制台 CORS 错误 | COG 所在域没配 CORS | 见第 6 章的 CORS 三件套 |
| 检索框不出现 | 根文档没有 item-search conformance | 检查 conformsTo |
| 缩略图 404 | Item 的 thumbnail href 指向不存在的文件 | 生成缩略图或删掉该 asset |
| 发布后改了 catalogUrl 不生效 | index.html 或 runtime-config.js 被长缓存 | 给这两个文件设 no-cache |
| 中文标题乱码 | 服务器没返回 charset | 配 Content-Type: text/html; charset=utf-8 |
第一条最费时间,因为“看起来一样”的 URL 可能差一个斜杠。规范 1.1.0 专门澄清了末尾斜杠有语义,Browser 是严格按字符串比对的。
11.8 生产建议
Browser 与目录同域部署。 省掉 CORS 配置,也避免混合内容问题。子路径 /browser/ 与 /stac/ 分开即可。
构建产物进版本控制或制品库。 dist 是构建产物,但配置(catalogUrl、historyMode)决定了它的行为。把构建命令与配置一起记录,比只存 dist 更可靠。
不要给 Browser 开写权限。 transactions 打开意味着任何能访问页面的人都能改数据。确实需要时,前面加一层带鉴权的反向代理,并且只对特定路径放行。
用 STAC Index 做发现。 目录稳定后提交到 stacindex.org,Browser 的默认首页就是读它。这是让别人找到你的目录的最低成本方式。
监控首屏加载。 Browser 首屏要拉根文档 + 集合列表 + 缩略图,任何一环慢都会让首屏超过 3 秒。根文档建议放在 CDN 边缘,缩略图控制在 50 KB 以内。
11.9 本章小结
| 要点 | 结论 |
|---|---|
| 版本 | 5.2.0,支持 STAC 0.6.0–1.1.0 |
| 数据源 | 静态目录(逐层拉)与 STAC API(/collections + /search) |
| 关键约束 | catalogUrl 必须与根 self 链接完全一致 |
| 路由 | history 需要 URL 重写;S3 与 GitHub Pages 必须用 hash |
| 路径 | 子路径部署必须设 pathPrefix,且是 build-only |
| 部署 | 静态文件即可,与目录同域最省事 |
到这里,从影像上云、编目、建服务到浏览的链路已经闭环。后面的章节进入渲染层:TiTiler 如何把 COG 变成瓦片,以及 STAC 元数据如何驱动动态渲染。