import { Zondy } from '@mapgis/webclient-common'
import * as L from '@mapgis/leaflet'
// eslint-disable-next-line import/no-extraneous-dependencies
import { LevelRenderer, modifyDOMElement } from '@mapgis/webclient-graphic-render'
/**
* @class Zondy.ThemeLayer.ThemeLayer
* @classdesc 专题图层基类,调用建议使用其子类实现类。
* @extends {L.Layer}
* @param {string} name - 专题图图层名称。
* @param {Object} options - 可选参数。
* @param {string} [options.id] - 专题图层 ID。默认使用 CommonUtil.createUniqueID("themeLayer_") 创建专题图层 ID。
* @param {number} [options.opacity=1] - 图层透明度。
* @fires Zondy.ThemeLayer.ThemeLayer#featuresremoved
*/
const ThemeLayer = L.Layer.extend({
options: {
name: null,
opacity: 1,
// {Array} 专题要素事件临时存储,临时保存图层未添加到 map 前用户添加的事件监听,待图层添加到 map 后把这些事件监听添加到图层上,清空此图层。
// 这是一个二维数组,组成二维数组的每个一维数组长度为 2,分别是 event, callback。
TFEvents: null
},
initialize(name, options) {
L.Util.setOptions(this, options)
this.options.name = name
this.features = []
this.TFEvents = options && options.TFEvents ? options.TFEvents : []
this.levelRenderer = new LevelRenderer()
this.movingOffset = [0, 0]
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.getEvents
* @description 获取图层事件。
* @returns {Object} 返回图层支持的事件。
*/
getEvents() {
const me = this
const events = {
zoomend: me._reset,
moveend: me._reset,
resize: me._resize
}
if (this._map._zoomAnimated) {
events.zoomanim = me._zoomAnim
}
return events
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.onRemove
* @description 删除某个地图。
* @param {L.Map} map - 要删除的地图。
*/
onRemove(map) {
const me = this
L.DomUtil.remove(me.container)
map.off('mousemove', me.mouseMoveHandler)
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.onAdd
* @description 添加专题图。
* @param {L.Map} map - 要添加的地图。
* @private
*/
onAdd(map) {
const me = this
me.map = me._map = map
me._initContainer()
if (!me.levelRenderer) {
map.removeLayer(me)
return
}
// 初始化渲染器
const size = map.getSize()
me.container.style.width = `${size.x}px`
me.container.style.height = `${size.y}px`
me._updateOpacity()
me.renderer = me.levelRenderer.init(me.container)
me.renderer.clear()
if (me.features && me.features.length > 0) {
me._reset()
}
// 处理用户预先(在图层添加到 map 前)监听的事件
me.addTFEvents()
me.mouseMoveHandler = function (e) {
const xy = e.layerPoint
me.currentMousePosition = L.point(
xy.x + me.movingOffset[0],
xy.y + me.movingOffset[1]
)
}
map.on('mousemove', me.mouseMoveHandler)
me.update()
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.addFeatures
* @description 向专题图图层中添加数据。
* @param {Object} features - 待转要素。
*/
addFeatures(features) {
// eslint-disable-line no-unused-vars
// 子类实现此方法
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.redrawThematicFeatures
* @description 抽象方法,可实例化子类必须实现此方法。
* @param {L.bounds} bounds - 重绘专题要素范围。
*/
redrawThematicFeatures(bounds) {
// eslint-disable-line no-unused-vars
// 子类必须实现此方法
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.destroyFeatures
* @description 销毁要素。
* @param {Array.<Zondy.Feature.Vector>} features - 将被销毁的要素。
*/
destroyFeatures(features) {
if (features === undefined) {
features = this.features
}
if (!features) {
return
}
this.removeFeatures(features)
for (let i = features.length - 1; i >= 0; i--) {
features[i].destroy()
}
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.removeFeatures
* @description 从专题图中删除 feature。这个函数删除所有传递进来的矢量要素。
* @param {Array.<Zondy.Feature.Vector>} features - 将被删除的要素。
*/
removeFeatures(features) {
const me = this
if (!features || features.length === 0) {
return
}
if (features === me.features) {
return me.removeAllFeatures()
}
if (!L.Util.isArray(features)) {
features = [features]
}
const featuresFailRemoved = []
for (let i = features.length - 1; i >= 0; i--) {
var feature = features[i]
// 如果我们传入的feature在features数组中没有的话,则不进行删除,
// 并将其放入未删除的数组中。
const findex = L.Util.indexOf(me.features, feature)
if (findex === -1) {
featuresFailRemoved.push(feature)
continue
}
me.features.splice(findex, 1)
}
const drawFeatures = []
for (let hex = 0, len = me.features.length; hex < len; hex++) {
feature = me.features[hex]
drawFeatures.push(feature)
}
me.features = []
me.addFeatures(drawFeatures)
// 绘制专题要素
if (me.renderer) {
if (me._map) {
me.redrawThematicFeatures(me._map.getBounds())
} else {
me.redrawThematicFeatures()
}
}
const succeed = featuresFailRemoved.length === 0
/**
* @event Zondy.ThemeLayer.ThemeLayer#featuresremoved
* @description 删除的要素成功之后触发。
* @property {Array.<Zondy.Feature.Vector>} features - 事件对象。
* @property {boolean} succeed - 要输是否删除成功,true 为删除成功,false 为删除失败。
*/
me.fire('featuresremoved', {
features: featuresFailRemoved,
succeed
})
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.removeAllFeatures
* @description 清除当前图层所有的矢量要素。
*/
removeAllFeatures() {
const me = this
if (me.renderer) {
me.renderer.clear()
}
me.features = []
me.fire('featuresremoved', {
features: [],
succeed: true
})
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.getFeatures
* @description 查看当前图层中的有效数据。
* @returns {Array} 返回图层中的有效数据。
*/
getFeatures() {
const me = this
const len = me.features.length
const clonedFeatures = new Array(len)
for (let i = 0; i < len; ++i) {
clonedFeatures[i] = me.features[i]
}
return clonedFeatures
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.getFeatureBy
* @description 在专题图的要素数组 features 里面遍历每一个 feature,当 feature[property] === value 时,返回此 feature(并且只返回第一个)。
* @param {string} property - 要的某个属性名。
* @param {string} value - 对应属性名得值。
*/
getFeatureBy(property, value) {
const me = this
return me.features.find((feature) => {
const attributes = feature.attributes
if (feature[property] === value || attributes[property] === value) {
return feature
}
})
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.getFeatureById
* @description 通过给定一个 ID,返回对应的矢量要素,如果不存在则返回 null。
* @param {number} featureId - 要素 ID。
*/
getFeatureById(featureId) {
return this.getFeatureBy('id', featureId)
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.getFeaturesByAttribute
* @description 通过给定一个属性的 key 值和 value 值,返回所有匹配的要素数组。
* @param {string} attrName - key 值。
* @param {string} attrValue - value 值。
* @returns {Array} 返回所有匹配的要素数组。
*/
getFeaturesByAttribute(attrName, attrValue) {
const me = this
let feature
const foundFeatures = []
for (const id in me.features) {
feature = me.features[id]
if (
feature &&
feature.attributes &&
feature.attributes[attrName] === attrValue
) {
foundFeatures.push(feature)
}
}
return foundFeatures
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.update
* @description 更新图层。
* @param {L.bounds} bounds - 图层范围。
*/
update(bounds) {
const mapOffset = this._map.containerPointToLayerPoint([0, 0])
L.DomUtil.setPosition(this.container, mapOffset)
const me = this
// var bounds = me._map.getBounds();
// var topLeft = me._map.latLngToLayerPoint(bounds.getNorthWest());
// var mapOffset = [parseInt(topLeft.x, 10) || 0, parseInt(topLeft.y, 10) || 0]
// // var offsetLeft = parseInt(me._map.getContainer().style.left, 10);
// // offsetLeft = -Math.round(offsetLeft);
// //var offsetTop = parseInt(me._map.getContainer().style.top, 10);
// //offsetTop = -Math.round(offsetTop);
// me.container.style.left = mapOffset[0] + 'px';
// me.container.style.top = mapOffset[1] + 'px';
// 绘制专题要素
if (me.renderer) {
me.redrawThematicFeatures(bounds)
}
if (me.currentMousePosition) {
me.currentMousePosition = L.point(
me.currentMousePosition.x - me.movingOffset[0],
me.currentMousePosition.y - me.movingOffset[1]
)
}
me.movingOffset = [0, 0]
me._zoom = me._map.getZoom()
me._center = me._map.getCenter()
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.setOpacity
* @description 设置图层的不透明度,取值 [0-1] 之间。
* @param {number} opacity - 不透明度。
*/
setOpacity(opacity) {
const me = this
if (opacity === me.options.opacity) {
return
}
if (!isNaN(opacity)) {
me.options.opacity = opacity
me._updateOpacity()
}
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.redraw
* @description 重绘该图层。
* @returns {boolean} 返回是否重绘成功。
*/
redraw() {
const me = this
if (!me.renderer) {
return false
}
if (me._map) {
me.redrawThematicFeatures(me._map.getBounds())
} else {
me.redrawThematicFeatures()
}
return true
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.on
* @description 添加专题要素事件监听。添加专题要素事件监听。
* @param {Event} event - 监听事件。
* @param {Function} callback - 回调函数。
* @param {string} context - 信息。
*/
on(event, callback, context) {
// eslint-disable-line no-unused-vars
if (this.renderer) {
this.renderer.on(event, callback)
} else {
L.Layer.prototype.on.call(this, event, callback)
}
return this
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.off
* @description 移除专题要素事件监听。
* @param {Event} event - 监听事件。
* @param {Function} callback - 回调函数。
* @param {string} context - 信息。
*/
off(event, callback, context) {
// eslint-disable-line no-unused-vars
const me = this
if (me.renderer) {
me.renderer.un(event, callback)
} else {
L.Layer.prototype.off.call(this, event, callback)
}
return this
},
fire(type, data, propagate) {
// eslint-disable-line no-unused-vars
if (this.renderer) {
this.renderer.trigger(type, data)
}
L.Layer.prototype.fire.call(this, type, data, propagate)
return this
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.addTFEvents
* @description 将图层添加到地图上之前用户要求添加的事件监听添加到图层。
* @private
*/
addTFEvents() {
const me = this
const tfEs = me.TFEvents
const len = tfEs.length
for (let i = 0; i < len; i++) {
me.renderer.on(tfEs[i][0], tfEs[i][1])
}
},
/**
* @function Zondy.ThemeLayer.ThemeLayer.prototype.getLocalXY
* @description 地理坐标转为像素坐标。
* @param {Array} coordinate
*/
getLocalXY(coordinate) {
if (!this._map) {
return coordinate
}
let coor = coordinate
if (L.Util.isArray(coordinate)) {
coor = L.point(coordinate[0], coordinate[1])
} else if (!(coordinate instanceof L.Point)) {
if (coordinate instanceof Zondy.LonLat) {
coor = L.point(coordinate.lon, coordinate.lat)
} else {
coor = L.point(coordinate.x, coordinate.y)
}
}
const point = this._map.latLngToContainerPoint(
this._map.options.crs.unproject(coor)
)
return [point.x, point.y]
},
_initContainer() {
const parentContainer = this.getPane()
const animated = this._map.options.zoomAnimation && L.Browser.any3d
let className = this.options.name || 'themeLayer'
className += ` leaflet-layer leaflet-zoom-${animated ? 'animated' : 'hide'}`
this.container = L.DomUtil.create('div', className, parentContainer)
const originProp = L.DomUtil.testProp([
'transformOrigin',
'WebkitTransformOrigin',
'msTransformOrigin'
])
this.container.style[originProp] = '50% 50%'
this.container.style.position = 'absolute'
this.container.style.zIndex = 200
},
_zoomAnim(e) {
const scale = this._map.getZoomScale(e.zoom)
const offset = this._map
._getCenterOffset(e.center)
._multiplyBy(-scale)
.subtract(this._map._getMapPanePos())
if (L.DomUtil.setTransform) {
L.DomUtil.setTransform(this.container, offset, scale)
} else {
this.container.style[
L.DomUtil.TRANSFORM
] = `${L.DomUtil.getTranslateString(offset)} scale(${scale})`
}
},
_updateOpacity() {
const me = this
modifyDOMElement(
me.container,
null,
null,
null,
null,
null,
null,
me.options.opacity
)
if (me._map !== null) {
/**
* @event Zondy.ThemeLayer.ThemeLayer#changelayer
* @description 图层属性改变之后触发。
* @property {Object} layer - 图层。
* @property {string} property - 图层属性。
*/
me._map.fire('changelayer', {
layer: me,
property: 'opacity'
})
}
},
// 缩放移动重绘
_reset() {
const me = this
const latLngBounds = me._map.getBounds()
me.update(latLngBounds)
const size = me._map.getSize()
const mapOffset = this._map.containerPointToLayerPoint([0, 0])
L.DomUtil.setPosition(this.container, mapOffset)
if (parseFloat(me.container.width) !== parseFloat(size.x)) {
me.container.width = `${size.x}px`
}
if (parseFloat(me.container.height) !== parseFloat(size.y)) {
me.container.height = `${size.y}px`
}
me.redraw()
},
// 通知渲染器的尺寸变化
_resize() {
const me = this
const newSize = me._map.getSize()
me.container.style.width = `${newSize.x}px`
me.container.style.height = `${newSize.y}px`
me.renderer.resize()
}
})
Zondy.ThemeLayer.ThemeLayer = ThemeLayer
export default ThemeLayer