/**
 * Mars3D平台插件,支持气象 风向图 功能插件  mars3d-wind
 *
 * 版本信息：v3.4.7
 * 编译日期：2022-09-15 16:25:31
 * 版权所有：Copyright by 火星科技  http://mars3d.cn
 * 使用单位：安徽XX有限公司 ，2021-08-18
 */
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, (window.mars3d || require('mars3d'))) :
  typeof define === 'function' && define.amd ? define(['exports', 'mars3d'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["mars3d-wind"] = {}, global.mars3d));
})(this, (function (exports, mars3d) { 'use strict';

  function _interopNamespace(e) {
    if (e && e.__esModule) return e;
    var n = Object.create(null);
    if (e) {
      Object.keys(e).forEach(function (k) {
        if (k !== 'default') {
          var d = Object.getOwnPropertyDescriptor(e, k);
          Object.defineProperty(n, k, d.get ? d : {
            enumerable: true,
            get: function () { return e[k]; }
          });
        }
      });
    }
    n["default"] = e;
    return n;
  }

  var mars3d__namespace = /*#__PURE__*/_interopNamespace(mars3d);

  /**
   * 风场相关 静态方法，【需要引入 mars3d-wind 插件库】
   * @module WindUtil
   */
  const Cesium$7 = mars3d__namespace.Cesium;

  /**
   * 风速风向 转 U值
   *
   * @export
   * @param {Number} speed 风速
   * @param {Number} direction 风向
   * @return {Number} U值
   */
  function getU(speed, direction) {
    const u = speed * Math.cos(Cesium$7.Math.toRadians(direction));
    return u
  }

  /**
   * 风速风向 转 V值
   *
   * @export
   * @param {Number} speed 风速
   * @param {Number} direction 风向
   * @return {Number} V值
   */
  function getV(speed, direction) {
    const v = speed * Math.sin(Cesium$7.Math.toRadians(direction));
    return v
  }

  /**
   * UV值 转 风速, 风速是uv分量的平方和
   *
   * @export
   * @param {Number} u U值
   * @param {Number} v V值
   * @return {Number} 风速
   */
  function getSpeed(u, v) {
    const speed = Math.sqrt(Math.pow(u, 2) + Math.pow(v, 2));
    return speed
  }

  /**
   * UV 转 风向
   *
   * @export
   * @param {Number} u U值
   * @param {Number} v V值
   * @return {Number} 风向
   */
  function getDirection(u, v) {
    let direction = Cesium$7.Math.toDegrees(Math.atan2(v, u));
    direction += direction < 0 ? 360 : 0;

    return direction
  }

  var WindUtil = {
    __proto__: null,
    getU: getU,
    getV: getV,
    getSpeed: getSpeed,
    getDirection: getDirection
  };

  const Cesium$6 = mars3d__namespace.Cesium;

  class CustomPrimitive {
    constructor(options) {
      this.commandType = options.commandType;

      this.geometry = options.geometry;
      this.attributeLocations = options.attributeLocations;
      this.primitiveType = options.primitiveType;

      this.uniformMap = options.uniformMap;

      this.vertexShaderSource = options.vertexShaderSource;
      this.fragmentShaderSource = options.fragmentShaderSource;

      this.rawRenderState = options.rawRenderState;
      this.framebuffer = options.framebuffer;

      this.outputTexture = options.outputTexture;

      this.autoClear = options.autoClear ?? false;
      this.preExecute = options.preExecute;

      this.show = true;
      this.commandToExecute = undefined;
      this.clearCommand = undefined;
      if (this.autoClear) {
        this.clearCommand = new Cesium$6.ClearCommand({
          color: new Cesium$6.Color(0.0, 0.0, 0.0, 0.0),
          depth: 1.0,
          framebuffer: this.framebuffer,
          pass: Cesium$6.Pass.OPAQUE
        });
      }
    }

    createCommand(context) {
      switch (this.commandType) {
        case "Draw": {
          const vertexArray = Cesium$6.VertexArray.fromGeometry({
            context: context,
            geometry: this.geometry,
            attributeLocations: this.attributeLocations,
            bufferUsage: Cesium$6.BufferUsage.STATIC_DRAW
          });

          const shaderProgram = Cesium$6.ShaderProgram.fromCache({
            context: context,
            attributeLocations: this.attributeLocations,
            vertexShaderSource: this.vertexShaderSource,
            fragmentShaderSource: this.fragmentShaderSource
          });

          const renderState = Cesium$6.RenderState.fromCache(this.rawRenderState);
          return new Cesium$6.DrawCommand({
            owner: this,
            vertexArray: vertexArray,
            primitiveType: this.primitiveType,
            uniformMap: this.uniformMap,
            modelMatrix: Cesium$6.Matrix4.IDENTITY,
            shaderProgram: shaderProgram,
            framebuffer: this.framebuffer,
            renderState: renderState,
            pass: Cesium$6.Pass.OPAQUE
          })
        }
        case "Compute": {
          return new Cesium$6.ComputeCommand({
            owner: this,
            fragmentShaderSource: this.fragmentShaderSource,
            uniformMap: this.uniformMap,
            outputTexture: this.outputTexture,
            persists: true
          })
        }
      }
    }

    setGeometry(context, geometry) {
      this.geometry = geometry;
      const vertexArray = Cesium$6.VertexArray.fromGeometry({
        context: context,
        geometry: this.geometry,
        attributeLocations: this.attributeLocations,
        bufferUsage: Cesium$6.BufferUsage.STATIC_DRAW
      });
      this.commandToExecute.vertexArray = vertexArray;
    }

    update(frameState) {
      if (!this.show) {
        return
      }
      if (frameState.mode !== Cesium$6.SceneMode.SCENE3D) {
        // 三维模式下
        return
      }

      if (!Cesium$6.defined(this.commandToExecute)) {
        this.commandToExecute = this.createCommand(frameState.context);
      }

      if (Cesium$6.defined(this.preExecute)) {
        this.preExecute();
      }

      if (Cesium$6.defined(this.clearCommand)) {
        frameState.commandList.push(this.clearCommand);
      }
      frameState.commandList.push(this.commandToExecute);
    }

    isDestroyed() {
      return false
    }

    destroy() {
      if (Cesium$6.defined(this.commandToExecute)) {
        this.commandToExecute.shaderProgram = this.commandToExecute.shaderProgram && this.commandToExecute.shaderProgram.destroy();
      }
      return Cesium$6.destroyObject(this)
    }
  }

  const Cesium$5 = mars3d__namespace.Cesium;

  const Util = (function () {
    const getFullscreenQuad = function () {
      const fullscreenQuad = new Cesium$5.Geometry({
        attributes: new Cesium$5.GeometryAttributes({
          position: new Cesium$5.GeometryAttribute({
            componentDatatype: Cesium$5.ComponentDatatype.FLOAT,
            componentsPerAttribute: 3,
            //  v3----v2
            //  |     |
            //  |     |
            //  v0----v1
            values: new Float32Array([
              -1,
              -1,
              0, // v0
              1,
              -1,
              0, // v1
              1,
              1,
              0, // v2
              -1,
              1,
              0 // v3
            ])
          }),
          st: new Cesium$5.GeometryAttribute({
            componentDatatype: Cesium$5.ComponentDatatype.FLOAT,
            componentsPerAttribute: 2,
            values: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1])
          })
        }),
        indices: new Uint32Array([3, 2, 0, 0, 2, 1])
      });
      return fullscreenQuad
    };

    const createTexture = function (options, typedArray) {
      if (Cesium$5.defined(typedArray)) {
        // typed array needs to be passed as source option, this is required by Cesium.Texture
        const source = {};
        source.arrayBufferView = typedArray;
        options.source = source;
      }

      const texture = new Cesium$5.Texture(options);
      return texture
    };

    const createFramebuffer = function (context, colorTexture, depthTexture) {
      const framebuffer = new Cesium$5.Framebuffer({
        context: context,
        colorTextures: [colorTexture],
        depthTexture: depthTexture
      });
      return framebuffer
    };

    const createRawRenderState = function (options) {
      const translucent = true;
      const closed = false;
      const existing = {
        viewport: options.viewport,
        depthTest: options.depthTest,
        depthMask: options.depthMask,
        blending: options.blending
      };

      const rawRenderState = Cesium$5.Appearance.getDefaultRenderState(translucent, closed, existing);
      return rawRenderState
    };

    const viewRectangleToLonLatRange = function (viewRectangle) {
      const range = {};

      const postiveWest = Cesium$5.Math.mod(viewRectangle.west, Cesium$5.Math.TWO_PI);
      const postiveEast = Cesium$5.Math.mod(viewRectangle.east, Cesium$5.Math.TWO_PI);
      const width = viewRectangle.width;

      let longitudeMin;
      let longitudeMax;
      if (width > Cesium$5.Math.THREE_PI_OVER_TWO) {
        longitudeMin = 0.0;
        longitudeMax = Cesium$5.Math.TWO_PI;
      } else {
        if (postiveEast - postiveWest < width) {
          longitudeMin = postiveWest;
          longitudeMax = postiveWest + width;
        } else {
          longitudeMin = postiveWest;
          longitudeMax = postiveEast;
        }
      }

      range.lon = {
        min: Cesium$5.Math.toDegrees(longitudeMin),
        max: Cesium$5.Math.toDegrees(longitudeMax)
      };

      const south = viewRectangle.south;
      const north = viewRectangle.north;
      const height = viewRectangle.height;

      const extendHeight = height > Cesium$5.Math.PI / 12 ? height / 2 : 0;
      let extendedSouth = Cesium$5.Math.clampToLatitudeRange(south - extendHeight);
      let extendedNorth = Cesium$5.Math.clampToLatitudeRange(north + extendHeight);

      // extend the bound in high latitude area to make sure it can cover all the visible area
      if (extendedSouth < -Cesium$5.Math.PI_OVER_THREE) {
        extendedSouth = -Cesium$5.Math.PI_OVER_TWO;
      }
      if (extendedNorth > Cesium$5.Math.PI_OVER_THREE) {
        extendedNorth = Cesium$5.Math.PI_OVER_TWO;
      }

      range.lat = {
        min: Cesium$5.Math.toDegrees(extendedSouth),
        max: Cesium$5.Math.toDegrees(extendedNorth)
      };

      return range
    };

    return {
      getFullscreenQuad: getFullscreenQuad,
      createTexture: createTexture,
      createFramebuffer: createFramebuffer,
      createRawRenderState: createRawRenderState,
      viewRectangleToLonLatRange: viewRectangleToLonLatRange
    }
  })();

  var segmentDraw_vert = "attribute vec2 st;\n// it is not normal itself, but used to control normal\nattribute vec3 normal; // (point to use, offset sign, not used component)\n\nuniform sampler2D currentParticlesPosition;\nuniform sampler2D postProcessingPosition;\nuniform sampler2D postProcessingSpeed;\n\nuniform float particleHeight;\n\nuniform float aspect;\nuniform float pixelSize;\nuniform float lineWidth;\n\nvarying float speedNormalization;\n\nvec3 convertCoordinate(vec3 lonLatLev) {\n    // WGS84 (lon, lat, lev) -> ECEF (x, y, z)\n    // see https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#From_geodetic_to_ECEF_coordinates for detail\n\n    // WGS 84 geometric constants \n    float a = 6378137.0; // Semi-major axis \n    float b = 6356752.3142; // Semi-minor axis \n    float e2 = 6.69437999014e-3; // First eccentricity squared\n\n    float latitude = radians(lonLatLev.y);\n    float longitude = radians(lonLatLev.x);\n\n    float cosLat = cos(latitude);\n    float sinLat = sin(latitude);\n    float cosLon = cos(longitude);\n    float sinLon = sin(longitude);\n\n    float N_Phi = a / sqrt(1.0 - e2 * sinLat * sinLat);\n    float h = particleHeight; // it should be high enough otherwise the particle may not pass the terrain depth test\n\n    vec3 cartesian = vec3(0.0);\n    cartesian.x = (N_Phi + h) * cosLat * cosLon;\n    cartesian.y = (N_Phi + h) * cosLat * sinLon;\n    cartesian.z = ((b * b) / (a * a) * N_Phi + h) * sinLat;\n    return cartesian;\n}\n\nvec4 calcProjectedCoordinate(vec3 lonLatLev) {\n    // the range of longitude in Cesium is [-180, 180] but the range of longitude in the NetCDF file is [0, 360]\n    // [0, 180] is corresponding to [0, 180] and [180, 360] is corresponding to [-180, 0]\n    lonLatLev.x = mod(lonLatLev.x + 180.0, 360.0) - 180.0;\n    vec3 particlePosition = convertCoordinate(lonLatLev);\n    vec4 projectedCoordinate = czm_modelViewProjection * vec4(particlePosition, 1.0);\n    return projectedCoordinate;\n}\n\nvec4 calcOffset(vec4 currentProjectedCoordinate, vec4 nextProjectedCoordinate, float offsetSign) {\n    vec2 aspectVec2 = vec2(aspect, 1.0);\n    vec2 currentXY = (currentProjectedCoordinate.xy / currentProjectedCoordinate.w) * aspectVec2;\n    vec2 nextXY = (nextProjectedCoordinate.xy / nextProjectedCoordinate.w) * aspectVec2;\n\n    float offsetLength = lineWidth / 2.0;\n    vec2 direction = normalize(nextXY - currentXY);\n    vec2 normalVector = vec2(-direction.y, direction.x);\n    normalVector.x = normalVector.x / aspect;\n    normalVector = offsetLength * normalVector;\n\n    vec4 offset = vec4(offsetSign * normalVector, 0.0, 0.0);\n    return offset;\n}\n\nvoid main() {\n    vec2 particleIndex = st;\n\n    vec3 currentPosition = texture2D(currentParticlesPosition, particleIndex).rgb;\n    vec4 nextPosition = texture2D(postProcessingPosition, particleIndex);\n\n    vec4 currentProjectedCoordinate = vec4(0.0);\n    vec4 nextProjectedCoordinate = vec4(0.0);\n    if (nextPosition.w > 0.0) {\n        currentProjectedCoordinate = calcProjectedCoordinate(currentPosition);\n        nextProjectedCoordinate = calcProjectedCoordinate(currentPosition);\n    } else {\n        currentProjectedCoordinate = calcProjectedCoordinate(currentPosition);\n        nextProjectedCoordinate = calcProjectedCoordinate(nextPosition.xyz);\n    }\n\n    float pointToUse = normal.x; // -1 is currentProjectedCoordinate and +1 is nextProjectedCoordinate\n    float offsetSign = normal.y;\n\n    vec4 offset = pixelSize * calcOffset(currentProjectedCoordinate, nextProjectedCoordinate, offsetSign);\n    if (pointToUse < 0.0) {\n        gl_Position = currentProjectedCoordinate + offset;\n    } else {\n        gl_Position = nextProjectedCoordinate + offset;\n    }\n\n    speedNormalization = texture2D(postProcessingSpeed, particleIndex).a;\n}"; // eslint-disable-line

  var segmentDraw_frag = "uniform sampler2D colorTable;\n\nvarying float speedNormalization;\n\nvoid main() {\n    gl_FragColor = texture2D(colorTable, vec2(speedNormalization, 0.0));\n}"; // eslint-disable-line

  var fullscreen_vert = "attribute vec3 position;\r\nattribute vec2 st;\r\n\r\nvarying vec2 textureCoordinate;\r\n\r\nvoid main() {\r\n    textureCoordinate = st;\r\n    gl_Position = vec4(position, 1.0);\r\n}"; // eslint-disable-line

  var trailDraw_frag = "uniform sampler2D segmentsColorTexture;\r\nuniform sampler2D segmentsDepthTexture;\r\n\r\nuniform sampler2D currentTrailsColor;\r\nuniform sampler2D trailsDepthTexture;\r\n\r\nuniform float fadeOpacity;\r\n\r\nvarying vec2 textureCoordinate;\r\n\r\nvoid main() {\r\n    vec4 pointsColor = texture2D(segmentsColorTexture, textureCoordinate);\r\n    vec4 trailsColor = texture2D(currentTrailsColor, textureCoordinate);\r\n\r\n    trailsColor = floor(fadeOpacity * 255.0 * trailsColor) / 255.0; // make sure the trailsColor will be strictly decreased\r\n\r\n    float pointsDepth = texture2D(segmentsDepthTexture, textureCoordinate).r;\r\n    float trailsDepth = texture2D(trailsDepthTexture, textureCoordinate).r;\r\n    float globeDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, textureCoordinate));\r\n\r\n    gl_FragColor = vec4(0.0);\r\n    if (pointsDepth < globeDepth) {\r\n        gl_FragColor = gl_FragColor + pointsColor;\r\n    }\r\n    if (trailsDepth < globeDepth) {\r\n        gl_FragColor = gl_FragColor + trailsColor;\r\n    }\r\n    gl_FragDepthEXT = min(pointsDepth, trailsDepth);\r\n}"; // eslint-disable-line

  var screenDraw_frag = "uniform sampler2D trailsColorTexture;\r\nuniform sampler2D trailsDepthTexture;\r\n\r\nvarying vec2 textureCoordinate;\r\n\r\nvoid main() {\r\n    vec4 trailsColor = texture2D(trailsColorTexture, textureCoordinate);\r\n    float trailsDepth = texture2D(trailsDepthTexture, textureCoordinate).r;\r\n    float globeDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, textureCoordinate));\r\n\r\n    if (trailsDepth < globeDepth) {\r\n        gl_FragColor = trailsColor;\r\n    } else {\r\n        gl_FragColor = vec4(0.0);\r\n    }\r\n}"; // eslint-disable-line

  const Cesium$4 = mars3d__namespace.Cesium;

  class ParticlesRendering {
    constructor(context, data, options, viewerParameters, particlesComputing) {
      this.createRenderingTextures(context, data, options.colors);
      this.createRenderingFramebuffers(context);
      this.createRenderingPrimitives(context, options, viewerParameters, particlesComputing);
    }

    createRenderingTextures(context, data, colors) {
      const colorTextureOptions = {
        context: context,
        width: context.drawingBufferWidth,
        height: context.drawingBufferHeight,
        pixelFormat: Cesium$4.PixelFormat.RGBA,
        pixelDatatype: Cesium$4.PixelDatatype.UNSIGNED_BYTE
      };
      const depthTextureOptions = {
        context: context,
        width: context.drawingBufferWidth,
        height: context.drawingBufferHeight,
        pixelFormat: Cesium$4.PixelFormat.DEPTH_COMPONENT,
        pixelDatatype: Cesium$4.PixelDatatype.UNSIGNED_INT
      };

      // 颜色处理
      const colorNum = colors.length;
      const colorTable = new Float32Array(colorNum * 3);
      for (let i = 0; i < colorNum; i++) {
        const color = Cesium$4.Color.fromCssColorString(colors[i]);
        colorTable[3 * i] = color.red;
        colorTable[3 * i + 1] = color.green;
        colorTable[3 * i + 2] = color.blue;
      }
      const colorTableTextureOptions = {
        context: context,
        width: colorNum,
        height: 1,
        pixelFormat: Cesium$4.PixelFormat.RGB,
        pixelDatatype: Cesium$4.PixelDatatype.FLOAT,
        sampler: new Cesium$4.Sampler({
          minificationFilter: Cesium$4.TextureMinificationFilter.LINEAR,
          magnificationFilter: Cesium$4.TextureMagnificationFilter.LINEAR
        })
      };

      this.textures = {
        segmentsColor: Util.createTexture(colorTextureOptions),
        segmentsDepth: Util.createTexture(depthTextureOptions),

        currentTrailsColor: Util.createTexture(colorTextureOptions),
        currentTrailsDepth: Util.createTexture(depthTextureOptions),

        nextTrailsColor: Util.createTexture(colorTextureOptions),
        nextTrailsDepth: Util.createTexture(depthTextureOptions),

        colorTable: Util.createTexture(colorTableTextureOptions, colorTable)
      };
    }

    createRenderingFramebuffers(context) {
      this.framebuffers = {
        segments: Util.createFramebuffer(context, this.textures.segmentsColor, this.textures.segmentsDepth),
        currentTrails: Util.createFramebuffer(context, this.textures.currentTrailsColor, this.textures.currentTrailsDepth),
        nextTrails: Util.createFramebuffer(context, this.textures.nextTrailsColor, this.textures.nextTrailsDepth)
      };
    }

    createSegmentsGeometry(options) {
      const repeatVertex = 4;

      let st = [];
      for (let s = 0; s < options.particlesTextureSize; s++) {
        for (let t = 0; t < options.particlesTextureSize; t++) {
          for (let i = 0; i < repeatVertex; i++) {
            st.push(s / options.particlesTextureSize);
            st.push(t / options.particlesTextureSize);
          }
        }
      }
      st = new Float32Array(st);

      let normal = [];
      const pointToUse = [-1, 1];
      const offsetSign = [-1, 1];
      for (let i = 0; i < options.maxParticles; i++) {
        for (let j = 0; j < repeatVertex / 2; j++) {
          for (let k = 0; k < repeatVertex / 2; k++) {
            normal.push(pointToUse[j]);
            normal.push(offsetSign[k]);
            normal.push(0);
          }
        }
      }
      normal = new Float32Array(normal);

      const indexSize = 6 * options.maxParticles;
      const vertexIndexes = new Uint32Array(indexSize);
      for (let i = 0, j = 0, vertex = 0; i < options.maxParticles; i++) {
        vertexIndexes[j++] = vertex + 0;
        vertexIndexes[j++] = vertex + 1;
        vertexIndexes[j++] = vertex + 2;
        vertexIndexes[j++] = vertex + 2;
        vertexIndexes[j++] = vertex + 1;
        vertexIndexes[j++] = vertex + 3;
        vertex += 4;
      }

      const geometry = new Cesium$4.Geometry({
        attributes: new Cesium$4.GeometryAttributes({
          st: new Cesium$4.GeometryAttribute({
            componentDatatype: Cesium$4.ComponentDatatype.FLOAT,
            componentsPerAttribute: 2,
            values: st
          }),
          normal: new Cesium$4.GeometryAttribute({
            componentDatatype: Cesium$4.ComponentDatatype.FLOAT,
            componentsPerAttribute: 3,
            values: normal
          })
        }),
        indices: vertexIndexes
      });

      return geometry
    }

    createRenderingPrimitives(context, options, viewerParameters, particlesComputing) {
      const that = this;
      this.primitives = {
        segments: new CustomPrimitive({
          commandType: "Draw",
          attributeLocations: {
            st: 0,
            normal: 1
          },
          geometry: this.createSegmentsGeometry(options),
          primitiveType: Cesium$4.PrimitiveType.TRIANGLES,
          uniformMap: {
            currentParticlesPosition: function () {
              return particlesComputing.particlesTextures.currentParticlesPosition
            },
            postProcessingPosition: function () {
              return particlesComputing.particlesTextures.postProcessingPosition
            },
            postProcessingSpeed: function () {
              return particlesComputing.particlesTextures.postProcessingSpeed
            },
            colorTable: function () {
              return that.textures.colorTable
            },
            aspect: function () {
              return context.drawingBufferWidth / context.drawingBufferHeight
            },
            pixelSize: function () {
              return viewerParameters.pixelSize
            },
            lineWidth: function () {
              return options.lineWidth
            },
            particleHeight: function () {
              return options.particleHeight
            }
          },
          vertexShaderSource: new Cesium$4.ShaderSource({
            sources: [segmentDraw_vert]
          }),
          fragmentShaderSource: new Cesium$4.ShaderSource({
            sources: [segmentDraw_frag]
          }),
          rawRenderState: Util.createRawRenderState({
            // undefined value means let Cesium deal with it
            viewport: undefined,
            depthTest: {
              enabled: true
            },
            depthMask: true
          }),
          framebuffer: this.framebuffers.segments,
          autoClear: true
        }),

        trails: new CustomPrimitive({
          commandType: "Draw",
          attributeLocations: {
            position: 0,
            st: 1
          },
          geometry: Util.getFullscreenQuad(),
          primitiveType: Cesium$4.PrimitiveType.TRIANGLES,
          uniformMap: {
            segmentsColorTexture: function () {
              return that.textures.segmentsColor
            },
            segmentsDepthTexture: function () {
              return that.textures.segmentsDepth
            },
            currentTrailsColor: function () {
              return that.framebuffers.currentTrails.getColorTexture(0)
            },
            trailsDepthTexture: function () {
              return that.framebuffers.currentTrails.depthTexture
            },
            fadeOpacity: function () {
              return options.fadeOpacity
            }
          },
          // prevent Cesium from writing depth because the depth here should be written manually
          vertexShaderSource: new Cesium$4.ShaderSource({
            defines: ["DISABLE_GL_POSITION_LOG_DEPTH"],
            sources: [fullscreen_vert]
          }),
          fragmentShaderSource: new Cesium$4.ShaderSource({
            defines: ["DISABLE_LOG_DEPTH_FRAGMENT_WRITE"],
            sources: [trailDraw_frag]
          }),
          rawRenderState: Util.createRawRenderState({
            viewport: undefined,
            depthTest: {
              enabled: true,
              func: Cesium$4.DepthFunction.ALWAYS // always pass depth test for full control of depth information
            },
            depthMask: true
          }),
          framebuffer: this.framebuffers.nextTrails,
          autoClear: true,
          preExecute: function () {
            // swap framebuffers before binding
            const temp = that.framebuffers.currentTrails;
            that.framebuffers.currentTrails = that.framebuffers.nextTrails;
            that.framebuffers.nextTrails = temp;

            // keep the framebuffers up to date
            that.primitives.trails.commandToExecute.framebuffer = that.framebuffers.nextTrails;
            that.primitives.trails.clearCommand.framebuffer = that.framebuffers.nextTrails;
          }
        }),

        screen: new CustomPrimitive({
          commandType: "Draw",
          attributeLocations: {
            position: 0,
            st: 1
          },
          geometry: Util.getFullscreenQuad(),
          primitiveType: Cesium$4.PrimitiveType.TRIANGLES,
          uniformMap: {
            trailsColorTexture: function () {
              return that.framebuffers.nextTrails.getColorTexture(0)
            },
            trailsDepthTexture: function () {
              return that.framebuffers.nextTrails.depthTexture
            }
          },
          // prevent Cesium from writing depth because the depth here should be written manually
          vertexShaderSource: new Cesium$4.ShaderSource({
            defines: ["DISABLE_GL_POSITION_LOG_DEPTH"],
            sources: [fullscreen_vert]
          }),
          fragmentShaderSource: new Cesium$4.ShaderSource({
            defines: ["DISABLE_LOG_DEPTH_FRAGMENT_WRITE"],
            sources: [screenDraw_frag]
          }),
          rawRenderState: Util.createRawRenderState({
            viewport: undefined,
            depthTest: {
              enabled: false
            },
            depthMask: true,
            blending: {
              enabled: true
            }
          }),
          framebuffer: undefined // undefined value means let Cesium deal with it
        })
      };
    }
  }

  var getWind_frag = "// the size of UV textures: width = lon, height = lat*lev\nuniform sampler2D U; // eastward wind \nuniform sampler2D V; // northward wind\n\nuniform sampler2D currentParticlesPosition; // (lon, lat, lev)\n\nuniform vec3 dimension; // (lon, lat, lev)\nuniform vec3 minimum; // minimum of each dimension\nuniform vec3 maximum; // maximum of each dimension\nuniform vec3 interval; // interval of each dimension\n\nvarying vec2 v_textureCoordinates;\n\nvec2 mapPositionToNormalizedIndex2D(vec3 lonLatLev) {\n    // ensure the range of longitude and latitude\n    lonLatLev.x = mod(lonLatLev.x, 360.0);\n    lonLatLev.y = clamp(lonLatLev.y, -90.0, 90.0);\n\n    vec3 index3D = vec3(0.0);\n    index3D.x = (lonLatLev.x - minimum.x) / interval.x;\n    index3D.y = (lonLatLev.y - minimum.y) / interval.y;\n    index3D.z = (lonLatLev.z - minimum.z) / interval.z;\n\n    // the st texture coordinate corresponding to (col, row) index\n    // example\n    // data array is [0, 1, 2, 3, 4, 5], width = 3, height = 2\n    // the content of texture will be\n    // t 1.0\n    //    |  3 4 5\n    //    |\n    //    |  0 1 2\n    //   0.0------1.0 s\n\n    vec2 index2D = vec2(index3D.x, index3D.z * dimension.y + index3D.y);\n    vec2 normalizedIndex2D = vec2(index2D.x / dimension.x, index2D.y / (dimension.y * dimension.z));\n    return normalizedIndex2D;\n}\n\nfloat getWind(sampler2D windTexture, vec3 lonLatLev) {\n    vec2 normalizedIndex2D = mapPositionToNormalizedIndex2D(lonLatLev);\n    float result = texture2D(windTexture, normalizedIndex2D).r;\n    return result;\n}\n\nconst mat4 kernelMatrix = mat4(\n    0.0, -1.0, 2.0, -1.0, // first column\n    2.0, 0.0, -5.0, 3.0, // second column\n    0.0, 1.0, 4.0, -3.0, // third column\n    0.0, 0.0, -1.0, 1.0 // fourth column\n);\nfloat oneDimensionInterpolation(float t, float p0, float p1, float p2, float p3) {\n    vec4 tVec4 = vec4(1.0, t, t * t, t * t * t);\n    tVec4 = tVec4 / 2.0;\n    vec4 pVec4 = vec4(p0, p1, p2, p3);\n    return dot((tVec4 * kernelMatrix), pVec4);\n}\n\nfloat calculateB(sampler2D windTexture, float t, float lon, float lat, float lev) {\n    float lon0 = floor(lon) - 1.0 * interval.x;\n    float lon1 = floor(lon);\n    float lon2 = floor(lon) + 1.0 * interval.x;\n    float lon3 = floor(lon) + 2.0 * interval.x;\n\n    float p0 = getWind(windTexture, vec3(lon0, lat, lev));\n    float p1 = getWind(windTexture, vec3(lon1, lat, lev));\n    float p2 = getWind(windTexture, vec3(lon2, lat, lev));\n    float p3 = getWind(windTexture, vec3(lon3, lat, lev));\n\n    return oneDimensionInterpolation(t, p0, p1, p2, p3);\n}\n\nfloat interpolateOneTexture(sampler2D windTexture, vec3 lonLatLev) {\n    float lon = lonLatLev.x;\n    float lat = lonLatLev.y;\n    float lev = lonLatLev.z;\n\n    float lat0 = floor(lat) - 1.0 * interval.y;\n    float lat1 = floor(lat);\n    float lat2 = floor(lat) + 1.0 * interval.y;\n    float lat3 = floor(lat) + 2.0 * interval.y;\n\n    vec2 coefficient = lonLatLev.xy - floor(lonLatLev.xy);\n    float b0 = calculateB(windTexture, coefficient.x, lon, lat0, lev);\n    float b1 = calculateB(windTexture, coefficient.x, lon, lat1, lev);\n    float b2 = calculateB(windTexture, coefficient.x, lon, lat2, lev);\n    float b3 = calculateB(windTexture, coefficient.x, lon, lat3, lev);\n\n    return oneDimensionInterpolation(coefficient.y, b0, b1, b2, b3);\n}\n\nvec3 bicubic(vec3 lonLatLev) {\n    // https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm\n    float u = interpolateOneTexture(U, lonLatLev);\n    float v = interpolateOneTexture(V, lonLatLev);\n    float w = 0.0;\n    return vec3(u, v, w);\n}\n\nvoid main() {\n    // texture coordinate must be normalized\n    vec3 lonLatLev = texture2D(currentParticlesPosition, v_textureCoordinates).rgb;\n    vec3 windVector = bicubic(lonLatLev);\n    gl_FragColor = vec4(windVector, 0.0);\n}"; // eslint-disable-line

  var updateSpeed_frag = "uniform sampler2D currentParticlesSpeed; // (u, v, w, normalization)\nuniform sampler2D particlesWind;\n\n// used to calculate the wind norm\nuniform vec2 uSpeedRange; // (min, max);\nuniform vec2 vSpeedRange;\nuniform float pixelSize;\nuniform float speedFactor;\n\nvarying vec2 v_textureCoordinates;\n\nfloat calculateWindNorm(vec3 speed) {\n    vec3 percent = vec3(0.0);\n    percent.x = (speed.x - uSpeedRange.x) / (uSpeedRange.y - uSpeedRange.x);\n    percent.y = (speed.y - vSpeedRange.x) / (vSpeedRange.y - vSpeedRange.x);\n    float normalization = length(percent);\n\n    return normalization;\n}\n\nvoid main() {\n    // texture coordinate must be normalized\n    vec3 currentSpeed = texture2D(currentParticlesSpeed, v_textureCoordinates).rgb;\n    vec3 windVector = texture2D(particlesWind, v_textureCoordinates).rgb;\n\n    vec4 nextSpeed = vec4(speedFactor * pixelSize * windVector, calculateWindNorm(windVector));\n    gl_FragColor = nextSpeed;\n}"; // eslint-disable-line

  var updatePosition_frag = "uniform sampler2D currentParticlesPosition; // (lon, lat, lev)\nuniform sampler2D currentParticlesSpeed; // (u, v, w, normalization)\n\nvarying vec2 v_textureCoordinates;\n\nvec2 lengthOfLonLat(vec3 lonLatLev) {\n    // unit conversion: meters -> longitude latitude degrees\n    // see https://en.wikipedia.org/wiki/Geographic_coordinate_system#Length_of_a_degree for detail\n\n    // Calculate the length of a degree of latitude and longitude in meters\n    float latitude = radians(lonLatLev.y);\n\n    float term1 = 111132.92;\n    float term2 = 559.82 * cos(2.0 * latitude);\n    float term3 = 1.175 * cos(4.0 * latitude);\n    float term4 = 0.0023 * cos(6.0 * latitude);\n    float latLength = term1 - term2 + term3 - term4;\n\n    float term5 = 111412.84 * cos(latitude);\n    float term6 = 93.5 * cos(3.0 * latitude);\n    float term7 = 0.118 * cos(5.0 * latitude);\n    float longLength = term5 - term6 + term7;\n\n    return vec2(longLength, latLength);\n}\n\nvoid updatePosition(vec3 lonLatLev, vec3 speed) {\n    vec2 lonLatLength = lengthOfLonLat(lonLatLev);\n    float u = speed.x / lonLatLength.x;\n    float v = speed.y / lonLatLength.y;\n    float w = 0.0;\n    vec3 windVectorInLonLatLev = vec3(u, v, w);\n\n    vec3 nextParticle = lonLatLev + windVectorInLonLatLev;\n\n    gl_FragColor = vec4(nextParticle, 0.0);\n}\n\nvoid main() {\n    // texture coordinate must be normalized\n    vec3 lonLatLev = texture2D(currentParticlesPosition, v_textureCoordinates).rgb;\n    vec3 speed = texture2D(currentParticlesSpeed, v_textureCoordinates).rgb;\n\n    updatePosition(lonLatLev, speed);\n}"; // eslint-disable-line

  var postProcessingPosition_frag = "uniform sampler2D nextParticlesPosition;\nuniform sampler2D nextParticlesSpeed; // (u, v, w, normalization)\n\n// range (min, max)\nuniform vec2 lonRange;\nuniform vec2 latRange;\n\nuniform float randomCoefficient; // use to improve the pseudo-random generator\nuniform float dropRate; // drop rate is a chance a particle will restart at random position to avoid degeneration\nuniform float dropRateBump;\n\nvarying vec2 v_textureCoordinates;\n\n// pseudo-random generator\nconst vec3 randomConstants = vec3(12.9898, 78.233, 4375.85453);\nconst vec2 normalRange = vec2(0.0, 1.0);\nfloat rand(vec2 seed, vec2 range) {\n    vec2 randomSeed = randomCoefficient * seed;\n    float temp = dot(randomConstants.xy, randomSeed);\n    temp = fract(sin(temp) * (randomConstants.z + temp));\n    return temp * (range.y - range.x) + range.x;\n}\n\nvec3 generateRandomParticle(vec2 seed, float lev) {\n    // ensure the longitude is in [0, 360]\n    float randomLon = mod(rand(seed, lonRange), 360.0);\n    float randomLat = rand(-seed, latRange);\n\n    return vec3(randomLon, randomLat, lev);\n}\n\nbool particleOutbound(vec3 particle) {\n    return particle.y < -90.0 || particle.y > 90.0;\n}\n\nvoid main() {\n    vec3 nextParticle = texture2D(nextParticlesPosition, v_textureCoordinates).rgb;\n    vec4 nextSpeed = texture2D(nextParticlesSpeed, v_textureCoordinates);\n    float particleDropRate = dropRate + dropRateBump * nextSpeed.a;\n\n    vec2 seed1 = nextParticle.xy + v_textureCoordinates;\n    vec2 seed2 = nextSpeed.xy + v_textureCoordinates;\n    vec3 randomParticle = generateRandomParticle(seed1, nextParticle.z);\n    float randomNumber = rand(seed2, normalRange);\n\n    if (randomNumber < particleDropRate || particleOutbound(nextParticle)) {\n        gl_FragColor = vec4(randomParticle, 1.0); // 1.0 means this is a random particle\n    } else {\n        gl_FragColor = vec4(nextParticle, 0.0);\n    }\n}"; // eslint-disable-line

  var postProcessingSpeed_frag = "uniform sampler2D postProcessingPosition;\nuniform sampler2D nextParticlesSpeed;\n\nvarying vec2 v_textureCoordinates;\n\nvoid main() {\n    vec4 randomParticle = texture2D(postProcessingPosition, v_textureCoordinates);\n    vec4 particleSpeed = texture2D(nextParticlesSpeed, v_textureCoordinates);\n\n    if (randomParticle.a > 0.0) {\n        gl_FragColor = vec4(0.0);\n    } else {\n        gl_FragColor = particleSpeed;\n    }\n}"; // eslint-disable-line

  const Cesium$3 = mars3d__namespace.Cesium;

  class ParticlesComputing {
    constructor(context, data, options, viewerParameters) {
      this.data = data;

      this.createWindTextures(context, data);
      this.createParticlesTextures(context, options, viewerParameters);
      this.createComputingPrimitives(data, options, viewerParameters);
    }

    createWindTextures(context, data) {
      const windTextureOptions = {
        context: context,
        width: data.dimensions.lon,
        height: data.dimensions.lat * (data.dimensions.lev || 1),
        pixelFormat: Cesium$3.PixelFormat.LUMINANCE,
        pixelDatatype: Cesium$3.PixelDatatype.FLOAT,
        flipY: false,
        sampler: new Cesium$3.Sampler({
          // the values of texture will not be interpolated
          minificationFilter: Cesium$3.TextureMinificationFilter.NEAREST,
          magnificationFilter: Cesium$3.TextureMagnificationFilter.NEAREST
        })
      };

      this.windTextures = {
        U: Util.createTexture(windTextureOptions, data.U.array),
        V: Util.createTexture(windTextureOptions, data.V.array)
      };
    }

    createParticlesTextures(context, options, viewerParameters) {
      const particlesTextureOptions = {
        context: context,
        width: options.particlesTextureSize,
        height: options.particlesTextureSize,
        pixelFormat: Cesium$3.PixelFormat.RGBA,
        pixelDatatype: Cesium$3.PixelDatatype.FLOAT,
        flipY: false,
        sampler: new Cesium$3.Sampler({
          // the values of texture will not be interpolated
          minificationFilter: Cesium$3.TextureMinificationFilter.NEAREST,
          magnificationFilter: Cesium$3.TextureMagnificationFilter.NEAREST
        })
      };

      const particlesArray = this.randomizeParticles(options.maxParticles, viewerParameters);
      const zeroArray = new Float32Array(4 * options.maxParticles).fill(0);

      this.particlesTextures = {
        particlesWind: Util.createTexture(particlesTextureOptions),

        currentParticlesPosition: Util.createTexture(particlesTextureOptions, particlesArray),
        nextParticlesPosition: Util.createTexture(particlesTextureOptions, particlesArray),

        currentParticlesSpeed: Util.createTexture(particlesTextureOptions, zeroArray),
        nextParticlesSpeed: Util.createTexture(particlesTextureOptions, zeroArray),

        postProcessingPosition: Util.createTexture(particlesTextureOptions, particlesArray),
        postProcessingSpeed: Util.createTexture(particlesTextureOptions, zeroArray)
      };
    }

    randomizeParticles(maxParticles, viewerParameters) {
      const array = new Float32Array(4 * maxParticles);
      for (let i = 0; i < maxParticles; i++) {
        array[4 * i] = Cesium$3.Math.randomBetween(viewerParameters.lonRange.x, viewerParameters.lonRange.y);
        array[4 * i + 1] = Cesium$3.Math.randomBetween(viewerParameters.latRange.x, viewerParameters.latRange.y);
        array[4 * i + 2] = Cesium$3.Math.randomBetween(this.data.lev.min, this.data.lev.max);
        array[4 * i + 3] = 0.0;
      }
      return array
    }

    destroyParticlesTextures() {
      Object.keys(this.particlesTextures).forEach((key) => {
        this.particlesTextures[key].destroy();
      });
    }

    createComputingPrimitives(data, options, viewerParameters) {
      const dimension = new Cesium$3.Cartesian3(data.dimensions.lon, data.dimensions.lat, data.dimensions.lev);
      const minimum = new Cesium$3.Cartesian3(data.lon.min, data.lat.min, data.lev.min);
      const maximum = new Cesium$3.Cartesian3(data.lon.max, data.lat.max, data.lev.max);
      const interval = new Cesium$3.Cartesian3(
        (maximum.x - minimum.x) / (dimension.x - 1),
        (maximum.y - minimum.y) / (dimension.y - 1),
        dimension.z > 1 ? (maximum.z - minimum.z) / (dimension.z - 1) : 1.0
      );
      const uSpeedRange = new Cesium$3.Cartesian2(data.U.min, data.U.max);
      const vSpeedRange = new Cesium$3.Cartesian2(data.V.min, data.V.max);

      const that = this;

      this.primitives = {
        getWind: new CustomPrimitive({
          commandType: "Compute",
          uniformMap: {
            U: function () {
              return that.windTextures.U
            },
            V: function () {
              return that.windTextures.V
            },
            currentParticlesPosition: function () {
              return that.particlesTextures.currentParticlesPosition
            },
            dimension: function () {
              return dimension
            },
            minimum: function () {
              return minimum
            },
            maximum: function () {
              return maximum
            },
            interval: function () {
              return interval
            }
          },
          fragmentShaderSource: new Cesium$3.ShaderSource({
            sources: [getWind_frag]
          }),
          outputTexture: this.particlesTextures.particlesWind,
          preExecute: function () {
            // keep the outputTexture up to date
            that.primitives.getWind.commandToExecute.outputTexture = that.particlesTextures.particlesWind;
          }
        }),

        updateSpeed: new CustomPrimitive({
          commandType: "Compute",
          uniformMap: {
            currentParticlesSpeed: function () {
              return that.particlesTextures.currentParticlesSpeed
            },
            particlesWind: function () {
              return that.particlesTextures.particlesWind
            },
            uSpeedRange: function () {
              return uSpeedRange
            },
            vSpeedRange: function () {
              return vSpeedRange
            },
            pixelSize: function () {
              return viewerParameters.pixelSize
            },
            speedFactor: function () {
              return options.speedFactor
            }
          },
          fragmentShaderSource: new Cesium$3.ShaderSource({
            sources: [updateSpeed_frag]
          }),
          outputTexture: this.particlesTextures.nextParticlesSpeed,
          preExecute: function () {
            // swap textures before binding
            const temp = that.particlesTextures.currentParticlesSpeed;
            that.particlesTextures.currentParticlesSpeed = that.particlesTextures.postProcessingSpeed;
            that.particlesTextures.postProcessingSpeed = temp;

            // keep the outputTexture up to date
            that.primitives.updateSpeed.commandToExecute.outputTexture = that.particlesTextures.nextParticlesSpeed;
          }
        }),

        updatePosition: new CustomPrimitive({
          commandType: "Compute",
          uniformMap: {
            currentParticlesPosition: function () {
              return that.particlesTextures.currentParticlesPosition
            },
            currentParticlesSpeed: function () {
              return that.particlesTextures.currentParticlesSpeed
            }
          },
          fragmentShaderSource: new Cesium$3.ShaderSource({
            sources: [updatePosition_frag]
          }),
          outputTexture: this.particlesTextures.nextParticlesPosition,
          preExecute: function () {
            // swap textures before binding
            const temp = that.particlesTextures.currentParticlesPosition;
            that.particlesTextures.currentParticlesPosition = that.particlesTextures.postProcessingPosition;
            that.particlesTextures.postProcessingPosition = temp;

            // keep the outputTexture up to date
            that.primitives.updatePosition.commandToExecute.outputTexture = that.particlesTextures.nextParticlesPosition;
          }
        }),

        postProcessingPosition: new CustomPrimitive({
          commandType: "Compute",
          uniformMap: {
            nextParticlesPosition: function () {
              return that.particlesTextures.nextParticlesPosition
            },
            nextParticlesSpeed: function () {
              return that.particlesTextures.nextParticlesSpeed
            },
            lonRange: function () {
              return viewerParameters.lonRange
            },
            latRange: function () {
              return viewerParameters.latRange
            },
            randomCoefficient: function () {
              const randomCoefficient = Math.random();
              return randomCoefficient
            },
            dropRate: function () {
              return options.dropRate
            },
            dropRateBump: function () {
              return options.dropRateBump
            }
          },
          fragmentShaderSource: new Cesium$3.ShaderSource({
            sources: [postProcessingPosition_frag]
          }),
          outputTexture: this.particlesTextures.postProcessingPosition,
          preExecute: function () {
            // keep the outputTexture up to date
            that.primitives.postProcessingPosition.commandToExecute.outputTexture = that.particlesTextures.postProcessingPosition;
          }
        }),

        postProcessingSpeed: new CustomPrimitive({
          commandType: "Compute",
          uniformMap: {
            postProcessingPosition: function () {
              return that.particlesTextures.postProcessingPosition
            },
            nextParticlesSpeed: function () {
              return that.particlesTextures.nextParticlesSpeed
            }
          },
          fragmentShaderSource: new Cesium$3.ShaderSource({
            sources: [postProcessingSpeed_frag]
          }),
          outputTexture: this.particlesTextures.postProcessingSpeed,
          preExecute: function () {
            // keep the outputTexture up to date
            that.primitives.postProcessingSpeed.commandToExecute.outputTexture = that.particlesTextures.postProcessingSpeed;
          }
        })
      };
    }
  }

  const Cesium$2 = mars3d__namespace.Cesium;

  class ParticleSystem {
    constructor(context, data, options, viewerParameters) {
      this.context = context;

      data = { ...data };

      // 兼容不同格式数据
      if (data.udata && data.vdata) {
        data.dimensions = {};
        data.dimensions.lon = data.cols;
        data.dimensions.lat = data.rows;
        data.dimensions.lev = data.lev || 1;

        data.lon = {};
        data.lon.min = data.xmin;
        data.lon.max = data.xmax;

        data.lat = {};
        data.lat.min = data.ymin;
        data.lat.max = data.ymax;

        data.lev = {};
        data.lev.min = data.levmin ?? 1;
        data.lev.max = data.levmax ?? 1;

        data.U = {};
        data.U.array = new Float32Array(data.udata);
        data.U.min = data.umin ?? Math.min(...data.udata);
        data.U.max = data.umax ?? Math.max(...data.udata);

        data.V = {};
        data.V.array = new Float32Array(data.vdata);
        data.V.min = data.vmin ?? Math.min(...data.vdata);
        data.V.max = data.vmax ?? Math.max(...data.vdata);
      }
      this.data = data;

      this.options = options;
      this.viewerParameters = viewerParameters;

      this.particlesComputing = new ParticlesComputing(this.context, this.data, this.options, this.viewerParameters);
      this.particlesRendering = new ParticlesRendering(this.context, this.data, this.options, this.viewerParameters, this.particlesComputing);
    }

    canvasResize(context) {
      this.particlesComputing.destroyParticlesTextures();
      Object.keys(this.particlesComputing.windTextures).forEach((key) => {
        this.particlesComputing.windTextures[key].destroy();
      });

      this.particlesRendering.textures.colorTable.destroy();
      Object.keys(this.particlesRendering.framebuffers).forEach((key) => {
        this.particlesRendering.framebuffers[key].destroy();
      });

      this.context = context;
      this.particlesComputing = new ParticlesComputing(this.context, this.data, this.options, this.viewerParameters);
      this.particlesRendering = new ParticlesRendering(this.context, this.data, this.options, this.viewerParameters, this.particlesComputing);
    }

    clearFramebuffers() {
      const clearCommand = new Cesium$2.ClearCommand({
        color: new Cesium$2.Color(0.0, 0.0, 0.0, 0.0),
        depth: 1.0,
        framebuffer: undefined,
        pass: Cesium$2.Pass.OPAQUE
      });

      Object.keys(this.particlesRendering.framebuffers).forEach((key) => {
        clearCommand.framebuffer = this.particlesRendering.framebuffers[key];
        clearCommand.execute(this.context);
      });
    }

    refreshParticles(maxParticlesChanged) {
      this.clearFramebuffers();

      this.particlesComputing.destroyParticlesTextures();
      this.particlesComputing.createParticlesTextures(this.context, this.options, this.viewerParameters);

      if (maxParticlesChanged) {
        const geometry = this.particlesRendering.createSegmentsGeometry(this.options);
        this.particlesRendering.primitives.segments.geometry = geometry;
        const vertexArray = Cesium$2.VertexArray.fromGeometry({
          context: this.context,
          geometry: geometry,
          attributeLocations: this.particlesRendering.primitives.segments.attributeLocations,
          bufferUsage: Cesium$2.BufferUsage.STATIC_DRAW
        });
        this.particlesRendering.primitives.segments.commandToExecute.vertexArray = vertexArray;
      }
    }

    setOptions(options) {
      let maxParticlesChanged = false;
      if (this.options.maxParticles !== options.maxParticles) {
        maxParticlesChanged = true;
      }

      Object.keys(options).forEach((key) => {
        this.options[key] = options[key];
      });

      this.refreshParticles(maxParticlesChanged);
    }

    applyViewerParameters(viewerParameters) {
      Object.keys(viewerParameters).forEach((key) => {
        this.viewerParameters[key] = viewerParameters[key];
      });

      this.refreshParticles(false);
    }

    destroy() {
      clearTimeout(this.canrefresh);

      this.particlesComputing.destroyParticlesTextures();
      Object.keys(this.particlesComputing.windTextures).forEach((key) => {
        this.particlesComputing.windTextures[key].destroy();
      });

      this.particlesRendering.textures.colorTable.destroy();
      Object.keys(this.particlesRendering.framebuffers).forEach((key) => {
        this.particlesRendering.framebuffers[key].destroy();
      });

      // 删除所有绑定的数据
      for (const i in this) {
        delete this[i];
      }
    }
  }

  const Cesium$1 = mars3d__namespace.Cesium;

  const BaseLayer$1 = mars3d__namespace.layer.BaseLayer;

  /**
   * 风场图层， data数据结构
   *
   * @typedef {Object} WindLayer.DataOptions
   *
   * @property {Number} rows 行总数
   * @property {Number} cols 列总数
   * @property {Number} xmin 最小经度（度数，-180-180）
   * @property {Number} xmax 最大经度（度数，-180-180）
   * @property {Number} ymin 最小纬度（度数，-90-90）
   * @property {Number} ymax 最大纬度（度数，-90-90）
   * @property {Number[]} udata U值一维数组, 数组长度应该是 rows*cols。
   * @property {Number} [umin] 最小U值
   * @property {Number} [umax] 最大U值
   * @property {Number[]} vdata V值一维数组, 数组长度应该是 rows*cols。
   * @property {Number} [vmin] 最小v值
   * @property {Number} [vmax] 最大v值
   */

  const DEF_OPTIONS = {
    particlesNumber: 4096,
    fixedHeight: 0.0,
    fadeOpacity: 0.996,
    dropRate: 0.003,
    dropRateBump: 0.01,
    speedFactor: 0.5,
    lineWidth: 2.0,
    colors: ["rgb(206,255,255)"]
  };

  /**
   * 风场图层，基于粒子实现，
   * 【需要引入 mars3d-wind 插件库】
   *
   * @param {Object} [options] 参数对象，包括以下：
   * @param {WindLayer.DataOptions} [options.data] 风场数据
   * @param {Number} [options.particlesNumber =4096] 初始粒子总数
   * @param {Number} [options.fadeOpacity =0.996] 消失不透明度
   * @param {Number} [options.dropRate =0.003] 下降率
   * @param {Number} [options.dropRateBump =0.01] 下降速度
   * @param {Number} [options.speedFactor = 0.5] 速度系数
   * @param {Number} [options.lineWidth =2.0] 线宽度
   * @param {Number} [options.fixedHeight =0] 粒子点的固定的海拔高度
   * @param {String[]} [options.colors=["rgb(206,255,255)"]] 颜色色带数组
   *
   * @param {String|Number} [options.id = createGuid()] 图层id标识
   * @param {String|Number} [options.pid = -1] 图层父级的id，一般图层管理中使用
   * @param {String} [options.name = ''] 图层名称
   * @param {Boolean} [options.show = true] 图层是否显示
   * @param {BaseClass|Boolean} [options.eventParent]  指定的事件冒泡对象，默认为map对象，false时不冒泡
   * @param {Object} [options.center] 图层自定义定位视角 {@link Map#setCameraView}
   * @param {Number} options.center.lng 经度值, 180 - 180
   * @param {Number} options.center.lat 纬度值, -90 - 90
   * @param {Number} [options.center.alt] 高度值
   * @param {Number} [options.center.heading] 方向角度值，绕垂直于地心的轴旋转角度, 0至360
   * @param {Number} [options.center.pitch] 俯仰角度值，绕纬度线旋转角度, -90至90
   * @param {Number} [options.center.roll] 翻滚角度值，绕经度线旋转角度, -90至90
   * @param {Boolean} [options.flyTo] 加载完成数据后是否自动飞行定位到数据所在的区域。
   * @export
   * @class WindLayer
   * @extends {BaseLayer}
   */
  class WindLayer extends BaseLayer$1 {
    constructor(options = {}) {
      options = { ...DEF_OPTIONS, ...options };
      super(options);

      this._setOptionsHook(options);
    }

    /**
     * 存放风场粒子对象的容器
     * @type {Cesium.PrimitiveCollection}
     * @readonly
     */
    get layer() {
      return this.primitives
    }

    /**
     * 风场数据，数据结构见类的构造方法说明
     * @type {WindLayer.DataOptions}
     */
    get data() {
      return this._data
    }

    set data(value) {
      this.setData(value);
    }

    /**
     * 颜色色带数组
     * @type {String[]}
     */
    get colors() {
      return this.options.colors
    }

    set colors(value) {
      this.options.colors = value;

      if (this.particleSystem) {
        this.particleSystem.setOptions({ colors: value });
      }
      this.resize();
    }

    /**
     * 对象添加到地图前创建一些对象的钩子方法，
     * 只会调用一次
     * @return {void}  无
     * @private
     */
    _mountedHook() {}

    /**
     * 对象添加到地图上的创建钩子方法，
     * 每次add时都会调用
     * @return {void}  无
     * @private
     */
    _addedHook() {
      this.scene = this._map.scene;
      this.camera = this._map.camera;

      this.primitives = new Cesium$1.PrimitiveCollection();
      this._map.scene.primitives.add(this.primitives);

      this.viewerParameters = {
        lonRange: new Cesium$1.Cartesian2(),
        latRange: new Cesium$1.Cartesian2(),
        pixelSize: 0.0
      };
      // use a smaller earth radius to make sure distance to camera > 0
      this.globeBoundingSphere = new Cesium$1.BoundingSphere(Cesium$1.Cartesian3.ZERO, 0.99 * 6378137.0);
      this.updateViewerParameters();

      // 添加 resize 绑定事件
      window.addEventListener("resize", this.resize.bind(this), false);

      // 鼠标滚动、旋转后 需要重新生成风场
      this.mouse_down = false;
      this.mouse_move = false;

      this._map.on(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
      this._map.on(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
      this._map.on(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
      this._map.on(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);

      if (this._data) {
        this.setData(this._data);
      }
    }

    /**
     * 对象从地图上移除的创建钩子方法，
     * 每次remove时都会调用
     * @return {void}  无
     * @private
     */
    _removedHook() {
      window.removeEventListener("resize", this.resize);
      this._map.off(mars3d__namespace.EventType.preRender, this._onMap_preRenderEvent, this);

      this._map.off(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
      this._map.off(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
      this._map.off(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
      this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);

      this.primitives.removeAll();
      this._map.scene.primitives.remove(this.primitives);
    }

    // 更新canvas的高宽
    resize() {
      if (!this.show || !this.particleSystem) {
        return
      }

      this.primitives.show = false;
      this.primitives.removeAll();

      this._map.once(mars3d__namespace.EventType.preRender, this._onMap_preRenderEvent, this);
    }

    _onMap_preRenderEvent(event) {
      this.particleSystem.canvasResize(this.scene.context);
      this.addPrimitives();
      this.primitives.show = true;
    }

    // 鼠标交互
    _onMapWhellEvent(event) {
      clearTimeout(this.refreshTimer);
      if (!this.show || !this.particleSystem) {
        return
      }

      this.primitives.show = false;
      this.refreshTimer = setTimeout(() => {
        if (!this.show) {
          return
        }
        this.redraw();
      }, 200);
    }

    _onMouseDownEvent(event) {
      this.mouse_down = true;
    }

    _onMouseMoveEvent(event) {
      if (!this.show || !this.particleSystem) {
        return
      }

      if (this.mouse_down) {
        this.primitives.show = false;
        this.mouse_move = true;
      }
    }

    _onMouseUpEvent(event) {
      if (!this.show || !this.particleSystem) {
        return
      }

      if (this.mouse_down && this.mouse_move) {
        this.redraw();
      }
      this.primitives.show = true;
      this.mouse_down = false;
      this.mouse_move = false;
    }

    redraw() {
      if (!this._map || !this.show) {
        return
      }
      this.updateViewerParameters();
      this.particleSystem.applyViewerParameters(this.viewerParameters);
      this.primitives.show = true;
    }

    /**
     * 设置 风场数据
     * @param {WindLayer.DataOptions} data 风场数据
     * @return {void}  无
     */
    setData(data) {
      this._data = data;

      if (this.particleSystem) {
        this.particleSystem.destroy();
      }

      this.particleSystem = new ParticleSystem(this.scene.context, data, this.getOptions(), this.viewerParameters);
      this.addPrimitives();
    }

    _setOptionsHook(options, newOptions) {
      if (options) {
        for (const key in options) {
          this[key] = options[key];
        }
      }
      if (this.particleSystem) {
        this.particleSystem.setOptions(this.getOptions());
      }
    }

    getOptions() {
      // make sure particlesNumber is exactly the square of particlesTextureSize
      const particlesTextureSize = Math.ceil(Math.sqrt(this.particlesNumber));
      this.particlesNumber = particlesTextureSize * particlesTextureSize;

      return {
        particlesTextureSize: particlesTextureSize,
        maxParticles: this.particlesNumber,
        particleHeight: this.fixedHeight,
        fadeOpacity: this.fadeOpacity,
        dropRate: this.dropRate,
        dropRateBump: this.dropRateBump,
        speedFactor: this.speedFactor,
        lineWidth: this.lineWidth,
        globeLayer: this.globeLayer,
        WMS_URL: this.WMS_URL,
        colors: this.colors
      }
    }

    addPrimitives() {
      // the order of primitives.add() should respect the dependency of primitives
      this.primitives.add(this.particleSystem.particlesComputing.primitives.getWind);
      this.primitives.add(this.particleSystem.particlesComputing.primitives.updateSpeed);
      this.primitives.add(this.particleSystem.particlesComputing.primitives.updatePosition);
      this.primitives.add(this.particleSystem.particlesComputing.primitives.postProcessingPosition);
      this.primitives.add(this.particleSystem.particlesComputing.primitives.postProcessingSpeed);

      this.primitives.add(this.particleSystem.particlesRendering.primitives.segments);
      this.primitives.add(this.particleSystem.particlesRendering.primitives.trails);
      this.primitives.add(this.particleSystem.particlesRendering.primitives.screen);
    }

    updateViewerParameters() {
      let viewRectangle = this.camera.computeViewRectangle(this.scene.globe.ellipsoid);
      if (!viewRectangle) {
        // 非3d模式下会为空
        const extent = this._map.getExtent(); // mars3d扩展的方法
        viewRectangle = Cesium$1.Rectangle.fromDegrees(extent.xmin, extent.ymin, extent.xmax, extent.ymax);
      }

      const lonLatRange = Util.viewRectangleToLonLatRange(viewRectangle);
      this.viewerParameters.lonRange.x = lonLatRange.lon.min;
      this.viewerParameters.lonRange.y = lonLatRange.lon.max;
      this.viewerParameters.latRange.x = lonLatRange.lat.min;
      this.viewerParameters.latRange.y = lonLatRange.lat.max;

      const pixelSize = this.camera.getPixelSize(this.globeBoundingSphere, this.scene.drawingBufferWidth, this.scene.drawingBufferHeight);

      if (pixelSize > 0) {
        this.viewerParameters.pixelSize = pixelSize;
      }
    }
  }

  // 注册下
  mars3d__namespace.LayerUtil.register("wind", WindLayer);

  mars3d__namespace.layer.WindLayer = WindLayer;

  // 粒子对象
  class CanvasParticle {
    //= ========= 构造方法 ==========
    constructor() {
      this.lng = null; // 粒子初始经度
      this.lat = null; // 粒子初始纬度
      // this.x = null;//粒子初始x位置(相对于棋盘网格，比如x方向有360个格，x取值就是0-360，这个是初始化时随机生成的)
      // this.y = null;//粒子初始y位置(同上)
      this.tlng = null; // 粒子下一步将要移动的经度，这个需要计算得来
      this.tlat = null; // 粒子下一步将要移动的y纬度，这个需要计算得来
      this.age = null; // 粒子生命周期计时器，每次-1
      // this.speed = null;//粒子移动速度，可以根据速度渲染不同颜色
    }
  }

  // 棋盘类,根据风场数据生产风场棋盘网格
  // u表示经度方向上的风，u为正，表示西风，从西边吹来的风。
  // v表示纬度方向上的风，v为正，表示南风，从南边吹来的风。
  class CanvasWindField {
    //= ========= 构造方法 ==========
    constructor(data, reverseY) {
      // 行列总数
      this.rows = data.rows;
      this.cols = data.cols;

      // 经纬度边界
      this.xmin = data.xmin;
      this.xmax = data.xmax;
      this.ymin = data.ymin;
      this.ymax = data.ymax;

      this.grid = [];

      const uComponent = data.udata;
      const vComponent = data.vdata;

      let hasGrid = false;
      if (uComponent.length === this.rows && uComponent[0].length === this.cols) {
        hasGrid = true; // 本身是grid方式构建的
      }

      let index = 0;
      let arrRow = null;
      let uv = null;

      for (let row = 0; row < this.rows; row++) {
        arrRow = [];
        for (let col = 0; col < this.cols; col++, index++) {
          if (hasGrid) {
            uv = this._calcUV(uComponent[row][col], vComponent[row][col]);
          } else {
            uv = this._calcUV(uComponent[index], vComponent[index]);
          }

          arrRow.push(uv);
        }
        this.grid.push(arrRow);
      }
      if (reverseY) {
        this.grid.reverse();
      }
      // console.log(this.grid);
    }

    // 根据经纬度，算出棋盘格位置
    toGridXY(lng, lat) {
      const x = ((lng - this.xmin) / (this.xmax - this.xmin)) * (this.cols - 1);
      const y = ((this.ymax - lat) / (this.ymax - this.ymin)) * (this.rows - 1);
      return { x, y }
    }

    // 根据棋盘格获取UV值
    getUVByXY(x, y) {
      if (x < 0 || x >= this.cols || y >= this.rows) {
        return [0, 0, 0]
      }
      const x0 = Math.floor(x);
      const y0 = Math.floor(y);

      if (x0 === x && y0 === y) {
        return this.grid[y][x]
      }

      const x1 = x0 + 1;
      const y1 = y0 + 1;

      const g00 = this.getUVByXY(x0, y0);
      const g10 = this.getUVByXY(x1, y0);
      const g01 = this.getUVByXY(x0, y1);
      const g11 = this.getUVByXY(x1, y1);
      let result = null;
      try {
        result = this._bilinearInterpolation(x - x0, y - y0, g00, g10, g01, g11);
      } catch (e) {
        // eslint-disable-next-line no-console
        console.log(x, y);
      }
      return result
    }

    // 双线性插值算法计算给定节点的速度
    // https://blog.csdn.net/qq_37577735/article/details/80041586
    _bilinearInterpolation(x, y, g00, g10, g01, g11) {
      const rx = 1 - x;
      const ry = 1 - y;
      const a = rx * ry;
      const b = x * ry;
      const c = rx * y;
      const d = x * y;
      const u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d;
      const v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d;
      return this._calcUV(u, v)
    }

    _calcUV(u, v) {
      // u表示经度方向上的风，u为正，表示西风，从西边吹来的风。
      // v表示纬度方向上的风，v为正，表示南风，从南边吹来的风。
      return [+u, +v, Math.sqrt(u * u + v * v)] // u, v，风速
    }

    // 根据经纬度格获取UV值
    getUVByPoint(lng, lat) {
      if (!this.isInExtent(lng, lat)) {
        return null
      }
      const gridpos = this.toGridXY(lng, lat);
      const uv = this.getUVByXY(gridpos.x, gridpos.y);
      return uv
    }

    // 粒子是否在地图范围内
    isInExtent(lng, lat) {
      if (lng >= this.xmin && lng <= this.xmax && lat >= this.ymin && lat <= this.ymax) {
        return true
      } else {
        return false
      }
    }

    getRandomLatLng() {
      const lng = fRandomByfloat(this.xmin, this.xmax);
      const lat = fRandomByfloat(this.ymin, this.ymax);
      return { lat, lng }
    }
  }

  // 随机数生成器（小数）
  function fRandomByfloat(under, over) {
    return under + Math.random() * (over - under)
  }

  const Cesium = mars3d__namespace.Cesium;

  const BaseLayer = mars3d__namespace.layer.BaseLayer;

  /**
   * Canvas风场图层， data数据结构
   *
   * @typedef {Object} CanvasWindLayer.DataOptions
   *
   * @property {Number} rows 行总数
   * @property {Number} cols 列总数
   * @property {Number} xmin 最小经度（度数，-180-180）
   * @property {Number} xmax 最大经度（度数，-180-180）
   * @property {Number} ymin 最小纬度（度数，-90-90）
   * @property {Number} ymax 最大纬度（度数，-90-90）
   * @property {Number[]|Array[]} udata U值一维数组, 数组长度应该是 rows*cols 。也支持按rows行cols列构建好的二维数组。
   * @property {Number[]|Array[]} vdata V值一维数组, 数组长度应该是 rows*cols 。也支持按rows行cols列构建好的二维数组。
   *
   */

  /**
   * Canvas风场图层，
   * 基于Canvas绘制，【需要引入 mars3d-wind 插件库】
   *
   * @param {Object} [options] 参数对象，包括以下：
   * @param {CanvasWindLayer.DataOptions} [options.data] 风场数据
   * @param {Number} [options.speedRate =50] 风前进速率，意思是将当前风场横向纵向分成100份，再乘以风速就能得到移动位置，无论地图缩放到哪一级别都是一样的速度，可以用该数值控制线流动的快慢，值越大，越慢，
   * @param {Number} [options.particlesNumber =4096] 初始粒子总数
   * @param {Number} [options.maxAge =120] 每个粒子的最大生存周期
   * @param {Number} [options.frameRate =10] 每秒刷新次数，因为requestAnimationFrame固定每秒60次的渲染，所以如果不想这么快，就把该数值调小一些
   * @param {String} [options.color ='#ffffff'] 线颜色
   * @param {Number} [options.lineWidth =1] 线宽度
   * @param {Number} [options.fixedHeight =0] 点的固定的海拔高度
   *
   * @param {Boolean} [options.reverseY =false] 是否翻转纬度数组顺序，正常数据是从北往南的（纬度从大到小），如果反向时请传reverseY为true
   * @param {Boolean} [options.pointerEvents=false] 图层是否可以进行鼠标交互，为false时可以穿透操作及缩放地图
   *
   *
   * @param {String|Number} [options.id = createGuid()] 图层id标识
   * @param {String|Number} [options.pid = -1] 图层父级的id，一般图层管理中使用
   * @param {String} [options.name = ''] 图层名称
   * @param {Boolean} [options.show = true] 图层是否显示
   * @param {BaseClass|Boolean} [options.eventParent]  指定的事件冒泡对象，默认为map对象，false时不冒泡
   * @param {Object} [options.center] 图层自定义定位视角 {@link Map#setCameraView}
   * @param {Number} options.center.lng 经度值, 180 - 180
   * @param {Number} options.center.lat 纬度值, -90 - 90
   * @param {Number} [options.center.alt] 高度值
   * @param {Number} [options.center.heading] 方向角度值，绕垂直于地心的轴旋转角度, 0至360
   * @param {Number} [options.center.pitch] 俯仰角度值，绕纬度线旋转角度, -90至90
   * @param {Number} [options.center.roll] 翻滚角度值，绕经度线旋转角度, -90至90
   * @param {Boolean} [options.flyTo] 加载完成数据后是否自动飞行定位到数据所在的区域。
   *
   * @export
   * @class CanvasWindLayer
   * @extends {BaseLayer}
   */
  class CanvasWindLayer extends BaseLayer {
    //= ========= 构造方法 ==========
    constructor(options = {}) {
      super(options);

      this._setOptionsHook(options);

      /**
       * 图层对应的Canvas对象
       * @type {HTMLCanvasElement}
       * @readonly
       */
      this.canvas = null;
    }

    _setOptionsHook(options, newOptions) {
      this.calc_speedRate = [0, 0]; // 根据speedRate参数计算经纬度步进长度
      this.particles = [];
      this.speedRate = options.speedRate || 50;
      this._particlesNumber = options.particlesNumber || 4096;
      this._maxAge = options.maxAge || 120;
      this.frameTime = 1000 / (options.frameRate || 10);
      this._pointerEvents = this.options.pointerEvents ?? false;

      /**
       * 线颜色
       * @type {String}
       */
      this.color = options.color || "#ffffff";

      /**
       * 线宽度
       * @type {Number}
       */
      this.lineWidth = options.lineWidth || 1;

      /**
       * 点的固定的海拔高度
       * @type {Number}
       */
      this.fixedHeight = options.fixedHeight ?? 0;

      /**
       * 是否翻转纬度数组顺序，正常数据是从北往南的（纬度从大到小），如果反向时请传reverseY为true
       * @type {Boolean}
       */
      this.reverseY = options.reverseY ?? false;
    }

    /**
     * 图层对应的Canvas对象
     * @type {HTMLCanvasElement}
     * @readonly
     */
    get layer() {
      return this.canvas
    }

    /**
     * Canvas对象宽度（单位：像素）
     * @type {Number}
     * @readonly
     */
    get canvasWidth() {
      return this._map.scene.canvas.clientWidth
    }

    /**
     * Canvas对象高度（单位：像素）
     * @type {Number}
     * @readonly
     */
    get canvasHeight() {
      return this._map.scene.canvas.clientHeight
    }

    /**
     * 图层是否可以鼠标交互，为false时可以穿透操作及缩放地图
     * @type {Boolean}
     */
    get pointerEvents() {
      return this._pointerEvents
    }

    set pointerEvents(value) {
      this._pointerEvents = value;
      if (!this.canvas) {
        return
      }

      if (value) {
        this.canvas.style["pointer-events"] = "all";
      } else {
        /* 加上这个css后鼠标可以穿透，但是无法触发单击等鼠标事件 */
        this.canvas.style["pointer-events"] = "none";
      }
    }

    /**
     * 风前进速率，意思是将当前风场横向纵向分成100份，再乘以风速就能得到移动位置，无论地图缩放到哪一级别都是一样的速度，可以用该数值控制线流动的快慢，值越大，越慢，
     * @type {Number}
     */
    get speedRate() {
      return this._speedRate
    }

    set speedRate(value) {
      this._speedRate = (100 - (value > 99 ? 99 : value)) * 100;
      this._calcStep();
    }

    /**
     * 初始粒子总数
     * @type {Number}
     */
    get particlesNumber() {
      return this._particlesNumber
    }

    set particlesNumber(value) {
      this._particlesNumber = value;

      // 不能随时刷新，需要隔一段时间刷新，避免卡顿
      clearTimeout(this._canrefresh);
      this._canrefresh = setTimeout(() => {
        this.redraw();
      }, 500);
    }

    /**
     * 每个粒子的最大生存周期
     * @type {Number}
     */
    get maxAge() {
      return this._maxAge
    }

    set maxAge(value) {
      this._maxAge = value;

      // 不能随时刷新，需要隔一段时间刷新，避免卡顿
      clearTimeout(this._canrefresh);
      this._canrefresh = setTimeout(() => {
        this.redraw();
      }, 500);
    }

    /**
     * 风场数据，数据结构见类的构造方法说明
     * @type {CanvasWindLayer.DataOptions}
     */
    get data() {
      return this.windData
    }

    set data(value) {
      this.setData(value);
    }

    _showHook(val) {
      if (val) {
        this._addedHook();
      } else {
        if (this.windData) {
          this.options.data = this.windData;
        }
        this._removedHook();
      }
    }

    /**
     * 对象添加到地图前创建一些对象的钩子方法，
     * 只会调用一次
     * @return {void}  无
     * @private
     */
    _mountedHook() {}

    /**
     * 对象添加到地图上的创建钩子方法，
     * 每次add时都会调用
     * @return {void}  无
     * @private
     */
    _addedHook() {
      this.canvas = this._createCanvas();
      this.canvasContext = this.canvas.getContext("2d"); // canvas上下文

      this.bindEvent();

      if (this.options.data) {
        this.setData(this.options.data);
      }
    }

    /**
     * 对象从地图上移除的创建钩子方法，
     * 每次remove时都会调用
     * @return {void}  无
     * @private
     */
    _removedHook() {
      this.clear();
      this.unbindEvent();

      if (this.canvas) {
        this._map.container.removeChild(this.canvas);
        delete this.canvas;
      }
    }

    // canvas
    _createCanvas() {
      const windContainer = window.document.createElement("canvas");
      windContainer.style.position = "absolute";
      windContainer.style.top = "0px";
      windContainer.style.left = "0px";
      windContainer.style.width = "100%";
      windContainer.style.height = "100%";
      windContainer.style.pointerEvents = this._pointerEvents ? "auto" : "none"; // auto时可以交互，但是没法放大地球， none 没法交互
      windContainer.style.zIndex = 10;

      windContainer.setAttribute("id", "canvasWindy");
      windContainer.setAttribute("class", "canvasWindy");
      this._map.container.appendChild(windContainer);

      const scene = this._map.scene;
      windContainer.width = scene.canvas.clientWidth;
      windContainer.height = scene.canvas.clientHeight;

      return windContainer
    }

    // 更新canvas的高宽
    resize() {
      if (this.canvas) {
        this.canvas.width = this.canvasWidth;
        this.canvas.height = this.canvasHeight;
      }
    }

    // 事件处理
    bindEvent() {
      const that = this;

      // 更新动画
      let then = Date.now()
      ;(function frame() {
        // animateFrame: requestAnimationFrame事件句柄，用来清除操作
        that.animateFrame = window.requestAnimationFrame(frame);
        const now = Date.now();
        const delta = now - then;
        if (delta > that.frameTime) {
          then = now - (delta % that.frameTime);
          that.update(); // 按帧率执行
        }
      })();

      // 添加 resize 绑定事件
      window.addEventListener("resize", this.resize.bind(this), false);

      // 鼠标滚动、旋转后 需要重新生成风场
      this.mouse_down = false;
      this.mouse_move = false;

      this._map.on(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
      this._map.on(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
      this._map.on(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
    }

    unbindEvent() {
      window.cancelAnimationFrame(this.animateFrame);
      delete this.animateFrame;

      window.removeEventListener("resize", this.resize);

      this._map.off(mars3d__namespace.EventType.wheel, this._onMapWhellEvent, this);
      this._map.off(mars3d__namespace.EventType.mouseDown, this._onMouseDownEvent, this);
      this._map.off(mars3d__namespace.EventType.mouseUp, this._onMouseUpEvent, this);
      this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
    }

    _onMapWhellEvent(event) {
      clearTimeout(this.refreshTimer);
      if (!this.show || !this.canvas) {
        return
      }

      this.canvas.style.visibility = "hidden";
      this.refreshTimer = setTimeout(() => {
        if (!this.show) {
          return
        }
        this.redraw();
        this.canvas.style.visibility = "visible";
      }, 200);
    }

    _onMouseDownEvent(event) {
      this.mouse_down = true;

      this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
      this._map.on(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);
    }

    _onMouseMoveEvent(event) {
      if (!this.show || !this.canvas) {
        return
      }

      if (this.mouse_down) {
        this.canvas.style.visibility = "hidden";
        this.mouse_move = true;
      }
    }

    _onMouseUpEvent(event) {
      if (!this.show || !this.canvas) {
        return
      }
      this._map.off(mars3d__namespace.EventType.mouseMove, this._onMouseMoveEvent, this);

      if (this.mouse_down && this.mouse_move) {
        this.redraw();
      }
      this.canvas.style.visibility = "visible";
      this.mouse_down = false;
      this.mouse_move = false;
    }

    /**
     * 重绘，根据现有参数重新生成风场
     * @return {void}  无
     */
    redraw() {
      if (!this.show || !this.windField) {
        return
      }
      this.particles = [];
      this.drawWind();
    }

    /**
     * 设置 风场数据
     * @param {Object} data 风场数据
     * @return {void}  无
     */
    setData(data) {
      this.clear();
      this.windData = data; // 风场json数据

      // 创建风场网格
      this.windField = new CanvasWindField(this.windData, this.reverseY);
      this.drawWind();
    }

    // 绘制粒子效果处理
    drawWind() {
      // 计算经纬度步进长度
      this._calcStep();

      // 创建风场粒子
      for (let i = 0; i < this.particlesNumber; i++) {
        const particle = this.randomParticle(new CanvasParticle());
        this.particles.push(particle);
      }
      this.canvasContext.fillStyle = "rgba(0, 0, 0, 0.97)";
      this.canvasContext.globalAlpha = 0.6;
      this.update();
    }

    // 计算经纬度步进长度
    _calcStep() {
      if (!this.windField) {
        return
      }

      this.calc_speedRate = [(this.windField.xmax - this.windField.xmin) / this.speedRate, (this.windField.ymax - this.windField.ymin) / this.speedRate];
    }

    update() {
      if (!this.show || this.particles.length <= 0) {
        return
      }

      let nextLng = null;
      let nextLat = null;
      let uv = null;

      this.particles.forEach((particle) => {
        if (particle.age <= 0) {
          this.randomParticle(particle);
        }
        if (particle.age > 0) {
          const tlng = particle.tlng; // 上一次的位置
          const tlat = particle.tlat;

          // u表示经度方向上的风，u为正，表示西风，从西边吹来的风。
          // v表示纬度方向上的风，v为正，表示南风，从南边吹来的风。
          uv = this.windField.getUVByPoint(tlng, tlat);
          if (uv) {
            nextLng = tlng + this.calc_speedRate[0] * uv[0];
            nextLat = tlat + this.calc_speedRate[1] * uv[1];

            particle.lng = tlng;
            particle.lat = tlat;
            particle.tlng = nextLng;
            particle.tlat = nextLat;
            particle.age--;
          } else {
            particle.age = 0;
          }
        }
      });

      this._drawLines();
    }

    // 根据粒子当前所处的位置(棋盘网格位置)，计算经纬度，在根据经纬度返回屏幕坐标
    _tomap(lng, lat, particle) {
      const position = Cesium.Cartesian3.fromDegrees(lng, lat, this.fixedHeight);

      // 判断是否在球的背面
      const scene = this._map.scene;
      if (scene.mode === Cesium.SceneMode.SCENE3D) {
        const occluder = new Cesium.EllipsoidalOccluder(scene.globe.ellipsoid, scene.camera.positionWC);
        const visible = occluder.isPointVisible(position);
        // visible为true说明点在球的正面，否则点在球的背面。
        // 需要注意的是不能用这种方法判断点的可见性，如果球放的比较大，点跑到屏幕外面，它返回的依然为true
        if (!visible) {
          particle.age = 0;
          return null
        }
      }
      // 判断是否在球的背面

      const pos = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this._map.scene, position);
      if (pos) {
        return [pos.x, pos.y]
      } else {
        return null
      }
    }

    _drawLines() {
      const that = this;
      const particles = this.particles;
      this.canvasContext.lineWidth = that.lineWidth;
      // 后绘制的图形和前绘制的图形如果发生遮挡的话，只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分，示例：https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_all
      this.canvasContext.globalCompositeOperation = "destination-in";
      this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
      this.canvasContext.globalCompositeOperation = "lighter"; // 重叠部分的颜色会被重新计算
      this.canvasContext.globalAlpha = 0.9;
      this.canvasContext.beginPath();
      this.canvasContext.strokeStyle = this.color;

      const is2d = this._map.scene.mode !== Cesium.SceneMode.SCENE3D;

      particles.forEach(function (particle) {
        const movetopos = that._tomap(particle.lng, particle.lat, particle);
        const linetopos = that._tomap(particle.tlng, particle.tlat, particle);

        // console.log(movetopos,linetopos);
        if (movetopos != null && linetopos != null) {
          const step = Math.abs(movetopos[0] - linetopos[0]);
          if (is2d && step >= that.canvasWidth) ; else {
            that.canvasContext.moveTo(movetopos[0], movetopos[1]);
            that.canvasContext.lineTo(linetopos[0], linetopos[1]);
          }
        }
      });
      this.canvasContext.stroke();
    }

    // 根据当前风场extent随机生成粒子
    randomParticle(particle) {
      let point, uv;
      for (let i = 0; i < 30; i++) {
        point = this.windField.getRandomLatLng();
        uv = this.windField.getUVByPoint(point.lng, point.lat);

        if (uv && uv[2] > 0) {
          break
        }
      }
      if (!uv) {
        return particle
      }

      const nextLng = point.lng + this.calc_speedRate[0] * uv[0];
      const nextLat = point.lat + this.calc_speedRate[1] * uv[1];

      particle.lng = point.lng;
      particle.lat = point.lat;
      particle.tlng = nextLng;
      particle.tlat = nextLat;
      particle.age = Math.round(Math.random() * this.maxAge); // 每一次生成都不一样
      return particle
    }

    /**
     * 清除数据
     * @return {void}  无
     */
    clear() {
      this.particles = [];
      delete this.windField;
      delete this.windData;
    }
  }

  // 注册下
  mars3d__namespace.LayerUtil.register("canvasWind", CanvasWindLayer);

  mars3d__namespace.layer.CanvasWindLayer = CanvasWindLayer;

  // eslint-disable-next-line no-import-assign
  mars3d__namespace.WindUtil = WindUtil;

  exports.CanvasWindLayer = CanvasWindLayer;
  exports.WindLayer = WindLayer;
  exports.WindUtil = WindUtil;

  Object.defineProperty(exports, '__esModule', { value: true });

}));
