vue + echarts + 百度地图 实现散点图框选

需求:根据坐标将所有零售柜用散点显示在地图上,散点大小代表柜子销售额大小。可以连续框选柜子,在地图右侧显示所选柜子销售额对比曲线。

完成效果图:

 

 

1.确定方案

这个需求的难点主要在于连续框选,且要获得框选的散点信息。一开始查资料发现 echarts 有个区域选择组件 brush(https://echarts.apache.org/zh/option.html#brush),可以实现连续框选。踩了很多坑勉强实现功能后发现还是有些问题,比如鼠标拖拽跟框选事件冲突,拖拽或放大地图选框会跟着移动不能保持在原位置……后来改变方案,利用百度地图 BMapLib 基础类的 DrawingManager 库实现框选。

2. 引入百度地图和工具库

index.html 中加入

<!-- 百度地图 -->
<script type="text/javascript" src="https://api.map.baidu.com/api?v=3.0&ak=你的密钥"></script>

<!-- library 基础类 -->
<script type="text/javascript" src="https://api.map.baidu.com/library/GeoUtils/1.2/src/GeoUtils_min.js"></script>

<!-- DrawingManager库-->
<script type="text/javascript"   src="https://api.map.baidu.com/library/DrawingManager/1.4/src/DrawingManager_min.js"></script>

webpack.base.conf.js 中加入(改完记得重启)

externals: {
  "BMapLib": "BMapLib"
}

在组件中引入

import BMapLib from 'BMapLib'

3. echarts 配置

this.bmap = echarts.init(document.getElementById("map"));

配置分为几部分,首先是地图部分(配置的详细说明可以自行查找,我在官网上没有找到。地图与散点图配置可以参考官方实例 https://echarts.apache.org/examples/zh/editor.html?c=map-polygon):

bmap: {
  center: [104.114129, 37.550339],
  zoom: 5,
  roam: true,
  mapStyle: {
    styleJson: [
      {
        featureType: "land",
        elementType: "geometry",
        stylers: {
          color: "#f5f6f7ff",
        },
      },
      {
        featureType: "water",
        elementType: "geometry",
        stylers: {
          color: "#c4d7f5ff",
        },
      },
      {
        featureType: "green",
        elementType: "geometry",
        stylers: {
          color: "#dcf2d5ff",
        },
      },
      {
        featureType: "highway",
        elementType: "all",
        stylers: { visibility: "off" },
      },
    ],
  },
}

散点图部分:

var data = [
    {name: 'name1', value: 9},
    {name: 'name2', value: 12},
    {name: 'name3', value: 12}
]

var geoCoordMap = {
    'name1':[121.15,31.89],
    'name2':[109.781327,39.608266],
    'name3':[120.38,37.35]
}

var convertData = function (data) {
    var res = [];
    for (var i = 0; i < data.length; i++) {
        var geoCoord = geoCoordMap[data[i].name];
        if (geoCoord) {
            res.push({
                name: data[i].name,
                value: geoCoord.concat(data[i].value)
            });
        }
    }
    return res;
};

// 散点图配置
series: [
  {
    type: "scatter",
    coordinateSystem: "bmap",
    data: this.convertData(this.data),
    symbolSize: function (val) {
        return val[2] / 10;
    }
  }
]

这里涉及到一个问题,散点图的大小怎么根据 value 设置,用上面的 symboSize 虽然能区别大小,但不好控制,如果 value 过大或过小显示在地图上都会很难看。最好的方法是给一个范围,让散点大小在这个范围里形成映射。数学不好就直接用工具了,D3js有个线性比例尺可以实现该功能(https://segmentfault.com/a/1190000011006780

在 index.html 引入 D3js

<script src="https://d3js.org/d3.v5.js"></script>

symbolSize 改写如下

// 将所有 value 值放在数组 valueArr 里
var valueArr = [];
data.forEach((item) => {
    valueArr.push(item.value);
});

symbolSize: (val, params) => {
// 将 valueArr 里的所有值映射到 [10, 40] 这个范围里
  let scale = d3
    .scaleLinear()
    .domain([
      Math.min(...valueArr),
      Math.max(...valueArr),
    ])
    .range([10, 40]);
  return scale(val[2]);
},

散点是画好了,下一个问题又来了:给散点加 tooltip,拖拽地图的时候 tooltip 会跟着地图一起移动,不能停留在 hover 的点附近,解决办法参考:https://blog.csdn.net/wooden_people/article/details/89668999

增加 tooltip 设置:

tooltip: {
  trigger: "item",
  formatter: function (params, ticket, callback) {
    return (
      params.value[4] +
      "<br />" +
      params.value[3] +
      "<br />" +
      params.name +
      "<br />" +
      "销售额: " +
      params.value[2] +
      "元"
    );
  },
  position: function (pos, params, dom, rect, size) {
    var top = $(".BMap_mask").css("top"); //.BMap_mask为固定格式
    var left = $(".BMap_mask").css("left");
    top = top.substring(0, top.length - 2); //去除px单位
    left = left.substring(0, left.length - 2);
    return [pos[0] + 20 - left, pos[1] + 20 - top]; //20可根据需要调整
  },
}

4. 框选功能实现

参考:http://lbsyun.baidu.com/jsdemo.htm#f0_7 

值的注意的是,我们的 bmap 对象并不是通过 new BMap.Map('map') 创建的,而是以bmap属性来设置的,因此不能直接使用百度地图api,好在 echarts 提供了获取 bmap 对象实例的方法 ;

// 获取 bmap 对象实例
this.bmapModel = this.bmap.getModel().getComponent("bmap").getBMap();

// 设置边框样式
var styleOptions = {
  strokeColor: "red", //边线颜色。
  fillColor: "red", //填充颜色。当参数为空时,圆形将没有填充效果。
  strokeWeight: 1, //边线的宽度,以像素为单位。
  strokeOpacity: 0.8, //边线透明度,取值范围0 - 1。
  fillOpacity: 0.3, //填充的透明度,取值范围0 - 1。
  strokeStyle: "solid", //边线的样式,solid或dashed。
};

var drawingManager = new BMapLib.DrawingManager(this.bmapModel, {
  isOpen: false, //是否开启绘制模式
  enableDrawingTool: true, //是否显示工具栏
  drawingToolOptions: {
    anchor: BMAP_ANCHOR_TOP_RIGHT, //位置
    offset: new BMap.Size(5, 5), //偏离值
    scale: 0.6,
    drawingModes: [BMAP_DRAWING_RECTANGLE],
  },
  rectangleOptions: styleOptions, //矩形的样式
});

现在就可以框选地图上的点了!下一步是获取所框选的点的信息,如何知道哪些点被框住了呢?可以根据点的坐标来判断,已知每个点的坐标以及选框四个点的坐标,百度接口中有判断围栏的功能,即一个点是否在某个矩形中。参考:https://blog.csdn.net/u012539364/article/details/46648147

drawingManager.addEventListener("overlaycomplete", (e) => {
  var pStart = e.overlay.getPath()[3]; //矩形左上角坐标
  var pEnd = e.overlay.getPath()[1]; //矩形右下角坐标
  var pt1 = new BMap.Point(pStart.lng, pStart.lat); //3象限
  var pt2 = new BMap.Point(pEnd.lng, pEnd.lat); //1象限
  var bds = new BMap.Bounds(pt1, pt2); //范围

  for (let key in geoCoordMap) {
    var pt = new BMap.Point(
      geoCoordMap[key][0],
      geoCoordMap[key][1]
    );
    if (BMapLib.GeoUtils.isPointInRect(pt, bds)) {
    // 将框选的点存储在 deviceArr 中
      if (this.deviceArr.indexOf(key) === -1) {
        this.deviceArr.push(key);
      }
    }
  }
  // 利用拿到的信息发起请求
  .......
});

至此基本功能就完成了,至于肯定会存在的其他问题就不一一说明了,echarts 不同版本配置不同也挺让人头疼的,而且我还没有找到以前版本的文档。另外这个框选功能好像不支持移动端,留待后续探究。

posted @ 2020-08-13 13:36  Stroyer  阅读(3211)  评论(6编辑  收藏  举报