From 5936f9aa47b7c4fcac49f5dcd475337c985dba46 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Mon, 17 Feb 2020 22:47:33 +0800 Subject: [PATCH] refactor(feature picking): feature picking --- package.json | 1 + packages/core/src/inversify.config.ts | 6 + .../core/src/services/config/ConfigService.ts | 2 +- .../services/interaction/IPickingService.ts | 3 + .../services/interaction/PickingService.ts | 247 ++++++++++++++++++ .../core/src/services/layer/ILayerService.ts | 2 + .../core/src/services/layer/LayerService.ts | 4 +- packages/core/src/services/log/LogService.ts | 2 +- .../renderer/passes/MultiPassRenderer.ts | 13 +- .../renderer/passes/PixelPickingPass.ts | 1 - .../core/src/services/scene/ISceneService.ts | 7 +- .../core/src/services/scene/SceneService.ts | 6 + packages/core/src/types.ts | 1 + packages/layers/src/core/BaseLayer.ts | 40 ++- packages/layers/src/heatmap/index.ts | 26 +- .../src/plugins/MultiPassRendererPlugin.ts | 42 +-- packages/layers/src/point/models/fill.ts | 3 + packages/layers/src/raster/raster.ts | 26 +- packages/layers/src/raster/raster2d.ts | 27 +- packages/maps/src/amap/map.ts | 10 + packages/react/src/component/AMapScene.tsx | 51 ++++ packages/react/src/component/Control.tsx | 39 +++ .../src/component/LayerAttribute/Source.tsx | 9 +- packages/react/src/component/MapboxScene.tsx | 51 ++++ packages/react/src/component/Scene.tsx | 6 + packages/react/src/index.ts | 3 + stories/Components/components/Scale.tsx | 2 +- stories/Layers/components/PointImage.tsx | 34 +-- stories/Layers/components/layertest.tsx | 0 yarn.lock | 4 +- 30 files changed, 566 insertions(+), 102 deletions(-) create mode 100644 packages/core/src/services/interaction/IPickingService.ts create mode 100644 packages/core/src/services/interaction/PickingService.ts create mode 100644 packages/react/src/component/AMapScene.tsx create mode 100644 packages/react/src/component/Control.tsx create mode 100644 packages/react/src/component/MapboxScene.tsx create mode 100644 stories/Layers/components/layertest.tsx diff --git a/package.json b/package.json index 42f687a91f..a14beaa9ec 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/core/src/inversify.config.ts b/packages/core/src/inversify.config.ts index 6e97dea9f4..93a158f0cc 100644 --- a/packages/core/src/inversify.config.ts +++ b/packages/core/src/inversify.config.ts @@ -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(TYPES.IInteractionService) .to(InteractionService) .inSingletonScope(); + sceneContainer + .bind(TYPES.IPickingService) + .to(PickingService) + .inSingletonScope(); sceneContainer .bind(TYPES.IControlService) .to(ControlService) diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index cdbffd5c0e..9255466a11 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -55,7 +55,7 @@ const defaultLayerConfig: Partial = { zIndex: 0, blend: 'normal', pickedFeatureID: -1, - enableMultiPassRenderer: true, + enableMultiPassRenderer: false, enablePicking: true, active: false, activeColor: '#2f54eb', diff --git a/packages/core/src/services/interaction/IPickingService.ts b/packages/core/src/services/interaction/IPickingService.ts new file mode 100644 index 0000000000..c118cfc4ef --- /dev/null +++ b/packages/core/src/services/interaction/IPickingService.ts @@ -0,0 +1,3 @@ +export interface IPickingService { + init(): void; +} diff --git a/packages/core/src/services/interaction/PickingService.ts b/packages/core/src/services/interaction/PickingService.ts new file mode 100644 index 0000000000..551ad1ec0c --- /dev/null +++ b/packages/core/src/services/interaction/PickingService.ts @@ -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, + ) { + const pickedColors = encodePickingColor(featureId as number); + this.selectFeature(layer, new Uint8Array(pickedColors)); + } + + private highlightFeatureHandle( + layer: ILayer, + { featureId }: Partial, + ) { + const pickedColors = encodePickingColor(featureId as number); + this.highlightPickedFeature(layer, new Uint8Array(pickedColors)); + } +} diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 7638e2458c..758cec510a 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -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; diff --git a/packages/core/src/services/layer/LayerService.ts b/packages/core/src/services/layer/LayerService.ts index 1f2c46e9e3..746676bd50 100644 --- a/packages/core/src/services/layer/LayerService.ts +++ b/packages/core/src/services/layer/LayerService.ts @@ -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; diff --git a/packages/core/src/services/log/LogService.ts b/packages/core/src/services/log/LogService.ts index 07ce6ed403..4c8bc60ce0 100644 --- a/packages/core/src/services/log/LogService.ts +++ b/packages/core/src/services/log/LogService.ts @@ -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 { diff --git a/packages/core/src/services/renderer/passes/MultiPassRenderer.ts b/packages/core/src/services/renderer/passes/MultiPassRenderer.ts index 3a6f1e4bf4..43e75ad7af 100644 --- a/packages/core/src/services/renderer/passes/MultiPassRenderer.ts +++ b/packages/core/src/services/renderer/passes/MultiPassRenderer.ts @@ -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(pass: IPass, config?: Partial) { diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index 6f678412cb..bd93335ae4 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -60,7 +60,6 @@ export default class PixelPickingPass< getViewportSize, } = this.rendererService; const { width, height } = getViewportSize(); - // 创建 picking framebuffer,后续实时 resize this.pickingFBO = createFramebuffer({ color: createTexture2D({ diff --git a/packages/core/src/services/scene/ISceneService.ts b/packages/core/src/services/scene/ISceneService.ts index 03748bc587..88d247044a 100644 --- a/packages/core/src/services/scene/ISceneService.ts +++ b/packages/core/src/services/scene/ISceneService.ts @@ -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', +]; diff --git a/packages/core/src/services/scene/SceneService.ts b/packages/core/src/services/scene/SceneService.ts index e60adb1692..d026c724c2 100644 --- a/packages/core/src/services/scene/SceneService.ts +++ b/packages/core/src/services/scene/SceneService.ts @@ -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`); }); diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c2570b616f..e915b9b446 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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'), diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 0dae3f409d..43d63cce02 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -170,6 +170,8 @@ export default class BaseLayer extends EventEmitter private aniamateStatus: boolean = false; + // private pickingPassRender: IPass<'pixelPicking'>; + constructor(config: Partial = {}) { super(); this.name = config.name || this.id; @@ -228,7 +230,10 @@ export default class BaseLayer extends EventEmitter // 设置配置项 const sceneId = this.container.get(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 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 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 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 extends EventEmitter return this; } + protected getConfigSchema() { + throw new Error('Method not implemented.'); + } + protected getModelType(): unknown { throw new Error('Method not implemented.'); } diff --git a/packages/layers/src/heatmap/index.ts b/packages/layers/src/heatmap/index.ts index 86042d222d..13212b8380 100644 --- a/packages/layers/src/heatmap/index.ts +++ b/packages/layers/src/heatmap/index.ts @@ -12,19 +12,7 @@ export default class HeatMapLayer extends BaseLayer { 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 { ); return this; } + protected getConfigSchema() { + return { + properties: { + opacity: { + type: 'number', + minimum: 0, + maximum: 1, + }, + }, + }; + } + protected getModelType(): HeatMapModelType { const shapeAttribute = this.styleAttributeService.getLayerStyleAttribute( 'shape', diff --git a/packages/layers/src/plugins/MultiPassRendererPlugin.ts b/packages/layers/src/plugins/MultiPassRendererPlugin.ts index 2f8b9191e3..333bc8b712 100644 --- a/packages/layers/src/plugins/MultiPassRendererPlugin.ts +++ b/packages/layers/src/plugins/MultiPassRendererPlugin.ts @@ -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; } diff --git a/packages/layers/src/point/models/fill.ts b/packages/layers/src/point/models/fill.ts index 35aa64a9e5..33778bfb14 100644 --- a/packages/layers/src/point/models/fill.ts +++ b/packages/layers/src/point/models/fill.ts @@ -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 { diff --git a/packages/layers/src/raster/raster.ts b/packages/layers/src/raster/raster.ts index c91f142845..6d2fb75c92 100644 --- a/packages/layers/src/raster/raster.ts +++ b/packages/layers/src/raster/raster.ts @@ -57,19 +57,7 @@ export default class RasterLayer extends BaseLayer { }); 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 { 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]; diff --git a/packages/layers/src/raster/raster2d.ts b/packages/layers/src/raster/raster2d.ts index e7f26d8093..bb2074c54f 100644 --- a/packages/layers/src/raster/raster2d.ts +++ b/packages/layers/src/raster/raster2d.ts @@ -57,20 +57,7 @@ export default class Raster2dLayer extends BaseLayer { }), ]; } - - 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 { return this; } + protected getConfigSchema() { + return { + properties: { + opacity: { + type: 'number', + minimum: 0, + maximum: 1, + }, + }, + }; + } + private registerBuiltinAttributes() { // point layer size; this.styleAttributeService.registerStyleAttribute({ diff --git a/packages/maps/src/amap/map.ts b/packages/maps/src/amap/map.ts index 59b2af238f..e3523fd2bd 100644 --- a/packages/maps/src/amap/map.ts +++ b/packages/maps/src/amap/map.ts @@ -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'); + } + } } diff --git a/packages/react/src/component/AMapScene.tsx b/packages/react/src/component/AMapScene.tsx new file mode 100644 index 0000000000..4ba132c065 --- /dev/null +++ b/packages/react/src/component/AMapScene.tsx @@ -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; +} +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 ( + + {createElement( + 'div', + { + ref: container, + style, + className, + }, + scene && props.children, + )} + + ); +}); + +export default AMapScene; diff --git a/packages/react/src/component/Control.tsx b/packages/react/src/component/Control.tsx new file mode 100644 index 0000000000..776e402a12 --- /dev/null +++ b/packages/react/src/component/Control.tsx @@ -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; +}); diff --git a/packages/react/src/component/LayerAttribute/Source.tsx b/packages/react/src/component/LayerAttribute/Source.tsx index d86d8ef9fc..edccf83784 100644 --- a/packages/react/src/component/LayerAttribute/Source.tsx +++ b/packages/react/src/component/LayerAttribute/Source.tsx @@ -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; }); diff --git a/packages/react/src/component/MapboxScene.tsx b/packages/react/src/component/MapboxScene.tsx new file mode 100644 index 0000000000..b6b29e26b5 --- /dev/null +++ b/packages/react/src/component/MapboxScene.tsx @@ -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; +} +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 ( + + {createElement( + 'div', + { + ref: container, + style, + className, + }, + scene && props.children, + )} + + ); +}); + +export default MapboxScene; diff --git a/packages/react/src/component/Scene.tsx b/packages/react/src/component/Scene.tsx index 498c22bc01..3fe4d9eac6 100644 --- a/packages/react/src/component/Scene.tsx +++ b/packages/react/src/component/Scene.tsx @@ -23,6 +23,12 @@ const MapScene = React.memo((props: IMapSceneConig) => { sceneInstance.destroy(); }; }, []); + useEffect(() => { + if (!scene) { + return; + } + scene.setMapStyle(style); + }, [style]); return ( diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 7f6e51c563..e4d7c5e008 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -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'; diff --git a/stories/Components/components/Scale.tsx b/stories/Components/components/Scale.tsx index 6255ed124f..c474d43923 100644 --- a/stories/Components/components/Scale.tsx +++ b/stories/Components/components/Scale.tsx @@ -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, diff --git a/stories/Layers/components/PointImage.tsx b/stories/Layers/components/PointImage.tsx index 4af0192c04..4213f159ca 100644 --- a/stories/Layers/components/PointImage.tsx +++ b/stories/Layers/components/PointImage.tsx @@ -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() { diff --git a/stories/Layers/components/layertest.tsx b/stories/Layers/components/layertest.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/yarn.lock b/yarn.lock index cb6dd9d42d..c912d33789 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"