Skip to content

heatmapjs 热力图

使用 leaflet 、heatmapjs 、vue 、加载热力图。

示例

安装依赖

官方示例仓库包存在问题 ImageData data assignment in Strict Mode 使用修改过的版本具体可查看仓库 issues

webpack 使用 npm 引入

shell
pnpm install leaflet-heatmap @mars3d/heatmap.js

vite 使用 script 引入

leaflet-heatmap.js 、heatmap.js 下载后在index.html 中引入猛击查看在线示例

js
<script src="./heatmap.js"></script>
<script src="./leaflet-heatmap.js"></script>

代码实现

vue
<script setup>
import { ref, defineAsyncComponent } from 'vue';
import { testData } from './testData.js';

const InitMap = defineAsyncComponent(() =>
  import('../../components/InitMapTianditu.vue')
);

const cfg = {
  // 只有当scaleRadius为true时半径才应该很小(或者想要小半径)
  // 如果scaleRadius为假,它将是以像素为单位使用的恒定半径
  radius: 2,
  maxOpacity: 0.8,
  // 根据地图缩放比例缩放半径
  scaleRadius: true,
  // 如果设置为 false,热图将使用全局最大值进行着色
  // 如果激活:使用当前地图边界内的数据最大值
  // (useLocalExtremas 为 true 时总会出现红点)
  useLocalExtrema: true,
  // 数据中的哪个字段名称代表纬度 - 默认“lat”
  latField: 'lat',
  // 数据中的哪个字段名称代表经度 - 默认“lng”
  lngField: 'lng',
  // 数据中的哪个字段名称代表数据值 - 默认“值”
  valueField: 'count'
};

const mapObj = ref();

const initHeatmap = () => {
  mapObj.value.setView(new L.LatLng(25.6586, -80.3768), 4);

  const heatmapLayer = new HeatmapOverlay(cfg);

  mapObj.value.addLayer(heatmapLayer);
  heatmapLayer.setData(testData);
};

/**
 * todo 这样写只是因为是在 vitePress 中
 * todo 项目中可直接在 script 中引入,需要注意的是 leaflet 要先于插件引入
 */
const setHeatmapjsToHead = () => {
  const heatmapJs = document.createElement('script');
  heatmapJs.src = '/heatmapjs/heatmap.js';
  document.head.appendChild(heatmapJs);

  const leafletHeatmapJs = document.createElement('script');
  leafletHeatmapJs.src = '/heatmapjs/leaflet-heatmap.js';
  document.head.appendChild(leafletHeatmapJs);

  setTimeout(() => {
    initHeatmap();
  }, 1000);
};

const mapLoad = (map) => {
  mapObj.value = map;

  setHeatmapjsToHead();
};
</script>

<template>
  <init-map @map-load="mapLoad"></init-map>
</template>

<style scoped></style>
vue
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
// todo 项目使用请放开 leaflet 引入
// import L from 'leaflet';

const props = defineProps({
  center: {
    type: Array,
    default: [32.0237855, 118.8075675]
  }
})

const emit = defineEmits(['mapLoad']);

const mapRef = ref();

const initMap = () => {
  const map = L.map(mapRef.value, {
    center: props.center,
    zoom: 11,
    minZoom: 6,
    maxZoom: 20
  });

  const mapType = 'vec';
  L.tileLayer(
    'https://t{s}.tianditu.gov.cn/' +
      mapType +
      '_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' +
      mapType +
      '&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=b72aa81ac2b3cae941d1eb213499e15e',
    {
      subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
      attribution:
        '&copy; <a href="http://lbs.tianditu.gov.cn/home.html">天地图 GS(2022)3124号 - 甲测资字1100471</a>'
    }
  ).addTo(map);

  const mapLabelType = 'cva';
  L.tileLayer(
    'https://t{s}.tianditu.gov.cn/' +
      mapLabelType +
      '_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' +
      mapLabelType +
      '&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=b72aa81ac2b3cae941d1eb213499e15e',
    {
      subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
    }
  ).addTo(map);

  // 地图初始化完成发送事件
  emit('mapLoad', map);

  return map;
};

const mapObj = ref();

// 在 onMounted 中初始化地图
onMounted(() => {
  mapObj.value = initMap();
});

const removeMap = () => {
  if (mapObj.value) {
    mapObj.value.remove();
  }
};

// 在组件卸载时删除地图
onUnmounted(() => {
  removeMap();
});
</script>

<template>
  <div ref="mapRef" class="map"></div>
</template>

<style scoped>
.map {
  height: 40vh;
  z-index: 0;
}
</style>
js
export const testData = {
  max: 8,
  data: [
    { lat: 24.6408, lng: 46.7728, count: 3 },
    { lat: 50.75, lng: -1.55, count: 1 },
    {
      lat: 52.6333,
      lng: 1.75,
      count: 1
    },
    { lat: 48.15, lng: 9.4667, count: 1 },
    { lat: 52.35, lng: 4.9167, count: 2 },
    {
      lat: 60.8,
      lng: 11.1,
      count: 1
    },
    { lat: 43.561, lng: -116.214, count: 1 },
    { lat: 47.5036, lng: -94.685, count: 1 },
    {
      lat: 42.1818,
      lng: -71.1962,
      count: 1
    },
    { lat: 42.0477, lng: -74.1227, count: 1 },
    { lat: 40.0326, lng: -75.719, count: 1 },
    {
      lat: 40.7128,
      lng: -73.2962,
      count: 2
    },
    { lat: 27.9003, lng: -82.3024, count: 1 },
    { lat: 38.2085, lng: -85.6918, count: 1 },
    {
      lat: 46.8159,
      lng: -100.706,
      count: 1
    },
    { lat: 30.5449, lng: -90.8083, count: 1 },
    { lat: 44.735, lng: -89.61, count: 1 },
    {
      lat: 41.4201,
      lng: -75.6485,
      count: 2
    },
    { lat: 39.4209, lng: -74.4977, count: 1 },
    { lat: 39.7437, lng: -104.979, count: 1 },
    {
      lat: 39.5593,
      lng: -105.006,
      count: 1
    },
    { lat: 45.2673, lng: -93.0196, count: 1 },
    { lat: 41.1215, lng: -89.4635, count: 1 },
    {
      lat: 43.4314,
      lng: -83.9784,
      count: 1
    },
    { lat: 43.7279, lng: -86.284, count: 1 },
    { lat: 40.7168, lng: -73.9861, count: 1 },
    {
      lat: 47.7294,
      lng: -116.757,
      count: 1
    },
    { lat: 47.7294, lng: -116.757, count: 2 },
    { lat: 35.5498, lng: -118.917, count: 1 },
    {
      lat: 34.1568,
      lng: -118.523,
      count: 1
    },
    { lat: 39.501, lng: -87.3919, count: 3 },
    { lat: 33.5586, lng: -112.095, count: 1 },
    {
      lat: 38.757,
      lng: -77.1487,
      count: 1
    },
    { lat: 33.223, lng: -117.107, count: 1 },
    { lat: 30.2316, lng: -85.502, count: 1 },
    {
      lat: 39.1703,
      lng: -75.5456,
      count: 8
    },
    { lat: 30.0041, lng: -95.2984, count: 2 },
    { lat: 29.7755, lng: -95.4152, count: 1 },
    {
      lat: 41.8014,
      lng: -87.6005,
      count: 1
    },
    { lat: 37.8754, lng: -121.687, count: 7 },
    { lat: 38.4493, lng: -122.709, count: 1 },
    {
      lat: 40.5494,
      lng: -89.6252,
      count: 1
    },
    { lat: 42.6105, lng: -71.2306, count: 1 },
    { lat: 40.0973, lng: -85.671, count: 1 },
    {
      lat: 40.3987,
      lng: -86.8642,
      count: 1
    },
    { lat: 40.4224, lng: -86.8031, count: 4 },
    { lat: 47.2166, lng: -122.451, count: 1 },
    {
      lat: 32.2369,
      lng: -110.956,
      count: 1
    },
    { lat: 41.3969, lng: -87.3274, count: 2 },
    { lat: 41.7364, lng: -89.7043, count: 2 },
    {
      lat: 42.3425,
      lng: -71.0677,
      count: 1
    },
    { lat: 33.8042, lng: -83.8893, count: 1 },
    { lat: 36.6859, lng: -121.629, count: 2 },
    {
      lat: 41.0957,
      lng: -80.5052,
      count: 1
    },
    { lat: 46.8841, lng: -123.995, count: 1 },
    { lat: 40.2851, lng: -75.9523, count: 2 },
    {
      lat: 42.4235,
      lng: -85.3992,
      count: 1
    },
    { lat: 39.7437, lng: -104.979, count: 2 },
    { lat: 25.6586, lng: -80.3568, count: 7 },
    {
      lat: 33.0975,
      lng: -80.1753,
      count: 1
    },
    { lat: 25.7615, lng: -80.2939, count: 1 },
    { lat: 26.3739, lng: -80.1468, count: 1 },
    {
      lat: 37.6454,
      lng: -84.8171,
      count: 1
    },
    { lat: 34.2321, lng: -77.8835, count: 1 },
    { lat: 34.6774, lng: -82.928, count: 1 },
    {
      lat: 39.9744,
      lng: -86.0779,
      count: 1
    },
    { lat: 35.6784, lng: -97.4944, count: 2 },
    { lat: 33.5547, lng: -84.1872, count: 1 },
    {
      lat: 27.2498,
      lng: -80.3797,
      count: 1
    },
    { lat: 41.4789, lng: -81.6473, count: 1 },
    { lat: 41.813, lng: -87.7134, count: 1 },
    {
      lat: 41.8917,
      lng: -87.9359,
      count: 1
    },
    { lat: 35.0911, lng: -89.651, count: 1 },
    { lat: 32.6102, lng: -117.03, count: 1 },
    {
      lat: 41.758,
      lng: -72.7444,
      count: 1
    },
    { lat: 39.8062, lng: -86.1407, count: 1 },
    { lat: 41.872, lng: -88.1662, count: 1 },
    {
      lat: 34.1404,
      lng: -81.3369,
      count: 1
    },
    { lat: 46.15, lng: -60.1667, count: 1 },
    { lat: 36.0679, lng: -86.7194, count: 1 },
    {
      lat: 43.45,
      lng: -80.5,
      count: 1
    },
    { lat: 44.3833, lng: -79.7, count: 1 },
    { lat: 45.4167, lng: -75.7, count: 2 },
    {
      lat: 43.75,
      lng: -79.2,
      count: 2
    },
    { lat: 45.2667, lng: -66.0667, count: 3 },
    { lat: 42.9833, lng: -81.25, count: 2 },
    {
      lat: 44.25,
      lng: -79.4667,
      count: 3
    },
    { lat: 45.2667, lng: -66.0667, count: 2 },
    { lat: 34.3667, lng: -118.478, count: 3 },
    {
      lat: 42.734,
      lng: -87.8211,
      count: 1
    },
    { lat: 39.9738, lng: -86.1765, count: 1 },
    { lat: 33.7438, lng: -117.866, count: 1 },
    {
      lat: 37.5741,
      lng: -122.321,
      count: 1
    },
    { lat: 42.2843, lng: -85.2293, count: 1 },
    { lat: 34.6574, lng: -92.5295, count: 1 },
    {
      lat: 41.4881,
      lng: -87.4424,
      count: 1
    },
    { lat: 25.72, lng: -80.2707, count: 1 },
    { lat: 34.5873, lng: -118.245, count: 1 },
    {
      lat: 35.8278,
      lng: -78.6421,
      count: 1
    }
  ]
};

// const testData = {
//   max: 8,
//   data: [
//     {
//       lat: 32.020274,
//       lng: 118.803319,
//       count: 3
//     },
//     {
//       lat: 32.020274,
//       lng: 118.803319,
//       count: 3
//     },
//     {
//       lat: 32.015762,
//       lng: 118.800572,
//       count: 3
//     },
//     {
//       lat: 32.015762,
//       lng: 118.800572,
//       count: 3
//     },
//     {
//       lat: 32.015762,
//       lng: 118.800572,
//       count: 3
//     },
//     {
//       lat: 32.013869,
//       lng: 118.803834,
//       count: 3
//     },
//     {
//       lat: 32.013869,
//       lng: 118.803834,
//       count: 3
//     },
//     {
//       lat: 32.013869,
//       lng: 118.803834,
//       count: 3
//     }
//   ]
// };

FAQ

vite 使用 heatmap.js 报错 "Cannot assign to read only property 'data' of object"

这个问题存在 heatmap.js' 原仓库,有许多这个问题的请求但没有被合并 猛击查看 ,有人发布了修改后的三方版本猛击查看 @mars3d/heatmap.js

webpack 正常引入使用无问题

js
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import '@mars3d/heatmap.js';
import HeatmapOverlay from 'leaflet-heatmap';

vite 中会抛出上述错误

原因在于在 vite 中使用会执行 if (typeof module !== "undefined" && module.exports) 判断中的逻辑猛击查看 ,仍会去加载 heatmap.js 而不是 @mars3d/heatmap.js。 解决办法:1. 按照如下方式修改 leaflet-heatmap.js, 2. 按照上述 vite 使用将文件下载到本地在 index.html 中引入

js
  // Supports UMD. AMD, CommonJS/Node.js and browser context
if (typeof module !== "undefined" && module.exports) {
    module.exports = factory(
        require('@mars3d/heatmap.js'),
        require('leaflet')
    );
} else if (typeof define === "function" && define.amd) {
    define(['@mars3d/heatmap.js', 'leaflet'], factory);
} else {
    // browser globals
    if (typeof window.h337 === 'undefined') {
        throw new Error('heatmap.js must be loaded before the leaflet heatmap plugin');
    }
    if (typeof window.L === 'undefined') {
        throw new Error('Leaflet must be loaded before the leaflet heatmap plugin');
    }
    context[name] = factory(window.h337, window.L);
}

贡献者

Released under the MIT License.