Source: tmapwms.js

import mapwms from './mapwms'
import {
    BASELAYER,
    FOCUSLAYER,
    EXTRALAYER,
    codec
} from './helper/tools'
import anime from 'animejs'
import axios from 'axios'
import Qs from 'qs'

/**
 * 扩展天地图Overlay,以实现自定义wms图层
 */
function defineLayer() {
    var PostwmsLayer = T.Overlay.extend({

        _layerContainer: null,
        _map: null,
        _pixelOrigin: null,
        _ogc: null,
        _extra: null,
        _levels: {}, //{zoom:number,origin:[number,number],tiles:[{index,x,y,bbox,img}]}
        _tileSize: 256,
        _options: {
            opacity: 1,
        },

        //初始化方法
        initialize: function (ogc, extra, options) {
            this._ogc = ogc
            this._extra = extra
            if (options)
                Object.assign(this._options, options)
        },

        /**
         * onAdd接口-加入到地图时执行
         * @param {*} map 
         */
        onAdd: function (map) {
            this._map = map
            //添加图层容器
            let dom = (this._layerContainer = document.createElement('div'))
            dom.style.position = 'absolute'
            dom.style.left = 0
            dom.style.top = 0
            dom.classList.add('jm-tdt-wmslayer')
            map.getPanes().overlayPane.appendChild(dom)
            if (this._options.opacity < 1) {
                this.setOpacity(this._options.opacity);
            }
            //监听缩放平移事件,加载缺少切片,删除多余切片
            this._map.on('viewreset', this._onMapViewReset, this)
            this._map.on('moveend', this._onMapMoveend, this)
            this._map.on('zoomend', this._onMapZoomend, this)
            this._updataTiles()
        },

        /**
         * onRemove接口-从地图移除时执行
         */
        onRemove: function () {
            //移除图层
            this._map.getPanes().overlayPane.removeChild(this._layerContainer)
            //移除事件
            this._map.off('moveend', this._onMapMoveend, this)
            this._map.off('zoomend', this._onMapZoomend, this)
            this._map.off('viewreset', this._onMapViewReset, this)
            this._levels = null
            this._ogc = null
            this._extra = null
        },

        /**
         * 设置图层透明度
         * @param {number} opacity 透明度,取值0-1,0-完全透明,1-完全不透明
         */
        setOpacity: function (opacity) {
            this._layerContainer.style.opacity = opacity;
        },

        /**
         * 重置地图视图事件
         */
        _onMapViewReset: function () {
            this._pixelOrigin = null
            this._updateLevels()
            this._updataTiles()
        },

        /**
         * 地图平移、缩放后,加载缺失图片
         */
        _onMapMoveend: function () {
            this._updataTiles()
        },

        /**
         * 地图缩放后,更新地图级别信息、网格原点信息
         */
        _onMapZoomend: function () {
            this._updateLevels()
        },

        /**
         * 构造图片,使用get或post请求
         * @param {*} tile 
         * @param {*} done 
         */
        _createTile: async function (tile, done) {
            let img = document.createElement('img')
            var {
                url,
                service,
                version,
                request,
                crs,
                layers,
                format,
                transparent,
                buffer,
                format_options,
            } = this._ogc
            //自定义dpi时计算新的切片大小
            let requestTileSize = /dpi/.test(format_options) ? Math.floor(this._tileSize * window.devicePixelRatio) : this._tileSize
            let params = {
                service,
                layers,
                version,
                request,
                crs,
                format,
                transparent,
                width: requestTileSize,
                height: requestTileSize,
                buffer,
                bbox: tile.bbox,
                format_options,
                ...this._extra
            }
            //根据请求参数长度,切换post和get
            if (this._extra !== undefined && ((this._extra.FEATUREID && this._extra.FEATUREID.length > 1024) || (this._extra.CQL_FILTER && this._extra.CQL_FILTER.length > 1024))) {
                try {
                    codec.decodeURIComponent(params)
                    let req = await axios.post(url, Qs.stringify(params), {
                        responseType: 'blob'
                    })
                    let src = URL.createObjectURL(req.data)
                    img.src = src
                    img.onload = function () {
                        URL.revokeObjectURL(src)
                    }
                } catch (error) {
                    if (done) done(img, error)
                }
            } else
                img.src = url + codec.getParamString(params, url)
            img.style.position = 'absolute'
            img.style.width = this._tileSize + 'px'
            img.style.height = this._tileSize + 'px'
            img.style.transform = `translate3d(${tile.x}px, ${tile.y}px, 0px)`
            img.setAttribute('index', tile.index)
            if (done) done(img)
        },

        /**
         * 更新当前地图级别的网格信息
         */
        _updateLevels() {
            let z = this._map.getZoom()
            let center = this._map.getCenter()
            let size = this._map.getSize()
            // 缺少_pixelOrigin或地图缩放时才更新level,但对缩放至有影响?
            if (this._pixelOrigin == null || (this._levels[z] == undefined)) {
                this._pixelOrigin = this._getNewPixelOrigin(center, size, z)
            }
            //删除不需要的level和tile
            let remainedTiles = []
            if (this._levels)
                Object.keys(this._levels).forEach((zoom) => {
                    if (z != zoom) {
                        this._levels[zoom].tiles.forEach((tile) => {
                            this._layerContainer.removeChild(tile.img)
                        })
                        delete this._levels[zoom]
                    } else {
                        remainedTiles = this._levels[zoom].tiles
                    }
                })
            //更新levels
            let level = (this._levels[z] = {})
            level.zoom = z
            level.tiles = remainedTiles
        },
        /**
         * 经纬度转网格坐标(size=1是经纬度转行列号,size等于切片大小是像素坐标)
         * @param {*} lng 
         * @param {*} lat 
         * @param {*} z 
         */
        _latLngToPoint: function (lng, lat, z, size = 1) {
            let scale = size * Math.pow(2, z - 1)
            let x = scale * (lng / 180 + 1)
            let y = scale * (0.5 - lat / 180)
            return [x, y]
        },
        /**
         * 网格坐标转经纬度(size=1是行列号转经纬度,size等于切片大小是像素坐标)
         */
        _pointToLatLng: function (x, y, z, size = 1) {
            let scale = size * Math.pow(2, z - 1)
            let lng = 180 * (x / scale - 1)
            let lat = 180 * (0.5 - y / scale)
            return [lng, lat]
        },
        /**
         * 获取地图的坐标原点
         * @param {*} center 
         * @param {*} size 
         * @param {*} zoom 
         */
        _getNewPixelOrigin: function (center, size, zoom) {
            let n = {
                x: size.x / 2,
                y: size.y / 2,
            }
            let lngLat = this._latLngToPoint(center.lng, center.lat, zoom, this._tileSize)
            let mapOffset = this._getMapPanePos()
            let x = Math.round(lngLat[0] - n.x + mapOffset.x)
            let y = Math.round(lngLat[1] - n.y + mapOffset.y)
            return [x, y]
        },
        /**
         * 查询地图容器的偏移
         */
        _getMapPanePos: function () {
            let element = document.querySelector('.tdt-pane.tdt-map-pane')
            const style = window.getComputedStyle(element)
            const matrix = new DOMMatrixReadOnly(style.transform)
            return {
                x: matrix.m41,
                y: matrix.m42,
            }
        },

        /**
         * 更新当前地图的切片显示
         */
        _updataTiles: function () {
            let bound = this._map.getBounds()
            let z = this._map.getZoom()
            //新地图范围下需要的切片
            let tiles = this._buildTiles(bound, this._tileSize, z)
            if (tiles == null) return
            //删除remain tiles中不需要的的切片?
            this._levels[z].tiles.forEach((tile, index) => {
                if (tiles.filter(item => item.index == tile.index).length == 0) {
                    this._layerContainer.removeChild(tile.img)
                    delete this._levels[z].tiles[index]
                }
            })
            this._levels[z].tiles = this._levels[z].tiles.filter(item => item)
            //修改或创建新tile
            tiles.forEach((tile) => {
                let remainedTiles = this._levels[z].tiles.filter(item => item.index == tile.index)
                if (remainedTiles.length > 0) {
                    //修改已有tile的位置
                    remainedTiles.forEach(rtile => {
                        rtile.img.style.transform = `translate3d(${tile.x}px, ${tile.y}px, 0px)`
                        rtile.x = tile.x
                        rtile.y = tile.y
                    })
                } else //创建新tile
                    this._createTile(tile, function (img, error) {
                        if (error === undefined && this._levels && this._levels[z]) {
                            tile.img = img
                            this._layerContainer.appendChild(img)
                            this._levels[z].tiles.push(tile)
                        }
                    }.bind(this))
            })
        },

        /**
         * 构建指定范围内所需切片的切片集合(包含切片的序号,像素坐标,地图范围)
         * @param {*} bound 
         * @param {*} size 
         * @param {*} z 
         */
        _buildTiles(bound, size, z) {
            this._updateLevels()
            //根据地图像素坐标和图片大小构建需要的wms图片参数集合
            let grid = this._getGrids(bound, z)
            let tiles = []
            for (let c = grid[0]; c <= grid[2]; c++) {
                for (let r = grid[1]; r <= grid[3]; r++) {
                    let tile = {
                        index: `${c}:${r}:${z}`,
                        x: c * size - this._pixelOrigin[0],
                        y: r * size - this._pixelOrigin[1],
                        bbox: this._getTileBbox(c, r, z),
                    }
                    tiles.push(tile)
                }
            }
            return tiles
        },

        /**
         * 获取指定范围和缩放级别所需的行列范围
         * @param {*} bound 
         * @param {*} z 
         * @return //返回行列号范围[minColumn,minRow,maxColumn,maxRow]
         */
        _getGrids: function (bound, z) {
            let lnglatSW = bound.getSouthWest()
            let lnglatNE = bound.getNorthEast()
            let topleft = this._latLngToPoint(lnglatSW.lng, lnglatNE.lat, z)
            let bottomright = this._latLngToPoint(lnglatNE.lng, lnglatSW.lat, z)
            return [Math.floor(topleft[0]), Math.floor(topleft[1]), Math.floor(bottomright[0]), Math.floor(bottomright[1])]
        },
        /**
         * 根据行列号计算地图坐标范围
         * @param {*} x 
         * @param {*} y 
         * @param {*} z 
         */
        _getTileBbox: function (x, y, z) {
            let topleft = this._pointToLatLng(x, y, z)
            let bottomright = this._pointToLatLng(x + 1, y + 1, z)
            return [bottomright[1], topleft[0], topleft[1], bottomright[0]].join(',')
        },
    })
    return PostwmsLayer
}

/**
 * 用于封装前端和geoserver的交互,不考虑前端GIS技术框架,只考虑和标准的OGC服务交互(基于天地图)
 * @extends mapwms
 */
class tmapwms extends mapwms {
    // AMap实例
    _map = null
    // 缓存图层
    _cacheLayers = {}
    /**
     * 构造方法
     * @param {String} service GeoServer服务器地址,示例:http://localhost:8085/geoserver
     * @param {String} workspace GeoServer工作空间,示例:demo
     * @param {Object} options 选项配置
     * @param {Boolean} [options.supportClick] 支持点击事件,值为true时,点击地图触发 queryclick 事件
     * @param {Boolean} [options.supportRightClick] 支持右键点击事件,值为true时,点击地图触发 queryrightclick 事件
     * @param {Boolean} [options.autoHighlight] 自动高亮选中的元素
     * @param {Boolean} [options.autoCancelHighlight] 自动取消高亮选中的元素(当未点击到地图元素时,是否取消之前的),始终高亮一个
     * @param {Boolean} [options.supportRightClick] 支持右键点击事件,值为true时,点击地图触发 queryrightclick 事件
     * @param {String} [options.hcolor] 元素高亮颜色,仅在 autoHighlight = true 时有效
     * @param {Boolean} [options.testconflict] 是否进行点符号的重叠检测,取值为true时,对点符号进行重叠检测并抽稀;取值为false时,允许点符号重叠显示
     * @param {Object} ogc OGC选项配置
     * @param {String} [ogc.version] OGC版本,默认值 1.3.0
     * @param {Number} [ogc.tileSize] OGC 分块大小,默认值 512
     * @param {Number} [ogc.zIndex] OGC 显示层级,默认值 1
     * @param {Number} [ogc.buffer] OGC 缓冲距离,处理点符号被剪切的情况,默认值60
     */
    constructor(map, service = '/geoserver', workspace = '', options = {}, ogc = {}) {
        super(service, workspace, options, ogc)
        this._map = map
        this._ogc.crs = 'EPSG:4326'
        map && this.setMap(map)
    }
    /**
     * 设置高德地图对象
     * 
     * @fires amapwms#wms:initialized 初始化完成事件
     * 
     * @param {AMap} map 高德地图实例 
     */
    setMap(map) {
        // 重新设置地图对象前,需清理地图
        this.clear()
        if (!map) return
        this._map = map

        var {
            supportClick,
            supportRightClick
        } = this._options
        supportClick && map.on('click', this.mapClick, this)
        supportRightClick && map.on('contextmenu', this.mapRightClick, this)
        this.on('wms:upload', this.createWMSLayer);
    }
    // 创建WMS图层
    createWMSLayer(layerName, OGC, extras) {
        if (!layerName) throw '必须设置图层名称'
        var map = this._map
        var cacheLayers = this._cacheLayers
        var ls = cacheLayers[layerName]
        if (!ls) cacheLayers[layerName] = ls = []

        while (ls.length > 0) map.removeLayer(ls.shift())

        if (OGC) {
            (Array.isArray(OGC) ? OGC : [OGC]).forEach(p => {
                (Array.isArray(extras) ? extras : [extras]).forEach(extra => {
                    let layer = new(defineLayer())(p, extra)
                    map.addLayer(layer)
                    ls.push(layer);
                });
            })
        }
        this.emit('wms:initialized', this);
    }

    /**
     * 地图左键点击事件
     * @param {type, target, lnglat, containerPoint } e 
     */
    mapClick(e) {
        var bounds = this._map.getBounds()
        var size = this._map.getSize()

        var X, Y, WIDTH, HEIGHT, BBOX;
        X = e.containerPoint.x
        Y = e.containerPoint.y
        WIDTH = size.x
        HEIGHT = size.y
        BBOX = `${bounds.getSouthWest().lng},${bounds.getSouthWest().lat},${bounds.getNorthEast().lng},${bounds.getNorthEast().lat}`

        this.query(X, Y, WIDTH, HEIGHT, BBOX).then(response => {
            var {
                autoHighlight,
                autoCancelHighlight,
                hcolor
            } = this._options
            if (!autoHighlight) return
            var feature = response.data.features[0]
            if (feature) this.setHilightFeature(feature.id, hcolor)
            else if (autoCancelHighlight) this.setHilightFeature()
            this.onQueryClick(e.lnglat, response.data)
        })
    }

    /**
     * 触发点击事件
     * @fires amapwms#queryclick
     */
    onQueryClick(lnglat, data) {
        /**
         * 地图点击查询事件
         * @event amapwms#queryclick
         * @type {Object}
         * @property {Object} lnglat 经纬度
         * @property {Number} lnglat.lng 经度
         * @property {Number} lnglat.lat 纬度
         * @property {...Object} others 其他参数,注意这是others不是代表属性名,而是代表有其他的变量
         */
        this.emit('queryclick', {
            lnglat,
            ...data
        })
    }

    /**
     * 鼠标右键事件
     * @param {type, target, lnglat, containerPoint} e 
     */
    mapRightClick(e) {
        var bounds = this._map.getBounds()
        var size = this._map.getSize()

        var X, Y, WIDTH, HEIGHT, BBOX;
        X = e.containerPoint.x
        Y = e.containerPoint.y
        WIDTH = size.x
        HEIGHT = size.y
        BBOX = `${bounds.getSouthWest().lng},${bounds.getSouthWest().lat},${bounds.getNorthEast().lng},${bounds.getNorthEast().lat}`

        this.query(X, Y, WIDTH, HEIGHT, BBOX).then(response => {
            var {
                autoHighlight,
                autoCancelHighlight,
                hcolor
            } = this._options
            if (!autoHighlight) return
            var feature = response.data.features[0]
            if (feature) this.setHilightFeature(feature.id, hcolor)
            else if (autoCancelHighlight) this.setHilightFeature()
            this.onQueryRightClick(e.lnglat, response.data)
        })
    }

    /**
     * 触发右键点击事件
     * @fires amapwms#queryrightclick
     */
    onQueryRightClick(lnglat, data) {
        /**
         * 地图右键点击查询事件
         * @event amapwms#queryrightclick
         * @type {Object}
         * @property {Object} lnglat 经纬度
         * @property {Number} lnglat.lng 经度
         * @property {Number} lnglat.lat 纬度
         * @property {...Object} others 其他参数,注意这是others不是代表属性名,而是代表有其他的变量
         */
        this.emit('queryrightclick', {
            lnglat,
            ...data
        })
    }

    /**
     * 设置地图缩放合适的视野级别
     * @param {String|Array<String>} feature 要素ID或要素ID数组
     * @example
     * 
     * this.fitView('pipe.131')
     * this.fitView(['pipe.132', 'pipe.133', 'pipe.131'])
     */
    fitView(feature) {
        if (feature === undefined || feature === null) return Promise.reject('缩放视野级别时,必选传入参考的要素')
        var map = this._map
        return this.queryFeature(feature).then((req) => {
            var c = {
                Point: function (coordinate) {
                    return new T.LngLat(coordinate[0], coordinate[1])
                },
                LineString: function (coordinates) {
                    return coordinates.map(
                        (coordinate) => new T.LngLat(coordinate[0], coordinate[1])
                    )
                },
                MultiLineString: function (coordinates) {
                    return coordinates[0].map(
                        (coordinate) => new T.LngLat(coordinate[0], coordinate[1])
                    )
                }
            }
            // 计算显示范围
            let pointArray = []
            req.features
                .map(
                    (f) =>
                    c[f.geometry.type] && c[f.geometry.type](f.geometry.coordinates)
                )
                .forEach(item => pointArray = pointArray.concat(item))
            if (pointArray.length === 0) return Promise.reject('未查询到地图要素')
            map.setViewport(pointArray)
            return Promise.resolve()
        })
    }

    /**
     * 使指定图层闪烁
     * @param {String} layerName 图层名称
     * @param {Object} options animejs 动画参数
     * @see {@link https://www.animejs.cn/documentation/#duration options animejs 动画参数}
     */
    twinkle(layerName, options) {
        var ls = this._cacheLayers[layerName]
        if (!ls || ls.length === 0) return
        this._animeObj = anime({
            targets: {
                opacity: 1
            },
            opacity: 0,
            direction: 'alternate',
            loop: true,
            easing: 'easeInOutSine',
            update: function (a) {
                var opacity = a.animations[0].currentValue
                ls.forEach(l => l.setOpacity(opacity))
            },
            ...options
        })
    }
    /**
     * 清理图层闪烁动画
     */
    clearTwinkle() {
        if (this._animeObj) {
            this._animeObj.pause()
            this._animeObj.seek(0)
        }
        this._animeObj = null
    }

    /**
     * 清理地图
     */
    clear() {
        this.off('wms:upload')
        var {
            supportClick,
            supportRightClick
        } = this._options
        //清理事件
        supportClick && this._map && this._map.off('click')
        supportRightClick && this._map && this._map.off('contextmenu')

        var cacheLayers = this._cacheLayers
        Object.keys(cacheLayers).forEach(layerName => {
            var ls = cacheLayers[layerName]
            if (!ls) cacheLayers[layerName] = ls = []
            while (ls.length > 0) ls.shift().setMap(null)
        });
    }

    /**
     * 新建BASE图层(底图)
     */
    clearBaseLayer() {
        // todo 清理地图后,由于未设置查询条件导致要素还能查询出来
        this.onWmsUpload(BASELAYER)
    }

    /**
     * 新建自定义临时图层(自定义图层)
     * 
     * @param {String} layerName 图层名称
     * @param {(String|Object|Array)} [feature] 要素
     * @param {String} [color=#00ffff] 要素默认高亮颜色,十六进制格式,示意:'#00ffff'
     * @param {Boolean} [twinkle=false] 是否闪烁该图层,默认值为false
     * @param {Boolean} [duration=1000] 闪烁图层动画时长,默认值为1000
     * 
     * @example
     * 
     * 示例一:this.addCustomLayer('BASELAYER','pipe.1','#00ffff') // 设置一个高亮要素,pipe.1的高亮颜色为 '#00ffff'
     * 示例二:this.addCustomLayer('BASELAYER',{id:'pipe.1'},'#00ffff') // 设置一个高亮要素,pipe.1的高亮颜色为 '#00ffff'
     * 示例三:this.addCustomLayer('BASELAYER',{id:'pipe.1',color:'#00ffff'}) // 设置一个高亮要素,pipe.1的高亮颜色为 '#00ffff'
     * 示例四:this.addCustomLayer('BASELAYER',['pipe.1','pipe.2'],'#00ffff') // 设置两个高亮要素,pipe.1、pipe.2的高亮颜色为 '#00ffff'
     * 示例五:this.addCustomLayer('BASELAYER',[{id:'pipe.1'},{id:'pipe.2'}],'#00ffff') // 设置两个高亮要素,pipe.1、pipe.2的高亮颜色为 '#00ffff'
     * 示例六:this.addCustomLayer('BASELAYER',[{id:'pipe.1',color:'#00ffff'},{id:'pipe.2',color:'#00ffff'}]) // 设置两个高亮要素,pipe.1、pipe.2的高亮颜色为 '#00ff00',注意此情况时一般使用 示例七,需要设置默认高亮色
     * 示例七:this.addCustomLayer('BASELAYER',[{id:'pipe.1',color:'#00ff00'},{id:'pipe.2'}],'#00ffff') // 设置两个高亮要素,pipe.1的高亮颜色为 '#00ff00',pipe.2的高亮颜色为默认值 #00ffff'
     * 示例八:this.addCustomLayer('BASELAYER') // 清除自定义临时图层
     */
    addCustomLayer(layerName, feature, color, twinkle = false, duration = 1000) {
        this.setHierarchyFeature(feature, color || undefined, layerName)
        twinkle && this.twinkle(layerName, {
            duration
        })
    }
    /**
     * 新建自定义临时图层(自定义图层)
     * @param {String} layerName 图层名称
     */
    clearCustomLayer(layerName) {
        this.onWmsUpload(layerName)
    }

    /**
     * 新建自定义临时图层(拓展图层)
     * 
     * @param {(String|Object|Array)} [feature] 要素
     * @param {String} [color=#00ffff] 要素默认高亮颜色,十六进制格式,示意:'#00ffff'
     * @param {Boolean} [twinkle=false] 是否闪烁该图层,默认值为false
     * @param {Boolean} [duration=1000] 闪烁图层动画时长,默认值为1000
     * 
     * @example
     * 
     * 示例一:this.addExtraLayer('pipe.1','#00ffff') // 设置一个高亮要素,pipe.1的高亮颜色为 '#00ffff'
     * 示例二:this.addExtraLayer({id:'pipe.1'},'#00ffff') // 设置一个高亮要素,pipe.1的高亮颜色为 '#00ffff'
     * 示例三:this.addExtraLayer({id:'pipe.1',color:'#00ffff'}) // 设置一个高亮要素,pipe.1的高亮颜色为 '#00ffff'
     * 示例四:this.addExtraLayer(['pipe.1','pipe.2'],'#00ffff') // 设置两个高亮要素,pipe.1、pipe.2的高亮颜色为 '#00ffff'
     * 示例五:this.addExtraLayer([{id:'pipe.1'},{id:'pipe.2'}],'#00ffff') // 设置两个高亮要素,pipe.1、pipe.2的高亮颜色为 '#00ffff'
     * 示例六:this.addExtraLayer([{id:'pipe.1',color:'#00ffff'},{id:'pipe.2',color:'#00ffff'}]) // 设置两个高亮要素,pipe.1、pipe.2的高亮颜色为 '#00ff00',注意此情况时一般使用 示例七,需要设置默认高亮色
     * 示例七:this.addExtraLayer([{id:'pipe.1',color:'#00ff00'},{id:'pipe.2'}],'#00ffff') // 设置两个高亮要素,pipe.1的高亮颜色为 '#00ff00',pipe.2的高亮颜色为默认值 #00ffff'
     * 示例八:this.addExtraLayer() // 清除高亮要素
     */
    addExtraLayer(feature, color, twinkle = false, duration = 1000) {
        this.setHierarchyFeature(feature, color || undefined)
        twinkle && this.twinkle(EXTRALAYER, {
            duration
        })
    }
    /**
     * 清理自定义临时图层(拓展图层)
     */
    clearExtraLayer() {
        this.setHierarchyFeature()
    }

    /**
     * 上传天地图元素覆盖物,当前仅支持 T.Marker 和 T.Polyline
     *
     * 天地图元素对象需设置 extData 数据,例如:
     * var marker = new T.Marker(new T.LngLat(116.411794, 39.9068),
     *                    {
     *                    extData:{
     *                           type: '',
     *                           property: feature.properties,
     *                           style: {},
     *                           fixed: true
     *                      }
     *                    });
     * 访问方式:marker.options.extData.subtype
     * @example
     *
     * var overlay = new AMap.Marker()
     *
     * overlay.setExtData({
     *    // *必填项,设备或者管线是否已矢量缩放的形式进行呈现,默认值为true
     *    fixed: true,
     *    // *必填项,设备类型id,不管是管道还是阀门还是其他设备,均需要填写该值
     *    type: '20ab312e-d505-4c37-8b85-ac75007e2a31',
     *    // 选填项,设备业务属性,支持自定义对象
     *    property: {
     *
     *    },
     *    // 选填项,设备样式属性,对于管线而言size是线宽度,对于点而言,size是图标大小
     *    style:{
     *        // 选填项,符号层级
     *        zindex:1,
     *        // 选填项,图标大小或者管线宽度,注意 fixed 为真是代表矢量缩放,此时size的单位为m,否则size的单位是px
     *        size: 8,
     *        // 选填项,图标颜色或者管线颜色,
     *        color: '#ffffff'
     *        zoom:[15,19]?暂不能实现
     *    }
     * })
     *
     * util.uploadOverlays(overlay) 或者 util.uploadOverlays([overlay])
     * @param {Object|Array} 高德元素对象或者高德元素对象数组
     * @returns {Promise}
     */
    uploadOverlays(overlay) {
        if (!overlay) return Promise.resolve()
        let overlays = Array.isArray(overlay) ? overlay : [overlay]

        // 插入管线
        let lines = overlays.filter(o => o instanceof T.Polyline).map(o => {
            let path = o.getLngLats().map(o => [o.lng, o.lat])
            let extData = o.options.extData
            extData.property = Object.assign({}, extData.property, {
                status: 0 //默认设备状态,0-正常
            });
            return {
                path,
                property: extData.property,
                style: extData.style,
                type: extData.type,
                fixed: extData.fixed
            }
        });

        // 插入设备
        let points = overlays.filter(o => o instanceof T.Marker).map(o => {
            let pos = o.getLngLat()
            let extData = o.options.extData
            extData.property = Object.assign({}, extData.property, {
                status: 0 //默认设备状态,0-正常
            });
            return {
                lng: pos.lng,
                lat: pos.lat,
                property: extData.property,
                style: extData.style,
                type: extData.type,
                fixed: extData.fixed
            }
        });
        return Promise.all([this.addPolyline(lines), this.addPoint(points)]);
    }
}

export default tmapwms