maptoposter 版本演进解析:从 0.1.0 到 Unreleased 的变更记录与源码印证
maptoposter 版本演进解析:从 0.1.0 到 Unreleased 的变更记录与源码印证
maptoposter 是一个将城市 OpenStreetMap 数据转化为极简风格海报的工具,其 CHANGELOG.md 按 Keep a Changelog 规范完整记录了项目从 0.1.0 初始发布到 Unreleased 社区贡献阶段的每一次功能新增、默认参数调整与缺陷修复。本文以这份变更记录为骨架,逐版本结合当前仓库中的源码、主题配置与 CI 工作流进行印证,帮助你理解 uv 包管理接入、渲染层级(z-order)修复、横向画布文字缩放、本地缓存与 API 限速等关键变更背后的实现细节,以及如何在本仓库中复核这些变更。
变更记录的编写规范与版本总览
CHANGELOG.md 开头声明了两条约定:格式基于 Keep a Changelog 规范,版本管理遵循语义化版本(Semantic Versioning)。全文按版本倒序组织,每个版本分 Added / Changed / Removed / Fixed 小节,0.2.1 起每个版本还标注了日期与维护者(Maintainer: @originalankur),关键条目附带对应的 PR/Issue 编号(如 PR #106、Issue #39)。
文件中共包含五个版本区块,覆盖约两周的密集迭代:
| 版本 | 日期 | 核心主题 |
|---|---|---|
| Unreleased | 未发布 | 社区贡献:uv 支持、z-order 修复、横向文字缩放 |
| 0.3.0 | 2026-01-27 | 自定义坐标、emerald 主题、CI 工作流、默认参数调整 |
| 0.2.1 | 2026-01-18 | SVG/PDF 导出、可变尺寸、本地缓存、API 限速 |
| 0.2.0 | 2026-01-17(Tag: v0.2) | README 示例图片、初始主题集合 |
| 0.1.0 | 2026-01-17 | 初始源码、17 个内置主题、核心渲染能力 |
v0.1.0:初始发布,奠定核心渲染管线
0.1.0 交付了项目的完整初始形态,CHANGELOG 记录的内容包括:
- 初始 maptoposter 源码,即当前的单脚本入口 create_map_poster.py;
- 带使用说明的 README.md;
- 17 个内置主题:autumn、blueprint、contrast_zones、copper_patina、forest、gradient_roads、japanese_ink、midnight_blue、monochrome_blue、neon_cyberpunk、noir、ocean、pastel_dream、sunset、terracotta、warm_beige 等;
- 核心能力清单:基于城市/国家的地图生成、JSON 自定义主题、按 OSM 道路等级着色、水系与公园要素渲染、Roboto 字体排版、坐标显示、OpenStreetMap 版权署名。
当前仓库的 themes/ 目录中正好存在 17 个主题 JSON 文件,与变更记录的规模相符。不过初始版本的 17 个主题与当前目录并非完全同一批:CHANGELOG 记载 0.3.0 移除了 feature_based 主题并新增了 emerald 主题,一进一出后总数保持 17 不变,这也解释了为何当前目录中没有 feature_based.json 却有 themes/emerald.json。
v0.2.0 与 v0.2.1:矢量输出、可变尺寸与本地缓存
v0.2.0:补齐文档与主题集合
0.2.0(2026-01-17,Tag: v0.2)的变更较小:在 README 中加入示例海报图片、形成初始主题集合。当前 README.md 顶部的 Examples 表格(多城市、多主题海报缩略图)即为这一条目的延续。
v0.2.1 新增能力:矢量导出、可变尺寸、缓存与限速
0.2.1(2026-01-18)是功能增量最大的版本之一,四项新增能力均可在源码中找到对应实现:
1. SVG/PDF 矢量导出(PR #57)
CHANGELOG 记录通过 --format 标志支持矢量输出。当前源码中该参数注册为 --format / -f,取值限定为 png、svg、pdf,默认 png(见 create_map_poster.py#L951-L957)。保存逻辑中只有 PNG 格式显式指定 dpi=300,SVG/PDF 作为矢量格式不依赖 DPI(见 create_map_poster.py#L758-L769),这与"矢量输出"的定位一致。
2. 可变海报尺寸(PR #59)
-W / -H 分别对应 --width / --height,单位为英寸,默认 12×16(见 create_map_poster.py#L917-L930)。README 中还给出了 300 DPI 分辨率速查表(Instagram 1080×1080 对应 3.6×3.6 英寸、4K 对应 12.8×7.2 英寸、A4 打印对应 8.3×11.7 英寸等)。
3. 本地缓存
下载的 OSM 数据现在会被本地缓存。从源码看,缓存实现为 pickle 对象,存放在 CACHE_DIR(默认 cache/ 目录,可通过环境变量 CACHE_DIR 覆盖,见 create_map_poster.py#L40-L42),缓存键按"坐标 + 半径"等要素拼接,例如路网键为 graph_{lat}_{lon}_{dist}(见 create_map_poster.py#L424-L428),地理编码结果、水系、公园分别独立缓存。缓存读写失败会抛出专用的 CacheError 异常(见 create_map_poster.py#L36-L108),对应 CHANGELOG 中"Improved cache handling with hashed filenames and error handling"与 0.3.0 的"Cache directory handling"修复(PR #109)。
4. API 限速
CHANGELOG 记录"0.3s delay between API requests"。当前源码中限速体现在三处:要素抓取后 time.sleep(0.3)(create_map_poster.py#L471)、路网抓取后 time.sleep(0.5)(create_map_poster.py#L433)、地理编码请求前 time.sleep(1)(create_map_poster.py#L334)。
v0.2.1 的修复与投影变更
CHANGELOG 的 Fixed/Changed 小节记录了多项数据完整性修复,逐项与源码对应:
| 变更项 | 说明 | 源码印证 |
|---|---|---|
| 可变尺寸下的地图变形 | 变量尺寸导致投影变形 | get_crop_limits() 按画布宽高比向内裁剪,保证覆盖半径的同时维持纵横比(create_map_poster.py#L373-L406) |
| 边缘节点保留 | 保持路网完整 | 抓图时传入 truncate_by_edge=True(create_map_poster.py#L431) |
| 点状几何过滤 | 防止点要素在图上渲染成圆点 | 水系与公园先按 Polygon/MultiPolygon 类型过滤再绘制(create_map_poster.py#L575、create_map_poster.py#L586) |
| 长城市名的动态字号 | 防止文字截断 | 城市名长度超过 10 字符时按比例缩小主标题字号(create_map_poster.py#L668-L672) |
| Nominatim 超时 | 超时提升至 10 秒 | Nominatim(user_agent=..., timeout=10)(create_map_poster.py#L331) |
| 线性投影 | 保证纵横比正确 | 路网绘制前经 ox.project_graph() 投影到以米为单位的线性坐标系(create_map_poster.py#L569) |
v0.3.0:自定义坐标、emerald 主题与 CI 自动化
坐标覆盖:--latitude / --longitude(PR #106)
新增 --latitude / -lat 与 --longitude / -long 两个参数,用于覆盖地理编码得到的中心点,实现精确的选址控制(例如放大某个街区)。参数以字符串注册,运行时经 lat_lon_parser.parse() 解析为浮点数(见 create_map_poster.py#L876-L889 与 create_map_poster.py#L1016-L1022)。CHANGELOG 特别强调:即使用坐标覆盖,仍须提供 --city 和 --country,因为这两个参数决定海报上的显示名称——源码中对二者的缺失校验是无条件的(create_map_poster.py#L972-L975)。
一个典型用法(来自 README.md):
python create_map_poster.py --city "New York" --country "USA" -lat 40.776676 -long -73.971321 -t noir
从源码结构看有一处值得注意的小不一致:脚本 help 文本中的示例写的是短参 -l(create_map_poster.py#L867),而实际注册的短参是 -lat / -long,帮助示例与参数定义并不同步。
默认参数调整与尺寸上限
0.3.0 对三个默认行为做了调整,均在 argparse 与主题加载逻辑中得到印证:
- 默认主题:从
feature_based改为terracotta(PR #131)。load_theme()的默认参数与主题缺失时的内嵌回退主题都是 terracotta 调色板(create_map_poster.py#L177-L200),CLI 默认值同为terracotta(create_map_poster.py#L900); - 默认半径:从 12000 米改为 18000 米(PR #128),对应
--distance默认值18000(create_map_poster.py#L911-L916),README 的 Distance Guide 也随之给出 4000–20000 米分档建议; - 尺寸上限:宽/高强制上限 20 英寸,以支撑 4K 分辨率输出(PR #128、#129)。超限时会打印警告并钳制到 20(create_map_poster.py#L978-L987);
- 移除:
feature_based主题(PR #131),当前themes/目录中确实不存在该文件。
emerald 主题(PR #114)
新增的 emerald 主题以深绿底色搭配薄荷色点缀,themes/emerald.json 的完整配色为:
{
"name": "Emerald City",
"description": "Lush dark green aesthetic with mint accents",
"bg": "#062C22",
"text": "#E3F9F1",
"gradient_color": "#062C22",
"water": "#0D4536",
"parks": "#0F523E",
"road_motorway": "#4ADEB0",
"road_primary": "#2DB88F",
"road_secondary": "#249673",
"road_tertiary": "#1B7559",
"road_residential": "#155C46",
"road_default": "#155C46"
}
这体现了主题体系的通用结构:每个主题是一个 11 个颜色槽位的 JSON(背景、文字、渐变、水系、公园、四级道路色加默认道路色),load_theme() 按文件名加载。仓库中 posters/seattle_emerald_20260124_162244.png 即为该主题的实际输出。
GitHub Actions 与冲突自动标记
0.3.0 引入了两条工作流(PR #98):
- pr-checks.yml:在 ubuntu-latest 与 windows-latest × Python 3.11/3.12/3.13/3.14 矩阵上执行
compileall语法校验、--help与--list-themes冒烟测试、flake8(含E9,F63,F7,F82硬性错误检查)、pylint 与 mypy,另有独立的 pip-audit 依赖安全审计作业; - conflicts.yml:基于
eps1lon/actions-label-merge-conflict,给存在合并冲突的 PR 自动打Conflicts标签并留言提示。
Unreleased(社区贡献):uv 支持、层级修复与文字缩放
文件顶部的 Unreleased - Community Contributions 区块汇集了尚未发布、来自社区的三项新增与两项修复。
uv 包管理器支持(PR #20)
该条目包含四个配套改动,均可在当前仓库中验证:
- pyproject.toml:声明项目元数据与锁定版本的全部依赖(osmnx 2.0.7、matplotlib 3.10.8、geopandas 1.1.2 等 30 余项,见 pyproject.toml#L13-L44),构建后端为 setuptools;
- uv.lock:约 244KB 的锁定文件,保证可复现构建;
- shebang 行:create_map_poster.py#L1 为
#!/usr/bin/env python3,且文件具备可执行位,支持uv run ./create_map_poster.py直接运行; - README.md 中的 uv 安装说明:
# First run will automatically install dependencies
uv run ./create_map_poster.py --city "Paris" --country "France"
# Or sync dependencies explicitly first (using locked versions)
uv sync --locked
uv run ./create_map_poster.py --city "Paris" --country "France"
同一区块还记录了 requires-python = ">=3.11" 的声明(修复 Issue #79),对应 pyproject.toml#L6。从当前仓库文件看,pyproject.toml#L3 的 version 字段仍为 0.2.0,与 CHANGELOG 已推进到 0.3.0 + Unreleased 的状态不同步,阅读版本信息时以 CHANGELOG 为准。测试脚本 test/all_variations.sh 中也统一采用 uv run python3 create_map_poster.py ... 的方式调用,印证了 uv 已成为项目的标准运行方式。
渲染层级(z-order)修复(Issue #39 / PR #42)
这是 Unreleased 区块中最具体的一项修复:此前道路被水系和公园盖住,原因是水系层 zorder=1、公园层 zorder=2,与 matplotlib 默认的 2 级道路层冲突。修复后的层级关系为:
- 水系:
zorder=1→zorder=0.5(create_map_poster.py#L582) - 公园:
zorder=2→zorder=0.8(create_map_poster.py#L593) - 道路:保持 matplotlib 默认的
zorder=2,由ox.plot_graph()绘制(create_map_poster.py#L602-L609)
由此形成"背景 → 水系(0.5)→ 公园(0.8)→ 道路(2)→ 上下渐变(10)→ 文字(11)"的完整叠放顺序,渐变与文字层的 zorder=10/11 可在 create_map_poster.py#L615-L752 中逐一确认。
横向画布的文字缩放修复(Issue #112)
此前字号只按宽度缩放,导致横向(宽 > 高)海报上文字比例失调。修复后缩放因子改为取宽高较小值,以 12 英寸为基准:
# create_map_poster.py L618-L626
scale_factor = min(height, width) / 12.0
base_main = 60
base_sub = 22
base_coords = 14
base_attr = 8
(见 create_map_poster.py#L618-L626)。该因子同时作用于副标题、坐标、署名文字、装饰线宽与动态主标题,保证竖版 12×16、方形 1080×1080 与横版 4K 等比例画面上的文字观感一致。这与 0.3.0 修复小节中"Dynamic font scaling based on poster width"是同一条演进线:先按宽度缩放,再改为按 min(height, width) 缩放。
其他变更
Unreleased 区块还记录了 .gitignore 的更新,覆盖海报产物、Python 构建产物、IDE 文件与操作系统杂项文件。当前 .gitignore 中可见 cache/*、虚拟环境目录、*.egg-info/、__pycache__/、IDE 配置与 .DS_Store/Thumbs.db 等条目,与该记录相符。
在当前仓库中复核这些变更
以下操作可帮助你在本地逐条验证 CHANGELOG 的记载(仓库为只读资源,以下均为查看/运行类操作):
# 1. 查看当前主题集合(应为 17 个,含 emerald、不含 feature_based)
uv run ./create_map_poster.py --list-themes
# 2. 验证 CLI 参数与默认值(terracotta、18000、-W/-H 上限 20)
uv run ./create_map_poster.py --help
# 3. 验证 z-order 修复:确认水系 0.5、公园 0.8
grep -n "zorder=" create_map_poster.py
# 4. 验证文字缩放修复:确认 min(height, width)
sed -n '618,626p' create_map_poster.py
# 5. 验证 uv 支持:检查项目元数据与 Python 版本约束
sed -n '1,11p' pyproject.toml
# 6. 一键生成全参数变体(针对 Bengaluru 的主题、分辨率、距离矩阵)
bash test/all_variations.sh
其中 test/all_variations.sh 覆盖了基本用法、全部 17 个主题、显示名/国家标签覆盖、-lat/-long 坐标覆盖、印地/卡纳达语 i18n 示例、README 分辨率速查表的五档尺寸以及 Distance Guide 的三档半径,是验证各版本新增 CLI 能力现成可用的回归脚本。
小结
maptoposter 的 CHANGELOG 呈现了一条紧凑的演进主线:0.1.0 确立"地理编码 → OSMnx 抓图 → 主题化分层渲染 → matplotlib 导出"的管线;0.2.1 补齐矢量输出、可变尺寸、缓存与限速等工程能力;0.3.0 引入坐标覆盖、emerald 主题、CI 检查并收敛默认参数(terracotta 主题、18000 米半径、20 英寸上限);Unreleased 则落地了 uv 包管理、渲染层级与横向文字缩放两项渲染修复。上述每一条记录都能在 create_map_poster.py、pyproject.toml、themes/ 与 .github/workflows/ 中找到对应的源码事实,这也是该文档对贡献者与使用者兼具参考价值的原因——它不仅是版本流水账,更是理解当前代码形态的路线图。

