Cesium 添加加载聚合点信息

/**
 * 加载聚合点数据
 */
const aggregationPoint = async () => {
  if (!viewer.value) return;

  try {
    // 请求GeoJSON数据
    const response = await geoserver.get("/geoserver/czc/ows", {
      params: {
        service: "WFS",
        version: "1.1.0",
        request: "GetFeature",
        typeName: "czc:gz_POI",
        outputFormat: "application/json",
        srsName: "EPSG:4326",
      },
    });

    console.log(response, "response");

    // 配置显示规则
    const displayRules = {
      1: {
        showRange: [0, 10000],
        imageUrl: location08, // 1. 村名 TYPE=1
      },
      2: {
        showRange: [0, 4000],
        imageUrl: `data:image/svg+xml,${encodeURIComponent(`<svg t="1754727042072" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="36892" width="200" height="200"><path d="M356.503704 925.392593a151.703704 47.407407 0 1 0 303.407407 0 151.703704 47.407407 0 1 0-303.407407 0Z" fill="#F9D523" p-id="36893"></path><path d="M877.985185 417.185185c0 189.62963-263.585185 422.874074-343.229629 489.244445-13.274074 11.377778-32.237037 11.377778-45.511112 0C409.6 840.059259 146.014815 606.814815 146.014815 417.185185 146.014815 214.281481 309.096296 51.2 512 51.2S877.985185 214.281481 877.985185 417.185185z" fill="#EF6926" p-id="36894"></path><path d="M512 373.57037m-106.192593 0a106.192593 106.192593 0 1 0 212.385186 0 106.192593 106.192593 0 1 0-212.385186 0Z" fill="#FFFFFF" p-id="36895"></path></svg>`)}`, // 2. 自然村(蓝色圆形) TYPE=2
      },
      3: {
        showRange: [0, 2500],
        imageUrl: location10, // 3. 企业 TYPE=3
      },
      4: {
        showRange: [0, 2000],
        imageUrl: location11, // 4. 商店、超市、理发店、饭店一类 TYPE=4
      },
      5: {
        showRange: [0, 10000],
        imageUrl: `data:image/svg+xml,${encodeURIComponent(`<svg t="1754725696188" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12068" width="200" height="200"><path d="M777.980507 828.372086v0.00998H631.267057v-61.160546l146.71345 0.249513v0.029942c0.59883 0 1.197661-0.019961 1.796491-0.019961l13.174269 0.019961v-0.658714A171.664717 171.664717 0 0 0 777.980507 424.17154c-5.070097 0-10.080312 0.229552-15.030643 0.658713C751.172865 293.536686 640.868304 190.62768 506.510721 190.62768c-142.211244 0-257.497076 115.284834-257.497076 257.497076a259.332491 259.332491 0 0 0 3.183782 40.520858A140.736125 140.736125 0 1 0 216.077973 766.393138v0.109786l151.703703 0.259493V828.382066H218.074074v-0.129746c-110.646893-1.447173-199.899571-91.581131-199.899571-202.564367 0-101.42191 74.510472-185.427836 171.773505-200.27883C201.595259 260.73076 338.865154 130.744639 506.510721 130.744639c146.50386 0 269.803041 99.276101 306.351657 234.212554C924.773801 382.003899 1010.526316 478.655127 1010.526316 595.337232c0 128.538947-104.066745 232.76538-232.545809 233.034854zM326.861598 581.863548a19.961014 19.961014 0 0 1 19.923088-19.961014H653.264094a19.934066 19.934066 0 0 1 14.77115 33.304951L517.259727 844.770058c-0.019961-0.009981-0.049903-0.019961-0.069863-0.029941a19.953029 19.953029 0 0 1-35.237178-1.337388l-149.960109-248.215205A19.847236 19.847236 0 0 1 326.861598 581.863548z m173.161794 189.63961L595.337232 611.805068H404.709552z" fill="#E61D15" p-id="12069"></path><path d="M777.481481 828.382066c-1.497076 0-2.994152-0.029942-4.491228-0.059883v-60.901052c1.656764 0.049903 3.323509 0.079844 4.990254 0.079844a171.664717 171.664717 0 1 0-131.123899-282.448343h-74.714074A233.002916 233.002916 0 0 1 777.481481 362.292398c128.708616 0 233.044834 104.336218 233.044835 233.044834S906.190097 828.382066 777.481481 828.382066z" fill="#E61D15" p-id="12070"></path></svg>`)}`, // 5. 村名字 TYPE=5
      },
      default: {
        showRange: [0, 1000],
        imageUrl: `data:image/svg+xml,${encodeURIComponent(`<svg t="1754726456255" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15529" width="200" height="200"><path d="M512 921.6a409.6 409.6 0 1 0 0-819.2 409.6 409.6 0 0 0 0 819.2z m0 102.4C229.2224 1024 0 794.7776 0 512S229.2224 0 512 0s512 229.2224 512 512-229.2224 512-512 512z m0-366.0288L353.792 739.328l30.208-172.3392L256 444.9792 432.896 419.84 512 263.0656 591.104 419.84 768 444.928l-128 122.0608 30.208 172.3392L512 657.9712z" fill="#666666" p-id="15530"></path></svg>`)}`, // 默认图标
      },
    };

    // 使用Cesium的GeoJsonDataSource加载数据
    const dataSource = await Cesium.GeoJsonDataSource.load(response, {
      stroke: Cesium.Color.HOTPINK,
      fill: Cesium.Color.PINK.withAlpha(0.5),
      strokeWidth: 3,
      markerSymbol: "?",
      markerColor: Cesium.Color.YELLOW,
      markerSize: 48,
      clampToGround: true,
    });

    // 添加数据源到viewer
    await viewer.value.dataSources.add(dataSource);

    // 保存数据源引用以便后续控制显示隐藏
    aggregationDataSource = dataSource;

    // 根据store中的初始状态设置聚合点显示状态
    dataSource.show = screenStore.aggregationControl;

    // 启用聚合功能
    dataSource.clustering.enabled = false; // 启用聚合功能
    dataSource.clustering.pixelRange = 20; // 聚合范围(像素)
    dataSource.clustering.minimumClusterSize = 3; // 最小聚合数量

    // 自定义聚合点样式
    dataSource.clustering.clusterBillboards = false;
    dataSource.clustering.clusterLabels = false;
    dataSource.clustering.clusterPoints = false;

    // 创建现代化聚合点样式
    const createClusterIcon = (
      text: string,
      color: string,
      size: number = 48
    ) => {
      const canvas = document.createElement("canvas");
      const ctx = canvas.getContext("2d")!;
      canvas.width = size;
      canvas.height = size;

      // 绘制外圆阴影
      ctx.shadowColor = "rgba(0,0,0,0.3)";
      ctx.shadowBlur = 4;
      ctx.shadowOffsetX = 2;
      ctx.shadowOffsetY = 2;

      // 绘制主圆
      const gradient = ctx.createRadialGradient(
        size / 2,
        size / 2,
        0,
        size / 2,
        size / 2,
        size / 2
      );
      gradient.addColorStop(0, color);
      gradient.addColorStop(1, adjustBrightness(color, -30));

      ctx.fillStyle = gradient;
      ctx.beginPath();
      ctx.arc(size / 2, size / 2, size / 2 - 4, 0, 2 * Math.PI);
      ctx.fill();

      // 绘制内圆
      ctx.shadowColor = "transparent";
      ctx.fillStyle = "rgba(255,255,255,0.9)";
      ctx.beginPath();
      ctx.arc(size / 2, size / 2, size / 2 - 8, 0, 2 * Math.PI);
      ctx.fill();

      // 绘制文字
      ctx.fillStyle = color;
      ctx.font = `bold ${size / 3}px Arial, sans-serif`;
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText(text, size / 2, size / 2);

      return canvas.toDataURL();
    };

    // 调整颜色亮度的辅助函数
    const adjustBrightness = (color: string, amount: number) => {
      const hex = color.replace("#", "");
      const r = Math.max(
        0,
        Math.min(255, parseInt(hex.substr(0, 2), 16) + amount)
      );
      const g = Math.max(
        0,
        Math.min(255, parseInt(hex.substr(2, 2), 16) + amount)
      );
      const b = Math.max(
        0,
        Math.min(255, parseInt(hex.substr(4, 2), 16) + amount)
      );
      return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
    };

    // 创建不同级别的聚合图标
    const pin50 = createClusterIcon("50+", "#e74c3c", 56); // 红色,最大
    const pin40 = createClusterIcon("40+", "#f39c12", 52); // 橙色
    const pin30 = createClusterIcon("30+", "#f1c40f", 48); // 黄色
    const pin20 = createClusterIcon("20+", "#27ae60", 44); // 绿色
    const pin10 = createClusterIcon("10+", "#3498db", 40); // 蓝色

    // 根据聚合点数量设置不同样式
    dataSource.clustering.clusterEvent.addEventListener(
      (clusteredEntities: Cesium.Entity[], cluster: any) => {
        cluster.label.show = false;
        cluster.billboard.show = true;
        cluster.billboard.id = cluster.label.id;
        cluster.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;

        if (clusteredEntities.length >= 50) {
          cluster.billboard.image = pin50;
        } else if (clusteredEntities.length >= 40) {
          cluster.billboard.image = pin40;
        } else if (clusteredEntities.length >= 30) {
          cluster.billboard.image = pin30;
        } else if (clusteredEntities.length >= 20) {
          cluster.billboard.image = pin20;
        } else if (clusteredEntities.length >= 10) {
          cluster.billboard.image = pin10;
        } else {
          // 小于10个点的聚合使用紫色样式
          cluster.billboard.image = createClusterIcon(
            clusteredEntities.length.toString(),
            "#9b59b6",
            36
          );
        }
      }
    );

    // 设置单个点的样式
    const entities = dataSource.entities.values;
    for (let i = 0; i < entities.length; i++) {
      const entity = entities[i];

      // 获取POI类型
      const poiType = entity.properties?.TYPE?.getValue() || "default";
      const rule =
        displayRules[poiType as keyof typeof displayRules] ||
        displayRules.default;

      if (entity.billboard) {
        // 根据TYPE类型使用对应的图标
        entity.billboard.image = new Cesium.ConstantProperty(rule.imageUrl);
        entity.billboard.verticalOrigin = new Cesium.ConstantProperty(
          Cesium.VerticalOrigin.TOP
        );

        if (poiType === 5) {
          // return;
          console.log(entity.billboard, "123123");
          // return;
        }

        // 根据类型设置不同的尺寸
        const iconSize =
          poiType === 5 || poiType === 1
            ? 38
            : poiType === 2
              ? 32
              : poiType === 3 || poiType === 4
                ? 28
                : 32;

        // 创建浮动动画(上下轻微浮动)
        const floatAnimation = new Cesium.CallbackProperty(() => {
          const time = Date.now() / 1000;
          const offset = i * 0.6;
          // 使用余弦函数创造平滑的浮动效果
          const float = Math.cos(time * 0.95 + offset);
          return new Cesium.Cartesian2(0, -3 * float); // 轻微的上下浮动
        }, false);

        // 移除透明度动画,使用固定颜色

        // 设置固定图标大小
        entity.billboard.width = new Cesium.ConstantProperty(iconSize);
        entity.billboard.height = new Cesium.ConstantProperty(iconSize);

        // 应用浮动动画偏移
        // entity.billboard.pixelOffset = floatAnimation;

        // 设置固定的白色
        entity.billboard.color = new Cesium.ConstantProperty(
          Cesium.Color.WHITE
        );

        entity.billboard.heightReference = new Cesium.ConstantProperty(
          Cesium.HeightReference.CLAMP_TO_GROUND
        );

        // 设置距离显示条件
        entity.billboard.distanceDisplayCondition = new Cesium.ConstantProperty(
          new Cesium.DistanceDisplayCondition(
            rule.showRange[0],
            rule.showRange[1] - 1500
          )
        );

        entity.billboard.disableDepthTestDistance = new Cesium.ConstantProperty(
          Number.POSITIVE_INFINITY
        );

        entity.billboard.scale = new Cesium.ConstantProperty(0.7);

        // // 禁用深度测试,确保图标始终可见
        // entity.billboard.disableDepthTestDistance = new Cesium.ConstantProperty(
        //   Number.POSITIVE_INFINITY
        // );
      }

      // 添加标签显示POI名称,优化文字可读性和垂直居中
      if (entity.properties && entity.properties.NAME) {
        // 根据类型设置不同的标签显示距离
        const labelShowRange = rule.showRange;

        // 移除标签缩放动画,使用固定大小

        // 创建标签浮动偏移(与billboard同步浮动)
        const labelOffset = new Cesium.CallbackProperty(() => {
          const time = Date.now() / 1000;
          const offset = i * 0.6;
          const float = Math.cos(time * 0.8 + offset);
          const floatY = -35 - 8 * float; // 增加Y轴偏移,确保标签不被遮挡
          return new Cesium.Cartesian2(0, floatY);
        }, false);

        // 移除标签透明度动画,使用固定颜色

        entity.label = new Cesium.LabelGraphics({
          text: entity.properties.NAME.getValue(),
          font: new Cesium.ConstantProperty(
            "16px Microsoft YaHei, Arial, sans-serif"
          ),
          fillColor: new Cesium.ConstantProperty(Cesium.Color.BLACK),
          outlineColor: Cesium.Color.BLACK,
          outlineWidth: 2,
          style: new Cesium.ConstantProperty(Cesium.LabelStyle.FILL),
          verticalOrigin: new Cesium.ConstantProperty(
            Cesium.VerticalOrigin.TOP
          ),
          horizontalOrigin: new Cesium.ConstantProperty(
            Cesium.HorizontalOrigin.CENTER
          ),
          // pixelOffset: labelOffset, // 使用浮动动画偏移
          pixelOffset: new Cesium.Cartesian2(0, -30),
          distanceDisplayCondition: new Cesium.ConstantProperty(
            new Cesium.DistanceDisplayCondition(
              labelShowRange[0],
              labelShowRange[1] - 1500
            )
          ), // 标签显示距离
          disableDepthTestDistance: Number.POSITIVE_INFINITY,
          show: new Cesium.ConstantProperty(true),
          scale: new Cesium.ConstantProperty(0.9), // 使用固定大小
          // 添加背景样式增强可读性
          showBackground: true,
          backgroundColor: new Cesium.ConstantProperty(
            Cesium.Color.WHITE.withAlpha(1)
          ),
          backgroundPadding: new Cesium.ConstantProperty(
            new Cesium.Cartesian2(8, 4)
          ),
        });
      }
    }

    console.log(`成功加载 ${entities.length} 个POI点,已启用聚合功能`);
  } catch (error) {
    console.error("加载POI数据失败:", error);
  }
};

四下皆无人