MapboxGL是一个基于WebGL的地图绘制引擎,其地图样式采用一个json格式的文件进行描述,但是对于渲染的地图,Mapbox并没有提供图例的生成方法,因此需要自己根据这个样式描述文件来生成地图图例。
MapboxGL中数据与样式是分开的,地图的所有样式都是由这个唯一的样式描述文件来描述。并不是地图中的所有样式都需要图例,例如简单的文字注记就不需要生成图例,而且MapboxGL的样式中对同一个数据源(例如:铁路)会有多个样式图层(style layer)描述其样式(例如:灰色的实线图层加白色的虚线图层)。因此基于该样式文件绘制图例至少要先解决两个难题:
1. 从众多的样式中筛选出哪些样式需要绘制图例;
2. 从筛选出的样式中提取哪些样式描述的是同一个数据源。
一、 筛选需要绘制图例的图层
首先我们需要将所有的样式图层进行分类,按照要素类型分为点、线、面三类,分别为pointLayers
、lineLayers
、polygonLayers
。
其次分别从中筛选出需要绘制图例的图层,删掉不需要绘制图例的图层。筛选规则很简单,对于所有要素都需要满足以下条件:
1、当前地图的级别必须在样式图层的可见级别范围之内。例如当前地图为第8级,而某一个样式的可见范围是9-12,那么这个样式是可以被忽略的。
2、图层的visibility
属性设置的为“visible
”,即必须可见。
理论上来说只要满足以上两个条件,那么这个图层在当前地图级别就是可见的,但是对于点状要素来说还有一个条件:
3、如果为点状要素的样式,则该样式必须具有“icon-image
”属性。即该点状符号具有图标,而不是纯文字。
二、 合并同一数据源的图层
经过以上筛选后,上下的样式都是需要绘制图例的,但是有些样式可能描述的是同一个数据源,对于这些样式应该属于一个要素,因此需要合并。怎样判断哪些样式是属于统一数据源呢,基本原则如下:
1、type
必须相同,都是线状或都是面状。
2、source
属性必须相同,即同一数据源。
3、source-layer
属性必须相同,即同一数据图层。
4、filter
必须相同,具有相同的数据过滤条件。
满足以上要求被认为是描述的同一个数据要素。
三、 绘制图例
经过以上步骤得到的应该就是所有需要绘制图例的样式了,而要对图例进行可视化就需要选择对应的渲染引擎对图例进行渲染,目前我使用过的有两种渲染方法:
1、采用WebGL渲染
考虑到MapboxGL本身就是基于WebGL渲染的,而现在筛选后的图例的样式正好与地图的样式具有相同的格式,可以考虑直接用MapboxGL渲染到地图上,这时就需要创建geojson数据源。根据点线面三种类型分别创建三种geojson的模板,然后根据筛选出的图例样式的类型,分别获取对应的geojson,并利用图例样式对其进行符号化。
2、采用svg渲染
采用WebGL的方法渲染缺点是不方便编辑,而且采用的经纬度的坐标,相当于重新建立一个map。而svg则更加轻量,并且可以缩放和编辑。
但是svg的样式描述采用的dom属性的方式,因此与不能直接使用筛选的图例样式,而要转换成svg支持的属性。
附件(图例自动生成实例):
① 一个完整的Style Json文件如下:
{
"name": "新闻",
"version": 8,
"sources": {
"data2012": {
"type": "vector",
"url":"http://192.9.105.205:8090/api/v1/tilesets/foxgis/admin"
}
},
"sprite": "http://192.9.105.205:8090/api/v1/sprites/foxgis/Hk6NFtw8/sprite",
"glyphs": "http://192.9.105.205:8090/api/v1/fonts/foxgis/{fontstack}/{range}.pbf",
"layers": [
{
"id": "背景",
"type": "background",
"paint": {
"background-color": "#87c2e4"
}
},
{
"id": "亚洲面",
"type": "fill",
"source": "data2012",
"source-layer": "asia_a",
"paint": {
"fill-color": "#ebecec",
"fill-opacity": 1,
"fill-outline-color": "#ebecec"
}
},
{
"id": "国界",
"type": "line",
"source": "data2012",
"source-layer": "china_l",
"maxzoom": 11,
"paint": {
"line-color": "#e8ae59",
"line-width": 5
}
},
{
"id":"国界光晕",
"type":"line",
"source":"data2012",
"source-layer":"china_l",
"minzoom":3,
"maxzoom": 11,
"paint":{
"line-offset":-3,
"line-color":"rgba(255, 214, 211, 0.98)",
"line-width":{
"stops":[[3,4],[4,6],[6,8]]
}
}
},
{
"id": "省界线",
"type": "line",
"source": "data2012",
"source-layer": "boua_prov",
"minzoom": 4,
"maxzoom": 9,
"layout": {
"line-cap": "round",
"line-join": "round"
},
"paint": {
"line-color": "rgb(150,150,150)",
"line-width": 1,
"line-dasharray": [1.5,3]
}
},
{
"id": "县界",
"source": "data2012",
"source-layer": "boua",
"metadata": {
"replaceLayer": true
},
"minzoom": 7,
"maxzoom": 11,
"type": "line",
"filter": ["==","pac",320111],
"layout": {
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": "rgb(0,0,255)",
"line-width": 2,
"line-dasharray": [3,3]
}
},
{
"id":"铁路(实线)",
"source":"data2012",
"source-layer":"lrrl",
"minzoom":7,
"maxzoom":11,
"filter":["any",
["==","gb",410101],
["==","gb",410102],
["==","gb",410103],
["==","gb",410200]
],
"type":"line",
"paint":{
"line-width": 2.4,
"line-color":"#949494"
}
},
{
"id":"铁路(虚线)",
"source":"data2012",
"source-layer":"lrrl",
"minzoom":7,
"maxzoom":11,
"filter":["any",
["==","gb",410101],
["==","gb",410102],
["==","gb",410103],
["==","gb",410200]
],
"type":"line",
"paint":{
"line-width":1.2,
"line-color":"#fff",
"line-dasharray":[10,10]
}
},
{
"id": "省名注记",
"source": "data2012",
"source-layer": "prov_label",
"type": "symbol",
"minzoom": 4,
"maxzoom": 5,
"layout": {
"text-size": 12,
"text-font": [
"STXinwei Regular"
],
"symbol-placement": "point",
"text-field": "{name}",
"text-max-width": 8
},
"paint": {
"text-color": "rgb(250,0,0)",
"text-halo-color": "rgb(255,255,255)",
"text-halo-width": 1.5,
"text-halo-blur": 1
}
},
{
"id": "首都注记",
"type": "symbol",
"source": "data2012",
"source-layer": "agnp",
"filter": ["==","capname","北京"],
"minzoom": 3,
"maxzoom": 11,
"layout": {
"text-size": 14,
"icon-image": "star-15",
"text-font": ["SimHei Regular"],
"symbol-placement": "point",
"text-allow-overlap": false,
"text-offset": [0,1],
"text-anchor": "top",
"text-field": "{capname}",
"text-letter-spacing": 0.02,
"text-max-width": 8
},
"paint": {
"text-color": "rgb(255,0,0)",
"text-halo-color": "rgb(255,255,255)",
"text-halo-width": 1.5,
"text-halo-blur": 1
}
},
{
"id": "省会城市注记",
"type": "symbol",
"source": "data2012",
"source-layer": "agnp",
"filter": ["==","class","AB"],
"minzoom": 5,
"maxzoom": 8,
"layout": {
"text-size": 15,
"icon-image": "circle-11",
"text-font": [
"SimHei Regular"
],
"symbol-placement": "point",
"text-offset": [0,0.7],
"text-anchor": "top",
"text-field": "{capname}",
"text-letter-spacing": 0.02,
"text-max-width": 8
},
"paint": {
"icon-color": "#ff0000",
"text-color": "rgb(20,20,20)",
"text-halo-color": "rgb(255,255,255)",
"text-halo-width": 1.5,
"text-halo-blur": 1
}
},
{
"id": "地级市注记",
"type": "symbol",
"source": "data2012",
"source-layer": "agnp",
"minzoom": 7,
"maxzoom": 11,
"layout": {
"text-line-height": 1.2,
"text-size": 13,
"symbol-avoid-edges": true,
"icon-image": "circle-11",
"text-ignore-placement": false,
"text-max-angle": 38,
"text-font": [
"SimHei Regular"
],
"symbol-placement": "point",
"visibility": "visible",
"text-offset": [0,0.7],
"icon-optional": false,
"text-rotation-alignment": "viewport",
"text-anchor": "top",
"text-field": "{name}",
"text-letter-spacing": 0.02,
"text-max-width": 8
},
"paint": {
"icon-color": "#ff0000",
"text-color": "#fe001f",
"text-halo-color": "rgb(255,255,255)",
"text-halo-width": 1.5,
"text-halo-blur": 1
}
},
{
"id": "县注记",
"source": "data2012",
"source-layer": "agnp",
"filter": ["any",["==","class","AE"],["==","class","AF"]],
"minzoom": 8,
"maxzoom": 11,
"type": "symbol",
"layout": {
"text-size": 13,
"icon-image": "circle-stroked-11",
"text-max-angle": 38,
"text-font": ["SimHei Regular"],
"symbol-placement": "point",
"text-anchor": "top",
"text-field": "{name}",
"text-letter-spacing": 0.02,
"text-max-width": 7
},
"paint": {
"text-color": "rgb(60,60,60)",
"text-halo-color": "rgb(255,255,255)",
"text-halo-width": 1.5,
"text-halo-blur": 1
}
}
]
}
② 经过第一步筛选后
从中筛选出需要绘制图例的图层,例如其中的“县注记”、“省名注记”等纯文字注记就被剔除掉了,得到的结果如下图,共有10个图层:
这里我是打算采用svg的绘制方式,所以对其样式进行了转换,只保留了svg需要用到的样式:
③ 经过第一步筛选后
以上得到的图例样式有些样式描述的是同一要素,例如铁路有虚线和实线两个图层,国界有边线和光晕两个图层,因此需要进行合并。合并后的结果如下图所示:
可以发现合并后剩下8个图层,铁路和国界的两个样式合并到一个数组。
④ SVG绘制
剩下的就是根据这个图例数组绘制svg了,绘制效果如下图:
这些图例只是地图上可能需要展示的所有图例,考虑到在实际制图中,可能有些不太重要的要素并不一定需要绘制在地图上,因此我在自动生成图例的基础上加入了人工控制,用户可以自行选择显示哪些图例,并且可以设置图例的列数、顺序、文字内容、字体、字号等属性。