    import {
      Zondy,
      defaultValue,
      BaseView,
      ViewEventType,
      LayerType,
      Point,
      Extent,
      T,
      showLayerByScale,
      SpatialReference,
      Projection,
      MapEventType,
      Geometry,
      Config,
      cloneObject,
      TileInfoUtil,
      // Utils,
      Map,
      isNull,
      toJSON
    } from '@mapgis/webclient-common'
    import * as L from '@mapgis/leaflet'
    import UI from './ui/UI'
    import MapViewUtil from './utils/MapViewUtil'
    import { getLayerView } from '../document/index'
    import CRS from './utils/support/CRS'
    import Popup from './utils/Popup'
    import Screenshot from './utils/Screenshot'

    /**
     * 二维场景视图(leaflet引擎)，对地图引擎进行管理，如果要对地图图层进行管理请参考[Map]{@link Map}，<br/>
     * 参考示例：
     * <a href='#MapView'>[初始化二维场景视图]</a>
     * <br>[ES5引入方式]：<br/>
     * Zondy.MapView() <br/>
     * [ES6引入方式]：<br/>
     * import { MapView } from '@mapgis/webclient-leaflet-plugin' <br/>
     * @class MapView
     * @extends Evented
     * @moduleEX ViewModule
     * @fires BaseView#地图视图加载完毕事件
     * @fires BaseView#鼠标点击事件
     * @fires BaseView#鼠标双击事件
     * @fires BaseView#鼠标按下事件
     * @fires BaseView#鼠标抬起事件
     * @fires BaseView#鼠标右键点击事件
     * @fires BaseView#鼠标移动事件
     * @fires BaseView#鼠标移出视图事件
     * @fires BaseView#鼠标移入视图事件
     * @fires BaseView#zoom变化事件
     * @fires BaseView#地图移动事件
     * @fires BaseView#地图大小变化事件
     * @fires BaseView#键盘输入事件
     * @fires BaseView#键盘按下事件
     * @fires BaseView#键盘抬起事件
     * @fires BaseView#地图视图改变事件
     * @param {Object} options 构造参数
     * @param {Map}  [options.Map]  图层管理容器对象
     * @param {String} [options.viewId] 二维场景视图的容器(html的div标签)ID
     * @param {Number} [options.minZoom = 0] 最小缩放级数，在此设置则所有图层都不会浏览小于该级数的图片
     * @param {Number} [options.maxZoom = 19] 最大缩放级数，在此设置则所有图层都不会浏览大于该级数的图片
     * @param {Number} [options.zoom = 1] 初始化二维场景视图时显示级数
     * @param {Boolean} [options.attributionControl = false] 是否显示右下角水印
     * @param {Boolean} [options.zoomControl = true] 是否显示缩放控件
     * @param {Boolean | String} [options.doubleClickZoom = true] 是否允许双击鼠标左键缩放或者缩放至图层中心点
     * @param {Boolean} [options.dragging = true] 是否允许拖拽
     * @param {Number} [options.zoomSnap = 1] 当使用flyTo缩放至中心点时，缩放级数乘以的系数
     * @param {Number} [options.zoomDelta = 1] 当触发zoomIn或者zoomOut操作时，缩放级数乘以的系数
     * @param {Boolean} [options.trackResize = true] 是否允许图层大小随视窗变化
     * @param {Boolean} [options.keyboard = true] 是否允使用键盘的+/-号，来缩放地图
     * @param {Number} [options.keyboardPanDelta = 80] 使用键盘来平移或缩放地图时的系数，单位px
     * @param {Boolean | String} [options.scrollWheelZoom = true] 使用键盘来平移或缩放地图时的系数，单位px
     * @param {Number} [options.wheelDebounceTime = 40] 滚轮事件的触发事件，单位毫秒
     * @param {Number} [options.wheelPxPerZoomLevel = 60] 滚轮缩放时，地图缩放的像素单位，单位像素
     * @param {Boolean} [options.tapHold = true] 是否开启移动端，手指按压不放事件
     * @param {Number} [options.tapTolerance = 15] 手指有效触发范围，单位像素
     * @param {Boolean | String} [options.touchZoom = true] 是否启用手指两指缩放，当值为center，表示两只滑动，缩放至地图中心
     * @param {Boolean} [options.bounceAtZoomLimits = true] 当过最大或最小级数后不再缩放
     * @param {Boolean} [options.animation = true] 是否启用动画
     * @param {Point} [options.center = new Point({coordinates:[0,0]})] 地图视图中心点
     * @param {Extent} [options.extent = undefined] 地图视图可视范围
     * @param {Number} [options.scale = undefined] 地图视图比例尺
     * @param {Number} [options.maxScale = undefined] 最大比例尺
     * @param {Number} [options.minScale = undefined] 最小比例尺
     * @param {Object} [options.popup = undefined] 地图弹框
     * @param {Number} [options.rotation = 0] 地图视图旋转选项。单位为度，默认为0，表示不进行旋转
     *
     * @summary <h5>支持如下方法：</h5>
     * <a href='#flyTo'>[1、视点跳转]</a><br/>
     * <a href='#destroy'>[2、销毁视图对象]</a><br/>
     * <a href='#getCenter'>[3、获取当前视图的中心点]</a><br/>
     * <a href='#getPixelCenter'>[3、获取当前视图的像素中心点]</a><br/>
     * <a href='#getZoom'>[4、获取当前缩放级数]</a><br/>
     * <a href='#getExtent'>[5、获取当前视图的地理范围]</a><br/>
     * <a href='#getPixelWorldExtent'>[6、获取当前视图的像素范围]</a><br/>
     * <a href='#getMinZoom'>[7、获取最小缩放级数]</a><br/>
     * <a href='#getMaxZoom'>[8、获取最大缩放级数]</a><br/>
     * <a href='#getSize'>[9、获取当前视图容器的宽高]</a><br/>
     * <a href='#toJSON'>[10、导出场景视图的配置文件]</a><br/>
     * <a href='#clone'>[11、克隆并返回一个新的场景视图对象]</a><br/>
     * <a href='#fromJSON'>[12、通过json构造并返回一个新的场景视图对象]</a><br/>
     * <a href='#takeScreenshot'>[13、屏幕快照]</a><br/>
     * [14、注册事件]{@link Evented#on}<br/>
     * [15、移除事件]{@link Evented#off}<br/>
     * <a href='#toMap'>[15、屏幕像素坐标点转地理坐标点]</a><br/>
     * <a href='#toScreen'>[16、地理坐标点转屏幕像素坐标点]</a><br/>
     * <a href='#hitTest'>[17、穿透检测]</a><br/>
     * <a href='#getLayer'>[18、根据实际图层对象查询并返回基础图层]</a><br/>
     * <a href='#getScale'>[19、获取当前比例尺]</a><br/>
     *
     * @example <caption><h7 id='MapView'>初始化一个二维场景视图</h7></caption>
     * // ES5引入方式
     * const { Map, MapView } = Zondy
     * // ES6引入方式
     * import { Map, MapView } from "@mapgis/webclient-leaflet-plugin"
     * // 初始化图层管理容器
     * const map = new Map();
     * // 初始化地图视图对象
     * const mapView = new MapView({
     *   // 二维场景视图的容器(html的div标签)ID
     *   viewId: "二维场景视图的容器的id",
     *   // 图层管理容器
     *   map: map
     * });
     */
    class MapView extends BaseView {
      constructor(options) {
        super(options)
        // eslint-disable-next-line no-param-reassign
        options = defaultValue(options, {})
        this._options = options
        /**
         * 地图视图中心点
         * @member {Point} MapView.prototype.center
         */
        // 统一center的类型
        this._center = defaultValue(
          options.center,
          new Point({ coordinates: [0, 0] })
        )
        if (
          this._center &&
          Array.isArray(this._center) &&
          this._center.length > 1
        ) {
          this._center = new Point({
            coordinates: [this._center[0], this._center[1]]
          })
        }
        /**
         * 地图坐标系
         * @member {Number} MapView.prototype.zoom
         */
        this.crs = undefined // defaultValue(options.crs, 'EPSG3857')
        /**
         * 地图视图可视范围
         * @member {Extent} MapView.prototype.extent
         */
        this._extent = defaultValue(options.extent, undefined)
        /**
         * 是否启用视角跳转动画
         * @member {Boolean} MapView.prototype.animation
         */
        this.animation = defaultValue(options.animation, true)
        /**
         * 地图视图宽度
         * @readonly
         * @member {Number} MapView.prototype.width
         */
        this.width = undefined
        /**
         * 地图视图高度
         * @readonly
         * @member {Number} MapView.prototype.height
         */
        this.height = undefined
        /**
         * 地图层级
         * @member {Number} MapView.prototype.zoom
         */
        this._zoom = defaultValue(options.zoom, 0)
        this._resolution = 0 // defaultValue(options.resolution, 0)
        /**
         * 地图视图比例尺
         * @member {Number} MapView.prototype.scale
         */
        this._scale = defaultValue(options.scale, undefined)
        /**
         * 地图视图范围
         * @member {Extent} MapView.prototype.extent
         */
        this._extent = defaultValue(options.extent, undefined)
        /**
         * 地图视图最大比例尺
         * @member {Number} MapView.prototype.maxScale
         */
        this.maxScale = defaultValue(options.maxScale, undefined)
        /**
         * 地图视图最小比例尺
         * @member {Number} MapView.prototype.minScale
         */
        this.minScale = defaultValue(options.minScale, undefined)
        /**
         * 地图弹框popup
         * @member {Popup} MapView.prototype.popup
         */
        this.popup = defaultValue(options.popup, undefined)
        /**
         * 视图渲染方式是否为canvas
         * @member {Boolean} MapView.prototype.preferCanvas
         */
        this.preferCanvas = defaultValue(options.preferCanvas, false)
        // if (typeof this.crs === 'string') {
        //   if (L.CRS[this.crs]) {
        //     this.crs = L.CRS[this.crs]
        //   } else {
        //     this.crs = L.CRS.EPSG4326
        //   }
        // }
        /**
         * 视图空间参考系
         * @readonly
         * @member {SpatialReference} MapView.prototype._spatialReference
         */
        this._spatialReference = undefined
        this._referenceLayer = undefined
        /**
         * 是否锁定视图空间参考系
         * @member {Boolean} MapView.prototype.spatialReferenceLocked
         */
        this.spatialReferenceLocked = defaultValue(
          options.spatialReferenceLocked,
          false
        )
        /**
         * 地图视图旋转选项。单位为度，默认为0，表示不进行旋转
         * @member {Boolean} MapView.prototype.rotation
         */
        this._rotation = defaultValue(options.rotation, 0)
        // 是否重置地图视图的坐标系
        this._isCustomCrs = false
        // 是否绑定Leaflet
        this._map._initLeafletView = true
        // 屏幕打印对象
        this._screenshot = null
        // 初始化地图引擎
        // this._initView(false, this._options)
      }

      /**
       * 视点跳转<a id='flyTo'></a>
       * @param options 跳转参数
       * @param {Array}  [options.center]  跳转中心点
       * @param {Number}  [options.zoom = 1]  地图层级
       * @param {Extent}  [options.extent]  按范围跳转
       * @example <caption><h7>中心点跳转示例</h7></caption>
       * // ES5引入方式
       * const { Map, MapView } = Zondy
       * // ES6引入方式
       * import { Map, MapView } from "@mapgis/webclient-leaflet-plugin"
       * // 初始化图层管理容器
       * map = new .Map();
       * // 初始化地图视图对象
       * mapView = new MapView({
       *   // 视图id
       *   viewId: "view-id",
       *   // 图层管理容器
       *   map: map
       * });
       * // 视点跳转
       * mapView.flyTo({
       *   // 跳转中心点
       *   center: [{x}, {y}],
       *   // 地图层级
       *   zoom: {zoom}
       * });
       *
       * @example <caption><h7>按范围跳转示例</h7></caption>
       * // ES5引入方式
       * const { Map, MapView } = Zondy
       * const { Extent } = Zondy.Geometry
       * // ES6引入方式
       * import { Map, MapView, Extent } from "@mapgis/webclient-leaflet-plugin"
       * // 初始化图层管理容器
       * map = new Map();
       * // 初始化地图视图对象
       * mapView = new MapView({
       *   // 视图id
       *   viewId: "view-id",
       *   // 图层管理容器
       *   map: map
       * });
       * mapView.flyTo({
       *   // 范围几何
       *   extent: new Extent({
       *      "xmin":10,
       *      "xmax":210,
       *      "ymin":0,
       *      "ymax":100,
       *   })
       * });
       *
       * @example <caption><h7>按范围跳转示例-拿到图层信息后跳转</h7></caption>
       * // ES5引入方式
       * const { IGSMapImageLayer } = Zondy.Layer
       * // ES6引入方式
       * import { IGSMapImageLayer } from "@mapgis/webclient-leaflet-plugin"
       * const igsMapImageLayer = new IGSMapImageLayer({
       *   url: 'http://192.168.82.89:8089/igs/rest/services/Map/Hubei4326/MapServer'
       * });
       * map.add(igsMapImageLayer);
       * // 图层加载完毕
       * igsMapImageLayer.on('layer-view-created', function (result) {
       *   console.log("加载完毕:", result.layer)
       *   //视点跳转
       *   mapView.flyTo({
       *     extent: result.layer.extent
       *   });
       * })
       */
      flyTo(options) {
        options = defaultValue(options, {})
        const zoom = defaultValue(options.zoom, 1)

        // 获取center
        const center = this._getFlyToCenter(options)

        // 获取extent
        const extent = this._getFlyToExtent(options)

        if (this._innerView) {
          if (center) {
            this._innerView.setView(
              [center.coordinates[1], center.coordinates[0]],
              zoom
            )
          } else if (extent) {
            this._innerView.fitBounds([
              [extent.ymin, extent.xmin],
              [extent.ymax, extent.xmax]
            ])
          }
        }
      }

      /**
       * 从extent获取经纬度范围
       * @private
       * @param {Extent} extent 范围对象
       * @return {Object} 经纬度范围
       * */
      _getLatLngFromExtent(extent) {
        const spatialReference = new SpatialReference('EPSG:4326')
        const newExtent = Projection.project(extent, spatialReference)
        const northEastLatlng = new L.latLng(newExtent.ymax, newExtent.xmax)
        const southWestLatlng = new L.latLng(newExtent.ymin, newExtent.xmin)

        return {
          northEastLatlng,
          southWestLatlng
        }
      }

      /**
       * 从center获取经纬度范围
       * @private
       * @param {Point} point 范围对象
       * @return {Object} 地理坐标
       * */
      _getLatLngFromPoint(point) {
        const spatialReference = new SpatialReference('EPSG:4326')
        const newCenter = Projection.project(point, spatialReference)
        const centerLatlng = new L.latLng(
          newCenter.coordinates[1],
          newCenter.coordinates[0]
        )
        return centerLatlng
      }

      /**
       * 初始化地图视图事件
       * @private
       * */
      _initViewEvent() {
        const self = this
        // 定义一个变量存储单击触发事件
        this._clickStore = null
        // 定义一个变量存储拖拽事件参数
        this._dragParams = { isDrag: false, origin: null, action: '', button: null }

        // 地图视图加载完毕事件
        self.fire(ViewEventType.loaded, {})

        // leaflet注册点击事件
        this._innerView.on('click', (event) => {
          // 单击触发事件，300ms延迟
          self._clickStore = setTimeout(function () {
            // 发送左键单击点击事件
            if (self._clickStore) {
              self.fire(ViewEventType.click, MapViewUtil.getMouseEvent(event, self))
            }
          }, 300)
          // 发送立即点击事件
          self.fire(
            ViewEventType.immediateClick,
            MapViewUtil.getMouseEvent(event, self)
          )
        })

        // 注册鼠标右键按下事件
        this._innerView.on('contextmenu', (event) => {
          // 单击触发事件，300ms延迟
          self._clickStore = setTimeout(function () {
            // 发送鼠标右键按下事件
            if (self._clickStore) {
              self.fire(ViewEventType.click, MapViewUtil.getMouseEvent(event, self))
            }
          }, 300)
          // 发送立即点击事件
          self.fire(
            ViewEventType.immediateClick,
            MapViewUtil.getMouseEvent(event, self)
          )
        })

        // 注册双击事件
        this._innerView.on('dblclick', (event) => {
          // 单击事件清理
          if (self._clickStore) {
            clearTimeout(this._clickStore)
            self._clickStore = null
          }
          // 发送双击事件
          self.fire(
            ViewEventType.doubleClick,
            MapViewUtil.getMouseEvent(event, self)
          )
        })

        // 注册鼠标按下事件
        this._innerView.on('mousedown', (event) => {
          // 发送鼠标按下事件
          self.fire(
            ViewEventType.pointerDown,
            MapViewUtil.getMouseEvent(event, self)
          )
          // 发送鼠标拖拽开始事件
          if (!this._dragParams.isDrag) {
            this._dragParams.origin = {
              x: event.originalEvent.layerX,
              y: event.originalEvent.layerY
            }
            this._dragParams.button = event.originalEvent.button
            this._dragParams.action = 'mouse-down'
            this._dragParams.isDrag = false
          }
        })

        // 注册鼠标抬起事件
        this._innerView.on('mouseup', (event) => {
          // 发送鼠标抬起事件
          self.fire(ViewEventType.pointerUp, MapViewUtil.getMouseEvent(event, self))
          // 发送鼠标拖拽结束事件
          if (self._dragParams.isDrag && this._dragParams.action === 'update') {
            this._dragParams.action = 'end'
            self.fire(
              ViewEventType.drag,
              MapViewUtil.getDragEvent(
                event,
                self,
                self._dragParams.origin,
                this._dragParams.action
              )
            )
          }
          self._dragParams = {
            isDrag: false,
            origin: null,
            action: '',
            button: null
          }
        })

        // 注册鼠标移动事件
        this._innerView.on('mousemove', (event) => {
          // 发送鼠标移动视图事件
          self.fire(
            ViewEventType.pointerMove,
            MapViewUtil.getMouseEvent(event, self)
          )
          // 发送鼠标拖拽中事件
          if (self._dragParams.isDrag) {
            self.fire(
              ViewEventType.drag,
              MapViewUtil.getDragEvent(
                event,
                self,
                self._dragParams.origin,
                self._dragParams.action,
                self._dragParams.button
              )
            )
            self._dragParams.action = 'update'
          } else if (this._dragParams.action === 'mouse-down') {
            this._dragParams.action = 'start'
            self._dragParams.isDrag = true
          }
        })

        // 注册键盘按下事件
        this._innerView.on('keydown', (event) => {
          // 发送键盘按下事件
          self.fire(
            ViewEventType.keyDown,
            MapViewUtil.getKeyEvent(event, self, ViewEventType.keyDown)
          )
        })

        // 注册键盘抬起事件
        this._innerView.on('keyup', (event) => {
          // 发送键盘抬起事件
          self.fire(
            ViewEventType.keyUp,
            MapViewUtil.getKeyEvent(event, self, ViewEventType.keyUp)
          )
        })

        // 注册视图开始改变事件
        this._innerView.on('movestart', () => {
          self.stationary = false
        })

        // 注册zoom变化事件
        this._innerView.on('zoom', (event) => {
          self._map.layers.forEach(function (layer) {
            switch (layer.type) {
              case LayerType.igsFeature:
              case LayerType.graphics:
                showLayerByScale(self, layer)
                break
              default:
                break
            }
          })
          // 发送zoom变化事件
          self.fire(ViewEventType.zoom, {
            zoom: event.target._zoom,
            event
          })
        })
        // 注册zoom end事件
        this._innerView.on('zoomend', (event) => {
          // 更新地图视野范围，比例尺
          self._zoomEndHandler()
          // 发送地图视图改变事件
          self.fire(
            ViewEventType.viewChange,
            MapViewUtil.getViewChangeEvent(event, self)
          )
        })

        // 注册地图移动完成事件
        this._innerView.on('moveend', (event) => {
          // 更新地图视中心
          self._moveEndHandler()
          // 发送地图视图改变事件
          self.fire(
            ViewEventType.viewChange,
            MapViewUtil.getViewChangeEvent(event, self)
          )
          // self.fire(ViewEventType.mapMove, event)
          self.stationary = true
        })

        // 注册地图大小变化事件
        this._innerView.on('resize', (event) => {
          // 发送地图初始化后触发的事件
          self.fire(ViewEventType.resize, {
            oldWidth: event.oldSize.x,
            oldHeight: event.oldSize.y,
            width: event.newSize.x,
            height: event.newSize.y
          })
          // 更新地图窗口宽高
          self._initSize()
        })
      }

      /**
       * 设置（鼠标）操作地图事件是否可用
       * @private
       * @param {String} actionType 操作类型
       * @param {Boolean} isEnable 是否可用
       * */
      _mapActionControl(actionType, isEnable) {
        if (!this._innerView) return
        if (actionType === 'drag-pan') {
          if (isEnable) {
            this._innerView.dragging.enable()
          } else {
            this._innerView.dragging.disable()
          }
        } else if (actionType === 'double-click-zoom') {
          if (isEnable) {
            this._innerView.doubleClickZoom.enable()
          } else {
            this._innerView.doubleClickZoom.disable()
          }
        }
      }

      /**
       * 初始化一个地图视图对象
       * @param isReset 是否是重置视图
       * @private
       * */
      _initView(isReset, options) {
        this._innerView = L.map(this._viewId, {
          // 是否使用Canvas渲染方式
          preferCanvas: this.preferCanvas,
          // 是否显示右下角水印
          attributionControl: defaultValue(options.attributionControl, false),
          // 是否显示缩放控件
          zoomControl: defaultValue(options.zoomControl, true),
          // 是否允许双击鼠标左键缩放或者缩放至图层中心点
          doubleClickZoom: defaultValue(options.doubleClickZoom, true),
          // 是否允许拖拽
          dragging: defaultValue(options.dragging, true),
          // 当使用flyTo缩放至中心点时，缩放级数乘以的系数
          zoomSnap: defaultValue(options.zoomSnap, 1),
          // 当触发zoomIn或者zoomOut操作时，缩放级数乘以的系数
          zoomDelta: defaultValue(options.zoomDelta, 1),
          // 是否允许图层大小随视窗变化
          trackResize: defaultValue(options.trackResize, true),
          // 是否允使用键盘的+/-号，来缩放地图或者使用方向键来平移地图
          keyboard: defaultValue(options.keyboard, true),
          // 使用键盘来平移或缩放地图时的系数，单位px
          keyboardPanDelta: defaultValue(options.keyboardPanDelta, 80),
          // 是否允许使用鼠标滚轮来缩放地图，当值为center时，表示使用滚轮缩放至地图中心点
          scrollWheelZoom: defaultValue(options.scrollWheelZoom, true),
          // 滚轮事件的触发事件，单位毫秒
          wheelDebounceTime: defaultValue(options.wheelDebounceTime, 40),
          // 滚轮缩放时，地图缩放的像素单位，单位像素
          wheelPxPerZoomLevel: defaultValue(options.wheelDebounceTime, 60),
          // 是否开启移动端，手指按压不放事件
          tapHold: defaultValue(options.tapHold, true),
          // 手指有效触发范围，单位像素
          tapTolerance: defaultValue(options.tapTolerance, 15),
          // 是否启用手指两指缩放，当值为center，表示两只滑动，缩放至地图中心
          touchZoom: defaultValue(options.touchZoom, true),
          // 当过最大或最小级数后不再缩放
          bounceAtZoomLimits: defaultValue(options.bounceAtZoomLimits, true),
          // 图层空间参考系
          crs: this.crs,
          // 图层中心点
          center:
            this._center && this._center.coordinates
              ? [this._center.coordinates[1], this._center.coordinates[0]]
              : [0, 0],
          // 可视范围
          extent: defaultValue(this._extent, undefined),
          // 最大缩放级数
          maxZoom: this.maxZoom,
          // 最小缩放级数
          minZoom: this.minZoom,
          // 初始化时显示级数
          zoom: this._zoom,
          // 是否启用视点跳转动画
          zoomAnimation: this.animation,
          // Config
          config: Config,
          // 是否进行旋转，目前对外没有constraints，默认设置为true
          rotate: true,
          // 旋转bearing，单位为度
          bearing: this._rotation
        })
        // 初始化ui容器
        this.ui = new UI({
          view: this
        })
        // 初始化视图事件
        this._initViewEvent()
        // 初始化可视范围
        if (this._extent && !isReset) {
          this.flyTo({ extent: this._extent })
        }
        // 初始化比例尺，extent定位优先于scale，center定位
        if (!this._extent && this._scale && this._center && !isReset) {
          this._initScale()
        }
        // 获取视图大小
        this._initSize()
        // 初始化最大比例尺和最小比例尺
        this._initScaleLimit()
        // 初始化地图视图弹框，目前仅支持location方式打开弹框
        this._initPopup()
      }

      // 设置spatialReference
      _setSpatialReference() {
        // 如果basemap有坐标系，则view坐标系取basemap坐标系的值。
        // 没有，则view根据加载的图层动态坐标系的值。
        let spatialReference = null
        let referenceLayer = null
        if (this._map.basemap && this._map.basemap.spatialReference) {
          spatialReference = this._map.basemap.spatialReference
          referenceLayer = this._map.basemap._referenceLayer
        } else if (this._map.layers) {
          // const target = Utils.getSpatialReference(this._map.layers)
          const target = TileInfoUtil.getSpatialReference(this._map.layers)
          spatialReference = target.spatialReference
          referenceLayer = target.referenceLayer
        }
        // 没有获取到图层参考系，则直接返回
        if (!spatialReference) return
        // 当view上未设置空间参考系锁定，
        // 或空间参考系锁定但没有spatialReference时，则获取新的spatialReference
        if (
          !this.spatialReferenceLocked ||
          (this.spatialReferenceLocked && !this._spatialReference)
        ) {
          this._spatialReference = spatialReference
          this._referenceLayer = referenceLayer
          // 如果是MapImageLayer，则重置坐标系为当前坐标系
          if (this._spatialReference) {
            const isBasemapLayer = referenceLayer._isBasemapLayer
            TileInfoUtil.setLayerSpatialReference(
              this._map.layers,
              this._spatialReference,
              isBasemapLayer
            )
          }
          // 设置crs
          this.setCrs(referenceLayer)
        }
      }

      setCrs(referenceLayer) {
        const crs = this.getCrsByReferenceLayer(referenceLayer)
        let isNew = false
        let isFirst = false
        if (this.crs) {
          // 比较被设置的新Crs和当前this.crs是否相同。
          // 判断标准 crs.option的resolutions,origin,bounds是否相等。
          if (crs !== null) {
            const newCrsInfo = {
              wkid: crs.code.split(':')[1]
            }
            const currentCrsInfo = {
              wkid: this.crs.code.split(':')[1]
            }
            if (newCrsInfo.wkid === currentCrsInfo.wkid) {
              // ArcgisMapImage图层临时处理，服务数据extent有问题，暂为解决。4326、3857参考系构建对应的Leaflet参考系对象，
              // 因此没有options，不参与对比bounds、resolutions，暂时组织比较wkid
              // 问题解决后，统一比较 crs.options，该if条件可移除
              if (
                (newCrsInfo.wkid === '4326' || newCrsInfo.wkid === '3857') &&
                !crs.options
              ) {
                isNew = false
              }
              if (crs.options) {
                const newCrsOptions = crs.options
                newCrsInfo.resolutions = newCrsOptions.resolutions
                newCrsInfo.origin = newCrsOptions.origin
                newCrsInfo.bounds = newCrsOptions.bounds
                currentCrsInfo.resolutions = this.crs.options.resolutions
                currentCrsInfo.origin = this.crs.options.origin
                currentCrsInfo.bounds = this.crs.options.bounds
                if (
                  JSON.stringify(newCrsInfo.resolutions) !==
                    JSON.stringify(currentCrsInfo.resolutions) ||
                  JSON.stringify(newCrsInfo.origin) !==
                    JSON.stringify(currentCrsInfo.origin) ||
                  JSON.stringify(newCrsInfo.bounds) !==
                    JSON.stringify(currentCrsInfo.bounds)
                ) {
                  isNew = true
                }
              }
            } else {
              isNew = true
            }
          }
        } else if (!this.crs && crs) {
          isNew = true
          isFirst = true
        }
        if (isNew) {
          this.crs = crs
          if (referenceLayer.extent) {
            const extent = new Extent({
              xmin: referenceLayer.extent.xmin,
              xmax: referenceLayer.extent.xmax,
              ymin: referenceLayer.extent.ymin,
              ymax: referenceLayer.extent.ymax,
              spatialReference: referenceLayer.spatialReference
            })
            const latLng = this._getLatLngFromExtent(extent)
            this._center = new Point({
              coordinates: [
                (latLng.southWestLatlng.lng + latLng.northEastLatlng.lng) / 2,
                (latLng.southWestLatlng.lat + latLng.northEastLatlng.lat) / 2
              ]
            })
          }
          const layers = []
          const baseLayers = []
          this._map.layers.items.forEach((layer) => {
            // 如果是当前图层load完之后触发的重置方法，则不需要重载图层

            // 图层信息已加载完全并且不是第一次设置坐标系，则需要重新加载
            // 图层信息没加载完全并且是第一次设置坐标系，则需要重新加载
            // 图层是第一次设置坐标系，并且是graphicsLayer（不管graphicsLayer是否loaded），则需要重新加载
            // 图层是参考图层，extent没变，则不需要重新加载。图层是参考图层，extent变了，则需要重新加载
            if (this._loadedLayer && layer.id === this._loadedLayer.id) {
              return
            }
            if (
              (layer.loaded && !isFirst) ||
              (!layer.loaded && isFirst) ||
              (isFirst && layer.type === LayerType.graphics)
            ) {
              layers.push(layer)
              if (layer._isBasemapLayer) {
                layer._isBasemapLayer = false
                baseLayers.push(layer)
              }
            }
          })
          this._map._canResetView = false
          this._map.removeMany(layers)
          baseLayers.forEach((layer) => {
            layer._isBasemapLayer = true
          })
          this._reCreateView()
          this._map.addMany(layers)
          this._map._canResetView = true
          if (isFirst) {
            referenceLayer.on('layerview-created', () => {
              // 初始化可视范围
              if (this._extent) {
                this.flyTo({ extent: this._extent })
              }
              // 初始化比例尺，extent定位优先于scale，center定位
              if (!this._extent && this._scale && this._center) {
                this._initScale()
              }
              // 获取视图大小
              this._initSize()
              // 初始化最大比例尺和最小比例尺
              this._initScaleLimit()
              // 初始化地图视图弹框，目前仅支持location方式打开弹框
              this._initPopup()
            })
          }
        }
      }

      /**
       * 删除当前视图，重新创建一个视图
       * @private
       * */
      _reCreateView() {
        if (this._innerView) {
          this._innerView.remove()
          this._resetExtent()
        }
        this._initView(true, this._options)
      }

      /**
       * 根据crs重新创建extent，center等属性
       * @private
       * */
      _resetExtent() {
        if (this._extent) {
          this._extent = undefined
          this._zoom = 0
        }
      }

      /**
       * 根据图层的spatialReference对象，生成一个坐标系，在leaflet上一定要resolutions、origin、extent以及椭球体参数，
       * 才可以创建一个坐标系，瓦片或者WMTS自带resolutions、origin、extent，
       * 矢量或者WMS需要从extent上计算出resolutions和origin，计算操作在基础图层中完成
       * @private
       * @param {Layer} layer 图层
       * @return {CRS} 地图视图的坐标系对象
       * */
      _getCrs(layer) {
        const spatialReference = layer.spatialReference

        // 投影范围
        const layerExtent = layer.spatialReference.extent || layer.extent
        const extent = Extent.fromJSON(layerExtent.toJSON())

        const isSameSpatialReference = function (
          spatialReference1,
          spatialReference2
        ) {
          let isSame = false
          if (
            [3857, 102113, 102100, 900913].indexOf(spatialReference1.wkid) > -1 &&
            [3857, 102113, 102100, 900913].indexOf(spatialReference2.wkid) > -1
          ) {
            isSame = true
          } else if (
            [4326, 4490, 4610, 4214].indexOf(spatialReference1.wkid) > -1 &&
            [4326, 4490, 4610, 4214].indexOf(spatialReference2.wkid) > -1
          ) {
            isSame = true
          } else if (spatialReference1.wkid === spatialReference2.wkid) {
            isSame = true
          }
          return isSame
        }
        this._map.layers.forEach((layerItem) => {
          // 如果当前layer是基础底图，则不受普通图层的参考系范围影响，但也会受其他基础底图图层的参考系范围影响
          if (layer._isBasemapLayer && !layerItem._isBasemapLayer) {
            return
          }
          if (
            layerItem.spatialReference &&
            layer.spatialReference &&
            isSameSpatialReference(
              layerItem.spatialReference,
              layer.spatialReference
            ) &&
            layerItem.loaded
          ) {
            if (extent.xmin > layerItem.extent.xmin) {
              extent.xmin = layerItem.extent.xmin
            }
            if (extent.ymin > layerItem.extent.ymin) {
              extent.ymin = layerItem.extent.ymin
            }
            if (extent.xmax < layerItem.extent.xmax) {
              extent.xmax = layerItem.extent.xmax
            }
            if (extent.ymax < layerItem.extent.ymax) {
              extent.ymax = layerItem.extent.ymax
            }
          }
        })

        let tileInfo = layer.tileInfo
        // 处理 瓦片图层、arcgis瓦片图层、矢量瓦片图层
        if (layer.type === LayerType.wmts) {
          tileInfo = TileInfoUtil.parseWMTSTileInfo(layer)
        } else if (layer.type === LayerType.wms) {
          tileInfo = TileInfoUtil.parseWMSTileInfo(layer, extent)
        } else if (
          // igsFeature、wfs、geojson图层是矢量图形，图层没有tile size概念，
          // 但Leaflet引擎需要构造分辨率级，给定一个256默认tile size，用于计算分辨率级。
          // igsMapImage图层，如果是图像渲染模式则和上序逻辑相同。如果是瓦片渲染模式，tileSize取图层上设置的瓦片宽高。
          // arcgisMapImage服务对接有暂时问题，暂不自定义计算，直接取arcgisMapImage的tileInfo的分辨率
          layer.type === LayerType.igsMapImage ||
          // layer.type === LayerType.arcgisMapImage ||
          layer.type === LayerType.igsFeature ||
          layer.type === LayerType.wfs ||
          layer.type === LayerType.geojson
        ) {
          tileInfo = TileInfoUtil.parseMapImageTileInfo(layer, extent)
        }
        // igsMapImage图层临时处理，目前根据arcgisMapImage的extent、origin和tileInfo.lods构造出来的参考系有问题。
        // 问题解决后，统一走 tileInfo = TileInfoUtil.parseMapImageTileInfo(layer)逻辑。
        if (layer.type === LayerType.arcgisMapImage) {
          if (
            [3857, 102113, 102100, 900913].indexOf(layer.spatialReference.wkid) > -1
          ) {
            return L.CRS.EPSG3857
          } else if (
            [4326, 4490, 4610, 4214].indexOf(layer.spatialReference.wkid) > -1
          ) {
            return L.CRS.EPSG4326
          } else {
            tileInfo = TileInfoUtil.parseMapImageTileInfo(layer)
          }
        }

        // 原点
        const origin = Point.toCoordinates(tileInfo.origin)
        // 获取分辨率数组
        const resolutions = tileInfo.lods.map((lod) => lod.resolution)
        const length = resolutions.length
        // 如果数组长度小于20，则将数组最后一个除以2，将数组扩充为20，否则地图视图无法继续缩放
        if (length < 20) {
          let lastResolutions = resolutions[length - 1]
          let lastLod = tileInfo.lods[length - 1]
          for (let i = 0; i < 20 - length; i++) {
            resolutions.push(lastResolutions / 2)
            lastLod = {
              resolution: lastResolutions / 2,
              scale: lastLod.scale / 2,
              level: tileInfo.lods.length
            }
            tileInfo.lods.push(lastLod)
            lastResolutions /= 2
          }
        }
        // 创建一个坐标系对象
        return new CRS({
          // 指定wkid，wkid的优先级比wkt高，和igs保持一致
          wkid: spatialReference.wkid,
          // 指定wkt
          wkt: spatialReference.wkt,
          resolutions,
          lods: tileInfo.lods,
          origin,
          extent,
          // 加载矢量瓦片特殊逻辑，需要传入矢量瓦片tileSize(mapboxgl对于初级瓦片的要求),仅影响矢量瓦片显示
          mapboxTileSize: spatialReference._mapboxTileSize
        })
      }

      /**
       * @description 创建图层视图
       * @private
       * @param {Layer} layer 图层
       * @return {LayerView| null}
       */
      _createLayerView(layer) {
        const layerView = getLayerView(this, layer) || null
        return layerView
      }

      /**
       * @description 事件处理器，重载基类方法
       * @private
       */
      _processEvent(event) {
        super._processEvent(event)
        if (event.type === MapEventType.layerRemove) {
          if (
            !this._map._isRemoveMany &&
            !this._map._isRemoveAll &&
            this._map._canResetView
          ) {
            this._setSpatialReference()
          }
        }
        if (
          event.type === MapEventType.layerRemoveMany &&
          this._map._canResetView
        ) {
          if (event.layers) {
            this._setSpatialReference()
          }
        }
      }

      /**
       * @description 通过参考系图层获取CRS对象
       * @private
       * @param {Layer} referenceLayer 图层加载完毕后的对象
       * @return {CRS} CRS对象
       */
      getCrsByReferenceLayer(referenceLayer) {
        let crs = null
        if (
          !referenceLayer ||
          !referenceLayer.spatialReference ||
          !referenceLayer.loaded
        ) {
          return crs
        }
        // 根据基础图层对象上的spatialReference，创建一个自定义坐标系
        // (4326、3857也更具图层信息构造Crs，而不是定义为Leaflet的默认4326、3857Crs)
        crs = this._getCrs(referenceLayer)
        return crs
      }

      /**
       * @description 初始化获取视图尺寸大小
       * @private
       */
      _initSize() {
        const size = this._innerView.getSize()
        this.width = size.x
        this.height = size.y
      }

      /**
       * 等待图层加载完毕，重写BaseView类的该方法
       * @param {Layer} layer 基础图层对象
       * @param {Object} result 图层加载完毕后的对象
       * @param {Function} fireCreatedError 创建失败回调
       * */
      _waitLayerLoaded(layer, result, fireCreatedError) {
        const self = this
        // 是提供坐标系的图层，则加载图层
        if (layer._isSRLayer) {
          const promise = self._addLayer(result, event)
          self._layerLoaded(promise, layer, result, fireCreatedError)
        }
        // 不是的则等待图层加载完毕
        else {
          if (layer._interval) {
            clearInterval(layer._interval)
            layer._interval = undefined
          }
          layer._interval = setInterval(function () {
            const srLayer = self._map._getSRLayer()
            if (!srLayer || (srLayer && srLayer.loaded)) {
              clearInterval(layer._interval)
              layer._interval = undefined
              const promise = self._addLayer(result, event)
              self._layerLoaded(promise, layer, result, fireCreatedError)
            }
          }, 50)
        }
      }

      /**
       * 销毁视图对象<a id='destroy'></a>
       * */
      destroy() {
        this._innerView.remove()
      }

      /**
       * 获取当前视图的中心点(经纬度中心点)<a id='getCenter'></a>
       * @return {Object} 中心点对象
       * */
      getCenter() {
        const center = this._innerView.getCenter()
        let centerCrs = new Point({ coordinates: [center.lng, center.lat] })
        this._center = centerCrs
        const spatialReference = new SpatialReference(this.crs.code)
        if (!spatialReference.isGeographic) {
          centerCrs = Projection.project(centerCrs, spatialReference)
        }
        return centerCrs
      }

      /**
       * <a id='setCenter'></a>
       * 设置缩视野中心<a id='setCenter'></a>
       * @private
       * @param {Point} value 视图中心
       * */
      setCenter(value) {
        if (!this._innerView) return
        if (Array.isArray(value) && value.length > 1) {
          this._center = new Point({
            coordinates: [value[0], value[1]],
            spatialReference: new SpatialReference(this.crs.code)
          })
          this.flyTo({ zoom: this._zoom, center: this._center })
        } else if (value instanceof Point) {
          this._center = value
          this.flyTo({ zoom: this._zoom, center: this._center })
        }
      }

      /**
       * 获取当前缩放级数<a id='getZoom'></a>
       * @return {Number} 当前缩放级数
       * */
      getZoom() {
        this._zoom = this._innerView.getZoom()
        return this._zoom
      }

      /**
       * <a id='setZoom'></a>
       * 设置缩放级数
       * @private
       * @param {Number} value 视图层级
       * */
      setZoom(value) {
        if (!this._innerView) return

        this._zoom = value
        this.flyTo({ zoom: value, center: this._center })
      }

      /**
       * 获取最小缩放级数<a id='getMinZoom'></a>
       * @return {Number} 最小缩放级数
       * */
      getMinZoom() {
        return this._innerView.getMinZoom()
      }

      /**
       * @description 获取当前视图容器的宽高，单位像素<a id='getSize'></a>
       * @return {Object} 当前视图容器的宽高对象
       * */
      getSize() {
        return this._innerView.getSize()
      }

      /**
       * 获取最大缩放级数<a id='getMaxZoom'></a>
       * @return {Number} 最大缩放级数
       * */
      getMaxZoom() {
        return this._innerView.getMaxZoom()
      }

      /**
       * 获取当前视图的宽高范围，单位像素<a id='getPixelExtent'></a>
       * @return {Object} 视图宽高对象
       * */
      getPixelExtent() {
        return this._innerView.getPixelBounds()
      }

      /**
       * 获取当前视图的像素中心点<a id='getPixelCenter'></a>
       * @return {Object} 像素中心点对象
       * */
      getPixelCenter() {
        return this._innerView.getPixelOrigin()
      }

      /**
       * 获取当前视图的像素范围<a id='getPixelWorldExtent'></a>
       * @return {Object} 当前视图的像素范围
       * */
      getPixelWorldExtent() {
        return this._innerView.getPixelWorldBounds()
      }

      /**
       * 导出场景视图的配置文件<a id='toJSON'></a>
       * @return {Object} 导出的配置文件
       * */
      toJSON() {
        const _json = {}
        _json.extent = toJSON(this._extent, Extent)
        _json.center = toJSON(this._center, Point)
        _json.map = toJSON(this._map, Map)
        _json.viewId = this._viewId
        _json.animation = this.animation
        _json.width = this.width
        _json.height = this.height
        _json.zoom = this._zoom
        _json.scale = this._scale
        _json.maxScale = this.maxScale
        _json.minScale = this.minScale
        _json.preferCanvas = this.preferCanvas
        _json.spatialReferenceLocked = this.spatialReferenceLocked

        return _json
      }

      /**
       * 克隆并返回一个新的场景视图对象<a id='clone'></a>
       * @return {MapView} 一个新的场景视图对象
       * */
      clone() {
        return cloneObject(this)
      }

      /**
       * 通过json构造并返回一个新的场景视图对象<a id='fromJSON'></a>
       * @param {Object} json json对象
       * @return {MapView} 一个新的场景视图对象
       * */
      static fromJSON(json) {
        return new MapView(json)
      }

      /**
       * <a id='toMap'></a>
       * 屏幕像素坐标点转地理坐标点
       * @param {Object} screenPoint 屏幕像素坐标点，例如{ x: 900, y: 500 }
       * @return {Point} 地理坐标点
       * @example <caption><h5>屏幕像素坐标点转地理坐标示例</h5></caption>
       * // ES6引入方式
       * import { MapView } from "@mapgis/webclient-leaflet-plugin"
       * import { Map, Point, Extent } from "@mapgis/webclient-common"
       * // 初始化图层管理容器
       * const map = new Map();
       * // 初始化地图视图对象
       * const mapView = new MapView({
       *   // 视图id
       *   viewId: "mapgis-2d-viewer",
       *   // 图层管理容器
       *   map: map
       * });
       * const screenPoint = { x: 900, y: 500 }
       * mapView.toMap(screenPoint)
       * */
      toMap(screenPoint) {
        const latLngPoint = this._innerView.containerPointToLatLng(screenPoint)
        let geoPoint = new Point({
          coordinates: [latLngPoint.lng, latLngPoint.lat]
        })
        const spatialReference = new SpatialReference(this.crs.code)
        if (!spatialReference.isGeographic) {
          geoPoint = Projection.project(geoPoint, spatialReference)
        }
        return geoPoint
      }

      /**
       * <a id='toScreen'></a>
       * 地理坐标点转屏幕像素坐标点
       * @param  {Point}point 地理坐标点
       * @return {Object} 屏幕像素坐标点
       * @example <caption><h5>地理坐标点转屏幕像素坐标示例</h5></caption>
       * // ES6引入方式
       * import { MapView } from "@mapgis/webclient-leaflet-plugin"
       * import { Map, Point, Extent } from "@mapgis/webclient-common"
       * // 初始化图层管理容器
       * const map = new Map();
       * // 初始化地图视图对象
       * const mapView = new MapView({
       *   // 视图id
       *   viewId: "mapgis-2d-viewer",
       *   // 图层管理容器
       *   map: map
       * });
       * const geoPoint = new Point({ coordinates: [123, 23, 0] })
       * mapView.toScreen(geoPoint)
       * */
      toScreen(point) {
        let _point = Geometry.fromJSON(point)
        if (!_point.spatialReference.isGeographic) {
          _point = Projection.project(
            _point,
            new SpatialReference({
              wkid: 4326
            })
          )
        }
        const screenPoint = this._innerView.latLngToContainerPoint([
          _point.coordinates[1],
          _point.coordinates[0]
        ])
        return screenPoint
      }

      /**
       * <a id='takeScreenshot'></a>
       * @description 屏幕快照
       * @param {Object} [options = {}] 屏幕快照配置配置
       * @param {PictureFormat} [options.format = PictureFormat.png] 照片格式，支持png，jpeg格式
       * @param {String} [options.filename = 'screenshotFile'] 下载文件名
       * @param {Number} [options.width = undefined] 图片宽度
       * @param {Number} [options.height = undefined] 图片高度
       * @param {Number} [options.x = undefined] 图片原点x
       * @param {Number} [options.y = undefined] 图片原点y
       * @param {Boolean} [options.isDownload = true] 是否下载图片
       * @return {Object} 屏幕快照 {dataUrl String }，且浏览器会下载图片
       * @example <caption><h7>屏幕快照</h7></caption>
       * // ES5引入方式
       * const { Map, MapView } = Zondy
       * const { PictureFormat } = Zondy.Enum
       * // ES6引入方式
       * import { Map, MapView } from "@mapgis/webclient-leaflet-plugin"
       * import { PictureFormat } from "@mapgis/webclient-common"
       * // 初始化图层管理容器
       * const map = new Map();
       * // 初始化地图视图对象
       * const mapView = new MapView({
       *   // 二维场景视图的容器(html的div标签)ID
       *   viewId: "二维场景视图的容器的id",
       *   // 图层管理容器
       *   map: map
       * })
       * // 设置屏幕快照参数
       * const screenshotOptions: {
       *    format: PictureFormat.png
       * }
       * // 开始屏幕快照
       * mapView.takeScreenshot(screenshotOptions).then((result) => {
       *   // 获取base64格式的url字符串
       *   console.log("dataUrl:", result.dataUrl)
       * })
       * */
      takeScreenshot(options) {
        options.view = this
        let screenshotResult = null
        if (!this._screenshot) {
          this._screenshot = new Screenshot(options)
          screenshotResult = this._screenshot._addView()
        } else {
          screenshotResult = this._screenshot._addView()
        }
        return screenshotResult
      }

      /**
       * 获取当前视图的地理范围<a id='getExtent'></a>
       * @return {Extent}  获取当前视图的地理范围
       * */
      getExtent() {
        if (!this._innerView) return
        const latLngBounds = this._innerView.getBounds()
        let extent = new Extent({
          xmin: latLngBounds._southWest.lng,
          ymin: latLngBounds._southWest.lat,
          xmax: latLngBounds._northEast.lng,
          ymax: latLngBounds._northEast.lat
        })
        const spatialReference = new SpatialReference(this.crs.code)
        if (!spatialReference.isGeographic) {
          extent = Projection.project(extent, spatialReference)
        }
        this._extent = extent
        return extent
      }

      /**
       * <a id='setExtent'></a>
       * 设置视野范围
       * @private
       * @param {Extent} value 范围对象
       * */
      setExtent(value) {
        if (!this._innerView) return
        this._extent = value
        this.flyTo({ extent: value })
      }

      /**
       * zoom end事件处理器
       * @private
       * */
      _zoomEndHandler() {
        // 更新地图视野范围
        this.getExtent()
        // 更新地图层级
        this.getZoom()
        // 更新地图比例尺
        this.getScale()
        this.getResolution()
      }

      /**
       * move end事件处理器
       * @private
       * */
      _moveEndHandler() {
        // 更新地图视野中心
        this.getCenter()
        // 更新地图视野范围
        this.getExtent()
      }

      /**
       * 初始化最大比例尺和最小比例尺
       * @private
       * */
      _initScaleLimit() {
        if (this.minScale) {
          const result = this._getScaleZoom(this.minScale)
          if (result.zoom) {
            this.maxZoom = result.zoom
            this._innerView.setMaxZoom(this.maxZoom)
          }
        }
        if (this.maxScale) {
          const result = this._getScaleZoom(this.maxScale)
          if (result.zoom) {
            this.minZoom = result.zoom
            this._innerView.setMinZoom(this.minZoom)
          }
        }
        if (
          !this._scale ||
          this._scale <= this.minScale ||
          this._scale >= this.minScale
        ) {
          this._scale = this.maxScale || this.minScale
          this.setScale(this._scale)
        }
      }

      /**
       * 初始化弹窗
       * @private
       * */
      _initPopup() {
        this.popup = new Popup({ view: this })
      }

      /**
       * <a id='hitTest'></a>
       * @description 穿透检测，图元拾取。目前支持graphic类型拾取结果，支持图层类型GraphicLayer，FeatureLayer。
       * @param {Object} screenPoint  屏幕像素坐标点，例如{ x: 900, y: 500 }
       * @return {Array} 图元检测结果
       * @example <caption><h7>根据基础图层对象或者图层id查询并返回实际图层</h7></caption>
       * // ES6引入方式
       * import { MapView } from "@mapgis/webclient-leaflet-plugin";
       * import { Map, Point, Polygon, MultiPolygon ,Extent, GraphicsLayer, Feature, Circle, IGSFeatureLayer, IGSTileLayer } from "@mapgis/webclient-common";

       * // 初始化图层管理容器
       * const map = new Map();
       * // 初始化地图视图对象
       * this.mapView = new MapView({
       *   // 视图id
       *   viewId: "mapgis-2d-viewer",
       *   // 图层管理容器
       *   map: map,
       * })
       * // 创建一个要素
       * const feature = [
       *   new Feature({
       *     id: '11113',
       *     geometry: new Circle({
       *       center: [113, 35],
       *       radius: 10000,
       *       radiusUnit: 'kilometers',
       *     })
       *   }),
       *   new Feature({
       *     id: '11114',
       *     geometry: new Polygon({
       *       coordinates: [
       *         // 外圈
       *         [
       *           [113.0, 29.0],
       *           [116.0, 29.0],
       *           [116.0, 35.0],
       *           [113.0, 35.0],
       *           [113.0, 29.0]
       *         ]
       *       ]
       *     })
       *   }),
       *   new Feature({
       *     id: '11115',
       *     geometry:new MultiPolygon({
       *       coordinates: [
       *         [
       *           // 外圈
       *           [
       *             [112.0, 28.0],
       *             [115.0, 28.0],
       *             [115.0, 30.0],
       *             [112.0, 30.0],
       *             [112.0, 28.0]
       *           ],
       *           // 第一个内圈
       *           [
       *             [112.2, 28.2],
       *             [112.2, 29.8],
       *             [114.8, 29.8],
       *             [114.8, 28.2],
       *             [112.2, 28.2]
       *           ]
       *         ]
       *       ]
       *     })
       *   })
       * ]
       * // 初始化几何图层
       * const graphicsLayer = new GraphicsLayer({
       *   graphics:feature
       * })
       * map.add(this.graphicsLayer)
       * const result = this.mapView.hitTest({x:1100,y:600})
       * */
      hitTest(screenPoint) {
        let hitTestResult = []
        if (this.preferCanvas) {
          hitTestResult = this._hitTestCanvas(screenPoint)
        } else {
          hitTestResult = this._hitTestSVG(screenPoint)
        }
        return hitTestResult
      }

      /**
       * SVG渲染方式下图元拾取
       * @private
       * @param {Object} screenPoint  屏幕像素坐标点，例如{ x: 900, y: 500 }
       * @return {Array} SVG渲染方式下图元检测结果
       * */
      _hitTestSVG(screenPoint) {
        const self = this
        const pickedInnerLayer = []
        const clientRect = this._innerView._container.getBoundingClientRect()
        const elements = document.elementsFromPoint(
          clientRect.x + screenPoint.x,
          clientRect.y + screenPoint.y
        )
        elements.forEach((el) => {
          if (el.classList.contains('leaflet-interactive') && el._leaflet_id) {
            pickedInnerLayer.push(self._innerView._targets[el._leaflet_id])
          }
        })
        const hitTestResult = []
        pickedInnerLayer.forEach((innerLayer) => {
          const layer = this.getLayer(innerLayer.commonLayerId)
          let features = null
          if (layer.type === LayerType.igsFeature) {
            const layerView = this._getLayerView(layer)
            features = layerView._featureSetCache.filter((item) => {
              if (item.id === innerLayer.commonFeatureId) {
                return item
              }
            })
          } else if (layer.type === LayerType.graphics) {
            features = layer.graphics.filter((item) => {
              if (item.id === innerLayer.commonFeatureId) {
                return item
              }
            })
          }
          const feature =
            features && features.items.length > 0 ? features.items[0] : null
          const graphicHit = {
            graphic: feature,
            layer,
            mapPoint: this.toMap(screenPoint),
            type: 'graphic'
          }
          hitTestResult.push(graphicHit)
        })
        return hitTestResult
      }

      /**
       * Canvas渲染方式下图元拾取
       * @private
       * @param {Object} screenPoint  屏幕像素坐标点，例如{ x: 900, y: 500 }
       * @return {Array} Canvas渲染方式下图元检测结果
       * */
      _hitTestCanvas(screenPoint) {
        const pickedInnerLayer = []
        const renderer = this._innerView._renderer
        const e = { clientX: screenPoint.x, clientY: screenPoint.y }
        const point = renderer._map.mouseEventToContainerPoint(e)
        for (let order = renderer._dragFirst; order; order = order.next) {
          const layer = order.layer
          if (layer.options.interactive && layer._containsPoint(point)) {
            if (
              !(e.type === 'click' || e.type === 'preclick') ||
              !renderer._map._draggableMoved(layer)
            ) {
              if (layer.feature) {
                pickedInnerLayer.push(layer)
              }
            }
          }
        }
        const hitTestResult = []
        pickedInnerLayer.forEach((innerLayer) => {
          const layer = this.getLayer(innerLayer.commonLayerId)
          let features = null
          if (layer.type === 'igs-feature') {
            const layerView = this.getLayerView(layer)
            features = layerView._featureSetCache.filter((item) => {
              if (item.id === innerLayer.commonFeatureId) {
                return item
              }
            })
          } else {
            features = layer.graphics.filter((item) => {
              if (item.id === innerLayer.commonFeatureId) {
                return item
              }
            })
          }
          const feature = features.items.length > 0 ? features.items[0] : null
          const graphicHit = {
            graphic: feature,
            layer,
            mapPoint: this.toMap(screenPoint),
            type: 'graphic'
          }
          hitTestResult.push(graphicHit)
        })
        return hitTestResult
      }

      /**
       * 初始化地图视图比例尺
       * @private
       * */
      _initScale() {
        if (this._scale) {
          if (this.minScale && this._scale > this.minScale) {
            this.setScale()
          }
          if (this.maxScale && this._scale < this.maxScale) {
            this.setScale()
          }
        }
      }

      _getScaleZoom(value) {
        let zoom = undefined
        let scale = undefined
        if (this.crs && this.crs.options && this.crs.options.lods) {
          for (let i = 0; i < this.crs.options.lods.length; i++) {
            const item = this.crs.options.lods[i]
            const lastItem = this.crs.options.lods[i - 1]
            if (i === 0) {
              if (value >= item.scale) {
                zoom = item.level
                scale = item.scale
                break
              }
            } else if (i === this.crs.options.lods.length - 1) {
              if (value <= item.scale) {
                zoom = item.level
                scale = item.scale
                break
              }
            } else if (value >= item.scale && value <= lastItem.scale) {
              zoom = item.level
              scale = item.scale
              break
            }
          }
        }
        return { zoom, scale }
      }

      /**
       * 地图比例尺转视野范围
       * @private
       * @param {Number}  scale 比例尺
       * @return {Extent}  视野范围
       * */
      _getScaleExtent(scale) {
        const extent = this._getScaleDefaultExtent(scale)
        return extent
      }

      /**
       * 地图比例尺转默认坐标系的视野范围
       * @private
       * @param {Number}  scale 比例尺
       * @return {Extent}  视野范围
       * */
      _getScaleDefaultExtent(scale) {
        const ppi = 96 // 1 inch=2.54 厘米(cm)
        const size = this.getSize()
        const lengthX = (size.x / ppi) * 0.0254 * scale // 米
        const lengthY = (size.y / ppi) * 0.0254 * scale
        const length = Math.sqrt(lengthX * lengthX + lengthY * lengthY)
        const absAngle = (180 / Math.PI) * Math.atan2(lengthX, lengthY)

        const center = this._center.coordinates
        const getTargetPoint = (point, distance, bearing) => {
          point = T.point(point)
          const options = { units: 'kilometers' }
          const destination = T.destination(point, distance, bearing, options)
          return destination.geometry.coordinates
        }
        const leftTopPoint = getTargetPoint(center, length / 2000, -absAngle)
        const rightBottomPoint = getTargetPoint(
          center,
          length / 2000,
          180 - absAngle
        )
        const extent = new Extent({
          xmin: leftTopPoint[0],
          xmax: rightBottomPoint[0],
          ymin: rightBottomPoint[1],
          ymax: leftTopPoint[1]
        })
        return extent
      }

      /**
       * <a id='getScale'></a>
       * 获取当前比例尺
       * @return {Number}  比例尺 实际10000米:地图1米
       * */
      getScale0() {
        const ppi = 96 // 1 inch=2.54 厘米(cm)
        const size = this.getSize()
        const screenX = (size.x / ppi) * 0.0254
        const screenY = (size.y / ppi) * 0.0254
        const length = Math.sqrt(screenX * screenX + screenY * screenY)
        const latLngBounds = this._innerView.getBounds()
        const southWestPoint = T.point([
          latLngBounds._southWest.lng,
          latLngBounds._southWest.lat
        ])
        const northEastPoint = T.point([
          latLngBounds._northEast.lng,
          latLngBounds._northEast.lat
        ])
        const options = { units: 'kilometers' }
        const distance = T.distance(southWestPoint, northEastPoint, options)
        const scale = (distance * 1000) / length
        this._scale = scale
        return scale
      }

      getScale() {
        let scale = 0
        if (this.crs && this.crs.options && this.crs.options.lods) {
          if (!isNull(this._zoom)) {
            const lod = this.crs.options.lods[this._zoom]
            scale = lod.scale
          }
        }
        this._scale = scale
        return scale
      }

      /**
       * 通过视野范围参数 获取当前比例尺
       * @param {Extent}  extent 比例尺
       * @return {Number}  比例尺 实际10000米:地图1米
       * @private
       * */
      _getScaleByExtent(extent) {
        const ppi = 96 // 1 inch=2.54 厘米(cm)
        const size = this.getSize()
        const screenX = (size.x / ppi) * 0.0254
        const screenY = (size.y / ppi) * 0.0254
        const length = Math.sqrt(screenX * screenX + screenY * screenY)
        const latLngBounds = this._getLatLngFromExtent(extent)
        const southWestPoint = T.point([
          latLngBounds.southWestLatlng.lng,
          latLngBounds.southWestLatlng.lat
        ])
        const northEastPoint = T.point([
          latLngBounds.northEastLatlng.lng,
          latLngBounds.northEastLatlng.lat
        ])
        const options = { units: 'kilometers' }
        const distance = T.distance(southWestPoint, northEastPoint, options)
        const scale = (distance * 1000) / length
        this._scale = scale
        return scale
      }

      /**
       * <a id='setScale'></a>
       * 设置当前比例尺
       * @private
       * @param {Number} value 比例尺 实际10000米:地图1米
       * */
      setScale(value) {
        if (!this._innerView) return
        const result = this._getScaleZoom(value)
        // 视图定位
        if (result.zoom) {
          this.flyTo({ center: this._center, zoom: result.zoom + 1 })
        }
      }

      /**
       * 获取视图范围的比例尺
       * @private
       * @param {Extent} extent 范围对象
       * @return {Object} 比例尺
       * */
      getExtentScale(extent) {
        const ppi = 96 // 1 inch=2.54 厘米(cm)
        const size = this.getSize()
        const screenX = (size.x / ppi) * 0.0254
        const screenY = (size.y / ppi) * 0.0254
        const length = Math.sqrt(screenX * screenX + screenY * screenY)
        const leftTopPoint = T.point([extent.xmin, extent.ymax])
        const rightBottomPoint = T.point([extent.xmax, extent.ymin])
        const options = { units: 'kilometers' }
        const distance = T.distance(leftTopPoint, rightBottomPoint, options)
        const scale = (distance * 1000) / length
        this._scale = scale
        return scale
      }

      getResolutionByScale(scale) {
        const ppi = 96
        this._resolution = (0.0254 / ppi) * scale
        return this._resolution
      }

      getResolution() {
        const size = this.getSize()
        const length = Math.sqrt(size.x * size.x + size.y * size.y)
        const latLngBounds = this._innerView.getBounds()
        const southWestPoint = T.point([
          latLngBounds._southWest.lng,
          latLngBounds._southWest.lat
        ])
        const northEastPoint = T.point([
          latLngBounds._northEast.lng,
          latLngBounds._northEast.lat
        ])
        const options = { units: 'kilometers' }
        const distance = T.distance(southWestPoint, northEastPoint, options)
        this._resolution = (distance * 1000) / length
        return this._resolution
      }
    }

    /**
     * 通过一个配置生成一个场景视图对象
     * @param {Object} json 场景视图配置
     * @return {MapView}
     * */
    MapView.fromJSON = function (json) {
      json = defaultValue(json, {})

      return new MapView(json)
    }

    Object.defineProperties(MapView.prototype, {
      scale: {
        get() {
          return this._scale
        },
        set(value) {
          this.setScale(value)
        }
      },
      zoom: {
        get() {
          return this._zoom
        },
        set(value) {
          this.setZoom(value)
        }
      },
      extent: {
        get() {
          return this._extent
        },
        set(value) {
          this.setExtent(value)
        }
      },
      center: {
        get() {
          let center = this._center
          if (this.crs) {
            const spatialReference = new SpatialReference(this.crs.code)
            if (!spatialReference.isGeographic) {
              center = Projection.project(this._center, spatialReference)
            }
          }
          return center
        },
        set(value) {
          this.setCenter(value)
        }
      },
      spatialReference: {
        get() {
          return this._spatialReference
        },
        set(value) {
          this._spatialReference = value
        }
      },
      rotation: {
        get() {
          return this._rotation
        },
        set(value) {
          this._rotation = value
          if (this._innerView) {
            this._innerView.setBearing(value)
          }
        }
      }
    })
    Zondy.PluginVersion = '16.5.2'
    Zondy.MapView = MapView
    export default MapView