NWP helper in GuangDong

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

当前为 2020-05-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name NWP helper in GuangDong
  3. // @description 广东省气象业务网数值预报页面增强
  4. // @namespace minhill.com
  5. // @include http://10.148.8.228/to_fore_homepage.action*
  6. // @version 1.5.3
  7. // @require https://lib.baomitu.com/echarts/4.6.0/echarts.min.js
  8. // @require https://lib.baomitu.com/moment.js/2.24.0/moment.min.js
  9. // @grant GM_addStyle
  10. // @grant @grant unsafeWindow
  11. // @grant GM_openInTab
  12. // @grant GM_xmlhttpRequest
  13. // @license The MIT License (MIT); http://opensource.org/licenses/MIT
  14. // @connect 172.22.1.175
  15. // @connect api.map.baidu.com
  16. // @compatible firefox
  17. // @compatible chrome
  18. // @note 2018/01/08 增加时效转换按钮
  19. // @note 2018/01/15 增加经纬度功能
  20. // @supportURL https://greasyfork.org/scripts/26259
  21. // @author Hanchy Hill
  22. // TODO 数据格式错误时显示提示
  23. // TODO require resData
  24. // TODO 风向tooltip改进
  25. // TODO 增加气压
  26. // TODO 增加集合预报-32天预报
  27. // TODO 单站停止经纬度捕捉
  28. // TODO 分钟降水时时效切换错误
  29. // TODO 显示单击的点
  30. // TODO 垂直剖面NCL测试
  31. // TODO 垂直探空曲线
  32. // TODO 降水量, 风力分级
  33. // ==/UserScript==
  34.  
  35. let mvPatternList = [
  36. [{
  37. "elem": "t2mm",
  38. "elemCN": "2m气温",
  39. "region": "cn",
  40. "model": "ecmwffine_b",
  41. "modelFileName":"ecmwffine"
  42. },
  43. {
  44. "elem": "t2mm24",
  45. "elemCN": "2m气温24h变温",
  46. "region": "cn",
  47. "model": "ecmwffine_b",
  48. "modelFileName":"ecmwffine"
  49. },
  50. {
  51. "elem": "wind10m",
  52. "elemCN": "10m风",
  53. "region": "cn",
  54. "model": "ecmwffine_b",
  55. "modelFileName":"ecmwffine"
  56. },
  57. {
  58. "elem": "10gust3",
  59. "elemCN": "10m阵风(过去3h)",
  60. "region": "cn",
  61. "model": "ecmwffine_b",
  62. "modelFileName":"ecmwffine"
  63. }]
  64. ]
  65.  
  66. let userConfig = { // 用户设置
  67. alterDate : false, // 默认不修改时次
  68. timelineMove: false,//是否启用滑动时间触发
  69. };
  70.  
  71. let $ = unsafeWindow.$;
  72.  
  73. const elemsConfig = {
  74. latLonInput: undefined,
  75. $doc:()=>unsafeWindow.$doc,
  76. $settings:()=>unsafeWindow.$settings,
  77. $home:unsafeWindow.$home,
  78. fixPoint: undefined,
  79. pointerPoint:{lat:0,lon:0},// 地图锚点位置
  80. point01: undefined,
  81. point02: undefined,
  82. state:'timeseries',//'vertical','skewT'
  83. compareImgDOM:{img:[],info:[]},//前后预报时次对比DOM引用
  84. mvImgDOM:{//四分图DOM引用
  85. img:[],
  86. info:[],
  87. mode:'multiElem',//multiTime, multiModel, multiElem,multiInitime
  88. matchPattens:[ {
  89. "elem": "t2mm",
  90. "elemCN": "2m气温",
  91. "region": "cn",
  92. "model": "ecmwffine_b",
  93. "modelFileName":"ecmwffine"
  94. },
  95. {
  96. "elem": "t2mm24",
  97. "elemCN": "2m气温24h变温",
  98. "region": "cn",
  99. "model": "ecmwffine_b",
  100. "modelFileName":"ecmwffine"
  101. },
  102. {
  103. "elem": "wind10m",
  104. "elemCN": "10m风",
  105. "region": "cn",
  106. "model": "ecmwffine_b",
  107. "modelFileName":"ecmwffine"
  108. },
  109. {
  110. "elem": "10gust3",
  111. "elemCN": "10m阵风(过去3h)",
  112. "region": "cn",
  113. "model": "ecmwffine_b",
  114. "modelFileName":"ecmwffine"
  115. },],
  116. },
  117. };
  118.  
  119. const helperConfig = {
  120. region:{
  121. hn:{//华南
  122. isMap: true,// 是否是等经纬地图
  123. projection:'Equidistant',// 投影
  124. latLon:{
  125. xRight:636.98, xLeft:172.56,
  126. yTop:56.516, yBottom:547.576,
  127. lon0:105.0, lon1:120.0,
  128. lat0:15.0, lat1:30.0,
  129. },
  130. },
  131. cn:{//中国
  132. isMap: true,// 是否是等经纬地图
  133. projection:'Lambert',
  134. latLon:{
  135. refLon0:110.25,refLat0:20,
  136. m0:342.98333,n0:382.51666,
  137. // refLon0:110,refLat0:20,
  138. // m0:338.98,n0:382.51,
  139. refLat1:1.001,refLat2:25.0,
  140. lonr : 80,latr :40,
  141. mr:94.983,nr:167.51,
  142. },
  143.  
  144. },
  145. oy:{//欧亚
  146. isMap: true,// 是否是等经纬地图
  147. projection:'Mercator',
  148. latLon:{
  149. xRight:692.9833, xLeft:156.9833,
  150. yTop:54.51666, yBottom:533.51666,
  151. lon0:60.0, lon1:160.0,
  152. lat0:10.0, lat1:70.0,
  153. },
  154. },
  155. gd:{//广东
  156. isMap: true,// 是否是等经纬地图
  157. projection:'Equidistant',// 投影
  158. latLon:{
  159. xRight:589.98334, xLeft:124.98334,
  160. yTop:104.51666, yBottom:437.51666,
  161. lon0:110, lon1:116,
  162. lat0:21.0, lat1:25.0,
  163. },
  164. },
  165. hy:{//海洋
  166. isMap: true,// 是否是等经纬地图
  167. projection:'Lambert',// 投影
  168. latLon:{
  169. refLon0:110,refLat0:0,
  170. m0:274.983,n0:490.51666,
  171. refLat1:1.001,refLat2:25.0,
  172. lonr : 100,latr: 20,
  173. mr:171.983,nr:270.51666,
  174. },
  175. },
  176. '86st':{//单站
  177. isMap: false,
  178. },
  179. rainnest:{//雨涡
  180. isMap: false,
  181. },
  182.  
  183. },
  184. currentRegion:'hn',
  185. matchImgXY:()=>{},// 根据经纬度获取图像位置
  186. matchLoc:()=>{},// 获取经纬度的函数
  187. matchParam:'',// 调用上式的第二参数
  188. };
  189.  
  190.  
  191. //
  192. /**
  193. * unsafeWindow函数
  194. * selectProItem
  195. */
  196.  
  197. const utils = {
  198. changeRegion(region){
  199. helperConfig.currentRegion = region;
  200. return helperConfig.region[region].isMap;// 返回是否是地图
  201. },
  202. projection:{
  203. Mercator:{// 墨卡托投影
  204. calBasicInfo(lat1=0,lat2=0,n1=0,n2=0){
  205. /*参数lat 纬度, n坐标数值
  206. n0 赤道,d0放大系数
  207. */
  208. const y1 = Math.log(Math.tan(lat1)+1/Math.cos(lat1));
  209. const y2 = Math.log(Math.tan(lat2)+1/Math.cos(lat2));
  210. const n0 = (n1 - (y1/y2) * n2) / (1.0 - y1/y2);
  211. const d0 = y1/(n0 - n1);
  212. return {n0,d0};
  213. },
  214. calLatLon(mouseXY,dims){
  215. // console.log(dims);
  216. const n0 = dims.n0;
  217. const d0 = dims.d0;
  218. // console.log(Math.sinh((n0-mouseXY.y)*d0));
  219. const lat = Math.atan(Math.sinh((n0-mouseXY.y)*d0));
  220. ///---------------//
  221. const r = dims.xLeft;
  222. const o = dims.xRight;
  223. const s = (dims.lon1 - dims.lon0) / (o - r);
  224. const u = dims.lon0 + (mouseXY.x - r) * s;
  225. return {lat:lat*180/Math.PI,lon:u};
  226. },
  227. calImgLoc(latlon, dims={xLeft, xRight,lon1,lon0,n0,d0}){
  228. const n0 = dims.n0;
  229. const d0 = dims.d0;
  230. const sec = (x=0)=> 1/Math.cos(x);// 正割
  231.  
  232. const lon = latlon.lon;
  233. const r = dims.xLeft;
  234. const o = dims.xRight;
  235. const s = (dims.lon1 - dims.lon0) / (o - r);
  236. const x = (lon - dims.lon0)/s + r;
  237.  
  238. const phi = latlon.lat/180*Math.PI;
  239. const y = n0 - Math.log(Math.tan(phi) + sec(phi))/d0;
  240. const mouseXY = {x,y};
  241. return mouseXY;
  242. },
  243. },
  244. Equidistant:{//等经纬度
  245. calBasicInfo(){
  246. return helperConfig.region[helperConfig.currentRegion].latLon
  247. },
  248. calLatLon(mouseXY,dims){
  249. // console.log(dims);
  250. const r = dims.xLeft;
  251. const o = dims.xRight;
  252. const i = dims.yTop;
  253. const l = dims.yBottom;
  254. const s = (dims.lon1 - dims.lon0) / (o - r); // o - r 内框宽度 -> s = lon/height
  255. const d = (dims.lat1 - dims.lat0) / (i - l);// i - l 内框高度 -> d = lat/width
  256. const u = dims.lon0 + (mouseXY.x - r) * s;
  257. const m = dims.lat1 + (mouseXY.y - i) * d;
  258. return {lat:m,lon:u};
  259. },
  260. calImgLoc(latlon={lat,lon},dims){
  261. const r = dims.xLeft;
  262. const o = dims.xRight;
  263. const i = dims.yTop;
  264. const l = dims.yBottom;
  265. const s = (dims.lon1 - dims.lon0) / (o - r); // o - r 内框宽度 -> s = lon/height
  266. const d = (dims.lat1 - dims.lat0) / (i - l);// i - l 内框高度 -> d = lat/width
  267. const lat = latlon.lat;
  268. const lon = latlon.lon;
  269. const x = (lon - dims.lon0)/s + r;
  270. const y = (lat - dims.lat1)/d + i;
  271. const mouseXY = {x,y};
  272. return mouseXY;
  273. },
  274.  
  275. },
  276. Lambert:{// 兰伯特投影
  277. calBasicInfo({refLon0,refLat0,m0,n0, refLat1,refLat2,lonr,latr,mr,nr}){
  278. /*
  279. refLat0,refLon0,m0,n0 参考纬度、经度,屏幕X坐标,Y坐标;
  280. 屏幕坐标与实际坐标映射:
  281. x = (m-m0)*dx // dx为x方向比例系数
  282. y = (n0-n)*dy // dy为y方向比例系数,y方向屏幕坐标反向,所以取反
  283. refLat1,refLat2 2个平行纬度
  284. latr,lonr,mr,nr 选取的另外一个点的经纬度和屏幕坐标
  285.  
  286. phi 纬度,lambda经度
  287.  
  288. */
  289. const ang2rad = (x=0)=> Math.PI * x/180.0;
  290. const [phi0,phi1,phi2] = [ang2rad(refLat0),ang2rad(refLat1),ang2rad(refLat2)];
  291. //console.log(phi0,phi1,phi2);
  292. const lambda0 = ang2rad(refLon0);
  293. const [tan,cos,sin,pow,PI,ln] = [Math.tan,Math.cos,Math.sin,Math.pow,Math.PI,Math.log];
  294. const cot = (x=0)=> Math.cos(x)/Math.sin(x);// 余切
  295. const sec = (x=0)=> 1/Math.cos(x);// 正割
  296.  
  297. const n = ln(cos(phi1)*sec(phi2))/ln(tan(0.25*PI+0.5*phi2)*cot(0.25*PI+0.5*phi1));
  298. const F = (cos(phi1)*pow(tan(0.25*PI+0.5*phi1),n))/n;
  299. // n,F常量参数
  300. let rho = (phi=0)=>F*pow(cot(0.25*PI+0.5*phi),n); // rho变量
  301. let rho0 = rho(phi0);
  302. let fx = (phi,lambda)=>rho(phi)*sin(n*(lambda-lambda0));
  303. let fy = (phi,lambda)=>rho0 - rho(phi)*cos(n*(lambda-lambda0));
  304. const dx = fx(ang2rad(latr),ang2rad(lonr))/(mr-m0);
  305. const dy = fy(ang2rad(latr),ang2rad(lonr))/(n0-nr);
  306. // console.log(dx,dy);
  307. return {dx, dy, F, n, rho, rho0, lambda0, m0, n0};
  308. },
  309. calLatLon(mouseXY,{dx,dy,F,n,rho0, lambda0, m0,n0}){
  310. const x = (mouseXY.x - m0)*dx;
  311. const y = (n0-mouseXY.y)*dy;
  312.  
  313. let rho = (x,y)=>Math.sign(n)*Math.sqrt(Math.pow(x,2)+Math.pow((rho0-y),2));
  314.  
  315. let theta = (x,y)=>Math.atan(x/(rho0-y));
  316. let phi = (rho)=>2*Math.atan(Math.pow(F/rho,1/n))-0.5*Math.PI;
  317. let lambda = (theta)=>lambda0 + theta/n;
  318. const lat = phi(rho(x,y))*180/Math.PI;
  319. const lon = lambda(theta(x,y))*180/Math.PI;
  320. // console.log(mouseXY.x-m0,n0-mouseXY.y);
  321. //console.log(mouseXY.x - m0,n0-mouseXY.y);
  322. return {lat,lon};
  323. },
  324. calImgLoc(latlon,{dx, dy, rho=()=>{},rho0,m0,n0, lambda0}){
  325. const [cos,sin,PI] = [Math.cos,Math.sin,Math.PI];
  326. const phi = latlon.lat*PI/180;
  327. const lambda = latlon.lon*PI/180;
  328. const originX = (phi,lambda)=>rho(phi)*sin(n*(lambda-lambda0));
  329. const originY = (phi,lambda)=>rho0 - rho(phi)*cos(n*(lambda-lambda0));
  330. let mouseXY = {x:0, y:0};
  331. mouseXY.x = originX(phi,lambda)/dx + m0;
  332. mouseXY.y = n0 - originY(phi,lambda)/dy;
  333. return mouseXY;
  334. }
  335. }
  336.  
  337. },
  338. debounce:(fn, delay, scope)=>{
  339. let timer = null;
  340. // 返回函数对debounce作用域形成闭包
  341. return function () {
  342. // setTimeout()中用到函数环境总是window,故需要当前环境的副本;
  343. let context = scope || this, args = arguments;
  344. // 如果事件被触发,清除timer并重新开始计时
  345. clearTimeout(timer);
  346. timer = setTimeout(function () {
  347. fn.apply(context, args);
  348. }, delay);
  349. }
  350. },
  351. throttle(fn, threshold, scope) {
  352. let timer;
  353. let prev = Date.now();
  354. return function () {
  355. let context = scope || this, args = arguments;
  356. let now = Date.now();
  357. if (now - prev > threshold) {
  358. prev = now;
  359. fn.apply(context, args);
  360. }
  361. }
  362. },
  363. showSkewT(point={lat:21,lon:120},runtime='2019012018',fcHour='18'){
  364. const latRange = [point.lat-0.125*2,point.lat+0.125*2];
  365. const lonRange = [point.lon-0.125*2,point.lon+0.125*2];
  366. let fcTime = utils.getFcTime();
  367. const initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH');
  368. runtime = initTime.format('YYYYMMDDHH');
  369. fcHour = fcTime.fcHour;
  370. 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`);
  371. // https://www.tropicaltidbits.com/analysis/models/sounding/?model=gfs&runtime=2018122418&fh=18&domain=120,122.5,20,22.5&stationID=&tc=&mode=regular
  372. },
  373. showVertical(p0={lat:21,lon:120},p1={lat:23,lon:130},runtime='2019012018',fcHour='18'){
  374. let fcTime = utils.getFcTime();
  375. const initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH');
  376. runtime = initTime.format('YYYYMMDDHH');
  377. fcHour = fcTime.fcHour;
  378. let type = 'RH_and_Omega';//[FGEN,_%CE%B8%E2%82%91,_Omega,RH_and_Omega,Normal_Wind,In-Plane_Wind]
  379. 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=`);
  380. // 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=
  381. },
  382. showTimeSeries(point={lat:21,lon:120}){
  383. console.log(point);
  384. if(point.mouseXY){
  385. const elem = document.getElementById('map-pointer');
  386. elem.style.marginLeft = point.mouseXY.x + 29.31 + 'px';
  387. elem.style.marginTop = point.mouseXY.y - 33.066 + 'px';
  388. }
  389. const fcTime = utils.getFcTime();
  390. const initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH');
  391. const sT = initTime.format('YYYY-MM-DD%20HH:mm:ss');
  392. const eT = moment(initTime).add(10*24,'hours').format('YYYY-MM-DD%20HH:mm:ss');
  393. point.lat = utils.fix2Grid(point.lat);
  394. point.lon = utils.fix2Grid(point.lon);//取格点位置
  395. getTimeSeries(model='ecmwfthin',
  396. sT,eT,
  397. lon=point.lon,lat=point.lat,
  398. eles=['t2mm','mn2t','mx2t','u10m','v10m','tcco','lcco','tppm']);
  399. },
  400. fix2Grid(number,interval=0.125){
  401. let fixNum = Math.round(number/interval)*interval;
  402. return fixNum;
  403. },
  404. getFcTime(){
  405. const fcHour = typeof(unsafeWindow.forecastHour)==='string'?unsafeWindow.forecastHour:unsafeWindow.forecastHour[0];
  406. let idate = unsafeWindow.$settings.date;
  407. if(!idate.length) throw new Error('日期为空错误');
  408. if(idate[6]=='-') idate = idate.slice(0,5)+'0'+idate.slice(5);
  409. if(idate.length===9) idate = idate.slice(0,8)+'0'+idate.slice(8);
  410. const hr = unsafeWindow.$settings.HH;
  411. return {date:idate,hr,fcHour};//format->date:'2019-01-22;, hr:'12';fcHour:'000'
  412. },
  413. getTimeSeries(point={lat:21,lon:115},model='giftdaily',eles=[t2mm,tmax,tmin],start='2014-11-22%2000:00:00'){
  414.  
  415. //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
  416. },
  417. calWind(u10,v10){
  418. const iSpeed = Math.sqrt(Math.pow(u10,2)+Math.pow(v10,2));//风速
  419. const iR = Math.sign(v10)*Math.acos(u10/iSpeed);//标准坐标系弧度
  420. const arrowR = iR - Math.PI/2;//矢量箭头偏移弧度
  421. let northDir = -(iR + Math.PI - Math.PI/2);//与北向的角度差
  422. if(northDir<0){
  423. northDir = northDir + Math.PI*2;
  424. }
  425. const dir = northDir/Math.PI*180;
  426. if(dir>360) dir = dir - 360;
  427. return {speed:iSpeed,rotation:arrowR,northDir:northDir};
  428. },
  429. renderArrow(param, api) {
  430. let arrowSize = 10;
  431. var point = api.coord([
  432. api.value(2),//dims.timeIndex
  433. api.value(0)//5//api.value(dims.windSpeed)
  434. ]);
  435.  
  436. return {
  437. type: 'path',
  438. shape: {
  439. //pathData: 'M31 16l-15-15v9h-26v12h26v9z',
  440. pathData:'M250 0 L140 350 L250 250 L360 350 Z',
  441. x: -arrowSize / 2,
  442. y: -arrowSize / 2,
  443. width: arrowSize,
  444. height: arrowSize
  445. },
  446. rotation: api.value(1),//api.value(dims.R),//Math.PI / 8 * index;
  447. position: point,
  448. style: api.style({
  449. stroke: '#555',
  450. lineWidth: 1,
  451. fill:'green',
  452. })
  453. };
  454. },
  455. createElement(type='div',props={}){
  456. let newEle = document.createElement(type);
  457. for(let attr in props){
  458. newEle.setAttribute(attr,props[attr]);
  459. }
  460. return newEle;
  461. },
  462. getImgNow(imgSrc){
  463. // TODO 改进匹配规则
  464. let fcTime = utils.getFcTime();
  465. //const initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH');
  466. // runtime = initTime.format('YYYYMMDDHH');
  467. fcHour = fcTime.fcHour;
  468. let url = imgSrc||unsafeWindow.imgSrc[fcHour];
  469. if(!url) throw new Error('无法获取初始图像');
  470. // url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_000_2019012800.png';
  471. const reg = /(http:.*?\/)(\d{8})(.*?_)(\d+)_(\d+)(.*?$)/;
  472. const matchUrl = url.match(reg);
  473. /*0: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_030_2019012800.png"
  474. 1: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/"
  475. 2: "20190128"
  476. 3: "/ecmwffine_hn_mslp_"
  477. 4: "030"
  478. 5: "2019012800"
  479. 6: ".png"
  480. index: 0
  481. input: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_030_2019012800.png"
  482. length: 7 */
  483.  
  484. const base = [matchUrl[1],matchUrl[3],matchUrl[6]];
  485. const model = base[0].match(/znwp\/(.*?)\//)[1];
  486. // console.log(base[1]);
  487. const eleName = base[1].match(/_.*?_(.+?)_$/)[1];
  488. const date = matchUrl[2];
  489. const hour = matchUrl[4];
  490. const initDate = matchUrl[5];
  491.  
  492. const imgInfo = {
  493. url,
  494. date,
  495. base,
  496. hour,
  497. initDate,
  498. model,
  499. eleName,
  500. getUrl(date=date,hour=hour,initDate=initDate){
  501. return base[0]+date+base[1]+hour+'_'+initDate+base[2];
  502. }
  503. }
  504. return imgInfo;
  505. },
  506. matchImgPattern(pat={elem: "t2mm",elemCN: "2m气温",region: "cn",model: "ecmwffine_b",modelFileName:"ecmwffine"}){
  507. let base0 = `http://10.148.8.228/files_home/znwp/${pat.model}/${pat.region}/`;
  508. let base1 = `/${pat.modelFileName}_${pat.region}_${pat.elem}_`;
  509. return {
  510. // base:['http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/','/ecmwffine_hn_mslp_','.png'],
  511. base:[base0,base1,'.png'],
  512. };
  513. },
  514. getImgUrl(base,date,hour,initDate){
  515. return base[0]+date+base[1]+hour+'_'+initDate+base[2];
  516. },
  517. paddingInt(num, length=3) {
  518. //这里用slice和substr均可
  519. return (Array(length).join("0") + num).slice(-length);
  520. },
  521. getElemRelPos (e, t, n) {// e.target, e.clientX, e.clientY
  522. var a = e.getBoundingClientRect(),
  523. r = getComputedStyle(e);
  524. return {
  525. x: t - (a.left + parseFloat(r.paddingLeft) + parseFloat(r.borderLeftWidth)),
  526. y: n - (a.top + parseFloat(r.paddingTop) + parseFloat(r.borderTopWidth))
  527. };
  528. },
  529. };
  530.  
  531.  
  532. var NWP_init = function(){
  533. var fcHour = document.getElementById('forecast_hour');
  534. var iniTime = document.getElementById('create_day');
  535. var infoBar = document.getElementById('pic_info');
  536. var referNode = document.getElementById('to_contrast');
  537. var divTime = document.createElement('span');
  538. divTime.textContent = 'hello world';
  539. divTime.setAttribute('class', 'lcTime');
  540. divTime.style.position = 'relative';
  541. divTime.style.float = 'right';
  542. divTime.style.right = '120px';
  543. infoBar.insertBefore(divTime, referNode);
  544. // document.querySelector("#forecast_hours div").textContent = "日期";
  545.  
  546. // create an observer instance
  547. var UTC8 = new MutationObserver(function (mutations) {
  548. var dateString = iniTime.textContent.match(/(\d+).*?(\d+).*?(\d+).*?(\d+)/);
  549. var fcDate = [];
  550. fcDate[0] = Number(dateString[1]);
  551. fcDate[1] = Number(dateString[2]);
  552. fcDate[2] = Number(dateString[3]);
  553. fcDate[3] = Number(dateString[4]);
  554. fcDate[4] = Number(fcHour.textContent.match(/\d+/));
  555. fcDate[5] = new Date(fcDate[0], fcDate[1] - 1, fcDate[2], fcDate[3] + fcDate[4] + 8);
  556. var localTime = String(fcDate[5].getMonth() + 1) + '月' + fcDate[5].getDate() +
  557. '日' + fcDate[5].getHours() + '时 GMT+8';
  558. divTime.textContent = localTime;
  559. });
  560. // configuration of the observer:
  561. var config = {
  562. attributes: true,
  563. childList: true,
  564. characterData: true
  565. };
  566. UTC8.observe(fcHour, config);
  567. // later, you can stop observing
  568. //observer.disconnect();
  569. //
  570. //
  571. /////////////////////////////////////////////////////////////
  572.  
  573.  
  574. //
  575. ///
  576. ////////////////////修改时效列/////////////////////////////////////////
  577. var alterTimelist = function (mutations) {
  578. //alert(timeBar.length);
  579. if(userConfig.timelineMove){
  580. timeMoveOver();//是否启用滑动时间触发
  581. }
  582. if(!userConfig.alterDate) return; // 不修改则直接返回
  583. var dateString = iniTime.textContent.match(/(\d+).*?(\d+).*?(\d+).*?(\d+)/);
  584. var fcDate = [];
  585. fcDate[0] = Number(dateString[1]);
  586. fcDate[1] = Number(dateString[2]);
  587. fcDate[2] = Number(dateString[3]);
  588. fcDate[3] = Number(dateString[4]);
  589. for (let i = 0; i < timeBar.length; i++) {
  590. let oValue = timeBar[i].value;
  591. fcDate[4] = Number(timeBar[i].value);
  592.  
  593. fcDate[5] = new Date(fcDate[0], fcDate[1] - 1, fcDate[2], fcDate[3] + fcDate[4] + 8);
  594.  
  595. let iday = String(fcDate[5].getDate());
  596. iday = Array(2 > iday.length ? 2 - iday.length + 1 || 0 : 0).join(0) + iday;
  597.  
  598. let ihour = String(fcDate[5].getHours());
  599. ihour = Array(2 > ihour.length ? 2 - ihour.length + 1 || 0 : 0).join(0) + ihour;
  600.  
  601. let localTime = iday+' ' + ihour+' ;';
  602. let styleText = '#'+timeBar[i].getAttribute("id")+':before{white-space:pre;content: " '+localTime+' "}';
  603. GM_addStyle(styleText);
  604.  
  605. switch(fcDate[5].getHours()){
  606. // case 5:
  607. // timeBar[i].style.cssText = "border-left:2px solid #9B30FF";
  608. // break;
  609. // case 14:
  610. // timeBar[i].style.cssText = "border-left:2px solid #EE3B3B";
  611. // break;
  612. case 20:
  613. timeBar[i].style.cssText = "border-bottom:1px dotted #8E8E8E;border-left:2px solid #ffffff;";
  614. break;
  615. default:
  616. timeBar[i].style.cssText = "border-left:2px solid #ffffff;";
  617. }
  618. }
  619. };
  620. /////////////////////////////////////////////////////////////
  621. ///
  622. var selectObserver = new MutationObserver(alterTimelist);
  623. // configuration of the observer:
  624. var timeBar = document.querySelector("#forecast_hours select");
  625. var config2 = {
  626. attributes: false,
  627. childList: true,
  628. characterData: false,
  629. };
  630. selectObserver.observe(timeBar, config2);
  631. GM_addStyle("#forecast_hours option{width: 50px!important; overflow: hidden!important;}");
  632.  
  633. //-----------------------------------------------------------------------------//
  634. let imgObserver = new MutationObserver(fitImgLoc);
  635. imgObserver.observe(timeBar, config2);
  636. //-----------------------------------绑定坐标-----------------------------------//
  637. function fitImgLoc(){ // 绑定img包含的div元素
  638. // console.log(unsafeWindow._region);
  639. // TODO
  640. const isMap = utils.changeRegion(unsafeWindow._region); // 改变地图;
  641. if(!isMap) return; // 如果不是地图直接返回
  642. /////TODO 判断地图逻辑分离
  643. const currMap = helperConfig.region[helperConfig.currentRegion];
  644. const currProjection = currMap.projection;
  645. //let matchLoc = ({});
  646. //let param = ({});
  647. switch (currProjection) {
  648. case 'Mercator':{
  649. const dims = currMap.latLon;
  650. const lat1 = Math.PI/180.0 * dims.lat0;
  651. const lat2 = Math.PI/180.0 *dims.lat1;
  652. const n1 = dims.yBottom;
  653. const n2 = dims.yTop;
  654. // console.log(dims);
  655. const param1 = utils.projection.Mercator.calBasicInfo(
  656. lat1, lat2, n1, n2
  657. );
  658. helperConfig.matchParam = {...param1,
  659. xRight:dims.xRight,
  660. xLeft:dims.xLeft,
  661. lon0:dims.lon0,
  662. lon1:dims.lon1,
  663. };
  664. // console.log(helperConfig.matchParam);
  665. helperConfig.matchLoc = utils.projection.Mercator.calLatLon;
  666. helperConfig.matchImgXY = utils.projection.Mercator.calImgLoc;
  667. break;
  668. }
  669. case 'Lambert':{
  670. const LamDim = currMap.latLon;
  671. /* const LamParam1 =
  672. [ LamDim.refLon0, LamDim.refLat0, LamDim.m0, LamDim.n0,
  673. LamDim.refLat1, LamDim.refLat2,
  674. LamDim.lonr, LamDim.latr, LamDim.mr, LamDim.nr]; */
  675. const LamParam2 = utils.projection.Lambert.calBasicInfo(LamDim);
  676. // console.log(LamParam2);
  677. helperConfig.matchParam = LamParam2;
  678. helperConfig.matchLoc = utils.projection.Lambert.calLatLon;
  679. helperConfig.matchImgXY = utils.projection.Lambert.calImgLoc;
  680. break;
  681. }
  682. default:
  683. helperConfig.matchParam = currMap.latLon;
  684. helperConfig.matchLoc = utils.projection.Equidistant.calLatLon;
  685. helperConfig.matchImgXY = utils.projection.Equidistant.calImgLoc;
  686. break;
  687. }
  688.  
  689. //let wrapDiv = document.querySelector('#pic_frame div');
  690. let wrapDiv = document.querySelector('#pic_frame');
  691. // document.addEventListener('keyup', console.log(elemsConfig.fixPoint));
  692. if(wrapDiv){
  693. wrapDiv.addEventListener('mousemove', utils.debounce(getMouseLatLon,100));
  694. // wrapDiv.addEventListener('mousemove', getMouseLatLon);
  695. // console.log(wrapDiv.clientWidth);// offsetWidth , clientWidth, scrollWidth
  696. }
  697. }
  698.  
  699. function getElemRelPos (e, t, n) {// e.target, e.clientX, e.clientY
  700. var a = e.getBoundingClientRect(),
  701. r = getComputedStyle(e);
  702. /**
  703. * e.getBoundingClientRect()
  704. * bottom: 916.1999969482422
  705. height: 716 ​
  706. left: 686.9000244140625 ​
  707. right: 1565.1333618164062 ​
  708. top: 200.1999969482422 ​
  709. width: 878.2333374023438 ​
  710. x: 686.9000244140625 ​
  711. y: 200.1999969482422
  712. borderLeftWidth: "1px"
  713. borderTopWidth: "1px"
  714. paddingLeft: "0px"
  715. paddingTop: "0px"
  716. */
  717. return {
  718. x: t - (a.left + parseFloat(r.paddingLeft) + parseFloat(r.borderLeftWidth)),
  719. y: n - (a.top + parseFloat(r.paddingTop) + parseFloat(r.borderTopWidth))
  720. };
  721. }
  722.  
  723. function getMouseLatLon(event){
  724. // console.log(event);
  725. // console.log(event.target);
  726. // let target = event.target;//
  727. let target = document.querySelector('#pic_frame img[style~="inline;"]');//
  728. // console.log(target);
  729. const mouseXY = getElemRelPos(target, event.clientX, event.clientY); // 相对图像的像素位置{x,y}
  730. const loc = helperConfig.matchLoc(mouseXY, helperConfig.matchParam);
  731. elemsConfig.latLonInput.lat.innerHTML = loc.lat; // mouseXY.y
  732. elemsConfig.latLonInput.lon.innerHTML = loc.lon; // mouseXY.x
  733. // elemsConfig.latLonInput.lat.innerHTML = mouseXY.y
  734. // elemsConfig.latLonInput.lon.innerHTML = mouseXY.x
  735. elemsConfig.fixPoint = {lat: loc.lat, lon: loc.lon,mouseXY};
  736. return {lat: loc.lat, lon: loc.lon,mouseXY};
  737. }
  738. //------------------------------24小时跳跃-------------------------------------//
  739. const timeJump = function(){
  740. //var hourBar = document.getElementById('from_hour');float-l
  741. var jumpParent = document.querySelector('.float-l');
  742. var pre24 = document.createElement('button');
  743. pre24.addEventListener("click", function(){timeTrigger(-24);});
  744. pre24.textContent = "-24";
  745. jumpParent.appendChild(pre24);
  746.  
  747. var next24 = document.createElement('button');
  748. next24.addEventListener("click", function(){timeTrigger(24);});
  749. next24.textContent = "+24";
  750. jumpParent.appendChild(next24);
  751.  
  752. var timeTrigger = function(timer){
  753. let selectedVal = timeBar[timeBar.selectedIndex].getAttribute("data-hour");
  754. let nextVal = String(Number(selectedVal) + timer);
  755. var posi = 3;
  756. nextVal = Array(posi > nextVal.length ? posi - nextVal.length + 1 || 0 : 0).join(0) + nextVal;
  757. let nextopt = timeBar.querySelector("#option_"+nextVal);
  758. //alert(nextopt);
  759. if(!nextopt) return;
  760. timeBar[timeBar.selectedIndex].selected = false;
  761. nextopt.selected = true;
  762.  
  763. // var changeEvt = document.createEvent('HTMLEvents');
  764. // changeEvt.initEvent('change',true,true);
  765. // timeBar.dispatchEvent(changeEvt);
  766. var changeEvt = new Event('change', { 'bubbles': true });
  767. timeBar.dispatchEvent(changeEvt);
  768. };
  769. };
  770.  
  771. timeJump();
  772. /////切换时效
  773.  
  774. function switchDate(){
  775. userConfig.alterDate = !userConfig.alterDate;
  776. if(userConfig.alterDate){
  777. switchDateBtn.textContent = "切换成时效";
  778. alterTimelist();
  779. }
  780. else{
  781. switchDateBtn.textContent = "切换成日期";
  782. for(let ele of timeBar){
  783. ele.style.cssText = '';
  784. let styleText = '#'+ele.getAttribute("id")+':before{white-space:pre;content: ""}';
  785. GM_addStyle(styleText);
  786. }
  787. }
  788. }
  789.  
  790. var switchParent = document.querySelector('.float-l');
  791. let switchDateBtn = document.createElement('button');
  792. switchDateBtn.addEventListener("click", switchDate);
  793. switchDateBtn.textContent = "切换成日期";
  794. switchParent.appendChild(switchDateBtn);
  795. /////end 切换时效 /////
  796.  
  797. //设置鼠标事件//
  798. document.onkeydown = function(evt){
  799. if(evt.key=='x'&&!evt.ctrlKey){
  800. console.log(utils.showSkewT(elemsConfig.fixPoint));
  801. }
  802. else if(evt.key=='x'&&evt.ctrlKey){
  803. if(!elemsConfig.point01){
  804. elemsConfig.point01 = Object.assign({},elemsConfig.fixPoint);
  805. }
  806. else{
  807. elemsConfig.point02 = Object.assign({},elemsConfig.fixPoint);
  808. console.log(elemsConfig.point01,elemsConfig.point02);
  809. utils.showVertical(elemsConfig.point01,elemsConfig.point02);
  810. elemsConfig.point01 = null;
  811. elemsConfig.point02 = null;
  812. }
  813. }
  814. if(evt.key=='c'&&!evt.ctrlKey&&!evt.altKey){
  815. utils.showTimeSeries(elemsConfig.fixPoint);
  816. }
  817. }
  818. //设置鼠标事件//
  819.  
  820. /**
  821. * 时间鼠标滑过
  822. */
  823. function timeMoveOver(){
  824. // var changeEvent = document.createEvent('HTMLEvents');
  825. // changeEvent.initEvent("change", true, true);
  826. var changeEvent = new Event('change', { 'bubbles': true });
  827. var clickOpt = (evt)=>{
  828. console.log(evt.target);
  829. timeBar[timeBar.selectedIndex].selected = false;
  830. evt.target.selected = true;
  831. timeBar.dispatchEvent(changeEvent);
  832. }
  833. var timeBar = document.querySelector("#forecast_hours select");
  834. var opts = document.querySelectorAll('#forecast_hours option');
  835. opts.forEach(item => {
  836. item.addEventListener('mouseover',clickOpt);
  837. });
  838. }
  839. };
  840.  
  841. function showNotification(text='测试'){
  842. // 先检查浏览器是否支持
  843. if (!("Notification" in window)) {
  844. alert("This browser does not support desktop notification");
  845. }
  846.  
  847. // 检查用户是否同意接受通知
  848. else if (Notification.permission === "granted") {
  849. // If it's okay let's create a notification
  850. var notification = new Notification(text);
  851. }
  852.  
  853. // 否则我们需要向用户获取权限
  854. else if (Notification.permission !== 'denied') {
  855. Notification.requestPermission(function (permission) {
  856. // 如果用户同意,就可以向他们发送通知
  857. if (permission === "granted") {
  858. var notification = new Notification(text);
  859. }
  860. });
  861. }
  862. }
  863.  
  864. function toggleSelectMode(){
  865. let mapPointer = document.getElementById('map-pointer');
  866. let bodyDOM = document.getElementsByTagName('body')[0];
  867. let linePanel = document.getElementById('line_panel');
  868. let selectModePanel = document.getElementById('select-mode-panel');
  869. if(bodyDOM.classList.contains("full-screen-mode")){
  870. bodyDOM.classList.remove("full-screen-mode");
  871. linePanel.style.display='none';
  872. selectModePanel.style.display='none';
  873. mapPointer.classList.add("display-none");
  874. return;
  875. }else{
  876. bodyDOM.classList.add("full-screen-mode");
  877. let contentWidth = window.innerWidth - document.body.clientWidth - 40;
  878. if(contentWidth<200){
  879. linePanel.style.width='220px';
  880. }else{
  881. linePanel.style.width=contentWidth+'px';
  882. }
  883. linePanel.style.display='block';
  884. selectModePanel.style.display='block';
  885. mapPointer.classList.add("display-none");
  886. mapPointer.classList.remove("display-none");
  887. }
  888. const imgXY = latlon2XY({lon:113.375,lat:23.125});
  889.  
  890. utils.showTimeSeries({lon:'113.375',lat:'23.125',mouseXY:imgXY});
  891. }
  892. // 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
  893. 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']){
  894.  
  895. const url = `http://172.22.1.175/di/grid.action?userId=sqxt&pwd=shengqxt123&dataFormat=json&interfaceId=intGetMultElesDataTimeSerial&modelid=${model}&element=${eles.join('%20')}&level=0&starttime=${sT}&endtime=${eT}&lon=${lon}&lat=${lat}`;
  896. console.log('获取数据');
  897. showNotification('正在从数据中心获取数据');
  898. console.log(url);
  899. GM_xmlhttpRequest({ //获取时间序列
  900. method : 'GET',
  901. synchronous : false,
  902. url : url,
  903. onload : function (reDetails) {
  904. if (reDetails.status !== 200&&reDetails.status !== 304){
  905. console.error('获取URL错误');
  906. showNotification('数据中心数据获取异常');
  907. return;
  908. }
  909. getLocation(lat,lon);
  910. const data = JSON.parse(reDetails.responseText);
  911. //console.log(data.DATA);
  912. if(data.DATA.length==0){
  913. console.error('此时次数据为空,请等待更新');
  914. showNotification('此时次数据为空,请等待更新');
  915. return;
  916. }
  917. let series = decodeSeries(data.DATA, 241);
  918. series[1] = mixUndefined(series[0].map(v=>v[1]),series[1]);
  919. series[2] = mixUndefined(series[0].map(v=>v[1]),series[2]);
  920. //console.log(series[1]);
  921. drawLine(series);
  922. }
  923. });
  924. }
  925.  
  926. /**
  927. * 从百度接口获取地址
  928. * @param {Number} lat 纬度
  929. * @param {Number} lon 经度
  930. */
  931. function getLocation(lat,lon){
  932. let ak = 'kMW5fXfhhsMat6Ud9jYPqnxCRQGbl2eV';
  933. let url = `http://api.map.baidu.com/geocoder/v2/?callback=renderReverse&location=${lat},${lon}&output=json&pois=1&ak=${ak}`;
  934. let latlonSpan = document.querySelectorAll('#line_info > span');
  935. latlonSpan[0].innerHTML = lat;
  936. latlonSpan[1].innerHTML = lon;
  937. latlonSpan[2].innerHTML = '';
  938. console.log(url);
  939. GM_xmlhttpRequest({ //获取时间序列
  940. method : 'GET',
  941. synchronous : false,
  942. url : url,
  943. onload : function (reDetails) {
  944. if (reDetails.status !== 200&&reDetails.status !== 304){
  945. console.error('获取百度地址异常');
  946. //showNotification('数据中心数据获取异常');
  947. return;
  948. }
  949. let raw = reDetails.responseText.replace('renderReverse&&renderReverse(','').slice(0,-1);
  950. const addreJSON = JSON.parse(raw);
  951. if(addreJSON.result.formatted_address==''){
  952. return;
  953. }else{
  954. latlonSpan[2].innerHTML = '&nbsp; 地址: '+ addreJSON.result.formatted_address;
  955. }
  956. //console.log(data);
  957. }
  958. });
  959. }
  960.  
  961. function mixUndefined(index,data){
  962. let newData = [];
  963. for(let i of index){
  964. let value = data.find(v=>v[1]==i);
  965. if(value===undefined){
  966. newData.push([undefined,i]);
  967. }else{
  968. newData.push([value[0],i]);
  969. }
  970. }
  971. return newData
  972. }
  973.  
  974. /**解析数据 */
  975. function decodeSeries(data=[], len=241){
  976. if(!data.length) return [];
  977. let splitData = [];
  978. let eles = data.length/len;//元素个数
  979. for(let ie=0;ie<eles;ie++){//看有几个要素
  980. splitData.push(data.slice(ie*len,(ie+1)*len));
  981. }
  982. splitData = splitData.map(data=>data.map((v,i)=>[Number(v),i]).filter(v=>v[0]>-999));//分离出[数值,时效]//-999.900024
  983. // console.log(splitData);
  984. return splitData;
  985. }
  986.  
  987. function drawLine(series=[]){
  988. // getTimeSeries();
  989. console.log('绘图');
  990. if(series.length==0) return console.log('空数据');
  991. const tempChart = echarts.init(document.getElementById('show-temp'));
  992. let fcTime = utils.getFcTime();
  993. let initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH').add(8,'hours');//北京时
  994. let temp = series[0].map(v=>(v[0]-273.15).toFixed(2));
  995. let mn2t = series[1].map(v=>{
  996. if(v[0]===undefined){
  997. return undefined;
  998. }else{
  999. return (v[0]-273.15).toFixed(2);
  1000. }
  1001. });
  1002. let mx2t = series[2].map(v=>{
  1003. if(v[0]===undefined){
  1004. return undefined;
  1005. }else{
  1006. return (v[0]-273.15).toFixed(2);
  1007. }
  1008. });
  1009. let xTime = series[0].map(v=>{
  1010. const hour = v[1];
  1011. return moment(initTime).add(hour,'hours').format('DD-HH');
  1012. });
  1013. // console.log(mn2t);
  1014. // 指定图表的配置项和数据
  1015. const OptionTemp = {
  1016. /* title: {
  1017. text: 'Temp.',
  1018. // left: 'center'
  1019. }, */
  1020. tooltip: {
  1021. trigger: 'axis',
  1022. },
  1023. toolbox: {
  1024. // y: '30px',
  1025. feature: {
  1026. dataZoom: {
  1027. yAxisIndex: 'none'
  1028. },
  1029. restore: {},
  1030. //saveAsImage: {}
  1031. }
  1032. },
  1033. grid: {top:10,bottom:45,right:11,left:25},
  1034. dataZoom: [
  1035. {
  1036. show: true,
  1037. realtime: true,
  1038. start: 0,
  1039. end: 80,
  1040. // handleSize: '50%',
  1041. height: '20',
  1042. bottom: '0',
  1043. },
  1044. {
  1045. type: 'inside',
  1046. realtime: true,
  1047. start: 0,
  1048. end: 80,
  1049. }
  1050. ],
  1051. legend: {
  1052. y: '-5',
  1053. data:['Temp','Pre6min','Pre6max']
  1054. },
  1055. xAxis: {
  1056. type : 'category',
  1057. data: xTime,
  1058. // boundaryGap : false,
  1059. //splitArea : {show : true},
  1060. splitLine:{show: true},
  1061. axisLine: {onZero: true},
  1062. },
  1063. yAxis: [{
  1064. name:'℃',
  1065. type: 'value',
  1066. scale:true,
  1067. axisLabel: {
  1068. formatter: '{value}'
  1069. },
  1070. },
  1071. ],
  1072. series: [
  1073. {
  1074. name: 'Temp',
  1075. smooth: true,
  1076. //symbol: 'triangle',
  1077. //symbolSize: 5,
  1078. lineStyle: {normal: {color: 'green',width: 2,}},//type: 'dashed'
  1079. //itemStyle: {normal: {borderWidth: 1,borderColor: 'green',color: 'green'}},
  1080. type: 'line',
  1081. data: temp,
  1082. },
  1083. {
  1084. name: 'Pre6min',
  1085. smooth: true,
  1086. connectNulls: true,
  1087. symbolSize: 0,
  1088. lineStyle: {normal: {color: 'blue',width: 1,type: 'dashed'}},//type: 'dashed'
  1089. type: 'line',
  1090. data: mn2t,
  1091. },
  1092. {
  1093. name: 'Pre6max',
  1094. smooth: true,
  1095. connectNulls: true,
  1096. symbolSize: 0,
  1097. lineStyle: {normal: {color: 'red',width: 1,type: 'dashed'}},//type: 'dashed'
  1098. type: 'line',
  1099. data: mx2t,
  1100. },
  1101. ]
  1102. };
  1103. // 使用刚指定的配置项和数据显示图表。
  1104. tempChart.setOption(OptionTemp);
  1105. /**
  1106. * 风
  1107. */
  1108. const windChart = echarts.init(document.getElementById('show-wind'));
  1109. let wind = series[3].map((v,i)=>utils.calWind(v[0],series[4][i][0]));
  1110. let speed = wind.map(v=>v.speed);
  1111. let rotation = wind.map(v=>v.rotation);
  1112. let windData = speed.map((v,i)=>[v,rotation[i],i]);
  1113. let dims = {
  1114. speed:0,
  1115. rotation:1,
  1116. timeIndex:2,
  1117. }
  1118. const optionWind = {
  1119. /* title: {
  1120. text: 'Wind',
  1121. }, */
  1122. tooltip: {
  1123. trigger: 'axis',
  1124. },
  1125. toolbox: {
  1126. feature: {
  1127. dataZoom: {
  1128. yAxisIndex: 'none'
  1129. },
  1130. // restore: {},
  1131. //saveAsImage: {}
  1132. }
  1133. },
  1134. grid: {show:true,top:10,bottom:45,right:11,left:25},
  1135. dataZoom: [
  1136. {
  1137. show: true,
  1138. realtime: true,
  1139. start: 0,
  1140. end: 80,
  1141. // handleSize: '50%',
  1142. height: '20',
  1143. bottom: '0',
  1144. },
  1145. {
  1146. type: 'inside',
  1147. realtime: true,
  1148. start: 0,
  1149. end: 80,
  1150. }
  1151. ],
  1152. legend: {
  1153. y: '-5',
  1154. data:['w10m'],
  1155. },
  1156. xAxis: {
  1157. type : 'category',
  1158. data: xTime,
  1159. splitLine:{show: true},
  1160. axisLine: {onZero: true},
  1161. },
  1162. yAxis: {
  1163. name:'m/s',
  1164. type: 'value',
  1165. axisLabel: {
  1166. formatter: '{value}'
  1167. },
  1168. },
  1169. series: [
  1170. {
  1171. name: 'w10m',
  1172. type: 'line',
  1173. smooth: true,
  1174. lineStyle: {normal: {width: 2,}},//type: 'dashed'
  1175. itemStyle: {normal: {borderWidth: 1,borderColor: 'black',color: 'black'}},
  1176. data: speed.map(v=>v.toFixed(1)),
  1177. },
  1178. {
  1179. type: 'custom',
  1180. name:'dir',
  1181. renderItem: utils.renderArrow,
  1182. encode: {
  1183. x: dims.timeIndex,
  1184. y: dims.speed,
  1185. },
  1186. data: windData,
  1187. z: 10
  1188. },
  1189. ]
  1190. };
  1191. windChart.setOption(optionWind);
  1192. /**
  1193. * 云
  1194. */
  1195. const cloudChart = echarts.init(document.getElementById('show-cloud'));
  1196. let totalCloud = series[5].map(v=>v[0]*100);
  1197. let lowCloud = series[6].map(v=>v[0]*100);
  1198. const optionCloud = {
  1199. tooltip: {
  1200. trigger: 'axis',
  1201. axisPointer: {
  1202. type: 'shadow'
  1203. }
  1204. },
  1205. toolbox: {
  1206. feature: {
  1207. dataZoom: {
  1208. yAxisIndex: 'none'
  1209. },
  1210. // restore: {},//saveAsImage: {}
  1211. }
  1212. },
  1213. grid: {show:true,top:10,bottom:45,right:11,left:30},
  1214. dataZoom: [
  1215. {
  1216. show: true,
  1217. realtime: true,
  1218. start: 0,
  1219. end: 80,
  1220. height: '20',
  1221. bottom: '0',
  1222. },
  1223. {
  1224. type: 'inside',
  1225. realtime: true,
  1226. start: 0,
  1227. end: 80,
  1228. }
  1229. ],
  1230. legend: {
  1231. y: '-5',
  1232. data:['中高云','低云'],
  1233. },
  1234. xAxis: {
  1235. type : 'category',
  1236. data: xTime,
  1237. splitLine:{show: true},
  1238. axisLine: {onZero: true},
  1239. },
  1240. yAxis: {
  1241. name:'%',
  1242. type: 'value',
  1243. axisLabel: {
  1244. formatter: '{value}'
  1245. },
  1246. },
  1247. series: [
  1248. {
  1249. name: '低云',
  1250. type: 'bar',
  1251. stack: '云量',
  1252. data: lowCloud.map(v=>v.toFixed(1)),
  1253. },
  1254. {
  1255. name: '中高云',
  1256. type: 'bar',
  1257. stack: '云量',
  1258. data: totalCloud.map((v,i)=>(v-lowCloud[i]).toFixed(1)),
  1259. },
  1260. ]
  1261. };
  1262. cloudChart.setOption(optionCloud);
  1263. /**
  1264. * 降水
  1265. */
  1266. const preChart = echarts.init(document.getElementById('show-pre'));
  1267. let preSeri = series[7];
  1268. let precipitaion = preSeri.map((v,i)=>{
  1269. let pre = i===0? v[0]: v[0] - preSeri[i-1][0]; //(v[0]*1000).toFixed(1)
  1270. return (pre*1000).toFixed(1);
  1271. }
  1272. );
  1273. //let lowCloud = series[6].map(v=>v[0]);
  1274. const optionPre = {
  1275. tooltip: {
  1276. trigger: 'axis',
  1277. axisPointer : { // 坐标轴指示器,坐标轴触发有效
  1278. type : 'shadow' // 默认为直线,可选为:'line' | 'shadow'
  1279. }
  1280. },
  1281. toolbox: {
  1282. feature: {
  1283. dataZoom: {
  1284. yAxisIndex: 'none'
  1285. },
  1286. }
  1287. },
  1288. grid: {show:true,top:10,bottom:45,right:11,left:30},
  1289. dataZoom: [
  1290. {
  1291. show: true,
  1292. realtime: true,
  1293. start: 0,
  1294. end: 80,
  1295. height: '20',
  1296. bottom: '0',
  1297. },
  1298. {
  1299. type: 'inside',
  1300. realtime: true,
  1301. start: 0,
  1302. end: 80,
  1303. }
  1304. ],
  1305. legend: {
  1306. y: '-5',
  1307. data:['Precipitaion'],
  1308. },
  1309. xAxis: {
  1310. type : 'category',
  1311. data: xTime,
  1312. splitLine:{show: true},
  1313. axisLine: {onZero: true},
  1314. },
  1315. yAxis: {
  1316. name:'mm',
  1317. type: 'value',
  1318. axisLabel: {
  1319. formatter: '{value}'
  1320. },
  1321. },
  1322. series: [
  1323. {
  1324. name: 'Precipitaion',
  1325. type: 'bar',
  1326. stack: 'Rain',
  1327. data: precipitaion,
  1328. },
  1329. ]
  1330. };
  1331. preChart.setOption(optionPre);
  1332. }
  1333.  
  1334. function createPanel(){
  1335. //-创建面板-//
  1336. const panelWrap = document.createElement("div");
  1337. panelWrap.setAttribute("id","helper_panel");
  1338. panelWrap.setAttribute("class","top_panel show_panel");
  1339. panelWrap.innerHTML = '多图模式: ';
  1340. /* 设置添加对比模式*/
  1341. const compareMode = document.createElement('div');
  1342. compareMode.innerHTML = `<span class="panel-button" id="open-compare">多起报</span>`;
  1343. panelWrap.appendChild(compareMode);
  1344.  
  1345. /* 设置添加多时效模式*/
  1346. const mvTime = document.createElement('div');
  1347. mvTime.innerHTML = `<span class="panel-button" id="open-mvTime">多时效</span>`;
  1348. panelWrap.appendChild(mvTime);
  1349. /* 设置添加多模式
  1350. const mvModel = document.createElement('div');
  1351. mvModel.innerHTML = `<span class="panel-button" id="open-mvModel">多模式</span>`;
  1352. panelWrap.appendChild(mvModel);*/
  1353. /* 设置添加多图模式
  1354. const mvElem = document.createElement('div');
  1355. mvElem.innerHTML = `<span class="panel-button" id="open-mv">多要素</span>`;
  1356. panelWrap.appendChild(mvElem);*/
  1357. /* 设置添加点选模式*/
  1358. const panelWrap2 = utils.createElement('div',{id:'edit-helper-panel',class:'top_panel show_panel'});
  1359. const pointMode = document.createElement('div');
  1360. pointMode.innerHTML = `<span class="panel-button">点选模式</span>`;
  1361. panelWrap2.appendChild(pointMode);
  1362. pointMode.addEventListener('click', ()=>{toggleSelectMode()});
  1363.  
  1364. // pointMode.addEventListener('click', ()=>toggleSelectMode());
  1365. /* 设置Lat lon面板*/
  1366. // const latLonWarp = document.createElement("span");
  1367. // latLonWarp.setAttribute("id","helper_latLon");
  1368. // latLonWarp.setAttribute("class","show_latLon");
  1369. // latLonWarp.innerHTML = '<span>Lat <span class="fixLoc" id="helper_lat"></span> Lon <span class="fixLoc" id="helper_lon"></span></span>';
  1370. // panelWrap2.appendChild(latLonWarp);
  1371. //
  1372. const ibody = document.getElementsByTagName("body")[0];
  1373. ibody.appendChild(panelWrap);
  1374. ibody.appendChild(panelWrap2);
  1375. //-------//
  1376. //-注册到全局变量-//
  1377. elemsConfig.latLonInput = {
  1378. lat:document.getElementById('helper_lat'),
  1379. lon:document.getElementById('helper_lon'),
  1380. };
  1381. }
  1382. // 添加面板样式
  1383. function createTlinePanel(){
  1384. //-创建面板-//
  1385. const fragment = document.createDocumentFragment();
  1386. const panelWrap = document.createElement("div");
  1387. panelWrap.setAttribute("id","line_panel");
  1388. panelWrap.setAttribute("class","show_panel");
  1389. panelWrap.style.display='none';
  1390.  
  1391. const infoWrap = document.createElement('div');
  1392. infoWrap.setAttribute("id","line_info");
  1393. infoWrap.innerHTML = 'EC模式预报<br>纬度:<span></span> 经度:<span></span><span></span>';
  1394. panelWrap.appendChild(infoWrap);
  1395. /* 设置添加temp*/
  1396. const tempWrap = document.createElement('div');
  1397. tempWrap.innerHTML = `温度<div id="show-temp"></div>`;
  1398. panelWrap.appendChild(tempWrap);
  1399.  
  1400. const windWrap = document.createElement('div');
  1401. windWrap.innerHTML = `风MSLP<div id="show-wind"></div>`;
  1402. panelWrap.appendChild(windWrap);
  1403.  
  1404. const preWrap = document.createElement('div');
  1405. preWrap.innerHTML = `降水<div id="show-pre"></div>`;
  1406. panelWrap.appendChild(preWrap);
  1407.  
  1408. const cloudWrap = document.createElement('div');
  1409. cloudWrap.innerHTML = `云量<div id="show-cloud"></div>`;
  1410. panelWrap.appendChild(cloudWrap);
  1411. fragment.appendChild(panelWrap);
  1412. const ibody = document.getElementsByTagName("body")[0];
  1413. ibody.appendChild(fragment);
  1414. //-------//
  1415. }
  1416.  
  1417. function createSelectModePanel(){
  1418. //-创建面板-//
  1419. const fragment = document.createDocumentFragment();
  1420. const panelWrap = document.createElement("div");
  1421. panelWrap.setAttribute("id","select-mode-panel");
  1422. panelWrap.setAttribute("class","show_panel");
  1423. panelWrap.style.display='none';
  1424. panelWrap.innerHTML = `快捷键[C] -> 单点时间序列`;
  1425. /*<br>
  1426. [X] -> 垂直探空; 两次[Ctrl+X] -> 垂直剖面
  1427. */
  1428. fragment.appendChild(panelWrap);
  1429. const ibody = document.getElementsByTagName("body")[0];
  1430. ibody.appendChild(fragment);
  1431. /* 设置Lat lon面板*/
  1432. const latLonWarp = document.createElement("div");
  1433. latLonWarp.setAttribute("id","helper_latLon");
  1434. latLonWarp.setAttribute("class","show_latLon");
  1435. latLonWarp.innerHTML = '<span>Lat <span class="fixLoc" id="helper_lat"></span> Lon <span class="fixLoc" id="helper_lon"></span></span>';
  1436. panelWrap.appendChild(latLonWarp);
  1437. //-------//
  1438. }
  1439.  
  1440.  
  1441. //console.log(compareMode.imgDOM);
  1442. // compareMode.forward();
  1443.  
  1444. GM_addStyle(`
  1445. .show_panel{
  1446. z-index:11;
  1447. font-size: 18px;
  1448. }
  1449. .top_panel{
  1450. background-color: rgb(45,53,63);
  1451. border-bottom: 0px solid rgb(20,20,20); padding:5px;
  1452. border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28);
  1453. box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95);
  1454. color: white;
  1455. }
  1456. #helper_panel{
  1457. position:absolute;
  1458. top:5px;
  1459. left:740px;
  1460. }
  1461. #edit-helper-panel{
  1462. position:absolute;
  1463. top:45px;
  1464. left:740px;
  1465. }
  1466. .panel-button{
  1467. cursor:pointer;
  1468. }
  1469. #select-mode-panel{
  1470. position:absolute; top:5px;left:370px;
  1471. background-color: rgb(45,53,63);
  1472. border-bottom: 0px solid rgb(20,20,20); padding:5px;
  1473. border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28);
  1474. box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95);
  1475. color: white;}
  1476. `);
  1477. /**暂时隐藏按钮 */
  1478. GM_addStyle(`#helper_latLon .fixLoc{display:inline-block;width:55px;height:15px;overflow:hidden;}`);
  1479. //GM_addStyle(`#helper_latLon{display:none;}`);
  1480. /**重要添加十字鼠标 */
  1481. // GM_addStyle('#pic_frame div{border:1px solid !important;cursor:crosshair !important;}');
  1482. createSelectModePanel();
  1483. createPanel();
  1484. NWP_init();
  1485. createTlinePanel();
  1486.  
  1487.  
  1488. //创建对比框
  1489. function createComparePanel(){
  1490. const panel = document.createDocumentFragment();
  1491. const mainWrapper = utils.createElement('div',{id:'compare-main',class:'display-none'});
  1492. const controlWrapper = utils.createElement('div',{class:'compare-warpper'});
  1493. controlWrapper.innerHTML = `<div id="compare-backward" class="my-button">step -6</div><div class="my-button" id="compare-foreward">step +6</div>`;
  1494. controlWrapper.innerHTML += `<select id="compare-interval"><option value="48">间隔48小时</option>
  1495. <option selected="selected" value="24">间隔24小时</option>
  1496. <option value="12">间隔12小时</option></select>
  1497. <div id="close-compare" class="my-button">关闭对比</div>
  1498. <span class="info"></span>`;
  1499.  
  1500. const imgWrapper = utils.createElement('div',{class:'compare-img-main'});
  1501. imgWrapper.innerHTML =
  1502. `<div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1503. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1504. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1505. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>`;
  1506. panel.append(mainWrapper);
  1507. mainWrapper.append(controlWrapper,imgWrapper);
  1508. //mainWrapper.append(forewardBut,backwardBut,intervalInput);
  1509. document.body.append(panel);
  1510. }
  1511.  
  1512. function createCompareMode(){
  1513. const compareHandler = {
  1514. set(obj, prop, value,receiver){
  1515. if(prop === 'interval'){
  1516. obj.interval = value;
  1517. receiver.firstImg = obj.firstImg;
  1518. //obj.img[1] = obj.img[0] + value;
  1519. //obj.img[2] = obj.img[0] + value*2;
  1520. //obj.img[3] = obj.img[0] + value*3;
  1521. }else if(prop === 'firstHr'){
  1522. // console.log(obj.imgInfo.toSource());
  1523. // const initURL = obj.imgInfo.url;
  1524. const date = obj.imgInfo.date;
  1525. const initDate = obj.imgInfo.initDate;
  1526. const hour = utils.paddingInt(value,3);
  1527. const url = obj.imgInfo.getUrl(date,hour,initDate)
  1528. receiver.firstImg = url;
  1529. //receiver.interval = obj.interval;
  1530. }else if(prop === 'imgInfo'){
  1531. console.log('设置初始图像为:'+value.url);
  1532. }else if(prop === 'firstImg'){
  1533. const imgList = obj.fit2sameday(value);
  1534. // console.log(imgList);
  1535. obj.showInfo(value);
  1536. imgList.forEach((imgSrc,i)=>{
  1537. receiver.img[i] = imgSrc;
  1538. })
  1539. //obj.img[0] = imgList;
  1540. // obj.img[1] = ;
  1541. }
  1542. Reflect.set(obj,prop,value);
  1543. },
  1544. };
  1545.  
  1546. const imgHandler = {
  1547. set(obj, prop, value,receiver){
  1548. console.log(`第${Number(prop)+1}个图像地址为${value}`);
  1549. elemsConfig.compareImgDOM.img[prop].src = value;
  1550. iImgInfo = utils.getImgNow(value);
  1551. const initTime = moment(iImgInfo.initDate,'YYYYMMDDHH');
  1552. const nowTime = moment(initTime).add(Number.parseInt(iImgInfo.hour)+8,'hours');
  1553. elemsConfig.compareImgDOM.info[prop].innerHTML =
  1554. `起报: ${initTime.format('MM-DD HH')} UTC, 【北京时${nowTime.format('MM月DD日HH时')}(${iImgInfo.hour}h)】${iImgInfo.model}, ${iImgInfo.eleName}`;
  1555. //console.log(value);
  1556. //let imgDom = document.querySelectorAll('.compare .imgSrc');
  1557. //imgDom[prop].src = value;
  1558. Reflect.set(obj,prop,value);
  1559. },
  1560. };
  1561. let imgSrc = new Proxy([1,2,3,4], imgHandler);
  1562. const modeProto = {
  1563. img:imgSrc,
  1564. firstImg:'',
  1565. interval:24,
  1566. foreward(step=6){
  1567. this.firstHr = this.firstHr + step;
  1568. console.log('步进');
  1569. },
  1570. backward(step=6){
  1571. if(this.firstHr - step>=0){
  1572. this.firstHr = this.firstHr - step;
  1573. }else{
  1574. alert('不能再退了');
  1575. }
  1576. },
  1577. firstHr:1,
  1578. urlMode(base='http://10.148'){
  1579. let url = base;
  1580. return url;
  1581. },
  1582. initIMG(){// TODO addEventListener('error',(evt)=>{evt.srcElement.src})
  1583. let DOMs = document.querySelectorAll('#compare-main .compare-img');
  1584. let imgDOM = [];
  1585. for(let img of DOMs){
  1586. img.addEventListener('error',(evt)=>{let ele = evt.target;ele.onerror=null;ele.src='/images/weather/nopic_800_600.jpg';});
  1587. imgDOM.push(img);
  1588. };
  1589. elemsConfig.compareImgDOM.img = imgDOM;
  1590.  
  1591. let infoDOMs = document.querySelectorAll('#compare-main .compare-img-info');
  1592. let infoList = [];
  1593. for(let ele of infoDOMs){
  1594. infoList.push(ele);
  1595. };
  1596. elemsConfig.compareImgDOM.info = infoList;
  1597. //return imgDOM;
  1598. },
  1599. imgInfo:{},
  1600. openCompare(){
  1601. const wrapper = document.getElementById('compare-main');
  1602. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  1603. wrapper.classList.remove('display-none');
  1604. try{
  1605. this.imgInfo = utils.getImgNow();
  1606. this.firstHr = Number.parseInt(this.imgInfo.hour);
  1607. }catch(err){
  1608. alert(err.message);
  1609. console.error(err);
  1610. }
  1611. },
  1612. closeCompare(){
  1613. const wrapper = document.getElementById('compare-main');
  1614. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  1615. wrapper.classList.add('display-none');
  1616. // this.imgInfo = utils.getImgNow();
  1617. },
  1618. fit2sameday(firstImg){
  1619. const interval = this.interval;
  1620. const firstInfo = utils.getImgNow(firstImg);
  1621. // console.log(firstImg);
  1622. // console.log(firstInfo);
  1623. const imgList = [firstImg];
  1624. const iniTime = moment(firstInfo.initDate,'YYYYMMDDHH');
  1625. for (let i = 1; i < 4; i++){
  1626. const fitTime = moment(iniTime).add(-1*interval*i,'hours');
  1627. const iDate = moment(fitTime).format('YYYYMMDD');
  1628. const iInit = moment(fitTime).format('YYYYMMDDHH');
  1629. const hour = Number.parseInt(firstInfo.hour)+interval*i;
  1630. // console.log('预报时效'+hour);
  1631. const iHour = utils.paddingInt(hour,3);
  1632. const url = firstInfo.getUrl(iDate,iHour,iInit);
  1633. imgList.push(url);
  1634. }
  1635. return imgList;
  1636. },
  1637. changeInterval(evt){
  1638. this.interval = Number.parseInt(evt.target.value);
  1639. console.log(evt);
  1640. },
  1641. showInfo(img){
  1642. const controlInfo = document.querySelector('#compare-main .compare-warpper .info');
  1643. const imgInfo = utils.getImgNow(img);
  1644. const nowTime = moment(imgInfo.initDate,'YYYYMMDDHH').add(Number.parseInt(imgInfo.hour)+8,'hours');
  1645. controlInfo.innerHTML = ' UTC+8 '+ nowTime.format('YYYY年MM月DD日HH时');
  1646. controlInfo.innerHTML += ' | GMT'+ nowTime.add(-8,'hours').format('YYYY-MM-DD HH:00');
  1647. },
  1648. };
  1649. const compareMode = new Proxy(modeProto,compareHandler);
  1650. return compareMode;
  1651. }
  1652.  
  1653. createComparePanel();//创建对比DOM框架
  1654. const compareMode = createCompareMode();
  1655. compareMode.hook = function(selector='', listener='',callback=()=>{},bindObj=compareMode) {
  1656. var targetEle = document.querySelector(selector);
  1657. targetEle.addEventListener(listener,()=>callback.call(bindObj),false);
  1658. };
  1659. compareMode.hook('#compare-backward','click',compareMode.backward,compareMode);
  1660. compareMode.hook('#compare-foreward','click',compareMode.foreward,compareMode);
  1661. compareMode.hook('#open-compare','click',compareMode.openCompare,compareMode);
  1662. compareMode.hook('#close-compare','click',compareMode.closeCompare,compareMode);
  1663. //compareMode.hook('#compare-interval','change',(evt)=>compareMode.changeInterval(evt),compareMode);
  1664. document.getElementById('compare-interval').addEventListener('change',(evt)=>compareMode.changeInterval(evt));
  1665.  
  1666. compareMode.initIMG();
  1667. //compareMode.imgInfo = utils.getImgNow();
  1668.  
  1669.  
  1670. GM_addStyle(`
  1671. #line_panel {
  1672. position: absolute;
  1673. top: 5px;
  1674. right: 5px;
  1675. background-color: #f5f5f5;
  1676. border-bottom: 0px solid rgb(20, 20, 20);
  1677. padding: 5px;
  1678. border-radius: 4px;
  1679. border: 1px solid rgb(22, 25, 28);
  1680. box-shadow: 0 1px 0 rgba(162, 184, 204, 0.25) inset, 0 0 4px hsla(0, 0%, 0%, 0.95);
  1681. color: black;
  1682. width: 0px;
  1683. }
  1684. #show-temp,#show-wind,#show-pre,#show-cloud{
  1685. width: 100%;
  1686. height:210px;
  1687. }
  1688. #show-pre{
  1689. display:block;
  1690. }
  1691. .full-screen-mode{
  1692. float:left;
  1693. padding-left:10px;
  1694. }
  1695. .full-screen-mode #float_icons{
  1696. display:none!important;
  1697. }
  1698. .full-screen-mode #pic_frame div{
  1699. border:1px solid !important;cursor:crosshair !important;
  1700. }
  1701. .top_panel div{
  1702. display:inline-block;
  1703. margin-left:5px;
  1704. margin-right:5px;
  1705. padding:2px;
  1706. border: 1px solid white;
  1707. }
  1708. .top_panel div:hover{
  1709. background-color:orange;
  1710. }
  1711. #compare-main, #multiviews-main{
  1712. z-index: 12;
  1713. position: absolute;
  1714. top: 0px;
  1715. right: 1%;
  1716. bottom: 1%;
  1717. left: 1%;
  1718. display: flex;
  1719. flex-direction: column;
  1720. align-items: center;
  1721. }
  1722. #float_icons{
  1723. display:none!important;
  1724. }
  1725. .compare-warpper {
  1726. display: flex;
  1727. justify-content: center;
  1728. min-width: 1000px;
  1729. background: white;
  1730. font-size: 18px;
  1731. align-items: center;
  1732. }
  1733. .compare-img-main {
  1734. border: 1px dotted red;
  1735. max-width: 1520px;
  1736. height: 100%;
  1737. display: grid;
  1738. grid-template-columns: 1fr 1fr;
  1739. background-color: white;
  1740. }
  1741. .compare-img-main .compare-img-wrapper {
  1742. border: 2px solid blue;
  1743. overflow: hidden;
  1744. position:relative;
  1745. }
  1746. .compare-img-main .compare-img {
  1747. border: 2px solid yellow;
  1748. position: relative;
  1749. top: -50px;
  1750. height: 120%;
  1751. }
  1752. .my-button{
  1753. cursor:pointer;
  1754. margin-left:2px;
  1755. margin-right:2px;
  1756. padding:1px;
  1757. background-color: rgb(45,53,63);
  1758. border-bottom: 0px solid rgb(20,20,20); padding:5px;
  1759. border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28);
  1760. box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95);
  1761. color: white;
  1762. }
  1763. .my-button:hover{
  1764. background-color:orange;
  1765. }
  1766. .display-none{
  1767. display:none!important;
  1768. }
  1769. .compare-img-info {
  1770. position: absolute;
  1771. top: 0;
  1772. left: 0;
  1773. background-color: rgba(0, 121, 13, 0.6);
  1774. color: white;
  1775. padding:2px;
  1776. font-size:20px;
  1777. font-family:"Arial","Microsoft YaHei","黑体","STXihei","华文细黑";
  1778. }
  1779. `)
  1780.  
  1781.  
  1782. //创建多视窗对比框
  1783. function createMVPanel(){
  1784. const panel = document.createDocumentFragment();
  1785. const mainWrapper = utils.createElement('div',{id:'multiviews-main',class:'display-none'});
  1786. const controlWrapper = utils.createElement('div',{class:'compare-warpper'});
  1787. controlWrapper.innerHTML = `<div id="mv-backward" class="my-button">step -6</div><div class="my-button" id="mv-foreward">step +6</div>`;
  1788. controlWrapper.innerHTML += `<select id="mv-interval"><option selected="selected" value="6">间隔6小时</option>
  1789. <option value="12">间隔12小时</option>
  1790. <option value="24">间隔24小时</option></select>
  1791. <div id="close-mv" class="my-button">关闭多图模式</div>
  1792. <span class="info"></span>`;
  1793.  
  1794. const imgWrapper = utils.createElement('div',{class:'compare-img-main'});
  1795. imgWrapper.innerHTML =
  1796. `<div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1797. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1798. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>
  1799. <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>`;
  1800. panel.append(mainWrapper);
  1801. mainWrapper.append(controlWrapper,imgWrapper);
  1802. //mainWrapper.append(forewardBut,backwardBut,intervalInput);
  1803. document.body.append(panel);
  1804. }
  1805.  
  1806. function createMultiviewsMode(){
  1807. const compareHandler = {
  1808. set(obj, prop, value,receiver){
  1809. if(prop === 'interval'){
  1810. obj.interval = value;
  1811. // receiver.firstImg = obj.firstImg;
  1812. receiver.step = value * obj.imgArr.length;
  1813. const firstImg = obj.imgArr[0];
  1814. let imgInfo = utils.getImgNow(firstImg);
  1815. let iHour = Number.parseInt(imgInfo.hour);
  1816. for(let i = 0; i < obj.imgArr.length; i++){
  1817. let currentHour = iHour + i * value;
  1818. const hourString = utils.paddingInt(currentHour);
  1819. obj.imgArr[i] = utils.getImgUrl(imgInfo.base,imgInfo.date,hourString,imgInfo.initDate);
  1820. }
  1821. }else if(prop === 'firstHr'){
  1822. const date = obj.imgInfo.date;
  1823. const initDate = obj.imgInfo.initDate;
  1824. const hour = utils.paddingInt(value,3);
  1825. const url = obj.imgInfo.getUrl(date,hour,initDate)
  1826. receiver.firstImg = url;
  1827. }else if(prop === 'step'){
  1828. // console.log('step');
  1829. // obj.step = value;
  1830. document.getElementById('mv-foreward').innerText = 'step +' + value;
  1831. document.getElementById('mv-backward').innerText = 'step -' + value;
  1832. }else if(prop === 'imgInfo'){
  1833. console.log('设置初始图像为:'+value.url);
  1834. }else if(prop === 'firstImg'){
  1835. const imgList = obj.fit2sameday(value);
  1836. // console.log(imgList);
  1837. obj.showInfo(value);
  1838. imgList.forEach((imgSrc,i)=>{
  1839. receiver.imgArr[i] = imgSrc;
  1840. })
  1841. //obj.img[0] = imgList;
  1842. // obj.img[1] = ;
  1843. }
  1844. Reflect.set(obj,prop,value);
  1845. },
  1846. };
  1847.  
  1848. const imgHandler = {
  1849. set(obj, prop, value){
  1850. console.log(`第${Number(prop)+1}个图像地址为${value}`);
  1851. elemsConfig.mvImgDOM.img[prop].src = value;
  1852. iImgInfo = utils.getImgNow(value);
  1853. const initTime = moment(iImgInfo.initDate,'YYYYMMDDHH');
  1854. const nowTime = moment(initTime).add(Number.parseInt(iImgInfo.hour)+8,'hours');
  1855. // controlInfo.innerHTML = ' UTC+8 '+ nowTime.format('YYYY年MM月DD日HH时');
  1856. elemsConfig.mvImgDOM.info[prop].innerHTML =
  1857. `【北京时${nowTime.format('MM月DD日HH时')}(${iImgInfo.hour}h)】${iImgInfo.model}, ${iImgInfo.eleName}, 起报: ${initTime.format('MM-DD HH')} UTC`;
  1858. Reflect.set(obj,prop,value);
  1859. },
  1860. };
  1861. let imgSrc = new Proxy([1,2,3,4], imgHandler);
  1862. const modeProto = {
  1863. imgArr:imgSrc,
  1864. firstImg:'',
  1865. interval:6,
  1866. step:6,
  1867. foreward(step=6){
  1868. // this.firstHr = this.firstHr + step;
  1869. // console.log(this);
  1870. this.changeHour(this.step);
  1871. console.log('步进');
  1872. },
  1873. backward(step=-6){
  1874. let imgInfo = utils.getImgNow(this.imgArr[0]);
  1875. // console.dir(imgInfo);
  1876. // console.log(Number.parseInt(imgInfo.hour) - this.step>=0)
  1877. if(Number.parseInt(imgInfo.hour) - this.step>=0){
  1878. this.changeHour(-1*this.step);
  1879. console.log('步退');
  1880. }else{
  1881. alert('不能再退了');
  1882. }
  1883. },
  1884. changeHour(hr=6){
  1885. let hour = parseInt(hr);
  1886. for(let i = 0; i<this.imgArr.length;i++){
  1887. let iInfo = utils.getImgNow(this.imgArr[i]);
  1888. let newHour = parseInt(iInfo.hour) + hour;
  1889. // console.log(newHour);
  1890. this.imgArr[i] = iInfo.getUrl(iInfo.date,utils.paddingInt(newHour),iInfo.initDate);
  1891. }
  1892. },
  1893. firstHr:1,
  1894. urlMode(base='http://10.148'){
  1895. let url = base;
  1896. return url;
  1897. },
  1898. initIMG(){// TODO addEventListener('error',(evt)=>{evt.srcElement.src})
  1899. let DOMs = document.querySelectorAll('#multiviews-main .compare-img');
  1900. let imgDOM = [];
  1901. for(let img of DOMs){
  1902. img.addEventListener('error',(evt)=>{let ele = evt.target;ele.onerror=null;ele.src='/images/weather/nopic_800_600.jpg';});
  1903. imgDOM.push(img);
  1904. };
  1905. elemsConfig.mvImgDOM.img = imgDOM;
  1906.  
  1907. let infoDOMs = document.querySelectorAll('#multiviews-main .compare-img-info');
  1908. let infoList = [];
  1909. for(let ele of infoDOMs){
  1910. infoList.push(ele);
  1911. };
  1912. elemsConfig.mvImgDOM.info = infoList;
  1913. //return imgDOM;
  1914. },
  1915. imgInfo:{},
  1916. openCompare(){
  1917. const wrapper = document.getElementById('multiviews-main');
  1918. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  1919. wrapper.classList.remove('display-none');
  1920. try{
  1921. let imgInfo = utils.getImgNow();
  1922. if(elemsConfig.mvImgDOM.mode === 'multiElem'){
  1923. for(let i = 0; i < this.imgArr.length; i++){
  1924. const iPatt = elemsConfig.mvImgDOM.matchPattens[i];
  1925. // console.log(iPatt);
  1926. const iMatch = utils.matchImgPattern(iPatt);
  1927. console.log(iMatch);
  1928. this.imgArr[i] = utils.getImgUrl(iMatch.base,imgInfo.date,imgInfo.hour,imgInfo.initDate);
  1929. }
  1930. }else if(elemsConfig.mvImgDOM.mode === 'multiTime'){
  1931. this.step = this.interval * this.imgArr.length;
  1932. let iHour = Number.parseInt(imgInfo.hour);
  1933. for(let i = 0; i < this.imgArr.length; i++){
  1934. let currentHour = iHour + i * this.interval;
  1935. const hourString = utils.paddingInt(currentHour);
  1936. this.imgArr[i] = utils.getImgUrl(imgInfo.base,imgInfo.date,hourString,imgInfo.initDate);
  1937. }
  1938. }
  1939. }catch(err){
  1940. alert(err.message);
  1941. console.error(err);
  1942. }
  1943. },
  1944. closeCompare(){
  1945. const wrapper = document.getElementById('multiviews-main');
  1946. //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none')
  1947. wrapper.classList.add('display-none');
  1948. // this.imgInfo = utils.getImgNow();
  1949. },
  1950. fit2sameday(firstImg){
  1951. const interval = this.interval;
  1952. const firstInfo = utils.getImgNow(firstImg);
  1953. // console.log(firstImg);
  1954. // console.log(firstInfo);
  1955. const imgList = [firstImg];
  1956. const iniTime = moment(firstInfo.initDate,'YYYYMMDDHH');
  1957. for (let i = 1; i < 4; i++){
  1958. const fitTime = moment(iniTime).add(-1*interval*i,'hours');
  1959. const iDate = moment(fitTime).format('YYYYMMDD');
  1960. const iInit = moment(fitTime).format('YYYYMMDDHH');
  1961. const hour = Number.parseInt(firstInfo.hour)+interval*i;
  1962. // console.log('预报时效'+hour);
  1963. const iHour = utils.paddingInt(hour,3);
  1964. const url = firstInfo.getUrl(iDate,iHour,iInit);
  1965. imgList.push(url);
  1966. }
  1967. return imgList;
  1968. },
  1969. changeInterval(evt){
  1970. this.interval = Number.parseInt(evt.target.value);
  1971. // console.log(evt);
  1972. },
  1973. showInfo(img){
  1974. const controlInfo = document.querySelector('#multiviews-main .compare-warpper .info');
  1975. const imgInfo = utils.getImgNow(img);
  1976. const nowTime = moment(imgInfo.initDate,'YYYYMMDDHH').add(Number.parseInt(imgInfo.hour)+8,'hours');
  1977. controlInfo.innerHTML = ' UTC+8 '+ nowTime.format('YYYY年MM月DD日HH时');
  1978. controlInfo.innerHTML += ' | GMT'+ nowTime.add(-8,'hours').format('YYYY-MM-DD HH:00');
  1979. },
  1980. };
  1981. const compareMode = new Proxy(modeProto,compareHandler);
  1982. return compareMode;
  1983. }
  1984.  
  1985. createMVPanel();//创建对比DOM框架
  1986. const mvMode = createMultiviewsMode();
  1987. mvMode.hook = function(selector='', listener='',callback=()=>{},bindObj=mvMode) {
  1988. var targetEle = document.querySelector(selector);
  1989. targetEle.addEventListener(listener,()=>callback.call(bindObj),false);
  1990. };
  1991. mvMode.hook('#mv-backward','click',mvMode.backward,mvMode);
  1992. mvMode.hook('#mv-foreward','click',mvMode.foreward,mvMode);
  1993. // mvMode.hook('#open-mv','click',mvMode.openCompare,mvMode);
  1994. mvMode.hook('#close-mv','click',mvMode.closeCompare,mvMode);
  1995. //compareMode.hook('#compare-interval','change',(evt)=>compareMode.changeInterval(evt),compareMode);
  1996. document.getElementById('mv-interval').addEventListener('change',(evt)=>mvMode.changeInterval(evt));
  1997.  
  1998. mvMode.initIMG();
  1999. let openMvTime = document.getElementById('open-mvTime');
  2000. // console.log(openMvTime)
  2001. openMvTime.addEventListener('click',()=>{
  2002. elemsConfig.mvImgDOM.mode = 'multiTime';
  2003. mvMode.openCompare();
  2004. });
  2005. // let openMv = document.getElementById('open-mv');
  2006. // openMv.addEventListener('click',()=>{
  2007. // elemsConfig.mvImgDOM.mode = 'multiElem';
  2008. // mvMode.openCompare();
  2009. // });
  2010. /**
  2011. * 地图单点标志
  2012. */
  2013. function createMapIndicator(){
  2014. let wrapDiv = document.querySelector('#main_frame');
  2015. let pointer = utils.createElement('div',{id:'map-pointer',draggable:'true',class:'display-none'});
  2016. pointer.innerHTML = '<span class="water-dot scale-lg"></span>'
  2017. wrapDiv.insertAdjacentElement('afterbegin', pointer);
  2018. //保存位置的状态值
  2019. var pos={
  2020. // parent_top:0,
  2021. // parent_left:0,
  2022. // cur_top:0,
  2023. // cur_left:0,
  2024. x_diff:0,// 鼠标相对目标物的位置
  2025. y_diff:0,
  2026. }
  2027. function allowDrop(ev){ //ev是事件对象
  2028. ev.preventDefault(); //取消事件已经关联的默认活动
  2029. }
  2030. function drag(ev){
  2031. //dataTransfer是一个媒介,将目标对象放入媒介
  2032. //dataTransfer对象用来保存被拖动的数据,仅在拖动事件有效
  2033. //这里将被拖动元素的id保存为名为Text的键值对中
  2034. ev.dataTransfer.setData("Pointer",ev.target.id);
  2035. //获取被拖动对象相对于上层元素顶边和左边位置
  2036. let mouseXY = utils.getElemRelPos(ev.currentTarget,ev.clientX, ev.clientY);
  2037. // let eStyle = getComputedStyle(ev.target);
  2038. pos.x_diff = mouseXY.x;
  2039. pos.y_diff = mouseXY.y;
  2040. // console.log('current');
  2041. // console.log(ev.currentTarget);
  2042. // console.log(ev.target);
  2043. // pos.parent_top=ev.target.offsetTop;
  2044. // pos.parent_left=ev.target.offsetLeft;
  2045. // pos.cur_top=ev.screenY;
  2046. // pos.cur_left=ev.screenX;
  2047. // console.log(mouseXY);
  2048. // console.log(eStyle.marginLeft);
  2049. // console.log(ev);
  2050. }
  2051. function drop(ev){
  2052. var new_top,new_left;
  2053. ev.preventDefault();
  2054. // alert(2);
  2055. var data=ev.dataTransfer.getData("Pointer"); //从媒介中获取目标对象
  2056. var elem=document.getElementById(data);
  2057. //这里不能这样使用,因为offset*的值是只读的,不能改变
  2058. // elem.offsetLeft=v.parent_left+ev.screenX-v.cur_left+"px";
  2059. // elem.offsetTop=v.parent_top+ev.screenY-v.cur_top+"px";
  2060. let mouseXY = utils.getElemRelPos(ev.target,ev.clientX, ev.clientY);
  2061. // let eStyle = getComputedStyle(elem);
  2062. // console.log(mouseXY);
  2063. // console.log(mouseXY.x+eStyle.width);
  2064. // 此处有微小的位移
  2065. elem.style.marginLeft = mouseXY.x - pos.x_diff + 35.8 + "px";
  2066. elem.style.marginTop = mouseXY.y - pos.y_diff + "px";
  2067. // console.log([parseFloat(elem.style.marginTop)+23.2 + 12.3,parseFloat(elem.style.marginLeft)+10]);
  2068. let newMouseXY = {
  2069. x:parseFloat(elem.style.marginLeft) - 29.31,
  2070. // x:parseFloat(elem.style.marginLeft) + 5.0,
  2071. y:parseFloat(elem.style.marginTop) + 33.066,
  2072. };
  2073. const loc = helperConfig.matchLoc(newMouseXY, helperConfig.matchParam);
  2074. elemsConfig.latLonInput.lat.innerHTML = loc.lat; // mouseXY.y
  2075. elemsConfig.latLonInput.lon.innerHTML = loc.lon; // mouseXY.x
  2076. elemsConfig.fixPoint = {lat: loc.lat, lon: loc.lon};
  2077. // console.log(elemsConfig.fixPoint);
  2078. utils.showTimeSeries(elemsConfig.fixPoint);
  2079.  
  2080. // elem.style.marginLeft=pos.parent_left+ev.screenX-pos.cur_left-1+"px";
  2081. // elem.style.marginTop=pos.parent_top+ev.screenY-pos.cur_top-23.2+"px";
  2082. /* TODO
  2083. const imgXY = {
  2084. x: elem.style.marginLeft;
  2085. y: elem.style.marginTop;
  2086. }
  2087. const loc = helperConfig.matchLoc(imgXY, helperConfig.matchParam);
  2088. elemsConfig.pointerPoint.lat = loc.lat;
  2089. elemsConfig.pointerPoint.lon = loc.lon;
  2090. utils.showTimeSeries(elemsConfig.pointerPoint);
  2091. */
  2092.  
  2093. }
  2094. pointer.addEventListener('dragstart',drag);
  2095. wrapDiv.addEventListener('dragover',allowDrop);
  2096. wrapDiv.addEventListener('drop',drop);
  2097. }
  2098.  
  2099. createMapIndicator();
  2100. GM_addStyle(`
  2101. .water-dot {
  2102. position: relative;
  2103. display: inline-block;
  2104. height: 26px;
  2105. width: 16px;
  2106. }
  2107.  
  2108. .water-dot:before,
  2109. .water-dot:after {
  2110. content: '';
  2111. position: absolute;
  2112. display: inline-block;
  2113. }
  2114.  
  2115. .water-dot:before {
  2116. left: 0;
  2117. width: 16px;
  2118. height: 16px;
  2119. border-radius: 50%;
  2120. background-image: repeating-radial-gradient(8px 8px at 50% 8px, transparent 0%, transparent 3px, #dd1010 3px, #dd1010 100%);
  2121. }
  2122.  
  2123. .water-dot:after {
  2124. bottom: 0;
  2125. left: 50%;
  2126. border: 14px solid #dd1010;
  2127. border-bottom-width: 0;
  2128. border-right-width: 7px;
  2129. border-left-width: 7px;
  2130. transform: translate(-50%,0);
  2131. border-bottom-color: transparent;
  2132. border-right-color: transparent;
  2133. border-left-color: transparent;
  2134. }
  2135. .water-dot.scale-lg{
  2136. transform: scale(1.5);
  2137. }
  2138. `);
  2139. /**
  2140. * 经纬度到图像位置
  2141. */
  2142. function latlon2XY(loc={lat:10, lon:120}){
  2143. const imgXY = helperConfig.matchImgXY(loc, helperConfig.matchParam);
  2144. return imgXY;
  2145. }
  2146.  
  2147. function createCrossSectionCanvas(){
  2148. // let wrapDiv = document.querySelector('#main_frame');
  2149. let wrapDiv = document.querySelector('#pic_frame');
  2150. const cv = utils.createElement('canvas',{id:'cv-draw-line',width:"800", height:"600",style:"border:1px solid #d3d3d3;"});
  2151. wrapDiv.insertAdjacentElement('afterbegin', cv);
  2152. const ctx = cv.getContext('2d');
  2153. const line = {start:[0,0],end:[0,0],init:false};
  2154. const cvMsMove = function(ev){
  2155. // console.log('test2');
  2156. ctx.clearRect(0,0,800,600);
  2157. ctx.beginPath();
  2158. ctx.lineWidth=3;
  2159. let mouseXY = utils.getElemRelPos(ev.target,ev.clientX, ev.clientY);
  2160. line.end = [mouseXY.x, mouseXY.y];
  2161. ctx.moveTo(...line.start);
  2162. ctx.lineTo(...line.end);
  2163. ctx.strokeStyle="green";
  2164. ctx.stroke();
  2165. // console.log(line);
  2166. }
  2167.  
  2168. const cvMsClick = function (ev) {
  2169. // console.log('test');
  2170. if(line.init){
  2171. cv.removeEventListener('mousemove',cvMsMove, false);
  2172. line.init = false;
  2173. }else{
  2174. // ctx.beginPath();
  2175. // ctx.lineWidth=10;
  2176. // ctx.moveTo(0,0);
  2177. // ctx.lineTo(50,50);
  2178. // ctx.strokeStyle="green";
  2179. // ctx.stroke();
  2180. let mouseXY = utils.getElemRelPos(ev.target,ev.clientX, ev.clientY);
  2181. line.start = [mouseXY.x, mouseXY.y];
  2182. cv.addEventListener('mousemove',cvMsMove, false);
  2183. line.init = true;
  2184. }
  2185.  
  2186. }
  2187. cv.addEventListener('click', cvMsClick, false);
  2188. ctx.fillStyle = 'rgba(255,255,255,0)';
  2189. }
  2190. setTimeout(()=>{createCrossSectionCanvas()},5000);
  2191.  
  2192. GM_addStyle(`.show_panel{z-index:11;}
  2193. #pic_frame,#main_frame{
  2194. position:relative;
  2195. }
  2196. #map-pointer {
  2197. position: absolute;
  2198. top: 22px;
  2199. left: 0px;
  2200. /*background-color: rgba(0, 152, 50, 0.7);*/
  2201. color: white;
  2202. /*width:15px;*/
  2203. /*height:15px;*/
  2204. display:inline-block;
  2205. padding:2px;
  2206. z-index:10;
  2207. cursor:pointer;
  2208. }
  2209. #cv-draw-line{
  2210. position: absolute;
  2211. z-index:9;
  2212. left:0px;
  2213. cursor:crosshair;
  2214. display:none
  2215. }
  2216. `);