cesium.vue 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286
  1. <template>
  2. <div class="mapBox">
  3. <div id="cesiumContainer" style="width: 100%; height: 100vh"></div>
  4. <div class="menuComC" v-if="0">
  5. <!-- @handleWindspeed="switchWindLayer"
  6. @handleCloudLayer="switchCloudLayer"
  7. @handleRainLayer="switchRainLayer"
  8. @handleTemperatureLayerr="switchTemperatureLayerr"
  9. @handleCity="switchCity" -->
  10. <menuCom
  11. :showwindspeed="false"
  12. :showcloud="false"
  13. :showrainfall="false"
  14. :showtemperature="false"
  15. :showcity="false"
  16. @handleInit="initresetViewport"
  17. @handleBaseMap="setMapImageryProvider"
  18. @handleTopographicMap="switchTopographicMap"
  19. />
  20. </div>
  21. <div
  22. class="tag"
  23. :style="`left:${userClickLeft}px;top:${userClickTop}px`"
  24. v-if="tagMsg || tagMsg === ''"
  25. >
  26. <el-icon class="is-loading" v-if="tagMsg === ''">
  27. <Loading />
  28. </el-icon>
  29. <span v-else>{{ tagMsg || "" }}</span>
  30. </div>
  31. <!-- Loading 层 -->
  32. <div id="loading" class="dataLoading">加载中...</div>
  33. <!-- <div class="devInfoBox" v-if="showDevInfoBox">
  34. <div class="item">===&nbsp;帧率与内存&nbsp;===</div>
  35. <div class="item">运行帧率:&nbsp;{{ fps }}</div>
  36. <div class="item">响应时长:&nbsp;{{ ms }}</div>
  37. <div class="item">内存占用:&nbsp;{{ jsHeapSize }}</div>
  38. <template v-if="gVendor || gRenderer">
  39. <div class="item" style="margin-top: 12px">
  40. ====&nbsp;显卡信息&nbsp;====
  41. </div>
  42. <el-tooltip
  43. effect="dark"
  44. :content="gVendor"
  45. placement="top-end"
  46. v-if="gVendor"
  47. >
  48. <div class="item">制造商:&nbsp;{{ gVendor }}</div>
  49. </el-tooltip>
  50. <el-tooltip
  51. effect="dark"
  52. :content="gRenderer"
  53. placement="top-end"
  54. v-if="gRenderer"
  55. >
  56. <div class="item">型号:&nbsp;{{ gRenderer }}</div>
  57. </el-tooltip>
  58. </template>
  59. </div> -->
  60. <!-- <div class="tempImg" v-if="showtempImg">
  61. <img src="../assets/cesiumImg/temp.png" />
  62. </div> -->
  63. <el-drawer
  64. v-model="windDrawer"
  65. direction="rtl"
  66. class="windModelDrawer"
  67. :before-close="handleClose"
  68. >
  69. <template #header>
  70. <h3 style="font-weight: bold">{{ windDrawerHeader }}</h3>
  71. </template>
  72. <template #default>
  73. <div class="windDrawerCla">
  74. <div class="line" v-if="!showModelMsg">
  75. <div class="leftContent">
  76. <span>{{ windDrawerTitle }}</span>
  77. </div>
  78. </div>
  79. <div class="jcxx" v-if="showBasicMsg">
  80. <windHome :modelValItem="modelVal" />
  81. </div>
  82. <div class="spjk" v-if="showVideoMsg">
  83. <!-- src="https://www.bilibili.com/video/BV1421DYCELw?t=12.1" -->
  84. <iframe
  85. src="/public/static/windVideo.mp4"
  86. frameborder="0"
  87. style="width: 100%; height: 100%"
  88. ></iframe>
  89. <!-- src="https://www.bilibili.com/video/BV1421DYCELw?t=12.1" -->
  90. <video ref="videoPlayer" controls width="95%">
  91. <source src="/public/static/windVideo.mp4" type="video/mp4" />
  92. </video>
  93. </div>
  94. <div class="gzck" v-if="showProblemMsg">
  95. <windPro />
  96. </div>
  97. </div>
  98. </template>
  99. </el-drawer>
  100. <cesiumweatherView
  101. :viewer="viewer"
  102. :sidebarRightData="sidebarRightData"
  103. :stationValue="stationValue"
  104. @coverOnChange="coverOnChange"
  105. @changeType="changeType"
  106. v-if="showTypeViewer"
  107. />
  108. <cesiumwindView
  109. v-if="showWindDetail"
  110. @showDetail="menuComTSty"
  111. @backStations="backStations"
  112. />
  113. </div>
  114. </template>
  115. <script>
  116. // import * as Cesium from "../Cesium";
  117. // import "../Cesium/Widgets/widgets.css";
  118. import * as Cesium from "cesium";
  119. import "cesium/Build/Cesium/Widgets/widgets.css";
  120. import { createWind } from "../assets/wind/Windy.js";
  121. // import { parseGIF } from 'gifuct-js';
  122. // import { decompressFrames } from 'gifuct-js';
  123. import cesiumweatherView from "./weatherComponents/cesiumweatherView.vue";
  124. import jsonData from "./weatherComponents/weatherBase.json";
  125. import allStationJson from "./cesiumComponents/allStationJson.json";
  126. import fjLonLatJson from "./cesiumComponents/fjLonLat.json";
  127. import { WindLayer } from "cesium-wind";
  128. import output from "../assets/WechatIMG1693.jpg";
  129. import rainoutput from "../assets/rainoutput.jpg";
  130. import cloudJson from "/public/static/exportData/cloud/layer.json";
  131. import rainJson from "/public/static/exportData/rain/layer.json";
  132. import tempJson from "/public/static/exportData/tmp/layer.json";
  133. import windJson from "/public/static/exportData/wind/layer.json";
  134. import basicGeoJson from "../assets/geoJson/basic.json";
  135. import windLineJson from "./cesiumComponents/windspeed.json";
  136. import windGridData from "./cesiumComponents/windGridData.json";
  137. import axios from "axios";
  138. import ModelUnpack from "@/components/modelUnpack.vue";
  139. import menuCom from "./menuCom.vue";
  140. import { getTempData, getBaiduTempData, getWWTempData } from "@/api/index.js";
  141. //风场展示图标
  142. import fc from "@/assets/windimgs/fanSvg/feng.gif";
  143. // import fc1 from "@/assets/windimgs/fanSvg/feng.mp4"
  144. //火电展示图标
  145. import hd from "@/assets/windimgs/fanSvg/huo.gif";
  146. // import hd1 from "@/assets/windimgs/fanSvg/huo.mp4"
  147. //光伏电站展示图标
  148. import gf from "@/assets/windimgs/fanSvg/guang.gif";
  149. // import gf1 from "@/assets/windimgs/fanSvg/guang.mp4"
  150. //储能展示图标
  151. import chu from "@/assets/windimgs/fanSvg/chu.gif";
  152. // import chu1 from "@/assets/windimgs/fanSvg/chu.mp4"
  153. //待机
  154. import dj from "@/assets/windimgs/fanSvg/dj.svg";
  155. //动图使用柱子和扇叶
  156. import bwzhu from "@/assets/windimgs/fanSvg/bwzhu.svg";
  157. import bwshan from "@/assets/windimgs/fanSvg/bwshan.png";
  158. import windHome from "@/components/windHome/index.vue";
  159. import windPro from "@/components/windProDetail/windProblem.vue";
  160. import cesiumwindView from "./cesiumComponents/cesiumwindView.vue";
  161. export default {
  162. name: "CesiumMap",
  163. components: {
  164. ModelUnpack,
  165. menuCom,
  166. cesiumweatherView,
  167. windHome,
  168. windPro,
  169. cesiumwindView,
  170. },
  171. data() {
  172. return {
  173. modelUnpackType: "fengji",
  174. checkMode: false, // 调试模式
  175. allyShow: true,
  176. viewer: null,
  177. windLayer: null, // 风场图
  178. windLayerTimmer: null, // 风场图计时器
  179. cloudImagesLayer: [], // 卫星云图
  180. cloudLayer: null, // 卫星云图
  181. cloudintervalId: null,
  182. rainImagesLayer: [], // 降雨图
  183. rainLayer: null, // 降雨图
  184. rainintervalId: null, // 降雨图
  185. tempImagesLayer: [], //温度图
  186. temperatureLayer: null, //温度图
  187. tempintervalId: null, //温度图
  188. basicMapId: "gaodeyingxiang", // 地球底图 ID
  189. basicMapList: [
  190. {
  191. id: "gaodeyingxiang",
  192. name: "高德影像地图",
  193. url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
  194. minimumLevel: 3,
  195. maximumLevel: 17,
  196. credit: "basicMap",
  197. },
  198. {
  199. id: "gaodeshiliang",
  200. name: "高德矢量地图",
  201. url: "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
  202. minimumLevel: 3,
  203. maximumLevel: 17,
  204. credit: "basicMap",
  205. },
  206. {
  207. id: "carto",
  208. name: "Carto地图",
  209. url: "http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
  210. credit: "basicMap",
  211. },
  212. ],
  213. earthLayer: [],
  214. userClickLeft: 0,
  215. userClickTop: 0,
  216. tagMsg: null,
  217. systemInfoTimmer: null,
  218. showDevInfoBox: true,
  219. fps: "", // 设备帧率
  220. ms: "", // 设备响应时间
  221. jsHeapSize: "", // 内存占用
  222. gVendor: "",
  223. gRenderer: "",
  224. labelLayer: null, // 城市名称 label 集合
  225. loadDone: false, // 地球首次加载滚动到 reset 位置是否完成
  226. showtempImg: false,
  227. handlerAction: null,
  228. sidebarRightData: null,
  229. imageryProviderV: null,
  230. isWindVisible: true,
  231. minSpeed: 0,
  232. maxSpeed: 0,
  233. showcomModelDia: false,
  234. modelVal: null,
  235. menuComTStyB: false,
  236. modelUnpackType: "fengji",
  237. windDrawer: false,
  238. windDrawerTitle: "",
  239. windDrawerHeader: "",
  240. showBasicMsg: false,
  241. showVideoMsg: false,
  242. showProblemMsg: false,
  243. showModelMsg: false,
  244. showWindDetail: false,
  245. allStationentitys: [],
  246. allWindEntitys: [],
  247. showTypeViewer: true,
  248. stationValue: [],
  249. };
  250. },
  251. created() {
  252. this.getWeatherData();
  253. },
  254. mounted() {
  255. this.initEventListener();
  256. this.initCesium();
  257. if (this.showDevInfoBox) {
  258. this.initSystemInfo();
  259. }
  260. // this.getData();
  261. // this.test();
  262. },
  263. unmounted() {
  264. if (this.windLayer !== null) {
  265. this.windLayer.destroy();
  266. this.windLayer = null;
  267. }
  268. clearInterval(this.systemInfoTimmer);
  269. this.systemInfoTimmer = null;
  270. },
  271. methods: {
  272. changeType(value) {
  273. this.stationValue = value;
  274. this.allStationentitys.forEach(({ entity, handler }) => {
  275. this.viewer.entities.remove(entity); // 移除实体
  276. if (!handler.isDestroyed()) {
  277. handler.destroy(); // 销毁事件处理器(关键!)
  278. }
  279. });
  280. this.allStationentitys = [];
  281. let showStation = [];
  282. value.forEach((it) => {
  283. allStationJson.station.forEach((ic) => {
  284. if (it === ic.energytype) {
  285. showStation.push(ic);
  286. }
  287. });
  288. });
  289. this.showAllStation(this.viewer, showStation);
  290. },
  291. coverOnChange(val) {
  292. if (val.value === "风场") {
  293. this.switchWindLayer(val.check);
  294. } else if (val.value === "云层") {
  295. this.switchCloudLayer(val.check);
  296. } else if (val.value === "降雨") {
  297. this.switchRainLayer(val.check);
  298. } else if (val.value === "温度") {
  299. this.switchTemperatureLayerr(val.check);
  300. }
  301. },
  302. test() {
  303. axios
  304. // .get("http://localhost:3007/api/test", {
  305. .post("http://localhost:3007/weather/getWeatherWindData", {
  306. longitude: 106.169866,
  307. latitude: 38.46637,
  308. level: "surface",
  309. variable: "PRATE,PRES,SUNSD,VEG,VIS,VRATE,LCDC",
  310. })
  311. .then((res) => {
  312. console.log(11111, res.data);
  313. });
  314. },
  315. async getWeatherData() {
  316. let that = this;
  317. await that.weatherJsonChange(jsonData.data);
  318. },
  319. weatherJsonChange(datas) {
  320. let obj = {
  321. base: datas.base,
  322. weather: datas.weather,
  323. zhishu: datas.recommend_zhishu,
  324. pm25: datas.ps_pm25,
  325. position: datas.position,
  326. // fiftyDay: datas['15_day_forecast'],
  327. "15tq": datas["15_day_forecast"].info,
  328. "24tg": datas["24_hour_forecast"].info,
  329. "40tq": datas["long_day_forecast"].info,
  330. feature: datas.feature,
  331. };
  332. this.sidebarRightData = obj;
  333. },
  334. getData() {
  335. getWWTempData().then((res) => {
  336. console.log("res===>>>", res);
  337. });
  338. },
  339. // 初始化一些监听事件
  340. initEventListener() {
  341. const mapBox = document.querySelector(".mapBox");
  342. mapBox.addEventListener("click", (e) => {
  343. const rect = mapBox.getBoundingClientRect();
  344. this.userClickLeft = (e.clientX - rect.left).toFixed(0);
  345. this.userClickTop = (e.clientY - rect.top + 20).toFixed(0);
  346. });
  347. },
  348. // 初始化地球
  349. async initCesium() {
  350. // 需要从 https://cesium.com/ion/signup 获取
  351. Cesium.Ion.defaultAccessToken =
  352. "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwYTQwNDk3MC05YTZkLTQ2ZTEtODc0MS1lZTFkYjFlOTFmNmQiLCJpZCI6MTcyNDQ1LCJpYXQiOjE3NTQ4ODA4MzF9.KnhENYiHxNwTkhTWRA-lHqG59coLVT2FsIyOru2TV3E";
  353. // 修改 Cesium 默认地图视角为宁夏,狗东西没效果不知道为什么
  354. // Cesium.Camera.DEFAULT_VIEW_RACTANGLE = Cesium.Rectangle.fromDegrees(
  355. // 104.17,
  356. // 35.14,
  357. // 107.72,
  358. // 39.23
  359. // );
  360. // const cesiumOptions = {
  361. // geocoder: false, // 地址搜索控件
  362. // homeButton: false, // 返回地图初始位置控件
  363. // infoBox: false, // 地图默认的信息控件
  364. // sceneModePicker: false, // 场景模式切换控件
  365. // baseLayerPicker: false, // 底图切换控件
  366. // navigationHelpButton: false, // 帮助控件
  367. // animation: false, // 动画控制控件
  368. // timeline: false, // 时间线控件
  369. // fullscreenButton: false, // 全屏按钮控件
  370. // // imageryProvider: false, // 是否显示 Cesium 默认地图的底图
  371. // vrButton: false,
  372. // selectionIndicator: false,
  373. // shouldAnimate: true,
  374. // // terrain: Cesium.Terrain.fromWorldTerrain(),
  375. // // terrainProvider: new Cesium.CesiumTerrainProvider({
  376. // // url: "/static/layer.json", // 对应 public/terrain-data 目录
  377. // // requestVertexNormals: true, // 保留法线数据(光照效果)
  378. // // requestWaterMask: false, // 本地地形通常无水面效果(需自定义)
  379. // // }),
  380. // };
  381. // const viewer = new Cesium.Viewer("cesiumContainer", cesiumOptions);
  382. const cesiumOptions = {
  383. // terrainProvider: Cesium.createWorldTerrain(),
  384. baseLayerPicker: false, //是否显示底图切换按钮
  385. animation: false, //是否显示动画控制按钮
  386. vrButton: false,
  387. geocoder: false, //是否显示地理编码按钮
  388. homeButton: false, //是否显示地图导航按钮
  389. infoBox: false,
  390. sceneModePicker: false, //是否显示场景模式切换按钮
  391. selectionIndicator: false,
  392. timeline: false, //是否显示时间轴
  393. fullscreenButton: false, //是否显示全屏按钮
  394. navigationHelpButton: false,
  395. shouldAnimate: true,
  396. imageryProvider: false, //控制默认底图的显示
  397. // terrain: Cesium.Terrain.fromWorldTerrain(),
  398. };
  399. const viewer = new Cesium.Viewer("cesiumContainer", cesiumOptions);
  400. // 隐藏 Cesium Logo
  401. viewer.cesiumWidget.creditContainer.style.display = "none";
  402. viewer.scene.globe.baseColor = Cesium.Color.BLACK;
  403. this.viewer = viewer;
  404. // this.setMapImageryProvider();
  405. this.initCesiumTerrain();
  406. this.initCesiumBaseMapImage();
  407. // this.switchWindLayer();
  408. this.showAllStation(viewer, allStationJson.station);
  409. this.initGeoJsonData();
  410. },
  411. // 初始化地形
  412. async initCesiumTerrain() {
  413. const terrainProvider = await Cesium.CesiumTerrainProvider.fromUrl(
  414. // "http://localhost:3007/tiles/dixing",
  415. "./public/static/dixing",
  416. {
  417. requestWaterMask: true, // 如果需要水效果,设置为true
  418. requestVertexNormals: true, // 如果需要光照效果,设置为true
  419. requestMetadata: true, // 请求元数据
  420. // minimumLevel: 10,
  421. maximumLevel: 15,
  422. }
  423. );
  424. this.viewer.scene.verticalExaggeration = 14.0;
  425. this.viewer.scene.verticalExaggerationRelativeHeight = 2400.0;
  426. this.viewer.terrainProvider = terrainProvider;
  427. // const terrainProvider = new Cesium.CesiumTerrainProvider({
  428. // url: "http://localhost:3007/tiles/dixing", // 地形数据所在目录的URL
  429. // requestWaterMask: true, // 如果需要水效果,设置为true
  430. // requestVertexNormals: true, // 如果需要光照效果,设置为true
  431. // minimumLevel: 10,
  432. // maximumLevel: 15,
  433. // });
  434. // this.viewer.terrainProvider = terrainProvider;
  435. // console.log(111, terrainProvider);
  436. // const imageryProvider = new Cesium.UrlTemplateImageryProvider({
  437. // url: "http://localhost:3007/tiles/dixing/{z}/{x}/{y}",
  438. // // url: "./public/static/dixing/{z}/{x}/{y}.terrain",
  439. // credit: "地形数据",
  440. // });
  441. // this.viewer.imageryLayers.addImageryProvider(imageryProvider);
  442. // this.viewer.scene.globe.depthTestAgainstTerrain = true; //地形遮挡效果开关,打开后 地形会遮挡看不到的区域
  443. // this.viewer.scene.globe.enableLighting = true; //对大气和雾启用动态照明效
  444. },
  445. // 初始化底图
  446. async initCesiumBaseMapImage() {
  447. const imageryProvider = await new Cesium.UrlTemplateImageryProvider({
  448. url: "https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
  449. // url: "http://localhost:3007/tiles/map/{z}/{x}/{y}",
  450. // url: "./public/static/ditu/{z}/{x}/{y}.png",
  451. credit: "影像地图",
  452. });
  453. imageryProvider.alpha = 0.55; // 透明度
  454. imageryProvider.brightness = 1; // 亮度
  455. imageryProvider.contrast = 1; // 对比度
  456. this.viewer.imageryLayers.addImageryProvider(imageryProvider);
  457. },
  458. // 初始化城市
  459. async initCityCesium() {
  460. Cesium.Ion.defaultAccessToken =
  461. "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIwYTQwNDk3MC05YTZkLTQ2ZTEtODc0MS1lZTFkYjFlOTFmNmQiLCJpZCI6MTcyNDQ1LCJpYXQiOjE3NTQ4ODA4MzF9.KnhENYiHxNwTkhTWRA-lHqG59coLVT2FsIyOru2TV3E";
  462. const cesiumOptions = {
  463. geocoder: false, // 地址搜索控件
  464. homeButton: false, // 返回地图初始位置控件
  465. infoBox: false, // 地图默认的信息控件
  466. sceneModePicker: false, // 场景模式切换控件
  467. baseLayerPicker: false, // 底图切换控件
  468. navigationHelpButton: false, // 帮助控件
  469. animation: false, // 动画控制控件
  470. timeline: false, // 时间线控件
  471. fullscreenButton: false, // 全屏按钮控件
  472. // imageryProvider: false, // 是否显示 Cesium 默认地图的底图
  473. vrButton: false,
  474. selectionIndicator: false,
  475. shouldAnimate: true,
  476. terrain: Cesium.Terrain.fromWorldTerrain(),
  477. // terrainProvider: new Cesium.CesiumTerrainProvider({
  478. // url: "/static/layer.json", // 对应 public/terrain-data 目录
  479. // requestVertexNormals: true, // 保留法线数据(光照效果)
  480. // requestWaterMask: false, // 本地地形通常无水面效果(需自定义)
  481. // }),
  482. };
  483. const viewer = new Cesium.Viewer("cesiumContainer", cesiumOptions);
  484. // 隐藏 Cesium Logo
  485. viewer.cesiumWidget.creditContainer.style.display = "none";
  486. this.viewer = viewer;
  487. // this.setMapImageryProvider();
  488. this.initGeoJsonData();
  489. },
  490. // 初始化Cesium内部鼠标事件
  491. initEventInputAction() {
  492. this.$nextTick(() => {
  493. const viewer = this.viewer;
  494. // 添加点击事件显示坐标
  495. viewer.screenSpaceEventHandler.setInputAction((movement) => {
  496. const ray = this.viewer.camera.getPickRay(movement.position);
  497. if (!ray) {
  498. this.tagMsg = null;
  499. console.log("无法获取射线");
  500. return;
  501. }
  502. const position = this.viewer.scene.globe.pick(ray, this.viewer.scene);
  503. if (!position) {
  504. this.tagMsg = null;
  505. console.log("未找到地球表面交点");
  506. return;
  507. }
  508. const cartographic = Cesium.Cartographic.fromCartesian(position);
  509. if (!cartographic) {
  510. this.tagMsg = null;
  511. console.log("坐标转换失败");
  512. return;
  513. }
  514. this.getClickCloudOpacity(cartographic);
  515. this.getLocationData(cartographic);
  516. return;
  517. const cartesian = viewer.camera.pickEllipsoid(
  518. movement.position,
  519. viewer.scene.globe.ellipsoid
  520. );
  521. if (cartesian) {
  522. const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
  523. const lon = Cesium.Math.toDegrees(cartographic.longitude).toFixed(
  524. 5
  525. );
  526. const lat = Cesium.Math.toDegrees(cartographic.latitude).toFixed(5);
  527. viewer.entities.removeAll();
  528. viewer.entities.add({
  529. position: cartesian,
  530. point: {
  531. pixelSize: 10,
  532. color: Cesium.Color.RED,
  533. },
  534. label: {
  535. text: `经度: ${lon}°, 纬度: ${lat}°`,
  536. font: '16px "Microsoft YaHei"',
  537. fillColor: Cesium.Color.WHITE,
  538. outlineColor: Cesium.Color.BLACK,
  539. outlineWidth: 2,
  540. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  541. pixelOffset: new Cesium.Cartesian2(0, -30),
  542. },
  543. });
  544. }
  545. }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  546. // 监听鼠标滚轮事件
  547. viewer.screenSpaceEventHandler.setInputAction((wheelment) => {
  548. this.tagMsg = null;
  549. }, Cesium.ScreenSpaceEventType.WHEEL);
  550. viewer.scene.camera.moveEnd.addEventListener(() => {
  551. //获取当前相机高度
  552. // const height = Math.ceil(earth.camera.positionCartographic.height);
  553. });
  554. // 监听鼠标移动事件
  555. // viewer.screenSpaceEventHandler.setInputAction((movement) => {
  556. // this.getHoverCityLabel(movement);
  557. // }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  558. });
  559. },
  560. // 获取鼠标划过位置的城市名称 label
  561. getHoverCityLabel(movement) {
  562. const picked = this.viewer.scene.pick(movement.startPosition);
  563. const label = picked ? picked.primitive : null;
  564. if (label) {
  565. this.viewer.scene.canvas.style.cursor = "pointer";
  566. } else {
  567. this.viewer.scene.canvas.style.cursor = "default";
  568. }
  569. this.labelLayer._labels.forEach((ele) => {
  570. if (ele.id === label?.id) {
  571. label.fillColor = Cesium.Color.YELLOW;
  572. label.outlineColor = Cesium.Color.BLACK;
  573. } else {
  574. ele.fillColor = Cesium.Color.fromCssColorString("#000");
  575. ele.outlineColor = Cesium.Color.WHITE;
  576. }
  577. });
  578. },
  579. // 根据经纬度获取周围一定范围城市基础信息
  580. getLocationData({ latitude, longitude }) {
  581. const lat = Cesium.Math.toDegrees(latitude);
  582. const lon = Cesium.Math.toDegrees(longitude);
  583. axios
  584. .get(
  585. `/ventusky/ventusky_location.json.php?lat=${lat}&lon=${lon}&zoom=${this.getZoomLevel()}`
  586. )
  587. .then((res) => {
  588. console.log(111, res.data);
  589. // https://api.waqi.info/feed/geo:35.3286804492;108.9025100708/?token=904a1bc6edf77c428347f2fe54cf663bcffaec21
  590. // res.data.city
  591. });
  592. },
  593. // 获取 zoom 级别
  594. getZoomLevel() {
  595. let zoomLevel = 0;
  596. const tilesToRender = this.viewer.scene.globe._surface._tilesToRender;
  597. if (tilesToRender.length !== 0) {
  598. zoomLevel = tilesToRender[0].level;
  599. }
  600. return zoomLevel - 1;
  601. },
  602. // 初始化性能监控
  603. async initSystemInfo() {
  604. this.viewer.scene.debugShowFramesPerSecond = false;
  605. // 性能监控变量
  606. let lastFrameTime = performance.now();
  607. let frameCount = 0;
  608. let fps = 0;
  609. let frameTime = 0;
  610. // 获取帧率与响应时长
  611. this.viewer.scene.postRender.addEventListener(() => {
  612. const now = performance.now();
  613. const delta = now - lastFrameTime;
  614. frameCount++;
  615. // 每秒更新一次数据(避免更新太频繁)
  616. if (delta >= 1000) {
  617. fps = Math.round((frameCount * 1000) / delta);
  618. frameTime = delta / frameCount;
  619. // 更新显示
  620. this.fps = `${fps} FPS`;
  621. this.ms = `${frameTime.toFixed(1) + " ms"}`;
  622. // 重置计数器
  623. frameCount = 0;
  624. lastFrameTime = now;
  625. }
  626. });
  627. // 获取内存占用
  628. if (window.performance && performance.memory) {
  629. const jsHeapSize = performance.memory.usedJSHeapSize / 1048576;
  630. this.jsHeapSize = `${parseInt(jsHeapSize)} MB`;
  631. // const memory = performance.memory;
  632. // console.log("已分配堆内存:", memory.totalJSHeapSize / 1048576 + " MB");
  633. // console.log("已使用堆内存:", memory.usedJSHeapSize / 1048576 + " MB");
  634. // console.log("堆内存限制:", memory.jsHeapSizeLimit / 1048576 + " MB");
  635. }
  636. // 获取显卡信息
  637. const canvas = document.createElement("canvas");
  638. const gl = canvas.getContext("webgl");
  639. if (gl) {
  640. const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
  641. if (debugInfo) {
  642. const gVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
  643. const gRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
  644. // console.log("GPU厂商:", gVendor);
  645. // console.log("GPU型号:", gRenderer);
  646. this.gVendor = gVendor;
  647. this.gRenderer = gRenderer.split(",")?.[1]
  648. ? gRenderer.split(",")?.[1]
  649. : "";
  650. }
  651. }
  652. },
  653. getClickCloudOpacity(cartographic) {
  654. try {
  655. const level = this.calculateTileLevel(this.viewer);
  656. const tilingScheme = new Cesium.WebMercatorTilingScheme();
  657. // 确保 tilingScheme 有 positionToTileXY 方法
  658. if (!tilingScheme.positionToTileXY) {
  659. console.error("tilingScheme没有positionToTileXY方法");
  660. return;
  661. }
  662. if (!this.cloudLayer) {
  663. this.tagMsg = null;
  664. } else {
  665. this.tagMsg = "";
  666. }
  667. const tileXY = tilingScheme.positionToTileXY(cartographic, level);
  668. this.checkMode &&
  669. console.log(`瓦片坐标: 级别=${level}, X=${tileXY.x}, Y=${tileXY.y}`);
  670. const clickTileUrl = this.replaceTemplate(
  671. "https://tile.openweathermap.org/map/clouds_new/{z}/{x}/{y}.png?appid=3b66d35579770393051599f8d518df4a",
  672. level,
  673. tileXY
  674. );
  675. this.checkMode && console.log(`用户点击位置瓦片url: ${clickTileUrl}`);
  676. // 存储当前瓦片信息
  677. const layer = this.viewer.imageryLayers.get(0);
  678. const provider = layer.imageryProvider;
  679. const currentTile = {
  680. x: tileXY.x,
  681. y: tileXY.y,
  682. level: level,
  683. rectangle: tilingScheme.tileXYToRectangle(tileXY.x, tileXY.y, level),
  684. size: {
  685. width: provider.tileWidth || 256,
  686. height: provider.tileHeight || 256,
  687. },
  688. };
  689. // 计算并显示在瓦片内的位置
  690. const clickPos = this.calculateTilePosition(cartographic, currentTile);
  691. if (this.cloudLayer) {
  692. this.getTileImageOpacity(
  693. clickTileUrl,
  694. clickPos.pixelX,
  695. clickPos.pixelY
  696. ).then((imgSource) => {
  697. const {
  698. rawAlpha, // 原始透明度值 (0-255)
  699. alphaPercentage, // 透明度百分比
  700. whiteScore, // 白色程度得分 (0-100)
  701. } = imgSource;
  702. this.checkMode && console.log(111, alphaPercentage);
  703. this.tagMsg = `${alphaPercentage * 2}%`;
  704. });
  705. }
  706. if (this.checkMode) {
  707. const tileRectangle = tilingScheme.tileXYToRectangle(
  708. tileXY.x,
  709. tileXY.y,
  710. level
  711. );
  712. this.highlightTile(viewer, tileRectangle);
  713. }
  714. } catch (error) {
  715. console.error("获取瓦片时出错:", error);
  716. }
  717. },
  718. // 计算瓦片级别的辅助函数
  719. calculateTileLevel(viewer) {
  720. // 方法1:根据相机高度估算
  721. const height = viewer.camera.positionCartographic.height;
  722. if (!height) return 12; // 默认值
  723. // 高度与级别的近似关系(根据实际需求调整)
  724. const level = Math.floor(20 - Math.log(height / 1000) / Math.log(2));
  725. return Math.max(0, Math.min(18, level)); // 限制在0-18级之间
  726. // 方法2:使用当前视图的细节层次
  727. // return viewer.scene.globe.maximumScreenSpaceError;
  728. },
  729. // 高亮显示瓦片的辅助函数
  730. highlightTile(viewer, rectangle) {
  731. // 移除之前的高亮
  732. viewer.entities.removeById("highlighted-tile");
  733. // 添加新的高亮
  734. viewer.entities.add({
  735. id: "highlighted-tile",
  736. rectangle: {
  737. coordinates: rectangle,
  738. material: Cesium.Color.RED.withAlpha(0.3),
  739. outline: true,
  740. outlineColor: Cesium.Color.RED,
  741. outlineWidth: 2,
  742. },
  743. });
  744. },
  745. // 计算在瓦片内的位置
  746. calculateTilePosition(cartographic, tile) {
  747. const rect = tile.rectangle;
  748. const size = tile.size;
  749. // 计算在瓦片内的归一化位置
  750. const lonNormalized =
  751. (cartographic.longitude - rect.west) / (rect.east - rect.west);
  752. const latNormalized =
  753. (cartographic.latitude - rect.south) / (rect.north - rect.south);
  754. // 转换为像素坐标(原点在左上角)
  755. const pixelX = Math.floor(lonNormalized * size.width);
  756. const pixelY = Math.floor((1 - latNormalized) * size.height); // 翻转Y轴
  757. this.checkMode && console.log(`left:${pixelX},top:${pixelY}`);
  758. return { pixelX, pixelY };
  759. },
  760. // canvas 获取地图瓦片颜色信息
  761. async getTileImageOpacity(imageUrl, left, top) {
  762. // 1. 创建临时图像加载网络图片
  763. const img = new Image();
  764. img.crossOrigin = "Anonymous"; // 解决跨域问题
  765. img.src = imageUrl;
  766. // 2. 图片加载完成后处理
  767. await new Promise((resolve) => (img.onload = resolve));
  768. // 3. 创建Canvas并绘制图像
  769. const canvas = document.createElement("canvas");
  770. canvas.width = img.width;
  771. canvas.height = img.height;
  772. const ctx = canvas.getContext("2d");
  773. ctx.drawImage(img, 0, 0);
  774. // 4. 获取鼠标点击位置的像素数据
  775. const pixelData = ctx.getImageData(left, top, 1, 1).data;
  776. const [r, g, b, alpha] = pixelData;
  777. // 5. 计算白色程度(RGB接近255的程度)
  778. const whiteRatio = (r + g + b) / (3 * 255); // RGB平均值归一化
  779. const whiteScore = Math.round(whiteRatio * 100); // 映射到0-100
  780. // 6. 返回结果
  781. return {
  782. rawAlpha: alpha, // 原始透明度值 (0-255)
  783. alphaPercentage: Math.round((alpha / 255) * 100), // 透明度百分比
  784. whiteScore: whiteScore, // 白色程度得分 (0-100)
  785. };
  786. },
  787. // 替换底图 xyz 值为鼠标点击位置的值并返回
  788. replaceTemplate(str, level, tileXY) {
  789. return str.replace(/\{([xyz])\}/g, (match, key) => {
  790. switch (key) {
  791. case "x":
  792. return tileXY.x;
  793. case "y":
  794. return tileXY.y;
  795. case "z":
  796. return level;
  797. default:
  798. return match; // 未匹配时返回原内容(理论上不会执行)
  799. }
  800. });
  801. },
  802. // 设置地球底图
  803. setMapImageryProvider(val) {
  804. if (this.imageryProvider) {
  805. this.viewer.imageryLayers.remove(this.imageryProvider);
  806. this.imageryProvider = null;
  807. }
  808. let imageryProvider = null;
  809. if (val) {
  810. imageryProvider = val;
  811. } else {
  812. imageryProvider = this.basicMapList.find((ele) => {
  813. return ele.id === this.basicMapId;
  814. });
  815. }
  816. this.imageryProvider = new Cesium.UrlTemplateImageryProvider(
  817. imageryProvider
  818. );
  819. // 添加底图
  820. this.viewer.imageryLayers.addImageryProvider(this.imageryProvider);
  821. },
  822. // 初始化 geoJson 数据
  823. async initGeoJsonData() {
  824. // 创建GeoJSON数据源
  825. await new Cesium.GeoJsonDataSource.load(basicGeoJson, {
  826. stroke: Cesium.Color.GRAY, // 边界线颜色
  827. fill: Cesium.Color.BLACK.withAlpha(0), // 填充颜色
  828. strokeWidth: 1, // 边界线宽度
  829. markerSymbol: "?", // 点要素的符号
  830. clampToGround: true, // 贴地
  831. height: 0,
  832. }).then((dataSource) => {
  833. // 添加到视图
  834. this.viewer.dataSources.add(dataSource);
  835. var entities = dataSource.entities.values;
  836. for (let i = 0; i < entities.length; i++) {
  837. let entity = entities[i];
  838. entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()).positions;
  839. //单独设置线条样式
  840. var positions = entity.polygon.hierarchy._value.positions;
  841. entity.polyline = {
  842. positions: positions,
  843. width: 2,
  844. outline: false,
  845. clampToGround: true,
  846. };
  847. }
  848. // 添加中文标签图层
  849. const labelLayer = new Cesium.LabelCollection();
  850. this.viewer.scene.primitives.add(labelLayer);
  851. const cities = [];
  852. basicGeoJson?.features?.forEach((ele) => {
  853. if (Array.isArray(ele.properties.centroid)) {
  854. const name = ele.properties.name;
  855. const lon = ele.properties.centroid[0];
  856. const lat = ele.properties.centroid[1];
  857. cities.push({ name, lon, lat });
  858. }
  859. });
  860. cities.forEach((city, index) => {
  861. labelLayer.add({
  862. id: index,
  863. name: "cityLabel",
  864. position: Cesium.Cartesian3.fromDegrees(city.lon, city.lat, 10),
  865. text: city.name,
  866. font: 'bold 14px "Microsoft YaHei", sans-serif',
  867. fillColor: Cesium.Color.fromCssColorString("#000"),
  868. outlineColor: Cesium.Color.WHITE,
  869. outlineWidth: 2,
  870. style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  871. pixelOffset: new Cesium.Cartesian2(0, 0), // 设置为0
  872. horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 水平居中
  873. verticalOrigin: Cesium.VerticalOrigin.CENTER, // 垂直居中
  874. });
  875. });
  876. this.labelLayer = labelLayer;
  877. // this.csceneElliposid(this.viewer, null)
  878. // this.resetViewport1();
  879. });
  880. },
  881. initresetViewport() {
  882. this.resetViewport1();
  883. },
  884. // 城市视角重置视角
  885. resetViewport(height = 0, callback) {
  886. // 设置初始视图为宁夏
  887. const that = this;
  888. this.viewer.camera.flyTo({
  889. destination: Cesium.Cartesian3.fromDegrees(
  890. //宁夏银川
  891. // 106.169866,
  892. // 38.46637,
  893. // height || 10000
  894. //北京
  895. 116.391586,
  896. 39.898832,
  897. height || 500
  898. // 106.2,
  899. // 38.467,
  900. // 15000.0
  901. // 113.3191,
  902. // 23.109,
  903. // 1000
  904. //上海
  905. // 121.47,
  906. // 31.23,
  907. // 1000
  908. ),
  909. // orientation: {
  910. // heading: 0, //北京天安门角度 0 上海角度 1
  911. // pitch: -0.2,
  912. // roll: 0,
  913. // },
  914. duration: 3,
  915. // orientation: {
  916. // heading: Cesium.Math.toRadians(0),
  917. // pitch: Cesium.Math.toRadians(-90),
  918. // roll: 0.0,
  919. // },
  920. // duration: 1.0,
  921. complete() {
  922. // 为什么要加这个?因为破库地球没完全加载完成时如果执行了监听鼠标滑动事件会光速报错滑跪
  923. if (!that.loadDone) {
  924. that.initEventInputAction();
  925. that.loadDone = true;
  926. }
  927. callback && callback();
  928. },
  929. });
  930. },
  931. resetViewport1(height = 0) {
  932. // 设置初始视图为宁夏
  933. const that = this;
  934. this.viewer.camera.flyTo({
  935. destination: Cesium.Cartesian3.fromDegrees(
  936. 106.169866,
  937. 38.46637,
  938. height
  939. ),
  940. // orientation: {
  941. // heading: 1,
  942. // pitch: -0.2,
  943. // roll: 0,
  944. // },
  945. // duration: 3,
  946. orientation: {
  947. heading: Cesium.Math.toRadians(0),
  948. pitch: Cesium.Math.toRadians(-90),
  949. roll: 0.0,
  950. },
  951. duration: 1.0,
  952. complete() {
  953. // 为什么要加这个?因为破库地球没完全加载完成时如果执行了监听鼠标滑动事件会光速报错滑跪
  954. if (!that.loadDone) {
  955. that.initEventInputAction();
  956. that.loadDone = true;
  957. }
  958. },
  959. });
  960. },
  961. resetCloudTempRain(height = 0) {
  962. // 设置初始视图为宁夏
  963. this.viewer.camera.flyTo({
  964. destination: Cesium.Cartesian3.fromDegrees(
  965. 106.169866,
  966. 38.46637,
  967. 10000000
  968. ),
  969. orientation: {
  970. heading: Cesium.Math.toRadians(0),
  971. pitch: Cesium.Math.toRadians(-90),
  972. roll: 0.0,
  973. },
  974. duration: 3.0,
  975. });
  976. },
  977. // 添加风场图
  978. async showWindLayer() {
  979. if (!this.windLayer) {
  980. // this.resetViewport(6000000);
  981. // 色值面板
  982. const CONTROL_COLOR = {
  983. uv: [
  984. { color: "#f4f4f4", min: 0, max: 5, label: "0m/s ~ 5m/s" },
  985. { color: "#fff465", min: 5, max: 10, label: "5m/s ~ 10m/s" },
  986. { color: "#c5171e", min: 10, max: 15, label: "10m/s ~ 15m/s" },
  987. { color: "#930f00", min: 15, max: 20, label: "15m/s ~ 20m/s" },
  988. ],
  989. shr: [
  990. { color: "#00BFFF", min: 0, max: 0.034, label: "0 ~ 0.034-轻度" },
  991. {
  992. color: "#1E90FF",
  993. min: 0.034,
  994. max: 0.068,
  995. label: "0.034 ~ 0.068-中度",
  996. },
  997. ],
  998. edr: [
  999. { color: "#87CEFA", min: 0, max: 0.22, label: "0 ~ 0.22-轻度" },
  1000. {
  1001. color: "#4169E1",
  1002. min: 0.22,
  1003. max: 0.35,
  1004. label: "0.22 ~ 0.35-中度",
  1005. },
  1006. {
  1007. color: "#0000CD",
  1008. min: 0.35,
  1009. max: 0.5,
  1010. label: "0.35 ~ 0.5-重度",
  1011. },
  1012. ],
  1013. };
  1014. this.windLayer = await createWind(
  1015. this.viewer,
  1016. this.trJsonData(windGridData),
  1017. CONTROL_COLOR
  1018. );
  1019. // this.windLayer = new WindLayer(windGridData, {
  1020. // particleSize: 2.0,
  1021. // particleOpacity: 0.6,
  1022. // particleSpeed: 0.01,
  1023. // maxVelocity: 25,// 风速最大值(用于颜色映射和速度缩放)
  1024. // minVelocity: 0, // 风速最小值阈值(低于此值不显示粒子)
  1025. // colorScale: [
  1026. // "rgb(36,104, 180)",
  1027. // "rgb(60,157, 194)",
  1028. // "rgb(128,205,193)",
  1029. // "rgb(151,218,168)",
  1030. // "rgb(198,231,181)",
  1031. // "rgb(238,247,217)",
  1032. // "rgb(255,238,159)",
  1033. // "rgb(252,217,125)",
  1034. // "rgb(255,182,100)",
  1035. // "rgb(252,150,75)",
  1036. // "rgb(250,112,52)",
  1037. // "rgb(245,64,32)",
  1038. // "rgb(237,45,28)",
  1039. // "rgb(220,24,32)",
  1040. // "rgb(180,0,35)",
  1041. // ],// 颜色强度缩放
  1042. // frameRate: 15,
  1043. // fadeOpacity: 0.995,
  1044. // particleAge: 150,
  1045. // maxAge: 60,
  1046. // globalAlpha: 0.8,
  1047. // velocityScale: 1 / 30,// 粒子移动速度缩放因子(控制动画快慢)
  1048. // paths: 250,
  1049. // lineWidth: 2,
  1050. // });
  1051. // this.windLayer.addTo(this.viewer);
  1052. }
  1053. },
  1054. trJsonData(jsonData) {
  1055. function arrayMin(arr) {
  1056. let min = Infinity;
  1057. for (let i = 0; i < arr.length; i++) {
  1058. if (arr[i] < min) min = arr[i];
  1059. }
  1060. return min;
  1061. }
  1062. function arrayMax(arr) {
  1063. let max = -Infinity;
  1064. for (let i = 0; i < arr.length; i++) {
  1065. if (arr[i] > max) max = arr[i];
  1066. }
  1067. return max;
  1068. }
  1069. const uData = jsonData.find((ele) => {
  1070. return ele.header.parameterNumberName === "U-component_of_wind";
  1071. });
  1072. const uMin = arrayMin(uData.data);
  1073. const uMax = arrayMax(uData.data);
  1074. const vData = jsonData.find((ele) => {
  1075. return ele.header.parameterNumberName === "V-component_of_wind";
  1076. });
  1077. const vMin = arrayMin(vData.data);
  1078. const vMax = arrayMax(vData.data);
  1079. const bounds = {
  1080. west: uData.header.lo1,
  1081. south: uData.header.la2,
  1082. east: uData.header.lo2,
  1083. north: uData.header.la1,
  1084. };
  1085. return {
  1086. u: {
  1087. array: uData.data,
  1088. min: uMin,
  1089. max: uMax,
  1090. },
  1091. v: {
  1092. array: vData.data,
  1093. min: vMin,
  1094. max: vMax,
  1095. },
  1096. width: uData.header.nx,
  1097. height: uData.header.ny,
  1098. bounds,
  1099. };
  1100. },
  1101. // 将风向角度数据转换为矢量坐标
  1102. windToVector(speed, direction) {
  1103. // 转换为弧度(气象角度:0°=北风,90°=东风)
  1104. const rad = Cesium.Math.toRadians(direction);
  1105. // 计算UV分量(U: 东西方向,V: 南北方向)
  1106. return {
  1107. u: -speed * Math.sin(rad), // 东正西负
  1108. v: -speed * Math.cos(rad), // 北正南负
  1109. };
  1110. },
  1111. // 获取当前地图瓦片级别(暂时没用到怀疑有BUG)
  1112. getTileLevel() {
  1113. let tiles = new Set();
  1114. let tilesToRender = this.viewer.scene.globe._surface._tilesToRender;
  1115. if (Cesium.defined(tilesToRender)) {
  1116. for (let i = 0; i < tilesToRender.length; i++) {
  1117. tiles.add(tilesToRender[i].level);
  1118. }
  1119. return [...tiles].sort((a, b) => {
  1120. return b - a;
  1121. })[0];
  1122. }
  1123. },
  1124. async showeveryTypeImagesLayer(imageUrls, intervalId, ImagesLayers) {
  1125. // 存储所有图片图层的数组
  1126. let imageLayers = [];
  1127. // 当前显示的图片索引
  1128. let currentImageIndex = -1; // 初始为-1,表示没有图片显示
  1129. // 创建所有图片图层并添加到Viewer,初始时全部隐藏
  1130. await imageUrls.forEach((url) => {
  1131. const provider = new Cesium.SingleTileImageryProvider({
  1132. url: url,
  1133. // url: URL.createObjectURL(url),
  1134. rectangle: Cesium.Rectangle.fromDegrees(-180.0, -90.0, 180.0, 90.0), // 全球覆盖
  1135. tileWidth: 1440, // 根据你的图片实际宽度修改
  1136. tileHeight: 721,
  1137. // 如果你的图片只覆盖特定区域,请修改rectangle参数
  1138. });
  1139. const Layer = this.viewer.imageryLayers.addImageryProvider(provider);
  1140. Layer.alpha = 0.8; // 透明度
  1141. Layer.brightness = 1; // 亮度
  1142. Layer.contrast = 1; // 对比度
  1143. Layer.show = false; // 初始隐藏
  1144. imageLayers.push(Layer);
  1145. ImagesLayers.push(Layer);
  1146. });
  1147. function showNextImage() {
  1148. // 隐藏当前图片
  1149. if (currentImageIndex >= 0 && currentImageIndex < imageLayers.length) {
  1150. imageLayers[currentImageIndex].show = false;
  1151. }
  1152. // 计算下一张图片的索引
  1153. currentImageIndex = (currentImageIndex + 1) % imageLayers.length;
  1154. // 显示下一张图片
  1155. imageLayers[currentImageIndex].show = true;
  1156. // imageLayers[currentImageIndex + 1].show = true;
  1157. console.log("当前显示图片: " + imageUrls[currentImageIndex]);
  1158. }
  1159. // 设置切换间隔(毫秒),例如每5秒切换一次
  1160. const intervalMs = 5000;
  1161. intervalId = setInterval(showNextImage, intervalMs);
  1162. // 初始显示第一张图片
  1163. showNextImage();
  1164. },
  1165. // 显示云图
  1166. showCloudLayer() {
  1167. const imageUrls = [];
  1168. cloudJson.forEach((it) => {
  1169. imageUrls.push("/public/static" + it.path);
  1170. });
  1171. this.showeveryTypeImagesLayer(
  1172. imageUrls,
  1173. this.cloudintervalId,
  1174. this.cloudImagesLayer
  1175. );
  1176. this.resetCloudTempRain();
  1177. },
  1178. //显示降雨图
  1179. showRainLayer() {
  1180. const imageUrls = [];
  1181. rainJson.forEach((it) => {
  1182. imageUrls.push("/public/static" + it.path);
  1183. });
  1184. this.showeveryTypeImagesLayer(
  1185. imageUrls,
  1186. this.rainintervalId,
  1187. this.rainImagesLayer
  1188. );
  1189. this.csceneElliposid(this.viewer, "rain");
  1190. this.resetCloudTempRain();
  1191. },
  1192. //显示温度图
  1193. showTemperatureLayer() {
  1194. const imageUrls = [];
  1195. tempJson.forEach((it) => {
  1196. imageUrls.push("/public/static" + it.path);
  1197. });
  1198. this.showeveryTypeImagesLayer(
  1199. imageUrls,
  1200. this.tempintervalId,
  1201. this.tempImagesLayer
  1202. );
  1203. this.csceneElliposid(this.viewer, "temp");
  1204. this.resetCloudTempRain();
  1205. },
  1206. // 提供控制函数以便在需要时停止循环
  1207. stopCycling(intervalId) {
  1208. if (intervalId) {
  1209. clearInterval(intervalId);
  1210. intervalId = null;
  1211. console.log("循环已停止");
  1212. }
  1213. },
  1214. // 移除风场图
  1215. removeWindLayer() {
  1216. if (this.windLayer) {
  1217. this.windLayer.destroy();
  1218. this.windLayer = null;
  1219. }
  1220. },
  1221. // 移除卫星云图
  1222. removeCloudLayer() {
  1223. if (this.cloudLayer) {
  1224. this.tagMsg = null;
  1225. this.viewer.imageryLayers.remove(this.cloudLayer);
  1226. this.cloudLayer = null;
  1227. }
  1228. if (this.cloudImagesLayer.length > 0) {
  1229. this.cloudImagesLayer.forEach((it) => {
  1230. this.viewer.imageryLayers.remove(it);
  1231. });
  1232. this.cloudImagesLayer = [];
  1233. }
  1234. if (this.imageryProviderV) {
  1235. this.viewer.imageryLayers.remove(this.imageryProviderV);
  1236. this.imageryProviderV = null;
  1237. }
  1238. },
  1239. // 移除降雨图
  1240. removeRainLayer() {
  1241. if (this.rainLayer) {
  1242. this.tagMsg = null;
  1243. this.viewer.imageryLayers.remove(this.rainLayer);
  1244. this.rainLayer = null;
  1245. this.setMapImageryProvider();
  1246. this.handlerAction.removeInputAction(
  1247. Cesium.ScreenSpaceEventType.LEFT_CLICK
  1248. );
  1249. }
  1250. if (this.rainImagesLayer.length > 0) {
  1251. this.rainImagesLayer.forEach((it) => {
  1252. this.viewer.imageryLayers.remove(it);
  1253. });
  1254. this.rainImagesLayer = [];
  1255. }
  1256. if (this.imageryProviderV) {
  1257. this.viewer.imageryLayers.remove(this.imageryProviderV);
  1258. this.imageryProviderV = null;
  1259. }
  1260. },
  1261. // 移除温度图
  1262. removeTemperatureLayer() {
  1263. if (this.temperatureLayer) {
  1264. this.tagMsg = null;
  1265. this.viewer.imageryLayers.remove(this.temperatureLayer);
  1266. this.temperatureLayer = null;
  1267. this.setMapImageryProvider();
  1268. this.handlerAction.removeInputAction(
  1269. Cesium.ScreenSpaceEventType.LEFT_CLICK
  1270. );
  1271. }
  1272. if (this.tempImagesLayer.length > 0) {
  1273. this.tempImagesLayer.forEach((it) => {
  1274. this.viewer.imageryLayers.remove(it);
  1275. });
  1276. this.tempImagesLayer = [];
  1277. }
  1278. if (this.imageryProviderV) {
  1279. this.viewer.imageryLayers.remove(this.imageryProviderV);
  1280. this.imageryProviderV = null;
  1281. }
  1282. },
  1283. //取消所有图层加载
  1284. cancleAllLayer() {
  1285. if (this.windLayer) {
  1286. this.removeWindLayer();
  1287. }
  1288. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1289. this.removeRainLayer();
  1290. this.stopCycling(this.rainintervalId);
  1291. }
  1292. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1293. this.removeCloudLayer();
  1294. this.stopCycling(this.cloudintervalId);
  1295. }
  1296. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1297. this.removeTemperatureLayer();
  1298. this.stopCycling(this.tempintervalId);
  1299. }
  1300. },
  1301. // 切换风场图显隐
  1302. switchWindLayer() {
  1303. this.showtempImg = false;
  1304. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1305. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1306. this.removeRainLayer();
  1307. this.stopCycling(this.rainintervalId);
  1308. }
  1309. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1310. this.removeCloudLayer();
  1311. this.stopCycling(this.cloudintervalId);
  1312. }
  1313. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1314. this.removeTemperatureLayer();
  1315. this.stopCycling(this.tempintervalId);
  1316. }
  1317. if (this.windLayer) {
  1318. this.removeWindLayer();
  1319. } else {
  1320. this.showWindLayer();
  1321. }
  1322. },
  1323. // 切换卫星云图显隐
  1324. switchCloudLayer(val) {
  1325. this.showtempImg = false;
  1326. if (this.windLayer) {
  1327. this.removeWindLayer();
  1328. }
  1329. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1330. this.removeRainLayer();
  1331. this.stopCycling(this.rainintervalId);
  1332. }
  1333. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1334. this.removeTemperatureLayer();
  1335. this.stopCycling(this.tempintervalId);
  1336. }
  1337. if (!val || this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1338. this.removeCloudLayer();
  1339. this.stopCycling(this.cloudintervalId);
  1340. } else {
  1341. this.showCloudLayer();
  1342. }
  1343. },
  1344. // 切换降雨图显隐
  1345. switchRainLayer() {
  1346. this.showtempImg = false;
  1347. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1348. if (this.windLayer) {
  1349. this.removeWindLayer();
  1350. }
  1351. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1352. this.removeCloudLayer();
  1353. this.stopCycling(this.cloudintervalId);
  1354. }
  1355. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1356. this.removeTemperatureLayer();
  1357. this.stopCycling(this.tempintervalId);
  1358. }
  1359. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1360. this.removeRainLayer();
  1361. this.stopCycling(this.rainintervalId);
  1362. } else {
  1363. this.showRainLayer();
  1364. }
  1365. },
  1366. switchTemperatureLayerr(val) {
  1367. this.showtempImg = val;
  1368. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1369. if (this.windLayer) {
  1370. this.removeWindLayer();
  1371. }
  1372. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1373. this.removeCloudLayer();
  1374. this.stopCycling(this.cloudintervalId);
  1375. }
  1376. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1377. this.removeRainLayer();
  1378. this.stopCycling(this.rainintervalId);
  1379. }
  1380. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1381. this.removeTemperatureLayer();
  1382. this.stopCycling(this.tempintervalId);
  1383. } else {
  1384. this.showTemperatureLayer();
  1385. }
  1386. },
  1387. switchCity() {
  1388. this.$router.push({
  1389. path: "/satellitecloudchart",
  1390. });
  1391. },
  1392. switchTopographicMap() {
  1393. this.viewer.scene.screenSpaceCameraController.enableZoom = true;
  1394. if (this.windLayer) {
  1395. this.removeWindLayer();
  1396. }
  1397. if (this.cloudLayer || this.cloudImagesLayer.length > 0) {
  1398. this.removeCloudLayer();
  1399. this.stopCycling(this.cloudintervalId);
  1400. }
  1401. if (this.rainLayer || this.rainImagesLayer.length > 0) {
  1402. this.removeRainLayer();
  1403. this.stopCycling(this.rainintervalId);
  1404. }
  1405. if (this.temperatureLayer || this.tempImagesLayer.length > 0) {
  1406. this.removeTemperatureLayer();
  1407. this.stopCycling(this.tempintervalId);
  1408. }
  1409. this.$router.push({
  1410. path: "/topographicMap",
  1411. });
  1412. },
  1413. csceneElliposid(viewer, type) {
  1414. let that = this;
  1415. // 获取 scene 和 ellipsoid
  1416. var scene = viewer.scene;
  1417. var labels = viewer.scene.primitives.add(new Cesium.LabelCollection());
  1418. // 创建 ScreenSpaceEventHandler
  1419. var handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
  1420. // 监听左键点击事件
  1421. handler.setInputAction(async (click) => {
  1422. // 获取点击位置的笛卡尔坐标
  1423. var position = click.position;
  1424. if (!position) return;
  1425. // 使用 globe.pick 获取包含地形高度的坐标
  1426. var ray = viewer.camera.getPickRay(position);
  1427. var cartesian = viewer.scene.globe.pick(ray, viewer.scene);
  1428. if (cartesian) {
  1429. // 转换为地理坐标
  1430. var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
  1431. var longitude = Cesium.Math.toDegrees(cartographic.longitude);
  1432. var latitude = Cesium.Math.toDegrees(cartographic.latitude);
  1433. var height = cartographic.height;
  1434. // 显示 loading
  1435. that.showLoading();
  1436. let params = {
  1437. lat: latitude,
  1438. lon: longitude,
  1439. appid: "3b66d35579770393051599f8d518df4a",
  1440. };
  1441. let res = await getTempData(params);
  1442. // 取消显示 loading
  1443. that.hideLoading();
  1444. // 格式化坐标
  1445. var text = `经度: ${longitude.toFixed(6)}°\n纬度: ${latitude.toFixed(
  1446. 6
  1447. )}°\n城市: ${res.name}\n降雨量: ${
  1448. res.rain ? res.rain["1h"] + "mm/h" : "无降雨"
  1449. }`;
  1450. var text2 = `经度: ${longitude.toFixed(6)}°\n纬度: ${latitude.toFixed(
  1451. 6
  1452. )}°\n城市: ${res.name}\n温度: ${Math.ceil(res.main.temp - 273.15)}℃`;
  1453. // 创建一个标签
  1454. var label = labels.add({
  1455. position: cartesian,
  1456. text: type === "rain" ? text : text2,
  1457. font: "14px monospace",
  1458. fillColor: Cesium.Color.fromCssColorString("#1d70df"),
  1459. // style: Cesium.LabelStyle.FILL_AND_OUTLINE,
  1460. // outlineColor: Cesium.Color.BLACK,
  1461. outlineWidth: 2,
  1462. verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 标签在点的上方
  1463. pixelOffset: new Cesium.Cartesian2(0, -20), // 向上偏移一点
  1464. disableDepthTest: true, // 让标签始终可见(即使在地球背面)
  1465. scale: 0.8,
  1466. });
  1467. // 5秒后移除标签
  1468. setTimeout(function () {
  1469. labels.remove(label);
  1470. }, 5000);
  1471. console.log(
  1472. `点击坐标: ${longitude.toFixed(6)}, ${latitude.toFixed(
  1473. 6
  1474. )}, ${height.toFixed(2)}m`
  1475. );
  1476. }
  1477. }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  1478. this.handlerAction = handler;
  1479. },
  1480. showLoading() {
  1481. var loadingEl = document.getElementById("loading");
  1482. loadingEl.style.display = "block";
  1483. },
  1484. hideLoading() {
  1485. var loadingEl = document.getElementById("loading");
  1486. loadingEl.style.display = "none";
  1487. },
  1488. // 展示所有风场
  1489. showAllStation(viewer, station) {
  1490. station.forEach((e, index) => {
  1491. if (e.energytype === "Wind") {
  1492. this.showStationFn(viewer, e, index, fc);
  1493. } else if (e.energytype === "Fire") {
  1494. this.showStationFn(viewer, e, index, hd);
  1495. } else if (e.energytype === "Storage") {
  1496. this.showStationFn(viewer, e, index, chu);
  1497. } else {
  1498. this.showStationFn(viewer, e, index, gf);
  1499. }
  1500. });
  1501. this.resetViewport(6000000);
  1502. },
  1503. // 根据状态展示不同颜色风机贴图
  1504. async showStationFn(viewer, e, index, images) {
  1505. // // 获取GIF数据
  1506. // const response = await fetch(images);
  1507. // const buffer = await response.arrayBuffer();
  1508. // // 解析GIF
  1509. // const gif = parseGIF(buffer);
  1510. // const frames = decompressFrames(gif, true);
  1511. // // 创建一个离屏Canvas来绘制每一帧
  1512. // const canvas = document.createElement('canvas');
  1513. // const ctx = canvas.getContext('2d');
  1514. // canvas.width = gif.lsd.width;
  1515. // canvas.height = gif.lsd.height;
  1516. const position = Cesium.Cartesian3.fromDegrees(e.longitude, e.latitude);
  1517. const entity = viewer.entities.add({
  1518. id: index,
  1519. position, // 模型位置
  1520. billboard: {
  1521. image: images, // 也可以是 SVG 路径,如 'icon.svg'
  1522. // image: new Cesium.ConstantProperty(images),
  1523. // image: canvas.toDataURL('image/png'),
  1524. scale: 0.08,
  1525. // width: 256,
  1526. // height: 256,
  1527. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  1528. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  1529. // 模型贴地
  1530. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  1531. },
  1532. // name: 'video_entity',
  1533. label: {
  1534. text: e.plantname,
  1535. font: "14px sans-serif",
  1536. fillColor: Cesium.Color.fromBytes(255, 255, 255),
  1537. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  1538. },
  1539. });
  1540. // let currentFrameIndex = 0;
  1541. // // 定义动画函数
  1542. // const animate = () => {
  1543. // const frame = frames[currentFrameIndex];
  1544. // // 清空Canvas
  1545. // ctx.clearRect(0, 0, canvas.width, canvas.height);
  1546. // // 绘制当前帧
  1547. // const imageData = new ImageData(frame.patch, frame.dims.width, frame.dims.height);
  1548. // ctx.putImageData(imageData, frame.dims.left, frame.dims.top);
  1549. // // 更新Billboard的图像
  1550. // entity.billboard.image = canvas.toDataURL('image/png');
  1551. // // 计算下一帧的时间(使用GIF本身的延迟时间)
  1552. // currentFrameIndex = (currentFrameIndex + 1) % frames.length;
  1553. // const delay = 200; // 默认100ms
  1554. // setTimeout(animate, delay);
  1555. // };
  1556. // // 开始动画
  1557. // animate();
  1558. let that = this;
  1559. const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  1560. handler.setInputAction(function (movement) {
  1561. var position = movement.position;
  1562. var pickedObject = viewer.scene.pick(position);
  1563. if (pickedObject && pickedObject.id.id === index) {
  1564. console.log("你点击了标签或模型!", entity);
  1565. console.log("选中风场或新能源场", e.plantname);
  1566. that.showRightClickPopup(position, e, "station", viewer);
  1567. }
  1568. }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  1569. that.allStationentitys.push({ entity, handler });
  1570. },
  1571. // 展示所选风场的风机
  1572. showWindFromStation(viewer) {
  1573. fjLonLatJson.data.forEach((e, index) => {
  1574. if (e.status) {
  1575. if (e.status === 1) {
  1576. this.showStatuswind(viewer, e, dj);
  1577. }
  1578. } else {
  1579. this.showAnimatewind(viewer, e);
  1580. }
  1581. });
  1582. this.resetWindViewport();
  1583. },
  1584. // 根据状态展示不同颜色风机贴图
  1585. showStatuswind(viewer, e, type, index) {
  1586. this.addSvg(
  1587. viewer,
  1588. type,
  1589. e.longitude,
  1590. e.latitude,
  1591. e,
  1592. Math.random().toFixed(4) * 10000
  1593. );
  1594. },
  1595. // 风机柱与扇叶分开展示转动效果贴图
  1596. showAnimatewind(viewer, e, index) {
  1597. this.addSvgs(
  1598. viewer,
  1599. bwshan,
  1600. e.longitude,
  1601. e.latitude,
  1602. e,
  1603. Math.random().toFixed(3) * 1000
  1604. );
  1605. this.addSvg(
  1606. viewer,
  1607. bwzhu,
  1608. e.longitude,
  1609. e.latitude,
  1610. e,
  1611. Math.random().toFixed(3) * 2000
  1612. );
  1613. },
  1614. //添加svg或png贴图
  1615. addSvg(viewer, uri, lon, lat, val, index) {
  1616. let that = this;
  1617. let ids = that.generateUniqueId(val.id);
  1618. const position = Cesium.Cartesian3.fromDegrees(lon, lat);
  1619. const entity = viewer.entities.add({
  1620. id: ids,
  1621. position, // 模型位置
  1622. billboard: {
  1623. image: uri, // 也可以是 SVG 路径,如 'icon.svg'
  1624. scale: 0.5,
  1625. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  1626. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  1627. // 模型贴地
  1628. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  1629. },
  1630. label: {
  1631. text: val.name ? val.name : val.plantname,
  1632. font: "14px sans-serif",
  1633. fillColor: Cesium.Color.fromBytes(255, 255, 255),
  1634. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  1635. },
  1636. });
  1637. // 创建事件处理器
  1638. const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  1639. handler.setInputAction(function (movement) {
  1640. var position = movement.position;
  1641. var pickedObject = viewer.scene.pick(position);
  1642. if (pickedObject && pickedObject.id.id === ids) {
  1643. console.log("你点击了标签或模型!", entity);
  1644. console.log("标签或模型数据!", val);
  1645. that.modelVal = val;
  1646. // 找到实体,显示包含实体信息的弹框
  1647. that.showRightClickPopup(position, val, "wind");
  1648. return;
  1649. }
  1650. }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  1651. this.allWindEntitys.push({ entity, handler });
  1652. },
  1653. //添加svg或png贴图
  1654. addSvgs(viewer, uri, lon, lat, val, index) {
  1655. let that = this;
  1656. let ids = that.generateUniqueId(val.id);
  1657. const position = Cesium.Cartesian3.fromDegrees(lon, lat);
  1658. const entity = viewer.entities.add({
  1659. id: ids,
  1660. position, // 模型位置
  1661. billboard: {
  1662. image: uri, // 也可以是 SVG 路径,如 'icon.svg'
  1663. scale: 0.5,
  1664. pixelOffset: new Cesium.Cartesian2(0, -15),
  1665. verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
  1666. horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
  1667. // 模型贴地
  1668. heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
  1669. // 核心:使用 CallbackProperty 实现旋转动画
  1670. rotation: new Cesium.CallbackProperty(function (time) {
  1671. // 每 3 秒转一圈(可调速度)
  1672. const seconds = time.secondsOfDay;
  1673. const angle = Cesium.Math.toRadians(((seconds % 3) * 360) / 3); // 每3秒一圈
  1674. return angle;
  1675. }, false),
  1676. // 确保绕中心旋转
  1677. rotationAlignment: Cesium.HeightReference.CENTER,
  1678. alignedAxis: Cesium.Cartesian3.UNIT_Z,
  1679. },
  1680. });
  1681. // 创建事件处理器
  1682. const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  1683. handler.setInputAction(function (movement) {
  1684. var position = movement.position;
  1685. var pickedObject = viewer.scene.pick(position);
  1686. if (pickedObject && pickedObject.id.id === ids) {
  1687. console.log("你点击了标签或模型!", entity);
  1688. console.log("标签或模型数据!", val);
  1689. that.modelVal = val;
  1690. // 找到实体,显示包含实体信息的弹框
  1691. that.showRightClickPopup(position, val, "wind");
  1692. return;
  1693. }
  1694. }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  1695. this.allWindEntitys.push({ entity, handler });
  1696. },
  1697. generateUniqueId(prefix = "id") {
  1698. let idCounter = 0;
  1699. return `${prefix}-${Date.now()}-${Math.random()
  1700. .toString(36)
  1701. .substr(2, 9)}-${idCounter++}`;
  1702. },
  1703. // 右键展示元素
  1704. showRightClickPopup(screenPosition, val, type, viewer) {
  1705. // 创建或获取弹框元素
  1706. var popup = document.getElementById("rightClickPopup");
  1707. if (!popup) {
  1708. popup = document.createElement("div");
  1709. popup.id = "rightClickPopup";
  1710. popup.style.position = "absolute";
  1711. // popup.style.backgroundColor = 'white';
  1712. popup.style.border = "1px solid rgba(255, 255, 255, 0.2)";
  1713. popup.style.borderRadius = "4px";
  1714. popup.style.padding = "10px";
  1715. popup.style.boxShadow = "0 2px 10px rgba(0,0,0,0.2)";
  1716. popup.style.backdropFilter = `blur(10px)`;
  1717. popup.style.zIndex = "1000";
  1718. popup.style.pointerEvents = "auto"; // 确保弹框可交互
  1719. // 添加一个最小宽度,避免内容过短
  1720. popup.style.minWidth = "100px";
  1721. // 可选:添加箭头指向点击点
  1722. // 这需要更复杂的 CSS 或额外的 DOM 元素
  1723. document.body.appendChild(popup);
  1724. }
  1725. // <span class="popup-menu-item" data-message-type="model" style="cursor:pointer">模型解构</span>
  1726. let content = "";
  1727. if (type === "station") {
  1728. content = `
  1729. <div style="display: flex;gap: 10px;flex-direction: column;text-align: center;color: #fff">
  1730. <span class="popup-menu-item" data-message-type="windbasic2d" style="cursor:pointer">风机详情(2D)</span>
  1731. </div>
  1732. `;
  1733. } else {
  1734. content = `
  1735. <div style="display: flex;gap: 10px;flex-direction: column;text-align: center;color: #fff">
  1736. <span class="popup-menu-item" data-message-type="basic" style="cursor:pointer">基本信息</span>
  1737. <span class="popup-menu-item" data-message-type="video" style="cursor:pointer">视频监控</span>
  1738. <span class="popup-menu-item" data-message-type="problem" style="cursor:pointer">故障详情</span>
  1739. </div>
  1740. `;
  1741. }
  1742. // 设置弹框内容
  1743. popup.innerHTML = content;
  1744. // --- 关键修正:准确计算弹框位置 ---
  1745. // 1. 获取 Cesium 画布 (Canvas) 相对于视口 (viewport) 的位置
  1746. var canvas = this.viewer.scene.canvas;
  1747. var canvasRect = canvas.getBoundingClientRect();
  1748. // console.log('Canvas Rect:', canvasRect); // 调试用
  1749. // 2. 计算弹框左上角在页面中的绝对 X 坐标
  1750. // screenPosition.x 是相对于画布左上角的 X 偏移
  1751. // canvasRect.left 是画布左上角相对于浏览器视口左上角的 X 偏移
  1752. // window.pageXOffset 是页面水平滚动的距离
  1753. var popupLeft = canvasRect.left + screenPosition.x + window.pageXOffset;
  1754. // 3. 计算弹框左上角在页面中的绝对 Y 坐标
  1755. var popupTop = canvasRect.top + screenPosition.y + window.pageYOffset;
  1756. // --- 可选:添加偏移量,避免完全覆盖鼠标指针 ---
  1757. // 例如,向下和向右偏移 10px
  1758. var offsetX = 10;
  1759. var offsetY = 10;
  1760. popupLeft += offsetX;
  1761. popupTop += offsetY;
  1762. // --- 可选:边界检查,防止弹框超出屏幕 ---
  1763. var popupWidth = popup.offsetWidth || 150; // 确保有宽度
  1764. var popupHeight = popup.offsetHeight || 50; // 确保有高度
  1765. var windowWidth = window.innerWidth;
  1766. var windowHeight = window.innerHeight;
  1767. // 如果弹框右边会超出窗口,则左移
  1768. if (popupLeft + popupWidth > windowWidth) {
  1769. popupLeft = windowWidth - popupWidth - 10; // 靠右留 10px 边距
  1770. }
  1771. // 如果弹框底边会超出窗口,则上移
  1772. if (popupTop + popupHeight > windowHeight) {
  1773. popupTop = windowHeight - popupHeight - 10; // 靠下留 10px 边距
  1774. }
  1775. // 如果左边超出 (不太可能,但安全起见)
  1776. if (popupLeft < 0) popupLeft = 10;
  1777. // 如果顶边超出
  1778. if (popupTop < 0) popupTop = 10;
  1779. // --- 设置最终位置 ---
  1780. popup.style.left = popupLeft + "px";
  1781. popup.style.top = popupTop + "px";
  1782. // console.log('Popup Position:', { left: popupLeft, top: popupTop }); // 调试用
  1783. // 显示弹框
  1784. popup.style.display = "block";
  1785. // --- 隐藏弹框的逻辑 (保持不变) ---
  1786. function hidePopup(event) {
  1787. // 检查点击是否发生在弹框内部,如果是,则不隐藏
  1788. if (event && popup.contains(event.target)) {
  1789. return;
  1790. }
  1791. popup.style.display = "none";
  1792. document.removeEventListener("click", hidePopup);
  1793. document.removeEventListener("keydown", keyDownHandler);
  1794. }
  1795. function keyDownHandler(event) {
  1796. if (event.key === "Escape") {
  1797. hidePopup();
  1798. }
  1799. }
  1800. let that = this;
  1801. // if (type === "station") {
  1802. // } else {
  1803. that.showBasicMsg = false;
  1804. that.showVideoMsg = false;
  1805. that.showProblemMsg = false;
  1806. that.showModelMsg = false;
  1807. // }
  1808. function popupMenuItemClick(event) {
  1809. // 检查点击的是否是菜单项
  1810. if (event.target.classList.contains("popup-menu-item")) {
  1811. event.stopPropagation(); // 阻止冒泡,防止弹框立即关闭
  1812. var messageType = event.target.dataset.messageType; // 获取 data-message-type
  1813. console.log("点击了菜单项:", messageType);
  1814. // 调用您的 showMessage 函数
  1815. that.showMessage(messageType, val, viewer);
  1816. hidePopup();
  1817. // 可选:执行后关闭弹框
  1818. // hideRightClickPopup();
  1819. }
  1820. }
  1821. // 移除旧的监听器(避免重复绑定)
  1822. document.removeEventListener("click", hidePopup);
  1823. document.removeEventListener("click", popupMenuItemClick);
  1824. document.removeEventListener("keydown", keyDownHandler);
  1825. // 添加新的监听器
  1826. document.addEventListener("click", hidePopup);
  1827. document.addEventListener("click", popupMenuItemClick);
  1828. document.addEventListener("keydown", keyDownHandler);
  1829. },
  1830. showMessage(type, val, viewer) {
  1831. console.log("type===>>>", type);
  1832. if (type !== "windbasic2d") {
  1833. this.windDrawer = true;
  1834. this.windDrawerHeader = val.name + "数据详情";
  1835. if (type === "basic") {
  1836. this.windDrawerTitle = "基础信息";
  1837. this.showBasicMsg = true;
  1838. } else if (type === "video") {
  1839. this.windDrawerTitle = "视频监控";
  1840. this.showVideoMsg = true;
  1841. } else if (type === "problem") {
  1842. this.windDrawerTitle = "故障查看";
  1843. this.showProblemMsg = true;
  1844. }
  1845. } else {
  1846. // this.showWindDetail = true;
  1847. this.allStationentitys.forEach(({ entity, handler }) => {
  1848. viewer.entities.remove(entity); // 移除实体
  1849. if (!handler.isDestroyed()) {
  1850. handler.destroy(); // 销毁事件处理器(关键!)
  1851. }
  1852. });
  1853. this.allStationentitys = [];
  1854. // this.showWindFromStation(viewer);
  1855. this.showTypeViewer = false;
  1856. this.cancleAllLayer();
  1857. this.getCameraPosition(viewer)
  1858. }
  1859. // else {
  1860. // this.windDrawerTitle = '模型解构'
  1861. // this.showModelMsg = true
  1862. // }
  1863. },
  1864. // 获取当前经纬度和高度并跳转风机详情
  1865. getCameraPosition(viewer) {
  1866. const camera = viewer.camera;
  1867. const ellipsoid = viewer.scene.globe.ellipsoid;
  1868. // 获取相机的笛卡尔坐标
  1869. const cartesianPosition = camera.position;
  1870. // 转换为地理坐标(弧度)
  1871. const cartographicPosition = ellipsoid.cartesianToCartographic(cartesianPosition);
  1872. if (cartographicPosition) {
  1873. const longitude = Cesium.Math.toDegrees(cartographicPosition.longitude); // 经度(度)
  1874. const latitude = Cesium.Math.toDegrees(cartographicPosition.latitude); // 纬度(度)
  1875. const height = cartographicPosition.height; // 高度(米)
  1876. // return { longitude, latitude, height };
  1877. this.$router.push({
  1878. path: "/windMap",
  1879. query: {
  1880. longitude: longitude,
  1881. latitude: latitude,
  1882. height: Math.ceil(height)
  1883. }
  1884. });
  1885. } else {
  1886. return undefined; // 相机可能在地球之外或无效位置
  1887. }
  1888. },
  1889. // 重置风场中所有风机视角
  1890. resetWindViewport() {
  1891. this.viewer.camera.flyTo({
  1892. destination: Cesium.Cartesian3.fromDegrees(
  1893. 114.502778,
  1894. 35.326667,
  1895. 20000
  1896. ),
  1897. orientation: {
  1898. heading: Cesium.Math.toRadians(0),
  1899. pitch: Cesium.Math.toRadians(-90),
  1900. roll: 0.0,
  1901. },
  1902. duration: 3.0,
  1903. });
  1904. },
  1905. backStations() {
  1906. this.showWindDetail = false;
  1907. this.showTypeViewer = true;
  1908. // this.switchWindLayer();
  1909. this.allWindEntitys.forEach(({ entity, handler }) => {
  1910. this.viewer.entities.remove(entity); // 移除实体
  1911. if (!handler.isDestroyed()) {
  1912. handler.destroy(); // 销毁事件处理器(关键!)
  1913. }
  1914. });
  1915. this.allWindEntitys = [];
  1916. this.changeType(this.stationValue);
  1917. },
  1918. menuComTSty(val) {
  1919. this.menuComTStyB = val;
  1920. },
  1921. },
  1922. };
  1923. </script>
  1924. <style lang="less" scoped>
  1925. .mapBox {
  1926. width: 100%;
  1927. height: 100%;
  1928. position: relative;
  1929. box-sizing: content-box;
  1930. position: relative;
  1931. .menuComC {
  1932. position: fixed;
  1933. bottom: 20px;
  1934. left: 20px;
  1935. }
  1936. .menuBox {
  1937. position: absolute;
  1938. left: 0;
  1939. top: 0;
  1940. width: 100%;
  1941. background: #fff;
  1942. opacity: 0.8;
  1943. display: flex;
  1944. justify-content: flex-end;
  1945. align-items: center;
  1946. padding: 10px;
  1947. .el-button {
  1948. margin: 0;
  1949. }
  1950. .item {
  1951. font-size: 12px;
  1952. margin-left: 10px;
  1953. }
  1954. &.switch {
  1955. opacity: 0;
  1956. transition: 0.2s;
  1957. &:hover {
  1958. opacity: 1;
  1959. transition: 0.2s;
  1960. }
  1961. }
  1962. }
  1963. .tag {
  1964. position: absolute;
  1965. left: 0;
  1966. top: 0;
  1967. background: #fff;
  1968. font-size: 12px;
  1969. color: #000;
  1970. padding: 4px 8px;
  1971. border-radius: 28px;
  1972. transform: translate(-50%, -50%);
  1973. transition: 0.1s;
  1974. pointer-events: none;
  1975. }
  1976. .devInfoBox {
  1977. width: 130px;
  1978. position: absolute;
  1979. right: 0;
  1980. bottom: 0;
  1981. padding: 4px;
  1982. font-size: 12px;
  1983. color: #fff;
  1984. background: rgba(0, 0, 0, 0.25);
  1985. display: flex;
  1986. flex-direction: column;
  1987. justify-content: flex-start;
  1988. align-items: flex-start;
  1989. z-index: 500;
  1990. user-select: none;
  1991. .item {
  1992. width: 100%;
  1993. margin-top: 4px;
  1994. overflow: hidden;
  1995. text-overflow: ellipsis;
  1996. white-space: nowrap;
  1997. }
  1998. }
  1999. .dataLoading {
  2000. position: absolute;
  2001. top: 50%;
  2002. left: 50%;
  2003. transform: translate(-50%, -50%);
  2004. background: rgba(0, 0, 0, 0.7);
  2005. color: white;
  2006. padding: 15px 20px;
  2007. border-radius: 6px;
  2008. font-size: 14px;
  2009. pointer-events: none;
  2010. display: none;
  2011. z-index: 999;
  2012. }
  2013. .tempImg {
  2014. // width: 40px;
  2015. height: 500px;
  2016. position: absolute;
  2017. right: 0;
  2018. bottom: 0px;
  2019. img {
  2020. height: 300px;
  2021. }
  2022. }
  2023. }
  2024. </style>
  2025. <style lang="less">
  2026. .modelDialog {
  2027. .el-dialog__body {
  2028. height: 750px;
  2029. .el-tabs {
  2030. height: 100%;
  2031. .el-tab-pane {
  2032. width: 100%;
  2033. height: 100%;
  2034. }
  2035. .el-tabs__content {
  2036. height: 100%;
  2037. }
  2038. }
  2039. }
  2040. }
  2041. </style>
  2042. <style lang="less">
  2043. .el-overlay {
  2044. background-color: transparent !important;
  2045. .windModelDrawer {
  2046. width: 80% !important;
  2047. backdrop-filter: blur(15px) !important;
  2048. background: rgba(255, 255, 255, 0.8) !important;
  2049. border-radius: 10px 0 0 10px !important;
  2050. .el-drawer__body {
  2051. overflow: hidden;
  2052. padding-top: 0;
  2053. }
  2054. }
  2055. }
  2056. .windDrawerCla {
  2057. .line {
  2058. display: flex;
  2059. flex-direction: row;
  2060. align-items: center;
  2061. justify-content: space-between;
  2062. width: 100%;
  2063. margin-bottom: 10px;
  2064. .leftContent {
  2065. width: 242px;
  2066. height: 41px;
  2067. display: flex;
  2068. align-items: center;
  2069. background: url("@/assets/cesiumImg/title_left_bg.png") no-repeat;
  2070. span {
  2071. font-size: 16px;
  2072. font-family: Microsoft YaHei;
  2073. font-weight: 400;
  2074. color: #ffffff;
  2075. margin-left: 25px;
  2076. }
  2077. }
  2078. }
  2079. .jcxx,
  2080. .gzck {
  2081. height: 100%;
  2082. }
  2083. .spjk,
  2084. .third {
  2085. height: 80vh;
  2086. }
  2087. }
  2088. </style>