react项目结合echarts,百度地图实现热力图

一.最近在一个react项目(antd pro)中需要展示一个热力地图。需求是:

1.热力地图可缩放;

2.鼠标点击可以展示该点地理坐标,及热力值。

3.初始化时候自适应展示所有的热力点。

4.展示热力标尺。

二.在实现的过程中出现了不少的问题.

引入地图出现问题
1.缩放地图的时候中心点会漂移。
   解决方案:
      a.把地图放到页面的顶部。
      b.每次缩放后获取地图中心点,再次设置中心点。关键代码如下:

复制代码

     let cp;
     bmap.addEventListener("mousemove",function(){ // 加载完成时,触发
       cp = bmap.getCenter();
     });
     bmap.addEventListener("mouseend",function(){ // 加载完成时,触发
       cp = bmap.getCenter();
     });
     bmap.addEventListener("tilesloaded",function(){ // 加载完成时,触发
       bmap.setCenter(cp);
     });

复制代码

2.热力图标尺不展示。
   解决方案:
       a.将百度地图引入echanrts图表中,通过配置项加热力标尺。(visualMap属性)

       b.手写标尺放入百度地图页面。(可以自己尝试弄,这里我不介绍)

3.热力地图的点不能点击。 

   解决方案:创建标注(这个标注可能有点丑,如果不想要,可以用透明的图片替代)

   这有篇博客写的很详细,https://blog.csdn.net/zjuwwj/article/details/53374947#commentsedit

 // 创建一个标注
     var point = new BMap.Point(data[0][0], data[0][1]);
     var marker = new BMap.Marker(point);  // 创建标注
     bmap.addOverlay(marker);

4.百度地图默认不会展示目标点的地理坐标(本来引入echarts,想着直接用tooltip属性,结果不生效,查找原因发现热力点是个映射关系,不是实实在在的坐标点,获取不到坐标信息)。
    解决方案: 获取目标点地理坐标,手动加入悬浮层。

5.初始化时候需要自适应展示所有的热力点(打开地图的时候有默认的比例尺,或者自己设定的固定的比例尺。)

   解决方案:计算所有热力点中最远的两个的距离,我是设定数据的第一个点为中心点,计算此点与其他所有点的距离,找出最大值,然后对比百度地图等级设定。

百度地图对应的等级可参考:http://api.map.baidu.com/lbsapi/getpoint/index.html

综合实现这些东西,可费了不少劲呢。

三.先来看看热力地图代码:heatMap.js

这是在react项目里使用了echarts和百度地图。

复制代码

// heatMap.js

import React, { Component } from 'react';

import echarts from 'echarts/lib/echarts';
// 引入热力图
import 'echarts/lib/chart/heatmap';
// 引入提示框和标题组件
import 'echarts/lib/component/tooltip';
import 'echarts/lib/component/title';
import 'echarts/extension/bmap/bmap';
import 'echarts/lib/component/visualMap';

class EchartsTest extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: props.data,
    };
  }

  componentDidMount() {
    this.getCharts();
  }

  // 保证再次请求数据时候,地图重新加载
  // eslint-disable-next-line no-unused-vars
  componentWillReceiveProps(nextProps, nextContext) {
    this.setState({
      data: nextProps.data,
    });
    setTimeout(() => {
      if(!nextProps.data.length===0){
        this.getCharts();
      }
    }, 500);
  }

  getCharts = () => {
    const { data } = this.state;
   // 这里data格式:[[120.122,35.666,67],[120.12323,23.45555,23]] ,里面是经度,维度,热力值
    const maxdata = data
      .map(item => item[2])
      .sort()
      .reverse()[0];
    const myChart = echarts.init(document.getElementById('main'));
    const option = {
      animation: false,
      // tooltip: { // 悬浮层提示框在热力图上无效
      //   trigger: 'item',
      //   triggerOn: 'click',
      // },
      bmap: {
        center: [data[0][0], data[0][1]],
        zoom: this.getMapGrade(data),
        roam: true,
      },
      visualMap: {
        show: true,
        // top: 'top',
        bottom: 50,
        left: 0,
        min: 0,
        max: maxdata,
        seriesIndex: 0,
        calculable: true,
        inRange: {
          color: ['blue', 'green', 'yellow', 'red'],
        },
      },
      series: [
        {
          name: 'gid热力值',
          type: 'heatmap',
          coordinateSystem: 'bmap',
          data,
          pointSize: 8,
          blurSize: 8,
        },
      ],
    };
    myChart.setOption(option);
    // 添加百度地图插件
    const bmap = myChart
      .getModel()
      .getComponent('bmap')
      .getBMap();

    // 解决地图放大地图中心点漂移的问题。当地图不在页面顶部使用
    // let cp;
    // bmap.addEventListener("mousemove",function(){ // 加载完成时,触发
    //   cp = bmap.getCenter();
    // });
    // bmap.addEventListener("mouseend",function(){ // 加载完成时,触发
    //   cp = bmap.getCenter();
    // });
    // bmap.addEventListener("tilesloaded",function(){ // 加载完成时,触发
    //   bmap.setCenter(cp);
    // });

    // eslint-disable-next-line no-undef
    bmap.addControl(new BMap.NavigationControl()); // 地图平移缩放控件
    // eslint-disable-next-line no-undef
    bmap.addControl(new BMap.ScaleControl()); // 地图比例尺控件
    // 创建一个标注
    // var point = new BMap.Point(data[0][0], data[0][1]);
    // var marker = new BMap.Marker(point);  // 创建标注
    // bmap.addOverlay(marker);
    // eslint-disable-next-line no-plusplus
    for (let i = 0; i < data.length; i++) {
      // eslint-disable-next-line no-undef
      const hotPoint = new BMap.Point(data[i][0], data[i][1]);
      // eslint-disable-next-line no-undef
      const marker = new BMap.Marker(hotPoint); // 创建标注
      bmap.addOverlay(marker); // 将标注添加到地图中

      // eslint-disable-next-line no-use-before-define
      // marker.addEventListener("click",getAttr);
      // function getAttr(){
      //   var p = marker.getPosition();       // 获取marker的位置
      //   p.id = "123";
      //   console.log("点的位置是" + hotPoint.lng + "," + hotPoint.lat);
      //   console.log("marker的位置是" + p.lng + "," + p.lat);
      // }

      // eslint-disable-next-line no-undef
      const infoWindow = new BMap.InfoWindow(`
        <div style="margin:0;line-height:20px;padding:2px;">
          标题:热点详细信息
          <br/>地理位置:${data[i][0]}, ${data[i][1]}
          <br/>最近三个月热力值:${data[i][2]}
        </div>`);

      marker.infoWindow = infoWindow; // 给当前标注新增一个属性以便保存窗口信息infoWindow
      marker.addEventListener('click', function(e) {
        this.openInfoWindow(e.target.infoWindow); // 点击标注时,打开改标注对打开改标注对应的回调信息
        // 如果使用下面的方式,那样就会导致每次标注点击后,弹出的窗口信息都是最后一次循环的infoWindow。因为在click的时候只会去找infoWindow这个变量值,而你的click肯定是在所有循环的,标注都产生完之后,此时infoWindow变量已经被赋值成了最后一次循环的值。
        // this.openInfoWindow(infoWindow);
      });
    }
    // eslint-disable-next-line no-undef
    bmap.addControl(
      // eslint-disable-next-line no-undef
      new BMap.MapTypeControl({
        mapTypes: [
          // eslint-disable-next-line no-undef
          BMAP_NORMAL_MAP,
          // BMAP_HYBRID_MAP
        ],
      })
    );
    // 去掉上面的{mapTypes:[...]}  就会显示地图,卫星,三维三个图层
  };

  // 计算经纬度距离(千米),四个参数分别是点A的纬度,经度,点B的纬度,经度(位置不要搞错了,我就弄错了,搞了好久)
  getDistance =(lat1, lng1, lat2, lng2)=>{
    const radLat1 = lat1*Math.PI / 180.0;
    const radLat2 = lat2*Math.PI / 180.0;
    const a = radLat1 - radLat2;
    const b = lng1*Math.PI / 180.0 - lng2*Math.PI / 180.0;
    // eslint-disable-next-line no-restricted-properties
    let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) +
      // eslint-disable-next-line no-restricted-properties
      Math.cos(radLat1)*Math.cos(radLat2)*Math.pow(Math.sin(b/2),2)));
    s *=6378.137 ;
    s = Math.round(s * 10000) / 10000;
    return s;
  };

  // 计算地图初始化所有地理坐标中距离最大值
  getZoom = (val)=>{
    const arr = [];
    if(val.length===1){
      arr.push(1)
    }else{
      for(let i=1;i<val.length;i+=1){
        arr.push(this.getDistance(val[0][1],val[0][0],val[i][1],val[i][0]))
      }
    }
  // console.log("所有距离",arr);
    return Math.max(...arr)
  };

  // 计算比例尺对应的百度地图等级
  getMapGrade=(val)=>{
  // console.log("数据 ",val);
    const num=this.getZoom(val);
  //   console.log("最大距离",num);
    let zoom=0;
   if(num<=1){
     zoom=15
   }else if(num>1&&num<=50){
     zoom=10
    }else if(num>50&&num<=100){
     zoom=9
   }else if(num>100&&num<=500){
     zoom=7
   }else if(num>500&&num<=1000){
     zoom=6
   }else{
     zoom=4
   }
   return zoom
  };


  render() {
    return (
      <div id="main" style={{ width: '100%', height: 600 }}></div>
    );
  }
}

export default EchartsTest;

复制代码

四.引用

1.在项目的入口html文件中引入:

需要自己申请百度地图的密钥,直接用下面的密钥也可以:

<script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=Imejyag6D5IPg4lOfu0LiDUWBGh2SNmc"></script>
<script type="text/javascript" src="http://api.map.baidu.com/library/Heatmap/2.0/src/Heatmap_min.js"></script>
<script type="text/javascript" src="http://echarts.baidu.com/gallery/vendors/echarts/extension/bmap.min.js"></script>

2.在自己页面中引入heatMap.js

import HeatMap from './heatMap';  //引入自己写好的热力地图
const arry=[
[120.1234555,35.234234,56],
[120.3287898,34.876575,10],
];           
<HeatMap data={arry} />   //直接在页面引入标签使用  arry的数据格式你可以有自己的格式,不过你的格式改了的话,heatMap.js参数使用也得自己改下。