自建OpenStreetmap地图瓦片服务

自建OpenStreetsmap地图瓦片服务

地图显示效果

迫于openstreetsmap官方的瓦片服务器(tile server)速度太慢,而提供矢量瓦片(vector tile)服务的mapbox和maptiler的免费额度太少,更新慢。笔者最近尝试基于tileserver-gl,openmaptiles和tilemaker等工具自建了一个openstreetsmap的地图瓦片服务器。

数据准备

我们所需的地图数据有两种格式,分别为pbf和mbtiles。其中,mbtiles格式是可以直接被tileserver-gl使用的,而pbf格式需要转换为mbtiles之后才能被tileserver-gl读取。

mbtiles

如果希望快速搭建地图服务器,我们可以直接在https://extract.bbbike.org/这个网站上选择自己想要的区域并下载。下载时格式选择MB Vector Tiles Openmaptiles即可。但这样下载下来的mbtiles只有地标的name字段,其他的翻译全部丢失了。如果不介意这一点,可以在下载完后直接跳到「服务搭建」一节。

PBF

如果希望下载带有完整数据的PBF文件进行修改,我们可以在网站上选择下载PBF格式的地图,或者去 https://download.geofabrik.de/ 上按照国家下载PBF格式的地图。

转换格式

转换格式可以用两个软件,分别是tilemakeropenmaptiles。tilemaker的转换速度很快,不过转换出的mbtiles地图还是有上一节提到的「丢失属性」的问题,而openmaptiler在属性保留这一方面做的很好,而且还能从wikidata里自动下载对应地点的翻译(可选),不过转换的速度很慢。

根据笔者自己测试的转换速度比较,转换的是https://download.geofabrik.de/上下载的中国大陆地区地图,文件大小大约900M,用一台96C372G的机器进行转换。tilemaker转换用了大约半小时,而openmaptiler花了12个小时左右。转换完之后的mbtiles大小约3.5G。

使用tilemaker转换

首先下载tilemaker的二进制文件,之后准备好输入的文件,进行转换即可,机器可能需要安装luajitsqlite3shapelib等依赖。:

1
/root/build/tilemaker --input /data/asia.osm.pbf --output /data/asia.mbtiles --config /root/resources/config-openmaptiles.json --process /root/resources/process-openmaptiles.lua

其中,json配置文件和lua文件直接使用发行版自带的配置文件即可,ZOOM的值最大使用14即可,更大的值不会带来更多的细节

使用openmaptiles转换

参考openmaptiles的README进行转换即可,机器需要预装dockerdocker-compose,具体依赖可以参考这里。首先clone repo,之后在目录中执行(以下的几步需要拉取若干个docker镜像,加起来大约有10G,加上处理的数据,需要在磁盘中至少预留30G的空间比较保险):

(可选)如果要拉取维基百科的数据,则需要给docker添加代理。在.env中添加:

1
2
3
4
http_proxy: http://192.168.59.100:8118
https_proxy: http://192.168.59.100:8118
HTTP_PROXY: http://192.168.59.100:8118
HTTPS_PROXY: http://192.168.59.100:8118

(可选)如果PBF数据是从bbbike上切出来的,需要预处理一下数据的边界(mydata即下载下来的数据文件名):

1
2
3
4
mkdir -p data
mv mydata.osm.pbf data/
make generate-bbox-file area=mydata
./quickstart.sh mydata

如果需要增大执行数据库操作和地图切割操作的线程,可以更改.env里的MAX_PARALLEL_PSQLCOPY_CONCURRENCY

初始化数据文件夹:

1
make

准备数据库:

1
make start-db

导入PBF的数据:

1
make import-data

导入边界数据:

1
2
make import-osm
make import-borders

(可选)

导入维基百科的附加数据:

1
make import-wikidata

清理数据库:

1
2
3
make clean
make
make import-sql

生成地图边界:

1
make generate-bbox-file  # compute data bbox -- not needed for the whole planet

生成mbtiles文件:

1
make generate-tiles      # generate tiles

执行完成之后,在data文件夹下就能看到一个名为tiles.mbtiles的地图数据文件,复制出来即可。

服务搭建

随后使用repo中的docker-compose文件拉起容器,注意修改配置路径为自己的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
version: '3.3'
services:
tileserver-gl:
image: maptiler/tileserver-gl:latest
build: .
command: --public_url https://example.com/osm-tile/ --no-cors --config /config/config.json
ports:
- "58085:8080"
volumes:
- '/data/mirrors-zfs/cra-service/vector-tile-server/map:/data'
- '/data/mirrors-zfs/cra-service/vector-tile-server/config:/config'
- '/data/mirrors-zfs/cra-service/vector-tile-server/style/styles:/app/node_modules/tileserver-gl-styles/styles'
- '/data/mirrors-zfs/cra-service/vector-tile-server/style/fonts:/app/node_modules/tileserver-gl-styles/fonts'

docker compose中提到的styles,config等文件都已经放在了上面的repo中了。另外记得修改--public_url后面的值,以及docker暴露出来的端口。

如果修改了样式文件或配置文件,重启容器即可重载配置。有关tileserver-gl的更多用法,可以参考这里:https://tileserver.readthedocs.io/en/latest/

(可选)修改sprite

在部分的样式文件中,一些图标(比如麦当劳,肯德基,高速公路标志等)需要从外联的样式文件加载,可能会造成瓦片地图渲染缓慢。如果需要替换成本地文件或网络上的其他文件,可以修改样式json文件中的"sprite"一节。sprite文件的具体类型可以参考这个repo

sprite数据中的图标

全部配置完成之后,运行docker-compose up,等待镜像被拉起即可查看地图了。

如果打开地图时发现地图是空的,有可能是因为你并没有下载地图对应位置的数据,这时修改url末尾的经纬度即可(下面的url中#后面的那一串,分别是缩放级别/纬度/经度):

1
example.com/osm-tile/styles/osm-street/#11.16/22.5429/114.0402

在其他地图中引用瓦片服务器

可以参考mapbox写的这个例子,更改style变量即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://cdn.maptiler.com/mapbox-gl-js/v1.5.1/mapbox-gl.js"></script>
<link href="https://cdn.maptiler.com/mapbox-gl-js/v1.5.1/mapbox-gl.css" rel="stylesheet" />
<link href="/static/css/cloud_base.css?t=1627890485" rel="stylesheet" />
<style>
#map {position: absolute; top: 0; right: 0; bottom: 0; left: 0;}
</style>
</head>
<body>
<div id="map">
<a href="https://www.maptiler.com" style="position:absolute;left:10px;bottom:10px;z-index:999;"><img src="https://api.maptiler.com/resources/logo.svg" alt="MapTiler logo"></a>
</div>
<p><a href="https://www.maptiler.com/copyright/" target="_blank">&copy; MapTiler</a> <a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a></p>
<script>
// You can remove the following line if you don't need support for RTL (right-to-left) labels:
mapboxgl.setRTLTextPlugin('https://cdn.maptiler.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.1.2/mapbox-gl-rtl-text.js');
var map = new mapboxgl.Map({
container: 'map',
style: 'https://example.com/path/styles/your-style/style.json',
center: [113.99548, 22.60003],
zoom: 15.92
});
map.addControl(new MapboxLanguage({
defaultLanguage: 'en'
}));
</script>
</body>
</html>

https://github.com/systemed/tilemaker/issues/187

参考文档