上一篇写到,GeoD 最初只是想把选区、下载、拼接、坐标和裁剪这几步接起来,让我在做 GIS 数据准备时少开几个软件。
文章结尾,我留了一个问题:那个最早的 Python 小工具,后来是怎样变成今天这个跨平台桌面应用的?
翻完仓库历史后,我发现这段过程并不是一次规划周全的“技术升级”。
它更像两次被现实推着发生的重写:第一次解决桌面应用的底座,第二次解决越来越复杂的界面和状态。
在这两次重写里,还有一个从第一天就参与其中的角色:AI。
GeoD 从最早的 Python 原型开始,就是我使用 AI 辅助开发的项目。但它并不是按下按钮就自动做出一个产品,而是随着项目成长,从早期的代码助手,逐渐变成参与分析、修改、测试和发布的编程 Agent。
第一版,先把想法做出来
2025 年 12 月 4 日,GeoD 的第一个版本提交到仓库。
当时的技术组合很典型:Python 负责下载和影像处理,FastAPI 提供本地接口,HTML、CSS、JavaScript 和 Leaflet 负责地图界面,再用 PyWebView 把网页装进一个桌面窗口。
它甚至同时支持两种启动方式:可以作为本地 Web 服务运行,也可以通过 desktop.py 打开桌面窗口。
Python 首版正在按边界下载影像2025 年 12 月的 Python 首版。选区、图源、下载进度和矢量数据已经放进了同一个界面。
这套方案最大的优点不是性能,而是快。
Python 里已经有 Rasterio、Pillow、Fiona、PyProj 等成熟工具。我要验证的又不是一种新的图像算法,而是“这条工作流放进一个窗口里,究竟有没有用”。
这个首版从一开始就是我和 AI 一起完成的。我负责把 GIS 工作里真实的步骤、输入输出和使用习惯讲清楚,AI 帮我快速搭建 FastAPI、地图页面、下载接口和桌面封装,再由我运行、检查结果并继续修改。
现在回头看,那时的协作方式还比较直接:想到一个功能,把需求告诉 AI,生成代码,运行,报错,再继续修。没有完整的项目记忆,也没有今天这套测试和发布流程,但它把“我有一个想法”到“真的出现一个能运行的软件”之间的距离大幅缩短了。
所以第一版没有必要从最理想的桌面架构开始。先把流程跑通,先得到一份带坐标的 GeoTIFF,先看它能不能解决真实问题,这比一开始讨论什么语言更重要。
从这个角度看,Python 版本完成了它的任务。
能运行,开始不等于适合长期维护
当工具只在自己电脑上运行时,很多问题不会马上暴露。
但只要开始发给别人,事情就变了。
第一版依赖 Python 3.10、FastAPI、PyWebView、PyInstaller,以及带有 GDAL 能力的 Rasterio。为了让 Windows 环境更容易安装,仓库里当时甚至直接放进了一份约 25 MB 的 Rasterio wheel。
这不代表 Python 或这些库不好。恰恰相反,它们让我很快完成了原型。但对于一个要交给普通用户安装的桌面工具,运行环境、原生依赖、打包体积和安装失败都会变成产品问题。
与此同时,下载任务也不再只是“发几个请求,然后保存图片”。
用户会一次下载上万张瓦片,会要求看到每个任务的进度,会暂停、取消和继续,会在程序重启后找回没有完成的任务。导出范围变大以后,整张影像一次性放进内存也不再可靠。
这些问题单独看都能继续补丁式解决,但它们共同说明了一件事:原型的底座开始限制产品继续往前走。
第一次重写,只换最需要换的部分
2026 年 2 月 6 日,仓库出现了一次很大的提交:Python/FastAPI + PyWebView 被替换为 Tauri 2 + Rust。
旧版 Python 后端、PyInstaller 配置和批处理脚本被移除,下载、瓦片计算、拼接、导出、任务和历史记录被重新放进 Rust 后端。
但有一个细节很重要:前端没有同时推倒重来。
HTML、CSS、JavaScript 和 Leaflet 基本保留,只增加了一层 Tauri IPC 适配。也就是说,这次重写真正处理的是桌面容器和核心任务,而不是顺手把所有代码都换成“更新”的技术。
Tauri 初期版本界面迁移到 Tauri 后的早期界面。外观仍能看到首版的延续,但应用内部已经换成 Rust 负责下载、任务和导出。
当时选择 Tauri 和 Rust,主要解决的是几件很具体的事:
- • 用系统 WebView 提供更明确的桌面应用边界;
- • 让取消、超时、重试和进度成为后端任务的一部分;
- • 用 Rust 图像库直接完成拼接和 TIFF 编码;
- • 通过 GitHub Actions 自动构建安装包和发布版本;
这次迁移不是因为“Rust 一定比 Python 好”,而是 GeoD 当时的问题,已经从快速调用 GIS 库,转向了长时间任务、内存、并发、状态和分发。
技术选择应该跟着主要矛盾变化。
这次重写,我不是一个人完成的
GeoD 从 Python 首版开始就在使用 AI 开发,只是 2025 年 12 月的提交还没有保留模型名称或联合署名。代码记录能证明当时做了什么,却不能完整记录我和 AI 是怎样对话、怎样反复修改出来的。
仓库里第一组能够独立核验具体 AI 工具的证据,出现在 2026 年 2 月 6 日。
Tauri 桌面版重构、自定义图源、多任务下载、自动更新和 GitHub Actions CI/CD 的提交里,都保留了同一个联合署名:Co-Authored-By: Warp <agent@warp.dev>。
从 2 月 6 日到 2 月 8 日,共有 4 个提交带着 Warp 的联合署名。它们不只包含最初的 Rust 重写,还包括构建修复、安装包调整,以及 v2.0.0 的 BigTIFF 流式写入、断点续传和任务日志。
这时 AI 对我的意义,已经不只是补全几行代码。
我会给出目标、现有结构和需要保留的行为,让它跨文件阅读代码、提出修改方案并完成实现。我再运行程序,检查下载结果,判断功能是不是我真正想要的。
这种协作让一次原本工作量很大的技术迁移变得可行,但方向仍然要由人来定。比如保留原来的前端、只替换桌面容器和后端,就是为了控制重写范围,而不是让 AI 顺手把所有东西都换掉。
重写之后,难题并没有消失
换成 Rust 并不会自动得到一个稳定的下载器。
迁移完成两天后发布的 v2.0.0,才真正补上 BigTIFF 流式写入、暂停与恢复、任务日志、多边形裁剪修正等能力。
这里面最关键的是“流式”。如果下载一个很大的区域,最直观的做法是先在内存里拼成一张完整影像,再一次性写入文件。但范围和级别上去以后,内存占用会快速膨胀,最后不是下载慢,而是整个程序直接退出。
流式写入改变的是处理方式:数据按块进入最终文件,而不是等一张巨大的完整图片全部待在内存里。
所以真正解决大图问题的,不是把代码换成 Rust 这一个动作,而是重新设计任务和数据怎样流过整个程序。
后来又陆续加入断点续传、任务恢复、缓存、历史日志、自动更新和异常处理。这些功能在界面上可能只是几个按钮,背后却要求应用能够准确回答:任务现在是什么状态?哪些文件已经完成?程序突然退出后还能不能继续?
到 2026 年 4 月的 v3.2.0,GeoD 才把 macOS 和 Linux 构建也加入发布流程。所谓“跨平台”,也不是改一行配置就结束,它还包括依赖、路径、签名、安装包和持续集成。
第二次重写,发生在前端
第一次重写时被保留下来的原生 JavaScript 前端,后来也走到了自己的边界。
随着 DEM、Wayback 历史影像、3D Tiles、多级别下载、图源管理、任务中心和设置不断加入,一个页面已经不再是“加一点脚本的地图”。
2026 年 4 月 27 日的迁移规划里,旧前端的 app.js 已经增长到 5260 行、212 KB、167 个函数,index.html 也有 1135 行。
问题不只是文件长,而是地图、表单、弹窗、任务、设置和不同数据模式都在共享状态。修改一个地方,很容易让另一个模式出现回归。
静态前端后期的 GeoD 界面静态前端后期,GeoTIFF、3D Tiles 和历史影像已经进入同一应用,界面和状态明显比首版复杂。
于是有了第二次重写:React + TypeScript + Vite。
这一次同样没有选择“一次性替换”。旧 static 前端被保留,新 frontend 工程并行搭建,再按设置、地图、下载、Wayback、3D Tiles 等模块逐步迁移。每个阶段都要求能构建、能验证,也能回退。
2026 年 5 月 5 日,v3.4.0 发布,React 前端成为正式版本。
当前 React 版 GeoD 界面现在的 GeoD。核心仍然是地图和数据工作流,但不同模式、设置和任务已经被拆进更明确的模块。
React 并没有让下载更快,它解决的是另一类问题:让复杂界面的状态可追踪,让功能边界更清楚,让后续修改不必继续往一个几千行的脚本里叠加。
AI 也从工具变成了项目协作者
项目越来越大以后,我遇到了一个新的问题:AI 每次打开仓库,都要重新理解一遍上下文。
它可能不知道某个看起来多余的分支是为 Windows WebView2 留的,不知道 TIFF 的一个标签为什么必须用特殊方式写入,也不知道某次修复已经在真实用户环境里验证过。
如果只靠一段临时提示,模型可以很快写代码,也可以很快重复踩坑。
2026 年 5 月 11 日,仓库专门加入了 memories/ 项目记忆目录和 Windsurf 规则。里面记录构建命令、关键模块、协作边界、已知问题和高频踩坑,同时为 Copilot/Windsurf 一类工具准备跨会话的上下文。
这看起来只是几份 Markdown,实际反映的是开发方式的变化:我不再只问 AI“这个功能怎么写”,而是开始为它维护一份项目说明书,让不同会话、不同工具接手时先知道哪些事情不能想当然。
到了 2026 年 6 月以后,AI 参与的范围又扩大了一步。仓库中可以查到 22 个带 Claude 联合署名的提交,内容不只包括界面功能,也包括:
- • 修复流式导出后不规则边界变成外接矩形的问题,并补充回归测试;
- • 分析 GitHub Actions 的耗时,把每个平台重复编译 Tauri CLI 改成下载预编译工具;
- • 根据真实 issue 查找跨前端、Rust 后端和文件输出链路的问题。
现在,我更多会把用户反馈、截图、issue、日志和实际运行结果一起交给 Codex、Claude 这类编程 Agent。它们可以读仓库、修改代码、运行测试、检查 GitHub Actions,甚至协助整理 issue 和准备发布说明。
AI 参与得更深了,但我的工作并没有变成只写一句需求然后等待结果。
AI 让开发变快,也让验证更重要
GeoD 的开发过程中,AI 也做过不少“看起来已经修好”的修改。
有时编译和测试都通过了,真正打开应用后,鼠标点击却没有反应;有时一个下载任务可以暂停,创建第二个任务以后就暂停不住;有时为了修复弹窗遮挡,AI 会选择直接关闭另一个面板,但用户恰恰需要一边看回答一边操作那个面板。
这些问题很难只靠代码静态分析发现,因为它们涉及真实交互、多个任务的时序,以及用户对“暂停”“停止”“删除”这些词的理解。
所以后来我的协作方式逐渐固定下来:
- 2. 让 AI 阅读相关代码,但不急着大范围重构;
- 3. 修改后运行单元测试、构建和针对性的并发测试;
- 6. 确认后再合并到正式版本,并回复对应 issue。
AI 很擅长扩大一个人的执行能力,但它不会自动知道产品应该是什么样。暂停和停止是否应该同时存在,一个任务应不应该拆成多个状态卡片,这些都不是代码生成问题,而是产品判断。
对我来说,AI 开发真正有价值的部分,不是“它替我写了多少行代码”,而是我能不能把问题描述、工程上下文、自动测试和真实用户验证连接成一个闭环。
我从这两次重写里学到的事
回头看,GeoD 的两次重写有一个共同点:都不是从技术潮流开始的。
第一次,是原型已经验证了价值,但桌面分发、长任务和大图处理开始成为主要问题。
第二次,是后端能力已经越来越多,但原生 JavaScript 页面开始承受不了应用复杂度。
如果在 2025 年 12 月就直接搭建今天这套架构,第一版可能会晚很多,甚至根本不会出现。如果在问题已经变化以后仍然坚持最初的结构,GeoD 也很难继续走到现在。
所以我现在更愿意这样理解技术选型:
不是选择看起来最先进的方案,而是判断眼前最值得解决的问题是什么。
原型阶段,速度和验证最重要;进入真实使用,稳定、状态、分发和可维护性才会逐渐成为主角。
技术没有替产品做决定。真正推动 GeoD 改变的,始终是那些具体的问题:一次装不上、一次下载中断、一张大图崩溃、一个功能改完又影响了另一个页面。
AI 从第一版开始就参与了实现,但它没有替我完成产品判断。它让一个人能够处理更大的代码库、更快验证方案,也让我必须更认真地留下规则、测试和历史记录。
GeoD 能走到今天,不是因为某个模型一次给出了正确答案,而是因为人、AI 和真实用户之间形成了持续反馈:用户指出问题,我判断它是否值得解决,AI 帮我完成大量分析和实现,最后再回到真实软件里验证。
项目地址
官网:https://geodownloader.pages.dev/
GitHub 仓库:https://github.com/gaopengbin/geo-downloader
下载安装:https://github.com/gaopengbin/geo-downloader/releases/latest
问题反馈:https://github.com/gaopengbin/geo-downloader/issues
下一篇
两次重写搭起了今天的应用骨架,但一个下载器真正难处理的,往往不是“开始下载”,而是下载中断以后怎么办。
下一篇,我想专门聊聊 GeoD 里的任务系统:暂停、继续、取消、断点续传和程序重启恢复,看起来只是几个按钮,为什么会成为最容易出问题的一部分。