Windy.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import * as Cesium from "cesium";
  2. import { WindLayer } from "../../kLayer/index.js";
  3. /**
  4. * 创建风场
  5. * @param {*} viewer
  6. * @param {*} controlColor
  7. */
  8. async function createWind(viewer, windData, controlColor) {
  9. // 加载风场
  10. // Create wind layer with options
  11. const windLayer = await new WindLayer(viewer, windData, {
  12. // ==================== 粒子相关 ====================
  13. particlesTextureSize: 1500, // 粒子纹理尺寸,决定粒子总数(size * size)
  14. particleHeight: 2500, // 粒子高度(相对于地表)
  15. lineWidth: { min: 1.5, max: 2.5 }, // 粒子尾迹宽度范围
  16. lineLength: { min: 300, max: 600 }, // 粒子尾迹长度范围
  17. speedFactor: 1.0, // 粒子速度倍数
  18. dropRate: 0.003, // 粒子掉落率
  19. dropRateBump: 0.001, // 慢粒子额外掉落率
  20. dynamic: true, // 是否启用动态动画
  21. flipY: false, // 是否翻转 Y 坐标(视数据情况而定)
  22. // ==================== 风场数据相关 ====================
  23. domain: undefined, // 可选:风速范围 domain = [min, max]
  24. displayRange: undefined, // 可选:可视化速度范围 displayRange = [min, max]
  25. uMax: undefined, // 可选:u 方向速度最大值(用于归一化)
  26. vMax: undefined, // 可选:v 方向速度最大值(用于归一化)
  27. // ==================== 颜色相关 ====================
  28. colors: [
  29. // 粒子颜色映射,可按速度渐变
  30. "rgb(255,255,0)",
  31. "rgb(255,0,0)",
  32. "rgb(0,0,255)",
  33. // "rgb(0,255,255)",
  34. ],
  35. // ==================== 位置和范围 ====================
  36. bounds: undefined, // 可选:指定风场边界 [xmin, ymin, xmax, ymax]
  37. projection: undefined, // 可选:投影函数,用于坐标转换
  38. extent: undefined, // 可选:覆盖区域(Cesium.Rectangle)
  39. // ==================== 粒子纹理/形状 ====================
  40. particleTexture: controlColor, // 可选:自定义粒子纹理
  41. fadeOpacity: 0.996, // 粒子尾迹渐隐率(0~1,越小越快消失)
  42. // ==================== 性能优化 ====================
  43. maxParticles: 50000000, // 最大粒子数量(默认由 particlesTextureSize 决定)
  44. skipRate: 1, // 粒子更新跳帧率(提升性能)
  45. // ==================== 其他 ====================
  46. animation: true, // 是否开启动画
  47. autoUpdate: true, // 是否自动更新风场
  48. });
  49. return windLayer;
  50. }
  51. /**
  52. * @description 相机飞行动画
  53. * @param {Object} options 配置对象
  54. * @param {Cesium.Viewer} options.viewer Cesium实例
  55. * @param {Array} options.points 动画路径数组
  56. * - lon {number} 经度
  57. * - lat {number} 纬度
  58. * - height {number} 高度
  59. * - heading {number} 朝向角度(度)
  60. * - pitch {number} 俯仰角度(度)
  61. * - roll {number} 翻滚角度(度)
  62. * - duration {number} 飞行动画时长(秒)
  63. * @param {Function} [options.easingFunction] 缓动函数
  64. */
  65. function flyCameraPath({
  66. viewer,
  67. points,
  68. easingFunction = Cesium.EasingFunction.LINEAR_NONE,
  69. }) {
  70. if (!viewer || !points || points.length === 0) {
  71. console.error("flyCameraPath: 参数错误,viewer或points缺失");
  72. return;
  73. }
  74. // 生成动画步骤
  75. const steps = [];
  76. for (let i = 0; i < points.length; i++) {
  77. const p = points[i];
  78. // 基础飞行动画
  79. steps.push({
  80. destination: Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height),
  81. duration: p.duration || 3,
  82. orientation: {
  83. heading: Cesium.Math.toRadians(p.heading || 0),
  84. pitch: Cesium.Math.toRadians(p.pitch || -90),
  85. roll: Cesium.Math.toRadians(p.roll || 0),
  86. },
  87. maximumHeight: p.maximumHeight || p.height,
  88. });
  89. }
  90. // 执行动画(递归)
  91. function runStep(index) {
  92. if (index >= steps.length) return;
  93. const step = steps[index];
  94. viewer.camera.flyTo({
  95. ...step,
  96. easingFunction,
  97. complete: () => runStep(index + 1),
  98. });
  99. }
  100. runStep(0);
  101. }
  102. /**
  103. * 添加降雪效果
  104. * @param {Viewer} viewer - 视图对象,通常是一个包含 DOM 元素和其他相关信息的对象。
  105. * @see {@link https://sandcastle.cesium.com/?src=Particle%20System%20Weather.html|Cesium 天气示例}
  106. */
  107. function addPrimitivesSnowCesium(viewer) {
  108. const scene = viewer.scene;
  109. // snow
  110. const snowParticleSize = 12.0;
  111. const snowRadius = 100000.0;
  112. const minimumSnowImageSize = new Cesium.Cartesian2(
  113. snowParticleSize,
  114. snowParticleSize
  115. );
  116. const maximumSnowImageSize = new Cesium.Cartesian2(
  117. snowParticleSize * 2.0,
  118. snowParticleSize * 2.0
  119. );
  120. let snowGravityScratch = new Cesium.Cartesian3();
  121. const snowUpdate = function (particle) {
  122. snowGravityScratch = Cesium.Cartesian3.normalize(
  123. particle.position,
  124. snowGravityScratch
  125. );
  126. Cesium.Cartesian3.multiplyByScalar(
  127. snowGravityScratch,
  128. Cesium.Math.randomBetween(-30.0, -300.0),
  129. snowGravityScratch
  130. );
  131. particle.velocity = Cesium.Cartesian3.add(
  132. particle.velocity,
  133. snowGravityScratch,
  134. particle.velocity
  135. );
  136. const distance = Cesium.Cartesian3.distance(
  137. scene.camera.position,
  138. particle.position
  139. );
  140. if (distance > snowRadius) {
  141. particle.endColor.alpha = 0.0;
  142. } else {
  143. particle.endColor.alpha = 1.0 / (distance / snowRadius + 0.1);
  144. }
  145. };
  146. const data = scene.primitives.add(
  147. new Cesium.ParticleSystem({
  148. modelMatrix: new Cesium.Matrix4.fromTranslation(scene.camera.position),
  149. minimumSpeed: -1.0,
  150. maximumSpeed: 0.0,
  151. lifetime: 15.0,
  152. emitter: new Cesium.SphereEmitter(snowRadius),
  153. startScale: 0.5,
  154. endScale: 1.0,
  155. image: "snowflake_particle.png",
  156. emissionRate: 7000.0,
  157. startColor: Cesium.Color.WHITE.withAlpha(0.0),
  158. endColor: Cesium.Color.WHITE.withAlpha(1.0),
  159. minimumImageSize: minimumSnowImageSize,
  160. maximumImageSize: maximumSnowImageSize,
  161. updateCallback: snowUpdate,
  162. })
  163. );
  164. scene.skyAtmosphere.hueShift = -0.8;
  165. scene.skyAtmosphere.saturationShift = -0.7;
  166. scene.skyAtmosphere.brightnessShift = -0.33;
  167. scene.fog.density = 0.001;
  168. scene.fog.minimumBrightness = 0.8;
  169. return data;
  170. }
  171. /**
  172. * 添加降雨效果
  173. * @param {Viewer} viewer - 视图对象,通常是一个包含 DOM 元素和其他相关信息的对象。
  174. * @see {@link https://sandcastle.cesium.com/?src=Particle%20System%20Weather.html|Cesium 天气示例}
  175. */
  176. function addPrimitivesRianCesium(viewer, size = 4) {
  177. // 先清空其他效果
  178. const scene = viewer.scene;
  179. // rain
  180. const rainParticleSize = 15.0;
  181. const rainRadius = 100000.0;
  182. const rainImageSize = new Cesium.Cartesian2(
  183. rainParticleSize,
  184. rainParticleSize * size //图片大小
  185. );
  186. let rainGravityScratch = new Cesium.Cartesian3();
  187. const rainUpdate = function (particle) {
  188. rainGravityScratch = Cesium.Cartesian3.normalize(
  189. particle.position,
  190. rainGravityScratch
  191. );
  192. rainGravityScratch = Cesium.Cartesian3.multiplyByScalar(
  193. rainGravityScratch,
  194. -1050.0,
  195. rainGravityScratch
  196. );
  197. particle.position = Cesium.Cartesian3.add(
  198. particle.position,
  199. rainGravityScratch,
  200. particle.position
  201. );
  202. const distance = Cesium.Cartesian3.distance(
  203. scene.camera.position,
  204. particle.position
  205. );
  206. if (distance > rainRadius) {
  207. particle.endColor.alpha = 0.0;
  208. } else {
  209. particle.endColor.alpha =
  210. Cesium.Color.BLUE.alpha / (distance / rainRadius + 0.1);
  211. }
  212. };
  213. const data = scene.primitives.add(
  214. new Cesium.ParticleSystem({
  215. modelMatrix: new Cesium.Matrix4.fromTranslation(scene.camera.position),
  216. speed: -1.0,
  217. lifetime: 15.0,
  218. emitter: new Cesium.SphereEmitter(rainRadius),
  219. startScale: 1.0,
  220. endScale: 0.0,
  221. image: "circular_particle.png",
  222. emissionRate: 9000.0,
  223. startColor: new Cesium.Color(0.27, 0.5, 0.7, 0.0),
  224. endColor: new Cesium.Color(0.27, 0.5, 0.7, 0.98),
  225. imageSize: rainImageSize,
  226. updateCallback: rainUpdate,
  227. })
  228. );
  229. scene.skyAtmosphere.hueShift = -0.97;
  230. scene.skyAtmosphere.saturationShift = 0.25;
  231. scene.skyAtmosphere.brightnessShift = -0.4;
  232. scene.fog.density = 0.00025;
  233. scene.fog.minimumBrightness = 0.01;
  234. return data;
  235. }
  236. // 加载glb模型
  237. function addGlbModel(viewer) {
  238. if (!viewer || !devJson || !Array.isArray(devJson)) return;
  239. devJson.forEach((item) => {
  240. const { name, position } = item;
  241. if (!position || position.length < 3) return;
  242. const [lon, lat, height] = position;
  243. const modal = viewer.entities.add({
  244. name: name,
  245. position: Cesium.Cartesian3.fromDegrees(lon, lat, height),
  246. model: {
  247. uri: "/modal/turbine.glb", // 模型路径
  248. scale: 0.1, // 缩放比例
  249. minimumPixelSize: 64, // 保证远距离也能显示
  250. maximumScale: 200, // 最大缩放
  251. runAnimations: true, // 如果模型有动画,自动播放
  252. // 关键:直接调整模型的亮度和光照相关属性
  253. color: Cesium.Color.WHITE, // 可以尝试给模型一个整体颜色乘数
  254. colorBlendMode: Cesium.ColorBlendMode.MIX, // 颜色混合模式
  255. silhouetteColor: Cesium.Color.WHITE, // 轮廓颜色
  256. // 最有效的属性:直接放大光源的影响
  257. luminanceAtZenith: 1.0, // 天顶亮度(增大此值可使模型更亮)
  258. },
  259. label: {
  260. show: true,
  261. scale: 1,
  262. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  263. fillColor: Cesium.Color.WHITE,
  264. text: name,
  265. showBackground: true,
  266. backgroundColor: Cesium.Color.BLACK.withAlpha(0.5),
  267. pixelOffset: new Cesium.Cartesian2(0, -420),
  268. distanceDisplayCondition: new Cesium.DistanceDisplayCondition(
  269. 100,
  270. 10000
  271. ),
  272. },
  273. });
  274. console.log(`加载模型`, modal);
  275. });
  276. }
  277. /**
  278. * 绑定右键事件,输出当前相机完整参数
  279. * @param {Cesium.Viewer} viewer
  280. */
  281. function bindRightClickLogCamera(viewer) {
  282. const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  283. handler.setInputAction(function () {
  284. const camera = viewer.camera;
  285. const pos = camera.positionCartographic;
  286. const lon = Cesium.Math.toDegrees(pos.longitude).toFixed(6);
  287. const lat = Cesium.Math.toDegrees(pos.latitude).toFixed(6);
  288. const height = pos.height.toFixed(2);
  289. const heading = Cesium.Math.toDegrees(camera.heading).toFixed(2);
  290. const pitch = Cesium.Math.toDegrees(camera.pitch).toFixed(2);
  291. const roll = Cesium.Math.toDegrees(camera.roll).toFixed(2);
  292. console.log(
  293. `相机参数:经度=${lon}, 纬度=${lat}, 高度=${height}, heading=${heading}, pitch=${pitch}, roll=${roll}`
  294. );
  295. }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  296. return handler; // 可以用 handler.destroy() 解绑
  297. }
  298. export {
  299. createWind,
  300. flyCameraPath,
  301. addGlbModel,
  302. addPrimitivesSnowCesium,
  303. addPrimitivesRianCesium,
  304. bindRightClickLogCamera,
  305. };