likes
comments
collection
share

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

作者站长头像
站长
· 阅读数 19

哈喽,各位高贵的倔友们你们好呀(✪ω✪),今天这一篇文章继续分享高德地图的内容,这是第三篇也是最后一篇关于高德地图内容的分享了,后续将继续带来一些新的其他类型的文章分享,期望能继续得到你的关注。那么,如果各位看官大大觉得本章还不错,也希望能给点个赞呗,我们话不多说,直接进入正题。

写在开头

高德地图的申请、安装与初始化我们在上一篇文章讲过,这里就不多说啦,直接来进入本章的主题内容。

同样的配方,同样的操作,搞起。

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

数据准备

这里小编先准备了一些测试数据,下面会以这种数据结构来编写逻辑,如果你真实的数据结构与此不同,可以自行再转换一下或者适当调整一下代码逻辑。

创建 data.js 文件:

const data = [
  {
    "province": "北京市",
    "city": "北京城区",
    "area": "西城区",
    "append": "北京市北京城区西城区3",
    "lng": "116.365868",
    "lat": "39.912289",
    "count": "10",
  },
  {
    "province": "上海市",
    "city": "上海城区",
    "area": "静安区",
    "append": "上海市上海城区静安区5",
    "lng": "121.459384",
    "lat": "31.247105",
    "count": "2",
  },
  {
    "province": "广东省",
    "city": "广州市",
    "area": "黄埔区",
    "append": "广东省广州市黄埔区4",
    "lng": "113.459749",
    "lat": "23.106402",
    "count": "20",
  },
  {
    "province": "四川省",
    "city": "成都市",
    "area": "青羊区",
    "append": "四川省成都市青羊区6",
    "lng": "104.062499",
    "lat": "30.674406",
    "count": "1",
  },
  {
    "province": "河北省",
    "city": "石家庄市",
    "area": "桥西区",
    "append": "河北省石家庄市桥西区2",
    "lng": "114.461154",
    "lat": "38.004043",
    "count": "2",
  },
  {
    "province": "重庆市",
    "city": "重庆城区",
    "area": "涪陵区",
    "append": "重庆市重庆城区涪陵区1",
    "lng": "107.389298",
    "lat": "29.703113",
    "count": "2",
  },
  {
    "province": "广西省",
    "city": "桂林市",
    "area": "七星区",
    "append": "广西壮族自治区2",
    "lng": "110.317826",
    "lat": "25.252701",
    "count": "10",
  },
  {
    "province": "湖南省",
    "city": "长沙市",
    "area": "望城区",
    "append": "湖南省长沙市望城区2",
    "lng": "112.819549",
    "lat": "28.347458",
    "count": "20",
  },
  {
    "province": "湖南省",
    "city": "郴州市",
    "area": "永兴县",
    "append": "湖南省郴州市永兴县4",
    "lng": "113.116528",
    "lat": "26.127151",
    "count": "12",
  },
];

export default data;

绘制热力区域

地图上绘制热力图,我们依旧使用的是高德地图提供的对象,这次使用到的是 AMap.HeatMap 对象,但是该热力图是高德基于第三方 heatmap.js 来实现的,所以如果需要更详细的参数解释,可以直接去 heatmap.js 官方文档看看。

以下是高德地图文档中对 AMap.HeatMap 对象的参数说明,大部分是和 heatmap.js 参数一致的,你也可以自己上官网瞅瞅(✪ω✪)。

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

下面我们来看看以我们本章的数据,如何来绘制出热力图,直接来看看代码:

<template>
  <div id="container" />
</template>

<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import data from "./data.js";

export default {
  data() {
    return {
      // map:null,
    };
  },
  async mounted() {
    await this.initMap();
    this.drawHeatMap();
  },
  methods: {
    /** @name 初始化地图 **/
    initMap() {
      return new Promise(resolve => {
        window._AMapSecurityConfig = {
          securityJsCode: '你的安全密钥',
        }
        AMapLoader.load({
          key: '你的Key',
          version: '2.0',
          plugins: [
            'AMap.HeatMap'
          ],
        }).then((AMap)=>{
          this.map = new AMap.Map('container', {
            center: [110.17293, 35.20173],
            zoom: 3.8,
        });
        this.map.on('complete', () => {
           resolve();
        });
        }).catch(e=>{
           console.log(e);
        })
      })
    }, 
    /** @name 根据地址从高德获取地址的经纬度信息 **/
    geoCode(address) {
      return new Promise(resolve => {
        if (!geocoder) {
          geocoder = new window.AMap.Geocoder();
        }
        geocoder.getLocation(address, (status, result) => {
          if (status === 'complete' && result.geocodes.length) {
            const lnglat = result.geocodes[0].location;
            resolve(lnglat);
          } else {
            console.warn('根据地址查询位置失败:' + address);
          }
        });
      });
    },
    /** @name 绘制热力地图 **/
    async drawHeatMap() {
      // 最大值
      let max = 0;
      // 转化热力需要的数据格式
      const heatData = [];
      for (const [index, address] of data.entries()) {
        // 寻找最大值
        if (max < address.count) {
          max = address.count;
        }
        // 经纬度
        let center = [address.lng, address.lat];
        // 兼容缺失经纬度坐标的数据
        if (!center[0] || !center[1]) {
          // 前端通过高德API自行查经纬度
          const result = await this.geoCode(address.append);
          center = [result.lng, result.lat];
          // 如果还是查不到经纬度,就放弃这个点
          if (!center[0] || !center[1]) continue;
        }
        if (center[0] && center[1]) {
          heatData.push({
            lng: +center[0],
            lat: +center[1],
            count: address.count,
          });
        }
      }
      const densityInstance = new window.AMap.HeatMap(this.map, {
        radius: 25,
        opacity: [0, 0.8],
        // 由外向内
        gradient: {
          0.2: 'blue',
          0.4: 'rgb(117, 211, 248)',
          0.6: 'rgb(0, 255, 0)',
          0.8: '#ffea00',
          1.0: 'red',
        },
        zIndex: 1,
      });
      densityInstance.setDataSet({
        data: heatData,
        max,
      });
    },
  }
}
</script>

<style scoped>
#container {
  width: 740px;
  height: 500px;
}
</style>

注意,data.js 文件数据与前面两篇文章中的数据有所变化。

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

AMap.HeatMap 对象使用起来还是挺简单方便的,仅需要把数据转换成合适的结构丢进去就行了,上面可能唯一比较让人觉得不懂的是 countmax 参数代表着什么意思?

  • count:权重,可以简单理解为每条数据对比的那个字段值。
  • max:权重的最大值,max 可以不填,会默认取数据集 count 的最大值。

绑定交互事件

仅仅绘制静态的热力图还不能满足我们实际的业务需求(可恨的产品经理 ̄へ ̄),还需要我们给这个热力图添加交互效果,例如以下的效果:

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

看着好像也挺简单?但由于高德地图对于热力图是不支持任何事件绑定监听的,这就需要绕点弯路了。

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

上一篇文章中,我们学会了绘制散点并给它们绑定事件交互,只要将该功能迁移过来就行啦(✪ω✪)。so easy !!!

我们来看看具体的编码过程:

<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import data from "./data.js";

let scatterInstanceAll = [];

export default {
  ...,
  methods: {
    ...,
    /** @name 绘制热力地图 **/
    async drawHeatMap() {
      let max = 0;
      const heatData = [];
      for (const [index, address] of data.entries()) {
        if (max < address.count) {
          max = address.count;
        }
        let center = [address.lng, address.lat];
        if (!center[0] || !center[1]) {
          const result = await this.geoCode(address.append);
          center = [result.lng, result.lat];
          if (!center[0] || !center[1]) continue;
        }
        if (center[0] && center[1]) {
          heatData.push({
            lng: +center[0],
            lat: +center[1],
            count: address.count,
          });
          // 绘制散点
          const scatterInstance = new window.AMap.CircleMarker({
            center,
            radius: 6,
            zIndex: index,
            strokeWeight: 1,
            strokeColor: '#FFFFFF',
            strokeOpacity: 0,
            fillColor: 'blue',
            fillOpacity: 0,
            cursor: 'pointer',
            clickable: true,
            extData: {
              index,
              address,
            },
          });
          scatterInstance.setMap(this.map);
          scatterInstanceAll.push(scatterInstance);
          scatterInstance.on('mouseover', e => {
            const extData = e.target._opts.extData;
            // DOTO: 其他业务逻辑
            scatterInstanceAll[extData.index].setOptions({
              zIndex: scatterInstanceAll.length + 10,
              strokeOpacity: 1,
              fillOpacity: 0.8,
            });
          });
          scatterInstance.on('mouseout', e => {
            const extData = e.target._opts.extData;
            // DOTO: 其他业务逻辑
            scatterInstanceAll[extData.index].setOptions({
              zIndex: extData.index,
              strokeOpacity: 0,
              fillOpacity: 0,
            });
          });
        }
      }
      const densityInstance = new window.AMap.HeatMap(this.map, {
        radius: 25,
        opacity: [0, 0.8],
        gradient: {
          0.2: 'blue',
          0.4: 'rgb(117, 211, 248)',
          0.6: 'rgb(0, 255, 0)',
          0.8: '#ffea00',
          1.0: 'red',
        },
        zIndex: 1,
      });
      densityInstance.setDataSet({
        data: heatData,
        max,
      });
    },
  }
}
</script>

上面小编把绘制过程与事件监听过程写上去了,至于其他业务交互逻辑就看你实际需求来完善了。

工具栏操作

接下来,我们再来完成一个工具栏的小功能作为结尾,直接随小编来看代码:

<template>
  <div class="map">
    <div id="container" />
      <div class="tool-nav">
      <div @click="toolClick('enlarge')" class="nav-item" title="放大">
        <svg t="1691484120795" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5104" width="32" height="32"><path d="M640 456h-118.4V320a32 32 0 0 0-64 0v136H320a32 32 0 0 0 0 64h137.6V640a32 32 0 1 0 64 0v-120H640a32 32 0 1 0 0-64z" p-id="5105"></path><path d="M919.264 905.984l-138.912-138.912C851.808 692.32 896 591.328 896 480c0-229.376-186.624-416-416-416S64 250.624 64 480s186.624 416 416 416c95.008 0 182.432-32.384 252.544-86.208l141.44 141.44a31.904 31.904 0 0 0 45.248 0 32 32 0 0 0 0.032-45.248zM128 480C128 285.92 285.92 128 480 128s352 157.92 352 352-157.92 352-352 352S128 674.08 128 480z" p-id="5106"></path></svg>
      </div>
      <div @click="toolClick('reduce')" class="nav-item" title="缩小">
        <svg t="1691484136916" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6088" width="32" height="32"><path d="M919.264 905.984l-138.912-138.912C851.808 692.32 896 591.328 896 480c0-229.376-186.624-416-416-416S64 250.624 64 480s186.624 416 416 416c95.008 0 182.432-32.384 252.544-86.208l141.44 141.44a31.904 31.904 0 0 0 45.248 0 32 32 0 0 0 0.032-45.248zM128 480C128 285.92 285.92 128 480 128s352 157.92 352 352-157.92 352-352 352S128 674.08 128 480z" p-id="6089"></path><path d="M625.792 448H336a32 32 0 0 0 0 64h289.792a32 32 0 1 0 0-64z" p-id="6090"></path></svg>
      </div>
      <div @click="toolClick('fixed')" class="nav-item" title="固定">
        <svg t="1691484253704" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8498" width="32" height="32"><path d="M935.15 375.34c0-25.38-9.88-49.23-27.83-67.17L727.96 128.8c-37.04-37.04-97.31-37.04-134.35 0L338.53 383.89l-72.74-30.44c-35.71-14.94-76.48-6.91-103.85 20.46l-38.15 38.15c-37.04 37.04-37.04 97.31 0 134.35L282 704.62 127.5 859.13c-13.67 13.67-13.67 35.83 0 49.5 6.83 6.83 15.79 10.25 24.75 10.25s17.92-3.42 24.75-10.25l154.5-154.51 158.21 158.21c17.94 17.94 41.8 27.83 67.18 27.83s49.23-9.88 67.17-27.83l38.15-38.15c27.37-27.37 35.4-68.14 20.46-103.85l-30.44-72.74L907.32 442.5c17.95-17.93 27.83-41.79 27.83-67.16z m-77.33 17.67l-262.2 262.2c-15.85 15.85-20.5 39.45-11.85 60.12l34.32 82.02c3.93 9.4 1.82 20.13-5.38 27.33l-38.15 38.15c-4.72 4.72-11 7.32-17.68 7.32s-12.96-2.6-17.68-7.32L173.29 496.91c-9.75-9.75-9.75-25.61 0-35.36l38.15-38.15c4.8-4.8 11.16-7.34 17.65-7.34 3.25 0 6.54 0.64 9.68 1.95l82.02 34.32c20.68 8.65 44.28 4 60.12-11.85l262.2-262.2c9.75-9.75 25.61-9.75 35.36 0l179.36 179.36c9.74 9.77 9.74 25.63-0.01 35.37z" p-id="8499"></path></svg>
      </div>
      <div @click="toolClick('reset')" v-if="isZoomed" class="nav-item" title="重置">
        <svg t="1691484362015" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14076" width="32" height="32"><path d="M943.8 484.1c-17.5-13.7-42.8-10.7-56.6 6.8-5.7 7.3-8.5 15.8-8.6 24.4h-0.4c-0.6 78.3-26.1 157-78 223.3-124.9 159.2-356 187.1-515.2 62.3-31.7-24.9-58.2-54-79.3-85.9h77.1c22.4 0 40.7-18.3 40.7-40.7v-3c0-22.4-18.3-40.7-40.7-40.7H105.5c-22.4 0-40.7 18.3-40.7 40.7v177.3c0 22.4 18.3 40.7 40.7 40.7h3c22.4 0 40.7-18.3 40.7-40.7v-73.1c24.2 33.3 53 63.1 86 89 47.6 37.3 101 64.2 158.9 79.9 55.9 15.2 113.5 19.3 171.2 12.3 57.7-7 112.7-24.7 163.3-52.8 52.5-29 98-67.9 135.3-115.4 37.3-47.6 64.2-101 79.9-158.9 10.2-37.6 15.4-76 15.6-114.6h-0.1c-0.3-11.6-5.5-23.1-15.5-30.9zM918.7 135.2h-3c-22.4 0-40.7 18.3-40.7 40.7V249c-24.2-33.3-53-63.1-86-89-47.6-37.3-101-64.2-158.9-79.9-55.9-15.2-113.5-19.3-171.2-12.3-57.7 7-112.7 24.7-163.3 52.8-52.5 29-98 67.9-135.3 115.4-37.3 47.5-64.2 101-79.9 158.8-10.2 37.6-15.4 76-15.6 114.6h0.1c0.2 11.7 5.5 23.2 15.4 30.9 17.5 13.7 42.8 10.7 56.6-6.8 5.7-7.3 8.5-15.8 8.6-24.4h0.4c0.6-78.3 26.1-157 78-223.3 124.9-159.2 356-187.1 515.2-62.3 31.7 24.9 58.2 54 79.3 85.9h-77.1c-22.4 0-40.7 18.3-40.7 40.7v3c0 22.4 18.3 40.7 40.7 40.7h177.3c22.4 0 40.7-18.3 40.7-40.7V175.8c0.1-22.3-18.2-40.6-40.6-40.6z" p-id="14077"></path></svg>
      </div>
    </div>
  </div>
</template>

<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import data from "./data.js";

let scatterInstanceAll = [];
const ZOOM = 3.8;

export default {
  data() {
    return {
      // map:null,
      zoom: ZOOM,
      fixed: false,
    };
  },
  computed: {
    /** @name 是否缩放过 **/
    isZoomed() {
      return this.zoom !== ZOOM;
    }
  },
  ...,
  methods: {
    ...,
    /** @name 工具栏点击 **/
    toolClick(flag) {
      switch (flag) {
        case 'enlarge':
          this.zoom++;
          this.map.setZoom(this.zoom);
          break;
        case 'reduce':
          this.zoom--;
          this.map.setZoom(this.zoom);
          break;
        case 'fixed':
          this.fixed = !this.fixed;
          this.map.setStatus({
            doubleClickZoom: this.fixed,
            scrollWheel: this.fixed,
          });
          break;
        case 'reset':
          this.zoom = ZOOM;
          this.map.setZoom(this.zoom);
          break;
      }
    },
  }
}
</script>

<style scoped>
.map {
  width: 740px;
  height: 500px;
  position: relative;
  display: flex;
  justify-content: flex-start;
  align-items: flex-start;
}
#container {
  width: 100%;
  height: 100%;
}
.tool-nav {
  position: absolute;
  right: 12px;
  bottom: 12px;
  display: flex;
}
.nav-item {
  width: 30px;
  height: 30px;
  border-radius: 4px;
  background-color: rgba(0, 0, 0, 0.5);
  margin-left: 12px;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  line-height: 1;
  font-size: 17px;
  fill: #fff;
}
.nav-item .icon {
  width: 20px;
  height: 20px;
}
</style>

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

完整源码

点我点我


至此,本篇文章就写完啦,撒花撒花。

高德地图-热力地图-实现在地图上绘制热力区域与工具栏操作

希望本文对你有所帮助,如有任何疑问,期待你的留言哦。 老样子,点赞+评论=你会了,收藏=你精通了。