ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Arnis:将OpenStreetMap真实城市数据自动生成Minecraft方块世界

Arnis:将OpenStreetMap真实城市数据自动生成Minecraft方块世界 1. 这个项目到底在玩什么花样第一次刷到 Arnis 这个项目的时候我盯着它的演示图看了足足半分钟——有人把整个曼哈顿的街道、建筑轮廓、甚至中央公园的树线一比一还原进了 Minecraft 里。不是那种手工搭的像素画是程序自动生成的输入一个坐标范围等一会儿一座城市就长出来了。这个项目的核心逻辑其实一句话就能说清楚把 OpenStreetMap 的真实地理数据转换成 Minecraft 的方块世界。OpenStreetMap 是一个全球性的开源地图数据库里面的数据全是志愿者一点点标注出来的道路、建筑、河流、绿地、甚至路灯和长椅都有记录。Arnis 做的事情就是读取这些数据然后按照一定的规则把它们翻译成 Minecraft 能理解的方块坐标和材质。为什么我觉得这个项目特别有创意因为它把两个看起来完全不搭界的东西缝在了一起。一边是严肃的地理信息系统一边是沙盒游戏。但仔细想想这两者的底层逻辑其实高度一致——都是基于网格的空间数据。OpenStreetMap 用经纬度描述位置Minecraft 用 XYZ 坐标描述方块中间只需要一个投影转换和比例尺映射就能把现实世界“搬”进游戏里。这个项目适合谁如果你是对地理数据可视化感兴趣的开发者Arnis 是一个极好的练手项目代码结构清晰依赖不算复杂。如果你是 Minecraft 玩家想在自己的世界里复刻家乡的街道这个工具能帮你省下几百个小时的手工搭建时间。如果你只是单纯觉得“把真实城市变成游戏地图”这件事很酷那也值得花点时间了解一下它的实现思路。我花了几个晚上把它的源码翻了一遍又在本地跑了几次生成任务踩了一些坑也总结了一些经验。下面我会从设计思路、核心细节、实操过程、常见问题几个方面把这个项目拆开来讲清楚。2. 项目整体设计与思路拆解2.1 为什么选择 OpenStreetMap 而不是其他地图数据源Arnis 选择 OpenStreetMap 作为数据源这个决定背后有几个非常实际的考量。第一是数据开放性。OpenStreetMap 采用 ODbL 协议任何人都可以自由下载、使用、修改它的数据只要署名并以相同方式共享。这对于一个开源项目来说是刚需——如果用了某商业地图的 API不仅需要申请密钥还有调用次数限制项目就没法做到“任何人克隆下来就能跑”。第二是数据结构的规范性。OpenStreetMap 的数据模型非常清晰核心就是三种元素节点Node、路径Way和关系Relation。节点是带经纬度的点路径是一串节点的有序集合关系则用来描述更复杂的组合。建筑、道路、河流这些要素在 OSM 里都有标准的标签体系来标注比如buildingyes表示建筑轮廓highwayresidential表示住宅区道路。Arnis 只需要按照这些标签来分类处理就能把不同类型的地理要素映射成不同的 Minecraft 方块。第三是数据覆盖的广度。OSM 在全球范围内的覆盖已经相当可观尤其是城市地区建筑轮廓和道路网络的完整度很高。这意味着 Arnis 不只能生成某个特定城市的模型理论上你可以输入地球上任何一个 OSM 数据覆盖良好的区域都能得到对应的 Minecraft 世界。提示OSM 的数据质量在不同地区差异很大。欧美城市的建筑轮廓通常很完整但一些发展中地区可能只有主干道有记录。生成之前最好先去 OSM 官网看一眼目标区域的数据密度。2.2 从经纬度到方块坐标的映射逻辑这是整个项目最核心的技术点也是我花最多时间理解的部分。现实世界的位置用经纬度表示这是一个球面坐标系。Minecraft 的世界是一个平面直角坐标系X 轴和 Z 轴构成水平面Y 轴是高度。要把球面上的点映射到平面上就需要一个地图投影。Arnis 采用的是一种简化的局部投影方式。它首先确定你要生成区域的中心点经纬度然后以这个中心点为原点计算其他所有点相对于中心点的偏移量。具体来说它用了一个近似的公式在纬度不太高的区域经度方向每变化 1 度大约对应 111320 米乘以纬度的余弦值纬度方向每变化 1 度大约对应 110540 米。这两个系数来自地球椭球模型的局部近似在几公里到几十公里的范围内误差可以忽略。得到以米为单位的平面坐标后还需要一个比例尺来决定现实中的一米对应 Minecraft 里的几个方块。Arnis 默认的比例尺是 1:1也就是现实中一米等于游戏里一个方块。这个比例下一栋普通住宅大约占 10×10 个方块一条城市道路宽约 8 到 12 个方块走在里面感觉和现实中的尺度感比较接近。但 1:1 的比例有个问题如果你要生成一个很大的区域比如整个城区方块数量会非常庞大。Minecraft 的世界高度是有限的默认 384 格水平方向虽然理论上可以无限延伸但区块加载和渲染的压力会急剧增加。所以 Arnis 也支持调整比例尺比如 1:2 或 1:5把现实世界缩小后再映射进游戏。2.3 建筑高度的推断策略OSM 数据里建筑轮廓是二维的多边形但 Minecraft 是三维世界建筑需要有高度。Arnis 怎么决定每栋楼盖多高我翻源码的时候发现它有一套优先级递减的高度推断规则。首先看 OSM 数据里有没有height标签这是最直接的有些建筑会标注实际高度单位是米。如果没有就看building:levels标签也就是楼层数然后乘以每层 3 米来估算。如果这两个都没有就根据建筑类型给一个默认值——比如buildinghouse默认 2 层buildingapartments默认 5 层buildingcommercial默认 3 层。这个策略很务实因为 OSM 里大部分建筑确实没有高度信息但建筑类型标签通常都有。用类型来推断高度虽然不够精确但生成的 skyline 至少不会太离谱。我在本地测试的时候生成了一片住宅区大部分房子都是 2 到 3 层的高度和实际街景照片对比感觉还算合理。2.4 道路与地形的处理方式道路在 OSM 里是线状要素Arnis 会把它们转换成 Minecraft 里的条带状方块。不同等级的道路用不同材质区分高速公路用深色方块主干道用灰色住宅区小路用浅灰色或土路。道路的宽度也是根据等级来定的高速公路可能 12 格宽住宅区小路 4 格宽。地形方面Arnis 默认生成的是平坦地面所有建筑和道路都放在同一个 Y 轴高度上。这样做的好处是实现简单、生成速度快缺点是失去了真实地形的起伏感。不过项目也留了扩展空间理论上可以接入高程数据比如 SRTM 数据来生成有坡度的地形但这部分目前还没有实现。水域的处理比较直接OSM 里标记为naturalwater或waterwayriver的区域会被填充成水方块。我在测试的时候生成了一段河岸区域河流的轮廓和 OSM 上的形状基本吻合边缘用沙子方块过渡视觉效果还不错。3. 核心细节解析与实操要点3.1 数据解析从 PBF 文件到内存对象Arnis 的数据输入是 OSM 的 PBF 格式文件。PBF 是一种压缩的二进制格式比传统的 XML 格式小很多解析速度也快。项目里用了一个 Java 的 OSM 解析库来处理这个格式把 PBF 里的节点、路径、关系读出来转换成内存里的对象。这里有个细节值得注意PBF 文件通常包含整个国家或地区的数据但 Arnis 只需要你指定区域内的数据。所以解析的时候需要做一次空间过滤只保留落在目标边界框内的元素。这个过滤是在解析过程中同步进行的避免把不必要的数据加载进内存。我实测下来一个包含中等城市核心区的 PBF 文件大约 50 到 100 MB解析时间在十几秒左右。如果你的机器内存比较小建议不要一次性加载太大的区域可以分块生成后再在 Minecraft 里拼接。注意OSM 的 PBF 文件可以从 Geofabrik 等镜像站下载按洲、国家、省份划分。下载之前先确认目标区域在哪个文件里避免下载几个 GB 的数据只为了提取一个小城市。3.2 坐标转换的精度控制前面提到了经纬度到平面坐标的转换这里补充一个实操中容易忽略的点浮点数精度。经纬度是双精度浮点数直接做减法再乘以系数在几公里的范围内精度是足够的。但如果区域跨度超过几十公里近似公式的误差就会累积导致生成的建筑位置偏移。Arnis 的做法是限制单次生成的区域大小建议不超过 5 公里见方。如果你需要生成更大的区域可以分多次生成每次用一个子区域的中心点做投影然后在 Minecraft 里手动对齐拼接。另一个精度相关的细节是方块坐标的取整方式。现实坐标转换成方块坐标后是浮点数需要取整。Arnis 用的是四舍五入而不是直接截断。这个选择有讲究如果直接截断所有建筑都会往坐标原点方向偏移半格累积起来会导致道路和建筑之间的缝隙不均匀。四舍五入能让误差在正负半格之间均匀分布视觉效果更整齐。3.3 建筑轮廓的填充算法OSM 里的建筑轮廓是多边形有凸多边形也有凹多边形甚至还有带内孔的多边形比如回字形的建筑。Arnis 需要把这些多边形转换成 Minecraft 里的实心方块区域。对于简单的凸多边形可以用扫描线算法逐行填充。对于凹多边形需要先做三角剖分再逐个三角形填充。带内孔的多边形更复杂需要先填充外轮廓再把内孔区域挖掉。我在源码里看到Arnis 用的是一个通用的多边形填充库支持带孔多边形的处理。填充的时候会判断每个方块中心点是否在多边形内部如果在就放置建筑方块。这个判断用的是射线法从方块中心点向任意方向发一条射线如果和多边形边的交点数是奇数就在内部偶数就在外部。这个算法的时间复杂度是 O(n×m)n 是多边形边数m 是边界框内的方块数。对于普通建筑几十个顶点几百个方块速度很快。但如果遇到特别复杂的建筑轮廓比如某些历史建筑的精细轮廓有上千个顶点生成时间会明显增加。3.4 材质映射表的配置Arnis 有一个材质映射表定义了 OSM 标签到 Minecraft 方块的对应关系。这个表是 YAML 格式的用户可以自己修改。比如buildinghouse: minecraft:oak_planks buildingapartments: minecraft:stone_bricks buildingcommercial: minecraft:glass highwaymotorway: minecraft:black_concrete highwayresidential: minecraft:gray_concrete naturalwater: minecraft:water这个设计很聪明把映射逻辑从代码里抽出来变成配置文件。你想把住宅变成石英块或者把河流变成岩浆改一行配置就行不用重新编译。我自己的做法是准备了两套配置一套“写实风格”用接近真实材质的方块一套“卡通风格”用鲜艳的彩色方块。生成不同用途的地图时切换配置很方便。提示修改材质映射表之后记得检查方块名称是否拼写正确。Minecraft 的方块 ID 是区分大小写的写错了不会报错但会生成空气方块导致建筑“消失”。3.5 生成速度的优化空间Arnis 的生成速度主要受两个因素影响区域大小和建筑复杂度。我实测的数据是一个 1 公里见方的住宅区大约 200 栋建筑生成时间在 30 秒左右。如果换成建筑密集的市中心同样面积可能有上千栋建筑生成时间会增加到 2 到 3 分钟。优化的思路有几个方向。一是并行化建筑填充是相互独立的可以用多线程并行处理。二是空间索引用 R 树或网格索引加速“哪些建筑落在当前区块内”的查询。三是增量生成只生成玩家附近的区域远处用低精度模型代替。不过对于个人使用来说目前的生成速度已经可以接受了。毕竟你不需要频繁重新生成一次生成好就可以在游戏里玩很久。4. 实操过程与核心环节实现4.1 环境准备与依赖安装Arnis 是一个 Java 项目需要 JDK 17 或更高版本。我用的是一台 Ubuntu 22.04 的机器8 核 CPU16 GB 内存。Windows 和 macOS 理论上也可以跑但我在 Windows 上遇到过一个文件路径分隔符的 bug后来在 Linux 下没再出现。安装步骤大致如下# 克隆仓库 git clone https://github.com/louis-e/arnis.git cd arnis # 如果你在国内克隆速度慢的话可以用镜像站 # git clone https://ghproxy.com/https://github.com/louis-e/arnis.git # 构建项目 ./gradlew build # 运行 java -jar build/libs/arnis.jar构建过程会下载 Gradle 和一堆依赖库第一次跑可能需要几分钟。如果卡在下载依赖的步骤可以配置一下 Gradle 的镜像源把 Maven Central 换成国内的镜像。4.2 下载 OSM 数据前面说过OSM 数据可以从 Geofabrik 下载。我以生成某个城市为例先去 Geofabrik 找到对应的省份或国家文件下载.osm.pbf格式。下载下来之后不需要做任何预处理Arnis 可以直接读取。但如果你只需要城市的一小部分可以用osmium工具先裁剪一下# 安装 osmium sudo apt install osmium-tool # 裁剪出指定边界框内的数据 osmium extract --bbox 116.30,39.90,116.50,40.00 input.osm.pbf -o output.osm.pbf--bbox参数的格式是minLon,minLat,maxLon,maxLat。这个步骤不是必须的但可以显著减少解析时间。4.3 配置生成参数Arnis 的配置文件里你需要指定几个关键参数参数名说明示例值inputFileOSM PBF 文件路径./data/beijing.osm.pbfcenterLat生成区域中心纬度39.9042centerLon生成区域中心经度116.4074radius生成半径米2000scale比例尺1.0outputFile输出文件路径./output/world.mcaradius是生成半径单位是米。2000 米意味着生成一个直径 4 公里的圆形区域。scale是比例尺1.0 表示 1:10.5 表示现实 2 米对应游戏 1 格。我建议第一次测试的时候把 radius 设小一点比如 500 米先看看生成效果和速度再逐步扩大。4.4 执行生成并导入 Minecraft配置好之后运行生成命令java -jar arnis.jar --config config.yaml生成过程中会在控制台输出进度包括已处理的建筑数量、当前区块坐标等。生成完成后会得到一个.mca文件这是 Minecraft 的区块文件格式。把这个文件放到 Minecraft 存档的region文件夹里启动游戏传送到对应的坐标就能看到生成的城市了。坐标可以通过中心点的经纬度反推Arnis 会把中心点放在游戏坐标的 (0, 0) 附近具体偏移量可以在日志里找到。注意导入之前备份你的存档。Arnis 生成的区块会覆盖目标区域原有的地形如果坐标没算对可能会把你原来的建筑覆盖掉。4.5 后期调整与细节打磨生成出来的城市是“毛坯”状态建筑是实心的没有门窗道路是平坦的没有路灯和树木。如果你想让场景更生动可以手动做一些后期调整。我自己的做法是先用 Arnis 生成基础的城市骨架然后在 Minecraft 里用 WorldEdit 等工具批量添加细节。比如在建筑外墙上挖出窗户的洞在道路两侧种树在路口放置路灯。这些操作虽然手工但因为有基础骨架在工作量比从零搭建小得多。另一个技巧是分层生成。先用地形工具生成地面和河流再用 Arnis 生成建筑和道路最后手动添加植被和装饰。这样每一层都可以单独调整不会互相干扰。5. 常见问题与排查技巧实录5.1 生成出来的城市是空白的这是最常见的问题通常有几个原因。原因一PBF 文件里没有目标区域的数据。你可能下载了错误的国家或省份文件。解决办法是先用 osmium 工具查看 PBF 文件的边界框确认目标区域在范围内。原因二中心点经纬度写反了。纬度是南北方向范围 -90 到 90经度是东西方向范围 -180 到 180。如果你把北京的中心点写成centerLat: 116.4074, centerLon: 39.9042程序会去南极洲附近找数据当然找不到。原因三radius 设得太小。如果半径只有 100 米而中心点恰好落在一条宽阔的河流或公园里可能真的没有任何建筑。把半径调大一些再试。5.2 建筑高度全部是默认值如果你发现生成的所有建筑都是一样高的说明 OSM 数据里缺少height和building:levels标签。这在一些数据稀疏的区域很常见。解决办法有两个一是接受默认高度至少建筑轮廓是对的二是手动在 OSM 数据里补充高度信息但这需要你熟悉 OSM 的编辑流程工作量不小。我个人的建议是如果只是做视觉参考默认高度完全够用。如果要做精确的城市模型可能需要结合其他数据源比如 LiDAR 点云来补充高度信息。5.3 生成速度突然变慢如果你发现生成到某个区域时速度骤降大概率是遇到了复杂多边形。某些历史建筑或大型公共设施的轮廓有上千个顶点填充算法需要逐个判断每个方块是否在多边形内计算量很大。排查方法是看日志里哪个建筑的生成时间最长。如果是少数几个建筑拖慢了整体速度可以在配置里把它们排除掉或者简化它们的轮廓。另一个可能的原因是内存不足。生成大区域时所有建筑数据都放在内存里如果 JVM 的堆内存不够会频繁触发垃圾回收导致速度变慢。可以通过-Xmx4G参数增加堆内存。5.4 道路和建筑之间有缝隙这个问题通常和坐标取整方式有关。如果 Arnis 用的是截断而不是四舍五入建筑边缘和道路边缘之间会出现半格的缝隙。解决办法是检查源码里的取整逻辑确认用的是Math.round()而不是直接强制类型转换。如果你不想改代码也可以在 Minecraft 里手动填补缝隙但工作量会很大。我在测试的时候还遇到过一个特殊情况某些建筑的轮廓本身就不闭合OSM 数据质量问题导致填充算法无法正确判断内外。这种情况下建筑会生成成一条线或者一个空壳。解决办法是用 OSM 的校验工具修复数据或者手动在配置里跳过这些建筑。5.5 常见问题速查表问题现象可能原因排查方法解决方案生成结果空白PBF 文件不含目标区域用 osmium 查看文件边界下载正确的 PBF 文件生成结果空白经纬度写反检查配置文件纬度范围 -90~90经度 -180~180建筑高度全部相同缺少高度标签查看 OSM 数据接受默认值或手动补充生成速度慢复杂多边形查看日志排除或简化复杂建筑生成速度慢内存不足监控 JVM 内存增加 -Xmx 参数道路建筑有缝隙坐标取整方式检查源码改用四舍五入建筑变成空壳轮廓不闭合检查 OSM 数据修复数据或跳过5.6 几个我踩过的坑第一个坑是文件路径。Arnis 在 Windows 下对反斜杠的处理有问题配置文件里写C:\data\beijing.osm.pbf会报错得写成C:/data/beijing.osm.pbf或者用双反斜杠。这个问题在 Linux 和 macOS 下不存在。第二个坑是Minecraft 版本兼容性。Arnis 生成的区块文件格式和 Minecraft Java 版的某个特定版本对应。如果你用的游戏版本和生成格式不匹配导入后可能会显示为未知方块或者直接崩溃。建议先用一个测试存档试一下确认版本兼容后再导入主存档。第三个坑是坐标偏移。Arnis 默认把生成区域的中心放在游戏坐标 (0, 0)但 Minecraft 的 (0, 0) 可能已经在你的主城里了。导入之前一定要确认目标坐标是空的否则会覆盖原有建筑。我一般会先把生成结果导入一个超平坦的测试世界确认无误后再用结构方块或者 WorldEdit 复制到主世界。第四个坑是水体渲染。OSM 里的水域生成的是静态水方块不会流动。如果你想要动态水流效果需要手动在游戏里调整。另外大面积的静态水方块可能会影响渲染性能建议在远处用低分辨率的水面代替。6. 这个项目还能怎么玩Arnis 目前的功能已经很有意思了但它的潜力远不止于此。我在折腾的过程中想到了几个扩展方向有些已经在社区里看到有人在做了。第一个方向是接入高程数据。目前生成的地形是平坦的如果能把 SRTM 或 ASTER 的高程数据读进来给每个方块设置不同的 Y 轴高度就能生成有山坡、河谷的真实地形。这个改动的核心是在坐标转换阶段增加一个高度查询把高程值映射到 Y 轴坐标。技术难度不大主要是数据源的获取和配准。第二个方向是实时生成。现在的流程是离线生成再导入如果做成 Minecraft 的模组玩家走到哪里就生成哪里体验会更流畅。这需要把 Arnis 的核心逻辑移植到模组框架里并且做好区块加载的异步处理避免卡顿。第三个方向是历史版本对比。OSM 的数据是有历史记录的如果你能获取同一个区域不同年份的数据就可以生成“过去”和“现在”两个版本的 Minecraft 世界直观地看到城市的变化。这个玩法对于城市规划爱好者来说应该很有吸引力。第四个方向是多人协作标注。在 Minecraft 里逛自己生成的城市时如果发现某个建筑的高度不对或者某条路缺失了能不能直接在游戏里修改然后同步回 OSM这个想法有点疯狂但技术上并非不可能。需要做一个 Minecraft 和 OSM API 之间的双向同步工具把游戏里的方块变化翻译成 OSM 的编辑操作。我自己最想尝试的是第一个方向。手头正好有一份某山区的高程数据打算周末花点时间改改代码看看能不能生成一个有起伏地形的村庄。如果成功了再来写一篇后续分享。提示如果你也想做扩展开发建议先 fork 一份代码在自己的分支上折腾。原仓库的 issue 区里已经有不少人在讨论高程数据的接入方案可以去翻翻看避免重复造轮子。最后分享一个我在使用 Arnis 时发现的小技巧生成之前先在 OSM 官网的编辑器里看一眼目标区域的数据质量。如果建筑轮廓画得很粗糙生成出来的效果也会很粗糙。花十分钟检查一下数据比生成后再返工要划算得多。
返回列表