可视化大屏绘制地图,需要的知识点都在这里!

5,199 阅读8分钟

我正在参加「掘金·启航计划」

前言

之前输出过一篇关于可视化框架搭建的文章,开荒可视化大屏项目,我们需要面对那些问题?

这里就可视化大屏项目的一个细分领域地图板块做一个整理,相信有很多小伙伴在做大屏相关项目时,除了各种图表、表格之外,最容易遇到的一个需求就是地图可视化展示了。

今天,在这里,我将我遇到的所有与地图板块相关的经验和知识点以及小工具都分享给大家。

目录

  1. GeoJSON 是什么?
  2. GeoJSON 该怎么获取?
  3. 常规地图板块的生成
  4. 包含地图底图的板块生成
  5. 地图板块区域合并

正文

接触了可视化大屏开发的小伙伴们或多或少都会地图模块的绘制与生成,不论是通过 echarts 还是别的可视化绘图工具,相关 api 都会要求我们提供一份目标地图对应的 GeoJSON 文件,可以是本地 JSON文件,可以是异步的链接。

那么没有 GIS 相关经验的前端小伙伴肯定就想知道了,GeoJSON 究竟是个啥?为啥地图模块绘制必须依赖它?

1、GeoJSON 是什么?

GeoJSON 是一种对各种地理数据结构进行编码的格式,基于 JavaScript 对象表示法( JavaScript Object Notation), 简称 JSON 的地理空间信息数据交换格式。GeoJSON 对象可以表示几何、特征或者特征集合。GeoJSON 支持下面这几种几何类型:点(Point)、线(LineString)、面(Polygon)、多点(MultiPoint)、多线(MultiLineString)、多面(MultiPolygon)和几何集合(GeometryCollection)。GeoJSON 里的特征(Feature)包含一个几何对象和其他属性,特征集合(FeatureCollection)表示一系列特征。

2、GeoJSON 该如何获取?

  1. 在线生成 GeoJSON 文件:geojson.io

这里可以操作地图添加点线面来生成自己需要展示的 GeoJSON,也可以导入现成的地图块进行预览查看和编辑。

  1. 在线获取目标行政区域的 GeoJSON 文件:datav.aliyun.com/portal/scho…

这里主要依靠 AliDatav 产品提供的增值服务,可以让我们选择我们需要渲染的目标行政区域对应的 GeoJSON,可以支持全国、省份、城市、区县

之前有过一段时间出现过暂停服务的情况,所以个人建议将对应文件下载到自己的服务器或 CDN 进行保存,防止获取时出现异常。

例如:

杭州市 GeoJSON:geo.datav.aliyun.com/areas_v3/bo…

我们打开它,可以发现行政区域描述主要是由一个特征集合以及多个区域的特征组成的,里面包含每个区域的名称、中心点坐标、重心坐标以及构成这个区域的经纬度坐标,这些我们在后面绘制地图时都需要用到。

3、常规地图板块的生成?

这里先给大家看一个生成好的例子: ztstory.github.io/vue-datav/#…

中间的镂空地图就是借助 echarts 通过浙江省的 GeoJSON 生成的浙江板块图,后面还会在此基础上增加一些流向线和特效。

具体实现是利用 options.geo 来完成的

ECharts geo 配置文档

// 获取到行政区域的 geoJson 后,需要先全局注册地图名称,方便后续配置匹配
echarts.registerMap('registerMapName', geoJson);

geo: {
    map: 'registerMapName', // 这里名称需要对应
    // ...相关样式配置这里就不赘述了,参见文档即可
    // 如果对具体某些板块有自定义需求,可以用regions,下面列出常见的配置
    regions: [{
        name: '区域板块的名称',
        selected: true,
        select: {
            // 设置选中样式
            ...
        },
        tooltip: {
            // 一些区域板块绑定的提示框组件
        }
    }],
    // 这里对于一些有 i18n 需求的小伙伴可以在这里处理,自定义每个板块显示名称
    nameMap: {
        '浙江': 'Zhe Jiang'
    }
}

这些基本配置做好我们已经可以通过 echarts 生成一个基本的地图模块了。

4、包含地图底图的板块生成?

之前我自己这边遇到一个需求:

需要呈现的效果是需要有天地图底图瓦片,基于地图坐标系建立对应行政区域色块,并且需要呈现一定的数据流向的导流线,而且也有点击对应色块展示不同的数据信息。

最终效果如图.png

思考了一下,感觉单纯使用 echarts 很难完成这个效果,还是需要和地图框架结合,由于有天地图的要求,我采用了 leaflet 作为地图基座,用来展示 MapTiles 以及行政区域色块渲染,数据流向的导流线echarts 来完成。

这里我用到了 echarts-extension-leaflet

这里稍微展开说明一下,插件做了这些事:

  1. leaflet 对象作为一个 Component 挂载到 echartsInstance 上,后期可通过 echartsInstance.getModel().getComponent('lmap').getLeaflet() 来获取到 leafletMap 对象,可以用于操作各种 MapLayer.
  2. 将地图图层作为一个特殊 layer 放在 echarts 组件的最底部,然后将 echarts 相关的 canvas 放在 leaflet-overlay-pane 这一层级,这样就保证一些数据流向图或者一些散点标记可以在地图瓦片之上呈现。

image.png

leaflet 对于覆盖物的层级显示是有默认的设计,分别用不同的标签展示,并且预置有不同的 z-index 控制,对于有特殊小伙伴需要调整相应图层位置的可以通过覆盖 z-index 来实现。

集成了这个插件之后,接下来的操作就很简单了:

1. 初始化地图:

在 echarts 的 options 中配置 leaflet 相关的初始化信息,具体配置方案可参考上面的插件 README。

const option: ECOption = {
  // Leaflet extension option
  lmap: {
    // ...
  },
  series: [
    {
      type: 'scatter',
      // Use Leaflet coordinate system
      coordinateSystem: 'lmap',
      // ...
    }
  ]
  // ...
}
// initialize echart
var chart = init(document.getElementById("echarts-lmap"));
chart.setOption(option);

2. 添加底图瓦片:

// Get Leaflet extension component
// getModel and getComponent do not seem to be exported in echarts typescript
// add the following two comments to circumvent this
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const lmapComponent = chart.getModel().getComponent('lmap');
// Get the instance of Leaflet
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const lmap = lmapComponent.getLeaflet();

L.tileLayer(
  "https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
  {
   subdomains: ['1', '2', '3', '4']
  }
).addTo(lmap);

3. 通过 L.geoJSON 方法创建全国行政区块层

// 此处的 geojson 我是缓存在本地文件的,如果是网络资源则需要自行实现下载对应数据
L.geoJSON(geojson, {
        style: (feature) => {
            // 这里可以配置每个 feature 的颜色、样式
        },
        onEachFeature: (feature) => {
            // 这里可以处理每个 feature 的交互逻辑,点击弹出 popup 等 
     })
        .on('mouseover', (e: L.LeafletMouseEvent) => {
            // 这里可以监听鼠标移入 feature 变色或者浮现 popup 等逻辑
        })
        .on('mouseout', (e: L.LeafletMouseEvent) => {
            // 这里可以监听鼠标移出
        })
        .on('click', (e: L.LeafletMouseEvent) => {
            // 点击事件
        });

4. 为每个区块添加数据流向箭头

// 因为我们已经有了 leaflet 提供的坐标系
// 所以我们只需要按照常规的方式配置相应的 series 即可

echarts.value.updateOptions(
    {
        series: createEchartsSeries(),
    },
    {
        // 这里需要注意的是我们更新配置需要用 replaceMerge 参数,不然 leaflet 的配置会被替换掉
        replaceMerge: ['series'],
    }
);

5、地图板块的区域合并

这里举例一个我遇到的特殊需求:

需要将城市中的部分区域合并,并称为主城区,地图数据属于这几个区域的都归类为主城区

看字面意思应该很好理解,就是做区域合并,数据层面的合并很容易,地图显示我们应该怎么做的?

回想一下我们一开始了解的知识:GeoJson 是什么?

那我们是不是只需要将这几个区域的边界数据融合,创建一个新的 feature 区域,命名为主城区,设置一下他的中心、重心之类的参数,把他模拟成一个独立区域不就好了吗?

具体做法如下展示:

1. 提取目标区域边界:

通过遍历原 GeoJson 文件提取出需要合并的区域数据信息,并以此创建一个新的 GeoJson,这一步我偷懒用了 AI 来完成

2. 合并目标区域

通过在线网站 mapshaper.org/ 进行区域合并

  • 先导入提取好的 GeoJson
  • 在右上角打开 console 面板 image.png
  • 输入命令: dissolve -o mainArea.json image.png 此时我们发现,中间的交界线已经变成了虚线,打开合并好的 json 文件发现之前的 4 个 feature 已经被合并为一个 feature

3. 配置合并区域基本信息

为了方便我们后续的逻辑操作,我们需要配置 adCode、name、center、centroid 这几个属性

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "adcode": 99999,
                "name": "主城区",
                "center": [120.15, 30.24],
                "centroid": [120.15, 30.24]
            },
            "geometry": {
                "type": "Polygon",
                "coordinates": [
                    [
                        ...
                    ]
                ]
            }
        }
    ]
}

4. 将处理好的合并区域 merge 到原来的市行政区域文件中

image.png

结语

至此,相信大家已经基本了解大屏项目要处理地图相关的需求需要的基本知识,以及一些场景下的解决方案,如果有小伙伴在大屏地图处理这方面的特殊需求,欢迎在评论区分享讨论,我们一起探索解决方案。