Greasy Fork is available in English.
广东省气象业务网数值预报页面增强
// ==UserScript== // @name NWP helper in GuangDong // @description 广东省气象业务网数值预报页面增强 // @namespace minhill.com // @match http://10.148.8.228/to_fore_homepage.action* // @match http://10.148.8.18/ywwhome/vision/multiDataApply/gisloader.html* // @version 1.9.4 // @require https://lib.baomitu.com/echarts/4.6.0/echarts.min.js // @require https://lib.baomitu.com/moment.js/2.24.0/moment.min.js // @require https://lib.baomitu.com/d3/5.15.0/d3.min.js // @grant GM_addStyle // @grant unsafeWindow // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant GM_notification // @license The MIT License (MIT); http://opensource.org/licenses/MIT // @connect 172.22.1.175 // @connect 127.0.0.1:5000 // @connect api.map.baidu.com // @connect trident.gdmo.gq // @connect research.gdmo.gq // @compatible firefox // @compatible chrome // @compatible edge // @note 2018/01/08 增加时效转换按钮 // @note 2018/01/15 增加经纬度功能 // @supportURL https://greasyfork.org/scripts/26259 // @author Hanchy Hill // ==/UserScript== // TODO 数据格式错误时显示提示 // TODO require resData // TODO 风向tooltip改进 // TODO 增加气压 // TODO 增加集合预报-32天预报 // TODO 单站停止经纬度捕捉 // TODO 分钟降水时时效切换错误 // TODO 显示单击的点 // TODO 垂直剖面NCL测试 // TODO 垂直探空曲线 // TODO 降水量, 风力分级 // TODO 多图模式时次选择 const modelMeta = [ { "model": "grapes1km", "rule": "/{yyyymmdd}/{HH}/grapes1km_{range}_{name}_m{hhh}_{yyyy}{mm}{dd}{HH}{mi}.png", "modelName": "Grapes_gz_R_1km" }, { "model": "grapesmeso_b", "rule": "grapesmeso_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_Meso" }, { "model": "grapescma_b", "rule": "grapescma_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_全球" }, { "model": "SCMOC", "rule": "SCMOC_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "客观短时预报" }, { "model": "ecmwffine_b", "rule": "ecmwffine_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF_细网格" }, { "model": "ecmwfs2d_b", "rule": "ecmwfs2d_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF_细网格(S2D)" }, { "model": "ecmwfs2s", "rule": "ecmwfs2s_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF细网格(S2S)" }, { "model": "ecmwfc3e_b", "rule": "ecmwfc3e_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF_集合" }, { "model": "ecmwfs4f", "rule": "ecmwfs4f_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF_32天" }, { "model": "ecmwfEnsExt", "rule": "ecmwfEnsExt_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF_46天" }, { "model": "ecmwfc3y", "rule": "ecmwfc3y_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "ECMWF_C3Y" }, { "model": "ncepgfs0p5", "rule": "ncepgfs0p5_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "NCEP" }, { "model": "chaf", "rule": "chaf_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_R_3km" }, { "model": "GTRAMS3KMEC_b", "rule": "GTRAMS3KMEC_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_3km" }, { "model": "GTRAMS3KMNCEP_b", "rule": "GTRAMS3KMEC_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_3km(N)" }, { "model": "Gtrams3kmCNEC", "rule": "Gtrams3kmCN_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_3km(CN)" }, { "model": "Gtrams3kmCNGraGFS", "rule": "Gtrams3kmCN_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_3km(G)" }, { "model": "grapes9ec", "rule": "grapes9_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_9km" }, { "model": "grapes9", "rule": "grapes9_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_9km(N)" }, { "model": "grapes18kmec_b", "rule": "grapes18_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_18km" }, { "model": "grapes18km_b", "rule": "grapes18_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes_gz_18km(N)" }, { "model": "wavepng_b", "rule": "wave_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes-海洋" }, { "model": "seafog_b", "rule": "{name}{yyyy}{mm}{dd}{HH}r###lt.gif", "modelName": "Grapes-海雾" }, { "model": "graces_b", "rule": "graces_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "Grapes-环境" }, { "model": "environment_b", "rule": "environment_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "CMA环境" }, { "model": "fax_b", "modelName": "FAX传真图", "rule": "" }, { "model": "obor_b", "rule": "ecmwfs2d_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "一带一路" }, { "model": "clean_plot", "rule": "gz3km{yyyy}{mm}{dd}{HH}_{hhh}_{name}.png", "modelName": "Grapes_3km(CN)" }, { "model": "cldas", "rule": "cldas_hn_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "国家局实况融合" }, { "model": "OCEN_CODAS", "rule": "OCEN_CODAS_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "国家局海洋融合" }, { "model": "SWMA", "rule": "swma_{range}_{name}_{hhh}_{yyyy}{mm}{dd}{HH}.png", "modelName": "热带所海面风融合" } ]; let multiModelRule = [ { rule: [ ['ecmwffine_b', ['oy', 'cn', 'hn', 'gd',], null], ['ncepgfs0p5', ['oy', 'cn', 'hn', 'gd',], null], ['grapescma_b', ['oy', 'cn', 'hn', 'gd',], null], ['grapes9ec', ['oy', 'cn', 'hn', 'gd',], null], ], name: 'EC/NCEP/G全球/G9kmEC', description: 'ECMWF, NCEP, Grapes全球, ##台风模式9km', opt: { stritFit: true } }, { rule: [ ['ecmwffine_b', ['oy', 'cn', 'hn', 'gd',], null], ['ncepgfs0p5', ['oy', 'cn', 'hn', 'gd',], null], ['grapes9ec', ['cn', 'hn', 'gd',], null], ['grapesmeso_b', ['cn', 'hn', 'gd',], null], ], name: 'EC/NCEP/G9kmEC/G-meso', description: 'ECMWF, NCEP, ##台风模式9km, Grapes-Meso', opt: { stritFit: true } }, { rule: [ ['ecmwffine_b', ['oy', 'cn', 'hn', 'gd',], null], ['ncepgfs0p5', ['oy', 'cn', 'hn', 'gd',], null], ['grapes9ec', ['cn', 'hn', 'gd',], null], ['GTRAMS3KMEC_b', ['hn', 'gd',], null], ], name: 'EC/NCEP/G9kmEC/G-meso', description: 'ECMWF, NCEP, ##台风模式9km, Grapes-Meso', opt: { stritFit: true } }, ] let mvPatternList = [ [{ "elem": "t2mm", "elemCN": "2m气温", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }, { "elem": "t2mm24", "elemCN": "2m气温24h变温", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }, { "elem": "wind10m", "elemCN": "10m风", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }, { "elem": "10gust3", "elemCN": "10m阵风(过去3h)", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }] ] let userConfig = { // 用户设置 alterDate: false, // 默认不修改时次 timelineMove: false,//是否启用滑动时间触发 debug: false, mvMode:undefined, }; let $ = unsafeWindow.$; const elemsConfig = { latLonInput: undefined, $doc: () => unsafeWindow.$doc, $settings: () => unsafeWindow.$settings, $home: unsafeWindow.$home, fixPoint: undefined, pointerPoint: { lat: 0, lon: 0 },// 地图锚点位置 point01: undefined, point02: undefined, state: 'timeseries',//'vertical','skewT' compareImgDOM: { img: [], info: [] },//前后预报时次对比DOM引用 mvImgDOM: {//四分图DOM引用 img: [], info: [], mode: 'multiElem',//multiTime, multiModel, multiElem,multiInitime matchedSrcList: [], matchPattens: [{ "elem": "t2mm", "elemCN": "2m气温", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }, { "elem": "t2mm24", "elemCN": "2m气温24h变温", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }, { "elem": "wind10m", "elemCN": "10m风", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" }, { "elem": "10gust3", "elemCN": "10m阵风(过去3h)", "region": "cn", "model": "ecmwffine_b", "modelFileName": "ecmwffine" },], }, }; let proxyConfig = new Proxy({ showTcMode: false, showDetTcMode: true, showEnsTcMode: true, tcShowLock: false, preTime: '', preRegion: '', domTarget: { showTcMode: '#tc-mode-btn', showDetTcMode: '#tc-det-btn', showEnsTcMode: '#tc-ens-btn', }, svgTarget: { showTcMode: '#wrap-draw-tc > svg', showDetTcMode: '#wrap-draw-tc > svg .svg-tc-det', showEnsTcMode: '#wrap-draw-tc > svg .svg-tc-ens', } }, { set: function (obj, prop, value, receiver) { if (prop === 'tcShowLock') return; const tcShowDom = document.querySelector(obj.domTarget.showTcMode); obj[prop] = value; // console.error(obj.showDetTcMode); // console.error(obj.showEnsTcMode); if (prop === 'showTcMode') { let detDom = document.querySelector(obj.domTarget['showDetTcMode']); let ensDom = document.querySelector(obj.domTarget['showEnsTcMode']); if (value === true) { tcShowDom.classList.add('active-button'); detDom.classList.remove('display-none'); ensDom.classList.remove('display-none'); if (obj.showDetTcMode || obj.showEnsTcMode) showSvgTC(); // 如果全为false则不执行showSvgTC(); // TODO 显示SVG // 重绘SVG } else { tcShowDom.classList.remove('active-button'); detDom.classList.add('display-none'); ensDom.classList.add('display-none'); let svgTC = document.querySelector('#wrap-draw-tc'); if (svgTC) svgTC.classList.add('display-none'); } } else if (prop == 'showDetTcMode' || prop == 'showEnsTcMode') { let targetDom = document.querySelector(obj.domTarget[prop]); if (value === true) { targetDom.classList.add('active-button'); showSvgTC(); } else { targetDom.classList.remove('active-button'); let targetSvgGroup = document.querySelector(obj.svgTarget[prop]); if (targetSvgGroup) targetSvgGroup.classList.add('display-none'); if (!obj.showDetTcMode && !obj.showEnsTcMode) {// 全为flase则取消台风选择 receiver.showTcMode = false; } // svg-tc-ens // TODO 隐藏对应的<g>元素 } } } }) const helperConfig = { region: { hn: {//华南 isMap: true,// 是否是等经纬地图 projection: 'Equidistant',// 投影 latLon: { xRight: 636.98, xLeft: 172.56, yTop: 56.516, yBottom: 547.576, lon0: 105.0, lon1: 120.0, lat0: 15.0, lat1: 30.0, }, }, cn: {//## isMap: true,// 是否是等经纬地图 projection: 'Lambert', latLon: { refLon0: 110.25, refLat0: 20, m0: 342.98333, n0: 382.51666, // refLon0:110,refLat0:20, // m0:338.98,n0:382.51, refLat1: 1.001, refLat2: 25.0, lonr: 80, latr: 40, mr: 94.983, nr: 167.51, }, }, oy: {//欧亚 isMap: true,// 是否是等经纬地图 projection: 'Mercator', latLon: { xRight: 692.9833, xLeft: 156.9833, yTop: 54.51666, yBottom: 533.51666, lon0: 60.0, lon1: 160.0, lat0: 10.0, lat1: 70.0, }, }, gd: {//广东 isMap: true,// 是否是等经纬地图 projection: 'Equidistant',// 投影 latLon: { xRight: 589.98334, xLeft: 124.98334, yTop: 104.51666, yBottom: 437.51666, lon0: 110, lon1: 116, lat0: 21.0, lat1: 25.0, }, }, hy: {//海洋 isMap: true,// 是否是等经纬地图 projection: 'Lambert',// 投影 latLon: { refLon0: 110, refLat0: 0, m0: 274.983, n0: 490.51666, refLat1: 1.001, refLat2: 25.0, lonr: 100, latr: 20, mr: 171.983, nr: 270.51666, }, }, '86st': {//单站 isMap: false, }, rainnest: {//雨涡 isMap: false, }, '5pro': {//雨涡 isMap: false, }, }, currentRegion: 'hn', matchImgXY: () => { },// 根据经纬度获取图像位置 matchLoc: () => { },// 获取经纬度的函数 matchParam: '',// 调用上式的第二参数 }; // /** * unsafeWindow函数 * selectProItem */ const utils = { log(arg) { userConfig.debug ? console.log(arg) : ''; }, changeRegion(region) { helperConfig.currentRegion = region; return helperConfig.region[region].isMap;// 返回是否是地图 }, getJSON(url) {// 获取json数据 return new Promise(function (resolve, reject) { GM_xmlhttpRequest({ //获取时间序列 method: 'GET', synchronous: false, url: url, onload: function (reDetails) { if (reDetails.status !== 200 && reDetails.status !== 304) { console.error('获取URL错误'); // showNotification('数据中心数据获取异常'); let errorNotice = GM_notification({ text: '数据获取异常' + url, image: 'http://10.148.8.228/images/logo.png', title: '接口异常', timeout: 3000 }); if (errorNotice) setTimeout(() => errorNotice.remove(), 3000); reject(new Error('数据获取异常' + url)); } const data = JSON.parse(reDetails.responseText); //console.log(data.DATA); resolve(data); } }); }) }, projection: { Mercator: {// 墨卡托投影 calBasicInfo(lat1 = 0, lat2 = 0, n1 = 0, n2 = 0) { /*参数lat 纬度, n坐标数值 n0 赤道,d0放大系数 */ const y1 = Math.log(Math.tan(lat1) + 1 / Math.cos(lat1)); const y2 = Math.log(Math.tan(lat2) + 1 / Math.cos(lat2)); const n0 = (n1 - (y1 / y2) * n2) / (1.0 - y1 / y2); const d0 = y1 / (n0 - n1); return { n0, d0 }; }, calLatLon(mouseXY, dims) { // console.log(dims); const n0 = dims.n0; const d0 = dims.d0; // console.log(Math.sinh((n0-mouseXY.y)*d0)); const lat = Math.atan(Math.sinh((n0 - mouseXY.y) * d0)); ///---------------// const r = dims.xLeft; const o = dims.xRight; const s = (dims.lon1 - dims.lon0) / (o - r); const u = dims.lon0 + (mouseXY.x - r) * s; return { lat: lat * 180 / Math.PI, lon: u }; }, calImgLoc(latlon, dims = { xLeft, xRight, lon1, lon0, n0, d0 }) { const n0 = dims.n0; const d0 = dims.d0; const sec = (x = 0) => 1 / Math.cos(x);// 正割 const lon = latlon.lon; const r = dims.xLeft; const o = dims.xRight; const s = (dims.lon1 - dims.lon0) / (o - r); const x = (lon - dims.lon0) / s + r; const phi = latlon.lat / 180 * Math.PI; const y = n0 - Math.log(Math.tan(phi) + sec(phi)) / d0; const mouseXY = { x, y }; return mouseXY; }, }, Equidistant: {//等经纬度 calBasicInfo() { return helperConfig.region[helperConfig.currentRegion].latLon }, calLatLon(mouseXY, dims) { // console.log(dims); const r = dims.xLeft; const o = dims.xRight; const i = dims.yTop; const l = dims.yBottom; const s = (dims.lon1 - dims.lon0) / (o - r); // o - r 内框宽度 -> s = lon/height const d = (dims.lat1 - dims.lat0) / (i - l);// i - l 内框高度 -> d = lat/width const u = dims.lon0 + (mouseXY.x - r) * s; const m = dims.lat1 + (mouseXY.y - i) * d; return { lat: m, lon: u }; }, calImgLoc(latlon = { lat, lon }, dims) { const r = dims.xLeft; const o = dims.xRight; const i = dims.yTop; const l = dims.yBottom; const s = (dims.lon1 - dims.lon0) / (o - r); // o - r 内框宽度 -> s = lon/height const d = (dims.lat1 - dims.lat0) / (i - l);// i - l 内框高度 -> d = lat/width const lat = latlon.lat; const lon = latlon.lon; const x = (lon - dims.lon0) / s + r; const y = (lat - dims.lat1) / d + i; const mouseXY = { x, y }; return mouseXY; }, }, Lambert: {// 兰伯特投影 calBasicInfo({ refLon0, refLat0, m0, n0, refLat1, refLat2, lonr, latr, mr, nr }) { /* refLat0,refLon0,m0,n0 参考纬度、经度,屏幕X坐标,Y坐标; 屏幕坐标与实际坐标映射: x = (m-m0)*dx // dx为x方向比例系数 y = (n0-n)*dy // dy为y方向比例系数,y方向屏幕坐标反向,所以取反 refLat1,refLat2 2个平行纬度 latr,lonr,mr,nr 选取的另外一个点的经纬度和屏幕坐标 phi 纬度,lambda经度 */ const ang2rad = (x = 0) => Math.PI * x / 180.0; const [phi0, phi1, phi2] = [ang2rad(refLat0), ang2rad(refLat1), ang2rad(refLat2)]; //console.log(phi0,phi1,phi2); const lambda0 = ang2rad(refLon0); const [tan, cos, sin, pow, PI, ln] = [Math.tan, Math.cos, Math.sin, Math.pow, Math.PI, Math.log]; const cot = (x = 0) => Math.cos(x) / Math.sin(x);// 余切 const sec = (x = 0) => 1 / Math.cos(x);// 正割 const n = ln(cos(phi1) * sec(phi2)) / ln(tan(0.25 * PI + 0.5 * phi2) * cot(0.25 * PI + 0.5 * phi1)); const F = (cos(phi1) * pow(tan(0.25 * PI + 0.5 * phi1), n)) / n; // n,F常量参数 let rho = (phi = 0) => F * pow(cot(0.25 * PI + 0.5 * phi), n); // rho变量 let rho0 = rho(phi0); let fx = (phi, lambda) => rho(phi) * sin(n * (lambda - lambda0)); let fy = (phi, lambda) => rho0 - rho(phi) * cos(n * (lambda - lambda0)); const dx = fx(ang2rad(latr), ang2rad(lonr)) / (mr - m0); const dy = fy(ang2rad(latr), ang2rad(lonr)) / (n0 - nr); // console.log(dx,dy); return { dx, dy, F, n, rho, rho0, lambda0, m0, n0 }; }, calLatLon(mouseXY, { dx, dy, F, n, rho0, lambda0, m0, n0 }) { const x = (mouseXY.x - m0) * dx; const y = (n0 - mouseXY.y) * dy; let rho = (x, y) => Math.sign(n) * Math.sqrt(Math.pow(x, 2) + Math.pow((rho0 - y), 2)); let theta = (x, y) => Math.atan(x / (rho0 - y)); let phi = (rho) => 2 * Math.atan(Math.pow(F / rho, 1 / n)) - 0.5 * Math.PI; let lambda = (theta) => lambda0 + theta / n; const lat = phi(rho(x, y)) * 180 / Math.PI; const lon = lambda(theta(x, y)) * 180 / Math.PI; // console.log(mouseXY.x-m0,n0-mouseXY.y); //console.log(mouseXY.x - m0,n0-mouseXY.y); return { lat, lon }; }, calImgLoc(latlon, { dx, dy, rho = () => { }, rho0, m0, n0, lambda0, n }) { const [cos, sin, PI] = [Math.cos, Math.sin, Math.PI]; const phi = latlon.lat * PI / 180; const lambda = latlon.lon * PI / 180; const originX = (phi, lambda) => rho(phi) * sin(n * (lambda - lambda0)); const originY = (phi, lambda) => rho0 - rho(phi) * cos(n * (lambda - lambda0)); let mouseXY = { x: 0, y: 0 }; mouseXY.x = originX(phi, lambda) / dx + m0; mouseXY.y = n0 - originY(phi, lambda) / dy; return mouseXY; } } }, debounce: (fn, delay, scope) => { let timer = null; // 返回函数对debounce作用域形成闭包 return function () { // setTimeout()中用到函数环境总是window,故需要当前环境的副本; let context = scope || this, args = arguments; // 如果事件被触发,清除timer并重新开始计时 clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay); } }, throttle(fn, threshold, scope) { let timer; let prev = Date.now(); return function () { let context = scope || this, args = arguments; let now = Date.now(); if (now - prev > threshold) { prev = now; fn.apply(context, args); } } }, showSkewT(point = { lat: 21, lon: 120 }, runtime = '2019012018', fcHour = '18') { const latRange = [point.lat - 0.125 * 2, point.lat + 0.125 * 2]; const lonRange = [point.lon - 0.125 * 2, point.lon + 0.125 * 2]; let fcTime = utils.getFcTime(); const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH'); runtime = initTime.format('YYYYMMDDHH'); fcHour = fcTime.fcHour; 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`); // https://www.tropicaltidbits.com/analysis/models/sounding/?model=gfs&runtime=2018122418&fh=18&domain=120,122.5,20,22.5&stationID=&tc=&mode=regular }, showVertical(p0 = { lat: 21, lon: 120 }, p1 = { lat: 23, lon: 130 }, runtime = '2019012018', fcHour = '18') { let fcTime = utils.getFcTime(); const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH'); runtime = initTime.format('YYYYMMDDHH'); fcHour = fcTime.fcHour; let type = 'RH_and_Omega';//[FGEN,_%CE%B8%E2%82%91,_Omega,RH_and_Omega,Normal_Wind,In-Plane_Wind] 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=`); // 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= }, showCross(p0 = { lat: 21, lon: 120 }, p1 = { lat: 23, lon: 130 }, runtime = '2020112812', fcHour = '18') { let fcTime = utils.getFcTime(); const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH'); runtime = initTime.format('YYYYMMDDHH'); fcHour = fcTime.fcHour; // let type = 'RH_and_Omega';//[FGEN,_%CE%B8%E2%82%91,_Omega,RH_and_Omega,Normal_Wind,In-Plane_Wind] // 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=`); 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}`); }, showTimeSeries(point = { lat: 21, lon: 120 }) { utils.log(point); // showSvgTC() // .catch(err=>{ // console.error(err); // }); if (point.mouseXY) { const elem = document.getElementById('map-pointer'); elem.style.marginLeft = point.mouseXY.x + 29.31 + 'px'; elem.style.marginTop = point.mouseXY.y - 33.066 + 'px'; } const fcTime = utils.getFcTime(); const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH'); const sT = initTime.format('YYYY-MM-DD%20HH:mm:ss'); const eT = moment(initTime).add(10 * 24, 'hours').format('YYYY-MM-DD%20HH:mm:ss'); point.lat = utils.fix2Grid(point.lat); point.lon = utils.fix2Grid(point.lon);//取格点位置 getTimeSeries(model = 'ecmwfthin', sT, eT, lon = point.lon, lat = point.lat, eles = ['t2mm', 'mn2t', 'mx2t', 'u10m', 'v10m', 'tcco', 'lcco', 'tppm']); }, fix2Grid(number, interval = 0.125) { let fixNum = Math.round(number / interval) * interval; return fixNum; }, getFcTime() { const fcHour = typeof (unsafeWindow.forecastHour) === 'string' ? unsafeWindow.forecastHour : unsafeWindow.forecastHour[0]; let idate = unsafeWindow.$settings.date; if (!idate.length) throw new Error('日期为空错误'); if (idate[6] == '-') idate = idate.slice(0, 5) + '0' + idate.slice(5); if (idate.length === 9) idate = idate.slice(0, 8) + '0' + idate.slice(8); const hr = unsafeWindow.$settings.HH; return { date: idate, hr, fcHour };//format->date:'2019-01-22;, hr:'12';fcHour:'000' }, getTimeSeries(point = { lat: 21, lon: 115 }, model = 'giftdaily', eles = [t2mm, tmax, tmin], start = '2014-11-22%2000:00:00') { //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 }, calWind(u10, v10) { const iSpeed = Math.sqrt(Math.pow(u10, 2) + Math.pow(v10, 2));//风速 const iR = Math.sign(v10) * Math.acos(u10 / iSpeed);//标准坐标系弧度 const arrowR = iR - Math.PI / 2;//矢量箭头偏移弧度 let northDir = -(iR + Math.PI - Math.PI / 2);//与北向的角度差 if (northDir < 0) { northDir = northDir + Math.PI * 2; } const dir = northDir / Math.PI * 180; if (dir > 360) dir = dir - 360; return { speed: iSpeed, rotation: arrowR, northDir: northDir }; }, renderArrow(param, api) { let arrowSize = 10; var point = api.coord([ api.value(2),//dims.timeIndex api.value(0)//5//api.value(dims.windSpeed) ]); return { type: 'path', shape: { //pathData: 'M31 16l-15-15v9h-26v12h26v9z', pathData: 'M250 0 L140 350 L250 250 L360 350 Z', x: -arrowSize / 2, y: -arrowSize / 2, width: arrowSize, height: arrowSize }, rotation: api.value(1),//api.value(dims.R),//Math.PI / 8 * index; position: point, style: api.style({ stroke: '#555', lineWidth: 1, fill: 'green', }) }; }, createElement(type = 'div', props = {}) { let newEle = document.createElement(type); for (let attr in props) { newEle.setAttribute(attr, props[attr]); } return newEle; }, getImgNow(imgSrc) { // TODO 改进匹配规则 let fcTime = utils.getFcTime(); //const initTime = moment(fcTime.date+fcTime.hr,'YYYY-MM-DDHH'); // runtime = initTime.format('YYYYMMDDHH'); fcHour = fcTime.fcHour; let url = imgSrc || unsafeWindow.imgSrc[fcHour]; if (!url) throw new Error('无法获取初始图像'); // url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_000_2019012800.png'; const reg = /(http:.*?\/)(\d{8})(.*?_)(\d+)_(\d+)(.*?$)/; const matchUrl = url.match(reg); /*0: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_030_2019012800.png" 1: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/" 2: "20190128" 3: "/ecmwffine_hn_mslp_" 4: "030" 5: "2019012800" 6: ".png" index: 0 input: "http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20190128/ecmwffine_hn_mslp_030_2019012800.png" length: 7 */ const base = [matchUrl[1], matchUrl[3], matchUrl[6]]; const model = base[0].match(/znwp\/(.*?)\//)[1]; // console.log(base[1]); const eleName = base[1].match(/_.*?_(.+?)_$/)[1]; const date = matchUrl[2]; const hour = matchUrl[4]; const initDate = matchUrl[5]; const imgInfo = { url, date, base, hour, initDate, model, eleName, getUrl(date = date, hour = hour, initDate = initDate) { return base[0] + date + base[1] + hour + '_' + initDate + base[2]; } } return imgInfo; }, matchImgPattern(pat = { elem: "t2mm", elemCN: "2m气温", region: "cn", model: "ecmwffine_b", modelFileName: "ecmwffine" }) { let base0 = `http://10.148.8.228/files_home/znwp/${pat.model}/${pat.region}/`; let base1 = `/${pat.modelFileName}_${pat.region}_${pat.elem}_`; return { // base:['http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/','/ecmwffine_hn_mslp_','.png'], base: [base0, base1, '.png'], }; }, getImgUrl(base, date, hour, initDate) { return base[0] + date + base[1] + hour + '_' + initDate + base[2]; }, paddingInt(num, length = 3) { //这里用slice和substr均可 return (Array(length).join("0") + num).slice(-length); }, getElemRelPos(e, t, n) {// e.target, e.clientX, e.clientY var a = e.getBoundingClientRect(), r = getComputedStyle(e); return { x: t - (a.left + parseFloat(r.paddingLeft) + parseFloat(r.borderLeftWidth)), y: n - (a.top + parseFloat(r.paddingTop) + parseFloat(r.borderTopWidth)) }; }, }; var NWP_init = function () { var fcHour = document.getElementById('forecast_hour'); var iniTime = document.getElementById('create_day'); var infoBar = document.getElementById('pic_info'); var referNode = document.getElementById('to_contrast'); var divTime = document.createElement('span'); divTime.textContent = 'hello world'; divTime.setAttribute('class', 'lcTime'); divTime.style.position = 'relative'; divTime.style.float = 'right'; divTime.style.right = '120px'; infoBar.insertBefore(divTime, referNode); // document.querySelector("#forecast_hours div").textContent = "日期"; // create an observer instance var UTC8 = new MutationObserver(function (mutations) { // TODO 对分钟变化变量的修正 var dateString = iniTime.textContent.match(/(\d+).*?(\d+).*?(\d+).*?(\d+)/); var fcDate = []; fcDate[0] = Number(dateString[1]); fcDate[1] = Number(dateString[2]); fcDate[2] = Number(dateString[3]); fcDate[3] = Number(dateString[4]); fcDate[4] = Number(fcHour.textContent.match(/\d+/)); fcDate[5] = new Date(fcDate[0], fcDate[1] - 1, fcDate[2], fcDate[3] + fcDate[4] + 8); var localTime = String(fcDate[5].getMonth() + 1) + '月' + fcDate[5].getDate() + '日' + fcDate[5].getHours() + '时 GMT+8'; divTime.textContent = localTime; }); // configuration of the observer: var config = { attributes: true, childList: true, characterData: true }; UTC8.observe(fcHour, config); // later, you can stop observing //observer.disconnect(); // // ///////////////////////////////////////////////////////////// // /// ////////////////////修改时效列///////////////////////////////////////// var alterTimelist = function (mutations) { //alert(timeBar.length); // console.log('修改时效'); if (userConfig.timelineMove) { timeMoveOver();//是否启用滑动时间触发 } if (!userConfig.alterDate) return; // 不修改则直接返回 var dateString = iniTime.textContent.match(/(\d+).*?(\d+).*?(\d+).*?(\d+)/); var fcDate = []; fcDate[0] = Number(dateString[1]); fcDate[1] = Number(dateString[2]); fcDate[2] = Number(dateString[3]); fcDate[3] = Number(dateString[4]); for (let i = 0; i < timeBar.length; i++) { let oValue = timeBar[i].value; fcDate[4] = Number(timeBar[i].value); fcDate[5] = new Date(fcDate[0], fcDate[1] - 1, fcDate[2], fcDate[3] + fcDate[4] + 8); let iday = String(fcDate[5].getDate()); iday = Array(2 > iday.length ? 2 - iday.length + 1 || 0 : 0).join(0) + iday; let ihour = String(fcDate[5].getHours()); ihour = Array(2 > ihour.length ? 2 - ihour.length + 1 || 0 : 0).join(0) + ihour; let localTime = iday + ' ' + ihour + ' ;'; let styleText = '#' + timeBar[i].getAttribute("id") + ':before{white-space:pre;content: " ' + localTime + ' "}'; GM_addStyle(styleText); switch (fcDate[5].getHours()) { // case 5: // timeBar[i].style.cssText = "border-left:2px solid #9B30FF"; // break; // case 14: // timeBar[i].style.cssText = "border-left:2px solid #EE3B3B"; // break; case 20: timeBar[i].style.cssText = "border-bottom:1px dotted #8E8E8E;border-left:2px solid #ffffff;"; break; default: timeBar[i].style.cssText = "border-left:2px solid #ffffff;"; } } }; ///////////////////////////////////////////////////////////// /// var selectObserver = new MutationObserver(alterTimelist); // configuration of the observer: var timeBar = document.querySelector("#forecast_hours select"); var config2 = { attributes: false, childList: true, characterData: false, }; selectObserver.observe(timeBar, config2); GM_addStyle("#forecast_hours option{width: 50px!important; overflow: hidden!important;}"); //-----------------------------------------------------------------------------// //-- 添加多模式、多要素按钮mvBtnObesrver, initMvMode---// let imgObserver = new MutationObserver(fitImgLoc); imgObserver.observe(timeBar, config2); //-----------------------------------绑定坐标-----------------------------------// function fitImgLoc() { // 绑定img包含的div元素 const isMap = utils.changeRegion(unsafeWindow._region); // 改变地图; // console.log(isMap); if (!isMap) { proxyConfig.showTcMode = false; return; // TODO 不是地图需要去除掉经纬度计算 } else { if (proxyConfig.showTcMode) { let currentFcTime = utils.getFcTime(); let currentTime = currentFcTime.date + currentFcTime.hr; let currentRegion = unsafeWindow._region; console.log(currentTime, proxyConfig.preTime); console.log(currentRegion, proxyConfig.preRegion); if (currentTime != proxyConfig.preTime || currentRegion != proxyConfig.preRegion) { showSvgTC(); // 绘制台风 } else { ''; } } /////TODO 判断地图逻辑分离, 不是地图应该取消掉交互模式和latlon显示功能 const currMap = helperConfig.region[helperConfig.currentRegion]; const currProjection = currMap.projection; //let matchLoc = ({}); //let param = ({}); switch (currProjection) { case 'Mercator': { const dims = currMap.latLon; const lat1 = Math.PI / 180.0 * dims.lat0; const lat2 = Math.PI / 180.0 * dims.lat1; const n1 = dims.yBottom; const n2 = dims.yTop; // console.log(dims); const param1 = utils.projection.Mercator.calBasicInfo( lat1, lat2, n1, n2 ); helperConfig.matchParam = { ...param1, xRight: dims.xRight, xLeft: dims.xLeft, lon0: dims.lon0, lon1: dims.lon1, }; // console.log(helperConfig.matchParam); helperConfig.matchLoc = utils.projection.Mercator.calLatLon; helperConfig.matchImgXY = utils.projection.Mercator.calImgLoc; break; } case 'Lambert': { const LamDim = currMap.latLon; /* const LamParam1 = [ LamDim.refLon0, LamDim.refLat0, LamDim.m0, LamDim.n0, LamDim.refLat1, LamDim.refLat2, LamDim.lonr, LamDim.latr, LamDim.mr, LamDim.nr]; */ const LamParam2 = utils.projection.Lambert.calBasicInfo(LamDim); // console.log(LamParam2); helperConfig.matchParam = LamParam2; helperConfig.matchLoc = utils.projection.Lambert.calLatLon; helperConfig.matchImgXY = utils.projection.Lambert.calImgLoc; break; } default: helperConfig.matchParam = currMap.latLon; helperConfig.matchLoc = utils.projection.Equidistant.calLatLon; helperConfig.matchImgXY = utils.projection.Equidistant.calImgLoc; break; } //let wrapDiv = document.querySelector('#pic_frame div'); let wrapDiv = document.querySelector('#pic_frame'); // document.addEventListener('keyup', console.log(elemsConfig.fixPoint)); if (wrapDiv) { wrapDiv.addEventListener('mousemove', utils.debounce(getMouseLatLon, 100)); // wrapDiv.addEventListener('mousemove', getMouseLatLon); // console.log(wrapDiv.clientWidth);// offsetWidth , clientWidth, scrollWidth } } } function getElemRelPos(e, t, n) {// e.target, e.clientX, e.clientY var a = e.getBoundingClientRect(), r = getComputedStyle(e); /** * e.getBoundingClientRect() * bottom: 916.1999969482422 height: 716 left: 686.9000244140625 right: 1565.1333618164062 top: 200.1999969482422 width: 878.2333374023438 x: 686.9000244140625 y: 200.1999969482422 borderLeftWidth: "1px" borderTopWidth: "1px" paddingLeft: "0px" paddingTop: "0px" */ return { x: t - (a.left + parseFloat(r.paddingLeft) + parseFloat(r.borderLeftWidth)), y: n - (a.top + parseFloat(r.paddingTop) + parseFloat(r.borderTopWidth)) }; } function getMouseLatLon(event) { // console.log(event); // console.log(event.target); // let target = event.target;// let target = document.querySelector('#pic_frame img[style~="inline;"]');// // console.log(target); const mouseXY = getElemRelPos(target, event.clientX, event.clientY); // 相对图像的像素位置{x,y} const loc = helperConfig.matchLoc(mouseXY, helperConfig.matchParam); elemsConfig.latLonInput.lat.innerHTML = loc.lat; // mouseXY.y elemsConfig.latLonInput.lon.innerHTML = loc.lon; // mouseXY.x // elemsConfig.latLonInput.lat.innerHTML = mouseXY.y // elemsConfig.latLonInput.lon.innerHTML = mouseXY.x elemsConfig.fixPoint = { lat: loc.lat, lon: loc.lon, mouseXY }; return { lat: loc.lat, lon: loc.lon, mouseXY }; } //------------------------------24小时跳跃-------------------------------------// const timeJump = function () { //var hourBar = document.getElementById('from_hour');float-l var jumpParent = document.querySelector('.float-l'); var pre24 = document.createElement('button'); pre24.addEventListener("click", function () { timeTrigger(-24); }); pre24.textContent = "-24"; jumpParent.appendChild(pre24); var next24 = document.createElement('button'); next24.addEventListener("click", function () { timeTrigger(24); }); next24.textContent = "+24"; jumpParent.appendChild(next24); var timeTrigger = function (timer) { let selectedVal = timeBar[timeBar.selectedIndex].getAttribute("data-hour"); let nextVal = String(Number(selectedVal) + timer); var posi = 3; nextVal = Array(posi > nextVal.length ? posi - nextVal.length + 1 || 0 : 0).join(0) + nextVal; let nextopt = timeBar.querySelector("#option_" + nextVal); //alert(nextopt); if (!nextopt) return; timeBar[timeBar.selectedIndex].selected = false; nextopt.selected = true; // var changeEvt = document.createEvent('HTMLEvents'); // changeEvt.initEvent('change',true,true); // timeBar.dispatchEvent(changeEvt); var changeEvt = new Event('change', { 'bubbles': true }); timeBar.dispatchEvent(changeEvt); }; }; timeJump(); /////切换时效 function switchDate() { userConfig.alterDate = !userConfig.alterDate; if (userConfig.alterDate) { switchDateBtn.textContent = "切换成时效"; alterTimelist(); } else { switchDateBtn.textContent = "切换成日期"; for (let ele of timeBar) { ele.style.cssText = ''; let styleText = '#' + ele.getAttribute("id") + ':before{white-space:pre;content: ""}'; GM_addStyle(styleText); } } } var switchParent = document.querySelector('.float-l'); let switchDateBtn = document.createElement('button'); switchDateBtn.addEventListener("click", switchDate); switchDateBtn.textContent = "切换成日期"; switchParent.appendChild(switchDateBtn); /////end 切换时效 ///// //设置鼠标事件// document.onkeydown = function (evt) { if (evt.key == 'x' && !evt.ctrlKey) { console.log(utils.showSkewT(elemsConfig.fixPoint)); } else if (evt.key == 'x' && evt.ctrlKey) { if (!elemsConfig.point01) { elemsConfig.point01 = Object.assign({}, elemsConfig.fixPoint); } else { elemsConfig.point02 = Object.assign({}, elemsConfig.fixPoint); console.log(elemsConfig.point01, elemsConfig.point02); utils.showVertical(elemsConfig.point01, elemsConfig.point02); elemsConfig.point01 = null; elemsConfig.point02 = null; } } if (evt.key == 'c' && !evt.ctrlKey && !evt.altKey) { utils.showTimeSeries(elemsConfig.fixPoint); } } //设置鼠标事件// /** * 时间鼠标滑过 */ function timeMoveOver() { // var changeEvent = document.createEvent('HTMLEvents'); // changeEvent.initEvent("change", true, true); var changeEvent = new Event('change', { 'bubbles': true }); var clickOpt = (evt) => { console.log(evt.target); timeBar[timeBar.selectedIndex].selected = false; evt.target.selected = true; timeBar.dispatchEvent(changeEvent); } var timeBar = document.querySelector("#forecast_hours select"); var opts = document.querySelectorAll('#forecast_hours option'); opts.forEach(item => { item.addEventListener('mouseover', clickOpt); }); } }; function toggleSelectMode() { let mapPointer = document.getElementById('map-pointer'); let bodyDOM = document.getElementsByTagName('body')[0]; let linePanel = document.getElementById('line_panel'); let selectModePanel = document.getElementById('select-mode-panel'); if (bodyDOM.classList.contains("full-screen-mode")) { bodyDOM.classList.remove("full-screen-mode"); linePanel.style.display = 'none'; selectModePanel.style.display = 'none'; mapPointer.classList.add("display-none"); let crossCanvas = document.getElementById('cv-draw-line'); crossCanvas.classList.add('display-none'); let activeBtn = document.getElementById('edit-helper-panel'); activeBtn.classList.remove('active-button'); let crossBtn = document.getElementById('helper_cross'); crossBtn ? crossBtn.classList.remove('active-button') : ''; return; } else { bodyDOM.classList.add("full-screen-mode"); let contentWidth = window.innerWidth - document.body.clientWidth - 40; if (contentWidth < 200) { linePanel.style.width = '220px'; } else { linePanel.style.width = contentWidth + 'px'; } linePanel.style.display = 'block'; selectModePanel.style.display = 'block'; mapPointer.classList.add("display-none"); mapPointer.classList.remove("display-none"); let activeBtn = document.getElementById('edit-helper-panel'); activeBtn.classList.add('active-button'); // let crossBtn = document.getElementById('helper_cross'); // activeBtn.classList.add('active-button'); } const imgXY = latlon2XY({ lon: 113.375, lat: 23.125 }); utils.showTimeSeries({ lon: '113.375', lat: '23.125', mouseXY: imgXY }); } /** * 切换垂直剖面 */ function toggleCrossSection() { let crossCanvas = document.getElementById('cv-draw-line'); let activeBtn = document.getElementById('helper_cross'); crossCanvas.classList.toggle('display-none'); if (crossCanvas.classList.contains('display-none')) { activeBtn.classList.remove('active-button'); } else { activeBtn.classList.add('active-button'); } } // 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 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']) { 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}`; utils.log('获取数据'); // showNotification('正在从数据中心获取数据'); let notification = GM_notification({ text: '正在从数据中心获取数据!', image: 'http://10.148.8.228/images/logo.png', title: '提示', timeout: 3000 }); // console.log(notification); if (notification) setTimeout(() => notification.remove(), 3000); // console.log(url); GM_xmlhttpRequest({ //获取时间序列 method: 'GET', synchronous: false, url: url, onload: function (reDetails) { if (reDetails.status !== 200 && reDetails.status !== 304) { console.error('获取URL错误'); // showNotification('数据中心数据获取异常'); let errorNotice = GM_notification({ text: '数据中心数据获取异常', image: 'http://10.148.8.228/images/logo.png', title: '接口异常', timeout: 3000 }); if (errorNotice) setTimeout(() => errorNotice.remove(), 3000); return; } getLocation(lat, lon); const data = JSON.parse(reDetails.responseText); //console.log(data.DATA); if (data.DATA.length == 0) { console.error('此时次数据为空,请等待更新'); // showNotification('此时次数据为空,请等待更新'); let errorNotice = GM_notification({ text: '此时次数据为空,请等待更新', image: 'http://10.148.8.228/images/logo.png', title: '接口异常', timeout: 3000 }); if (errorNotice) setTimeout(() => errorNotice.remove(), 3000); return; } let series = decodeSeries(data.DATA, 241); series[1] = mixUndefined(series[0].map(v => v[1]), series[1]); series[2] = mixUndefined(series[0].map(v => v[1]), series[2]); //console.log(series[1]); drawLine(series); } }); } /** * 从百度接口获取地址 * @param {Number} lat 纬度 * @param {Number} lon 经度 */ function getLocation(lat, lon) { let ak = 'kMW5fXfhhsMat6Ud9jYPqnxCRQGbl2eV'; let url = `http://api.map.baidu.com/geocoder/v2/?callback=renderReverse&location=${lat},${lon}&output=json&pois=1&ak=${ak}`; let latlonSpan = document.querySelectorAll('#line_info > span'); latlonSpan[0].innerHTML = lat; latlonSpan[1].innerHTML = lon; latlonSpan[2].innerHTML = ''; // console.log(url); GM_xmlhttpRequest({ //获取时间序列 method: 'GET', synchronous: false, url: url, onload: function (reDetails) { if (reDetails.status !== 200 && reDetails.status !== 304) { console.error('获取百度地址异常'); //showNotification('数据中心数据获取异常'); return; } let raw = reDetails.responseText.replace('renderReverse&&renderReverse(', '').slice(0, -1); const addreJSON = JSON.parse(raw); if (addreJSON.r###lt.formatted_address == '') { return; } else { latlonSpan[2].innerHTML = ' 地址: ' + addreJSON.r###lt.formatted_address; } //console.log(data); } }); } function mixUndefined(index, data) { let newData = []; for (let i of index) { let value = data.find(v => v[1] == i); if (value === undefined) { newData.push([undefined, i]); } else { newData.push([value[0], i]); } } return newData } /**解析数据 */ function decodeSeries(data = [], len = 241) { if (!data.length) return []; let splitData = []; let eles = data.length / len;//元素个数 for (let ie = 0; ie < eles; ie++) {//看有几个要素 splitData.push(data.slice(ie * len, (ie + 1) * len)); } splitData = splitData.map(data => data.map((v, i) => [Number(v), i]).filter(v => v[0] > -999));//分离出[数值,时效]//-999.900024 // console.log(splitData); return splitData; } function drawLine(series = []) { // getTimeSeries(); utils.log('绘图'); if (series.length == 0) return console.log('空数据'); const tempChart = echarts.init(document.getElementById('show-temp')); let fcTime = utils.getFcTime(); let initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH').add(8, 'hours');//北#时 let temp = series[0].map(v => (v[0] - 273.15).toFixed(2)); let mn2t = series[1].map(v => { if (v[0] === undefined) { return undefined; } else { return (v[0] - 273.15).toFixed(2); } }); let mx2t = series[2].map(v => { if (v[0] === undefined) { return undefined; } else { return (v[0] - 273.15).toFixed(2); } }); let xTime = series[0].map(v => { const hour = v[1]; return moment(initTime).add(hour, 'hours').format('DD-HH'); }); // console.log(mn2t); // 指定图表的配置项和数据 const OptionTemp = { /* title: { text: 'Temp.', // left: 'center' }, */ tooltip: { trigger: 'axis', }, toolbox: { // y: '30px', feature: { dataZoom: { yAxisIndex: 'none' }, restore: {}, //saveAsImage: {} } }, grid: { top: 10, bottom: 45, right: 11, left: 25 }, dataZoom: [ { show: true, realtime: true, start: 0, end: 80, // handleSize: '50%', height: '20', bottom: '0', }, { type: 'inside', realtime: true, start: 0, end: 80, } ], legend: { y: '-5', data: ['Temp', 'Pre6min', 'Pre6max'] }, xAxis: { type: 'category', data: xTime, // boundaryGap : false, //splitArea : {show : true}, splitLine: { show: true }, axisLine: { onZero: true }, }, yAxis: [{ name: '℃', type: 'value', scale: true, axisLabel: { formatter: '{value}' }, }, ], series: [ { name: 'Temp', smooth: true, //symbol: 'triangle', //symbolSize: 5, lineStyle: { normal: { color: 'green', width: 2, } },//type: 'dashed' //itemStyle: {normal: {borderWidth: 1,borderColor: 'green',color: 'green'}}, type: 'line', data: temp, }, { name: 'Pre6min', smooth: true, connectNulls: true, symbolSize: 0, lineStyle: { normal: { color: 'blue', width: 1, type: 'dashed' } },//type: 'dashed' type: 'line', data: mn2t, }, { name: 'Pre6max', smooth: true, connectNulls: true, symbolSize: 0, lineStyle: { normal: { color: 'red', width: 1, type: 'dashed' } },//type: 'dashed' type: 'line', data: mx2t, }, ] }; // 使用刚指定的配置项和数据显示图表。 tempChart.setOption(OptionTemp); /** * 风 */ const windChart = echarts.init(document.getElementById('show-wind')); let wind = series[3].map((v, i) => utils.calWind(v[0], series[4][i][0])); let speed = wind.map(v => v.speed); let rotation = wind.map(v => v.rotation); let windData = speed.map((v, i) => [v, rotation[i], i]); let dims = { speed: 0, rotation: 1, timeIndex: 2, } const optionWind = { /* title: { text: 'Wind', }, */ tooltip: { trigger: 'axis', }, toolbox: { feature: { dataZoom: { yAxisIndex: 'none' }, // restore: {}, //saveAsImage: {} } }, grid: { show: true, top: 10, bottom: 45, right: 11, left: 25 }, dataZoom: [ { show: true, realtime: true, start: 0, end: 80, // handleSize: '50%', height: '20', bottom: '0', }, { type: 'inside', realtime: true, start: 0, end: 80, } ], legend: { y: '-5', data: ['w10m'], }, xAxis: { type: 'category', data: xTime, splitLine: { show: true }, axisLine: { onZero: true }, }, yAxis: { name: 'm/s', type: 'value', axisLabel: { formatter: '{value}' }, }, series: [ { name: 'w10m', type: 'line', smooth: true, lineStyle: { normal: { width: 2, } },//type: 'dashed' itemStyle: { normal: { borderWidth: 1, borderColor: 'black', color: 'black' } }, data: speed.map(v => v.toFixed(1)), }, { type: 'custom', name: 'dir', renderItem: utils.renderArrow, encode: { x: dims.timeIndex, y: dims.speed, }, data: windData, z: 10 }, ] }; windChart.setOption(optionWind); /** * 云 */ const cloudChart = echarts.init(document.getElementById('show-cloud')); let totalCloud = series[5].map(v => v[0] * 100); let lowCloud = series[6].map(v => v[0] * 100); const optionCloud = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, toolbox: { feature: { dataZoom: { yAxisIndex: 'none' }, // restore: {},//saveAsImage: {} } }, grid: { show: true, top: 10, bottom: 45, right: 11, left: 30 }, dataZoom: [ { show: true, realtime: true, start: 0, end: 80, height: '20', bottom: '0', }, { type: 'inside', realtime: true, start: 0, end: 80, } ], legend: { y: '-5', data: ['中高云', '低云'], }, xAxis: { type: 'category', data: xTime, splitLine: { show: true }, axisLine: { onZero: true }, }, yAxis: { name: '%', type: 'value', axisLabel: { formatter: '{value}' }, }, series: [ { name: '低云', type: 'bar', stack: '云量', data: lowCloud.map(v => v.toFixed(1)), }, { name: '中高云', type: 'bar', stack: '云量', data: totalCloud.map((v, i) => (v - lowCloud[i]).toFixed(1)), }, ] }; cloudChart.setOption(optionCloud); /** * 降水 */ const preChart = echarts.init(document.getElementById('show-pre')); let preSeri = series[7]; let precipitaion = preSeri.map((v, i) => { let pre = i === 0 ? v[0] : v[0] - preSeri[i - 1][0]; //(v[0]*1000).toFixed(1) return (pre * 1000).toFixed(1); } ); //let lowCloud = series[6].map(v=>v[0]); const optionPre = { tooltip: { trigger: 'axis', axisPointer: { // 坐标轴指示器,坐标轴触发有效 type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' } }, toolbox: { feature: { dataZoom: { yAxisIndex: 'none' }, } }, grid: { show: true, top: 10, bottom: 45, right: 11, left: 30 }, dataZoom: [ { show: true, realtime: true, start: 0, end: 80, height: '20', bottom: '0', }, { type: 'inside', realtime: true, start: 0, end: 80, } ], legend: { y: '-5', data: ['Precipitaion'], }, xAxis: { type: 'category', data: xTime, splitLine: { show: true }, axisLine: { onZero: true }, }, yAxis: { name: 'mm', type: 'value', axisLabel: { formatter: '{value}' }, }, series: [ { name: 'Precipitaion', type: 'bar', stack: 'Rain', data: precipitaion, }, ] }; preChart.setOption(optionPre); } function createPanel() { //-创建面板-// const panelWrap = document.createElement("div"); panelWrap.setAttribute("id", "helper_panel"); panelWrap.setAttribute("class", "top_panel show_panel"); panelWrap.innerHTML = '多图模式: '; /* 设置添加对比模式*/ const compareMode = utils.createElement('div', { class: 'single-btn' }); compareMode.innerHTML = `<span class="panel-button" id="open-compare">多起报</span>`; panelWrap.appendChild(compareMode); /* 设置添加多时效模式*/ const mvTime = utils.createElement('div', { class: 'single-btn' }); mvTime.innerHTML = `<span class="panel-button" id="open-mvTime">多时效</span>`; panelWrap.appendChild(mvTime); /* 添加多模式 */ const mvModelBtn = utils.createElement('div', { class: 'dropdown mv-model-panel' }); mvModelBtn.innerHTML = `<button class="dropbtn"><div class="tooltip">多模式<span class="tooltiptext">ECMWF, NCEP, Grapes全球, ##台风模式9km</span></div></button> <div class="dropdown-content"> <a href="#" class="tooltip"> EC/NCEP/G全球/G9km <span class="tooltiptext">ECMWF, NCEP, Grapes全球, Grages9kmEC</span> </a> <a href="#" class="tooltip"> EC/NCEP/G9km/G-meso <span class="tooltiptext">ECMWF, NCEP, Grages9kmEC, Grapes-Meso</span> </a> <a href="#" class="tooltip"> EC/NCEP/G9km/GZ-3km <span class="tooltiptext">ECMWF, NCEP, Grages9kmEC, 华南3km高分辨率</span> </a> </div> `; panelWrap.appendChild(mvModelBtn); /* 设置添加多图模式 const mvElem = document.createElement('div'); mvElem.innerHTML = `<span class="panel-button" id="open-mv">多要素</span>`; panelWrap.appendChild(mvElem);*/ /* 设置添加点选模式*/ const panelWrap2 = utils.createElement('div', { id: 'edit-helper-panel', class: 'top_panel show_panel' }); const pointMode = utils.createElement('div', { class: 'single-btn' }); pointMode.innerHTML = `<span class="panel-button">交互模式</span>`; panelWrap2.appendChild(pointMode); pointMode.addEventListener('click', () => { toggleSelectMode() }); /* 设置Lat lon面板*/ //-------// //-注册到全局变量-// elemsConfig.latLonInput = { lat: document.getElementById('helper_lat'), lon: document.getElementById('helper_lon'), }; /* 设置添加增强显示*/ const panelWrap3 = utils.createElement('div', { id: 'enhanced-helper-panel', class: 'top_panel show_panel' }); const tcModeBtn = utils.createElement('div', { class: 'single-btn', id: 'tc-mode-btn' }); tcModeBtn.innerHTML = `<span class="panel-button">台风路径</span>`; panelWrap3.appendChild(tcModeBtn); tcModeBtn.addEventListener('click', () => { toggleTCMode('tcShow') }); const detTCbtn = utils.createElement('div', { class: 'single-btn display-none active-button', id: 'tc-det-btn' }); detTCbtn.innerHTML = `<span class="panel-button">EC确定性预报</span>`; panelWrap3.appendChild(detTCbtn); detTCbtn.addEventListener('click', () => { toggleTCMode('detShow') }); const ensTCbtn = utils.createElement('div', { class: 'single-btn display-none active-button', id: 'tc-ens-btn' }); ensTCbtn.innerHTML = `<span class="panel-button">EC集合预报</span>`; panelWrap3.appendChild(ensTCbtn); ensTCbtn.addEventListener('click', () => { toggleTCMode('ensShow') }); /* 注册到body*/ const ibody = document.getElementsByTagName("body")[0]; ibody.appendChild(panelWrap); ibody.appendChild(panelWrap2); ibody.appendChild(panelWrap3); } // 添加面板样式 function createTlinePanel() { //-创建面板-// const fragment = document.createDocumentFragment(); const panelWrap = document.createElement("div"); panelWrap.setAttribute("id", "line_panel"); panelWrap.setAttribute("class", "show_panel"); panelWrap.style.display = 'none'; const infoWrap = document.createElement('div'); infoWrap.setAttribute("id", "line_info"); infoWrap.innerHTML = 'EC模式预报<br>纬度:<span></span> 经度:<span></span><span></span>'; panelWrap.appendChild(infoWrap); /* 设置添加temp*/ const tempWrap = document.createElement('div'); tempWrap.innerHTML = `温度<div id="show-temp"></div>`; panelWrap.appendChild(tempWrap); const windWrap = document.createElement('div'); windWrap.innerHTML = `风MSLP<div id="show-wind"></div>`; panelWrap.appendChild(windWrap); const preWrap = document.createElement('div'); preWrap.innerHTML = `降水<div id="show-pre"></div>`; panelWrap.appendChild(preWrap); const cloudWrap = document.createElement('div'); cloudWrap.innerHTML = `云量<div id="show-cloud"></div>`; panelWrap.appendChild(cloudWrap); fragment.appendChild(panelWrap); const ibody = document.getElementsByTagName("body")[0]; ibody.appendChild(fragment); //-------// } function createSelectModePanel() { //-创建面板-// const fragment = document.createDocumentFragment(); const panelWrap = document.createElement("div"); panelWrap.setAttribute("id", "select-mode-panel"); panelWrap.setAttribute("class", "show_panel"); panelWrap.style.display = 'none'; panelWrap.innerHTML = `快捷键[C] -> 单点时间序列`; /*<br> [X] -> 垂直探空; 两次[Ctrl+X] -> 垂直剖面 */ fragment.appendChild(panelWrap); const ibody = document.getElementsByTagName("body")[0]; ibody.appendChild(fragment); /* 设置Lat lon面板*/ const latLonWarp = document.createElement("div"); latLonWarp.setAttribute("id", "helper_latLon"); latLonWarp.setAttribute("class", "show_latLon"); latLonWarp.innerHTML = '<span>Lat <span class="fixLoc" id="helper_lat"></span> Lon <span class="fixLoc" id="helper_lon"></span></span>'; panelWrap.appendChild(latLonWarp); /* 设置垂直剖面面板*/ const crossWrap = utils.createElement('div', { id: 'helper_cross', class: 'top_panel' }) crossWrap.innerHTML = `<div class="panel-button single-btn">垂直剖面</div>`; panelWrap.appendChild(crossWrap); crossWrap.addEventListener('click', () => { toggleCrossSection() }); //-------// } //console.log(compareMode.imgDOM); // compareMode.forward(); /**绑定多模式 */ function bindingMvModel() { let dropList = document.querySelectorAll('.mv-model-panel .dropdown-content>a'); for (let elem of dropList) { elem.addEventListener('click', clickDrop); } let dropBtn = document.querySelector('.dropbtn'); dropBtn.addEventListener('click', clickDrop); function clickDrop(evt) { const current = evt.currentTarget; let index; // console.log(current.classList); if (current.classList.contains('dropbtn')) { index = 0; } else { // console.log(current); const parent = current.parentElement; index = Array.from(parent.children).indexOf(current); // parent.style.display='none'; } let currentImgInfo = utils.getImgNow(); matchedRule = multiModelRule[index]; let srcList = getMVSrcListFromRule(currentImgInfo.url, matchedRule); // console.log(srcList); elemsConfig.mvImgDOM.matchedSrcList = srcList; elemsConfig.mvImgDOM.mode = 'multiModel'; // elemsConfig.mvImgDOM.mode = 'multiModel'; userConfig.mvMode.openCompare(); } } //创建对比框 function createComparePanel() { const panel = document.createDocumentFragment(); const mainWrapper = utils.createElement('div', { id: 'compare-main', class: 'display-none' }); const controlWrapper = utils.createElement('div', { class: 'compare-warpper' }); controlWrapper.innerHTML = `<div id="compare-backward" class="my-button">step -6</div><div class="my-button" id="compare-foreward">step +6</div>`; controlWrapper.innerHTML += `<select id="compare-interval"><option value="48">起报间隔48小时</option> <option value="24">起报间隔24小时</option> <option selected="selected" value="12">起报间隔12小时</option></select> <div id="close-compare" class="my-button">关闭对比</div> <span class="info"></span>`; const imgWrapper = utils.createElement('div', { class: 'compare-img-main' }); imgWrapper.innerHTML = `<div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div> <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div> <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div> <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>`; panel.append(mainWrapper); mainWrapper.append(controlWrapper, imgWrapper); //mainWrapper.append(forewardBut,backwardBut,intervalInput); document.body.append(panel); } function createCompareMode() { const compareHandler = { set(obj, prop, value, receiver) { if (prop === 'interval') { obj.interval = value; receiver.firstImg = obj.firstImg; //obj.img[1] = obj.img[0] + value; //obj.img[2] = obj.img[0] + value*2; //obj.img[3] = obj.img[0] + value*3; } else if (prop === 'firstHr') { // console.log(obj.imgInfo.toSource()); // const initURL = obj.imgInfo.url; const date = obj.imgInfo.date; const initDate = obj.imgInfo.initDate; const hour = utils.paddingInt(value, 3); const url = obj.imgInfo.getUrl(date, hour, initDate) receiver.firstImg = url; //receiver.interval = obj.interval; } else if (prop === 'imgInfo') { console.log('设置初始图像为:' + value.url); } else if (prop === 'firstImg') { const imgList = obj.fit2sameday(value); // console.log(imgList); obj.showInfo(value); imgList.forEach((imgSrc, i) => { receiver.img[i] = imgSrc; }) //obj.img[0] = imgList; // obj.img[1] = ; } Reflect.set(obj, prop, value); }, }; const imgHandler = { set(obj, prop, value, receiver) { utils.log(`第${Number(prop) + 1}个图像地址为${value}`); elemsConfig.compareImgDOM.img[prop].src = value; iImgInfo = utils.getImgNow(value); const initTime = moment(iImgInfo.initDate, 'YYYYMMDDHH'); const nowTime = moment(initTime).add(Number.parseInt(iImgInfo.hour) + 8, 'hours'); elemsConfig.compareImgDOM.info[prop].innerHTML = `起报: ${initTime.format('MM-DD HH')} UTC, 【北#时${nowTime.format('MM月DD日HH时')}(${iImgInfo.hour}h)】${iImgInfo.model}, ${iImgInfo.eleName}`; //console.log(value); //let imgDom = document.querySelectorAll('.compare .imgSrc'); //imgDom[prop].src = value; Reflect.set(obj, prop, value); }, }; let imgSrc = new Proxy([1, 2, 3, 4], imgHandler); const modeProto = { img: imgSrc, firstImg: '', interval: 12, foreward(step = 6) { this.firstHr = this.firstHr + step; // console.log('步进'); }, backward(step = 6) { if (this.firstHr - step >= 0) { this.firstHr = this.firstHr - step; } else { alert('不能再退了'); } }, firstHr: 1, urlMode(base = 'http://10.148') { let url = base; return url; }, initIMG() {// TODO addEventListener('error',(evt)=>{evt.srcElement.src}) let DOMs = document.querySelectorAll('#compare-main .compare-img'); let imgDOM = []; for (let img of DOMs) { img.addEventListener('error', (evt) => { let ele = evt.target; ele.onerror = null; ele.src = '/images/weather/nopic_800_600.jpg'; }); imgDOM.push(img); }; elemsConfig.compareImgDOM.img = imgDOM; let infoDOMs = document.querySelectorAll('#compare-main .compare-img-info'); let infoList = []; for (let ele of infoDOMs) { infoList.push(ele); }; elemsConfig.compareImgDOM.info = infoList; //return imgDOM; }, imgInfo: {}, openCompare() { const wrapper = document.getElementById('compare-main'); //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none') wrapper.classList.remove('display-none'); try { this.imgInfo = utils.getImgNow(); this.firstHr = Number.parseInt(this.imgInfo.hour); } catch (err) { alert(err.message); console.error(err); } }, closeCompare() { const wrapper = document.getElementById('compare-main'); //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none') wrapper.classList.add('display-none'); // this.imgInfo = utils.getImgNow(); }, fit2sameday(firstImg) { const interval = this.interval; const firstInfo = utils.getImgNow(firstImg); // console.log(firstImg); // console.log(firstInfo); const imgList = [firstImg]; const iniTime = moment(firstInfo.initDate, 'YYYYMMDDHH'); for (let i = 1; i < 4; i++) { const fitTime = moment(iniTime).add(-1 * interval * i, 'hours'); const iDate = moment(fitTime).format('YYYYMMDD'); const iInit = moment(fitTime).format('YYYYMMDDHH'); const hour = Number.parseInt(firstInfo.hour) + interval * i; // console.log('预报时效'+hour); const iHour = utils.paddingInt(hour, 3); const url = firstInfo.getUrl(iDate, iHour, iInit); imgList.push(url); } return imgList; }, changeInterval(evt) { this.interval = Number.parseInt(evt.target.value); // console.log(evt); }, showInfo(img) { const controlInfo = document.querySelector('#compare-main .compare-warpper .info'); const imgInfo = utils.getImgNow(img); const nowTime = moment(imgInfo.initDate, 'YYYYMMDDHH').add(Number.parseInt(imgInfo.hour) + 8, 'hours'); controlInfo.innerHTML = ' UTC+8 ' + nowTime.format('YYYY年MM月DD日HH时'); controlInfo.innerHTML += ' | GMT' + nowTime.add(-8, 'hours').format('YYYY-MM-DD HH:00'); }, }; const compareMode = new Proxy(modeProto, compareHandler); return compareMode; } //compareMode.imgInfo = utils.getImgNow(); //创建多视窗对比框 function createMVPanel() { const panel = document.createDocumentFragment(); const mainWrapper = utils.createElement('div', { id: 'multiviews-main', class: 'display-none' }); const controlWrapper = utils.createElement('div', { class: 'compare-warpper' }); controlWrapper.innerHTML = `<div id="mv-backward" class="my-button">step -6</div><div class="my-button" id="mv-foreward">step +6</div>`; controlWrapper.innerHTML += `<select id="mv-interval"><option selected="selected" value="6">间隔6小时</option> <option value="12">间隔12小时</option> <option value="24">间隔24小时</option></select> <div id="close-mv" class="my-button">关闭多图模式</div> <span class="info"></span>`; const imgWrapper = utils.createElement('div', { class: 'compare-img-main' }); imgWrapper.innerHTML = `<div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div> <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div> <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div> <div class="compare-img-wrapper"><img class="compare-img" src=""><div class="compare-img-info">init Time</div></div>`; panel.append(mainWrapper); mainWrapper.append(controlWrapper, imgWrapper); //mainWrapper.append(forewardBut,backwardBut,intervalInput); document.body.append(panel); } function createMultiviewsMode() { const compareHandler = { set(obj, prop, value, receiver) { if (prop === 'interval') { obj.interval = value; // receiver.firstImg = obj.firstImg; receiver.step = value * obj.imgArr.length; const firstImg = obj.imgArr[0]; let imgInfo = utils.getImgNow(firstImg); let iHour = Number.parseInt(imgInfo.hour); for (let i = 0; i < obj.imgArr.length; i++) { let currentHour = iHour + i * value; const hourString = utils.paddingInt(currentHour); obj.imgArr[i] = utils.getImgUrl(imgInfo.base, imgInfo.date, hourString, imgInfo.initDate); } } else if (prop === 'firstHr') { const date = obj.imgInfo.date; const initDate = obj.imgInfo.initDate; const hour = utils.paddingInt(value, 3); const url = obj.imgInfo.getUrl(date, hour, initDate) receiver.firstImg = url; } else if (prop === 'step') { // console.log('step'); // obj.step = value; document.getElementById('mv-foreward').innerText = 'step +' + value; document.getElementById('mv-backward').innerText = 'step -' + value; } else if (prop === 'imgInfo') { console.log('设置初始图像为:' + value.url); } else if (prop === 'firstImg') { const imgList = obj.fit2sameday(value); // console.log(imgList); obj.showInfo(value); imgList.forEach((imgSrc, i) => { receiver.imgArr[i] = imgSrc; }) //obj.img[0] = imgList; // obj.img[1] = ; } Reflect.set(obj, prop, value); }, }; const imgHandler = { set(obj, prop, value) { utils.log(`第${Number(prop) + 1}个图像地址为${value}`); elemsConfig.mvImgDOM.img[prop].src = value; iImgInfo = utils.getImgNow(value); const initTime = moment(iImgInfo.initDate, 'YYYYMMDDHH'); const nowTime = moment(initTime).add(Number.parseInt(iImgInfo.hour) + 8, 'hours'); // controlInfo.innerHTML = ' UTC+8 '+ nowTime.format('YYYY年MM月DD日HH时'); elemsConfig.mvImgDOM.info[prop].innerHTML = `【北#时${nowTime.format('MM月DD日HH时')}(${iImgInfo.hour}h)】${iImgInfo.model}, ${iImgInfo.eleName}, 起报: ${initTime.format('MM-DD HH')} UTC`; Reflect.set(obj, prop, value); }, }; let imgSrc = new Proxy([1, 2, 3, 4], imgHandler); const modeProto = { imgArr: imgSrc, firstImg: '', interval: 6, step: 6, foreward(step = 6) { // this.firstHr = this.firstHr + step; // console.log(this); this.changeHour(this.step); // console.log('步进'); }, backward(step = -6) { let imgInfo = utils.getImgNow(this.imgArr[0]); // console.dir(imgInfo); // console.log(Number.parseInt(imgInfo.hour) - this.step>=0) if (Number.parseInt(imgInfo.hour) - this.step >= 0) { this.changeHour(-1 * this.step); // console.log('步退'); } else { alert('不能再退了'); } }, changeHour(hr = 6) { let hour = parseInt(hr); for (let i = 0; i < this.imgArr.length; i++) { let iInfo = utils.getImgNow(this.imgArr[i]); let newHour = parseInt(iInfo.hour) + hour; // console.log(newHour); this.imgArr[i] = iInfo.getUrl(iInfo.date, utils.paddingInt(newHour), iInfo.initDate); } }, firstHr: 1, urlMode(base = 'http://10.148') { let url = base; return url; }, initIMG() {// TODO addEventListener('error',(evt)=>{evt.srcElement.src}) let DOMs = document.querySelectorAll('#multiviews-main .compare-img'); let imgDOM = []; for (let img of DOMs) { img.addEventListener('error', (evt) => { let ele = evt.target; ele.onerror = null; ele.src = '/images/weather/nopic_800_600.jpg'; }); imgDOM.push(img); }; elemsConfig.mvImgDOM.img = imgDOM; let infoDOMs = document.querySelectorAll('#multiviews-main .compare-img-info'); let infoList = []; for (let ele of infoDOMs) { infoList.push(ele); }; elemsConfig.mvImgDOM.info = infoList; //return imgDOM; }, imgInfo: {}, openCompare() { const wrapper = document.getElementById('multiviews-main'); //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none') wrapper.classList.remove('display-none'); document.getElementById('mv-interval').classList.remove('display-none'); try { let imgInfo = utils.getImgNow(); if (elemsConfig.mvImgDOM.mode === 'multiElem') { for (let i = 0; i < this.imgArr.length; i++) { const iPatt = elemsConfig.mvImgDOM.matchPattens[i]; // console.log(iPatt); const iMatch = utils.matchImgPattern(iPatt); console.log(iMatch); this.imgArr[i] = utils.getImgUrl(iMatch.base, imgInfo.date, imgInfo.hour, imgInfo.initDate); } } else if (elemsConfig.mvImgDOM.mode === 'multiTime') { this.step = this.interval * this.imgArr.length; let iHour = Number.parseInt(imgInfo.hour); for (let i = 0; i < this.imgArr.length; i++) { let currentHour = iHour + i * this.interval; const hourString = utils.paddingInt(currentHour); this.imgArr[i] = utils.getImgUrl(imgInfo.base, imgInfo.date, hourString, imgInfo.initDate); } } if (elemsConfig.mvImgDOM.mode === 'multiModel' || elemsConfig.mvImgDOM.mode === 'multiElems') { for (let i = 0; i < elemsConfig.mvImgDOM.matchedSrcList.length; i++) { this.imgArr[i] = elemsConfig.mvImgDOM.matchedSrcList[i]; } document.getElementById('mv-interval').classList.add('display-none'); this.step = 6; } } catch (err) { alert(err.message); console.error(err); } }, closeCompare() { const wrapper = document.getElementById('multiviews-main'); //if(wrapper.classList.contains('display-none')) wrapper.classList.remove('display-none') wrapper.classList.add('display-none'); // this.imgInfo = utils.getImgNow(); }, fit2sameday(firstImg) { const interval = this.interval; const firstInfo = utils.getImgNow(firstImg); // console.log(firstImg); // console.log(firstInfo); const imgList = [firstImg]; const iniTime = moment(firstInfo.initDate, 'YYYYMMDDHH'); for (let i = 1; i < 4; i++) { const fitTime = moment(iniTime).add(-1 * interval * i, 'hours'); const iDate = moment(fitTime).format('YYYYMMDD'); const iInit = moment(fitTime).format('YYYYMMDDHH'); const hour = Number.parseInt(firstInfo.hour) + interval * i; // console.log('预报时效'+hour); const iHour = utils.paddingInt(hour, 3); const url = firstInfo.getUrl(iDate, iHour, iInit); imgList.push(url); } return imgList; }, changeInterval(evt) { if (elemsConfig.mvImgDOM.mode === 'multiModel' || elemsConfig.mvImgDOM.mode === 'multiElems') { return confirm('多模式下不支持调整间隔'); } this.interval = Number.parseInt(evt.target.value); // console.log(evt); }, showInfo(img) { const controlInfo = document.querySelector('#multiviews-main .compare-warpper .info'); const imgInfo = utils.getImgNow(img); const nowTime = moment(imgInfo.initDate, 'YYYYMMDDHH').add(Number.parseInt(imgInfo.hour) + 8, 'hours'); controlInfo.innerHTML = ' UTC+8 ' + nowTime.format('YYYY年MM月DD日HH时'); controlInfo.innerHTML += ' | GMT' + nowTime.add(-8, 'hours').format('YYYY-MM-DD HH:00'); }, }; const compareMode = new Proxy(modeProto, compareHandler); return compareMode; } if (location.href.includes('to_fore_homepage.action')) { if (window.frames.length != parent.frames.length) { return; } createSelectModePanel(); createPanel(); NWP_init(); createTlinePanel(); bindingMvModel(); createComparePanel(); const compareMode = createCompareMode();//创建对比DOM框架 compareMode.hook = function (selector = '', listener = '', callback = () => { }, bindObj = compareMode) { var targetEle = document.querySelector(selector); targetEle.addEventListener(listener, () => callback.call(bindObj), false); }; compareMode.hook('#compare-backward', 'click', compareMode.backward, compareMode); compareMode.hook('#compare-foreward', 'click', compareMode.foreward, compareMode); compareMode.hook('#open-compare', 'click', compareMode.openCompare, compareMode); compareMode.hook('#close-compare', 'click', compareMode.closeCompare, compareMode); //compareMode.hook('#compare-interval','change',(evt)=>compareMode.changeInterval(evt),compareMode); document.getElementById('compare-interval').addEventListener('change', (evt) => compareMode.changeInterval(evt)); compareMode.initIMG(); createMVPanel();//创建对比DOM框架 const mvMode = createMultiviewsMode(); mvMode.hook = function (selector = '', listener = '', callback = () => { }, bindObj = mvMode) { var targetEle = document.querySelector(selector); targetEle.addEventListener(listener, () => callback.call(bindObj), false); }; mvMode.hook('#mv-backward', 'click', mvMode.backward, mvMode); mvMode.hook('#mv-foreward', 'click', mvMode.foreward, mvMode); mvMode.hook('#close-mv', 'click', mvMode.closeCompare, mvMode); //compareMode.hook('#compare-interval','change',(evt)=>compareMode.changeInterval(evt),compareMode); document.getElementById('mv-interval').addEventListener('change', (evt) => mvMode.changeInterval(evt)); mvMode.initIMG(); userConfig.mvMode = mvMode; let openMvTime = document.getElementById('open-mvTime'); // console.log(openMvTime) openMvTime.addEventListener('click', () => { elemsConfig.mvImgDOM.mode = 'multiTime'; mvMode.openCompare(); }); createMapIndicator(); } if (location.href.includes('to_fore_homepage.action')) { GM_addStyle(` /* Style The Dropdown Button */ /*background-color: #4CAF50;*/ .dropbtn { background-color: rgb(45,53,63); color: white; font-size: 16px; cursor: pointer; border: none; padding: 5px; border: 1px solid white; } /* The container <div> - needed to position the dropdown content */ .dropdown { position: relative; display: inline-block; margin-left:5px; margin-right:5px; } .dropbtn .tooltiptext{ width: intrinsic; width: -webkit-max-content; width:-moz-max-content; width: max-content; } /* Dropdown Content (Hidden by Default) */ .dropdown-content { display: none; position: absolute; left: 0px; top: 30px; background-color: #f9f9f9; box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); z-index: 1; width: intrinsic; width: -webkit-max-content; width:-moz-max-content; width: max-content; } /* Links inside the dropdown */ .dropdown-content>a { color: black; padding: 6px 8px; text-decoration: none; display: block; border-bottom: 1px dashed #4CAF50; } /* Change color of dropdown links on hover */ .dropdown-content a:hover { background-color: rgb(150, 185, 125); font-weight: bold; color: rgb(255, 255, 255); } /* Show the dropdown menu on hover */ .dropdown:hover .dropdown-content { display: inline-block; } /* Change the background color of the dropdown button when the dropdown content is shown */ .dropdown:hover .dropbtn { background-color: #3e8e41; font-weight: bold; } .tooltip { position: relative; display: inline-block; /*border-bottom: 1px dotted black;*/ } .tooltip .tooltiptext { visibility: hidden; min-width: 120px; background-color: black; color: #fff; text-align: center; border-radius: 6px; padding: 5px; position: absolute; z-index: 2; top: -5px; left: 110%; opacity: 0.8; word-break: keep-all; } .tooltip .tooltiptext::after { content: ""; position: absolute; top: 50%; right: 100%; margin-top: -5px; border-width: 5px; border-style: solid; border-color: transparent black transparent transparent; } .tooltip:hover .tooltiptext { visibility: visible; } `); GM_addStyle(` .show_panel{ z-index:11; font-size: 18px; } .top_panel{ background-color: rgb(45,53,63); border-bottom: 0px solid rgb(20,20,20); padding:5px; border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28); box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95); color: white; } #helper_panel{ position:absolute; top:5px; left:740px; display: flex; align-items: center; } #edit-helper-panel{ position:absolute; top:50px; left:740px; } #enhanced-helper-panel{ position:absolute; top:50px; left:848px; } .panel-button{ cursor:pointer; } #select-mode-panel{ position:absolute; top:5px;left:370px; background-color: rgb(45,53,63); border-bottom: 0px solid rgb(20,20,20); padding:5px; border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28); box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95); color: white;} `); /**暂时隐藏按钮 */ GM_addStyle(`#helper_latLon .fixLoc{display:inline-block;width:55px;height:15px;overflow:hidden;}`); //GM_addStyle(`#helper_latLon{display:none;}`); /**重要添加十字鼠标 */ // GM_addStyle('#pic_frame div{border:1px solid !important;cursor:crosshair !important;}'); GM_addStyle(` #line_panel { position: absolute; top: 5px; right: 5px; background-color: #f5f5f5; border-bottom: 0px solid rgb(20, 20, 20); padding: 5px; border-radius: 4px; border: 1px solid rgb(22, 25, 28); box-shadow: 0 1px 0 rgba(162, 184, 204, 0.25) inset, 0 0 4px hsla(0, 0%, 0%, 0.95); color: black; width: 0px; } #show-temp,#show-wind,#show-pre,#show-cloud{ width: 100%; height:210px; } #show-pre{ display:block; } .full-screen-mode{ float:left; padding-left:10px; } .full-screen-mode #float_icons{ display:none!important; } .full-screen-mode #pic_frame div{ border:1px solid; cursor:crosshair !important; } .top_panel .single-btn{ display:inline-block; margin-left:5px; margin-right:5px; padding:5px; border: 1px solid white; /*background-color: #4CAF50;*/ } .top_panel .single-btn:hover{ background-color:#47CB89; } .active-button > div, .active-button.single-btn{ background-color:green; } #compare-main, #multiviews-main{ z-index: 12; position: absolute; top: 0px; right: 1%; bottom: 1%; left: 1%; display: flex; flex-direction: column; align-items: center; } #float_icons{ display:none!important; } .compare-warpper { display: flex; justify-content: center; min-width: 1000px; background: white; font-size: 18px; align-items: center; } .compare-img-main { border: 1px dotted red; max-width: 1520px; height: 100%; display: grid; grid-template-columns: 1fr 1fr; background-color: white; } .compare-img-main .compare-img-wrapper { border: 2px solid blue; overflow: hidden; position:relative; } .compare-img-main .compare-img { position: relative; top: -50px; height: 120%; } .my-button{ cursor:pointer; margin-left:2px; margin-right:2px; padding:1px; background-color: rgb(45,53,63); border-bottom: 0px solid rgb(20,20,20); padding:5px; border-bottom: 0px solid rgb(20,20,20);border-radius: 4px;border: 1px solid rgb(22,25,28); box-shadow:0 1px 0 rgba(162,184,204,0.25) inset,0 0 4px hsla(0,0%,0%,0.95); color: white; } .my-button:hover{ background-color:orange; } .display-none{ display:none!important; } .compare-img-info { position: absolute; top: 0; left: 0; background-color: rgba(0, 121, 13, 0.6); color: white; padding:2px; font-size:20px; font-family:"Arial","Microsoft YaHei","黑体","STXihei","华文细黑"; } `); GM_addStyle(` .water-dot { position: relative; display: inline-block; height: 26px; width: 16px; } .water-dot:before, .water-dot:after { content: ''; position: absolute; display: inline-block; } .water-dot:before { left: 0; width: 16px; height: 16px; border-radius: 50%; background-image: repeating-radial-gradient(8px 8px at 50% 8px, transparent 0%, transparent 3px, #dd1010 3px, #dd1010 100%); } .water-dot:after { bottom: 0; left: 50%; border: 14px solid #dd1010; border-bottom-width: 0; border-right-width: 7px; border-left-width: 7px; transform: translate(-50%,0); border-bottom-color: transparent; border-right-color: transparent; border-left-color: transparent; } .water-dot.scale-lg{ transform: scale(1.5); } `); GM_addStyle(`.show_panel{z-index:11;} #pic_frame,#main_frame{ position:relative; } #map-pointer { position: absolute; top: 22px; left: 0px; /*background-color: rgba(0, 152, 50, 0.7);*/ color: white; /*width:15px;*/ /*height:15px;*/ display:inline-block; padding:2px; z-index:10; cursor:pointer; } #cv-draw-line{ position: absolute; z-index:9; left:0px; cursor:crosshair; } #wrap-draw-tc{ display: inline-block; position: absolute !important; z-index: 1; border:none !important; margin-left: 37px; pointer-events:none; left: 0; top: 0 !important; } `); } // let openMv = document.getElementById('open-mv'); // openMv.addEventListener('click',()=>{ // elemsConfig.mvImgDOM.mode = 'multiElem'; // }); /** * 地图单点标志 */ function createMapIndicator() { let wrapDiv = document.querySelector('#main_frame'); let pointer = utils.createElement('div', { id: 'map-pointer', draggable: 'true', class: 'display-none' }); pointer.innerHTML = '<span class="water-dot scale-lg"></span>' wrapDiv.insertAdjacentElement('afterbegin', pointer); //保存位置的状态值 var pos = { // parent_top:0, // parent_left:0, // cur_top:0, // cur_left:0, x_diff: 0,// 鼠标相对目标物的位置 y_diff: 0, } function allowDrop(ev) { //ev是事件对象 ev.preventDefault(); //取消事件已经关联的默认活动 } function drag(ev) { //dataTransfer是一个媒介,将目标对象放入媒介 //dataTransfer对象用来保存被拖动的数据,仅在拖动事件有效 //这里将被拖动元素的id保存为名为Text的键值对中 ev.dataTransfer.setData("Pointer", ev.target.id); //获取被拖动对象相对于上层元素顶边和左边位置 let mouseXY = utils.getElemRelPos(ev.currentTarget, ev.clientX, ev.clientY); // let eStyle = getComputedStyle(ev.target); pos.x_diff = mouseXY.x; pos.y_diff = mouseXY.y; // console.log('current'); // console.log(ev.currentTarget); // console.log(ev.target); // pos.parent_top=ev.target.offsetTop; // pos.parent_left=ev.target.offsetLeft; // pos.cur_top=ev.screenY; // pos.cur_left=ev.screenX; // console.log(mouseXY); // console.log(eStyle.marginLeft); // console.log(ev); } function drop(ev) { var new_top, new_left; ev.preventDefault(); // alert(2); var data = ev.dataTransfer.getData("Pointer"); //从媒介中获取目标对象 var elem = document.getElementById(data); //这里不能这样使用,因为offset*的值是只读的,不能改变 // elem.offsetLeft=v.parent_left+ev.screenX-v.cur_left+"px"; // elem.offsetTop=v.parent_top+ev.screenY-v.cur_top+"px"; let target = document.querySelector('#pic_frame img[style~="inline;"]'); let mouseXY = utils.getElemRelPos(target, ev.clientX, ev.clientY); // let mouseXY = utils.getElemRelPos(ev.target,ev.clientX, ev.clientY); // console.log(ev.target); // let eStyle = getComputedStyle(elem); // console.log(mouseXY); // console.log(mouseXY.x+eStyle.width); // 此处有微小的位移 elem.style.marginLeft = mouseXY.x - pos.x_diff + 35.8 + "px"; elem.style.marginTop = mouseXY.y - pos.y_diff + "px"; // console.log([parseFloat(elem.style.marginTop)+23.2 + 12.3,parseFloat(elem.style.marginLeft)+10]); let newMouseXY = { x: parseFloat(elem.style.marginLeft) - 29.31, // x:parseFloat(elem.style.marginLeft) + 5.0, y: parseFloat(elem.style.marginTop) + 33.066, }; const loc = helperConfig.matchLoc(newMouseXY, helperConfig.matchParam); elemsConfig.latLonInput.lat.innerHTML = loc.lat; // mouseXY.y elemsConfig.latLonInput.lon.innerHTML = loc.lon; // mouseXY.x elemsConfig.fixPoint = { lat: loc.lat, lon: loc.lon }; // console.log(elemsConfig.fixPoint); utils.showTimeSeries(elemsConfig.fixPoint); // elem.style.marginLeft=pos.parent_left+ev.screenX-pos.cur_left-1+"px"; // elem.style.marginTop=pos.parent_top+ev.screenY-pos.cur_top-23.2+"px"; /* TODO const imgXY = { x: elem.style.marginLeft; y: elem.style.marginTop; } const loc = helperConfig.matchLoc(imgXY, helperConfig.matchParam); elemsConfig.pointerPoint.lat = loc.lat; elemsConfig.pointerPoint.lon = loc.lon; utils.showTimeSeries(elemsConfig.pointerPoint); */ } pointer.addEventListener('dragstart', drag); wrapDiv.addEventListener('dragover', allowDrop); wrapDiv.addEventListener('drop', drop); } /** * 经纬度到图像位置 */ function latlon2XY(loc = { lat: 10, lon: 120 }) { const imgXY = helperConfig.matchImgXY(loc, helperConfig.matchParam); return imgXY; } function createCrossSectionCanvas() { // let wrapDiv = document.querySelector('#main_frame'); let wrapDiv = document.querySelector('#pic_frame'); const cv = utils.createElement('canvas', { id: 'cv-draw-line', class: 'display-none', width: "800", height: "600", style: "border:1px solid #d3d3d3;" }); wrapDiv.insertAdjacentElement('afterbegin', cv); const ctx = cv.getContext('2d'); const line = { start: [0, 0], end: [0, 0], init: false }; const cvMsMove = function (ev) { // console.log('test2'); ctx.clearRect(0, 0, 800, 600); ctx.beginPath(); ctx.lineWidth = 3; let mouseXY = utils.getElemRelPos(ev.target, ev.clientX, ev.clientY); line.end = [mouseXY.x, mouseXY.y]; ctx.moveTo(...line.start); ctx.lineTo(...line.end); ctx.strokeStyle = "green"; ctx.stroke(); // console.log(line); } const cvMsClick = function (ev) { // console.log('test'); if (line.init) { cv.removeEventListener('mousemove', cvMsMove, false); // console.log(elemsConfig.latLonInput.lon.innerText,elemsConfig.latLonInput.lat.innerText); const deltaX = 37.11666870117187; line.startLatlon = { x: line.start[0] - deltaX, y: line.start[1], } line.endLatlon = { x: line.end[0] - deltaX, y: line.end[1], } line.startLatlon.loc = helperConfig.matchLoc(line.startLatlon, helperConfig.matchParam); line.endLatlon.loc = helperConfig.matchLoc(line.endLatlon, helperConfig.matchParam); console.log(line); utils.showCross(line.startLatlon.loc, line.endLatlon.loc) line.init = false; } else { // ctx.beginPath(); // ctx.lineWidth=10; // ctx.moveTo(0,0); // ctx.lineTo(50,50); // ctx.strokeStyle="green"; // ctx.stroke(); let mouseXY = utils.getElemRelPos(ev.target, ev.clientX, ev.clientY); line.start = [mouseXY.x, mouseXY.y]; cv.addEventListener('mousemove', cvMsMove, false); line.init = true; } } cv.addEventListener('click', cvMsClick, false); ctx.fillStyle = 'rgba(255,255,255,0)'; } setTimeout(() => { createCrossSectionCanvas() }, 5000); /* 绘制台风部分 */ function createSvgTC() { let wrapDiv = document.querySelector('#pic_frame'); const svgTc = utils.createElement('span', { id: 'wrap-draw-tc', class: 'display-none', width: "800", height: "600" }); wrapDiv.insertAdjacentElement('afterbegin', svgTc); // console.log('getTime'); } // setTimeout(()=>{createSvgTC()},5000); async function showSvgTC() { // if(proxyConfig.tcShowLock) return; // proxyConfig.tcShowLock = true; let svgTC = document.querySelector('#wrap-draw-tc'); if (!svgTC) { createSvgTC(); svgTC = document.querySelector('#wrap-draw-tc'); } svgTC.classList.remove('display-none'); const fcTime = utils.getFcTime(); proxyConfig.preRegion = unsafeWindow._region; proxyConfig.preTime = fcTime.date + fcTime.hr; const initTime = moment(fcTime.date + fcTime.hr, 'YYYY-MM-DDHH'); const sT = moment(initTime).add(7, 'hours').format('YYYY-MM-DD%20HH:mm'); const eT = moment(initTime).add(9, 'hours').format('YYYY-MM-DD%20HH:mm'); // demoUrl = 'https://trident.gdmo.gq/api?interface=tc-ens>=2020-07-29%2007:00<=2020-07-29%2009:00&dateFormat=YYYY-MM-DD%20HH:mm&ins=ecmwf&basin=WPAC&spe=all'; // 接口为北#时 const url = `https://trident.gdmo.gq/api?interface=tc-ens>=${sT}<=${eT}&dateFormat=YYYY-MM-DD%20HH:mm&ins=ecmwf&basin=WPAC&spe=all`; let jsonRaw = await utils.getJSON(url) .catch(err => { // proxyConfig.tcShowLock = false; throw err; }); // proxyConfig.tcShowLock = false; let tcArr = jsonRaw.data; if (tcArr.length) { let allTC = tcUtil.catTC(tcArr); let cyclonesWrap = allTC[0].ins[0]; if (!cyclonesWrap) return console.error('没数据'); // console.log(cyclonesWrap); drawTCMap(cyclonesWrap.tc); } else { let errorNotice = GM_notification({ text: '当前没有台风数据', image: 'http://10.148.8.228/images/logo.png', title: '数据缺失', timeout: 3000 }); if (errorNotice) setTimeout(() => errorNotice.remove(), 3000); d3.select("#wrap-draw-tc > svg").remove(); } } function toggleTCMode(whichBtn) { if (whichBtn === 'tcShow') { proxyConfig.showTcMode = !proxyConfig.showTcMode; } else if (whichBtn === 'detShow') { proxyConfig.showDetTcMode = !proxyConfig.showDetTcMode; } else if (whichBtn === 'ensShow') { proxyConfig.showEnsTcMode = !proxyConfig.showEnsTcMode; } else { console.error('不正确的参数 in toggleTCMode') } } function drawTCMap(multiTC) { // console.log(multiTC); // TODO 缺少 projection 和 path 函数 let timeInterval = tcUtil.model[multiTC[0].ins].interval; function path(geoData = { type: "LineString", coordinates: [111.5, 23.5] }) { if (geoData.type === "LineString") { let loc0 = { lon: geoData.coordinates[0][0], lat: geoData.coordinates[0][1] }; let iXY0 = latlon2XY(loc0);// {x,y} let loc1 = { lon: geoData.coordinates[1][0], lat: geoData.coordinates[1][1] }; let iXY1 = latlon2XY(loc1);// {x,y} let svgDstring = `M${iXY0.x},${iXY0.y} L${iXY1.x},${iXY1.y}`; return svgDstring; } else { let loc = { lon: geoData.coordinates[0][0], lat: geoData.coordinates[0][1] }; let iXY = latlon2XY(loc); return iXY; } } let allLineArr = new Array(); for (let iTc = 0; iTc < multiTC.length; iTc++) { let tcRaw = multiTC[iTc]; let catArr = tcRaw.tracks .map(member => member.track) .map(track => { let twoPointLineArr = []; for (let i = 0; i < track.length - 1; i++) { let point0 = track[i][1]; let point1 = track[i + 1][1]; let nextWind = track[i + 1][3]; let nextCat = tcUtil.wind2cat(nextWind); let nextColor = tcUtil.tcColor[nextCat]; let time0 = track[i][0]; let time1 = track[i + 1][0]; if (time1 - time0 > timeInterval * 2) continue; const distance = Math.sqrt(Math.pow(point1[0] - point0[0], 2) + Math.pow(point1[1] - point0[1], 2)); if (distance > 9) continue; // 如果大于9个经纬度则断线 twoPointLineArr.push({ line: { type: "LineString", coordinates: [point0, point1] }, nextCat, nextColor, curCat: tcUtil.wind2cat(track[i][3]) }); } return twoPointLineArr; }) .reduce((acc, val) => acc.concat(val), []); allLineArr.push(catArr); } allLineArr = allLineArr.reduce((acc, val) => acc.concat(val), []); d3.select("#wrap-draw-tc > svg").remove(); let baseMap = d3 .select('#wrap-draw-tc') .append("svg") .attr("width", 800) .attr("height", 600) .attr("class", "map-svg"); let tcEnsSvgG = baseMap.append("g").attr("class", "svg-tc-ens"); if (proxyConfig.showEnsTcMode) { tcEnsSvgG .selectAll("path") .data(allLineArr) .enter() .append("path") .attr("d", d => path(d.line)) .attr("class", d => `track-line ${d.nextCat}`) .style("stroke", d => 'rgb(74, 80, 87)') .attr("opacity", 0.6); // .style("stroke", d => d.nextColor) let allPointArr = new Array(); for (let iTc = 0; iTc < multiTC.length; iTc++) { let tcRaw = multiTC[iTc]; let pointArr = tcRaw.tracks .map(member => member.track) .map(track => track.map(point => { let cat = tcUtil.wind2cat(point[3]); return { pointXY: path({ type: "Point", coordinates: [point[1]] }), // project: projection(point[1]), color: tcUtil.tcColor[cat], cat }; }) ) .reduce((acc, val) => acc.concat(val), []); // console.log(pointArr); allPointArr = allPointArr.concat(pointArr); } // console.log(allPointArr); let pointSvg = tcEnsSvgG.append("g"); pointSvg.attr("class", "point-g"); pointSvg .selectAll("circle") .data(allPointArr) .enter() .append("circle") .attr("class", "point") .attr("cx", d => d.pointXY.x) .attr("cy", d => d.pointXY.y) .attr("r", 3) .attr("opacity", 0.8) .style("stroke", d => d.color) .style("stroke-width", 1.0) .style("fill", 'none'); } if (proxyConfig.showDetTcMode) { /* 确定性预报 */ let allDetCatArr = new Array(); for (let iTc = 0; iTc < multiTC.length; iTc++) { let tcRaw = multiTC[iTc]; if (!tcRaw.detTrack || !tcRaw.detTrack.track) continue;//不存在退出 let detArr = (() => { let track = tcRaw.detTrack.track let twoPointLineArr = []; for (let i = 0; i < track.length - 1; i++) { let point0 = track[i][1]; let point1 = track[i + 1][1]; let nextWind = track[i + 1][3]; let nextCat = tcUtil.wind2cat(nextWind); let nextColor = tcUtil.tcColor[nextCat]; let time0 = track[i][0]; let time1 = track[i + 1][0]; if (time1 - time0 > timeInterval * 2) continue;// 大于时间间隔跳过连线 const distance = Math.sqrt(Math.pow(point1[0] - point0[0], 2) + Math.pow(point1[1] - point0[1], 2)); if (distance > 9) continue; // 如果大于9个经纬度则断线 twoPointLineArr.push({ line: { type: "LineString", coordinates: [point0, point1] }, nextCat, nextColor, curCat: tcUtil.wind2cat(track[i][3]) }); } return twoPointLineArr; })(); allDetCatArr.push(detArr); } allDetCatArr = allDetCatArr.reduce((acc, val) => acc.concat(val), []); let allDetPointArr = new Array(); for (let iTc = 0; iTc < multiTC.length; iTc++) { let tcRaw = multiTC[iTc]; if (!tcRaw.detTrack || !tcRaw.detTrack.track) continue;//不存在退出 const detPoints = tcRaw.detTrack.track .map(point => { let cat = tcUtil.wind2cat(point[3]); return { pointXY: path({ type: "Point", coordinates: [point[1]] }), color: tcUtil.tcColor[cat], cat }; }); allDetPointArr = allDetPointArr.concat(detPoints); } const detTrackSvg = baseMap.append("g").attr("class", "svg-tc-det"); detTrackSvg .selectAll("path") .data(allDetCatArr) .enter() .append("path") .attr("d", d => path(d.line)) .attr("class", d => `track-line-det ${d.nextCat}`) .style("stroke", d => d.nextColor) .style("stroke-width", 3); const detPointSvg = detTrackSvg.append("g"); detPointSvg.attr("class", "point-g"); detPointSvg .selectAll("circle") .data(allDetPointArr) .enter() .append("circle") .attr("class", "point") .attr("cx", d => d.pointXY.x) .attr("cy", d => d.pointXY.y) .attr("r", 3.5) .style("fill", d => d.color) .style("stroke", d => d.color) .style("stroke-width", 1.0) } } let tcUtil = { worldGeo: null, geoMap: [], tcColor: { SuperTY: "rgb(128,0,255)", STY: "rgb(153,20,8)", TY: "rgb(255,0,0)", STS: "rgb(255,128,0)", TS: "rgb(0,0,255)", TD: "rgb(105,163,74)", LOW: "rgb(85,85,79)" }, wind2cat(wind) { if (wind >= 10.8 && wind < 17.2) { return "TD"; } else if (wind >= 17.2 && wind < 24.5) { return "TS"; } else if (wind >= 24.5 && wind < 32.7) { return "STS"; } else if (wind >= 32.7 && wind < 41.5) { return "TY"; } else if (wind >= 41.5 && wind < 51.0) { return "STY"; } else if (wind >= 51.0) { return "SuperTY"; } else { return "LOW"; } }, timeColor: [ { name: "H24", color: "rgb(0,0,0)" }, { name: "H48", color: "rgb(255,0,0)" }, { name: "H72", color: "rgb(0,140,48)" }, { name: "H96", color: "rgb(255,128,0)" }, { name: "H120", color: "rgb(0,0,102)" }, { name: "H144", color: "rgb(0,255,0)" }, { name: "H168", color: "rgb(153,20,8)" }, { name: "H192", color: "rgb(0,255,255)" }, { name: "H216", color: "rgb(255,0,255)" }, { name: "H240", color: "rgb(178,178,178)" } ], matchTimeColor(time = 24) { let count = Math.ceil(time / 24) - 1; if (count === -1) count = 0; // 颜色下边界 let colorLen = tcUtil.timeColor.length; if (count > colorLen - 1) count = colorLen - 1; //超过颜色上界 return tcUtil.timeColor[count].color; }, model: { ecmwf: { enNumber: 51, interval: 6, timeRange() { return Array.from(new Array(40), (val, index) => index * 6); } }, NCEP: { enNumber: 21, interval: 6, timeRange() { return Array.from(new Array(40), (val, index) => index * 6); } }, "ncep-R": { enNumber: 21, interval: 12, timeRange() { return Array.from(new Array(30), (val, index) => index * 12); } }, "ukmo-R": { enNumber: 35, interval: 12, timeRange() { return Array.from(new Array(30), (val, index) => index * 12); } }, "ecmwf-R": { enNumber: 35, interval: 12, timeRange() { return Array.from(new Array(30), (val, index) => index * 12); } }, UKMO: {} }, catTC(tcArr = []) { let timeSet = new Set(tcArr.map(tc => tc.initTime)); //选出日期并去重 let insSet = new Set(tcArr.map(tc => tc.ins)); let tcAll = [] for (let iTime of timeSet) { let timeWrap = { time: iTime, ins: [] }; let sameTime = tcArr.filter(tc => tc.initTime == iTime); for (let iIns of insSet) { let insWrap = { ins: iIns, tc: [] } let sameIns = sameTime.filter(tc => tc.ins == iIns); insWrap.tc = sameIns; timeWrap.ins.push(insWrap); } tcAll.push(timeWrap); } // [ { time: '2020-05-15T00:00:00.000Z', ins: [ {} ] } ] return tcAll; }, }; /** 多模式部分代码 */ function GetURLFromRule({ year = '2020', month = '02', day = '25', hour = '12', fc = '000', minute = '', region, eleName, modelFileName, model }) { let imgUrl = this.rule.replace(/{yyyy}/g, year) .replace(/{mm}/g, month) .replace(/{dd}/g, day) .replace(/{HH}/g, hour) .replace(/{hhh}/g, fc) .replace(/{mi}/g, minute) .replace(/{yyyymmdd}/g, year + month + day) .replace(/{date}/g, year + month + day) return imgUrl; } function GetTimeInfo() { let timeInfo = { year: this.year, month: this.month, day: this.day, hour: this.hour, fc: this.fc, minute: this.minute ? this.minute : undefined, } return timeInfo; } function getImgInfoFromURL(url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20200225/ecmwffine_hn_mslp_000_2020022512.png') { let model, region, date, modelFileName, eleName, fc, year, month, day, hour, minute; let regPatten; let reg = /http:.*?\/znwp\/(.*?)\/(.*?)\/(\d{8})\/(.*?)_(.*?)_(.*?)_(\d{3})_(\d{4})(\d{2})(\d{2})(\d{2})(.*?$)/; let matchUrl = url.match(reg); if (matchUrl) { regPatten = 0; model = matchUrl[1]; region = matchUrl[2]; date = matchUrl[3]; modelFileName = matchUrl[4]; eleName = matchUrl[6]; fc = matchUrl[7]; year = matchUrl[8]; month = matchUrl[9]; day = matchUrl[10]; hour = matchUrl[11]; } else { let reg2 = /http:.*?\/znwp\/(.*?)\/(.*?)\/+(\d{8})\/(\d{2})\/(.*?)_(.*?)_(.*?)_m(\d{3})_(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(.*?$)/; matchUrl = url.match(reg2); if (matchUrl) { regPatten = 1; model = matchUrl[1]; region = matchUrl[2]; date = matchUrl[3]; modelFileName = matchUrl[5]; eleName = matchUrl[7]; fc = matchUrl[8]; year = matchUrl[9]; month = matchUrl[10]; day = matchUrl[11]; hour = matchUrl[12]; minute = matchUrl[12]; } else { throw new Error('无法匹配的图像地址:' + url) } } // ["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"] let info = { regPatten, model, region, date, modelFileName, eleName, fc, year, month, day, hour, minute }; info.getTimeInfo = GetTimeInfo; return info; } function matchModel2Rule(model = '', region, eleName) { let matchModel = modelMeta.find(v => v.model === model); if (!matchModel) throw new new ReferenceError('未找到匹配的模式'); const rule = matchModel.rule; //TODO: 有些特殊模式需要特殊判断 let newRule = `http://10.148.8.228/files_home/znwp/${model}/{range}/{yyyy}{mm}{dd}/` + rule; if (model === 'grapes1km') newRule = `http://10.148.8.228/files_home/znwp/${model}/{range}/` + rule; newRule = newRule.replace(/{range}/g, region) .replace(/{name}/g, eleName); const newInfo = { model, region, eleName, modelName: matchModel.modelName, modelFileName: matchModel.modelName, }; newInfo.getUrl = GetURLFromRule; newInfo.rule = newRule; return newInfo; } // testMain(); function matchMVRule(url = 'http://10.148.8.228/files_home/znwp/ecmwffine_b/hn/20200225/ecmwffine_hn_mslp_000_2020022512.png', mvRule) { const imgInfo = getImgInfoFromURL(url); const infoList = [imgInfo.model, imgInfo.region, imgInfo.eleName]; let matchedRules = []; function isMatchCondition(iInfo, rule) { let condition = false; if (Array.isArray(rule)) { condition = rule.includes(iInfo); } else if (typeof (rule) === 'string') { condition = rule === iInfo; } else if (!Boolean(rule)) {// rule === null condition = true; } else { throw new TypeError('无法识别的规则类型:' + item); } return condition; } for (let item of mvRule) { // console.log(mvRule); const fitIndex = item.rule.findIndex(list => {// 查找四个规则中是哪个匹配 let isMatch = list.every((rule, i) => { const iInfo = infoList[i]; return isMatchCondition(iInfo, rule); }); return isMatch; }) if (fitIndex === -1) { continue; } else { let copyItem = Object.assign({}, item); copyItem.fitIndex = fitIndex; matchedRules.push(copyItem); } } return matchedRules; } function getMVSrcListFromRule(url, ruleItem = { rule: [] }) { let imgList = [0, 0, 0, 0]; const imgInfo = getImgInfoFromURL(url); const infoList = [imgInfo.model, imgInfo.region, imgInfo.eleName]; const rule = ruleItem.rule; for (let i = 0; i < rule.length; i++) {// rule.length = 4; if (i === ruleItem.fitIndex) { imgList[i] = url; } else { let iRule = rule[i];// ['ncep',['oy','cn','hn','gd',],null], let iBaseInfo = iRule.map((ele, j) => { if (Array.isArray(ele)) { return infoList[j]; } else if (typeof (ele) === 'string') { return ele; } else if (!Boolean(ele)) {// rule === null return infoList[j]; } else { throw new TypeError('无法识别的规则类型:' + iRule); } }) const iImgRule = matchModel2Rule(iBaseInfo[0], iBaseInfo[1], iBaseInfo[2]); imgList[i] = iImgRule.getUrl(imgInfo.getTimeInfo()); } } return imgList; } if(location.href.includes('gisloader')){ GM_addStyle(` .maps-more-image .maps-more-header { position: absolute !important; font-size: 22px !important; z-index: 20; background-color: #ededed6e !important; color: #1400ff !important; text-shadow: #FC0 1px 0 10px; left: 5%; } #mapMain .maps-more-image .maps-header-info { font-size: 20px !important; } #mapMain .maps-more-image .map-img { top: 49%!important; left: 47%!important; height: 123%!important; } .group-mode-instruction { height: auto; } .group-mode[data-drop-index="0"] { z-index: 10!important; } .group-mode[data-drop-index="1"] { z-index: 9!important; } .group-mode[data-drop-index="2"] { z-index: 8; } .group-mode[data-drop-index="3"] { z-index: 7; } .group-mode[data-drop-index="4"] { z-index: 6; } .group-mode[data-drop-index="5"] { z-index: 5; } .group-mode[data-drop-index="6"] { z-index: 4; } .group-mode[data-drop-index="7"] { z-index: 3; } .group-mode[data-drop-index="8"] { z-index: 2; } #mapMain .maps-more-image .maps-more-body { overflow: hidden; height: 100%!important; } `); }