mapboxgl动态点图标制作

mapboxgl动态点图标制作_图层

interface PulsingDot {
  width: number;
  height: number;
  data: Uint8Array;
  context: CanvasRenderingContext2D | null;
  onAdd: () => void;
  render: () => boolean;
}

class AnimatePointLayer {
  private map: mapboxgl.Map | undefined;
  private layerId: string;
  private iconName: string;
  private lngLat: GeoJSON.Position;

  private animatePointGeoJson: GeoJSON.FeatureCollection<GeoJSON.Point>;
  constructor(lngLat?: GeoJSON.Position) {
    this.layerId = 'ship-dot-point';
    this.iconName = 'pulsing-dot';
    this.lngLat = lngLat ?? [];
    // 动态点的 GeoJSON 数据
    this.animatePointGeoJson = {
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          properties: {},
          geometry: {
            type: 'Point',
            coordinates: [],
          },
        },
      ],
    };
  }

  /**
   * 初始化方法
   */
  private initialize() {
    this.addIcon();
  }
  /**
   * 更新点图标位置
   * @param lngLat
   */
  public setLngLat(lngLat: GeoJSON.Position) {
    let feature = this.animatePointGeoJson.features.at(0);
    if (feature) feature.geometry.coordinates = lngLat;
    let source = this.map?.getSource(this.layerId) as mapboxgl.GeoJSONSource;
    if (source) {
      source.setData(this.animatePointGeoJson);
      let poi = lngLat as any;
      this.map?.flyTo({ center: poi, zoom: 5 });
    } else {
      console.error('请先将实例添加至地图');
    }
  }
  /**
   * 添加图层
   */
  public addTo(map: mapboxgl.Map) {
    this.map = map;
    this.initialize();
    let source = this.map?.getSource(this.layerId);
    if (source) return;
    this.map?.addLayer({
      id: this.layerId,
      type: 'symbol',
      source: {
        type: 'geojson',
        data: this.animatePointGeoJson,
      },
      layout: {
        'symbol-sort-key': 999,
        'icon-image': this.iconName,
        'icon-allow-overlap': true,
      },
    });
    if (this.lngLat.length) this.setLngLat(this.lngLat);
    return this;
  }
  /**
   * 移除图层
   */
  public remove() {
    if (this.map?.getLayer(this.layerId)) {
      this.map.removeLayer(this.layerId);
    }
    if (this.map?.getSource(this.layerId)) {
      this.map.removeSource(this.layerId);
    }
  }
  /**
   * 添加动态点图标
   */
  private addIcon() {
    let map = this.map;
    if (map?.hasImage(this.iconName)) return;
    let size = 150;
    let pulsingDot: PulsingDot = {
      width: size,
      height: size,
      data: new Uint8Array(size * size * 4),
      context: null,
      onAdd: function () {
        const canvas = document.createElement('canvas');
        canvas.width = this.width;
        canvas.height = this.height;
        this.context = canvas.getContext('2d');
      },

      render: function () {
        const duration = 1000; // 动画的持续时间为 1000 毫秒(1 秒)
        const t = (performance.now() % duration) / duration; // 计算当前时间相对于动画周期的比例

        const radius = (size / 2) * 0.1; // 内圆的半径
        const outerRadius = (size / 2) * 0.9 * t + radius; // 动态计算外圆的半径
        const context = this.context!;

        // 清除画布
        context?.clearRect(0, 0, this.width, this.height);

        // 画外圆
        context.beginPath();
        context.arc(this.width / 2, this.height / 2, outerRadius, 0, Math.PI * 2);
        context.fillStyle = `rgba(255, 200, 200, ${1 - t})`; // 外圆的填充颜色,随着时间变化透明度减少
        context.fill();

        // 画内圆
        context.beginPath();
        context.arc(this.width / 2, this.height / 2, radius, 0, Math.PI * 2);
        context.fillStyle = 'rgba(255, 100, 100, 1)'; // 内圆的填充颜色
        context.strokeStyle = 'rgba(255, 100, 100, 1)'; // 内圆的描边颜色
        context.lineWidth = 2 + 4 * (1 - t); // 内圆的描边宽度,随着时间变化宽度减少
        context.fill();
        context.stroke();

        // 获取画布的数据
        this.data = context.getImageData(0, 0, this.width, this.height).data as any;

        // 触发地图重新绘制以生成平滑的动画效果
        if (typeof map !== 'undefined' && map.triggerRepaint) {
          map.triggerRepaint();
        }

        // 返回 true 以通知地图图像已更新
        return true;
      },
    };
    map?.addImage(this.iconName, pulsingDot, { pixelRatio: 2 });
  }
}
export default AnimatePointLayer;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.