|
|
@@ -17,7 +17,28 @@ import axios from 'axios'
|
|
|
const Cesium = (typeof window !== 'undefined' && window.Cesium) ? window.Cesium : CesiumNS;
|
|
|
|
|
|
// ============================================================
|
|
|
-// 风场纹理双三次插值(Catmull-Rom)—— 供色阶片元着色器使用
|
|
|
+// 风速色阶(CPU 光栅化用)—— 与原片元着色器 speedColor 完全一致
|
|
|
+// 静态色阶层在加载数据时一次性 CPU 光栅化为 PNG,叠加为
|
|
|
+// SingleTileImageryProvider 静态影像,运行时 GPU 0 持续消耗。
|
|
|
+// ============================================================
|
|
|
+const SPEED_STOPS = [
|
|
|
+ [0.05, 0.1, 0.6],
|
|
|
+ [0.0, 0.7, 1.0],
|
|
|
+ [0.2, 1.0, 0.4],
|
|
|
+ [1.0, 0.9, 0.1],
|
|
|
+ [1.0, 0.3, 0.05]
|
|
|
+];
|
|
|
+function speedColorRGB(t) {
|
|
|
+ if (t <= 0) return SPEED_STOPS[0];
|
|
|
+ if (t >= 1) return SPEED_STOPS[4];
|
|
|
+ const i = Math.min(Math.floor(t / 0.25), 3);
|
|
|
+ const f = (t - i * 0.25) / 0.25;
|
|
|
+ const a = SPEED_STOPS[i], b = SPEED_STOPS[i + 1];
|
|
|
+ return [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f, a[2] + (b[2] - a[2]) * f];
|
|
|
+}
|
|
|
+
|
|
|
+// ============================================================
|
|
|
+// 风场纹理双三次插值(Catmull-Rom)—— 供粒子顶点着色器使用
|
|
|
// ============================================================
|
|
|
const WIND_BICUBIC_GLSL = `
|
|
|
uniform vec2 u_windTexSize;
|
|
|
@@ -282,65 +303,6 @@ void main() {
|
|
|
}
|
|
|
`;
|
|
|
|
|
|
-// ============================================================
|
|
|
-// 底层风速底色着色器(细分网格覆盖 region,片元着色器按像素采样风场纹理)
|
|
|
-// GLSL ES 1.00
|
|
|
-// ============================================================
|
|
|
-const COLOR_VS = `
|
|
|
-attribute vec2 a_uv;
|
|
|
-uniform float u_height;
|
|
|
-uniform vec4 u_region;
|
|
|
-varying vec2 v_uv;
|
|
|
-
|
|
|
-vec3 lonLatToECEF(vec2 ll, float h) {
|
|
|
- float lon = radians(ll.x);
|
|
|
- float lat = radians(ll.y);
|
|
|
- float cosLat = cos(lat);
|
|
|
- float sinLat = sin(lat);
|
|
|
- float a = 6378137.0;
|
|
|
- float f = 1.0 / 298.257223563;
|
|
|
- float e2 = f * (2.0 - f);
|
|
|
- float N = a / sqrt(1.0 - e2 * sinLat * sinLat);
|
|
|
- return vec3((N + h) * cosLat * cos(lon), (N + h) * cosLat * sin(lon), (N * (1.0 - e2) + h) * sinLat);
|
|
|
-}
|
|
|
-
|
|
|
-void main() {
|
|
|
- v_uv = a_uv;
|
|
|
- float lon = mix(u_region.x, u_region.z, a_uv.x);
|
|
|
- float lat = mix(u_region.y, u_region.w, a_uv.y);
|
|
|
- gl_Position = czm_viewProjection * vec4(lonLatToECEF(vec2(lon, lat), u_height), 1.0);
|
|
|
-}
|
|
|
-`;
|
|
|
-
|
|
|
-const COLOR_FS = `
|
|
|
-uniform sampler2D u_wind;
|
|
|
-uniform float u_alpha;
|
|
|
-uniform vec4 u_region;
|
|
|
-varying vec2 v_uv;
|
|
|
-
|
|
|
-vec3 speedColor(float t) {
|
|
|
- vec3 c0 = vec3(0.05, 0.1, 0.6);
|
|
|
- vec3 c1 = vec3(0.0, 0.7, 1.0);
|
|
|
- vec3 c2 = vec3(0.2, 1.0, 0.4);
|
|
|
- vec3 c3 = vec3(1.0, 0.9, 0.1);
|
|
|
- vec3 c4 = vec3(1.0, 0.3, 0.05);
|
|
|
- if (t < 0.25) return mix(c0, c1, t / 0.25);
|
|
|
- if (t < 0.5) return mix(c1, c2, (t - 0.25) / 0.25);
|
|
|
- if (t < 0.75) return mix(c2, c3, (t - 0.5) / 0.25);
|
|
|
- return mix(c3, c4, (t - 0.75) / 0.25);
|
|
|
-}
|
|
|
-
|
|
|
-void main() {
|
|
|
- float lon = mix(u_region.x, u_region.z, v_uv.x);
|
|
|
- float lat = mix(u_region.y, u_region.w, v_uv.y);
|
|
|
- float s = fract(lon / 360.0);
|
|
|
- float t = (90.0 - lat) / 180.0;
|
|
|
- vec2 wind = sampleWindBicubic(u_wind, vec2(s, t));
|
|
|
- float tSpeed = clamp(length(wind), 0.0, 1.0);
|
|
|
- gl_FragColor = vec4(speedColor(tSpeed), u_alpha);
|
|
|
-}
|
|
|
-`;
|
|
|
-
|
|
|
/**
|
|
|
* 风场可视化类
|
|
|
*/
|
|
|
@@ -378,8 +340,10 @@ export default class WindField {
|
|
|
// 渲染资源
|
|
|
this._updateCmd = null;
|
|
|
this._particleCmd = null;
|
|
|
- this._colorCmd = null;
|
|
|
this._primitive = null;
|
|
|
+ // 静态色阶影像(SingleTileImageryProvider + Blob URL)
|
|
|
+ this._colorImageryLayer = null;
|
|
|
+ this._colorBlobUrl = null;
|
|
|
this._randomSeed = 0;
|
|
|
this._frameCount = 0;
|
|
|
this._forceRespawn = false;
|
|
|
@@ -397,10 +361,31 @@ export default class WindField {
|
|
|
throw new Error('当前环境不支持渲染到 FLOAT 纹理 (EXT_color_buffer_float)');
|
|
|
}
|
|
|
|
|
|
- const resp = await axios.get(url);
|
|
|
+ const resp = await axios.get(url, {
|
|
|
+ // 显式解析 JSON:axios 默认 transformResponse 在 JSON.parse 失败时会
|
|
|
+ // 静默返回原字符串,导致后续 json.windData 为 undefined 而误报"缺少 windData",
|
|
|
+ // 掩盖真实原因(如代理未命中返回 HTML、响应被截断、502 错误页等)。
|
|
|
+ // 注意:transformResponse 在 axios.get 执行期间被调用,此时外层 const resp 尚未初始化,
|
|
|
+ // 闭包内不能引用 resp(会触发 TDZ "Cannot access resp before initialization"),
|
|
|
+ // HTTP 状态码通过 transformResponse 的第三个参数 status 获取(axios v1.x 支持)。
|
|
|
+ transformResponse: [(data, headers, status) => {
|
|
|
+ if (typeof data !== 'string') return data;
|
|
|
+ try {
|
|
|
+ return JSON.parse(data);
|
|
|
+ } catch (e) {
|
|
|
+ const preview = data.length > 200
|
|
|
+ ? data.slice(0, 200) + '...(truncated, total ' + data.length + ' bytes)'
|
|
|
+ : data;
|
|
|
+ throw new Error(`风场响应不是合法 JSON (解析失败: ${e.message}),HTTP=${status},原始内容前 200 字符: ${preview}`);
|
|
|
+ }
|
|
|
+ }]
|
|
|
+ });
|
|
|
const json = resp.data;
|
|
|
const windDataStr = json && json.windData;
|
|
|
- if (!windDataStr) throw new Error('风场数据格式错误: 缺少 windData');
|
|
|
+ if (!windDataStr) {
|
|
|
+ const keys = (json && typeof json === 'object') ? Object.keys(json).join(',') : `(type=${typeof json})`;
|
|
|
+ throw new Error(`风场数据格式错误: 缺少 windData 字段,HTTP=${resp.status},响应实际 keys=[${keys}]`);
|
|
|
+ }
|
|
|
if (this._destroyed) return this;
|
|
|
|
|
|
const records = JSON.parse(windDataStr);
|
|
|
@@ -416,6 +401,11 @@ export default class WindField {
|
|
|
this._createWindTexture();
|
|
|
this._createParticles();
|
|
|
this._createPrimitive();
|
|
|
+ // 静态色阶影像(CPU 一次性光栅化 → SingleTileImageryProvider)
|
|
|
+ // 异步生成 PNG Blob,不阻塞粒子动画启动;销毁时如果在生成中会自动丢弃
|
|
|
+ this._createColorImageryLayer().catch((e) => {
|
|
|
+ console.warn('[WindField] 静态色阶影像生成失败(粒子动画不受影响):', e);
|
|
|
+ });
|
|
|
console.warn('[WindField] init done, particles=', this.options.particleSize ** 2);
|
|
|
return this;
|
|
|
}
|
|
|
@@ -581,62 +571,135 @@ export default class WindField {
|
|
|
],
|
|
|
indexBuffer: this._particleIB
|
|
|
});
|
|
|
-
|
|
|
- // 底色网格几何
|
|
|
- this._createColorGeometry();
|
|
|
}
|
|
|
|
|
|
- _createColorGeometry() {
|
|
|
- const N = 256;
|
|
|
- const vertsPerSide = N + 1;
|
|
|
- const vertexData = new Float32Array(vertsPerSide * vertsPerSide * 2);
|
|
|
- for (let j = 0; j < vertsPerSide; j++) {
|
|
|
- for (let i = 0; i < vertsPerSide; i++) {
|
|
|
- const idx = j * vertsPerSide + i;
|
|
|
- vertexData[idx * 2] = i / N;
|
|
|
- vertexData[idx * 2 + 1] = j / N;
|
|
|
- }
|
|
|
+ // ---------- 静态色阶影像(CPU 一次性光栅化 → SingleTileImageryProvider)----------
|
|
|
+ // 替代原色阶 DrawCommand,运行时 GPU 0 持续消耗(像降雨一样静态叠加)
|
|
|
+ // 原理:加载数据时一次性把 U/V 数据光栅化为 PNG(与降雨等值面同思路),
|
|
|
+ // 叠加为 SingleTileImageryProvider 静态影像,由 Cesium 按 LOD 自动重采样,
|
|
|
+ // 静态场景下不占用任何 GPU 帧渲染时间
|
|
|
+ //
|
|
|
+ // 关键:CPU 端用 Catmull-Rom 双三次插值(与原片元着色器 sampleWindBicubic 等价)
|
|
|
+ // 在【数据→颜色映射前】对 U/V 做插值,输出 4 倍分辨率 canvas,消除双线性在
|
|
|
+ // 数据点附近产生的色彩"平台"导致网格感明显的问题。
|
|
|
+ async _createColorImageryLayer() {
|
|
|
+ if (this._colorImageryLayer || this._destroyed) return;
|
|
|
+
|
|
|
+ const h = this._header;
|
|
|
+ const nx = h.nx, ny = h.ny;
|
|
|
+ const lo1 = h.lo1;
|
|
|
+ const la1 = h.la1;
|
|
|
+ const dx = h.dx || (nx > 1 ? 360 / nx : 1);
|
|
|
+ const dy = h.dy || (ny > 1 ? (la1 > 0 ? 180 / ny : 1) : 1);
|
|
|
+ const la2 = h.la2 !== undefined ? h.la2 : la1 - (ny - 1) * dy;
|
|
|
+
|
|
|
+ // 经度重排:让 canvas x=0 对应 lon=-180(使矩形 west=-180,east=180 一次性覆盖全球,
|
|
|
+ // 避免跨越 ±180 日界线导致 SingleTileImageryProvider 渲染异常)
|
|
|
+ // offset = (180 + lo1) / dx (通常 lo1=0 → offset = nx/2)
|
|
|
+ const xOffset = ((Math.round((180 + lo1) / dx) % nx) + nx) % nx;
|
|
|
+
|
|
|
+ // 4 倍分辨率输出:在数据空间 bicubic 插值后再映射颜色,过渡平滑无平台
|
|
|
+ // 注:cpu 计算量约 16 × nx × ny × SCALE²(4×4 邻域),加载数据时一次性,
|
|
|
+ // 1440×720 数据约 2.5 亿次浮点运算,单核 1-2 秒可完成
|
|
|
+ const SCALE = 4;
|
|
|
+ const outW = nx * SCALE;
|
|
|
+ const outH = ny * SCALE;
|
|
|
+ const canvas = document.createElement('canvas');
|
|
|
+ canvas.width = outW;
|
|
|
+ canvas.height = outH;
|
|
|
+ const ctx = canvas.getContext('2d');
|
|
|
+ const imgData = ctx.createImageData(outW, outH);
|
|
|
+ const data = imgData.data;
|
|
|
+ const u = this._u, v = this._v;
|
|
|
+ const maxSpeed = this._maxSpeed || 1;
|
|
|
+ const alphaByte = Math.round((this.options.colorLayerAlpha ?? 0.42) * 255);
|
|
|
+
|
|
|
+ // Catmull-Rom 权重(与 WIND_BICUBIC_GLSL 中 catmullRom 完全一致)
|
|
|
+ const catmullRom = (t) => {
|
|
|
+ const t2 = t * t, t3 = t2 * t;
|
|
|
+ return [
|
|
|
+ -0.5 * t3 + t2 - 0.5 * t,
|
|
|
+ 1.5 * t3 - 2.5 * t2 + 1.0,
|
|
|
+ -1.5 * t3 + 2.0 * t2 + 0.5 * t,
|
|
|
+ 0.5 * t3 - 0.5 * t2
|
|
|
+ ];
|
|
|
+ };
|
|
|
+
|
|
|
+ // 行预计算:每行的 4 个 iy 邻域索引 + wy 权重(避免内层重复计算)
|
|
|
+ const rowInfo = new Array(outH);
|
|
|
+ for (let y = 0; y < outH; y++) {
|
|
|
+ // 输出 y → 数据 fy = y / SCALE - 0.5(与片元着色器 coord = uv*size - 0.5 一致)
|
|
|
+ const fy = y / SCALE - 0.5;
|
|
|
+ const iy = Math.floor(fy);
|
|
|
+ const ty = fy - iy;
|
|
|
+ const wy = catmullRom(ty);
|
|
|
+ // 纬度方向 clamp(不环绕,南北界外重复边界数据点)
|
|
|
+ const sy = [
|
|
|
+ Math.max(0, Math.min(ny - 1, iy - 1)),
|
|
|
+ Math.max(0, Math.min(ny - 1, iy)),
|
|
|
+ Math.max(0, Math.min(ny - 1, iy + 1)),
|
|
|
+ Math.max(0, Math.min(ny - 1, iy + 2))
|
|
|
+ ];
|
|
|
+ rowInfo[y] = { sy, wy };
|
|
|
}
|
|
|
- const indexData = new Uint32Array(N * N * 6);
|
|
|
- for (let j = 0; j < N; j++) {
|
|
|
- for (let i = 0; i < N; i++) {
|
|
|
- const v0 = j * vertsPerSide + i;
|
|
|
- const v1 = v0 + 1;
|
|
|
- const v2 = v0 + vertsPerSide;
|
|
|
- const v3 = v2 + 1;
|
|
|
- const o = (j * N + i) * 6;
|
|
|
- indexData[o] = v0;
|
|
|
- indexData[o + 1] = v1;
|
|
|
- indexData[o + 2] = v3;
|
|
|
- indexData[o + 3] = v0;
|
|
|
- indexData[o + 4] = v3;
|
|
|
- indexData[o + 5] = v2;
|
|
|
+
|
|
|
+ for (let y = 0; y < outH; y++) {
|
|
|
+ const { sy, wy } = rowInfo[y];
|
|
|
+ for (let x = 0; x < outW; x++) {
|
|
|
+ const fx = x / SCALE - 0.5;
|
|
|
+ const ix = Math.floor(fx);
|
|
|
+ const tx = fx - ix;
|
|
|
+ const wx = catmullRom(tx);
|
|
|
+
|
|
|
+ let sumU = 0, sumV = 0;
|
|
|
+ for (let dj = 0; dj < 4; dj++) {
|
|
|
+ const wyv = wy[dj];
|
|
|
+ if (wyv === 0) continue;
|
|
|
+ const rowBase = sy[dj] * nx;
|
|
|
+ for (let di = 0; di < 4; di++) {
|
|
|
+ const wxv = wx[di];
|
|
|
+ if (wxv === 0) continue;
|
|
|
+ // 经度环绕(全球数据),并应用 xOffset 重排
|
|
|
+ let sxRaw = (ix + di - 1) % nx;
|
|
|
+ if (sxRaw < 0) sxRaw += nx;
|
|
|
+ const sx = (sxRaw + xOffset) % nx;
|
|
|
+ const w = wxv * wyv;
|
|
|
+ sumU += u[rowBase + sx] * w;
|
|
|
+ sumV += v[rowBase + sx] * w;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const speed = Math.sqrt(sumU * sumU + sumV * sumV);
|
|
|
+ const t = Math.min(1, Math.max(0, speed / maxSpeed));
|
|
|
+ const c = speedColorRGB(t);
|
|
|
+ const p = (y * outW + x) * 4;
|
|
|
+ data[p] = Math.round(c[0] * 255);
|
|
|
+ data[p + 1] = Math.round(c[1] * 255);
|
|
|
+ data[p + 2] = Math.round(c[2] * 255);
|
|
|
+ data[p + 3] = alphaByte;
|
|
|
}
|
|
|
}
|
|
|
- this._colorVB = Cesium.Buffer.createVertexBuffer({
|
|
|
- context: this.context,
|
|
|
- typedArray: vertexData,
|
|
|
- usage: Cesium.BufferUsage.STATIC_DRAW
|
|
|
- });
|
|
|
- this._colorIB = Cesium.Buffer.createIndexBuffer({
|
|
|
- context: this.context,
|
|
|
- typedArray: indexData,
|
|
|
- usage: Cesium.BufferUsage.STATIC_DRAW,
|
|
|
- indexDatatype: Cesium.IndexDatatype.UNSIGNED_INT
|
|
|
- });
|
|
|
- this._colorVA = new Cesium.VertexArray({
|
|
|
- context: this.context,
|
|
|
- attributes: [{
|
|
|
- index: 0,
|
|
|
- vertexBuffer: this._colorVB,
|
|
|
- componentsPerAttribute: 2,
|
|
|
- componentDatatype: Cesium.ComponentDatatype.FLOAT,
|
|
|
- normalize: false,
|
|
|
- offsetInBytes: 0,
|
|
|
- strideInBytes: 8
|
|
|
- }],
|
|
|
- indexBuffer: this._colorIB
|
|
|
+ ctx.putImageData(imgData, 0, 0);
|
|
|
+
|
|
|
+ // Blob URL → SingleTileImageryProvider 静态叠加(参考云图大 base64 处理思路)
|
|
|
+ const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));
|
|
|
+ if (this._destroyed) {
|
|
|
+ // 销毁后异步回调,直接丢弃
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this._colorBlobUrl = URL.createObjectURL(blob);
|
|
|
+
|
|
|
+ const provider = new Cesium.SingleTileImageryProvider({
|
|
|
+ url: this._colorBlobUrl,
|
|
|
+ rectangle: Cesium.Rectangle.fromDegrees(-180, la2, 180, la1),
|
|
|
+ credit: 'WindField'
|
|
|
});
|
|
|
+ this._colorImageryLayer = this.viewer.imageryLayers.addImageryProvider(provider);
|
|
|
+ if (this._colorImageryLayer) {
|
|
|
+ this._colorImageryLayer.show = !!this.options.colorLayerVisible;
|
|
|
+ // 置顶,确保色阶在底图之上(粒子是 primitive 独立通道,不受 imageryLayer 顺序影响)
|
|
|
+ try { this.viewer.imageryLayers.raiseToTop(this._colorImageryLayer); } catch (e) {}
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ---------- 着色器与命令 ----------
|
|
|
@@ -664,14 +727,6 @@ export default class WindField {
|
|
|
throw new Error('粒子着色器编译失败: ' + (e.message || e));
|
|
|
}
|
|
|
|
|
|
- // 底色着色器
|
|
|
- const colorProgram = Cesium.ShaderProgram.fromCache({
|
|
|
- context: this.context,
|
|
|
- vertexShaderSource: COLOR_VS,
|
|
|
- fragmentShaderSource: WIND_BICUBIC_GLSL + COLOR_FS,
|
|
|
- attributeLocations: { a_uv: 0 }
|
|
|
- });
|
|
|
-
|
|
|
// 区域包围球(避免被视锥裁剪)
|
|
|
const centerLon = (lonMin + lonMax) * 0.5;
|
|
|
const centerLat = (latMin + latMax) * 0.5;
|
|
|
@@ -708,35 +763,7 @@ export default class WindField {
|
|
|
owner: this
|
|
|
});
|
|
|
|
|
|
- // 底色绘制命令(关闭深度测试使色阶不被地形遮挡,背面剔除防止穿透地球)
|
|
|
- const colorRenderState = Cesium.RenderState.fromCache({
|
|
|
- blending: {
|
|
|
- enabled: true,
|
|
|
- equationRgb: Cesium.BlendEquation.ADD,
|
|
|
- equationAlpha: Cesium.BlendEquation.ADD,
|
|
|
- functionSourceRgb: Cesium.BlendFunction.SOURCE_ALPHA,
|
|
|
- functionSourceAlpha: Cesium.BlendFunction.ONE,
|
|
|
- functionDestinationRgb: Cesium.BlendFunction.ONE_MINUS_SOURCE_ALPHA,
|
|
|
- functionDestinationAlpha: Cesium.BlendFunction.ONE_MINUS_SOURCE_ALPHA
|
|
|
- },
|
|
|
- depthTest: { enabled: false },
|
|
|
- depthMask: false,
|
|
|
- cull: { enabled: true, face: Cesium.CullFace.BACK }
|
|
|
- });
|
|
|
- this._colorCmd = new Cesium.DrawCommand({
|
|
|
- primitiveType: Cesium.PrimitiveType.TRIANGLES,
|
|
|
- vertexArray: this._colorVA,
|
|
|
- shaderProgram: colorProgram,
|
|
|
- renderState: colorRenderState,
|
|
|
- pass: Cesium.Pass.TRANSLUCENT,
|
|
|
- cull: false,
|
|
|
- occlude: false,
|
|
|
- boundingVolume: regionBS,
|
|
|
- owner: this
|
|
|
- });
|
|
|
-
|
|
|
this._particleProgram = particleProgram;
|
|
|
- this._colorProgram = colorProgram;
|
|
|
|
|
|
const self = this;
|
|
|
this._primitive = {
|
|
|
@@ -802,17 +829,9 @@ export default class WindField {
|
|
|
};
|
|
|
frameState.commandList.push(this._particleCmd);
|
|
|
|
|
|
- // ---- 底色 DrawCommand ----
|
|
|
- if (this.options.colorLayerVisible) {
|
|
|
- this._colorCmd.uniformMap = {
|
|
|
- u_wind: () => this._windTexture,
|
|
|
- u_windTexSize: () => this._windTexSize,
|
|
|
- u_alpha: () => this.options.colorLayerAlpha,
|
|
|
- u_region: () => regionVec4,
|
|
|
- u_height: () => 3000
|
|
|
- };
|
|
|
- frameState.commandList.push(this._colorCmd);
|
|
|
- }
|
|
|
+ // 色阶已改为静态 SingleTileImageryProvider(CPU 一次性光栅化为 PNG),
|
|
|
+ // 不再每帧 push colorCmd,由 Cesium imagery 内部按 LOD 重采样渲染,
|
|
|
+ // 静态场景下 GPU 0 持续消耗(与降雨等值面同思路)
|
|
|
|
|
|
// 首帧诊断
|
|
|
if (!this._diagLogged) {
|
|
|
@@ -831,6 +850,9 @@ export default class WindField {
|
|
|
|
|
|
setColorLayerVisible(visible) {
|
|
|
this.options.colorLayerVisible = !!visible;
|
|
|
+ if (this._colorImageryLayer) {
|
|
|
+ this._colorImageryLayer.show = !!visible;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
isDestroyed() {
|
|
|
@@ -852,15 +874,20 @@ export default class WindField {
|
|
|
destroy(this._particleVB);
|
|
|
destroy(this._particleIB);
|
|
|
destroy(this._particleVA);
|
|
|
- destroy(this._colorVB);
|
|
|
- destroy(this._colorIB);
|
|
|
- destroy(this._colorVA);
|
|
|
destroy(this._particleProgram);
|
|
|
- destroy(this._colorProgram);
|
|
|
+
|
|
|
+ // 清理静态色阶影像图层 + 释放 Blob URL
|
|
|
+ if (this._colorImageryLayer && this.viewer && this.viewer.imageryLayers) {
|
|
|
+ try { this.viewer.imageryLayers.remove(this._colorImageryLayer); } catch (e) {}
|
|
|
+ this._colorImageryLayer = null;
|
|
|
+ }
|
|
|
+ if (this._colorBlobUrl) {
|
|
|
+ try { URL.revokeObjectURL(this._colorBlobUrl); } catch (e) {}
|
|
|
+ this._colorBlobUrl = null;
|
|
|
+ }
|
|
|
|
|
|
this._updateCmd = null;
|
|
|
this._particleCmd = null;
|
|
|
- this._colorCmd = null;
|
|
|
this._primitive = null;
|
|
|
}
|
|
|
|