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)
}
})