大屏地图:从瓦片到引擎,再到手把手实战
点击小卡片参与粉丝专属福利
??阅读本文,你将了解 mapbox-gl 和 maplibre-gl 这两款地图引擎的长短了解 「天地图」 这一权威地图平台的使用进行一个 「瓦片风」 地图的开发实战关于 「锤子」 的隐喻有人说:
?“手里捏着锤子的人,看什么都像钉子。”
?虽然有点挖苦的意思,但其实也可以理解为一种解决问题的方法和思路:“先把各种难题转换为自己熟悉的问题,然后就可以用自己熟悉的方式解决问题了。”
当然,这思路可能有 「缘木求鱼」 的挖苦意味在里面。
但是,有没有一种可能:
?“我手里是一把多功能锤子!”
?噢,对于我而言,mapbox-gl/maplibre-gl 就是我面对各种大屏地图开发需求的那把 「多功能锤子」。
让我们看看这把锤子,究竟如何。
一、地图引擎的选择谈到 GIS,就很难绕开 Mapbox 这家公司,毕竟目前世界上最广泛使用的 「矢量瓦片标准」 MAPBOX CECTOR TILE SPECIFICATION 正是这家公司发布制定的。
除此之外, mapbox 还提供了非常完全的地理信息服务、非常多的地图开发工具,其中就包括一款在前端开发者圈中非常热门的地图渲染引擎:
?mapbox-gl。
?这也是我日常进行地图开发,所选择的地图引擎。
1.1 认识 mapbox-glmapbox-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 模型" 这两点,是非常亮眼的,相比于 OpenLayers 和 Leaflet 这两款竞品,这也是它最为吸引人的地方所在。
但也不能盲目乐观,我也总结了使用中感受到不足的点:
无法支持 「地下管网开挖」 这种形式的页面展示(相比于 Cesium)3D 支持上能力比较弱(相比于 Cesium)不够 open“「不够 open ?」” 想必你也有这样的困惑吧,为什么我会这样说?
?mapbox-gl 开源,但很可惜,它也不是纯粹的 「开源作品」,虽然它确实 「开源」。
?这得从它的 accessToken 和账号注册 说起。
1.2 使用 mapbox?可能没那么容易不久前,我曾在掘金发过一篇文章介绍 mapbox-gl: 《【一库】mapbox-gl!一款开箱即用的地图引擎》
但文章发布后,却收到很多小伙伴的反馈:"「注册 mapbox 账号居然需要国际信用卡...」"
我去试了试:「还真是」!
?这是 mapbox 在 2022年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-gl的 v2 版本。
好家伙,它是懂资本的。
那么?我的意思是:别用 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 就是你后期请求瓦片的一个重要凭证。
//在文本后续的代码引用中,我都会用全局变量MY_KEY来代替我申请到的这个`key`,这是为了避免你图方便把它用到了项目中。那对你而言是一件危险的事情。
window.MY_KEY='88******************2030'
有了这个密钥后,访问 「地图服务清单」(http://lbs.tianditu.gov.cn/server/MapService.html),查看天地图提供的各类地图服务:
各类地图瓦片、标注瓦片,应有尽有。
通过这些提供的瓦片,你将可以快速搭建一个完全免费、且完全权威的地图页面,并且把业务数据展示其上。
四、用引擎显示地图3.1 安装地图引擎按照本文第 1.4 节【一个简单的选择原则】中所说,我们要使用 「天地图」 的瓦片,因此我们选用 maplibre-gl:
yarnaddmaplibre-gl@latest
或者通过 cdn 的形式完成代码引入。
scriptsrc='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.js'/script
linkhref='https://unpkg.com/maplibre-gl@latest/dist/maplibre-gl.css'rel='stylesheet'/
3.1 渲染天地图瓦片的地图在 mapbox 的设计思路中,“地图” 是一个对象,你可以通过使用如下 API 快速初始化一个地图实例:
template
divref="mapEl"class="map"/div
/template
scriptsetup
importmapboxglfrom'maplibre-gl';
import'maplibre-gl/dist/maplibre-gl.css';
import{onMounted,ref}from'vue'
constmapEl=ref(null)
constinitOption={
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(()={
constmap=newmapboxgl.Map({
container:mapEl.value,
...initOption,
})
/script
stylelang="scss"scoped
.map{
width:600px;
height:300px;
}
/style
通过以上代码,就能快速渲染一个基于 「墨卡托投影」、「天地图瓦片」 的平面 「瓦片风」 二维地图。
发现没,不仅可以正确加载天地图的瓦片服务,还可以完成 2.5D 的视角倾斜。
上面代码中,所做的,正是简单生成了一个地图实例,其中最核心的代码在这里:
"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 思路了:「图层与资源」。
图层(Layers): 我们所能看到的绝大部分内容都属于图层,这和 PhotoShop 里的图层概念很相似,图层间有层级关系;图层上可以设置各种布局(layout)属性和绘制(paint)属性,用来规定自己的显示特点。但归根结底,一张图层上显示什么,还是取决于它所引用的 「资源(source)」。
资源(Sources): 瓦片是资源,GeoJSON是资源,图片也是资源。资源是影响显示的第一要素。
所以,我们可以理解,如果在 mapbox 系中,要显示一个内容,起码需要两步:
//step1:添加资源
map.addSource(...)
//step2:添加图层
map.addLayer(...)
当然,上面生效的这段代码,是通过在初始化阶段把 「资源」 和 「图层」 注入到了地图实例当中,我们完全可以换一种写法,同样能实现相关功能:
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.2 添加标注层只有地理瓦片,对于很多人而言依然不足以表达出足够的地理信息,比如:
?当前看到的是什么省、什么市、什么街道?
?因此,在一张健全的地图上,「地图标注」 也是必要而关键的。
在 3.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",
},
]
这样一来,我们的地图就不再单调了:
在码上掘金里亲手尝试吧:
代码片段
3.3 对地图颜色进行微调通常来说,大屏是以深色作为主色调的,目前市面上最常见的大屏主题,前三排名为:
科技蓝科技蓝还TM是科技蓝因此,如果地图底色过于鲜亮,可能会和 「科技蓝」 风格不搭,此时,你可以选择通过 layers.raster.paint 提供的一些配置,进行色相转换,满足自己的审美诉求。
比如,修改底图 layer 为:
{
"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//饱和度
}
},
如果这种风格还不能满足你的诉求,你可以选择 "天地图 影像底图" 作为背景进行展示,修改底图和标注的来源为:
"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
}
两相对比:
很显然,影像底图会具备更好的在大屏上展示的效果。
四、加载业务信息甲方要的不是世界地图,而是业务地图。
没有业务属性的地图,对于甲方而言,并无价值。
4.1 加载多边形块假设我在地图上绘制了两个多边形,形成了一个 FeatureCollection 的 GeoJSON 数据。
?你问我什么是 GeoJSON ? 你是不是还没看过上一篇基础知识篇?看紧去补补:《前端开发大屏地图?必知必会的基本知识》
?那么,我应该如何把它们在地图上绘制出来,表现出两块区域的形状呢?
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,
},
})
})
没错,就是这么容易,还是我们之前总结的两步走:
添加资源添加图层4.2 加载图标及文本假设,我们现在又 3 位靓仔正在地图上玩躲猫猫,我们希望标注出他们的位置,以及名称,我们应该怎么做?
记住两步走的法则:「先加资源,再加图层」。
资源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:三位靓仔的坐标和信息{
"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 的挂在图片的逻辑:
//注册图片的方法
constloadImages=async(imgs)={
awaitPromise.all(
Object.entries(imgs).map(
([key,url])=
newPromise((resolve)={
map.loadImage(url,(error,res)={
if(error)throwerror;
map.addImage(key,res);
resolve(res);
}),
),
};
然后,两步走(先加资源,再加图层):
//加载图片
awaitloadImages(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",
},
})
效果达成:
可以在码上掘金里亲自尝试:
代码片段
总体上来说,业务信息的加载,都是同样的逻辑,只要记住两步走的基本方针,就能完成绝大多数的业务需求。
五、总结在本篇文章,我们系统性地了解了:
mapbox-gl 和 maplibre-gl 两个库的使用范畴。学习了天地图的使用方法并且实战了几个简单的业务场景碰到 「瓦片风」 的大屏地图开发,想必不会再难倒你了。
下一章,我们将继续学习如何开发 「线框风」 的大屏地图!
如果文章对你有帮助的话欢迎「关注+点赞+收藏」
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线