/* *********************************************************** */ // Created by Macheng on 2019/07/31. // Description: // This is a class of map engine. // Ver 1.0 save marker path by each copy, this will cost more space. /* *********************************************************** */ import * as maptalks from 'maptalks' import * as THREE from 'three' import { PathPlayerPos, PathUwbDev, PathCamera, PathUwbAGV } from './symbolSvg.js' import { Laycon, LAYER_ID_DEFS, LAYER_DEFINES } from './laycon.js' import { DrawEx } from './drawExFunc.js' import { ClusterLayer } from './MyClusterLayer' // import Color from 'color' // import colorString from 'color-string' // const ANC_DEFAULT_MARKER_FILL = '#0000FF' // const ANC_SELECT_MARKER_FILL = '#FF0000' function createResolutions() { const resolutions = [] var d = 2 * 6378137 * Math.PI for (var i = 0; i < 48; i++) { resolutions[i] = d / (256 * Math.pow(2, i)) } return resolutions } const ATLAS_VERSION = 210 class BackgroundConf { mode = 0 minLng = -10000 minLat = -10000 maxLng = 10000 maxLat = 10000 color = '#ffffff' url = 'static/img/white.png' fromJSON(jObj) { this.mode = Number(jObj['mode']) || 0 if (jObj.hasOwnProperty('minLng')) { this.minLng = Number(jObj['minLng']) } if (jObj.hasOwnProperty('maxLng')) { this.maxLng = Number(jObj['maxLng']) } if (jObj.hasOwnProperty('minLat')) { this.minLat = Number(jObj['minLat']) } if (jObj.hasOwnProperty('maxLat')) { this.maxLat = Number(jObj['maxLat']) } this.url = jObj['url'] || '' this.color = jObj['color'] || '#ffffff' } toJSON() { return { mode: this.mode, minLng: this.minLng, maxLng: this.maxLng, minLat: this.minLat, maxLat: this.maxLat, url: this.url, color: this.color } } } class AtlasConf { name = '' oldVer = 0 id = 0 speedLimit = -1 altitudeMode = 0 encrypt = 1 // 0: no encrypt, 1: AES-128-CBC PKCS#7 mapMode = 2 centerX = 113.18553 centerY = 22.01226 // centerX = 0 // centerY = 0 zoom = 18 fromJSON(jObj) { if (!jObj) jObj = {} this.name = jObj['name'] || '' this.oldVer = jObj['version'] || 0 this.id = Number(jObj['id']) || 0 this.speedLimit = Number(jObj['speedLimit']) || 0 this.altitudeMode = Number(jObj['altitudeMode']) || 0 this.mapMode = Number(jObj['mapMode']) || 0 this.centerX = Number(jObj['centerX']) || 0 this.centerY = Number(jObj['centerY']) || 0 } toJSON() { return { id: this.id, name: this.name, version: ATLAS_VERSION, speedLimit: this.speedLimit, altitudeMode: this.altitudeMode, encrypt: 1, mapMode: this.mapMode, centerX: this.centerX, centerY: this.centerY } } } const DRAW_MODE = { SELECT: 'select', POLYGON: 'polygon', CIRCLE: 'circle', ELLIPSE: 'ellipse', RECTANGLE: 'rectangle', LABEL: 'label', TEXTBOX: 'textbox', PLAYER: 'player', ANCHOR: 'anchor' } class MapEngine { _node = null // 关联的DOM节点 _tickRefresh = 0 // 刷新计时器 _editable = false // 是否启用编辑 _drawMode = '' // 特殊的绘制模式,如player, anchor等 _drawProperties = null /** * {maptalks.Map} */ mt = null // MapTalks的Map实例 bearing = 0 bgConf = new BackgroundConf() centerX = 0 centerY = 0 cfg = new AtlasConf() drawExFunc = null // DrawTool 的特殊绘制函数:基站、文本、文本框等 drawTool = null // DrawTool实例 drawSymbol = { // DrawTool的Symbol实例 'lineWidth': 1, 'lineColor': 'rgba(0,0,0,1)', 'textSize': 10, 'textFill': 'rgba(0,0,0,1)', 'textHaloRadius': 1, 'textHaloFill': 'rgba(255,255,255,1)', 'fillColor': 'rgba(135,135,135,1)', 'altitude': 0, 'height': 0, 'boxWidth': 200, 'boxHeight': 30 } laycons = {} // 全部图层容器 _layer3d = null // 特别的 3D 图层 originView = null // 原始视图 pitch = 0 raycaster = null resolutions = createResolutions() selLaycon = null // 当前选择的图层 selGeos = [] // 选择的物件 zoom = 5 _tags = [] // 保持关联的TAG列表 _sheets = [] // 保持关联的SHEET列表 _anchors = [] // 保持关联的ANCHOR列表 isRt = true cbAddGeometry = null // 新增Geometry的回调函数 cbAddDevice = null // 新增Device的回调函数 cbOnClickCluster = null // 点击Cluster的回调函数 cbOnClusterChange = null // Cluster改变回调函数 cbOnClickMarker = null // 点击Marker的回调函数 _clusterEnableMode = { 'sign': 21 } // 保持的Cluster启用模式 constructor(opts) { if (!opts) opts = {} this.init(opts) } get editable() { return this._editable } set editable(val) { this._editable = val this.resetMapOperation() } get selLayer() { return this.selLaycon ? this.selLaycon.layer : null } get toolSymbol() { return this.drawTool ? this.drawTool.options['symbol'] : null } getClusterEnable(layerName) { if (this._clusterEnableMode[layerName]) { return this._clusterEnableMode[layerName] } return null } setClusterEnable(layerName, mode) { const layer = this.getLayer(layerName) if (layer) { layer.config('maxClusterZoom', mode) this._clusterEnableMode[layerName] = mode } } _initMapClick() { this.mt.on('click', (e) => { // Editable if (this.editable) { if (this.selLaycon && this.selLaycon.layer !== this._layer3d) { if (this._drawMode) { if (this.drawExFunc) { this.drawExFunc(this, e.coordinate, this.drawSymbol, this._drawMode) } this._drawMode = null } else { if (this.drawTool && this.drawTool.isEnabled()) { return } // reset colors this.clearSelection() this.selGeos = [] if (this.selLaycon && this.selLaycon.layer) { this.selLaycon.layer.forEach(function (g) { g.endEdit() g.config('draggable', false) }) } // identify this.mt.identify( { 'coordinate': e.coordinate, 'layers': [this.selLaycon.layer] }, (geos) => { this.selGeos = geos geos.forEach(function (g) { if (g.isLock) return g.config('draggable', true) g.startEdit() if (g.getJSONType().toLowerCase() === 'textbox' || g.getJSONType().toLowerCase() === 'label') { // g.endEdit() } // console.log('sel geo', g) }) this.adapterModel() } ) } } else { const mouse = new THREE.Vector2() const width = this._node.offsetWidth const height = this._node.offsetHeight // console.log('width', width, 'height', height) mouse.x = (e.domEvent.layerX / width) * 2 - 1 mouse.y = -(e.domEvent.layerY / height) * 2 + 1 const objects = [] if (this.selLaycon) { this.selLaycon.getScene().children.forEach(child => { if (child instanceof THREE.Mesh) { objects.push(child) } }) this.raycaster.setFromCamera(mouse, this.selLaycon.getCamera()) } const intersects = this.raycaster.intersectObjects(objects) if (intersects.length > 0) { const m = intersects[0] console.log('m', m) // console.log('Mesh', intersects[0]) } } } else { // Not editable const signLayer = this.getLayer('sign') if (signLayer) { const rtn = signLayer.identify(e.coordinate) if (rtn && rtn.children && rtn.children.length && this.cbOnClickCluster) { this.cbOnClickCluster('sign', rtn) } } const tagLayer = this.getLayer('tag') if (tagLayer) { const rtn = tagLayer.identify(e.coordinate) if (rtn && rtn.children && rtn.children.length && this.cbOnClickCluster) { this.cbOnClickCluster('tag', rtn) } } } }) } _initMapDblClick() { this.mt.on('dblclick', (e) => { if (this.editable) { const vecDisableEditType = ['dylabel'] if (this.selLaycon && this.selLaycon.layer !== this._layer3d) { this.selGeos.forEach(function (g) { if (g.getJSONType().toLowerCase() === 'textbox' || g.getJSONType().toLowerCase() === 'label') { const props = g.getProperties() if (props && props['type'] && vecDisableEditType.indexOf(props['type']) >= 0) { return } g.endEdit() g.startEditText() } }) } } }) } _initContextMenu() { if (this.editable) { const options = { 'items': [ '-', { item: '取消绘制', click: () => { if (self.drawTool) { self.drawTool.disable() } } }, { item: '取消选择', click: () => { self.clearSelection() } } ] } if (this.mt) { this.mt.setMenu(options) } } else { if (this.mt) { this.mt.removeMenu() } } } _initDrawTool() { console.log('initDrawTool') this.drawTool = new maptalks.DrawTool({ mode: 'Point' }).addTo(this.mt).disable() this.drawExFunc = DrawEx this.drawTool.on('drawstart', (param) => { }) this.drawTool.on('drawend', (param) => { console.log('param', param) param.geometry.properties = param.geometry.getSymbol() if (!param.geometry.properties) { param.geometry.properties = {} } param.geometry.properties['altitude'] = this.drawSymbol.altitude param.geometry.properties['height'] = this.drawSymbol.height if (this.selLaycon) { Math.random() Math.random() if (!param.geometry.getId()) { const id = this.selLaycon.name + Math.floor((new Date().getTime() / 1000) % 1000000000) param.geometry.setId(id) param.geometry.isLock = false } switch (this.selLaycon.name) { case 'border': param.geometry.properties['limit'] = 0 break case 'area': param.geometry.properties['addr'] = 0 param.geometry.properties['level'] = 0 param.geometry.properties['delay'] = 0 // this.tmpPoly = param.geometry.getCoordinates()[0] // this.tmpPoly.splice(this.tmpPoly.length - 1, 1) // console.log('area poly', this.tmpPoly) break case 'thing': // const c = param.geometry.getCoordinates() // console.log('thing', c) // if (this.tmpPoly.length) { // const b = pointInPolygon(this.tmpPoly, c) // console.log('pointInPolygon', b) // if (b) { // const vec = nearPoints(this.tmpPoly, c) // const svec = this.tmpPoly.slice(0) // console.log('near pts', svec) // for (let i = 0; i < svec.length - 1; i++) { // const co = getCrossPtInOrthByPts(svec[i], svec[i + 1], c) // if (co.x < Math.min(svec[i].x, svec[i + 1].x) || // co.x > Math.max(svec[i].x, svec[i + 1].x) || // co.y < Math.min(svec[i].y, svec[i + 1].y) || // co.y > Math.max(svec[i].y, svec[i + 1].y) // ) { // continue // } // vec.push(co) // } // console.log('candidate pts', vec) // let m = -1 // let pickC = null // for (const v of vec) { // const dis = (v.x - c.x) * (v.x - c.x) + (v.y - c.y) * (v.y - c.y) // if (m > dis || m < 0) { // m = dis // pickC = v // } // } // console.log('pick pt', pickC) // // const jObj = param.geometry.toJSON() // const tmpGeo = maptalks.Geometry.fromJSON(jObj) // tmpGeo.setCoordinates(pickC) // const id2 = this.selectLayer.getId() + Math.floor(Math.random() * 10000) // tmpGeo.setId(id2) // this.selectLayer.addGeometry(tmpGeo) // } // } break } if (this._drawProperties) { param.geometry.setProperties(this._drawProperties) } this._drawProperties = null param.geometry.properties['name'] = param.geometry.getId() param.geometry.properties['action'] = '' this.selLaycon.addGeometry(param.geometry) if (this.cbAddGeometry) { this.cbAddGeometry(param.geometry, this.selLaycon.name) } } // this.mapEngine.selLaycon.addGeometry(param.geometry) this.drawTool.disable() }) // const toolSymbol = this.drawTool.options['symbol'] // toolSymbol.lineWidth = this.drawSymbol.lineWidth // toolSymbol.lineColor = this.drawSymbol.lineColor // toolSymbol.polygonFill = this.drawSymbol.fillColor // toolSymbol.textSize = this.drawSymbol.textSize // toolSymbol.textFill = this.drawSymbol.textFill // toolSymbol.textHaloRadius = this.drawSymbol.textHaloRadius // toolSymbol.textHaloFill = this.drawSymbol.textHaloFill // toolSymbol.altitude = this.drawSymbol.altitude // toolSymbol.boxWidth = this.drawSymbol.boxWidth // toolSymbol.boxHeight = this.drawSymbol.boxHeight } adapterModel() { if (this.selGeos.length <= 0 && this.drawTool) { const toolSymbol = this.drawTool.options['symbol'] this.drawSymbol.lineWidth = toolSymbol.lineWidth || this.drawSymbol.lineWidth this.drawSymbol.lineColor = toolSymbol.lineColor || this.drawSymbol.lineColor this.drawSymbol.fillColor = toolSymbol.polygonFill || this.drawSymbol.polygonFill this.drawSymbol.textSize = toolSymbol.textSize || this.drawSymbol.textSize this.drawSymbol.textFill = toolSymbol.textFill || this.drawSymbol.textFill this.drawSymbol.textHaloRadius = toolSymbol.textHaloRadius || this.drawSymbol.textHaloRadius this.drawSymbol.textHaloFill = toolSymbol.textHaloFill || this.drawSymbol.textHaloFill this.drawSymbol.altitude = toolSymbol.altitude || this.drawSymbol.altitude this.drawSymbol.height = toolSymbol.height || this.drawSymbol.height this.drawSymbol.boxWidth = toolSymbol.boxWidth || this.drawSymbol.boxWidth this.drawSymbol.boxHeight = toolSymbol.boxHeight || this.drawSymbol.boxHeight return } const m = this.selGeos[0] switch (m.getJSONType().toLowerCase()) { case 'textbox': this.drawSymbol.lineWidth = m.getBoxSymbol().markerLineWidth this.drawSymbol.lineColor = m.getBoxSymbol().markerLineColor this.drawSymbol.fillColor = m.getBoxSymbol().markerFill this.drawSymbol.textSize = m.getTextStyle().symbol.textSize this.drawSymbol.textFill = m.getTextStyle().symbol.textFill this.drawSymbol.textHaloRadius = m.getTextStyle().symbol.textHaloRadius this.drawSymbol.textHaloFill = m.getTextStyle().symbol.textHaloFill this.drawSymbol.boxWidth = m.getWidth() this.drawSymbol.boxHeight = m.getHeight() break case 'label': this.drawSymbol.lineWidth = m.getBoxStyle().symbol.markerLineWidth this.drawSymbol.lineColor = m.getBoxStyle().symbol.markerLineColor this.drawSymbol.fillColor = m.getBoxStyle().symbol.markerFill this.drawSymbol.textSize = m.getTextSymbol().textSize this.drawSymbol.textFill = m.getTextSymbol().textFill this.drawSymbol.textHaloRadius = m.getTextSymbol().textHaloRadius this.drawSymbol.textHaloFill = m.getTextSymbol().textHaloFill this.drawSymbol.boxWidth = m.getBoxStyle().minWidth this.drawSymbol.boxHeight = m.getBoxStyle().minHeight break default: this.drawSymbol.lineWidth = m.properties.lineWidth || 0 this.drawSymbol.lineColor = m.properties.lineColor || '#000000' this.drawSymbol.fillColor = m.properties.polygonFill || '#333333' break } this.drawSymbol.altitude = m.properties.altitude || 0 this.drawSymbol.height = m.properties.height || 0 } addAnchor(anchor) { if (!anchor) return for (const v of this._anchors) { if (v.addr == anchor.addr) { return } } this._anchors.push(anchor) const start = { x: anchor.x, y: anchor.y } const geo = new maptalks.Marker( anchor.coordinate || start, { 'symbol': { 'markerType': 'path', 'markerPath': PathUwbDev, 'markerPathWidth': 1024, 'markerPathHeight': 1024, 'markerFill': '#0000ff', // will override tiger path's style properties // 'markerLineColor' : 12, 'markerWidth': 32, 'markerHeight': 32, 'markerDy': 16, 'markerDx': 0, 'textName': 'A' + anchor.addr, 'textDy': -20 }, 'properties': { 'altitude': anchor.altitude, 'addr': anchor.addr, 'name': anchor.name, 'jobId': anchor.jobId } } ) geo.setId(anchor.id || anchor.name) geo.isLock = false geo.setCoordinates(anchor.coordinates || start) this.getLayer('anchor').addGeometry(geo) if (anchor.setGeo) { anchor.setGeo(geo) } } addArea(area) { const geo = maptalks.GeoJSON.toGeometry(area.geoJson) geo.setId(area.id || area.name) geo.updateSymbol(geo.properties) geo.isLock = false this.getLayer('area').addGeometry(geo) } addDataItem(dataItem) { const geo = maptalks.Geometry.fromJSON(dataItem.geoJson) geo.setId(dataItem.id || dataItem.name) geo.isLock = false geo.properties.defVal = geo.properties.val this.getLayer('data').addGeometry(geo) } addDevice(device) { let markerPath = [] switch (device.type) { case 'camera': markerPath = PathCamera break } const geo = new maptalks.Marker( device.coordinate, { 'symbol': { 'markerType': 'path', 'markerPath': markerPath, 'markerPathWidth': 1024, 'markerPathHeight': 1024, 'markerFill': '#0000ff', // will override tiger path's style properties // 'markerLineColor' : 12, 'markerWidth': 32, 'markerHeight': 32, 'markerDy': 0, 'markerDx': 0, 'textName': '{jobId}\nA{addr}', 'textDy': 14 }, 'properties': device.props } ) geo.setId(device.id || device.name) geo.isLock = false geo.setCoordinates(device.coordinates) this.getLayer('device').addGeometry(geo) if (this.cbAddDevice) { this.cbAddDevice(device, geo) } } addLaycon(laycon) { if (!this.laycons.hasOwnProperty(laycon.name)) { this.laycons[laycon.name] = laycon this.mt.addLayer(laycon.layer) } return this } /** * * @param {Array} signs * @param {Array} mainTypeDefs */ addSigns(signs, mainTypeDefs) { const markers = [] for (const sign of signs) { let symbol = {} if (mainTypeDefs && sign.mainType < mainTypeDefs.length && mainTypeDefs[sign.mainType].iconBase64) { symbol = { 'markerFile': mainTypeDefs[sign.mainType].iconBase64, 'markerPathWidth': 1024, 'markerPathHeight': 1024, 'markerFill': '#ff4444', // will override tiger path's style properties // 'markerLineColor' : 12, 'markerWidth': 32, 'markerHeight': 32, 'markerDy': 0, 'markerDx': 0, 'textName': '{id}', 'textWeight': 900, 'textDy': 10 } } else { symbol = { 'markerType': 'Ellipse', 'markerPathWidth': 1024, 'markerPathHeight': 1024, 'markerFill': '#ff4444', // will override tiger path's style properties // 'markerLineColor' : 12, 'markerWidth': 32, 'markerHeight': 32, 'markerDy': 0, 'markerDx': 0, 'textName': '{id}', 'textWeight': 900, 'textDy': 10 } } const m = new maptalks.Marker( [sign.lng, sign.lat], { 'symbol': symbol, 'properties': { 'id': sign.id, 'mainType': sign.mainType } } ).on('click', () => { if (this.cbOnClickMarker) { this.cbOnClickMarker(m, sign) } }) markers.push(m) } const layerSign = this.getLayer('sign') if (layerSign) { layerSign.addGeometry(markers) } this.setSelLaycon('sign') } replay(line, dt, needShow, finishClear, pathCoords) { // console.log('needShow', needShow) //line's animateShow line.hide() if (needShow) { line.animateShow({ duration: dt, easing: 'linear' }, function (frame) { if (frame.state.playState === 'finished') { if (finishClear) { if (line.tag) { if (line.tag.path) { line.tag.path.setCoordinates(pathCoords) } line.tag.removeLine(line) } } } }) // if (line.createTime) { // line.show() // } else { // // } } else { line.hide() } } addSheet(sheet) { if (!sheet) return for (const v of this._sheets) { if (v.id === sheet.id) { return } } const geo = maptalks.GeoJSON.toGeometry(sheet.geoJson) geo.setId(sheet.id || sheet.name) geo.updateSymbol(geo.properties) geo.isLock = false geo.sheet = sheet sheet.setGeo(geo) this.getLayer(LAYER_DEFINES[LAYER_ID_DEFS.IdSheet].name).addGeometry(geo) } addPath(x, y, addr, needShow, color, lineWidth = 6) { const tag = this.getTag(addr) if (!tag) return const traceLayer = this.getLayer('trace') tag.removeLines(-1) if (!tag.path) { tag.path = new maptalks.LineString([], { smoothness: 0.2, 'symbol': { 'lineColor': color, 'lineWidth': lineWidth, 'lineJoin': 'round', //miter, round, bevel 'lineCap': 'round', //butt, round, square 'lineDasharray': null, //dasharray, e.g. [10, 5, 5] 'lineOpacity ': 0.5 } }).addTo(traceLayer) } if (tag.showInAtlas) { if (tag.path) { tag.path.show() } } else { if (tag.path) { tag.path.hide() } } const pathCoords = tag.path.getCoordinates() pathCoords.push([x, y]) tag.path.setCoordinates(pathCoords) } addTrace(x, y, nx, ny, dt, addr, needShow, color, nowData = -1, lineWidth = 6) { const tag = this.getTag(addr) if (!tag) return const traceLayer = this.getLayer('trace') const line = new maptalks.LineString( [ [x, y], [nx, ny] ], { symbol: { 'lineColor': color, 'lineWidth': lineWidth, 'lineJoin': 'round', //miter, round, bevel 'lineCap': 'round', //butt, round, square 'lineDasharray': null, //dasharray, e.g. [10, 5, 5] 'lineOpacity ': 0.5 } } ).addTo(traceLayer) tag.addLine(line) if (tag.showInAtlas) { line.show() if (tag.path) { tag.path.show() } } else { line.hide() if (tag.path) { tag.path.hide() } } if (nowData < 0) { line.createTime = -1 } else { line.createTime = nowData } if (tag.showInAtlas) { let pathCoords = [] if (nowData < 0) { if (!tag.path) { tag.path = new maptalks.LineString([], { 'smoothness': 0.08, 'symbol': { 'lineColor': color, 'lineWidth': lineWidth, 'lineJoin': 'round', //miter, round, bevel 'lineCap': 'round', //butt, round, square 'lineDasharray': null, //dasharray, e.g. [10, 5, 5] 'lineOpacity ': 0.5 } }).addTo(traceLayer) } pathCoords = tag.path.getCoordinates() pathCoords.push(new maptalks.Coordinate([nx, ny])) } this.replay(line, dt, needShow, nowData < 0, pathCoords) } } addTag(tag, radius) { if (!tag) return for (const v of this._tags) { if (v.addr === tag.addr) { return } } const start = { x: tag.x, y: tag.y } // const end = { x: 0, y: 0 } const thingLayer = this.getLayer('tag') const geoIcon = new maptalks.Marker(start, { 'symbol': { 'markerType': 'path', 'markerPath': tag.addr > 200 ? PathUwbAGV : PathPlayerPos, 'markerPathWidth': tag.addr > 200 ? 1359 : 1024, 'markerPathHeight': 1024, 'markerFill': '#4444ff', // will override tiger path's style properties // 'markerLineColor' : 12, 'markerWidth': 24, 'markerHeight': 24, 'markerDy': 0, 'markerDx': 0, 'textFaceName': 'sans-serif', 'textName': 'name', // value from name in geometry's properties 'textWeight': 'normal', // 'bold', 'bolder' 'textStyle': 'normal', // 'italic', 'oblique' 'textSize': 20, 'textFont': null, // same as CanvasRenderingContext2D.font, override textName, textWeight and textStyle 'textFill': '#4444ff', 'textOpacity': 1, 'textHaloFill': '#fff', 'textHaloRadius': 2, 'textWrapWidth': null, 'textWrapCharacter': '\n', 'textLineSpacing': 0, 'textDx': 0, 'textDy': -40, 'textHorizontalAlignment': 'middle', // left | middle | right | auto 'textVerticalAlignment': 'middle', // top | middle | bottom | auto 'textAlign': 'center' // left | right | center | auto }, 'properties': { 'altitude': 0, 'addr': tag ? tag.addr : 0, 'name': tag ? tag.playerName : '(undefined)' } }) // geoIcon.hide() if (!thingLayer.geoIconList) { thingLayer.geoIconList = [] } thingLayer.geoIconList.push(geoIcon) // thingLayer.geoIconList.push(geoIcon) // geoIcon.noMesh = true geoIcon.updateSymbol({ 'textName': tag.playerName }) thingLayer.addGeometry(geoIcon) if (tag.setGeoIcon) { tag.setGeoIcon(geoIcon) } // const clusterLayer = this.getLayer('cluster') // const geoCluster = new maptalks.Circle(start, radius, { // 'symbol': { // lineColor: '#ffffff', // lineWidth: 2, // polygonFill: '#ff0000', // polygonOpacity: 0.4 // } // }) // if (!clusterLayer.clusterList) { // clusterLayer.clusterList = [] // } // clusterLayer.clusterList.push(geoCluster) // clusterLayer.addGeometry(geoCluster) // tag.setGeoCluster ? tag.setGeoCluster(geoCluster) : null // tag.refreshCluster(tag.cluster) this._tags.push(tag) } clean() { this.mt.remove() } clearSelection() { for (const g of this.selGeos) { g.endEdit() g.config('draggable', false) } this.selGeos = [] } visibleTag(addr, show) { const tag = this.getTag(addr) if (tag) { tag.showInAtlas = show } } sortLayers() { const sortLayerIds = [] for (const v of LAYER_DEFINES) { if (this.getLayer(v.name)) { sortLayerIds.push(v.name) } } for (const layer of this.mt.getLayers()) { if (sortLayerIds.indexOf(layer.getId()) < 0) { sortLayerIds.push(layer.getId()) } } const drawLayer = this.mt.getLayer('_maptalks__internal_layer_drawtool') if (drawLayer) { sortLayerIds.push(drawLayer.getId()) } this.mt.sortLayers(sortLayerIds) } clearTraceByTimespan(timespan) { for (const tag of this._tags) { tag.removeLines(timespan) tag.clearPath() } } create3dMesh() { const self = this const features = [] this.laycons['wall'].layer.forEach(function (g) { features.push(g.toGeoJSON()) }) this.laycons['thing'].layer.forEach(function (g) { if (!g.noMesh) { features.push(g.toGeoJSON()) } }) this._layer3d.clearMesh() this._layer3d.hide() features.forEach(function (g) { let altitude = 0 let height = 0 let color = '#333333' let opacity = 1 if (g.properties) { altitude = g.properties.altitude || altitude height = g.properties.height || height color = g.properties.polygonFill || color opacity = g.properties.polygonOpacity || opacity } const m = new THREE.MeshPhongMaterial({ color: color, opacity: opacity }) const mesh = self._layer3d.toExtrudeMesh(maptalks.GeoJSON.toGeometry(g), altitude + height, m, height) // mesh.rotateY(angle2radian(90)) if (Array.isArray(mesh)) { self._layer3d.getScene().add.apply(self._layer3d.getScene(), mesh) } else { self._layer3d.getScene().add(mesh) } }) this._layer3d.show() } createAni(styles, options, stepCb) { return maptalks.animation.Animation.animate( styles, options, stepCb) } recreateAtlas() { const jObj = this.toJSON() this.clean() this.fromJSON(jObj) } createAtlas(node) { this.zoom = this.cfg.zoom this._node = node this.centerX = this.cfg.centerX this.centerY = this.cfg.centerY this.mt = new maptalks.Map(node, { // zoomControl : true, center: [this.centerX, this.centerY], zoom: this.zoom, pitch: this.pitch, bearing: this.bearing, attribution: { 'content1': '© 上海迅见 自主研发', 'content': '' } }) // const dpr = this.mt.getDevicePixelRatio() // const scaler = dpr > 1 ? 2 : 1 console.log('mapMode', this.cfg.mapMode) if (this.cfg.mapMode != 0) { console.log('PROJECTIONS[this.cfg.mapMode]', PROJECTIONS[this.cfg.mapMode]) this.mt.setBaseLayer(new maptalks.TileLayer('base', { maxAvailableZoom: PROJECTIONS[this.cfg.mapMode].maxAvailableZoom, urlTemplate: PROJECTIONS[this.cfg.mapMode].urlTemplate, subdomains: PROJECTIONS[this.cfg.mapMode].subdomains, attribution: ' ' })) } this.mt.setSpatialReference({ resolutions: PROJECTIONS[this.cfg.mapMode].resolutions, projection: PROJECTIONS[this.cfg.mapMode].projection, fullExtent: PROJECTIONS[this.cfg.mapMode].fullExtent }) this.mt.setMaxZoom(24) this.originView = this.mt.getView() // this.focusZero() // this.focusCenter() this.raycaster = new THREE.Raycaster() this.initMapOperation() return this } createLaycons() { for (const item of LAYER_DEFINES) { const laycon = new Laycon(item, this) laycon.show() if (laycon.type === 'vec3d') { this._layer3d = laycon.layer } } } fitExtent(opts) { const inVec = ['border', 'wall', 'anchor', 'thing'] let extent = opts if (!extent) { for (const item of LAYER_DEFINES) { if (item.type === 'vector' && inVec.indexOf(item.name) >= 0) { const layer = this.getLayer(item.name) if (layer) { const e = layer.getExtent() if (e) { if (extent == null) { extent = new maptalks.Extent(e.xmin, e.ymin, e.xmax, e.ymax) } if (e.xmin < extent.xmin) { extent.xmin = e.xmin } if (e.ymin < extent.ymin) { extent.ymin = e.ymin } if (e.xmax > extent.xmax) { extent.xmax = e.xmax } if (e.ymax > extent.ymax) { extent.ymax = e.ymax } } } } } } this.mt.fitExtent(extent, 0, { 'duration': 50 }) return extent } fromJSON(jObj, additionAnchors = null, additionDevices = null) { this.mt.remove() this._drawMode = null this.laycons = {} this._layer3d = null this.selGeos = [] // load mt cfg this.cfg.fromJSON(jObj.cfg) this.centerX = this.cfg.centerX this.centerY = this.cfg.centerY let content = {} content = jObj.content this.zoom = this.cfg.zoom // load mt this.mt = maptalks.Map.fromJSON(this._node, content.mt) this.mt.setMaxZoom(30) if (!this.mt.getBaseLayer()) { if (this.cfg.mapMode != 0) { console.log('PROJECTIONS[this.cfg.mapMode]', PROJECTIONS[this.cfg.mapMode]) this.mt.setBaseLayer(new maptalks.TileLayer('base', { maxAvailableZoom: PROJECTIONS[this.cfg.mapMode].maxAvailableZoom, maxZoom: 24, urlTemplate: PROJECTIONS[this.cfg.mapMode].urlTemplate, subdomains: PROJECTIONS[this.cfg.mapMode].subdomains, attribution: ' ' // tileSystem: PROJECTIONS[this.cfg.mapMode].tileSystem, })) } } this.mt.config('seamlessZoom', true) this.mt.config('spatialReference', { resolutions: PROJECTIONS[this.cfg.mapMode].resolutions, projection: PROJECTIONS[this.cfg.mapMode].projection, fullExtent: PROJECTIONS[this.cfg.mapMode].fullExtent }) this.mt.config('fpsOnInteracting', 0) // load vector layers for (const item of LAYER_DEFINES) { for (const layer of this.mt.getLayers()) { if (layer.getId() === item.name) { const laycon = new Laycon(item, this, layer) if (this.editable) { laycon.show() } else { item.runShow ? laycon.show() : laycon.hide() } if (laycon.type === 'vec3d') { this._layer3d = laycon.layer } break } } } // load bg conf this.bgConf.fromJSON(jObj.bgConf) const bgLaycon = new Laycon(LAYER_DEFINES[LAYER_ID_DEFS['IdBg']], this) this.refreshBg(this.bgConf) bgLaycon.show() let laycon = null // load anchors let idxLaycon = LAYER_ID_DEFS['IdAnchor'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) !this.editable && LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() const anchors = jObj.content.anchors || [] for (const anc of anchors) { this.addAnchor(anc) } // load data idxLaycon = LAYER_ID_DEFS['IdData'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) !this.editable && LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() const dataItems = content.dataItems || [] for (const d of dataItems) { this.addDataItem(d) } // load devices idxLaycon = LAYER_ID_DEFS['IdDevice'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) !this.editable && LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() const devices = content.devices || [] for (const dev of devices) { if (additionDevices) { for (const v of additionDevices) { if (Number(v.addr) === Number(dev.addr)) { dev.jobId = v.jobId break } } } this.addDevice(dev) } // load areas idxLaycon = LAYER_ID_DEFS['IdArea'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) !this.editable && LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() const areas = jObj.content.areas || [] for (const area of areas) { this.addArea(area) } // load trace idxLaycon = LAYER_ID_DEFS['IdTrace'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) !this.editable && LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() // create 3D idxLaycon = LAYER_ID_DEFS['Id3d'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) this._layer3d = laycon.layer this._layer3d.clearMesh() this._layer3d.show() // create sheet idxLaycon = LAYER_ID_DEFS['IdSheet'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) this.editable || LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() // create tag idxLaycon = LAYER_ID_DEFS['IdTag'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) this.editable || LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() // create stat idxLaycon = LAYER_ID_DEFS['IdStat'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) this.editable || LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() // create heat idxLaycon = LAYER_ID_DEFS['IdHeat'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) this.editable || LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() // create sign if (this.getLaycon('sign')) { this.removeLaycon('sign') } idxLaycon = LAYER_ID_DEFS['IdSign'] laycon = new Laycon(LAYER_DEFINES[idxLaycon], this) this.editable || LAYER_DEFINES[idxLaycon].runShow ? laycon.show() : laycon.hide() const sortLayerIds = [] for (const v of LAYER_DEFINES) { if (this.getLayer(v.name)) { sortLayerIds.push(v.name) } } this.mt.sortLayers(sortLayerIds) // load view const view = content.view view ? this.mt.setView(view) : null this.originView = this.mt.getView() this.raycaster = new THREE.Raycaster() this.initMapOperation() this._tickRefresh++ this.sortLayers() this.focusCenter() } getLaycon(name) { return this.laycons[name] } getLayer(name) { if (this.laycons[name]) { return this.laycons[name].layer } return null } getMap() { return this.mt } getTag(addr) { for (const tag of this._tags) { if (tag.addr === Number(addr)) { return tag } } return null } init(opts) { if (!opts) opts = {} } initMapOperation() { this._initMapClick() this._initMapDblClick() this.mt.on('addlayer', (type, target, layers) => { //console.log('addlayer', type, target) this.sortLayers() }) this.mt.on('removelayer', (type, target, layers) => { //console.log('removelayer', type, target) }) this.resetMapOperation() this.mt.on('cluster-compute-grid', () => { if (this.cbOnClusterChange) { this.cbOnClusterChange() } }) } resetMapOperation() { this._initContextMenu() if (this.drawTool) { this.removeControl(this.drawTool) this.drawTool = null } if (this.editable) { this._initDrawTool() } } atlasId() { return this.cfg.id } atlasName() { return this.cfg.name } clearFocusTag() { for (let i = 0; i < this._tags.length; i++) { const tag = this._tags[i] tag.isFocus = false } } refreshBg(bgConf) { this.bgConf.fromJSON(bgConf) const ly = this.getLayer('bg') if (ly) { ly.setImages([ { url: this.bgConf.url, extent: [this.bgConf.minLng, this.bgConf.minLat, this.bgConf.maxLng, this.bgConf.maxLat], opacity: 1 } ]) } else { console.log('not found layer bg') } this.focusCenter() } removeAllTags() { for (let i = 0; i < this._tags.length; i++) { const tag = this._tags[i] tag.geoIcon ? tag.geoIcon.remove() : null tag.geoCluster ? tag.geoCluster.remove() : null tag.setGeoIcon(null) tag.setGeoCluster(null) } this._tags.splice(0, this._tags.length) } removeControl(control) { this.mt.removeControl(control) } removeLaycon(name) { if (this.laycons[name]) { this.mt.removeLayer(this.laycons[name].layer) delete this.laycons[name] } } removeAnchorByAddr(addr) { for (let i = 0; i < this._anchors.length; i++) { const anchor = this._anchors[i] if (anchor.addr === Number(addr)) { anchor.geo ? anchor.geo.remove() : null anchor.setGeo(null) this._anchors.splice(i, 1) return } } } removeTagByAddr(tagAddr) { for (let i = 0; i < this._tags.length; i++) { const tag = this._tags[i] if (tag.addr === Number(tagAddr)) { tag.geoIcon ? tag.geoIcon.remove() : null tag.geoCluster ? tag.geoCluster.remove() : null tag.setGeoIcon(null) tag.setGeoCluster(null) this._tags.splice(i, 1) return } } } returnOriginView() { this.mt.setView(this.originView) } setDrawMode(opt, layerName, properties) { if (this.editable && this.mt && this.drawTool) { this._drawProperties = properties switch (opt) { case 'anchor': this._drawMode = 'anchor' break case 'dylabel': this._drawMode = 'dylabel' break case 'label': this._drawMode = 'label' break case 'player': this.drawTool.endDraw() this.drawTool.setMode('point').enable() break case 'select': this.drawTool.disable() break case 'textbox': this._drawMode = 'textbox' break case 'camera': this._drawMode = 'camera' break default: this.drawTool.endDraw() this.drawTool.setMode(opt).enable() break } if (layerName) { this.setSelLaycon(layerName) } } } focusCenter() { this.mt.setCenter({ x: this.centerX, y: this.centerY }) } focusPt(x, y) { this.mt.setCenter({ x: x, y: y }) } focusZero() { this.mt.setCenter({ x: 0, y: 0 }) } setCfg(cfg) { this.cfg.fromJSON(cfg) } setSelLaycon(name) { this.selLaycon = this.laycons[name] return this } setZoom(zoom) { this.mt.setZoom(zoom) } showLaycon(name) { const laycon = this.getLaycon(name) laycon ? laycon.show() : null } ChangeTo3D() { const self = this console.log('切换为3d') console.log('layer 3d c', this._layer3d, this._layer3d.getScene()) const c2 = new maptalks.Coordinate([0.1, 0.1]) this.mt.panBy(c2, { during: 600 }) const targetStyles = { opacity: 1 } const aniGlow = maptalks.animation.Animation.animate( targetStyles, { duration: 1000, easing: 'out' }, // callback of each frame function step(frame) { if (frame.state.playState === 'running') { if (self._layer3d) { self._layer3d.setOpacity(frame.styles.opacity) } } if (frame.state.playState === 'finished') { if (self._layer3d) { self._layer3d.setOpacity(1) } } } ) setTimeout(() => { this.mt.config('dragRotatePitch', true) this.mt.config('dragRotate', true) this.mt.config('dragPitch', true) this.mt.animateTo({ // center: [-74.10704772446428, 40.66032606133018], // zoom: 18, pitch: 50, bearing: 50 }, { duration: 1000 }, (frame) => { if (frame.state.playState === 'finished') { this.create3dMesh() this._layer3d.setOpacity(0) this._layer3d.hide() this._layer3d.show() setTimeout(() => { aniGlow.play() }, 100) // setTimeout(() => { // this.create3dMesh() // this.layer3d.hide() // this.layer3d.show() // }, 600) } }) }, 650) } ChangeTo2D() { console.log('2d') this._layer3d.clearMesh() this.mt.animateTo({ // center: [-74.10704772446428, 40.66032606133018], // zoom: 18, pitch: 0, bearing: 0 }, { duration: 1500 }, (frame) => { if (frame.state.playState === 'finished') { // setTimeout(() => { // this.mt.config('dragRotate', false) // this.mt.config('dragPitch', false) // }, 800) } }) } toggleLaycon(name) { const laycon = this.getLaycon(name) if (laycon) { if (laycon.isVisible()) { laycon.hide() return false } else { laycon.show() return true } } return false } showAnchors() { const laycon = this.getLaycon('anchor') laycon.show() } showSheets() { const laycon = this.getLaycon('sheet') laycon.show() } hideAnchors() { const laycon = this.getLaycon('anchor') laycon.hide() } hideSheets() { const laycon = this.getLaycon('sheet') laycon.hide() } tickRefresh() { return this._tickRefresh } toJSON() { const obj = {} // export vector layers const layerExports = [] layerExports.push({ id: 'border' }) layerExports.push({ id: 'wall' }) layerExports.push({ id: 'thing' }) layerExports.push({ id: 'label' }) layerExports.push({ id: 'sign' }) // layerExports.push({ id: 'anchor' }) // layerExports.push({ id: 'area' }) // save map bg obj.bgConf = this.bgConf.toJSON() // save map defines obj.cfg = this.cfg.toJSON() const content = {} content.mt = this.mt.toJSON({ layers: layerExports }) content.view = this.mt.getView() content.anchors = [] this.getLayer('anchor').forEach((g) => { content.anchors.push({ id: g.getId(), name: g.properties['name'] || '', addr: g.properties['addr'] || 0, altitude: g.properties['altitude'] || 0, coordinates: g.getCoordinates() }) }) content.areas = [] this.getLayer('area').forEach((g) => { content.areas.push({ id: g.getId(), addr: g.properties['addr'] || 0, level: g.properties['level'] || 0, delay: g.properties['delay'] || 0, coordinates: g.getCoordinates(), geoJson: g.toGeoJSON() }) }) content.dataItems = [] this.getLayer('data').forEach((g) => { content.dataItems.push({ id: g.getId(), props: g.properties, type: g.properties['type'] || 'unknown', coordinates: g.getCoordinates(), geoJson: g.toJSON() }) }) content.devices = [] this.getLayer('device').forEach((g) => { content.devices.push({ id: g.getId(), props: g.properties, type: g.properties['type'] || 'unknown', coordinates: g.getCoordinates(), geoJson: g.toGeoJSON() }) }) //obj.content = content obj.content = (JSON.stringify(content)) // save anchor includes return obj } updateLayconData(source) { const ly = this.getLayer('data') if (!ly) { return } ly.forEach((g) => { const props = g.getProperties() const bindDev = g.properties['bindDev'] const bindAddr = g.properties['bindAddr'] const bindProp = g.properties['bindProp'] const bindEnable = g.properties['bindEnable'] if (source[bindDev]) { const src = source[bindDev] if (src instanceof Array) { for (const v of src) { if (v && Number(v['addr']) === Number(bindAddr)) { let flag = true if (bindEnable && !v[bindEnable]) { flag = false } if (flag) { const p = Math.pow(10, Number(props['precision'])) props['val'] = Math.round(Number(v[bindProp]) * p) / p g.setProperties(props) } else { props['val'] = props['defVal'] g.setProperties(props) } break } } } else { if (src[bindAddr]) { let flag = true if (bindEnable && !src[bindAddr][bindEnable]) { flag = false } if (flag) { const p = Math.pow(10, Number(props['precision'])) props['val'] = Math.round(Number(src[bindAddr][bindProp]) * p) / p g.setProperties(props) } else { props['val'] = props['defVal'] g.setProperties(props) } } } } }) } updateHisLayconData(src, devType) { this.getLayer('data').forEach((g) => { const props = g.getProperties() const hisEnable = props['hisEnable'] if (!hisEnable) return const bindHisDev = g.properties['bindHisDev'] if (Number(bindHisDev) !== Number(devType)) return const bindHisAddr = g.properties['bindHisAddr'] const bindHisProp = g.properties['bindHisProp'] const bindHisEnable = g.properties['bindHisEnable'] if (src && Number(src['addr']) === Number(bindHisAddr)) { let flag = true if (bindHisEnable && !src[bindHisEnable]) { flag = false } if (flag) { const p = Math.pow(10, Number(props['precision'])) props['val'] = Math.round(Number(src[bindHisProp]) * p) / p g.setProperties(props) } else { props['val'] = props['defVal'] g.setProperties(props) } } }) } } const PROJECTIONS = [ { id: 0, name: '虚拟底图', resolutions: createResolutions(), projection: 'identity', urlTemplate: null, subdomains: null, fullExtent: { 'top': 10000, 'left': -10000, 'bottom': -10000, 'right': 10000 } }, { id: 1, name: '默认底图', resolutions: createResolutions(), projection: null, urlTemplate: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c', 'd'] }, { id: 2, name: '百度底图', resolutions: null, // resolutions: createResolutions(), projection: 'baidu', maxAvailableZoom: 19, // urlTemplate: 'https://maponline{s}.bdimg.com/tile/?qt=vtile&styles=pl&from=jsapi2_0&x={x}&y={y}&z={z}&styles=pl&scaler=1&udt=', urlTemplate: 'https://maponline{s}.bdimg.com/tile/?qt=vtile&styles=pl&from=jsapi2_0&x={x}&y={y}&z={z}&styles=pl&scaler=1&udt=2019&scale=2', subdomains: [0, 1, 2, 3] }, // { // id: 2, name: '百度底图', // resolutions: null, // resolutions: createResolutions(), // // projection: 'baidu', // maxAvailableZoom: 18, // // urlTemplate: 'https://maponline{s}.bdimg.com/tile/?qt=vtile&styles=pl&from=jsapi2_0&x={x}&y={y}&z={z}&styles=pl&scaler=1&udt=', // urlTemplate: 'https://swapi2.siweiearth.com/sj_raster/v6/wmts/tile/10001601/2?ak=mt5b5ec0404e8795f7b267d7f3b94fc595&tilematrix={z}&tilecol={x}&tilerow={y}&request=gettile&service=wmTs', // subdomains: ['0', '1', '2', '3'], // }, { id: 3, name: '百度统计底图', resolutions: null, projection: 'baidu', urlTemplate: 'https://gss{s}.bdstatic.com/8bo_dTSlRsgBo1vgoIiO_jowehsv/tile/?qt=tile&x={x}&y={y}&z={z}&styles=pl&scaler=1&udt=20170927', subdomains: [0, 1, 2, 3] }, { id: 4, name: '腾讯地图', resolutions: null, urlTemplate: 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0', subdomains: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], tileSystem: 'tms-global-mercator' }, { id: 5, name: '高德底图', resolutions: createResolutions(), urlTemplate: 'http://wprd{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&z={z}&x={x}&y={y}&scl=1<ype=11', subdomains: ['01', '02', '03', '04'] }, { id: 6, name: '天地图底图', resolutions: createResolutions(), maxAvailableZoom: 18, urlTemplate: 'https://t5.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=074d4a21c8f34a3a20cd1f69f81b26bf', subdomains: ['a', 'b', 'c', 'd'] //负载均衡-以提高地图数据加载的效率 }, { id: 7, name: 'ARC-GIS底图', resolutions: createResolutions(), maxAvailableZoom: 18, urlTemplate: 'https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', subdomains: null } ] export { MapEngine, DRAW_MODE, PROJECTIONS }