Skip to content

Releases: JungleZy/TerraForge

v0.3.3

Choose a tag to compare

@github-actions github-actions released this 12 Aug 02:35

v0.3.3 —— 一个任务不再能拖垮其他任务;缺瓦片不再静默;区域可以导入、成果可以导出成 MBTiles

先说结论:这一版补的是「多任务同时跑」和「结果不完整」两个长期靠运气的地方 —— 现在四条管线共用一份全局并发与磁盘预算,缺瓦片会被分类记账并在界面上要你拍板,区域可以直接导入 GeoJSON / KML / KMZ / Shapefile,瓦片成果可以导出成单个 .mbtiles 文件。已下载的数据、已切好的地形、配置与历史全部照旧,不必重做任何东西。 升级后首次启动会做一次自动迁移(改缓存目录名、改一列历史状态值),不需要你做任何事,但装回旧版会让缓存全部落空——这一条在下面「升级须知」里说清楚。

一个任务不再能把其它任务拖垮

  • 以前四条管线(瓦片 / 高程 / 地形 / 等高线)各管各的并发:配置里的「并发下载数」是每个任务的数字,同时跑两个任务就是两倍连接、两倍内存、两倍磁盘写入,没有任何上限。现在多了一层全局预算,四条管线一起排队。
  • 配置页新增五项:同时运行的任务数(出厂 2)、全局网络连接上限(64)、CPU 工作线程(0 = 按机器 CPU 数自己算)、GDAL 并发槽位(2)、缓存总容量上限(0 = 不限,与旧行为一致)。原来的「并发下载数」照旧存在,但它现在会被全局上限压住 —— 两个任务各填 50,实际不会开出 100 条连接。
  • 出厂值是保守的。机器好、网络快,把「同时运行的任务数」和「全局网络连接上限」调高即可;这些是可配置的默认值,不是硬限制。

磁盘:先估算,再开工,跑到一半空间不够也能安全停下

  • 建任务时会先估这一单要占多少(瓦片、临时文件、拼接产物分别算),减去磁盘预留(出厂 2 GB)再乘一个安全系数(出厂 1.15),不够就直接拦住并告诉你差多少 —— 而不是跑到 80% 时写失败。
  • 估算不再用固定的「平均一张瓦片多少 KB」,而是拿你磁盘上已有的瓦片现量。同类工具在这一点上出过 17 倍的偏差,那个坑的成因就是固定均值。
  • 临时/工作目录现在跟着输出盘走,不再固定用系统临时目录。跨盘意味着每一次拼接都要把数据完整搬一遍。
  • 觉得估算过于保守?配置页里有开关可以整个关掉(磁盘预算检查)。

缺瓦片不再静默:分类记账 + 由你拍板

  • 以前一张瓦片取不到就只是「失败」一个词。现在分五类记账:成功 / 该处本来就没有数据 / 可重试的失败 / 永久失败 / 缓存写入失败
  • 由此多出三个任务状态:补漏中待决策已完成(有缺口)。规则只有一条,值得记住:
    • 只有「该处本来就没有数据」这一类缺口 —— 比如你框到了大洋深处、或者框出了高程数据集的覆盖范围 —— 任务自动完成,状态是「已完成(有缺口)」,不打扰你。
    • 出现任何一类真失败,任务停在待决策不会给你一张残缺的拼接图。你可以点「补漏」只重跑那些格子,也可以点「接受缺口」让它按现状出图 —— 后者出的成果和历史记录会永久带缺块标记,不会伪装成完整成品。
  • 补漏可以反复点,重复点不会重复下载。
  • 任务详情里能看到缺口的分类计数和最多 20 个样例格子(层级 / 行列号 / 失败原因)。

每个任务一份自己的日志,出问题可以直接导出

  • 以前排障只有一份全局日志,多任务并行时几条管线的输出交织在一起。现在每个任务写一份 logs/tasks/<管线>_<任务号>.log,任务详情里能直接看,也能下载一份诊断文本
  • 日志里的密码和 Token 在落盘前就被抹掉了 —— 这份文件设计上就是可以直接贴进 issue 的。
  • 出厂单份上限 4 MB、保留 14 天,超期的在启动时自动清掉。不想要就在配置里关掉。

删任务不再误伤别的任务的缓存

  • 以前删任务时清缓存是按样式整片清的:两个任务框的范围有重叠,删掉其中一个,另一个下次恢复要重新下载重叠的那部分。现在只清这个任务独占的那些格子,别人还用得着的一张不动。
  • 配置页的缓存管理多了两项:按来源命名空间看占用(见下),以及一键清理「没有任何任务认领」的孤儿缓存。

区域输入:可以导入文件了,也能按地名搜

  • 除了在地图上拉框,现在可以直接导入 GeoJSON / KML / KMZ / Shapefile(.zip),支持多边形、多部件与孔洞(挖空的部分不会被下载)。
  • 跨 180° 经线的区域从「报错拒绝」改成「自动拆成两段」。以前这种范围要么被拦下、要么被算成绕地球一圈。
  • 地名搜索需要你自己填一个服务地址才会出现 —— 出厂是空的,程序不内置任何地名服务,理由见下面「这一版刻意没做什么」。

瓦片成果可以打包成单个 .mbtiles

  • 一个 .mbtiles 就是一个文件,里面装着整棵瓦片金字塔 —— 拷给别人、丢进 QGIS / ArcGIS,不用再搬几十万个小文件。影像和等高线用的是同一套写入端。
  • 两个入口,随你用哪个:建任务时在「输出格式」旁边勾上**「同时导出 MBTiles」,跑完自动打包;或者对已经跑完的任务**事后点一次导出。
  • 这是「多给一份产物」,不是「换一种输出格式」。 输出格式的三个选项(只要瓦片 / 只要拼接图 / 两个都要)一个没变,勾了 MBTiles 也不会删掉原来的瓦片目录 —— 那个目录正是打包的原料,也是程序里预览用的那份。同一个任务可以同时留着瓦片目录、拼接图和 .mbtiles
  • 打包出来的库可以直接在本程序里预览(/mbtiles/...)。
  • 打包失败不会把一个已经下载完的任务判成失败 —— 瓦片已经在盘上了,重新点一次导出即可。
  • 有缺口的任务打包时会带缺块标记,不会假装完整。

图源向导

  • 配置页里粘一条瓦片服务地址,程序会替你认出模板形态({z}/{x}/{y}{s} 子域、TMS 行号方向、查询参数),并指出可疑的地方,而不是让你手工拼一遍格式再靠试错。

升级须知(五条,都不需要你动手,但请读一下)

  1. 缓存目录改名了。cache/<样式>/… 改成 cache/<样式>-<源指纹>/…,例如 cache/s/…cache/s-3f8a1c2d/…。首次启动自动改名,是重命名不是重新下载,一张瓦片都不会丢。改这个是因为原来的目录名只认样式不认服务器:同一个「卫星」样式换了服务器列表之后,新旧两家的瓦片会混进同一个成品,而且事后无从分辨。注意:改名之后如果你把程序装回旧版本,旧版会按老目录名去找,结果是缓存全部落空、全部重新下载(数据不会损坏,只是白下一遍)。
  2. 历史任务里的瓦片状态值改了写法。 数据库里旧的 failed 会被改写成 retryable_failure(可重试的失败)—— 这是保守的读法,意味着这些格子仍然可以用「补漏」重跑。一次性迁移,自动完成。
  3. 多了三个任务状态。 如果你有自己写的脚本在读任务接口,注意 status 现在还可能是 retrying / pending_decision / completed_with_gaps。把它们当成「未结束 / 未结束 / 已结束」处理即可。
  4. 只影响直接调 API 的人:删除本地地形任务的 delete_files 默认值从「删」改成「不删」。 DELETE /api/terrain/local/tasks/<id> 此前不带参数时默认连磁盘产物一起删,而另外三条管线的同名接口默认都是保留。四条现在统一为默认保留,要删就显式带 ?delete_files=true(这个写法一直有效,没有变)。界面上的删除按钮不受影响 —— 它一直是显式带着这个参数发的,你在界面上看到和勾选的行为与上一版完全一样。会受影响的只有自己写脚本、依赖了那个隐式「默认删」的人:同样的请求现在会把文件留在盘上。改的只有 HTTP 这一层LocalTerrainTaskManager.delete_task 自己的签名默认值仍然是 True,从代码里直接调它的地方行为一个字没变。
  5. 只影响直接调 API 的人:POST /api/dem/tasks 多了一种写区域的方式。 老的 north/south/east/west 四至照旧可用,没有废弃;新增的 region 字段(一个 RegionSpec)与它二选一,给了 region 就不必再给四至。跨 180° 经线的 DEM 任务只能用 region —— 裸四至那条路对 east <= west 一律回 400,那道校验是有意保留的(它挡的是「填反了四至」这个高频错误)。另外 DELETE /api/tasks/<id> 新增可选的 ?clear_cache=1,带上它会顺带清掉只被这个任务引用的共享缓存,响应里多出 cache_removed_bytes / cache_removed_files / cache_deferred 三个字段;不带就是旧行为。

这一版刻意没做什么(写出来是为了让你不用去找)

  • 不内置地名 / 行政区搜索的数据源。 功能本身做好了,但 地名服务地址 出厂是空的,你不填就不会在界面上出现。原因有两条:公共地名服务(如 OSM Nominatim)都有明确的批量使用政策,程序替你内置一个等于替你接受了那份政策;中国境内的行政区数据还叠着测绘资质的要求。这两件都不是工程能替用户决定的事。要用就自己填一个 Nominatim 兼容的地址,程序会把它当作不可信的外部 URL 做安全校验后再请求。(依据:docs/notes/external-projects-takeaways.md §11「不内置未经政策审核的公共或商业批量下载源」,以及 §13 末尾把「行政区与地名搜索的数据源与测绘合规」列为仍待产品层决定。)
  • 没有安装包,仍然是解压即用。 不做 MSI / DMG / DEB,也没有自动更新(§13-6 的决定)。自动更新的前置条件是签名清单、资产哈希与代码签名,一样都还不具备 —— 与其做一个「从某个地址下载 exe 然后直接执行」的更新器,不如不做。
  • 没有任何遥测、埋点或使用统计。 一行都没有,将来也不打算加(§11)。程序除了你自己配置的图源、高程源与代理之外不连接任何服务器。
  • 可选数据插件(Wayback / MVT / OSM 矢量 / 3D Tiles)一行未写。 那是下一阶段的事,插件契约都还没定稿。

给排障和构建的人

  • 新增合同层 src/contracts/region / region_tiles / source / outcome / artifact / reservation)。region_tiles.py 是全仓唯一一处经纬度↔瓦片换算 —— 估算、下载、拼接、MBTiles 四至与界面预览共用它,「预览说的张数」和「实际切的张数」因此不会再各算各的。
  • 新增服务:resource_scheduler / disk_budget / task_logging / cache_exclusive / source_registry / mbtiles / artifact_export / region_import / url_guard / source_wizard / geocoding / artifact_store。架构说明、配置键含义与状态机规则都写进了 CLAUDE.md
  • 数据库 user_version 推到 6:5 = task_tiles.statusfailedretryable_failure,6 = 缓存目录改名。两条都是幂等的一次性迁移。新增 artifacts 表,tasks 新增 export_mbtiles 列(与 output_format 正交,见下条)。
  • MBTiles 是「多一份产物」而不是第四种 output_format OutputFormat 一个值没加;打包由独立的布尔列 tasks.export_mbtiles 驱动,打包器只有一处 src/services/artifact_export.pyexport_task_mbtiles 幂等,按管线查一张布局表,同时覆盖影像与等高线)。做成第四种格式会删掉打包的原料目录 —— 而那个目录正是 /tiles/<id>/ 预览用的那份。
  • MBTiles 对外只有两条路由:读走 /mbtiles/<管线>/<任务号>/<z>/<x>/<y>.<扩展名>(影像、等高线与将来的矢量共用,刻意不按数据类型各开一条,它同时登记进 5001 瓦片端口的路径白名单,前后端两份名单由一条相等性断言钉死);写走 POST /api/export/<管线>/<任务号>,管线名对着 contracts.artifact.PIPELINES 校验。
  • 日志尾随走 REST 轮询,不走 Socket.IO。 本应用没有 room / namespace,任何 emit 都会发给所有连着的客户端,逐行日志事件等于把一个任务的日志广播给所有人。新增的 socket 事件只有一个:task_gap_decision
  • CI 三项新门禁:pytest-cov 覆盖率地板 --cov-fail-under=55棘轮起步值,只准往上调)、tag 与 Config.APP_VERSION 的一致性检查、发布产物的 SHA-256 清单(随包挂在 Release 上,生成后立刻自校验)。
  • 高程/地形的规模预告修了两处:速度档下超高分辨率源的最大层级此前少报一级(层级偏移被加在上限截断之后,而实际切片是在之后才截断的);跨 180° 经线的栅格张数此前少报约六成。/api/raster/inspect 因此在每个文件与汇总节点上都多返回一个 recommended_maxzoom_by_quality(三档 → 实际会切到的层级),recommended_maxzoom 本身没变。

验证

  • 本节没有全量测试的通过数 —— 发版时由跑完整套件的那一步填。这里只写已经机器核对过的事实,不给一个编出来的数字。
  • 已机器核对:src/routes/ 里的 71 条路由与本仓 README「API 端点」一节双向逐条对上(多一条、少一条都会让 tests/test_docs_claims.py 变红);README 项目结构树里列的每一个仓内路径都真的存在;两个 CI workflow 里 python -m pytest tests/ 仍然是字面子串且仍排在 python nuitka_build.py 之前;新增的界面文案中英双语齐全、占位符两边一致。
  • 打包不需要为新模块加任何 Nuitka 参数,这一条是核对出来的不是猜的:从 app.py 出发对 src/ 做了一次导入图可达性遍历,src/contracts/ 与全部新服务都静态可达(含几处函数体内的 import —— 模块名是编译期常量,静态分析跟得住)。理由与链路记在 nuitka_build.py 的注释里。
  • 没有做的:三平台产物的实机冒烟由 CI 在发版时跑;本轮没有做「快了多少 / 省了多少磁盘」的改前改后计时对比,所以上面任何一条都不带性能倍数。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。程序另会监听 5001 出瓦片,不放行也能用。
  • 许可证与第三方声明:程序目录下的 LICENSE(MIT)与 THIRD_PARTY_NOTICES.md。MIT 只覆盖软件代码,不授予任何数据与在线服务的使用权。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.3.2

Choose a tag to compare

@github-actions github-actions released this 11 Aug 14:25

v0.3.2 —— 补齐许可证与第三方声明;地形切片器内部重写,产物逐字节不变

先说结论:这一版程序功能一处没动,不必重做任何东西 —— 已下载的瓦片、已切好的地形、配置与历史全部照旧。 它做的是两件此前欠着的事:把 MIT 许可证和第三方组件声明真正补齐(此前 README 徽章声明了 MIT,仓库里却没有 LICENSE 文件),以及把地形切片器里一段来源不清的代码重写掉。地形切片的输出经过逐字节比对,与上一版完全相同,所以已有地形瓦片不失效、不必重切。

发行包里现在带着许可证了

  • 解压后能在程序目录里看到 LICENSE(MIT)与 THIRD_PARTY_NOTICES.md。此前两个都没有 —— 程序内嵌了 CesiumJS(Apache-2.0,要求随附许可证与 NOTICE)、Inter 与 JetBrains Mono 字体(OFL 1.1,要求随附全文)、随包的 167 MB 全球底图(GEBCO 2024 派生品,要求署名),以及一批带原生库的 Python 依赖,而这些声明一个都没跟着发出来。
  • 打包脚本此前根本不会把根目录的许可证收进产物,等于写了也白写。现在它们被列进构建期的必需文件清单,漏收会让构建直接失败,而不是静默发出一个缺声明的包。
  • 如果你在二次分发这个程序,或者分发它产出的地形成果,请读一下 THIRD_PARTY_NOTICES.md。其中最容易漏的一条:随包的全球底图来自 GEBCO 2024,使用它出的成果需要保留一句 GEBCO Compilation Group (2024) GEBCO 2024 Grid.;而它会被自动植入每一个地形任务的输出目录,也就是说你拷给别人的任务目录里就有它。
  • static/vendor/ 下五个内嵌前端组件此前一个许可证文件都没有,现已从各自上游取回全文放在组件目录旁。

地形切片器重写了一部分内部实现(对你没有任何影响)

  • 这个文件当初是从一个第三方产品的安装目录里取来的,仓库里没有留下任何授权凭据。这一版把其中确实属于原创表达的部分重写掉了:与原件逐字重合的代码从 210 行压到 125 行,最大连续相同段落从 44 行压到 10 行,剩下的都是 quantized-mesh 规范规定的编码方式、标准大地测量公式和函数签名这类换不掉的东西。
  • 为什么可以放心升级:重写前后用合成 DEM 跑了 5 组切片配置(两种三角化后端、开关顶点法线、带层级偏移),共 2396 个产物逐个比对 SHA-256,全部逐字节相同;采样器与切片方案也单独取证对账。全量测试 2263 项通过 / 3 项跳过,与改动前逐项一致。
  • 需要如实告知的一点:那个 vendored 起点的授权状态没有取得书面确认,本版的决定是维持现状(继续随 MIT 分发)。这是一个被明确记录下来的已知风险,来龙去脉、逐块判定理由与后续选项都写在仓库的 docs/reference/cesium-terrain-provenance.md 里。如果你要把本程序用于商业发行或需要做许可证尽调,请先读它。

给排障和构建的人

  • 构建期新增四个必需文件哨兵:根目录 LICENSE / THIRD_PARTY_NOTICES.mdstatic/vendor/cesium/*/LICENSE.mdstatic/vendor/fonts/LICENSE-Inter.txt。这是那份哨兵列表里唯一一组法律义务 —— 漏收之后程序功能完全正常,只是每一份发出去的拷贝都缺了它必须携带的声明,没有任何运行期信号,只能在构建期挡。
  • CLAUDE.md 里关于切片器来源的描述此前有两处错:把一个商业产品误作 CesiumJS,且与重写后的现状不符,已改正并指向溯源文档。
  • tests/test_terrain_normals.py 里有一段是 CesiumJS 算法的逐字转写(用于逐字节等价断言,不进发行产物),已就地标注 Apache-2.0 出处。

验证

  • 全量测试 2263 项通过 / 3 项跳过(开发机 Linux;跳过的只在特定平台上有意义),与改动前基线逐项相同。
  • 地形产物字节对账见上;应用真实启动、各接口返回正常。
  • 打包参数不是靠猜的:读了 Nuitka 4.1.3 的 IncludedDataFiles.py,确认单文件 SRC=DEST 形式落到产物根目录,且默认忽略清单(py.typed / .DS_Store / 代码类扩展名)不会吞掉 LICENSE.md.txt

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。程序另会监听 5001 出瓦片,不放行也能用。
  • 许可证与第三方声明:程序目录下的 LICENSE(MIT)与 THIRD_PARTY_NOTICES.md。MIT 只覆盖软件代码,不授予任何数据与在线服务的使用权。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 10 Aug 23:20

v0.3.1 —— 地形切片层级改成按源数据分辨率自动决定;瓦片挪到独立端口,看地图时页面不再跟着卡住

给已经看到 v0.3.0 的人:v0.3.0 的 Windows 构建因三条测试自身的跨平台问题(路径分隔符、node 输出按 Windows 本地编码解码)中断,那个版本只挂出了 Linux 与 macOS 的包。v0.3.1 的功能内容与 v0.3.0 完全相同,程序代码一行未改,只修了那三条测试;三个平台的包都在这一版。已经装了 v0.3.0 的 Linux / macOS 用户不必升级。

先说结论:这一版有两处升级即生效、不需要你动手的变化 —— 地形切片的「最大层级」不再固定在 14,改成按源数据的分辨率现算;底图、地形、等高线和历史预览的瓦片挪到一个独立端口 5001,出图期间页面上其他操作不再排队等它。已有瓦片不失效,已下载的数据、已切好的地形都不必重做。 要留意的只有两件事:程序从此多监听一个端口(首次运行防火墙可能多问一次,但不放行也照常能用,只是慢回去),以及那个端口对任意网页开放跨源读取 —— 这条写在第二节末尾,请读一下再决定要不要放行。

地形切片层级改成按源数据分辨率自动决定(升级即生效,出厂默认)

  • 结论:切片的「最大层级」不再固定在 14。新装的程序、以及配置里这一项还是出厂 14 的老用户,升级后每次切片都按源数据的像素尺寸现算基准层级。自己把它设成过别的数(12、16 之类)的人完全不受影响,那条配置原样保留。
  • 对本工具默认下载的 30 m 源(Copernicus GLO-30 / ASTER)产物零变化。 按分辨率算出来的基准层级对它正好就是 14:同一份 DEM、同一个档位,切出来的层级、张数、体积与上一版逐位相同。这也正是敢动出厂值的理由。
  • 变化只发生在非 30 m 源上,此前两个方向都在做无用功:
    • 粗源不再超建。 3″(约 93 m)的 DEM 此前照样建到 z14 = 77.4 MB / 12071 张;按它自己的分辨率只需建到 z12 = 6.9 MB / 1445 张。多出来的 11 倍体积不含任何新地形,只是同一批 93 m 数据被插得更平滑。
    • 细源不再被截断。 5 m 的 DEM 应当建到 z16,此前一律卡在固定的 14,你手里的细节根本没进瓦片 —— 而且从界面上看不出来。
  • 想要固定层级的照旧可以:在「数据处理」表单里把层级旁边的「自动」取消勾选,数字框就恢复可填,行为与上一版完全一样。
  • 起切之前先告诉你规模:选好源文件后,信息卡里多一行「预计切片」,写明基准层级、当前档位实际会切到的层级、瓦片张数与体积估算(勾了顶点法线再乘 1.4)。只预告、不拦你。跨 180° 经线的 DEM 上这一行会隐藏 —— 那种数据的张数会少算约六成,与其给个错数不如不给。
  • 顺带把三个档位的含义坐实了:精细 / 均衡 / 快速此前是「比你填的那个数多切一级 / 不变 / 少切一级」,你填错了它们就跟着错;现在锚在源分辨率上,三档实打实等于顶点间距约 0.3 / 0.6 / 1.2 倍源像素
  • 升级后首次启动会自动改一次配置:配置里的切片层级如果还是出厂的 14,会被改写成 auto;你自己填过别的数就一个字不动。这是一次性的,不需要你做任何事。任务详情里那一格也跟着改口 —— 自动挡下显示「自动(按源数据分辨率)」,切完之后显示实际切到的层级,不再拿一个填过的数冒充结果。

瓦片挪到一个独立端口出图,看地图的时候页面不再被拖住(升级即生效)

  • 结论:程序现在多监听一个端口 5001,底图、地形、等高线和历史任务预览的瓦片全部走它;页面、配置、任务接口和实时进度仍然走 5000。已有瓦片、已下载的数据、已切好的地形全部不失效,不必重做,也没有数据库迁移。
  • 修的是什么现场:浏览器对同一个「地址 + 端口」在 HTTP/1.1 下只开约 6 条连接。此前所有瓦片都由程序在 5000 上转发出去,首屏几十张瓦片、每张回源要秒级,这 6 条被占满期间页面上其他动作全在浏览器自己的队列里等着 —— 配置保存点了没反应、任务列表刷不出来、历史页转圈,连实时进度的连接握手都排在同一条队里。换句话说,堵在浏览器到程序这一段,不是程序只能同时处理 6 个请求,调大配置里的「并发下载数」对它一点用都没有。磁盘缓存也挡不住:它只挡重复访问,第一次看、或者平移到没看过的区域时照旧。
  • 分成两个端口之后,浏览器按「地址 + 端口」各给一套连接池,瓦片风暴再也挤不到接口那一侧。
  • 顺带修好一条串行:历史页的表格和统计不再等地图。以前小地图不初始化完,表格根本不开始加载;现在两边并行,地图起不来也不连累表格。
  • 不必为 5001 专门开防火墙。 本机用 localhost 访问走的是回环地址,不经过入站规则;只放行 5000 的环境照样能用 —— 瓦片会自动退回原来的同源路径,功能一模一样,只是慢回去。降级有三种触发方式:端口被别的程序占住(服务端启动时就知道,当场退回,不重试也不换端口)、端口起来了但客户端够不着(防火墙拦入站、反代只转了 5000、从另一台设备访问 —— 由浏览器每次开页面探一次,1 秒内没回应就整页退回)、以及通过 HTTPS 访问时根本不启用(页面不会去探一个明文端口,那本来就会被浏览器按混合内容拦掉)。
  • 探测结果只存在当前页面里,改完防火墙或转发规则要刷新一次页面才重新判定。
  • 5001 不是第二个 API 入口。 它只放行 /basemap//tiles//terrain//contour/ 四类瓦片路径和一个健康检查 /tile-health,其余请求(页面、静态资源、所有 /api/)一律 404。
  • ⚠️ 一条 5001 带来的新暴露面,放行之前请读:它对自己的每一个响应(包括 404)都发 Access-Control-Allow-Origin: * —— 换个端口对浏览器就是跨源,不发这个头 Cesium 取瓦片和 layer.json 会直接失败,而且失败会被浏览器盖成一句 CORS 错误、真实状态码看不见。代价是你在浏览器里打开的任意一个网页,都可以跨源读取 5001 上的内容:底图瓦片、已下载任务的瓦片、地形与等高线瓦片。边界是清楚的 —— /api/ 全部 404,任务列表、配置、磁盘路径读不到;只有读、没有写(这些路径只登记了 GET);泄露的是「这台机器上存在哪些瓦片」,比如挨个探任务 ID 的瓦片是否 200,从而知道你下过哪片区域。这一条与防火墙无关:跨源读取来自你本机浏览器里打开的页面,走回环地址,入站规则拦不到。不想要它,让 5001 起不来即可(比如让别的程序先占住这个端口),程序会自动退回同源出图,功能不变、只是慢一点。
  • 地形任务的父层地址不再写死 localhost:5000 那个值原本固定指向本机主端口,于是从另一台机器打开时,localhost 指的是你自己那台机器,父层根本取不到 —— 而 Cesium 对这种取不到并不报错:它塞一个假图层,还会连累你自己那份地形按错的格式解析,瓦片全 200、控制台干净、高程全错。现在默认写成相对路径,由浏览器按「谁提供了这份 layer.json」自己解析,换端口、走反代、从别的机器访问都对。已经切好的任务不用重切:磁盘上的文件一个字不动,只在程序对外提供它的时候归一。改写口径故意很窄 —— 只认「http + localhost/127.0.0.1 + 5000 端口 + /terrain/ 前缀」这一种旧写法;自己把它配成局域网 IP 或者外部地形服务地址的原样保留(那多半是部署者有意为之,猜错方向就是把一个能用的地形改成 404)。另外,把任务目录直接拷去 nginx 之类的静态服务器对外提供的,磁盘上仍是旧值,症状照旧 —— 别把这条归一当保险

给排障和构建的人

  • CI 的冒烟测试现在两个端口都探http://127.0.0.1:5000/ 要 200,http://127.0.0.1:5001/tile-health 要 204,缺一条就判失败。原因是瓦片端口起不来属于静默降级(主服务照常 200),只探 5000 的话这类回归永远报绿,用户拿到的是一个瓦片全走同源、首屏照样被浏览器连接池堵死的包。
  • /tile-health 只有一个用途:让客户端确认这个端口真的够得着。服务端知道自己绑上了没有,但不知道浏览器能不能连上(防火墙、NAT、反代、从另一台设备访问都可能让它连不上)。它返回 204、没有正文、不许缓存,且是精确匹配 —— /tile-health//tile-healthx 都是 404。
  • 瓦片端口不可配置(固定主端口 + 1),被占用时不会自动换到 5002,只会降级;服务端日志里有一条 warning,界面上没有提示 —— 因为它只影响速度,不影响功能。

验证

  • 本版全量测试 2214 项通过 / 3 项跳过(开发机 Linux;跳过的只在特定平台上有意义)。上一版发布时是 2035 项。
  • 两部分都是逐个任务做的:写用例 → 在未改的实现上跑一遍确认它确实会红 → 再改、再跑绿,每个任务单独过一轮评审,最后再对整包做一次整体评审。自动层级那部分另有两轮专门的清理:先纠了 7 条「说法与事实不符」的注释与文档,再收紧 11 条「断言其实咬不住」的用例,每一条都用变异体自证过(把实现改坏,盯着断言真的变红)。
  • 瓦片端口那部分的前端探测逻辑不是靠读代码断言的:用 Node 真的执行 static/js/ui.js,验证探测成功、只探一次、超时会取消请求、任何情况下都不抛错、HTTPS 页面直接跳过、外部地址不被改写。前后端那份瓦片路径白名单由一条相等性断言钉死,改一边不改另一边当场红。
  • 这一节没有「快了多少」的现场数字:连接池上限是浏览器行为,本轮没有做改前改后的计时对比,只做了功能与契约验证。宁可不写,也不给一个编出来的倍数。
  • 一处需要说明的口径:预告的瓦片张数是「预计生成的张数」,不是「Cesium 认得的可用张数」—— 后者还要过一道覆盖率闸门(v0.2.14 加的那条),比预告的少。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。程序另会监听 5001 出瓦片,不放行也能用(见上)。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 10 Aug 22:44

v0.3.0 —— 地形切片层级改成按源数据分辨率自动决定;瓦片挪到独立端口,看地图时页面不再跟着卡住

先说结论:这一版有两处升级即生效、不需要你动手的变化 —— 地形切片的「最大层级」不再固定在 14,改成按源数据的分辨率现算;底图、地形、等高线和历史预览的瓦片挪到一个独立端口 5001,出图期间页面上其他操作不再排队等它。已有瓦片不失效,已下载的数据、已切好的地形都不必重做。 要留意的只有两件事:程序从此多监听一个端口(首次运行防火墙可能多问一次,但不放行也照常能用,只是慢回去),以及那个端口对任意网页开放跨源读取 —— 这条写在第二节末尾,请读一下再决定要不要放行。

地形切片层级改成按源数据分辨率自动决定(升级即生效,出厂默认)

  • 结论:切片的「最大层级」不再固定在 14。新装的程序、以及配置里这一项还是出厂 14 的老用户,升级后每次切片都按源数据的像素尺寸现算基准层级。自己把它设成过别的数(12、16 之类)的人完全不受影响,那条配置原样保留。
  • 对本工具默认下载的 30 m 源(Copernicus GLO-30 / ASTER)产物零变化。 按分辨率算出来的基准层级对它正好就是 14:同一份 DEM、同一个档位,切出来的层级、张数、体积与上一版逐位相同。这也正是敢动出厂值的理由。
  • 变化只发生在非 30 m 源上,此前两个方向都在做无用功:
    • 粗源不再超建。 3″(约 93 m)的 DEM 此前照样建到 z14 = 77.4 MB / 12071 张;按它自己的分辨率只需建到 z12 = 6.9 MB / 1445 张。多出来的 11 倍体积不含任何新地形,只是同一批 93 m 数据被插得更平滑。
    • 细源不再被截断。 5 m 的 DEM 应当建到 z16,此前一律卡在固定的 14,你手里的细节根本没进瓦片 —— 而且从界面上看不出来。
  • 想要固定层级的照旧可以:在「数据处理」表单里把层级旁边的「自动」取消勾选,数字框就恢复可填,行为与上一版完全一样。
  • 起切之前先告诉你规模:选好源文件后,信息卡里多一行「预计切片」,写明基准层级、当前档位实际会切到的层级、瓦片张数与体积估算(勾了顶点法线再乘 1.4)。只预告、不拦你。跨 180° 经线的 DEM 上这一行会隐藏 —— 那种数据的张数会少算约六成,与其给个错数不如不给。
  • 顺带把三个档位的含义坐实了:精细 / 均衡 / 快速此前是「比你填的那个数多切一级 / 不变 / 少切一级」,你填错了它们就跟着错;现在锚在源分辨率上,三档实打实等于顶点间距约 0.3 / 0.6 / 1.2 倍源像素
  • 升级后首次启动会自动改一次配置:配置里的切片层级如果还是出厂的 14,会被改写成 auto;你自己填过别的数就一个字不动。这是一次性的,不需要你做任何事。任务详情里那一格也跟着改口 —— 自动挡下显示「自动(按源数据分辨率)」,切完之后显示实际切到的层级,不再拿一个填过的数冒充结果。

瓦片挪到一个独立端口出图,看地图的时候页面不再被拖住(升级即生效)

  • 结论:程序现在多监听一个端口 5001,底图、地形、等高线和历史任务预览的瓦片全部走它;页面、配置、任务接口和实时进度仍然走 5000。已有瓦片、已下载的数据、已切好的地形全部不失效,不必重做,也没有数据库迁移。
  • 修的是什么现场:浏览器对同一个「地址 + 端口」在 HTTP/1.1 下只开约 6 条连接。此前所有瓦片都由程序在 5000 上转发出去,首屏几十张瓦片、每张回源要秒级,这 6 条被占满期间页面上其他动作全在浏览器自己的队列里等着 —— 配置保存点了没反应、任务列表刷不出来、历史页转圈,连实时进度的连接握手都排在同一条队里。换句话说,堵在浏览器到程序这一段,不是程序只能同时处理 6 个请求,调大配置里的「并发下载数」对它一点用都没有。磁盘缓存也挡不住:它只挡重复访问,第一次看、或者平移到没看过的区域时照旧。
  • 分成两个端口之后,浏览器按「地址 + 端口」各给一套连接池,瓦片风暴再也挤不到接口那一侧。
  • 顺带修好一条串行:历史页的表格和统计不再等地图。以前小地图不初始化完,表格根本不开始加载;现在两边并行,地图起不来也不连累表格。
  • 不必为 5001 专门开防火墙。 本机用 localhost 访问走的是回环地址,不经过入站规则;只放行 5000 的环境照样能用 —— 瓦片会自动退回原来的同源路径,功能一模一样,只是慢回去。降级有三种触发方式:端口被别的程序占住(服务端启动时就知道,当场退回,不重试也不换端口)、端口起来了但客户端够不着(防火墙拦入站、反代只转了 5000、从另一台设备访问 —— 由浏览器每次开页面探一次,1 秒内没回应就整页退回)、以及通过 HTTPS 访问时根本不启用(页面不会去探一个明文端口,那本来就会被浏览器按混合内容拦掉)。
  • 探测结果只存在当前页面里,改完防火墙或转发规则要刷新一次页面才重新判定。
  • 5001 不是第二个 API 入口。 它只放行 /basemap//tiles//terrain//contour/ 四类瓦片路径和一个健康检查 /tile-health,其余请求(页面、静态资源、所有 /api/)一律 404。
  • ⚠️ 一条 5001 带来的新暴露面,放行之前请读:它对自己的每一个响应(包括 404)都发 Access-Control-Allow-Origin: * —— 换个端口对浏览器就是跨源,不发这个头 Cesium 取瓦片和 layer.json 会直接失败,而且失败会被浏览器盖成一句 CORS 错误、真实状态码看不见。代价是你在浏览器里打开的任意一个网页,都可以跨源读取 5001 上的内容:底图瓦片、已下载任务的瓦片、地形与等高线瓦片。边界是清楚的 —— /api/ 全部 404,任务列表、配置、磁盘路径读不到;只有读、没有写(这些路径只登记了 GET);泄露的是「这台机器上存在哪些瓦片」,比如挨个探任务 ID 的瓦片是否 200,从而知道你下过哪片区域。这一条与防火墙无关:跨源读取来自你本机浏览器里打开的页面,走回环地址,入站规则拦不到。不想要它,让 5001 起不来即可(比如让别的程序先占住这个端口),程序会自动退回同源出图,功能不变、只是慢一点。
  • 地形任务的父层地址不再写死 localhost:5000 那个值原本固定指向本机主端口,于是从另一台机器打开时,localhost 指的是你自己那台机器,父层根本取不到 —— 而 Cesium 对这种取不到并不报错:它塞一个假图层,还会连累你自己那份地形按错的格式解析,瓦片全 200、控制台干净、高程全错。现在默认写成相对路径,由浏览器按「谁提供了这份 layer.json」自己解析,换端口、走反代、从别的机器访问都对。已经切好的任务不用重切:磁盘上的文件一个字不动,只在程序对外提供它的时候归一。改写口径故意很窄 —— 只认「http + localhost/127.0.0.1 + 5000 端口 + /terrain/ 前缀」这一种旧写法;自己把它配成局域网 IP 或者外部地形服务地址的原样保留(那多半是部署者有意为之,猜错方向就是把一个能用的地形改成 404)。另外,把任务目录直接拷去 nginx 之类的静态服务器对外提供的,磁盘上仍是旧值,症状照旧 —— 别把这条归一当保险

给排障和构建的人

  • CI 的冒烟测试现在两个端口都探http://127.0.0.1:5000/ 要 200,http://127.0.0.1:5001/tile-health 要 204,缺一条就判失败。原因是瓦片端口起不来属于静默降级(主服务照常 200),只探 5000 的话这类回归永远报绿,用户拿到的是一个瓦片全走同源、首屏照样被浏览器连接池堵死的包。
  • /tile-health 只有一个用途:让客户端确认这个端口真的够得着。服务端知道自己绑上了没有,但不知道浏览器能不能连上(防火墙、NAT、反代、从另一台设备访问都可能让它连不上)。它返回 204、没有正文、不许缓存,且是精确匹配 —— /tile-health//tile-healthx 都是 404。
  • 瓦片端口不可配置(固定主端口 + 1),被占用时不会自动换到 5002,只会降级;服务端日志里有一条 warning,界面上没有提示 —— 因为它只影响速度,不影响功能。

验证

  • 本版全量测试 2214 项通过 / 3 项跳过(开发机 Linux;跳过的只在特定平台上有意义)。上一版发布时是 2035 项。
  • 两部分都是逐个任务做的:写用例 → 在未改的实现上跑一遍确认它确实会红 → 再改、再跑绿,每个任务单独过一轮评审,最后再对整包做一次整体评审。自动层级那部分另有两轮专门的清理:先纠了 7 条「说法与事实不符」的注释与文档,再收紧 11 条「断言其实咬不住」的用例,每一条都用变异体自证过(把实现改坏,盯着断言真的变红)。
  • 瓦片端口那部分的前端探测逻辑不是靠读代码断言的:用 Node 真的执行 static/js/ui.js,验证探测成功、只探一次、超时会取消请求、任何情况下都不抛错、HTTPS 页面直接跳过、外部地址不被改写。前后端那份瓦片路径白名单由一条相等性断言钉死,改一边不改另一边当场红。
  • 这一节没有「快了多少」的现场数字:连接池上限是浏览器行为,本轮没有做改前改后的计时对比,只做了功能与契约验证。宁可不写,也不给一个编出来的倍数。
  • 一处需要说明的口径:预告的瓦片张数是「预计生成的张数」,不是「Cesium 认得的可用张数」—— 后者还要过一道覆盖率闸门(v0.2.14 加的那条),比预告的少。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。程序另会监听 5001 出瓦片,不放行也能用(见上)。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.2.14

Choose a tag to compare

@github-actions github-actions released this 09 Aug 17:58

v0.2.14 —— 堵掉两条「静默产出错数据」:地形不再拿假高原冒充真地形,拼接不再把缺瓦片的半张图当成品

先说结论:这一版没有新功能,是一轮纠错,重点是两条会安静地给出错数据的缺陷。 它们的共同形态是:文件打得开、任务显示「已完成」、日志一个字都不报,只有你自己拿 GIS 去量才发现不对。另外还修了一批「界面/文档对你说反话」的问题 —— 其中最要紧的一条是配置页当面告诉你「底图不走代理」,而底图恰恰是走代理的,正好把「底图打不开」这个最常见的现场问题引到了错误的方向。已有瓦片不失效、不必重切;升级后首次启动会自动给两张地形表加列,不需要你做任何事。

地形不再把「假高原」声明成可用数据

  • 症状:上传一小块 DEM(比如 0.05° 见方,约 5.5 公里)切地形,在最浅的那一层会看到两块几十公里见方的巨型台地贴在真实地形旁边,高差可达数千米。全程 HTTP 200、作业完成、零告警。
  • 原因:判断「这张瓦片要不要声明为可用」用的是「它和 DEM 有没有交集」,而交集可以只有一个像素。DEM 之外的采样点并不是空值,而是被最外圈的源像素向外拉平成台地。实测那一层真实数据只占瓦片面积的 0.51%,也就是 4225 个顶点里只有 16 个是真的。Cesium 取「第一个声明可用的层」,于是随包的全球底图永远没机会出场,用户看到的就是那张假的。
  • 现在改成看覆盖率:某一层的瓦片被真实数据覆盖得太少就不再声明(照常出图,只是不声明),Cesium 自然回落到全球底图那份真实的粗地形;瓦片每深一级缩小一半,覆盖率很快追上来,你的 DEM 在它真正有意义的层级上照常出现。正常规模的 DEM(1°×1° 的标准 granule、全球栅格)完全不受影响

瓦片下载:有瓦片失败时不再拼接,也不会把上一轮的半张图当成品留下

  • 症状:选了「瓦片 + GeoTIFF」或只要 GeoTIFF 的任务,拿到的那张 GeoTIFF 地理范围比你框的选区小(或者内部有洞),而任务显示「已完成」、错误信息是空的。
  • 原因是两步叠加:拼接阶段拿到的是「成功的那些瓦片」,失败的根本不在里面,于是用一个比你选区小的集合拼出一张看起来很完整的图;随后暂停/恢复时,「文件已存在就跳过拼接」这条断点逻辑又把那张残缺图原样保留了下来。
  • 现在:某一层只要还有失败的瓦片就整层不拼,任务判失败并写明「哪些层没有拼接产物」;补齐重跑时,本轮下到过新瓦片的层一律重拼。纯粹的重跑(全部命中缓存、一块新的都没下)仍然跳过拼接,十分钟级的重算照样省掉。
  • 同一段代码还有一处:最后一次进度写库失败时,以前只记一条日志,而「任务成没成功」恰恰是查库得出的 —— 那批失败记录就在丢掉的那次写库里,于是「N 块瓦片失败」被写成了「已完成,无错误」。现在这种情况直接判失败并说明计数不可信。

配置页那句关于底图和代理的话,说反了

  • 配置页一直写着「底图由浏览器直连加载,不经过代理设置」。自 v0.2.12 起底图瓦片就是由程序在服务端转发的,和下载走同一条出网路径,同样吃你填的代理。而「底图打不开、只剩一个蓝球」是这个程序最常见的现场问题 —— 那句话等于告诉你「配代理没用」,正好把你从唯一有效的那一步支开了。中英两版都改了。

换了底图之后,看过的区域不会再一整天不变

  • 底图瓦片带 24 小时浏览器缓存,而它的地址里没有任何「这是哪一家」的标识。于是你在配置页把底图从 Esri 换成 Google 再刷新,已经浏览过的区域画面纹丝不动,而且整整一天都是这样,界面上没有任何补救办法。现在地址里带上了源标识,换源即换地址,立刻生效。

框选:三个入口的校验口径统一了

  • 以前用鼠标在地图上单击(没有拖动)会得到一个零面积的选区,界面还会一本正经地给出「约 6 张瓦片」的预估;手动输入范围时,经度填 400 会被收下、状态栏显示「已选区域 300.000°」;跨 180° 经线的选区不但被收下,状态栏还会显示一个负数宽度,「创建下载任务」按钮照样可点,点下去才收到后端一句英文报错,而且报错里的经度数字是你从来没输入过的(提交前被静默改写过)。
  • 现在三个入口走同一道校验,规则与后端逐条一致(北纬 > 南纬、纬度 ±90、经度 ±180、东经 > 西经),拒绝时给的是中文说明。跨反经线的选区明确不支持 —— 后端从来就不接受它,前端以前的「放行」纯粹是个陷阱。

弹窗上按 Esc,不会再把身后的面板一起关掉

  • 从配置面板里点「浏览」打开目录选择框,按一次 Esc 只想关掉这个框,结果连配置面板一起滑走了。键盘操作也受影响:Tab 反复被拉回弹窗的第一个控件,到不了目录列表和「选择」按钮。

给排障和构建的人

  • 临时目录配置不再接受 ~ stitch_tmpdir / contour_warp_tmpdir~/xxx 时保存会成功,但真正用它的三处代码都不展开 ~:等高线任务在预处理阶段直接失败,拼接的中间产物会落进一个名字真的叫 ~ 的目录。现在保存时就会拒绝,请填完整的绝对路径。
  • CI 现在会验 GDAL 绑定。 之前只有本地 build.sh / build.bat 跑这道闸,CI 直接调打包脚本绕过去了 —— 一个缺 _gdal_array 的坏包能构建成功、冒烟通过、照样发出去。
  • 发版构建不再一个平台失败就取消另外两个,避免留下只挂了部分平台产物的 Release。
  • 切片作业永久卡在「运行中」的最后两个入口补上了(DEM 的切片作业、本地地形任务)。上一版补了三张表,这两张当时被一条「它们自己有兜底」的注释漏掉了,而那条注释是错的。

以下是本版早前已经完成的部分

详情面板改说实话:显示实际切到的层级,说不准的写「未知」;任务不再永久卡在「运行中」

任务详情面板显示的是实际切到的层级,不再是你填的那一级(这一版唯一的新能力)

  • 以前 DEM 地形和本地地形两个详情面板显示的都是你在表单里填的「最大层级」—— 那是基准层级。一旦选了精细或快速档,它与产物就差一级:面板写「0 - 14」,layer.json 里写的是 15。本地地形那个模态框更糟,0 - 14 是一个自称精确的错范围。
  • 现在切片收尾时会把产物实际的最深层级写进库,面板读的是它,标签写「实际层级」。同一份 DEM 换个档位重切一遍,两条记录并排摆着就知道差在哪。
  • 这件事没被记录的时候会明说,不拿基准值冒充结果。 这一版之前切的作业(那时还没有这一列)、以及还没切完的作业,库里根本没有这个数;面板回落到基准值,同时把标签换成「基准层级」。
  • 本地地形的详情里补上了「切片档位」和「顶点法线」两行 —— 上一版只有 DEM 侧有,而本地地形(上传 tif 那条路)恰恰是用户唯一能亲手选档位的入口。

「顶点法线」这一行现在会说「未知」—— 在此之前,它对着一批真的带光照的地形断言「未开启」

  • 上一版新增「顶点法线」这一列时,库里的存量行统一填成了 0,面板据此显示「未开启(无光照数据)」,鼠标悬停还会给出「整幅场景的光照会退化成全球日夜渐变」那段警告。方向正好说反了:这一列出现之前,切片器默认就是烘法线的,那批老瓦片的光照好得很。面板等于用一种确定的语气,描述了一件它根本没记录、而且猜反了的事。
  • 现在这一列是三态:没有记录 → 「未知(这一行没有记录)」;明确关闭 → 「未开启(无光照数据)」;明确开启 → 「已开启」。那段吓人的悬停说明只挂在明确关闭这一档上 —— 未知状态下挂出去,是拿一件没记录的事吓人。
  • 顺带修掉一个真的会改变产物的错:本地地形重新起切时,把「没有记录」按布尔读成了「关」,也就是把一个原本带光照的老任务,静默地按「不要光照」重切一遍。现在没有记录就走配置里的默认值,并且把这一轮真正用的值写回那一行 —— 切过一次之后,它就不再是未知了。
  • ⚠️ 一条必须说清楚的局限:已经装过 v0.2.13 的人,那批老作业会继续显示「未开启」。 上一版的升级已经把它们回填成 0,在数据库里和「用户真的关掉了法线」逐位相同,分不出来。这里刻意没有做数据修复迁移:方向猜错的批量改写,比一个错标签更糟。从 v0.2.12 或更早直接升上来的库不受影响,那些行显示的是「未知」。

任务不会再永久卡在「运行中」(DEM 与本地地形两条管线都堵上了)

  • 症状:一个任务在界面上显示「运行中」,后台却根本没有线程在跑,而且它既开始不了、也删不掉 ——「开始」只受理待处理和已暂停,删除同样被拒;唯一的出路是重启程序。
  • 触发条件说人话:在切片刚要启动的那一瞬间出了岔子 —— 数据库被别的进程短暂占住(database is locked)、磁盘出错,或者配置里有个坏值。此刻「运行中」这一行已经提交进库,工作线程却还没起来;出错时的回滚是空操作,回滚不掉已经提交的东西。本地地形这条路上还有一步要另开一条数据库连接去读配置表,正是最容易在这个窗口里抛出来的地方。
  • 现在两条管线都把「写下运行中」推到线程登记之后:中间任何一步出错,任务直接判失败、可以重新开始,而不是变成一具删不掉的僵尸。

本地地形的「层级」那一格不再把话藏在悬停提示里

  • 上面说的那个「基准层级」限定词,原本只写在鼠标悬停里。触摸设备上悬停根本不存在、键盘也够不着,而 0 - 140 - 14 长得一模一样 —— 用户连「这里有话要说」都看不出来。现在限定词写进文字本身

几处小的

  • 状态值不认识的时候,徽章不会再静默失灵。 任务状态或样式如果是 constructor__proto__toString 这类值(手改过库,或者将来新增一个状态忘了登记),前端查表会取到 JavaScript 对象自带的成员,而不是落到兜底分支:徽章的 class 变成 bg-function Object() { [native code] }(一颗无色徽章)、文字变成一段函数源码、地图上任务矩形的描边色变成 undefined 于是边框直接消失 —— 三样都不报错。涉及的四张查表现在只认自己登记过的键。
  • 档位配置填错时,报错点名的是配置键。 接口调用方没传 quality 时,校验的是配置值,报错却写成 quality ('ultra') must be one of —— 指着一个用户从来没提交过的字段。现在报的是 terrain_quality_preset
  • 给排障的人:切片日志里那行 chose_grid 不能按「全 grid = 山地」读。 这条读法只在 triangulator='auto' 下成立;应用侧固定用规则网格,chose_grid 无条件等于渲染总数,与地形是什么样毫无关系,照那条读法会把每一批 DEM 都判成山地。三处注释都补上了这个限定(日志行本身没动,它一直印着 triangulator=)。另外,「用 CLI 复现生产切片」那条说明补全了:只加 --triangulator grid 不够,还要 --tile-size 65(CLI 默认 17、生产是 65),顶点密度差 4 倍,体积 / 耗时 / 三角形数照样对不上,等于没复现。

验证

  • 本版全量测试 2035 项通过 / 3 项跳过(开发机 Linux;跳过的只在特定平台上有意义)。上一版发布时是 1930 项。
  • 这一轮修的每个问题都配了一条会变红的用例,而且都是先在未修的实现上跑一遍、确认它确实失败,再修、再跑绿 —— 避免写出「怎么改都绿」的空断言。本轮这样逐个做过的变异超过 40 个
  • 上面那两条「静默产出错数据」以及框选、底图、Esc 这几条,另外用无头浏览器驱动真实服务端复验过:改之前的错误行为逐条复现,改之后逐条消失。
  • 顺带补上了四处「测试看着有、其实抓不到」的洞 —— 其中一组用例断言的是测试文件里手抄的一份实现副本,把真实代码改坏四种花样它都不会红。
  • 前端那几条另外逐个做了变异验证:把对应的错误一个一个改回去,盯着断言真的变红。详情面板这批 12 个变异;上一版给面板新增的三个 HTML 转义点补登记用例,6 个变异(那三处转义原本删掉,全量测试照样全绿)。
  • 三处注释订正另做了「零可执行改动」的证明:把源码的文档字符串剥掉之后比对语法树,而不是靠眼睛看 diff。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.2.13

Choose a tag to compare

@github-actions github-actions released this 09 Aug 10:00

v0.2.13 —— 地形切片快 4~7 倍、高程精度好约 5 倍(新切的地形默认不带光照)+ 底图取不到瓦片会自动换一张

先说结论:升级即生效的有三件事 —— 底图不会再是一颗蓝球,选完 tif 立刻能看到它是什么数据,「数据处理」表单里多了切片档位与地形光照两个开关。地形变快变准那件事要重切才拿得到,而且新切的地形默认没有山体光照。 已有的地形瓦片不失效、不必重切。

地形切片换了一套默认做法:同一份 DEM,快 4~7 倍、高程精度好约 5 倍,代价是新切的地形默认不再带光照。已经切好的瓦片不失效、照常用,不必重切;但想拿到这些改进就必须重切 —— 切片参数是烘进产物的。

  • 旧做法是「逐瓦片在规则网格和减面网格之间择优 + 无条件烘焙顶点法线」。这一版改成固定用规则网格、默认不烘法线。
  • 为什么改:拿 6 个真实 Copernicus GLO-30 数据块(坡度中位 0.5138.85,从极平原到黄土塬)、20 组配置做三轴(耗时 / 体积 / 高程误差)支配判定,旧默认一次都没进过 Pareto 前沿(即:总能找到另一组配置三项全面胜过它),而且在三个地形上都比现在最费的「精细」档还慢。它多花的 2.65.9 倍时间很大一部分是白花的:崎岖地形上它 98.8% 的瓦片本来就选了规则网格,等于把同一个产物用 6 倍 CPU 重算一遍。
  • 华北平原那块 1°×1° 实测,同样切到 z14:耗时 15.36 s → 3.90 s,高程 RMS 误差 0.543 m → 0.082 m,体积 44.5 MB → 94.6 MB。江南丘陵、黄土塬同口径分别快 5.0 倍7.2 倍
  • 六种地形取中位:时间约 1/5、精度 5.1 倍、体积 +58%体积是唯一变差的一项,而且只在平缓地形上变差:最平的荷兰 12.9 MB → 18.9 MB(+46%),华北 +112%;而最崎岖的黄土塬反而从 171.1 MB 降到 94.5 MB(−45%)
  • 规则网格不减面,平缓地形上每张瓦片恒定 8192 个三角形,是旧做法的 7.86 倍 —— 这是本次改动最该被质疑的地方,所以拿仓库自带的 CesiumJS 1.143.0 在一块弱核显(Intel UHD 620)上真测了:同屏瓦片数逐格相同(23/23、45/45、48/48)—— Cesium 选层级看的是几何误差,与瓦片里有多少三角形无关;32 万同屏三角形的帧时中位 0.3~0.4 ms、显存 6 MB,比旧做法还略快一点。唯一真实的代价是首次加载按字节等比变长,且只在平缓地形上出现:华北冷缓存 45 张瓦片就位 1093 ms → 1905 ms(1.74 倍),黄土塬无差别。
  • 完整选型实测见 docs/reference/terrain/tiling-presets-measured.md

⚠️ 新切的地形不含光照法线,「地形光照」对它无效 —— 而且是静默无效

  • 顶点法线(每顶点 2 字节的编码扩展段)对几何精度零贡献 —— 开关两侧的 RMS、P95、最大误差逐位相同 —— 却要吃 +35%~+100% 的体积约 2.1 倍的切片时间(华北 +35%、江南 +67%、黄土塬 +100%)。而前端的「地形光照」本来就默认关着,所以默认不再烘它。
  • 关掉的后果必须说在前面:layer.jsonextensions 会写成 [],而 Cesium 的 hasVertexNormals整个地形提供者一个标志。于是点「地形光照」不会有任何山体明暗,只得到全球日夜渐变,连随包底图自带的法线也一并作废,全程零报错、任务照样显示完成。这正是本仓栓过三次的那类「作业完成、HTTP 200、前端不报错、就是不对」,所以宁可写在发版说明里。
  • 法线是烘焙进瓦片的:事后改配置对已经切完的产物没有任何影响,想要光照只能带着法线重切一遍。反过来,升级前切好的老瓦片自带法线,光照照常能用。

任务详情面板多了两行:「切片档位」和「顶点法线」

  • 显示的是这个作业实际用的值,不是当前配置值 —— 同一份 DEM 换个档位重切一遍,两条记录并排摆着就知道差在哪。
  • 档位不直吐后端的 precision / balanced / speed,写成「精细(比基准层级多切一级)」这种带参照物的说法 —— 那三个词本身说不清「和什么比、差在哪」。参照物特意写「基准层级」而不是「默认档位」:把默认改成快速之后,一个存成均衡的旧作业仍会被标成「默认」,而它其实比默认多切了一级。
  • 法线关闭时写「未开启(无光照数据)」,鼠标悬停给出上一段那个后果的全文。认不出的档位值(手改过库、将来新增的档)原样显示,不冒充「均衡」
  • 一条读数上的局限:升级前切的作业会被显示成「均衡 / 未开启」,这是加列时填的默认值,不是它们当时真实用的参数(当时是择优 + 烘法线)。老作业的实际参数当时根本没存,补不出来。

「数据处理」表单里可以直接选档位和法线了

  • 上传 DEM 建本地高程切片时,「最大层级」下面多了「切片档位」下拉(精细 / 均衡 / 快速)和「生成地形光照法线」复选框;对已经下载好的 DEM 起切也走同一组控件。旁边各有一行说明:档位那条讲清楚它是在你填的层级上 +1 / +0 / −1,法线那条把上面那个「静默失效」的后果原样写着。
  • 两个控件的初值由服务端按配置渲染,不是写死的。 写死就意味着运维在配置页把默认调成了精细、表单却仍然发均衡 —— 那是个「改了没反应的假旋钮」。法线那条尤其不能写死:配置里开着、用户不动复选框却发了个 false 出去,几小时切完才发现光照点不亮。
  • 配置页仍然没有这两项的控件,改默认还是走 PUT /api/configterrain_quality_presetprecision / balanced / speed,默认 balanced)与 terrain_vertex_normalstrue / false,默认 false)。
  • 接口调用方注意三态:不传 quality / vertex_normals 是「走配置默认」,与「传了 false」是两回事(本地地形走 multipart,字符串 'true' / 'false';DEM 起切走 JSON body,真布尔)。另外 HTML 复选框的 .value 恒为 on,直接发它会 400 —— 要发 String(el.checked)
  • 表单一直开着、期间配置被人改过的话,提交写回的是你打开页面时那一份值。刷新一下就同步了。
  • 三档只差一件事:实际切到的最深层级 = 你填的「最大层级」+1 / +0 / −1。取值表全项目只有一份(src/services/geo_validation.pyTILING_QUALITY_OFFSETS),配置键、数据库列、界面文案都从它派生 —— 不存第二份,就不会出现「界面写着一回事、切出来另一回事」。
  • 为什么用「层级」而不是「简化网格」拉开档位:两个旋钮都能拿精度换体积,但层级的兑换率高 2.4~3.9 倍,而且层级省时间、简化网格反过来多花 2.6~5.9 倍时间
  • 每加一级约 3.3 倍体积换 2.8 倍精度,这个比例与源数据分辨率无关(同一块 DEM 重采样成 1″ / 3″ / 9″ 各建 5 个层级,六组数据全落在这个区间)。华北那块的三档完整金字塔:精细 z15 / 45560 张 / 354.2 MB / 13.31 s / RMS 0.029 m,均衡 z14 / 12071 张 / 94.6 MB / 3.90 s / RMS 0.082 m,快速 z13 / 3607 张 / 24.6 MB / 1.53 s / RMS 0.226 m。
  • **在意磁盘或首次加载的,快速档是真划算:**24.6 MB 比旧默认的 44.5 MB 还小,高程精度还好 2.4 倍。
  • 别拿瓦片张数判断档位有没有生效。 z0–z4 那 682 张是全球覆盖的固定底座,三档逐层完全相同,只有塔尖跟着档位变。以基准 z12 实测,三档是 3607 / 1445 / 893 张,而体积是 24.6 / 6.7 / 2.2 MB —— 体积逐档约 1/3.3,张数远不到。

几个现在就能踩到的点(走接口的人看)

  • 档位名拼错当场 400,不静默退回均衡。「改了档位重切、结果一模一样且零报错」是这条路径最难查的假象。法线开关同理,只认 true / false 两个字面量 —— HTML 复选框的 .value 恒为 on,直接发它会 400(要发 String(el.checked))。静默折成「关」的后果是:你勾了法线、瓦片没烘、任务显示完成。
  • 最大层级填 21 再选精细,实际还是 21。 层级校验发生在加偏移之前,拦不到 21+1;偏移叠完会被钳在 0~21。反方向一样:填 8 选快速会压到 7,而地形切片的起始层级恒为 8(z0–z7 是随包底图的地盘)—— 这种情况程序会把起始层级一起压下去,不会出现「切了 0 张瓦片却报完成」。
  • 配置里的 terrain_local_maxzoom 填了越界值(比如 99),现在两个入口一致地退回出厂默认 14,并在日志里打出被丢弃的原值。 以前本地地形那条是裸取值,99 会让建任务直接 400;DEM 那条读同一个键却软退回照跑 —— 同一个坏配置两种结果。浏览器上传恒带层级,真正中招的是省略该字段的接口调用方。一个坏配置不应该让所有任务都建不起来,但它必须在日志里留痕 —— 静默吞掉你写过的 99,会让它在系统里一处都查不到。

两条没改、但应该说清楚的

  • 基准层级仍然是你填的那个数,不看源数据分辨率。 93 m 的 DEM 切到 z14 是 77.4 MB,按分辨率估算只需 z12 / 6.9 MB —— 11 倍体积换不到任何新地形;反过来 5 m 的 DEM 该切到 z17,被 14 截断,细节根本进不了瓦片。选型实测顺手查出了这一条,但修它要动两张表的约束,本版没做。
  • 全球随包底图不受影响,也不该受影响。 它是预先切好、随包分发的,构建脚本仍走旧的逐瓦片择优 —— 它覆盖海洋和大片平原,正是减面收益最大、规则网格字节代价最高的地方,而且只构建一次,CPU 代价无所谓。两边的取舍条件本来就是相反的。

底图取不到瓦片时会自动换一张,不再是一颗蓝球

  • 实测过的真实故障:Esri 的 CDN(Akamai)封了代理的出口 IP,每块瓦片 403;而那台机器上 Google 只有走代理才通——两张卫星图分属两条网络路径,谁都可能单独挂掉。现在后端按 Esri 卫星 → Google 卫星 → OpenStreetMap 路网依次试,第一张通的就出图,挂掉的源冷却 60 秒后自动重试(上游恢复了不用你做任何事)。
  • 换了会明说:界面弹一句「底图已自动切换到 Google 卫星影像:Esri 卫星影像取不到瓦片」,同时 /api/basemap 会同步报出实际在用的源、最大层级和署名——底图默默换一张而你不知道,比蓝球更糟。换回来也会说:配置的源恢复之后弹一句「底图已恢复为 Esri 卫星影像」。整个会话每 30 秒查一次,只在真的换了的时候说话,不会重复弹同一句。
  • 回退时最大层级和署名跟着实际那张图走。 Esri 封顶 z19、Google 封顶 z21,回退之后如果还按原来那个上限请求,多出来的层级全是 404 黑瓦片;署名不跟着换则是许可证问题(Esri 与 OSM 的署名是硬要求,不是装饰)。
  • 回退链里只有 WGS-84 的源。 底图是用来框选下载范围的,静默换上一张 GCJ-02 的图等于让你框错地方(国内偏移 100–700 米)。Google 路网图(lyrs=m)因此不在链里:它中国区是 GCJ-02,而且与 Google 卫星同主机——卫星取不到时它也取不到,放进来是拿坐标系风险换零可用性。想用它可以在配置页里显式选,那是你自己的决定。
  • 配置值本身写错(不是 http(s)、或指向 169.254.x.x 这类链路本地地址)不会被回退掩盖,照旧当场 502:那是配置错误不是上游故障,盖掉的话你永远不知道自己写错了。

选完 tif 就能看到这份数据到底是什么(本地高程切片 + 等高线)

  • 以前「数据处理」里选完文件只显示一个文件名,坐标系、覆盖范围、分辨率一概看不到 —— 层级填多少全靠猜,选错文件(比如拿了一份没有坐标系的 tif)要等任务跑起来失败了才知道。
  • 现在选完文件当场列出:影像尺寸、坐标系(EPSG 码 + 名称)、WGS84 覆盖范围、像元分辨率(度 + 米)、数据类型 / 波段 / NoData、GDAL 算过统计时还有高程范围,以及按切片管线自己的算法给出的建议最大层级(与不填层级时实际切出来的一致)。多选时另有一块合并总览:合并范围、最细分辨率、按最细分辨率给的建议层级。
  • 有问题会直接说,而不是等任务失败:缺地理参考 / 坐标系不可识别 / 读不出 TIFF 头部(红字,这些切不了片);不是 WGS84 会自动重投影、多波段只用第 1 波段、多个文件坐标系不一致(橙字,提醒)。
  • 不会为此上传文件。浏览器只读文件开头几 KB 的 TIFF 目录,把标签发给后端做地理解释(EPSG → 坐标系名称、投影坐标 → 经纬度这些必须由 GDAL 来算)。实测一个 200 MB 的 DEM 从选中到出信息 117 毫秒,页面内存涨 0 —— 真正的上传仍然只发生在点「创建处理任务」的时候。
  • 两个表单用的是同一张卡,只有「建议最大层级」分开算:高程切片按 Cesium 的经纬度分块、等高线按 Web Mercator 瓦片,各自调用它自己那条管线的函数 —— 卡片上写的数就是你不填层级时真正会切出来的那一级。

配置里的路径不再被限制在程序目录内

  • 「拼接临时目录」stitch_tmpdir、「等高线重投影临时目录」contour_warp_tmpdir、「随包底图位置」terrain_global_base_path 现在可以指到任意一块盘。上一版把它们限制在安装目录 / 下载目录 / 缓存目录之内,而这条规则与这几个键的用途直接打架:两个临时目录存在的全部意义就是把 GB 级中间产物挪到另一块盘,terrain_global_base_path 指的是 224 MB / 4.3 万个文件的随包底图,放大盘同样正当;而「保存目录」自 v0.2.4 起本来就是全盘可选 —— 同为路径键却两套口径。
  • 仍然拒收的两种值只管功能正确性:两个临时目录不收相对路径(相对路径按进程当前目录解析,打包版从快捷方式启动时那不是安装目录,中间产物会落到谁也想不到的位置);terrain_global_base_path 不收空值(空值会把底图静态服务的根落到安装目录本身,而且底图判定必然失败)。

发版前的代码评审又拦下 11 条,其中 4 条会让你拿到错数据或看到 500

底图那三条都是回退功能自己带进来的新伤:

  • 一张缺瓦片不再把整幅底图换掉。 404 是每个 XYZ 服务说「这里没有图」的正常方式(Esri 在覆盖空洞和层级上限之外就会 404),可原本的实现把它和 403 同等对待:一张 404 就给整个源判 60 秒死刑、后续瓦片全换成另一家、还弹一句「底图已自动切换」——而根本没有任何故障。现在 404 原样透传,只有 403 / 429 / 5xx 和网络层异常才算源挂了。
  • 回退取到的瓦片不再被浏览器缓存一整天。 原本回退瓦片和正常瓦片一样发 max-age=86400,于是上游抖动 30 秒,另一家的影像就被烤进浏览器缓存 24 小时 —— 上游恢复之后你会看到两家影像拼在同一屏里,而且缓存不会再发请求,永远自己好不了。现在回退瓦片只缓存 60 秒。
  • 整条链都挂掉时,报的仍是你配置的那个源的状态码。 冷却会把失败过的源排到队尾,原本的实现取「第一个试的」的状态码,于是配置源一旦进过冷却,你看到的就是链尾那家的错误 —— 想查 Esri 为什么 403,屏幕上却是别人家的 504。另外,Cloudflare 那类回 520/521/525 的自建镜像原本会让服务端抛 LookupError 变成 500,真实状态码反而被埋掉,现在统一收敛成 502。

tif 信息卡那三条:

  • 自定义投影的 DEM 不再被当成经纬度。 国内 GIS 软件导出的自定义 Albers / 兰勃特 / 高斯克吕格,投影码写的是「用户自定义」,而 GeoTIFF 规范要求同时写上它的基准地理坐标系(如 CGCS2000)—— 原本的实现读到后者就当成了像素单位,把 50 万米、300 万米这样的坐标当作经纬度,给出一个精确、自信、彻底错误的覆盖范围,且不发任何警告。现在这种情况老老实实报「坐标系不可识别」。宁可说不知道,也不能给错的范围。
  • 服务端认不出 EPSG 码时,不再谎称「服务端缺少 GDAL」。 原本四种完全不同的失败(GDAL 没装、EPSG 码查不到、osr 抛异常、坐标换算失败)共用同一句提示,于是 GDAL 装得好好的人被指去排查一个根本没问题的安装。现在两种成因分开说。
  • 坏数据不再变成 500。 一个超长整数、一个不是对象的 JSON、一个越界坐标算出的无穷大,原本都会让这条接口 500(最后那种还会吐出 Infinity 这种 JSON 解析不了的字面量,卡片直接空白且不报错)。现在一律降级成正常的提示。顺带:这条接口只需要几 KB 的标签,原本却继承了 2 GB 的上传上限,现在超过 1 MB 直接 413;报错文案也终于跟着界面语言走,不再是中文界面里夹一句英文。

验证

  • 本版全量测试 1930 项通过 / 3 项跳过(开发机 Linux;跳过的只在特定平台上有意义,CI 上各平台的跳过数略有不同)。上一版是 1758 项。
  • 上面评审修的 11 条每条都配了会变红的用例:先在未修的实现上跑一遍确认它们确实失败,再修、再跑绿 —— 避免写出「怎么改都绿」的空断言。
  • 底图回退、同源约束与信息卡这三项另做了真机验证:起真服务、真浏览器,全程 91 个请求全部同源、零外部依赖(离线可用与「浏览器永远看不到上游地址」这两条硬约束都是这么验的);回退发生时确实换了图层上限与署名,同一状态重复轮询不重复弹提示。
  • 表单这两个从配置取初值的控件也顺手收了口:越界的 terrain_local_maxzoom(这个键没有写入校验,PUT /api/config 收得下 99)此前会让整张「数据处理」表单变成 :invalid —— 原生校验拦下 submit,「创建」点了没反应,连等高线任务一起建不了,且气泡弹不出来。现在渲染前就收敛掉,并且在日志里点名被丢弃的原值;认不出的档位同理。模板是这条链上唯一记不了日志的一环,在那儿悄悄修好的值,运维一辈子查不到。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.2.12

Choose a tag to compare

@github-actions github-actions released this 08 Aug 19:22

v0.2.12 —— 「取消」和「移除」两颗按钮下岗,删除任何时候都能点

先说结论:功能一个没少,反而少按一次。 以前想清掉一个正在跑的任务要「先取消、再删除」两步,现在直接点删除就行 —— 任何状态都受理。这正是 v0.2.11 发版说明里预告过的那件事:「下一版把取消和删除两个动作合并成一个」。

任务行的动作按钮从 7 颗减到 5 颗(说的是首页任务面板;/history 独立页一直只有删除一颗,没有变化)

  • 没了的两颗:「取消 ✕」(原本只对进行中的任务显示)和 「移除 ✕」(原本只对失败的任务显示)。
  • 留下的五颗:启动 / 暂停 / 恢复 / 预览 / 删除。
  • 为什么不算丢功能:
    • 「取消」的唯一实际用途就是给删除当前置步骤 —— 删除以前拒收正在跑的任务,你必须先把它停下来。现在删除自己会停:点下去行立刻消失,后台去收尾。这一步不需要你操心了。
    • 「移除」只对失败的任务有效,而且它只是把那一行从眼前拿走,数据库里的记录原封不动,下次刷新还在。删除是真的删,覆盖面比它大。

删除的确认框从两个减到一个,删正在跑的任务会先给你一句明确的警告

  • 以前点删除要连过两个框:先问「确定要删除这个任务吗?」,再问「清理下载产物」,第二个框的两颗按钮是「删除产物」和「保留产物」。第二个框问的是另一件事,可它的取消位(ESC、点空白处、左边那颗「保留产物」)看上去像在撤销整个删除 —— 实际上任务照删不误,区别只在产物删不删。现在只有一个框:任务删不删走「确定 / 取消」,产物删不删走框里的一个勾选框「同时删除磁盘上的下载产物」,默认不勾(跟以前按 ESC 的结果一致,保留产物)。取消、ESC、点空白处现在就是什么都不做,一个删除请求也不会发出去。
  • 删的是还没跑完的任务时,确认框会说清楚你会失去什么,而且三种状态分开说,不含糊成一句通用的:正在运行的说「删除会立即停止它,已下载的进度不保留」;还在排队的说「尚未开始,删除会把它移出队列」;已暂停的说「还没结束,删除会直接终止它」。上一版这句话不需要,因为后端会用 400 把这类删除挡下来(你看到的是「删除失败」)—— 这一版删除不再拒收任何状态,那层拦阻没了,就得把话说在前面。
  • 产物在后台清理时,界面会告诉你:勾了「同时删除磁盘上的下载产物」而删的又是正在跑的任务,产物得等工作线程收工才能删(见下一段)。以前这种情况界面只说一句「任务已删除」,你转头去文件管理器一看几十 GB 还在,分不清是该等还是该自己动手删。现在它会明说「任务已删除,磁盘产物正在后台清理」。

磁盘产物怎么清理:删的是正在跑的任务时,行先消失、文件后台删

  • 任务没在跑:行和文件同步删完,跟以前一样。
  • 任务正在跑:程序先给它发停止信号,然后立刻删掉记录、把界面上那行拿走并返回;等下载 / 切片线程真正退出后,后台线程再去删磁盘产物。之所以不能当场等,是因为拼接、DEM 合并、等高线 warp 这些 GDAL 阶段一跑就是几分钟到几十分钟,中途打不断 —— 当场等的话你的浏览器会一直转圈。
  • 万一在后台收尾之前程序被强杀,产物目录名已经记在数据库的待删清单里,下次启动会自动补删

升级影响:库里已有的「已取消」任务会变成「失败」(一次性,不可逆)

  • 「已取消」这个状态随功能一起从状态机里删掉了。你的数据库里如果还有这类记录,升级后首次启动时会自动改成「失败」,并写一句说明性的失败原因:此版本移除了「取消任务」,该任务原为已取消
  • 不做这次迁移的话,那些行在界面上会显示成一列「未知」。
  • 这个动作是一次性的、不可逆的;在意的话升级前备份一下 data/map_downloader.db

v0.2.11 里那两块「没有任何入口能触发」的准备代码,这一版通电了

  • DEM 地形切片中途可停:上一版加了登记与停止信号,但没有任何按钮能用到它。现在删除一个正在切片的 DEM 任务,就会真的把它停下来。
  • 「待删产物清单」表开始真的往里写:上一版只建了表、零写入。现在删一个正在跑、**且勾了「同时清理磁盘产物」**的任务,就会往里记一笔,作为后台清理的兜底线索。

底图换了一条路:不再由浏览器直连上游,改走服务端转发

  • 以前地图底图是浏览器直接去上游取瓦片,两个坑:上游返回 4xx 时错误页不带 CORS 头,浏览器把真实状态码(实测是 Esri 的 403)盖成一句 "blocked by CORS policy",看报错的人会去查一个根本不存在的 CORS 问题;更要命的是浏览器不吃配置页里的代理,底图和下载因此走两条出网路径——代理配好、瓦片哗哗下,底图照样可以是一个蓝色球体。
  • 现在底图瓦片由后端转发(同源 /basemap/{z}/{x}/{y}),与下载共用同一套代理解析。配好一个,两个都通。
  • 配置页新增「地图底图」一项:默认 Esri 卫星影像(国内直连可达),可选 Google 卫星 / 路网、「跟随下载源」,或自填一个 {z}/{x}/{y} 模板。底图和下载源从此是两件事——底图决定你框选时看到什么,瓦片服务器列表决定你下载到什么。
  • 预设只收 WGS-84 的源,不会有高德 / 腾讯:它们按 GCJ-02 切片,在国内偏移 100–700 米,底图上框住的山谷,下载下来是隔壁那个。

顺带一处界面变化

  • /history 页的状态筛选里,「已取消」那枚筛选按钮跟着没了 —— 这个状态已经不存在,它永远筛不出任何一行。剩下「全部 / 进行中 / 失败 / 已完成」四枚。

这一版还修了一轮全项目评审查出来的问题(完整证据见 docs/reviews/2026-08-08-full-project-review.md

先说三条会让你拿到错数据的:

  • DEM 边界外那一圈「假地形」没了。 以前只要 DEM 的北边界或东边界正好压在瓦片格线上(1°×1° 的高程数据块几乎总是如此),每一级都会多出一整行/列瓦片;它们与真实数据零重叠,内容是把最边上一行像素向北抹平的一块台地,而且被声明成「可用」,Cesium 不会回落到底图。表现是:真实地形旁边贴着一块几十度宽的高原,HTTP 全 200、任务显示完成、日志一句报错都没有。
  • 随包底图搬家不会再搬坏。 升级时底图缓存要从 downloads/ 挪到 assets/(224 MB)。以前是直接往目标位置拷,跨盘时一旦中断(关机、磁盘满),目标位置会留下「有索引文件、没有瓦片」的半棵树 —— 而程序只看索引文件就判定底图可用,于是高程全错(实测把 4154 米读成 −744 米),并且这次迁移被记成已完成,永远不会重试。现在改成拷到临时名字、拷完才改名,中断就等于没搬,下次启动重新解压。
  • 切片进度写库失败不再作废整个作业。 切片每秒往数据库记一次进度,这个写操作以前没有兜底:一次数据库锁冲突或磁盘写满,就会让一个已经跑了几小时、瓦片 99% 已经落盘的切片作业判失败,而切片没有断点续跑,重来要从最粗的层级全部重算。现在记账失败只写一行警告。

然后是任务卡住 / 状态说谎那一类:

  • 任务不会再永久卡在「运行中」。 有两条路会留下「数据库说在跑、其实没有线程」的任务:一是启动时连不上数据库这类异常绕过了原有的兜底;二是删除任务时数据库提交失败,工作线程停下来了却没人改状态。卡住之后「开始」按钮会被拒(只接受待处理/已暂停),唯一的出路是先点一次「暂停」或者重启程序。现在线程退出时会自查一次,卡住的行直接判失败,可以重新开始。
  • 暂停之后点「恢复」不会再报「已在运行」。 大区域拼接一跑十几分钟,中途打不断;这段时间界面显示「已暂停」,而每次点恢复都返回「任务已在运行」—— 两个互相矛盾的说法。现在这种情况会明确告诉你「上一轮还在收尾,请稍后重试」,而且拼接本身加了停止检查点,等待从「整个拼接」缩短到「当前这一张瓦片」。
  • 删除任务不会再谎报「产物已删除」。 产物目录里只要有一个文件被别的程序占着(Windows 上资源管理器预览、杀毒扫描都会),以前接口照样回「已删除」,而整个瓦片金字塔还在盘上,且没有任何补删记录。现在如实回报,并登记下来,下次启动自动重试。
  • 选「保留产物」不会再留下找不回来的目录。 删任务时如果选择保留磁盘产物,数据库里那行一走,产物目录就再没有任何引用,启动清扫也找不到它 —— 一个半成品切片目录(往往还带着一个与源数据同量级的中间文件)从此永久占着盘。现在会登记一条引用,响应里也直接告诉你保留在哪。

等高线:填错参数当场就说,不再让你白等几小时

  • 颜色填成 #zzzzzz 这种非法值,以前会一路通到渲染,然后每一张瓦片都失败,最后报一句「没有渲染出任何瓦片(请检查 DEM 覆盖 / 间距 / 缩放范围)」—— 指的三个参数全是对的。现在创建时就用渲染器自己的解析器校验,直接告诉你哪个字段的哪个值不对。
  • 等高距填 0.1 米这种值,单张瓦片要画上万条线,而且瓦片画到一半既不能暂停也不能删。现在创建时就要求不小于 1 米。

凭据与配置

  • NASA Earthdata 的账号密码不再出现在页面里。 以前配置页的密码框会把真实密码回填进 HTML,GET /api/config 也原样返回;同时首页把全部 45 个配置项(含密码和代理地址里的用户名密码)灌进一个页面级 JS 全局变量。现在密码框回填的是占位标记,接口也只回占位标记,页面里的 JS 变量只剩地图真正要用的 6 项。改密码、清空密码都照常。
  • Earthdata 登录只会把密码发给 NASA 自己的域名。 以前判断「是不是跳到了 NASA 登录页」用的是子串匹配,https://攻击者站点/?next=https://urs.earthdata.nasa.gov/... 能骗过它。
  • 配置里的路径和网址会被校验了。 以前除了十来个键,其余配置项(包括所有路径类和网址类)填什么都收。现在路径必须落在程序自己的目录范围内,网址必须是同源相对路径或正常的 http(s) 地址。

键盘和读屏

  • 状态栏的「任务数」「光标坐标」「选区范围」三处以前只能用鼠标点(是 <span> 不是按钮),键盘用户既打不开任务面板,也复制不了坐标。现在是真正的按钮。
  • 状态筛选、主题、语言那几组分段按钮以前只用颜色表示选中,读屏软件读不出来;侧滑面板行为上是模态框却没有对应标记,Tab 会跑到被遮住的控件上。都补了。

给自己构建的人

  • ./build.sh 修好了。 它开头要检查 GDAL 版本,判据却是「requirements.txt 里有没有精确钉版本」—— 而那个文件里有一整段注释说明为什么不能精确钉。于是脚本在读取那一行时就静默退出,连准备好的报错都打不出来(set -euo pipefail 让失败的 grep 直接终止脚本)。CI 走的是另一条命令,所以发版一直正常,没人发现。现在两个构建脚本共用 scripts/check_gdal.py,判的是「装出来的版本落在声明范围内」加「_gdal_array 在位」——后者才是真正会让所有高程/地形/等高线作业炸掉的那个坑,而它读版本号是读不出来的。
  • 单实例冲突提示改了。 以前它建议「确认上一个实例已崩溃就删掉锁文件」。锁锁的是文件本身而不是路径,删掉它只会让下一个实例新建一个文件再锁上 —— 两个实例同时认为自己独占,而第二个实例启动时的清扫会删掉第一个实例正在写的中间产物。锁在进程退出时由系统自动释放,本来就不需要手动删。
  • 发布包里不再夹带冒烟测试留下的日志和半成品解压目录;Nuitka 与 matplotlib 钉了版本(前者用的是私有 API,上游一改签名就会在打完 tag 之后炸 Windows 构建);构建现在会校验 templates/static/ 的关键文件真的进了产物(少了的话程序能启动、首页也 200,但地图是一片白,日志里什么都没有)。
  • 多进程池在 Linux 上从 fork 改成 spawn,与 Windows / macOS 一致 —— 从多线程进程 fork 是 Python 3.12 起就在警告的用法,3.14 会改默认值。

⚠️ 破坏性变更(只影响直接调 HTTP API 的脚本;用界面的不受影响)

变更 谁受影响 怎么改
四条 POST .../cancel 端点删除(/api/tasks/<id>/cancel/api/dem/tasks/<id>/cancel/api/contour/tasks/<id>/cancel/api/terrain/local/tasks/<id>/cancel 调取消接口的脚本 改调对应的 DELETE(现在任何状态都能删);只想停不想删就用 pause
失败的任务不能再重新开始(start 只收 pending / paused 直连 API 的脚本(界面上本来就没这个入口) 删掉重建一个
四条管线的 DELETE 遇到正在跑的任务不再回 400,改为 200;同时要求清理磁盘产物时响应带 files_deferred: true(产物留给后台删) 靠 400 判断「删不了」的脚本 files_deferred 决定要不要等产物清理
删除响应新增 files_deferred 字段;它为 true 时不下发 files_removed / files_message没要求清理产物时这个字段根本不出现delete_files 对地图 / 高程 / 等高线默认关,界面上选「保留产物」也走这条) 解析删除响应的脚本 从三态处理改成四态处理,并按「键不存在」处理默认路径
本地地形 DELETE 找不到任务时从 400 改成 404 靠 400 判断的脚本 四条管线现在统一 404
GET /api/configearthdata_password 回的是占位标记 __TF_UNCHANGED__,不再是真实密码 读这个接口取密码的脚本 拿不到真值了。把这个占位标记原样 PUT 回去等于「不修改」;要改就传新值,要清空就传空串
PUT /api/config 会拒收越界的路径类与网址类配置值(以前除十来个键之外一律照收) 写配置的脚本 路径要落在程序目录范围内;terrain_base_parent_url 要么是同源相对路径,要么是正常 http(s) 地址
POST /api/contour/tasks:等高距小于 1 米、颜色值非法一律 400(以前收下、跑几小时后再失败) 建等高线任务的脚本 按 400 里回的字段名和值改
等高线任务的 dataset 只接受 upload / dem_task;其它值(如 ASTGTM.003)会明确失败 直接往库里塞行或调老接口的脚本 先在「数据下载」里下 DEM,再基于那个 DEM 任务建等高线任务
删除响应在「保留产物」时新增 files_retained_path 字段 解析删除响应的脚本 可选:用它告诉用户产物留在哪

验证

  • 本版全量测试 1757 项通过 / 3 项跳过(跳过的只在特定平台上有意义:Windows 文件锁、POSIX 专属的锁语义用例)。上一版是 1457 项。
  • 上面「破坏性变更」各条都有常驻用例钉着(含一条反向断言:四条 /cancel 路由不许重新出现在路由表里);「删正在跑的任务」四条管线另有各自的端到端用例。
  • 这一轮评审修的每一条都做过变异校验:把实现换回旧写法,对应用例必须变红 —— 避免写出「怎么改都绿」的空断言。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.2.11

Choose a tag to compare

@github-actions github-actions released this 07 Aug 17:39

v0.2.11 —— 修好高程下载的两处老毛病(不用重做任何已完成的任务)

先说结论:升级即生效,不需要重下或重切任何东西。 这一版只修 DEM(高程)下载的两个问题,外加一批为下一版「简化任务操作」做的内部准备。

下载中的临时文件不再堆在你的产物目录里

  • 现象:downloads/dem/dem_task_2/ 里混着 Copernicus_DSM_COG_10_N41_00_E086_00_DEM.tif.part 这种半成品。
  • 原因:程序写大文件时为了防止断电留下残缺文件,会先写一个临时名、写完再改名。这个临时文件的落点算错了 —— 代码注释里写的是「放在缓存目录」,实际却放进了你的任务产物目录。而开机自动清理只扫缓存目录,所以那些残留既碍眼、又永远清不掉。
  • 已修:开着缓存时临时文件写在 cache/dem/ 下,下载完成并校验通过后才原子地放进任务目录。你的产物目录全程只会出现最终文件。关掉缓存时没有缓存目录可用,退回原来的位置写 —— 强行绕道缓存会让不用缓存的人凭空多一次跨盘整份拷贝。
  • ⚠️ 已有的残留不会自动清理:如果你的任务目录里已经躺着 *.tif.part,需要手工删除(find <你的输出目录> -name '*.tif.part' -delete)。在用户自选的全盘路径里按通配符删文件,这个风险不该由程序替你担。

下载高程时任务行终于能看到速度

  • 现象:DEM 任务下载中,速度一直显示 0 B/s
  • 原因:速度只在「一整个文件下完」时才记一笔。而单个 Copernicus 高程文件是 30–50 MB,实测走完要几分钟 —— 这几分钟里一次都不记,前端 5 秒收不到新数据就判定「停了」,于是显示 0。
  • 已修:改成下载过程中每 0.25 秒汇报一次真实收到的字节数。实测下载一个 35.7 MB 的文件,全程 923 次汇报,速度连续可见。
  • 顺带修正了一个口径问题:命中本地缓存的文件此前也会被算进「网速」,那其实是读硬盘,会让数字虚高一个数量级。现在只统计真正走网络的字节。

等高线任务同享这两个修复 —— 它和 DEM 下载用的是同一个引擎。

这一版还有一批你看不见的改动

  • DEM 地形切片现在能被中途停止了。 之前它完全停不下来:切片线程既不登记、也收不到停止信号,只能靠杀进程中断。但本版没有任何按钮能触发它 —— 这是给下一版「简化任务操作」(把取消和删除两个动作合并成一个)铺的路。
  • 新增一张「待删产物清单」表,为下一版的「删除任务时行立即消失、磁盘产物后台清理」兜底。同样,本版没有任何代码会往里写。
  • 修掉一个 Windows 上的代理探测缺陷(上一版引入,本版发版构建时才暴露):Windows 的环境变量不分大小写,程序读 HTTPS_PROXYhttps_proxy 拿到的其实是同一个,于是同一个代理会被排进候选列表两次。最终选出的代理是对的(后面还有一道去重),所以你不会看到任何异常 —— 但探测过程会对同一个地址白跑一次网络验证。在 Linux / macOS 上同时设置大小写两份也会触发,一并修了。
  • 之所以现在就合进来:这两块各自独立、可单独验证,而且第一块本身就修掉了「切片停不下来」这个现存缺陷。数据库会在升级后首次启动时自动建表,不需要你做任何事。

验证

  • 1478 项测试全部通过(三平台 CI 各跑一遍)。
  • 真实网络下载了一个 35.7 MB 的 Copernicus 高程文件(经代理):下载全程任务目录零 .part,临时文件只出现在 cache/dem/ 下,收尾时两边都无残留,在途字节累计与文件实际大小一致。
  • 「切片能停」这条做了额外核对:确认本版没有任何入口能触发它,所以切片的执行路径与上一版逐字等价 —— 这是「不改变任何用户可见行为」这句话的依据,不是推测。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.2.10

Choose a tag to compare

@github-actions github-actions released this 07 Aug 00:24

v0.2.10 —— 四个「任务报成功、产物其实是坏的」修复(PNG 导出过的图要重新导出)

先说结论:如果你用过 PNG 格式导出地图,那些文件里没有地理信息,需要重新导出一次。 另外三个修复都是「本来会静默出错、现在会明确报错」,不改变正常任务的结果,升级即生效、不必重做任何已完成的任务。

这一版没有新功能,全部是把四处「出错了却不吭声」的地方堵上。它们的共同形态是:任务显示完成、没有任何警告、文件也确实生成了、打开尺寸都对 —— 但数据缺了一块。这类问题不主动找就永远发现不了。

PNG 导出的图丢了全部地理信息(用过 PNG 导出的需要重新导出)

  • 选 GeoTIFF 输出不受影响,只影响选 PNG 的情况。
  • 原因:PNG 这种格式内部没地方存坐标,GDAL 把坐标和投影写在旁边一个同名的 .aux.xml 小文件里。程序为了防止写一半断电留下半成品,采用「先写临时名、写完再改名」的做法,但改名时只搬了图片本身,那个存坐标的小文件被当成临时垃圾删掉了。
  • 后果:导出的 PNG 拿到 QGIS / ArcGIS 里打开,会落在坐标原点而不是它该在的位置。图像内容本身是好的,只是不知道自己在地球上的哪里。
  • 已修:坐标文件跟着图片一起改名。修完实测导出的 PNG 带正确的坐标与 EPSG:4326 投影。

大区域拼接超过 4 GB 时,下半张图是空白的

  • 只在单个缩放层级拼出来的图超过 4 GB 时发生 —— 大致是几万张瓦片起步的大范围高精度下载。
  • 原因:GeoTIFF 有个古老的 4 GB 上限,超过要用扩展格式。GDAL 只在不压缩的时候会自动判断要不要用扩展格式,而本程序一直是带压缩写的,于是它一律按老格式建文件,写到 4 GB 就写不进去了。
  • 后果最狠的地方在于它完全不报错:文件停在 4294967275 字节,但程序问它「你多大」,它照样回答完整尺寸;图片左上角和源数据一模一样,右下角全是空白。任务标记完成,没有任何警告。
  • 已修:显式要求 GDAL 在需要时使用扩展格式。地形那边从 0.2.8 起就是这么做的,这次把地图拼接这条路径补齐。

磁盘写满 / 配额超限时,拼接出的坏图会被当成正品

  • 原因:程序此前只看 GDAL 「有没有返回一个对象」来判断成功。但写盘失败时 GDAL 照样返回正常对象,错误只记在它内部的日志栈里 —— 那次 4 GB 实验里,栈里堆了一万多条写入失败记录,而程序一条都没看。
  • 已修:写完之后主动检查 GDAL 的错误记录,有失败就报错并删掉半成品,不让它变成正式文件。
  • 说明一个仍未覆盖的边角:如果写失败只发生在最后一次落盘上,GDAL 连错误记录都不留,这道检查也拦不住。要彻底解决得校验产物完整性,本版没有做。

地形任务里有一幅 DEM 文件损坏时,会静默少切一整块地

  • 只影响一个任务包含多幅 DEM 的情况(下载范围跨了好几个分幅)。
  • 原因:把多幅 DEM 拼在一起时,遇到打不开的文件(下载中断留下的空文件、磁盘坏块),GDAL 只打印一行警告就跳过它,然后返回一个「看起来完全正常」的结果。而后续所有的校验都是拿产物和这个已经缺了一块的拼接结果对比 —— 当然处处一致。
  • 后果:地形切出来少一块,瓦片请求全部正常返回,layer.json 也正常,前端一条错误都没有,只是那片区域没有地形。
  • 已修:拼接前逐个检查每份 DEM 能否打开,并核对拼出来的范围是否等于所有输入的范围之和,对不上就直接报错并指出是哪个文件坏了。
  • 实测区分过一个容易搞混的情况:文件被截断但文件头完好时,GDAL 不会跳过它(能打开、尺寸也报得对),那种情况由原有的另一道校验接住 —— 两道防线各管一段,都需要。

⚠️ 由此带来一个你可能会当成新 bug 的变化:混用不同类型的 DEM 现在会直接报错

  • 具体说:一个地形任务里同时传入 Int16 的 DEM(例如 ASTER 导出)和 Float32 的 DEM(例如 Copernicus 导出),或者传入波段数不同的文件。
  • 以前:程序静默丢掉其中一类,少切一块地,任务显示完成 —— 你不会知道缺了。
  • 现在:整个任务失败,并在错误信息里点名是哪个文件、它的数据类型是什么、和第一个文件差在哪。
  • 这不是退步。GDAL 拼接多幅栅格时本来就要求所有文件的波段数、数据类型、投影一致,不一致的会被它悄悄扔掉。以前是「悄悄扔掉 + 报成功」,现在是「明确告诉你哪个文件不对」。
  • 怎么办:把 DEM 统一成同一种数据类型再传(用 GDAL 的 gdal_translate -ot Float32 之类转一下),或者分成两个任务各自切。
  • 最难发现的一种情况也堵上了:坏的那幅夹在中间时(左中右三幅、坏的是中间那幅),产物的宽高完全正确、看不出任何异常,只有中间那块数据全是 0。这种情况靠对比范围是查不出来的,现在改成直接问 GDAL「你到底用了哪几个文件」。

跑过等高线之后,地形切片的第一道错误检查会失效

  • 内部质量问题,不改变你能看到的任何东西,但会让上面那些防护少一层。
  • 原因:等高线模块开启了一个 GDAL 的全进程开关(把错误改成抛异常)。四条流水线跑在同一个进程里,所以你只要跑过一次等高线任务,地形模块那套「检查 GDAL 错误记录」的逻辑就再也读不到东西了 —— 错误以另一种形式抛出,不再进它读的那个记录栈。
  • 已修:地形的关键段落现在显式声明自己需要的模式,不再依赖「但愿没人动过这个全局开关」。顺带对未来的 GDAL 4.0 也免疫了(4.0 会把这个开关默认打开)。

关于 GDAL 版本(开发者相关,普通用户可忽略)

  • requirements.txt 里的 GDAL 从写死的 ==3.8.4 改成范围 >=3.8,<4。GDAL 的 Python 绑定必须现场编译、且版本必须与机器上装的 GDAL 库一致,所以它跟随机器而不是由我们选定:开发机 3.11.4、CI 3.8.4、Windows/macOS 3.8。写死任何一个值都会让另外两处卸载重编,而重编时缺少 numpy 支持,所有涉及数组读写的功能会直接崩。
  • 已在 GDAL 3.11.4 上完整验证。唯一有差异的输出是等高线的低缩放层级瓦片(GDAL 3.9 收紧了降采样选层规则),实测新结果更准(误差从 1.01.4 米降到 0.40.7 米),无需重做。

v0.2.9 的内容(未单独发版,一并包含在本次发布中)

切好的地形目录可以整个拷走用了(旧瓦片不必重切,但要享受这个才需要重切)

先说结论:地形任务的产出目录现在是自包含的 —— 拷到一台没装本程序的机器上,直接就能当地形源用。 以前那个目录里只有你下载范围内的那一小块,镜头一拉远就什么都没有;低层级要靠目录里写着的一个 localhost:5000 地址回头向本程序要,换台机器必然连不上。

旧任务不受影响,还按老方式工作(本程序在跑的时候照常看)。想要自包含就重切一次。

全球底图现在随任务植入

  • 切片跑完后,程序会把随包分发的全球底图(z0–z7,GEBCO 2024,含海底地形、带法线)的瓦片放进你的任务目录,并把 layer.json 合成一份完整的声明,同时删掉那个指向 localhost 的地址
  • 磁盘怎么算:底图与任务目录在同一个盘时用硬链接,多少个任务都只占一份 224 MB;跨盘时退回实体复制,每个任务目录多 224 MB。DEM 任务的输出路径是你自选的全盘路径,跨盘是常态而不是例外,按后者预留更保险。
  • 硬链接不影响「拷走能用」:tar 会把它们存成归档内的链接(解出来仍是完整文件),zip 和复制粘贴直接展开成独立文件。副作用是「这个目录占多少磁盘」变得不直观 —— du 对同一份数据只算一次,看到的数字会随统计顺序变化。

底图不用再手工还原了,而且不用等

  • v0.2.8 把底图打进了安装包,但还原脚本没进去,只能让你手工敲 copy /b / cattar。现在程序一启动就在后台解压,不用你管。
  • 启动不会因此变慢一秒:解压跑在后台线程里,服务照常在几十毫秒内起来。以前这件事是等到你第一次切地形时才做的,那几分钟里任务进度条一动不动,看着就像卡死了。
  • 底部状态栏右侧实时显示「底图解压 47%」,所有页面都看得到(首页、历史、配置页)。解压完自动消失。窄屏下会临时挤掉几个次要读数给它让位,完事自动恢复。
  • 解压失败会一直显示「底图不可用」,鼠标悬停能看到具体原因(最常见的是装在只读目录)。这条不会自己消失 —— 否则你几小时后才会奇怪为什么地形产出不自包含。就算你是在失败很久之后才打开浏览器,也照样看得到。
  • 失败不影响切片:地形照常能切,只是产出目录不自包含(退回旧的级联方式)。
  • 两个任务同时切片时,后到的那个会等第一个解压完,不会重复解压。中途关窗口也不会留下一个「看着像好的、其实缺瓦片」的半成品。
  • 解压最后一步会重试。实测发现过一次:4.3 万个文件全部写完之后的最后一步改名被系统拒绝(Windows 侧的杀毒软件正在扫刚落盘的文件、还攥着句柄),十几分钟的解压全部白费。现在遇到这种瞬时占用会退避重试,最多等 15 秒。
  • 解压位置从 downloads/terrain/base_z8 换到了 assets/terrain/base_z8(跟分卷放一起:assets/ 是随包带的数据,downloads/ 是你的产出)。升级时程序会自动把已有的底图搬过去,同一个盘上是瞬时的,不会重新解压 224 MB。所以别被 downloads/terrain/ 突然空了吓到。
  • 想提前把这几分钟花掉,或者怀疑底图坏了要重解:uv run python scripts/unpack_base_terrain.py(加 --force 强制重解)。从源码运行才有这个脚本,exe 用户不需要它。

顺带解决了一个陈年问题:拉远看时地形是假的

  • 以前每个任务都会切出 z0–z4 的全球瓦片,可那里根本没有你的高程数据 —— 程序把 DEM 边缘的一行高程沿着法线方向拉出去填满整个世界。一块 4000 米的高原在全球视角下会糊成横跨半个半球的阶梯台地。更糟的是这些假瓦片还会把真正的全球底图整个挡住。
  • 现在任务只切 z8 及以上,z0–z7 全部交给底图,两边零重叠,也就没有「半张是真数据、半张是外推值」的接缝。切片还因此少切 682 张瓦片。

代价

  • 任务目录的文件数增加 43,690 个。删除任务时要删的文件变多,会比以前慢一些。
  • 第一次启动后的几分钟内磁盘会比较忙(在后台写 4.3 万个小文件),可能和同时进行的瓦片下载抢 IO。只有第一次,之后所有任务都直接复用。
  • 不切地形的人也会被占掉这 224 MB。启动就预热是为了「任何时候开始切片都是零等待」,代价是只用地图下载或等高线的用户也付这份磁盘。不想要就删掉 assets/terrain/ 里的两个分卷,程序会照常工作(只是没有底图)。
  • 跨盘时每个任务目录多占 224 MB(见上文)。
  • 配置页和历史页现在也会建立一个实时连接(此前只有首页有)——这是让它们能显示解压进度的前提。

底图不可用时会怎样

  • 有人删了 assets/terrain/ 里的分卷,或者程序装在只读目录(Program Files、只读介质)解压不出来 —— 这两种情况下切片照常完成,行为退回 v0.2.8:从 z0 开始切,layer.json 里写回那个级联地址。产出目录就不是自包含的。日志里会有一条说明原因的警告。

验证

  • 1298 项测试全部通过。
  • 覆盖到的边界:跨盘退回复制后内容逐字节一致;植入中途磁盘满时整批回滚、不留半个底图;任务自己的瓦片永远不被底图覆盖;重切时先摘掉上一轮的硬链接(否则会顺着链接改写全局底图);合成后的 layer.json 层号对齐、不含 localhost 地址;以及「解压 → 植入 → 合成」跑完之后目录真的自包含。
  • 解压与进度显示这部分做了真实环境端到端验收:启动后服务在几十毫秒内响应、后台解压全程进度实时更新、完成后状态栏元素自动消失、配置页在不加载三维地图引擎的前提下同样显示进度、解压失败时的红色提示与悬停原因、以及窄屏下中英文两种文案的完整可见性 —— 逐项在真实浏览器里量过,不是只跑了测试。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 下载体积:每个平台仍包含 167 MB 的全球底图分卷(自 v0.2.8 起)。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md

v0.2.8

Choose a tag to compare

@github-actions github-actions released this 05 Aug 18:16

v0.2.8 —— 地形的四个静默失败修复 + 界面可切英文 + 切片进度终于看得见(已有地形瓦片必须全部重切)

先说结论:已经切好的地形瓦片必须全部重切,否则地形部分的修复对你没有任何效果。 四个互相独立的原因,任何一个单独成立都要求重切。界面语言、切片进度、启动清扫这三块与瓦片无关,升级即生效。

原因一:此前的地形瓦片 Cesium 根本解不出来 —— 地形从未真正渲染过

  • quantized-mesh 里的 triangleCount 字段被写成了索引元素数(正确值的 3 倍),读端按它去取索引直接越界报错。全程静默:瓦片请求全部返回 200、任务标记完成、页面不报错,只是地形出不来。
  • 这一版修好了写瓦片的代码,但已经写在磁盘上的瓦片仍然是坏的 —— 那个错误的数字就在瓦片字节里。

原因二:这一版往瓦片里新增了法线数据与择优三角化,旧瓦片里没有这些内容。

原因三:相邻瓦片接缝处的高程此前对不齐

  • 取样时的降采样格子跟着「这一批点的外框」走,而相邻两张瓦片的外框不同 —— 于是同一个经纬度位置,在左边那张和右边那张里被采成了不同的高程。合成数据实测公共边最差 20.2 米,真实山地在中间缩放层级上平均 3.4 米、最差 16.8 米(最高精度层刚好不触发降采样,所以那一层是准的)。
  • 表现是相邻瓦片交界处的地形对不上,以及开光照后接缝处的明暗断层。
  • 已修:降采样格子改为锚定在源数据的像素网格上,与请求范围无关。修复后同一批真实瓦片的 543 对相邻边全部逐点一致。这条改变了所有瓦片的高程取值,所以旧瓦片必须重切。

原因四:用多幅 DEM 拼出来的地形,接缝处最大差了 50.9 米

  • 只影响一个任务里有多幅 DEM 的情况(比如下载一片跨好几个 1°×1° 分幅的区域)。单幅不受影响。
  • 表现和原因三一样是接缝对不上,但根子完全不同,所以修完原因三之后它还在。实测 6 幅拼的天山地形,在某一个缩放层级上有 16 对相邻瓦片的公共边对不齐,最大 50.9 米
  • 原因说起来有点绕:DEM 文件里通常预存了几张缩略图(不同倍率的低分辨率版本),程序按需要的精度挑一张用。ASTER 数据预存的倍率是 2、3、4、7.988.98、16、63、80 倍 —— 有几档挨得特别近。而把多幅拼在一起读取时,程序会把请求拆给各幅分别处理,拆完之后「需要多少倍」这个数字就不再整齐了,于是相邻两张瓦片可能一张挑了 7.98 倍那张缩略图、另一张挑了 8.98 倍的。同一个经纬度,读到了两个不同精度的数据。
  • 已修:一个任务里有多幅 DEM 时,先把它们合并成一个完整文件再切,并且重新生成一套倍率规整(2/4/8/16…)的缩略图。修复后同一批数据 8565 对公共边(含横向与纵向)全部精确为零。
  • 顺带变快了:合并后低缩放层级的取样从每张 2.3 毫秒降到 0.2 毫秒。
  • 代价:切片期间临时多占一份磁盘,切完自动删除。留量按数据源估:ASTER 实测约为源的 78%(6 幅 118 MB 的数据多占 92 MB),而默认的 Copernicus GLO-30 精度更高,约为源的 1.9 倍 —— 按后者预留更保险。空间不够时这一步会直接报错并清掉半成品,不会留下残骸,也不会交出一份缺数据的地形。

另一个静默失败:地形高程整体是错的

  • layer.json 里的 parentUrl 默认值以 /layer.json 结尾,而 Cesium 把这个值当目录用(自己再拼一次 layer.json),于是请求 404。Cesium 对这个 404 不报错,而是塞进一个假的高程图图层,并且这个错误假设会污染整个地形数据源 —— 连任务自己的瓦片也被按错误格式解析。
  • 实测:4154 米的山峰被解成海平面以下 744 米,而页面一切正常、控制台一条错误都没有。
  • 已修,但同样只对新切的瓦片生效:存量 layer.json 里写死的还是坏值。配置表里那一行也仍是旧值(这一版改的是新建数据库的默认值,不动已有的行),不过写 layer.json 时会统一规整一次,所以重切就能兜住,不需要手动去改配置。

逐顶点法线与地形光照开关

  • 每张瓦片额外写入逐顶点法线(每个顶点 2 字节)。地图工具栏多了一个「光照」按钮,默认关闭:它是纯渲染端开关,开关不需要重切瓦片。开着会与卫星影像自带的光照叠加,未必好看,所以数据先备着、什么时候开由你决定。这个偏好只存在浏览器本地,不进配置表。

全球底图现在随包分发,不用再自己准备

  • 以前的全球底图要你自备几 GB 到上百 GB 的全球高程数据自己切;这一版直接把切好的底图打进了安装包(GEBCO 2024 数据源,覆盖全球含海底地形,带法线)。
  • 首次使用前要还原一次,把 assets/terrain/ 里的两个分卷拼回 downloads/terrain/base_z8/⚠️ 下载 exe 的用户目前没有现成脚本可用(还原脚本只在源码仓库里,且需要 Python 环境),请在解压目录下手工执行一次:
    • Windows:copy /b assets\terrain\base_z8.tar.gz.partaa+assets\terrain\base_z8.tar.gz.partab base.tar.gz 然后 tar -xzf base.tar.gz -C downloads\terrain(Windows 10 起自带 tar)
    • Linux / macOS:cat assets/terrain/base_z8.tar.gz.part* > base.tar.gz && mkdir -p downloads/terrain && tar -xzf base.tar.gz -C downloads/terrain
    • 从源码运行的话直接跑 uv run python scripts/unpack_base_terrain.py,它会自己判断是否已还原。
      还原后 downloads/terrain/base_z8/ 下应有 layer.json 与 z0~z7 八层目录,中间文件 base.tar.gz 可以删掉。
  • 不还原也不会出问题 —— 切片时检测到底图缺失就不写那个级联地址(这正是上面「原因二」那个坑的修复)。只是镜头拉到高程数据范围之外时没有地形。
  • 分成两卷是因为打包后 167 MB,而 GitHub 单文件上限是 100 MB。
  • ⚠️ 如果你手上有 2026-08-05 之前自己切的旧底图,它里面没有法线。开光照后它覆盖的区域会显示为近乎纯黑的一块 —— 用上面的还原脚本换成新的即可。

三角化:逐瓦片挑更小的那一个

  • 新增了自适应三角化(Martini/RTIN),能按地形起伏把三角形数减掉八成。但不是无条件用它:实测 11 万张配对瓦片,压缩后山地反而涨 17.6%、丘陵涨 9.8%,只有平缓地形省 54.2%。原因是瓦片以压缩形式落盘和传输,而压缩后规则网格每个三角形只占约 0.91 字节、Martini 要约 4.04 字节,贵 4.4 倍 —— 减面必须超过 77% 才能在体积上打平,山地做不到。
  • 所以最终做法是:每张瓦片两种都编一遍,取压缩后更小的那一个。单张瓦片的体积不可能比原来更差。
  • 高程精度一步没让:误差预算参数保持原值,没有为了省体积去放宽它。

界面可以切英文了(zh / en)

  • 配置页「外观」区块多了一个「语言」开关,两个选项:中文 / English。切换后整页刷新一次(页面是服务端渲染的,语种必须先到服务端)。偏好存在浏览器 cookie 里,默认中文。
  • 覆盖范围:所有页面文案、按钮、提示气泡、接口返回的错误与确认文本、以及填错参数时的即时校验提示。刻意不翻的三类:日志、启动时那段控制台横幅、以及后台任务写进数据库的错误文本 —— 它们是给排查问题用的,写进去那一刻的界面语种说明不了将来是谁在看。
  • 漏翻不会静默:翻译表里查不到的词条,界面上直接显示词条名(形如 js.tasks.status.running),而不是偷偷回落成中文。
  • 英文界面下地图工具条的按钮会按文字宽度自动伸展 —— 英文标签比两个汉字宽一倍不止(Settings / Zoom out 约 44~52 像素),原来 32 像素的方按钮会把它裁成「etting」。中文界面的按钮尺寸一个像素没动。

地形切片的进度终于看得见

  • 此前的实际情况:下载任务显示「已完成」之后,切片还要再跑几十分钟到几小时,而这段时间任务行上什么都不显示 —— 后端一直在广播进度,但前端根本没人监听,只能靠手点详情弹窗刷新才看得到。
  • 现在任务行会实时显示「合并 DEM 43% …」「建金字塔 …」「切片中 1204 / 8192 …」。本地地形任务此前连进度回调都没接,这次一并补上。
  • 为什么分成两种文案:合并 DEM、建金字塔、等高线重投影这几步跑在「总共有多少张瓦片」算出来之前(要先有合并后的栅格才能建采样器,有采样器才知道总数),压根没有分母,所以只能报百分比;进入瓦片循环之后才有 n/m。

启动时会清掉上次没删干净的中间文件

  • 上面「原因四」的合并会在切片期间临时生成一份与源数据同量级的中间文件(GB 级任务就是 GB),正常结束会自己删。但直接关窗口或强杀进程时删不掉,会一直占着磁盘。
  • 现在启动时会扫一遍并清掉它们。判断「这份文件还有没有人在用」看的是文件名里记的进程号而不是修改时间 —— 中间文件写完时间就冻住了,而切片还能再跑几小时,按时间判断几乎必然误删。写它的进程还活着就跳过。
  • 扫描范围包含数据库里记录的任务输出目录(DEM 任务的输出是你自选的全盘路径,不一定在程序目录下)。

其他

  • 底部状态栏改成悬浮胶囊(不再占一整行布局),窄屏下自动收紧。
  • 启动横幅不再打印数据库路径。

代价

  • 切片 CPU 增加约 36%(算法线 + 两种三角化各跑一遍)。换来的是法线数据与逐瓦片最优的字节数。
  • 瓦片体积因法线而增大:压缩后规则网格分支 +55.4%、Martini 分支 +22.5%。

验证

  • 1192 项测试全部通过(Linux / Windows / macOS 三平台 CI 各跑一遍)。
  • 浏览器端实测:真实 DEM 切片后经 HTTP 取回,Cesium 解出的高程与源 DEM 交叉验证 —— 源 DEM 真值 2672 / 1086 / 4154 米,Cesium 读到 2658.7 / 1092.9 / 4161.9 米;法线段完整,长度与顶点数逐张对得上;随包的全球底图同样逐层验过(法线齐全、级联接得上、没有退化成错误格式)。

通用说明

  • 下载安装:从下方 Assets 下载对应平台压缩包(terraforge-windows.zip / terraforge-linux.tar.gz / terraforge-macos.tar.gz),解压即用,无需安装 Python 环境。
  • 这一版下载体积明显变大(每个平台约多 167 MB):全球底图的分卷随包分发,见上文「全球底图现在随包分发」。
  • 首次运行:启动可执行文件后,浏览器访问 http://localhost:5000 ;代理、并发、缓存管理等在「配置」页修改。
  • 历史版本:完整更新历史见仓库 CHANGELOG.md
  • 使用文档:见仓库 README.mddocs/guides/QUICKSTART.md