基于Vue+Leaflet实现自己制作一份切片游戏地图

3,759 阅读13分钟

前言:前段时间一直想做一个有意思的地图相关的小玩意,但是想想,如果做常见的基于卫星图的什么导航啊之类的主题又太没创意,鉴于最近沉迷于荒野大镖客,突然灵光一想,用leaflet+Vue来实现一个大表哥2的地图来纪念一下这款R星神作。即本文提供了一个将JPG格式的地图图片转换生成Leaflet支持加载的离线瓦片地图的解决方案和思路。

一、实现步骤:

第一步:找到一张大表哥2的地图的图片,并且是带有地理信息的大表哥2的地图图片!!!(当然,我相信基本上大家跟我一样弄不到,别急,你先找一张JPG或者PNG格式的就行,等会会告诉你怎么弄,才能让它变成一张真正的地图)。

第二步:生成MBTiles格式的切片地图数据

第四步:在leaflet中加载MBTiles格式的数据(提供了能让leaflet去加载离线瓦片地图的方案)

第三步:加上一些相关的交互(看个人兴趣,不想加可以不看)

二、生成一张带有地理信息地图图片

一般呢,JPG,PNG格式的图片默认是不会带有地理信息的数据,如果想人为的给图片加上地理数据,就需要借用到一些GIS的软件,目前主流的GIS软件主要是ArcGIS,SuperMap等等,在这里我选用了SuperMap,之所以选用它是因为,它提供了免费使用的功能,并且提供了一些生成地图切片的服务,内容都比较全面,但有一个缺点哈,毕竟是免费的,生成的图片会有水印!。因为是国产的软件,并且超图的生态我觉得还是做的比较好的,所以下载流程也比较全面,直接看官网步骤来就行这里就不多介绍了。我这里用的是SuperMap桌面端的iDesktop 10i。

0.打开软件(废话),然后点击新建工作空间,然后选择保存的数据源路径,这里随便选一个模板就行。

1665204593850.png

因为我们不需要他的模板内容,所以我们右键这两个数据源,点击关闭就好

image.png

1.然后将事先准备好的JPG格式的大表哥2的地图给它拖进来。

image.png

2.然后核心步骤要来了,就是给这张JPG格式的图片加上地理信息。(简单点就是加上坐标系)

image.png

image.png

最后点击左上角的保存就完事,然后会弹窗然后填一下地图名字。这样就给这张普普通通的图片配置成一张带有地理坐标的地图啦。 这里注意一下,正常来说,需要对地图进行配准(通俗的说就是建立起图上点的坐标与坐标系真实坐标的关系,这里内容比较广泛,就不给大家展开讲了,想了解的可以自行搜索“地图配准”),但是由于我们是游戏地图,说白了就是虚拟的,鬼知道它真实坐标在哪,所以就从简直接简单粗暴设置坐标系就完事了,并且这里选用的坐标系是WGS_1984/Web_Mercator主要是因为方便,Leaflet支持的ESPG:3857说的就是它,所以就比较方便。

三、生成地图切片

这里我们需要到SuperMap的另一个家族成员iserver来帮我们生成切片地图,当然他也可以用来直接发布地图服务,我这里就选择了切片功能来帮我生成切片服务就行了。这里具体下载配置流程我就不说了,百度一大堆,咱们直接简单粗暴切正题。 0.打开软件,在iserver的bin目录下找到startup.bat。双击运行。

image.png

当出现这句时候,就是启动好了

image.png

1.然后打开浏览器输入localhost:8090 进入服务,iserver默认启动在8090端口,如果打开显示了没问题就证明服务没问题。接着我们进到主界面,点击菜单栏里的"服务管理"选项,注意跳转前需要让你登录(账户名就是你supermap桌面端的账户,要保持一致,不然找不到你的地图)

2.点击"分布式切图"

image.png

3.点击创建切片服务

image.png

4.设置要切片的地图

image.png

5.设置缓存范围(似乎这种自定义类型图片需要设置缓存范围才生效)

image.png

image.png

在设置好以后点击“创建切图任务”iserver就开始帮切片了等切好以后,就会在你填写的储存路径位置多了一个.mbtiles的文件,到这里我们地图的切片就搞好了 这里说明一下,当然还有其他方法来生成离线切片,并且也有其他格式的切片数据,有兴趣的可以研究一下,我这里也是探索了半天才找到一个.mbtiles格式的生成方案。

三.在leaflet中加载地图

很不幸,Leaflet没办法加载离线的切片地图,这里我提供了两种方式来让lealfet加载切片地图,我把方法类型分成两大类,第一大类是离线加载模式,即不需要地图服务直接可以加载的模式,第二大类则是通过地图服务来加载的方式。

离线瓦片加载模式

因为在正常的GIS开发过程中,一般都是加载地图服务器上的对应的地图服务来加载地图底图,所谓的“离线加载模式”呢,就是不同于往常的开发模式,即不需要把地图通过地图服务器发布成地图服务进行调用,直接调用文件夹中的瓦片地图。方法如下:

方法一:我们需要借助一个插件leaflet-tilelayer-mbtiles,这是一个可以让leaflet支持去加载mbtiles格式的离线瓦片地图。

(废弃,存在问题:开发环境下没问题,打包部署以后插件内部报错,但是这里也告诉大家一下,有兴趣的自己研究)

npm i leaflet-tilelayer-mbtiles

然后在加载地图代码如下:

     //实例化一个地图
      this.map = this.L.map("map", {
      center:[0.0129476784070194,0.017348860865779],
      crs:this.L.CRS.EPSG3857,
      maxBoundsViscosity:0.7,
      minZoom:15,
      maxZoom:18,
      zoomControl:false,
      attributionControl:false
      });
      //核心就是利用L.tileLayer.mbTiles这个方法来加载mbtiles格式的瓦片地图
      let url = "http://localhost:8080/map/dbg2Demo00_1007043336_256X256_PNG.mbtiles";
      const mb =   this.L.tileLayer.mbTiles(url).addTo(this.map);
        
        
        this.map.setView([0.0129476784070194,0.017348860865779], 16)
        this.L.marker(
        [0.0129476784070194,0.017348860865779],
        {icon:this.L.divIconPlus({iconUrl:'/icon/home.png',size:18})}
        ).addTo(this.map)

这里有一个值得注意的问题,因为当我们初始化地图时候要设置center属性来设置中点,这个中点我们去哪得到呢?主要是通过supermap的桌面端可以查看。

image.png

注意,这里从上到下是经纬度,但是在Lealet中是纬度经度,所以设置center时候要注意看清楚反过来写。 这时候,启动项目就可以看到地图了。

image.png

方法二、通过一个小工具将mbtiles转成离线切片服务(目前所用的方法),这个方法跟第一个方法不同的是需要一个工具来生成正常的离线切片,不需要第三方插件。这个工具叫做mbutil(需要Python环境),地址如下: github.com/mapbox/mbut… 当我们把他从github上clone下来并且安装好python环境以后,我们只需要两步,即可完成切片 1.在mbutil的文件夹下打开CMD,输入一下命令

python setup.py install

当他install完成以后,再输入

python mb-util 你的mbtiles文件的路径 切片生成后储存路径

注意:切片生成后储存路径 一定要是一个不存在的文件夹,就会失败

例如:

image.png

我的这个D:\dbg2Demo\test2是存在的,但是D:\dbg2Demo\test2下的tile文件夹是不存在的。这样他才能起作用

切好以后就是这个样子

image.png

接下来就是把这个tile文件夹放到项目里就可以用了

image.png

到这里我们就像平常使用一样直接

this.L.tileLayer(url).addTo(this.map);

不需要第三方的插件就可以直接展示地图啦。效果跟第一种一样,并且这个亲测部署起来以后没问题

image.png

以上就是目前想到的离线切片的地图加载方式,接下来介绍如何将游戏地图发布成地图服务。

地图服务加载模式

接下来介绍的是,跟传统开发那样,通过发布地图服务的方式进行地图的加载。

方法一、通过GeoServer发布。想必搞WebGIS开发的都知道GeoServer吧?GeoServer本质上是一个地图服务器,它是遵循OpenGIS Web 服务器规范的J2EE实现,通过它可以方便的将地图数据发布为地图服务,实现地理空间数据在用户之间的共享。另外,它也提供了相应的接口以允许用户对地理空间数据进行插入、更新、删除等操作。关于GeoServer的下载安装我这里就不多赘述了,网上一大堆,自行百度就好了,我这里主要关注于解决地图加载的问题。 值得注意的是,虽然可以通过扩展插件的方法来让GeoServer支持加载.mbtiles格式的文件,但是在测试中总出现一点麻烦,导致没办法实现。所以这里用到了另一种格式来进行操作。

1.与上面生成.mbtiles格式的流程一样,先打开iserver,来到"分布式切图服务",点击“创建切片服务”

image.png

这里的步骤完全跟上面生成mbtiles格式的文件一摸一样,只不过把储存类型换成GeoPackage格式即可。

2.打开GeoServer

image.png

然后新建一个工作空间

image.png

然后新建一个“存储仓库”

image.png

选择格式为"Geopackge"格式

image.png

填写相关信息,然后点击"保存",然后再点击"发布"

image.png

image.png

在填写好页面的相关信息以后,点击"保存"即可。

image.png

image.png

然后在图层预览中查看我们发布的服务是否成功

image.png

image.png

3.在leaflet中加载地图服务,还记得我们之前创建工作空间时候,勾选的"WMS"吗?没错,这里我们就需要通过Leaflet提供的加载wms的L.tileLayer.wms方法来加载就可以

        let url = "http://localhost:8089/geoserver/dbg2demo/wms"
       let rest= this.L.tileLayer.wms(url,{
          layers:'dbg2demo:dbg2Demo00999'
        }).addTo(this.map)
       this.map.setView([0.0129476784070194,0.017348860865779], 16)
   

WMS的URL就是地图预览页面的这一串

image.png

而layers则是

image.png

这样就可以完成在leaflet中加载GeoServer发布的WMS的地图服务了

这么一大段看下来有没有觉得比较麻烦?又是iserver又是geoserver的,别急!接下来介绍一下更简单的方法!!

方法二、不用GeoServer了iserver本身就有发布地图服务的功能!直接用它!一步到位

1.打开Iserver来到“服务管理"页面,选择“快速发布一个或一组服务”

image.png

选择“工作空间”,然后下一步

image.png 选择工作空间地址 image.png 选择要发布的服务,这里选rest服务和wms服务,rest作用我们等会儿会揭晓,然后一直下一步就好直到完成。

image.png

当出现打勾时候,就证明我们的服务发布好了 image.png

然后点击发布管理,来到你发布的地图服务页面。找到你刚发布的地图服务选项,点击进入。

image.png

image.png

这里可以看到有两个地址一个是wms的地址,一个是REST的。

image.png 先点击wms的地址,来到wms服务页面,复制这两个东西,他们分别是L.tileLayer.wms方法的url和layers image.png

        let url = "http://localhost:8090/iserver/services/map-dbg2Demo-4/wms111/dbg2Demo00"
       let rest= this.L.tileLayer.wms(url,{
          layers:'dbg2Demo00'
        }).addTo(this.map)
       this.map.setView([0.0129476784070194,0.017348860865779], 16)

这样我们就可以通过iserver发布一个wms的地图服务并在leaflet中加载了。 对了,之前我们勾选时候还选了一个rest服务你们还记得吗?他有什么用呢?其实目前他似乎是需要通过超图提供的iclient leaflet插件来加载,所以介绍一下怎么去使用这个地图服务 1.首先需要npm一个iclient leaflet的插件

npm install @supermap/iclient-leaflet 然后在项目中引用

import '@supermap/iclient-leaflet';

然后代码如下:

    var map, url ="http://localhost:8090/iserver/services/map-dbg2Demo-4/rest/maps/dbg2Demo00";
    map = L.map('map', {
        crs: L.CRS.EPSG3857,
        center: [0.0129476784070194,0.017348860865779],
        maxZoom: 18,
        zoom: 1
    });
    map.setView([0.0129476784070194,0.017348860865779],16);
    new L.supermap.TiledMapLayer(url).addTo(map);

url的地址来源:

image.png

image.png

image.png

这样一个rest服务的地图就已经可以在leaflet中加载了。

地图交互(不需要的可以不看)

当然只做一个地图啥也没有也不太舒服,于是我给地图加上了一些图标表明一些NPC的位置和自己拍摄的一些风景位置。可以给大家一个思路,想到就是用Marker在图上标点,然后确定点后,添加一个popup,点击图标弹出一个form表单的填写该点的信息然后发请求提交,因为Leaflet的popup要想显示表单,只能拼html字符串,但是字符串写html+js代码这很不优雅嘛,并且毕竟用了vue嘛,组件那么好用还能复用,怎么能不用呢?但是,leaflet并不支持在pop内容中直接加载vue组件。还好我有方法来实现,如果有需要的可以看小弟的另一篇文章《Leaflet中实现在popup中展示Vue组件功能》,这篇文章剖析了如何使得leaflet的popup可以去加载Vue子组件并渲染到地图上。需要用到的自取。目前效果如下:

image.png

image.png 该地图的部署在这里, 大伙有兴趣的可以瞅一哈,等我想到更多有意思的跟游戏有关的功能了我再往上加,或者大家有什么有意思的点可以跟我说一下。

最后

可能会存在更好更方便的解决方法,由于本人水平有限还没想出更快捷的方案吗,如果哪位大佬有的可以告知我一下下。把自己的想法逻辑发出来一是因为自己只是一个小菜鸟希望有不足之处可以被大佬们的指点然后好提高自己的能力,二是希望能帮助到大家,同时也想为社区做出一点贡献(我实在讨厌某论坛中一些只会C+V的帖子)。最后如果这篇文章对大家有帮助的话,请给一点本菜鸟一个小小的赞,让我这个卑微的菜狗能在今年秋招时候有点东西可以跟面试官吹牛。