前言

mapsforge 是一个 Android & Java Desktop 平台可用的地图库,支持 OpenStreetMap 地图数据的离线呈现。虽然这个库的应用并不是很广泛,但它能避免我们被卷入到任何商业屎坑当中,对于真正小而美的应用,这种纯粹由社区驱动的东西是不错的选择。

这篇文章主要记录自己裁切 OpenStreetMap 数据并交由 mapsforge 使用的过程,虽然这个过程看起来非常简单,但其实坑点遍布。

OpenStreetMap 地图要素概览

OpenStreetMap 的原始数据是由下列三要素组成的:

  • Nodes (点):以经纬度形式记录,既可以是 POI (兴趣点,比如一个厕所),也可以用以构成下面两个要素。
  • Ways (线):由一系列的点构成,既可以表示一条路,也可以表示一条河,还能够闭合形成多边形(polygons)。
  • Relation (关系):由点、线或其它关系组成,既可以是公交线路,又可以是多边形集合(multipolygon)。

OpenStreetMap 的原始数据可以由 xml 的形式存储,这种存储方式的后缀名为 osm ,以下是一个 osm 数据示例:

  <!-- 只具有坐标的普通点(用来被线和关系引用) -->
  <node id="1667371570" lat="30.3945386" lon="120.4455589" version="1" timestamp="2012-03-09T15:12:26Z" changeset="10920999" uid="17497" user="katpatuka"/>
  <node id="1667371571" lat="30.3946982" lon="120.4461141" version="1" timestamp="2012-03-09T15:12:26Z" changeset="10920999" uid="17497" user="katpatuka"/>
  ......
  <!-- 带有额外信息的兴趣点 -->
  <node id="5597510802" lat="30.3492928" lon="120.5494671" version="1" timestamp="2018-05-05T01:39:41Z" changeset="58694831" uid="6161085" user="方恨少">
    <tag k="bus" v="yes"/>
    <tag k="highway" v="bus_stop"/>
    <tag k="name" v="西三路长福杭路口"/>
    <tag k="public_transport" v="platform"/>
  </node>
  ......
  <!-- 使用线来描述道路 -->
  <way id="249654176" version="11" timestamp="2020-02-21T17:37:31Z" changeset="81327490" uid="10696633" user="billSJC">
    <!-- 根据 id 引用先前定义的点 -->
    <nd ref="4715922265"/>
    <nd ref="4715922266"/>
    <nd ref="7233661048"/>
    <nd ref="4715922267"/>
    <nd ref="4715922268"/>
    <nd ref="5597565392"/>
    <nd ref="4715922269"/>
    <nd ref="4729249918"/>
    <nd ref="4729249938"/>
    <!-- 解释它是什么,可以看到,它是道路 -->
    <tag k="highway" v="primary"/>
    <tag k="name" v="梅林大道"/>
    <tag k="oneway" v="yes"/>
  </way>
  ......
  <!-- 使用线来描述建筑物(画个建筑轮廓) -->
  <way id="1085232325" version="1" timestamp="2022-08-10T02:33:41Z" changeset="124708679" uid="13793923" user="BJMTR">
    <nd ref="9943953335"/>
    <nd ref="9943953336"/>
    <nd ref="9943953337"/>
    <nd ref="9943953338"/>
    <nd ref="9943953335"/>
    <tag k="building" v="train_station"/>
  </way>
  <!-- 使用关系来描述区域边界 -->
  <relation id="13705741" version="1" timestamp="2022-01-22T11:04:42Z" changeset="116461048" uid="7398995" user="xhtongyin">
    <!-- 引用点与线 -->
    <member type="node" ref="5468808304" role="admin_centre"/>
    <member type="way" ref="1023557420" role="outer"/>
    <member type="way" ref="1023557434" role="outer"/>
    <member type="way" ref="1023583493" role="outer"/>
    <member type="way" ref="1023557439" role="outer"/>
    <member type="way" ref="1023557432" role="outer"/>
    <member type="way" ref="1023557424" role="outer"/>
    <tag k="admin_level" v="8"/>
    <tag k="boundary" v="administrative"/>
    <tag k="name" v="河庄街道"/>
    <tag k="type" v="boundary"/>
  </relation>
  ......
  <!-- 使用多边形集合来绘制河流 -->
  <relation id="8272338" version="3" timestamp="2020-08-25T07:19:04Z" changeset="89896465" uid="7863825" user="season3">
    <!-- 这里有区分 inner 和 outer ,可以理解为在大区域中扣除小区域,比如去掉河中间的小岛 -->
    <member type="way" ref="585825585" role="inner"/>
    <member type="way" ref="585825584" role="outer"/>
    <member type="way" ref="585825586" role="inner"/>
    <member type="way" ref="585825587" role="inner"/>
    <member type="way" ref="840435380" role="inner"/>
    <tag k="natural" v="water"/>
    <tag k="type" v="multipolygon"/>
    <tag k="water" v="river"/>
  </relation>

可以看到,相同的要素类型可以是截然不同的东西,它是什么完全是由 tag 决定的,way 既可以是公路又可以用来描绘江河湖海与建筑,这一切的一切都需要地图渲染器的实现,才能将如此复杂的信息呈现在人们的眼前 ↓

OpenStreetMap 格式

上面,我们看到的是以 xml 形式组织的 osm 格式数据,这种格式易于阅读,但是不易于保存,因为它的占用空间实在是有点太大了。

于是,社区基于 protobuf 开发了一种自带序列化、高压缩率、高读写效率的格式,命名为 pbf 。这种格式是一种 binary ,也就是说用文本编辑器打开只会看到乱码,它其中所保存的信息量是与 osm 格式相等的,由于具有更高的压缩率,它更适合通过互联网进行分发。

社区提供了工具用于在 osmpbf 格式之间进行转换,以下是两个典型的工具:

  • osmosis : Java 实现、支持插件、高拓展性,可作为 OpenStreetMap 数据处理的标准参照,除了具有格式转换功能,还能用于数据的裁切、合并、过滤、映射等,甚至还能和数据库协同工作。
  • osmconvert : Native 实现,效率更高,功能少一些且不支持插件拓展,但基本的格式转换和裁切还是 ok 的。

使用 osmosis 进行格式转换的简单示例:

osmosis --read-pbf file=myregion.pbf --write-xml file=myregion.osm
osmosis --read-xml file=myregion.osm --write-pbf file=myregion.pbf

使用 osmconvert 进行格式转换的简单示例(它会通过后缀名自动判断格式):

osmconvert region.osm -o=region.pbf
osmconvert region.pbf -o=region.osm

↑ 当然,你可以在上面链接到的 wiki 中找到它们的详细使用示例和安装方法,像 osmosis 是需要额外配置 java 环境的,这里就不赘述了。

需要注意的是,上面我们讨论的是 OpenStreetMap 的数据格式,这些格式是为 OpenStreetMap 量身定制的。放眼到整个 GIS (Geographic information system) 生态中,osmpbf 格式并不是常见和通用的,也并不是所有 GIS 软件都支持打开和编辑它们(1)。在实际的 GIS 工作流中,OpenStreetMap 数据更多只是被作为数据源,需要使用一些工具对其进行提取和转换,从而得到相关 GIS 软件能够实际接受的格式,如 shapefiles

↑ 之所以要提一嘴 shapefiles ,是因为在后面的 mapsforge 地图制作中会用到它们,因此不要太陌生。

注释:

  • (1) e.g. 我试了下 QGIS 虽然能正常打开 osmpbf ,但是其中的地图元素并不能完美的渲染和呈现。

mapsforge 格式转换

就像各种 GIS 软件会整出自己的私有格式一样,mapsforge 也有自己的私有格式——是的,它并不能直接打开 osm / pbf 文件,必须对原始的 OpenStreetMap 格式进行转换(1),才能得到 mapsforge 所能呈现的格式。

mapsforge 的格式说明可以在这里找到(看起来私有格式是为了性能和效率,不过作为使用者并不关心这么多)。

mapsforge 官方提供了一个 osmosis 插件用于将 OpenStreetMap 数据转换为 mapsforge 的格式,可以在这里找到其使用说明。

简单来说,

  • 安装 osmosis
  • 这里(2)下载插件本体。
  • 将插件放到 $HOME/.openstreetmap/osmosis/plugins

插件的基本使用也非常简单,

osmosis --read-xml file=example.osm --mapfile-writer file=out.map
osmosis --read-pbf file=example.pbf --mapfile-writer file=out.map

(输入部分的命令格式和上面的 osmosis 格式转换案例是一样的,唯一需要改动的是输出部分,将原来的 --write-xml / --write-pbf 改为插件提供的 --mapfile-writer

那么,是不是拿一组 osm 数据丢进去转换,就能得到完美结果了呢?

试试,

↑ 我海呢??

很显然,我们好像缺失了一些什么。

注释:

  • (1) 这一格式转换是单向的,或者说,双向的转换并没有被开发,你只能从 OpenStreetMap 格式得到 mapsforge 格式,不能反过来,因此,二次裁切别人已经预制好的 mapsforge 地图是不可行的。
  • (2) 这里是链接到插件的 0.20.0 版本,如果后续插件有更新,你可能需要跟着原始使用说明手动下个最新版。

海与 mapsforge 地图层次结构

先前的例子中,我已经展示了河流和湖泊的呈现方式——凡是具有一定宽度的河流/湖泊都会采用多边形(集合)的方式进行呈现,说白了,它们都有自己的形状数据。

那么,海呢?海是如何呈现的呢?

海,很特别,它们是难以用多边形(集合)来呈现的:

  • 无论是去记录海洋还是陆地的多边形(集合),它们都太大了,难以维护且性能不佳。
  • 一些海洋区域没有可验证的边界,而多边形(集合)是有边界的,这可能导致不必要的争端。

早期, OpenStreetMap 确实用多边形来记录过部分海区,但这种行为现在被认为是不可取的

现在,无论是海还是洋,都只用简单的 node 记录——在海洋的中心区域打个点,让地图在那里显示个标签就行了。

至于海和陆地的颜色区分,就靠 coastline 了。

coastline 就是普通的 way ,只是被打上了 natural=coastline 的特殊 tag 。

  <way id="365056245" version="8" timestamp="2024-01-20T12:32:05Z" changeset="146477179" uid="7120051" user="anthropologist">
    <nd ref="7719253628"/>
    <nd ref="7719253629"/>
    <nd ref="1684830963"/>
    <nd ref="7642469589"/>
    ......
    <tag k="natural" v="coastline"/>
    <tag k="source" v="PGS"/>
  </way>

由于维护不当,coastline 不一定是闭合连续的,因此海岸线的渲染就对渲染器提出了很高的要求。

事实上,openstreetmap.org 上的在线地图看起来也并没有用实时的海岸线数据来生成海洋,而是使用了预处理的陆地多边形来蒙混过关

同理,mapsforge 也在早先版本放弃了对海洋的实时渲染,转而使用了地图制作时的预处理手段,可以在这里找到处理方法。

简单来说,mapsforge 也采用预先处理好的陆地多边形来蒙混过关,制作地图时,需要:

  • 放一块蓝色的矩形区域在最底层,作为海洋。
  • 在蓝色矩形上摆上陆地多边形,于是就有了海洋和陆地。
  • 将 OpenStreetMap 的地图数据放在最上层,于是就得到了完整的地图。

实践 mapsforge 地图制作

接下来,跟着官方提供的处理方法,实践一下这个过程。虽然官方文档看起来比较详细,但其中还是有许多坑点。

地图数据选择与裁切

首先,地图数据从何而来?

不可用:OpenStreetMap 数据导出功能

OpenStreetMap 本身提供了导出功能,较小的区域可以直接通过导出按钮导出,稍大的区域则可以通过 Overpass API 导出。

↑ 这种导出可以直接得到对应区域的 OSM 格式数据,但是,用此类 OSM 数据制作得到的 mapsforge 地图是存在问题的 ↓

(↑ 嗯?我钱塘江里的水呢?)

这种导出方式是存在问题的,由其得到的数据很有可能会丢失多边形(集合),无论是河流、湖泊还是绿地或建筑。

造成问题的原因很简单:这种导出方式只是简单过滤并保留了目标区域内的 node ,但是多边形(集合)可能部分的位于区域外,这就会造成多边形(集合)被破坏(比如变得不闭合),从而造成数据丢失。

解决方法也是显而易见的:我们需要在本地裁切数据,并在裁切时保障数据不被破坏。

正确裁切数据

我们稍后再谈数据源,先来看看裁切方式。

Both osmosis and osmconvert 都可以提供不具有破坏性的数据裁切功能,但是,我们先来简单复现一下服务器上的那种破坏性裁切:

osmosis --rb file=example.pbf --bb left=左经度 right=右经度 top=上纬度 bottom=下纬度 --wx file=out.osm
osmconvert example.pbf -b=左经度,下纬度,右经度,上纬度 -o=out.osm

↑ 其中 --rb 就是之前 --read-pbf 的缩写,--wx 则是 --write-xml 的缩写,--bb-b 则分别对应着两个程序的裁切功能,需要在后面传入区域的边界信息。

想要数据不被破坏,则需要传入额外的参数,变成:

osmosis --rb file=example.pbf --bb left=左经度 right=右经度 top=上纬度 bottom=下纬度 completeRelations=yes --wx file=out.osm
osmconvert example.pbf -b=左经度,下纬度,右经度,上纬度 --complete-ways --complete-multipolygons --complete-boundaries -o=out.osm

你可以在对应程序的文档中(osmosis osmconvert)找到这些参数的含义,简单来说,当一个 way 或 relation 中的一个成员存在于目标区域中时,这个 relation 中的其它成员数据也需要被保留,从而确保目标的完整性。这个过程是需要算力的,实际运行一下就会发现这种 complete 的裁切要比简单裁切慢得多,也许这就是为什么服务器没有提供 complete 的导出功能吧(毕竟简单的导出只要 filter 一下数据库)。

osmosisosmconvert 都可以用于数据裁切,得到的结果在肉眼上没有区别,但不知为什么 osmconvert 得到的结果总是更小,无论是 osm 还是 pbf 文件:

(↑ 两个工具裁切相同区域得到的结果大小对比,其中 osmapi 是 Overpass API 导出的结果)

而且,实际体验下来,osmconvert 的速度要快非常多,因此我在这里并不建议使用 osmosis 来进行裁切。

除此之外,mapsforge 提供的插件本身也带有裁切功能,比如:

osmosis --read-pbf file=example.pbf --mapfile-writer bbox=下纬度,左经度,上纬度,右经度 file=out.map

那么,先裁切再转换,和由插件在转换时进行裁切,有何区别呢?

简单来说,插件的裁切功能是效率堪忧且内存消耗巨大的,如果输入的地图非常大,先裁切再转换是非常有必要的。

比如,对于以下任务:将中国地图裁切到杭州区域并导出为 mapsforge 格式。

如果全部交给 mapsforge 的插件进行,使用插件自带的裁切功能,需要 40G 左右内存才能完成(虽然内存不足时可以传入 type=hd 的参数来用临时文件代替内存,但那样速度会更更更慢)。
如果先使用 osmconvert 裁切到杭州区域,再使用插件导出和裁切,可能 2G 内存就能搞定,而且速度会快不少。

下面的世界地图,就是另一个非常夸张的例子。

正确选择数据源:为了效率,不要对世界地图下手

也许,初次玩转 OSM 的你会选择从“OSM 星球”下载完整的 OpenStreetMap 数据库副本作为裁切基础,但,别这么做,你应该在下面的 “Geofabrik” 中下载更小的、以区域或国家为单位的数据,因为裁切世界地图是一件极为痛苦的事情。

这里有一些非常夸张的踩坑。

比如,我先尝试使用 mapsforge 插件直接将世界地图裁切到杭州区域,因为内存完全不够所以采用了 type=hd 的模式。
结果:生成了 1.1TB 的临时文件,卡了一天也没什么进度。
(为了避免 /tmp 被耗尽,你可以使用 export JAVA_OPTS="-Djava.io.tmpdir=/path/to/another/tmp 来改变临时文件的存储位置)

然后,我又尝试了使用 osmosis 将世界地图裁切到杭州区域。
结果:生成了 148G 临时文件,运行了近半天后终于裁切成功。

最后,我尝试了使用 osmconvert 将世界地图裁切到杭州区域。
结果:约一个小时就完成了,我记得临时文件也没多大。

从这里也可以看出,osmconvert 的效率不知道高到哪里去了。不过,还是那句话,你永远应该使用 Geofabrik 中预先裁切好的数据,e.g. 将预先裁切好的中国地图使用 osmconvert 裁切到杭州区域只需要花费几分钟的时间,比处理世界地图效率高了 114514 倍。

获得陆地多边形

在上一节中,我们获得了地图数据,在这一节中,我们将要获取的是地图之下的陆地多边形。

我们参照官方文档的操作,从 openstreetmap.de 获取陆地多边形数据。

你可以将这个网站的数据理解为:对 OpenStreetMap 的原始数据进行了修复和加工,并从中提取了一些关键信息(比如将 coastline 实际转换为可用的陆地多边形),供其它 GIS 工作者使用。由于其服务对象的广大的 GIS 工作者,因此其采用 shapefile 的方式提供陆地多边形数据。

对于我们,需要下载的是采用 WGS84 坐标系统的 split 数据 ↓

至于 split 和 not split 数据的区别,可以导入到 QGIS 瞅一瞅 ↓

split 其实就是将大多边形拆成了多个小多边形,这种操作可以显著提高处理性能(not split 的文件在 QGIS 中根本就难以渲染)。

接下来,解压压缩包,我们可以使用 ogr2ogr 程序对陆地多边形进行裁切(比如将世界的陆地多边形裁切到杭州区域),这一步的裁切操作其实是可选的,但对效率的提升是有帮助的(和上面同理,可以交给 mapsforge 的插件进行最终裁切,但性能非常烂就是了)。

ogr2ogr -overwrite -progress -skipfailures -clipsrc 左经度 下纬度 右经度 上纬度 out.shp input.shp

↑ 需要注意的是,虽然看起来输入输出的只是 shp 文件,但 shapefile 的那一堆文件是共同提供信息的,因此,不要删除压缩包中除 shp 外的其它文件,同理,生成的文件也不仅仅只是 out.shp ,也许你需要准备一个新文件夹来防止它弄脏当前目录 :) 。

ogr2ogr 是一个程序,它可以在不同的地理矢量数据格式之间进行转换和操作。它支持多种选项来定制输出格式,坐标系,几何类型和空间过滤。它是 GDAL 工具的一部分,GDAL 是一个用于处理地理空间数据的开源库。 ← 来自 new bing

ogr2ogr 可以通过 sudo apt install gdal-bin 安装。

在上面的这步中,我们只是对 shapefile 进行了裁切,它仍然是 shapefile ,接下来,我们需要将 shapefile 转换成 osm 文件,从而能够合成到我们最终的 mapsforge 地图文件中去。

从 shapefile 到 osm 的转换需要用到 shape2osm.py 脚本,这个脚本也依赖于 gdal-bin ,所以请先安装它。

要执行转换,只需运行 python3 shape2osm.py -l out input.shp 即可,input.shp 是作为输出的 shapefile ,脚本运行后会生成一个或多个 out 开头的 osm 文件,这些文件将在后续的合成过程中用到。

值得注意的是,这里得到的 osm 文件中的多边形(集合)会带有 natural=nosea 的 tag ,以确保在后续渲染中被呈现为陆地的颜色。

最后再强调一下,一定要下载 split 的 shapefile ,虽然原始文档中说 the non split version could also work ,但那其实是错的。

比如对于这样一个区域 ↓

采用 non split 版本得到的结果会是这样的 ↓

↑ 可以看到,对左侧成块陆地区域的处理直接出问题了,陆地消失不见,只留下了小岛。

此外,ogr2ogr 本身也有概率抽风,比如这样,一个区块直接丢失了 ↓

↑ 对于这种情况,稍稍调整裁切的经纬度往往可以解决,不过要是嫌烦,可以干脆跳过使用 ogr2ogr 进行裁切的过程,直接执行 shape2osm ,让 mapsforge 插件提供的裁切功能为你兜底。

此外,我也试了一下对 non split 版本直接执行 shape2osm ,然后喂给 mapsforge 插件执行裁切,你猜怎么招?直接卡死在 handle ways 了,嗯,这个 non split 版本是真的一点也没法用。

准备海洋

海洋,只是一个普通的 osm 矩形,带有 natural=sea 的 tag 以确保被以蓝色呈现。

这里有一个海洋的范本,只需要将里面的 $LEFT$RIGHT$TOP$BOTTOM 字符替换为你的经纬度边界即可。

合成

合成需要使用 osmosis 工具进行,你可以将合成的结果输出为 osm / pbf 文件,也可以像这样一次性输出为 mapsforge 格式:

osmosis --rx file=地图.osm --rx file=海洋矩形.osm --s --m --rx file=陆地多边形1.osm --s --m --rx file=陆地多边形2.osm --s --m --rx file=陆地多边形3.osm --s --m --mapfile-writer out.map bbox=下纬度,左经度,上纬度,右经度

说白了,除第一个输入外,后续每一个输出都要带上 --s --m 来执行合并,由于陆地多边形可能存在多个文件,因此每个文件都需要被输入到其中,最后的 bbox 是 mapsforge 的插件提供的裁切功能,起到兜底作用,因此即使前面传入的参数未经裁切预处理,也能保障输出结果的正确,只是由插件进行裁切效率极差。

造好的轮子

其实,mapsforge 官方本身就提供了预编译好的地图,所以,如果你要求不高,完全可以取用这里的。

用以编译这些预编译地图的脚本在这里 (mapsforge-creator) ,上面的指南中也是取用了这里的 sea.osmshape2osm.py

但是,如果你像我一样,有着裁切特定区域的需求,那上面的脚本就不适用了,因为它采用的是 Geofabrik 提供的预裁切数据,并不支持手动输入区域边界。

于是,我也写了一个脚本,支持手动的区域裁切,还原正确的海洋,并采用了上面总结出来的最佳实践。

#!/usr/bin/bash
set -e

# 如果最后一步报堆空间不足的话,可以选择更大的堆空间
# export JAVA_OPTS="-Xmx32G"

HOME_PATH="$(dirname "$0")"
WORK_PATH="$(dirname "$0")/tmp"

OSM_PBF=$1
LAND_SHP=$2
LEFT=$3
BOTTOM=$4
RIGHT=$5
TOP=$6

cd "$(dirname "$0")"
rm -rf "$WORK_PATH"
mkdir -p "$WORK_PATH"

# 预裁切地图,大大加快后续处理速度和降低内存占用
echo "Clipping the raw map..."
osmconvert "$OSM_PBF" "-b=$LEFT,$BOTTOM,$RIGHT,$TOP" \
    --complete-ways --complete-multipolygons --complete-boundaries \
    "-o=$WORK_PATH/clipped.pbf"

# 准备陆地层,根据陆地的 shapefile ,生成对应的 osm 文件
echo "Processing shapefiles..."
# if 需要裁切 shapefile 的话(能使后续处理速度更快,但可能丢失区块)
ogr2ogr -overwrite -progress -skipfailures -clipsrc "$LEFT" "$BOTTOM" "$RIGHT" "$TOP" "$WORK_PATH/land.shp" "$LAND_SHP"
python3 "$HOME_PATH/shape2osm.py" -l "$WORK_PATH/land" "$WORK_PATH/land.shp"
# else
# python3 "$HOME_PATH/shape2osm.py" -l "$WORK_PATH/land" "$LAND_SHP"
# endif

# 准备一个和所需区域等大的海洋层
cp -f "$HOME_PATH/sea.osm" "$WORK_PATH"
sed -i "s/\$BOTTOM/$BOTTOM/g" "$WORK_PATH/sea.osm"
sed -i "s/\$LEFT/$LEFT/g" "$WORK_PATH/sea.osm"
sed -i "s/\$TOP/$TOP/g" "$WORK_PATH/sea.osm"
sed -i "s/\$RIGHT/$RIGHT/g" "$WORK_PATH/sea.osm"

# 叠加海洋、陆地、地图,输出最终结果
CMD="osmosis --rb 'file=$WORK_PATH/clipped.pbf'"
CMD="$CMD --rx 'file=$WORK_PATH/sea.osm' --s --m"
for f in "$WORK_PATH"/land*.osm; do
    CMD="$CMD --rx 'file=$f' --s --m"
done
CMD="$CMD --mapfile-writer 'file=$HOME_PATH/map.map' \
bbox=$BOTTOM,$LEFT,$TOP,$RIGHT tag-values=true"
echo "$CMD"
eval "$CMD"

脚本依赖于 mapsforge-creator 仓库中的部分文件,因此,请先克隆该仓库,然后在仓库中创建本脚本(不妨命名为 run.sh ),最后:

bash run.sh 地图文件.osm/pbf shapefile路径 左经度 下纬度 右经度 上纬度

# 比如
bash run.sh china-latest.pbf land-polygons-split-4326/land_polygons.shp 120.0751 30.0750 120.9382 30.4185

↑ 别忘了 sudo apt install gdal-bin ,还要把 osmosisosmconvert 增加到 PATH 中 。

hint: osmosis 所执行的每一个流程基本都是单核负载,所以如果你发现运行 osmosis 时长期负载过高(比如直接全核满载了),很有可能是因为 java heap space 不足造成 jvm 不断进行 gc ,此时增大 jvm 堆空间可以有效降低 CPU 负载并提升速度。此外,虽然 mapsforge 的插件提供了 threads 选项允许指定线程数量,但不是很建议用它,因为好像它很容易导致 osmosis 卡死。

参考资料