mirror of https://gitee.com/antv-l7/antv-l7
feat(picking): support PixelPickingPass and highlight the picked feature
This commit is contained in:
parent
18dda7c5b3
commit
9e29d1a949
|
@ -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 被选中则使用高亮颜色混合,否则直接输出原始计算结果 |
|
||||
|
||||
## 参考资料
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<ILayerService>(TYPES.ILayerService)
|
||||
.to(LayerService)
|
||||
.inSingletonScope();
|
||||
container
|
||||
.bind<IStyleAttributeService>(TYPES.IStyleAttributeService)
|
||||
.to(StyleAttributeService);
|
||||
container
|
||||
.bind<ICameraService>(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;
|
||||
|
|
|
@ -23,6 +23,7 @@ const defaultGlobalConfig: Partial<IGlobalConfig> = {
|
|||
],
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<unknown>;
|
||||
beforeRender: SyncHook<unknown>;
|
||||
afterRender: SyncHook<unknown>;
|
||||
beforePickingEncode: SyncHook<unknown>;
|
||||
afterPickingEncode: SyncHook<unknown>;
|
||||
beforeHighlight: SyncHook<unknown>;
|
||||
afterHighlight: SyncHook<unknown>;
|
||||
beforeDestroy: SyncHook<unknown>;
|
||||
afterDestroy: SyncHook<unknown>;
|
||||
};
|
||||
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<ILayerInitializationOptions>;
|
||||
setEncodedData(encodedData: IEncodeFeature[]): void;
|
||||
getEncodedData(): IEncodeFeature[];
|
||||
getStyleOptions(): Partial<ILayerInitializationOptions>;
|
||||
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<string | [string, { [key: string]: unknown }]>;
|
||||
|
||||
/**
|
||||
* 开启拾取
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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<IAttributeInitializationOptions, 'buffer'> {
|
||||
/**
|
||||
* 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<IStyleAttributeInitializationOptions>): 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<IStyleAttributeInitializationOptions>,
|
||||
): IStyleAttribute;
|
||||
updateStyleAttribute(
|
||||
attributeName: string,
|
||||
attributeOptions: Partial<IStyleAttributeInitializationOptions>,
|
||||
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;
|
||||
}
|
|
@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<IStyleAttributeInitializationOptions>) {
|
||||
this.setProps(options);
|
||||
}
|
||||
|
||||
public setProps(options: Partial<IStyleAttributeInitializationOptions>) {
|
||||
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;
|
||||
});
|
||||
};
|
||||
}
|
|
@ -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<IStyleAttributeInitializationOptions>,
|
||||
) {
|
||||
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<IStyleAttributeInitializationOptions>,
|
||||
updateOptions?: Partial<IStyleAttributeUpdateOptions>,
|
||||
) {
|
||||
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 = [];
|
||||
}
|
||||
}
|
|
@ -11,16 +11,18 @@ export interface IPoint {
|
|||
}
|
||||
export interface IMapService {
|
||||
init(config: Partial<IMapConfig>): 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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface IElementsInitializationOptions {
|
|||
| gl.TRIANGLES
|
||||
| gl.TRIANGLE_STRIP
|
||||
| gl.TRIANGLE_FAN;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export interface IElements {
|
||||
|
|
|
@ -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<void>;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -81,9 +81,9 @@ export default class BasePostProcessingPass<InitializationOptions = {}>
|
|||
|
||||
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({
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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'),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ChildLayerStyleOptions = {}> 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<ILayerStyleAttribute>;
|
||||
} = {};
|
||||
|
||||
@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<ILayerInitializationOptions>;
|
||||
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<ILayerInitializationOptions>) {
|
||||
this.initializationOptions = initializationOptions;
|
||||
constructor(
|
||||
styleOptions: Partial<ILayerInitializationOptions & ChildLayerStyleOptions>,
|
||||
) {
|
||||
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<IStyleAttributeUpdateOptions>,
|
||||
) {
|
||||
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<ILayerStyleAttribute>;
|
||||
} {
|
||||
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,
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
// }),
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
// }),
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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) : [];
|
||||
}
|
||||
}
|
|
@ -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) : [];
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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]];
|
||||
|
|
|
@ -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),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -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`,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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<IPointLayerStyleOptions>) {
|
||||
// // this.layerStyleService.update(options);
|
||||
// // this.styleOptions = {
|
||||
// // ...this.styleOptions,
|
||||
// // ...options,
|
||||
// // };
|
||||
// }
|
||||
// // public style(options: Partial<IPointLayerStyleOptions>) {
|
||||
// // // 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,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
// // }),
|
||||
// }),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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); // 顶部坐标
|
||||
}
|
||||
}
|
|
@ -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<IPolygonLayerStyleOptions> {
|
||||
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,
|
||||
}),
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
// }),
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -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<void> {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<void> {
|
||||
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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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()`
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<IRendererService>(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<IMapService>(TYPES.IMapService)
|
||||
.to(mapService)
|
||||
.inSingletonScope();
|
||||
} else {
|
||||
if (!container.isBound(TYPES.IMapService)) {
|
||||
container
|
||||
.bind<IMapService>(TYPES.IMapService)
|
||||
.to(mapService)
|
||||
.to(mapServiceImpl)
|
||||
.inSingletonScope();
|
||||
} else if (type !== mapType) {
|
||||
container
|
||||
.rebind<IMapService>(TYPES.IMapService)
|
||||
.to(mapServiceImpl)
|
||||
.inSingletonScope();
|
||||
}
|
||||
|
||||
|
@ -75,6 +77,7 @@ class Scene {
|
|||
this.sceneService.init(config);
|
||||
this.mapService = container.get<IMapService>(TYPES.IMapService);
|
||||
this.iconService = container.get<IIconService>(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
|
||||
}
|
||||
|
||||
// 资源管理
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import Polygon from './components/Polygon';
|
||||
|
||||
storiesOf('动画', module).add('动态更新指定 feature(s)', () => <Polygon />);
|
|
@ -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 (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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('高德地图', () => <AMap />, {
|
||||
notes: { markdown: notes },
|
||||
})
|
||||
.add('Mapbox', () => <Mapbox />, {
|
||||
notes: { markdown: notes },
|
||||
})
|
||||
.add('Polygon', () => <Polygon />)
|
||||
.add('Point3D', () => <Point3D />)
|
||||
.add('Line', () => <Line />)
|
||||
.add('GridHeatMap', () => <GridHeatMap />)
|
||||
.add('Image', () => <ImageLayer />)
|
||||
.add('pointImage', () => <PointImage />);
|
||||
});
|
||||
// .add('Polygon', () => <Polygon />);
|
||||
// .add('Point3D', () => <Point3D />)
|
||||
// .add('Line', () => <Line />)
|
||||
// .add('GridHeatMap', () => <GridHeatMap />)
|
||||
// .add('Image', () => <ImageLayer />)
|
||||
// .add('pointImage', () => <PointImage />);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import * as React from 'react';
|
||||
import Polygon from './components/Polygon';
|
||||
|
||||
storiesOf('MultiPassRenderer', module).add('blur', () => <Polygon />);
|
|
@ -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 (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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('拾取 & 高亮', () => <Highlight />)
|
||||
.add('拾取 & Tooltip', () => <Tooltip />)
|
||||
.add('高级拾取 API', () => <AdvancedAPI />);
|
|
@ -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 (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
"noEmit": true,
|
||||
"strict": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"downlevelIteration": true,
|
||||
"jsx": "react",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
|
|
45
yarn.lock
45
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"
|
||||
|
|
Loading…
Reference in New Issue