mapbox-gl | 3.1 聚合(上)

简述

聚合效果,在不同的缩放级别下,将临近的点变成一个点,在视觉上能够清晰的知道点分布情况。举一个场景,全国建立了许多充电站,如果我想将所有的充电站都标出来,那么整个地图就会变得十分拥挤,那如果在全国的视角下,可以使用一个点去代表周围有多少充电站数量(大概是一个省一点),如果在市级别,我们也可以采用同样的方案去展示充电站数量。

在通常我们实现聚类效果有两种办法,一种是Marker(html),另一种是纯Symbol(也可以是circle),两者的原理其实是相同的,Marker更适合对复杂图标效果的情景,因为Symbol能力有限,并不能表达所有的图标样式(比如饼图)。本节优先介绍Symbol

聚合有两个例子,先将一个简单例子,然后再讲一个官方的例子

示例1

代码

js 复制代码
  // 添加图片资源
  function addImageSource() {
    const images = [
      png1,
      png2,
      png3,
      png4,
      png5,
      png6,
      greenMarkerPng,
    ];
    const imageNames = ["1", "2", "3", "4", "5", "6", "greenMarker"];
    images.forEach((item, index) => {
      map.loadImage(item, (error, image) => {
        if (error) throw error;
        map.addImage(imageNames[index], image);
      });
    });
  }
  addImageSource();
  // 随机造数据
  const geojson = {
    type: "FeatureCollection",
    features: [],
  };
  for(let i = 0;i<100;i++){
    geojson.features.push({
      type: "Feature",
      properties: {
        type: 1 + Math.floor(Math.random() * 5),
      },
      geometry: {
        type: "Point",
        coordinates: [110 + Math.random()*10, 30 + Math.random()*10],
      },
    });
  }
  // 添加source
  map.addSource("cluster", {
    type: "geojson",
    data: geojson,
    cluster: true,
    clusterRadius: 30,
  });
  // 添加layer
  map.addLayer({
    id: "cluster",
    type: "symbol",
    source: "cluster",
    layout: {
      "icon-size": ["case", ["has", "point_count"], 0.5, 0.3],
      "icon-image": [
        "case",
        ["has", "point_count"],
        "greenMarker",
        ["get", "type"],
      ],
      "text-field": ["get", "point_count_abbreviated"],
      "text-offset": [0.5, -0.1],
      "text-size": 20,
      "icon-ignore-placement": true,
    },
    paint: {
      "text-color": "#F0F0F0",
      "text-halo-width": 0.5,
      "text-halo-color": "#F0F0F0",
    },
  });
  // 添加点击事件
  map.on("click", "cluster", (e) => {
    if (e.features[0].properties.point_count !== undefined) {
      console.log("cluster");
      // 放大地图
      map.flyTo({
        center: e.lngLat,
        zoom: map.getZoom() + 2.5,
      });
    } else {
      console.log("single");
    }
  });
  map.flyTo({
    center: [110, 35],
    zoom: 3,
  });

示例逻辑

  1. 添加需要的图片资源
  2. 生成一些示例数据
  3. 添加source
  4. 添加layer
  5. 添加点击事件

解析

  1. 添加需要的图片资源
js 复制代码
  function addImageSource() {
    const images = [
      png1,
      png2,
      png3,
      png4,
      png5,
      png6,
      greenMarkerPng,
    ];
    const imageNames = ["1", "2", "3", "4", "5", "6", "greenMarker"];
    images.forEach((item, index) => {
      map.loadImage(item, (error, image) => {
        if (error) throw error;
        map.addImage(imageNames[index], image);
      });
    });
  }

这一步使用了api两个方法 loadImageaddImage, 两个方法在1.8中介绍过,不再重复介绍。这一步创建了图片资源和图片名称两个数组,长度相同,随后循环添加图片。

  1. 生成一些示例数据
js 复制代码
  // 随机造数据
  const geojson = {
    type: "FeatureCollection",
    features: [],
  };
  for(let i = 0;i<100;i++){
    geojson.features.push({
      type: "Feature",
      properties: {
        type: 1 + Math.floor(Math.random() * 5),
      },
      geometry: {
        type: "Point",
        coordinates: [110 + Math.random()*10, 30 + Math.random()*10],
      },
    });
  }

创建geojson格式的点数据,生成范围在经度110120,纬度30 40之间,同时生成一个属性type其值为1-6之间的整数

  1. 添加source
js 复制代码
  // 添加source
  map.addSource("cluster", {
    type: "geojson",
    data: geojson,
    cluster: true,
    clusterRadius: 30,
  });

addSource添加上一步生成的随机数据,与以往不同的是,这里设置了两个参数 clusterclusterRadius。这里需要注意的是,虽然聚合效果是在layer上所展现的,但是设置聚合却是在source中设置的。

参数 描述
cluster 是否聚合,默认为false
clusterRadius 聚合半径,在cluster为true时生效,默认为50

示例这一步的效果为:添加名为cluster的数据源,并开启聚合,且聚合半径为30,需要注意的是,这里的聚合半径的单位即不是像素,也不是实际的长度单位。官方解释为:如果聚合半径为512,那么就等于瓦片的宽,也就是和所在瓦片有关(或者说是层级),如果上述解释还是不太理解,当作像素理解也可以。

addSource还可以设置其他一些属性,感兴趣的可以自行查阅官方文档

  1. 添加layer
js 复制代码
  // 添加layer
  map.addLayer({
    id: "cluster",
    type: "symbol",
    source: "cluster",
    layout: {
      "icon-size": ["case", ["has", "point_count"], 0.5, 0.3],
      "icon-image": [
        "case",
        ["has", "point_count"],
        "greenMarker",
        ["get", "type"],
      ],
      "text-field": ["get", "point_count_abbreviated"],
      "text-offset": [0.5, -0.1],
      "text-size": 20,
      "icon-ignore-placement": true,
    },
    paint: {
      "text-color": "#F0F0F0",
      "text-halo-width": 0.5,
      "text-halo-color": "#F0F0F0",
    },
  });

添加一个点图层cluster,数据源设置为cluster, 在上面代码中,首先需要说明的是以下两个

js 复制代码
"icon-size": ["case", ["has", "point_count"], 0.5, 0.3],
"text-field": ["get", "point_count_abbreviated"],

以往的入门文章中,很少去讲点图层的样式,是因为这里面的东西非常多,只能见一个讲一个,不增加理解负担。首先需要知道的是,在mapbox中,点图层包含了图标和文字两个部分,可以是仅图标,可以是仅文字,也可以两者都有(同一图层)。而其他线、面图层都是没有文字的,需额外的点图层,这部分会在2.x中讲解。

icon-sizetext-field分别设置的是图标的大小和展示文字的字段,icon-size 用了case表达式,意思为如果有 point_count字段,设置为0.5,否则是0.3。这里最大疑惑是point_countpoint_count_abbreviated两个字段,在我们生成的数据里,只有type一个属性字段,这两个是哪里来的呢?

在设置聚合后,当多个点聚合成1个聚合点时,这个聚合点的属性中就会出现新的字段:

参数 描述
cluster 是否是聚合点
cluster_id 聚合ID(不重复)
point_count 被聚合的数量
point_count_abbreviated 被聚合的数量(缩写)

根据以上字段,可以区分聚合点和原始点,这样便可以设置各自的样式。

js 复制代码
      "icon-image": [
        "case",
        ["has", "point_count"],
        "greenMarker",
        ["get", "type"],
      ],

icon-image: 当有字段point_count时,图标是greenMarker,否则用type字段值的图片,前面步骤中,造的type字段数据为16的整数,添加的图片名称相应的也是16,这样便匹配上了。

js 复制代码
 "text-offset": [0.5, -0.1], // 文字的偏移,分别是左右偏移(右为正),上下偏移(下为正)
  "text-size": 20, // 文字大小 单位是像素
  "icon-ignore-placement": true, //是否忽略碰撞
  // paint
  "text-color": "#F0F0F0", // 文字颜色
  "text-halo-width": 0.5, // 字体轮廓宽度
  "text-halo-color": "#F0F0F0",// 字体轮廓颜色
  // 上面三个是为了加粗字体,其他加粗字体的方法比较麻烦

到了这一步,我们可以先看一下效果

聚合

放大

再放大

  1. 添加点击事件
js 复制代码
  map.on("click", "cluster", (e) => {
    if (e.features[0].properties.point_count !== undefined) {
      console.log("cluster");
      // 放大地图
      map.flyTo({
        center: e.lngLat,
        zoom: map.getZoom() + 2.5,
      });
    } else {
      console.log("single");
    }
  });

在这一步中,给cluster图层注册点击事件,聚合点的属性字段有point_count,根据这个判断我们点击的是聚合点和原始点,在这步可以打印e.features[0]查看其他的属性。通过判断,对聚合点和原始点执行不同的事件

总结

本节是聚合的第一个例子,整个过程相对简单,只是需要理解聚合多出来的属性和数据源的设置,接下来是官网的一个例子。

如有错误,欢迎指正;如有疑问,评论留言。

相关推荐
_龙衣5 分钟前
将 swagger 接口导入 apifox 查看及调试
前端·javascript·css·vue.js·css3
GIS数据转换器1 小时前
当三维地理信息遇上气象预警:电网安全如何实现“先知先觉”?
人工智能·科技·安全·gis·智慧城市·交互
进取星辰1 小时前
25、Tailwind:魔法速记术——React 19 样式新思路
前端·react.js·前端框架
x-cmd2 小时前
[250512] Node.js 24 发布:ClangCL 构建,升级 V8 引擎、集成 npm 11
前端·javascript·windows·npm·node.js
夏之小星星2 小时前
el-tree结合checkbox实现数据回显
前端·javascript·vue.js
crazyme_62 小时前
前端自学入门:HTML 基础详解与学习路线指引
前端·学习·html
撸猫7912 小时前
HttpSession 的运行原理
前端·后端·cookie·httpsession
亦世凡华、3 小时前
Rollup入门与进阶:为现代Web应用构建超小的打包文件
前端·经验分享·rollup·配置项目·前端分享
Bl_a_ck3 小时前
【React】Craco 简介
开发语言·前端·react.js·typescript·前端框架
augenstern4164 小时前
webpack重构优化
前端·webpack·重构