效果展示

初始化地图
<template>
  <div id="map" class="map"></div>
</template>
<script>
import Map from "ol/Map.js";
import View from "ol/View.js";
import { centerPoint } from "@/utils/showPage.js";
import { gaodeMapLayer } from "@/utils/MapLayer.js";
import { clusters, clusterCircleStyle, clusterCircles } from "@/utils/cluster";
import { createEmpty, extend, getHeight, getWidth } from "ol/extent.js";
export default {
  name: "cluster",
  data() {
    return {
      map: null
    };
  },
  mounted() {
    const map = new Map({
      layers: [gaodeMapLayer, clusters, clusterCircles],
      target: "map",
      view: new View({
        center: centerPoint,
        zoom: 11,
        minZoom: 2,
        maxZoom: 30,
        projection: "EPSG:4326",
        rotation: 0,
      }),
    });
  },
};
</script>
<style scoped>
.map {
  width: 100%;
  height: 900px;
}
</style>
图层详解
- 公共方法和样式
 function gasPointType() {
   let customPoint = config.CUSTOM_POINT.val,
     GAS_POINT_TYPE = config.GAS_POINT_TYPE.children;
   GAS_POINT_TYPE.forEach((item) => {
     item.type = item.val.typeId.value;
   });
   return [
     ...GAS_POINT_TYPE,
     { type: customPoint.typeId.value, val: customPoint },
   ];
 }
 function getICon(properties) {
   let typeId = properties.typeId || "1",
     allPoint = gasPointType();
   return allPoint.find((i) => i.type == typeId).val;
 }
 function clusterMemberStyle(clusterMember) {
   let properties = clusterMember.getProperties();
   let item = getICon(properties);
   return new Style({
     geometry: clusterMember.getGeometry(),
     image: new Icon({
       rotation: 0,
       src: item.iconUrl.value,
       anchorXUnits: "fraction",
       imgSize: [item.width.value, item.height.value],
     }),
   });
 }
 function generatePointsCircle(count, clusterCenter, resolution) {
   const circumference =
     circleDistanceMultiplier * circleFootSeparation * (2 + count);
   let legLength = circumference / (Math.PI * 2); 
   const angleStep = (Math.PI * 2) / count;
   const res = [];
   let angle;
   legLength = Math.max(legLength, 35) * resolution;
   for (let i = 0; i < count; ++i) {
     angle = circleStartAngle + i * angleStep;
     res.push([
       clusterCenter[0] + legLength * Math.cos(angle),
       clusterCenter[1] + legLength * Math.sin(angle),
     ]);
   }
   return res;
 }
 
- clusters图层
  const vectorSource = new VectorSource({
    features: new GeoJSON().readFeatures({
      type: devList.type,
      features: _devList,
    }),
  });
  const clusterSource = new Cluster({
    distance: 35,
    source: vectorSource,
  });
  }
  function clusterStyle(feature) {
    const size = feature.get("features").length;
    if (size > 1) {
      return [
        new Style({
          image: outerCircle,
        }),
        new Style({
          image: innerCircle,
          text: new Text({
            text: size.toString(),
            fill: textFill,
            stroke: textStroke,
          }),
        }),
      ];
    }
    const originalFeature = feature.get("features")[0];
    return clusterMemberStyle(originalFeature);
  }
  const clusters = new VectorLayer({
    source: clusterSource,
    style: clusterStyle,
  });
 
- clusterCircles图层
   const clusterCircles = new VectorLayer({
     source: clusterSource,
     style: clusterCircleStyle,
   });
   function clusterCircleStyle(
     cluster,
     resolution,
     clickFeature,
     clickResolution
   ) {
     if (cluster !== clickFeature || resolution !== clickResolution) {
       return null;
     }
     const clusterMembers = cluster.get("features");
     const centerCoordinates = cluster.getGeometry().getCoordinates();
     return generatePointsCircle(
       clusterMembers.length,
       cluster.getGeometry().getCoordinates(),
       resolution
     ).reduce((styles, coordinates, i) => {
       const point = new Point(coordinates);
       const line = new LineString([centerCoordinates, coordinates]);
       styles.unshift(
         new Style({
           geometry: line,
           stroke: convexHullStroke,
         })
       );
       styles.push(
         clusterMemberStyle(
           new Feature({
             ...clusterMembers[i].getProperties(),
             geometry: point,
           })
         )
       );
       return styles;
     }, []);
   }
 
地图pointermove和click
  map.on("pointermove", (event) => {
      clusters.getFeatures(event.pixel).then((features) => {
        map.getTargetElement().style.cursor =
          features[0] && features[0].get("features").length > 1
            ? "pointer"
            : "";
      });
    });
    map.on("click", (event) => {
      clusters.getFeatures(event.pixel).then((features) => {
        if (features.length > 0) {
          const clusterMembers = features[0].get("features");
          if (clusterMembers.length > 1) {
            
            const extent = createEmpty();
            clusterMembers.forEach((feature) =>
              extend(extent, feature.getGeometry().getExtent())
            );
            const view = map.getView();
            const resolution = map.getView().getResolution();
            if (
              view.getZoom() === view.getMaxZoom() ||
              (getWidth(extent) < resolution && getHeight(extent) < resolution)
            ) {
              
              let clickFeature = features[0];
              let clickResolution = resolution;
              clusterCircles.setStyle((cluster, resolution) => {
                clusterCircleStyle(
                  cluster,
                  resolution,
                  clickFeature,
                  clickResolution
                );
              });
            } else {
              
              view.fit(extent, { duration: 500, padding: [50, 50, 50, 50] });
            }
          }
        }
      });
    });