经纬志
首页 / OpenLayers / 教程

聚合与热力图

先看结论

点位太密时,逐点绘制既看不清也没有信息量。聚合把相近的点合并成一个计数标记, 热力图把密度变成颜色,两者解决的是同一个问题的不同侧面。

最后验证 2026-11-03 · OpenLayers 10.3.1 · 难度:进阶 · 核验 2026-10-29 · 引用本文

聚合是数据源的包装

ol.source.Cluster 不改变原始数据,而是在每次解析时把距离相近的要素 合并为一个新要素。新要素的几何是一个点,属性里带 features 数组, 记录被合并的原始要素。

var raw = new ol.source.Vector({
  url: '/data/stations.geojson',
  format: new ol.format.GeoJSON({
    dataProjection: 'EPSG:4326',
    featureProjection: 'EPSG:3857'
  })
});

var clustered = new ol.source.Cluster({
  distance: 40,        // 聚合阈值,单位是像素
  minZoom: 13,         // 低于该级别不聚合
  source: raw
});

var clusterLayer = new ol.layer.Vector({
  source: clustered,
  style: function (feature) {
    var items = feature.get('features');
    var size = items.length;
    var radius = 8 + Math.min(size, 60) * 0.35;

    return new ol.style.Style({
      image: new ol.style.Circle({
        radius: radius,
        fill: new ol.style.Fill({ color: 'rgba(31, 95, 208, 0.85)' }),
        stroke: new ol.style.Stroke({ color: '#ffffff', width: 2 })
      }),
      text: new ol.style.Text({
        text: String(size),
        font: '600 11px sans-serif',
        fill: new ol.style.Fill({ color: '#ffffff' })
      })
    });
  }
});

distance 是屏幕像素,不是米。缩放后同样的 40 像素覆盖的地面范围变化很大, 所以聚合结果会随缩放变化,这是预期行为。

聚合后没有原始几何。取 feature.getGeometry() 得到的是聚合中心点。要拿原始点,遍历 features 数组, 每个元素的几何才是真实位置。

点击聚合点展开

常见交互是「点一下聚合点,放大到能看清单个点位」。判断聚合大小后调整视图, 比直接遍历原始要素简单。

map.on('singleclick', function (event) {
  var hit = map.forEachFeatureAtPixel(event.pixel, function (f) { return f; }, {
    hitTolerance: 6,
    layerFilter: function (layer) { return layer === clusterLayer; }
  });

  if (!hit) return;

  var items = hit.get('features');
  if (items.length === 1) {
    console.log('单点', items[0].get('name'));
    return;
  }

  var extent = ol.extent.createEmpty();
  items.forEach(function (f) {
    ol.extent.extend(extent, f.getGeometry().getExtent());
  });
  map.getView().fit(extent, { duration: 400, maxZoom: 17, padding: [40, 40, 40, 40] });
});

热力图的三个参数

ol.layer.Heatmap 基于点的权重在画布上叠加径向渐变。 radius 是单个点的模糊半径(像素),blur 控制衰减, weight 决定每个点的贡献值。权重需要归一化,否则颜色会全部饱和。

var maxFlow = flowStats.p95;   // 预先算好的 95 分位值

var heat = new ol.layer.Heatmap({
  source: raw,
  radius: 14,
  blur: 18,
  weight: function (feature) {
    var v = feature.get('flow') || 0;
    return Math.min(v / maxFlow, 1);
  },
  gradient: [
    'rgba(0, 0, 255, 0)',
    'rgba(0, 128, 255, 0.6)',
    'rgba(0, 255, 200, 0.8)',
    'rgba(255, 220, 0, 0.9)',
    'rgba(255, 80, 0, 1)'
  ]
});

权重函数返回 0 到 1 之间的数。直接用原始流量做权重时, 少数极大值会把整片区域染成暖色,其余点全被压成冷色。

两种表达怎么选

关注点聚合热力图
回答的问题这个位置有几个点哪一片区域密度高
可读性精确,能读出计数连续,适合看趋势
交互可点击、可展开只能看,不适合拾取
数据量万级以内表现良好十万级仍可渲染
缩放适配随缩放自动拆分半径固定,缩放后模糊

业务报表里经常两者并用:低缩放级别用热力图看分布, 放大到街区级别切换成聚合或原始点位。切换时把两个图层放在同一位置,用 setVisible 控制,不要让它们同时渲染。

注意事项

  • 聚合的 distance 以像素计,设备像素比不同时,同样的数值观感会略有差异。
  • minZoom 只对聚合生效,超过该级别后聚合源直接透出原始要素。
  • 聚合样式函数每次重绘都会调用,样式对象要按半径区间缓存,避免频繁创建。
  • 热力图不支持 declutter,叠加标注时要注意图层顺序。
  • 热力图底图过亮时颜色层次会丢失,深色底图或加一层半透明遮罩效果更稳。

先算分位数再定权重。用最大值归一化会让绝大多数点接近 0; 用 95 分位作为上限,颜色分布更均匀,极端值也不会让整图过曝。

引用本文

经纬志(2026)。《聚合与热力图》。https://www.jingweizhi.com/openlayers/tutorial/cluster-heatmap.html(访问日期 2026-11-03)。