From 76a75f5df71145539169e0081299c7b34ab95228 Mon Sep 17 00:00:00 2001 From: "yuqi.pyq" Date: Thu, 24 Oct 2019 10:38:11 +0800 Subject: [PATCH] feat(picking): support PixelPickingPass and highlight the picked feature --- dev-docs/PixelPickingEngine.md | 11 +- packages/core/src/index.ts | 5 +- packages/core/src/inversify.config.ts | 12 +- .../core/src/services/config/ConfigService.ts | 4 +- .../interaction/IInteractionService.ts | 9 + .../interaction/InteractionService.ts | 77 ++-- .../core/src/services/layer/ILayerService.ts | 101 +++-- .../src/services/layer/ILayerStyleService.ts | 31 -- .../services/layer/IStyleAttributeService.ts | 157 +++++++ .../core/src/services/layer/LayerService.ts | 19 +- .../src/services/layer/LayerStyleService.ts | 18 - .../core/src/services/layer/StyleAttribute.ts | 80 ++++ .../services/layer/StyleAttributeService.ts | 268 ++++++++++++ packages/core/src/services/map/IMapService.ts | 11 +- .../core/src/services/renderer/IAttribute.ts | 6 + .../core/src/services/renderer/IElements.ts | 1 + .../src/services/renderer/IRendererService.ts | 14 +- .../renderer/passes/BasePostProcessingPass.ts | 4 +- .../renderer/passes/PixelPickingPass.ts | 178 +++++++- .../services/renderer/passes/RenderPass.ts | 4 +- .../core/src/services/scene/SceneService.ts | 7 +- .../services/shader/ShaderModuleService.ts | 3 + packages/core/src/shaders/picking.frag.glsl | 46 +++ packages/core/src/shaders/picking.vert.glsl | 27 ++ packages/core/src/types.ts | 1 + packages/layers/src/core/BaseBuffer.ts | 193 --------- packages/layers/src/core/BaseLayer.ts | 227 +++++++--- packages/layers/src/core/ScaleController.ts | 113 ----- packages/layers/src/core/StyleAttribute.ts | 90 ---- .../layers/src/heatmap/buffers/GridBuffer.ts | 92 ++--- packages/layers/src/heatmap/index.ts | 212 +++++----- packages/layers/src/index.ts | 20 +- packages/layers/src/line/buffers/line.ts | 188 ++++----- packages/layers/src/line/index.ts | 196 ++++----- .../layers/src/plugins/DataEncodePlugin.ts | 125 ------ .../layers/src/plugins/DataMappingPlugin.ts | 98 +++++ .../layers/src/plugins/DataSourcePlugin.ts | 14 +- .../layers/src/plugins/FeatureScalePlugin.ts | 174 ++++++++ .../src/plugins/MultiPassRendererPlugin.ts | 8 +- .../layers/src/plugins/PixelPickingPlugin.ts | 92 +++++ .../plugins/RegisterStyleAttributePlugin.ts | 66 +++ .../layers/src/plugins/ShaderUniformPlugin.ts | 1 + .../src/plugins/UpdateStyleAttributePlugin.ts | 31 ++ .../layers/src/point/buffers/ExtrudeBuffer.ts | 154 +++---- .../layers/src/point/buffers/ImageBuffer.ts | 44 +- packages/layers/src/point/index.ts | 390 +++++++++--------- packages/layers/src/point/point.ts | 248 +++++------ .../__tests__/polygonTriangulation.spec.ts | 16 + .../src/polygon/buffers/ExtrudeBuffer.ts | 137 ------ .../layers/src/polygon/buffers/FillBuffer.ts | 69 ---- packages/layers/src/polygon/index.ts | 105 ++--- .../src/polygon/shaders/polygon_frag.glsl | 12 +- .../src/polygon/shaders/polygon_vert.glsl | 12 +- .../layers/src/raster/buffers/ImageBuffer.ts | 66 +-- packages/layers/src/raster/index.ts | 168 ++++---- packages/maps/src/amap/index.ts | 29 +- packages/maps/src/mapbox/index.ts | 33 +- packages/renderer/src/regl/ReglAttribute.ts | 15 +- packages/renderer/src/regl/ReglElements.ts | 3 +- packages/renderer/src/regl/ReglModel.ts | 10 +- packages/renderer/src/regl/index.ts | 34 +- packages/scene/src/index.ts | 34 +- stories/Animation/Animation.stories.tsx | 5 + stories/Animation/components/Polygon.tsx | 101 +++++ stories/MapAdaptor/Map.stories.tsx | 28 +- stories/MapAdaptor/components/AMap.tsx | 35 +- stories/MapAdaptor/components/Mapbox.tsx | 75 ++-- stories/MapAdaptor/components/Polygon.tsx | 71 ++-- .../MultiPassRenderer.stories.tsx | 5 + .../MultiPassRenderer/components/Polygon.tsx | 85 ++++ stories/Picking/Picking.stories.tsx | 10 + stories/Picking/components/AdvancedAPI.tsx | 117 ++++++ stories/Picking/components/Highlight.tsx | 117 ++++++ stories/Picking/components/Tooltip.tsx | 71 ++++ tsconfig.json | 1 + yarn.lock | 45 +- 76 files changed, 3233 insertions(+), 2146 deletions(-) delete mode 100644 packages/core/src/services/layer/ILayerStyleService.ts create mode 100644 packages/core/src/services/layer/IStyleAttributeService.ts delete mode 100644 packages/core/src/services/layer/LayerStyleService.ts create mode 100644 packages/core/src/services/layer/StyleAttribute.ts create mode 100644 packages/core/src/services/layer/StyleAttributeService.ts create mode 100644 packages/core/src/shaders/picking.frag.glsl create mode 100644 packages/core/src/shaders/picking.vert.glsl delete mode 100644 packages/layers/src/core/BaseBuffer.ts delete mode 100644 packages/layers/src/core/ScaleController.ts delete mode 100644 packages/layers/src/core/StyleAttribute.ts delete mode 100644 packages/layers/src/plugins/DataEncodePlugin.ts create mode 100644 packages/layers/src/plugins/DataMappingPlugin.ts create mode 100644 packages/layers/src/plugins/FeatureScalePlugin.ts create mode 100644 packages/layers/src/plugins/PixelPickingPlugin.ts create mode 100644 packages/layers/src/plugins/RegisterStyleAttributePlugin.ts create mode 100644 packages/layers/src/plugins/UpdateStyleAttributePlugin.ts create mode 100644 packages/layers/src/polygon/__tests__/polygonTriangulation.spec.ts delete mode 100644 packages/layers/src/polygon/buffers/ExtrudeBuffer.ts delete mode 100644 packages/layers/src/polygon/buffers/FillBuffer.ts create mode 100644 stories/Animation/Animation.stories.tsx create mode 100644 stories/Animation/components/Polygon.tsx create mode 100644 stories/MultiPassRenderer/MultiPassRenderer.stories.tsx create mode 100644 stories/MultiPassRenderer/components/Polygon.tsx create mode 100644 stories/Picking/Picking.stories.tsx create mode 100644 stories/Picking/components/AdvancedAPI.tsx create mode 100644 stories/Picking/components/Highlight.tsx create mode 100644 stories/Picking/components/Tooltip.tsx diff --git a/dev-docs/PixelPickingEngine.md b/dev-docs/PixelPickingEngine.md index f4d8504c36..7a99ec6433 100644 --- a/dev-docs/PixelPickingEngine.md +++ b/dev-docs/PixelPickingEngine.md @@ -28,10 +28,10 @@ ClearPass -> PixelPickingPass -> RenderPass -> [ ...其他后处理 Pass ] -> Co ``` PixelPickingPass 分解步骤如下: -1. 逐要素编码(idx -> color),传入 attributes 渲染 Layer 到纹理。 +1. `ENCODE` 阶段。逐要素编码(idx -> color),传入 attributes 渲染 Layer 到纹理。 2. 获取鼠标在视口中的位置。由于目前 L7 与地图结合的方案为双 Canvas 而非共享 WebGL Context,事件监听注册在地图底图上。 3. 读取纹理在指定位置的颜色,进行解码(color -> idx),查找对应要素,作为 Layer `onHover/onClick` 回调参数传入。 -4. (可选)将待高亮要素对应的颜色传入 Vertex Shader 用于每个 Vertex 判断自身是否被选中,如果被选中,在 Fragment Shader 中将高亮颜色与计算颜色混合。 +4. `HIGHLIGHT` 阶段(可选)。将待高亮要素对应的颜色传入 Vertex Shader 用于每个 Vertex 判断自身是否被选中,如果被选中,在 Fragment Shader 中将高亮颜色与计算颜色混合。 ## 使用方法 @@ -69,7 +69,8 @@ const layer = new PolygonLayer({ ```typescript const layer = new PolygonLayer({ enablePicking: true, // 开启拾取 - highlightColor: 'red', // 设置高亮颜色 + enableHighlight: true, // 开启高亮 + highlightColor: [0, 0, 1, 1], // 设置高亮颜色为蓝色 }); ``` @@ -132,7 +133,7 @@ void main() { void main() { // 必须在末尾,保证后续不会再对 gl_FragColor 进行修改 - gl_FragColor = highlightPickingColor(gl_FragColor); + gl_FragColor = filterPickingColor(gl_FragColor); } ``` @@ -141,7 +142,7 @@ void main() { | 方法名 | 应用 shader | 说明 | | -------- | --- | ------------- | | `setPickingColor` | `vertex` | 比较自身颜色编码与高亮颜色,判断是否被选中,传递结果给 fragment | -| `highlightPickingColor` | `fragment` | 当前 fragment 被选中则使用高亮颜色混合,否则直接输出原始计算结果 | +| `filterPickingColor` | `fragment` | 当前 fragment 被选中则使用高亮颜色混合,否则直接输出原始计算结果 | ## 参考资料 diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 45d90ad78e..452f672d1c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,4 @@ import container, { lazyInject } from './inversify.config'; -import IconService from './services/asset/IconService'; import ClearPass from './services/renderer/passes/ClearPass'; import MultiPassRenderer from './services/renderer/passes/MultiPassRenderer'; import PixelPickingPass from './services/renderer/passes/PixelPickingPass'; @@ -28,7 +27,6 @@ export { * 各个 Service 接口 */ SceneService, - IconService, packCircleVertex, /** pass */ MultiPassRenderer, @@ -41,8 +39,8 @@ export { }; /** 暴露服务接口供其他 packages 实现 */ -export * from './services/layer/ILayerStyleService'; export * from './services/layer/ILayerService'; +export * from './services/layer/IStyleAttributeService'; export * from './services/source/ISourceService'; export * from './services/map/IMapService'; export * from './services/coordinate/ICoordinateSystemService'; @@ -52,6 +50,7 @@ export * from './services/config/IConfigService'; export * from './services/scene/ISceneService'; export * from './services/shader/IShaderModuleService'; export * from './services/asset/IIconService'; +export * from './services/log/ILogService'; /** 全部渲染服务接口 */ export * from './services/renderer/IAttribute'; diff --git a/packages/core/src/inversify.config.ts b/packages/core/src/inversify.config.ts index 30e69bcc00..303d221b13 100644 --- a/packages/core/src/inversify.config.ts +++ b/packages/core/src/inversify.config.ts @@ -12,6 +12,7 @@ import { IGlobalConfigService } from './services/config/IConfigService'; import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService'; import { IInteractionService } from './services/interaction/IInteractionService'; import { ILayerService } from './services/layer/ILayerService'; +import { IStyleAttributeService } from './services/layer/IStyleAttributeService'; import { ILogService } from './services/log/ILogService'; import { IShaderModuleService } from './services/shader/IShaderModuleService'; @@ -22,7 +23,7 @@ import GlobalConfigService from './services/config/ConfigService'; import CoordinateSystemService from './services/coordinate/CoordinateSystemService'; import InteractionService from './services/interaction/InteractionService'; import LayerService from './services/layer/LayerService'; -import LayerStyleService from './services/layer/LayerStyleService'; +import StyleAttributeService from './services/layer/StyleAttributeService'; import LogService from './services/log/LogService'; import ShaderModuleService from './services/shader/ShaderModuleService'; @@ -40,6 +41,9 @@ container .bind(TYPES.ILayerService) .to(LayerService) .inSingletonScope(); +container + .bind(TYPES.IStyleAttributeService) + .to(StyleAttributeService); container .bind(TYPES.ICameraService) .to(CameraService) @@ -68,9 +72,9 @@ container // @see https://github.com/inversify/InversifyJS/blob/master/wiki/inheritance.md#what-can-i-do-when-my-base-class-is-provided-by-a-third-party-module decorate(injectable(), EventEmitter); -// 支持 L7 使用 new 而非容器实例化的场景 -// @see https://github.com/inversify/inversify-inject-decorators -const DECORATORS = getDecorators(container); +// 支持 L7 使用 new 而非容器实例化的场景,同时禁止 lazyInject cache +// @see https://github.com/inversify/inversify-inject-decorators#caching-vs-non-caching-behaviour +const DECORATORS = getDecorators(container, false); interface IBabelPropertyDescriptor extends PropertyDescriptor { initializer(): any; diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index 2d6ff3cabf..8a0c71a6f2 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -23,6 +23,7 @@ const defaultGlobalConfig: Partial = { ], size: 10000, shape: 'circle', + scales: {}, }; @injectable() @@ -38,7 +39,8 @@ export default class GlobalConfigService implements IGlobalConfigService { ...this.config, ...config, }; - // TODO: validate config + // TODO: validate config with JSON schema + // @see https://github.com/webpack/schema-utils return true; } diff --git a/packages/core/src/services/interaction/IInteractionService.ts b/packages/core/src/services/interaction/IInteractionService.ts index d1b20a01e9..627c171f0f 100644 --- a/packages/core/src/services/interaction/IInteractionService.ts +++ b/packages/core/src/services/interaction/IInteractionService.ts @@ -1,4 +1,13 @@ +export enum InteractionEvent { + Hover = 'hover', + Click = 'click', +} + export interface IInteractionService { init(): void; destroy(): void; + on( + eventName: InteractionEvent, + callback: (params: { x: number; y: number }) => void, + ): void; } diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts index b8fd4435f2..e29ba59ad9 100644 --- a/packages/core/src/services/interaction/InteractionService.ts +++ b/packages/core/src/services/interaction/InteractionService.ts @@ -1,14 +1,20 @@ +import EventEmitter from 'eventemitter3'; import Hammer from 'hammerjs'; import { inject, injectable } from 'inversify'; import { TYPES } from '../../types'; import { ILogService } from '../log/ILogService'; -import { IRendererService } from '../renderer/IRendererService'; -import { IInteractionService } from './IInteractionService'; +import { IMapService } from '../map/IMapService'; +import { IInteractionService, InteractionEvent } from './IInteractionService'; +/** + * 由于目前 L7 与地图结合的方案为双 canvas 而非共享 WebGL Context,事件监听注册在地图底图上。 + * 除此之外,后续如果支持非地图场景,事件监听就需要注册在 L7 canvas 上。 + */ @injectable() -export default class InteractionService implements IInteractionService { - @inject(TYPES.IRendererService) - private readonly rendererService: IRendererService; +export default class InteractionService extends EventEmitter + implements IInteractionService { + @inject(TYPES.IMapService) + private readonly mapService: IMapService; @inject(TYPES.ILogService) private readonly logger: ILogService; @@ -16,45 +22,46 @@ export default class InteractionService implements IInteractionService { private hammertime: HammerManager; public init() { - const $containter = this.rendererService.getContainer(); - if ($containter) { - const hammertime = new Hammer($containter); - hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); - hammertime.get('pinch').set({ enable: true }); - - // hammertime.on('panstart', this.onPanstart); - hammertime.on('panmove', this.onPanmove); - // hammertime.on('panend', this.onPanend); - // hammertime.on('pinch', this.onPinch); - - // $containter.addEventListener('wheel', this.onMousewheel); - this.hammertime = hammertime; - } + // 注册事件在地图底图上 + this.addEventListenerOnMap(); } public destroy() { if (this.hammertime) { this.hammertime.destroy(); } - const $containter = this.rendererService.getContainer(); + this.removeEventListenerOnMap(); + this.off(InteractionEvent.Hover); + } + + private addEventListenerOnMap() { + const $containter = this.mapService.getMapContainer(); if ($containter) { - // $containter.removeEventListener('wheel', this.onMousewheel); + const hammertime = new Hammer($containter); + hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); + hammertime.get('pinch').set({ enable: true }); + + // hammertime.on('panstart', this.onPanstart); + // hammertime.on('panmove', this.onPanmove); + // hammertime.on('panend', this.onPanend); + // hammertime.on('pinch', this.onPinch); + + $containter.addEventListener('mousemove', this.onHover); + this.hammertime = hammertime; + + // TODO: 根据场景注册事件到 L7 canvas 上 + this.logger.info('add event listeners on canvas'); } } - private onPanmove = (e: HammerInput) => { - // @ts-ignore - // this.logger.info(e); - // if (this.isMoving) { - // this.deltaX = e.center.x - this.lastX; - // this.deltaY = e.center.y - this.lastY; - // this.lastX = e.center.x; - // this.lastY = e.center.y; - // this.emit(Mouse.MOVE_EVENT, { - // deltaX: this.deltaX, - // deltaY: this.deltaY, - // deltaZ: this.deltaZ - // }); - // } + private removeEventListenerOnMap() { + const $containter = this.mapService.getMapContainer(); + if ($containter) { + $containter.removeEventListener('mousemove', this.onHover); + } + } + + private onHover = ({ x, y }: MouseEvent) => { + this.emit(InteractionEvent.Hover, { x, y }); }; } diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 2bbe5868bd..785d6e061e 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -2,62 +2,33 @@ import { AsyncParallelHook, SyncHook } from 'tapable'; import { IModel } from '../renderer/IModel'; import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer'; import { ISource, ISourceCFG } from '../source/ISourceService'; -import { ILayerStyleOptions } from './ILayerStyleService'; -export enum ScaleTypes { - LINEAR = 'linear', - POWER = 'power', - LOG = 'log', - IDENTITY = 'identity', - TIME = 'time', - QUANTILE = 'quantile', - QUANTIZE = 'quantize', - THRESHOLD = 'threshold', - CAT = 'cat', -} -export enum StyleScaleType { - CONSTANT = 'constant', - VARIABLE = 'variable', -} -export interface IScaleOption { - field?: string; - type: ScaleTypes; - ticks?: any[]; - nice?: boolean; - format?: () => any; - domain?: any[]; -} -export interface IStyleScale { - scale: any; - field: string; - type: StyleScaleType; - option: IScaleOption; -} +import { + IEncodeFeature, + IScale, + IStyleAttributeService, + StyleAttrField, + StyleAttributeOption, +} from './IStyleAttributeService'; export interface ILayerGlobalConfig { colors: string[]; size: number; shape: string; scales: { - [key: string]: IScaleOption; + [key: string]: IScale; }; } -type CallBack = (...args: any[]) => any; -export type StyleAttributeField = string | string[]; -export type StyleAttributeOption = string | number | boolean | any[] | CallBack; -export type StyleAttrField = string | string[] | number | number[]; -export interface ILayerStyleAttribute { - type: string; - names: string[]; - field: StyleAttributeField; - values?: any[]; - scales?: IStyleScale[]; - setScales: (scales: IStyleScale[]) => void; - callback?: (...args: any[]) => []; - mapping?(...params: unknown[]): unknown[]; + +export interface IPickedFeature { + x: number; + y: number; + lnglat?: { lng: number; lat: number }; + feature?: unknown; } export interface ILayer { - name: string; + id: string; // 一个场景中同一类型 Layer 可能存在多个 + name: string; // 代表 Layer 的类型 // visible: boolean; // zIndex: number; // type: string; @@ -67,16 +38,20 @@ export interface ILayer { init: SyncHook; beforeRender: SyncHook; afterRender: SyncHook; + beforePickingEncode: SyncHook; + afterPickingEncode: SyncHook; + beforeHighlight: SyncHook; + afterHighlight: SyncHook; + beforeDestroy: SyncHook; + afterDestroy: SyncHook; }; models: IModel[]; - styleAttributes: { - [attributeName: string]: ILayerStyleAttribute; - }; sourceOption: { data: any; options?: ISourceCFG; }; multiPassRenderer: IMultiPassRenderer; + styleAttributeService: IStyleAttributeService; init(): ILayer; size(field: StyleAttrField, value?: StyleAttributeOption): ILayer; color(field: StyleAttrField, value?: StyleAttributeOption): ILayer; @@ -84,7 +59,7 @@ export interface ILayer { // pattern(field: string, value: StyleAttributeOption): ILayer; // filter(field: string, value: StyleAttributeOption): ILayer; // active(option: ActiveOption): ILayer; - style(options: ILayerStyleOptions): ILayer; + style(options: unknown): ILayer; // hide(): ILayer; // show(): ILayer; // animate(field: string, option: any): ILayer; @@ -94,11 +69,15 @@ export interface ILayer { addPlugin(plugin: ILayerPlugin): ILayer; getSource(): ISource; setSource(source: ISource): void; - setEncodedData(encodedData: Array<{ [key: string]: unknown }>): void; - getEncodedData(): Array<{ [key: string]: unknown }>; - getInitializationOptions(): Partial; + setEncodedData(encodedData: IEncodeFeature[]): void; + getEncodedData(): IEncodeFeature[]; + getStyleOptions(): Partial; + isDirty(): boolean; } +/** + * Layer 插件 + */ export interface ILayerPlugin { apply(layer: ILayer): void; } @@ -108,8 +87,22 @@ export interface ILayerPlugin { */ export interface ILayerInitializationOptions { enableMultiPassRenderer: boolean; - enablePicking: boolean; passes: Array; + + /** + * 开启拾取 + */ + enablePicking: boolean; + /** + * 开启高亮 + */ + enableHighlight: boolean; + /** + * 高亮颜色 + */ + highlightColor: string | number[]; + onHover(pickedFeature: IPickedFeature): void; + onClick(pickedFeature: IPickedFeature): void; } /** @@ -119,5 +112,5 @@ export interface ILayerService { add(layer: ILayer): void; initLayers(): void; renderLayers(): void; - clean(): void; + destroy(): void; } diff --git a/packages/core/src/services/layer/ILayerStyleService.ts b/packages/core/src/services/layer/ILayerStyleService.ts deleted file mode 100644 index fcdaf32e84..0000000000 --- a/packages/core/src/services/layer/ILayerStyleService.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * 1. 提供各个 Layer 样式属性初始值的注册服务 - * 2. 当 Layer 通过 style() 改变某些样式属性时,需要感知并标记该属性已经失效, - * 随后当 Layer 重绘时通过 dirty 标记进行脏检查。重新传入 uniform 或者构建顶点数据(更新 Buffer 中的指定位置)。 - * @see https://yuque.antfin-inc.com/yuqi.pyq/fgetpa/qfuzg8 - */ - -type OptionType = 'attribute' | 'uniform'; -type Color = [number, number, number]; -type StyleOption = - | { - type: OptionType; - value: string | number | number[] | Color; - dirty: boolean; - } - | string - | number - | number[] - | Color; - -export interface ILayerStyleOptions { - [key: string]: StyleOption; -} - -// tslint:disable-next-line:no-empty-interface -export default interface ILayerStyleService { - registerDefaultStyleOptions( - layerName: string, - options: ILayerStyleOptions, - ): void; -} diff --git a/packages/core/src/services/layer/IStyleAttributeService.ts b/packages/core/src/services/layer/IStyleAttributeService.ts new file mode 100644 index 0000000000..23bf7384cc --- /dev/null +++ b/packages/core/src/services/layer/IStyleAttributeService.ts @@ -0,0 +1,157 @@ +import { + IAttribute, + IAttributeInitializationOptions, +} from '../renderer/IAttribute'; +import { IBufferInitializationOptions } from '../renderer/IBuffer'; +import { IElements } from '../renderer/IElements'; +import { ILayer } from './ILayerService'; + +/** + * 1. 提供各个 Layer 样式属性初始值的注册服务 + * 2. 当 Layer 通过 style() 改变某些样式属性时,需要感知并标记该属性已经失效, + * 随后当 Layer 重绘时通过 dirty 标记进行脏检查。重新传入 uniform 或者构建顶点数据(更新 Buffer 中的指定位置)。 + * @see https://yuque.antfin-inc.com/yuqi.pyq/fgetpa/qfuzg8 + */ + +export enum ScaleTypes { + LINEAR = 'linear', + POWER = 'power', + LOG = 'log', + IDENTITY = 'identity', + TIME = 'time', + QUANTILE = 'quantile', + QUANTIZE = 'quantize', + THRESHOLD = 'threshold', + CAT = 'cat', +} + +export interface IScale { + type: ScaleTypes; + ticks?: any[]; + nice?: boolean; + format?: () => any; + domain?: any[]; +} + +export enum AttributeType { + Attribute, + InstancedAttribute, + Uniform, +} + +export interface IEncodeFeature { + color?: Color; + size?: number | number[]; + shape?: string | number; + pattern?: string; + id?: number; + coordinates: Position[][]; +} + +export interface IVertexAttributeDescriptor + extends Omit { + /** + * attribute name in vertex shader + */ + name: string; + /** + * 创建 buffer 的参数 + */ + buffer: IBufferInitializationOptions; + update?: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + ) => number[]; +} + +type Position = number[]; +type Color = [number, number, number, number]; +type CallBack = (...args: any[]) => any; +export type StyleAttributeField = string | string[]; +export type StyleAttributeOption = string | number | boolean | any[] | CallBack; +export type StyleAttrField = string | string[] | number | number[]; + +export interface IStyleAttributeInitializationOptions { + name: string; + type: AttributeType; + scale?: { + field: StyleAttributeField; + values: unknown[]; + callback?: (...args: any[]) => []; + scalers?: Array<{ + field: string; + func: unknown; + }>; + }; + descriptor: IVertexAttributeDescriptor; +} + +export interface IFeatureRange { + startIndex: number; + endIndex: number; +} + +export interface IStyleAttribute extends IStyleAttributeInitializationOptions { + needRescale: boolean; + needRemapping: boolean; + needRegenerateVertices: boolean; + featureRange: IFeatureRange; + /** + * 保存渲染层 IAttribute 引用 + */ + vertexAttribute: IAttribute; + mapping?(...params: unknown[]): unknown[]; + setProps(props: Partial): void; +} + +export type Triangulation = ( + feature: IEncodeFeature, +) => { + vertices: number[]; + indices: number[]; + size: number; +}; + +export interface IStyleAttributeUpdateOptions { + featureRange: IFeatureRange; +} + +export interface IStyleAttributeService { + // registerDefaultStyleOptions( + // layerName: string, + // options: ILayerStyleOptions, + // ): void; + registerStyleAttribute( + options: Partial, + ): IStyleAttribute; + updateStyleAttribute( + attributeName: string, + attributeOptions: Partial, + updateOptions: IStyleAttributeUpdateOptions, + ): void; + getLayerStyleAttributes(): IStyleAttribute[] | undefined; + getLayerStyleAttribute(attributeName: string): IStyleAttribute | undefined; + createAttributesAndIndices( + encodedFeatures: IEncodeFeature[], + triangulation: Triangulation, + ): { + attributes: { + [attributeName: string]: IAttribute; + }; + elements: IElements; + }; + /** + * 根据 feature range 更新指定属性 + */ + updateAttributeByFeatureRange( + attributeName: string, + features: IEncodeFeature[], + startFeatureIdx?: number, + endFeatureIdx?: number, + ): void; + /** + * 清除当前管理的所有属性 + */ + clearAllAttributes(): void; +} diff --git a/packages/core/src/services/layer/LayerService.ts b/packages/core/src/services/layer/LayerService.ts index 389af14056..9581a61136 100644 --- a/packages/core/src/services/layer/LayerService.ts +++ b/packages/core/src/services/layer/LayerService.ts @@ -30,16 +30,19 @@ export default class LayerService implements ILayerService { } public renderLayers() { - this.layers.forEach((layer) => { - // trigger hooks - layer.hooks.beforeRender.call(layer); - layer.render(); - layer.hooks.afterRender.call(layer); - }); + // TODO:脏检查,只渲染发生改变的 Layer + this.layers + // .filter((layer) => layer.isDirty()) + .forEach((layer) => { + // trigger hooks + layer.hooks.beforeRender.call(layer); + layer.render(); + layer.hooks.afterRender.call(layer); + }); } - public clean() { - // TODO: destroy every layer first + public destroy() { + this.layers.forEach((layer) => layer.destroy()); this.layers = []; } } diff --git a/packages/core/src/services/layer/LayerStyleService.ts b/packages/core/src/services/layer/LayerStyleService.ts deleted file mode 100644 index 2a4f0ee9d1..0000000000 --- a/packages/core/src/services/layer/LayerStyleService.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { inject, injectable } from 'inversify'; -import ILayerStyleService, { ILayerStyleOptions } from './ILayerStyleService'; - -@injectable() -export default class LayerStyleService implements ILayerStyleService { - private registry: { - [layerName: string]: ILayerStyleOptions; - } = {}; - - public registerDefaultStyleOptions( - layerName: string, - options: ILayerStyleOptions, - ) { - if (!this.registry[layerName]) { - this.registry[layerName] = options; - } - } -} diff --git a/packages/core/src/services/layer/StyleAttribute.ts b/packages/core/src/services/layer/StyleAttribute.ts new file mode 100644 index 0000000000..184a510303 --- /dev/null +++ b/packages/core/src/services/layer/StyleAttribute.ts @@ -0,0 +1,80 @@ +import { IStyleAttribute } from '@l7/core'; +import { isNil } from 'lodash'; +import { IAttribute } from '../renderer/IAttribute'; +import { IBuffer } from '../renderer/IBuffer'; +import { + AttributeType, + IEncodeFeature, + IFeatureRange, + IStyleAttributeInitializationOptions, + IVertexAttributeDescriptor, +} from './IStyleAttributeService'; + +export default class StyleAttribute implements IStyleAttribute { + public name: string; + public type: AttributeType; + public scale?: { + field: string | string[]; + values: unknown[]; + callback?: (...args: any[]) => []; + scalers?: Array<{ + field: string; + func: unknown; + }>; + }; + public descriptor: IVertexAttributeDescriptor; + public featureBufferLayout: Array<{ + feature: IEncodeFeature; + featureIdx: number; + bufferOffset: number; + length: number; + }> = []; + + public needRescale: boolean = false; + public needRemapping: boolean = false; + public needRegenerateVertices: boolean = false; + public featureRange: IFeatureRange = { + startIndex: 0, + endIndex: Infinity, + }; + public vertexAttribute: IAttribute; + + private buffer: IBuffer; + + constructor(options: Partial) { + this.setProps(options); + } + + public setProps(options: Partial) { + Object.assign(this, options); + } + + public mapping(params: unknown[]): unknown[] { + /** + * 当用户设置的 callback 返回 null 时, 应该返回默认 callback 中的值 + */ + if (this!.scale!.callback) { + // 使用用户返回的值处理 + const ret = this!.scale!.callback(params); + if (!isNil(ret)) { + return [ret]; + } + } + + // 没有 callback 或者用户 callback 返回值为空,则使用默认的逻辑处理 + return this.defaultCallback(params); + } + + private defaultCallback = (params: unknown[]): unknown[] => { + // 没有 params 的情况,是指没有指定 fields,直接返回配置的 values 常量 + if (params.length === 0) { + return this!.scale!.values; + } + return params.map((param, idx) => { + const scaleFunc = this.scale!.scalers![idx].func; + // @ts-ignore + const value = scaleFunc(param); + return value; + }); + }; +} diff --git a/packages/core/src/services/layer/StyleAttributeService.ts b/packages/core/src/services/layer/StyleAttributeService.ts new file mode 100644 index 0000000000..f1a6a89b21 --- /dev/null +++ b/packages/core/src/services/layer/StyleAttributeService.ts @@ -0,0 +1,268 @@ +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../types'; +import { gl } from '../renderer/gl'; +import { IAttribute } from '../renderer/IAttribute'; +import { IElements } from '../renderer/IElements'; +import { IRendererService } from '../renderer/IRendererService'; +import { ILayer } from './ILayerService'; +import { + IEncodeFeature, + IStyleAttribute, + IStyleAttributeInitializationOptions, + IStyleAttributeService, + IStyleAttributeUpdateOptions, + IVertexAttributeDescriptor, + Triangulation, +} from './IStyleAttributeService'; +import StyleAttribute from './StyleAttribute'; + +const bytesPerElementMap = { + [gl.FLOAT]: 4, + [gl.UNSIGNED_BYTE]: 1, + [gl.UNSIGNED_SHORT]: 2, +}; + +/** + * 每个 Layer 都拥有一个,用于管理样式属性的注册和更新 + */ +@injectable() +export default class StyleAttributeService implements IStyleAttributeService { + @inject(TYPES.IRendererService) + private readonly rendererService: IRendererService; + + private attributes: IStyleAttribute[] = []; + + private featureLayout: { + sizePerElement: number; + elements: Array<{ + featureIdx: number; + vertices: number[]; + offset: number; + }>; + } = { + sizePerElement: 0, + elements: [], + }; + + public registerStyleAttribute( + options: Partial, + ) { + let attributeToUpdate = + options.name && this.getLayerStyleAttribute(options.name); + if (attributeToUpdate) { + attributeToUpdate.setProps(options); + } else { + attributeToUpdate = new StyleAttribute(options); + this.attributes.push(attributeToUpdate); + } + return attributeToUpdate; + } + + public updateStyleAttribute( + attributeName: string, + options: Partial, + updateOptions?: Partial, + ) { + let attributeToUpdate = this.getLayerStyleAttribute(attributeName); + if (!attributeToUpdate) { + attributeToUpdate = this.registerStyleAttribute({ + ...options, + name: attributeName, + }); + } + const { scale } = options; + if (scale) { + // TODO: 需要比较新旧值确定是否需要 rescale + // 需要重新 scale,肯定也需要重新进行数据映射 + attributeToUpdate.scale = scale; + attributeToUpdate.needRescale = true; + attributeToUpdate.needRemapping = true; + attributeToUpdate.needRegenerateVertices = true; + if (updateOptions && updateOptions.featureRange) { + attributeToUpdate.featureRange = updateOptions.featureRange; + } + } + } + + public getLayerStyleAttributes(): IStyleAttribute[] | undefined { + return this.attributes; + } + + public getLayerStyleAttribute( + attributeName: string, + ): IStyleAttribute | undefined { + return this.attributes.find( + (attribute) => attribute.name === attributeName, + ); + } + + public updateAttributeByFeatureRange( + attributeName: string, + features: IEncodeFeature[], + startFeatureIdx: number = 0, + endFeatureIdx?: number, + ) { + const attributeToUpdate = this.attributes.find( + (attribute) => attribute.name === attributeName, + ); + if (attributeToUpdate) { + const { descriptor } = attributeToUpdate; + const { update, buffer, size = 0 } = descriptor; + const bytesPerElement = bytesPerElementMap[buffer.type || gl.FLOAT]; + if (update) { + const { elements, sizePerElement } = this.featureLayout; + // 截取待更新的 feature 范围 + const featuresToUpdate = elements.slice(startFeatureIdx, endFeatureIdx); + + // [n, n] 中断更新 + if (!featuresToUpdate.length) { + return; + } + const { offset } = featuresToUpdate[0]; + // 以 byte 为单位计算 buffer 中的偏移 + const bufferOffsetInBytes = offset * size * bytesPerElement; + const updatedBufferData = featuresToUpdate + .map(({ featureIdx, vertices }) => { + const verticesNumForCurrentFeature = + vertices.length / sizePerElement; + const featureData: number[] = []; + for ( + let vertexIdx = 0; + vertexIdx < verticesNumForCurrentFeature; + vertexIdx++ + ) { + featureData.push( + ...update( + features[featureIdx], + featureIdx, + vertices.slice( + vertexIdx * sizePerElement, + vertexIdx * sizePerElement + sizePerElement, + ), + ), + ); + } + return featureData; + }) + .reduce((prev, cur) => { + prev.push(...cur); + return prev; + }, []); + + // 更新底层 IAttribute 中包含的 IBuffer,使用 subdata + attributeToUpdate.vertexAttribute.updateBuffer({ + data: updatedBufferData, + offset: bufferOffsetInBytes, + }); + } + } + } + + public createAttributesAndIndices( + features: IEncodeFeature[], + triangulation: Triangulation, + ): { + attributes: { + [attributeName: string]: IAttribute; + }; + elements: IElements; + } { + const descriptors = this.attributes.map((attr) => attr.descriptor); + + let verticesNum = 0; + const vertices: number[] = []; + const indices: number[] = []; + let size = 3; + + features.forEach((feature, featureIdx) => { + // 逐 feature 进行三角化 + const { + indices: indicesForCurrentFeature, + vertices: verticesForCurrentFeature, + size: vertexSize, + } = triangulation(feature); + indices.push(...indicesForCurrentFeature.map((i) => i + verticesNum)); + vertices.push(...verticesForCurrentFeature); + size = vertexSize; + const verticesNumForCurrentFeature = + verticesForCurrentFeature.length / vertexSize; + + // 记录三角化结果,用于后续精确更新指定 feature + this.featureLayout.sizePerElement = size; + this.featureLayout.elements.push({ + featureIdx, + vertices: verticesForCurrentFeature, + offset: verticesNum, + }); + + verticesNum += verticesNumForCurrentFeature; + + // 根据 position 顶点生成其他顶点数据 + for ( + let vertexIdx = 0; + vertexIdx < verticesNumForCurrentFeature; + vertexIdx++ + ) { + descriptors.forEach((descriptor, attributeIdx) => { + if (descriptor.update) { + (descriptor.buffer.data as number[]).push( + ...descriptor.update( + feature, + featureIdx, + verticesForCurrentFeature.slice( + vertexIdx * vertexSize, + vertexIdx * vertexSize + vertexSize, + ), + // TODO: 传入顶点索引 vertexIdx + ), + ); + } + }); + } + }); + + const { + createAttribute, + createBuffer, + createElements, + } = this.rendererService; + + const attributes: { + [attributeName: string]: IAttribute; + } = {}; + + descriptors.forEach((descriptor, attributeIdx) => { + // IAttribute 参数透传 + const { buffer, update, name, ...rest } = descriptor; + + const vertexAttribute = createAttribute({ + // IBuffer 参数透传 + buffer: createBuffer(buffer), + ...rest, + }); + attributes[descriptor.name || ''] = vertexAttribute; + + // 在 StyleAttribute 上保存对 VertexAttribute 的引用 + this.attributes[attributeIdx].vertexAttribute = vertexAttribute; + }); + + const elements = createElements({ + data: indices, + type: gl.UNSIGNED_INT, + count: indices.length, + }); + + return { + attributes, + elements, + }; + } + + public clearAllAttributes() { + // 销毁关联的 vertex attribute buffer objects + this.attributes.forEach((attribute) => { + attribute.vertexAttribute.destroy(); + }); + this.attributes = []; + } +} diff --git a/packages/core/src/services/map/IMapService.ts b/packages/core/src/services/map/IMapService.ts index afe47e6e0e..d74df86672 100644 --- a/packages/core/src/services/map/IMapService.ts +++ b/packages/core/src/services/map/IMapService.ts @@ -11,16 +11,18 @@ export interface IPoint { } export interface IMapService { init(config: Partial): void; + destroy(): void; onCameraChanged(callback: (viewport: IViewport) => void): void; - // get map status method + // get map params + getType(): MapType; getZoom(): number; getCenter(): ILngLat; getPitch(): number; getRotation(): number; getBounds(): Bounds; + getMapContainer(): HTMLElement | null; - // set Map status - + // control with raw map setRotation(rotation: number): void; zoomIn(): void; zoomOut(): void; @@ -30,12 +32,13 @@ export interface IMapService { setZoomAndCenter(zoom: number, center: Point): void; setMapStyle(style: string): void; - // conversion Method + // coordinates methods pixelToLngLat(pixel: Point): ILngLat; lngLatToPixel(lnglat: Point): IPoint; containerToLngLat(pixel: Point): ILngLat; lngLatToContainer(lnglat: Point): IPoint; } + export enum MapType { amap = 'amap', mapbox = 'mapbox', diff --git a/packages/core/src/services/renderer/IAttribute.ts b/packages/core/src/services/renderer/IAttribute.ts index a8379c609f..ac99d6ba09 100644 --- a/packages/core/src/services/renderer/IAttribute.ts +++ b/packages/core/src/services/renderer/IAttribute.ts @@ -26,5 +26,11 @@ export interface IAttributeInitializationOptions { } export interface IAttribute { + updateBuffer(options: { + // 用于替换的数据 + data: number[] | number[][] | Uint8Array | Uint16Array | Uint32Array; + // 原 Buffer 替换位置,单位为 byte + offset: number; + }): void; destroy(): void; } diff --git a/packages/core/src/services/renderer/IElements.ts b/packages/core/src/services/renderer/IElements.ts index 016718e6eb..a1b9402958 100644 --- a/packages/core/src/services/renderer/IElements.ts +++ b/packages/core/src/services/renderer/IElements.ts @@ -21,6 +21,7 @@ export interface IElementsInitializationOptions { | gl.TRIANGLES | gl.TRIANGLE_STRIP | gl.TRIANGLE_FAN; + count?: number; } export interface IElements { diff --git a/packages/core/src/services/renderer/IRendererService.ts b/packages/core/src/services/renderer/IRendererService.ts index b0b15786f3..44ef5fe87c 100644 --- a/packages/core/src/services/renderer/IRendererService.ts +++ b/packages/core/src/services/renderer/IRendererService.ts @@ -29,6 +29,16 @@ export interface IClearOptions { framebuffer?: IFramebuffer | null; } +export interface IReadPixelsOptions { + x: number; + y: number; + width: number; + height: number; + // gl.bindFrameBuffer + framebuffer?: IFramebuffer; + data?: Uint8Array; +} + export interface IRendererService { init($container: HTMLDivElement): Promise; clear(options: IClearOptions): void; @@ -38,11 +48,13 @@ export interface IRendererService { createElements(options: IElementsInitializationOptions): IElements; createTexture2D(options: ITexture2DInitializationOptions): ITexture2D; createFramebuffer(options: IFramebufferInitializationOptions): IFramebuffer; - renderToFramebuffer( + useFramebuffer( framebuffer: IFramebuffer | null, drawCommands: () => void, ): void; getViewportSize(): { width: number; height: number }; getContainer(): HTMLElement | null; viewport(size: { x: number; y: number; width: number; height: number }): void; + readPixels(options: IReadPixelsOptions): Uint8Array; + destroy(): void; } diff --git a/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts b/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts index 952400bfa1..2150f1392e 100644 --- a/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts +++ b/packages/core/src/services/renderer/passes/BasePostProcessingPass.ts @@ -81,9 +81,9 @@ export default class BasePostProcessingPass public render(layer: ILayer) { const postProcessor = layer.multiPassRenderer.getPostProcessor(); - const { renderToFramebuffer } = this.rendererService; + const { useFramebuffer } = this.rendererService; - renderToFramebuffer( + useFramebuffer( this.renderToScreen ? null : postProcessor.getWriteFBO(), () => { this.model.draw({ diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index bbf7f7286e..b95f2da9d1 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -1,28 +1,63 @@ import { inject, injectable } from 'inversify'; import { lazyInject } from '../../../index'; import { TYPES } from '../../../types'; +import { + IInteractionService, + InteractionEvent, +} from '../../interaction/IInteractionService'; import { ILayer, ILayerService } from '../../layer/ILayerService'; +import { ILogService } from '../../log/ILogService'; import { gl } from '../gl'; import { IFramebuffer } from '../IFramebuffer'; import { IPass, PassType } from '../IMultiPassRenderer'; import { IRendererService } from '../IRendererService'; +function decodePickingColor(color: Uint8Array): number { + const [i1, i2, i3] = color; + // 1 was added to seperate from no selection + const index = i1 + i2 * 256 + i3 * 65536 - 1; + return index; +} + /** - * PixelPickingPass based on + * color-based PixelPickingPass + * @see https://github.com/antvis/L7/blob/next/dev-docs/PixelPickingEngine.md */ @injectable() export default class PixelPickingPass implements IPass { @lazyInject(TYPES.IRendererService) protected readonly rendererService: IRendererService; + @lazyInject(TYPES.IInteractionService) + protected readonly interactionService: IInteractionService; + + @lazyInject(TYPES.ILogService) + protected readonly logger: ILogService; + + /** + * picking framebuffer,供 attributes 颜色编码后输出 + */ private pickingFBO: IFramebuffer; + /** + * 保存 layer 引用 + */ + private layer: ILayer; + + /** + * 简单的 throttle,防止连续触发 hover 时导致频繁渲染到 picking framebuffer + */ + private alreadyInRendering: boolean = false; + public getType() { return PassType.Normal; } public init(layer: ILayer) { + this.layer = layer; const { createTexture2D, createFramebuffer } = this.rendererService; + + // 创建 picking framebuffer,后续实时 resize this.pickingFBO = createFramebuffer({ color: createTexture2D({ width: 1, @@ -31,16 +66,147 @@ export default class PixelPickingPass implements IPass { wrapT: gl.CLAMP_TO_EDGE, }), }); + + // 监听 hover 事件 + this.interactionService.on(InteractionEvent.Hover, this.pickFromPickingFBO); } public render(layer: ILayer) { - const { getViewportSize, renderToFramebuffer } = this.rendererService; - this.pickingFBO.resize(getViewportSize()); + if (this.alreadyInRendering) { + return; + } - renderToFramebuffer(this.pickingFBO, () => { - layer.multiPassRenderer.setRenderFlag(false); + const { getViewportSize, useFramebuffer, clear } = this.rendererService; + const { width, height } = getViewportSize(); + + // throttled + this.alreadyInRendering = true; + + // resize first, fbo can't be resized in use + this.pickingFBO.resize({ width, height }); + + useFramebuffer(this.pickingFBO, () => { + clear({ + framebuffer: this.pickingFBO, + color: [0, 0, 0, 0], + stencil: 0, + depth: 1, + }); + + this.logger.info(`picking fbo cleared ${width} ${height}`); + + /** + * picking pass 不需要 multipass,原因如下: + * 1. 已经 clear,无需 ClearPass + * 2. 只需要 RenderPass + * 3. 后处理 pass 需要跳过 + */ + const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag(); + this.layer.multiPassRenderer.setRenderFlag(false); + // trigger hooks + layer.hooks.beforeRender.call(layer); layer.render(); - layer.multiPassRenderer.setRenderFlag(true); + layer.hooks.afterRender.call(layer); + this.layer.multiPassRenderer.setRenderFlag(originRenderFlag); + + this.alreadyInRendering = false; }); } + + /** + * 拾取视口指定坐标属于的要素 + * TODO:支持区域拾取 + */ + private pickFromPickingFBO = ({ x, y }: { x: number; y: number }) => { + const { + getViewportSize, + readPixels, + useFramebuffer, + } = this.rendererService; + const { width, height } = getViewportSize(); + const { enableHighlight } = this.layer.getStyleOptions(); + + let pickedColors: Uint8Array | undefined; + useFramebuffer(this.pickingFBO, () => { + // avoid realloc + pickedColors = readPixels({ + x: Math.round(x * window.devicePixelRatio), + // 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴 + y: Math.round(height - (y + 1) * window.devicePixelRatio), + width: 1, + height: 1, + data: new Uint8Array(1 * 1 * 4), + framebuffer: this.pickingFBO, + }); + + this.logger.info('try to picking'); + + if ( + pickedColors[0] !== 0 || + pickedColors[1] !== 0 || + pickedColors[2] !== 0 + ) { + this.logger.info('picked'); + const pickedFeatureIdx = decodePickingColor(pickedColors); + const rawFeature = this.layer.getSource()!.data!.dataArray[ + pickedFeatureIdx + ]; + + // trigger onHover/Click callback on layer + this.triggerHoverOnLayer({ x, y, feature: rawFeature }); + } + }); + + if (enableHighlight) { + this.highlightPickedFeature(pickedColors); + } + }; + + private triggerHoverOnLayer({ + x, + y, + feature, + }: { + x: number; + y: number; + feature: unknown; + }) { + // TODO: onClick + const { onHover, onClick } = this.layer.getStyleOptions(); + if (onHover) { + onHover({ + x, + y, + feature, + }); + } + } + + /** + * highlight 如果直接修改选中 feature 的 buffer,存在两个问题: + * 1. 鼠标移走时无法恢复 + * 2. 无法实现高亮颜色与原始原色的 alpha 混合 + * 因此高亮还是放在 shader 中做比较好 + * @example + * this.layer.color('name', ['#000000'], { + * featureRange: { + * startIndex: pickedFeatureIdx, + * endIndex: pickedFeatureIdx + 1, + * }, + * }); + */ + private highlightPickedFeature(pickedColors: Uint8Array | undefined) { + const [r, g, b] = pickedColors; + + // TODO: highlight pass 需要 multipass + const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag(); + this.layer.multiPassRenderer.setRenderFlag(false); + this.layer.hooks.beforeRender.call(this.layer); + // @ts-ignore + this.layer.hooks.beforeHighlight.call(this.layer, [r, g, b]); + this.layer.render(); + this.layer.hooks.afterHighlight.call(this.layer); + this.layer.hooks.afterRender.call(this.layer); + this.layer.multiPassRenderer.setRenderFlag(originRenderFlag); + } } diff --git a/packages/core/src/services/renderer/passes/RenderPass.ts b/packages/core/src/services/renderer/passes/RenderPass.ts index 4a4b770cb7..29f2ea5bcf 100644 --- a/packages/core/src/services/renderer/passes/RenderPass.ts +++ b/packages/core/src/services/renderer/passes/RenderPass.ts @@ -22,9 +22,9 @@ export default class RenderPass implements IPass { } public render(layer: ILayer) { - const { renderToFramebuffer, clear } = this.rendererService; + const { useFramebuffer, clear } = this.rendererService; const readFBO = layer.multiPassRenderer.getPostProcessor().getReadFBO(); - renderToFramebuffer(readFBO, () => { + useFramebuffer(readFBO, () => { clear({ color: [0, 0, 0, 0], depth: 1, diff --git a/packages/core/src/services/scene/SceneService.ts b/packages/core/src/services/scene/SceneService.ts index 62b58dbc6f..3dc5e54a29 100644 --- a/packages/core/src/services/scene/SceneService.ts +++ b/packages/core/src/services/scene/SceneService.ts @@ -121,7 +121,6 @@ export default class Scene extends EventEmitter implements ISceneService { // 初始化 container 上的交互 this.interactionService.init(); - // TODO:init renderer this.logger.info('renderer loaded'); }); @@ -148,11 +147,13 @@ export default class Scene extends EventEmitter implements ISceneService { } public destroy() { - this.emit('destory'); + this.emit('destroy'); this.inited = false; - this.layerService.clean(); + this.layerService.destroy(); this.configService.reset(); this.interactionService.destroy(); + this.rendererService.destroy(); + this.map.destroy(); window.removeEventListener('resize', this.handleWindowResized, false); } private handleWindowResized = () => { diff --git a/packages/core/src/services/shader/ShaderModuleService.ts b/packages/core/src/services/shader/ShaderModuleService.ts index 6a67709e56..b6a734c549 100644 --- a/packages/core/src/services/shader/ShaderModuleService.ts +++ b/packages/core/src/services/shader/ShaderModuleService.ts @@ -5,6 +5,8 @@ import { IModuleParams, IShaderModuleService } from './IShaderModuleService'; import decode from '../../shaders/decode.glsl'; import lighting from '../../shaders/lighting.glsl'; +import pickingFrag from '../../shaders/picking.frag.glsl'; +import pickingVert from '../../shaders/picking.vert.glsl'; import projection from '../../shaders/projection.glsl'; import sdf2d from '../../shaders/sdf_2d.glsl'; @@ -23,6 +25,7 @@ export default class ShaderModuleService implements IShaderModuleService { this.registerModule('projection', { vs: projection, fs: '' }); this.registerModule('sdf_2d', { vs: '', fs: sdf2d }); this.registerModule('lighting', { vs: lighting, fs: '' }); + this.registerModule('picking', { vs: pickingVert, fs: pickingFrag }); } public registerModule(moduleName: string, moduleParams: IModuleParams) { diff --git a/packages/core/src/shaders/picking.frag.glsl b/packages/core/src/shaders/picking.frag.glsl new file mode 100644 index 0000000000..66e78052ac --- /dev/null +++ b/packages/core/src/shaders/picking.frag.glsl @@ -0,0 +1,46 @@ +varying vec4 v_PickingResult; +uniform vec4 u_HighlightColor : [0, 0, 0, 0]; +uniform float u_PickingStage : 0.0; + +#define PICKING_NONE 0.0 +#define PICKING_ENCODE 1.0 +#define PICKING_HIGHLIGHT 2.0 +#define COLOR_SCALE 1. / 255. + +/* + * Returns highlight color if this item is selected. + */ +vec4 filterHighlightColor(vec4 color) { + bool selected = bool(v_PickingResult.a); + + if (selected) { + vec4 highLightColor = u_HighlightColor * COLOR_SCALE; + + float highLightAlpha = highLightColor.a; + float highLightRatio = highLightAlpha / (highLightAlpha + color.a * (1.0 - highLightAlpha)); + + vec3 resultRGB = mix(color.rgb, highLightColor.rgb, highLightRatio); + return vec4(resultRGB, color.a); + } else { + return color; + } +} + +/* + * Returns picking color if picking enabled else unmodified argument. + */ +vec4 filterPickingColor(vec4 color) { + vec3 pickingColor = v_PickingResult.rgb; + if (u_PickingStage == PICKING_ENCODE && length(pickingColor) < 0.001) { + discard; + } + return u_PickingStage == PICKING_ENCODE ? vec4(pickingColor, 1.0) : color; +} + +/* + * Returns picking color if picking is enabled if not + * highlight color if this item is selected, otherwise unmodified argument. + */ +vec4 filterColor(vec4 color) { + return filterPickingColor(filterHighlightColor(color)); +} \ No newline at end of file diff --git a/packages/core/src/shaders/picking.vert.glsl b/packages/core/src/shaders/picking.vert.glsl new file mode 100644 index 0000000000..7b1f2c8b5a --- /dev/null +++ b/packages/core/src/shaders/picking.vert.glsl @@ -0,0 +1,27 @@ +attribute vec3 a_PickingColor; +varying vec4 v_PickingResult; + +uniform vec3 u_PickingColor : [0, 0, 0]; +uniform vec4 u_HighlightColor : [0, 0, 0, 0]; +uniform float u_PickingStage : 0.0; +uniform float u_PickingThreshold : 1.0; + +#define PICKING_NONE 0.0 +#define PICKING_ENCODE 1.0 +#define PICKING_HIGHLIGHT 2.0 +#define COLOR_SCALE 1. / 255. + +bool isVertexPicked(vec3 vertexColor) { + return + abs(vertexColor.r - u_PickingColor.r) < u_PickingThreshold && + abs(vertexColor.g - u_PickingColor.g) < u_PickingThreshold && + abs(vertexColor.b - u_PickingColor.b) < u_PickingThreshold; +} + +void setPickingColor(vec3 pickingColor) { + // compares only in highlight stage + v_PickingResult.a = float((u_PickingStage == PICKING_HIGHLIGHT) && isVertexPicked(pickingColor)); + + // Stores the picking color so that the fragment shader can render it during picking + v_PickingResult.rgb = pickingColor * COLOR_SCALE; +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b6af81e25e..680f377c31 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -11,6 +11,7 @@ const TYPES = { IShaderModuleService: Symbol.for('IShaderModuleService'), IIconService: Symbol.for('IIconService'), IInteractionService: Symbol.for('IInteractionService'), + IStyleAttributeService: Symbol.for('IStyleAttributeService'), /** multi-pass */ ClearPass: Symbol.for('ClearPass'), diff --git a/packages/layers/src/core/BaseBuffer.ts b/packages/layers/src/core/BaseBuffer.ts deleted file mode 100644 index c4a977af49..0000000000 --- a/packages/layers/src/core/BaseBuffer.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { IICONMap, ILayerStyleOptions } from '@l7/core'; -import { lngLatToMeters } from '@l7/utils'; -import { vec3 } from 'gl-matrix'; -import { IExtrudeGeomety } from '../point/shape/extrude'; -interface IBufferCfg { - data: unknown[]; - iconMap?: IICONMap; - style?: ILayerStyleOptions; -} -export type Position = number[]; -type Color = [number, number, number, number]; -export interface IBufferInfo { - vertices?: any; - indexArray?: any; - indexOffset: any; - verticesOffset: number; - faceNum?: any; - dimensions: number; - [key: string]: any; -} -export interface IEncodeFeature { - color?: Color; - size?: number | number[]; - shape?: string | number; - pattern?: string; - id?: number; - coordinates: unknown; - bufferInfo: unknown; -} - -export default class Buffer { - public attributes: { - [key: string]: Float32Array; - } = {}; - public verticesCount: number = 0; - public indexArray: Uint32Array = new Uint32Array(0); - public indexCount: number = 0; - public instanceGeometry: IExtrudeGeomety; - protected data: unknown[]; - protected iconMap: IICONMap; - protected style: any; - - constructor({ data, iconMap, style }: IBufferCfg) { - this.data = data; - this.iconMap = iconMap as IICONMap; - this.style = style; - this.init(); - } - public computeVertexNormals( - field: string = 'positions', - flag: boolean = true, - ) { - const normals = (this.attributes.normals = new Float32Array( - this.verticesCount * 3, - )); - const indexArray = this.indexArray; - const positions = this.attributes[field]; - let vA; - let vB; - let vC; - const cb = vec3.create(); - const ab = vec3.create(); - const normal = vec3.create(); - for (let i = 0, li = indexArray.length; i < li; i += 3) { - vA = indexArray[i + 0] * 3; - vB = indexArray[i + 1] * 3; - vC = indexArray[i + 2] * 3; - const [ax, ay] = flag - ? lngLatToMeters([positions[vA], positions[vA + 1]]) - : [positions[vA], positions[vA + 1]]; - const pA = vec3.fromValues(ax, ay, positions[vA + 2]); - const [bx, by] = flag - ? lngLatToMeters([positions[vB], positions[vB + 1]]) - : [positions[vB], positions[vB + 1]]; - const pB = vec3.fromValues(bx, by, positions[vB + 2]); - const [cx, cy] = flag - ? lngLatToMeters([positions[vC], positions[vC + 1]]) - : [positions[vC], positions[vC + 1]]; - const pC = vec3.fromValues(cx, cy, positions[vC + 2]); - vec3.sub(cb, pC, pB); - vec3.sub(ab, pA, pB); - vec3.cross(normal, cb, ab); - normals[vA] += cb[0]; - normals[vA + 1] += cb[1]; - normals[vA + 2] += cb[2]; - normals[vB] += cb[0]; - normals[vB + 1] += cb[1]; - normals[vB + 2] += cb[2]; - normals[vC] += cb[0]; - normals[vC + 1] += cb[1]; - normals[vC + 2] += cb[2]; - } - this.normalizeNormals(); - } - - // 计算每个要素顶点个数,记录索引位置 - protected calculateFeatures() { - throw new Error('Method not implemented.'); - } - protected buildFeatures() { - throw new Error('Method not implemented.'); - } - - protected checkIsClosed(points: Position[][]) { - const p1 = points[0][0]; - const p2 = points[0][points[0].length - 1]; - return p1[0] === p2[0] && p1[1] === p2[1]; - } - - protected concat(arrayType: Float32Array, arrays: any) { - let totalLength = 0; - for (const arr of arrays) { - totalLength += arr.length; - } - const arrayBuffer = new ArrayBuffer( - totalLength * arrayType.BYTES_PER_ELEMENT, - ); - let offset = 0; - // @ts-ignore - const result = new arrayType(arrayBuffer); - for (const arr of arrays) { - result.set(arr, offset); - offset += arr.length; - } - return result; - } - protected encodeArray(feature: IEncodeFeature, num: number) { - const { color, id, pattern, size } = feature; - const bufferInfo = feature.bufferInfo as IBufferInfo; - const { verticesOffset } = bufferInfo; - const imagePos = this.iconMap; - const start1 = verticesOffset; - for (let i = 0; i < num; i++) { - if (color) { - this.attributes.colors[start1 * 4 + i * 4] = color[0]; - this.attributes.colors[start1 * 4 + i * 4 + 1] = color[1]; - this.attributes.colors[start1 * 4 + i * 4 + 2] = color[2]; - this.attributes.colors[start1 * 4 + i * 4 + 3] = color[3]; - } - if (id) { - this.attributes.pickingIds[start1 + i] = id; - } - if (size) { - let size2: number[] = []; - if (Array.isArray(size) && size.length === 2) { - // TODO 多维size支持 - size2 = [size[0], size[0], size[1]]; - } - if (!Array.isArray(size)) { - size2 = [size]; - } - this.attributes.sizes.set(size2, (start1 + i) * size2.length); - } - if (pattern) { - // @ts-ignore - const patternPos = imagePos[pattern] || { x: 0, y: 0 }; - this.attributes.patterns[start1 * 2 + i * 2] = patternPos.x; - this.attributes.patterns[start1 * 2 + i * 2 + 1] = patternPos.y; - } - } - } - protected initAttributes() { - this.attributes.positions = new Float32Array(this.verticesCount * 3); - this.attributes.colors = new Float32Array(this.verticesCount * 4); - this.attributes.pickingIds = new Float32Array(this.verticesCount); - this.attributes.sizes = new Float32Array(this.verticesCount); - this.attributes.pickingIds = new Float32Array(this.verticesCount); - this.indexArray = new Uint32Array(this.indexCount); - } - - private init() { - // 1. 计算 attribute 长度 - this.calculateFeatures(); - // 2. 初始化 attribute - this.initAttributes(); - // 3. 拼接attribute - this.buildFeatures(); - } - - private normalizeNormals() { - const { normals } = this.attributes; - for (let i = 0, li = normals.length; i < li; i += 3) { - const normal = vec3.fromValues( - normals[i], - normals[i + 1], - normals[i + 2], - ); - const newNormal = vec3.create(); - vec3.normalize(newNormal, normal); - normals.set(newNormal, i); - } - } -} diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index a757000890..9a541d3f48 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -1,31 +1,61 @@ import { + IEncodeFeature, IGlobalConfigService, IIconService, ILayer, ILayerInitializationOptions, ILayerPlugin, - ILayerStyleAttribute, - ILayerStyleOptions, IModel, IMultiPassRenderer, IRendererService, - ISource, + IShaderModuleService, ISourceCFG, + IStyleAttributeService, + IStyleAttributeUpdateOptions, lazyInject, StyleAttributeField, StyleAttributeOption, + Triangulation, TYPES, } from '@l7/core'; import Source from '@l7/source'; import { isFunction } from 'lodash'; import { SyncHook } from 'tapable'; -import DataEncodePlugin from '../plugins/DataEncodePlugin'; +import DataMappingPlugin from '../plugins/DataMappingPlugin'; import DataSourcePlugin from '../plugins/DataSourcePlugin'; +import FeatureScalePlugin from '../plugins/FeatureScalePlugin'; import MultiPassRendererPlugin from '../plugins/MultiPassRendererPlugin'; +import PixelPickingPlugin from '../plugins/PixelPickingPlugin'; +import RegisterStyleAttributePlugin from '../plugins/RegisterStyleAttributePlugin'; import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin'; -import StyleAttribute from './StyleAttribute'; +import UpdateStyleAttributePlugin from '../plugins/UpdateStyleAttributePlugin'; -export default class BaseLayer implements ILayer { +export interface ILayerModelInitializationOptions { + moduleName: string; + vertexShader: string; + fragmentShader: string; + triangulation: Triangulation; +} + +/** + * 分配 layer id + */ +let layerIdCounter = 0; + +/** + * Layer 基类默认样式属性 + */ +const defaultLayerInitializationOptions: Partial< + ILayerInitializationOptions +> = { + enableMultiPassRenderer: true, + enablePicking: false, + enableHighlight: false, + highlightColor: 'red', +}; + +export default class BaseLayer implements ILayer { + public id: string = `${layerIdCounter++}`; public name: string; // 生命周期钩子 @@ -33,6 +63,13 @@ export default class BaseLayer implements ILayer { init: new SyncHook(['layer']), beforeRender: new SyncHook(['layer']), afterRender: new SyncHook(['layer']), + beforePickingEncode: new SyncHook(['layer']), + afterPickingEncode: new SyncHook(['layer']), + // @ts-ignore + beforeHighlight: new SyncHook(['layer', 'pickedColor']), + afterHighlight: new SyncHook(['layer']), + beforeDestroy: new SyncHook(['layer']), + afterDestroy: new SyncHook(['layer']), }; // 待渲染 model 列表 @@ -44,37 +81,73 @@ export default class BaseLayer implements ILayer { // 插件集 public plugins: ILayerPlugin[] = [ new DataSourcePlugin(), - new DataEncodePlugin(), + /** + * 根据 StyleAttribute 创建 VertexAttribute + */ + new RegisterStyleAttributePlugin(), + /** + * 根据 Source 创建 Scale + */ + new FeatureScalePlugin(), + /** + * 使用 Scale 进行数据映射 + */ + new DataMappingPlugin(), + /** + * 负责属性更新 + */ + new UpdateStyleAttributePlugin(), + /** + * Multi Pass 自定义渲染管线 + */ new MultiPassRendererPlugin(), + /** + * 传入相机坐标系参数 + */ new ShaderUniformPlugin(), + /** + * 负责拾取过程中 Encode 以及 Highlight 阶段及结束后恢复 + */ + new PixelPickingPlugin(), ]; public sourceOption: { data: any; options?: ISourceCFG; }; - public styleOption: ILayerStyleOptions = { - opacity: 1.0, - }; - // 样式属性 - public styleAttributes: { - [key: string]: Required; - } = {}; + + @lazyInject(TYPES.IStyleAttributeService) + public styleAttributeService: IStyleAttributeService; + @lazyInject(TYPES.IIconService) protected readonly iconService: IIconService; protected layerSource: Source; - private encodedData: Array<{ [key: string]: unknown }>; - private initializationOptions: Partial; + private encodedData: IEncodeFeature[]; + + /** + * 保存样式属性 + */ + private styleOptions: Partial< + ILayerInitializationOptions & ChildLayerStyleOptions + >; @lazyInject(TYPES.IGlobalConfigService) private readonly configService: IGlobalConfigService; + @lazyInject(TYPES.IShaderModuleService) + private readonly shaderModuleService: IShaderModuleService; + @lazyInject(TYPES.IRendererService) private readonly rendererService: IRendererService; - constructor(initializationOptions: Partial) { - this.initializationOptions = initializationOptions; + constructor( + styleOptions: Partial, + ) { + this.styleOptions = { + ...defaultLayerInitializationOptions, + ...styleOptions, + }; } public addPlugin(plugin: ILayerPlugin) { @@ -92,42 +165,36 @@ export default class BaseLayer implements ILayer { this.buildModels(); return this; } + public color( field: StyleAttributeField, values?: StyleAttributeOption, - ): ILayer { - this.createStyleAttribute( + updateOptions?: Partial, + ) { + this.styleAttributeService.updateStyleAttribute( 'color', - field, - values, - this.configService.getConfig().colors, + { + // @ts-ignore + scale: { + field, + ...this.splitValuesAndCallbackInAttribute( + // @ts-ignore + values, + this.configService.getConfig().colors, + ), + }, + }, + // @ts-ignore + updateOptions, ); return this; } - public size( - field: StyleAttributeField, - values?: StyleAttributeOption, - ): ILayer { - this.createStyleAttribute( - 'size', - field, - values, - this.configService.getConfig().size, - ); + public size(field: StyleAttributeField, values?: StyleAttributeOption) { return this; } - public shape( - field: StyleAttributeField, - values?: StyleAttributeOption, - ): ILayer { - this.createStyleAttribute( - 'shape', - field, - values, - this.configService.getConfig().shape, - ); + public shape(field: StyleAttributeField, values?: StyleAttributeOption) { return this; } @@ -138,8 +205,12 @@ export default class BaseLayer implements ILayer { }; return this; } - public style(options: ILayerStyleOptions): ILayer { - this.styleOption = options; + public style(options: unknown): ILayer { + // @ts-ignore + this.styleOptions = { + ...this.styleOptions, + ...options, + }; return this; } public render(): ILayer { @@ -152,13 +223,25 @@ export default class BaseLayer implements ILayer { } public destroy() { + this.hooks.beforeDestroy.call(this); + + // 清除所有属性以及关联的 vao + this.styleAttributeService.clearAllAttributes(); + // 销毁所有 model this.models.forEach((model) => model.destroy()); + + this.hooks.afterDestroy.call(this); } - public getStyleAttributes(): { - [key: string]: Required; - } { - return this.styleAttributes; + public isDirty() { + return !!( + this.styleAttributeService.getLayerStyleAttributes() || [] + ).filter( + (attribute) => + attribute.needRescale || + attribute.needRemapping || + attribute.needRegenerateVertices, + ).length; } public setSource(source: Source) { @@ -168,18 +251,43 @@ export default class BaseLayer implements ILayer { return this.layerSource; } - public getInitializationOptions() { - return this.initializationOptions; + public getStyleOptions() { + return this.styleOptions; } - public setEncodedData(encodedData: Array<{ [key: string]: unknown }>) { + public setEncodedData(encodedData: IEncodeFeature[]) { this.encodedData = encodedData; } - public getEncodedData() { return this.encodedData; } + protected buildLayerModel(options: ILayerModelInitializationOptions): IModel { + const { moduleName, vertexShader, fragmentShader, triangulation } = options; + this.shaderModuleService.registerModule(moduleName, { + vs: vertexShader, + fs: fragmentShader, + }); + const { vs, fs, uniforms } = this.shaderModuleService.getModule(moduleName); + const { createModel } = this.rendererService; + + const { + attributes, + elements, + } = this.styleAttributeService.createAttributesAndIndices( + this.encodedData, + triangulation, + ); + + return createModel({ + attributes, + uniforms, + fs, + vs, + elements, + }); + } + protected buildModels() { throw new Error('Method not implemented.'); } @@ -188,18 +296,15 @@ export default class BaseLayer implements ILayer { throw new Error('Method not implemented.'); } - private createStyleAttribute( - attributeName: string, - field: StyleAttributeField, - valuesOrCallback: any, - defaultValues: any, + private splitValuesAndCallbackInAttribute( + valuesOrCallback?: unknown[], + defaultValues?: unknown[], ) { - this.styleAttributes[attributeName] = new StyleAttribute({ - field, + return { values: isFunction(valuesOrCallback) ? undefined : valuesOrCallback || defaultValues, callback: isFunction(valuesOrCallback) ? valuesOrCallback : undefined, - }); + }; } } diff --git a/packages/layers/src/core/ScaleController.ts b/packages/layers/src/core/ScaleController.ts deleted file mode 100644 index 849e10ac01..0000000000 --- a/packages/layers/src/core/ScaleController.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { - IScaleOption, - IStyleScale, - ScaleTypes, - StyleScaleType, -} from '@l7/core'; -import { extent } from 'd3-array'; -import * as d3 from 'd3-scale'; -import { isNil, isNumber, isString, uniq } from 'lodash'; - -const dateRegex = /^(?:(?!0000)[0-9]{4}([-/.]+)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))(\s+([01]|([01][0-9]|2[0-3])):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9]))?$/; - -const scaleMap = { - [ScaleTypes.LINEAR]: d3.scaleLinear, - [ScaleTypes.POWER]: d3.scalePow, - [ScaleTypes.LOG]: d3.scaleLog, - [ScaleTypes.IDENTITY]: d3.scaleIdentity, - [ScaleTypes.TIME]: d3.scaleTime, - [ScaleTypes.QUANTILE]: d3.scaleQuantile, - [ScaleTypes.QUANTIZE]: d3.scaleQuantize, - [ScaleTypes.THRESHOLD]: d3.scaleThreshold, - [ScaleTypes.CAT]: d3.scaleOrdinal, -}; - -export default class ScaleController { - private scaleOptions: { - [field: string]: IScaleOption; - }; - constructor(cfg: { [field: string]: IScaleOption }) { - this.scaleOptions = cfg; - } - - public createScale(field: string, data?: any[]): IStyleScale { - let scaleOption: IScaleOption = this.scaleOptions[field]; - const scale: IStyleScale = { - field, - scale: undefined, - type: StyleScaleType.VARIABLE, - option: scaleOption, - }; - if (!data || !data.length) { - // 数据为空 - if (scaleOption && scaleOption.type) { - scale.scale = this.generateScale(scaleOption.type, scaleOption); - } else { - scale.scale = d3.scaleOrdinal([field]); - scale.type = StyleScaleType.CONSTANT; - } - return scale; - } - let firstValue = null; - data.some((item) => { - if (!isNil(item[field])) { - firstValue = item[field]; - return true; - } - firstValue = null; - }); - // 常量 Scale - if (isNumber(field) || (isNil(firstValue) && !scaleOption)) { - scale.scale = d3.scaleOrdinal([field]); - scale.type = StyleScaleType.CONSTANT; - } else { - // 根据数据类型判断 默认等分位,时间,和枚举类型 - const type = - scaleOption && scaleOption.type - ? scaleOption.type - : this.getDefaultType(field, firstValue); - const cfg = this.getScaleCfg(type, field, data); - Object.assign(cfg, scaleOption); - scaleOption = cfg; // 更新scale配置 - scale.scale = this.generateScale(type, cfg); - scale.option = scaleOption; - } - return scale; - } - - private getDefaultType(field: string, firstValue: any) { - let type = ScaleTypes.QUANTIZE; - if (dateRegex.test(firstValue)) { - type = ScaleTypes.TIME; - } else if (isString(firstValue)) { - type = ScaleTypes.CAT; - } - return type; - } - - private getScaleCfg(type: ScaleTypes, field: string, data: any[]) { - const cfg: IScaleOption = { - field, - type, - }; - const values = data.map((item) => item[field]); - // 默认类型为 Quantile Scales https://github.com/d3/d3-scale#quantile-scales - if (type !== ScaleTypes.CAT && type !== ScaleTypes.QUANTILE) { - cfg.domain = extent(values); - } else if (type === ScaleTypes.CAT) { - cfg.domain = uniq(values); - } - return cfg; - } - - private generateScale(type: ScaleTypes, scaleOption: IScaleOption) { - // @ts-ignore - let scale = scaleMap[type](); - if (scaleOption.hasOwnProperty('domain')) { - // 处理同一字段映射不同视觉通道的问题 - scale = scale.copy().domain(scaleOption.domain); - } - // TODO 其他属性支持 - return scale; - } -} diff --git a/packages/layers/src/core/StyleAttribute.ts b/packages/layers/src/core/StyleAttribute.ts deleted file mode 100644 index 459e0bc358..0000000000 --- a/packages/layers/src/core/StyleAttribute.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { ILayerStyleAttribute, IStyleScale, StyleScaleType } from '@l7/core'; -import { isNil, isString } from 'lodash'; - -type CallBack = (...args: any[]) => any; - -export default class StyleAttribute implements ILayerStyleAttribute { - public type: StyleScaleType; - public names: string[]; - public scales: IStyleScale[] = []; - public values: any[] = []; - public field: string; - constructor(cfg: any) { - const { - type = StyleScaleType.CONSTANT, - scales = [], - values = [], - callback, - field, - } = cfg; - this.field = field; - this.type = type; - this.scales = scales; - this.values = values; - this.names = this.parseFields(field) || []; - if (callback) { - this.type = StyleScaleType.VARIABLE; - } - this.callback = (...params: any[]): any[] => { - /** - * 当用户设置的 callback 返回 null 时, 应该返回默认 callback 中的值 - */ - if (callback) { - // 使用用户返回的值处理 - const ret = callback(...params); - if (!isNil(ret)) { - return [ret]; - } - } - - // 没有 callback 或者用户 callback 返回值为空,则使用默认的逻辑处理 - return this.defaultCallback.apply(this, params); - }; - } - public callback: CallBack = () => []; - public setScales(scales: IStyleScale[]): void { - if (scales.some((scale) => scale.type === StyleScaleType.VARIABLE)) { - this.type = StyleScaleType.VARIABLE; - scales.forEach((scale) => { - if (this.values.length > 0) { - scale.scale.range(this.values); - } - }); - } else { - // 设置attribute 常量值 - this.values = scales.map((scale, index) => { - return scale.scale(this.names[index]); - }); - } - this.scales = scales; - } - public mapping(...params: unknown[]): unknown[] { - return this.callback.apply(this, params); - } - - private defaultCallback(...params: unknown[]): unknown[] { - // 没有 params 的情况,是指没有指定 fields,直接返回配置的 values 常量 - if (params.length === 0) { - return this.values; - } - return params.map((param, idx) => { - const scale = this.scales[idx]; - const value = scale.scale(param); - return value; - }); - } - /** - * @example - * 'w*h' => ['w', 'h'] - * 'w' => ['w'] - */ - private parseFields(field: string[] | string): string[] { - if (Array.isArray(field)) { - return field; - } - if (isString(field)) { - return field.split('*'); - } - return [field]; - } -} diff --git a/packages/layers/src/heatmap/buffers/GridBuffer.ts b/packages/layers/src/heatmap/buffers/GridBuffer.ts index b75f64fc16..6eb6d66636 100644 --- a/packages/layers/src/heatmap/buffers/GridBuffer.ts +++ b/packages/layers/src/heatmap/buffers/GridBuffer.ts @@ -1,46 +1,46 @@ -import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer'; -import extrudePolygon, { - fillPolygon, - IExtrudeGeomety, -} from '../../point/shape/extrude'; -import { - geometryShape, - ShapeType2D, - ShapeType3D, -} from '../../point/shape/Path'; -export default class GridHeatMapBuffer extends BufferBase { - private verticesOffset: number = 0; - protected buildFeatures() { - this.verticesOffset = 0; - const layerData = this.data as IEncodeFeature[]; - layerData.forEach((feature: IEncodeFeature) => { - this.calculateFill(feature); - }); - } - protected calculateFeatures() { - const layerData = this.data as IEncodeFeature[]; - const shape = layerData[0].shape as ShapeType3D | ShapeType2D; - this.verticesCount = layerData.length; - this.indexCount = 0; - this.instanceGeometry = this.getGeometry(shape as - | ShapeType2D - | ShapeType3D); - } - protected calculateFill(feature: IEncodeFeature) { - feature.bufferInfo = { verticesOffset: this.verticesOffset }; - const coordinates = feature.coordinates as Position; - this.encodeArray(feature, 1); - this.attributes.positions.set([...coordinates, 1], this.verticesOffset * 3); - this.verticesOffset++; - } - private getGeometry(shape: ShapeType2D | ShapeType3D): IExtrudeGeomety { - const path = geometryShape[shape] - ? geometryShape[shape]() - : geometryShape.circle(); - // const geometry = ShapeType2D[str as ShapeType2D] - // ? fillPolygon([path]) - // : extrudePolygon([path]); - const geometry = fillPolygon([path]); - return geometry; - } -} +// import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer'; +// import extrudePolygon, { +// fillPolygon, +// IExtrudeGeomety, +// } from '../../point/shape/extrude'; +// import { +// geometryShape, +// ShapeType2D, +// ShapeType3D, +// } from '../../point/shape/Path'; +// export default class GridHeatMapBuffer extends BufferBase { +// private verticesOffset: number = 0; +// protected buildFeatures() { +// this.verticesOffset = 0; +// const layerData = this.data as IEncodeFeature[]; +// layerData.forEach((feature: IEncodeFeature) => { +// this.calculateFill(feature); +// }); +// } +// protected calculateFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// const shape = layerData[0].shape as ShapeType3D | ShapeType2D; +// this.verticesCount = layerData.length; +// this.indexCount = 0; +// this.instanceGeometry = this.getGeometry(shape as +// | ShapeType2D +// | ShapeType3D); +// } +// protected calculateFill(feature: IEncodeFeature) { +// feature.bufferInfo = { verticesOffset: this.verticesOffset }; +// const coordinates = feature.coordinates as Position; +// this.encodeArray(feature, 1); +// this.attributes.positions.set([...coordinates, 1], this.verticesOffset * 3); +// this.verticesOffset++; +// } +// private getGeometry(shape: ShapeType2D | ShapeType3D): IExtrudeGeomety { +// const path = geometryShape[shape] +// ? geometryShape[shape]() +// : geometryShape.circle(); +// // const geometry = ShapeType2D[str as ShapeType2D] +// // ? fillPolygon([path]) +// // : extrudePolygon([path]); +// const geometry = fillPolygon([path]); +// return geometry; +// } +// } diff --git a/packages/layers/src/heatmap/index.ts b/packages/layers/src/heatmap/index.ts index c92514070d..2e62569547 100644 --- a/packages/layers/src/heatmap/index.ts +++ b/packages/layers/src/heatmap/index.ts @@ -1,112 +1,112 @@ -import { - gl, - IRendererService, - IShaderModuleService, - lazyInject, - TYPES, -} from '@l7/core'; -import BaseLayer from '../core/BaseLayer'; -import GridHeatMapBuffer from './buffers/GridBuffer'; -import hexagon_frag from './shaders/hexagon_frag.glsl'; -import hexagon_vert from './shaders/hexagon_vert.glsl'; +// import { +// gl, +// IRendererService, +// IShaderModuleService, +// lazyInject, +// TYPES, +// } from '@l7/core'; +// import BaseLayer from '../core/BaseLayer'; +// import GridHeatMapBuffer from './buffers/GridBuffer'; +// import hexagon_frag from './shaders/hexagon_frag.glsl'; +// import hexagon_vert from './shaders/hexagon_vert.glsl'; -export default class HeatMapLayer extends BaseLayer { - public name: string = 'HeatMapLayer'; +// export default class HeatMapLayer extends BaseLayer { +// public name: string = 'HeatMapLayer'; - @lazyInject(TYPES.IShaderModuleService) - private readonly shaderModule: IShaderModuleService; +// @lazyInject(TYPES.IShaderModuleService) +// private readonly shaderModule: IShaderModuleService; - @lazyInject(TYPES.IRendererService) - private readonly renderer: IRendererService; +// @lazyInject(TYPES.IRendererService) +// private readonly renderer: IRendererService; - protected renderModels() { - this.models.forEach((model) => - model.draw({ - uniforms: { - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - }, - }), - ); - return this; - } +// protected renderModels() { +// this.models.forEach((model) => +// model.draw({ +// uniforms: { +// u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], +// }, +// }), +// ); +// return this; +// } - protected buildModels(): void { - this.shaderModule.registerModule('grid', { - vs: hexagon_vert, - fs: hexagon_frag, - }); - this.models = []; - const { vs, fs, uniforms } = this.shaderModule.getModule('grid'); - const buffer = new GridHeatMapBuffer({ - data: this.getEncodedData(), - }); - const { - createAttribute, - createBuffer, - createElements, - createModel, - } = this.renderer; +// protected buildModels(): void { +// this.shaderModule.registerModule('grid', { +// vs: hexagon_vert, +// fs: hexagon_frag, +// }); +// this.models = []; +// const { vs, fs, uniforms } = this.shaderModule.getModule('grid'); +// const buffer = new GridHeatMapBuffer({ +// data: this.getEncodedData(), +// }); +// const { +// createAttribute, +// createBuffer, +// createElements, +// createModel, +// } = this.renderer; - this.models.push( - createModel({ - attributes: { - a_miter: createAttribute({ - buffer: createBuffer({ - data: buffer.instanceGeometry.positions, - type: gl.FLOAT, - }), - size: 3, - divisor: 0, - }), - // a_normal: createAttribute({ - // buffer: createBuffer({ - // data: buffer.attributes.normals, - // type: gl.FLOAT, - // }), - // size: 3, - // }), - a_color: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.colors, - type: gl.FLOAT, - }), - size: 4, - divisor: 1, - }), - // a_size: createAttribute({ - // buffer: createBuffer({ - // data: buffer.attributes.sizes, - // type: gl.FLOAT, - // }), - // size: 1, - // divisor: 1, - // }), - a_Position: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.positions, - type: gl.FLOAT, - }), - size: 3, - divisor: 1, - }), - }, - uniforms: { - ...uniforms, - u_opacity: (this.styleOption.opacity as number) || 1.0, - u_radius: [ - this.getSource().data.xOffset, - this.getSource().data.yOffset, - ], - }, - fs, - vs, - count: buffer.instanceGeometry.index.length, - instances: buffer.verticesCount, - elements: createElements({ - data: buffer.instanceGeometry.index, - type: gl.UNSIGNED_INT, - }), - }), - ); - } -} +// this.models.push( +// createModel({ +// attributes: { +// a_miter: createAttribute({ +// buffer: createBuffer({ +// data: buffer.instanceGeometry.positions, +// type: gl.FLOAT, +// }), +// size: 3, +// divisor: 0, +// }), +// // a_normal: createAttribute({ +// // buffer: createBuffer({ +// // data: buffer.attributes.normals, +// // type: gl.FLOAT, +// // }), +// // size: 3, +// // }), +// a_color: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.colors, +// type: gl.FLOAT, +// }), +// size: 4, +// divisor: 1, +// }), +// // a_size: createAttribute({ +// // buffer: createBuffer({ +// // data: buffer.attributes.sizes, +// // type: gl.FLOAT, +// // }), +// // size: 1, +// // divisor: 1, +// // }), +// a_Position: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.positions, +// type: gl.FLOAT, +// }), +// size: 3, +// divisor: 1, +// }), +// }, +// uniforms: { +// ...uniforms, +// u_opacity: (this.styleOption.opacity as number) || 1.0, +// u_radius: [ +// this.getSource().data.xOffset, +// this.getSource().data.yOffset, +// ], +// }, +// fs, +// vs, +// count: buffer.instanceGeometry.index.length, +// instances: buffer.verticesCount, +// elements: createElements({ +// data: buffer.instanceGeometry.index, +// type: gl.UNSIGNED_INT, +// }), +// }), +// ); +// } +// } diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index 91a9c56138..19b3cc8d2a 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -1,16 +1,16 @@ import BaseLayer from './core/BaseLayer'; -import HeatMapLayer from './heatmap'; -import Line from './line'; -import PointLayer from './point'; -import Point from './point/point'; +// import HeatMapLayer from './heatmap'; +// import Line from './line'; +// import PointLayer from './point'; +// import Point from './point/point'; import PolygonLayer from './polygon'; -import ImageLayer from './raster'; +// import ImageLayer from './raster'; export { BaseLayer, - PointLayer, + // PointLayer, PolygonLayer, - Point, - Line, - ImageLayer, - HeatMapLayer, + // Point, + // Line, + // ImageLayer, + // HeatMapLayer, }; diff --git a/packages/layers/src/line/buffers/line.ts b/packages/layers/src/line/buffers/line.ts index 522b124220..224542279c 100644 --- a/packages/layers/src/line/buffers/line.ts +++ b/packages/layers/src/line/buffers/line.ts @@ -1,95 +1,95 @@ -import { lngLatToMeters, Point } from '@l7/utils'; -import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer'; -import getNormals from '../../utils/polylineNormal'; -interface IBufferInfo { - normals: number[]; - arrayIndex: number[]; - positions: number[]; - attrDistance: number[]; - miters: number[]; - verticesOffset: number; - indexOffset: number; -} -export default class LineBuffer extends BufferBase { - private hasPattern: boolean; - protected buildFeatures() { - const layerData = this.data as IEncodeFeature[]; - layerData.forEach((feature: IEncodeFeature) => { - this.calculateLine(feature); - delete feature.bufferInfo; - }); - this.hasPattern = layerData.some((feature: IEncodeFeature) => { - return feature.pattern; - }); - } - protected initAttributes() { - super.initAttributes(); - this.attributes.dashArray = new Float32Array(this.verticesCount); - this.attributes.attrDistance = new Float32Array(this.verticesCount); - this.attributes.totalDistances = new Float32Array(this.verticesCount); - this.attributes.patterns = new Float32Array(this.verticesCount * 2); - this.attributes.miters = new Float32Array(this.verticesCount); - this.attributes.normals = new Float32Array(this.verticesCount * 3); - } - protected calculateFeatures() { - const layerData = this.data as IEncodeFeature[]; - // 计算长 - layerData.forEach((feature: IEncodeFeature, index: number) => { - let coordinates = feature.coordinates as Position[] | Position[][]; - if (Array.isArray(coordinates[0][0])) { - coordinates = coordinates[0] as Position[]; - } - // @ts-ignore - const projectCoord: number[][] = coordinates.map((item: Position[]) => { - // @ts-ignore - const p: Point = [...item]; - return lngLatToMeters(p); - }); - const { normals, attrIndex, attrPos, attrDistance, miters } = getNormals( - coordinates as number[][], - false, - this.verticesCount, - ); - const bufferInfo: IBufferInfo = { - normals, - arrayIndex: attrIndex, - positions: attrPos, - attrDistance, - miters, - verticesOffset: this.verticesCount, - indexOffset: this.indexCount, - }; - this.verticesCount += attrPos.length / 3; - this.indexCount += attrIndex.length; - feature.bufferInfo = bufferInfo; - }); - } - private calculateLine(feature: IEncodeFeature) { - const bufferInfo = feature.bufferInfo as IBufferInfo; - const { - normals, - arrayIndex, - positions, - attrDistance, - miters, - verticesOffset, - indexOffset, - } = bufferInfo; - const { dashArray = 200 } = this.style; +// import { lngLatToMeters, Point } from '@l7/utils'; +// import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer'; +// import getNormals from '../../utils/polylineNormal'; +// interface IBufferInfo { +// normals: number[]; +// arrayIndex: number[]; +// positions: number[]; +// attrDistance: number[]; +// miters: number[]; +// verticesOffset: number; +// indexOffset: number; +// } +// export default class LineBuffer extends BufferBase { +// private hasPattern: boolean; +// protected buildFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// layerData.forEach((feature: IEncodeFeature) => { +// this.calculateLine(feature); +// delete feature.bufferInfo; +// }); +// this.hasPattern = layerData.some((feature: IEncodeFeature) => { +// return feature.pattern; +// }); +// } +// protected initAttributes() { +// super.initAttributes(); +// this.attributes.dashArray = new Float32Array(this.verticesCount); +// this.attributes.attrDistance = new Float32Array(this.verticesCount); +// this.attributes.totalDistances = new Float32Array(this.verticesCount); +// this.attributes.patterns = new Float32Array(this.verticesCount * 2); +// this.attributes.miters = new Float32Array(this.verticesCount); +// this.attributes.normals = new Float32Array(this.verticesCount * 3); +// } +// protected calculateFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// // 计算长 +// layerData.forEach((feature: IEncodeFeature, index: number) => { +// let coordinates = feature.coordinates as Position[] | Position[][]; +// if (Array.isArray(coordinates[0][0])) { +// coordinates = coordinates[0] as Position[]; +// } +// // @ts-ignore +// const projectCoord: number[][] = coordinates.map((item: Position[]) => { +// // @ts-ignore +// const p: Point = [...item]; +// return lngLatToMeters(p); +// }); +// const { normals, attrIndex, attrPos, attrDistance, miters } = getNormals( +// coordinates as number[][], +// false, +// this.verticesCount, +// ); +// const bufferInfo: IBufferInfo = { +// normals, +// arrayIndex: attrIndex, +// positions: attrPos, +// attrDistance, +// miters, +// verticesOffset: this.verticesCount, +// indexOffset: this.indexCount, +// }; +// this.verticesCount += attrPos.length / 3; +// this.indexCount += attrIndex.length; +// feature.bufferInfo = bufferInfo; +// }); +// } +// private calculateLine(feature: IEncodeFeature) { +// const bufferInfo = feature.bufferInfo as IBufferInfo; +// const { +// normals, +// arrayIndex, +// positions, +// attrDistance, +// miters, +// verticesOffset, +// indexOffset, +// } = bufferInfo; +// const { dashArray = 200 } = this.style; - this.encodeArray(feature, positions.length / 3); - const totalLength = attrDistance[attrDistance.length - 1]; - // 增加长度 - const totalDistances = Array(positions.length / 3).fill(totalLength); - // 虚线比例 - const ratio = dashArray / totalLength; - const dashArrays = Array(positions.length / 3).fill(ratio); - this.attributes.positions.set(positions, verticesOffset * 3); - this.indexArray.set(arrayIndex, indexOffset); - this.attributes.miters.set(miters, verticesOffset); - this.attributes.normals.set(normals, verticesOffset * 3); - this.attributes.attrDistance.set(attrDistance, verticesOffset); - this.attributes.totalDistances.set(totalDistances, verticesOffset); - this.attributes.dashArray.set(dashArrays, verticesOffset); - } -} +// this.encodeArray(feature, positions.length / 3); +// const totalLength = attrDistance[attrDistance.length - 1]; +// // 增加长度 +// const totalDistances = Array(positions.length / 3).fill(totalLength); +// // 虚线比例 +// const ratio = dashArray / totalLength; +// const dashArrays = Array(positions.length / 3).fill(ratio); +// this.attributes.positions.set(positions, verticesOffset * 3); +// this.indexArray.set(arrayIndex, indexOffset); +// this.attributes.miters.set(miters, verticesOffset); +// this.attributes.normals.set(normals, verticesOffset * 3); +// this.attributes.attrDistance.set(attrDistance, verticesOffset); +// this.attributes.totalDistances.set(totalDistances, verticesOffset); +// this.attributes.dashArray.set(dashArrays, verticesOffset); +// } +// } diff --git a/packages/layers/src/line/index.ts b/packages/layers/src/line/index.ts index 069e5af944..59d2fe5818 100644 --- a/packages/layers/src/line/index.ts +++ b/packages/layers/src/line/index.ts @@ -1,102 +1,102 @@ -import { - gl, - IRendererService, - IShaderModuleService, - lazyInject, - TYPES, -} from '@l7/core'; -import BaseLayer from '../core/BaseLayer'; -import LineBuffer from './buffers/line'; -import line_frag from './shaders/line_frag.glsl'; -import line_vert from './shaders/line_vert.glsl'; -export default class LineLayer extends BaseLayer { - public name: string = 'LineLayer'; - @lazyInject(TYPES.IShaderModuleService) - private readonly shaderModule: IShaderModuleService; +// import { +// gl, +// IRendererService, +// IShaderModuleService, +// lazyInject, +// TYPES, +// } from '@l7/core'; +// import BaseLayer from '../core/BaseLayer'; +// import LineBuffer from './buffers/line'; +// import line_frag from './shaders/line_frag.glsl'; +// import line_vert from './shaders/line_vert.glsl'; +// export default class LineLayer extends BaseLayer { +// public name: string = 'LineLayer'; +// @lazyInject(TYPES.IShaderModuleService) +// private readonly shaderModule: IShaderModuleService; - @lazyInject(TYPES.IRendererService) - private readonly renderer: IRendererService; +// @lazyInject(TYPES.IRendererService) +// private readonly renderer: IRendererService; - protected renderModels() { - this.models.forEach((model) => - model.draw({ - uniforms: { - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - }, - }), - ); - return this; - } - protected buildModels(): void { - this.shaderModule.registerModule('line', { - vs: line_vert, - fs: line_frag, - }); +// protected renderModels() { +// this.models.forEach((model) => +// model.draw({ +// uniforms: { +// u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], +// }, +// }), +// ); +// return this; +// } +// protected buildModels(): void { +// this.shaderModule.registerModule('line', { +// vs: line_vert, +// fs: line_frag, +// }); - this.models = []; - const { vs, fs, uniforms } = this.shaderModule.getModule('line'); - const buffer = new LineBuffer({ - data: this.getEncodedData(), - style: this.styleOption, - }); - const { - createAttribute, - createBuffer, - createElements, - createModel, - } = this.renderer; +// this.models = []; +// const { vs, fs, uniforms } = this.shaderModule.getModule('line'); +// const buffer = new LineBuffer({ +// data: this.getEncodedData(), +// style: this.styleOption, +// }); +// const { +// createAttribute, +// createBuffer, +// createElements, +// createModel, +// } = this.renderer; - this.models.push( - createModel({ - attributes: { - a_Position: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.positions, - type: gl.FLOAT, - }), - size: 3, - }), - a_normal: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.normals, - type: gl.FLOAT, - }), - size: 3, - }), - a_color: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.colors, - type: gl.FLOAT, - }), - size: 4, - }), - a_size: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.sizes, - type: gl.FLOAT, - }), - size: 1, - }), - a_miter: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.miters, - type: gl.FLOAT, - }), - size: 1, - }), - }, - uniforms: { - ...uniforms, - u_opacity: this.styleOption.opacity as number, - }, - fs, - vs, - count: buffer.indexArray.length, - elements: createElements({ - data: buffer.indexArray, - type: gl.UNSIGNED_INT, - }), - }), - ); - } -} +// this.models.push( +// createModel({ +// attributes: { +// a_Position: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.positions, +// type: gl.FLOAT, +// }), +// size: 3, +// }), +// a_normal: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.normals, +// type: gl.FLOAT, +// }), +// size: 3, +// }), +// a_color: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.colors, +// type: gl.FLOAT, +// }), +// size: 4, +// }), +// a_size: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.sizes, +// type: gl.FLOAT, +// }), +// size: 1, +// }), +// a_miter: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.miters, +// type: gl.FLOAT, +// }), +// size: 1, +// }), +// }, +// uniforms: { +// ...uniforms, +// u_opacity: this.styleOption.opacity as number, +// }, +// fs, +// vs, +// count: buffer.indexArray.length, +// elements: createElements({ +// data: buffer.indexArray, +// type: gl.UNSIGNED_INT, +// }), +// }), +// ); +// } +// } diff --git a/packages/layers/src/plugins/DataEncodePlugin.ts b/packages/layers/src/plugins/DataEncodePlugin.ts deleted file mode 100644 index 39255bb131..0000000000 --- a/packages/layers/src/plugins/DataEncodePlugin.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { - IGlobalConfigService, - ILayer, - ILayerPlugin, - ILayerStyleAttribute, - IParseDataItem, - IStyleScale, - lazyInject, - StyleScaleType, - TYPES, -} from '@l7/core'; -import { isString } from 'lodash'; -import ScaleController from '../core/ScaleController'; -import { rgb2arr } from '../utils/color'; - -export default class DataEncodePlugin implements ILayerPlugin { - @lazyInject(TYPES.IGlobalConfigService) - private readonly configService: IGlobalConfigService; - - private scaleController: ScaleController; - - private scaleCache: { - [fieldName: string]: IStyleScale; - } = {}; - - public apply(layer: ILayer) { - layer.hooks.init.tap('DataEncodePlugin', () => { - const source = layer.getSource(); - const dataArray = source && source.data && source.data.dataArray; - if (!dataArray) { - return; - } - - this.scaleController = new ScaleController( - this.configService.getConfig().scales || {}, - ); - - // create scales by source data & config - Object.keys(layer.styleAttributes).forEach((attributeName) => { - const attribute = layer.styleAttributes[attributeName]; - const scales: any[] = []; - attribute.names.forEach((field: string) => { - scales.push(this.getOrCreateScale(attribute, field, dataArray)); - }); - attribute.setScales(scales); - }); - // mapping with source data - layer.setEncodedData(this.mapping(layer.styleAttributes, dataArray)); - }); - - // TODO: remapping before render - // layer.hooks.beforeRender.tap() - } - - private getOrCreateScale( - attribute: ILayerStyleAttribute, - field: string, - data: any[], - ): IStyleScale { - let scale = this.scaleCache[field as string]; - if (!scale) { - scale = this.scaleController.createScale(field as string, data); - if ( - scale.type === StyleScaleType.VARIABLE && - attribute.values && - attribute.values.length > 0 - ) { - scale.scale.range(attribute.values); - } - this.scaleCache[field as string] = scale; - } - return { - ...scale, - scale: scale.scale.copy(), // 存在相同字段映射不同通道的情况 - }; - } - private mapping( - attributes: { - [attributeName: string]: ILayerStyleAttribute; - }, - data: IParseDataItem[], - ): Array<{ [key: string]: unknown }> { - return data.map((record: IParseDataItem) => { - const encodeRecord: { [key: string]: unknown } = { - id: record._id, - coordinates: record.coordinates, - }; - Object.keys(attributes).forEach((attributeName: string) => { - const attribute = attributes[attributeName]; - // const { type } = attribute; - // if (type === StyleScaleType.CONSTANT) { - // return; - // } - let values = this.getAttrValue(attribute, record); - if (attributeName === 'color') { - values = values.map((c: unknown) => { - return rgb2arr(c as string); - }); - } - encodeRecord[attributeName] = - Array.isArray(values) && values.length === 1 ? values[0] : values; - }); - return encodeRecord; - }); - } - - private getAttrValue( - attribute: ILayerStyleAttribute, - record: { [key: string]: unknown }, - ) { - const scales = attribute.scales || []; - const params: unknown[] = []; - - scales.forEach((scale) => { - const { field, type } = scale; - if (type === StyleScaleType.CONSTANT) { - params.push(scale.field); - } else { - params.push(record[field]); - } - }); - - return attribute.mapping ? attribute.mapping(...params) : []; - } -} diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts new file mode 100644 index 0000000000..4a2ce7f1b4 --- /dev/null +++ b/packages/layers/src/plugins/DataMappingPlugin.ts @@ -0,0 +1,98 @@ +import { + IEncodeFeature, + IGlobalConfigService, + ILayer, + ILayerPlugin, + ILogService, + IParseDataItem, + IStyleAttribute, + lazyInject, + TYPES, +} from '@l7/core'; +import { rgb2arr } from '../utils/color'; + +export default class DataMappingPlugin implements ILayerPlugin { + @lazyInject(TYPES.IGlobalConfigService) + private readonly configService: IGlobalConfigService; + + @lazyInject(TYPES.ILogService) + private readonly logger: ILogService; + + public apply(layer: ILayer) { + layer.hooks.init.tap('DataMappingPlugin', () => { + const attributes = + layer.styleAttributeService.getLayerStyleAttributes() || []; + const { dataArray } = layer.getSource().data; + + // TODO: FIXME + if (!dataArray) { + return; + } + + // mapping with source data + layer.setEncodedData(this.mapping(attributes, dataArray)); + }); + + // remapping before render + layer.hooks.beforeRender.tap('DataMappingPlugin', () => { + const attributes = + layer.styleAttributeService.getLayerStyleAttributes() || []; + const { dataArray } = layer.getSource().data; + const attributesToRemapping = attributes.filter( + (attribute) => attribute.needRemapping, + ); + if (attributesToRemapping.length) { + layer.setEncodedData(this.mapping(attributesToRemapping, dataArray)); + this.logger.info('remapping finished'); + } + }); + } + + private mapping( + attributes: IStyleAttribute[], + data: IParseDataItem[], + ): IEncodeFeature[] { + return data.map((record: IParseDataItem) => { + const encodeRecord: IEncodeFeature = { + id: record._id, + coordinates: record.coordinates, + }; + // TODO 数据过滤 + attributes.forEach((attribute: IStyleAttribute) => { + let values = this.applyAttributeMapping(attribute, record); + attribute.needRemapping = false; + + // TODO: 支持每个属性配置 postprocess + if (attribute.name === 'color') { + values = values.map((c: unknown) => { + return rgb2arr(c as string); + }); + } + // @ts-ignore + encodeRecord[attribute.name] = + Array.isArray(values) && values.length === 1 ? values[0] : values; + }); + return encodeRecord; + }) as IEncodeFeature[]; + } + + private applyAttributeMapping( + attribute: IStyleAttribute, + record: { [key: string]: unknown }, + ) { + if (!attribute.scale) { + return []; + } + + const scalers = attribute!.scale!.scalers || []; + const params: unknown[] = []; + + scalers.forEach(({ field }) => { + if (record[field]) { + params.push(record[field]); + } + }); + + return attribute.mapping ? attribute.mapping(params) : []; + } +} diff --git a/packages/layers/src/plugins/DataSourcePlugin.ts b/packages/layers/src/plugins/DataSourcePlugin.ts index 84829eb9eb..9c916cd9ac 100644 --- a/packages/layers/src/plugins/DataSourcePlugin.ts +++ b/packages/layers/src/plugins/DataSourcePlugin.ts @@ -1,20 +1,10 @@ -import { - IGlobalConfigService, - ILayer, - ILayerPlugin, - ILayerStyleAttribute, - IParseDataItem, - ISourceCFG, - IStyleScale, - lazyInject, - StyleScaleType, - TYPES, -} from '@l7/core'; +import { ILayer, ILayerPlugin } from '@l7/core'; import Source from '@l7/source'; export default class DataSourcePlugin implements ILayerPlugin { public apply(layer: ILayer) { layer.hooks.init.tap('DataSourcePlugin', () => { const { data, options } = layer.sourceOption; + // @ts-ignore layer.setSource(new Source(data, options)); }); } diff --git a/packages/layers/src/plugins/FeatureScalePlugin.ts b/packages/layers/src/plugins/FeatureScalePlugin.ts new file mode 100644 index 0000000000..8bd47366c1 --- /dev/null +++ b/packages/layers/src/plugins/FeatureScalePlugin.ts @@ -0,0 +1,174 @@ +import { + IGlobalConfigService, + ILayer, + ILayerPlugin, + ILogService, + IScale, + IStyleAttribute, + lazyInject, + ScaleTypes, + TYPES, +} from '@l7/core'; +import { IParseDataItem } from '@l7/source'; +import { extent } from 'd3-array'; +import * as d3 from 'd3-scale'; +import { isNil, isNumber, isString, uniq } from 'lodash'; + +const dateRegex = /^(?:(?!0000)[0-9]{4}([-/.]+)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))(\s+([01]|([01][0-9]|2[0-3])):([0-9]|[0-5][0-9]):([0-9]|[0-5][0-9]))?$/; + +const scaleMap = { + [ScaleTypes.LINEAR]: d3.scaleLinear, + [ScaleTypes.POWER]: d3.scalePow, + [ScaleTypes.LOG]: d3.scaleLog, + [ScaleTypes.IDENTITY]: d3.scaleIdentity, + [ScaleTypes.TIME]: d3.scaleTime, + [ScaleTypes.QUANTILE]: d3.scaleQuantile, + [ScaleTypes.QUANTIZE]: d3.scaleQuantize, + [ScaleTypes.THRESHOLD]: d3.scaleThreshold, + [ScaleTypes.CAT]: d3.scaleOrdinal, +}; + +/** + * 根据 Source 原始数据为指定字段创建 Scale,保存在 StyleAttribute 上,供下游插件使用 + */ +export default class FeatureScalePlugin implements ILayerPlugin { + @lazyInject(TYPES.IGlobalConfigService) + private readonly configService: IGlobalConfigService; + + @lazyInject(TYPES.ILogService) + private readonly logger: ILogService; + + private scaleCache: { + [field: string]: unknown; + } = {}; + + public apply(layer: ILayer) { + layer.hooks.init.tap('FeatureScalePlugin', () => { + const attributes = layer.styleAttributeService.getLayerStyleAttributes(); + const { dataArray } = layer.getSource().data; + this.caculateScalesForAttributes(attributes || [], dataArray); + }); + + layer.hooks.beforeRender.tap('FeatureScalePlugin', () => { + const attributes = layer.styleAttributeService.getLayerStyleAttributes(); + if (attributes) { + const { dataArray } = layer.getSource().data; + const attributesToRescale = attributes.filter( + (attribute) => attribute.needRescale, + ); + if (attributesToRescale.length) { + this.caculateScalesForAttributes(attributesToRescale, dataArray); + this.logger.info('rescale finished'); + } + } + }); + } + + private caculateScalesForAttributes( + attributes: IStyleAttribute[], + dataArray: IParseDataItem[], + ) { + this.scaleCache = {}; + attributes.forEach((attribute) => { + if (attribute.scale) { + attribute!.scale!.scalers = this.parseFields( + attribute!.scale!.field || '', + ).map((field: string) => ({ + field, + func: this.getOrCreateScale(field, attribute, dataArray), + })); + attribute.needRescale = false; + } + }); + } + + private getOrCreateScale( + field: string, + attribute: IStyleAttribute, + dataArray: IParseDataItem[], + ) { + if (this.scaleCache[field]) { + return this.scaleCache[field]; + } + this.scaleCache[field] = this.createScale(field, dataArray); + (this.scaleCache[field] as { + range: (c: unknown[]) => void; + }).range(attribute!.scale!.values); + return this.scaleCache[field]; + } + + /** + * @example + * 'w*h' => ['w', 'h'] + * 'w' => ['w'] + */ + private parseFields(field: string[] | string): string[] { + if (Array.isArray(field)) { + return field; + } + if (isString(field)) { + return field.split('*'); + } + return [field]; + } + + private createScale(field: string, data?: IParseDataItem[]): unknown { + // 首先查找全局默认配置例如 color + const scaleOption: IScale = this.configService.getConfig()!.scales![field]; + + if (!data || !data.length) { + // 数据为空 + return scaleOption && scaleOption.type + ? this.createDefaultScale(scaleOption) + : d3.scaleOrdinal([field]); + } + const firstValue = data!.find((d) => !isNil(d[field]))![field]; + // 常量 Scale + if (isNumber(field) || (isNil(firstValue) && !scaleOption)) { + return d3.scaleOrdinal([field]); + } else { + // 根据数据类型判断 默认等分位,时间,和枚举类型 + const type = + (scaleOption && scaleOption.type) || this.getDefaultType(firstValue); + return this.createDefaultScale( + this.createDefaultScaleConfig(type, field, data), + ); + } + } + + private getDefaultType(firstValue: unknown) { + let type = ScaleTypes.QUANTIZE; + if (typeof firstValue === 'string') { + type = dateRegex.test(firstValue) ? ScaleTypes.TIME : ScaleTypes.CAT; + } + return type; + } + + private createDefaultScaleConfig( + type: ScaleTypes, + field: string, + data?: IParseDataItem[], + ) { + const cfg: IScale = { + type, + }; + const values = data!.map((item) => item[field]); + // 默认类型为 Quantile Scales https://github.com/d3/d3-scale#quantile-scales + if (type !== ScaleTypes.CAT && type !== ScaleTypes.QUANTILE) { + cfg.domain = extent(values); + } else if (type === ScaleTypes.CAT) { + cfg.domain = uniq(values); + } + return cfg; + } + + private createDefaultScale({ type, domain }: IScale) { + // @ts-ignore + const scale = scaleMap[type](); + if (domain) { + scale.domain(domain); + } + // TODO 其他属性支持 + return scale; + } +} diff --git a/packages/layers/src/plugins/MultiPassRendererPlugin.ts b/packages/layers/src/plugins/MultiPassRendererPlugin.ts index fac1623412..e6c3970139 100644 --- a/packages/layers/src/plugins/MultiPassRendererPlugin.ts +++ b/packages/layers/src/plugins/MultiPassRendererPlugin.ts @@ -58,10 +58,7 @@ export default class MultiPassRendererPlugin implements ILayerPlugin { public apply(layer: ILayer) { layer.hooks.init.tap('MultiPassRendererPlugin', () => { - const { - enableMultiPassRenderer, - passes = [], - } = layer.getInitializationOptions(); + const { enableMultiPassRenderer, passes = [] } = layer.getStyleOptions(); // SceneConfig 的 enableMultiPassRenderer 配置项可以统一关闭 this.enabled = @@ -103,13 +100,12 @@ export default class MultiPassRendererPlugin implements ILayerPlugin { multiPassRenderer.add(new ClearPass()); - if (layer.getInitializationOptions().enablePicking) { + if (layer.getStyleOptions().enablePicking) { multiPassRenderer.add(new PixelPickingPass()); } multiPassRenderer.add(new RenderPass()); // post processing - // TODO: pass initialization params normalizePasses(passes).forEach( (pass: [string, { [key: string]: unknown }]) => { const PostProcessingPassClazz = builtinPostProcessingPassMap[pass[0]]; diff --git a/packages/layers/src/plugins/PixelPickingPlugin.ts b/packages/layers/src/plugins/PixelPickingPlugin.ts new file mode 100644 index 0000000000..235ae66536 --- /dev/null +++ b/packages/layers/src/plugins/PixelPickingPlugin.ts @@ -0,0 +1,92 @@ +import { + AttributeType, + gl, + IEncodeFeature, + ILayer, + ILayerPlugin, + IRendererService, + lazyInject, + TYPES, +} from '@l7/core'; +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, + ]; +} + +const PickingStage = { + NONE: 0.0, + ENCODE: 1.0, + HIGHLIGHT: 2.0, +}; + +export default class PixelPickingPlugin implements ILayerPlugin { + @lazyInject(TYPES.IRendererService) + private readonly rendererService: IRendererService; + + public apply(layer: ILayer) { + // TODO: 由于 Shader 目前无法根据是否开启拾取进行内容修改,因此即使不开启也需要生成 a_PickingColor + layer.hooks.init.tap('PixelPickingPlugin', () => { + const { enablePicking } = layer.getStyleOptions(); + layer.styleAttributeService.registerStyleAttribute({ + name: 'pickingColor', + type: AttributeType.Attribute, + descriptor: { + name: 'a_PickingColor', + buffer: { + data: [], + type: gl.FLOAT, + }, + size: 3, + // TODO: 固定 feature range 范围内的 pickingColor 都是固定的,可以生成 cache + update: (feature: IEncodeFeature, featureIdx: number) => + // 只有开启拾取才需要 encode + enablePicking ? encodePickingColor(featureIdx) : [0, 0, 0], + }, + }); + }); + // 必须要与 PixelPickingPass 结合使用,因此必须开启 multiPassRenderer + // if (layer.multiPassRenderer) { + layer.hooks.beforeRender.tap('PixelPickingPlugin', () => { + layer.models.forEach((model) => + model.addUniforms({ + u_PickingStage: PickingStage.ENCODE, + }), + ); + }); + + layer.hooks.afterRender.tap('PixelPickingPlugin', () => { + layer.models.forEach((model) => + model.addUniforms({ + u_PickingStage: PickingStage.NONE, + u_PickingColor: [0, 0, 0], + u_HighlightColor: [0, 0, 0, 0], + }), + ); + }); + + layer.hooks.beforeHighlight.tap( + 'PixelPickingPlugin', + // @ts-ignore + (l: unknown, pickedColor: unknown) => { + const { highlightColor } = layer.getStyleOptions(); + const highlightColorInArray = + typeof highlightColor === 'string' + ? rgb2arr(highlightColor) + : highlightColor || [1, 0, 0, 1]; + layer.models.forEach((model) => + model.addUniforms({ + u_PickingStage: PickingStage.HIGHLIGHT, + u_PickingColor: pickedColor as number[], + u_HighlightColor: highlightColorInArray.map((c) => c * 255), + }), + ); + }, + ); + // } + } +} diff --git a/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts b/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts new file mode 100644 index 0000000000..519fd98b68 --- /dev/null +++ b/packages/layers/src/plugins/RegisterStyleAttributePlugin.ts @@ -0,0 +1,66 @@ +import { + AttributeType, + gl, + IEncodeFeature, + ILayer, + ILayerPlugin, + ILogService, + IStyleAttributeService, + lazyInject, + TYPES, +} from '@l7/core'; + +/** + * 在初始化阶段完成属性的注册,以及首次根据 Layer 指定的三角化方法完成 indices 和 attribute 的创建 + */ +export default class RegisterStyleAttributePlugin implements ILayerPlugin { + @lazyInject(TYPES.ILogService) + private readonly logger: ILogService; + + public apply(layer: ILayer) { + layer.hooks.init.tap('RegisterStyleAttributePlugin', () => { + this.registerBuiltinAttributes(layer); + }); + } + + private registerBuiltinAttributes(layer: ILayer) { + layer.styleAttributeService.registerStyleAttribute({ + name: 'position', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Position', + buffer: { + data: [], + type: gl.FLOAT, + }, + size: 3, + update: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + ) => { + return [vertex[0], vertex[1], 0]; + }, + }, + }); + + layer.styleAttributeService.registerStyleAttribute({ + name: 'color', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Color', + buffer: { + // give the WebGL driver a hint that this buffer may change + usage: gl.DYNAMIC_DRAW, + data: [], + type: gl.FLOAT, + }, + size: 4, + update: (feature: IEncodeFeature, featureIdx: number) => { + const { color } = feature; + return !color || !color.length ? [0, 0, 0, 0] : color; + }, + }, + }); + } +} diff --git a/packages/layers/src/plugins/ShaderUniformPlugin.ts b/packages/layers/src/plugins/ShaderUniformPlugin.ts index c67d27ea6a..4918d71c1b 100644 --- a/packages/layers/src/plugins/ShaderUniformPlugin.ts +++ b/packages/layers/src/plugins/ShaderUniformPlugin.ts @@ -53,6 +53,7 @@ export default class ShaderUniformPlugin implements ILayerPlugin { // 其他参数,例如视口大小、DPR 等 u_ViewportSize: [width, height], u_DevicePixelRatio: window.devicePixelRatio, + u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], }), ); diff --git a/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts b/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts new file mode 100644 index 0000000000..b11338575b --- /dev/null +++ b/packages/layers/src/plugins/UpdateStyleAttributePlugin.ts @@ -0,0 +1,31 @@ +import { ILayer, ILayerPlugin, ILogService, lazyInject, TYPES } from '@l7/core'; + +/** + * 在初始化阶段完成属性的注册,以及首次根据 Layer 指定的三角化方法完成 indices 和 attribute 的创建 + */ +export default class UpdateStyleAttributePlugin implements ILayerPlugin { + @lazyInject(TYPES.ILogService) + private readonly logger: ILogService; + + public apply(layer: ILayer) { + layer.hooks.beforeRender.tap('UpdateStyleAttributePlugin', () => { + const attributes = + layer.styleAttributeService.getLayerStyleAttributes() || []; + attributes + .filter((attribute) => attribute.needRegenerateVertices) + .forEach((attribute) => { + // 精确更新某个/某些 feature(s),需要传入 featureIdx + layer.styleAttributeService.updateAttributeByFeatureRange( + attribute.name, + layer.getEncodedData(), // 获取经过 mapping 最新的数据 + attribute.featureRange.startIndex, + attribute.featureRange.endIndex, + ); + attribute.needRegenerateVertices = false; + this.logger.info( + `regenerate vertex attributes: ${attribute.name} finished`, + ); + }); + }); + } +} diff --git a/packages/layers/src/point/buffers/ExtrudeBuffer.ts b/packages/layers/src/point/buffers/ExtrudeBuffer.ts index a3b168f3a3..f0cd0d8d1a 100644 --- a/packages/layers/src/point/buffers/ExtrudeBuffer.ts +++ b/packages/layers/src/point/buffers/ExtrudeBuffer.ts @@ -1,79 +1,79 @@ -import BaseBuffer, { - IBufferInfo, - IEncodeFeature, - Position, -} from '../../core/BaseBuffer'; -import extrudePolygon, { IExtrudeGeomety } from '../shape/extrude'; -import { geometryShape, ShapeType2D, ShapeType3D } from '../shape/Path'; -interface IGeometryCache { - [key: string]: IExtrudeGeomety; -} -export default class ExtrudeBuffer extends BaseBuffer { - private indexOffset: number = 0; - private verticesOffset: number = 0; - private geometryCache: IGeometryCache; - public buildFeatures() { - const layerData = this.data as IEncodeFeature[]; - layerData.forEach((feature: IEncodeFeature) => { - this.calculateFill(feature); - }); - } +// import BaseBuffer, { +// IBufferInfo, +// IEncodeFeature, +// Position, +// } from '../../core/BaseBuffer'; +// import extrudePolygon, { IExtrudeGeomety } from '../shape/extrude'; +// import { geometryShape, ShapeType2D, ShapeType3D } from '../shape/Path'; +// interface IGeometryCache { +// [key: string]: IExtrudeGeomety; +// } +// export default class ExtrudeBuffer extends BaseBuffer { +// private indexOffset: number = 0; +// private verticesOffset: number = 0; +// private geometryCache: IGeometryCache; +// public buildFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// layerData.forEach((feature: IEncodeFeature) => { +// this.calculateFill(feature); +// }); +// } - protected calculateFeatures() { - const layerData = this.data as IEncodeFeature[]; - this.geometryCache = {}; - this.verticesOffset = 0; - this.indexOffset = 0; - layerData.forEach((feature: IEncodeFeature) => { - const { shape } = feature; - const { positions, index } = this.getGeometry(shape as ShapeType3D); - this.verticesCount += positions.length / 3; - this.indexCount += index.length; - }); - } - protected initAttributes() { - super.initAttributes(); - this.attributes.miters = new Float32Array(this.verticesCount * 3); - this.attributes.normals = new Float32Array(this.verticesCount * 3); - this.attributes.sizes = new Float32Array(this.verticesCount * 3); - } - private calculateFill(feature: IEncodeFeature) { - const { coordinates, shape } = feature; - const instanceGeometry = this.getGeometry(shape as ShapeType3D); - const numPoint = instanceGeometry.positions.length / 3; - feature.bufferInfo = { - verticesOffset: this.verticesOffset, - indexOffset: this.indexOffset, - dimensions: 3, - }; - this.encodeArray(feature, numPoint); - this.attributes.miters.set( - instanceGeometry.positions, - this.verticesOffset * 3, - ); - const indexArray = instanceGeometry.index.map((v) => { - return v + this.verticesOffset; - }); - this.indexArray.set(indexArray, this.indexOffset); - const position: number[] = []; - for (let i = 0; i < numPoint; i++) { - const coor = coordinates as Position; - position.push(coor[0], coor[1], coor[2] || 0); - } - this.attributes.positions.set(position, this.verticesOffset * 3); - this.verticesOffset += numPoint; - this.indexOffset += indexArray.length; - } +// protected calculateFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// this.geometryCache = {}; +// this.verticesOffset = 0; +// this.indexOffset = 0; +// layerData.forEach((feature: IEncodeFeature) => { +// const { shape } = feature; +// const { positions, index } = this.getGeometry(shape as ShapeType3D); +// this.verticesCount += positions.length / 3; +// this.indexCount += index.length; +// }); +// } +// protected initAttributes() { +// super.initAttributes(); +// this.attributes.miters = new Float32Array(this.verticesCount * 3); +// this.attributes.normals = new Float32Array(this.verticesCount * 3); +// this.attributes.sizes = new Float32Array(this.verticesCount * 3); +// } +// private calculateFill(feature: IEncodeFeature) { +// const { coordinates, shape } = feature; +// const instanceGeometry = this.getGeometry(shape as ShapeType3D); +// const numPoint = instanceGeometry.positions.length / 3; +// feature.bufferInfo = { +// verticesOffset: this.verticesOffset, +// indexOffset: this.indexOffset, +// dimensions: 3, +// }; +// this.encodeArray(feature, numPoint); +// this.attributes.miters.set( +// instanceGeometry.positions, +// this.verticesOffset * 3, +// ); +// const indexArray = instanceGeometry.index.map((v) => { +// return v + this.verticesOffset; +// }); +// this.indexArray.set(indexArray, this.indexOffset); +// const position: number[] = []; +// for (let i = 0; i < numPoint; i++) { +// const coor = coordinates as Position; +// position.push(coor[0], coor[1], coor[2] || 0); +// } +// this.attributes.positions.set(position, this.verticesOffset * 3); +// this.verticesOffset += numPoint; +// this.indexOffset += indexArray.length; +// } - private getGeometry(shape: ShapeType3D): IExtrudeGeomety { - if (this.geometryCache && this.geometryCache[shape]) { - return this.geometryCache[shape]; - } - const path = geometryShape[shape] - ? geometryShape[shape]() - : geometryShape.cylinder(); - const geometry = extrudePolygon([path]); - this.geometryCache[shape] = geometry; - return geometry; - } -} +// private getGeometry(shape: ShapeType3D): IExtrudeGeomety { +// if (this.geometryCache && this.geometryCache[shape]) { +// return this.geometryCache[shape]; +// } +// const path = geometryShape[shape] +// ? geometryShape[shape]() +// : geometryShape.cylinder(); +// const geometry = extrudePolygon([path]); +// this.geometryCache[shape] = geometry; +// return geometry; +// } +// } diff --git a/packages/layers/src/point/buffers/ImageBuffer.ts b/packages/layers/src/point/buffers/ImageBuffer.ts index 510a77bdd8..03aef61af5 100644 --- a/packages/layers/src/point/buffers/ImageBuffer.ts +++ b/packages/layers/src/point/buffers/ImageBuffer.ts @@ -1,22 +1,22 @@ -import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer'; -export default class ImageBuffer extends BaseBuffer { - protected calculateFeatures() { - const layerData = this.data as IEncodeFeature[]; - this.verticesCount = layerData.length; - this.indexCount = layerData.length; - } - protected buildFeatures() { - const layerData = this.data as IEncodeFeature[]; - this.attributes.uv = new Float32Array(this.verticesCount * 2); - layerData.forEach((item: IEncodeFeature, index: number) => { - const { color = [0, 0, 0, 0], size, id, shape, coordinates } = item; - const { x, y } = this.iconMap[shape as string] || { x: 0, y: 0 }; - const coor = coordinates as Position; - this.attributes.positions.set(coor, index * 3); - this.attributes.colors.set(color, index * 4); - this.attributes.pickingIds.set([id as number], index); - this.attributes.sizes.set([size as number], index); // - this.attributes.uv.set([x, y], index * 2); - }); - } -} +// import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer'; +// export default class ImageBuffer extends BaseBuffer { +// protected calculateFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// this.verticesCount = layerData.length; +// this.indexCount = layerData.length; +// } +// protected buildFeatures() { +// const layerData = this.data as IEncodeFeature[]; +// this.attributes.uv = new Float32Array(this.verticesCount * 2); +// layerData.forEach((item: IEncodeFeature, index: number) => { +// const { color = [0, 0, 0, 0], size, id, shape, coordinates } = item; +// const { x, y } = this.iconMap[shape as string] || { x: 0, y: 0 }; +// const coor = coordinates as Position; +// this.attributes.positions.set(coor, index * 3); +// this.attributes.colors.set(color, index * 4); +// this.attributes.pickingIds.set([id as number], index); +// this.attributes.sizes.set([size as number], index); // +// this.attributes.uv.set([x, y], index * 2); +// }); +// } +// } diff --git a/packages/layers/src/point/index.ts b/packages/layers/src/point/index.ts index 0a7c4e3a64..bca931557b 100644 --- a/packages/layers/src/point/index.ts +++ b/packages/layers/src/point/index.ts @@ -1,216 +1,216 @@ -import { - gl, - ILayer, - IRendererService, - IShaderModuleService, - lazyInject, - packCircleVertex, - TYPES, -} from '@l7/core'; -import { featureEach } from '@turf/meta'; -import BaseLayer from '../core/BaseLayer'; -import circleFrag from './shaders/circle_frag.glsl'; -import circleVert from './shaders/circle_vert.glsl'; +// import { +// gl, +// ILayer, +// IRendererService, +// IShaderModuleService, +// lazyInject, +// packCircleVertex, +// TYPES, +// } from '@l7/core'; +// import { featureEach } from '@turf/meta'; +// import BaseLayer from '../core/BaseLayer'; +// import circleFrag from './shaders/circle_frag.glsl'; +// import circleVert from './shaders/circle_vert.glsl'; -export interface IPointLayerStyleOptions { - pointShape: string; - pointColor: [number, number, number]; - pointRadius: number; - pointOpacity: number; - strokeWidth: number; - strokeColor: [number, number, number]; - strokeOpacity: number; -} +// export interface IPointLayerStyleOptions { +// pointShape: string; +// pointColor: [number, number, number]; +// pointRadius: number; +// pointOpacity: number; +// strokeWidth: number; +// strokeColor: [number, number, number]; +// strokeOpacity: number; +// } -interface IPointFeature { - coordinates: [number, number] | [number, number]; - [key: string]: any; -} +// interface IPointFeature { +// coordinates: [number, number] | [number, number]; +// [key: string]: any; +// } -/** - * PointLayer - */ -export default class PointLayer extends BaseLayer { - public styleOptions: IPointLayerStyleOptions = { - pointShape: 'circle', - pointColor: [81, 187, 214], - pointRadius: 10, - pointOpacity: 1, - strokeWidth: 2, - strokeColor: [255, 255, 255], - strokeOpacity: 1, - }; +// /** +// * PointLayer +// */ +// export default class PointLayer extends BaseLayer { +// public styleOptions: IPointLayerStyleOptions = { +// pointShape: 'circle', +// pointColor: [81, 187, 214], +// pointRadius: 10, +// pointOpacity: 1, +// strokeWidth: 2, +// strokeColor: [255, 255, 255], +// strokeOpacity: 1, +// }; - public name: string = 'pointLayer'; +// public name: string = 'pointLayer'; - @lazyInject(TYPES.IShaderModuleService) - private readonly shaderModule: IShaderModuleService; +// @lazyInject(TYPES.IShaderModuleService) +// private readonly shaderModule: IShaderModuleService; - @lazyInject(TYPES.IRendererService) - private readonly renderer: IRendererService; +// @lazyInject(TYPES.IRendererService) +// private readonly renderer: IRendererService; - private pointFeatures: IPointFeature[] = []; +// private pointFeatures: IPointFeature[] = []; - // public style(options: Partial) { - // // this.layerStyleService.update(options); - // // this.styleOptions = { - // // ...this.styleOptions, - // // ...options, - // // }; - // } +// // public style(options: Partial) { +// // // this.layerStyleService.update(options); +// // // this.styleOptions = { +// // // ...this.styleOptions, +// // // ...options, +// // // }; +// // } - public render(): ILayer { - this.models.forEach((model) => - model.draw({ - uniforms: { - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - u_stroke_width: 1, - u_blur: 0, - u_opacity: 1, - u_stroke_color: [1, 1, 1, 1], - u_stroke_opacity: 1, - }, - }), - ); - return this; - } +// public render(): ILayer { +// this.models.forEach((model) => +// model.draw({ +// uniforms: { +// u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], +// u_stroke_width: 1, +// u_blur: 0, +// u_opacity: 1, +// u_stroke_color: [1, 1, 1, 1], +// u_stroke_opacity: 1, +// }, +// }), +// ); +// return this; +// } - protected buildModels() { - this.shaderModule.registerModule('circle', { - vs: circleVert, - fs: circleFrag, - }); +// protected buildModels() { +// this.shaderModule.registerModule('circle', { +// vs: circleVert, +// fs: circleFrag, +// }); - this.models = []; - const { vs, fs, uniforms } = this.shaderModule.getModule('circle'); - // TODO: fix me - const source = this.getSource(); - featureEach( - // @ts-ignore - source.originData, - ({ geometry: { coordinates }, properties }) => { - this.pointFeatures.push({ - coordinates, - }); - }, - ); +// this.models = []; +// const { vs, fs, uniforms } = this.shaderModule.getModule('circle'); +// // TODO: fix me +// const source = this.getSource(); +// featureEach( +// // @ts-ignore +// source.originData, +// ({ geometry: { coordinates }, properties }) => { +// this.pointFeatures.push({ +// coordinates, +// }); +// }, +// ); - const { - packedBuffer, - packedBuffer2, - packedBuffer3, - indexBuffer, - positionBuffer, - // @ts-ignore - } = this.buildPointBuffers(this.pointFeatures); +// const { +// packedBuffer, +// packedBuffer2, +// packedBuffer3, +// indexBuffer, +// positionBuffer, +// // @ts-ignore +// } = this.buildPointBuffers(this.pointFeatures); - const { - createAttribute, - createBuffer, - createElements, - createModel, - } = this.renderer; +// const { +// createAttribute, +// createBuffer, +// createElements, +// createModel, +// } = this.renderer; - this.models.push( - createModel({ - attributes: { - a_Position: createAttribute({ - buffer: createBuffer({ - data: positionBuffer, - type: gl.FLOAT, - }), - }), - a_packed_data: createAttribute({ - buffer: createBuffer({ - data: packedBuffer, - type: gl.FLOAT, - }), - }), - }, - uniforms, - fs, - vs, - count: indexBuffer.length, - primitive: gl.TRIANGLES, - elements: createElements({ - data: indexBuffer, - type: gl.UNSIGNED_INT, - }), - depth: { enable: false }, - blend: { - enable: true, - func: { - srcRGB: gl.SRC_ALPHA, - srcAlpha: 1, - dstRGB: gl.ONE_MINUS_SRC_ALPHA, - dstAlpha: 1, - }, - }, - }), - ); - } +// this.models.push( +// createModel({ +// attributes: { +// a_Position: createAttribute({ +// buffer: createBuffer({ +// data: positionBuffer, +// type: gl.FLOAT, +// }), +// }), +// a_packed_data: createAttribute({ +// buffer: createBuffer({ +// data: packedBuffer, +// type: gl.FLOAT, +// }), +// }), +// }, +// uniforms, +// fs, +// vs, +// count: indexBuffer.length, +// primitive: gl.TRIANGLES, +// elements: createElements({ +// data: indexBuffer, +// type: gl.UNSIGNED_INT, +// }), +// depth: { enable: false }, +// blend: { +// enable: true, +// func: { +// srcRGB: gl.SRC_ALPHA, +// srcAlpha: 1, +// dstRGB: gl.ONE_MINUS_SRC_ALPHA, +// dstAlpha: 1, +// }, +// }, +// }), +// ); +// } - private buildPointBuffers(pointFeatures: IPointFeature[]) { - const packedBuffer: number[][] = []; - const packedBuffer2: number[][] = []; - const packedBuffer3: number[][] = []; - const positionBuffer: number[][] = []; - const indexBuffer: Array<[number, number, number]> = []; +// private buildPointBuffers(pointFeatures: IPointFeature[]) { +// const packedBuffer: number[][] = []; +// const packedBuffer2: number[][] = []; +// const packedBuffer3: number[][] = []; +// const positionBuffer: number[][] = []; +// const indexBuffer: Array<[number, number, number]> = []; - const { - pointColor, - pointRadius, - pointShape, - pointOpacity, - strokeColor, - strokeWidth, - strokeOpacity, - } = this.styleOptions; +// const { +// pointColor, +// pointRadius, +// pointShape, +// pointOpacity, +// strokeColor, +// strokeWidth, +// strokeOpacity, +// } = this.styleOptions; - let i = 0; - pointFeatures.forEach((pointFeature) => { - // TODO: 判断是否使用瓦片坐标 - const [tileX, tileY] = pointFeature.coordinates; +// let i = 0; +// pointFeatures.forEach((pointFeature) => { +// // TODO: 判断是否使用瓦片坐标 +// const [tileX, tileY] = pointFeature.coordinates; - // 压缩顶点数据 - // TODO: 某些变量通过 uniform 而非 vertex attribute 传入 - const { - packedBuffer: packed1, - packedBuffer2: packed2, - packedBuffer3: packed3, - } = packCircleVertex({ - color: [...pointColor, 255], - radius: pointRadius, - tileX: 0, - tileY: 0, - shape: pointShape, - opacity: pointOpacity, - strokeColor: [...strokeColor, 255], - strokeOpacity, - strokeWidth, - }); - packedBuffer.push(...packed1); - packedBuffer2.push(...packed2); - packedBuffer3.push(...packed3); +// // 压缩顶点数据 +// // TODO: 某些变量通过 uniform 而非 vertex attribute 传入 +// const { +// packedBuffer: packed1, +// packedBuffer2: packed2, +// packedBuffer3: packed3, +// } = packCircleVertex({ +// color: [...pointColor, 255], +// radius: pointRadius, +// tileX: 0, +// tileY: 0, +// shape: pointShape, +// opacity: pointOpacity, +// strokeColor: [...strokeColor, 255], +// strokeOpacity, +// strokeWidth, +// }); +// packedBuffer.push(...packed1); +// packedBuffer2.push(...packed2); +// packedBuffer3.push(...packed3); - // 经纬度坐标 - positionBuffer.push([tileX, tileY]); - positionBuffer.push([tileX, tileY]); - positionBuffer.push([tileX, tileY]); - positionBuffer.push([tileX, tileY]); +// // 经纬度坐标 +// positionBuffer.push([tileX, tileY]); +// positionBuffer.push([tileX, tileY]); +// positionBuffer.push([tileX, tileY]); +// positionBuffer.push([tileX, tileY]); - // 构造 index - indexBuffer.push([0 + i, 1 + i, 2 + i]); - indexBuffer.push([2 + i, 3 + i, 0 + i]); - i += 4; - }); +// // 构造 index +// indexBuffer.push([0 + i, 1 + i, 2 + i]); +// indexBuffer.push([2 + i, 3 + i, 0 + i]); +// i += 4; +// }); - return { - packedBuffer, - packedBuffer2, - packedBuffer3, - indexBuffer, - positionBuffer, - }; - } -} +// return { +// packedBuffer, +// packedBuffer2, +// packedBuffer3, +// indexBuffer, +// positionBuffer, +// }; +// } +// } diff --git a/packages/layers/src/point/point.ts b/packages/layers/src/point/point.ts index bcd55c97be..59f97bef18 100644 --- a/packages/layers/src/point/point.ts +++ b/packages/layers/src/point/point.ts @@ -1,130 +1,130 @@ -import { - gl, - IIconService, - IRendererService, - IShaderModuleService, - lazyInject, - TYPES, -} from '@l7/core'; -import BaseLayer from '../core/BaseLayer'; -import ExtrudeBuffer from './buffers/ExtrudeBuffer'; -import ImageBuffer from './buffers/ImageBuffer'; -import extrude_frag from './shaders/extrude_frag.glsl'; -import extrude_vert from './shaders/extrude_vert.glsl'; -import image_frag from './shaders/image_frag.glsl'; -import image_vert from './shaders/image_vert.glsl'; +// import { +// gl, +// IIconService, +// IRendererService, +// IShaderModuleService, +// lazyInject, +// TYPES, +// } from '@l7/core'; +// import BaseLayer from '../core/BaseLayer'; +// import ExtrudeBuffer from './buffers/ExtrudeBuffer'; +// import ImageBuffer from './buffers/ImageBuffer'; +// import extrude_frag from './shaders/extrude_frag.glsl'; +// import extrude_vert from './shaders/extrude_vert.glsl'; +// import image_frag from './shaders/image_frag.glsl'; +// import image_vert from './shaders/image_vert.glsl'; -export default class PointLayer extends BaseLayer { - public name: string = 'PointLayer'; +// export default class PointLayer extends BaseLayer { +// public name: string = 'PointLayer'; - @lazyInject(TYPES.IShaderModuleService) - private readonly shaderModule: IShaderModuleService; +// @lazyInject(TYPES.IShaderModuleService) +// private readonly shaderModule: IShaderModuleService; - @lazyInject(TYPES.IRendererService) - private readonly renderer: IRendererService; +// @lazyInject(TYPES.IRendererService) +// private readonly renderer: IRendererService; - protected renderModels() { - this.models.forEach((model) => - model.draw({ - uniforms: { - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - }, - }), - ); - return this; - } +// protected renderModels() { +// this.models.forEach((model) => +// model.draw({ +// uniforms: { +// u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], +// }, +// }), +// ); +// return this; +// } - protected buildModels(): void { - this.shaderModule.registerModule('point', { - vs: extrude_vert, - fs: extrude_frag, - }); - this.shaderModule.registerModule('pointImage', { - vs: image_vert, - fs: image_frag, - }); +// protected buildModels(): void { +// this.shaderModule.registerModule('point', { +// vs: extrude_vert, +// fs: extrude_frag, +// }); +// this.shaderModule.registerModule('pointImage', { +// vs: image_vert, +// fs: image_frag, +// }); - this.models = []; - const { vs, fs, uniforms } = this.shaderModule.getModule('pointImage'); - // const buffer = new ExtrudeBuffer({ - // data: this.getEncodedData(), - // }); - // buffer.computeVertexNormals('miters', false); - const { - createAttribute, - createBuffer, - createElements, - createTexture2D, - createModel, - } = this.renderer; - const buffer = new ImageBuffer({ - data: this.getEncodedData(), - iconMap: this.iconService.getIconMap(), - }); - this.models.push( - createModel({ - attributes: { - a_Position: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.positions, - type: gl.FLOAT, - }), - size: 3, - }), - a_normal: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.normals, - type: gl.FLOAT, - }), - size: 3, - }), - a_color: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.colors, - type: gl.FLOAT, - }), - size: 4, - }), - a_size: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.sizes, - type: gl.FLOAT, - }), - size: 1, - }), - a_uv: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.uv, - type: gl.FLOAT, - }), - size: 2, - }), - // a_shape: createAttribute({ - // buffer: createBuffer({ - // data: buffer.attributes.miters, - // type: gl.FLOAT, - // }), - // size: 3, - // }), - }, - uniforms: { - ...uniforms, - u_opacity: this.styleOption.opacity as number, - u_texture: createTexture2D({ - data: this.iconService.getCanvas(), - width: 1024, - height: this.iconService.canvasHeight, - }), - }, - fs, - vs, - primitive: gl.POINTS, - count: buffer.verticesCount, - // elements: createElements({ - // data: buffer.indexArray, - // type: gl.UNSIGNED_INT, - // }), - }), - ); - } -} +// this.models = []; +// const { vs, fs, uniforms } = this.shaderModule.getModule('pointImage'); +// // const buffer = new ExtrudeBuffer({ +// // data: this.getEncodedData(), +// // }); +// // buffer.computeVertexNormals('miters', false); +// const { +// createAttribute, +// createBuffer, +// createElements, +// createTexture2D, +// createModel, +// } = this.renderer; +// const buffer = new ImageBuffer({ +// data: this.getEncodedData(), +// iconMap: this.iconService.getIconMap(), +// }); +// this.models.push( +// createModel({ +// attributes: { +// a_Position: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.positions, +// type: gl.FLOAT, +// }), +// size: 3, +// }), +// a_normal: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.normals, +// type: gl.FLOAT, +// }), +// size: 3, +// }), +// a_color: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.colors, +// type: gl.FLOAT, +// }), +// size: 4, +// }), +// a_size: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.sizes, +// type: gl.FLOAT, +// }), +// size: 1, +// }), +// a_uv: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.uv, +// type: gl.FLOAT, +// }), +// size: 2, +// }), +// // a_shape: createAttribute({ +// // buffer: createBuffer({ +// // data: buffer.attributes.miters, +// // type: gl.FLOAT, +// // }), +// // size: 3, +// // }), +// }, +// uniforms: { +// ...uniforms, +// u_opacity: this.styleOption.opacity as number, +// u_texture: createTexture2D({ +// data: this.iconService.getCanvas(), +// width: 1024, +// height: this.iconService.canvasHeight, +// }), +// }, +// fs, +// vs, +// primitive: gl.POINTS, +// count: buffer.verticesCount, +// // elements: createElements({ +// // data: buffer.indexArray, +// // type: gl.UNSIGNED_INT, +// // }), +// }), +// ); +// } +// } diff --git a/packages/layers/src/polygon/__tests__/polygonTriangulation.spec.ts b/packages/layers/src/polygon/__tests__/polygonTriangulation.spec.ts new file mode 100644 index 0000000000..d44cf2681b --- /dev/null +++ b/packages/layers/src/polygon/__tests__/polygonTriangulation.spec.ts @@ -0,0 +1,16 @@ +import { IEncodeFeature } from '@l7/core'; +import { polygonTriangulation } from '..'; + +describe('PolygonTriangulation', () => { + it('should do triangulation with a polygon correctly', () => { + const mockFeature: IEncodeFeature = { + coordinates: [[[0, 0], [1, 0], [1, 1], [0, 1]]], + color: [1, 0, 0, 0], + }; + const { indices, vertices, size } = polygonTriangulation(mockFeature); + + expect(indices).toEqual([2, 3, 0, 0, 1, 2]); + expect(vertices).toEqual([0, 0, 1, 0, 1, 1, 0, 1]); + expect(size).toEqual(2); + }); +}); diff --git a/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts b/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts deleted file mode 100644 index 9b0697d590..0000000000 --- a/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts +++ /dev/null @@ -1,137 +0,0 @@ -import earcut from 'earcut'; -import BufferBase, { - IBufferInfo, - IEncodeFeature, - Position, -} from '../../core/BaseBuffer'; -export default class ExtrudeBuffer extends BufferBase { - public buildFeatures() { - const layerData = this.data as IEncodeFeature[]; - layerData.forEach((feature: IEncodeFeature) => { - this.calculateTop(feature); - this.calculateWall(feature); - delete feature.bufferInfo; - }); - } - - public calculateFeatures() { - const layerData = this.data as IEncodeFeature[]; - // 计算长 - layerData.forEach((feature: IEncodeFeature) => { - const coordinates = feature.coordinates as Position[][]; - const flattengeo = earcut.flatten(coordinates); - const n = this.checkIsClosed(coordinates) - ? coordinates[0].length - 1 - : coordinates[0].length; - const { vertices, dimensions, holes } = flattengeo; - const indexArray = earcut(vertices, holes, dimensions).map( - (v) => this.verticesCount + v, - ); - const bufferInfo: IBufferInfo = { - dimensions, - vertices, - indexArray, - verticesOffset: this.verticesCount + 0, - indexOffset: this.indexCount + 0, - faceNum: n, - }; - this.indexCount += indexArray.length + n * 6; - this.verticesCount += vertices.length / dimensions + n * 4; - feature.bufferInfo = bufferInfo; - }); - } - protected calculateWall(feature: IEncodeFeature) { - const size = feature.size || 0; - const bufferInfo = feature.bufferInfo as IBufferInfo; - const { - vertices, - indexOffset, - verticesOffset, - faceNum, - dimensions, - } = bufferInfo; - this.encodeArray(feature, faceNum * 4); - for (let i = 0; i < faceNum; i++) { - const prePoint = vertices.slice(i * dimensions, (i + 1) * dimensions); - const nextPoint = vertices.slice( - (i + 1) * dimensions, - (i + 2) * dimensions, - ); - this.calculateExtrudeFace( - prePoint, - nextPoint, - verticesOffset + i * 4, - indexOffset + i * 6, - size as number, - ); - bufferInfo.verticesOffset += 4; - bufferInfo.indexOffset += 6; - feature.bufferInfo = bufferInfo; - } - } - private calculateTop(feature: IEncodeFeature) { - const size = feature.size || 1; - const bufferInfo = feature.bufferInfo as IBufferInfo; - const { - indexArray, - vertices, - indexOffset, - verticesOffset, - dimensions, - } = bufferInfo; - const pointCount = vertices.length / dimensions; - this.encodeArray(feature, vertices.length / dimensions); - // 添加顶点 - for (let i = 0; i < pointCount; i++) { - this.attributes.positions.set( - [vertices[i * dimensions], vertices[i * dimensions + 1], size], - (verticesOffset + i) * 3, - ); - // 顶部文理坐标计算 - // if (this.uv) { - // // TODO 用过BBox计算纹理坐标 - // this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2); - // } - } - bufferInfo.verticesOffset += pointCount; - // 添加顶点索引 - this.indexArray.set(indexArray, indexOffset); // 顶部坐标 - bufferInfo.indexOffset += indexArray.length; - feature.bufferInfo = bufferInfo; - } - private calculateExtrudeFace( - prePoint: number[], - nextPoint: number[], - positionOffset: number, - indexOffset: number | undefined, - size: number, - ) { - this.attributes.positions.set( - [ - prePoint[0], - prePoint[1], - size, - nextPoint[0], - nextPoint[1], - size, - prePoint[0], - prePoint[1], - 0, - nextPoint[0], - nextPoint[1], - 0, - ], - positionOffset * 3, - ); - const indexArray = [1, 2, 0, 3, 2, 1].map((v) => { - return v + positionOffset; - }); - // if (this.uv) { - // this.attributes.uv.set( - // [0.1, 0, 0, 0, 0.1, size / 2000, 0, size / 2000], - // positionOffset * 2, - // ); - // } - this.indexArray.set(indexArray, indexOffset); - } -} diff --git a/packages/layers/src/polygon/buffers/FillBuffer.ts b/packages/layers/src/polygon/buffers/FillBuffer.ts deleted file mode 100644 index c1e03a1459..0000000000 --- a/packages/layers/src/polygon/buffers/FillBuffer.ts +++ /dev/null @@ -1,69 +0,0 @@ -import earcut from 'earcut'; -import BufferBase, { - IBufferInfo, - IEncodeFeature, - Position, -} from '../../core/BaseBuffer'; -export default class FillBuffer extends BufferBase { - protected buildFeatures() { - const layerData = this.data as IEncodeFeature[]; - layerData.forEach((feature: IEncodeFeature) => { - this.calculateFill(feature); - delete feature.bufferInfo; - }); - } - - protected calculateFeatures() { - const layerData = this.data as IEncodeFeature[]; - // 计算长 - layerData.forEach((feature: IEncodeFeature) => { - const { coordinates } = feature; - const flattengeo = earcut.flatten(coordinates as Position[][]); - const { vertices, dimensions, holes } = flattengeo; - const indexArray = earcut(vertices, holes, dimensions).map( - (v) => this.verticesCount + v, - ); - const bufferInfo: IBufferInfo = { - vertices, - indexArray, - verticesOffset: this.verticesCount + 0, - indexOffset: this.indexCount + 0, - dimensions, - }; - this.indexCount += indexArray.length; - this.verticesCount += vertices.length / dimensions; - feature.bufferInfo = bufferInfo; - }); - } - - private calculateFill(feature: IEncodeFeature) { - const bufferInfo = feature.bufferInfo as IBufferInfo; - const { - indexArray, - vertices, - indexOffset, - verticesOffset, - dimensions = 3, - } = bufferInfo; - const pointCount = vertices.length / dimensions; - this.encodeArray(feature, pointCount); - // 添加顶点 - for (let i = 0; i < pointCount; i++) { - this.attributes.positions.set( - [vertices[i * dimensions], vertices[i * dimensions + 1], 0], - (verticesOffset + i) * 3, - ); - // if (this.uv) { - // // TODO 用过BBox计算纹理坐标 - // this.attributes.uv.set( - // [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0], - // (verticesOffset + i) * 3, - // ); - // } - } - bufferInfo.verticesOffset += pointCount; - feature.bufferInfo = bufferInfo; - // 添加顶点索引 - this.indexArray.set(indexArray, indexOffset); // 顶部坐标 - } -} diff --git a/packages/layers/src/polygon/index.ts b/packages/layers/src/polygon/index.ts index 38f355aacf..c6d5d4a270 100644 --- a/packages/layers/src/polygon/index.ts +++ b/packages/layers/src/polygon/index.ts @@ -1,95 +1,48 @@ -import { - gl, - IRendererService, - IShaderModuleService, - lazyInject, - TYPES, -} from '@l7/core'; +import { IEncodeFeature } from '@l7/core'; +import earcut from 'earcut'; import BaseLayer from '../core/BaseLayer'; -import ExtrudeBuffer from './buffers/ExtrudeBuffer'; -import FillBuffer from './buffers/FillBuffer'; import polygon_frag from './shaders/polygon_frag.glsl'; import polygon_vert from './shaders/polygon_vert.glsl'; -export default class PolygonLayer extends BaseLayer { +interface IPolygonLayerStyleOptions { + opacity: number; +} + +export function polygonTriangulation(feature: IEncodeFeature) { + const { coordinates } = feature; + const flattengeo = earcut.flatten(coordinates); + const { vertices, dimensions, holes } = flattengeo; + + return { + indices: earcut(vertices, holes, dimensions), + vertices, + size: dimensions, + }; +} + +export default class PolygonLayer extends BaseLayer { public name: string = 'PolygonLayer'; - @lazyInject(TYPES.IShaderModuleService) - private readonly shaderModule: IShaderModuleService; - - @lazyInject(TYPES.IRendererService) - private readonly renderer: IRendererService; - protected renderModels() { + const { opacity } = this.getStyleOptions(); this.models.forEach((model) => model.draw({ uniforms: { - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + u_Opacity: opacity || 0, }, }), ); return this; } - protected buildModels(): void { - this.shaderModule.registerModule('polygon', { - vs: polygon_vert, - fs: polygon_frag, - }); - - this.models = []; - const { vs, fs, uniforms } = this.shaderModule.getModule('polygon'); - // const buffer = new ExtrudeBuffer({ - // data: this.getEncodedData(), - // }); - // buffer.computeVertexNormals(); - const buffer = new FillBuffer({ - data: this.getEncodedData(), - }); - const { - createAttribute, - createBuffer, - createElements, - createModel, - } = this.renderer; - - this.models.push( - createModel({ - attributes: { - a_Position: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.positions, - type: gl.FLOAT, - }), - size: 3, - }), - a_normal: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.normals, - type: gl.FLOAT, - }), - size: 3, - }), - a_color: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.colors, - type: gl.FLOAT, - }), - size: 4, - }), - }, - uniforms: { - ...uniforms, - u_opacity: this.styleOption.opacity as number, - }, - fs, - vs, - count: buffer.indexArray.length, - elements: createElements({ - data: buffer.indexArray, - type: gl.UNSIGNED_INT, - }), + protected buildModels() { + this.models = [ + this.buildLayerModel({ + moduleName: 'polygon', + vertexShader: polygon_vert, + fragmentShader: polygon_frag, + triangulation: polygonTriangulation, }), - ); + ]; } } diff --git a/packages/layers/src/polygon/shaders/polygon_frag.glsl b/packages/layers/src/polygon/shaders/polygon_frag.glsl index 6f26ac6083..6543d1b4d5 100644 --- a/packages/layers/src/polygon/shaders/polygon_frag.glsl +++ b/packages/layers/src/polygon/shaders/polygon_frag.glsl @@ -1,6 +1,10 @@ -varying vec4 v_color; -uniform float u_opacity: 1.0; +uniform float u_Opacity: 1.0; +varying vec4 v_Color; + +#pragma include "picking" + void main() { - gl_FragColor = v_color; - gl_FragColor.a *= u_opacity; + gl_FragColor = v_Color; + gl_FragColor.a *= u_Opacity; + gl_FragColor = filterColor(gl_FragColor); } \ No newline at end of file diff --git a/packages/layers/src/polygon/shaders/polygon_vert.glsl b/packages/layers/src/polygon/shaders/polygon_vert.glsl index b6a1610cb9..e83cbd85bb 100644 --- a/packages/layers/src/polygon/shaders/polygon_vert.glsl +++ b/packages/layers/src/polygon/shaders/polygon_vert.glsl @@ -1,14 +1,18 @@ -attribute vec4 a_color; +attribute vec4 a_Color; attribute vec3 a_Position; -attribute vec3 a_normal; +// attribute vec3 a_normal; uniform mat4 u_ModelMatrix; -varying vec4 v_color; +varying vec4 v_Color; #pragma include "projection" +#pragma include "picking" + void main() { - v_color = a_color; + v_Color = a_Color; vec4 project_pos = project_position(vec4(a_Position, 1.0)); gl_Position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0)); + + setPickingColor(a_PickingColor); } diff --git a/packages/layers/src/raster/buffers/ImageBuffer.ts b/packages/layers/src/raster/buffers/ImageBuffer.ts index 66f3398e2d..8126606a6b 100644 --- a/packages/layers/src/raster/buffers/ImageBuffer.ts +++ b/packages/layers/src/raster/buffers/ImageBuffer.ts @@ -1,33 +1,33 @@ -import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer'; -interface IImageFeature extends IEncodeFeature { - images: any[]; -} -export default class ImageBuffer extends BaseBuffer { - protected calculateFeatures() { - this.verticesCount = 6; - this.indexCount = 6; - } - protected buildFeatures() { - this.attributes.uv = new Float32Array(this.verticesCount * 2); - const layerData = this.data as IImageFeature[]; - const coordinates = layerData[0].coordinates as Position[]; - const positions: number[] = [ - ...coordinates[0], - 0, - coordinates[1][0], - coordinates[0][1], - 0, - ...coordinates[1], - 0, - ...coordinates[0], - 0, - ...coordinates[1], - 0, - coordinates[0][0], - coordinates[1][1], - 0, - ]; - this.attributes.positions.set(positions, 0); - this.attributes.uv.set([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0], 0); - } -} +// import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer'; +// interface IImageFeature extends IEncodeFeature { +// images: any[]; +// } +// export default class ImageBuffer extends BaseBuffer { +// protected calculateFeatures() { +// this.verticesCount = 6; +// this.indexCount = 6; +// } +// protected buildFeatures() { +// this.attributes.uv = new Float32Array(this.verticesCount * 2); +// const layerData = this.data as IImageFeature[]; +// const coordinates = layerData[0].coordinates as Position[]; +// const positions: number[] = [ +// ...coordinates[0], +// 0, +// coordinates[1][0], +// coordinates[0][1], +// 0, +// ...coordinates[1], +// 0, +// ...coordinates[0], +// 0, +// ...coordinates[1], +// 0, +// coordinates[0][0], +// coordinates[1][1], +// 0, +// ]; +// this.attributes.positions.set(positions, 0); +// this.attributes.uv.set([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0], 0); +// } +// } diff --git a/packages/layers/src/raster/index.ts b/packages/layers/src/raster/index.ts index 1b4366dc4f..93ebc02f8f 100644 --- a/packages/layers/src/raster/index.ts +++ b/packages/layers/src/raster/index.ts @@ -1,87 +1,87 @@ -import { - gl, - IRendererService, - IShaderModuleService, - ITexture2D, - lazyInject, - TYPES, -} from '@l7/core'; -import BaseLayer from '../core/BaseLayer'; -import ImageBuffer from './buffers/ImageBuffer'; -import image_frag from './shaders/image_frag.glsl'; -import image_vert from './shaders/image_vert.glsl'; -export default class ImageLayer extends BaseLayer { - public name: string = 'imageLayer'; - @lazyInject(TYPES.IShaderModuleService) - private readonly shaderModule: IShaderModuleService; +// import { +// gl, +// IRendererService, +// IShaderModuleService, +// ITexture2D, +// lazyInject, +// TYPES, +// } from '@l7/core'; +// import BaseLayer from '../core/BaseLayer'; +// import ImageBuffer from './buffers/ImageBuffer'; +// import image_frag from './shaders/image_frag.glsl'; +// import image_vert from './shaders/image_vert.glsl'; +// export default class ImageLayer extends BaseLayer { +// public name: string = 'imageLayer'; +// @lazyInject(TYPES.IShaderModuleService) +// private readonly shaderModule: IShaderModuleService; - @lazyInject(TYPES.IRendererService) - private readonly renderer: IRendererService; +// @lazyInject(TYPES.IRendererService) +// private readonly renderer: IRendererService; - protected renderModels() { - this.models.forEach((model) => - model.draw({ - uniforms: { - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - }, - }), - ); - return this; - } - protected buildModels() { - const { - createAttribute, - createBuffer, - createElements, - createTexture2D, - createModel, - } = this.renderer; - this.shaderModule.registerModule('image', { - vs: image_vert, - fs: image_frag, - }); +// protected renderModels() { +// this.models.forEach((model) => +// model.draw({ +// uniforms: { +// u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], +// }, +// }), +// ); +// return this; +// } +// protected buildModels() { +// const { +// createAttribute, +// createBuffer, +// createElements, +// createTexture2D, +// createModel, +// } = this.renderer; +// this.shaderModule.registerModule('image', { +// vs: image_vert, +// fs: image_frag, +// }); - this.models = []; - const { vs, fs, uniforms } = this.shaderModule.getModule('image'); - const source = this.getSource(); - // const imageData = await source.data.images; - const buffer = new ImageBuffer({ - data: this.getEncodedData(), - }); - source.data.images.then((imageData: HTMLImageElement[]) => { - const texture: ITexture2D = createTexture2D({ - data: imageData[0], - width: imageData[0].width, - height: imageData[0].height, - }); - this.models.push( - createModel({ - attributes: { - a_Position: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.positions, - type: gl.FLOAT, - }), - size: 3, - }), - a_uv: createAttribute({ - buffer: createBuffer({ - data: buffer.attributes.uv, - type: gl.FLOAT, - }), - size: 2, - }), - }, - uniforms: { - ...uniforms, - u_texture: texture, - u_opacity: 1.0, - }, - fs, - vs, - count: buffer.verticesCount, - }), - ); - }); - } -} +// this.models = []; +// const { vs, fs, uniforms } = this.shaderModule.getModule('image'); +// const source = this.getSource(); +// // const imageData = await source.data.images; +// const buffer = new ImageBuffer({ +// data: this.getEncodedData(), +// }); +// source.data.images.then((imageData: HTMLImageElement[]) => { +// const texture: ITexture2D = createTexture2D({ +// data: imageData[0], +// width: imageData[0].width, +// height: imageData[0].height, +// }); +// this.models.push( +// createModel({ +// attributes: { +// a_Position: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.positions, +// type: gl.FLOAT, +// }), +// size: 3, +// }), +// a_uv: createAttribute({ +// buffer: createBuffer({ +// data: buffer.attributes.uv, +// type: gl.FLOAT, +// }), +// size: 2, +// }), +// }, +// uniforms: { +// ...uniforms, +// u_texture: texture, +// u_opacity: 1.0, +// }, +// fs, +// vs, +// count: buffer.verticesCount, +// }), +// ); +// }); +// } +// } diff --git a/packages/maps/src/amap/index.ts b/packages/maps/src/amap/index.ts index b97f894e67..448a65c124 100644 --- a/packages/maps/src/amap/index.ts +++ b/packages/maps/src/amap/index.ts @@ -6,12 +6,11 @@ import { CoordinateSystem, ICoordinateSystemService, ILngLat, - IMapCamera, IMapConfig, IMapService, IPoint, IViewport, - Point, + MapType, TYPES, } from '@l7/core'; import { inject, injectable } from 'inversify'; @@ -33,9 +32,16 @@ export default class AMapService implements IMapService { private map: AMap.Map; + private $mapContainer: HTMLElement | null; + private $jsapi: HTMLScriptElement; + private viewport: Viewport; private cameraChangedCallback: (viewport: IViewport) => void; + + public getType() { + return MapType.amap; + } public getZoom(): number { return this.map.getZoom(); } @@ -119,6 +125,8 @@ export default class AMapService implements IMapService { public async init(mapConfig: IMapConfig): Promise { const { id, style, ...rest } = mapConfig; + this.$mapContainer = document.getElementById(id); + // tslint:disable-next-line:typedef await new Promise((resolve) => { // 异步加载高德地图 @@ -137,15 +145,24 @@ export default class AMapService implements IMapService { }; const url: string = `https://webapi.amap.com/maps?v=${AMAP_VERSION}&key=${AMAP_API_KEY}&plugin=Map3D&callback=onLoad`; - const jsapi: HTMLScriptElement = document.createElement('script'); - jsapi.charset = 'utf-8'; - jsapi.src = url; - document.head.appendChild(jsapi); + this.$jsapi = document.createElement('script'); + this.$jsapi.charset = 'utf-8'; + this.$jsapi.src = url; + document.head.appendChild(this.$jsapi); }); this.viewport = new Viewport(); } + public destroy() { + this.map.destroy(); + document.head.removeChild(this.$jsapi); + } + + public getMapContainer() { + return this.$mapContainer; + } + public onCameraChanged(callback: (viewport: IViewport) => void): void { this.cameraChangedCallback = callback; } diff --git a/packages/maps/src/mapbox/index.ts b/packages/maps/src/mapbox/index.ts index a07232cd3e..bf317901d2 100644 --- a/packages/maps/src/mapbox/index.ts +++ b/packages/maps/src/mapbox/index.ts @@ -10,6 +10,7 @@ import { IMapService, IPoint, IViewport, + MapType, TYPES, } from '@l7/core'; import { inject, injectable } from 'inversify'; @@ -20,6 +21,8 @@ mapboxgl.accessToken = 'pk.eyJ1IjoieGlhb2l2ZXIiLCJhIjoiY2pxcmc5OGNkMDY3cjQzbG42cXk5NTl3YiJ9.hUC5Chlqzzh0FFd_aEc-uQ'; const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12; +let counter = 1; + /** * AMapService */ @@ -29,8 +32,17 @@ export default class MapboxService implements IMapService { private readonly coordinateSystemService: ICoordinateSystemService; private map: Map & IMapboxInstance; + + private $mapContainer: HTMLElement | null; + private $link: HTMLLinkElement; + private viewport: Viewport; + private cameraChangedCallback: (viewport: IViewport) => void; + + public getType() { + return MapType.mapbox; + } public getZoom(): number { return this.map.getZoom(); } @@ -95,6 +107,9 @@ export default class MapboxService implements IMapService { public async init(mapConfig: IMapConfig): Promise { const { id, ...rest } = mapConfig; + this.$mapContainer = document.getElementById(id); + this.$mapContainer!.classList.add(`${counter++}`); + this.viewport = new Viewport(); /** @@ -112,11 +127,21 @@ export default class MapboxService implements IMapService { // 不同于高德地图,需要手动触发首次渲染 this.handleCameraChanged(); - const $link: HTMLLinkElement = document.createElement('link'); - $link.href = + this.$link = document.createElement('link'); + this.$link.href = 'https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.1/mapbox-gl.css'; - $link.rel = 'stylesheet'; - document.head.appendChild($link); + this.$link.rel = 'stylesheet'; + document.head.appendChild(this.$link); + } + + public destroy() { + document.head.removeChild(this.$link); + this.$mapContainer = null; + this.map.remove(); + } + + public getMapContainer() { + return this.$mapContainer; } public onCameraChanged(callback: (viewport: IViewport) => void): void { diff --git a/packages/renderer/src/regl/ReglAttribute.ts b/packages/renderer/src/regl/ReglAttribute.ts index e3b5d2a5cd..b91f7e8310 100644 --- a/packages/renderer/src/regl/ReglAttribute.ts +++ b/packages/renderer/src/regl/ReglAttribute.ts @@ -1,4 +1,4 @@ -import { IAttribute, IAttributeInitializationOptions } from '@l7/core'; +import { IAttribute, IAttributeInitializationOptions, IBuffer } from '@l7/core'; import regl from 'regl'; import ReglBuffer from './ReglBuffer'; @@ -7,9 +7,11 @@ import ReglBuffer from './ReglBuffer'; */ export default class ReglAttribute implements IAttribute { private attribute: regl.Attribute; + private buffer: IBuffer; constructor(gl: regl.Regl, options: IAttributeInitializationOptions) { const { buffer, offset, stride, normalized, size, divisor } = options; + this.buffer = buffer; this.attribute = { buffer: (buffer as ReglBuffer).get(), offset: offset || 0, @@ -27,7 +29,16 @@ export default class ReglAttribute implements IAttribute { return this.attribute; } + public updateBuffer(options: { + // 用于替换的数据 + data: number[] | number[][] | Uint8Array | Uint16Array | Uint32Array; + // 原 Buffer 替换位置,单位为 byte + offset: number; + }) { + this.buffer.subData(options); + } + public destroy() { - // TODO: destroy buffer? + this.buffer.destroy(); } } diff --git a/packages/renderer/src/regl/ReglElements.ts b/packages/renderer/src/regl/ReglElements.ts index 51cd564d74..72e648ab74 100644 --- a/packages/renderer/src/regl/ReglElements.ts +++ b/packages/renderer/src/regl/ReglElements.ts @@ -9,7 +9,7 @@ export default class ReglElements implements IElements { private elements: regl.Elements; constructor(reGl: regl.Regl, options: IElementsInitializationOptions) { - const { data, usage, type } = options; + const { data, usage, type, count } = options; this.elements = reGl.elements({ data, @@ -18,6 +18,7 @@ export default class ReglElements implements IElements { | 'uint8' | 'uint16' | 'uint32', + count, }); } diff --git a/packages/renderer/src/regl/ReglModel.ts b/packages/renderer/src/regl/ReglModel.ts index e688a19e4f..9f0fc60873 100644 --- a/packages/renderer/src/regl/ReglModel.ts +++ b/packages/renderer/src/regl/ReglModel.ts @@ -67,12 +67,16 @@ export default class ReglModel implements IModel { vert: vs, primitive: primitiveMap[primitive === undefined ? gl.TRIANGLES : primitive], - count, }; if (instances) { drawParams.instances = instances; } + // elements 中可能包含 count,此时不应传入 + if (count) { + drawParams.count = count; + } + if (elements) { drawParams.elements = (elements as ReglElements).get(); } @@ -133,9 +137,7 @@ export default class ReglModel implements IModel { } public destroy() { - // release all resources - // @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up - this.reGl.destroy(); + // don't need do anything since we will call `rendererService.cleanup()` } /** diff --git a/packages/renderer/src/regl/index.ts b/packages/renderer/src/regl/index.ts index 8b1d617c03..0683d816e0 100644 --- a/packages/renderer/src/regl/index.ts +++ b/packages/renderer/src/regl/index.ts @@ -14,6 +14,7 @@ import { IFramebufferInitializationOptions, IModel, IModelInitializationOptions, + IReadPixelsOptions, IRendererService, ITexture2D, ITexture2DInitializationOptions, @@ -92,19 +93,13 @@ export default class ReglRendererService implements IRendererService { public createFramebuffer = (options: IFramebufferInitializationOptions) => new ReglFramebuffer(this.gl, options); - public renderToFramebuffer = ( + public useFramebuffer = ( framebuffer: IFramebuffer | null, drawCommands: () => void, ) => { - const useFramebuffer = this.gl({ - // since post-processor will swap read/write fbos, we must retrieve it dynamically - framebuffer: framebuffer - ? () => (framebuffer as ReglFramebuffer).get() - : null, - }); - - // TODO: pass other options - useFramebuffer({}, drawCommands); + this.gl({ + framebuffer: framebuffer ? (framebuffer as ReglFramebuffer).get() : null, + })(drawCommands); }; public clear = (options: IClearOptions) => { @@ -141,6 +136,20 @@ export default class ReglRendererService implements IRendererService { this.gl._refresh(); }; + public readPixels = (options: IReadPixelsOptions) => { + const { framebuffer, x, y, width, height } = options; + const readPixelsOptions: regl.ReadOptions = { + x, + y, + width, + height, + }; + if (framebuffer) { + readPixelsOptions.framebuffer = (framebuffer as ReglFramebuffer).get(); + } + return this.gl.read(readPixelsOptions); + }; + public getViewportSize = () => { return { width: this.gl._gl.drawingBufferWidth, @@ -151,4 +160,9 @@ export default class ReglRendererService implements IRendererService { public getContainer = () => { return this.$container; }; + + public destroy = () => { + // @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up + this.gl.destroy(); + }; } diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts index 88e7a55662..2c6c57d994 100644 --- a/packages/scene/src/index.ts +++ b/packages/scene/src/index.ts @@ -1,7 +1,6 @@ import { Bounds, container, - IconService, IIconService, IImage, ILayer, @@ -19,13 +18,16 @@ import { } from '@l7/core'; import { AMapService, MapboxService } from '@l7/maps'; import { ReglRendererService } from '@l7/renderer'; -import { inject, injectable } from 'inversify'; // 绑定渲染引擎服务 container .bind(TYPES.IRendererService) .to(ReglRendererService) .inSingletonScope(); + +// 缓存当前地图类型,便于 DEMO 中切换底图时动态绑定 +let mapType: MapType; + /** * 暴露 Scene API * @@ -39,34 +41,34 @@ container * scene.render(); */ class Scene { - @inject(TYPES.IIconService) - protected readonly iconService: IIconService; + private iconService: IIconService; private sceneService: ISceneService; private mapService: IMapService; + private mapType: MapType; public constructor(config: IMapConfig & IRenderConfig) { const { type = MapType.amap } = config; // 根据用户传入参数绑定地图服务 - let mapService: new (...args: any[]) => IMapService; + let mapServiceImpl: new (...args: any[]) => IMapService; if (type === MapType.mapbox) { - mapService = MapboxService; + mapServiceImpl = MapboxService; } else if (type === MapType.amap) { - mapService = AMapService; + mapServiceImpl = AMapService; } else { throw new Error('不支持的地图服务'); } - // this.mapService = mapService; + // DEMO 中切换底图实现时,需要重新绑定底图服务 // @see https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md#containerrebindserviceidentifier-serviceidentifier - if (container.isBound(TYPES.IMapService)) { - container - .rebind(TYPES.IMapService) - .to(mapService) - .inSingletonScope(); - } else { + if (!container.isBound(TYPES.IMapService)) { container .bind(TYPES.IMapService) - .to(mapService) + .to(mapServiceImpl) + .inSingletonScope(); + } else if (type !== mapType) { + container + .rebind(TYPES.IMapService) + .to(mapServiceImpl) .inSingletonScope(); } @@ -75,6 +77,7 @@ class Scene { this.sceneService.init(config); this.mapService = container.get(TYPES.IMapService); this.iconService = container.get(TYPES.IIconService); + mapType = this.mapService.getType(); } public addLayer(layer: ILayer): void { @@ -150,6 +153,7 @@ class Scene { public destroy() { this.sceneService.destroy(); + // TODO: 清理其他 Service 例如 IconService } // 资源管理 diff --git a/stories/Animation/Animation.stories.tsx b/stories/Animation/Animation.stories.tsx new file mode 100644 index 0000000000..190a9cf917 --- /dev/null +++ b/stories/Animation/Animation.stories.tsx @@ -0,0 +1,5 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import Polygon from './components/Polygon'; + +storiesOf('动画', module).add('动态更新指定 feature(s)', () => ); diff --git a/stories/Animation/components/Polygon.tsx b/stories/Animation/components/Polygon.tsx new file mode 100644 index 0000000000..be6b7594ad --- /dev/null +++ b/stories/Animation/components/Polygon.tsx @@ -0,0 +1,101 @@ +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore +import { Scene } from '@l7/scene'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +function convertRGB2Hex(rgb: number[]) { + return ( + '#' + rgb.map((r) => ('0' + Math.floor(r).toString(16)).slice(-2)).join('') + ); +} + +export default class Mapbox extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + if (this.gui) { + this.gui.destroy(); + } + if (this.$stats) { + document.body.removeChild(this.$stats); + } + this.scene.destroy(); + } + + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); + const scene = new Scene({ + id: 'map', + type: 'mapbox', + style: 'mapbox://styles/mapbox/streets-v9', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }); + this.scene = scene; + const layer = new PolygonLayer({ + enablePicking: false, + }); + + layer + .source(await response.json()) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 0.8, + }); + scene.addLayer(layer); + scene.render(); + /*** 运行时修改样式属性 ***/ + const gui = new dat.GUI(); + this.gui = gui; + const styleOptions = { + color: [0, 0, 0], + featureRange: { + startIndex: 0, + endIndex: Infinity, + }, + }; + const pointFolder = gui.addFolder('精确更新 feature'); + pointFolder.add(styleOptions.featureRange, 'startIndex', 0, 100, 1); + pointFolder.add(styleOptions.featureRange, 'endIndex', 0, 100, 1); + pointFolder.addColor(styleOptions, 'color').onChange((color: number[]) => { + layer.color('name', [convertRGB2Hex(color)], { + featureRange: { + startIndex: styleOptions.featureRange.startIndex, + endIndex: styleOptions.featureRange.endIndex, + }, + }); + scene.render(); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/MapAdaptor/Map.stories.tsx b/stories/MapAdaptor/Map.stories.tsx index 677f177358..4a20b1216c 100644 --- a/stories/MapAdaptor/Map.stories.tsx +++ b/stories/MapAdaptor/Map.stories.tsx @@ -2,25 +2,25 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; import AMap from './components/AMap'; import Mapbox from './components/Mapbox'; -import Polygon from './components/Polygon'; -import Point3D from './components/Point3D'; -import Line from './components/Line'; -import ImageLayer from './components/Image'; -import GridHeatMap from './components/GridHeatmap'; -import PointImage from './components/pointImage'; +// import Polygon from './components/Polygon'; +// import Point3D from './components/Point3D'; +// import Line from './components/Line'; +// import ImageLayer from './components/Image'; +// import GridHeatMap from './components/GridHeatmap'; +// import PointImage from './components/pointImage'; // @ts-ignore import notes from './Map.md'; -storiesOf('地图底图测试', module) +storiesOf('地图底图', module) .add('高德地图', () => , { notes: { markdown: notes }, }) .add('Mapbox', () => , { notes: { markdown: notes }, - }) - .add('Polygon', () => ) - .add('Point3D', () => ) - .add('Line', () => ) - .add('GridHeatMap', () => ) - .add('Image', () => ) - .add('pointImage', () => ); + }); +// .add('Polygon', () => ); +// .add('Point3D', () => ) +// .add('Line', () => ) +// .add('GridHeatMap', () => ) +// .add('Image', () => ) +// .add('pointImage', () => ); diff --git a/stories/MapAdaptor/components/AMap.tsx b/stories/MapAdaptor/components/AMap.tsx index 7b47b941ff..c66ca5f408 100644 --- a/stories/MapAdaptor/components/AMap.tsx +++ b/stories/MapAdaptor/components/AMap.tsx @@ -1,7 +1,8 @@ -import { PointLayer } from '@l7/layers'; +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore import { Scene } from '@l7/scene'; import * as React from 'react'; -import data from './data.json'; export default class AMap extends React.Component { private scene: Scene; @@ -10,18 +11,36 @@ export default class AMap extends React.Component { this.scene.destroy(); } - public componentDidMount() { + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); const scene = new Scene({ - center: [120.19382669582967, 30.258134], + center: [110.19382669582967, 50.258134], id: 'map', pitch: 0, style: 'dark', type: 'amap', - zoom: 1, + zoom: 3, }); - const pointLayer = new PointLayer({}); - pointLayer.source(data); - scene.addLayer(pointLayer); + const layer = new PolygonLayer({}); + + layer + .source(await response.json()) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 0.8, + }); + scene.addLayer(layer); scene.render(); this.scene = scene; } diff --git a/stories/MapAdaptor/components/Mapbox.tsx b/stories/MapAdaptor/components/Mapbox.tsx index 8141f38f00..1f467d2f4c 100644 --- a/stories/MapAdaptor/components/Mapbox.tsx +++ b/stories/MapAdaptor/components/Mapbox.tsx @@ -1,73 +1,48 @@ -import { PointLayer } from '@l7/layers'; +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore import { Scene } from '@l7/scene'; -import * as dat from 'dat.gui'; import * as React from 'react'; -import data from './data2.json'; export default class Mapbox extends React.Component { - private gui: dat.GUI; - private $stats: Node; private scene: Scene; public componentWillUnmount() { - if (this.gui) { - this.gui.destroy(); - } - if (this.$stats) { - document.body.removeChild(this.$stats); - } this.scene.destroy(); } public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); const scene = new Scene({ id: 'map', type: 'mapbox', style: 'mapbox://styles/mapbox/streets-v9', - center: [120.19382669582967, 30.258134], + center: [110.19382669582967, 50.258134], pitch: 0, - zoom: 2, + zoom: 3, }); - const pointLayer = new PointLayer({}); - - // TODO: new GeoJSONSource() - pointLayer.source(data); - // .size('mag', [2, 10]) - // .color('mag', [ - // '#2E8AE6', - // '#69D1AB', - // '#DAF291', - // '#FFD591', - // '#FF7A45', - // '#CF1D49', - // ]); - scene.addLayer(pointLayer); - scene.render(); - this.scene = scene; + const layer = new PolygonLayer({}); - /*** 运行时修改样式属性 ***/ - - const gui = new dat.GUI(); - this.gui = gui; - const pointFolder = gui.addFolder('Point 样式属性'); - pointFolder - .addColor(pointLayer.styleOptions, 'pointColor') - .onChange((pointColor: [number, number, number]) => { - pointLayer.style({ - pointColor, - }); - scene.render(); - }); - - pointFolder - .add(pointLayer.styleOptions, 'strokeWidth', 1, 10, 0.1) - .onChange((strokeWidth: number) => { - pointLayer.style({ - strokeWidth, - }); - scene.render(); + layer + .source(await response.json()) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 0.8, }); + scene.addLayer(layer); + scene.render(); } public render() { diff --git a/stories/MapAdaptor/components/Polygon.tsx b/stories/MapAdaptor/components/Polygon.tsx index e3207cb878..c44abc5a70 100644 --- a/stories/MapAdaptor/components/Polygon.tsx +++ b/stories/MapAdaptor/components/Polygon.tsx @@ -1,8 +1,16 @@ +// @ts-ignore import { PolygonLayer } from '@l7/layers'; +// @ts-ignore import { Scene } from '@l7/scene'; import * as dat from 'dat.gui'; import * as React from 'react'; +function convertRGB2Hex(rgb: number[]) { + return ( + '#' + rgb.map((r) => ('0' + Math.floor(r).toString(16)).slice(-2)).join('') + ); +} + export default class Mapbox extends React.Component { private gui: dat.GUI; private $stats: Node; @@ -22,29 +30,6 @@ export default class Mapbox extends React.Component { const response = await fetch( 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', ); - const data = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - properties: { - name: 'test', - }, - geometry: { - type: 'Polygon', - coordinates: [ - [ - [108.28125, 40.17887331434696], - [114.78515624999999, 24.367113562651262], - [119.88281249999999, 31.952162238024975], - [108.28125, 40.17887331434696], - ], - ], - }, - }, - ], - }; - // data.features = data.features.slice(1, 12); const scene = new Scene({ id: 'map', type: 'mapbox', @@ -53,10 +38,8 @@ export default class Mapbox extends React.Component { pitch: 0, zoom: 3, }); - const layer = new PolygonLayer({ - enableMultiPassRenderer: true, - passes: [], - }); + this.scene = scene; + const layer = new PolygonLayer({}); // TODO: new GeoJSONSource() layer @@ -72,22 +55,32 @@ export default class Mapbox extends React.Component { ]) .shape('fill') .style({ - opacity: 0.8, + opacity: 0.2, }); scene.addLayer(layer); scene.render(); /*** 运行时修改样式属性 ***/ - // const gui = new dat.GUI(); - // this.gui = gui; - // const pointFolder = gui.addFolder('Polygon 样式属性'); - // pointFolder - // .add(layer.styleOptions, 'opacity') - // .onChange((opacity: number) => { - // layer.style({ - // opacity, - // }); - // scene.render(); - // }); + const gui = new dat.GUI(); + this.gui = gui; + const styleOptions = { + color: [0, 0, 0], + featureRange: { + startIndex: 0, + endIndex: Infinity, + }, + }; + const pointFolder = gui.addFolder('精确更新 feature'); + pointFolder.add(styleOptions.featureRange, 'startIndex', 0, 100, 1); + pointFolder.add(styleOptions.featureRange, 'endIndex', 0, 100, 1); + pointFolder.addColor(styleOptions, 'color').onChange((color: number[]) => { + layer.color('name', [convertRGB2Hex(color)], { + featureRange: { + startIndex: styleOptions.featureRange.startIndex, + endIndex: styleOptions.featureRange.endIndex, + }, + }); + scene.render(); + }); } public render() { diff --git a/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx b/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx new file mode 100644 index 0000000000..a04c7aea2b --- /dev/null +++ b/stories/MultiPassRenderer/MultiPassRenderer.stories.tsx @@ -0,0 +1,5 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import Polygon from './components/Polygon'; + +storiesOf('MultiPassRenderer', module).add('blur', () => ); diff --git a/stories/MultiPassRenderer/components/Polygon.tsx b/stories/MultiPassRenderer/components/Polygon.tsx new file mode 100644 index 0000000000..9e5efca203 --- /dev/null +++ b/stories/MultiPassRenderer/components/Polygon.tsx @@ -0,0 +1,85 @@ +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore +import { Scene } from '@l7/scene'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +export default class Mapbox extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + if (this.gui) { + this.gui.destroy(); + } + if (this.$stats) { + document.body.removeChild(this.$stats); + } + this.scene.destroy(); + } + + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); + const data = await response.json(); + const scene = new Scene({ + id: 'map', + type: 'mapbox', + style: 'mapbox://styles/mapbox/streets-v9', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }); + const layer = new PolygonLayer({ + enablePicking: false, + passes: [ + 'blurH', + [ + 'blurV', + { + blurRadius: 8, + }, + ], + ], + }); + + layer + .source(data) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 0.8, + }); + + scene.addLayer(layer); + scene.render(); + + this.scene = scene; + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Picking/Picking.stories.tsx b/stories/Picking/Picking.stories.tsx new file mode 100644 index 0000000000..082cd64885 --- /dev/null +++ b/stories/Picking/Picking.stories.tsx @@ -0,0 +1,10 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import AdvancedAPI from './components/AdvancedAPI'; +import Highlight from './components/Highlight'; +import Tooltip from './components/Tooltip'; + +storiesOf('交互', module) + .add('拾取 & 高亮', () => ) + .add('拾取 & Tooltip', () => ) + .add('高级拾取 API', () => ); diff --git a/stories/Picking/components/AdvancedAPI.tsx b/stories/Picking/components/AdvancedAPI.tsx new file mode 100644 index 0000000000..9823928652 --- /dev/null +++ b/stories/Picking/components/AdvancedAPI.tsx @@ -0,0 +1,117 @@ +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore +import { Scene } from '@l7/scene'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +export default class AdvancedAPI extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + if (this.gui) { + this.gui.destroy(); + } + if (this.$stats) { + document.body.removeChild(this.$stats); + } + this.scene.destroy(); + } + + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); + const scene = new Scene({ + id: 'map', + type: 'mapbox', + style: 'mapbox://styles/mapbox/streets-v9', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }); + const layer = new PolygonLayer({ + enablePicking: true, + enableHighlight: true, + highlightColor: [0, 0, 1, 1], + onHover: (pickedFeature) => { + // tslint:disable-next-line:no-console + console.log(pickedFeature); + }, + }); + + layer + .source(await response.json()) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 0.8, + }); + scene.addLayer(layer); + scene.render(); + + this.scene = scene; + + /*** 运行时修改样式属性 ***/ + const gui = new dat.GUI(); + this.gui = gui; + const styleOptions = { + enablePicking: true, + enableHighlight: true, + highlightColor: [0, 0, 255], + }; + const pointFolder = gui.addFolder('拾取 & 高亮'); + // pointFolder + // .add(styleOptions, 'enablePicking') + // .onChange((enablePicking: boolean) => { + // // FIXME: 该配置项会影响到初始化阶段 PixelPickingPass 的添加,暂不支持在运行时更改 + // layer.style({ + // enablePicking, + // }); + // scene.render(); + // }); + pointFolder + .add(styleOptions, 'enableHighlight') + .onChange((enableHighlight: boolean) => { + layer.style({ + enableHighlight, + }); + scene.render(); + }); + pointFolder + .addColor(styleOptions, 'highlightColor') + .onChange((highlightColor: number[]) => { + const [r, g, b] = highlightColor.map((c) => c / 255); + layer.style({ + highlightColor: [r, g, b, 1], + }); + scene.render(); + }); + pointFolder.open(); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Picking/components/Highlight.tsx b/stories/Picking/components/Highlight.tsx new file mode 100644 index 0000000000..3211675500 --- /dev/null +++ b/stories/Picking/components/Highlight.tsx @@ -0,0 +1,117 @@ +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore +import { Scene } from '@l7/scene'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +export default class Highlight extends React.Component { + private gui: dat.GUI; + private $stats: Node; + private scene: Scene; + + public componentWillUnmount() { + if (this.gui) { + this.gui.destroy(); + } + if (this.$stats) { + document.body.removeChild(this.$stats); + } + this.scene.destroy(); + } + + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); + const scene = new Scene({ + id: 'map', + type: 'mapbox', + style: 'mapbox://styles/mapbox/streets-v9', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }); + const layer = new PolygonLayer({ + enablePicking: true, + enableHighlight: true, + highlightColor: [0, 0, 1, 1], + onHover: (pickedFeature) => { + // tslint:disable-next-line:no-console + console.log(pickedFeature); + }, + }); + + layer + .source(await response.json()) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 1.0, + }); + scene.addLayer(layer); + scene.render(); + + this.scene = scene; + + /*** 运行时修改样式属性 ***/ + const gui = new dat.GUI(); + this.gui = gui; + const styleOptions = { + enablePicking: true, + enableHighlight: true, + highlightColor: [0, 0, 255], + }; + const pointFolder = gui.addFolder('拾取 & 高亮'); + // pointFolder + // .add(styleOptions, 'enablePicking') + // .onChange((enablePicking: boolean) => { + // // FIXME: 该配置项会影响到初始化阶段 PixelPickingPass 的添加,暂不支持在运行时更改 + // layer.style({ + // enablePicking, + // }); + // scene.render(); + // }); + pointFolder + .add(styleOptions, 'enableHighlight') + .onChange((enableHighlight: boolean) => { + layer.style({ + enableHighlight, + }); + scene.render(); + }); + pointFolder + .addColor(styleOptions, 'highlightColor') + .onChange((highlightColor: number[]) => { + const [r, g, b] = highlightColor.map((c) => c / 255); + layer.style({ + highlightColor: [r, g, b, 1], + }); + scene.render(); + }); + pointFolder.open(); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/Picking/components/Tooltip.tsx b/stories/Picking/components/Tooltip.tsx new file mode 100644 index 0000000000..120f0dc957 --- /dev/null +++ b/stories/Picking/components/Tooltip.tsx @@ -0,0 +1,71 @@ +// @ts-ignore +import { PolygonLayer } from '@l7/layers'; +// @ts-ignore +import { Scene } from '@l7/scene'; +import * as dat from 'dat.gui'; +import * as React from 'react'; + +export default class Mapbox extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const response = await fetch( + 'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json', + ); + const scene = new Scene({ + id: 'map', + type: 'mapbox', + style: 'mapbox://styles/mapbox/streets-v9', + center: [110.19382669582967, 50.258134], + pitch: 0, + zoom: 3, + }); + const layer = new PolygonLayer({ + enablePicking: true, + enableHighlight: false, + onHover: (pickedFeature) => { + // tslint:disable-next-line:no-console + console.log(pickedFeature); + }, + }); + + layer + .source(await response.json()) + .size('name', [0, 10000, 50000, 30000, 100000]) + .color('name', [ + '#2E8AE6', + '#69D1AB', + '#DAF291', + '#FFD591', + '#FF7A45', + '#CF1D49', + ]) + .shape('fill') + .style({ + opacity: 0.8, + }); + scene.addLayer(layer); + scene.render(); + + this.scene = scene; + } + + public render() { + return ( +
+ ); + } +} diff --git a/tsconfig.json b/tsconfig.json index 643b2d5f12..3347c231f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "noEmit": true, "strict": true, "strictPropertyInitialization": false, + "downlevelIteration": true, "jsx": "react", "target": "es5", "lib": ["es6", "dom"], diff --git a/yarn.lock b/yarn.lock index 77e340c5fa..cd6b8272fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5000,7 +5000,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@~1.6.0: +concat-stream@^1.5.0, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -7895,20 +7895,20 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^3.0.4: - version "3.0.5" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.5.tgz#d7db27c346645a8dc52df02aa534a377ad7925e0" - integrity sha512-cKd09Jy9cDyNIvAdN2QQAP/oA21sle4FWXjIMDttailpLAYZuBE7WaPmhrkj+afS8Sj9isghAtFvWSQ0JiwOHg== +husky@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.9.tgz#a2c3e9829bfd6b4957509a9500d2eef5dbfc8044" + integrity sha512-Yolhupm7le2/MqC1VYLk/cNmYxsSsqKkTyBhzQHhPK1jFnC89mmmNVuGtLNabjDI6Aj8UNIr0KpRNuBkiC4+sg== dependencies: chalk "^2.4.2" + ci-info "^2.0.0" cosmiconfig "^5.2.1" execa "^1.0.0" get-stdin "^7.0.0" - is-ci "^2.0.0" opencollective-postinstall "^2.0.2" pkg-dir "^4.2.0" please-upgrade-node "^3.2.0" - read-pkg "^5.1.1" + read-pkg "^5.2.0" run-node "^1.0.0" slash "^3.0.0" @@ -10921,11 +10921,6 @@ os-name@^3.0.0: macos-release "^2.2.0" windows-release "^3.1.0" -os-shim@^0.1.2: - version "0.1.3" - resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" - integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc= - os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -11621,15 +11616,6 @@ potpack@^1.0.1: resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.1.tgz#d1b1afd89e4c8f7762865ec30bd112ab767e2ebf" integrity sha512-15vItUAbViaYrmaB/Pbw7z6qX2xENbFSTA7Ii4tgbPtasxm5v6ryKhKtL91tpWovDJzTiZqdwzhcFBCwiMVdVw== -pre-commit@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" - integrity sha1-287g7p3nI15X95xW186UZBpp7sY= - dependencies: - cross-spawn "^5.0.1" - spawn-sync "^1.0.15" - which "1.2.x" - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -12356,7 +12342,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -read-pkg@^5.1.1: +read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== @@ -13479,14 +13465,6 @@ space-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz#27910835ae00d0adfcdbd0ad7e611fb9544351fa" integrity sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA== -spawn-sync@^1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" - integrity sha1-sAeZVX63+wyDdsKdROih6mfldHY= - dependencies: - concat-stream "^1.4.7" - os-shim "^0.1.2" - spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -15173,13 +15151,6 @@ which@1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: dependencies: isexe "^2.0.0" -which@1.2.x: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" - integrity sha1-mofEN48D6CfOyvGs31bHNsAcFOU= - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"