windMap3D.vue 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  1. <template>
  2. <div id="loading" class="dataLoading" v-if="loading">
  3. <span class="loadText">数据加载中...</span>
  4. </div>
  5. <div class="mapBox">
  6. <div id="cesiumContainer" style="width: 100%; height: 100vh"></div>
  7. <div :class="!menuComTStyB ? 'menuComTSty' : 'menuComT'" v-if="0">
  8. <menuCom
  9. :showbasemap="false"
  10. :showwindspeed="false"
  11. :showcloud="false"
  12. :showrainfall="false"
  13. :showtemperature="false"
  14. :showcity="false"
  15. :showwind="false"
  16. :showexit="true"
  17. :showwindModel="true"
  18. @handleWindModel="showwindmodel"
  19. @handleInit="handleInitView"
  20. @handleExit="switchLayer"
  21. />
  22. </div>
  23. <el-drawer
  24. v-model="windDrawer"
  25. direction="rtl"
  26. class="windModelDrawer"
  27. :before-close="handleClose"
  28. >
  29. <template #header>
  30. <h3 style="font-weight: bold">{{ windDrawerHeader }}</h3>
  31. </template>
  32. <template #default>
  33. <div class="windDrawerCla">
  34. <div class="line" v-if="!showModelMsg">
  35. <div class="leftContent">
  36. <span>{{ windDrawerTitle }}</span>
  37. </div>
  38. </div>
  39. <div class="jcxx" v-if="showBasicMsg">
  40. <windHome :modelValItem="modelVal" />
  41. </div>
  42. <div class="spjk" v-if="showVideoMsg">
  43. <!-- <iframe
  44. src="/static/windVideo.mp4"
  45. frameborder="0"
  46. style="width: 100%; height: 100%"
  47. ></iframe> -->
  48. <video ref="videoPlayer" controls width="95%" muted autoplay>
  49. <source src="/static/windVideo.mp4" type="video/mp4" />
  50. </video>
  51. </div>
  52. <div class="gzck" v-if="showProblemMsg">
  53. <windPro />
  54. </div>
  55. <div class="third" v-if="showModelMsg">
  56. <ModelUnpack
  57. :modelUnpackType="modelUnpackType"
  58. :showModelMsg="showModelMsg"
  59. />
  60. </div>
  61. </div>
  62. </template>
  63. </el-drawer>
  64. <comModelDialog
  65. :showcomModelDia="showcomModelDia"
  66. :modelVal="modelVal"
  67. @showDia="showComDia"
  68. />
  69. <windView
  70. v-if="showWindDetail"
  71. :cesiumViewer="viewer"
  72. @coverOnChange="coverOnChange"
  73. :currentHeight="currentHeight"
  74. @showDetail="menuComTSty"
  75. @backStations="backStations"
  76. @initView="resetWindViewport"
  77. />
  78. </div>
  79. </template>
  80. <script>
  81. import * as Cesium from "../../Cesium";
  82. import "../../Cesium/Widgets/widgets.css";
  83. import fjMYLonLatJson from "../fjLonLatJson/fj_MY1.json";
  84. import fjWHZLonLatJson from "../fjLonLatJson/fj_WHZ.json"; //京能旺海庄
  85. import fjYPLLonLatJson from "../fjLonLatJson/fj_YPL.json"; //京能营盘梁
  86. import fjSMSLonLatJson from "../fjLonLatJson/fj_SMS.json"; //京能苏木山
  87. import mountainPosJson from "../fjLonLatJson/mountainPos.json";
  88. import allStationJson from "./allStationJson.json";
  89. import basicGeoJson from "../../assets/geoJson/basic.json";
  90. import comModelDialog from "@/components/comModelDialog.vue";
  91. import windView from "./windView.vue";
  92. import menuCom from "../menuCom.vue";
  93. import dayjs from "dayjs";
  94. import cloudJson from "/public/static/exportData/cloud/layer.json";
  95. import rainJson from "/public/static/exportData/rain/layer.json";
  96. import tempJson from "/public/static/exportData/tmp/layer.json";
  97. //风场展示图标
  98. import fc from "@/assets/windimgs/fanSvg/fc.png";
  99. //火电展示图标
  100. import hd from "@/assets/windimgs/fanSvg/hd.png";
  101. //光伏电站展示图标
  102. import gf from "@/assets/windimgs/fanSvg/gf.png";
  103. import windHome from "@/components/windHome/index.vue";
  104. import windPro from "@/components/windProDetail/windProblem.vue";
  105. import ModelUnpack from "@/components/modelUnpack.vue";
  106. import { WindLayer } from "cesium-wind";
  107. import windGridData from "./windGridData.json";
  108. import { HeightReference } from "cesium";
  109. import AdvancedBillboardGenerator from "@/tools/lightsign.js";
  110. export default {
  111. name: "windMap3D",
  112. components: {
  113. comModelDialog,
  114. windView,
  115. menuCom,
  116. windHome,
  117. windPro,
  118. ModelUnpack,
  119. },
  120. data() {
  121. return {
  122. restLatLon: [
  123. {
  124. longitude: 114.48789,
  125. latitude: 35.32916,
  126. name: "MYFDC",
  127. },
  128. {
  129. longitude: 112.88355172,
  130. latitude: 40.46617836,
  131. name: "JNWHZ",
  132. },
  133. {
  134. longitude: 112.5270545,
  135. latitude: 40.35920334,
  136. name: "JNYPL",
  137. },
  138. {
  139. longitude: 112.69922452,
  140. latitude: 40.31857399,
  141. name: "JNSMS",
  142. },
  143. ],
  144. loading: true,
  145. viewer: null,
  146. windLayer: null, // 风场图
  147. windLayerTimmer: null, // 风场图计时器
  148. cloudImagesLayer: [], // 卫星云图
  149. cloudLayer: null, // 卫星云图
  150. cloudintervalId: null,
  151. rainImagesLayer: [], // 降雨图
  152. rainLayer: null, // 降雨图
  153. rainintervalId: null, // 降雨图
  154. tempImagesLayer: [], //温度图
  155. temperatureLayer: null, //温度图
  156. tempintervalId: null, //温度图
  157. showcomModelDia: false,
  158. currentHeight: 0,
  159. modelVal: null,
  160. menuComTStyB: false,
  161. modelUnpackType: "fengji",
  162. windDrawer: false,
  163. windDrawerTitle: "",
  164. windDrawerHeader: "",
  165. showBasicMsg: false,
  166. showVideoMsg: false,
  167. showProblemMsg: false,
  168. showModelMsg: true,
  169. showWindDetail: true,
  170. allStationentitys: [],
  171. allWindEntitys: [],
  172. urlTiles: "/static/tiles/{z}/{x}/{y}.jpg",
  173. verticalExaggeration: 1.0,
  174. dixingAdd: false,
  175. infoBoxes: [],
  176. entityxy: {},
  177. };
  178. },
  179. mounted() {
  180. this.initCesium();
  181. setTimeout(() => {
  182. this.loading = false;
  183. }, 1000);
  184. },
  185. methods: {
  186. async initCesium() {
  187. const box = document.getElementById("cesiumContainer");
  188. const viewer = new Cesium.Viewer(box, {
  189. // terrainProvider: Cesium.createWorldTerrain(),
  190. baseLayerPicker: false, //是否显示底图切换按钮
  191. animation: false, //是否显示动画控制按钮
  192. vrButton: false,
  193. geocoder: false, //是否显示地理编码按钮
  194. homeButton: false, //是否显示地图导航按钮
  195. infoBox: false,
  196. sceneModePicker: false, //是否显示场景模式切换按钮
  197. selectionIndicator: false,
  198. timeline: false, //是否显示时间轴
  199. fullscreenButton: false, //是否显示全屏按钮
  200. navigationHelpButton: false,
  201. shouldAnimate: true,
  202. imageryProvider: false, //控制默认底图的显示
  203. // terrain: Cesium.Terrain.fromWorldTerrain(),
  204. });
  205. viewer._cesiumWidget._creditContainer.style.display = "none";
  206. // this.csceneElliposid(viewer)
  207. const utcStart = dayjs(
  208. `${dayjs().format("YYYY-MM-DD")} 00:00:00`
  209. ).subtract(4, "hour");
  210. const startTime = Cesium.JulianDate.fromDate(new Date(utcStart));
  211. const utcStop = dayjs(
  212. `${dayjs().format("YYYY-MM-DD")} 23:59:59`
  213. ).subtract(4, "hour");
  214. const stopTime = Cesium.JulianDate.fromDate(new Date(utcStop));
  215. const utcNow = dayjs().subtract(4, "hour");
  216. const nowTime = Cesium.JulianDate.fromDate(new Date(utcNow));
  217. // dayjs().format("YYYY-MM-DD")} 00:00:00
  218. viewer.clock.startTime = startTime;
  219. viewer.clock.stopTime = stopTime;
  220. viewer.clock.currentTime = nowTime;
  221. viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 循环模式:到达结束时跳回开始
  222. // viewer.clock.multiplier = 10000; // 时间流逝速度(加快模拟)
  223. viewer.clock.shouldAnimate = true;
  224. this.viewer = viewer;
  225. // 展示场站
  226. // this.showAllStation(viewer)
  227. // 展示风机
  228. this.initCesiumTerrain();
  229. this.initCesiumBaseMapImage();
  230. // this.initGeoJsonData();
  231. this.showWindFromStation(viewer);
  232. },
  233. // 初始化底图
  234. async initCesiumBaseMapImage() {
  235. const imageryProvider = await new Cesium.UrlTemplateImageryProvider({
  236. // url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
  237. // url: "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
  238. // url: "http://localhost:3007/tiles/map/{z}/{x}/{y}",
  239. url: "/static/ditu/{z}/{x}/{y}.png",
  240. // minimumLevel: 11,
  241. maximumLevel: 24,
  242. credit: "影像地图",
  243. });
  244. imageryProvider.alpha = 0.55; // 透明度
  245. imageryProvider.brightness = 1; // 亮度
  246. imageryProvider.contrast = 1; // 对比度
  247. this.viewer.imageryLayers.addImageryProvider(imageryProvider);
  248. },
  249. // 初始化 geoJson 数据
  250. async initGeoJsonData() {
  251. // 创建GeoJSON数据源
  252. await new Cesium.GeoJsonDataSource.load(basicGeoJson, {
  253. stroke: Cesium.Color.GRAY, // 边界线颜色
  254. fill: Cesium.Color.BLACK.withAlpha(0), // 填充颜色
  255. strokeWidth: 1, // 边界线宽度
  256. markerSymbol: "?", // 点要素的符号
  257. clampToGround: true, // 贴地
  258. }).then((dataSource) => {
  259. // 添加到视图
  260. this.viewer.dataSources.add(dataSource);
  261. var entities = dataSource.entities.values;
  262. for (let i = 0; i < entities.length; i++) {
  263. let entity = entities[i];
  264. entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()).positions;
  265. //单独设置线条样式
  266. var positions = entity.polygon.hierarchy._value.positions;
  267. entity.polyline = {
  268. positions: positions,
  269. width: 2,
  270. outline: false,
  271. // clampToGround: true,
  272. show: true,
  273. };
  274. entity.wall = {
  275. positions: entity.polyline.positions.getValue(
  276. Cesium.JulianDate.now()
  277. ),
  278. // maximumHeights: new Array(
  279. // entity.polyline.positions.getValue(Cesium.JulianDate.now()).length
  280. // ).fill(-3000), // 向下30米
  281. minimumHeights: new Array(
  282. entity.polyline.positions.getValue(Cesium.JulianDate.now()).length
  283. ).fill(-50000), // 向下50米
  284. material: Cesium.Color.BLACK.withAlpha(0.1),
  285. outline: false,
  286. // outlineColor: Cesium.Color.WHITE,
  287. };
  288. }
  289. // 添加中文标签图层
  290. const labelLayer = new Cesium.LabelCollection();
  291. this.viewer.scene.primitives.add(labelLayer);
  292. const cities = [];
  293. basicGeoJson?.features?.forEach((ele) => {
  294. if (Array.isArray(ele.properties.centroid)) {
  295. const name = ele.properties.name;
  296. const lon = ele.properties.centroid[0];
  297. const lat = ele.properties.centroid[1];
  298. cities.push({ name, lon, lat });
  299. }
  300. });
  301. cities.forEach((city, index) => {
  302. labelLayer.add({
  303. id: index,
  304. name: "cityLabel",
  305. position: Cesium.Cartesian3.fromDegrees(city.lon, city.lat, 10),
  306. text: city.name,
  307. font: 'bold 14px "Microsoft YaHei", sans-serif',
  308. fillColor: Cesium.Color.fromCssColorString("#000"),
  309. outlineColor: Cesium.Color.WHITE,
  310. outlineWidth: 2,
  311. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  312. pixelOffset: new Cesium.Cartesian2(0, 0), // 设置为0
  313. horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 水平居中
  314. verticalOrigin: Cesium.VerticalOrigin.CENTER, // 垂直居中
  315. });
  316. });
  317. this.labelLayer = labelLayer;
  318. // this.csceneElliposid(this.viewer, null)
  319. // this.resetViewport1();
  320. });
  321. },
  322. // 初始化地形
  323. async initCesiumTerrain() {
  324. const terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  325. // "http://localhost:3007/tiles/dixing",
  326. "/static/dixing",
  327. {
  328. requestWaterMask: true, // 如果需要水效果,设置为true
  329. requestVertexNormals: true, // 如果需要光照效果,设置为true
  330. requestMetadata: true, // 请求元数据
  331. // minimumLevel: 10,
  332. maximumLevel: 15,
  333. }
  334. );
  335. this.viewer.scene.verticalExaggeration = this.verticalExaggeration;
  336. // this.viewer.scene.verticalExaggerationRelativeHeight = 2400.0;
  337. this.viewer.terrainProvider = terrainProvider;
  338. // const terrainProvider = new Cesium.CesiumTerrainProvider({
  339. // url: "http://localhost:3007/tiles/dixing", // 地形数据所在目录的URL
  340. // requestWaterMask: true, // 如果需要水效果,设置为true
  341. // requestVertexNormals: true, // 如果需要光照效果,设置为true
  342. // minimumLevel: 10,
  343. // maximumLevel: 15,
  344. // });
  345. // this.viewer.terrainProvider = terrainProvider;
  346. // console.log(111, terrainProvider);
  347. // const imageryProvider = new Cesium.UrlTemplateImageryProvider({
  348. // url: "http://localhost:3007/tiles/dixing/{z}/{x}/{y}",
  349. // // url: "/static/dixing/{z}/{x}/{y}.terrain",
  350. // credit: "地形数据",
  351. // });
  352. // this.viewer.imageryLayers.addImageryProvider(imageryProvider);
  353. // this.viewer.scene.globe.depthTestAgainstTerrain = true; //地形遮挡效果开关,打开后 地形会遮挡看不到的区域
  354. this.viewer.scene.globe.enableLighting = true; //对大气和雾启用动态照明效
  355. },
  356. backStations() {
  357. this.showWindDetail = false;
  358. this.allWindEntitys.forEach(({ entity, handler }) => {
  359. this.viewer.entities.remove(entity); // 移除实体
  360. if (!handler.isDestroyed()) {
  361. handler.destroy(); // 销毁事件处理器(关键!)
  362. }
  363. });
  364. this.allWindEntitys = [];
  365. // if (this.viewer && this.viewer.destroy) {
  366. // this.viewer.destroy();
  367. // this.viewer = null
  368. // }
  369. // this.initCesium();
  370. this.showAllStation(this.viewer);
  371. },
  372. // 展示所有风场
  373. showAllStation(viewer) {
  374. allStationJson.station.forEach((e, index) => {
  375. if (e.energytype === "Wind") {
  376. this.showStationFn(viewer, e, index, fc);
  377. } else if (e.energytype === "Fire") {
  378. this.showStationFn(viewer, e, index, hd);
  379. } else {
  380. this.showStationFn(viewer, e, index, gf);
  381. }
  382. });
  383. this.resetAllStationViewport();
  384. },
  385. // 根据状态展示不同颜色风机贴图
  386. showStationFn(viewer, e, index, images) {
  387. const position = Cesium.Cartesian3.fromDegrees(e.longitude, e.latitude);
  388. const entity = viewer.entities.add({
  389. id: index,
  390. position, // 模型位置
  391. billboard: {
  392. image: images, // 也可以是 SVG 路径,如 'icon.svg'
  393. scale: 0.5,
  394. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  395. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  396. // 模型贴地
  397. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  398. },
  399. label: {
  400. text: e.plantname,
  401. font: "14px sans-serif",
  402. fillColor: Cesium.Color.fromBytes(255, 255, 255),
  403. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  404. },
  405. });
  406. let that = this;
  407. const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  408. handler.setInputAction(function (movement) {
  409. var position = movement.position;
  410. var pickedObject = viewer.scene.pick(position);
  411. if (pickedObject && pickedObject.id.id === index) {
  412. console.log("你点击了标签或模型!", entity);
  413. console.log("选中风场或新能源场", e.plantname);
  414. that.showWindDetail = true;
  415. that.allStationentitys.forEach(({ entity, handler }) => {
  416. viewer.entities.remove(entity); // 移除实体
  417. if (!handler.isDestroyed()) {
  418. handler.destroy(); // 销毁事件处理器(关键!)
  419. }
  420. });
  421. that.allStationentitys = [];
  422. that.showWindFromStation(viewer);
  423. }
  424. }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  425. that.allStationentitys.push({ entity, handler });
  426. },
  427. // 展示所选风场的风机
  428. showWindFromStation(viewer) {
  429. let stationName = this.$route.query.nameEn;
  430. let fjLonLatJson = [];
  431. if (stationName === "MYFDC") {
  432. fjLonLatJson = fjMYLonLatJson;
  433. } else if (stationName === "JNWHZ") {
  434. fjLonLatJson = fjWHZLonLatJson;
  435. } else if (stationName === "JNYPL") {
  436. fjLonLatJson = fjYPLLonLatJson;
  437. } else if (stationName === "JNSMS") {
  438. fjLonLatJson = fjSMSLonLatJson;
  439. }
  440. fjLonLatJson.data.forEach((e, index) => {
  441. this.showStatuswind(viewer, e);
  442. });
  443. // let mountainMsg = mountainPosJson.data.find(it => it.stationName === stationName)
  444. // this.addTreeain(
  445. // viewer,
  446. // mountainMsg
  447. // );
  448. this.resetWindViewport();
  449. },
  450. // 根据状态展示不同颜色风机贴图
  451. showStatuswind(viewer, e) {
  452. this.addModel(
  453. viewer,
  454. e.name,
  455. e.longitude,
  456. e.latitude,
  457. e.status,
  458. e.height || 0,
  459. e
  460. );
  461. },
  462. //给模型上描边
  463. getWtStatue(code, type) {
  464. if (code === 1) {
  465. //待机
  466. // return "#05bb4c";
  467. return type === "en" ? "dj" : "待机";
  468. } else if (code === 2) {
  469. //故障
  470. // return "#d83238";
  471. return type === "en" ? "gz" : "故障";
  472. } else if (code === 3) {
  473. //检修
  474. // return "#ff8300";
  475. return type === "en" ? "jx" : "检修";
  476. } else if (code === 4) {
  477. //限电
  478. // return "#c732ca";
  479. return type === "en" ? "xd" : "限电";
  480. } else if (code === 5) {
  481. //离线
  482. // return "#6f7881";
  483. return type === "en" ? "lx" : "离线";
  484. } else if (code === 6) {
  485. //受累
  486. // return "#cbd1d7";
  487. return type === "en" ? "sl" : "受累";
  488. } else {
  489. //并网
  490. // return "#42a7f9";
  491. return type === "en" ? "bw" : "并网";
  492. }
  493. },
  494. addTreeain(viewer, mountainMsg) {
  495. const hpRoll = new Cesium.HeadingPitchRoll(90.0, 0.0, 0.0);
  496. const position = Cesium.Cartesian3.fromDegrees(
  497. mountainMsg.longitude,
  498. mountainMsg.latitude,
  499. mountainMsg.height || 0
  500. );
  501. const orientation = Cesium.Transforms.headingPitchRollQuaternion(
  502. position,
  503. hpRoll
  504. );
  505. const offsetLocal = new Cesium.Cartesian3(
  506. mountainMsg.east,
  507. mountainMsg.north,
  508. mountainMsg.up
  509. ); // E, N, U
  510. const transform = Cesium.Transforms.eastNorthUpToFixedFrame(position);
  511. const offsetGlobal = new Cesium.Cartesian3();
  512. Cesium.Matrix4.multiplyByPointAsVector(
  513. transform,
  514. offsetLocal,
  515. offsetGlobal
  516. );
  517. const finalPos = Cesium.Cartesian3.add(
  518. position,
  519. offsetGlobal,
  520. new Cesium.Cartesian3()
  521. );
  522. const terrain = viewer.entities.add({
  523. name: mountainMsg.modelName, // 模型名称
  524. position: finalPos, // 模型位置
  525. orientation, // 模型朝向
  526. animation: false,
  527. model: {
  528. uri: `/static/model/dixing/${mountainMsg.modelNameEn}.glb`,
  529. // uri: "/static/model/dixing/terrain.glb",
  530. scale: mountainMsg.scale,
  531. // 模型贴地
  532. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  533. },
  534. });
  535. },
  536. //添加模型
  537. addModel(viewer, name, lon, lat, status, height, model) {
  538. const hpRoll = new Cesium.HeadingPitchRoll(45.0, 0.0, 0.0);
  539. const position = Cesium.Cartesian3.fromDegrees(lon, lat, height || 0);
  540. const orientation = Cesium.Transforms.headingPitchRollQuaternion(
  541. position,
  542. hpRoll
  543. );
  544. // const statueColor = this.getWtStatue(wtStatue);
  545. const statueFJ = this.getWtStatue(status, "en");
  546. const statueZh = this.getWtStatue(status, "zh");
  547. let that = this;
  548. const entity = viewer.entities.add({
  549. name, // 模型名称
  550. position, // 模型位置
  551. orientation, // 模型朝向
  552. animation: false,
  553. model: {
  554. // uri: this.dixingAdd
  555. // ? // ? "/static/model/fjSolo/model.glb"
  556. // // ? "/static/model/fjStatus/fj_anmation_bw.glb"
  557. // `/static/model/fjStatus/fj_${statueFJ}.glb`
  558. // : "/static/model/dixing/model.glb",
  559. // // : "/static/model/dixing/terrain.glb",
  560. uri: `/static/model/fjStatus/fj_${statueFJ}.glb`,
  561. scale: 0.5,
  562. // 模型贴地
  563. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  564. // heightReference: Cesium.HeightReference.NONE,
  565. // silhouetteSize: this.dixingAdd ? 2 : 0,
  566. // silhouetteColor: Cesium.Color.fromCssColorString(statueColor),
  567. // runAnimations: wtStatue !== 7 ? false : true,
  568. },
  569. label: {
  570. text: name,
  571. font: "14px sans-serif",
  572. fillColor: Cesium.Color.fromBytes(255, 255, 255),
  573. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  574. },
  575. });
  576. // 使用高级灯牌生成器
  577. const advancedGenerator = new AdvancedBillboardGenerator();
  578. let statusItems = [];
  579. const statusArray = [
  580. { label: "风速", value: "10m/s" },
  581. { label: "状态", value: statueZh },
  582. { label: "转速", value: "1000p" },
  583. { label: "功率", value: "50kW" },
  584. { label: "其他参数A", value: "...." },
  585. { label: "其他参数B", value: "...." },
  586. { label: "其他参数C", value: "...." },
  587. { label: "其他参数D", value: "...." },
  588. { label: "其他参数E", value: "...." },
  589. { label: "其他参数F", value: "...." },
  590. ];
  591. for (let i = 0; i <= this.randomNum(1, 9); i++) {
  592. statusItems.push(statusArray[i]);
  593. }
  594. const billboardImage = advancedGenerator.generateAdvancedBillboard({
  595. title: name,
  596. statusItems,
  597. borderGradient: ["#00e5ff", "#2979ff"],
  598. padding: 6,
  599. titleHeight: 22,
  600. itemHeight: 17,
  601. titleColor: "#1890ff",
  602. titleStrokeStyle: "#000",
  603. titleStrokeWidth: 1,
  604. labelStrokeStyle: "#000",
  605. labelStrokeWidth: 1,
  606. valueStrokeStyle: "#000",
  607. valueStrokeWidth: 1,
  608. });
  609. const entityxy = viewer.entities.add({
  610. name,
  611. position,
  612. billboard: {
  613. image: billboardImage,
  614. scale: 1,
  615. verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 原来已经是CENTER,现在改为BOTTOM
  616. pixelOffset: new Cesium.Cartesian2(80, -50), // 原来是-20,现在改为30,向上移动
  617. eyeOffset: new Cesium.Cartesian3(0, 0, 0), // 保持固定大小
  618. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  619. scaleByDistance: new Cesium.NearFarScalar(
  620. 6000, // 5010米内
  621. 1.2, // 1倍大小
  622. 9000, // 10000米外
  623. 0.5 // 0.1倍大小
  624. ),
  625. // 透明度渐变
  626. translucencyByDistance: new Cesium.NearFarScalar(
  627. 6000, // 500米内
  628. 1.0, // 完全不透明
  629. 9000, // 3000米外
  630. 0.5 // 完全透明
  631. ),
  632. },
  633. });
  634. this.dixingAdd = true;
  635. const btn = document.getElementById("windBtn");
  636. btn.addEventListener("click", function (event) {
  637. entityxy.show = !entityxy.show;
  638. });
  639. // viewer.camera.moveEnd.addEventListener(() => {
  640. // // 获取相机的笛卡尔3D坐标
  641. // const position = viewer.camera.position;
  642. // // 将笛卡尔坐标转换为地理坐标(弧度)
  643. // const cartographic = Cesium.Cartographic.fromCartesian(position);
  644. // // 高度(以米为单位)
  645. // // 注意:这个高度是相对于WGS84椭球体的高度,不是海拔高度(MSL)
  646. // const height = Cesium.Math.toDegrees(cartographic.height);
  647. // if (height > 3000000) {
  648. // entityxy.show = false;
  649. // console.log("entityxy false", entityxy.show);
  650. // }
  651. // if (height < 3000000) {
  652. // entityxy.show = true;
  653. // console.log("entityxy true", entityxy.show);
  654. // }
  655. // // 强制请求一次场景重绘
  656. // if (viewer && viewer.scene) {
  657. // viewer.scene.requestRender();
  658. // }
  659. // console.log("相机高度 (椭球体高):", height, "米");
  660. // });
  661. // 创建事件处理器
  662. const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  663. handler.setInputAction(function (movement) {
  664. var position = movement.position;
  665. var pickedObject = viewer.scene.pick(position);
  666. if (pickedObject && pickedObject.id === entity) {
  667. console.log("你点击了标签或模型!", entity);
  668. // console.log("标签或模型数据!", val);
  669. that.modelVal = model;
  670. // 找到实体,显示包含实体信息的弹框
  671. that.showRightClickPopup(position, model);
  672. return;
  673. }
  674. }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  675. },
  676. // 右键展示元素
  677. showRightClickPopup(screenPosition, val) {
  678. // 创建或获取弹框元素
  679. var popup = document.getElementById("rightClickPopup");
  680. if (!popup) {
  681. popup = document.createElement("div");
  682. popup.id = "rightClickPopup";
  683. popup.style.position = "absolute";
  684. // popup.style.backgroundColor = 'white';
  685. popup.style.border = "1px solid rgba(255, 255, 255, 0.2)";
  686. popup.style.borderRadius = "4px";
  687. popup.style.padding = "10px";
  688. popup.style.boxShadow = "0 2px 10px rgba(0,0,0,0.2)";
  689. popup.style.backdropFilter = `blur(10px)`;
  690. popup.style.zIndex = "1000";
  691. popup.style.pointerEvents = "auto"; // 确保弹框可交互
  692. // 添加一个最小宽度,避免内容过短
  693. popup.style.minWidth = "100px";
  694. // 可选:添加箭头指向点击点
  695. // 这需要更复杂的 CSS 或额外的 DOM 元素
  696. document.body.appendChild(popup);
  697. }
  698. // <span class="popup-menu-item" data-message-type="model" style="cursor:pointer">模型解构</span>
  699. let content = `
  700. <div style="display: flex;gap: 10px;flex-direction: column;text-align: center;color: #fff">
  701. <span class="popup-menu-item" data-message-type="basic" style="cursor:pointer">基本信息</span>
  702. <span class="popup-menu-item" data-message-type="video" style="cursor:pointer">视频监控</span>
  703. <span class="popup-menu-item" data-message-type="problem" style="cursor:pointer">故障详情</span>
  704. <span class="popup-menu-item" data-message-type="third" style="cursor:pointer">模型解构</span>
  705. </div>
  706. `;
  707. // 设置弹框内容
  708. popup.innerHTML = content;
  709. // --- 关键修正:准确计算弹框位置 ---
  710. // 1. 获取 Cesium 画布 (Canvas) 相对于视口 (viewport) 的位置
  711. var canvas = this.viewer.scene.canvas;
  712. var canvasRect = canvas.getBoundingClientRect();
  713. // console.log('Canvas Rect:', canvasRect); // 调试用
  714. // 2. 计算弹框左上角在页面中的绝对 X 坐标
  715. // screenPosition.x 是相对于画布左上角的 X 偏移
  716. // canvasRect.left 是画布左上角相对于浏览器视口左上角的 X 偏移
  717. // window.pageXOffset 是页面水平滚动的距离
  718. var popupLeft = canvasRect.left + screenPosition.x + window.pageXOffset;
  719. // 3. 计算弹框左上角在页面中的绝对 Y 坐标
  720. var popupTop = canvasRect.top + screenPosition.y + window.pageYOffset;
  721. // --- 可选:添加偏移量,避免完全覆盖鼠标指针 ---
  722. // 例如,向下和向右偏移 10px
  723. var offsetX = 10;
  724. var offsetY = 10;
  725. popupLeft += offsetX;
  726. popupTop += offsetY;
  727. // --- 可选:边界检查,防止弹框超出屏幕 ---
  728. var popupWidth = popup.offsetWidth || 150; // 确保有宽度
  729. var popupHeight = popup.offsetHeight || 50; // 确保有高度
  730. var windowWidth = window.innerWidth;
  731. var windowHeight = window.innerHeight;
  732. // 如果弹框右边会超出窗口,则左移
  733. if (popupLeft + popupWidth > windowWidth) {
  734. popupLeft = windowWidth - popupWidth - 10; // 靠右留 10px 边距
  735. }
  736. // 如果弹框底边会超出窗口,则上移
  737. if (popupTop + popupHeight > windowHeight) {
  738. popupTop = windowHeight - popupHeight - 10; // 靠下留 10px 边距
  739. }
  740. // 如果左边超出 (不太可能,但安全起见)
  741. if (popupLeft < 0) popupLeft = 10;
  742. // 如果顶边超出
  743. if (popupTop < 0) popupTop = 10;
  744. // --- 设置最终位置 ---
  745. popup.style.left = popupLeft + "px";
  746. popup.style.top = popupTop + "px";
  747. // console.log('Popup Position:', { left: popupLeft, top: popupTop }); // 调试用
  748. // 显示弹框
  749. popup.style.display = "block";
  750. // --- 隐藏弹框的逻辑 (保持不变) ---
  751. function hidePopup(event) {
  752. // 检查点击是否发生在弹框内部,如果是,则不隐藏
  753. if (event && popup.contains(event.target)) {
  754. return;
  755. }
  756. popup.style.display = "none";
  757. document.removeEventListener("click", hidePopup);
  758. document.removeEventListener("keydown", keyDownHandler);
  759. }
  760. function keyDownHandler(event) {
  761. if (event.key === "Escape") {
  762. hidePopup();
  763. }
  764. }
  765. let that = this;
  766. that.showBasicMsg = false;
  767. that.showVideoMsg = false;
  768. that.showProblemMsg = false;
  769. that.showModelMsg = false;
  770. function popupMenuItemClick(event) {
  771. // 检查点击的是否是菜单项
  772. if (event.target.classList.contains("popup-menu-item")) {
  773. event.stopPropagation(); // 阻止冒泡,防止弹框立即关闭
  774. var messageType = event.target.dataset.messageType; // 获取 data-message-type
  775. console.log("点击了菜单项:", messageType);
  776. // 调用您的 showMessage 函数
  777. that.showMessage(messageType, val);
  778. hidePopup();
  779. // 可选:执行后关闭弹框
  780. // hideRightClickPopup();
  781. }
  782. }
  783. // 移除旧的监听器(避免重复绑定)
  784. document.removeEventListener("click", hidePopup);
  785. document.removeEventListener("click", popupMenuItemClick);
  786. document.removeEventListener("keydown", keyDownHandler);
  787. // 添加新的监听器
  788. document.addEventListener("click", hidePopup);
  789. document.addEventListener("click", popupMenuItemClick);
  790. document.addEventListener("keydown", keyDownHandler);
  791. },
  792. showMessage(type, val) {
  793. console.log("type===>>>", type);
  794. this.windDrawer = true;
  795. this.windDrawerHeader = val.name + "数据详情";
  796. if (type === "basic") {
  797. this.windDrawerTitle = "基础信息";
  798. this.showBasicMsg = true;
  799. } else if (type === "video") {
  800. this.windDrawerTitle = "视频监控";
  801. this.showVideoMsg = true;
  802. } else if (type === "problem") {
  803. this.windDrawerTitle = "故障查看";
  804. this.showProblemMsg = true;
  805. } else if (type === "third") {
  806. this.windDrawerHeader = `${val.name}部件查询解构`;
  807. this.windDrawerTitle = "模型解构";
  808. this.showModelMsg = true;
  809. }
  810. },
  811. showwindmodel() {
  812. this.windDrawer = true;
  813. this.showBasicMsg = false;
  814. this.showVideoMsg = false;
  815. this.showProblemMsg = false;
  816. this.windDrawerHeader = "风机模型可视化解构说明";
  817. this.showModelMsg = true;
  818. },
  819. handleClose() {
  820. this.windDrawer = false;
  821. this.showBasicMsg = false;
  822. this.showVideoMsg = false;
  823. this.showProblemMsg = false;
  824. this.showModelMsg = false;
  825. },
  826. showComDia(val) {
  827. this.showcomModelDia = val;
  828. this.modelVal = null;
  829. },
  830. randomNum(minNum, maxNum) {
  831. switch (arguments.length) {
  832. case 1:
  833. return parseInt(Math.random() * minNum + 1, 10);
  834. case 2:
  835. return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
  836. default:
  837. return 0;
  838. }
  839. },
  840. csceneElliposid(viewer) {
  841. let that = this;
  842. // 获取 scene 和 ellipsoid
  843. var scene = viewer.scene;
  844. var labels = viewer.scene.primitives.add(new Cesium.LabelCollection());
  845. // 创建 ScreenSpaceEventHandler
  846. var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
  847. // 监听左键点击事件
  848. handler.setInputAction(async (click) => {
  849. // 获取点击位置的笛卡尔坐标
  850. var position = click.position;
  851. if (!position) return;
  852. // 使用 globe.pick 获取包含地形高度的坐标
  853. var ray = viewer.camera.getPickRay(position);
  854. var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
  855. if (cartesian) {
  856. // 转换为地理坐标
  857. var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
  858. var longitude = Cesium.Math.toDegrees(cartographic.longitude);
  859. var latitude = Cesium.Math.toDegrees(cartographic.latitude);
  860. var height = cartographic.height;
  861. // 格式化坐标
  862. var text = `经度: ${longitude.toFixed(6)}°\n纬度: ${latitude.toFixed(
  863. 6
  864. )}°`;
  865. // 创建一个标签
  866. var label = labels.add({
  867. position: cartesian,
  868. text: text,
  869. font: "14px monospace",
  870. fillColor: Cesium.Color.fromCssColorString("#1d70df"),
  871. // style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  872. // outlineColor: Cesium.Color.BLACK,
  873. outlineWidth: 2,
  874. verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 标签在点的上方
  875. pixelOffset: new Cesium.Cartesian2(0, -20), // 向上偏移一点
  876. disableDepthTest: true, // 让标签始终可见(即使在地球背面)
  877. scale: 0.8,
  878. });
  879. // 5秒后移除标签
  880. setTimeout(function () {
  881. labels.remove(label);
  882. }, 5000);
  883. console.log(
  884. `点击坐标: ${longitude.toFixed(6)}, ${latitude.toFixed(
  885. 6
  886. )}, ${height.toFixed(2)}m`
  887. );
  888. }
  889. }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  890. },
  891. generateUniqueId(prefix = "id") {
  892. let idCounter = 0;
  893. return `${prefix}-${Date.now()}-${Math.random()
  894. .toString(36)
  895. .substr(2, 9)}-${idCounter++}`;
  896. },
  897. handleInitView() {
  898. if (this.showWindDetail) {
  899. this.resetWindViewport();
  900. } else {
  901. this.resetAllStationViewport();
  902. }
  903. },
  904. // 重置所有风场视角
  905. resetAllStationViewport() {
  906. this.viewer.camera.flyTo({
  907. destination: Cesium.Cartesian3.fromDegrees(
  908. 114.502778,
  909. 35.326667,
  910. 3000000
  911. ),
  912. orientation: {
  913. heading: Cesium.Math.toRadians(0),
  914. pitch: Cesium.Math.toRadians(-90),
  915. roll: 0.0,
  916. },
  917. duration: 3.0,
  918. });
  919. },
  920. // 重置风场中所有风机视角
  921. resetWindViewport() {
  922. let fromLon = this.$route.query.longitude * 1;
  923. let fromLat = this.$route.query.latitude * 1;
  924. let fromheight = this.$route.query.height * 1;
  925. let fromname = this.$route.query.nameEn;
  926. // 设置镜头到指定的经纬度(度)、高度(米)
  927. this.viewer.camera.setView({
  928. destination: Cesium.Cartesian3.fromDegrees(
  929. fromLon, // 经度 (degrees)
  930. fromLat, // 纬度 (degrees)
  931. fromheight // 高度 (meters)
  932. ),
  933. orientation: {
  934. heading: Cesium.Math.toRadians(0.0), // 偏航角 (方向,0 指向北方)
  935. pitch: Cesium.Math.toRadians(-90.0), // 俯仰角 (-90 是垂直向下)
  936. roll: 0.0, // 翻滚角
  937. },
  938. });
  939. // 目标位置:经度、纬度、高度
  940. // const targetLon = 107.034945;
  941. // const targetLat = 37.309099;
  942. let targetLon = null;
  943. let targetLat = null;
  944. this.restLatLon.forEach((it) => {
  945. if (it.name === fromname) {
  946. targetLon = it.longitude;
  947. targetLat = it.latitude;
  948. }
  949. });
  950. const targetHeight = 5000;
  951. const draggableHeightTolerance = 5000; // 允许拖拽的高度范围:20,000 ~ 30,000
  952. const minHeight = 500; // 最低高度
  953. const maxHeight = 10000; // 最高高度
  954. const allowedOffsetDegrees = 2; // 允许拖拽的最大偏移(经纬度)
  955. const Lat3d = targetLat - 0.3;
  956. const minLon = targetLon - allowedOffsetDegrees;
  957. const maxLon = targetLon + allowedOffsetDegrees;
  958. const minLat = Lat3d - allowedOffsetDegrees;
  959. const maxLat = Lat3d + allowedOffsetDegrees;
  960. let that = this;
  961. that.viewer.camera.flyTo({
  962. destination: Cesium.Cartesian3.fromDegrees(
  963. targetLon,
  964. Lat3d,
  965. targetHeight
  966. ),
  967. // orientation: {
  968. // heading: Cesium.Math.toRadians(0),
  969. // pitch: Cesium.Math.toRadians(-90),
  970. // roll: 0.0,
  971. // },
  972. orientation: {
  973. heading: 0, //北京天安门角度 0 上海角度 1
  974. pitch: -0.3,
  975. roll: 0,
  976. },
  977. duration: 3.0,
  978. // complete: function () {
  979. // console.log("飞入完成,启用拖拽限制逻辑");
  980. // enableHeightBasedDragControl();
  981. // },
  982. });
  983. // ===== 控制逻辑:根据高度决定是否允许拖拽 =====
  984. function enableHeightBasedDragControl() {
  985. that.viewer.scene.preUpdate.addEventListener(function (scene, time) {
  986. const camera = that.viewer.camera;
  987. const posCartographic = camera.positionCartographic;
  988. if (!posCartographic) return;
  989. const currentHeight = posCartographic.height;
  990. that.currentHeight = currentHeight;
  991. const currentLon = Cesium.Math.toDegrees(posCartographic.longitude);
  992. const currentLat = Cesium.Math.toDegrees(posCartographic.latitude);
  993. // === 第一步:限制相机高度不能超出 [10000, 100000] ===
  994. if (currentHeight < minHeight) {
  995. // 强制拉高到 minHeight,但保持当前水平视角
  996. const newPos = Cesium.Cartesian3.fromDegrees(
  997. currentLon,
  998. currentLat,
  999. minHeight
  1000. );
  1001. camera.setView({
  1002. destination: newPos,
  1003. orientation: {
  1004. heading: camera.heading,
  1005. pitch: camera.pitch,
  1006. roll: camera.roll,
  1007. },
  1008. });
  1009. return; // 避免后续逻辑冲突
  1010. }
  1011. if (currentHeight > maxHeight) {
  1012. const newPos = Cesium.Cartesian3.fromDegrees(
  1013. currentLon,
  1014. currentLat,
  1015. maxHeight
  1016. );
  1017. camera.setView({
  1018. destination: newPos,
  1019. orientation: {
  1020. heading: camera.heading,
  1021. pitch: camera.pitch,
  1022. roll: camera.roll,
  1023. },
  1024. });
  1025. return;
  1026. }
  1027. // if (currentHeight < (maxHeight-700000)) {
  1028. // that.cancleAllLayer();
  1029. // }
  1030. // === 第二步:判断是否在“可拖拽高度区间” ===
  1031. const isInDraggableRange =
  1032. currentHeight >= targetHeight - draggableHeightTolerance &&
  1033. currentHeight <= targetHeight + draggableHeightTolerance;
  1034. if (isInDraggableRange) {
  1035. // ✅ 允许拖拽,但限制在指定范围内
  1036. const correctedLon = Cesium.Math.clamp(currentLon, minLon, maxLon);
  1037. const correctedLat = Cesium.Math.clamp(currentLat, minLat, maxLat);
  1038. if (
  1039. Math.abs(correctedLon - currentLon) > 1e-8 ||
  1040. Math.abs(correctedLat - currentLat) > 1e-8
  1041. ) {
  1042. // 越界了,纠正位置,保留当前高度和视角
  1043. camera.setView({
  1044. destination: Cesium.Cartesian3.fromDegrees(
  1045. correctedLon,
  1046. correctedLat,
  1047. currentHeight
  1048. ),
  1049. orientation: {
  1050. heading: camera.heading,
  1051. pitch: camera.pitch,
  1052. roll: camera.roll,
  1053. },
  1054. });
  1055. }
  1056. } else {
  1057. // ❌ 不在可拖拽高度:禁止平移,强制回正到目标点
  1058. const correctedPosition = Cesium.Cartesian3.fromDegrees(
  1059. targetLon,
  1060. targetLat,
  1061. currentHeight // 保留当前缩放高度
  1062. );
  1063. camera.setView({
  1064. destination: correctedPosition,
  1065. orientation: {
  1066. heading: camera.heading,
  1067. pitch: camera.pitch,
  1068. roll: camera.roll,
  1069. },
  1070. });
  1071. }
  1072. });
  1073. }
  1074. },
  1075. coverOnChange(val) {
  1076. if (val.value === "风场") {
  1077. this.switchWindLayer(val.check);
  1078. } else if (val.value === "云层") {
  1079. this.switchCloudLayer(val.check);
  1080. } else if (val.value === "降雨") {
  1081. this.switchRainLayer(val.check);
  1082. } else if (val.value === "温度") {
  1083. this.switchTemperatureLayerr(val.check);
  1084. }
  1085. },
  1086. // 提供控制函数以便在需要时停止循环
  1087. stopCycling(intervalId) {
  1088. if (intervalId) {
  1089. clearInterval(intervalId);
  1090. intervalId = null;
  1091. console.log("循环已停止");
  1092. }
  1093. },
  1094. // 切换风场图显隐
  1095. switchWindLayer() {
  1096. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1097. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1098. this.removeRainLayer();
  1099. this.stopCycling(this.rainintervalId);
  1100. }
  1101. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1102. this.removeCloudLayer();
  1103. this.stopCycling(this.cloudintervalId);
  1104. }
  1105. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1106. this.removeTemperatureLayer();
  1107. this.stopCycling(this.tempintervalId);
  1108. }
  1109. if (this.windLayer) {
  1110. this.removeWindLayer();
  1111. } else {
  1112. this.showWindLayer();
  1113. }
  1114. },
  1115. // 添加风场图
  1116. async showWindLayer() {
  1117. if (!this.windLayer) {
  1118. this.windLayer = new WindLayer(windGridData, {
  1119. particleSize: 2.0,
  1120. particleOpacity: 0.6,
  1121. particleSpeed: 0.01,
  1122. maxVelocity: 25, // 风速最大值(用于颜色映射和速度缩放)
  1123. minVelocity: 0, // 风速最小值阈值(低于此值不显示粒子)
  1124. colorScale: [
  1125. "rgb(36,104, 180)",
  1126. "rgb(60,157, 194)",
  1127. "rgb(128,205,193)",
  1128. "rgb(151,218,168)",
  1129. "rgb(198,231,181)",
  1130. "rgb(238,247,217)",
  1131. "rgb(255,238,159)",
  1132. "rgb(252,217,125)",
  1133. "rgb(255,182,100)",
  1134. "rgb(252,150,75)",
  1135. "rgb(250,112,52)",
  1136. "rgb(245,64,32)",
  1137. "rgb(237,45,28)",
  1138. "rgb(220,24,32)",
  1139. "rgb(180,0,35)",
  1140. ], // 颜色强度缩放
  1141. frameRate: 15,
  1142. fadeOpacity: 0.995,
  1143. particleAge: 150,
  1144. maxAge: 60,
  1145. globalAlpha: 0.8,
  1146. velocityScale: 1 / 30, // 粒子移动速度缩放因子(控制动画快慢)
  1147. paths: 500,
  1148. lineWidth: 2,
  1149. });
  1150. this.windLayer.addTo(this.viewer);
  1151. }
  1152. },
  1153. // 切换卫星云图显隐
  1154. switchCloudLayer(val) {
  1155. if (this.windLayer) {
  1156. this.removeWindLayer();
  1157. }
  1158. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1159. this.removeRainLayer();
  1160. this.stopCycling(this.rainintervalId);
  1161. }
  1162. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1163. this.removeTemperatureLayer();
  1164. this.stopCycling(this.tempintervalId);
  1165. }
  1166. if (!val || this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1167. this.removeCloudLayer();
  1168. this.stopCycling(this.cloudintervalId);
  1169. } else {
  1170. this.showCloudLayer();
  1171. }
  1172. },
  1173. // 切换降雨图显隐
  1174. switchRainLayer() {
  1175. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1176. if (this.windLayer) {
  1177. this.removeWindLayer();
  1178. }
  1179. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1180. this.removeCloudLayer();
  1181. this.stopCycling(this.cloudintervalId);
  1182. }
  1183. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1184. this.removeTemperatureLayer();
  1185. this.stopCycling(this.tempintervalId);
  1186. }
  1187. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1188. this.removeRainLayer();
  1189. this.stopCycling(this.rainintervalId);
  1190. } else {
  1191. this.showRainLayer();
  1192. }
  1193. },
  1194. // 切换温度图显隐
  1195. switchTemperatureLayerr() {
  1196. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1197. if (this.windLayer) {
  1198. this.removeWindLayer();
  1199. }
  1200. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1201. this.removeCloudLayer();
  1202. this.stopCycling(this.cloudintervalId);
  1203. }
  1204. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1205. this.removeRainLayer();
  1206. this.stopCycling(this.rainintervalId);
  1207. }
  1208. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1209. this.removeTemperatureLayer();
  1210. this.stopCycling(this.tempintervalId);
  1211. } else {
  1212. this.showTemperatureLayer();
  1213. }
  1214. },
  1215. // 显示云图
  1216. showCloudLayer() {
  1217. const imageUrls = [];
  1218. cloudJson.forEach((it) => {
  1219. imageUrls.push("/static" + it.path);
  1220. });
  1221. this.showeveryTypeImagesLayer(
  1222. imageUrls,
  1223. this.cloudintervalId,
  1224. this.cloudImagesLayer
  1225. );
  1226. },
  1227. //显示降雨图
  1228. showRainLayer() {
  1229. const imageUrls = [];
  1230. rainJson.forEach((it) => {
  1231. imageUrls.push("/static" + it.path);
  1232. });
  1233. this.showeveryTypeImagesLayer(
  1234. imageUrls,
  1235. this.rainintervalId,
  1236. this.rainImagesLayer
  1237. );
  1238. this.csceneElliposid(this.viewer, "rain");
  1239. },
  1240. //显示温度图
  1241. showTemperatureLayer() {
  1242. const imageUrls = [];
  1243. tempJson.forEach((it) => {
  1244. imageUrls.push("/static" + it.path);
  1245. });
  1246. this.showeveryTypeImagesLayer(
  1247. imageUrls,
  1248. this.tempintervalId,
  1249. this.tempImagesLayer
  1250. );
  1251. this.csceneElliposid(this.viewer, "temp");
  1252. },
  1253. async showeveryTypeImagesLayer(imageUrls, intervalId, ImagesLayers) {
  1254. // 存储所有图片图层的数组
  1255. let imageLayers = [];
  1256. // 当前显示的图片索引
  1257. let currentImageIndex = -1; // 初始为-1,表示没有图片显示
  1258. // 创建所有图片图层并添加到Viewer,初始时全部隐藏
  1259. await imageUrls.forEach((url) => {
  1260. const provider = new Cesium.SingleTileImageryProvider({
  1261. url: url,
  1262. // url: URL.createObjectURL(url),
  1263. rectangle: Cesium.Rectangle.fromDegrees(-180.0, -90.0, 180.0, 90.0), // 全球覆盖
  1264. tileWidth: 1440, // 根据你的图片实际宽度修改
  1265. tileHeight: 721,
  1266. // 如果你的图片只覆盖特定区域,请修改rectangle参数
  1267. });
  1268. const Layer = this.viewer.imageryLayers.addImageryProvider(provider);
  1269. Layer.alpha = 0.8; // 透明度
  1270. Layer.brightness = 1; // 亮度
  1271. Layer.contrast = 1; // 对比度
  1272. Layer.show = false; // 初始隐藏
  1273. imageLayers.push(Layer);
  1274. ImagesLayers.push(Layer);
  1275. });
  1276. function showNextImage() {
  1277. // 隐藏当前图片
  1278. if (currentImageIndex >= 0 && currentImageIndex < imageLayers.length) {
  1279. imageLayers[currentImageIndex].show = false;
  1280. }
  1281. // 计算下一张图片的索引
  1282. currentImageIndex = (currentImageIndex + 1) % imageLayers.length;
  1283. // 显示下一张图片
  1284. imageLayers[currentImageIndex].show = true;
  1285. // imageLayers[currentImageIndex + 1].show = true;
  1286. console.log("当前显示图片: " + imageUrls[currentImageIndex]);
  1287. }
  1288. // 设置切换间隔(毫秒),例如每5秒切换一次
  1289. const intervalMs = 5000;
  1290. intervalId = setInterval(showNextImage, intervalMs);
  1291. // 初始显示第一张图片
  1292. showNextImage();
  1293. },
  1294. // 移除风场图
  1295. removeWindLayer() {
  1296. if (this.windLayer) {
  1297. // this.windLayer.destroy();
  1298. this.windLayer.remove();
  1299. this.windLayer = null;
  1300. }
  1301. },
  1302. // 移除卫星云图
  1303. removeCloudLayer() {
  1304. if (this.cloudLayer) {
  1305. this.tagMsg = null;
  1306. this.viewer.imageryLayers.remove(this.cloudLayer);
  1307. this.cloudLayer = null;
  1308. }
  1309. if (this.cloudImagesLayer.length > 0) {
  1310. this.cloudImagesLayer.forEach((it) => {
  1311. this.viewer.imageryLayers.remove(it);
  1312. });
  1313. this.cloudImagesLayer = [];
  1314. }
  1315. if (this.imageryProviderV) {
  1316. this.viewer.imageryLayers.remove(this.imageryProviderV);
  1317. this.imageryProviderV = null;
  1318. }
  1319. },
  1320. // 移除降雨图
  1321. removeRainLayer() {
  1322. if (this.rainLayer) {
  1323. this.tagMsg = null;
  1324. this.viewer.imageryLayers.remove(this.rainLayer);
  1325. this.rainLayer = null;
  1326. this.setMapImageryProvider();
  1327. this.handlerAction.removeInputAction(
  1328. Cesium.ScreenSpaceEventType.LEFT_CLICK
  1329. );
  1330. }
  1331. if (this.rainImagesLayer.length > 0) {
  1332. this.rainImagesLayer.forEach((it) => {
  1333. this.viewer.imageryLayers.remove(it);
  1334. });
  1335. this.rainImagesLayer = [];
  1336. }
  1337. if (this.imageryProviderV) {
  1338. this.viewer.imageryLayers.remove(this.imageryProviderV);
  1339. this.imageryProviderV = null;
  1340. }
  1341. },
  1342. // 移除温度图
  1343. removeTemperatureLayer() {
  1344. if (this.temperatureLayer) {
  1345. this.tagMsg = null;
  1346. this.viewer.imageryLayers.remove(this.temperatureLayer);
  1347. this.temperatureLayer = null;
  1348. this.setMapImageryProvider();
  1349. this.handlerAction.removeInputAction(
  1350. Cesium.ScreenSpaceEventType.LEFT_CLICK
  1351. );
  1352. }
  1353. if (this.tempImagesLayer.length > 0) {
  1354. this.tempImagesLayer.forEach((it) => {
  1355. this.viewer.imageryLayers.remove(it);
  1356. });
  1357. this.tempImagesLayer = [];
  1358. }
  1359. if (this.imageryProviderV) {
  1360. this.viewer.imageryLayers.remove(this.imageryProviderV);
  1361. this.imageryProviderV = null;
  1362. }
  1363. },
  1364. //取消所有图层加载
  1365. cancleAllLayer() {
  1366. if (this.windLayer) {
  1367. this.removeWindLayer();
  1368. }
  1369. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1370. this.removeRainLayer();
  1371. this.stopCycling(this.rainintervalId);
  1372. }
  1373. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1374. this.removeCloudLayer();
  1375. this.stopCycling(this.cloudintervalId);
  1376. }
  1377. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1378. this.removeTemperatureLayer();
  1379. this.stopCycling(this.tempintervalId);
  1380. }
  1381. },
  1382. switchLayer() {
  1383. this.$router.push({
  1384. path: "/",
  1385. });
  1386. },
  1387. menuComTSty(val) {
  1388. this.menuComTStyB = val;
  1389. },
  1390. },
  1391. };
  1392. </script>
  1393. <style lang="less" scoped>
  1394. .dataLoading {
  1395. width: 100vw;
  1396. height: 100vh;
  1397. background: rgba(0, 0, 0, 0.5);
  1398. z-index: 999;
  1399. position: fixed;
  1400. .loadText {
  1401. position: absolute;
  1402. top: 50%;
  1403. left: 50%;
  1404. transform: translate(-50%, -50%);
  1405. background: rgba(255, 255, 255, 0.7);
  1406. padding: 15px 20px;
  1407. border-radius: 6px;
  1408. color: black;
  1409. font-size: 14px;
  1410. font-weight: bold;
  1411. }
  1412. }
  1413. .mapBox {
  1414. width: 100%;
  1415. height: 100%;
  1416. position: relative;
  1417. box-sizing: content-box;
  1418. overflow: hidden;
  1419. .menuComT {
  1420. position: fixed;
  1421. bottom: 400px;
  1422. left: 20px;
  1423. }
  1424. .menuComTSty {
  1425. position: fixed;
  1426. bottom: 20px;
  1427. left: 20px;
  1428. }
  1429. }
  1430. </style>
  1431. <style lang="less">
  1432. .el-overlay {
  1433. background-color: transparent !important;
  1434. .windModelDrawer {
  1435. width: 80% !important;
  1436. backdrop-filter: blur(15px) !important;
  1437. background: rgba(255, 255, 255, 0.8) !important;
  1438. border-radius: 10px 0 0 10px !important;
  1439. .el-drawer__body {
  1440. overflow: hidden;
  1441. padding-top: 0;
  1442. }
  1443. }
  1444. }
  1445. .windDrawerCla {
  1446. .line {
  1447. display: flex;
  1448. flex-direction: row;
  1449. align-items: center;
  1450. justify-content: space-between;
  1451. width: 100%;
  1452. margin-bottom: 10px;
  1453. .leftContent {
  1454. width: 242px;
  1455. height: 41px;
  1456. display: flex;
  1457. align-items: center;
  1458. background: url("@/assets/cesiumImg/title_left_bg.png") no-repeat;
  1459. span {
  1460. font-size: 16px;
  1461. font-family: Microsoft YaHei;
  1462. font-weight: 400;
  1463. color: #ffffff;
  1464. margin-left: 25px;
  1465. }
  1466. }
  1467. }
  1468. .jcxx,
  1469. .gzck {
  1470. height: 100%;
  1471. }
  1472. .spjk,
  1473. .third {
  1474. height: 80vh;
  1475. }
  1476. }
  1477. </style>