import * as zrUtil from 'zrender/src/core/util'; import zrender from 'zrender'; import localStore from 'storejs'; import Painter from './painter'; import Options from './options'; import MouseController from './mouseController'; import KeyboardController from './keyboardController'; import deviceState from './constant/deviceState'; // 初始化默认状态 import deviceType from './constant/deviceType'; import { selectLineCode } from './config/deviceStyle'; import { deviceFactory, createBoundingRect, calculateDCenter } from './utils/parser'; import { deepAssign } from '@/utils/index'; import * as adapter from '@/jmapNew/utils/adapter'; import store from '@/store/index'; import Vue from 'vue'; const renderer = 'canvas'; const devicePixelRatio = 1; class Jlmap { constructor(opts) { // 回调事件 this.methods = opts.methods; // 鼠标事件 this.events = { __Pan: 'pan', __Zoom: 'zoom', Selected: 'selected', Contextmenu: 'contextmenu', DataZoom: 'dataZoom', Keyboard: 'keyboard'}; // 线路参数 this.lineCode = ''; // 大屏判断 this.screenFlag = false; // 皮肤风格 this.style = {}; // 设备数据 this.mapDevice = {}; // 大屏配置 this.splitList = []; // 默认状态 this.defaultStateDict = this.loadDefaultState(); this.initMapInstance(opts); // 显示集中站 this.stationCode = ''; this.isUpdateShowTrainList = false; this.isUpdateRunLineTrain = false; this._disposeFlag = false; } // 初始化属性有鼠标事件 缩放等 initMapInstance(opts) { const width = opts.dom.clientWidth; const height = opts.dom.clientHeight; this.zoomOnMouseWheel = opts.options.zoomOnMouseWheel; this.previewOrMapDraw = opts.showConfig.previewOrMapDraw; this.$zr = zrender.init(opts.dom, deepAssign({ renderer, devicePixelRatio, width, height }, opts.config)); this.$zr.dom.setAttribute('tabIndex', -1); this.$options = new Options(deepAssign({ scaleRate: 1, offsetX: 0, offsetY: 0 }, opts.options || {}), (dataZoom) => { this.$mouseController.trigger(this.events.DataZoom, dataZoom); }); // 缩放 this.$painter = new Painter(this); this.$painter.updateZrSize({width: this.$zr.getWidth(), height: this.$zr.getHeight(), isUpdate:false}); this.$zr.dom.style.backgroundColor = this.style.backgroundColor || '#000'; this.optionsHandler = this.setOptions.bind(this); this.$mouseController = new MouseController(this); this.$mouseController.enable(); this.$mouseController.on(this.events.__Pan, this.optionsHandler); this.$mouseController.on(this.events.__Zoom, this.optionsHandler); this.$keyboardController = new KeyboardController(this); this.$keyboardController.enable(); this.showConfig = opts.showConfig; } loadStyle(lineCode) { return selectLineCode(lineCode); } loadDefaultState() { // 加载默认状态 const defaultStateDict = {}; zrUtil.each(Object.keys(deviceState), (type) => { defaultStateDict[type] = {}; zrUtil.each(Object.keys(deviceState[type] || {}), (state) => { defaultStateDict[type][state] = deviceState[type][state]; // 新版地图直接使用判断结果 }, this); }, this); return defaultStateDict; } setMap(map, mapDevice, logicData) { // 保存皮肤类型 if (map.skinVO) { this.lineCode = map.skinVO.code; this.$options.scaleRate = map.scaling || 1; this.$options.offsetX = map.origin ? map.origin.x : 0; this.$options.offsetY = map.origin ? map.origin.y : 0; } // 更新视图大小 this.$painter.updateTransform({ scaleRate: this.$options.scaleRate, offsetX: this.$options.offsetX, offsetY: this.$options.offsetY }); // 解析后的数据 this.mapDevice = mapDevice; // 进路数据 this.logicData = logicData; // 加载对应皮肤 this.style = this.loadStyle(this.lineCode); this.$zr.dom.style.backgroundColor = this.style.backgroundColor || '#000'; // 数据加载完成 回调 if (this.methods.dataLoaded instanceof Function) { this.methods.dataLoaded(this.mapDevice); } // 初次渲染视图 this.$painter.repaint(this.mapDevice); // 视图加载完成 回调 if (this.methods.viewLoaded instanceof Function) { this.methods.viewLoaded(this.mapDevice); } } setMapDevice(mapDevice) { this.mapDevice = mapDevice; } setDefaultState() { const list = []; Object.values(this.mapDevice).forEach(elem => { const code = elem.code; const type = elem._type; // 列车不需要设置默认状态 type != deviceType.Train && list.push(Object.assign({ code, _type: type }, this.defaultStateDict[type])); }); this.update(list); if (this.methods.stateLoaded instanceof Function) { this.methods.stateLoaded(list); } } setOptions(opts) { const options = this.pullBack(opts); if (this.screenFlag) { this.$options.update(options); this.$painter.updateScreen(options); } else { this.$options.update(options); this.$painter.updateTransform(this.$options); } if (this.$options.disabled == true) { this.$mouseController.disable(); } else { opts['zoomOnMouseWheel'] = this.zoomOnMouseWheel; opts['previewOrMapDraw'] = this.previewOrMapDraw; this.$mouseController.enable(opts); } if (this.methods.optionsUpdate instanceof Function) { this.methods.optionsUpdate(this.$options); } } switchScreen(data, num, sum, obj) { const opts = {}; if (obj.width == 1) { const dy = data.height / sum; opts['dx'] = 0; opts['dy'] = dy * (num - 1); } else { const sumHalf = sum <= 2 ? sum : sum / 2; const dx = data.width / sumHalf; const dy = data.height / sumHalf; const row = num <= sumHalf ? 0 : 1; opts['dx'] = dx * (row == 0 ? num - 1 : num - sumHalf - 1); opts['dy'] = dy * row; } this.$painter.updateScreenNum(opts); } setCenterWithOffset(deviceCode, dx, offsetY) { const arr = Object.keys(this.mapDevice); if (arr.length != 0) { const device = this.mapDevice[deviceCode]; if (device && device.instance) { var rect = createBoundingRect(device.instance); var dcenter = calculateDCenter(rect, dx); this.setOptions({...dcenter, offsetY}); } } else { setTimeout(() => { this.setCenterWithOffset(deviceCode, dx, offsetY); }, 60); } } setCenter(deviceCode) { const arr = Object.keys(this.mapDevice); if (arr.length != 0) { const device = this.mapDevice[deviceCode]; if (device && device.instance) { var rect = createBoundingRect(device.instance); var dcenter = calculateDCenter(rect, this.$zr.getWidth() / 2); this.setOptions(dcenter); } } else { setTimeout(() => { this.setCenter(deviceCode); }, 60); } } setRevoverBigScreen() { this.screenFlag = false; this.$painter.updateTransform({ scaleRate: this.$options.scaleRate, offsetX: this.$options.offsetX, offsetY: this.$options.offsetY }); } setRecover(opts) { this.screenFlag = false; this.$painter.updateTransform({ scaleRate: opts.scaleRate, offsetX: opts.offsetX, offsetY: opts.offsetY }); } setDepot(opts) { this.$options.scaleRate = opts.scaleRate; this.$options.offsetX = opts.offsetX; this.$options.offsetY = opts.offsetY; this.$painter.updateTransform({ scaleRate: opts.scaleRate, offsetX: opts.offsetX, offsetY: opts.offsetY }); } setUpdateScreen(opts) { this.setRecover({ scaleRate: 1, offsetX: 0, offsetY: 0 }); this.screenFlag = true; let rect = ''; for (const i in this.mapDevice) { const element = this.mapDevice[i]; if (element.instance && (element._type == 'Section' || element._type == 'Station' || element._type == 'StationStand') && store.getters['map/checkDeviceShow'](i)) { if (!rect) { rect = element.instance.getBoundingRect().clone(); } else { rect.union(element.instance.getBoundingRect().clone()); } } } if (!opts.offsetList) { opts.offsetList = []; } const screenSplit = opts.list.length ? opts.list : Vue.prototype.$theme.loadPropConvert(this.lineCode).screenSplit; this.splitList = JSON.parse(JSON.stringify(screenSplit)); const num = screenSplit.length + 1; const maxWidth = rect.width; this.splitList.push(maxWidth); const scaleX = Math.floor((((opts.width - 200) * num) / rect.width) * 100) / 100; const scaleY = Math.floor(((opts.height - 100) / (rect.height * num)) * 100) / 100; this.screenScale = Math.min(scaleX, scaleY); this.optsWidth = opts.width; this.optsHeight = opts.height; this.optsOffsetList = opts.offsetList; const scaleBili = this.screenScale; this.$options.scaleRate = this.screenScale || 1; this.$options.scaleIndex = 0; this.$options.offsetX = 0; this.$options.offsetY = 0; // 更新视图大小 // this.$painter.updateTransform({ scaleRate: this.$options.scaleRate, offsetX: this.$options.offsetX, offsetY: this.$options.offsetY }); const {screenList, rectList} = this.calculateScreenPosition(scaleBili); this.$painter.updateTransform1(screenList, rectList); } calculateScreenPosition(scaleBili) { const screenList = []; const rectList = []; const screenDifference = Vue.prototype.$theme.loadPropConvert(this.lineCode).screenDifference || 160; const bili = scaleBili / this.screenScale; const offsetY = (this.optsHeight - 100) / this.splitList.length; // 高度差 for (let i = 0; i < this.splitList.length; i++) { let offsetX = ''; if (i == 0) { offsetX = -(this.optsWidth - this.splitList[0] * scaleBili) / 2; } else { const dx = (this.optsWidth - (this.splitList[i] - this.splitList[i - 1]) * scaleBili) / 2; // 居中计算偏移值 offsetX = this.splitList[i - 1] * scaleBili - dx; } const offset = this.optsOffsetList[i] || 0; const param = { scaleRateX: scaleBili, scaleRateY: scaleBili, offsetX: offsetX, offsetY: (-screenDifference - (offsetY * i) - offset) * bili }; screenList.push(param); const rect = {x: 0, y: 0, width: Number(this.splitList[i]) + 5, height: this.optsHeight}; rectList.push(rect); } return { screenList, rectList }; } setLevelVisible(list) { this.$painter.setLevelVisible(list); } render(list = []) { list.forEach(elem => { const code = elem.code; const type = elem._type; const oDevice = deviceFactory(type, elem, this.showConfig); if (this.mapDevice[code]) { this.$painter.delete(this.mapDevice[code]); delete this.mapDevice[code]; } if (!elem._dispose) { this.mapDevice[code] = deepAssign(this.mapDevice[code] || {}, oDevice); this.$painter.add(this.mapDevice[code]); } }); if (this.methods.viewUpdate instanceof Function) { this.methods.viewUpdate(list); } } // 中间处理 hookHandle(oDevice, elem) { // 老数据 新数据 const code = elem.code; const type = elem._type; // 如果是延时计时,需要保存计数值到全局 if (type === deviceType.StationCounter) { let val = '' + elem.val; if (val === '0' || !elem.val) { val = elem.val = localStore.get(code) || '0'; } localStore(code, val); } for (var prop in elem) { if (oDevice[prop] != elem[prop]) { deepAssign(oDevice, elem); // 更新状态 return true; } } return false; } setUpdateMapDevice(list = [], fetch = true) { if (fetch) { store.commit('map/updateMapDevice', list); } list.forEach(elem => { const code = elem.code; const type = elem._type; const oDevice = this.mapDevice[code] || deviceFactory(type, elem); this.hookHandle(oDevice, elem); }); } getShowConfig() { return this.showConfig; } amendDevice(list) { list.forEach(elem => { if (store.getters['map/amendPoints'](elem.code)) { const oDevice = this.mapDevice[elem.code]; this.$painter.delete(oDevice); this.$painter.add(oDevice); } }); } updateShowStation(list = [], stationCode = '') { store.dispatch('map/setShowCentralizedStationCode', stationCode); list.forEach(elem => { const code = elem.code; const type = elem._type; const oDevice = this.mapDevice[code] || deviceFactory(type, elem, this.showConfig); this.$painter.update(oDevice); }); this.$painter.$transformHandle.revisibleAll(); if (stationCode) { this.$painter.$transformHandle.setFoldLines(store.state.map.foldLineMap[stationCode]); } else { this.$painter.$transformHandle.setFoldLines([]); } } // 模式选择 -> 更新 updateShowMode(list = [], showMode = '03') { this.showConfig.showMode = showMode; list.forEach(elem => { const code = elem.code; const type = elem._type; const oDevice = this.mapDevice[code] || deviceFactory(type, elem, this.showConfig); oDevice.showMode = showMode; oDevice.instance && oDevice.model && oDevice.instance.setState(oDevice.model); }); this.$painter.$transformHandle.revisibleAll(); } update(list = [], fetch = true) { this.setUpdateMapDevice(list, fetch); // 增加一个 前数据 处理 为了在区段中 获取全部的 道岔信息 const codeList = []; const controlTransferList = []; const signalDeviceList = []; const selected = store.state.menuOperation.selected; list.forEach((elem, index) => { const code = elem.code; const type = elem._type; if (elem.deviceType === 'ROUTE' && this.logicData.routeData) { // 处理进路数据状态 store.dispatch('map/updateRouteState', elem); const route = this.logicData.routeData[code]; if (route.automaticRouteCode) { const automaticRoute = this.mapDevice[route.automaticRouteCode]; const automaticRouteStatus = {fleetMode: elem.fleetMode}; if (this.hookHandle(automaticRoute, automaticRouteStatus)) { this.$painter.update(automaticRoute); } } const signalDevice = this.mapDevice[route.startSignalCode]; const index = signalDeviceList.indexOf(signalDevice); if (index === -1) { signalDeviceList.push(signalDevice); } } else if (elem.deviceType == 'OVERLAP') { const overlapRoute = this.mapDevice[elem.code]; const model = this.mapDevice[overlapRoute.pathList[0].sectionList[0]]; if (overlapRoute.pathList[0].right) { overlapRoute['points'] = { x: model.points[0].x, y: model.points[0].y }; } else { overlapRoute['points'] = { x: model.points[model.points.length - 1].x, y: model.points[model.points.length - 1].y }; } overlapRoute['offsetRight'] = overlapRoute.pathList[0].right; overlapRoute['sectionName'] = model.name; this.$painter.update(overlapRoute); } else if (elem.deviceType === 'CYCLE') { store.dispatch('map/updateAutoReentryState', elem); const autoReentryData = this.logicData.autoReentryData[code]; const cycleButton = this.mapDevice[autoReentryData.cycleButtonCode]; const cycleButtonStatus = {setUp: elem.setUp}; if (cycleButton && this.hookHandle(cycleButton, cycleButtonStatus)) { this.$painter.update(cycleButton); } } else if (elem.deviceType === 'STATION') { // 如果是控制权转移消息 if (elem.hasOwnProperty('applicantId')) { controlTransferList.push(elem); } if (!elem.applicantId) { const oDevice = this.mapDevice[code] || deviceFactory(type, elem, this.showConfig); const guideLock = this.mapDevice[oDevice.guideLockCode]; const guideLockStatus = {totalGuideLock: elem.totalGuideLock}; if (guideLock && this.hookHandle(guideLock, guideLockStatus)) { this.$painter.update(guideLock); } if (elem.dispose) { this.$painter.delete(oDevice); } else { this.$painter.update(oDevice); } } store.commit('map/mapStationStateUpdate'); } else { if (elem.deviceType === 'TRAIN') { this.isUpdateShowTrainList = true; if (elem.destinationCode) { this.isUpdateRunLineTrain = true; } store.commit('map/updateActiveTrainList', elem); } else if (elem.deviceType === 'STAND') { store.dispatch('map/updateStationStand', elem); const psdDevice = this.mapDevice[this.mapDevice[elem.code].psdCode]; if (psdDevice) { psdDevice.fault = elem.fault; this.$painter.update(psdDevice); } } else if (elem.deviceType == 'SWITCH') { const oDevice = this.mapDevice[code]; if (oDevice) { const sectionA = this.mapDevice[oDevice.sectionACode]; const sectionB = this.mapDevice[oDevice.sectionBCode]; const sectionC = this.mapDevice[oDevice.sectionCCode]; if (sectionA && sectionB && sectionC) { oDevice['cutOff'] = sectionA.cutOff; } } this.$painter.update(oDevice); } else if (elem.deviceType == 'SECTION') { const oDevice = this.mapDevice[code]; if (oDevice) { const sDevice = this.mapDevice[oDevice.relSwitchCode]; if (sDevice) { const sectionA = this.mapDevice[sDevice.sectionACode]; const sectionB = this.mapDevice[sDevice.sectionBCode]; const sectionC = this.mapDevice[sDevice.sectionCCode]; if (sectionA && sectionB && sectionC) { sDevice['cutOff'] = sectionA.cutOff; } this.$painter.update(sDevice); codeList.push(sDevice.code); } } } else if (elem.deviceType === 'DIRECTION_ROD' && elem.uniqueCode) { store.state.map.map.directionRodList.forEach(item => { if (elem.uniqueCode === item.uniqueCode) { const updateRod = this.mapDevice[item.code]; updateRod.reversed = elem.reversed; this.$painter.update(updateRod); } }); } const oDevice = this.mapDevice[code] || deviceFactory(type, elem, this.showConfig); if (elem.dispose) { this.$painter.delete(oDevice); } else { this.$painter.update(oDevice); } } codeList.push(code); }); if (controlTransferList.length > 0) { store.dispatch('map/setControlTransfer', controlTransferList); } if (this.isUpdateShowTrainList) { store.dispatch('map/setActiveTrainList'); this.isUpdateShowTrainList = false; } if (this.isUpdateRunLineTrain) { store.dispatch('map/setRunLineTrain'); this.isUpdateRunLineTrain = false; } if (selected && codeList.includes(selected.code)) { store.commit('menuOperation/setSelected', {device: {...selected}}); } this.handleRouteSignalStatus(signalDeviceList); if (this.methods.stateUpdate instanceof Function) { this.methods.stateUpdate(list); } } handleRouteSignalStatus(signalDeviceList) { const routeStartSignalData = store.state.map.routeStartSignalData; signalDeviceList.forEach((item, index)=> { const status = {}; if (routeStartSignalData[item.code] && routeStartSignalData[item.code].length) { routeStartSignalData[item.code].forEach((elem, index)=> { if (index) { status.atsControl = status.atsControl && elem.atsControl; status.checkConflict = status.checkConflict || elem.checkConflict; status.fleetMode = status.fleetMode || elem.fleetMode; status.ciControl = status.ciControl || elem.ciControl; status.lock = status.lock || elem.lock; } else { status.atsControl = elem.atsControl; status.checkConflict = elem.checkConflict; status.fleetMode = elem.fleetMode; status.ciControl = elem.ciControl; status.lock = elem.lock; } status.canSetCi = status.arc || elem.arc; }); } if (item && this.hookHandle(item, status)) { this.$painter.update(item); } }); } pullBack(payload) { if (payload.type === 'zoom') { const zrWidth = this.$zr.getWidth(); const zrHeight = this.$zr.getHeight(); const originX = payload.originX || zrWidth / 2; const originY = payload.originY || zrHeight / 2; const x = (this.$options.offsetX + originX) / this.$options.scaleRate; const y = (this.$options.offsetY + originY) / this.$options.scaleRate; const newScaleRate = this.$options.getScaleRate(payload.scale); const dx = originX - (x * newScaleRate - this.$options.offsetX); const dy = originY - (y * newScaleRate - this.$options.offsetY); payload.dx = dx; payload.dy = dy; } return payload || {}; } getZr() { return this.$zr; } getEvents() { return this.events; } getDeviceByCode(code) { return this.mapDevice[code]; } getShapeTipPoint(opts) { const device = adapter.adapterByDevice(this.mapDevice[opts.code], this.mapDevice); if (device) { return this.$painter.getShapeTipPoint(device.instance, opts); } } resize(opt) { this.$zr.resize(opt); opt.isUpdate = false; this.$painter.updateZrSize(opt); } refresh() { this.$painter.refresh(); } clearTrainView() { this.$painter.clearLevel(deviceType.Train); zrUtil.each(Object.values(this.mapDevice), device => { if (device._type == deviceType.Train) { device.instance = null; } }); } clearEvent() { this.$zr.curEvent = null; } clear() { this.lineCode = ''; this.style = {}; this.mapDevice = {}; this.$painter.clear(); } clearView() { // this.mapDevice = {}; this.$painter.clear(); } dispose() { this.off(this.events.Pan, this.optionsHandler); this.off(this.events.Zoom, this.optionsHandler); // this.clear(); this.$mouseController.dispose(); this.$keyboardController.dispose(); this.$zr && zrender.dispose(this.$zr); this.$painter.dispose(); this._disposeFlag = true; } on(eventname, cb, context) { const idx = Object.values(this.events).indexOf(eventname); if (idx >= 0) { switch (eventname) { case this.events.Selected: this.$mouseController.on(this.events.Selected, cb, context); break; case this.events.Contextmenu: this.$mouseController.on(this.events.Contextmenu, cb, context); break; case this.events.DataZoom: this.$mouseController.on(this.events.DataZoom, cb, context); break; case this.events.Keyboard: this.$keyboardController.on(this.events.Keyboard, cb, context); break; case this.events.__Pan: this.$mouseController.on(this.events.__Pan, this.optionsHandler); break; case this.events.__Zoom: this.$mouseController.on(this.events.__Zoom, this.optionsHandler); break; } } } off(eventname, cb) { const idx = Object.values(this.events).indexOf(eventname); if (idx >= 0) { switch (eventname) { case this.events.Selected: this.$mouseController.off(this.events.Selected, cb); break; case this.events.Contextmenu: this.$mouseController.off(this.events.Contextmenu, cb); break; case this.events.DataZoom: this.$mouseController.off(this.events.DataZoom, cb); break; case this.events.Keyboard: this.$keyboardController.off(this.events.keyboard, cb); break; case this.events.__Pan: this.$mouseController.off(this.events.__Pan, cb); break; case this.events.__Zoom: this.$mouseController.off(this.events.__Zoom, cb); break; } } } renderCheckBox(model) { const type = model._type; const code = model.code; const oDevice = this.mapDevice[code] || deviceFactory(type, model); const nDevice = deviceFactory(type, Object.assign(oDevice.model || {}, model)); delete this.mapDevice[code]; this.$painter.delete(oDevice); if (!model._dispose) { this.mapDevice[code] = nDevice; this.$painter.add(nDevice); } } deleteCheckBox(code) { const oDevice = this.mapDevice[code]; if (oDevice) { delete this.mapDevice[code]; this.$painter.delete(oDevice); } } } export default Jlmap;