NWP helper in GuangDong

广东省气象业务网数值预报页面增强

  1. // ==UserScript==
  2. // @name NWP helper in GuangDong
  3. // @description 广东省气象业务网数值预报页面增强
  4. // @namespace minhill.com
  5. // @match http://10.148.8.228/to_fore_homepage.action*
  6. // @match http://10.148.8.18/ywwhome/vision/multiDataApply/gisloader.html*
  7. // @version 1.9.4
  8. // @require https://lib.baomitu.com/echarts/4.6.0/echarts.min.js
  9. // @require https://lib.baomitu.com/moment.js/2.24.0/moment.min.js
  10. // @require https://lib.baomitu.com/d3/5.15.0/d3.min.js
  11. // @grant GM_addStyle
  12. // @grant unsafeWindow
  13. // @grant GM_openInTab
  14. // @grant GM_xmlhttpRequest
  15. // @grant GM_notification
  16. // @license The MIT License (MIT); http://opensource.org/licenses/MIT
  17. // @connect 172.22.1.175
  18. // @connect 127.0.0.1:5000
  19. // @connect api.map.baidu.com
  20. // @connect trident.gdmo.gq
  21. // @connect research.gdmo.gq
  22. // @compatible firefox
  23. // @compatible chrome
  24. // @compatible edge
  25. // @note 2018/01/08 增加时效转换按钮
  26. // @note 2018/01/15 增加经纬度功能
  27. // @supportURL https://greasyfork.org/scripts/26259
  28. // @author Hanchy Hill
  29. // ==/UserScript==
  30. // TODO 数据格式错误时显示提示
  31. // TODO require resData
  32. // TODO 风向tooltip改进
  33. // TODO 增加气压
  34. // TODO 增加集合预报-32天预报
  35. // TODO 单站停止经纬度捕捉
  36. // TODO 分钟降水时时效切换错误
  37. // TODO 显示单击的点
  38. // TODO 垂直剖面NCL测试
  39. // TODO 垂直探空曲线
  40. // TODO 降水量, 风力分级
  41. // TODO 多图模式时次选择
  42. const modelMeta = [
  43. {
  44. "model": "grapes1km",
  45. "rule": "/{yyyymmdd}/{HH}/grapes1km_{range}_{name}_m{hhh}_{yyyy}{mm}{dd}{HH}{mi}.png",
  46. "modelName": "Grapes_gz_R_1km"
  47. },
  48. {
  49. "model": "grapesmeso_b",
  50. "rule": "grapesmeso_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  51. "modelName": "Grapes_Meso"
  52. },
  53. {
  54. "model": "grapescma_b",
  55. "rule": "grapescma_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  56. "modelName": "Grapes_全球"
  57. },
  58. {
  59. "model": "SCMOC",
  60. "rule": "SCMOC_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  61. "modelName": "客观短时预报"
  62. },
  63. {
  64. "model": "ecmwffine_b",
  65. "rule": "ecmwffine_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  66. "modelName": "ECMWF_细网格"
  67. },
  68. {
  69. "model": "ecmwfs2d_b",
  70. "rule": "ecmwfs2d_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  71. "modelName": "ECMWF_细网格(S2D)"
  72. },
  73. {
  74. "model": "ecmwfs2s",
  75. "rule": "ecmwfs2s_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  76. "modelName": "ECMWF细网格(S2S)"
  77. },
  78. {
  79. "model": "ecmwfc3e_b",
  80. "rule": "ecmwfc3e_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  81. "modelName": "ECMWF_集合"
  82. },
  83. {
  84. "model": "ecmwfs4f",
  85. "rule": "ecmwfs4f_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  86. "modelName": "ECMWF_32天"
  87. },
  88. {
  89. "model": "ecmwfEnsExt",
  90. "rule": "ecmwfEnsExt_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  91. "modelName": "ECMWF_46天"
  92. },
  93. {
  94. "model": "ecmwfc3y",
  95. "rule": "ecmwfc3y_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  96. "modelName": "ECMWF_C3Y"
  97. },
  98. {
  99. "model": "ncepgfs0p5",
  100. "rule": "ncepgfs0p5_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  101. "modelName": "NCEP"
  102. },
  103. {
  104. "model": "chaf",
  105. "rule": "chaf_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  106. "modelName": "Grapes_gz_R_3km"
  107. },
  108. {
  109. "model": "GTRAMS3KMEC_b",
  110. "rule": "GTRAMS3KMEC_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  111. "modelName": "Grapes_gz_3km"
  112. },
  113. {
  114. "model": "GTRAMS3KMNCEP_b",
  115. "rule": "GTRAMS3KMEC_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  116. "modelName": "Grapes_gz_3km(N)"
  117. },
  118. {
  119. "model": "Gtrams3kmCNEC",
  120. "rule": "Gtrams3kmCN_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  121. "modelName": "Grapes_gz_3km(CN)"
  122. },
  123. {
  124. "model": "Gtrams3kmCNGraGFS",
  125. "rule": "Gtrams3kmCN_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  126. "modelName": "Grapes_gz_3km(G)"
  127. },
  128. {
  129. "model": "grapes9ec",
  130. "rule": "grapes9_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  131. "modelName": "Grapes_gz_9km"
  132. },
  133. {
  134. "model": "grapes9",
  135. "rule": "grapes9_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  136. "modelName": "Grapes_gz_9km(N)"
  137. },
  138. {
  139. "model": "grapes18kmec_b",
  140. "rule": "grapes18_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  141. "modelName": "Grapes_gz_18km"
  142. },
  143. {
  144. "model": "grapes18km_b",
  145. "rule": "grapes18_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  146. "modelName": "Grapes_gz_18km(N)"
  147. },
  148. {
  149. "model": "wavepng_b",
  150. "rule": "wave_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  151. "modelName": "Grapes-海洋"
  152. },
  153. {
  154. "model": "seafog_b",
  155. "rule": "{name}{yyyy}{mm}{dd}{HH}result.gif",
  156. "modelName": "Grapes-海雾"
  157. },
  158. {
  159. "model": "graces_b",
  160. "rule": "graces_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  161. "modelName": "Grapes-环境"
  162. },
  163. {
  164. "model": "environment_b",
  165. "rule": "environment_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  166. "modelName": "CMA环境"
  167. },
  168. {
  169. "model": "fax_b",
  170. "modelName": "FAX传真图",
  171. "rule": ""
  172. },
  173. {
  174. "model": "obor_b",
  175. "rule": "ecmwfs2d_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  176. "modelName": "一带一路"
  177. },
  178. {
  179. "model": "clean_plot",
  180. "rule": "gz3km{yyyy}{mm}{dd}{HH}_{hhh}_{name}.png",
  181. "modelName": "Grapes_3km(CN)"
  182. },
  183. {
  184. "model": "cldas",
  185. "rule": "cldas_hn_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  186. "modelName": "国家局实况融合"
  187. },
  188. {
  189. "model": "OCEN_CODAS",
  190. "rule": "OCEN_CODAS_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  191. "modelName": "国家局海洋融合"
  192. },
  193. {
  194. "model": "SWMA",
  195. "rule": "swma_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png",
  196. "modelName": "热带所海面风融合"
  197. }
  198. ];
  199.  
  200. let multiModelRule = [
  201. {
  202. rule: [
  203. ['ecmwffine_b', ['oy', 'cn', 'hn', 'gd',], null],
  204. ['ncepgfs0p5', ['oy', 'cn', 'hn', 'gd',], null],
  205. ['grapescma_b', ['oy', 'cn', 'hn', 'gd',], null],
  206. ['grapes9ec', ['oy', 'cn', 'hn', 'gd',], null],
  207. ],
  208. name: 'EC/NCEP/G全球/G9kmEC',
  209. description: 'ECMWF, NCEP, Grapes全球, 南海台风模式9km',
  210. opt: { stritFit: true }
  211. },
  212. {
  213. rule: [
  214. ['ecmwffine_b', ['oy', 'cn', 'hn', 'gd',], null],
  215. ['ncepgfs0p5', ['oy', 'cn', 'hn', 'gd',], null],
  216. ['grapes9ec', ['cn', 'hn', 'gd',], null],
  217. ['grapesmeso_b', ['cn', 'hn', 'gd',], null],
  218. ],
  219. name: 'EC/NCEP/G9kmEC/G-meso',
  220. description: 'ECMWF, NCEP, 南海台风模式9km, Grapes-Meso',
  221. opt: { stritFit: true }
  222. },
  223. {
  224. rule: [
  225. ['ecmwffine_b', ['oy', 'cn', 'hn', 'gd',], null],
  226. ['ncepgfs0p5', ['oy', 'cn', 'hn', 'gd',], null],
  227. ['grapes9ec', ['cn', 'hn', 'gd',], null],
  228. ['GTRAMS3KMEC_b', ['hn', 'gd',], null],
  229. ],
  230. name: 'EC/NCEP/G9kmEC/G-meso',
  231. description: 'ECMWF, NCEP, 南海台风模式9km, Grapes-Meso',
  232. opt: { stritFit: true }
  233. },
  234. ]
  235.  
  236. let mvPatternList = [
  237. [{
  238. "elem": "t2mm",
  239. "elemCN": "2m气温",
  240. "region": "cn",
  241. "model": "ecmwffine_b",
  242. "modelFileName": "ecmwffine"
  243. },
  244. {
  245. "elem": "t2mm24",
  246. "elemCN": "2m气温24h变温",
  247. "region": "cn",
  248. "model": "ecmwffine_b",
  249. "modelFileName": "ecmwffine"
  250. },
  251. {
  252. "elem": "wind10m",
  253. "elemCN": "10m风",
  254. "region": "cn",
  255. "model": "ecmwffine_b",
  256. "modelFileName": "ecmwffine"
  257. },
  258. {
  259. "elem": "10gust3",
  260. "elemCN": "10m阵风(过去3h)",
  261. "region": "cn",
  262. "model": "ecmwffine_b",
  263. "modelFileName": "ecmwffine"
  264. }]
  265. ]
  266.  
  267. let userConfig = { // 用户设置
  268. alterDate: false, // 默认不修改时次
  269. timelineMove: false,//是否启用滑动时间触发
  270. debug: false,
  271. mvMode:undefined,
  272. };
  273.  
  274. let $ = unsafeWindow.$;
  275.  
  276. const elemsConfig = {
  277. latLonInput: undefined,
  278.  
  279. $doc: () => unsafeWindow.$doc,
  280. $settings: () => unsafeWindow.$settings,
  281. $home: unsafeWindow.$home,
  282. fixPoint: undefined,
  283. pointerPoint: { lat: 0, lon: 0 },// 地图锚点位置
  284. point01: undefined,
  285. point02: undefined,
  286. state: 'timeseries',//'vertical','skewT'
  287. compareImgDOM: { img: [], info: [] },//前后预报时次对比DOM引用
  288. mvImgDOM: {//四分图DOM引用
  289. img: [],
  290. info: [],
  291. mode: 'multiElem',//multiTime, multiModel, multiElem,multiInitime
  292. matchedSrcList: [],
  293. matchPattens: [{
  294. "elem": "t2mm",
  295. "elemCN": "2m气温",
  296. "region": "cn",
  297. "model": "ecmwffine_b",
  298. "modelFileName": "ecmwffine"
  299. },
  300. {
  301. "elem": "t2mm24",
  302. "elemCN": "2m气温24h变温",
  303. "region": "cn",
  304. "model": "ecmwffine_b",
  305. "modelFileName": "ecmwffine"
  306. },
  307. {
  308. "elem": "wind10m",
  309. "elemCN": "10m风",
  310. "region": "cn",
  311. "model": "ecmwffine_b",
  312. "modelFileName": "ecmwffine"
  313. },
  314. {
  315. "elem": "10gust3",
  316. "elemCN": "10m阵风(过去3h)",
  317. "region": "cn",
  318. "model": "ecmwffine_b",
  319. "modelFileName": "ecmwffine"
  320. },],
  321. },
  322. };
  323.  
  324. let proxyConfig = new Proxy({
  325. showTcMode: false,
  326. showDetTcMode: true,
  327. showEnsTcMode: true,
  328. tcShowLock: false,
  329. preTime: '',
  330. preRegion: '',
  331. domTarget: {
  332. showTcMode: '#tc-mode-btn',
  333. showDetTcMode: '#tc-det-btn',
  334. showEnsTcMode: '#tc-ens-btn',
  335. },
  336. svgTarget: {
  337. showTcMode: '#wrap-draw-tc > svg',
  338. showDetTcMode: '#wrap-draw-tc > svg .svg-tc-det',
  339. showEnsTcMode: '#wrap-draw-tc > svg .svg-tc-ens',
  340. }
  341. }, {
  342. set: function (obj, prop, value, receiver) {
  343. if (prop === 'tcShowLock') return;
  344. const tcShowDom = document.querySelector(obj.domTarget.showTcMode);
  345. obj[prop] = value;
  346. // console.error(obj.showDetTcMode);
  347. // console.error(obj.showEnsTcMode);
  348. if (prop === 'showTcMode') {
  349.  
  350. let detDom = document.querySelector(obj.domTarget['showDetTcMode']);
  351. let ensDom = document.querySelector(obj.domTarget['showEnsTcMode']);
  352. if (value === true) {
  353. tcShowDom.classList.add('active-button');
  354. detDom.classList.remove('display-none');
  355. ensDom.classList.remove('display-none');
  356. if (obj.showDetTcMode || obj.showEnsTcMode) showSvgTC();
  357. // 如果全为false则不执行showSvgTC();
  358.  
  359. // TODO 显示SVG
  360. // 重绘SVG
  361. } else {
  362. tcShowDom.classList.remove('active-button');
  363. detDom.classList.add('display-none');
  364. ensDom.classList.add('display-none');
  365. let svgTC = document.querySelector('#wrap-draw-tc');
  366. if (svgTC) svgTC.classList.add('display-none');
  367. }
  368.  
  369. } else if (prop == 'showDetTcMode' || prop == 'showEnsTcMode') {
  370.  
  371. let targetDom = document.querySelector(obj.domTarget[prop]);
  372. if (value === true) {
  373. targetDom.classList.add('active-button');
  374. showSvgTC();
  375. } else {
  376. targetDom.classList.remove('active-button');
  377. let targetSvgGroup = document.querySelector(obj.svgTarget[prop]);
  378. if (targetSvgGroup) targetSvgGroup.classList.add('display-none');
  379.  
  380. if (!obj.showDetTcMode && !obj.showEnsTcMode) {// 全为flase则取消台风选择
  381. receiver.showTcMode = false;
  382. }
  383. // svg-tc-ens
  384. // TODO 隐藏对应的<g>元素
  385. }
  386. }
  387. }
  388. })
  389.  
  390. const helperConfig = {
  391. region: {
  392. hn: {//华南
  393. isMap: true,// 是否是等经纬地图
  394. projection: 'Equidistant',// 投影
  395. latLon: {
  396. xRight: 636.98, xLeft: 172.56,
  397. yTop: 56.516, yBottom: 547.576,
  398. lon0: 105.0, lon1: 120.0,
  399. lat0: 15.0, lat1: 30.0,
  400. },
  401. },
  402. cn: {//中国
  403. isMap: true,// 是否是等经纬地图
  404. projection: 'Lambert',
  405. latLon: {
  406. refLon0: 110.25, refLat0: 20,
  407. m0: 342.98333, n0: 382.51666,
  408. // refLon0:110,refLat0:20,
  409. // m0:338.98,n0:382.51,
  410. refLat1: 1.001, refLat2: 25.0,
  411. lonr: 80, latr: 40,
  412. mr: 94.983, nr: 167.51,
  413. },
  414.  
  415. },
  416. oy: {//欧亚
  417. isMap: true,// 是否是等经纬地图
  418. projection: 'Mercator',
  419. latLon: {
  420. xRight: 692.9833, xLeft: 156.9833,
  421. yTop: 54.51666, yBottom: 533.51666,
  422. lon0: 60.0, lon1: 160.0,
  423. lat0: 10.0, lat1: 70.0,
  424. },
  425. },
  426. gd: {//广东
  427. isMap: true,// 是否是等经纬地图
  428. projection: 'Equidistant',// 投影
  429. latLon: {
  430. xRight: 589.98334, xLeft: 124.98334,
  431. yTop: 104.51666, yBottom: 437.51666,
  432. lon0: 110, lon1: 116,
  433. lat0: 21.0, lat1: 25.0,
  434. },
  435. },
  436. hy: {//海洋
  437. isMap: true,// 是否是等经纬地图
  438. projection: 'Lambert',// 投影
  439. latLon: {
  440. refLon0: 110, refLat0: 0,
  441. m0: 274.983, n0: 490.51666,
  442. refLat1: 1.001, refLat2: 25.0,
  443. lonr: 100, latr: 20,
  444. mr: 171.983, nr: 270.51666,
  445. },
  446. },
  447. '86st': {//单站
  448. isMap: false,
  449. },
  450. rainnest: {//雨涡
  451. isMap: false,
  452. },
  453. '5pro': {//雨涡
  454. isMap: false,
  455. },
  456.  
  457. },
  458. currentRegion: 'hn',
  459. matchImgXY: () => { },// 根据经纬度获取图像位置
  460. matchLoc: () => { },// 获取经纬度的函数
  461. matchParam: '',// 调用上式的第二参数
  462. };
  463.  
  464.  
  465. //
  466. /**
  467. * unsafeWindow函数
  468. * selectProItem
  469. */
  470.  
  471. const utils = {
  472. log(arg) {
  473. userConfig.debug ? console.log(arg) : '';
  474. },
  475. changeRegion(region) {
  476. helperConfig.currentRegion = region;
  477. return helperConfig.region[region].isMap;// 返回是否是地图
  478. },
  479. getJSON(url) {// 获取json数据
  480. return new Promise(function (resolve, reject) {
  481. GM_xmlhttpRequest({ //获取时间序列
  482. method: 'GET',
  483. synchronous: false,
  484. url: url,
  485. onload: function (reDetails) {
  486. if (reDetails.status !== 200 && reDetails.status !== 304) {
  487. console.error('获取URL错误');
  488. // showNotification('数据中心数据获取异常');
  489. let errorNotice = GM_notification({ text: '数据获取异常' + url, image: 'http://10.148.8.228/images/logo.png', title: '接口异常', timeout: 3000 });
  490. if (errorNotice) setTimeout(() => errorNotice.remove(), 3000);
  491. reject(new Error('数据获取异常' + url));
  492. }
  493. const data = JSON.parse(reDetails.responseText);
  494. //console.log(data.DATA);
  495. resolve(data);
  496. }
  497. });
  498. })
  499. },
  500. projection: {
  501. Mercator: {// 墨卡托投影
  502. calBasicInfo(lat1 = 0, lat2 = 0, n1 = 0, n2 = 0) {
  503. /*参数lat 纬度, n坐标数值
  504. n0 赤道,d0放大系数
  505. */
  506. const y1 = Math.log(Math.tan(lat1) + 1 / Math.cos(lat1));
  507. const y2 = Math.log(Math.tan(lat2) + 1 / Math.cos(lat2));
  508. const n0 = (n1 - (y1 / y2) * n2) / (1.0 - y1 / y2);
  509. const d0 = y1 / (n0 - n1);
  510. return { n0, d0 };
  511. },
  512. calLatLon(mouseXY, dims) {
  513. // console.log(dims);
  514. const n0 = dims.n0;
  515. const d0 = dims.d0;
  516. // console.log(Math.sinh((n0-mouseXY.y)*d0));
  517. const lat = Math.atan(Math.sinh((n0 - mouseXY.y) * d0));
  518. ///---------------//
  519. const r = dims.xLeft;
  520. const o = dims.xRight;
  521. const s = (dims.lon1 - dims.lon0) / (o - r);
  522. const u = dims.lon0 + (mouseXY.x - r) * s;
  523. return { lat: lat * 180 / Math.PI, lon: u };
  524. },
  525. calImgLoc(latlon, dims = { xLeft, xRight, lon1, lon0, n0, d0 }) {
  526. const n0 = dims.n0;
  527. const d0 = dims.d0;
  528. const sec = (x = 0) => 1 / Math.cos(x);// 正割
  529.  
  530. const lon = latlon.lon;
  531. const r = dims.xLeft;
  532. const o = dims.xRight;
  533. const s = (dims.lon1 - dims.lon0) / (o - r);
  534. const x = (lon - dims.lon0) / s + r;
  535.  
  536. const phi = latlon.lat / 180 * Math.PI;
  537. const y = n0 - Math.log(Math.tan(phi) + sec(phi)) / d0;
  538. const mouseXY = { x, y };
  539. return mouseXY;
  540. },
  541. },
  542. Equidistant: {//等经纬度
  543. calBasicInfo() {
  544. return helperConfig.region[helperConfig.currentRegion].latLon
  545. },
  546. calLatLon(mouseXY, dims) {
  547. // console.log(dims);
  548. const r = dims.xLeft;
  549. const o = dims.xRight;
  550. const i = dims.yTop;
  551. const l = dims.yBottom;
  552. const s = (dims.lon1 - dims.lon0) / (o - r); // o - r 内框宽度 -> s = lon/height
  553. const d = (dims.lat1 - dims.lat0) / (i - l);// i - l 内框高度 -> d = lat/width
  554. const u = dims.lon0 + (mouseXY.x - r) * s;
  555. const m = dims.lat1 + (mouseXY.y - i) * d;
  556. return { lat: m, lon: u };
  557. },
  558. calImgLoc(latlon = { lat, lon }, dims) {
  559. const r = dims.xLeft;
  560. const o = dims.xRight;
  561. const i = dims.yTop;
  562. const l = dims.yBottom;
  563. const s = (dims.lon1 - dims.lon0) / (o - r); // o - r 内框宽度 -> s = lon/height
  564. const d = (dims.lat1 - dims.lat0) / (i - l);// i - l 内框高度 -> d = lat/width
  565. const lat = latlon.lat;
  566. const lon = latlon.lon;
  567. const x = (lon - dims.lon0) / s + r;
  568. const y = (lat - dims.lat1) / d + i;
  569. const mouseXY = { x, y };
  570. return mouseXY;
  571. },
  572.  
  573. },
  574. Lambert: {// 兰伯特投影
  575. calBasicInfo({ refLon0, refLat0, m0, n0, refLat1, refLat2, lonr, latr, mr, nr }) {
  576. /*
  577. refLat0,refLon0,m0,n0 参考纬度、经度,屏幕X坐标,Y坐标;
  578. 屏幕坐标与实际坐标映射:
  579. x = (m-m0)*dx // dx为x方向比例系数
  580. y = (n0-n)*dy // dy为y方向比例系数,y方向屏幕坐标反向,所以取反
  581. refLat1,refLat2 2个平行纬度
  582. latr,lonr,mr,nr 选取的另外一个点的经纬度和屏幕坐标
  583.  
  584. phi 纬度,lambda经度
  585.  
  586. */
  587. const ang2rad = (x = 0) => Math.PI * x / 180.0;
  588. const [phi0, phi1, phi2] = [ang2rad(refLat0), ang2rad(refLat1), ang2rad(refLat2)];
  589. //console.log(phi0,phi1,phi2);
  590. const lambda0 = ang2rad(refLon0);
  591. const [tan, cos, sin, pow, PI, ln] = [Math.tan, Math.cos, Math.sin, Math.pow, Math.PI, Math.log];
  592. const cot = (x = 0) => Math.cos(x) / Math.sin(x);// 余切
  593. const sec = (x = 0) => 1 / Math.cos(x);// 正割
  594.  
  595. const n = ln(cos(phi1) * sec(phi2)) / ln(tan(0.25 * PI + 0.5 * phi2) * cot(0.25 * PI + 0.5 * phi1));
  596. const F = (cos(phi1) * pow(tan(0.25 * PI + 0.5 * phi1), n)) / n;
  597. // n,F常量参数
  598. let rho = (phi = 0) => F * pow(cot(0.25 * PI + 0.5 * phi), n); // rho变量
  599. let rho0 = rho(phi0);
  600. let fx = (phi, lambda) => rho(phi) * sin(n * (lambda - lambda0));
  601. let fy = (phi, lambda) => rho0 - rho(phi) * cos(n * (lambda - lambda0));
  602. const dx = fx(ang2rad(latr), ang2rad(lonr)) / (mr - m0);
  603. const dy = fy(ang2rad(latr), ang2rad(lonr)) / (n0 - nr);
  604. // console.log(dx,dy);
  605. return { dx, dy, F, n, rho, rho0, lambda0, m0, n0 };
  606. },
  607. calLatLon(mouseXY, { dx, dy, F, n, rho0, lambda0, m0, n0 }) {
  608. const x = (mouseXY.x - m0) * dx;
  609. const y = (n0 - mouseXY.y) * dy;
  610.  
  611. let rho = (x, y) => Math.sign(n) * Math.sqrt(Math.pow(x, 2) + Math.pow((rho0 - y), 2));
  612.  
  613. let theta = (x, y) => Math.atan(x / (rho0 - y));
  614. let phi = (rho) => 2 * Math.atan(Math.pow(F / rho, 1 / n)) - 0.5 * Math.PI;
  615. let lambda = (theta) => lambda0 + theta / n;
  616. const lat = phi(rho(x, y)) * 180 / Math.PI;
  617. const lon = lambda(theta(x, y)) * 180 / Math.PI;
  618. // console.log(mouseXY.x-m0,n0-mouseXY.y);
  619. //console.log(mouseXY.x - m0,n0-mouseXY.y);
  620. return { lat, lon };
  621. },
  622. calImgLoc(latlon, { dx, dy, rho = () => { }, rho0, m0, n0, lambda0, n }) {
  623. const [cos, sin, PI] = [Math.cos, Math.sin, Math.PI];
  624. const phi = latlon.lat * PI / 180;
  625. const lambda = latlon.lon * PI / 180;
  626. const originX = (phi, lambda) => rho(phi) * sin(n * (lambda - lambda0));
  627. const originY = (phi, lambda) => rho0 - rho(phi) * cos(n * (lambda - lambda0));
  628. let mouseXY = { x: 0, y: 0 };
  629. mouseXY.x = originX(phi, lambda) / dx + m0;
  630. mouseXY.y = n0 - originY(phi, lambda) / dy;
  631. return mouseXY;
  632. }
  633. }
  634.  
  635. },
  636. debounce: (fn, delay, scope) => {
  637. let timer = null;
  638. // 返回函数对debounce作用域形成闭包
  639. return function () {
  640. // setTimeout()中用到函数环境总是window,故需要当前环境的副本;
  641. let context = scope || this, args = arguments;
  642. // 如果事件被触发,清除timer并重新开始计时
  643. clearTimeout(timer);
  644. timer = setTimeout(function () {
  645. fn.apply(context, args);
  646. }, delay);
  647. }
  648. },
  649. throttle(fn, threshold, scope) {
  650. let timer;
  651. let prev = Date.now();
  652. return function () {
  653. let context = scope || this, args = arguments;
  654. let now = Date.now();
  655. if (now - prev > threshold) {
  656. prev = now;
  657. fn.apply(context, args);
  658. }
  659. }
  660. },
  661. showSkewT(point = { lat: 21, lon: 120 }, runtime = '2019012018', fcHour = '18') {
  662. const latRange = [point.lat - 0.125 * 2, point.lat + 0.125 * 2];
  663. const lonRange = [point.lon - 0.125 * 2, point.lon + 0.125 * 2];
  664. let fcTime = utils.getFcTime();
  665. const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH');
  666. runtime = initTime.format('YYYYMMDDHH');
  667. fcHour = fcTime.fcHour;
  668. GM_openInTab(`https://www.tropicaltidbits.com/analysis/models/sounding/?model=gfs&runtime=${runtime}&fh=${fcHour}&domain=${lonRange[0].toFixed(2)},${lonRange[1].toFixed(2)},${latRange[0].toFixed(2)},${latRange[1].toFixed(2)}&stationID=&tc=&mode=regular`);
  669. // https://www.tropicaltidbits.com/analysis/models/sounding/?model=gfs&runtime=2018122418&fh=18&domain=120,122.5,20,22.5&stationID=&tc=&mode=regular
  670. },
  671. showVertical(p0 = { lat: 21, lon: 120 }, p1 = { lat: 23, lon: 130 }, runtime = '2019012018', fcHour = '18') {
  672. let fcTime = utils.getFcTime();
  673. const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH');
  674. runtime = initTime.format('YYYYMMDDHH');
  675. fcHour = fcTime.fcHour;
  676. let type = 'RH_and_Omega';//[FGEN,_%CE%B8%E2%82%91,_Omega,RH_and_Omega,Normal_Wind,In-Plane_Wind]
  677. GM_openInTab(`https://www.tropicaltidbits.com/analysis/models/xsection/?model=gfs&runtime=${runtime}&fh=${fcHour}&p0=${p0.lat.toFixed(2)},${p0.lon.toFixed(2)}&p1=${p1.lat.toFixed(2)},${p1.lon.toFixed(2)}&type=${type}&tc=`);
  678. // https://www.tropicaltidbits.com/analysis/models/xsection/?model=gfs&runtime=2018122500&fh=6&p0=31.23,-113.14&p1=39.72,-95.24&type=FGEN,_%CE%B8%E2%82%91,_Omega&tc=
  679. },
  680. showCross(p0 = { lat: 21, lon: 120 }, p1 = { lat: 23, lon: 130 }, runtime = '2020112812', fcHour = '18') {
  681. let fcTime = utils.getFcTime();
  682. const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH');
  683. runtime = initTime.format('YYYYMMDDHH');
  684. fcHour = fcTime.fcHour;
  685. // let type = 'RH_and_Omega';//[FGEN,_%CE%B8%E2%82%91,_Omega,RH_and_Omega,Normal_Wind,In-Plane_Wind]
  686. // GM_openInTab(`https://www.tropicaltidbits.com/analysis/models/xsection/?model=gfs&runtime=${runtime}&fh=${fcHour}&p0=${p0.lat.toFixed(2)},${p0.lon.toFixed(2)}&p1=${p1.lat.toFixed(2)},${p1.lon.toFixed(2)}&type=${type}&tc=`);
  687. GM_openInTab(`https://research.gdmo.gq/cross?source=ecmwf&method=cross&latA=${p0.lat.toFixed(2)}&lonA=${p0.lon.toFixed(2)}&latB=${p1.lat.toFixed(2)}&lonB=${p1.lon.toFixed(2)}&elems=rhum,uv&level=1000,200&initTime=${runtime}&fc=${fcHour}`);
  688. },
  689. showTimeSeries(point = { lat: 21, lon: 120 }) {
  690. utils.log(point);
  691. // showSvgTC()
  692. // .catch(err=>{
  693. // console.error(err);
  694. // });
  695. if (point.mouseXY) {
  696. const elem = document.getElementById('map-pointer');
  697. elem.style.marginLeft = point.mouseXY.x + 29.31 + 'px';
  698. elem.style.marginTop = point.mouseXY.y - 33.066 + 'px';
  699. }
  700. const fcTime = utils.getFcTime();
  701. const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH');
  702. const sT = initTime.format('YYYY-MM-DD%20HH:mm:ss');
  703. const eT = moment(initTime).add(10 * 24, 'hours').format('YYYY-MM-DD%20HH:mm:ss');
  704. point.lat = utils.fix2Grid(point.lat);
  705. point.lon = utils.fix2Grid(point.lon);//取格点位置
  706. getTimeSeries(model = 'ecmwfthin',
  707. sT, eT,
  708. lon = point.lon, lat = point.lat,
  709. eles = ['t2mm', 'mn2t', 'mx2t', 'u10m', 'v10m', 'tcco', 'lcco', 'tppm']);
  710. },
  711. fix2Grid(number, interval = 0.125) {
  712. let fixNum = Math.round(number / interval) * interval;
  713. return fixNum;
  714. },
  715. getFcTime() {
  716. const fcHour = typeof (unsafeWindow.forecastHour) === 'string' ? unsafeWindow.forecastHour : unsafeWindow.forecastHour[0];
  717. let idate = unsafeWindow.$settings.date;
  718. if (!idate.length) throw new Error('日期为空错误');
  719. if (idate[6] == '-') idate = idate.slice(0, 5) + '0' + idate.slice(5);
  720. if (idate.length === 9) idate = idate.slice(0, 8) + '0' + idate.slice(8);
  721. const hr = unsafeWindow.$settings.HH;
  722. return { date: idate, hr, fcHour };//format->date:'2019-01-22;, hr:'12';fcHour:'000'
  723. },
  724. getTimeSeries(point = { lat: 21, lon: 115 }, model = 'giftdaily', eles = [t2mm, tmax, tmin], start = '2014-11-22%2000:00:00') {
  725.  
  726. //http://172.22.1.175/di/grid.action?userId=sqxt&pwd=shengqxt123&interfaceId=intGetMultElesDataTimeSerial&dataFormat=xml2&modelid=giftdaily&element=t2mm%20tmax%20tmin&level=1000&starttime=2014-11-22%2000:00:00&endtime=2014-11-25%2000:00:00&lon=113.5&lat=24.5
  727. },
  728. calWind(u10, v10) {
  729. const iSpeed = Math.sqrt(Math.pow(u10, 2) + Math.pow(v10, 2));//风速
  730. const iR = Math.sign(v10) * Math.acos(u10 / iSpeed);//标准坐标系弧度
  731. const arrowR = iR - Math.PI / 2;//矢量箭头偏移弧度
  732. let northDir = -(iR + Math.PI - Math.PI / 2);//与北向的角度差
  733. if (northDir < 0) {
  734. northDir = northDir + Math.PI * 2;
  735. }
  736. const dir = northDir / Math.PI * 180;
  737. if (dir > 360) dir = dir - 360;
  738. return { speed: iSpeed, rotation: arrowR, northDir: northDir };
  739. },
  740. renderArrow(param, api) {
  741. let arrowSize = 10;
  742. var point = api.coord([
  743. api.value(2),//dims.timeIndex
  744. api.value(0)//5//api.value(dims.windSpeed)
  745. ]);
  746.  
  747. return {
  748. type: 'path',
  749. shape: {
  750. //pathData: 'M31 16l-15-15v9h-26v12h26v9z',
  751. pathData: 'M250 0 L140 350 L250 250 L360 350 Z',
  752. x: -arrowSize / 2,
  753. y: -arrowSize / 2,
  754. width: arrowSize,
  755. height: arrowSize
  756. },
  757. rotation: api.value(1),//api.value(dims.R),//Math.PI / 8 * index;
  758. position: point,
  759. style: api.style({
  760. stroke: '#555',
  761. lineWidth: 1,
  762. fill: 'green',
  763. })
  764. };
  765. },
  766. createElement(type = 'div', props = {}) {
  767. let newEle = document.createElement(type);
  768. for (let attr in props) {
  769. newEle.setAttribute(attr, props[attr]);
  770. }
  771. return newEle;
  772. },
  773. getImgNow(imgSrc) {
  774. // TODO 改进匹配规则
  775. let fcTime = utils.getFcTime();
  776. //const initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH');
  777. // runtime = initTime.format('YYYYMMDDHH');
  778. fcHour = fcTime.fcHour;
  779. let url = imgSrc || unsafeWindow.imgSrc[fcHour];
  780. if (!url) throw new Error('无法获取初始图像');
  781.  
  782. // url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_000_2019012800.png';
  783. const reg = /(http:.*?\/)(\d{8})(.*?_)(\d+)_(\d+)(.*?$)/;
  784. const matchUrl = url.match(reg);
  785. /*0: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_030_2019012800.png"
  786. 1: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/"
  787. 2: "20190128"
  788. 3: "/ecmwffine_hn_mslp_"
  789. 4: "030"
  790. 5: "2019012800"
  791. 6: ".png"
  792. index: 0
  793. input: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_030_2019012800.png"
  794. length: 7 */
  795.  
  796. const base = [matchUrl[1], matchUrl[3], matchUrl[6]];
  797. const model = base[0].match(/znwp\/(.*?)\//)[1];
  798. // console.log(base[1]);
  799. const eleName = base[1].match(/_.*?_(.+?)_$/)[1];
  800. const date = matchUrl[2];
  801. const hour = matchUrl[4];
  802. const initDate = matchUrl[5];
  803.  
  804. const imgInfo = {
  805. url,
  806. date,
  807. base,
  808. hour,
  809. initDate,
  810. model,
  811. eleName,
  812. getUrl(date = date, hour = hour, initDate = initDate) {
  813. return base[0] + date + base[1] + hour + '_' + initDate + base[2];
  814. }
  815. }
  816. return imgInfo;
  817. },
  818. matchImgPattern(pat = { elem: "t2mm", elemCN: "2m气温", region: "cn", model: "ecmwffine_b", modelFileName: "ecmwffine" }) {
  819. let base0 = `http://10.148.8.228/files_home/znwp/${pat.model}/${pat.region}/`;
  820. let base1 = `/${pat.modelFileName}_${pat.region}_${pat.elem}_`;
  821. return {
  822. // base:['http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/','/ecmwffine_hn_mslp_','.png'],
  823. base: [base0, base1, '.png'],
  824. };
  825. },
  826. getImgUrl(base, date, hour, initDate) {
  827. return base[0] + date + base[1] + hour + '_' + initDate + base[2];
  828. },
  829. paddingInt(num, length = 3) {
  830. //这里用slice和substr均可
  831. return (Array(length).join("0") + num).slice(-length);
  832. },
  833. getElemRelPos(e, t, n) {// e.target, e.clientX, e.clientY
  834. var a = e.getBoundingClientRect(),
  835. r = getComputedStyle(e);
  836. return {
  837. x: t - (a.left + parseFloat(r.paddingLeft) + parseFloat(r.borderLeftWidth)),
  838. y: n - (a.top + parseFloat(r.paddingTop) + parseFloat(r.borderTopWidth))
  839. };
  840. },
  841. };
  842.  
  843.  
  844. var NWP_init = function () {
  845. var fcHour = document.getElementById('forecast_hour');
  846. var iniTime = document.getElementById('create_day');
  847. var infoBar = document.getElementById('pic_info');
  848. var referNode = document.getElementById('to_contrast');
  849. var divTime = document.createElement('span');
  850. divTime.textContent = 'hello world';
  851. divTime.setAttribute('class', 'lcTime');
  852. divTime.style.position = 'relative';
  853. divTime.style.float = 'right';
  854. divTime.style.right = '120px';
  855. infoBar.insertBefore(divTime, referNode);
  856. // document.querySelector("#forecast_hours div").textContent = "日期";
  857.  
  858. // create an observer instance
  859. var UTC8 = new MutationObserver(function (mutations) {
  860. // TODO 对分钟变化变量的修正
  861. var dateString = iniTime.textContent.match(/(\d+).*?(\d+).*?(\d+).*?(\d+)/);
  862. var fcDate = [];
  863. fcDate[0] = Number(dateString[1]);
  864. fcDate[1] = Number(dateString[2]);
  865. fcDate[2] = Number(dateString[3]);
  866. fcDate[3] = Number(dateString[4]);
  867. fcDate[4] = Number(fcHour.textContent.match(/\d+/));
  868. fcDate[5] = new Date(fcDate[0], fcDate[1] - 1, fcDate[2], fcDate[3] + fcDate[4] + 8);
  869. var localTime = String(fcDate[5].getMonth() + 1) + '月' + fcDate[5].getDate() +
  870. '日' + fcDate[5].getHours() + '时 GMT+8';
  871. divTime.textContent = localTime;
  872. });
  873. // configuration of the observer:
  874. var config = {
  875. attributes: true,
  876. childList: true,
  877. characterData: true
  878. };
  879. UTC8.observe(fcHour, config);
  880. // later, you can stop observing
  881. //observer.disconnect();
  882. //
  883. //
  884. /////////////////////////////////////////////////////////////
  885.  
  886.  
  887. //
  888. ///
  889. ////////////////////修改时效列/////////////////////////////////////////
  890. var alterTimelist = function (mutations) {
  891. //alert(timeBar.length);
  892. // console.log('修改时效');
  893. if (userConfig.timelineMove) {
  894. timeMoveOver();//是否启用滑动时间触发
  895. }
  896. if (!userConfig.alterDate) return; // 不修改则直接返回
  897. var dateString = iniTime.textContent.match(/(\d+).*?(\d+).*?(\d+).*?(\d+)/);
  898. var fcDate = [];
  899. fcDate[0] = Number(dateString[1]);
  900. fcDate[1] = Number(dateString[2]);
  901. fcDate[2] = Number(dateString[3]);
  902. fcDate[3] = Number(dateString[4]);
  903. for (let i = 0; i < timeBar.length; i++) {
  904. let oValue = timeBar[i].value;
  905. fcDate[4] = Number(timeBar[i].value);
  906.  
  907. fcDate[5] = new Date(fcDate[0], fcDate[1] - 1, fcDate[2], fcDate[3] + fcDate[4] + 8);
  908.  
  909. let iday = String(fcDate[5].getDate());
  910. iday = Array(2 > iday.length ? 2 - iday.length + 1 || 0 : 0).join(0) + iday;
  911.  
  912. let ihour = String(fcDate[5].getHours());
  913. ihour = Array(2 > ihour.length ? 2 - ihour.length + 1 || 0 : 0).join(0) + ihour;
  914.  
  915. let localTime = iday + ' ' + ihour + ' ;';
  916. let styleText = '#' + timeBar[i].getAttribute("id") + ':before{white-space:pre;content: " ' + localTime + ' "}';
  917. GM_addStyle(styleText);
  918.  
  919. switch (fcDate[5].getHours()) {
  920. // case 5:
  921. // timeBar[i].style.cssText = "border-left:2px solid #9B30FF";
  922. // break;
  923. // case 14:
  924. // timeBar[i].style.cssText = "border-left:2px solid #EE3B3B";
  925. // break;
  926. case 20:
  927. timeBar[i].style.cssText = "border-bottom:1px dotted #8E8E8E;border-left:2px solid #ffffff;";
  928. break;
  929. default:
  930. timeBar[i].style.cssText = "border-left:2px solid #ffffff;";
  931. }
  932. }
  933. };
  934. /////////////////////////////////////////////////////////////
  935. ///
  936. var selectObserver = new MutationObserver(alterTimelist);
  937. // configuration of the observer:
  938. var timeBar = document.querySelector("#forecast_hours select");
  939. var config2 = {
  940. attributes: false,
  941. childList: true,
  942. characterData: false,
  943. };
  944. selectObserver.observe(timeBar, config2);
  945. GM_addStyle("#forecast_hours option{width: 50px!important; overflow: hidden!important;}");
  946.  
  947. //-----------------------------------------------------------------------------//
  948. //-- 添加多模式、多要素按钮mvBtnObesrver, initMvMode---//
  949. let imgObserver = new MutationObserver(fitImgLoc);
  950. imgObserver.observe(timeBar, config2);
  951. //-----------------------------------绑定坐标-----------------------------------//
  952. function fitImgLoc() { // 绑定img包含的div元素
  953. const isMap = utils.changeRegion(unsafeWindow._region); // 改变地图;
  954. // console.log(isMap);
  955. if (!isMap) {
  956. proxyConfig.showTcMode = false;
  957. return; // TODO 不是地图需要去除掉经纬度计算
  958. } else {
  959. if (proxyConfig.showTcMode) {
  960. let currentFcTime = utils.getFcTime();
  961. let currentTime = currentFcTime.date + currentFcTime.hr;
  962. let currentRegion = unsafeWindow._region;
  963. console.log(currentTime, proxyConfig.preTime);
  964. console.log(currentRegion, proxyConfig.preRegion);
  965. if (currentTime != proxyConfig.preTime || currentRegion != proxyConfig.preRegion) {
  966. showSvgTC(); // 绘制台风
  967. } else {
  968. '';
  969. }
  970. }
  971. /////TODO 判断地图逻辑分离, 不是地图应该取消掉交互模式和latlon显示功能
  972. const currMap = helperConfig.region[helperConfig.currentRegion];
  973. const currProjection = currMap.projection;
  974. //let matchLoc = ({});
  975. //let param = ({});
  976. switch (currProjection) {
  977. case 'Mercator': {
  978. const dims = currMap.latLon;
  979. const lat1 = Math.PI / 180.0 * dims.lat0;
  980. const lat2 = Math.PI / 180.0 * dims.lat1;
  981. const n1 = dims.yBottom;
  982. const n2 = dims.yTop;
  983. // console.log(dims);
  984. const param1 = utils.projection.Mercator.calBasicInfo(
  985. lat1, lat2, n1, n2
  986. );
  987. helperConfig.matchParam = {
  988. ...param1,
  989. xRight: dims.xRight,
  990. xLeft: dims.xLeft,
  991. lon0: dims.lon0,
  992. lon1: dims.lon1,
  993. };
  994. // console.log(helperConfig.matchParam);
  995. helperConfig.matchLoc = utils.projection.Mercator.calLatLon;
  996. helperConfig.matchImgXY = utils.projection.Mercator.calImgLoc;
  997. break;
  998. }
  999. case 'Lambert': {
  1000. const LamDim = currMap.latLon;
  1001. /* const LamParam1 =
  1002. [ LamDim.refLon0, LamDim.refLat0, LamDim.m0, LamDim.n0,
  1003. LamDim.refLat1, LamDim.refLat2,
  1004. LamDim.lonr, LamDim.latr, LamDim.mr, LamDim.nr]; */
  1005. const LamParam2 = utils.projection.Lambert.calBasicInfo(LamDim);
  1006. // console.log(LamParam2);
  1007. helperConfig.matchParam = LamParam2;
  1008. helperConfig.matchLoc = utils.projection.Lambert.calLatLon;
  1009. helperConfig.matchImgXY = utils.projection.Lambert.calImgLoc;
  1010. break;
  1011. }
  1012. default:
  1013. helperConfig.matchParam = currMap.latLon;
  1014. helperConfig.matchLoc = utils.projection.Equidistant.calLatLon;
  1015. helperConfig.matchImgXY = utils.projection.Equidistant.calImgLoc;
  1016. break;
  1017. }
  1018.  
  1019. //let wrapDiv = document.querySelector('#pic_frame div');
  1020. let wrapDiv = document.querySelector('#pic_frame');
  1021. // document.addEventListener('keyup', console.log(elemsConfig.fixPoint));
  1022. if (wrapDiv) {
  1023. wrapDiv.addEventListener('mousemove', utils.debounce(getMouseLatLon, 100));
  1024. // wrapDiv.addEventListener('mousemove', getMouseLatLon);
  1025. // console.log(wrapDiv.clientWidth);// offsetWidth , clientWidth, scrollWidth
  1026. }
  1027. }
  1028. }
  1029.  
  1030. function getElemRelPos(e, t, n) {// e.target, e.clientX, e.clientY
  1031. var a = e.getBoundingClientRect(),
  1032. r = getComputedStyle(e);
  1033. /**
  1034. * e.getBoundingClientRect()
  1035. * bottom: 916.1999969482422
  1036. height: 716 ​
  1037. left: 686.9000244140625 ​
  1038. right: 1565.1333618164062 ​
  1039. top: 200.1999969482422 ​
  1040. width: 878.2333374023438 ​
  1041. x: 686.9000244140625 ​
  1042. y: 200.1999969482422
  1043. borderLeftWidth: "1px"
  1044. borderTopWidth: "1px"
  1045. paddingLeft: "0px"
  1046. paddingTop: "0px"
  1047. */
  1048. return {
  1049. x: t - (a.left + parseFloat(r.paddingLeft) + parseFloat(r.borderLeftWidth)),
  1050. y: n - (a.top + parseFloat(r.paddingTop) + parseFloat(r.borderTopWidth))
  1051. };
  1052. }
  1053.  
  1054. function getMouseLatLon(event) {
  1055. // console.log(event);
  1056. // console.log(event.target);
  1057. // let target = event.target;//
  1058. let target = document.querySelector('#pic_frame img[style~="inline;"]');//
  1059. // console.log(target);
  1060. const mouseXY = getElemRelPos(target, event.clientX, event.clientY); // 相对图像的像素位置{x,y}
  1061. const loc = helperConfig.matchLoc(mouseXY, helperConfig.matchParam);
  1062. elemsConfig.latLonInput.lat.innerHTML = loc.lat; // mouseXY.y
  1063. elemsConfig.latLonInput.lon.innerHTML = loc.lon; // mouseXY.x
  1064. // elemsConfig.latLonInput.lat.innerHTML = mouseXY.y
  1065. // elemsConfig.latLonInput.lon.innerHTML = mouseXY.x
  1066. elemsConfig.fixPoint = { lat: loc.lat, lon: loc.lon, mouseXY };
  1067. return { lat: loc.lat, lon: loc.lon, mouseXY };
  1068. }
  1069. //------------------------------24小时跳跃-------------------------------------//
  1070. const timeJump = function () {
  1071. //var hourBar = document.getElementById('from_hour');float-l
  1072. var jumpParent = document.querySelector('.float-l');
  1073. var pre24 = document.createElement('button');
  1074. pre24.addEventListener("click", function () { timeTrigger(-24); });
  1075. pre24.textContent = "-24";
  1076. jumpParent.appendChild(pre24);
  1077.  
  1078. var next24 = document.createElement('button');
  1079. next24.addEventListener("click", function () { timeTrigger(24); });
  1080. next24.textContent = "+24";
  1081. jumpParent.appendChild(next24);
  1082.  
  1083. var timeTrigger = function (timer) {
  1084. let selectedVal = timeBar[timeBar.selectedIndex].getAttribute("data-hour");
  1085. let nextVal = String(Number(selectedVal) + timer);
  1086. var posi = 3;
  1087. nextVal = Array(posi > nextVal.length ? posi - nextVal.length + 1 || 0 : 0).join(0) + nextVal;
  1088. let nextopt = timeBar.querySelector("#option_" + nextVal);
  1089. //alert(nextopt);
  1090. if (!nextopt) return;
  1091. timeBar[timeBar.selectedIndex].selected = false;
  1092. nextopt.selected = true;
  1093.  
  1094. // var changeEvt = document.createEvent('HTMLEvents');
  1095. // changeEvt.initEvent('change',true,true);
  1096. // timeBar.dispatchEvent(changeEvt);
  1097. var changeEvt = new Event('change', { 'bubbles': true });
  1098. timeBar.dispatchEvent(changeEvt);
  1099. };
  1100. };
  1101.  
  1102. timeJump();
  1103. /////切换时效
  1104.  
  1105. function switchDate() {
  1106. userConfig.alterDate = !userConfig.alterDate;
  1107. if (userConfig.alterDate) {
  1108. switchDateBtn.textContent = "切换成时效";
  1109. alterTimelist();
  1110. }
  1111. else {
  1112. switchDateBtn.textContent = "切换成日期";
  1113. for (let ele of timeBar) {
  1114. ele.style.cssText = '';
  1115. let styleText = '#' + ele.getAttribute("id") + ':before{white-space:pre;content: ""}';
  1116. GM_addStyle(styleText);
  1117. }
  1118. }
  1119. }
  1120.  
  1121. var switchParent = document.querySelector('.float-l');
  1122. let switchDateBtn = document.createElement('button');
  1123. switchDateBtn.addEventListener("click", switchDate);
  1124. switchDateBtn.textContent = "切换成日期";
  1125. switchParent.appendChild(switchDateBtn);
  1126. /////end 切换时效 /////
  1127.  
  1128. //设置鼠标事件//
  1129. document.onkeydown = function (evt) {
  1130. if (evt.key == 'x' && !evt.ctrlKey) {
  1131. console.log(utils.showSkewT(elemsConfig.fixPoint));
  1132. }
  1133. else if (evt.key == 'x' && evt.ctrlKey) {
  1134. if (!elemsConfig.point01) {
  1135. elemsConfig.point01 = Object.assign({}, elemsConfig.fixPoint);
  1136. }
  1137. else {
  1138. elemsConfig.point02 = Object.assign({}, elemsConfig.fixPoint);
  1139. console.log(elemsConfig.point01, elemsConfig.point02);
  1140. utils.showVertical(elemsConfig.point01, elemsConfig.point02);
  1141. elemsConfig.point01 = null;
  1142. elemsConfig.point02 = null;
  1143. }
  1144. }
  1145. if (evt.key == 'c' && !evt.ctrlKey && !evt.altKey) {
  1146. utils.showTimeSeries(elemsConfig.fixPoint);
  1147. }
  1148. }
  1149. //设置鼠标事件//
  1150.  
  1151. /**
  1152. * 时间鼠标滑过
  1153. */
  1154. function timeMoveOver() {
  1155. // var changeEvent = document.createEvent('HTMLEvents');
  1156. // changeEvent.initEvent("change", true, true);
  1157. var changeEvent = new Event('change', { 'bubbles': true });
  1158.  
  1159. var clickOpt = (evt) => {
  1160. console.log(evt.target);
  1161. timeBar[timeBar.selectedIndex].selected = false;
  1162. evt.target.selected = true;
  1163. timeBar.dispatchEvent(changeEvent);
  1164. }
  1165. var timeBar = document.querySelector("#forecast_hours select");
  1166. var opts = document.querySelectorAll('#forecast_hours option');
  1167. opts.forEach(item => {
  1168. item.addEventListener('mouseover', clickOpt);
  1169. });
  1170. }
  1171. };
  1172.  
  1173.  
  1174. function toggleSelectMode() {
  1175. let mapPointer = document.getElementById('map-pointer');
  1176. let bodyDOM = document.getElementsByTagName('body')[0];
  1177. let linePanel = document.getElementById('line_panel');
  1178. let selectModePanel = document.getElementById('select-mode-panel');
  1179. if (bodyDOM.classList.contains("full-screen-mode")) {
  1180. bodyDOM.classList.remove("full-screen-mode");
  1181. linePanel.style.display = 'none';
  1182. selectModePanel.style.display = 'none';
  1183. mapPointer.classList.add("display-none");
  1184. let crossCanvas = document.getElementById('cv-draw-line');
  1185. crossCanvas.classList.add('display-none');
  1186. let activeBtn = document.getElementById('edit-helper-panel');
  1187. activeBtn.classList.remove('active-button');
  1188. let crossBtn = document.getElementById('helper_cross');
  1189. crossBtn ? crossBtn.classList.remove('active-button') : '';
  1190. return;
  1191. } else {
  1192. bodyDOM.classList.add("full-screen-mode");
  1193. let contentWidth = window.innerWidth - document.body.clientWidth - 40;
  1194. if (contentWidth < 200) {
  1195. linePanel.style.width = '220px';
  1196. } else {
  1197. linePanel.style.width = contentWidth + 'px';
  1198. }
  1199. linePanel.style.display = 'block';
  1200. selectModePanel.style.display = 'block';
  1201. mapPointer.classList.add("display-none");
  1202. mapPointer.classList.remove("display-none");
  1203. let activeBtn = document.getElementById('edit-helper-panel');
  1204. activeBtn.classList.add('active-button');
  1205. // let crossBtn = document.getElementById('helper_cross');
  1206. // activeBtn.classList.add('active-button');
  1207. }
  1208. const imgXY = latlon2XY({ lon: 113.375, lat: 23.125 });
  1209.  
  1210. utils.showTimeSeries({ lon: '113.375', lat: '23.125', mouseXY: imgXY });
  1211. }
  1212.  
  1213. /**
  1214. * 切换垂直剖面
  1215. */
  1216. function toggleCrossSection() {
  1217. let crossCanvas = document.getElementById('cv-draw-line');
  1218. let activeBtn = document.getElementById('helper_cross');
  1219. crossCanvas.classList.toggle('display-none');
  1220. if (crossCanvas.classList.contains('display-none')) {
  1221. activeBtn.classList.remove('active-button');
  1222. } else {
  1223. activeBtn.classList.add('active-button');
  1224. }
  1225.  
  1226. }
  1227.  
  1228. // http://172.22.1.175/di/grid.action?userId=idc&pwd=U3cuYV&interfaceId=intGetMultElesDataTimeSerial&dataFormat=xml2&modelid=giftdaily&element=t2mm%20tmax%20tmin&level=1000&starttime=2014-11-22%2000:00:00&endtime=2014-11-25%2000:00:00&lon=113.5&lat=24.5
  1229. function getTimeSeries(model = 'ecmwfthin', sT = '2019-01-22%2012:00:00', eT = '2019-02-01%2012:00:00', lon = '113.0', lat = '23.5', eles = ['t2mm', 'mn2t', 'mx2t', 'u10m', 'v10m', 'tcco', 'lcco', 'tppm']) {
  1230.  
  1231. const url = `http://research.gdmo.gq/api/di/grid.action?dataFormat=json&interfaceId=intGetMultElesDataTimeSerial&modelid=${model}&element=${eles.join('%20')}&level=0&starttime=${sT}&endtime=${eT}&lon=${lon}&lat=${lat}`;
  1232. utils.log('获取数据');
  1233. // showNotification('正在从数据中心获取数据');
  1234. let notification = GM_notification({ text: '正在从数据中心获取数据!', image: 'http://10.148.8.228/images/logo.png', title: '提示', timeout: 3000 });
  1235. // console.log(notification);
  1236. if (notification) setTimeout(() => notification.remove(), 3000);
  1237. // console.log(url);
  1238. GM_xmlhttpRequest({ //获取时间序列
  1239. method: 'GET',
  1240. synchronous: false,
  1241. url: url,
  1242. onload: function (reDetails) {
  1243. if (reDetails.status !== 200 && reDetails.status !== 304) {
  1244. console.error('获取URL错误');
  1245. // showNotification('数据中心数据获取异常');
  1246. let errorNotice = GM_notification({ text: '数据中心数据获取异常', image: 'http://10.148.8.228/images/logo.png', title: '接口异常', timeout: 3000 });
  1247. if (errorNotice) setTimeout(() => errorNotice.remove(), 3000);
  1248. return;
  1249. }
  1250. getLocation(lat, lon);
  1251. const data = JSON.parse(reDetails.responseText);
  1252. //console.log(data.DATA);
  1253. if (data.DATA.length == 0) {
  1254. console.error('此时次数据为空,请等待更新');
  1255. // showNotification('此时次数据为空,请等待更新');
  1256. let errorNotice = GM_notification({ text: '此时次数据为空,请等待更新', image: 'http://10.148.8.228/images/logo.png', title: '接口异常', timeout: 3000 });
  1257. if (errorNotice) setTimeout(() => errorNotice.remove(), 3000);
  1258. return;
  1259. }
  1260. let series = decodeSeries(data.DATA, 241);
  1261. series[1] = mixUndefined(series[0].map(v => v[1]), series[1]);
  1262. series[2] = mixUndefined(series[0].map(v => v[1]), series[2]);
  1263. //console.log(series[1]);
  1264. drawLine(series);
  1265. }
  1266. });
  1267. }
  1268.  
  1269. /**
  1270. * 从百度接口获取地址
  1271. * @param {Number} lat 纬度
  1272. * @param {Number} lon 经度
  1273. */
  1274. function getLocation(lat, lon) {
  1275. let ak = 'kMW5fXfhhsMat6Ud9jYPqnxCRQGbl2eV';
  1276. let url = `http://api.map.baidu.com/geocoder/v2/?callback=renderReverse&location=${lat},${lon}&output=json&pois=1&ak=${ak}`;
  1277. let latlonSpan = document.querySelectorAll('#line_info > span');
  1278. latlonSpan[0].innerHTML = lat;
  1279. latlonSpan[1].innerHTML = lon;
  1280. latlonSpan[2].innerHTML = '';
  1281. // console.log(url);
  1282. GM_xmlhttpRequest({ //获取时间序列
  1283. method: 'GET',
  1284. synchronous: false,
  1285. url: url,
  1286. onload: function (reDetails) {
  1287. if (reDetails.status !== 200 && reDetails.status !== 304) {
  1288. console.error('获取百度地址异常');
  1289. //showNotification('数据中心数据获取异常');
  1290. return;
  1291. }
  1292. let raw = reDetails.responseText.replace('renderReverse&&renderReverse(', '').slice(0, -1);
  1293. const addreJSON = JSON.parse(raw);
  1294. if (addreJSON.result.formatted_address == '') {
  1295. return;
  1296. } else {
  1297. latlonSpan[2].innerHTML = '&nbsp; 地址: ' + addreJSON.result.formatted_address;
  1298. }
  1299. //console.log(data);
  1300. }
  1301. });
  1302. }
  1303.  
  1304. function mixUndefined(index, data) {
  1305. let newData = [];
  1306. for (let i of index) {
  1307. let value = data.find(v => v[1] == i);
  1308. if (value === undefined) {
  1309. newData.push([undefined, i]);
  1310. } else {
  1311. newData.push([value[0], i]);
  1312. }
  1313. }
  1314. return newData
  1315. }
  1316.  
  1317. /**解析数据 */
  1318. function decodeSeries(data = [], len = 241) {
  1319. if (!data.length) return [];
  1320. let splitData = [];
  1321. let eles = data.length / len;//元素个数
  1322. for (let ie = 0; ie < eles; ie++) {//看有几个要素
  1323. splitData.push(data.slice(ie * len, (ie + 1) * len));
  1324. }
  1325. splitData = splitData.map(data => data.map((v, i) => [Number(v), i]).filter(v => v[0] > -999));//分离出[数值,时效]//-999.900024
  1326. // console.log(splitData);
  1327. return splitData;
  1328. }
  1329.  
  1330. function drawLine(series = []) {
  1331. // getTimeSeries();
  1332. utils.log('绘图');
  1333. if (series.length == 0) return console.log('空数据');
  1334. const tempChart = echarts.init(document.getElementById('show-temp'));
  1335. let fcTime = utils.getFcTime();
  1336. let initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH').add(8, 'hours');//北京时
  1337. let temp = series[0].map(v => (v[0] - 273.15).toFixed(2));
  1338. let mn2t = series[1].map(v => {
  1339. if (v[0] === undefined) {
  1340. return undefined;
  1341. } else {
  1342. return (v[0] - 273.15).toFixed(2);
  1343. }
  1344. });
  1345. let mx2t = series[2].map(v => {
  1346. if (v[0] === undefined) {
  1347. return undefined;
  1348. } else {
  1349. return (v[0] - 273.15).toFixed(2);
  1350. }
  1351. });
  1352. let xTime = series[0].map(v => {
  1353. const hour = v[1];
  1354. return moment(initTime).add(hour, 'hours').format('DD-HH');
  1355. });
  1356. // console.log(mn2t);
  1357. // 指定图表的配置项和数据
  1358. const OptionTemp = {
  1359. /* title: {
  1360. text: 'Temp.',
  1361. // left: 'center'
  1362. }, */
  1363. tooltip: {
  1364. trigger: 'axis',
  1365. },
  1366. toolbox: {
  1367. // y: '30px',
  1368. feature: {
  1369. dataZoom: {
  1370. yAxisIndex: 'none'
  1371. },
  1372. restore: {},
  1373. //saveAsImage: {}
  1374. }
  1375. },
  1376. grid: { top: 10, bottom: 45, right: 11, left: 25 },
  1377. dataZoom: [
  1378. {
  1379. show: true,
  1380. realtime: true,
  1381. start: 0,
  1382. end: 80,
  1383. // handleSize: '50%',
  1384. height: '20',
  1385. bottom: '0',
  1386. },
  1387. {
  1388. type: 'inside',
  1389. realtime: true,
  1390. start: 0,
  1391. end: 80,
  1392. }
  1393. ],
  1394. legend: {
  1395. y: '-5',
  1396. data: ['Temp', 'Pre6min', 'Pre6max']
  1397. },
  1398. xAxis: {
  1399. type: 'category',
  1400. data: xTime,
  1401. // boundaryGap : false,
  1402. //splitArea : {show : true},
  1403. splitLine: { show: true },
  1404. axisLine: { onZero: true },
  1405.  
  1406. },
  1407. yAxis: [{
  1408. name: '℃',
  1409. type: 'value',
  1410. scale: true,
  1411. axisLabel: {
  1412. formatter: '{value}'
  1413. },
  1414. },
  1415. ],
  1416. series: [
  1417. {
  1418. name: 'Temp',
  1419. smooth: true,
  1420. //symbol: 'triangle',
  1421. //symbolSize: 5,
  1422. lineStyle: { normal: { color: 'green', width: 2, } },//type: 'dashed'
  1423. //itemStyle: {normal: {borderWidth: 1,borderColor: 'green',color: 'green'}},
  1424. type: 'line',
  1425. data: temp,
  1426. },
  1427. {
  1428. name: 'Pre6min',
  1429. smooth: true,
  1430. connectNulls: true,
  1431. symbolSize: 0,
  1432. lineStyle: { normal: { color: 'blue', width: 1, type: 'dashed' } },//type: 'dashed'
  1433. type: 'line',
  1434. data: mn2t,
  1435. },
  1436. {
  1437. name: 'Pre6max',
  1438. smooth: true,
  1439. connectNulls: true,
  1440. symbolSize: 0,
  1441. lineStyle: { normal: { color: 'red', width: 1, type: 'dashed' } },//type: 'dashed'
  1442. type: 'line',
  1443. data: mx2t,
  1444. },
  1445. ]
  1446. };
  1447. // 使用刚指定的配置项和数据显示图表。
  1448. tempChart.setOption(OptionTemp);
  1449. /**
  1450. * 风
  1451. */
  1452. const windChart = echarts.init(document.getElementById('show-wind'));
  1453. let wind = series[3].map((v, i) => utils.calWind(v[0], series[4][i][0]));
  1454. let speed = wind.map(v => v.speed);
  1455. let rotation = wind.map(v => v.rotation);
  1456. let windData = speed.map((v, i) => [v, rotation[i], i]);
  1457. let dims = {
  1458. speed: 0,
  1459. rotation: 1,
  1460. timeIndex: 2,
  1461. }
  1462. const optionWind = {
  1463. /* title: {
  1464. text: 'Wind',
  1465. }, */
  1466. tooltip: {
  1467. trigger: 'axis',
  1468. },
  1469. toolbox: {
  1470. feature: {
  1471. dataZoom: {
  1472. yAxisIndex: 'none'
  1473. },
  1474. // restore: {},
  1475. //saveAsImage: {}
  1476. }
  1477. },
  1478. grid: { show: true, top: 10, bottom: 45, right: 11, left: 25 },
  1479. dataZoom: [
  1480. {
  1481. show: true,
  1482. realtime: true,
  1483. start: 0,
  1484. end: 80,
  1485. // handleSize: '50%',
  1486. height: '20',
  1487. bottom: '0',
  1488. },
  1489. {
  1490. type: 'inside',
  1491. realtime: true,
  1492. start: 0,
  1493. end: 80,
  1494. }
  1495. ],
  1496. legend: {
  1497. y: '-5',
  1498. data: ['w10m'],
  1499. },
  1500. xAxis: {
  1501. type: 'category',
  1502. data: xTime,
  1503. splitLine: { show: true },
  1504. axisLine: { onZero: true },
  1505. },
  1506. yAxis: {
  1507. name: 'm/s',
  1508. type: 'value',
  1509. axisLabel: {
  1510. formatter: '{value}'
  1511. },
  1512. },
  1513. series: [
  1514. {
  1515. name: 'w10m',
  1516. type: 'line',
  1517. smooth: true,
  1518. lineStyle: { normal: { width: 2, } },//type: 'dashed'
  1519. itemStyle: { normal: { borderWidth: 1, borderColor: 'black', color: 'black' } },
  1520. data: speed.map(v => v.toFixed(1)),
  1521. },
  1522. {
  1523. type: 'custom',
  1524. name: 'dir',
  1525. renderItem: utils.renderArrow,
  1526. encode: {
  1527. x: dims.timeIndex,
  1528. y: dims.speed,
  1529. },
  1530. data: windData,
  1531. z: 10
  1532. },
  1533. ]
  1534. };
  1535. windChart.setOption(optionWind);
  1536. /**
  1537. * 云
  1538. */
  1539. const cloudChart = echarts.init(document.getElementById('show-cloud'));
  1540. let totalCloud = series[5].map(v => v[0] * 100);
  1541. let lowCloud = series[6].map(v => v[0] * 100);
  1542. const optionCloud = {
  1543. tooltip: {
  1544. trigger: 'axis',
  1545. axisPointer: {
  1546. type: 'shadow'
  1547. }
  1548. },
  1549. toolbox: {
  1550. feature: {
  1551. dataZoom: {
  1552. yAxisIndex: 'none'
  1553. },
  1554. // restore: {},//saveAsImage: {}
  1555. }
  1556. },
  1557. grid: { show: true, top: 10, bottom: 45, right: 11, left: 30 },
  1558. dataZoom: [
  1559. {
  1560. show: true,
  1561. realtime: true,
  1562. start: 0,
  1563. end: 80,
  1564. height: '20',
  1565. bottom: '0',
  1566. },
  1567. {
  1568. type: 'inside',
  1569. realtime: true,
  1570. start: 0,
  1571. end: 80,
  1572. }
  1573. ],
  1574. legend: {
  1575. y: '-5',
  1576. data: ['中高云', '低云'],
  1577. },
  1578. xAxis: {
  1579. type: 'category',
  1580. data: xTime,
  1581. splitLine: { show: true },
  1582. axisLine: { onZero: true },
  1583. },
  1584. yAxis: {
  1585. name: '%',
  1586. type: 'value',
  1587. axisLabel: {
  1588. formatter: '{value}'
  1589. },
  1590. },
  1591. series: [
  1592. {
  1593. name: '低云',
  1594. type: 'bar',
  1595. stack: '云量',
  1596. data: lowCloud.map(v => v.toFixed(1)),
  1597. },
  1598. {
  1599. name: '中高云',
  1600. type: 'bar',
  1601. stack: '云量',
  1602. data: totalCloud.map((v, i) => (v - lowCloud[i]).toFixed(1)),
  1603. },
  1604. ]
  1605. };
  1606. cloudChart.setOption(optionCloud);
  1607. /**
  1608. * 降水
  1609. */
  1610. const preChart = echarts.init(document.getElementById('show-pre'));
  1611. let preSeri = series[7];
  1612. let precipitaion = preSeri.map((v, i) => {
  1613. let pre = i === 0 ? v[0] : v[0] - preSeri[i - 1][0]; //(v[0]*1000).toFixed(1)
  1614. return (pre * 1000).toFixed(1);
  1615. }
  1616. );
  1617. //let lowCloud = series[6].map(v=>v[0]);
  1618. const optionPre = {
  1619. tooltip: {
  1620. trigger: 'axis',
  1621. axisPointer: { // 坐标轴指示器,坐标轴触发有效
  1622. type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
  1623. }
  1624. },
  1625. toolbox: {
  1626. feature: {
  1627. dataZoom: {
  1628. yAxisIndex: 'none'
  1629. },
  1630. }
  1631. },
  1632. grid: { show: true, top: 10, bottom: 45, right: 11, left: 30 },
  1633. dataZoom: [
  1634. {
  1635. show: true,
  1636. realtime: true,
  1637. start: 0,
  1638. end: 80,
  1639. height: '20',
  1640. bottom: '0',
  1641. },
  1642. {
  1643. type: 'inside',
  1644. realtime: true,
  1645. start: 0,
  1646. end: 80,
  1647. }
  1648. ],
  1649. legend: {
  1650. y: '-5',
  1651. data: ['Precipitaion'],
  1652. },
  1653. xAxis: {
  1654. type: 'category',
  1655. data: xTime,
  1656. splitLine: { show: true },
  1657. axisLine: { onZero: true },
  1658. },
  1659. yAxis: {
  1660. name: 'mm',
  1661. type: 'value',
  1662. axisLabel: {
  1663. formatter: '{value}'
  1664. },
  1665. },
  1666. series: [
  1667. {
  1668. name: 'Precipitaion',
  1669. type: 'bar',
  1670. stack: 'Rain',
  1671. data: precipitaion,
  1672. },
  1673. ]
  1674. };
  1675. preChart.setOption(optionPre);
  1676. }
  1677.  
  1678. function createPanel() {
  1679. //-创建面板-//
  1680. const panelWrap = document.createElement("div");
  1681.  
  1682. panelWrap.setAttribute("id", "helper_panel");
  1683. panelWrap.setAttribute("class", "top_panel show_panel");
  1684. panelWrap.innerHTML = '多图模式: ';
  1685. /* 设置添加对比模式*/
  1686. const compareMode = utils.createElement('div', { class: 'single-btn' });
  1687. compareMode.innerHTML = `<span class="panel-button" id="open-compare">多起报</span>`;
  1688. panelWrap.appendChild(compareMode);
  1689.  
  1690. /* 设置添加多时效模式*/
  1691. const mvTime = utils.createElement('div', { class: 'single-btn' });
  1692. mvTime.innerHTML = `<span class="panel-button" id="open-mvTime">多时效</span>`;
  1693. panelWrap.appendChild(mvTime);
  1694.  
  1695. /* 添加多模式 */
  1696. const mvModelBtn = utils.createElement('div', { class: 'dropdown mv-model-panel' });
  1697. mvModelBtn.innerHTML =
  1698. `<button class="dropbtn"><div class="tooltip">多模式<span class="tooltiptext">ECMWF, NCEP, Grapes全球, 南海台风模式9km</span></div></button>
  1699. <div class="dropdown-content">
  1700. <a href="#" class="tooltip">
  1701. EC/NCEP/G全球/G9km
  1702. <span class="tooltiptext">ECMWF, NCEP, Grapes全球, Grages9kmEC</span>
  1703. </a>
  1704. <a href="#" class="tooltip">
  1705. EC/NCEP/G9km/G-meso
  1706. <span class="tooltiptext">ECMWF, NCEP, Grages9kmEC, Grapes-Meso</span>
  1707. </a>
  1708. <a href="#" class="tooltip">
  1709. EC/NCEP/G9km/GZ-3km
  1710. <span class="tooltiptext">ECMWF, NCEP, Grages9kmEC, 华南3km高分辨率</span>
  1711. </a>
  1712. </div>
  1713. `;
  1714. panelWrap.appendChild(mvModelBtn);
  1715.  
  1716. /* 设置添加多图模式
  1717. const mvElem = document.createElement('div');
  1718. mvElem.innerHTML = `<span class="panel-button" id="open-mv">多要素</span>`;
  1719. panelWrap.appendChild(mvElem);*/
  1720.  
  1721. /* 设置添加点选模式*/
  1722. const panelWrap2 = utils.createElement('div', { id: 'edit-helper-panel', class: 'top_panel show_panel' });
  1723. const pointMode = utils.createElement('div', { class: 'single-btn' });
  1724. pointMode.innerHTML = `<span class="panel-button">交互模式</span>`;
  1725. panelWrap2.appendChild(pointMode);
  1726. pointMode.addEventListener('click', () => { toggleSelectMode() });
  1727.  
  1728. /* 设置Lat lon面板*/
  1729.  
  1730. //-------//
  1731. //-注册到全局变量-//
  1732. elemsConfig.latLonInput = {
  1733. lat: document.getElementById('helper_lat'),
  1734. lon: document.getElementById('helper_lon'),
  1735. };
  1736.  
  1737. /* 设置添加增强显示*/
  1738. const panelWrap3 = utils.createElement('div', { id: 'enhanced-helper-panel', class: 'top_panel show_panel' });
  1739. const tcModeBtn = utils.createElement('div', { class: 'single-btn', id: 'tc-mode-btn' });
  1740. tcModeBtn.innerHTML = `<span class="panel-button">台风路径</span>`;
  1741. panelWrap3.appendChild(tcModeBtn);
  1742. tcModeBtn.addEventListener('click', () => { toggleTCMode('tcShow') });
  1743.  
  1744. const detTCbtn = utils.createElement('div', { class: 'single-btn display-none active-button', id: 'tc-det-btn' });
  1745. detTCbtn.innerHTML = `<span class="panel-button">EC确定性预报</span>`;
  1746. panelWrap3.appendChild(detTCbtn);
  1747. detTCbtn.addEventListener('click', () => { toggleTCMode('detShow') });
  1748. const ensTCbtn = utils.createElement('div', { class: 'single-btn display-none active-button', id: 'tc-ens-btn' });
  1749. ensTCbtn.innerHTML = `<span class="panel-button">EC集合预报</span>`;
  1750. panelWrap3.appendChild(ensTCbtn);
  1751. ensTCbtn.addEventListener('click', () => { toggleTCMode('ensShow') });
  1752.  
  1753. /* 注册到body*/
  1754. const ibody = document.getElementsByTagName("body")[0];
  1755. ibody.appendChild(panelWrap);
  1756. ibody.appendChild(panelWrap2);
  1757. ibody.appendChild(panelWrap3);
  1758. }
  1759.  
  1760.  
  1761. // 添加面板样式
  1762. function createTlinePanel() {
  1763. //-创建面板-//
  1764. const fragment = document.createDocumentFragment();
  1765. const panelWrap = document.createElement("div");
  1766.  
  1767. panelWrap.setAttribute("id", "line_panel");
  1768. panelWrap.setAttribute("class", "show_panel");
  1769. panelWrap.style.display = 'none';
  1770.  
  1771. const infoWrap = document.createElement('div');
  1772. infoWrap.setAttribute("id", "line_info");
  1773. infoWrap.innerHTML = 'EC模式预报<br>纬度:<span></span> 经度:<span></span><span></span>';
  1774. panelWrap.appendChild(infoWrap);
  1775. /* 设置添加temp*/
  1776. const tempWrap = document.createElement('div');
  1777. tempWrap.innerHTML = `温度<div id="show-temp"></div>`;
  1778. panelWrap.appendChild(tempWrap);
  1779.  
  1780. const windWrap = document.createElement('div');
  1781. windWrap.innerHTML = `风MSLP<div id="show-wind"></div>`;
  1782. panelWrap.appendChild(windWrap);
  1783.  
  1784. const preWrap = document.createElement('div');
  1785. preWrap.innerHTML = `降水<div id="show-pre"></div>`;
  1786. panelWrap.appendChild(preWrap);
  1787.  
  1788. const cloudWrap = document.createElement('div');
  1789. cloudWrap.innerHTML = `云量<div id="show-cloud"></div>`;
  1790. panelWrap.appendChild(cloudWrap);
  1791.  
  1792. fragment.appendChild(panelWrap);
  1793. const ibody = document.getElementsByTagName("body")[0];
  1794. ibody.appendChild(fragment);
  1795. //-------//
  1796. }
  1797.  
  1798. function createSelectModePanel() {
  1799. //-创建面板-//
  1800. const fragment = document.createDocumentFragment();
  1801. const panelWrap = document.createElement("div");
  1802.  
  1803. panelWrap.setAttribute("id", "select-mode-panel");
  1804. panelWrap.setAttribute("class", "show_panel");
  1805. panelWrap.style.display = 'none';
  1806. panelWrap.innerHTML = `快捷键[C] -> 单点时间序列`;
  1807. /*<br>
  1808. [X] -> 垂直探空; 两次[Ctrl+X] -> 垂直剖面
  1809. */
  1810.  
  1811. fragment.appendChild(panelWrap);
  1812. const ibody = document.getElementsByTagName("body")[0];
  1813. ibody.appendChild(fragment);
  1814. /* 设置Lat lon面板*/
  1815. const latLonWarp = document.createElement("div");
  1816. latLonWarp.setAttribute("id", "helper_latLon");
  1817. latLonWarp.setAttribute("class", "show_latLon");
  1818. latLonWarp.innerHTML = '<span>Lat <span class="fixLoc" id="helper_lat"></span> Lon <span class="fixLoc" id="helper_lon"></span></span>';
  1819. panelWrap.appendChild(latLonWarp);
  1820. /* 设置垂直剖面面板*/
  1821. const crossWrap = utils.createElement('div', { id: 'helper_cross', class: 'top_panel' })
  1822. crossWrap.innerHTML = `<div class="panel-button single-btn">垂直剖面</div>`;
  1823. panelWrap.appendChild(crossWrap);
  1824. crossWrap.addEventListener('click', () => { toggleCrossSection() });
  1825.  
  1826. //-------//
  1827. }
  1828.  
  1829.  
  1830. //console.log(compareMode.imgDOM);
  1831. // compareMode.forward();
  1832.  
  1833.  
  1834.  
  1835.  
  1836. /**绑定多模式 */
  1837. function bindingMvModel() {
  1838. let dropList = document.querySelectorAll('.mv-model-panel .dropdown-content>a');
  1839. for (let elem of dropList) {
  1840. elem.addEventListener('click', clickDrop);
  1841. }
  1842. let dropBtn = document.querySelector('.dropbtn');
  1843. dropBtn.addEventListener('click', clickDrop);
  1844. function clickDrop(evt) {
  1845. const current = evt.currentTarget;
  1846. let index;
  1847. // console.log(current.classList);
  1848. if (current.classList.contains('dropbtn')) {
  1849. index = 0;
  1850. } else {
  1851. // console.log(current);
  1852. const parent = current.parentElement;
  1853. index = Array.from(parent.children).indexOf(current);
  1854. // parent.style.display='none';
  1855. }
  1856.  
  1857. let currentImgInfo = utils.getImgNow();
  1858. matchedRule = multiModelRule[index];
  1859. let srcList = getMVSrcListFromRule(currentImgInfo.url, matchedRule);
  1860. // console.log(srcList);
  1861. elemsConfig.mvImgDOM.matchedSrcList = srcList;
  1862. elemsConfig.mvImgDOM.mode = 'multiModel';
  1863. // elemsConfig.mvImgDOM.mode = 'multiModel';
  1864. userConfig.mvMode.openCompare();
  1865. }
  1866. }
  1867.  
  1868.  
  1869. //创建对比框
  1870. function createComparePanel() {
  1871. const panel = document.createDocumentFragment();
  1872. const mainWrapper = utils.createElement('div', { id: 'compare-main', class: 'display-none' });
  1873. const controlWrapper = utils.createElement('div', { class: 'compare-warpper' });
  1874. controlWrapper.innerHTML = `<div id="compare-backward" class="my-button">step -6</div><div class="my-button" id="compare-foreward">step +6</div>`;
  1875. controlWrapper.innerHTML += `<select id="compare-interval"><option value="48">起报间隔48小时</option>
  1876. <option value="24">起报间隔24小时</option>
  1877. <option selected="selected" value="12">起报间隔12小时</option></select>
  1878. <div id="close-compare" class="my-button">关闭对比</div>
  1879. <span class="info"></span>`;
  1880.  
  1881. const imgWrapper = utils.createElement('div', { class: 'compare-img-main' });
  1882. imgWrapper.innerHTML =
  1883. `<div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1884. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1885. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1886. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>`;
  1887. panel.append(mainWrapper);
  1888. mainWrapper.append(controlWrapper, imgWrapper);
  1889. //mainWrapper.append(forewardBut,backwardBut,intervalInput);
  1890.  
  1891. document.body.append(panel);
  1892. }
  1893.  
  1894. function createCompareMode() {
  1895. const compareHandler = {
  1896. set(obj, prop, value, receiver) {
  1897. if (prop === 'interval') {
  1898. obj.interval = value;
  1899. receiver.firstImg = obj.firstImg;
  1900. //obj.img[1] = obj.img[0] + value;
  1901. //obj.img[2] = obj.img[0] + value*2;
  1902. //obj.img[3] = obj.img[0] + value*3;
  1903. } else if (prop === 'firstHr') {
  1904. // console.log(obj.imgInfo.toSource());
  1905. // const initURL = obj.imgInfo.url;
  1906. const date = obj.imgInfo.date;
  1907. const initDate = obj.imgInfo.initDate;
  1908. const hour = utils.paddingInt(value, 3);
  1909. const url = obj.imgInfo.getUrl(date, hour, initDate)
  1910. receiver.firstImg = url;
  1911. //receiver.interval = obj.interval;
  1912. } else if (prop === 'imgInfo') {
  1913.  
  1914. console.log('设置初始图像为:' + value.url);
  1915. } else if (prop === 'firstImg') {
  1916. const imgList = obj.fit2sameday(value);
  1917. // console.log(imgList);
  1918. obj.showInfo(value);
  1919. imgList.forEach((imgSrc, i) => {
  1920. receiver.img[i] = imgSrc;
  1921. })
  1922. //obj.img[0] = imgList;
  1923. // obj.img[1] = ;
  1924. }
  1925. Reflect.set(obj, prop, value);
  1926. },
  1927. };
  1928.  
  1929. const imgHandler = {
  1930. set(obj, prop, value, receiver) {
  1931. utils.log(`第${Number(prop) + 1}个图像地址为${value}`);
  1932. elemsConfig.compareImgDOM.img[prop].src = value;
  1933. iImgInfo = utils.getImgNow(value);
  1934. const initTime = moment(iImgInfo.initDate, 'YYYYMMDDHH');
  1935. const nowTime = moment(initTime).add(Number.parseInt(iImgInfo.hour) + 8, 'hours');
  1936. elemsConfig.compareImgDOM.info[prop].innerHTML =
  1937. `起报: ${initTime.format('MM-DD HH')} UTC, 【北京时${nowTime.format('MM月DD日HH时')}(${iImgInfo.hour}h)】${iImgInfo.model}, ${iImgInfo.eleName}`;
  1938. //console.log(value);
  1939. //let imgDom = document.querySelectorAll('.compare .imgSrc');
  1940. //imgDom[prop].src = value;
  1941. Reflect.set(obj, prop, value);
  1942. },
  1943. };
  1944. let imgSrc = new Proxy([1, 2, 3, 4], imgHandler);
  1945. const modeProto = {
  1946. img: imgSrc,
  1947. firstImg: '',
  1948. interval: 12,
  1949. foreward(step = 6) {
  1950. this.firstHr = this.firstHr + step;
  1951. // console.log('步进');
  1952. },
  1953. backward(step = 6) {
  1954. if (this.firstHr - step >= 0) {
  1955. this.firstHr = this.firstHr - step;
  1956. } else {
  1957. alert('不能再退了');
  1958. }
  1959. },
  1960. firstHr: 1,
  1961. urlMode(base = 'http://10.148') {
  1962. let url = base;
  1963. return url;
  1964. },
  1965. initIMG() {// TODO addEventListener('error',(evt)=>{evt.srcElement.src})
  1966. let DOMs = document.querySelectorAll('#compare-main .compare-img');
  1967. let imgDOM = [];
  1968. for (let img of DOMs) {
  1969. img.addEventListener('error', (evt) => { let ele = evt.target; ele.onerror = null; ele.src = '/images/weather/nopic_800_600.jpg'; });
  1970. imgDOM.push(img);
  1971. };
  1972. elemsConfig.compareImgDOM.img = imgDOM;
  1973.  
  1974. let infoDOMs = document.querySelectorAll('#compare-main .compare-img-info');
  1975. let infoList = [];
  1976. for (let ele of infoDOMs) {
  1977. infoList.push(ele);
  1978. };
  1979. elemsConfig.compareImgDOM.info = infoList;
  1980. //return imgDOM;
  1981. },
  1982. imgInfo: {},
  1983. openCompare() {
  1984. const wrapper = document.getElementById('compare-main');
  1985. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  1986. wrapper.classList.remove('display-none');
  1987. try {
  1988. this.imgInfo = utils.getImgNow();
  1989. this.firstHr = Number.parseInt(this.imgInfo.hour);
  1990. } catch (err) {
  1991. alert(err.message);
  1992. console.error(err);
  1993. }
  1994. },
  1995. closeCompare() {
  1996. const wrapper = document.getElementById('compare-main');
  1997. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  1998. wrapper.classList.add('display-none');
  1999. // this.imgInfo = utils.getImgNow();
  2000. },
  2001. fit2sameday(firstImg) {
  2002. const interval = this.interval;
  2003. const firstInfo = utils.getImgNow(firstImg);
  2004. // console.log(firstImg);
  2005. // console.log(firstInfo);
  2006. const imgList = [firstImg];
  2007. const iniTime = moment(firstInfo.initDate, 'YYYYMMDDHH');
  2008. for (let i = 1; i < 4; i++) {
  2009. const fitTime = moment(iniTime).add(-1 * interval * i, 'hours');
  2010. const iDate = moment(fitTime).format('YYYYMMDD');
  2011. const iInit = moment(fitTime).format('YYYYMMDDHH');
  2012. const hour = Number.parseInt(firstInfo.hour) + interval * i;
  2013. // console.log('预报时效'+hour);
  2014. const iHour = utils.paddingInt(hour, 3);
  2015. const url = firstInfo.getUrl(iDate, iHour, iInit);
  2016. imgList.push(url);
  2017. }
  2018. return imgList;
  2019. },
  2020. changeInterval(evt) {
  2021. this.interval = Number.parseInt(evt.target.value);
  2022. // console.log(evt);
  2023. },
  2024. showInfo(img) {
  2025. const controlInfo = document.querySelector('#compare-main .compare-warpper .info');
  2026. const imgInfo = utils.getImgNow(img);
  2027. const nowTime = moment(imgInfo.initDate, 'YYYYMMDDHH').add(Number.parseInt(imgInfo.hour) + 8, 'hours');
  2028. controlInfo.innerHTML = ' UTC+8 ' + nowTime.format('YYYY年MM月DD日HH时');
  2029. controlInfo.innerHTML += ' | GMT' + nowTime.add(-8, 'hours').format('YYYY-MM-DD HH:00');
  2030. },
  2031. };
  2032.  
  2033. const compareMode = new Proxy(modeProto, compareHandler);
  2034. return compareMode;
  2035. }
  2036.  
  2037.  
  2038. //compareMode.imgInfo = utils.getImgNow();
  2039.  
  2040.  
  2041.  
  2042.  
  2043.  
  2044. //创建多视窗对比框
  2045. function createMVPanel() {
  2046. const panel = document.createDocumentFragment();
  2047. const mainWrapper = utils.createElement('div', { id: 'multiviews-main', class: 'display-none' });
  2048. const controlWrapper = utils.createElement('div', { class: 'compare-warpper' });
  2049. controlWrapper.innerHTML = `<div id="mv-backward" class="my-button">step -6</div><div class="my-button" id="mv-foreward">step +6</div>`;
  2050. controlWrapper.innerHTML += `<select id="mv-interval"><option selected="selected" value="6">间隔6小时</option>
  2051. <option value="12">间隔12小时</option>
  2052. <option value="24">间隔24小时</option></select>
  2053. <div id="close-mv" class="my-button">关闭多图模式</div>
  2054. <span class="info"></span>`;
  2055.  
  2056. const imgWrapper = utils.createElement('div', { class: 'compare-img-main' });
  2057. imgWrapper.innerHTML =
  2058. `<div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  2059. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  2060. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  2061. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>`;
  2062. panel.append(mainWrapper);
  2063. mainWrapper.append(controlWrapper, imgWrapper);
  2064. //mainWrapper.append(forewardBut,backwardBut,intervalInput);
  2065.  
  2066. document.body.append(panel);
  2067. }
  2068.  
  2069. function createMultiviewsMode() {
  2070. const compareHandler = {
  2071. set(obj, prop, value, receiver) {
  2072. if (prop === 'interval') {
  2073. obj.interval = value;
  2074. // receiver.firstImg = obj.firstImg;
  2075. receiver.step = value * obj.imgArr.length;
  2076. const firstImg = obj.imgArr[0];
  2077. let imgInfo = utils.getImgNow(firstImg);
  2078. let iHour = Number.parseInt(imgInfo.hour);
  2079. for (let i = 0; i < obj.imgArr.length; i++) {
  2080. let currentHour = iHour + i * value;
  2081. const hourString = utils.paddingInt(currentHour);
  2082. obj.imgArr[i] = utils.getImgUrl(imgInfo.base, imgInfo.date, hourString, imgInfo.initDate);
  2083. }
  2084. } else if (prop === 'firstHr') {
  2085. const date = obj.imgInfo.date;
  2086. const initDate = obj.imgInfo.initDate;
  2087. const hour = utils.paddingInt(value, 3);
  2088. const url = obj.imgInfo.getUrl(date, hour, initDate)
  2089. receiver.firstImg = url;
  2090. } else if (prop === 'step') {
  2091. // console.log('step');
  2092. // obj.step = value;
  2093. document.getElementById('mv-foreward').innerText = 'step +' + value;
  2094. document.getElementById('mv-backward').innerText = 'step -' + value;
  2095. } else if (prop === 'imgInfo') {
  2096. console.log('设置初始图像为:' + value.url);
  2097. } else if (prop === 'firstImg') {
  2098. const imgList = obj.fit2sameday(value);
  2099. // console.log(imgList);
  2100. obj.showInfo(value);
  2101. imgList.forEach((imgSrc, i) => {
  2102. receiver.imgArr[i] = imgSrc;
  2103. })
  2104. //obj.img[0] = imgList;
  2105. // obj.img[1] = ;
  2106. }
  2107. Reflect.set(obj, prop, value);
  2108. },
  2109. };
  2110.  
  2111. const imgHandler = {
  2112. set(obj, prop, value) {
  2113. utils.log(`第${Number(prop) + 1}个图像地址为${value}`);
  2114. elemsConfig.mvImgDOM.img[prop].src = value;
  2115. iImgInfo = utils.getImgNow(value);
  2116. const initTime = moment(iImgInfo.initDate, 'YYYYMMDDHH');
  2117. const nowTime = moment(initTime).add(Number.parseInt(iImgInfo.hour) + 8, 'hours');
  2118. // controlInfo.innerHTML = ' UTC+8 '+ nowTime.format('YYYY年MM月DD日HH时');
  2119. elemsConfig.mvImgDOM.info[prop].innerHTML =
  2120. `【北京时${nowTime.format('MM月DD日HH时')}(${iImgInfo.hour}h)】${iImgInfo.model}, ${iImgInfo.eleName}, 起报: ${initTime.format('MM-DD HH')} UTC`;
  2121. Reflect.set(obj, prop, value);
  2122. },
  2123. };
  2124. let imgSrc = new Proxy([1, 2, 3, 4], imgHandler);
  2125. const modeProto = {
  2126. imgArr: imgSrc,
  2127. firstImg: '',
  2128. interval: 6,
  2129. step: 6,
  2130. foreward(step = 6) {
  2131. // this.firstHr = this.firstHr + step;
  2132. // console.log(this);
  2133. this.changeHour(this.step);
  2134. // console.log('步进');
  2135. },
  2136. backward(step = -6) {
  2137. let imgInfo = utils.getImgNow(this.imgArr[0]);
  2138. // console.dir(imgInfo);
  2139. // console.log(Number.parseInt(imgInfo.hour) - this.step>=0)
  2140. if (Number.parseInt(imgInfo.hour) - this.step >= 0) {
  2141. this.changeHour(-1 * this.step);
  2142. // console.log('步退');
  2143. } else {
  2144. alert('不能再退了');
  2145. }
  2146. },
  2147. changeHour(hr = 6) {
  2148.  
  2149. let hour = parseInt(hr);
  2150. for (let i = 0; i < this.imgArr.length; i++) {
  2151. let iInfo = utils.getImgNow(this.imgArr[i]);
  2152. let newHour = parseInt(iInfo.hour) + hour;
  2153. // console.log(newHour);
  2154. this.imgArr[i] = iInfo.getUrl(iInfo.date, utils.paddingInt(newHour), iInfo.initDate);
  2155. }
  2156. },
  2157. firstHr: 1,
  2158. urlMode(base = 'http://10.148') {
  2159. let url = base;
  2160. return url;
  2161. },
  2162. initIMG() {// TODO addEventListener('error',(evt)=>{evt.srcElement.src})
  2163. let DOMs = document.querySelectorAll('#multiviews-main .compare-img');
  2164. let imgDOM = [];
  2165. for (let img of DOMs) {
  2166. img.addEventListener('error', (evt) => { let ele = evt.target; ele.onerror = null; ele.src = '/images/weather/nopic_800_600.jpg'; });
  2167. imgDOM.push(img);
  2168. };
  2169. elemsConfig.mvImgDOM.img = imgDOM;
  2170.  
  2171. let infoDOMs = document.querySelectorAll('#multiviews-main .compare-img-info');
  2172. let infoList = [];
  2173. for (let ele of infoDOMs) {
  2174. infoList.push(ele);
  2175. };
  2176. elemsConfig.mvImgDOM.info = infoList;
  2177. //return imgDOM;
  2178. },
  2179. imgInfo: {},
  2180. openCompare() {
  2181. const wrapper = document.getElementById('multiviews-main');
  2182. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  2183. wrapper.classList.remove('display-none');
  2184. document.getElementById('mv-interval').classList.remove('display-none');
  2185. try {
  2186. let imgInfo = utils.getImgNow();
  2187. if (elemsConfig.mvImgDOM.mode === 'multiElem') {
  2188. for (let i = 0; i < this.imgArr.length; i++) {
  2189. const iPatt = elemsConfig.mvImgDOM.matchPattens[i];
  2190. // console.log(iPatt);
  2191. const iMatch = utils.matchImgPattern(iPatt);
  2192. console.log(iMatch);
  2193. this.imgArr[i] = utils.getImgUrl(iMatch.base, imgInfo.date, imgInfo.hour, imgInfo.initDate);
  2194. }
  2195. } else if (elemsConfig.mvImgDOM.mode === 'multiTime') {
  2196. this.step = this.interval * this.imgArr.length;
  2197. let iHour = Number.parseInt(imgInfo.hour);
  2198. for (let i = 0; i < this.imgArr.length; i++) {
  2199. let currentHour = iHour + i * this.interval;
  2200. const hourString = utils.paddingInt(currentHour);
  2201. this.imgArr[i] = utils.getImgUrl(imgInfo.base, imgInfo.date, hourString, imgInfo.initDate);
  2202. }
  2203. } if (elemsConfig.mvImgDOM.mode === 'multiModel' || elemsConfig.mvImgDOM.mode === 'multiElems') {
  2204. for (let i = 0; i < elemsConfig.mvImgDOM.matchedSrcList.length; i++) {
  2205. this.imgArr[i] = elemsConfig.mvImgDOM.matchedSrcList[i];
  2206. }
  2207. document.getElementById('mv-interval').classList.add('display-none');
  2208. this.step = 6;
  2209. }
  2210.  
  2211. } catch (err) {
  2212. alert(err.message);
  2213. console.error(err);
  2214. }
  2215. },
  2216. closeCompare() {
  2217. const wrapper = document.getElementById('multiviews-main');
  2218. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  2219. wrapper.classList.add('display-none');
  2220. // this.imgInfo = utils.getImgNow();
  2221. },
  2222. fit2sameday(firstImg) {
  2223. const interval = this.interval;
  2224. const firstInfo = utils.getImgNow(firstImg);
  2225. // console.log(firstImg);
  2226. // console.log(firstInfo);
  2227. const imgList = [firstImg];
  2228. const iniTime = moment(firstInfo.initDate, 'YYYYMMDDHH');
  2229. for (let i = 1; i < 4; i++) {
  2230. const fitTime = moment(iniTime).add(-1 * interval * i, 'hours');
  2231. const iDate = moment(fitTime).format('YYYYMMDD');
  2232. const iInit = moment(fitTime).format('YYYYMMDDHH');
  2233. const hour = Number.parseInt(firstInfo.hour) + interval * i;
  2234. // console.log('预报时效'+hour);
  2235. const iHour = utils.paddingInt(hour, 3);
  2236. const url = firstInfo.getUrl(iDate, iHour, iInit);
  2237. imgList.push(url);
  2238. }
  2239. return imgList;
  2240. },
  2241. changeInterval(evt) {
  2242. if (elemsConfig.mvImgDOM.mode === 'multiModel' || elemsConfig.mvImgDOM.mode === 'multiElems') {
  2243. return confirm('多模式下不支持调整间隔');
  2244. }
  2245. this.interval = Number.parseInt(evt.target.value);
  2246. // console.log(evt);
  2247. },
  2248. showInfo(img) {
  2249. const controlInfo = document.querySelector('#multiviews-main .compare-warpper .info');
  2250. const imgInfo = utils.getImgNow(img);
  2251. const nowTime = moment(imgInfo.initDate, 'YYYYMMDDHH').add(Number.parseInt(imgInfo.hour) + 8, 'hours');
  2252. controlInfo.innerHTML = ' UTC+8 ' + nowTime.format('YYYY年MM月DD日HH时');
  2253. controlInfo.innerHTML += ' | GMT' + nowTime.add(-8, 'hours').format('YYYY-MM-DD HH:00');
  2254. },
  2255. };
  2256.  
  2257. const compareMode = new Proxy(modeProto, compareHandler);
  2258. return compareMode;
  2259. }
  2260.  
  2261. if (location.href.includes('to_fore_homepage.action')) {
  2262. if (window.frames.length != parent.frames.length) {
  2263.   return;
  2264. }
  2265. createSelectModePanel();
  2266. createPanel();
  2267. NWP_init();
  2268. createTlinePanel();
  2269. bindingMvModel();
  2270. createComparePanel();
  2271. const compareMode = createCompareMode();//创建对比DOM框架
  2272. compareMode.hook = function (selector = '', listener = '', callback = () => { }, bindObj = compareMode) {
  2273. var targetEle = document.querySelector(selector);
  2274. targetEle.addEventListener(listener, () => callback.call(bindObj), false);
  2275. };
  2276. compareMode.hook('#compare-backward', 'click', compareMode.backward, compareMode);
  2277. compareMode.hook('#compare-foreward', 'click', compareMode.foreward, compareMode);
  2278. compareMode.hook('#open-compare', 'click', compareMode.openCompare, compareMode);
  2279. compareMode.hook('#close-compare', 'click', compareMode.closeCompare, compareMode);
  2280. //compareMode.hook('#compare-interval','change',(evt)=>compareMode.changeInterval(evt),compareMode);
  2281. document.getElementById('compare-interval').addEventListener('change', (evt) => compareMode.changeInterval(evt));
  2282. compareMode.initIMG();
  2283.  
  2284. createMVPanel();//创建对比DOM框架
  2285. const mvMode = createMultiviewsMode();
  2286. mvMode.hook = function (selector = '', listener = '', callback = () => { }, bindObj = mvMode) {
  2287. var targetEle = document.querySelector(selector);
  2288. targetEle.addEventListener(listener, () => callback.call(bindObj), false);
  2289. };
  2290. mvMode.hook('#mv-backward', 'click', mvMode.backward, mvMode);
  2291. mvMode.hook('#mv-foreward', 'click', mvMode.foreward, mvMode);
  2292. mvMode.hook('#close-mv', 'click', mvMode.closeCompare, mvMode);
  2293. //compareMode.hook('#compare-interval','change',(evt)=>compareMode.changeInterval(evt),compareMode);
  2294. document.getElementById('mv-interval').addEventListener('change', (evt) => mvMode.changeInterval(evt));
  2295.  
  2296. mvMode.initIMG();
  2297. userConfig.mvMode = mvMode;
  2298. let openMvTime = document.getElementById('open-mvTime');
  2299. // console.log(openMvTime)
  2300. openMvTime.addEventListener('click', () => {
  2301. elemsConfig.mvImgDOM.mode = 'multiTime';
  2302. mvMode.openCompare();
  2303. });
  2304. createMapIndicator();
  2305. }
  2306.  
  2307. if (location.href.includes('to_fore_homepage.action')) {
  2308. GM_addStyle(`
  2309. /* Style The Dropdown Button */
  2310. /*background-color: #4CAF50;*/
  2311. .dropbtn {
  2312. background-color: rgb(45,53,63);
  2313. color: white;
  2314. font-size: 16px;
  2315. cursor: pointer;
  2316. border: none;
  2317. padding: 5px;
  2318. border: 1px solid white;
  2319. }
  2320.  
  2321. /* The container <div> - needed to position the dropdown content */
  2322. .dropdown {
  2323. position: relative;
  2324. display: inline-block;
  2325. margin-left:5px;
  2326. margin-right:5px;
  2327.  
  2328. }
  2329.  
  2330. .dropbtn .tooltiptext{
  2331. width: intrinsic;
  2332. width: -webkit-max-content;
  2333. width:-moz-max-content;
  2334. width: max-content;
  2335. }
  2336.  
  2337. /* Dropdown Content (Hidden by Default) */
  2338. .dropdown-content {
  2339. display: none;
  2340. position: absolute;
  2341. left: 0px;
  2342. top: 30px;
  2343. background-color: #f9f9f9;
  2344. box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
  2345. z-index: 1;
  2346. width: intrinsic;
  2347. width: -webkit-max-content;
  2348. width:-moz-max-content;
  2349. width: max-content;
  2350. }
  2351.  
  2352. /* Links inside the dropdown */
  2353. .dropdown-content>a {
  2354. color: black;
  2355. padding: 6px 8px;
  2356. text-decoration: none;
  2357. display: block;
  2358. border-bottom: 1px dashed #4CAF50;
  2359.  
  2360. }
  2361.  
  2362. /* Change color of dropdown links on hover */
  2363. .dropdown-content a:hover {
  2364. background-color: rgb(150, 185, 125);
  2365. font-weight: bold;
  2366. color: rgb(255, 255, 255);
  2367. }
  2368.  
  2369. /* Show the dropdown menu on hover */
  2370. .dropdown:hover .dropdown-content {
  2371. display: inline-block;
  2372. }
  2373.  
  2374. /* Change the background color of the dropdown button when the dropdown content is shown */
  2375. .dropdown:hover .dropbtn {
  2376. background-color: #3e8e41;
  2377. font-weight: bold;
  2378. }
  2379.  
  2380. .tooltip {
  2381. position: relative;
  2382. display: inline-block;
  2383. /*border-bottom: 1px dotted black;*/
  2384. }
  2385.  
  2386. .tooltip .tooltiptext {
  2387. visibility: hidden;
  2388. min-width: 120px;
  2389. background-color: black;
  2390. color: #fff;
  2391. text-align: center;
  2392. border-radius: 6px;
  2393. padding: 5px;
  2394. position: absolute;
  2395. z-index: 2;
  2396. top: -5px;
  2397. left: 110%;
  2398. opacity: 0.8;
  2399. word-break: keep-all;
  2400. }
  2401.  
  2402. .tooltip .tooltiptext::after {
  2403. content: "";
  2404. position: absolute;
  2405. top: 50%;
  2406. right: 100%;
  2407. margin-top: -5px;
  2408. border-width: 5px;
  2409. border-style: solid;
  2410. border-color: transparent black transparent transparent;
  2411. }
  2412.  
  2413. .tooltip:hover .tooltiptext {
  2414. visibility: visible;
  2415. }
  2416. `);
  2417.  
  2418. GM_addStyle(`
  2419. .show_panel{
  2420. z-index:11;
  2421. font-size: 18px;
  2422. }
  2423. .top_panel{
  2424. background-color: rgb(45,53,63);
  2425. border-bottom: 0px solid rgb(20,20,20); padding:5px;
  2426. border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28);
  2427. box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95);
  2428. color: white;
  2429. }
  2430. #helper_panel{
  2431. position:absolute;
  2432. top:5px;
  2433. left:740px;
  2434. display: flex;
  2435. align-items: center;
  2436. }
  2437. #edit-helper-panel{
  2438. position:absolute;
  2439. top:50px;
  2440. left:740px;
  2441. }
  2442. #enhanced-helper-panel{
  2443. position:absolute;
  2444. top:50px;
  2445. left:848px;
  2446. }
  2447. .panel-button{
  2448. cursor:pointer;
  2449. }
  2450. #select-mode-panel{
  2451. position:absolute; top:5px;left:370px;
  2452. background-color: rgb(45,53,63);
  2453. border-bottom: 0px solid rgb(20,20,20); padding:5px;
  2454. border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28);
  2455. box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95);
  2456. color: white;}
  2457. `);
  2458. /**暂时隐藏按钮 */
  2459. GM_addStyle(`#helper_latLon .fixLoc{display:inline-block;width:55px;height:15px;overflow:hidden;}`);
  2460. //GM_addStyle(`#helper_latLon{display:none;}`);
  2461. /**重要添加十字鼠标 */
  2462. // GM_addStyle('#pic_frame div{border:1px solid !important;cursor:crosshair !important;}');
  2463. GM_addStyle(`
  2464. #line_panel {
  2465. position: absolute;
  2466. top: 5px;
  2467. right: 5px;
  2468. background-color: #f5f5f5;
  2469. border-bottom: 0px solid rgb(20, 20, 20);
  2470. padding: 5px;
  2471. border-radius: 4px;
  2472. border: 1px solid rgb(22, 25, 28);
  2473. box-shadow: 0 1px 0 rgba(162, 184, 204, 0.25) inset, 0 0 4px hsla(0, 0%, 0%, 0.95);
  2474. color: black;
  2475. width: 0px;
  2476. }
  2477. #show-temp,#show-wind,#show-pre,#show-cloud{
  2478. width: 100%;
  2479. height:210px;
  2480. }
  2481. #show-pre{
  2482. display:block;
  2483. }
  2484. .full-screen-mode{
  2485. float:left;
  2486. padding-left:10px;
  2487. }
  2488. .full-screen-mode #float_icons{
  2489. display:none!important;
  2490. }
  2491. .full-screen-mode #pic_frame div{
  2492. border:1px solid;
  2493. cursor:crosshair !important;
  2494. }
  2495. .top_panel .single-btn{
  2496. display:inline-block;
  2497. margin-left:5px;
  2498. margin-right:5px;
  2499. padding:5px;
  2500. border: 1px solid white;
  2501. /*background-color: #4CAF50;*/
  2502. }
  2503. .top_panel .single-btn:hover{
  2504. background-color:#47CB89;
  2505. }
  2506.  
  2507. .active-button > div, .active-button.single-btn{
  2508. background-color:green;
  2509. }
  2510.  
  2511. #compare-main, #multiviews-main{
  2512. z-index: 12;
  2513. position: absolute;
  2514. top: 0px;
  2515. right: 1%;
  2516. bottom: 1%;
  2517. left: 1%;
  2518. display: flex;
  2519. flex-direction: column;
  2520. align-items: center;
  2521. }
  2522. #float_icons{
  2523. display:none!important;
  2524. }
  2525. .compare-warpper {
  2526. display: flex;
  2527. justify-content: center;
  2528. min-width: 1000px;
  2529. background: white;
  2530. font-size: 18px;
  2531. align-items: center;
  2532. }
  2533. .compare-img-main {
  2534. border: 1px dotted red;
  2535. max-width: 1520px;
  2536. height: 100%;
  2537. display: grid;
  2538. grid-template-columns: 1fr 1fr;
  2539. background-color: white;
  2540. }
  2541. .compare-img-main .compare-img-wrapper {
  2542. border: 2px solid blue;
  2543. overflow: hidden;
  2544. position:relative;
  2545. }
  2546. .compare-img-main .compare-img {
  2547. position: relative;
  2548. top: -50px;
  2549. height: 120%;
  2550. }
  2551. .my-button{
  2552. cursor:pointer;
  2553. margin-left:2px;
  2554. margin-right:2px;
  2555. padding:1px;
  2556. background-color: rgb(45,53,63);
  2557. border-bottom: 0px solid rgb(20,20,20); padding:5px;
  2558. border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28);
  2559. box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95);
  2560. color: white;
  2561. }
  2562. .my-button:hover{
  2563. background-color:orange;
  2564. }
  2565. .display-none{
  2566. display:none!important;
  2567. }
  2568. .compare-img-info {
  2569. position: absolute;
  2570. top: 0;
  2571. left: 0;
  2572. background-color: rgba(0, 121, 13, 0.6);
  2573. color: white;
  2574. padding:2px;
  2575. font-size:20px;
  2576. font-family:"Arial","Microsoft YaHei","黑体","STXihei","华文细黑";
  2577. }
  2578. `);
  2579. GM_addStyle(`
  2580. .water-dot {
  2581. position: relative;
  2582. display: inline-block;
  2583. height: 26px;
  2584. width: 16px;
  2585. }
  2586.  
  2587. .water-dot:before,
  2588. .water-dot:after {
  2589. content: '';
  2590. position: absolute;
  2591. display: inline-block;
  2592. }
  2593.  
  2594. .water-dot:before {
  2595. left: 0;
  2596. width: 16px;
  2597. height: 16px;
  2598. border-radius: 50%;
  2599. background-image: repeating-radial-gradient(8px 8px at 50% 8px, transparent 0%, transparent 3px, #dd1010 3px, #dd1010 100%);
  2600. }
  2601.  
  2602. .water-dot:after {
  2603. bottom: 0;
  2604. left: 50%;
  2605. border: 14px solid #dd1010;
  2606. border-bottom-width: 0;
  2607. border-right-width: 7px;
  2608. border-left-width: 7px;
  2609. transform: translate(-50%,0);
  2610. border-bottom-color: transparent;
  2611. border-right-color: transparent;
  2612. border-left-color: transparent;
  2613. }
  2614. .water-dot.scale-lg{
  2615. transform: scale(1.5);
  2616. }
  2617. `);
  2618.  
  2619. GM_addStyle(`.show_panel{z-index:11;}
  2620. #pic_frame,#main_frame{
  2621. position:relative;
  2622. }
  2623. #map-pointer {
  2624. position: absolute;
  2625. top: 22px;
  2626. left: 0px;
  2627. /*background-color: rgba(0, 152, 50, 0.7);*/
  2628. color: white;
  2629. /*width:15px;*/
  2630. /*height:15px;*/
  2631. display:inline-block;
  2632. padding:2px;
  2633. z-index:10;
  2634. cursor:pointer;
  2635. }
  2636. #cv-draw-line{
  2637. position: absolute;
  2638. z-index:9;
  2639. left:0px;
  2640. cursor:crosshair;
  2641. }
  2642. #wrap-draw-tc{
  2643. display: inline-block;
  2644. position: absolute !important;
  2645. z-index: 1;
  2646. border:none !important;
  2647. margin-left: 37px;
  2648. pointer-events:none;
  2649. left: 0;
  2650. top: 0 !important;
  2651. }
  2652. `);
  2653.  
  2654. }
  2655.  
  2656. // let openMv = document.getElementById('open-mv');
  2657. // openMv.addEventListener('click',()=>{
  2658. // elemsConfig.mvImgDOM.mode = 'multiElem';
  2659. // });
  2660. /**
  2661. * 地图单点标志
  2662. */
  2663. function createMapIndicator() {
  2664. let wrapDiv = document.querySelector('#main_frame');
  2665. let pointer = utils.createElement('div', { id: 'map-pointer', draggable: 'true', class: 'display-none' });
  2666. pointer.innerHTML = '<span class="water-dot scale-lg"></span>'
  2667. wrapDiv.insertAdjacentElement('afterbegin', pointer);
  2668. //保存位置的状态值
  2669. var pos = {
  2670. // parent_top:0,
  2671. // parent_left:0,
  2672. // cur_top:0,
  2673. // cur_left:0,
  2674. x_diff: 0,// 鼠标相对目标物的位置
  2675. y_diff: 0,
  2676. }
  2677.  
  2678. function allowDrop(ev) { //ev是事件对象
  2679. ev.preventDefault(); //取消事件已经关联的默认活动
  2680. }
  2681.  
  2682. function drag(ev) {
  2683. //dataTransfer是一个媒介,将目标对象放入媒介
  2684. //dataTransfer对象用来保存被拖动的数据,仅在拖动事件有效
  2685. //这里将被拖动元素的id保存为名为Text的键值对中
  2686.  
  2687. ev.dataTransfer.setData("Pointer", ev.target.id);
  2688.  
  2689. //获取被拖动对象相对于上层元素顶边和左边位置
  2690. let mouseXY = utils.getElemRelPos(ev.currentTarget, ev.clientX, ev.clientY);
  2691. // let eStyle = getComputedStyle(ev.target);
  2692. pos.x_diff = mouseXY.x;
  2693. pos.y_diff = mouseXY.y;
  2694. // console.log('current');
  2695. // console.log(ev.currentTarget);
  2696. // console.log(ev.target);
  2697. // pos.parent_top=ev.target.offsetTop;
  2698. // pos.parent_left=ev.target.offsetLeft;
  2699. // pos.cur_top=ev.screenY;
  2700. // pos.cur_left=ev.screenX;
  2701. // console.log(mouseXY);
  2702. // console.log(eStyle.marginLeft);
  2703. // console.log(ev);
  2704. }
  2705.  
  2706. function drop(ev) {
  2707. var new_top, new_left;
  2708. ev.preventDefault();
  2709. // alert(2);
  2710. var data = ev.dataTransfer.getData("Pointer"); //从媒介中获取目标对象
  2711. var elem = document.getElementById(data);
  2712.  
  2713. //这里不能这样使用,因为offset*的值是只读的,不能改变
  2714. // elem.offsetLeft=v.parent_left+ev.screenX-v.cur_left+"px";
  2715. // elem.offsetTop=v.parent_top+ev.screenY-v.cur_top+"px";
  2716. let target = document.querySelector('#pic_frame img[style~="inline;"]');
  2717. let mouseXY = utils.getElemRelPos(target, ev.clientX, ev.clientY);
  2718. // let mouseXY = utils.getElemRelPos(ev.target,ev.clientX, ev.clientY);
  2719. // console.log(ev.target);
  2720. // let eStyle = getComputedStyle(elem);
  2721. // console.log(mouseXY);
  2722. // console.log(mouseXY.x+eStyle.width);
  2723. // 此处有微小的位移
  2724. elem.style.marginLeft = mouseXY.x - pos.x_diff + 35.8 + "px";
  2725. elem.style.marginTop = mouseXY.y - pos.y_diff + "px";
  2726. // console.log([parseFloat(elem.style.marginTop)+23.2 + 12.3,parseFloat(elem.style.marginLeft)+10]);
  2727. let newMouseXY = {
  2728. x: parseFloat(elem.style.marginLeft) - 29.31,
  2729. // x:parseFloat(elem.style.marginLeft) + 5.0,
  2730. y: parseFloat(elem.style.marginTop) + 33.066,
  2731. };
  2732. const loc = helperConfig.matchLoc(newMouseXY, helperConfig.matchParam);
  2733. elemsConfig.latLonInput.lat.innerHTML = loc.lat; // mouseXY.y
  2734. elemsConfig.latLonInput.lon.innerHTML = loc.lon; // mouseXY.x
  2735. elemsConfig.fixPoint = { lat: loc.lat, lon: loc.lon };
  2736. // console.log(elemsConfig.fixPoint);
  2737. utils.showTimeSeries(elemsConfig.fixPoint);
  2738.  
  2739. // elem.style.marginLeft=pos.parent_left+ev.screenX-pos.cur_left-1+"px";
  2740. // elem.style.marginTop=pos.parent_top+ev.screenY-pos.cur_top-23.2+"px";
  2741. /* TODO
  2742. const imgXY = {
  2743. x: elem.style.marginLeft;
  2744. y: elem.style.marginTop;
  2745. }
  2746. const loc = helperConfig.matchLoc(imgXY, helperConfig.matchParam);
  2747. elemsConfig.pointerPoint.lat = loc.lat;
  2748. elemsConfig.pointerPoint.lon = loc.lon;
  2749. utils.showTimeSeries(elemsConfig.pointerPoint);
  2750. */
  2751.  
  2752. }
  2753. pointer.addEventListener('dragstart', drag);
  2754. wrapDiv.addEventListener('dragover', allowDrop);
  2755. wrapDiv.addEventListener('drop', drop);
  2756. }
  2757.  
  2758.  
  2759.  
  2760. /**
  2761. * 经纬度到图像位置
  2762. */
  2763. function latlon2XY(loc = { lat: 10, lon: 120 }) {
  2764. const imgXY = helperConfig.matchImgXY(loc, helperConfig.matchParam);
  2765. return imgXY;
  2766. }
  2767.  
  2768. function createCrossSectionCanvas() {
  2769. // let wrapDiv = document.querySelector('#main_frame');
  2770. let wrapDiv = document.querySelector('#pic_frame');
  2771. const cv = utils.createElement('canvas', { id: 'cv-draw-line', class: 'display-none', width: "800", height: "600", style: "border:1px solid #d3d3d3;" });
  2772. wrapDiv.insertAdjacentElement('afterbegin', cv);
  2773. const ctx = cv.getContext('2d');
  2774. const line = { start: [0, 0], end: [0, 0], init: false };
  2775. const cvMsMove = function (ev) {
  2776. // console.log('test2');
  2777. ctx.clearRect(0, 0, 800, 600);
  2778. ctx.beginPath();
  2779. ctx.lineWidth = 3;
  2780. let mouseXY = utils.getElemRelPos(ev.target, ev.clientX, ev.clientY);
  2781. line.end = [mouseXY.x, mouseXY.y];
  2782. ctx.moveTo(...line.start);
  2783. ctx.lineTo(...line.end);
  2784. ctx.strokeStyle = "green";
  2785. ctx.stroke();
  2786. // console.log(line);
  2787. }
  2788.  
  2789. const cvMsClick = function (ev) {
  2790. // console.log('test');
  2791. if (line.init) {
  2792. cv.removeEventListener('mousemove', cvMsMove, false);
  2793.  
  2794. // console.log(elemsConfig.latLonInput.lon.innerText,elemsConfig.latLonInput.lat.innerText);
  2795. const deltaX = 37.11666870117187;
  2796. line.startLatlon = {
  2797. x: line.start[0] - deltaX,
  2798. y: line.start[1],
  2799. }
  2800. line.endLatlon = {
  2801. x: line.end[0] - deltaX,
  2802. y: line.end[1],
  2803. }
  2804. line.startLatlon.loc = helperConfig.matchLoc(line.startLatlon, helperConfig.matchParam);
  2805. line.endLatlon.loc = helperConfig.matchLoc(line.endLatlon, helperConfig.matchParam);
  2806. console.log(line);
  2807. utils.showCross(line.startLatlon.loc, line.endLatlon.loc)
  2808. line.init = false;
  2809. } else {
  2810. // ctx.beginPath();
  2811. // ctx.lineWidth=10;
  2812. // ctx.moveTo(0,0);
  2813. // ctx.lineTo(50,50);
  2814. // ctx.strokeStyle="green";
  2815. // ctx.stroke();
  2816. let mouseXY = utils.getElemRelPos(ev.target, ev.clientX, ev.clientY);
  2817. line.start = [mouseXY.x, mouseXY.y];
  2818. cv.addEventListener('mousemove', cvMsMove, false);
  2819. line.init = true;
  2820. }
  2821.  
  2822. }
  2823. cv.addEventListener('click', cvMsClick, false);
  2824. ctx.fillStyle = 'rgba(255,255,255,0)';
  2825. }
  2826. setTimeout(() => { createCrossSectionCanvas() }, 5000);
  2827.  
  2828. /*
  2829. 绘制台风部分
  2830. */
  2831. function createSvgTC() {
  2832. let wrapDiv = document.querySelector('#pic_frame');
  2833. const svgTc = utils.createElement('span', { id: 'wrap-draw-tc', class: 'display-none', width: "800", height: "600" });
  2834. wrapDiv.insertAdjacentElement('afterbegin', svgTc);
  2835. // console.log('getTime');
  2836. }
  2837.  
  2838. // setTimeout(()=>{createSvgTC()},5000);
  2839.  
  2840. async function showSvgTC() {
  2841. // if(proxyConfig.tcShowLock) return;
  2842. // proxyConfig.tcShowLock = true;
  2843. let svgTC = document.querySelector('#wrap-draw-tc');
  2844. if (!svgTC) {
  2845. createSvgTC();
  2846. svgTC = document.querySelector('#wrap-draw-tc');
  2847. }
  2848. svgTC.classList.remove('display-none');
  2849. const fcTime = utils.getFcTime();
  2850.  
  2851. proxyConfig.preRegion = unsafeWindow._region;
  2852. proxyConfig.preTime = fcTime.date + fcTime.hr;
  2853.  
  2854. const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH');
  2855. const sT = moment(initTime).add(7, 'hours').format('YYYY-MM-DD%20HH:mm');
  2856. const eT = moment(initTime).add(9, 'hours').format('YYYY-MM-DD%20HH:mm');
  2857. // demoUrl = 'https://trident.gdmo.gq/api?interface=tc-ens&gt=2020-07-29%2007:00&lt=2020-07-29%2009:00&dateFormat=YYYY-MM-DD%20HH:mm&ins=ecmwf&basin=WPAC&spe=all';
  2858. // 接口为北京时
  2859. const url = `https://trident.gdmo.gq/api?interface=tc-ens&gt=${sT}&lt=${eT}&dateFormat=YYYY-MM-DD%20HH:mm&ins=ecmwf&basin=WPAC&spe=all`;
  2860. let jsonRaw = await utils.getJSON(url)
  2861. .catch(err => {
  2862. // proxyConfig.tcShowLock = false;
  2863. throw err;
  2864. });
  2865. // proxyConfig.tcShowLock = false;
  2866. let tcArr = jsonRaw.data;
  2867. if (tcArr.length) {
  2868. let allTC = tcUtil.catTC(tcArr);
  2869.  
  2870. let cyclonesWrap = allTC[0].ins[0];
  2871. if (!cyclonesWrap) return console.error('没数据');
  2872. // console.log(cyclonesWrap);
  2873. drawTCMap(cyclonesWrap.tc);
  2874. } else {
  2875. let errorNotice = GM_notification({ text: '当前没有台风数据', image: 'http://10.148.8.228/images/logo.png', title: '数据缺失', timeout: 3000 });
  2876. if (errorNotice) setTimeout(() => errorNotice.remove(), 3000);
  2877. d3.select("#wrap-draw-tc > svg").remove();
  2878. }
  2879. }
  2880.  
  2881. function toggleTCMode(whichBtn) {
  2882. if (whichBtn === 'tcShow') {
  2883. proxyConfig.showTcMode = !proxyConfig.showTcMode;
  2884. } else if (whichBtn === 'detShow') {
  2885. proxyConfig.showDetTcMode = !proxyConfig.showDetTcMode;
  2886. } else if (whichBtn === 'ensShow') {
  2887. proxyConfig.showEnsTcMode = !proxyConfig.showEnsTcMode;
  2888. } else {
  2889. console.error('不正确的参数 in toggleTCMode')
  2890. }
  2891. }
  2892.  
  2893. function drawTCMap(multiTC) {
  2894. // console.log(multiTC);
  2895. // TODO 缺少 projection 和 path 函数
  2896. let timeInterval = tcUtil.model[multiTC[0].ins].interval;
  2897. function path(geoData = { type: "LineString", coordinates: [111.5, 23.5] }) {
  2898. if (geoData.type === "LineString") {
  2899. let loc0 = { lon: geoData.coordinates[0][0], lat: geoData.coordinates[0][1] };
  2900. let iXY0 = latlon2XY(loc0);// {x,y}
  2901. let loc1 = { lon: geoData.coordinates[1][0], lat: geoData.coordinates[1][1] };
  2902. let iXY1 = latlon2XY(loc1);// {x,y}
  2903. let svgDstring = `M${iXY0.x},${iXY0.y} L${iXY1.x},${iXY1.y}`;
  2904. return svgDstring;
  2905. } else {
  2906. let loc = { lon: geoData.coordinates[0][0], lat: geoData.coordinates[0][1] };
  2907. let iXY = latlon2XY(loc);
  2908. return iXY;
  2909. }
  2910. }
  2911.  
  2912. let allLineArr = new Array();
  2913. for (let iTc = 0; iTc < multiTC.length; iTc++) {
  2914. let tcRaw = multiTC[iTc];
  2915. let catArr = tcRaw.tracks
  2916. .map(member => member.track)
  2917. .map(track => {
  2918. let twoPointLineArr = [];
  2919. for (let i = 0; i < track.length - 1; i++) {
  2920. let point0 = track[i][1];
  2921. let point1 = track[i + 1][1];
  2922. let nextWind = track[i + 1][3];
  2923. let nextCat = tcUtil.wind2cat(nextWind);
  2924. let nextColor = tcUtil.tcColor[nextCat];
  2925. let time0 = track[i][0];
  2926. let time1 = track[i + 1][0];
  2927. if (time1 - time0 > timeInterval * 2) continue;
  2928. const distance = Math.sqrt(Math.pow(point1[0] - point0[0], 2) + Math.pow(point1[1] - point0[1], 2));
  2929. if (distance > 9) continue; // 如果大于9个经纬度则断线
  2930. twoPointLineArr.push({
  2931. line: { type: "LineString", coordinates: [point0, point1] },
  2932. nextCat,
  2933. nextColor,
  2934. curCat: tcUtil.wind2cat(track[i][3])
  2935. });
  2936. }
  2937. return twoPointLineArr;
  2938. })
  2939. .reduce((acc, val) => acc.concat(val), []);
  2940.  
  2941. allLineArr.push(catArr);
  2942. }
  2943. allLineArr = allLineArr.reduce((acc, val) => acc.concat(val), []);
  2944.  
  2945. d3.select("#wrap-draw-tc > svg").remove();
  2946. let baseMap = d3
  2947. .select('#wrap-draw-tc')
  2948. .append("svg")
  2949. .attr("width", 800)
  2950. .attr("height", 600)
  2951. .attr("class", "map-svg");
  2952.  
  2953. let tcEnsSvgG = baseMap.append("g").attr("class", "svg-tc-ens");
  2954. if (proxyConfig.showEnsTcMode) {
  2955. tcEnsSvgG
  2956. .selectAll("path")
  2957. .data(allLineArr)
  2958. .enter()
  2959. .append("path")
  2960. .attr("d", d => path(d.line))
  2961. .attr("class", d => `track-line ${d.nextCat}`)
  2962. .style("stroke", d => 'rgb(74, 80, 87)')
  2963. .attr("opacity", 0.6);
  2964. // .style("stroke", d => d.nextColor)
  2965.  
  2966. let allPointArr = new Array();
  2967. for (let iTc = 0; iTc < multiTC.length; iTc++) {
  2968. let tcRaw = multiTC[iTc];
  2969. let pointArr = tcRaw.tracks
  2970. .map(member => member.track)
  2971. .map(track =>
  2972. track.map(point => {
  2973. let cat = tcUtil.wind2cat(point[3]);
  2974. return {
  2975. pointXY: path({ type: "Point", coordinates: [point[1]] }),
  2976. // project: projection(point[1]),
  2977. color: tcUtil.tcColor[cat],
  2978. cat
  2979. };
  2980. })
  2981. )
  2982. .reduce((acc, val) => acc.concat(val), []);
  2983. // console.log(pointArr);
  2984. allPointArr = allPointArr.concat(pointArr);
  2985. }
  2986. // console.log(allPointArr);
  2987.  
  2988.  
  2989. let pointSvg = tcEnsSvgG.append("g");
  2990. pointSvg.attr("class", "point-g");
  2991. pointSvg
  2992. .selectAll("circle")
  2993. .data(allPointArr)
  2994. .enter()
  2995. .append("circle")
  2996. .attr("class", "point")
  2997. .attr("cx", d => d.pointXY.x)
  2998. .attr("cy", d => d.pointXY.y)
  2999. .attr("r", 3)
  3000. .attr("opacity", 0.8)
  3001. .style("stroke", d => d.color)
  3002. .style("stroke-width", 1.0)
  3003. .style("fill", 'none');
  3004.  
  3005. }
  3006.  
  3007. if (proxyConfig.showDetTcMode) {
  3008. /* 确定性预报 */
  3009. let allDetCatArr = new Array();
  3010. for (let iTc = 0; iTc < multiTC.length; iTc++) {
  3011. let tcRaw = multiTC[iTc];
  3012. if (!tcRaw.detTrack || !tcRaw.detTrack.track) continue;//不存在退出
  3013. let detArr = (() => {
  3014. let track = tcRaw.detTrack.track
  3015. let twoPointLineArr = [];
  3016. for (let i = 0; i < track.length - 1; i++) {
  3017. let point0 = track[i][1];
  3018. let point1 = track[i + 1][1];
  3019. let nextWind = track[i + 1][3];
  3020. let nextCat = tcUtil.wind2cat(nextWind);
  3021. let nextColor = tcUtil.tcColor[nextCat];
  3022. let time0 = track[i][0];
  3023. let time1 = track[i + 1][0];
  3024. if (time1 - time0 > timeInterval * 2) continue;// 大于时间间隔跳过连线
  3025. const distance = Math.sqrt(Math.pow(point1[0] - point0[0], 2) + Math.pow(point1[1] - point0[1], 2));
  3026. if (distance > 9) continue; // 如果大于9个经纬度则断线
  3027. twoPointLineArr.push({
  3028. line: { type: "LineString", coordinates: [point0, point1] },
  3029. nextCat,
  3030. nextColor,
  3031. curCat: tcUtil.wind2cat(track[i][3])
  3032. });
  3033. }
  3034. return twoPointLineArr;
  3035. })();
  3036. allDetCatArr.push(detArr);
  3037. }
  3038. allDetCatArr = allDetCatArr.reduce((acc, val) => acc.concat(val), []);
  3039.  
  3040. let allDetPointArr = new Array();
  3041. for (let iTc = 0; iTc < multiTC.length; iTc++) {
  3042. let tcRaw = multiTC[iTc];
  3043. if (!tcRaw.detTrack || !tcRaw.detTrack.track) continue;//不存在退出
  3044. const detPoints = tcRaw.detTrack.track
  3045. .map(point => {
  3046. let cat = tcUtil.wind2cat(point[3]);
  3047. return {
  3048. pointXY: path({ type: "Point", coordinates: [point[1]] }),
  3049. color: tcUtil.tcColor[cat],
  3050. cat
  3051. };
  3052. });
  3053. allDetPointArr = allDetPointArr.concat(detPoints);
  3054. }
  3055.  
  3056. const detTrackSvg = baseMap.append("g").attr("class", "svg-tc-det");
  3057. detTrackSvg
  3058. .selectAll("path")
  3059. .data(allDetCatArr)
  3060. .enter()
  3061. .append("path")
  3062. .attr("d", d => path(d.line))
  3063. .attr("class", d => `track-line-det ${d.nextCat}`)
  3064. .style("stroke", d => d.nextColor)
  3065. .style("stroke-width", 3);
  3066.  
  3067. const detPointSvg = detTrackSvg.append("g");
  3068. detPointSvg.attr("class", "point-g");
  3069. detPointSvg
  3070. .selectAll("circle")
  3071. .data(allDetPointArr)
  3072. .enter()
  3073. .append("circle")
  3074. .attr("class", "point")
  3075. .attr("cx", d => d.pointXY.x)
  3076. .attr("cy", d => d.pointXY.y)
  3077. .attr("r", 3.5)
  3078. .style("fill", d => d.color)
  3079. .style("stroke", d => d.color)
  3080. .style("stroke-width", 1.0)
  3081. }
  3082. }
  3083.  
  3084. let tcUtil = {
  3085. worldGeo: null,
  3086. geoMap: [],
  3087. tcColor: {
  3088. SuperTY: "rgb(128,0,255)",
  3089. STY: "rgb(153,20,8)",
  3090. TY: "rgb(255,0,0)",
  3091. STS: "rgb(255,128,0)",
  3092. TS: "rgb(0,0,255)",
  3093. TD: "rgb(105,163,74)",
  3094. LOW: "rgb(85,85,79)"
  3095. },
  3096.  
  3097. wind2cat(wind) {
  3098. if (wind >= 10.8 && wind < 17.2) {
  3099. return "TD";
  3100. } else if (wind >= 17.2 && wind < 24.5) {
  3101. return "TS";
  3102. } else if (wind >= 24.5 && wind < 32.7) {
  3103. return "STS";
  3104. } else if (wind >= 32.7 && wind < 41.5) {
  3105. return "TY";
  3106. } else if (wind >= 41.5 && wind < 51.0) {
  3107. return "STY";
  3108. } else if (wind >= 51.0) {
  3109. return "SuperTY";
  3110. } else {
  3111. return "LOW";
  3112. }
  3113. },
  3114. timeColor: [
  3115. { name: "H24", color: "rgb(0,0,0)" },
  3116. { name: "H48", color: "rgb(255,0,0)" },
  3117. { name: "H72", color: "rgb(0,140,48)" },
  3118. { name: "H96", color: "rgb(255,128,0)" },
  3119. { name: "H120", color: "rgb(0,0,102)" },
  3120. { name: "H144", color: "rgb(0,255,0)" },
  3121. { name: "H168", color: "rgb(153,20,8)" },
  3122. { name: "H192", color: "rgb(0,255,255)" },
  3123. { name: "H216", color: "rgb(255,0,255)" },
  3124. { name: "H240", color: "rgb(178,178,178)" }
  3125. ],
  3126. matchTimeColor(time = 24) {
  3127. let count = Math.ceil(time / 24) - 1;
  3128. if (count === -1) count = 0; // 颜色下边界
  3129. let colorLen = tcUtil.timeColor.length;
  3130. if (count > colorLen - 1) count = colorLen - 1; //超过颜色上界
  3131. return tcUtil.timeColor[count].color;
  3132. },
  3133. model: {
  3134. ecmwf: {
  3135. enNumber: 51,
  3136. interval: 6,
  3137. timeRange() {
  3138. return Array.from(new Array(40), (val, index) => index * 6);
  3139. }
  3140. },
  3141. NCEP: {
  3142. enNumber: 21,
  3143. interval: 6,
  3144. timeRange() {
  3145. return Array.from(new Array(40), (val, index) => index * 6);
  3146. }
  3147. },
  3148. "ncep-R": {
  3149. enNumber: 21,
  3150. interval: 12,
  3151. timeRange() {
  3152. return Array.from(new Array(30), (val, index) => index * 12);
  3153. }
  3154. },
  3155. "ukmo-R": {
  3156. enNumber: 35,
  3157. interval: 12,
  3158. timeRange() {
  3159. return Array.from(new Array(30), (val, index) => index * 12);
  3160. }
  3161. },
  3162. "ecmwf-R": {
  3163. enNumber: 35,
  3164. interval: 12,
  3165. timeRange() {
  3166. return Array.from(new Array(30), (val, index) => index * 12);
  3167. }
  3168. },
  3169. UKMO: {}
  3170. },
  3171. catTC(tcArr = []) {
  3172. let timeSet = new Set(tcArr.map(tc => tc.initTime)); //选出日期并去重
  3173. let insSet = new Set(tcArr.map(tc => tc.ins));
  3174. let tcAll = []
  3175. for (let iTime of timeSet) {
  3176. let timeWrap = { time: iTime, ins: [] };
  3177. let sameTime = tcArr.filter(tc => tc.initTime == iTime);
  3178. for (let iIns of insSet) {
  3179. let insWrap = { ins: iIns, tc: [] }
  3180. let sameIns = sameTime.filter(tc => tc.ins == iIns);
  3181. insWrap.tc = sameIns;
  3182. timeWrap.ins.push(insWrap);
  3183. }
  3184. tcAll.push(timeWrap);
  3185. }
  3186. // [ { time: '2020-05-15T00:00:00.000Z', ins: [ {} ] } ]
  3187. return tcAll;
  3188. },
  3189. };
  3190.  
  3191. /** 多模式部分代码 */
  3192. function GetURLFromRule({ year = '2020', month = '02', day = '25', hour = '12', fc = '000', minute = '', region, eleName, modelFileName, model }) {
  3193. let imgUrl = this.rule.replace(/{yyyy}/g, year)
  3194. .replace(/{mm}/g, month)
  3195. .replace(/{dd}/g, day)
  3196. .replace(/{HH}/g, hour)
  3197. .replace(/{hhh}/g, fc)
  3198. .replace(/{mi}/g, minute)
  3199. .replace(/{yyyymmdd}/g, year + month + day)
  3200. .replace(/{date}/g, year + month + day)
  3201. return imgUrl;
  3202. }
  3203.  
  3204. function GetTimeInfo() {
  3205. let timeInfo = {
  3206. year: this.year,
  3207. month: this.month,
  3208. day: this.day,
  3209. hour: this.hour,
  3210. fc: this.fc,
  3211. minute: this.minute ? this.minute : undefined,
  3212. }
  3213. return timeInfo;
  3214. }
  3215.  
  3216. function getImgInfoFromURL(url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20200225/ecmwffine_hn_mslp_000_2020022512.png') {
  3217. let model, region, date, modelFileName, eleName, fc, year, month, day, hour, minute;
  3218. let regPatten;
  3219. let reg = /http:.*?\/znwp\/(.*?)\/(.*?)\/(\d{8})\/(.*?)_(.*?)_(.*?)_(\d{3})_(\d{4})(\d{2})(\d{2})(\d{2})(.*?$)/;
  3220. let matchUrl = url.match(reg);
  3221. if (matchUrl) {
  3222. regPatten = 0;
  3223. model = matchUrl[1];
  3224. region = matchUrl[2];
  3225. date = matchUrl[3];
  3226. modelFileName = matchUrl[4];
  3227. eleName = matchUrl[6];
  3228. fc = matchUrl[7];
  3229. year = matchUrl[8];
  3230. month = matchUrl[9];
  3231. day = matchUrl[10];
  3232. hour = matchUrl[11];
  3233. }
  3234. else {
  3235. let reg2 = /http:.*?\/znwp\/(.*?)\/(.*?)\/+(\d{8})\/(\d{2})\/(.*?)_(.*?)_(.*?)_m(\d{3})_(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(.*?$)/;
  3236. matchUrl = url.match(reg2);
  3237. if (matchUrl) {
  3238. regPatten = 1;
  3239. model = matchUrl[1];
  3240. region = matchUrl[2];
  3241. date = matchUrl[3];
  3242. modelFileName = matchUrl[5];
  3243. eleName = matchUrl[7];
  3244. fc = matchUrl[8];
  3245. year = matchUrl[9];
  3246. month = matchUrl[10];
  3247. day = matchUrl[11];
  3248. hour = matchUrl[12];
  3249. minute = matchUrl[12];
  3250. } else {
  3251. throw new Error('无法匹配的图像地址:' + url)
  3252. }
  3253. }
  3254.  
  3255. // ["http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20200225/ecmwffine_hn_mslp_000_2020022512.png", "ecmwffine_b", "hn", "20200225", "ecmwffine", "hn", "mslp", "000", "2020", "02", "25", "12", ".png"]
  3256.  
  3257.  
  3258.  
  3259. let info = { regPatten, model, region, date, modelFileName, eleName, fc, year, month, day, hour, minute };
  3260. info.getTimeInfo = GetTimeInfo;
  3261. return info;
  3262. }
  3263.  
  3264. function matchModel2Rule(model = '', region, eleName) {
  3265. let matchModel = modelMeta.find(v => v.model === model);
  3266. if (!matchModel) throw new new ReferenceError('未找到匹配的模式');
  3267. const rule = matchModel.rule;
  3268. //TODO: 有些特殊模式需要特殊判断
  3269. let newRule = `http://10.148.8.228/files_home/znwp/${model}/{range}/{yyyy}{mm}{dd}/` + rule;
  3270. if (model === 'grapes1km') newRule = `http://10.148.8.228/files_home/znwp/${model}/{range}/` + rule;
  3271. newRule = newRule.replace(/{range}/g, region)
  3272. .replace(/{name}/g, eleName);
  3273.  
  3274. const newInfo = {
  3275. model,
  3276. region,
  3277. eleName,
  3278. modelName: matchModel.modelName,
  3279. modelFileName: matchModel.modelName,
  3280. };
  3281. newInfo.getUrl = GetURLFromRule;
  3282. newInfo.rule = newRule;
  3283. return newInfo;
  3284. }
  3285.  
  3286. // testMain();
  3287.  
  3288. function matchMVRule(url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20200225/ecmwffine_hn_mslp_000_2020022512.png', mvRule) {
  3289. const imgInfo = getImgInfoFromURL(url);
  3290. const infoList = [imgInfo.model, imgInfo.region, imgInfo.eleName];
  3291. let matchedRules = [];
  3292.  
  3293. function isMatchCondition(iInfo, rule) {
  3294. let condition = false;
  3295. if (Array.isArray(rule)) {
  3296. condition = rule.includes(iInfo);
  3297. } else if (typeof (rule) === 'string') {
  3298. condition = rule === iInfo;
  3299. } else if (!Boolean(rule)) {// rule === null
  3300. condition = true;
  3301. } else {
  3302. throw new TypeError('无法识别的规则类型:' + item);
  3303. }
  3304. return condition;
  3305. }
  3306.  
  3307. for (let item of mvRule) {
  3308. // console.log(mvRule);
  3309. const fitIndex = item.rule.findIndex(list => {// 查找四个规则中是哪个匹配
  3310. let isMatch = list.every((rule, i) => {
  3311. const iInfo = infoList[i];
  3312. return isMatchCondition(iInfo, rule);
  3313. });
  3314. return isMatch;
  3315. })
  3316. if (fitIndex === -1) {
  3317. continue;
  3318. } else {
  3319. let copyItem = Object.assign({}, item);
  3320. copyItem.fitIndex = fitIndex;
  3321. matchedRules.push(copyItem);
  3322. }
  3323. }
  3324. return matchedRules;
  3325. }
  3326.  
  3327. function getMVSrcListFromRule(url, ruleItem = { rule: [] }) {
  3328. let imgList = [0, 0, 0, 0];
  3329. const imgInfo = getImgInfoFromURL(url);
  3330. const infoList = [imgInfo.model, imgInfo.region, imgInfo.eleName];
  3331. const rule = ruleItem.rule;
  3332. for (let i = 0; i < rule.length; i++) {// rule.length = 4;
  3333. if (i === ruleItem.fitIndex) {
  3334. imgList[i] = url;
  3335. } else {
  3336.  
  3337. let iRule = rule[i];// ['ncep',['oy','cn','hn','gd',],null],
  3338. let iBaseInfo = iRule.map((ele, j) => {
  3339. if (Array.isArray(ele)) {
  3340. return infoList[j];
  3341. } else if (typeof (ele) === 'string') {
  3342. return ele;
  3343. } else if (!Boolean(ele)) {// rule === null
  3344. return infoList[j];
  3345. } else {
  3346. throw new TypeError('无法识别的规则类型:' + iRule);
  3347. }
  3348. })
  3349. const iImgRule = matchModel2Rule(iBaseInfo[0], iBaseInfo[1], iBaseInfo[2]);
  3350. imgList[i] = iImgRule.getUrl(imgInfo.getTimeInfo());
  3351. }
  3352. }
  3353. return imgList;
  3354. }
  3355.  
  3356. if(location.href.includes('gisloader')){
  3357. GM_addStyle(`
  3358. .maps-more-image .maps-more-header {
  3359. position: absolute !important;
  3360. font-size: 22px !important;
  3361. z-index: 20;
  3362. background-color: #ededed6e !important;
  3363. color: #1400ff !important;
  3364. text-shadow: #FC0 1px 0 10px;
  3365. left: 5%;
  3366. }
  3367. #mapMain .maps-more-image .maps-header-info {
  3368. font-size: 20px !important;
  3369. }
  3370. #mapMain .maps-more-image .map-img {
  3371. top: 49%!important;
  3372. left: 47%!important;
  3373. height: 123%!important;
  3374. }
  3375. .group-mode-instruction {
  3376. height: auto;
  3377. }
  3378. .group-mode[data-drop-index="0"] {
  3379. z-index: 10!important;
  3380. }
  3381. .group-mode[data-drop-index="1"] {
  3382. z-index: 9!important;
  3383. }
  3384. .group-mode[data-drop-index="2"] {
  3385. z-index: 8;
  3386. }
  3387. .group-mode[data-drop-index="3"] {
  3388. z-index: 7;
  3389. }
  3390. .group-mode[data-drop-index="4"] {
  3391. z-index: 6;
  3392. }
  3393. .group-mode[data-drop-index="5"] {
  3394. z-index: 5;
  3395. }
  3396. .group-mode[data-drop-index="6"] {
  3397. z-index: 4;
  3398. }
  3399. .group-mode[data-drop-index="7"] {
  3400. z-index: 3;
  3401. }
  3402. .group-mode[data-drop-index="8"] {
  3403. z-index: 2;
  3404. }
  3405. #mapMain .maps-more-image .maps-more-body {
  3406. overflow: hidden;
  3407. height: 100%!important;
  3408. }
  3409. `);
  3410. }