📖阅读本文,你将

  • 了解 mapbox-glmaplibre-gl 这两款地图引擎的长短

  • 了解 天地图 这一权威地图平台的使用

  • 进行一个 瓦片风 地图的开发实战

关于锤子的隐喻

有人说:

“手里捏着锤子的人,看什么都像钉子。”

虽然有点挖苦的意思,但其实也可以理解为一种解决问题的方法和思路:“先把各种难题转换为自己熟悉的问题,然后就可以用自己熟悉的方式解决问题了。”

当然,这思路可能有 缘木求鱼 的挖苦意味在里面。

但是,有没有一种可能:

“我手里是一把多功能锤子!”

噢,对于我而言,mapbox-gl/maplibre-gl 就是我面对各种大屏地图开发需求的那把 多功能锤子

让我们看看这把锤子,究竟如何。

一、地图引擎的选择

谈到 GIS,就很难绕开 Mapbox 这家公司,毕竟目前世界上最广泛使用的 矢量瓦片标准 MAPBOX CECTOR TILE SPECIFICATION 正是这家公司发布制定的。

除此之外, mapbox 还提供了非常完全的地理信息服务、非常多的地图开发工具,其中就包括一款在前端开发者圈中非常热门的地图渲染引擎:

mapbox-gl

这也是我日常进行地图开发,所选择的地图引擎。

1.1 认识 mapbox-gl

mapbox-gl 是一款开源地图引擎。

它的 npmjs 地址:https://www.npmjs.com/package/mapbox-gl

它的 github 官网:https://github.com/mapbox/mapbox-gl-js

它的使用文档:https://docs.mapbox.com/mapbox-gl-js/

首先,我们要认识这个库,就要认识它的能力和边界,以下是我的个人使用总结:

mapbox是一款地图引擎,它能做什么?

  • 能通过各种投影系进行地图瓦片的投影。

  • 支持在地图瓦片上叠加各种图层,支持 geojson、图片、文本 等多种信息在图层上进行加载显示。

  • 支持自定义 Style (矢量瓦片)

  • 支持 2.5D 视角旋转及显示

  • 支持加载 3D 模型

  • 支持通过 DOM 的方式添加 HTML 元素

  • 支持 web-gl 能力进行图形渲染

  • 支持进行 3D 形式的球星地理渲染和星空背景渲染

尤其是其 “2.5D 视角旋转及显示”、”加载 3D 模型” 这两点,是非常亮眼的,相比于 OpenLayersLeaflet 这两款竞品,这也是它最为吸引人的地方所在。

但也不能盲目乐观,我也总结了使用中感受到不足的点:

  • 无法支持 地下管网开挖 这种形式的页面展示(相比于 Cesium

  • 3D 支持上能力比较弱(相比于 Cesium

  • 不够 open

不够 open” 想必你也有这样的困惑吧,为什么我会这样说?

mapbox-gl 开源,但很可惜,它也不是纯粹的 开源作品,虽然它确实 开源

这得从它的 accessToken 和账号注册 说起。

1.2 使用 mapbox?可能没那么容易

不久前,我曾在掘金发过一篇文章介绍 mapbox-gl: 《【一库】mapbox-gl!一款开箱即用的地图引擎》

但文章发布后,却收到很多小伙伴的反馈:”注册 mapbox 账号居然需要国际信用卡…

我去试了试:还真是

这是 mapbox2022年6月 新出的规定,注册账号必须绑定一张国际信用卡。这个要求,就让很多国内小伙伴想试用的成本大大提升了。

那么,可能有人就会问了:“mapbox 不是开源产品吗?不注册它们官方的账号,难道用不了吗?”问的很好,也很合理。

但是:

抱歉,真的用不了。

纳尼?引用一段 stackoverflow.com 上小伙伴对其的评价吧:

Mapbox have now changed mapbox-gl-js in version 2 to no longer be Open, you will have to have a key going forward.

翻译一下:

Mapbox 在 mapbox-gl-js@2.0 版本开始,已经不再开放。你必须有它家的 accessToken 才能进行下一步。

没错,没有国际信用卡,不能注册 mapbox,没有 mapbox 账号用不了 mapbox-glv2 版本。

好家伙,它是懂资本的。

那么?我的意思是:别用 mapbox-gl 了吗?

并不是,我只是要推荐一下它的孪生弟弟:

maplibre-gl

1.3 maplibre-gl:我比哥哥更开放

如果你想尝试 mapbox-gl 的各种炫酷能力,但你不想(能)注册 Mapbox 官网账号,现在,有了一个更好的选择:

maplibre-gl

它的 npmjs 地址:https://www.npmjs.com/package/maplibre-gl

它的 github 官网:https://github.com/maplibre/maplibre-gl-js

简单介绍一下:它就是 mapbox-gl 仓库 fork 出来的开放版本,无需 accessToken 就能品尝 mapbox-gl 的强大能力。

其他介绍?不用了,参照本文关于 mapbox-gl 的相关介绍即可。

1.4 一个简单的选择原则

到底是用 mapbox-gl 还是使用 maplibre-gl? 我提供一个我自己的简单原则:

  • 如果你希望使用 Mapbox 官方提供的瓦片服务,那选 mapbox-gl 就完事了。

  • 如果你只是希望使用其地图引擎的相关能力,并不打算使用 Mapbox 官方的瓦片服务,很好,你可以选择maplibre-gl 这款更加 Open 的开源引擎。

按照这个原则,本系列涉及到的各类 Demo 都会以 maplibre-gl 作为地图引擎进行开发。

二、 大屏的地图一般怎么玩?

在各种各样场景的大屏开发中,关于地图的展示,一般存在两种常见的玩法:

  • 线框风格 地图

  • 瓦片风格 地图

一款大屏到底选取哪种风格作为地图样式,通常是由 业务特点 决定的:

  • 如果业务方并不在意具体的业务地理位置,只在乎自己在每个省的营收关系、投资情况等粗粒度的数据展示及分析,那天然适合 线框风 地图。没有瓦片带来的地理信息细节干扰,展示上也更加清爽明白。

  • 但如果业务方非常在意实际的地理业务数据,关心自己的辖区在 XX街道XX区域,区域与区域之间的关联,事件在地理位置上的准确显示,那则适合选用 瓦片风 地图,提供精准的参考和地理信息。

maplibre-gl 最擅长的便是 瓦片风格 的地图,但不必担心,作为一款 多功能锤子,它也能轻松驾驭 线框风 的地图场景。

三、通过 “天地图” 获取在线瓦片服务

“天地图” 是由 “国家基础地理信息中心” 提供的一个地理信息服务平台。

通过 “天地图”,我们能够获得免费、权威的地理信息数据,也是很多人获取地图瓦片的首选方案。

官网:https://www.tianditu.gov.cn/

注册完成后,访问控制台(https://console.tianditu.gov.cn/api/key),申请 称为个人开发者,然后注册一个应用。

这样,你就能够获得一个自动生成的 key(密钥)。

这个 key 就是你后期请求瓦片的一个重要凭证。

  1. // 在文本后续的代码引用中,我都会用全局变量 MY_KEY 来代替我申请到的这个 `key`,这是为了避免你图方便把它用到了项目中。那对你而言是一件危险的事情。window.MY_KEY = '88******************2030'

有了这个密钥后,访问 地图服务清单http://lbs.tianditu.gov.cn/server/MapService.html),查看天地图提供的各类地图服务:![](https://mmbiz.qpic.cn/mmbiz_png/lCQLg02gtibtlAPMic7aOqrNchMzribzhQvDr5MYcVtPUNXc6EWt2iaf3nmtiaOoGOibx5CUx5QzYM3RvImWSaAgQJkQ/640?wx_fmt=png#alt=)

各类地图瓦片、标注瓦片,应有尽有。

通过这些提供的瓦片,你将可以快速搭建一个完全免费、且完全权威的地图页面,并且把业务数据展示其上。

四、用引擎显示地图

3.1 安装地图引擎

按照本文第 1.4 节【一个简单的选择原则】中所说,我们要使用 天地图 的瓦片,因此我们选用 maplibre-gl:

  1. yarn add maplibre-gl@latest

或者通过 cdn 的形式完成代码引入。

  1. <script src='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js'></script><link href='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css' rel='stylesheet' />

3.1 渲染天地图瓦片的地图

mapbox 的设计思路中,“地图” 是一个对象,你可以通过使用如下 API 快速初始化一个地图实例:

  1. <template> <div ref="mapEl" class="map"></div></template><script setup>import mapboxgl from 'maplibre-gl';import 'maplibre-gl/dist/maplibre-gl.css';import { onMounted, ref } from 'vue'const mapEl = ref(null)const initOption = { style: { "version": 8, "id": "43f36e14-e3f5-43c1-84c0-50a9c80dc5c7", "sources": { "tdt-vec": { "type": "raster", "tiles": [`https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${MY_KEY}`], "tileSize": 256 } }, "layers": [{ "id": "tdt-tiles-layer", "type": "raster", "source": "tdt-vec", }] },}onMounted(() => { const map = new mapboxgl.Map({ container: mapEl.value, ...initOption, });})</script><style lang="scss" scoped>.map { width: 600px; height: 300px;}</style>

通过以上代码,就能快速渲染一个基于 墨卡托投影天地图瓦片 的平面 瓦片风 二维地图。

大屏地图:从瓦片到引擎,到手把手实战 - 图1

发现没,不仅可以正确加载天地图的瓦片服务,还可以完成 2.5D 的视角倾斜。

上面代码中,所做的,正是简单生成了一个地图实例,其中最核心的代码在这里:

  1. "tiles": [`https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${MY_KEY}`],

声明了天地图瓦片资源的请求方式。

在码上掘金中你也可以试试:

代码片段

效果实现了,代码有了,但你想必还是一脸懵逼:

为什么要这么写呢?

这要说到 mapbox 系框架的基本 API 思路了:图层与资源

大屏地图:从瓦片到引擎,到手把手实战 - 图2

  • 图层(Layers): 我们所能看到的绝大部分内容都属于图层,这和 PhotoShop 里的图层概念很相似,图层间有层级关系;图层上可以设置各种布局(layout)属性和绘制(paint)属性,用来规定自己的显示特点。但归根结底,一张图层上显示什么,还是取决于它所引用的 资源(source)

  • 资源(Sources): 瓦片是资源,GeoJSON是资源,图片也是资源。资源是影响显示的第一要素。

所以,我们可以理解,如果在 mapbox 系中,要显示一个内容,起码需要两步:

  1. // step 1:添加资源map.addSource(...)// step 2:添加图层map.addLayer(...)

当然,上面生效的这段代码,是通过在初始化阶段把 资源图层 注入到了地图实例当中,我们完全可以换一种写法,同样能实现相关功能:

  1. map.on('load', () => { map.addSource('tdt-vec', { "type": "raster", "tiles": [`https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${MY_KEY}`], "tileSize": 256 }) map.addLayer({ "id": 'tdt-tiles-layer', "type": "raster", "source": "tdt-vec", })})

思路上是一致,只是添加资源及图层的时机不同罢了。

大屏地图:从瓦片到引擎,到手把手实战 - 图3

3.2 添加标注层

只有地理瓦片,对于很多人而言依然不足以表达出足够的地理信息,比如:

当前看到的是什么省、什么市、什么街道?

因此,在一张健全的地图上,地图标注 也是必要而关键的。

3.1 节示例代码的基础上,我们按照解释说明的思路,再添加 一个标注资源一个标注图层

  1. "sources": { // ... 上一节内容省略 "tdt-cva": { "type": "raster", "tiles": [`https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${MY_KEY}`], "tileSize": 256 }},"layers": [ //... 上一节内容省略 { "id": "tdt-cva-layer", "type": "raster", "source": "tdt-cva", },]

这样一来,我们的地图就不再单调了:

大屏地图:从瓦片到引擎,到手把手实战 - 图4

在码上掘金里亲手尝试吧:

代码片段

3.3 对地图颜色进行微调

通常来说,大屏是以深色作为主色调的,目前市面上最常见的大屏主题,前三排名为:

  1. 科技蓝

  2. 科技蓝

  3. 还TM是科技蓝

因此,如果地图底色过于鲜亮,可能会和 科技蓝 风格不搭,此时,你可以选择通过 layers.raster.paint 提供的一些配置,进行色相转换,满足自己的审美诉求。

比如,修改底图 layer 为:

  1. { "id": "tdt-tiles-layer", "type": "raster", "source": "tdt-vec", "paint": { "raster-brightness-max": 0.7, // 最大亮度 "raster-brightness-min": 0.3, // 最小亮度 "raster-hue-rotate": 20, // 色相变换的角度 "raster-saturation": 0.7 // 饱和度 } },

大屏地图:从瓦片到引擎,到手把手实战 - 图5

如果这种风格还不能满足你的诉求,你可以选择 “天地图 影像底图” 作为背景进行展示,修改底图和标注的来源为:

  1. "tdt-vec": { "type": "raster", "tiles": [`https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${MY_KEY}`], "tileSize": 256},"tdt-cva": { "type": "raster", "tiles": [`https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=${MY_KEY}`], "tileSize": 256}

两相对比:

大屏地图:从瓦片到引擎,到手把手实战 - 图6

很显然,影像底图会具备更好的在大屏上展示的效果。

四、加载业务信息

甲方要的不是世界地图,而是业务地图。

没有业务属性的地图,对于甲方而言,并无价值。

4.1 加载多边形块

假设我在地图上绘制了两个多边形,形成了一个 FeatureCollectionGeoJSON 数据。

你问我什么是 GeoJSON ? 你是不是还没看过上一篇基础知识篇?看紧去补补:《前端开发大屏地图?必知必会的基本知识》

那么,我应该如何把它们在地图上绘制出来,表现出两块区域的形状呢?

  1. map.on('style.load', () => { map.addSource('geojson-area-source', { type: 'geojson', data: geojsonArea // 你得到的geojson }) map.addLayer({ id: 'geojson-area-layer', type: 'fill', source: 'geojson-area-source', layout: {}, paint: { 'fill-color': 'red', 'fill-opacity': 0.5, }, }) })

没错,就是这么容易,还是我们之前总结的两步走:

  1. 添加资源

  2. 添加图层

大屏地图:从瓦片到引擎,到手把手实战 - 图7

4.2 加载图标及文本

假设,我们现在又 3 位靓仔正在地图上玩躲猫猫,我们希望标注出他们的位置,以及名称,我们应该怎么做?

记住两步走的法则:先加资源,再加图层

  • 资源1:头像

分别创建了三个人的头像:

  1. {zhuren: 'https://pic.zhangshichun.top/pic/20221129-12.png'bao: 'https://pic.zhangshichun.top/pic/20221129-10.png'nan: 'https://pic.zhangshichun.top/pic/20221129-11.png'}
  • 资源2:三位靓仔的坐标和信息
  1. {"type": "FeatureCollection","features": [ { "type": "Feature", "properties": { "name": "德育处主任", "icon": "zhuren" }, "geometry": { "coordinates": [ 114.34495622042738, 30.51879704948628 ], "type": "Point" } }, { "type": "Feature", "properties": { "name": "战场小包", "icon": "bao" }, "geometry": { "coordinates": [ 114.46248908403493, 30.52385942598788 ], "type": "Point" } }, { "type": "Feature", "properties": { "name": "南方者", "icon": "nan" }, "geometry": { "coordinates": [ 114.4188340204089, 30.481906063384173 ], "type": "Point" } }]}

开始编码!

首先,先定义一个方法,简化 maplibre 的挂在图片的逻辑:

  1. // 注册图片的方法const loadImages = async (imgs) => { await Promise.all( Object.entries(imgs).map( ([key, url]) => new Promise((resolve) => { map.loadImage(url, (error, res) => { if (error) throw error; map.addImage(key, res); resolve(res); }); }), ), );};

然后,两步走(先加资源,再加图层):

  1. // 加载图片await loadImages(images)// 添加位置资源map.addSource('boys-source', { type: 'geojson', data: boys})// 添加ICON图层map.addLayer({ id: 'boys-icon-layer', type: 'symbol', source: 'boys-source', layout: { 'icon-image': '{icon}', 'icon-size': 0.2, 'icon-anchor': 'center', 'icon-rotation-alignment': 'viewport', 'icon-allow-overlap': true }})// 添加名字图层map.addLayer({ id: 'boys-name-layer', "type": "symbol", source: 'boys-source', "layout": { "text-field": '{name}', "text-size": 14, 'text-offset': [0, 2.4], // 名字要设置便宜,避免被头像挡住 'text-allow-overlap': true }, "paint": { "text-color": "white", },})

效果达成:大屏地图:从瓦片到引擎,到手把手实战 - 图8

可以在码上掘金里亲自尝试:

代码片段

总体上来说,业务信息的加载,都是同样的逻辑,只要记住两步走的基本方针,就能完成绝大多数的业务需求。

五、总结

在本篇文章,我们系统性地了解了:

  • mapbox-glmaplibre-gl 两个库的使用范畴。

  • 学习了天地图的使用方法

  • 并且实战了几个简单的业务场景

碰到 瓦片风 的大屏地图开发,想必不会再难倒你了。

以上便是本次分享的全部内容,希望对你有所帮助