mirror of https://gitee.com/antv-l7/antv-l7
606 lines
16 KiB
JavaScript
606 lines
16 KiB
JavaScript
/**
|
||
* @fileOverview Layer基类
|
||
* @author lzx199065@gmail.com
|
||
*/
|
||
import Base from './base';
|
||
import * as THREE from './three';
|
||
import Controller from './controller/index';
|
||
import source from './source';
|
||
import diff from '../util/diff';
|
||
import ColorUtil from '../attr/color-util';
|
||
import { updateObjecteUniform } from '../util/object3d-util';
|
||
import Util from '../util';
|
||
import Global from '../global';
|
||
let id = 1;
|
||
function parseFields(field) {
|
||
if (Util.isArray(field)) {
|
||
return field;
|
||
}
|
||
if (Util.isString(field)) {
|
||
return field.split('*');
|
||
}
|
||
return [ field ];
|
||
}
|
||
export default class Layer extends Base {
|
||
getDefaultCfg() {
|
||
return {
|
||
visible: true,
|
||
zIndex: 0,
|
||
type: '',
|
||
minZoom: 0,
|
||
maxZoom: 22,
|
||
rotation: 0,
|
||
option: {},
|
||
attrOptions: {
|
||
},
|
||
scaleOptions: {},
|
||
preScaleOptions: null,
|
||
scales: {},
|
||
attrs: {},
|
||
// 样式配置项
|
||
styleOptions: {
|
||
stroke: [ 1, 1, 1, 1 ],
|
||
strokeWidth: 1.0,
|
||
opacity: 1.0,
|
||
strokeOpacity: 1.0,
|
||
texture: false,
|
||
blending: 'normal'
|
||
},
|
||
destroyed: false,
|
||
// 选中时的配置项
|
||
selectedOptions: null,
|
||
// active 时的配置项
|
||
activedOptions: {
|
||
fill: [ 1.0, 0, 0, 1.0 ]
|
||
},
|
||
interactions: {},
|
||
animateOptions: {
|
||
enable: false
|
||
}
|
||
};
|
||
}
|
||
constructor(scene, cfg) {
|
||
super(cfg);
|
||
this.scene = scene;
|
||
this.map = scene.map;
|
||
this._object3D = new THREE.Object3D();
|
||
this._pickObject3D = new THREE.Object3D();
|
||
this._object3D.visible = this.get('visible');
|
||
this._object3D.renderOrder = this.get('zIndex') || 0;
|
||
this._mapEventHandlers = [];
|
||
const layerId = this._getUniqueId();
|
||
this.set('layerId', layerId);
|
||
this.set('mapType', this.scene.mapType);
|
||
this.layerId = layerId;
|
||
this._activeIds = null;
|
||
const world = scene._engine.world;
|
||
world.add(this._object3D);
|
||
this.layerMesh = null;
|
||
this.layerLineMesh = null;
|
||
}
|
||
/**
|
||
* 将图层添加加到 Object
|
||
* @param {*} object three 物体
|
||
*/
|
||
add(object) {
|
||
// composer合图层绘制
|
||
if (object.type === 'composer') {
|
||
this._object3D = object;
|
||
this.scene._engine.composerLayers.push(object);
|
||
return;
|
||
}
|
||
this.layerMesh = object;
|
||
this._visibleWithZoom();
|
||
object.onBeforeRender = () => { // 每次渲染前改变状态
|
||
const zoom = this.scene.getZoom();
|
||
updateObjecteUniform(this._object3D, {
|
||
u_time: this.scene._engine.clock.getElapsedTime(),
|
||
u_zoom: zoom
|
||
});
|
||
this.preRender();
|
||
};
|
||
|
||
object.onAfterRender = () => { // 每次渲染后改变状态
|
||
this.afterRender();
|
||
};
|
||
this._object3D.add(object);
|
||
this.get('pickingController').addPickMesh(object);
|
||
|
||
}
|
||
remove(object) {
|
||
if (object.type === 'composer') {
|
||
this.scene._engine.composerLayers = this.scene._engine.composerLayers.filter(layer => {
|
||
return (layer !== object);
|
||
});
|
||
return;
|
||
}
|
||
this._object3D.remove(object);
|
||
}
|
||
_getUniqueId() {
|
||
return id++;
|
||
}
|
||
_visible(visible) {
|
||
this.set('visible', visible);
|
||
this._object3D.visible = this.get('visible');
|
||
}
|
||
// 兼容瓦片source,非瓦片source
|
||
|
||
source(data, cfg = {}) {
|
||
// 根据Source类型判断,是不是瓦片图层
|
||
if (this.scene.getTileSource(data)) {
|
||
this.set('layerType', 'tile');
|
||
this.set('sourceOption', {
|
||
id: data,
|
||
...cfg
|
||
});
|
||
this.scene.style.addLayer(this);
|
||
// 初始化tiles
|
||
this.tiles = new THREE.Object3D();
|
||
this._object3D.add(this.tiles);
|
||
return this;
|
||
}
|
||
|
||
if (data instanceof source) {
|
||
this.layerSource = data;
|
||
this.layerSource.on('SourceUpdate', () => {
|
||
this.repaint();
|
||
});
|
||
return this;
|
||
}
|
||
cfg.data = data;
|
||
cfg.mapType = this.scene.mapType;
|
||
cfg.zoom = this.scene.getZoom();
|
||
this.layerSource = new source(cfg);
|
||
return this;
|
||
}
|
||
color(field, values) {
|
||
this._createAttrOption('color', field, values, Global.colors);
|
||
return this;
|
||
}
|
||
size(field, values) {
|
||
const fields = parseFields(field);
|
||
if (fields.indexOf('zoom') !== -1) {
|
||
this._zoomScale = true;
|
||
}
|
||
if (Util.isArray(fields) && !values) values = fields;
|
||
this._createAttrOption('size', field, values, Global.size);
|
||
return this;
|
||
}
|
||
scale(field, cfg) {
|
||
const options = this.get('scaleOptions');
|
||
const scaleDefs = options;
|
||
if (Util.isObject(field)) {
|
||
Util.mix(scaleDefs, field);
|
||
} else {
|
||
scaleDefs[field] = cfg;
|
||
}
|
||
return this;
|
||
}
|
||
shape(field, values) {
|
||
if (field.split(':').length === 2) {
|
||
this.shapeType = field.split(':')[0];
|
||
field = field.split(':')[1];
|
||
}
|
||
values === 'text' ? this.shapeType = values : null;
|
||
|
||
this._createAttrOption('shape', field, values, Global.shape);
|
||
return this;
|
||
}
|
||
pattern(field, values) {
|
||
this._createAttrOption('pattern', field, values, Global.pattern);
|
||
return this;
|
||
}
|
||
/**
|
||
* 是否允许使用默认的图形激活交互
|
||
* @param {Boolean} enable 是否允许激活开关
|
||
* @param {Object} cfg 激活的配置项
|
||
* @return {Geom} 返回 geom 自身
|
||
*/
|
||
active(enable, cfg) {
|
||
if (enable === false) {
|
||
this.set('allowActive', false);
|
||
} else if (Util.isObject(enable) && enable.fill) {
|
||
this.set('allowActive', true);
|
||
if (enable.fill) enable.fill = ColorUtil.color2RGBA(enable.fill);
|
||
this.set('activedOptions', enable);
|
||
} else {
|
||
this.set('allowActive', true);
|
||
this.set('activedOptions', cfg || { fill: ColorUtil.color2RGBA(Global.activeColor) });
|
||
}
|
||
return this;
|
||
}
|
||
|
||
style(field, cfg) {
|
||
const colorItem = [ 'fill', 'stroke', 'strokeColor', 'color', 'baseColor', 'brightColor', 'windowColor' ];
|
||
let styleOptions = this.get('styleOptions');
|
||
if (!styleOptions) {
|
||
styleOptions = {};
|
||
this.set('styleOptions', styleOptions);
|
||
}
|
||
if (Util.isObject(field)) {
|
||
cfg = field;
|
||
field = null;
|
||
}
|
||
let fields;
|
||
if (field) {
|
||
fields = parseFields(field);
|
||
}
|
||
styleOptions.fields = fields;
|
||
Util.assign(styleOptions, cfg);
|
||
for (const item in cfg) {
|
||
if (colorItem.indexOf(item) !== -1 && styleOptions[item] !== 'none') {
|
||
styleOptions[item] = ColorUtil.color2RGBA(styleOptions[item]);
|
||
}
|
||
}
|
||
this.set('styleOptions', styleOptions);
|
||
return this;
|
||
}
|
||
|
||
filter(field, values) {
|
||
this._createAttrOption('filter', field, values, true);
|
||
return this;
|
||
}
|
||
|
||
animate(field, cfg) {
|
||
let animateOptions = this.get('animateOptions');
|
||
if (!animateOptions) {
|
||
animateOptions = {};
|
||
this.set('animateOptions', animateOptions);
|
||
}
|
||
if (Util.isObject(field)) {
|
||
cfg = field;
|
||
field = null;
|
||
}
|
||
let fields;
|
||
if (field) {
|
||
fields = parseFields(field);
|
||
}
|
||
animateOptions.fields = fields;
|
||
Util.assign(animateOptions, cfg);
|
||
this.set('animateOptions', animateOptions);
|
||
return this;
|
||
}
|
||
fitBounds() {
|
||
const extent = this.layerSource.data.extent;
|
||
this.scene.fitBounds(extent);
|
||
}
|
||
hide() {
|
||
this._visible(false);
|
||
this.scene._engine.update();
|
||
return this;
|
||
}
|
||
show() {
|
||
this._visible(true);
|
||
this.scene._engine.update();
|
||
return this;
|
||
}
|
||
setData(data, cfg) {
|
||
this.layerSource.setData(data, cfg);
|
||
this.repaint();
|
||
this.scene._engine.update();
|
||
}
|
||
_createScale(field) {
|
||
// TODO scale更新
|
||
const scales = this.get('scales');
|
||
let scale = scales[field];
|
||
if (!scale) {
|
||
scale = this.createScale(field);
|
||
scales[field] = scale;
|
||
}
|
||
return scale;
|
||
}
|
||
_setAttrOptions(attrName, attrCfg) {
|
||
const options = this.get('attrOptions');
|
||
if (attrName === 'size' && this._zoomScale) {
|
||
attrCfg.zoom = this.map.getZoom();
|
||
}
|
||
options[attrName] = attrCfg;
|
||
}
|
||
_createAttrOption(attrName, field, cfg, defaultValues) {
|
||
|
||
const attrCfg = {};
|
||
attrCfg.field = field;
|
||
if (cfg) {
|
||
if (Util.isFunction(cfg)) {
|
||
attrCfg.callback = cfg;
|
||
} else {
|
||
attrCfg.values = cfg;
|
||
}
|
||
} else if (attrName !== 'color') {
|
||
attrCfg.values = defaultValues;
|
||
}
|
||
this._setAttrOptions(attrName, attrCfg);
|
||
}
|
||
_initControllers() {
|
||
const pickCtr = new Controller.Picking({ layer: this });
|
||
const interactionCtr = new Controller.Interaction({ layer: this });
|
||
const eventCtr = new Controller.Event({ layer: this });
|
||
this.set('pickingController', pickCtr);
|
||
this.set('interacionController', interactionCtr);
|
||
this.set('eventController', eventCtr);
|
||
}
|
||
_mapping() {
|
||
const mappingCtr = new Controller.Mapping({ layer: this });
|
||
this.set('mappingController', mappingCtr);
|
||
}
|
||
render() {
|
||
if (this.get('layerType') === 'tile') {
|
||
this._initControllers();
|
||
this._initInteraction();
|
||
this.scene.style.update(this._attrs);
|
||
return this;
|
||
}
|
||
this._updateDraw();
|
||
this.scene._engine.update();
|
||
return this;
|
||
}
|
||
// 重绘 度量, 映射,顶点构建
|
||
repaint() {
|
||
this.set('scales', {});
|
||
this._mapping();
|
||
this.redraw();
|
||
}
|
||
// 初始化图层
|
||
init() {
|
||
this._initControllers();
|
||
this._mapping();
|
||
}
|
||
_initInteraction() {
|
||
if (this.get('allowActive')) {
|
||
this.get('interacionController').addInteraction('active');
|
||
}
|
||
}
|
||
_initMapEvent() {
|
||
// zoomchange mapmove resize
|
||
const EVENT_TYPES = [ 'zoomchange', 'dragend' ];
|
||
Util.each(EVENT_TYPES, type => {
|
||
const handler = Util.wrapBehavior(this, `${type}`);
|
||
this.map.on(`${type}`, handler);
|
||
this._mapEventHandlers.push({ type, handler });
|
||
});
|
||
}
|
||
clearMapEvent() {
|
||
const eventHandlers = this._mapEventHandlers;
|
||
Util.each(eventHandlers, eh => {
|
||
this.map.off(eh.type, eh.handler);
|
||
});
|
||
}
|
||
zoomchange(ev) {
|
||
// 地图缩放等级变化
|
||
this._visibleWithZoom(ev);
|
||
}
|
||
dragend() {
|
||
|
||
}
|
||
resize() {
|
||
}
|
||
|
||
setActive(id, color) {
|
||
this._activeIds = id;
|
||
if (!color) color = Global.activeColor;
|
||
if (!Array.isArray(color)) {
|
||
color = ColorUtil.color2RGBA(color);
|
||
}
|
||
updateObjecteUniform(this._object3D, {
|
||
u_activeColor: color,
|
||
u_activeId: id
|
||
});
|
||
this.scene._engine.update();
|
||
}
|
||
|
||
_addActiveFeature(e) {
|
||
const { featureId } = e;
|
||
this._activeIds = featureId;
|
||
updateObjecteUniform(this._object3D, { u_activeId: featureId });
|
||
}
|
||
|
||
_setPreOption() {
|
||
const nextAttrs = this.get('attrOptions');
|
||
const nextStyle = this.get('styleOptions');
|
||
this.set('preAttrOptions', Util.clone(nextAttrs));
|
||
this.set('preStyleOption', Util.clone(nextStyle));
|
||
}
|
||
_updateDraw() {
|
||
const preAttrs = this.get('preAttrOptions');
|
||
const nextAttrs = this.get('attrOptions');
|
||
const preStyle = this.get('preStyleOption');
|
||
const nextStyle = this.get('styleOptions');
|
||
if (preAttrs === undefined && preStyle === undefined) { // 首次渲染
|
||
this._setPreOption();
|
||
this.init();
|
||
this._initInteraction();
|
||
this._initMapEvent();
|
||
if (this.layerData.length === 0) {
|
||
return;
|
||
}
|
||
this.draw();
|
||
return;
|
||
}
|
||
|
||
if (!Util.isEqual(preAttrs.filter, nextAttrs.filter) ||
|
||
!Util.isEqual(preAttrs.color, nextAttrs.color) ||
|
||
!Util.isEqual(preAttrs.size, nextAttrs.size) ||
|
||
!Util.isEqual(preAttrs.shape, nextAttrs.shape)
|
||
) {
|
||
this.repaint();
|
||
}
|
||
if (!Util.isEqual(preStyle, nextStyle)) {
|
||
// 判断新增,修改,删除
|
||
const newStyle = {};
|
||
Util.each(diff(preStyle, nextStyle), ({ type, key, value }) => {
|
||
(type !== 'remove') && (newStyle[key] = value);
|
||
// newStyle[key] = type === 'remove' ? null : value;
|
||
});
|
||
this._updateStyle(newStyle);
|
||
}
|
||
this._setPreOption();
|
||
}
|
||
|
||
_updateSize(zoom) {
|
||
const sizeOption = this.get('attrOptions').size;
|
||
const fields = parseFields(sizeOption.field);
|
||
const data = this.layerSource.data.dataArray;
|
||
if (!this.zoomSizeCache) this.zoomSizeCache = {};
|
||
if (!this.zoomSizeCache[zoom]) {
|
||
this.zoomSizeCache[zoom] = [];
|
||
for (let i = 0; i < data.length; i++) {
|
||
const params = fields.map(field => data[i][field]);
|
||
const indexZoom = fields.indexOf('zoom');
|
||
indexZoom !== -1 ? params[indexZoom] = zoom : null;
|
||
this.zoomSizeCache[zoom].push(sizeOption.callback(...params));
|
||
|
||
}
|
||
}
|
||
this.emit('sizeUpdated', this.zoomSizeCache[zoom]);
|
||
}
|
||
_updateStyle(option) {
|
||
const newOption = { };
|
||
for (const key in option) {
|
||
newOption['u_' + key] = option[key];
|
||
}
|
||
updateObjecteUniform(this._object3D, newOption);
|
||
}
|
||
_scaleByZoom() {
|
||
if (this._zoomScale) {
|
||
this.map.on('zoomend', () => {
|
||
const zoom = this.map.getZoom();
|
||
this._updateSize(Math.floor(zoom));
|
||
});
|
||
}
|
||
}
|
||
|
||
getSelectFeature(featureId, lnglat) {
|
||
if (this.get('layerType') === 'tile') {
|
||
const sourceCache = this.getSourceCache(this.get('sourceOption').id);
|
||
const feature = sourceCache.getSelectFeature(featureId, this.layerId, lnglat);
|
||
return {
|
||
feature
|
||
};
|
||
}
|
||
const feature = this.layerSource && this.layerSource.getSelectFeature(featureId) || {};
|
||
const style = this.layerData[featureId - 1];
|
||
return {
|
||
feature,
|
||
style
|
||
};
|
||
}
|
||
_updateFilter() {
|
||
this.get('mappingController').reMapping();
|
||
}
|
||
_visibleWithZoom() {
|
||
if (this._object3D === null) return;
|
||
const zoom = this.scene.getZoom();
|
||
const minZoom = this.get('minZoom');
|
||
const maxZoom = this.get('maxZoom');
|
||
// z-fighting
|
||
let offset = 0;
|
||
if (this.type === 'point') {
|
||
offset = 5;
|
||
|
||
} else if (this.type === 'polyline' || this.type === 'line') {
|
||
offset = 2;
|
||
} else if (this.type === 'polygon') {
|
||
offset = 1;
|
||
}
|
||
if (this.type === 'text') {
|
||
offset = 10;
|
||
}
|
||
this._object3D.position && (this._object3D.position.z = offset * Math.pow(2, 20 - zoom));
|
||
if (zoom < minZoom || zoom >= maxZoom) {
|
||
this._object3D.visible = false;
|
||
} else if (this.get('visible')) {
|
||
this._object3D.visible = true;
|
||
}
|
||
}
|
||
|
||
// 重新构建mesh
|
||
redraw() {
|
||
this._object3D.children.forEach(child => {
|
||
this._object3D.remove(child);
|
||
});
|
||
this.get('pickingController').removeAllMesh();
|
||
this.draw();
|
||
}
|
||
/**
|
||
* 重置高亮要素
|
||
*/
|
||
_resetStyle() {
|
||
this._activeIds = null;
|
||
updateObjecteUniform(this._object3D, { u_activeId: 0 });
|
||
}
|
||
/**
|
||
* 销毁Layer对象
|
||
*/
|
||
destroy() {
|
||
this.removeAllListeners();
|
||
this.get('interacionController').clearAllInteractions();
|
||
this.clearMapEvent();
|
||
if (this._object3D.type === 'composer') {
|
||
this.remove(this._object3D);
|
||
return;
|
||
}
|
||
if (this._object3D && this._object3D.children) {
|
||
let child;
|
||
for (let i = 0; i < this._object3D.children.length; i++) {
|
||
child = this._object3D.children[i];
|
||
if (!child) {
|
||
continue;
|
||
}
|
||
this.remove(child);
|
||
if (child.geometry) {
|
||
// child.geometry.dispose();
|
||
child.geometry = null;
|
||
}
|
||
if (child.material) {
|
||
if (child.material.map) {
|
||
child.material.map.dispose();
|
||
child.material.map = null;
|
||
}
|
||
|
||
child.material.dispose();
|
||
child.material = null;
|
||
}
|
||
child = null;
|
||
}
|
||
}
|
||
this.layerMesh.geometry = null;
|
||
this.layerMesh.material = null;
|
||
if (this._pickingMesh) {
|
||
this._pickingMesh.children[0].geometry = null;
|
||
this._pickingMesh.children[0].material.dispose();
|
||
this._pickingMesh.children[0].material = null;
|
||
}
|
||
this._buffer = null;
|
||
this._object3D = null;
|
||
this.scene._engine._scene.remove(this._object3D);
|
||
this.layerData.length = 0;
|
||
this.layerSource = null;
|
||
this.scene._engine._picking.remove(this._pickingMesh);
|
||
this.destroyed = true;
|
||
}
|
||
|
||
/**
|
||
* 获取图例配置项
|
||
* @param {*} field 字段
|
||
* @param {*} type 图例类型 color, size
|
||
* @return {*} 图例配置项
|
||
*/
|
||
getLegendCfg(field, type = 'color') {
|
||
const mapCtr = this.get('mappingController');
|
||
return mapCtr.getLegendCfg(field, type);
|
||
}
|
||
preRender() {
|
||
|
||
}
|
||
|
||
afterRender() {
|
||
|
||
}
|
||
|
||
// tileLayer
|
||
getSourceCache(id) {
|
||
return this.scene.style.getSource(id);
|
||
}
|
||
}
|
||
|