v0.2.10
·
118 commits
to master
since this release
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 不会跳过它(能打开、尺寸也报得对),那种情况由原有的另一道校验接住 —— 两道防线各管一段,都需要。
- 具体说:一个地形任务里同时传入 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.0
1.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/cat再tar。现在程序一启动就在后台解压,不用你管。 - 启动不会因此变慢一秒:解压跑在后台线程里,服务照常在几十毫秒内起来。以前这件事是等到你第一次切地形时才做的,那几分钟里任务进度条一动不动,看着就像卡死了。
- 底部状态栏右侧实时显示「底图解压 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.md 与 docs/guides/QUICKSTART.md。