惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
V
V2EX
Engineering at Meta
Engineering at Meta
美团技术团队
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 司徒正美
I
InfoQ
S
SegmentFault 最新的问题
博客园 - 叶小钗
N
Netflix TechBlog - Medium
Y
Y Combinator Blog
IT之家
IT之家
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
T
The Blog of Author Tim Ferriss
月光博客
月光博客
The Cloudflare Blog
U
Unit 42
GbyAI
GbyAI
L
LangChain Blog
Microsoft Azure Blog
Microsoft Azure Blog

ALBERTAZ

How I Built a Scannable 3D QR Code Generator | Every QR Code OpenStreetMap Data Pipeline for Canvas Maps | EveryCityMap 每日关灯:今天这题理论最少 8 步 OpenStreetMap + Canvas 城市地图实践:EveryCityMap 的五个工程取舍 博客 CDN 迁移到 Cloudflare R2 博客 CDN 迁移到 Cloudflare R2 Migrate my website to SvelteKit2 Migrate my website to SvelteKit2 漫谈编辑器 漫谈编辑器 Pure ESM package Pure ESM package 又折腾博客了 又折腾博客了 Rewrite my website with Svelte Rewrite my website with Svelte 字体漫谈-网站字体最佳实践 字体漫谈-网站字体最佳实践 你不知道的 Web Components - 现状 你不知道的 Web Components - 现状 你不知道的 Web Components - 过去和未来 你不知道的 Web Components - 过去和未来 Node 应用本地 HTTPS Node 应用本地 HTTPS docker 部署 Gatsby 应用 docker 部署 Gatsby 应用 再提示『您的磁盘几乎已满』算我输 再提示『您的磁盘几乎已满』算我输 G6-Mobile 的前世今生 G6-Mobile 的前世今生
OpenStreetMap + Canvas 城市地图实践:EveryCityMap 的五个...
https://www.albertaz.com/about · 2026-08-10 · via ALBERTAZ

EveryCityMap 的起点其实很简单:我想用真实城市做一些游戏地图。

上海被黄浦江切开,纽约由岛屿、河流和密集路网塑形,巴黎有向外发散的街道,东京则像一张不断生长的网络。真实城市本身已经很像经过设计的游戏关卡:道路是路径,河流是边界,桥梁是天然的连接点,不同城区也有自己的节奏。

我当时甚至没有先决定它会是一款策略游戏、探索游戏,还是别的什么类型。我只是想先得到一张足够干净、可以控制、也能继续加工的真实城市地图。现成地图适合导航和查找地点,却不一定适合作为游戏画面或创作素材,于是我决定从 OpenStreetMap 数据开始,自己 DIY 一个地图生成工具。

这次 DIY 最后慢慢长成了 EveryCityMap:一个运行在浏览器里的城市地图工作室。目前准备了 500 座城市,可以切换五种主题、编辑图层与颜色、生成分享图,并且无需注册就能导出 1440px WebP。

使用 OpenStreetMap 和 Canvas 制作的上海 Atlas 城市地图
上海 Atlas:城市的水系、道路和边缘共同决定了画面的构图。

从一张游戏地图到一个城市地图工作室

一开始,我以为只要把道路画出来就够了。真正开始做之后才发现,一张能看的截图和一套可持续使用的城市地图系统完全是两件事。

如果地图只是一次性素材,很多问题可以手工修补;但当城市数量从一座变成 500 座,主题从一种变成五种,同样的修补方式就会迅速失控。上海需要处理大片水域与海岸,纽约需要在极高的道路密度里保留层级,内陆城市又不应该凭空出现海洋效果。

EveryCityMap 因此没有继续沿着“一张图一套配置”的方向发展,而是逐渐形成了这条链路:

Plain text
真实城市数据 → 城市语义图层 → 主题规则 → Canvas 预览 / 分享 / 导出

这里面有很多实现细节,但真正影响产品形态的,是下面五个工程取舍。

取舍一:把城市当成关卡,而不是一个矩形

普通在线地图通常从一个矩形视窗开始:中心点在哪里、缩放级别是多少、屏幕能看到多大范围。做游戏地图和城市视觉作品时,我更关心的是另一件事:这座城市自己的形状是什么?

河流可能把城市分成两部分,岛屿可能决定交通连接,环线可能形成清晰的活动区域。随意截取一个矩形虽然简单,却会把这些特征切断,也让不同城市看起来只是换了名字的同一张底图。

所以 EveryCityMap 会尽量保留城市的自然构图,再把道路、水域、陆地和其他信息组织到同一个空间里。这样得到的地图不只适合导出成壁纸;如果以后继续做游戏,城市结构也能自然成为路线、区域和关卡边界的基础。

取舍二:一份城市几何,支持所有视觉主题

我不希望 Ink、Paper、Night、Metro 和 Atlas 各自保存一份城市地图。它们看到的应该是同一座上海或纽约,只是强调的内容不同。

因此,城市几何和视觉主题被分成两部分:前者描述“城市里有什么”,后者决定“应该怎样被看见”。切换主题时不需要重新准备一座城市,只需要让 Canvas 按新的视觉规则重绘。

这使五套主题不只是五组颜色:

主题 主要表达 更适合的画面
Ink 道路骨架与黑白对比 极简海报、游戏底图
Paper 温和的纸张质感 旅行记录、打印作品
Night 暗色空间中的城市网络 屏幕壁纸、夜间氛围
Metro 更强的路线层级 交通感、节奏感画面
Atlas 水系、地表与道路共同出现 完整城市表达与深度编辑

具体的颜色、线宽和组合参数仍然属于主题本身;但从工程上看,重要的是所有主题共享同一份城市事实,不会因为换了风格就变成另一座城市。

取舍三:先理解图层的意义,再决定它的颜色

如果所有线条都只是“线”,所有色块都只是“面”,地图很快会失去层次。主干道、普通道路、铁路和河流在画面上可能都表现为线,但它们对城市的意义完全不同。

EveryCityMap 先把城市整理为道路、水域、陆地、绿地、铁路等语义图层,再由主题决定显示哪些内容。Ink 可以只留下克制的道路网络;Atlas 则能展示更完整的自然和城市信息,并允许用户关闭图层、修改颜色。

这种设计最初是为了地图编辑,后来我发现它也保留了游戏化的可能:同一座城市可以强调水路、交通网络或某类区域,而不必重新处理底层几何。数据没有直接绑死在某个画面上,产品才有继续生长的空间。

取舍四:预览和导出必须是同一张图

地图编辑器最容易出现的体验问题,是屏幕上看到的效果和最终导出的图片不一致。字体、透明度、线宽和绘制顺序只要有一处走了不同路径,用户调好的地图就可能在下载后变样。

我最终让编辑器预览、分享图和 WebP 导出共同依赖同一套 Canvas2D 渲染逻辑。界面负责修改状态,渲染器负责把状态变成画面。这样做并不炫技,却能保证用户看到什么,导出的就是什么。

这部分也延续了我过去做 Canvas 数据可视化 时的经验:真正困难的通常不是把图形画出来,而是建立稳定的绘制顺序。EveryCityMap 会先处理水域与地表,再叠加自然特征、道路层级和必要文字。顺序一旦错了,道路会盖住河流,海岸会变成生硬边框,城市也会失去空间感。

EveryCityMap Studio 中使用 Canvas 编辑纽约 Atlas 城市地图
纽约 Atlas:主题、图层控制和最终地图共用一套渲染结果。

取舍五:500 座城市不能依赖临时在线计算

只做一张地图时,可以等待在线服务返回数据;做成产品后,用户不会关心数据来自哪里,只会感受到页面是不是稳定、第二次打开是不是还要重新等待。

因此,EveryCityMap 会提前准备已经进入目录的城市数据,再通过 CDN 和浏览器本地缓存交付。主题切换只改变视觉表达,不重复下载城市几何;用户再次打开同一座城市时,也可以尽量复用已经存在的数据。

我没有把公开地图服务当成产品实时可用性的保证。它们更适合数据准备与失败重试,而不是让每一次用户访问都重新经历采集过程。这个选择增加了前期准备工作,却换来了更可预测的体验,也让 500 座城市真正成为产品内容,而不是 500 个随时可能失败的在线请求。

最费时间的不是功能,而是浏览器里的“看起来不对”

Atlas 是目前最难调的一套主题。它需要让海岸、岛屿、河流、植被和道路同时存在,又不能让任何一层抢走整张地图。

其中一个很具体的问题出现在 Safari:原本用于柔化海岸的 Canvas 模糊效果,会产生有规律的色带。继续增大模糊值并不能解决问题,反而让边缘更脏。最后我改为先在较低分辨率的透明画布上分别处理海岸效果,再合成回最终画面。

这里真正有价值的经验不是某一段代码,而是:Canvas 在不同浏览器中的视觉结果不能只靠参数推测。对地图、绘画和数据可视化产品来说,“数学上连续”不代表“人眼看起来连续”,最终仍然需要用真实城市反复检查。

回到最初:真实城市还能变成什么?

EveryCityMap 现在首先是一个城市地图工作室,但我仍然喜欢它最初的那个问题:如果把真实城市当成游戏世界,而不是导航背景,会发生什么?

一条河可以成为边界,一座桥可以成为争夺点,城市道路可以生成移动网络,不同主题也可能对应完全不同的游戏规则。即使这些想法最后没有全部变成游戏,它们仍然推动我做出了一个可以被其他人使用的地图工具。

产品目前还很早。我会继续调整城市构图、主题和编辑体验,也会观察大家究竟把地图用作壁纸、旅行纪念、设计素材,还是一些我没有想到的东西。

如果你愿意,可以选择自己的城市试一下。我也很好奇:如果把你的城市做成一张游戏地图,你会先保留什么,又会隐藏什么?