使用echarts实现一个可拖拽缩放的立体地图

近期根据全新的需求重构一个老的项目,首页需要做一个立体的中国地图,原先的平面地图使用的是高德与echarts结合,地图用高德,点用echarts,而现在要做立体的地图,并且不需要世界地图的背景,于是我直接放弃了高德直接改全部由echarts来实现。

相关依赖

echarts 5.1.2
vue-echarts 6.0.0
lodash
element-ui

地图资源

采用高德的地图json
https://datav.aliyun.com/tools/atlas/index.html

基本地图显示

我绘制的立体地图的原理只是在页面上显示了两层的地图,简单来说就是底层使用暗色并且将中心点稍微下移,echarts的option配置如下

      optionInner: {
        animation: false, // 阻止拖拽和缩放时上下图层不同步
        geo: [
          { // 底图
            map: 'china', // 全国地图名称必须为china 不然无法显示南海的缩略图
            id: 'down',
            roam: true,
            zoom: 1,
            layoutCenter: ['50%', '51%'], // 地图位置
            layoutSize: '100%',
            itemStyle: {
              borderColor: 'grey',
              borderWidth: 0.5,
              areaColor: 'black',
              opacity: 0.5
            },
            emphasis: {
              label: {
                show: false
              },
              itemStyle: {
                borderColor: 'grey',
                borderWidth: 0.5,
                areaColor: 'black',
                opacity: 0.5
              }
            },
            select: {
              label: {
                show: false
              },
              itemStyle: {
                areaColor: 'black',
                opacity: 0.5
              }
            },
            regions: [
              {
                name: '南海诸岛',
                itemStyle: {
                  opacity: 0
                },
                emphasis: {
                  itemStyle: {
                    opacity: 0
                  }
                },
                select: {
                  itemStyle: {
                    opacity: 0
                  }
                }
              }
            ],
            z: 0
          },
          {
            map: 'china',
            id: 'up',
            roam: true,
            zoom: 1,
            layoutCenter: ['50%', '50%'], // 地图位置
            layoutSize: '100%',
            label: {
              show: true
            },
            itemStyle: {
              areaColor: {
                type: 'linear-gradient',
                x: 0,
                y: 0,
                x2: 0,
                y2: 1,
                colorStops: [{
                  offset: 0,
                  color: 'steelblue' // 0% 处的颜色
                }, {
                  offset: 1,
                  color: 'skyblue' // 50% 处的颜色
                }]
              },
            },
            zlevel: 1
          }
        ],
        series: [
          {
            name: '地图',
            type: 'map',
            geoIndex: 1,
            data: []
          }
        ]
      }

注意点:

  1. ehcarts的geo的map属性必须为'china',否则南海诸岛的缩略图不会显示!就这个问题折腾了我一整天,我一度想放弃echarts使用高德了,否则就政治不正确了。22/11/14更新:显示南海诸岛框只需要在json中添加南海诸岛对应的属性包括坐标就行。没有显示出来可能是map名称和注册时传入的map名称不对应。如需修改省份名称的位置,需要在json对应的省份/地区对象中添加cp属性,值为数组,即是label显示的位置。
  2. 为了展示效果不那么奇怪,隐藏底图的南海诸岛缩略图,需要在底图geo中设置南海诸岛的regions属性。
  3. 为了后面拖拽缩放时上下图层保持同步,需要设置animation属性为false,由于项目目前处于技术预研状态,所以我还不知道这个属性会不会影响其它的功能。

监听地图的缩放与拖动

通过监听georoam事件来实现,需要特别注意的是,缩放与拖动需要调用echartsInstance.getOption()来获取完整的option值并做出修改后覆盖原有的option,否则会出现bug。

    // 监听地图缩放/拖动
    handleGeoRoam(e) {
      // console.log(e);
      // getOption生成的对象包含了本身geo[1]里面没有的center和更新过的zoom,需要重新赋值
      // chartInstance是通过ref保存的vue-echarts实例
      const optionAll = _.cloneDeep(this.chartInstance.getOption());
      let center, zoom;
      if (e?.geoId === 'up') { // 正常在上层的地图上操作
        if (e?.zoom) { // 处理缩放
          zoom = optionAll.geo[1].zoom;
          Object.assign(optionAll.geo[0], { zoom });
        }
        // 处理拖拽
        center = optionAll.geo[1].center;
        Object.assign(optionAll.geo[0], { center });
      } else if (e?.geoId === 'down') { // 增加在底图上操作的判断
        if (e?.zoom) {
          zoom = optionAll.geo[0].zoom;
          Object.assign(optionAll.geo[1], { zoom });
        }
        center = optionAll.geo[0].center;
        Object.assign(optionAll.geo[1], { center });
      }
      this.optionInner = optionAll;
    },

地图双击省份下钻以及点击按钮返回上一级

单击事件有时会无法生效故改成了双击下钻(估计可能和拖拽有冲突)

    // 下钻(单击事件容易和拖拽冲突)
    handleClick(e) {
      // console.log(e);
      if (e?.data?.code){
        const { code, level, centroid } = e.data;
        // 最后一级不可下钻
        if (level !== 'country') {
          return;
        }
        // 无数据的地区不可下钻
        const noDataDistricts = [710000, 810000, 820000];
        if (noDataDistricts.includes(code)) {
          this.$message.warning('暂无相关数据');
          return;
        }
        this.currentMapInfo = {
          code,
          level: 'province'
        };
        this.status.loading = true;
        // 手动修改地图内容
        const mapName = String(this.currentMapInfo.code);
        const optionCopy = _.cloneDeep(this.optionInner);
        this.updateMapGeoValue(optionCopy, 'map', mapName);
        this.updateMapGeoValue(optionCopy, 'center', centroid);
        this.updateMapGeoValue(optionCopy, 'zoom', 1);
        this.optionInner = optionCopy;
        // echarts注册地图方法无需赘述
        this.registerMap(code);
        // TODO:
        // this.$emit('map-change', code);
        this.setMapData(code);
        this.status.loading = false;
      }
    },
    // 地图返回上一级
    handleMapBack() {
      if (this.currentMapInfo.level === 'country') {
        this.$message.warning('已经是全国地图了');
      } else {
        this.currentMapInfo = {
          code: '100000',
          level: 'country'
        };
        this.chartInstance.clear();
        const optionCopy = _.cloneDeep(this.optionInner);
        this.updateMapGeoValue(optionCopy, 'map', 'china');
        this.updateMapGeoValue(optionCopy, 'zoom', 1);
        this.updateMapGeoValue(optionCopy, 'center', [], 'delete');
        this.optionInner = optionCopy;
        this.setMapData();
      }
    },
    /**
     * @description: 同时设置两个geo的属性
     * @param {object} option 深拷贝后的option
     * @param {string} key option两个geo都有的key
     * @param {any} value
     * @param {string | undefined} type 'delete'为删除属性
     * @return {void}
     */    
    updateMapGeoValue(option, key, value, type) {
      option.geo.forEach(item => {
        if (type === 'delete') {
          delete item[key];
        } else {
          item[key] = value;
        }
      });
    }

说明:
1.code,centroid,level是数据组装的时候放入data中的,前两者直接从地图文件中取到,level的值只有'country'和'province'代表全国和省份。

  1. currentMapInfo保存当前地图的code和等级。
  2. setMapData方法根据当前code获取对应的地图文件,并设置随机数据,code,centroid,level等地图信息,由于是模拟的业务逻辑也不赘述。

至此一个完整的立体地图就实现了。

参考内容
https://www.makeapie.cn/echarts_content/xr5cqmiBBf.html

你可能感兴趣的:(使用echarts实现一个可拖拽缩放的立体地图)