import {Zondy} from '../../service/common/Base';
import ImageCanvasSource
from 'ol/source/ImageCanvas.js';
import {LevelRenderer} from '../../common/overlay/levelRender/LevelRenderer';
import {modifyDOMElement} from "../../service/common/Util";
import {createCanvasContext2D} from '../../service/common/Util';
import {isArray} from "../../service/common/Util";
import {indexOf} from "../../service/common/Util";
/**
* @class Zondy.Source.ThemeSource
* @classdesc 专题图基类。
* @param {string} name - 专题图图层名称。
* @param {Object} option - 参数。
* @param {ol.Map} option.map - 当前 openlayers 的 Map 对象。
* @param {string} [option.id] - 专题图层 ID。
* @param {number} [option.opacity=1] - 图层透明度。
* @param {ol.proj.Projection} [option.projection] - 投影信息。
* @param {number} [option.ratio=1.5] - 视图比,1 表示画布是地图视口的大小,2 表示地图视口的宽度和高度的两倍,依此类推。 必须是 1 或更高。
* @param {Array} [option.resolutions] - 分辨率数组。
* @param {ol.source.State} [option.state] - 资源状态。
* @extends {ol.source.ImageCanvas}
*/
class ThemeSource extends ImageCanvasSource {
constructor(name, options) {
var options = options ? options : {};
super({
canvasFunction: canvasFunctionInternal_,
logo: options.logo,
projection: options.projection,
ratio: options.ratio,
resolutions: options.resolutions,
state: options.state
});
this.id = options.id || "themeLayer_";
function canvasFunctionInternal_(extent, resolution, pixelRatio, size, projection) { // eslint-disable-line no-unused-vars
var mapWidth = size[0] * pixelRatio;
var mapHeight = size[1] * pixelRatio;
if (!this.context) {
this.context = createCanvasContext2D(mapWidth, mapHeight);
}
if (!this.features) {
return this.context.canvas;
}
this.pixelRatio = pixelRatio;
var width = this.map.getSize()[0] * pixelRatio;
var height = this.map.getSize()[1] * pixelRatio;
this.offset = [(mapWidth - width) / 2 / pixelRatio, (mapHeight - height) / 2 / pixelRatio];
if (!this.notFirst) {
this.redrawThematicFeatures(extent);
this.notFirst = true;
}
this.div.id = this.id;
this.div.className = "themeLayer";
this.div.style.width = mapWidth + "px";
this.div.style.height = mapHeight + "px";
this.map.getViewport().appendChild(this.div);
this.renderer.resize();
this.map.getViewport().removeChild(this.div);
this.themeCanvas = this.renderer.painter.root.getElementsByTagName('canvas')[0];
this.themeCanvas.width = mapWidth;
this.themeCanvas.height = mapHeight;
this.themeCanvas.style.width = mapWidth + "px";
this.themeCanvas.style.height = mapHeight + "px";
this.themeCanvas.getContext('2d').clearRect(0, 0, mapWidth, mapHeight);
var highLightContext = this.renderer.painter._layers.hover.ctx;
var highlightCanvas = highLightContext.canvas;
var copyHighLightContext = createCanvasContext2D(mapWidth, mapHeight);
copyHighLightContext.drawImage(highlightCanvas, 0, 0, highlightCanvas.width, highlightCanvas.height, 0, 0, mapWidth, mapHeight);
this.redrawThematicFeatures(extent);
var canvas = this.context.canvas;
this.context.clearRect(0, 0, canvas.width, canvas.height);
canvas.width = mapWidth;
canvas.height = mapHeight;
canvas.style.width = mapWidth + "px";
canvas.style.height = mapHeight + "px";
this.context.drawImage(this.themeCanvas, 0, 0, mapWidth, mapHeight, 0, 0, mapWidth, mapHeight);
this.context.drawImage(copyHighLightContext.canvas, 0, 0, mapWidth, mapHeight, 0, 0, mapWidth, mapHeight);
return this.context.canvas;
}
this.canvasFunctionInternal_ = canvasFunctionInternal_;
this.EVENT_TYPES = ["loadstart", "loadend", "loadcancel", "visibilitychanged", "move", "moveend", "added", "removed", "tileloaded", "beforefeaturesadded", "featuresadded", "featuresremoved"];
this.features = [];
this.TFEvents = options.TFEvents || [];
this.map = options.map;
var size = this.map.getSize();
this.div = document.createElement('div');
this.map.getViewport().appendChild(this.div);
this.div.style.width = size[0] + "px";
this.div.style.height = size[1] + "px";
this.setOpacity(options.opacity);
this.levelRenderer = new LevelRenderer();
this.movingOffset = [0, 0];
this.renderer = this.levelRenderer.init(this.div);
this.map.getViewport().removeChild(this.div);
this.renderer.clear();
//处理用户预先(在图层添加到 map 前)监听的事件
this.addTFEvents();
}
/**
* @function Zondy.Source.ThemeSource.prototype.destroy
* @description 释放资源,将引用资源的属性置空。
*/
destroy() {
this.EVENT_TYPES = null;
this.isBaseLayer = null;
this.TFEvents = null;
this.destroyFeatures();
this.features = null;
if (this.renderer) {
this.renderer.dispose();
}
this.renderer = null;
this.levelRenderer = null;
this.movingOffset = null;
this.currentMousePosition = null;
}
/**
* @function Zondy.Source.ThemeSource.prototype.destroyFeatures
* @description 销毁某个要素。
* @param {Zondy.Feature.Vector} features - 将被销毁的要素。
*/
destroyFeatures(features) {
var all = (features === undefined);
if (all) {
features = this.features;
}
if (features) {
this.removeFeatures(features);
features = null;
// for (var i = features.length - 1; i >= 0; i--) {
// features[i].destroy();
// }
}
}
/**
* @function Zondy.Source.ThemeSource.prototype.setOpacity
* @description 设置图层的不透明度,取值[0-1]之间。
* @param {number} opacity - 不透明度。
*/
setOpacity(opacity) {
if (opacity !== this.opacity) {
this.opacity = opacity;
var element = this.div;
modifyDOMElement(element, null, null, null,
null, null, null, opacity);
if (this.map !== null) {
this.dispatchEvent({
type: 'changelayer',
value: {
layer: this,
property: "opacity"
}
});
}
}
}
/**
* @function Zondy.Source.ThemeSource.prototype.addFeatures
* @param {Object} features - 待转要素。
* @description 抽象方法,可实例化子类必须实现此方法。向专题图图层中添加数据,
* 专题图仅接收 Zondy.Feature.Vector 类型数据,
* feature 将储存于 features 属性中,其存储形式为数组。
*/
addFeatures(features) { // eslint-disable-line no-unused-vars
}
/**
* @function Zondy.Source.ThemeSource.prototype.removeFeatures
* @param {Array.<Zondy.Feature.Vector>} features - 要删除 feature 的数组。
* @description 从专题图中删除 feature。这个函数删除所有传递进来的矢量要素。
* 参数中的 features 数组中的每一项,必须是已经添加到当前图层中的 feature,
* 如果无法确定 feature 数组,则可以调用 removeAllFeatures 来删除所有 feature。
* 如果要删除的 feature 数组中的元素特别多,推荐使用 removeAllFeatures,
* 删除所有 feature 后再重新添加。这样效率会更高。
*/
removeFeatures(features) {
if (!features || features.length === 0) {
return;
}
if (features === this.features) {
return this.removeAllFeatures();
}
if (!(isArray(features))) {
features = [features];
}
var featuresFailRemoved = [];
for (var i = features.length - 1; i >= 0; i--) {
var feature = features[i];
//如果我们传入的feature在features数组中没有的话,则不进行删除,
//并将其放入未删除的数组中。
var findex = indexOf(this.features, feature);
if (findex === -1) {
featuresFailRemoved.push(feature);
continue;
}
this.features.splice(findex, 1);
}
var drawFeatures = [];
for (var hex = 0, len = this.features.length; hex < len; hex++) {
feature = this.features[hex];
drawFeatures.push(feature);
}
this.features = [];
this.addFeatures(drawFeatures);
//绘制专题要素
if (this.renderer) {
this.redrawThematicFeatures(this.map.getView().calculateExtent());
}
var succeed = featuresFailRemoved.length === 0 ? true : false;
this.dispatchEvent({
type: "featuresremoved",
value: {
features: featuresFailRemoved,
succeed: succeed
}
});
}
/**
* @function Zondy.Source.ThemeSource.prototype.removeAllFeatures
* @description 清除当前图层所有的矢量要素。
*/
removeAllFeatures() {
if (this.renderer) {
this.renderer.clear();
}
this.features = [];
this.dispatchEvent({
type: 'featuresremoved',
value: {
features: [],
succeed: true
}
});
}
/**
* @function Zondy.Source.ThemeSource.prototype.getFeatures
* @description 查看当前图层中的有效数据。
* @returns {Zondy.Feature.Vector} 用户加入图层的有效数据。
*/
getFeatures() {
var len = this.features.length;
var clonedFeatures = new Array(len);
for (var i = 0; i < len; ++i) {
clonedFeatures[i] = this.features[i];
//clonedFeatures[i] = this.features[i].clone();
}
return clonedFeatures;
}
/**
* @function Zondy.Source.ThemeSource.prototype.getFeatureBy
* @description 在专题图的要素数组 features 里面遍历每一个 feature,当 feature[property] === value 时,
* 返回此 feature(并且只返回第一个)。
* @param {string} property - feature 的某个属性名称。
* @param {string} value - property 所对应的值。
* @returns {Zondy.Feature.Vector} 第一个匹配属性和值的矢量要素。
*/
getFeatureBy(property, value) {
var feature = null;
for (var id in this.features) {
if (this.features[id][property] === value) {
feature = this.features[id];
//feature = this.features[id].clone();
break;
}
}
return feature;
}
/**
* @function Zondy.Source.ThemeSource.prototype.getFeatureById
* @description 通过给定一个 ID,返回对应的矢量要素。
* @param {string} featureId - 矢量要素的属性 ID。
* @returns {Zondy.Feature.Vector} 对应 ID 的 feature,如果不存在则返回 null。
*/
getFeatureById(featureId) {
return this.getFeatureBy('FID', featureId);
}
/**
* @function Zondy.Source.ThemeSource.prototype.getFeaturesByAttribute
* @description 通过给定一个属性的 key 值和 value 值,返回所有匹配的要素数组。
* @param {string} attrName - 属性的 key。
* @param {string} attrValue - 矢量要素的属性 ID。
* @returns {Array.<Zondy.Feature.Vector>} 一个匹配的 feature 数组。
*/
getFeaturesByAttribute(attrName, attrValue) {
var feature,
foundFeatures = [];
for (var id in this.features) {
feature = this.features[id];
if (feature && feature.attributes) {
if (feature.attributes[attrName] === attrValue) {
foundFeatures.push(feature);
}
}
}
return foundFeatures;
}
/**
* @function Zondy.Source.ThemeSource.prototype.redrawThematicFeatures
* @description 抽象方法,可实例化子类必须实现此方法。重绘专题要素。
* @param {Array} extent - 当前级别下计算出的地图范围。
*/
redrawThematicFeatures(extent) { //eslint-disable-line no-unused-vars
}
/**
* @function Zondy.Source.ThemeSource.prototype.on
* @description 添加专题要素事件监听。支持的事件包括: click、mousedown、mousemove、mouseout、mouseover、mouseup。
* @param {string} event - 事件名称。
* @param {RequestCallback} callback - 事件回调函数。
*/
on(event, callback) {
var cb = callback;
if (!this.renderer) {
var evn = [];
evn.push(event);
evn.push(cb);
this.TFEvents.push(evn);
} else {
this.renderer.on(event, cb);
}
}
/**
* @function Zondy.Source.ThemeSource.prototype.fire
* @description 添加专题要素事件监听。
* @param {string} type - 事件类型。
* @param {string} event - 事件名称。
*/
fire(type, event) {
if (!this.offset) {
return;
}
event = event.originalEvent;
var x = this.getX(event);
var y = this.getY(event);
var rotation = -this.map.getView().getRotation();
var center = this.map.getPixelFromCoordinate(this.map.getView().getCenter());
var scaledP = this.scale([x, y], center, this.pixelRatio);
var rotatedP = this.rotate(scaledP, rotation, center);
var resultP = [rotatedP[0] + this.offset[0], rotatedP[1] + this.offset[1]];
var offsetEvent = document.createEvent('Event');
offsetEvent.initEvent('pointermove', true, true);
offsetEvent.offsetX = resultP[0];
offsetEvent.offsetY = resultP[1];
offsetEvent.layerX = resultP[0];
offsetEvent.layerY = resultP[1];
offsetEvent.clientX = resultP[0];
offsetEvent.clientY = resultP[1];
offsetEvent.x = x;
offsetEvent.y = y;
if (type === 'click') {
this.renderer.handler._clickHandler(offsetEvent);
}
if (type === 'dblclick') {
this.renderer.handler._dblclickHandler(offsetEvent);
}
if (type === 'onmousewheel') {
this.renderer.handler._mousewheelHandler(offsetEvent);
}
if (type === 'mousemove') {
this.renderer.handler._mousemoveHandler(offsetEvent);
this.changed();
}
if (type === 'onmouseout') {
this.renderer.handler._mouseoutHandler(offsetEvent);
}
if (type === 'onmousedown') {
this.renderer.handler._mousedownHandler(offsetEvent);
}
if (type === 'onmouseup') {
this.renderer.handler._mouseupHandler(offsetEvent);
}
}
getX(e) {
return typeof e.zrenderX != 'undefined' && e.zrenderX
|| typeof e.offsetX != 'undefined' && e.offsetX
|| typeof e.layerX != 'undefined' && e.layerX
|| typeof e.clientX != 'undefined' && e.clientX;
}
getY(e) {
return typeof e.zrenderY != 'undefined' && e.zrenderY
|| typeof e.offsetY != 'undefined' && e.offsetY
|| typeof e.layerY != 'undefined' && e.layerY
|| typeof e.clientY != 'undefined' && e.clientY;
}
/**
* @function Zondy.Source.ThemeSource.prototype.un
* @description 移除专题要素事件监听。
* @param {string} event - 事件名称。
* @param {RequestCallback} callback - 事件回调函数。
*/
un(event, callback) {
var cb = callback;
if (!this.renderer) {
var tfEs = this.TFEvents;
var len = tfEs.length;
var newtfEs = [];
for (var i = 0; i < len; i++) {
var tfEs_i = tfEs[i];
if (!(tfEs_i[0] === event && tfEs_i[1] === cb)) {
newtfEs.push(tfEs_i)
}
}
this.TFEvents = newtfEs;
} else {
this.renderer.un(event, cb);
}
}
/**
* @function Zondy.Source.ThemeSource.prototype.addTFEvents
* @description 将图层添加到地图上之前用户要求添加的事件监听添加到图层。
* @private
*/
addTFEvents() {
var tfEs = this.TFEvents;
var len = tfEs.length;
for (var i = 0; i < len; i++) {
this.renderer.on(tfEs[i][0], tfEs[i][1]);
}
}
/**
* @function Zondy.Source.ThemeSource.prototype.getLocalXY
* @description 获取坐标系统。
* @param {Object} coordinate - 坐标位置。
*/
getLocalXY(coordinate) {
var pixelP,
map = this.map;
if (isArray(coordinate)) {
pixelP = map.getPixelFromCoordinate(coordinate);
}
var rotation = -map.getView().getRotation();
var center = map.getPixelFromCoordinate(map.getView().getCenter());
var rotatedP = pixelP;
if (this.pixelRatio) {
rotatedP = this.scale(pixelP, center, this.pixelRatio);
}
if (pixelP && center) {
rotatedP = this.rotate(rotatedP, rotation, center);
}
if (this.offset && rotatedP) {
return [rotatedP[0] + this.offset[0], rotatedP[1] + this.offset[1]];
}
return rotatedP;
}
/**
* @function Zondy.Source.ThemeSource.prototype.rotate
* @description 获取某像素坐标点 pixelP 绕中心 center 逆时针旋转 rotation 弧度后的像素点坐标。
* @param {number} pixelP - 像素坐标点位置。
* @param {number} rotation - 旋转角度。
* @param {number} center - 中心位置。
*/
rotate(pixelP, rotation, center) {
var x = Math.cos(rotation) * (pixelP[0] - center[0]) - Math.sin(rotation) * (pixelP[1] - center[1]) + center[0];
var y = Math.sin(rotation) * (pixelP[0] - center[0]) + Math.cos(rotation) * (pixelP[1] - center[1]) + center[1];
return [x, y];
}
/**
* @function Zondy.Source.ThemeSource.prototype.scale
* @description 获取某像素坐标点 pixelP 相对于中心 center 进行缩放 scaleRatio 倍后的像素点坐标。
* @param {Object} pixelP - 像素点。
* @param {Object} center - 中心点。
* @param {number} scaleRatio - 缩放倍数。
* @returns {Array.<number>} 返回数组形比例
*/
scale(pixelP, center, scaleRatio) {
var x = (pixelP[0] - center[0]) * scaleRatio + center[0];
var y = (pixelP[1] - center[1]) * scaleRatio + center[1];
return [x, y];
}
}
export {ThemeSource};
Zondy.Source.ThemeSource = ThemeSource;