热力图

字数统计: 2k
阅读时长: 约 5 分钟
当前版本: 4.29

2017年致命车祸热力图,黄色为碰撞集中度较高的区域

什么是热力图?

热力图是用栅格表面表示点要素密度,一般使用连续色带表示区域中点密度的高低。

为什么用热力图?

热力图和聚类不透明度光晕都是用来表示点要素重叠时的可视化方法。与其他可视化密度的表示方法不同,热力图可以表示某个属性数据的密度,而其他的仅仅是基于位置计算的密度。

仅位置热力图属性数据热力图

导致死亡的车祸密度(2017年),黄色区域表示事故发生密度最高的区域

酒后驾驶致命车祸密度(2017),黄色区域表示与酒后驾驶有关的交通事故的最高密度

如何设置热力图

热力图是通过设置其渲染属性 rendererHeatmapRenderer 类型实例来实现的。HeatmapRenderer 有几个关键属性。radius 表示影响区域半径。例如,将半径设置为 10,则点周围 10 个像素范围都需要按其分配值。在点正上方的像素将具有最高值,而离点距离越远,密度值越小,像素的值与点的距离成反比。屏幕上落在所有点的 10 像素半径之外的像素,其密度值为 0

每个像素的密度值根据其与多个点的接近程度进行累积。例如,10 个点的 10px 以内的像素密度值将高于仅一个点的 10px 以内的像素密度值。

渲染器中的 colorStops 可根据像素的密度值与 maxDensity 的比率 (即 pixel density / maxDensity) 来确定如何对像素着色。密度值等于或高于 maxDensity 的像素的密度比为 1,并且应该指定最亮的颜色,因为这些像素代表最热的区域。maxDensity 值越大,地图中存在的热点就越少。

示例

位置热力图

要创建热力图,必须将 HeatmapRenderer 应用于点图层。可以在在线编辑器中打开此示例,然后调整 radiusmaxDensity 以查看这些属性如何影响热力图。

导致死亡的车祸密度 (2017 年)。黄色区域表示碰撞事故的密度最高

示例代码
js
<html>
  <head>
    
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"/>

    <title>
      Fatal car crashes
    </title>

    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }
    </style>

    <link rel="stylesheet" href="https://js.geoscene.cn/4.29/geoscene/themes/light/main.css" />
    <script src="https://js.geoscene.cn/4.29/"></script>

    <script>
      require([
        "geoscene/WebMap",
        "geoscene/layers/FeatureLayer",
        "geoscene/views/MapView",
        "geoscene/widgets/Legend"
      ], (WebMap, FeatureLayer, MapView, Legend) => {

        const url = "https://services1.arcgis.com/4yjifSiIG17X0gW4/arcgis/rest/services/FatalAccidents2017/FeatureServer";

        const layer = new FeatureLayer({
          title: "Fatal car accidents (2017)",
          url
        });

        const colors = ["rgba(115, 0, 115, 0)", "#820082", "#910091", "#a000a0", "#af00af", "#c300c3", "#d700d7", "#eb00eb", "#ff00ff", "#ff58a0", "#ff896b", "#ffb935", "#ffea00"];

        layer.renderer = {
          type: "heatmap",
          colorStops: [
            { color: colors[0], ratio: 0 },
            { color: colors[1], ratio: 0.083 },
            { color: colors[2], ratio: 0.166 },
            { color: colors[3], ratio: 0.249 },
            { color: colors[4], ratio: 0.332 },
            { color: colors[5], ratio: 0.415 },
            { color: colors[6], ratio: 0.498 },
            { color: colors[7], ratio: 0.581 },
            { color: colors[8], ratio: 0.664 },
            { color: colors[9], ratio: 0.747 },
            { color: colors[10], ratio: 0.83 },
            { color: colors[11], ratio: 0.913 },
            { color: colors[12], ratio: 1 }
          ],
          radius: 18,
          maxDensity: 0.04625,
          minDensity: 0
        };

        const map = new WebMap({
          basemap: {
            portalItem: {
              id: "90b5f4f58bb94d01b33028cc4dba3d74"
            }
          },
          layers: [ layer ]
        });

        const view = new MapView({
          container: "viewDiv",
          center: [-117.79621, 33.91474],
          scale: 1155581,
          constraints: {
            snapToZoom: false,
            minScale: 4622324,
            maxScale: 1155500
          },
          map
        });

        view.ui.add(
          new Legend({
            view
          }),
        "top-right");

      });
    </script>
  </head>

  <body>
    <div id="viewDiv"></div>
  </body>
</html>

数据加权热力图

此示例将修改上一个热力图,用数据变量对表面进行加权。此示例中使用的碰撞图层具有一个属性是关于事故中涉及酒驾的数量。通过对该字段进行加权,我们可以可视化更多酒驾事故的位置。

指定字段时,每个像素的密度将乘以数据值,相应地也需要调整 maxDensity的值。

因酒驾而导致死亡的车祸密度 (2017 年)。黄色区域表示碰撞事故的密度最高

js
    const colors = ["rgba(115, 0, 115, 0)", "#820082", "#910091", "#a000a0", "#af00af", "#c300c3", "#d700d7", "#eb00eb", "#ff00ff", "#ff58a0", "#ff896b", "#ffb935", "#ffea00"];
    layer.renderer = {
        type: "heatmap",
        field: "Number_of_Drinking_Drivers",
        colorStops: [
            { color: colors[0], ratio: 0 },
            { color: colors[1], ratio: 0.083 },
            { color: colors[2], ratio: 0.166 },
            { color: colors[3], ratio: 0.249 },
            { color: colors[4], ratio: 0.332 },
            { color: colors[5], ratio: 0.415 },
            { color: colors[6], ratio: 0.498 },
            { color: colors[7], ratio: 0.581 },
            { color: colors[8], ratio: 0.664 },
            { color: colors[9], ratio: 0.747 },
            { color: colors[10], ratio: 0.83 },
            { color: colors[11], ratio: 0.913 },
            { color: colors[12], ratio: 1 }
        ],
        radius: 18,
        maxDensity: 0.00875,
        minDensity: 0
    };

热力图的缩放

动态热力图在视觉上仅适用于几个地图比例级别。当用户缩小时,热力图将显示为更热。当用户放大时,热力图将显示为更冷。要防止用户查看过热的热力图,可以在图层上设置热力图的最小比例尺 minScale 或将其作为视图约束。而当用户放大时,热力图又变得无关紧要。我们可以在视图上设置一个监视,当图层放大到一定比例切换为各个点位置的简单渲染器表示。

加利福尼亚州圣地亚哥欺诈的犯罪密度。放大和缩小地图以查看开发人员如何限制用户查看过热或过冷的热力图。当用户放大时,热力图将关闭并将点位置显示为简单的渲染器

js
    const view = new MapView({
        container: "viewDiv",
        map: map,
        constraints: {
            minScale: 1155582,
            snapToZoom: false
        }
    });

缩放超过比例阈值后切换到位置渲染器

js
    reactiveUtils.watch(
        () => view.scale,
        () => {
            layer.renderer = view.scale <= 72224 ? simpleRenderer : heatmapRenderer;
        }
    );

静态热力图

静态热力图允许您在所有比例级别上保留热力图的视觉密度。当用户放大和缩小时,热力图将始终在视图中显示相同的热点。

当用户放大时,热力图变得无关紧要,因此您可能需要在 MapView 上设置约束以防止过度缩放。

纽约市树木密度 (2015 年)。放大和缩小以观察静态热力图如何成为突出显示高密度区域的更具吸引力的解决方案

js
        maxDensity: 0.319,
        minDensity: 0,
        // settings for heatmap apply only to this scale
        // so renderer will look consistent without
        // dynamically updating on zoom

        referenceScale: 36111,

        radius: 6,
        legendOptions: {
            minLabel: "Low tree density",
            maxLabel: "High tree density"
        }
    };

淡化的热力图

使用适当的密度值配置热力图渲染器可能很困难,尤其是在起始视图比例未知时。热力图智能制图渲染器创建器有助于根据当前视图比例和背景颜色生成建议的热力图。

它还会自动将颜色从密集区域(完全不透明度)淡入稀疏区域(高透明度)。颜色的淡化有助于建立模糊边界,这通常比热力图可视化的硬边缘更合适。

芝加哥的犯罪密度。请注意热力图如何自动淡化颜色,从而使边界变得更加模糊

js
    const { renderer } = await heatmapRendererCreator.createRenderer({ layer, view });
    renderer.referenceScale = view.scale;
    layer.renderer = renderer;