mirror of https://gitee.com/antv-l7/antv-l7
refactor(feature picking): feature picking
This commit is contained in:
parent
17f44bb2f7
commit
5936f9aa47
|
@ -104,6 +104,7 @@
|
|||
"stylelint-config-styled-components": "^0.1.1",
|
||||
"stylelint-processor-styled-components": "^1.3.2",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"tapable": "^1.1.3",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-prettier": "^1.15.0",
|
||||
|
|
|
@ -17,6 +17,7 @@ import { IControlService } from './services/component/IControlService';
|
|||
import { IGlobalConfigService } from './services/config/IConfigService';
|
||||
import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService';
|
||||
import { IInteractionService } from './services/interaction/IInteractionService';
|
||||
import { IPickingService } from './services/interaction/IPickingService';
|
||||
import { ILayerService } from './services/layer/ILayerService';
|
||||
import { IStyleAttributeService } from './services/layer/IStyleAttributeService';
|
||||
import { ILogService } from './services/log/ILogService';
|
||||
|
@ -33,6 +34,7 @@ import PopupService from './services/component/PopupService';
|
|||
import GlobalConfigService from './services/config/ConfigService';
|
||||
import CoordinateSystemService from './services/coordinate/CoordinateSystemService';
|
||||
import InteractionService from './services/interaction/InteractionService';
|
||||
import PickingService from './services/interaction/PickingService';
|
||||
import LayerService from './services/layer/LayerService';
|
||||
import StyleAttributeService from './services/layer/StyleAttributeService';
|
||||
import LogService from './services/log/LogService';
|
||||
|
@ -180,6 +182,10 @@ export function createSceneContainer() {
|
|||
.bind<IInteractionService>(TYPES.IInteractionService)
|
||||
.to(InteractionService)
|
||||
.inSingletonScope();
|
||||
sceneContainer
|
||||
.bind<IPickingService>(TYPES.IPickingService)
|
||||
.to(PickingService)
|
||||
.inSingletonScope();
|
||||
sceneContainer
|
||||
.bind<IControlService>(TYPES.IControlService)
|
||||
.to(ControlService)
|
||||
|
|
|
@ -55,7 +55,7 @@ const defaultLayerConfig: Partial<ILayerConfig> = {
|
|||
zIndex: 0,
|
||||
blend: 'normal',
|
||||
pickedFeatureID: -1,
|
||||
enableMultiPassRenderer: true,
|
||||
enableMultiPassRenderer: false,
|
||||
enablePicking: true,
|
||||
active: false,
|
||||
activeColor: '#2f54eb',
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export interface IPickingService {
|
||||
init(): void;
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
import { decodePickingColor, encodePickingColor } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
IMapService,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
} from '../../index';
|
||||
import { TYPES } from '../../types';
|
||||
import { ICameraService } from '../camera/ICameraService';
|
||||
import {
|
||||
IInteractionService,
|
||||
IInteractionTarget,
|
||||
InteractionEvent,
|
||||
} from '../interaction/IInteractionService';
|
||||
import { ILayer, ILayerService } from '../layer/ILayerService';
|
||||
import { ILngLat } from '../map/IMapService';
|
||||
import { gl } from '../renderer/gl';
|
||||
import { IFramebuffer } from '../renderer/IFramebuffer';
|
||||
import { IPickingService } from './IPickingService';
|
||||
|
||||
@injectable()
|
||||
export default class PickingService implements IPickingService {
|
||||
@inject(TYPES.IRendererService)
|
||||
private rendererService: IRendererService;
|
||||
|
||||
@inject(TYPES.IInteractionService)
|
||||
private interactionService: IInteractionService;
|
||||
|
||||
@inject(TYPES.ILayerService)
|
||||
private layerService: ILayerService;
|
||||
private pickingFBO: IFramebuffer;
|
||||
|
||||
private width: number = 0;
|
||||
|
||||
private height: number = 0;
|
||||
|
||||
private alreadyInPicking: boolean = false;
|
||||
|
||||
public init() {
|
||||
const {
|
||||
createTexture2D,
|
||||
createFramebuffer,
|
||||
getViewportSize,
|
||||
} = this.rendererService;
|
||||
const { width, height } = getViewportSize();
|
||||
// 创建 picking framebuffer,后续实时 resize
|
||||
this.pickingFBO = createFramebuffer({
|
||||
color: createTexture2D({
|
||||
width,
|
||||
height,
|
||||
wrapS: gl.CLAMP_TO_EDGE,
|
||||
wrapT: gl.CLAMP_TO_EDGE,
|
||||
}),
|
||||
});
|
||||
|
||||
// 监听 hover 事件
|
||||
this.interactionService.on(
|
||||
InteractionEvent.Hover,
|
||||
this.pickingAllLayer.bind(this),
|
||||
);
|
||||
}
|
||||
private async pickingAllLayer(target: IInteractionTarget) {
|
||||
if (this.alreadyInPicking || this.layerService.alreadyInRendering) {
|
||||
return;
|
||||
}
|
||||
this.alreadyInPicking = true;
|
||||
const layers = this.layerService.getLayers();
|
||||
layers
|
||||
.filter((layer) => layer.needPick())
|
||||
.reverse()
|
||||
.forEach(async (layer) => {
|
||||
await this.pickingLayer(layer, target); // 可以实现是否向下触发
|
||||
});
|
||||
this.layerService.renderLayers();
|
||||
this.alreadyInPicking = false;
|
||||
}
|
||||
|
||||
private async pickingLayer(layer: ILayer, target: IInteractionTarget) {
|
||||
const { getViewportSize, useFramebuffer, clear } = this.rendererService;
|
||||
const { width, height } = getViewportSize();
|
||||
|
||||
if (this.width !== width || this.height !== height) {
|
||||
this.pickingFBO.resize({ width, height });
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
useFramebuffer(this.pickingFBO, () => {
|
||||
clear({
|
||||
framebuffer: this.pickingFBO,
|
||||
color: [0, 0, 0, 0],
|
||||
stencil: 0,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
layer.hooks.beforePickingEncode.call();
|
||||
layer.renderModels();
|
||||
layer.hooks.afterPickingEncode.call();
|
||||
this.pickFromPickingFBO(layer, target);
|
||||
});
|
||||
}
|
||||
|
||||
private pickFromPickingFBO = (
|
||||
layer: ILayer,
|
||||
{ x, y, lngLat, type }: IInteractionTarget,
|
||||
) => {
|
||||
const {
|
||||
getViewportSize,
|
||||
readPixels,
|
||||
useFramebuffer,
|
||||
} = this.rendererService;
|
||||
const { width, height } = getViewportSize();
|
||||
|
||||
const { enableHighlight, enableSelect } = layer.getLayerConfig();
|
||||
|
||||
const xInDevicePixel = x * window.devicePixelRatio;
|
||||
const yInDevicePixel = y * window.devicePixelRatio;
|
||||
if (
|
||||
xInDevicePixel > width ||
|
||||
xInDevicePixel < 0 ||
|
||||
yInDevicePixel > height ||
|
||||
yInDevicePixel < 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let pickedColors: Uint8Array | undefined;
|
||||
|
||||
pickedColors = readPixels({
|
||||
x: Math.round(xInDevicePixel),
|
||||
// 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴
|
||||
y: Math.round(height - (y + 1) * window.devicePixelRatio),
|
||||
width: 1,
|
||||
height: 1,
|
||||
data: new Uint8Array(1 * 1 * 4),
|
||||
framebuffer: this.pickingFBO,
|
||||
});
|
||||
|
||||
if (
|
||||
pickedColors[0] !== 0 ||
|
||||
pickedColors[1] !== 0 ||
|
||||
pickedColors[2] !== 0
|
||||
) {
|
||||
const pickedFeatureIdx = decodePickingColor(pickedColors);
|
||||
const rawFeature = layer.getSource().getFeatureById(pickedFeatureIdx);
|
||||
const target = {
|
||||
x,
|
||||
y,
|
||||
type,
|
||||
lngLat,
|
||||
featureId: pickedFeatureIdx,
|
||||
feature: rawFeature,
|
||||
};
|
||||
if (!rawFeature) {
|
||||
// this.logger.error(
|
||||
// '未找到颜色编码解码后的原始 feature,请检查 fragment shader 中末尾是否添加了 `gl_FragColor = filterColor(gl_FragColor);`',
|
||||
// );
|
||||
} else {
|
||||
// trigger onHover/Click callback on layer
|
||||
layer.setCurrentPickId(pickedFeatureIdx);
|
||||
this.triggerHoverOnLayer(layer, target);
|
||||
}
|
||||
} else {
|
||||
const target = {
|
||||
x,
|
||||
y,
|
||||
lngLat,
|
||||
type: layer.getCurrentPickId() === null ? 'un' + type : 'mouseout',
|
||||
featureId: null,
|
||||
feature: null,
|
||||
};
|
||||
this.triggerHoverOnLayer(layer, {
|
||||
...target,
|
||||
type: 'unpick',
|
||||
});
|
||||
this.triggerHoverOnLayer(layer, target);
|
||||
layer.setCurrentPickId(null);
|
||||
}
|
||||
|
||||
if (enableHighlight) {
|
||||
this.highlightPickedFeature(layer, pickedColors);
|
||||
}
|
||||
if (
|
||||
enableSelect &&
|
||||
type === 'click' &&
|
||||
pickedColors?.toString() !== [0, 0, 0, 0].toString()
|
||||
) {
|
||||
this.selectFeature(layer, pickedColors);
|
||||
}
|
||||
};
|
||||
private triggerHoverOnLayer(
|
||||
layer: ILayer,
|
||||
target: {
|
||||
x: number;
|
||||
y: number;
|
||||
type: string;
|
||||
lngLat: ILngLat;
|
||||
feature: unknown;
|
||||
featureId: number | null;
|
||||
},
|
||||
) {
|
||||
layer.emit(target.type, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* highlight 如果直接修改选中 feature 的 buffer,存在两个问题:
|
||||
* 1. 鼠标移走时无法恢复
|
||||
* 2. 无法实现高亮颜色与原始原色的 alpha 混合
|
||||
* 因此高亮还是放在 shader 中做比较好
|
||||
* @example
|
||||
* this.layer.color('name', ['#000000'], {
|
||||
* featureRange: {
|
||||
* startIndex: pickedFeatureIdx,
|
||||
* endIndex: pickedFeatureIdx + 1,
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
private highlightPickedFeature(
|
||||
layer: ILayer,
|
||||
pickedColors: Uint8Array | undefined,
|
||||
) {
|
||||
const [r, g, b] = pickedColors;
|
||||
layer.hooks.beforeHighlight.call([r, g, b]);
|
||||
this.layerService.renderLayers();
|
||||
}
|
||||
|
||||
private selectFeature(layer: ILayer, pickedColors: Uint8Array | undefined) {
|
||||
const [r, g, b] = pickedColors;
|
||||
layer.hooks.beforeSelect.call([r, g, b]);
|
||||
this.layerService.renderLayers();
|
||||
}
|
||||
|
||||
private selectFeatureHandle(
|
||||
layer: ILayer,
|
||||
{ featureId }: Partial<IInteractionTarget>,
|
||||
) {
|
||||
const pickedColors = encodePickingColor(featureId as number);
|
||||
this.selectFeature(layer, new Uint8Array(pickedColors));
|
||||
}
|
||||
|
||||
private highlightFeatureHandle(
|
||||
layer: ILayer,
|
||||
{ featureId }: Partial<IInteractionTarget>,
|
||||
) {
|
||||
const pickedColors = encodePickingColor(featureId as number);
|
||||
this.highlightPickedFeature(layer, new Uint8Array(pickedColors));
|
||||
}
|
||||
}
|
|
@ -111,6 +111,7 @@ export interface ILayer {
|
|||
setCurrentPickId(id: number | null): void;
|
||||
getCurrentPickId(): number | null;
|
||||
prepareBuildModel(): void;
|
||||
renderModels(): void;
|
||||
buildModels(): void;
|
||||
buildLayerModel(
|
||||
options: ILayerModelInitializationOptions &
|
||||
|
@ -263,6 +264,7 @@ export interface ILayerConfig {
|
|||
*/
|
||||
export interface ILayerService {
|
||||
clock: Clock;
|
||||
alreadyInRendering: boolean;
|
||||
add(layer: ILayer): void;
|
||||
initLayers(): void;
|
||||
startAnimate(): void;
|
||||
|
|
|
@ -10,6 +10,8 @@ import { ILayerModel, ILayerService } from './ILayerService';
|
|||
export default class LayerService implements ILayerService {
|
||||
public clock = new Clock();
|
||||
|
||||
public alreadyInRendering: boolean = false;
|
||||
|
||||
private layers: ILayer[] = [];
|
||||
|
||||
private layerRenderID: number;
|
||||
|
@ -18,8 +20,6 @@ export default class LayerService implements ILayerService {
|
|||
|
||||
private animateInstanceCount: number = 0;
|
||||
|
||||
private alreadyInRendering: boolean = false;
|
||||
|
||||
@inject(TYPES.IRendererService)
|
||||
private readonly renderService: IRendererService;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import Probe, { Log } from 'probe.gl';
|
|||
import { ILogService } from './ILogService';
|
||||
const Logger = new Log({ id: 'L7' }).enable(true);
|
||||
// // 只输出 debug 级别以上的日志信息
|
||||
Logger.priority = 2;
|
||||
Logger.priority = 5;
|
||||
|
||||
@injectable()
|
||||
export default class LogService implements ILogService {
|
||||
|
|
|
@ -38,6 +38,10 @@ export default class MultiPassRenderer implements IMultiPassRenderer {
|
|||
private layer: ILayer;
|
||||
private renderFlag: boolean;
|
||||
|
||||
private width: number = 0;
|
||||
|
||||
private height: number = 0;
|
||||
|
||||
public setLayer(layer: ILayer) {
|
||||
this.layer = layer;
|
||||
}
|
||||
|
@ -58,11 +62,16 @@ export default class MultiPassRenderer implements IMultiPassRenderer {
|
|||
for (const pass of this.passes) {
|
||||
await pass.render(this.layer);
|
||||
}
|
||||
await this.postProcessor.render(this.layer);
|
||||
this.layer.renderModels();
|
||||
// await this.postProcessor.render(this.layer);
|
||||
}
|
||||
|
||||
public resize(width: number, height: number) {
|
||||
this.postProcessor.resize(width, height);
|
||||
if (this.width !== width || this.height !== height) {
|
||||
this.postProcessor.resize(width, height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
public add<T>(pass: IPass<T>, config?: Partial<T>) {
|
||||
|
|
|
@ -60,7 +60,6 @@ export default class PixelPickingPass<
|
|||
getViewportSize,
|
||||
} = this.rendererService;
|
||||
const { width, height } = getViewportSize();
|
||||
|
||||
// 创建 picking framebuffer,后续实时 resize
|
||||
this.pickingFBO = createFramebuffer({
|
||||
color: createTexture2D({
|
||||
|
|
|
@ -14,4 +14,9 @@ export interface ISceneService {
|
|||
destroy(): void;
|
||||
}
|
||||
// scene 事件
|
||||
export const SceneEventList = ['loaded', 'maploaded', 'resize', 'destroy'];
|
||||
export const SceneEventList: string[] = [
|
||||
'loaded',
|
||||
'maploaded',
|
||||
'resize',
|
||||
'destroy',
|
||||
];
|
||||
|
|
|
@ -14,6 +14,7 @@ import { IPopupService } from '../component/IPopupService';
|
|||
import { IGlobalConfigService, ISceneConfig } from '../config/IConfigService';
|
||||
import { ICoordinateSystemService } from '../coordinate/ICoordinateSystemService';
|
||||
import { IInteractionService } from '../interaction/IInteractionService';
|
||||
import { IPickingService } from '../interaction/IPickingService';
|
||||
import { ILayer, ILayerService } from '../layer/ILayerService';
|
||||
import { ILogService } from '../log/ILogService';
|
||||
import { IMapCamera, IMapService } from '../map/IMapService';
|
||||
|
@ -64,6 +65,9 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
@inject(TYPES.IInteractionService)
|
||||
private readonly interactionService: IInteractionService;
|
||||
|
||||
@inject(TYPES.IPickingService)
|
||||
private readonly pickingService: IPickingService;
|
||||
|
||||
@inject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModuleService: IShaderModuleService;
|
||||
|
||||
|
@ -150,6 +154,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.popupService.initPopup();
|
||||
// 地图初始化之后 才能初始化 container 上的交互
|
||||
this.interactionService.init();
|
||||
|
||||
this.logger.debug(`map ${this.id} loaded`);
|
||||
});
|
||||
|
||||
|
@ -174,6 +179,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
} else {
|
||||
this.logger.error('容器 id 不存在');
|
||||
}
|
||||
this.pickingService.init();
|
||||
|
||||
this.logger.debug(`scene ${this.id} renderer loaded`);
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ const TYPES = {
|
|||
IIconService: Symbol.for('IIconService'),
|
||||
IFontService: Symbol.for('IFontService'),
|
||||
IInteractionService: Symbol.for('IInteractionService'),
|
||||
IPickingService: Symbol.for('IPickingService'),
|
||||
IControlService: Symbol.for('IControlService'),
|
||||
IStyleAttributeService: Symbol.for('IStyleAttributeService'),
|
||||
ILayer: Symbol.for('ILayer'),
|
||||
|
|
|
@ -170,6 +170,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
private aniamateStatus: boolean = false;
|
||||
|
||||
// private pickingPassRender: IPass<'pixelPicking'>;
|
||||
|
||||
constructor(config: Partial<ILayerConfig & ChildLayerStyleOptions> = {}) {
|
||||
super();
|
||||
this.name = config.name || this.id;
|
||||
|
@ -228,7 +230,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
// 设置配置项
|
||||
const sceneId = this.container.get<string>(TYPES.SceneID);
|
||||
// 初始化图层配置项
|
||||
this.configService.setLayerConfig(sceneId, this.id, {});
|
||||
const { enableMultiPassRenderer = false } = this.rawConfig;
|
||||
this.configService.setLayerConfig(sceneId, this.id, {
|
||||
enableMultiPassRenderer,
|
||||
});
|
||||
|
||||
// 全局容器服务
|
||||
|
||||
|
@ -297,6 +302,9 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
// 触发 init 生命周期插件
|
||||
this.hooks.init.call();
|
||||
|
||||
// this.pickingPassRender = this.normalPassFactory('pixelPicking');
|
||||
// this.pickingPassRender.init(this);
|
||||
this.hooks.afterInit.call();
|
||||
|
||||
// 触发初始化完成事件;
|
||||
|
@ -465,11 +473,21 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
return this;
|
||||
}
|
||||
public render(): ILayer {
|
||||
if (this.multiPassRenderer && this.multiPassRenderer.getRenderFlag()) {
|
||||
this.multiPassRenderer.render();
|
||||
} else {
|
||||
this.renderModels();
|
||||
}
|
||||
// if (
|
||||
// this.needPick() &&
|
||||
// this.multiPassRenderer &&
|
||||
// this.multiPassRenderer.getRenderFlag()
|
||||
// ) {
|
||||
// this.multiPassRenderer.render();
|
||||
// } else if (this.needPick() && this.multiPassRenderer) {
|
||||
// this.renderModels();
|
||||
// } else {
|
||||
// this.renderModels();
|
||||
// }
|
||||
|
||||
this.renderModels();
|
||||
// this.multiPassRenderer.render();
|
||||
// this.renderModels();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -806,11 +824,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
protected getConfigSchema() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
public renderModels() {
|
||||
if (this.layerModelNeedUpdate) {
|
||||
this.models = this.layerModel.buildModels();
|
||||
this.hooks.beforeRender.call();
|
||||
|
@ -824,6 +838,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
return this;
|
||||
}
|
||||
|
||||
protected getConfigSchema() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
protected getModelType(): unknown {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
|
|
@ -12,19 +12,7 @@ export default class HeatMapLayer extends BaseLayer<IHeatMapLayerStyleOptions> {
|
|||
this.layerModel = new HeatMapModels[shape](this);
|
||||
this.models = this.layerModel.buildModels();
|
||||
}
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
public renderModels() {
|
||||
const shape = this.getModelType();
|
||||
if (shape === 'heatmap') {
|
||||
// if (this.layerModelNeedUpdate) {
|
||||
|
@ -49,6 +37,18 @@ export default class HeatMapLayer extends BaseLayer<IHeatMapLayerStyleOptions> {
|
|||
);
|
||||
return this;
|
||||
}
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected getModelType(): HeatMapModelType {
|
||||
const shapeAttribute = this.styleAttributeService.getLayerStyleAttribute(
|
||||
'shape',
|
||||
|
|
|
@ -76,11 +76,11 @@ export default class MultiPassRendererPlugin implements ILayerPlugin {
|
|||
});
|
||||
|
||||
layer.hooks.beforeRender.tap('MultiPassRendererPlugin', () => {
|
||||
if (this.enabled) {
|
||||
// 渲染前根据 viewport 调整 FBO size
|
||||
const { width, height } = rendererService.getViewportSize();
|
||||
layer.multiPassRenderer.resize(width, height);
|
||||
}
|
||||
// if (this.enabled) {
|
||||
// // 渲染前根据 viewport 调整 FBO size
|
||||
// const { width, height } = rendererService.getViewportSize();
|
||||
// layer.multiPassRenderer.resize(width, height);
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -103,26 +103,26 @@ export default class MultiPassRendererPlugin implements ILayerPlugin {
|
|||
}
|
||||
|
||||
// use TAA pass if enabled instead of render pass
|
||||
if (enableTAA) {
|
||||
multiPassRenderer.add(normalPassFactory('taa'));
|
||||
} else {
|
||||
// render all layers in this pass
|
||||
multiPassRenderer.add(normalPassFactory('render'));
|
||||
}
|
||||
// if (enableTAA) {
|
||||
// multiPassRenderer.add(normalPassFactory('taa'));
|
||||
// } else {
|
||||
// // render all layers in this pass
|
||||
// multiPassRenderer.add(normalPassFactory('render'));
|
||||
// }
|
||||
|
||||
// post processing
|
||||
normalizePasses(passes).forEach(
|
||||
(pass: [string, { [key: string]: unknown }]) => {
|
||||
const [passName, initializationOptions] = pass;
|
||||
multiPassRenderer.add(
|
||||
postProcessingPassFactory(passName),
|
||||
initializationOptions,
|
||||
);
|
||||
},
|
||||
);
|
||||
// normalizePasses(passes).forEach(
|
||||
// (pass: [string, { [key: string]: unknown }]) => {
|
||||
// const [passName, initializationOptions] = pass;
|
||||
// multiPassRenderer.add(
|
||||
// postProcessingPassFactory(passName),
|
||||
// initializationOptions,
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
|
||||
// 末尾为固定的 CopyPass
|
||||
multiPassRenderer.add(postProcessingPassFactory('copy'));
|
||||
// multiPassRenderer.add(postProcessingPassFactory('copy'));
|
||||
|
||||
return multiPassRenderer;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ interface IPointLayerStyleOptions {
|
|||
opacity: number;
|
||||
strokeWidth: number;
|
||||
stroke: string;
|
||||
strokeOpacity: number;
|
||||
}
|
||||
export default class FillModel extends BaseModel {
|
||||
public getUninforms(): IModelUniform {
|
||||
|
@ -25,11 +26,13 @@ export default class FillModel extends BaseModel {
|
|||
opacity = 1,
|
||||
stroke = 'rgb(0,0,0,0)',
|
||||
strokeWidth = 1,
|
||||
strokeOpacity = 1,
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
return {
|
||||
u_opacity: opacity,
|
||||
u_stroke_width: strokeWidth,
|
||||
u_stroke_color: rgb2arr(stroke),
|
||||
u_stroke_opacity: strokeOpacity,
|
||||
};
|
||||
}
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
|
|
|
@ -57,19 +57,7 @@ export default class RasterLayer extends BaseLayer<IRasterLayerStyleOptions> {
|
|||
});
|
||||
this.models = [this.buildRasterModel()];
|
||||
}
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
public renderModels() {
|
||||
const { opacity, heightRatio = 10 } = this.getLayerConfig();
|
||||
const parserDataItem = this.getSource().data.dataArray[0];
|
||||
const { coordinates, width, height, min, max } = parserDataItem;
|
||||
|
@ -91,6 +79,18 @@ export default class RasterLayer extends BaseLayer<IRasterLayerStyleOptions> {
|
|||
|
||||
return this;
|
||||
}
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private buildRasterModel() {
|
||||
const source = this.getSource();
|
||||
const sourceFeature = source.data.dataArray[0];
|
||||
|
|
|
@ -57,20 +57,7 @@ export default class Raster2dLayer extends BaseLayer<IRasterLayerStyleOptions> {
|
|||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
public renderModels() {
|
||||
const { opacity } = this.getLayerConfig();
|
||||
const parserDataItem = this.getSource().data.dataArray[0];
|
||||
const { min, max } = parserDataItem;
|
||||
|
@ -91,6 +78,18 @@ export default class Raster2dLayer extends BaseLayer<IRasterLayerStyleOptions> {
|
|||
return this;
|
||||
}
|
||||
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private registerBuiltinAttributes() {
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
|
|
|
@ -240,6 +240,7 @@ export default class AMapService
|
|||
if (mapInstance) {
|
||||
this.map = mapInstance as AMap.Map & IAMapInstance;
|
||||
this.$mapContainer = this.map.getContainer();
|
||||
this.removeLogoControl();
|
||||
setTimeout(() => {
|
||||
this.map.on('camerachange', this.handleCameraChanged);
|
||||
resolve();
|
||||
|
@ -255,6 +256,7 @@ export default class AMapService
|
|||
viewMode: '3D',
|
||||
...rest,
|
||||
});
|
||||
this.removeLogoControl();
|
||||
// 监听地图相机事件
|
||||
map.on('camerachange', this.handleCameraChanged);
|
||||
// @ts-ignore
|
||||
|
@ -390,4 +392,12 @@ export default class AMapService
|
|||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
private removeLogoControl(): void {
|
||||
// @ts-ignore
|
||||
const logo = document.getElementsByClassName('amap-logo');
|
||||
if (logo) {
|
||||
logo[0].setAttribute('style', 'display: none !important');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { IMapConfig, Scene } from '@antv/l7';
|
||||
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
import GaodeMap from '@antv/l7-maps/lib/amap';
|
||||
import React, { createElement, createRef, useEffect, useState } from 'react';
|
||||
import SceneContext from './SceneContext';
|
||||
interface IMapSceneConig {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
map: IMapConfig;
|
||||
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>;
|
||||
}
|
||||
const AMapScene = React.memo((props: IMapSceneConig) => {
|
||||
const { style, className, map } = props;
|
||||
const container = createRef();
|
||||
const [scene, setScene] = useState();
|
||||
useEffect(() => {
|
||||
const sceneInstance = new Scene({
|
||||
id: container.current as HTMLDivElement,
|
||||
map: new GaodeMap(map),
|
||||
});
|
||||
sceneInstance.on('loaded', () => {
|
||||
setScene(sceneInstance);
|
||||
});
|
||||
return () => {
|
||||
sceneInstance.destroy();
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
scene.setMapStyle(map.style);
|
||||
}, [map.style]);
|
||||
|
||||
return (
|
||||
<SceneContext.Provider value={scene}>
|
||||
{createElement(
|
||||
'div',
|
||||
{
|
||||
ref: container,
|
||||
style,
|
||||
className,
|
||||
},
|
||||
scene && props.children,
|
||||
)}
|
||||
</SceneContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export default AMapScene;
|
|
@ -0,0 +1,39 @@
|
|||
import { IControl, Logo, PositionType, Scale, Scene, Zoom } from '@antv/l7';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSceneValue } from './SceneContext';
|
||||
interface IControlProps {
|
||||
type: 'scale' | 'zoom' | 'logo';
|
||||
position: PositionType;
|
||||
[key: string]: any;
|
||||
}
|
||||
export default React.memo(function MapControl(props: IControlProps) {
|
||||
const scene = (useSceneValue() as unknown) as Scene;
|
||||
const [, setControl] = useState();
|
||||
const { type, position } = props;
|
||||
useEffect(() => {
|
||||
let ctr: IControl;
|
||||
switch (type) {
|
||||
case 'scale':
|
||||
ctr = new Scale({
|
||||
position,
|
||||
});
|
||||
break;
|
||||
case 'zoom':
|
||||
ctr = new Zoom({
|
||||
position,
|
||||
});
|
||||
break;
|
||||
case 'logo':
|
||||
ctr = new Logo({
|
||||
position,
|
||||
});
|
||||
}
|
||||
setControl(ctr);
|
||||
scene.addControl(ctr);
|
||||
return () => {
|
||||
scene.removeControl(ctr);
|
||||
};
|
||||
}, [type, position]);
|
||||
|
||||
return null;
|
||||
});
|
|
@ -12,8 +12,11 @@ export default React.memo(function Chart(props: ISourceProps) {
|
|||
const { data, ...sourceOption } = source;
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
layer.source(data, sourceOption);
|
||||
}, []);
|
||||
if (!layer.inited) {
|
||||
layer.source(data, sourceOption);
|
||||
} else {
|
||||
layer.setData(data, sourceOption);
|
||||
}
|
||||
}, [data, sourceOption]);
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { IMapConfig, Scene } from '@antv/l7';
|
||||
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
import Mapbox from '@antv/l7-maps/lib/mapbox';
|
||||
import React, { createElement, createRef, useEffect, useState } from 'react';
|
||||
import SceneContext from './SceneContext';
|
||||
interface IMapSceneConig {
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
map: IMapConfig;
|
||||
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>;
|
||||
}
|
||||
const MapboxScene = React.memo((props: IMapSceneConig) => {
|
||||
const { style, className, map } = props;
|
||||
const container = createRef();
|
||||
const [scene, setScene] = useState();
|
||||
useEffect(() => {
|
||||
const sceneInstance = new Scene({
|
||||
id: container.current as HTMLDivElement,
|
||||
map: new Mapbox(map),
|
||||
});
|
||||
sceneInstance.on('loaded', () => {
|
||||
setScene(sceneInstance);
|
||||
});
|
||||
return () => {
|
||||
sceneInstance.destroy();
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
scene.setMapStyle(map.style);
|
||||
}, [map.style]);
|
||||
|
||||
return (
|
||||
<SceneContext.Provider value={scene}>
|
||||
{createElement(
|
||||
'div',
|
||||
{
|
||||
ref: container,
|
||||
style,
|
||||
className,
|
||||
},
|
||||
scene && props.children,
|
||||
)}
|
||||
</SceneContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
export default MapboxScene;
|
|
@ -23,6 +23,12 @@ const MapScene = React.memo((props: IMapSceneConig) => {
|
|||
sceneInstance.destroy();
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (!scene) {
|
||||
return;
|
||||
}
|
||||
scene.setMapStyle(style);
|
||||
}, [style]);
|
||||
|
||||
return (
|
||||
<SceneContext.Provider value={scene}>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
export * from './component/SceneContext';
|
||||
export { default as Scene } from './component/Scene';
|
||||
export { default as AMapScene } from './component/AMapScene';
|
||||
export { default as MapboxScene } from './component/MapboxScene';
|
||||
export * from './component/Layer';
|
||||
export { default as Control } from './component/Control';
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class ScaleComponent extends React.Component {
|
|||
})
|
||||
.size('point_count', [5, 10, 15, 20, 25])
|
||||
.animate(false)
|
||||
.select(true)
|
||||
.active(true)
|
||||
.color('yellow')
|
||||
.style({
|
||||
opacity: 0.5,
|
||||
|
|
|
@ -35,22 +35,24 @@ export default class PointImage extends React.Component {
|
|||
'02',
|
||||
'https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*o16fSIvcKdUAAAAAAAAAAABkARQnAQ',
|
||||
);
|
||||
|
||||
const imageLayer = new PointLayer({})
|
||||
.source(await response.json(), {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'longitude',
|
||||
y: 'latitude',
|
||||
},
|
||||
})
|
||||
.shape('name', ['00', '01', '02'])
|
||||
.active(true)
|
||||
.size(30);
|
||||
scene.addLayer(imageLayer);
|
||||
imageLayer.on('click', (e) => {
|
||||
console.log(e);
|
||||
});
|
||||
let i = 0;
|
||||
const data = await response.json();
|
||||
while (i < 50) {
|
||||
const imageLayer = new PointLayer()
|
||||
.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'longitude',
|
||||
y: 'latitude',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.color('red')
|
||||
.active(false)
|
||||
.size(20);
|
||||
scene.addLayer(imageLayer);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -22411,8 +22411,8 @@ table@^5.0.0, table@^5.2.3:
|
|||
|
||||
tapable@^1.0.0, tapable@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha1-ofzMBrWNth/XpF2i2kT186Pme6I=
|
||||
resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||
|
||||
tapable@^2.0.0-beta.8:
|
||||
version "2.0.0-beta.9"
|
||||
|
|
Loading…
Reference in New Issue