feat: tile style update (#1428)

* feat: 增加瓦片的样式更新逻辑

* style: lint style

* feat: 补充瓦片样式的代理方法

* chore: 样式更新代码修改位置、瓦片代码清理

* style: lint style

Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>
This commit is contained in:
YiQianYao 2022-10-25 16:53:43 +08:00 committed by GitHub
parent 00e5aa04d7
commit c48781d30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 68 additions and 567 deletions

View File

@ -213,17 +213,27 @@ export interface IBaseTileLayerManager {
destroy(): void;
}
export interface ITileRenderService {
render(layers: ILayer[]): void;
export interface ITilePickService {
pickRender(target: IInteractionTarget): void;
}
export interface ITilePickService {
isLastPicked: boolean;
on(type: string, cb: (option: any) => void): void;
beforeHighlight(pickedColors: any): void;
beforeSelect(pickedColors: any): void;
clearPick(): void;
pick(layers: ILayer[], target: IInteractionTarget): boolean;
export interface ITile {
x: number;
y: number;
z: number;
key: string;
sourceTile: SourceTile;
visible: boolean;
isLoaded: boolean;
getLayers(): ILayer[];
styleUpdate(...args: any): void;
initTileLayer(): Promise<void>;
lnglatInBounds(lnglat: {
lng: number;
lat: number;
}): boolean;
updateVisible(value: boolean): void;
updateOptions(key: string, value: any): void;
destroy(): void;
}

View File

@ -1,128 +0,0 @@
import {
ILayer,
IPickingService,
IRendererService,
IInteractionTarget,
ITileRenderService,
ILayerService,
TYPES,
} from '@antv/l7-core';
import { EventEmitter } from 'eventemitter3';
export class TilePickService extends EventEmitter{
public isLastPicked: boolean = false;
private rendererService: IRendererService;
private pickingService: IPickingService;
private layerService: ILayerService;
private children: ILayer[];
private parent: ILayer;
private tileRenderService: ITileRenderService;
constructor(
parent: ILayer,
rendererService: IRendererService,
pickingService: IPickingService,
children: ILayer[],
tileRenderService: ITileRenderService
) {
super();
this.parent = parent;
this.rendererService = rendererService;
this.pickingService = pickingService;
this.children = children;
this.tileRenderService = tileRenderService;
const container = this.parent.getContainer();
this.layerService = container.get<ILayerService>(TYPES.ILayerService);
}
public pick(layers: ILayer[], target: IInteractionTarget) {
// Tip: 在进行拾取渲染的时候也需要先渲染一遍父组件然后再渲染子组件
// 如需要在 栅格瓦片存在 Mask 的时候发生的拾取,那么就需要先渲染父组件(渲染父组件的帧缓冲)
this.layerService.renderMask(this.parent.masks);
const isPicked = layers
.filter(
(layer) =>
this.parent.needPick(target.type) &&
layer.inited &&
layer.isVisible(),
)
.some((layer) => {
layer.hooks.beforePickingEncode.call();
if (layer.masks.length > 0) {
// 清除上一次的模版缓存
this.rendererService.clear({
stencil: 0,
depth: 1,
framebuffer: null,
});
layer.masks.map((m: ILayer) => {
m.hooks.beforeRender.call();
m.render();
m.hooks.afterRender.call();
});
}
layer.renderModels(true);
layer.hooks.afterPickingEncode.call();
const layerPicked = this.pickingService.pickFromPickingFBO(
layer,
target,
);
// RasterLayer 不参与拾取后的 shader 计算
if (layerPicked && this.parent.type !== 'RasterLayer') {
this.emit('pick', {
type: target.type,
pickedColors: this.pickingService.pickedColors,
layer,
});
this.pickingService.pickedTileLayers = [this.parent];
}
return layerPicked;
});
if (
this.parent.type !== 'RasterLayer' &&
!isPicked &&
this.isLastPicked &&
target.type !== 'click'
) {
// 只有上一次有被高亮选中,本次未选中的时候才需要清除选中状态
this.pickingService.pickedTileLayers = [];
this.emit('unpick', {});
this.beforeHighlight([0, 0, 0]);
}
this.isLastPicked = isPicked;
return isPicked;
}
public clearPick() {
this.children
.filter((child) => child.inited && child.isVisible())
.map((layer) => {
layer.hooks.beforeSelect.call([0, 0, 0]);
});
this.pickingService.pickedTileLayers = [];
}
public beforeHighlight(pickedColors: any) {
this.children
.filter((child) => child.inited && child.isVisible())
.map((child) => {
child.hooks.beforeHighlight.call(pickedColors);
});
}
public beforeSelect(pickedColors: any) {
this.children
.filter((child) => child.inited && child.isVisible())
.map((layer) => {
layer.hooks.beforeSelect.call(pickedColors);
});
}
public destroy(): void {
this.removeAllListeners();
}
}

View File

@ -1,89 +0,0 @@
import {
ILayer,
IMapService,
IRendererService,
ISubLayerInitOptions,
IBaseTileLayerManager,
} from '@antv/l7-core';
import { Base } from './base';
import { getLayerShape, getMaskValue } from '../utils';
export class BaseMapTileLayerManager extends Base implements IBaseTileLayerManager {
// only support vector layer
constructor(
parent: ILayer,
mapService: IMapService,
rendererService: IRendererService,
) {
super();
this.parent = parent;
this.children = parent.layerChildren;
this.mapService = mapService;
this.rendererService = rendererService;
this.setSubLayerInitOption();
this.initTileFactory();
}
public render(): void {
this.children
.filter((layer) => layer.inited)
.filter((layer) => layer.isVisible())
.map(async (layer) => {
if (layer.masks.length > 0) {
// 清除上一次的模版缓存
this.rendererService.clear({
stencil: 0,
depth: 1,
framebuffer: null,
});
layer.masks.map((m: ILayer) => {
m.render();
});
}
layer.render();
});
}
private setSubLayerInitOption() {
const {
zIndex = 0,
opacity = 1,
mask = false,
stroke = '#fff',
strokeWidth = 0,
strokeOpacity = 1,
workerEnabled = false,
sourceLayer,
} = this.parent.getLayerConfig() as ISubLayerInitOptions;
const source = this.parent.getSource();
const parentParserType = source.getParserType();
const colorAttribute = this.parent.getAttribute('color');
const basemapColor = (colorAttribute?.scale?.field || '#fff') as string;
const sizeAttribute = this.parent.getAttribute('size');
const basemapSize = (sizeAttribute?.scale?.field || 1) as number;
const layerShape = getLayerShape(this.parent.type, this.parent);
this.initOptions = {
usage: 'basemap',
visible: true,
layerType: this.parent.type,
shape: layerShape,
zIndex,
opacity,
sourceLayer: this.getSourceLayer(parentParserType, sourceLayer),
basemapColor,
basemapSize,
mask: getMaskValue(this.parent.type, mask),
stroke,
strokeWidth,
strokeOpacity,
// worker
workerEnabled,
};
}
}

View File

@ -1,31 +0,0 @@
import { ILayer, IRendererService, ITileRenderService } from '@antv/l7-core';
/**
*
*/
export class TileRenderService implements ITileRenderService{
private rendererService: IRendererService;
constructor(rendererService: IRendererService) {
this.rendererService = rendererService;
}
public render(layers: ILayer[]) {
layers
.filter((layer) => layer.inited)
.filter((layer) => layer.isVisible())
.map(async (layer) => {
if (layer.masks.length > 0) {
// 清除上一次的模版缓存
this.rendererService.clear({
stencil: 0,
depth: 1,
framebuffer: null,
});
layer.masks.map(async (m: ILayer) => {
m.render();
});
}
layer.render();
});
}
}

View File

@ -1,4 +1,4 @@
import { ILayerService, ITile } from '@antv/l7-core';
import { ILayerService, ITile, ITilePickService } from '@antv/l7-core';
import { TileLayerService } from './TileLayerService';
import { IInteractionTarget } from '@antv/l7-core';
export interface ITilePickServiceOptions {
@ -8,7 +8,7 @@ export interface ITilePickServiceOptions {
const SELECT = 'select';
const ACTIVE = 'active';
export class TilePickService {
export class TilePickService implements ITilePickService{
private layerService: ILayerService;
private tileLayerService: TileLayerService;
private tilePickID = new Map();

View File

@ -1,81 +0,0 @@
import { ILayer, IScaleValue, ISubLayerInitOptions } from '@antv/l7-core';
import EventEmitter from 'eventemitter3';
import { isEqual } from 'lodash';
export interface ITileStyleService {
setConfig(key: string, value: any): void;
checkConfig(layer: ILayer): void;
on(event: string, fn: (...args: any[]) => void): void;
getAttributeScale(layer: ILayer, name: string): IScaleValue;
}
export class TileStyleService extends EventEmitter {
public cacheConfig: Map<string, any>;
public checkConfigList: string[] = [];
constructor() {
super();
this.cacheConfig = new Map();
}
public setConfig(key: string, value: any) {
if (!this.checkConfigList.includes(key)) {
this.checkConfigList.push(key);
}
this.cacheConfig.set(key, value);
}
public removeConfig(key: string) {
const configIndex = this.checkConfigList.indexOf(key);
if (configIndex > -1) {
this.cacheConfig.delete(key);
this.checkConfigList.splice(configIndex, 1);
}
}
public checkConfig(layer: ILayer) {
if (!layer.inited) {
return;
}
const layerConfig = layer.getLayerConfig() as ISubLayerInitOptions;
const updateConfigs: string[] = [];
this.checkConfigList.map((key) => {
const cacheConfig = this.cacheConfig.get(key);
let currentConfig;
if (['color', 'size', 'shape'].includes(key)) {
currentConfig = layer.getAttribute(key)?.scale;
} else {
if (!(key in layerConfig)) {
return;
}
// @ts-ignore
currentConfig = layerConfig[key];
}
if (!isEqual(cacheConfig, currentConfig)) {
updateConfigs.push(key);
this.setConfig(key, currentConfig);
}
});
if (updateConfigs.length > 0) {
console.warn('tile config cache update!', updateConfigs);
this.emit('updateConfig', updateConfigs);
}
}
public getAttributeScale(layer: ILayer, name: string): IScaleValue {
const attribute = layer.getAttribute(name);
const scaleValue: IScaleValue = {
field: undefined,
values: undefined,
callback: undefined,
};
if (attribute && attribute.scale) {
const { field, values, callback } = attribute.scale;
scaleValue.field = field;
scaleValue.values = values;
scaleValue.callback = callback;
}
return scaleValue;
}
}

View File

@ -1,58 +1,20 @@
/**
*
*/
export const Attributes = ['size', 'color', 'shape'];
const common = [
'opacity',
'zIndex',
]
const rasterLayer = [
'mask',
'rampColors',
'domain',
'clampHigh',
'clampLow',
'pixelConstant',
'pixelConstantR',
'pixelConstantG',
'pixelConstantB',
'pixelConstantRGB',
...common
]
const pointLayer = [
'stroke',
'strokeWidth',
'strokeOpacity',
'color',
// TileLayer 需要代理的子图层的方法
// 一般都是在 BaseLayer 上的方法
export const ProxyFuncs = [
/**
* 1.
* 2. model
*/
'shape',
'color',
'size',
...common
'style',
'animate',
'filter',
'rotate',
'scale',
'setBlend',
'setSelect',
'setActive',
]
const lineLayer = [
'stroke',
'strokeWidth',
'strokeOpacity',
'color',
'shape',
'size',
...common
]
const polygonLayer = [
'color',
'shape',
...common
]
export type IStyles = 'PointLayer'| 'LineLayer' | 'PolygonLayer' | 'RasterLayer' | 'MaskLayer' | 'TileDebugLayer';
export const styles = {
'PointLayer': pointLayer,
'LineLayer': lineLayer,
'PolygonLayer': polygonLayer,
'RasterLayer': rasterLayer,
'MaskLayer': [],
'TileDebugLayer': [],
}

View File

@ -1,60 +0,0 @@
import { ILayer } from "@antv/l7-core";
const ProxyFuncs = [
/**
* 1.
* 2. model
*/
'shape',
'color',
'size',
'style',
'filter',
'rotate',
'scale',
'setBlend',
'setSelect',
'setActive',
]
export class LayerStyleProxy {
getLayers(layer: ILayer){
return layer.tileLayer.getLayers();
}
getTiles(layer: ILayer) {
return layer.tileLayer.tileLayerService.getTiles();
}
proxy(parent: ILayer) {
ProxyFuncs.forEach(func => {
// @ts-ignore
const oldStyleFunc = parent[func].bind(parent);
// @ts-ignore
parent[func] = (...args: any) => {
oldStyleFunc(...args);
this.getLayers(parent).map(child =>{
// @ts-ignore
child[func](...args);
})
// Tip: 目前在更新 RasterData 的 colorTexture 的时候需要额外优化
if(func === 'style') {
this.getTiles(parent).forEach(tile => tile.styleUpdate(...args));
}
return parent;
}
})
}
}
// const oldStyleFunc = parent.color.bind(parent);
// parent.color = (...args) => {
// oldStyleFunc(...args);
// getLayers(parent).map(child =>{
// child.color(...args);
// })
// return parent;
// }

View File

@ -1,27 +1,4 @@
import {
ILayer,
IRendererService,
} from '@antv/l7-core';
import { generateColorRamp, IColorRamp } from '@antv/l7-utils';
export function updateTexture(config: IColorRamp, layers: ILayer[], rendererService: IRendererService) {
const { createTexture2D } = rendererService;
const imageData = generateColorRamp(config) as ImageData;
const texture = createTexture2D({
data: imageData.data,
width: imageData.width,
height: imageData.height,
flipY: false,
});
layers.map(layer => {
layer.updateLayerConfig({
colorTexture: texture
});
})
return texture;
}
import { ILayer } from '@antv/l7-core';
export function updateLayersConfig(layers: ILayer[], key: string, value: any) {
layers.map((layer) => {
@ -38,39 +15,3 @@ export function updateLayersConfig(layers: ILayer[], key: string, value: any) {
});
}
export function getDefaultStyleAttributeField(layer: ILayer, type: string, style: string) {
switch (style) {
case 'size':
return 1;
case 'color':
return '#fff';
case 'shape':
return getLayerShape(type, layer);
default:
return '';
}
}
export function getLayerShape(layerType: string, layer: ILayer) {
const layerShape = layer.getAttribute('shape');
if (layerShape && layerShape.scale?.field) {
if (layerShape.scale?.values === 'text') {
return [layerShape.scale.field, layerShape.scale.values] as string[];
}
return layerShape.scale.field as string;
}
switch (layerType) {
case 'PolygonLayer':
return 'fill';
case 'LineLayer':
return 'tileline';
case 'PointLayer':
return 'circle';
case 'RasterLayer':
return 'image';
default:
return '';
}
}

View File

@ -13,11 +13,11 @@ import { TileLayerService } from '../service/TileLayerService';
import { TilePickService } from '../service/TilePickService';
import { debounce } from 'lodash';
import { getTileFactory } from '../tileFactory';
import { LayerStyleProxy } from '../style/style';
import { ProxyFuncs } from '../style/constants';
export default class BaseTileLayer {
private parent: ILayer;
private layerStyleProxy: LayerStyleProxy;
public tileLayerService: TileLayerService;
protected mapService: IMapService;
protected layerService: ILayerService;
@ -34,7 +34,6 @@ export default class BaseTileLayer {
constructor(parent: ILayer) {
this.parent = parent;
this.layerStyleProxy = new LayerStyleProxy();
const container = this.parent.getContainer();
this.rendererService = container.get<IRendererService>(
TYPES.IRendererService,
@ -59,7 +58,7 @@ export default class BaseTileLayer {
// 重置
this.parent.setLayerPickService(this.tilePickService);
this.layerStyleProxy.proxy(this.parent);
this.proxy(parent);
this.initTileSetManager();
@ -241,4 +240,30 @@ export default class BaseTileLayer {
}
/**
* TileLayer
* @param parent
*/
private proxy(parent: ILayer) {
ProxyFuncs.forEach(func => {
// @ts-ignore
const oldStyleFunc = parent[func].bind(parent);
// @ts-ignore
parent[func] = (...args: any) => {
oldStyleFunc(...args);
this.getLayers().map(child =>{
// @ts-ignore
child[func](...args);
})
// Tip: 目前在更新 RasterData 的 colorTexture 的时候需要额外优化
if(func === 'style') {
this.getTiles().forEach(tile => tile.styleUpdate(...args));
}
return parent;
}
})
}
}

View File

@ -1,6 +1,5 @@
import { createLayerContainer, ILayer } from '@antv/l7-core';
import { ILayer } from '@antv/l7-core';
import { DOM } from '@antv/l7-utils';
import { Container } from 'inversify';
export const tileVectorParser = ['mvt', 'geojsonvt', 'testTile'];
@ -14,53 +13,6 @@ export function isTileGroup(layer: ILayer) {
return tileVectorParser.includes(source.parser.type);
}
export function registerLayers(parentLayer: ILayer, layers: ILayer[]) {
layers.map((layer) => {
const container = createLayerContainer(
parentLayer.sceneContainer as Container,
);
layer.setContainer(container, parentLayer.sceneContainer as Container);
layer.init();
});
}
export function getLayerShape(layerType: string, layer: ILayer) {
const layerShape = layer.getAttribute('shape');
if (layerShape && layerShape.scale?.field) {
if (layerShape.scale?.values === 'text') {
return [layerShape.scale.field, layerShape.scale.values] as string[];
}
return layerShape.scale.field as string;
}
switch (layerType) {
case 'PolygonLayer':
return 'fill';
case 'LineLayer':
return 'tileline';
case 'PointLayer':
return 'circle';
case 'RasterLayer':
return 'image';
default:
return '';
}
}
export function getMaskValue(layerType: string, mask: boolean) {
switch (layerType) {
case 'PolygonLayer':
return true;
case 'LineLayer':
return true;
case 'PointLayer':
return false;
case 'RasterLayer':
return mask;
default:
return mask;
}
}
export function getContainerSize(container: HTMLCanvasElement | HTMLElement) {
if ((container as HTMLCanvasElement).getContext) {
return {