Files
kxfx/public/map/mars3d/plugins/wind/mars3d-wind-src.js
2025-09-10 00:13:57 +08:00

2117 lines
83 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 });
}));