mirror of https://gitee.com/antv-l7/antv-l7
feat(schema-validation): support validation for layer's options
implement with JSON Schema (ajv)
This commit is contained in:
parent
39fb89cc7f
commit
896b02c1e7
|
@ -0,0 +1,68 @@
|
||||||
|
# ConfigSchemaValidation 设计
|
||||||
|
|
||||||
|
用户在使用 L7 的 Scene/Layer API 时,由于参数配置项众多难免会误传。需要在运行时通过校验提前发现并给出友好的提示。
|
||||||
|
另外由于 L7 允许用户自定义 Layer 与 LayerPlugin,规范化参数配置项也能提升易用性和质量。
|
||||||
|
|
||||||
|
这方面 Webpack 做的很好,使用 [schema-utils](https://github.com/webpack/schema-utils) 基于 JSON Schema 对 Plugin 和 Loader 进行校验。如果传入了错误的配置项,会给出友好的提示:
|
||||||
|
```
|
||||||
|
Invalid configuration object. MyPlugin has been initialised using a configuration object that does not match the API schema.
|
||||||
|
- configuration.optionName should be a integer.
|
||||||
|
```
|
||||||
|
|
||||||
|
和 Webpack 一样,我们也选择 [ajv](https://github.com/epoberezkin/ajv) 作为 JSON Schema 校验器。
|
||||||
|
目前我们只在 Layer 初始阶段进行校验,一旦校验失败会中断后续初始化插件的处理,并在控制台给出校验失败信息。后续需要在属性更新时同样进行校验。
|
||||||
|
|
||||||
|
## Layer 基类配置项 Schema
|
||||||
|
|
||||||
|
目前在基类中我们声明了如下属性及其对应的校验规则:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default {
|
||||||
|
properties: {
|
||||||
|
// 开启拾取
|
||||||
|
enablePicking: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
// 开启高亮
|
||||||
|
enableHighlight: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
// 高亮颜色:例如 [0, 0, 1, 1] 或者 '#ffffff'
|
||||||
|
highlightColor: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'number',
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
如果传入了错误的配置项则会在控制台给出提示。
|
||||||
|
|
||||||
|
## Layer 子类配置项 Schema
|
||||||
|
|
||||||
|
Layer 子类可以通过重载 `getConfigSchema()` 方法定义自身的特有属性。例如 `PolygonLayer` 需要定义透明度:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
protected getConfigSchema() {
|
||||||
|
return {
|
||||||
|
properties: {
|
||||||
|
opacity: {
|
||||||
|
type: 'number',
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
|
@ -19,18 +19,20 @@
|
||||||
"author": "xiaoiver",
|
"author": "xiaoiver",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/tiny-sdf": "^1.1.1",
|
|
||||||
"eventemitter3": "^3.1.0",
|
|
||||||
"@l7/utils": "0.0.1",
|
"@l7/utils": "0.0.1",
|
||||||
|
"@mapbox/tiny-sdf": "^1.1.1",
|
||||||
|
"ajv": "^6.10.2",
|
||||||
|
"eventemitter3": "^3.1.0",
|
||||||
"gl-matrix": "^3.1.0",
|
"gl-matrix": "^3.1.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"inversify": "^5.0.1",
|
"inversify": "^5.0.1",
|
||||||
"inversify-inject-decorators": "^3.1.0",
|
"inversify-inject-decorators": "^3.1.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
"mapbox-gl": "^1.2.1",
|
||||||
|
"merge-json-schemas": "^1.0.0",
|
||||||
"probe.gl": "^3.1.1",
|
"probe.gl": "^3.1.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"tapable": "^2.0.0-beta.8",
|
"tapable": "^2.0.0-beta.8",
|
||||||
"mapbox-gl": "^1.2.1",
|
|
||||||
"viewport-mercator-project": "^6.2.1"
|
"viewport-mercator-project": "^6.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { inject, injectable } from 'inversify';
|
import Ajv from 'ajv';
|
||||||
|
import { injectable } from 'inversify';
|
||||||
import { IGlobalConfig, IGlobalConfigService } from './IConfigService';
|
import { IGlobalConfig, IGlobalConfigService } from './IConfigService';
|
||||||
|
|
||||||
const defaultGlobalConfig: Partial<IGlobalConfig> = {
|
const defaultGlobalConfig: Partial<IGlobalConfig> = {
|
||||||
|
@ -26,10 +27,23 @@ const defaultGlobalConfig: Partial<IGlobalConfig> = {
|
||||||
scales: {},
|
scales: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @see https://github.com/epoberezkin/ajv#options
|
||||||
|
const ajv = new Ajv({
|
||||||
|
allErrors: true,
|
||||||
|
verbose: true,
|
||||||
|
});
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class GlobalConfigService implements IGlobalConfigService {
|
export default class GlobalConfigService implements IGlobalConfigService {
|
||||||
private config: Partial<IGlobalConfig> = defaultGlobalConfig;
|
private config: Partial<IGlobalConfig> = defaultGlobalConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存每个 Layer 配置项的校验器
|
||||||
|
*/
|
||||||
|
private layerConfigValidatorCache: {
|
||||||
|
[layerName: string]: Ajv.ValidateFunction;
|
||||||
|
} = {};
|
||||||
|
|
||||||
public getConfig() {
|
public getConfig() {
|
||||||
return this.config;
|
return this.config;
|
||||||
}
|
}
|
||||||
|
@ -47,4 +61,29 @@ export default class GlobalConfigService implements IGlobalConfigService {
|
||||||
public reset() {
|
public reset() {
|
||||||
this.config = defaultGlobalConfig;
|
this.config = defaultGlobalConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerLayerConfigSchemaValidator(layerName: string, schema: object) {
|
||||||
|
if (!this.layerConfigValidatorCache[layerName]) {
|
||||||
|
this.layerConfigValidatorCache[layerName] = ajv.compile(schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public validateLayerConfig(layerName: string, data: object) {
|
||||||
|
const validate = this.layerConfigValidatorCache[layerName];
|
||||||
|
if (validate) {
|
||||||
|
const valid = validate(data);
|
||||||
|
if (!valid) {
|
||||||
|
return {
|
||||||
|
valid,
|
||||||
|
errors: validate.errors,
|
||||||
|
errorText: ajv.errorsText(validate.errors),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
errors: null,
|
||||||
|
errorText: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Ajv from 'ajv';
|
||||||
import { ILayerGlobalConfig } from '../layer/ILayerService';
|
import { ILayerGlobalConfig } from '../layer/ILayerService';
|
||||||
import { IMapConfig } from '../map/IMapService';
|
import { IMapConfig } from '../map/IMapService';
|
||||||
import { IRenderConfig } from '../renderer/IRendererService';
|
import { IRenderConfig } from '../renderer/IRendererService';
|
||||||
|
@ -8,4 +9,13 @@ export interface IGlobalConfigService {
|
||||||
getConfig(): Partial<IGlobalConfig>;
|
getConfig(): Partial<IGlobalConfig>;
|
||||||
setAndCheckConfig(config: Partial<IGlobalConfig>): boolean;
|
setAndCheckConfig(config: Partial<IGlobalConfig>): boolean;
|
||||||
reset(): void;
|
reset(): void;
|
||||||
|
registerLayerConfigSchemaValidator(layerName: string, schema: object): void;
|
||||||
|
validateLayerConfig(
|
||||||
|
layerName: string,
|
||||||
|
data: object,
|
||||||
|
): {
|
||||||
|
valid: boolean;
|
||||||
|
errors: Ajv.ErrorObject[] | null | undefined;
|
||||||
|
errorText: string | null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import ConfigService from '../ConfigService';
|
||||||
|
import { IGlobalConfigService } from '../IConfigService';
|
||||||
|
|
||||||
|
describe('ConfigService', () => {
|
||||||
|
let configService: IGlobalConfigService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
configService = new ConfigService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should validate layer's options according to JSON schema", () => {
|
||||||
|
configService.registerLayerConfigSchemaValidator('testLayer', {
|
||||||
|
properties: {
|
||||||
|
opacity: {
|
||||||
|
type: 'number',
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
},
|
||||||
|
enablePicking: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { valid, errorText } = configService.validateLayerConfig(
|
||||||
|
'testLayer',
|
||||||
|
{ opacity: 'invalid' },
|
||||||
|
);
|
||||||
|
expect(valid).toBeFalsy();
|
||||||
|
expect(errorText).toMatch('opacity should be number');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
configService.validateLayerConfig('testLayer', {
|
||||||
|
opacity: 1.5,
|
||||||
|
}).valid,
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
configService.validateLayerConfig('testLayer', {
|
||||||
|
enablePicking: 1.5,
|
||||||
|
}).valid,
|
||||||
|
).toBeFalsy();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
configService.validateLayerConfig('testLayer', {
|
||||||
|
opacity: 1.0,
|
||||||
|
}).valid,
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
configService.validateLayerConfig('testLayer', {
|
||||||
|
opacity: 0.0,
|
||||||
|
}).valid,
|
||||||
|
).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import { AsyncParallelHook, SyncHook } from 'tapable';
|
import { SyncBailHook, SyncHook } from 'tapable';
|
||||||
import { IModel } from '../renderer/IModel';
|
import { IModel } from '../renderer/IModel';
|
||||||
import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer';
|
import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer';
|
||||||
import { ISource, ISourceCFG } from '../source/ISourceService';
|
import { ISource, ISourceCFG } from '../source/ISourceService';
|
||||||
|
@ -31,19 +31,17 @@ export interface ILayer {
|
||||||
name: string; // 代表 Layer 的类型
|
name: string; // 代表 Layer 的类型
|
||||||
// visible: boolean;
|
// visible: boolean;
|
||||||
// zIndex: number;
|
// zIndex: number;
|
||||||
// type: string;
|
|
||||||
// id: number;
|
|
||||||
plugins: ILayerPlugin[];
|
plugins: ILayerPlugin[];
|
||||||
hooks: {
|
hooks: {
|
||||||
init: SyncHook<unknown>;
|
init: SyncBailHook<void, boolean | void>;
|
||||||
beforeRender: SyncHook<unknown>;
|
beforeRender: SyncBailHook<void, boolean | void>;
|
||||||
afterRender: SyncHook<unknown>;
|
afterRender: SyncHook<void>;
|
||||||
beforePickingEncode: SyncHook<unknown>;
|
beforePickingEncode: SyncHook<void>;
|
||||||
afterPickingEncode: SyncHook<unknown>;
|
afterPickingEncode: SyncHook<void>;
|
||||||
beforeHighlight: SyncHook<unknown>;
|
beforeHighlight: SyncHook<[number[]]>;
|
||||||
afterHighlight: SyncHook<unknown>;
|
afterHighlight: SyncHook<void>;
|
||||||
beforeDestroy: SyncHook<unknown>;
|
beforeDestroy: SyncHook<void>;
|
||||||
afterDestroy: SyncHook<unknown>;
|
afterDestroy: SyncHook<void>;
|
||||||
};
|
};
|
||||||
models: IModel[];
|
models: IModel[];
|
||||||
sourceOption: {
|
sourceOption: {
|
||||||
|
@ -72,6 +70,10 @@ export interface ILayer {
|
||||||
setEncodedData(encodedData: IEncodeFeature[]): void;
|
setEncodedData(encodedData: IEncodeFeature[]): void;
|
||||||
getEncodedData(): IEncodeFeature[];
|
getEncodedData(): IEncodeFeature[];
|
||||||
getStyleOptions(): Partial<ILayerInitializationOptions>;
|
getStyleOptions(): Partial<ILayerInitializationOptions>;
|
||||||
|
/**
|
||||||
|
* JSON Schema 用于校验配置项
|
||||||
|
*/
|
||||||
|
getConfigSchemaForValidation(): object;
|
||||||
isDirty(): boolean;
|
isDirty(): boolean;
|
||||||
/**
|
/**
|
||||||
* 直接调用拾取方法,在非鼠标交互场景中使用
|
* 直接调用拾取方法,在非鼠标交互场景中使用
|
||||||
|
|
|
@ -35,9 +35,9 @@ export default class LayerService implements ILayerService {
|
||||||
// .filter((layer) => layer.isDirty())
|
// .filter((layer) => layer.isDirty())
|
||||||
.forEach((layer) => {
|
.forEach((layer) => {
|
||||||
// trigger hooks
|
// trigger hooks
|
||||||
layer.hooks.beforeRender.call(layer);
|
layer.hooks.beforeRender.call();
|
||||||
layer.render();
|
layer.render();
|
||||||
layer.hooks.afterRender.call(layer);
|
layer.hooks.afterRender.call();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { inject, injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { lazyInject } from '../../../index';
|
import { lazyInject } from '../../../index';
|
||||||
import { TYPES } from '../../../types';
|
import { TYPES } from '../../../types';
|
||||||
import {
|
import {
|
||||||
IInteractionService,
|
IInteractionService,
|
||||||
InteractionEvent,
|
InteractionEvent,
|
||||||
} from '../../interaction/IInteractionService';
|
} from '../../interaction/IInteractionService';
|
||||||
import { ILayer, ILayerService } from '../../layer/ILayerService';
|
import { ILayer } from '../../layer/ILayerService';
|
||||||
import { ILogService } from '../../log/ILogService';
|
import { ILogService } from '../../log/ILogService';
|
||||||
import { gl } from '../gl';
|
import { gl } from '../gl';
|
||||||
import { IFramebuffer } from '../IFramebuffer';
|
import { IFramebuffer } from '../IFramebuffer';
|
||||||
|
@ -104,9 +104,9 @@ export default class PixelPickingPass implements IPass {
|
||||||
const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag();
|
const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag();
|
||||||
this.layer.multiPassRenderer.setRenderFlag(false);
|
this.layer.multiPassRenderer.setRenderFlag(false);
|
||||||
// trigger hooks
|
// trigger hooks
|
||||||
layer.hooks.beforeRender.call(layer);
|
layer.hooks.beforeRender.call();
|
||||||
layer.render();
|
layer.render();
|
||||||
layer.hooks.afterRender.call(layer);
|
layer.hooks.afterRender.call();
|
||||||
this.layer.multiPassRenderer.setRenderFlag(originRenderFlag);
|
this.layer.multiPassRenderer.setRenderFlag(originRenderFlag);
|
||||||
|
|
||||||
this.alreadyInRendering = false;
|
this.alreadyInRendering = false;
|
||||||
|
@ -145,8 +145,6 @@ export default class PixelPickingPass implements IPass {
|
||||||
framebuffer: this.pickingFBO,
|
framebuffer: this.pickingFBO,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info('try to picking');
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
pickedColors[0] !== 0 ||
|
pickedColors[0] !== 0 ||
|
||||||
pickedColors[1] !== 0 ||
|
pickedColors[1] !== 0 ||
|
||||||
|
@ -213,12 +211,11 @@ export default class PixelPickingPass implements IPass {
|
||||||
// TODO: highlight pass 需要 multipass
|
// TODO: highlight pass 需要 multipass
|
||||||
const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag();
|
const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag();
|
||||||
this.layer.multiPassRenderer.setRenderFlag(false);
|
this.layer.multiPassRenderer.setRenderFlag(false);
|
||||||
this.layer.hooks.beforeRender.call(this.layer);
|
this.layer.hooks.beforeRender.call();
|
||||||
// @ts-ignore
|
this.layer.hooks.beforeHighlight.call([r, g, b]);
|
||||||
this.layer.hooks.beforeHighlight.call(this.layer, [r, g, b]);
|
|
||||||
this.layer.render();
|
this.layer.render();
|
||||||
this.layer.hooks.afterHighlight.call(this.layer);
|
this.layer.hooks.afterHighlight.call();
|
||||||
this.layer.hooks.afterRender.call(this.layer);
|
this.layer.hooks.afterRender.call();
|
||||||
this.layer.multiPassRenderer.setRenderFlag(originRenderFlag);
|
this.layer.multiPassRenderer.setRenderFlag(originRenderFlag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
IMapService,
|
IMapService,
|
||||||
IModel,
|
IModel,
|
||||||
IMultiPassRenderer,
|
IMultiPassRenderer,
|
||||||
InteractionEvent,
|
|
||||||
IRendererService,
|
IRendererService,
|
||||||
IShaderModuleService,
|
IShaderModuleService,
|
||||||
ISourceCFG,
|
ISourceCFG,
|
||||||
|
@ -24,7 +23,10 @@ import {
|
||||||
} from '@l7/core';
|
} from '@l7/core';
|
||||||
import Source from '@l7/source';
|
import Source from '@l7/source';
|
||||||
import { isFunction } from 'lodash';
|
import { isFunction } from 'lodash';
|
||||||
import { SyncHook } from 'tapable';
|
// @ts-ignore
|
||||||
|
import mergeJsonSchemas from 'merge-json-schemas';
|
||||||
|
import { SyncBailHook, SyncHook } from 'tapable';
|
||||||
|
import ConfigSchemaValidationPlugin from '../plugins/ConfigSchemaValidationPlugin';
|
||||||
import DataMappingPlugin from '../plugins/DataMappingPlugin';
|
import DataMappingPlugin from '../plugins/DataMappingPlugin';
|
||||||
import DataSourcePlugin from '../plugins/DataSourcePlugin';
|
import DataSourcePlugin from '../plugins/DataSourcePlugin';
|
||||||
import FeatureScalePlugin from '../plugins/FeatureScalePlugin';
|
import FeatureScalePlugin from '../plugins/FeatureScalePlugin';
|
||||||
|
@ -33,6 +35,7 @@ import PixelPickingPlugin from '../plugins/PixelPickingPlugin';
|
||||||
import RegisterStyleAttributePlugin from '../plugins/RegisterStyleAttributePlugin';
|
import RegisterStyleAttributePlugin from '../plugins/RegisterStyleAttributePlugin';
|
||||||
import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin';
|
import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin';
|
||||||
import UpdateStyleAttributePlugin from '../plugins/UpdateStyleAttributePlugin';
|
import UpdateStyleAttributePlugin from '../plugins/UpdateStyleAttributePlugin';
|
||||||
|
import baseLayerSchema from './schema';
|
||||||
|
|
||||||
export interface ILayerModelInitializationOptions {
|
export interface ILayerModelInitializationOptions {
|
||||||
moduleName: string;
|
moduleName: string;
|
||||||
|
@ -64,16 +67,15 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
|
|
||||||
// 生命周期钩子
|
// 生命周期钩子
|
||||||
public hooks = {
|
public hooks = {
|
||||||
init: new SyncHook(['layer']),
|
init: new SyncBailHook<void, boolean | void>(),
|
||||||
beforeRender: new SyncHook(['layer']),
|
beforeRender: new SyncBailHook<void, boolean | void>(),
|
||||||
afterRender: new SyncHook(['layer']),
|
afterRender: new SyncHook<void>(),
|
||||||
beforePickingEncode: new SyncHook(['layer']),
|
beforePickingEncode: new SyncHook<void>(),
|
||||||
afterPickingEncode: new SyncHook(['layer']),
|
afterPickingEncode: new SyncHook<void>(),
|
||||||
// @ts-ignore
|
beforeHighlight: new SyncHook<[number[]]>(['pickedColor']),
|
||||||
beforeHighlight: new SyncHook(['layer', 'pickedColor']),
|
afterHighlight: new SyncHook<void>(),
|
||||||
afterHighlight: new SyncHook(['layer']),
|
beforeDestroy: new SyncHook<void>(),
|
||||||
beforeDestroy: new SyncHook(['layer']),
|
afterDestroy: new SyncHook<void>(),
|
||||||
afterDestroy: new SyncHook(['layer']),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 待渲染 model 列表
|
// 待渲染 model 列表
|
||||||
|
@ -84,6 +86,14 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
|
|
||||||
// 插件集
|
// 插件集
|
||||||
public plugins: ILayerPlugin[] = [
|
public plugins: ILayerPlugin[] = [
|
||||||
|
/**
|
||||||
|
* 校验传入参数配置项的正确性
|
||||||
|
* @see /dev-docs/ConfigSchemaValidation.md
|
||||||
|
*/
|
||||||
|
new ConfigSchemaValidationPlugin(),
|
||||||
|
/**
|
||||||
|
* 获取 Source
|
||||||
|
*/
|
||||||
new DataSourcePlugin(),
|
new DataSourcePlugin(),
|
||||||
/**
|
/**
|
||||||
* 根据 StyleAttribute 创建 VertexAttribute
|
* 根据 StyleAttribute 创建 VertexAttribute
|
||||||
|
@ -132,6 +142,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
|
|
||||||
private encodedData: IEncodeFeature[];
|
private encodedData: IEncodeFeature[];
|
||||||
|
|
||||||
|
private configSchema: object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存样式属性
|
* 保存样式属性
|
||||||
*/
|
*/
|
||||||
|
@ -174,7 +186,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
this.hooks.init.call(this);
|
this.hooks.init.call();
|
||||||
this.buildModels();
|
this.buildModels();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -244,14 +256,14 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public destroy() {
|
public destroy() {
|
||||||
this.hooks.beforeDestroy.call(this);
|
this.hooks.beforeDestroy.call();
|
||||||
|
|
||||||
// 清除所有属性以及关联的 vao
|
// 清除所有属性以及关联的 vao
|
||||||
this.styleAttributeService.clearAllAttributes();
|
this.styleAttributeService.clearAllAttributes();
|
||||||
// 销毁所有 model
|
// 销毁所有 model
|
||||||
this.models.forEach((model) => model.destroy());
|
this.models.forEach((model) => model.destroy());
|
||||||
|
|
||||||
this.hooks.afterDestroy.call(this);
|
this.hooks.afterDestroy.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
public isDirty() {
|
public isDirty() {
|
||||||
|
@ -283,6 +295,18 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
return this.encodedData;
|
return this.encodedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getConfigSchemaForValidation() {
|
||||||
|
if (!this.configSchema) {
|
||||||
|
// 相比 allOf, merge 有一些优势
|
||||||
|
// @see https://github.com/goodeggs/merge-json-schemas
|
||||||
|
this.configSchema = mergeJsonSchemas([
|
||||||
|
baseLayerSchema,
|
||||||
|
this.getConfigSchema(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return this.configSchema;
|
||||||
|
}
|
||||||
|
|
||||||
public pick({ x, y }: { x: number; y: number }) {
|
public pick({ x, y }: { x: number; y: number }) {
|
||||||
this.interactionService.triggerHover({ x, y });
|
this.interactionService.triggerHover({ x, y });
|
||||||
}
|
}
|
||||||
|
@ -313,6 +337,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected getConfigSchema() {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
protected buildModels() {
|
protected buildModels() {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* BaseLayer Schema
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
properties: {
|
||||||
|
enablePicking: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
enableHighlight: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
highlightColor: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'number',
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {
|
||||||
|
IGlobalConfigService,
|
||||||
|
ILayer,
|
||||||
|
ILayerPlugin,
|
||||||
|
ILogService,
|
||||||
|
lazyInject,
|
||||||
|
TYPES,
|
||||||
|
} from '@l7/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layer 初始化阶段以及重绘阶段首先校验传入参数,如果校验失败则中断后续插件处理。
|
||||||
|
*/
|
||||||
|
export default class ConfigSchemaValidationPlugin implements ILayerPlugin {
|
||||||
|
@lazyInject(TYPES.IGlobalConfigService)
|
||||||
|
private readonly configService: IGlobalConfigService;
|
||||||
|
|
||||||
|
@lazyInject(TYPES.ILogService)
|
||||||
|
private readonly logger: ILogService;
|
||||||
|
|
||||||
|
public apply(layer: ILayer) {
|
||||||
|
layer.hooks.init.tap('ConfigSchemaValidationPlugin', () => {
|
||||||
|
this.configService.registerLayerConfigSchemaValidator(
|
||||||
|
layer.name,
|
||||||
|
layer.getConfigSchemaForValidation(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { valid, errorText } = this.configService.validateLayerConfig(
|
||||||
|
layer.name,
|
||||||
|
layer.getStyleOptions(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
this.logger.error(errorText || '');
|
||||||
|
// 中断 init 过程
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layer.hooks.beforeRender.tap('ConfigSchemaValidationPlugin', () => {
|
||||||
|
// TODO: 配置项发生变化,需要重新校验
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,8 +71,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
|
||||||
|
|
||||||
layer.hooks.beforeHighlight.tap(
|
layer.hooks.beforeHighlight.tap(
|
||||||
'PixelPickingPlugin',
|
'PixelPickingPlugin',
|
||||||
// @ts-ignore
|
(pickedColor: number[]) => {
|
||||||
(l: unknown, pickedColor: unknown) => {
|
|
||||||
const { highlightColor } = layer.getStyleOptions();
|
const { highlightColor } = layer.getStyleOptions();
|
||||||
const highlightColorInArray =
|
const highlightColorInArray =
|
||||||
typeof highlightColor === 'string'
|
typeof highlightColor === 'string'
|
||||||
|
@ -81,7 +80,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
|
||||||
layer.models.forEach((model) =>
|
layer.models.forEach((model) =>
|
||||||
model.addUniforms({
|
model.addUniforms({
|
||||||
u_PickingStage: PickingStage.HIGHLIGHT,
|
u_PickingStage: PickingStage.HIGHLIGHT,
|
||||||
u_PickingColor: pickedColor as number[],
|
u_PickingColor: pickedColor,
|
||||||
u_HighlightColor: highlightColorInArray.map((c) => c * 255),
|
u_HighlightColor: highlightColorInArray.map((c) => c * 255),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,6 +23,18 @@ export function polygonTriangulation(feature: IEncodeFeature) {
|
||||||
export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
|
export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
|
||||||
public name: string = 'PolygonLayer';
|
public name: string = 'PolygonLayer';
|
||||||
|
|
||||||
|
protected getConfigSchema() {
|
||||||
|
return {
|
||||||
|
properties: {
|
||||||
|
opacity: {
|
||||||
|
type: 'number',
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected renderModels() {
|
protected renderModels() {
|
||||||
const { opacity } = this.getStyleOptions();
|
const { opacity } = this.getStyleOptions();
|
||||||
this.models.forEach((model) =>
|
this.models.forEach((model) =>
|
||||||
|
|
|
@ -85,13 +85,11 @@ export default class AdvancedAPI extends React.Component {
|
||||||
.add(styleOptions, 'pickingX', 0, window.innerWidth)
|
.add(styleOptions, 'pickingX', 0, window.innerWidth)
|
||||||
.onChange((pickingX: number) => {
|
.onChange((pickingX: number) => {
|
||||||
layer.pick({ x: pickingX, y: styleOptions.pickingY });
|
layer.pick({ x: pickingX, y: styleOptions.pickingY });
|
||||||
// scene.render();
|
|
||||||
});
|
});
|
||||||
pointFolder
|
pointFolder
|
||||||
.add(styleOptions, 'pickingY', 0, window.innerHeight)
|
.add(styleOptions, 'pickingY', 0, window.innerHeight)
|
||||||
.onChange((pickingY: number) => {
|
.onChange((pickingY: number) => {
|
||||||
layer.pick({ x: styleOptions.pickingX, y: pickingY });
|
layer.pick({ x: styleOptions.pickingX, y: pickingY });
|
||||||
// scene.render();
|
|
||||||
});
|
});
|
||||||
pointFolder
|
pointFolder
|
||||||
.addColor(styleOptions, 'highlightColor')
|
.addColor(styleOptions, 'highlightColor')
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -9618,6 +9618,11 @@ lodash.get@^4.4.2:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||||
|
|
||||||
|
lodash.isarray@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-4.0.0.tgz#2aca496b28c4ca6d726715313590c02e6ea34403"
|
||||||
|
integrity sha1-KspJayjEym1yZxUxNZDALm6jRAM=
|
||||||
|
|
||||||
lodash.isequal@^4.5.0:
|
lodash.isequal@^4.5.0:
|
||||||
version "4.5.0"
|
version "4.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
@ -9628,6 +9633,16 @@ lodash.ismatch@^4.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
|
resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
|
||||||
integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=
|
integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=
|
||||||
|
|
||||||
|
lodash.isnil@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c"
|
||||||
|
integrity sha1-SeKM1VkBNFjIFMVHnTxmOiG/qmw=
|
||||||
|
|
||||||
|
lodash.isplainobject@^4.0.6:
|
||||||
|
version "4.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
|
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
|
||||||
|
|
||||||
lodash.map@^4.5.1:
|
lodash.map@^4.5.1:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
|
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
|
||||||
|
@ -9638,6 +9653,11 @@ lodash.memoize@^4.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||||
|
|
||||||
|
lodash.mergewith@^4.6.0:
|
||||||
|
version "4.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
|
||||||
|
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
|
||||||
|
|
||||||
lodash.set@^4.3.2:
|
lodash.set@^4.3.2:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
||||||
|
@ -10070,6 +10090,17 @@ merge-descriptors@1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||||
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
|
||||||
|
|
||||||
|
merge-json-schemas@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/merge-json-schemas/-/merge-json-schemas-1.0.0.tgz#2d635eaa8401c5fa3d03f30f89349fc7cafee62f"
|
||||||
|
integrity sha1-LWNeqoQBxfo9A/MPiTSfx8r+5i8=
|
||||||
|
dependencies:
|
||||||
|
lodash.isarray "^4.0.0"
|
||||||
|
lodash.isnil "^4.0.0"
|
||||||
|
lodash.isplainobject "^4.0.6"
|
||||||
|
lodash.mergewith "^4.6.0"
|
||||||
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
merge-stream@^2.0.0:
|
merge-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
|
||||||
|
|
Loading…
Reference in New Issue