From 27a09a7a7a79b50598af22a0de18b062d60afcac Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Thu, 12 Dec 2019 00:35:15 +0800 Subject: [PATCH] feat(source render): source transfrom, layer event --- .gitignore | 1 + .../core/src/services/config/ConfigService.ts | 8 +- .../interaction/IInteractionService.ts | 4 +- .../interaction/InteractionService.ts | 17 ++- .../core/src/services/layer/ILayerService.ts | 36 +++++- .../renderer/passes/PixelPickingPass.ts | 28 ++++- .../src/services/source/ISourceService.ts | 1 + packages/layers/src/core/BaseLayer.ts | 103 ++++++++++++++---- packages/layers/src/heatmap/models/hexagon.ts | 46 ++++---- packages/layers/src/index.ts | 10 ++ .../layers/src/plugins/DataMappingPlugin.ts | 11 +- .../layers/src/plugins/LayerStylePlugin.ts | 30 +++++ .../layers/src/plugins/PixelPickingPlugin.ts | 22 ++-- packages/layers/src/utils/color.ts | 10 ++ packages/source/src/index.ts | 4 + packages/source/src/source.ts | 10 ++ packages/source/src/transform/filter.ts | 8 ++ packages/source/src/transform/map.ts | 9 ++ stories/Layers/Layers.stories.tsx | 2 - stories/Layers/components/HeatmapGrid.tsx | 85 --------------- stories/Picking/components/AdvancedAPI.tsx | 30 +++-- 21 files changed, 302 insertions(+), 173 deletions(-) create mode 100644 packages/layers/src/plugins/LayerStylePlugin.ts create mode 100644 packages/source/src/transform/filter.ts create mode 100644 packages/source/src/transform/map.ts delete mode 100644 stories/Layers/components/HeatmapGrid.tsx diff --git a/.gitignore b/.gitignore index 0b84735259..115b6ecf1f 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ git_log.sh node_modules/ packages/l7/package_bak.json +stories/Test diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index 0e3a7ff13b..0b76770525 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -47,9 +47,13 @@ const defaultLayerConfig: Partial = { minZoom: 0, maxZoom: 20, visible: true, + autoFit: false, zIndex: 0, - enableMultiPassRenderer: false, - enablePicking: false, + pickedFeatureID: -1, + enableMultiPassRenderer: true, + enablePicking: true, + active: false, + activeColor: 'red', enableHighlight: false, highlightColor: 'red', enableTAA: false, diff --git a/packages/core/src/services/interaction/IInteractionService.ts b/packages/core/src/services/interaction/IInteractionService.ts index df77a1d265..ed351c574e 100644 --- a/packages/core/src/services/interaction/IInteractionService.ts +++ b/packages/core/src/services/interaction/IInteractionService.ts @@ -8,7 +8,7 @@ export interface IInteractionService { destroy(): void; on( eventName: InteractionEvent, - callback: (params: { x: number; y: number }) => void, + callback: (params: { x: number; y: number; type: string }) => void, ): void; - triggerHover({ x, y }: { x: number; y: number }): void; + triggerHover({ x, y, type }: { x: number; y: number; type?: string }): void; } diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts index 65d1bb58a5..add63327e4 100644 --- a/packages/core/src/services/interaction/InteractionService.ts +++ b/packages/core/src/services/interaction/InteractionService.ts @@ -5,7 +5,6 @@ import { TYPES } from '../../types'; import { ILogService } from '../log/ILogService'; import { IMapService } from '../map/IMapService'; import { IInteractionService, InteractionEvent } from './IInteractionService'; - /** * 由于目前 L7 与地图结合的方案为双 canvas 而非共享 WebGL Context,事件监听注册在地图底图上。 * 除此之外,后续如果支持非地图场景,事件监听就需要注册在 L7 canvas 上。 @@ -49,8 +48,13 @@ export default class InteractionService extends EventEmitter // hammertime.on('panmove', this.onPanmove); // hammertime.on('panend', this.onPanend); // hammertime.on('pinch', this.onPinch); - $containter.addEventListener('mousemove', this.onHover); + $containter.addEventListener('click', this.onHover); + $containter.addEventListener('mousedown', this.onHover); + $containter.addEventListener('mouseup', this.onHover); + $containter.addEventListener('dblclick', this.onHover); + $containter.addEventListener('contextmenu', this.onHover); + this.hammertime = hammertime; // TODO: 根据场景注册事件到 L7 canvas 上 @@ -62,16 +66,21 @@ export default class InteractionService extends EventEmitter const $containter = this.mapService.getMapContainer(); if ($containter) { $containter.removeEventListener('mousemove', this.onHover); + $containter.removeEventListener('click', this.onHover); + $containter.removeEventListener('mousedown', this.onHover); + $containter.removeEventListener('mouseup', this.onHover); + $containter.removeEventListener('dblclick', this.onHover); + $containter.removeEventListener('contextmenu', this.onHover); } } - private onHover = ({ x, y }: MouseEvent) => { + private onHover = ({ x, y, type }: MouseEvent) => { const $containter = this.mapService.getMapContainer(); if ($containter) { const { top, left } = $containter.getBoundingClientRect(); x -= left; y -= top; } - this.emit(InteractionEvent.Hover, { x, y }); + this.emit(InteractionEvent.Hover, { x, y, type }); }; } diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index e864586575..c3164a439a 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -22,7 +22,13 @@ import { StyleAttributeOption, Triangulation, } from './IStyleAttributeService'; - +export interface IDataState { + dataSourceNeedUpdate: boolean; + dataMappingNeedUpdate: boolean; + filterNeedUpdate: boolean; + featureScaleNeedUpdate: boolean; + StyleAttrNeedUpdate: boolean; +} export interface ILayerModelInitializationOptions { moduleName: string; vertexShader: string; @@ -44,6 +50,10 @@ export interface IPickedFeature { lnglat?: { lng: number; lat: number }; feature?: unknown; } +// 交互样式 +export interface IActiveOption { + color: string | number[]; +} export interface ILayer { id: string; // 一个场景中同一类型 Layer 可能存在多个 @@ -52,9 +62,11 @@ export interface ILayer { zIndex: number; plugins: ILayerPlugin[]; layerModelNeedUpdate: boolean; - dataPluginsState: { [key: string]: boolean }; + dataState: IDataState; // 数据流状态 + pickedFeatureID: number; hooks: { init: SyncBailHook; + afterInit: SyncBailHook; beforeRenderData: SyncWaterfallHook; beforeRender: SyncBailHook; afterRender: SyncHook; @@ -87,7 +99,16 @@ export interface ILayer { animate(option: IAnimateOption): ILayer; // pattern(field: string, value: StyleAttributeOption): ILayer; filter(field: string, value: StyleAttributeOption): ILayer; - // active(option: ActiveOption): ILayer; + active(option: IActiveOption | boolean): ILayer; + setActive( + id: number | { x: number; y: number }, + option?: IActiveOption, + ): void; + select(option: IActiveOption | false): ILayer; + setSelect( + id: number | { x: number; y: number }, + option?: IActiveOption, + ): void; style(options: unknown): ILayer; hide(): ILayer; show(): ILayer; @@ -100,6 +121,7 @@ export interface ILayer { destroy(): void; source(data: any, option?: ISourceCFG): ILayer; setData(data: any, option?: ISourceCFG): ILayer; + fitBounds(): ILayer; /** * 向当前图层注册插件 * @param plugin 插件实例 @@ -110,11 +132,13 @@ export interface ILayer { setEncodedData(encodedData: IEncodeFeature[]): void; getEncodedData(): IEncodeFeature[]; getScaleOptions(): IScaleOptions; + /** * 事件 */ on(type: string, hander: (...args: any[]) => void): void; off(type: string, hander: (...args: any[]) => void): void; + emit(type: string, hander: unknown): void; once(type: string, hander: (...args: any[]) => void): void; /** * JSON Schema 用于校验配置项 @@ -125,6 +149,8 @@ export interface ILayer { * 直接调用拾取方法,在非鼠标交互场景中使用 */ pick(query: { x: number; y: number }): void; + + updateLayerConfig(configToUpdate: Partial): void; } /** @@ -159,6 +185,8 @@ export interface ILayerConfig { maxZoom: number; visible: boolean; zIndex: number; + autoFit: boolean; + pickedFeatureID: number; enableMultiPassRenderer: boolean; passes: Array; @@ -174,6 +202,8 @@ export interface ILayerConfig { * 高亮颜色 */ highlightColor: string | number[]; + active: boolean; + activeColor: string | number[]; /** * 开启 TAA */ diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index cad35e8eb8..4c1785cc76 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -112,7 +112,18 @@ export default class PixelPickingPass< * 拾取视口指定坐标属于的要素 * TODO:支持区域拾取 */ - private pickFromPickingFBO = ({ x, y }: { x: number; y: number }) => { + private pickFromPickingFBO = ({ + x, + y, + type, + }: { + x: number; + y: number; + type: string; + }) => { + if (!this.layer.isVisible()) { + return; + } const { getViewportSize, readPixels, @@ -152,9 +163,9 @@ export default class PixelPickingPass< ) { this.logger.debug('picked'); const pickedFeatureIdx = decodePickingColor(pickedColors); - const rawFeature = this.layer.getSource()?.data?.dataArray[ - pickedFeatureIdx - ]; + const rawFeature = this.layer + .getSource() + .getFeatureById(pickedFeatureIdx); if (!rawFeature) { // this.logger.error( @@ -162,7 +173,7 @@ export default class PixelPickingPass< // ); } else { // trigger onHover/Click callback on layer - this.triggerHoverOnLayer({ x, y, feature: rawFeature }); + this.triggerHoverOnLayer({ x, y, type, feature: rawFeature }); } } }); @@ -175,10 +186,12 @@ export default class PixelPickingPass< private triggerHoverOnLayer({ x, y, + type, feature, }: { x: number; y: number; + type: string; feature: unknown; }) { const { onHover, onClick } = this.layer.getLayerConfig(); @@ -196,6 +209,11 @@ export default class PixelPickingPass< feature, }); } + this.layer.emit(type, { + x, + y, + feature, + }); } /** diff --git a/packages/core/src/services/source/ISourceService.ts b/packages/core/src/services/source/ISourceService.ts index 2e03b0ac31..71d3ebbce9 100644 --- a/packages/core/src/services/source/ISourceService.ts +++ b/packages/core/src/services/source/ISourceService.ts @@ -62,6 +62,7 @@ export interface ISource { cluster: boolean; clusterOptions: Partial; updateClusterData(zoom: number): void; + getFeatureById(id: number): unknown; } export interface IRasterCfg { extent: [number, number, number, number]; diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index ae4463bc56..d70a1f20be 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -1,6 +1,8 @@ import { gl, + IActiveOption, IAnimateOption, + IDataState, IEncodeFeature, IFontService, IGlobalConfigService, @@ -44,12 +46,10 @@ import mergeJsonSchemas from 'merge-json-schemas'; import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable'; import { normalizePasses } from '../plugins/MultiPassRendererPlugin'; import baseLayerSchema from './schema'; - /** * 分配 layer id */ let layerIdCounter = 0; -const MapEventTypes = ['zoomchange', 'dragend', 'camerachange', 'resize']; export default class BaseLayer extends EventEmitter implements ILayer { @@ -61,15 +61,19 @@ export default class BaseLayer extends EventEmitter public maxZoom: number; public inited: boolean = false; public layerModelNeedUpdate: boolean = false; - public dataPluginsState: { [key: string]: boolean } = { - DataSource: false, - DataMapping: false, - FeatureScale: false, - StyleAttr: false, + public pickedFeatureID: number = -1; + + public dataState: IDataState = { + dataSourceNeedUpdate: false, + dataMappingNeedUpdate: false, + filterNeedUpdate: false, + featureScaleNeedUpdate: false, + StyleAttrNeedUpdate: false, }; // 生命周期钩子 public hooks = { init: new SyncBailHook(), + afterInit: new SyncBailHook(), beforeRender: new SyncBailHook(), beforeRenderData: new SyncWaterfallHook(['data']), afterRender: new SyncHook(), @@ -140,6 +144,8 @@ export default class BaseLayer extends EventEmitter private rawConfig: Partial; + private needUpdateConfig: Partial; + /** * 待更新样式属性,在初始化阶段完成注册 */ @@ -165,11 +171,20 @@ export default class BaseLayer extends EventEmitter public updateLayerConfig( configToUpdate: Partial, ) { - const sceneId = this.container.get(TYPES.SceneID); - this.configService.setLayerConfig(sceneId, this.id, { - ...this.configService.getLayerConfig(this.id), - ...configToUpdate, - }); + if (!this.inited) { + this.needUpdateConfig = { + ...this.needUpdateConfig, + ...configToUpdate, + }; + } else { + const sceneId = this.container.get(TYPES.SceneID); + this.configService.setLayerConfig(sceneId, this.id, { + ...this.configService.getLayerConfig(this.id), + ...this.needUpdateConfig, + ...configToUpdate, + }); + this.needUpdateConfig = {}; + } } /** @@ -273,10 +288,11 @@ export default class BaseLayer extends EventEmitter // 触发 init 生命周期插件 this.hooks.init.call(); + this.inited = true; - this.buildModels(); + this.hooks.afterInit.call(); - this.inited = true; + this.buildModels(); // 触发初始化完成事件; this.emit('inited'); return this; @@ -311,6 +327,7 @@ export default class BaseLayer extends EventEmitter }); return this; } + // 对mapping后的数据过滤,scale保持不变 public filter( field: StyleAttributeField, values?: StyleAttributeOption, @@ -322,6 +339,7 @@ export default class BaseLayer extends EventEmitter attributeValues: values, updateOptions, }); + this.dataState.dataMappingNeedUpdate = true; return this; } @@ -418,11 +436,51 @@ export default class BaseLayer extends EventEmitter return this; } + public active(options: IActiveOption) { + this.updateLayerConfig({ + enableHighlight: isObject(options) ? true : options, + highlightColor: isObject(options) + ? options.color + : this.getLayerConfig().highlightColor, + }); + return this; + } + public setActive( + id: number | { x: number; y: number }, + options?: IActiveOption, + ): void { + if (isObject(id)) { + const { x = 0, y = 0 } = id; + this.updateLayerConfig({ + highlightColor: isObject(options) + ? options.color + : this.getLayerConfig().highlightColor, + }); + this.pick({ x, y }); + } else { + this.updateLayerConfig({ + pickedFeatureID: id, + highlightColor: isObject(options) + ? options.color + : this.getLayerConfig().highlightColor, + }); + } + } + + public select(option: IActiveOption | false): ILayer { + return this; + } + + public setSelect( + id: number | { x: number; y: number }, + options?: IActiveOption, + ): void { + throw new Error('Method not implemented.'); + } public show(): ILayer { this.updateLayerConfig({ visible: true, }); - this.layerService.renderLayers(); return this; } @@ -430,7 +488,6 @@ export default class BaseLayer extends EventEmitter this.updateLayerConfig({ visible: false, }); - this.layerService.renderLayers(); return this; } @@ -467,13 +524,20 @@ export default class BaseLayer extends EventEmitter /** * zoom to layer Bounds */ - public fitBounds(): void { + public fitBounds(): ILayer { + if (!this.inited) { + this.updateLayerConfig({ + autoFit: true, + }); + return this; + } const source = this.getSource(); const extent = source.extent; this.mapService.fitBounds([ [extent[0], extent[1]], [extent[2], extent[3]], ]); + return this; } public destroy() { @@ -614,11 +678,6 @@ export default class BaseLayer extends EventEmitter }; } - private registerMapEvent() { - MapEventTypes.forEach((type) => { - this.mapService.on(type, this.layerMapHander.bind(this, type)); - }); - } private layerMapHander(type: string, data: any) { this.emit(type, data); } diff --git a/packages/layers/src/heatmap/models/hexagon.ts b/packages/layers/src/heatmap/models/hexagon.ts index 16df1f4102..11c9a6f473 100644 --- a/packages/layers/src/heatmap/models/hexagon.ts +++ b/packages/layers/src/heatmap/models/hexagon.ts @@ -45,29 +45,29 @@ export default class HexagonModel extends BaseModel { } protected registerBuiltinAttributes() { // point layer size; - this.styleAttributeService.registerStyleAttribute({ - name: 'size', - type: AttributeType.Attribute, - descriptor: { - name: 'a_Size', - buffer: { - // give the WebGL driver a hint that this buffer may change - usage: gl.DYNAMIC_DRAW, - data: [], - type: gl.FLOAT, - }, - size: 1, - update: ( - feature: IEncodeFeature, - featureIdx: number, - vertex: number[], - attributeIdx: number, - ) => { - const { size } = feature; - return Array.isArray(size) ? [size[0]] : [size as number]; - }, - }, - }); + // this.styleAttributeService.registerStyleAttribute({ + // name: 'size', + // type: AttributeType.Attribute, + // descriptor: { + // name: 'a_Size', + // buffer: { + // // give the WebGL driver a hint that this buffer may change + // usage: gl.DYNAMIC_DRAW, + // data: [], + // type: gl.FLOAT, + // }, + // size: 1, + // update: ( + // feature: IEncodeFeature, + // featureIdx: number, + // vertex: number[], + // attributeIdx: number, + // ) => { + // const { size } = feature; + // return Array.isArray(size) ? [size[0]] : [size as number]; + // }, + // }, + // }); // point layer size; this.styleAttributeService.registerStyleAttribute({ diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index b1672cafaa..408d77b348 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -13,6 +13,7 @@ import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin import DataMappingPlugin from './plugins/DataMappingPlugin'; import DataSourcePlugin from './plugins/DataSourcePlugin'; import FeatureScalePlugin from './plugins/FeatureScalePlugin'; +import LayerStylePlugin from './plugins/LayerStylePlugin'; import LightingPlugin from './plugins/LightingPlugin'; import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin'; import PixelPickingPlugin from './plugins/PixelPickingPlugin'; @@ -56,6 +57,15 @@ container .bind(TYPES.ILayerPlugin) .to(DataMappingPlugin) .inRequestScope(); + +/** + * 更新地图样式配置项 如active, show, hide + */ +container + .bind(TYPES.ILayerPlugin) + .to(LayerStylePlugin) + .inRequestScope(); + /** * 负责属性更新 */ diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts index 6e12dd1ac2..bd7a897893 100644 --- a/packages/layers/src/plugins/DataMappingPlugin.ts +++ b/packages/layers/src/plugins/DataMappingPlugin.ts @@ -27,13 +27,13 @@ export default class DataMappingPlugin implements ILayerPlugin { }: { styleAttributeService: IStyleAttributeService }, ) { layer.hooks.init.tap('DataMappingPlugin', () => { - this.doMaping(layer, { styleAttributeService }); + this.generateMaping(layer, { styleAttributeService }); }); layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => { - if (flag) { - layer.dataPluginsState.DataMapping = false; - this.doMaping(layer, { styleAttributeService }); + if (flag || layer.dataState.dataMappingNeedUpdate) { + layer.dataState.dataMappingNeedUpdate = false; + this.generateMaping(layer, { styleAttributeService }); return true; } return false; @@ -52,7 +52,7 @@ export default class DataMappingPlugin implements ILayerPlugin { } }); } - private doMaping( + private generateMaping( layer: ILayer, { styleAttributeService, @@ -62,6 +62,7 @@ export default class DataMappingPlugin implements ILayerPlugin { const filter = styleAttributeService.getLayerStyleAttribute('filter'); const { dataArray } = layer.getSource().data; let filterData = dataArray; + // 数据过滤完 在执行数据映射 if (filter?.scale) { filterData = dataArray.filter((record: IParseDataItem) => { return this.applyAttributeMapping(filter, record)[0]; diff --git a/packages/layers/src/plugins/LayerStylePlugin.ts b/packages/layers/src/plugins/LayerStylePlugin.ts new file mode 100644 index 0000000000..c6d3915604 --- /dev/null +++ b/packages/layers/src/plugins/LayerStylePlugin.ts @@ -0,0 +1,30 @@ +import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core'; +import Source from '@antv/l7-source'; +import { injectable } from 'inversify'; +import { encodePickingColor, rgb2arr } from '../utils/color'; +@injectable() +export default class LayerStylePlugin implements ILayerPlugin { + public apply(layer: ILayer) { + layer.hooks.afterInit.tap('LayerStylePlugin', () => { + layer.updateLayerConfig({}); + const { autoFit } = layer.getLayerConfig(); + if (autoFit) { + layer.fitBounds(); + } + }); + + layer.hooks.beforeRender.tap('LayerStylePlugin', () => { + const { + highlightColor = 'red', + pickedFeatureID = -1, + } = layer.getLayerConfig(); + layer.models.forEach((model) => + model.addUniforms({ + u_PickingStage: 2.0, + u_PickingColor: encodePickingColor(pickedFeatureID), + u_HighlightColor: rgb2arr(highlightColor as string), + }), + ); + }); + } +} diff --git a/packages/layers/src/plugins/PixelPickingPlugin.ts b/packages/layers/src/plugins/PixelPickingPlugin.ts index 782043f322..3afd3f0387 100644 --- a/packages/layers/src/plugins/PixelPickingPlugin.ts +++ b/packages/layers/src/plugins/PixelPickingPlugin.ts @@ -8,15 +8,7 @@ import { IStyleAttributeService, } from '@antv/l7-core'; import { injectable } from 'inversify'; -import { rgb2arr } from '../utils/color'; - -function encodePickingColor(featureIdx: number): [number, number, number] { - return [ - (featureIdx + 1) & 255, - ((featureIdx + 1) >> 8) & 255, - (((featureIdx + 1) >> 8) >> 8) & 255, - ]; -} +import { encodePickingColor, rgb2arr } from '../utils/color'; const PickingStage = { NONE: 0.0, @@ -50,9 +42,13 @@ export default class PixelPickingPlugin implements ILayerPlugin { }, size: 3, // TODO: 固定 feature range 范围内的 pickingColor 都是固定的,可以生成 cache - update: (feature: IEncodeFeature, featureIdx: number) => + update: (feature: IEncodeFeature, featureIdx: number) => { // 只有开启拾取才需要 encode - enablePicking ? encodePickingColor(featureIdx) : [0, 0, 0], + const { id } = feature; + return enablePicking && layer.isVisible() + ? encodePickingColor(id as number) + : [0, 0, 0]; + }, }, }); }); @@ -60,7 +56,7 @@ export default class PixelPickingPlugin implements ILayerPlugin { // if (layer.multiPassRenderer) { layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => { const { enablePicking } = layer.getLayerConfig(); - if (enablePicking) { + if (enablePicking && layer.isVisible()) { layer.models.forEach((model) => model.addUniforms({ u_PickingStage: PickingStage.ENCODE, @@ -71,7 +67,7 @@ export default class PixelPickingPlugin implements ILayerPlugin { layer.hooks.afterPickingEncode.tap('PixelPickingPlugin', () => { const { enablePicking } = layer.getLayerConfig(); - if (enablePicking) { + if (enablePicking && layer.isVisible()) { layer.models.forEach((model) => model.addUniforms({ u_PickingStage: PickingStage.NONE, diff --git a/packages/layers/src/utils/color.ts b/packages/layers/src/utils/color.ts index 20cbcff16e..f366874639 100644 --- a/packages/layers/src/utils/color.ts +++ b/packages/layers/src/utils/color.ts @@ -15,6 +15,16 @@ export function rgb2arr(str: string) { return arr; } +export function encodePickingColor( + featureIdx: number, +): [number, number, number] { + return [ + (featureIdx + 1) & 255, + ((featureIdx + 1) >> 8) & 255, + (((featureIdx + 1) >> 8) >> 8) & 255, + ]; +} + export function generateColorRamp(colorRamp: IColorRamp): ImageData { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index b4f693a4ca..cf2cbc9ae2 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -6,8 +6,10 @@ import json from './parser/json'; import raster from './parser/raster'; import Source from './source'; import { cluster } from './transform/cluster'; +import { filter } from './transform/filter'; import { aggregatorToGrid } from './transform/grid'; import { pointToHexbin } from './transform/hexagon'; +import { map } from './transform/map'; export default Source; registerParser('geojson', geojson); registerParser('image', image); @@ -15,6 +17,8 @@ registerParser('csv', csv); registerParser('json', json); registerParser('raster', raster); registerTransform('cluster', cluster); +registerTransform('filter', filter); +registerTransform('map', map); registerTransform('grid', aggregatorToGrid); registerTransform('hexagon', pointToHexbin); export { diff --git a/packages/source/src/source.ts b/packages/source/src/source.ts index 86a31b9350..fc7210ce77 100644 --- a/packages/source/src/source.ts +++ b/packages/source/src/source.ts @@ -121,6 +121,16 @@ export default class Source extends EventEmitter { }); this.executeTrans(); } + public getFeatureById(id: number): unknown { + const { type = 'geojson' } = this.parser; + if (type === 'geojson') { + return id < this.rawData.features.length + ? this.rawData.features[id] + : 'null'; + } else { + return id < this.data.dataArray.length ? this.data.dataArray[id] : 'null'; + } + } private excuteParser(): void { const parser = this.parser; diff --git a/packages/source/src/transform/filter.ts b/packages/source/src/transform/filter.ts new file mode 100644 index 0000000000..9a0b50a127 --- /dev/null +++ b/packages/source/src/transform/filter.ts @@ -0,0 +1,8 @@ +import { IParserData } from '@antv/l7-core'; +export function filter(data: IParserData, options: { [key: string]: any }) { + const { callback } = options; + if (callback) { + data.dataArray = data.dataArray.filter(callback); + } + return data; +} diff --git a/packages/source/src/transform/map.ts b/packages/source/src/transform/map.ts new file mode 100644 index 0000000000..2aedb29814 --- /dev/null +++ b/packages/source/src/transform/map.ts @@ -0,0 +1,9 @@ +import { IParserData } from '@antv/l7-core'; +type CallBack = (...args: any[]) => any; +export function map(data: IParserData, options: { [key: string]: any }) { + const { callback } = options; + if (callback) { + data.dataArray = data.dataArray.map(callback); + } + return data; +} diff --git a/stories/Layers/Layers.stories.tsx b/stories/Layers/Layers.stories.tsx index c8af5a793c..3800e9eae3 100644 --- a/stories/Layers/Layers.stories.tsx +++ b/stories/Layers/Layers.stories.tsx @@ -5,7 +5,6 @@ import ArcLineDemo from './components/Arcline'; import Column from './components/column'; import DataUpdate from './components/data_update'; import HeatMapDemo from './components/HeatMap'; -import GridHeatMap from './components/HeatmapGrid'; import LineLayer from './components/Line'; import PointDemo from './components/Point'; import Point3D from './components/Point3D'; @@ -25,7 +24,6 @@ storiesOf('图层', module) .add('线图层', () => ) .add('3D弧线', () => ) .add('2D弧线', () => ) - .add('网格热力图', () => ) .add('热力图', () => ) .add('栅格', () => ) .add('图片', () => ); diff --git a/stories/Layers/components/HeatmapGrid.tsx b/stories/Layers/components/HeatmapGrid.tsx deleted file mode 100644 index 82a48d4c43..0000000000 --- a/stories/Layers/components/HeatmapGrid.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { HeatmapLayer, Scene } from '@antv/l7'; -import { GaodeMap } from '@antv/l7-maps'; -import * as React from 'react'; - -export default class GridHeatMap extends React.Component { - // @ts-ignore - private scene: Scene; - - public componentWillUnmount() { - this.scene.destroy(); - } - - public async componentDidMount() { - const response = await fetch( - 'https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv', - ); - const data = await response.text(); - const scene = new Scene({ - id: 'map', - map: new GaodeMap({ - style: 'dark', - pitch: 0, - center: [110.097892, 33.853662], - zoom: 4.056, - }), - }); - const layer = new HeatmapLayer({ - enablePicking: true, - enableHighlight: true, - }) - .source(data, { - parser: { - type: 'csv', - x: 'lng', - y: 'lat', - }, - transforms: [ - { - type: 'grid', - size: 10000, - field: 'v', - method: 'sum', - }, - ], - }) - .size('count', (value) => { - return value * 0; - }) - .shape('square') - .style({ - coverage: 1, - angle: 0, - }) - .color( - 'count', - [ - '#FF3417', - '#FF7412', - '#FFB02A', - '#FFE754', - '#46F3FF', - '#02BEFF', - '#1A7AFF', - '#0A1FB2', - ].reverse(), - ); - scene.addLayer(layer); - this.scene = scene; - } - - public render() { - return ( -
- ); - } -} diff --git a/stories/Picking/components/AdvancedAPI.tsx b/stories/Picking/components/AdvancedAPI.tsx index 5c63622f46..5dfbe984c0 100644 --- a/stories/Picking/components/AdvancedAPI.tsx +++ b/stories/Picking/components/AdvancedAPI.tsx @@ -38,13 +38,18 @@ export default class AdvancedAPI extends React.Component { highlightColor: [0, 0, 1, 1], onHover: (pickedFeature) => { // tslint:disable-next-line:no-console - console.log(pickedFeature); + }, + onClick: (pickedFeature) => { + // tslint:disable-next-line:no-console }, }); layer .source(await response.json()) .size('name', [0, 10000, 50000, 30000, 100000]) + .active({ + color: 'red', + }) .color('name', [ '#2E8AE6', '#69D1AB', @@ -58,6 +63,9 @@ export default class AdvancedAPI extends React.Component { opacity: 0.8, }); scene.addLayer(layer); + layer.on('click', (e) => { + console.log(e); + }); this.scene = scene; @@ -67,9 +75,10 @@ export default class AdvancedAPI extends React.Component { const styleOptions = { enablePicking: true, enableHighlight: true, - highlightColor: [0, 0, 255], + highlightColor: [1, 0, 0], pickingX: window.innerWidth / 2, pickingY: window.innerHeight / 2, + visible: true, }; const pointFolder = gui.addFolder('非鼠标 hover 交互'); pointFolder @@ -80,23 +89,30 @@ export default class AdvancedAPI extends React.Component { }); scene.render(); }); + pointFolder.add(styleOptions, 'visible').onChange((visible: boolean) => { + layer.style({ + visible, + }); + scene.render(); + }); pointFolder .add(styleOptions, 'pickingX', 0, window.innerWidth) .onChange((pickingX: number) => { - layer.pick({ x: pickingX, y: styleOptions.pickingY }); + layer.setActive({ x: pickingX, y: styleOptions.pickingY }); }); pointFolder .add(styleOptions, 'pickingY', 0, window.innerHeight) .onChange((pickingY: number) => { - layer.pick({ x: styleOptions.pickingX, y: pickingY }); + layer.setActive({ x: styleOptions.pickingX, y: pickingY }); }); pointFolder .addColor(styleOptions, 'highlightColor') .onChange((highlightColor: number[]) => { const [r, g, b] = highlightColor.map((c) => c / 255); - layer.style({ - highlightColor: [r, g, b, 1], - }); + layer.setActive( + { x: styleOptions.pickingX, y: styleOptions.pickingY }, + { color: [r, g, b, 1] }, + ); scene.render(); }); pointFolder.open();