    import * as L from '@mapgis/leaflet'
    // @link https://github.com/Raruto/leaflet-rotate
    /**
     * L.Map
     */
    const mapProto = L.extend({}, L.Map.prototype)

    L.Map.mergeOptions({ rotate: false, bearing: 0 })

    L.Map.include({
      initialize(id, options) {
        // (HTMLElement or String, Object)
        if (options.rotate) {
          this._rotate = true
          this._bearing = 0
        }
        mapProto.initialize.call(this, id, options)
        if (this.options.rotate) {
          this.setBearing(this.options.bearing)
        }
      },

      containerPointToLayerPoint(point) {
        // (Point)
        if (!this._rotate) {
          return mapProto.containerPointToLayerPoint.call(this, point)
        }
        return L.point(point)
          .subtract(this._getMapPanePos())
          .rotateFrom(-this._bearing, this._getRotatePanePos())
          .subtract(this._getRotatePanePos())
      },

      getBounds() {
        if (!this._rotate) {
          return mapProto.getBounds.call(this)
        }
        const size = this.getSize()
        const topleft = this.layerPointToLatLng(
          this.containerPointToLayerPoint([0, 0])
        )
        const topright = this.layerPointToLatLng(
          this.containerPointToLayerPoint([size.x, 0])
        )
        const bottomright = this.layerPointToLatLng(
          this.containerPointToLayerPoint([size.x, size.y])
        )
        const bottomleft = this.layerPointToLatLng(
          this.containerPointToLayerPoint([0, size.y])
        )

        // Use LatLngBounds' build-in constructor that automatically extends the bounds to fit the passed points
        return new L.LatLngBounds([topleft, topright, bottomright, bottomleft])
      },

      layerPointToContainerPoint(point) {
        // (Point)
        if (!this._rotate) {
          return mapProto.layerPointToContainerPoint.call(this, point)
        }
        return L.point(point)
          .add(this._getRotatePanePos())
          .rotateFrom(this._bearing, this._getRotatePanePos())
          .add(this._getMapPanePos())
      },

      // Rotation methods
      // setBearing will work with just the 'theta' parameter.
      setBearing(theta) {
        if (!L.Browser.any3d || !this._rotate) {
          return
        }
        // 计算旋转容器位置，如果未定义，默认为[0,0]
        let rotatePanePos = this._getRotatePanePos()
        const halfSize = this.getSize().divideBy(2)
        const mapPanePos = this._getMapPanePos()
        // 计算新的旋转点
        this._pivot = mapPanePos.clone().multiplyBy(-1).add(halfSize)
        // 还原旋转位置
        rotatePanePos = rotatePanePos.rotateFrom(-this._bearing, this._pivot)
        this._bearing = theta * L.DomUtil.DEG_TO_RAD // TODO: mod 360
        // 计算新的旋转容器位置
        this._rotatePanePos = rotatePanePos.rotateFrom(this._bearing, this._pivot)
        // 作用旋转容器
        L.DomUtil.setPositionBearing(
          this._rotatePane,
          rotatePanePos,
          this._bearing,
          this._pivot
        )
        this.fire('rotate')
      },

      getBearing() {
        return this._bearing * L.DomUtil.RAD_TO_DEG
      },

      _initPanes() {
        const panes = (this._panes = {})
        this._paneRenderers = {}

        // @section
        //
        // Panes are DOM elements used to control the ordering of layers on the map. You
        // can access panes with [`map.getPane`](#map-getpane) or
        // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
        // [`map.createPane`](#map-createpane) method.
        //
        // Every map has the following default panes that differ only in zIndex.
        //
        // @pane mapPane: HTMLElement = 'auto'
        // Pane that contains all other map panes

        this._mapPane = this.createPane('mapPane', this._container)
        L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0))

        if (this._rotate) {
          this._rotatePane = this.createPane('rotatePane', this._mapPane)
          this._norotatePane = this.createPane('norotatePane', this._mapPane)

          // @pane tilePane: HTMLElement = 2
          // Pane for tile layers
          this.createPane('tilePane', this._rotatePane)
          // @pane overlayPane: HTMLElement = 4
          // Pane for overlays like polylines and polygons
          this.createPane('overlayPane', this._rotatePane)

          // @pane shadowPane: HTMLElement = 5
          // Pane for overlay shadows (e.g. marker shadows)
          this.createPane('shadowPane', this._norotatePane)
          // @pane markerPane: HTMLElement = 6
          // Pane for marker icons
          this.createPane('markerPane', this._norotatePane)
          // @pane tooltipPane: HTMLElement = 650
          // Pane for tooltips.
          this.createPane('tooltipPane', this._norotatePane)
          // @pane popupPane: HTMLElement = 700
          // Pane for popups.
          this.createPane('popupPane', this._norotatePane)
        } else {
          // @pane tilePane: HTMLElement = 2
          // Pane for tile layers
          this.createPane('tilePane')
          // @pane overlayPane: HTMLElement = 4
          // Pane for overlays like polylines and polygons
          this.createPane('overlayPane')
          // @pane shadowPane: HTMLElement = 5
          // Pane for overlay shadows (e.g. marker shadows)
          this.createPane('shadowPane')
          // @pane markerPane: HTMLElement = 6
          // Pane for marker icons
          this.createPane('markerPane')
          // @pane tooltipPane: HTMLElement = 650
          // Pane for tooltips.
          this.createPane('tooltipPane')
          // @pane popupPane: HTMLElement = 700
          // Pane for popups.
          this.createPane('popupPane')
        }

        if (!this.options.markerZoomAnimation) {
          L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide')
          L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide')
        }
      },

      // @method rotatedPointToMapPanePoint(point: Point): Point
      // Converts a coordinate from the rotated pane reference system
      // to the reference system of the not rotated map pane.
      rotatedPointToMapPanePoint(point) {
        // 旋转容器->不旋转容器
        return L.point(point).rotate(this._bearing)._add(this._getRotatePanePos())
      },

      // @method mapPanePointToRotatedPoint(point: Point): Point
      // Converts a coordinate from the not rotated map pane reference system
      // to the reference system of the rotated pane.
      mapPanePointToRotatedPoint(point) {
        // 不旋转容器->旋转容器
        return L.point(point)
          ._subtract(this._getRotatePanePos())
          .rotate(-this._bearing)
      },

      // offset of the specified place to the current center in pixels
      _getCenterOffset(latlng) {
        let centerOffset = mapProto._getCenterOffset.call(this, latlng)
        if (this._rotate) {
          centerOffset = centerOffset.rotate(this._bearing)
        }
        return centerOffset
      },

      _getRotatePanePos() {
        return this._rotatePanePos || new L.Point(0, 0)
      },

      _getNewPixelOrigin(center, zoom) {
        // 计算旋转后的像素原点
        const viewHalf = this.getSize()._divideBy(2)
        if (!this._rotate) {
          mapProto._getNewPixelOrigin.call(this, center, zoom)
        }
        return this.project(center, zoom)
          .rotate(this._bearing)
          ._subtract(viewHalf)
          ._add(this._getMapPanePos())
          ._add(this._getRotatePanePos())
          .rotate(-this._bearing)
          ._round()
      },

      _handleGeolocationResponse(pos) {
        const lat = pos.coords.latitude
        const lng = pos.coords.longitude
        // TODO: use mapProto._handleGeolocationResponse
        const hdg = pos.coords.heading
        const latlng = new L.LatLng(lat, lng)
        const bounds = latlng.toBounds(pos.coords.accuracy)
        const options = this._locateOptions

        if (options.setView) {
          const zoom = this.getBoundsZoom(bounds)
          this.setView(
            latlng,
            options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom
          )
        }

        const data = {
          latlng,
          bounds,
          timestamp: pos.timestamp,
          // TODO: use mapProto._handleGeolocationResponse
          heading: hdg
        }

        for (const i in pos.coords) {
          if (typeof pos.coords[i] === 'number') {
            data[i] = pos.coords[i]
          }
        }

        // @event locationfound: LocationEvent
        // Fired when geolocation (using the [`locate`](#map-locate) method)
        // went successfully.
        this.fire('locationfound', data)
      }
    })