mirror of https://gitee.com/antv-l7/antv-l7
feat(layer): pointLayer add text model
This commit is contained in:
parent
2ad4289e75
commit
84a9193e3a
|
@ -13,6 +13,7 @@ const VECTOR_TO_POINT_MATRIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0];
|
|||
@injectable()
|
||||
export default class CoordinateSystemService
|
||||
implements ICoordinateSystemService {
|
||||
public needRefresh: boolean = true;
|
||||
@inject(TYPES.ICameraService)
|
||||
private readonly cameraService: ICameraService;
|
||||
|
||||
|
@ -59,6 +60,9 @@ export default class CoordinateSystemService
|
|||
* TODO: 使用 memoize 缓存参数以及计算结果
|
||||
*/
|
||||
public refresh(): void {
|
||||
// if (!this.needRefresh) {
|
||||
// return;
|
||||
// }
|
||||
const zoom = this.cameraService.getZoom();
|
||||
const zoomScale = this.cameraService.getZoomScale();
|
||||
const center = this.cameraService.getCenter();
|
||||
|
@ -86,6 +90,7 @@ export default class CoordinateSystemService
|
|||
} else if (this.coordinateSystem === CoordinateSystem.P20_OFFSET) {
|
||||
this.calculateLnglatOffset(center, zoom, zoomScale, true);
|
||||
}
|
||||
this.needRefresh = false;
|
||||
|
||||
// TODO: 判断是否应用瓦片 & 常规坐标系
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export const CoordinateUniform = {
|
|||
};
|
||||
|
||||
export interface ICoordinateSystemService {
|
||||
needRefresh: boolean;
|
||||
refresh(): void;
|
||||
getCoordinateSystem(): CoordinateSystem;
|
||||
setCoordinateSystem(coordinateSystem: CoordinateSystem): void;
|
||||
|
|
|
@ -53,6 +53,7 @@ export interface ILayerModel {
|
|||
render(): void;
|
||||
getUninforms(): IModelUniform;
|
||||
getDefaultStyle(): unknown;
|
||||
getAnimateUniforms(): IModelUniform;
|
||||
buildModels(): IModel[];
|
||||
}
|
||||
export interface IModelUniform {
|
||||
|
@ -78,6 +79,7 @@ export interface ILayer {
|
|||
zIndex: number;
|
||||
plugins: ILayerPlugin[];
|
||||
layerModelNeedUpdate: boolean;
|
||||
layerModel: ILayerModel;
|
||||
dataState: IDataState; // 数据流状态
|
||||
pickedFeatureID: number;
|
||||
hooks: {
|
||||
|
|
|
@ -119,7 +119,7 @@ export interface IStyleAttributeInitializationOptions {
|
|||
type: AttributeType;
|
||||
scale?: {
|
||||
field: StyleAttributeField;
|
||||
values: unknown[];
|
||||
values: unknown[] | string;
|
||||
names: string[];
|
||||
type: StyleScaleType;
|
||||
callback?: (...args: any[]) => [];
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ICameraService, IViewport } from '../camera/ICameraService';
|
|||
import { IControlService } from '../component/IControlService';
|
||||
import { IMarkerService } from '../component/IMarkerService';
|
||||
import { IGlobalConfigService, ISceneConfig } from '../config/IConfigService';
|
||||
import { ICoordinateSystemService } from '../coordinate/ICoordinateSystemService';
|
||||
import { IInteractionService } from '../interaction/IInteractionService';
|
||||
import { ILayer, ILayerService } from '../layer/ILayerService';
|
||||
import { ILogService } from '../log/ILogService';
|
||||
|
@ -47,6 +48,9 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
@inject(TYPES.IMapService)
|
||||
private readonly map: IMapService;
|
||||
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
private readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IRendererService)
|
||||
private readonly rendererService: IRendererService;
|
||||
|
||||
|
@ -249,6 +253,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
});
|
||||
// 触发 Map, canvas
|
||||
DOM.triggerResize();
|
||||
this.coordinateSystemService.needRefresh = true;
|
||||
// repaint layers
|
||||
this.render();
|
||||
}
|
||||
|
|
|
@ -103,6 +103,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
options?: ISourceCFG;
|
||||
};
|
||||
|
||||
public layerModel: ILayerModel;
|
||||
|
||||
@lazyInject(TYPES.ILogService)
|
||||
protected readonly logger: ILogService;
|
||||
|
||||
|
@ -133,8 +135,6 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
) => IPostProcessingPass<unknown>;
|
||||
protected normalPassFactory: (name: string) => IPass<unknown>;
|
||||
|
||||
protected layerModel: ILayerModel;
|
||||
|
||||
protected animateOptions: IAnimateOption = { enable: false };
|
||||
|
||||
/**
|
||||
|
|
|
@ -57,7 +57,10 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
|
|||
this.cameraService = layer
|
||||
.getContainer()
|
||||
.get<ICameraService>(TYPES.ICameraService);
|
||||
// 注册 Attribute
|
||||
this.registerBuiltinAttributes();
|
||||
// 开启动画
|
||||
this.startModelAnimate();
|
||||
}
|
||||
public getBlend(): IBlendOptions {
|
||||
const { blend = 'normal' } = this.layer.getLayerConfig();
|
||||
|
@ -70,6 +73,10 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
|
|||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
return {};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin
|
|||
import DataMappingPlugin from './plugins/DataMappingPlugin';
|
||||
import DataSourcePlugin from './plugins/DataSourcePlugin';
|
||||
import FeatureScalePlugin from './plugins/FeatureScalePlugin';
|
||||
import LayerAnimateStylePlugin from './plugins/LayerAnimateStylePlugin';
|
||||
import LayerStylePlugin from './plugins/LayerStylePlugin';
|
||||
import LightingPlugin from './plugins/LightingPlugin';
|
||||
import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin';
|
||||
|
@ -87,6 +88,14 @@ container
|
|||
.bind<ILayerPlugin>(TYPES.ILayerPlugin)
|
||||
.to(ShaderUniformPlugin)
|
||||
.inRequestScope();
|
||||
|
||||
/**
|
||||
* 传入动画参数
|
||||
*/
|
||||
container
|
||||
.bind<ILayerPlugin>(TYPES.ILayerPlugin)
|
||||
.to(LayerAnimateStylePlugin)
|
||||
.inRequestScope();
|
||||
/**
|
||||
* 传入光照相关参数
|
||||
*/
|
||||
|
|
|
@ -1,211 +0,0 @@
|
|||
import { AttributeType, gl, IEncodeFeature } from '@antv/l7-core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import { LineTriangulation } from '../core/triangulation';
|
||||
import line_dash_frag from './shaders/line_dash_frag.glsl';
|
||||
import line_dash_vert from './shaders/line_dash_vert.glsl';
|
||||
interface IDashLineLayerStyleOptions {
|
||||
opacity: number;
|
||||
dashArray: [number, number];
|
||||
lineType: string;
|
||||
}
|
||||
export default class DashLineLayer extends BaseLayer<
|
||||
IDashLineLayerStyleOptions
|
||||
> {
|
||||
public type: string = 'LineLayer';
|
||||
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
const {
|
||||
opacity,
|
||||
dashArray = [10, 5],
|
||||
lineType = 'dash',
|
||||
} = this.getLayerConfig();
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_opacity: opacity || 1.0,
|
||||
u_dash_array: dashArray,
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected buildModels() {
|
||||
this.registerBuiltinAttributes();
|
||||
this.models = [
|
||||
this.buildLayerModel({
|
||||
moduleName: 'line_dash',
|
||||
vertexShader: line_dash_vert,
|
||||
fragmentShader: line_dash_frag,
|
||||
triangulation: LineTriangulation,
|
||||
blend: {
|
||||
enable: true,
|
||||
func: {
|
||||
srcRGB: gl.SRC_ALPHA,
|
||||
srcAlpha: 1,
|
||||
dstRGB: gl.ONE_MINUS_SRC_ALPHA,
|
||||
dstAlpha: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private registerBuiltinAttributes() {
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'size',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Size',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
const { size } = feature;
|
||||
return Array.isArray(size) ? [size[0]] : [size as number];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'normal',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Normal',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.STATIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 3,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
normal: number[],
|
||||
) => {
|
||||
return normal;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'miter',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Miter',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
return [vertex[4]];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// this.styleAttributeService.registerStyleAttribute({
|
||||
// name: 'startPos',
|
||||
// type: AttributeType.Attribute,
|
||||
// descriptor: {
|
||||
// name: 'a_StartPos',
|
||||
// buffer: {
|
||||
// // give the WebGL driver a hint that this buffer may change
|
||||
// usage: gl.DYNAMIC_DRAW,
|
||||
// data: [],
|
||||
// type: gl.FLOAT,
|
||||
// },
|
||||
// size: 3,
|
||||
// update: (
|
||||
// feature: IEncodeFeature,
|
||||
// featureIdx: number,
|
||||
// vertex: number[],
|
||||
// attributeIdx: number,
|
||||
// ) => {
|
||||
// const coordinates = feature.coordinates as number[][];
|
||||
// const coord = coordinates[0];
|
||||
// return coord.length === 3 ? coord : [...coord, 0.0];
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'distance',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Distance',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
return [vertex[3]];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'total_distance',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Total_Distance',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
return [vertex[5]];
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -29,7 +29,6 @@ export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
|
|||
return defaultConfig[type];
|
||||
}
|
||||
protected renderModels() {
|
||||
// console.log(this.layerModel.getUninforms());
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: this.layerModel.getUninforms(),
|
||||
|
|
|
@ -24,19 +24,23 @@ export default class ArcModel extends BaseModel {
|
|||
lineType = 'solid',
|
||||
dashArray = [10, 5],
|
||||
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_opacity: opacity || 1,
|
||||
segmentNumber: 30,
|
||||
u_line_type: lineStyleObj[lineType || 'solid'],
|
||||
u_dash_array: dashArray,
|
||||
};
|
||||
}
|
||||
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_aimate: this.animateOption2Array(animateOption as IAnimateOption),
|
||||
u_time: this.layer.getLayerAnimateTime(),
|
||||
};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
this.startModelAnimate();
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
moduleName: 'arc2dline',
|
||||
|
|
|
@ -23,19 +23,22 @@ export default class Arc3DModel extends BaseModel {
|
|||
lineType = 'solid',
|
||||
dashArray = [10, 5],
|
||||
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_opacity: opacity || 1,
|
||||
segmentNumber: 30,
|
||||
u_line_type: lineStyleObj[lineType as string] || 0.0,
|
||||
u_dash_array: dashArray,
|
||||
};
|
||||
}
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_aimate: this.animateOption2Array(animateOption as IAnimateOption),
|
||||
u_time: this.layer.getLayerAnimateTime(),
|
||||
};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
this.startModelAnimate();
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
moduleName: 'arc3Dline',
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import {
|
||||
AttributeType,
|
||||
gl,
|
||||
IEncodeFeature,
|
||||
ILayer,
|
||||
ILayerModel,
|
||||
IModel,
|
||||
} from '@antv/l7-core';
|
||||
|
||||
import BaseModel from '../../core/BaseModel';
|
||||
export default class ArcModel extends BaseModel {
|
||||
public getUninforms() {
|
||||
return {};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
protected registerBuiltinAttributes() {
|
||||
//
|
||||
}
|
||||
}
|
|
@ -25,22 +25,22 @@ export default class GreatCircleModel extends BaseModel {
|
|||
lineType = 'solid',
|
||||
dashArray = [10, 5],
|
||||
} = this.layer.getLayerConfig() as Partial<ILineLayerStyleOptions>;
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_opacity: opacity || 1,
|
||||
segmentNumber: 30,
|
||||
u_line_type: lineStyleObj[lineType as string] || 0.0,
|
||||
u_dash_array: dashArray,
|
||||
};
|
||||
}
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_aimate: this.animateOption2Array(animateOption as IAnimateOption),
|
||||
u_time: this.layer.getLayerAnimateTime(),
|
||||
};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
if (animateOption.enable) {
|
||||
this.layer.setAnimateStartTime();
|
||||
}
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
moduleName: 'arc2dline',
|
||||
|
@ -53,7 +53,6 @@ export default class GreatCircleModel extends BaseModel {
|
|||
];
|
||||
}
|
||||
protected registerBuiltinAttributes() {
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'size',
|
||||
type: AttributeType.Attribute,
|
||||
|
|
|
@ -24,18 +24,21 @@ export default class LineModel extends BaseModel {
|
|||
lineType = lineStyleType.solid,
|
||||
dashArray = [10, 5],
|
||||
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_opacity: opacity || 1.0,
|
||||
u_line_type: lineStyleObj[lineType],
|
||||
u_dash_array: dashArray,
|
||||
};
|
||||
}
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_aimate: this.animateOption2Array(animateOption as IAnimateOption),
|
||||
u_time: this.layer.getLayerAnimateTime(),
|
||||
};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
this.startModelAnimate();
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
moduleName: 'line',
|
||||
|
|
|
@ -13,6 +13,7 @@ uniform float u_dash_offset : 0.0;
|
|||
uniform float u_dash_ratio : 0.1;
|
||||
varying float v_distance_ratio;
|
||||
varying vec2 v_dash_array;
|
||||
varying float v_side;
|
||||
|
||||
|
||||
#pragma include "picking"
|
||||
|
@ -30,7 +31,8 @@ void main() {
|
|||
float alpha =1.0 - fract( mod(1.0- v_distance_ratio, u_aimate.z)* (1.0/ u_aimate.z) + u_time / u_aimate.y);
|
||||
alpha = (alpha + u_aimate.w -1.0) / u_aimate.w;
|
||||
alpha = smoothstep(0., 1., alpha);
|
||||
gl_FragColor.a *= alpha * blur;
|
||||
float alpha2 = exp(-abs(v_side));
|
||||
gl_FragColor.a *= alpha * blur * alpha2;
|
||||
// gl_FragColor.a = fract(u_time);
|
||||
}
|
||||
// dash line
|
||||
|
|
|
@ -24,6 +24,7 @@ varying vec4 v_color;
|
|||
varying vec2 v_dash_array;
|
||||
varying vec2 v_normal;
|
||||
varying float v_distance_ratio;
|
||||
varying float v_side;
|
||||
|
||||
|
||||
void main() {
|
||||
|
@ -39,6 +40,7 @@ void main() {
|
|||
v_color = a_Color;
|
||||
vec3 size = a_Miter * a_Size.x * reverse_offset_normal(a_Normal); //v_normal * vec3(1., -1., 1.0);
|
||||
vec2 offset = project_pixel(size.xy);
|
||||
v_side = a_Miter * a_Size.x;
|
||||
vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, a_Size.y, 1.0));
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
scales.forEach((scale) => {
|
||||
// 如果设置了回调, 这不需要设置让range
|
||||
if (!attributeScale.callback) {
|
||||
if (attributeScale.values) {
|
||||
if (attributeScale.values && attributeScale.values !== 'text') {
|
||||
if (
|
||||
scale.option?.type === 'linear' &&
|
||||
attributeScale.values.length > 2
|
||||
|
@ -131,6 +131,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
scale.scale.range(attributeScale.values);
|
||||
} else if (scale.option?.type === 'cat') {
|
||||
// 如果没有设置初值且 类型为cat,range ==domain;
|
||||
|
||||
scale.scale.range(scale.option.domain);
|
||||
}
|
||||
}
|
||||
|
@ -159,20 +160,13 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
dataArray: IParseDataItem[],
|
||||
) {
|
||||
const scalekey = [field, attribute.name].join('_');
|
||||
const values = attribute.scale?.values;
|
||||
if (this.scaleCache[scalekey]) {
|
||||
return this.scaleCache[scalekey];
|
||||
}
|
||||
const styleScale = this.createScale(field, dataArray);
|
||||
const styleScale = this.createScale(field, values, dataArray);
|
||||
this.scaleCache[scalekey] = styleScale;
|
||||
|
||||
// if (
|
||||
// styleScale.type === StyleScaleType.VARIABLE &&
|
||||
// attribute.scale?.values &&
|
||||
// attribute.scale?.values.length > 0
|
||||
// ) { // 只有变量初始化range
|
||||
// styleScale.scale.range(attribute.scale?.values);
|
||||
// }
|
||||
|
||||
return this.scaleCache[scalekey];
|
||||
}
|
||||
|
||||
|
@ -191,7 +185,11 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
return [field];
|
||||
}
|
||||
|
||||
private createScale(field: string, data?: IParseDataItem[]): IStyleScale {
|
||||
private createScale(
|
||||
field: string,
|
||||
values: unknown[] | string | undefined,
|
||||
data?: IParseDataItem[],
|
||||
): IStyleScale {
|
||||
// 首先查找全局默认配置例如 color
|
||||
const scaleOption: IScale | undefined = this.scaleOptions[field];
|
||||
const styleScale: IStyleScale = {
|
||||
|
@ -200,6 +198,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
type: StyleScaleType.VARIABLE,
|
||||
option: scaleOption,
|
||||
};
|
||||
|
||||
if (!data || !data.length) {
|
||||
if (scaleOption && scaleOption.type) {
|
||||
styleScale.scale = this.createDefaultScale(scaleOption);
|
||||
|
@ -216,9 +215,12 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
styleScale.type = StyleScaleType.CONSTANT;
|
||||
} else {
|
||||
// 根据数据类型判断 默认等分位,时间,和枚举类型
|
||||
const type =
|
||||
let type =
|
||||
(scaleOption && scaleOption.type) || this.getDefaultType(firstValue);
|
||||
|
||||
if (values === 'text') {
|
||||
// text 为内置变 如果是文本则为cat
|
||||
type = ScaleTypes.CAT;
|
||||
}
|
||||
const cfg = this.createDefaultScaleConfig(type, field, data);
|
||||
Object.assign(cfg, scaleOption);
|
||||
styleScale.scale = this.createDefaultScale(cfg);
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import {
|
||||
CameraUniform,
|
||||
CoordinateUniform,
|
||||
ICameraService,
|
||||
ICoordinateSystemService,
|
||||
ILayer,
|
||||
ILayerPlugin,
|
||||
IModel,
|
||||
IRendererService,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { inject, injectable } from 'inversify';
|
||||
|
||||
@injectable()
|
||||
export default class LayerAnimateStylePlugin implements ILayerPlugin {
|
||||
@inject(TYPES.ICameraService)
|
||||
private readonly cameraService: ICameraService;
|
||||
|
||||
@inject(TYPES.IRendererService)
|
||||
private readonly rendererService: IRendererService;
|
||||
|
||||
public apply(layer: ILayer) {
|
||||
layer.hooks.beforeRender.tap('LayerAnimateStylePlugin', () => {
|
||||
// 重新计算坐标系参数
|
||||
layer.models.forEach((model: IModel) => {
|
||||
model.addUniforms({
|
||||
...layer.layerModel.getAnimateUniforms(),
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ export default class ShaderUniformPlugin implements ILayerPlugin {
|
|||
public apply(layer: ILayer) {
|
||||
layer.hooks.beforeRender.tap('ShaderUniformPlugin', () => {
|
||||
// 重新计算坐标系参数
|
||||
|
||||
this.coordinateSystemService.refresh();
|
||||
|
||||
const { width, height } = this.rendererService.getViewportSize();
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
AttributeType,
|
||||
gl,
|
||||
IAnimateOption,
|
||||
IAttribute,
|
||||
IElements,
|
||||
IEncodeFeature,
|
||||
ILayerConfig,
|
||||
IModel,
|
||||
IModelUniform,
|
||||
} from '@antv/l7-core';
|
||||
|
@ -30,6 +32,13 @@ export default class FillModel extends BaseModel {
|
|||
u_stroke_color: rgb2arr(stroke),
|
||||
};
|
||||
}
|
||||
public getAnimateUniforms(): IModelUniform {
|
||||
const { animateOption } = this.layer.getLayerConfig() as ILayerConfig;
|
||||
return {
|
||||
u_aimate: this.animateOption2Array(animateOption as IAnimateOption),
|
||||
u_time: this.layer.getLayerAnimateTime(),
|
||||
};
|
||||
}
|
||||
|
||||
public getAttribute(): {
|
||||
attributes: {
|
||||
|
|
|
@ -3,6 +3,7 @@ import ExtrudeModel from './extrude';
|
|||
import FillModel from './fill';
|
||||
import IMageModel from './image';
|
||||
import NormalModel from './normal';
|
||||
import TextModel from './text';
|
||||
|
||||
export type PointType = 'fill' | 'image' | 'normal' | 'extrude' | 'text';
|
||||
|
||||
|
@ -11,7 +12,7 @@ const PointModels: { [key in PointType]: any } = {
|
|||
image: IMageModel,
|
||||
normal: NormalModel,
|
||||
extrude: ExtrudeModel,
|
||||
text: null,
|
||||
text: TextModel,
|
||||
};
|
||||
|
||||
export default PointModels;
|
||||
|
|
|
@ -1,15 +1,264 @@
|
|||
import { IModel, IModelUniform } from '@antv/l7-core';
|
||||
import {
|
||||
AttributeType,
|
||||
BlendType,
|
||||
gl,
|
||||
IEncodeFeature,
|
||||
ILayerConfig,
|
||||
IModel,
|
||||
IModelUniform,
|
||||
ITexture2D,
|
||||
} from '@antv/l7-core';
|
||||
import { rgb2arr } from '@antv/l7-utils';
|
||||
import BaseModel from '../../core/BaseModel';
|
||||
import { PointFillTriangulation } from '../../core/triangulation';
|
||||
import {
|
||||
getGlyphQuads,
|
||||
IGlyphQuad,
|
||||
shapeText,
|
||||
} from '../../utils/symbol-layout';
|
||||
import textFrag from '../shaders/text_frag.glsl';
|
||||
import textVert from '../shaders/text_vert.glsl';
|
||||
interface IPointTextLayerStyleOptions {
|
||||
opacity: number;
|
||||
textAnchor: string;
|
||||
spacing: number;
|
||||
padding: [number, number];
|
||||
stroke: string;
|
||||
strokeWidth: number;
|
||||
strokeOpacity: number;
|
||||
fontWeight: string;
|
||||
fontFamily: string;
|
||||
textOffset: [number, number];
|
||||
textAllowOverlap: boolean;
|
||||
}
|
||||
export function TextTriangulation(feature: IEncodeFeature) {
|
||||
const coordinates = feature.coordinates as number[];
|
||||
const { glyphQuads } = feature;
|
||||
const vertices: number[] = [];
|
||||
const indices: number[] = [];
|
||||
const coord =
|
||||
coordinates.length === 2
|
||||
? [coordinates[0], coordinates[1], 0]
|
||||
: coordinates;
|
||||
glyphQuads.forEach((quad: IGlyphQuad, index: number) => {
|
||||
vertices.push(
|
||||
...coord,
|
||||
quad.tex.x,
|
||||
quad.tex.y + quad.tex.height,
|
||||
quad.tl.x,
|
||||
quad.tl.y,
|
||||
...coord,
|
||||
quad.tex.x + quad.tex.width,
|
||||
quad.tex.y + quad.tex.height,
|
||||
quad.tr.x,
|
||||
quad.tr.y,
|
||||
...coord,
|
||||
quad.tex.x + quad.tex.width,
|
||||
quad.tex.y,
|
||||
quad.br.x,
|
||||
quad.br.y,
|
||||
...coord,
|
||||
quad.tex.x,
|
||||
quad.tex.y,
|
||||
quad.bl.x,
|
||||
quad.bl.y,
|
||||
);
|
||||
indices.push(
|
||||
0 + index * 4,
|
||||
1 + index * 4,
|
||||
2 + index * 4,
|
||||
2 + index * 4,
|
||||
3 + index * 4,
|
||||
0 + index * 4,
|
||||
);
|
||||
});
|
||||
return {
|
||||
vertices, // [ x, y, z, tex.x,tex.y, offset.x. offset.y]
|
||||
indices,
|
||||
size: 7,
|
||||
};
|
||||
}
|
||||
|
||||
export default class ExtrudeModel extends BaseModel {
|
||||
export default class TextModel extends BaseModel {
|
||||
private texture: ITexture2D;
|
||||
public getUninforms(): IModelUniform {
|
||||
throw new Error('Method not implemented.');
|
||||
const {
|
||||
fontWeight = 'normal',
|
||||
fontFamily,
|
||||
stroke,
|
||||
strokeWidth,
|
||||
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
|
||||
const { canvas, fontAtlas, mapping } = this.fontService;
|
||||
return {
|
||||
u_opacity: 1.0,
|
||||
u_sdf_map: this.texture,
|
||||
u_stroke: rgb2arr(stroke),
|
||||
u_halo_blur: 0.5,
|
||||
u_sdf_map_size: [canvas.width, canvas.height],
|
||||
u_strokeWidth: strokeWidth,
|
||||
};
|
||||
}
|
||||
|
||||
public buildModels(): IModel[] {
|
||||
throw new Error('Method not implemented.');
|
||||
this.initTextFont();
|
||||
this.generateGlyphLayout();
|
||||
this.registerBuiltinAttributes();
|
||||
this.updateTexture();
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
moduleName: 'pointText',
|
||||
vertexShader: textVert,
|
||||
fragmentShader: textFrag,
|
||||
triangulation: TextTriangulation,
|
||||
depth: { enable: false },
|
||||
blend: this.getBlend(),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected registerBuiltinAttributes() {
|
||||
throw new Error('Method not implemented.');
|
||||
const viewProjection = this.cameraService.getViewProjectionMatrix();
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'textOffsets',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_textOffsets',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.STATIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 2,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
return [vertex[5], vertex[6]];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'size',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Size',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
const { size } = feature;
|
||||
return Array.isArray(size) ? [size[0]] : [size as number];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'textUv',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_tex',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 2,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
return [vertex[3], vertex[4]];
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
private initTextFont() {
|
||||
const {
|
||||
fontWeight = 'normal',
|
||||
fontFamily,
|
||||
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
|
||||
const data = this.layer.getEncodedData();
|
||||
const characterSet: string[] = [];
|
||||
data.forEach((item: IEncodeFeature) => {
|
||||
let { shape = '' } = item;
|
||||
shape = shape.toString();
|
||||
for (const char of shape) {
|
||||
// 去重
|
||||
if (characterSet.indexOf(char) === -1) {
|
||||
characterSet.push(char);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.fontService.setFontOptions({
|
||||
characterSet,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
});
|
||||
}
|
||||
private generateGlyphLayout() {
|
||||
const { canvas, fontAtlas, mapping } = this.fontService;
|
||||
const {
|
||||
spacing = 2,
|
||||
textAnchor = 'center',
|
||||
textOffset,
|
||||
padding = [4, 4],
|
||||
textAllowOverlap,
|
||||
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
|
||||
const data = this.layer.getEncodedData();
|
||||
data.forEach((feature: IEncodeFeature) => {
|
||||
const { coordinates, shape = '' } = feature;
|
||||
const size = feature.size as number;
|
||||
const fontScale = size / 24;
|
||||
const shaping = shapeText(
|
||||
shape.toString(),
|
||||
mapping,
|
||||
24,
|
||||
textAnchor,
|
||||
'center',
|
||||
spacing,
|
||||
textOffset,
|
||||
);
|
||||
const glyphQuads = getGlyphQuads(shaping, textOffset, false);
|
||||
feature.shaping = shaping;
|
||||
feature.glyphQuads = glyphQuads;
|
||||
});
|
||||
}
|
||||
|
||||
private drawGlyph() {
|
||||
const {
|
||||
spacing = 2,
|
||||
textAnchor = 'center',
|
||||
textOffset = [0, 0],
|
||||
padding = [4, 4],
|
||||
textAllowOverlap,
|
||||
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
|
||||
const viewProjection = this.cameraService.getViewProjectionMatrix();
|
||||
}
|
||||
private updateTexture() {
|
||||
const { createTexture2D } = this.rendererService;
|
||||
const { canvas } = this.fontService;
|
||||
this.texture = createTexture2D({
|
||||
data: canvas,
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ uniform float u_stroke_opacity : 1;
|
|||
varying vec4 v_data;
|
||||
varying vec4 v_color;
|
||||
varying float v_radius;
|
||||
uniform float u_time;
|
||||
|
||||
#pragma include "sdf_2d"
|
||||
#pragma include "picking"
|
||||
|
@ -61,8 +62,14 @@ void main() {
|
|||
inner_df
|
||||
);
|
||||
vec4 strokeColor = u_stroke_color == vec4(0) ? v_color : u_stroke_color;
|
||||
float PI = 3.14159;
|
||||
float N_RINGS = 3.0;
|
||||
float FREQ = 1.0;
|
||||
// float intensity = 1.0;
|
||||
float intensity = clamp(cos(r * PI), 0.0, 1.0) * clamp(cos(2.0 * PI * (r * 2.0 * N_RINGS - FREQ * u_time / 1000.)), 0.0, 1.0);
|
||||
|
||||
gl_FragColor = opacity_t * mix(vec4(v_color.rgb, v_color.a * u_opacity), strokeColor * u_stroke_opacity, color_t);
|
||||
// gl_FragColor = vec4(gl_FragColor.xyz * intensity, intensity);
|
||||
|
||||
gl_FragColor = filterColor(gl_FragColor);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#define SDF_PX 8.0
|
||||
#define EDGE_GAMMA 0.105
|
||||
uniform sampler2D u_sdf_map;
|
||||
uniform float u_gamma_scale : 0.5;
|
||||
uniform float u_font_size : 24;
|
||||
|
@ -5,6 +7,7 @@ uniform float u_opacity : 1.0;
|
|||
uniform vec4 u_stroke : [0, 0, 0, 1];
|
||||
uniform float u_strokeWidth : 2.0;
|
||||
uniform float u_halo_blur : 0.5;
|
||||
uniform float u_DevicePixelRatio;
|
||||
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_uv;
|
||||
|
@ -17,7 +20,7 @@ void main() {
|
|||
float fontScale = u_font_size / 24.0;
|
||||
|
||||
lowp float buff = (6.0 - u_strokeWidth / fontScale) / SDF_PX;
|
||||
highp float gamma = (u_halo_blur * 1.19 / SDF_PX + EDGE_GAMMA) / (fontScale * u_gamma_scale);
|
||||
highp float gamma = (u_halo_blur * 1.19 / SDF_PX + EDGE_GAMMA) / (fontScale * u_gamma_scale) / 1.0;
|
||||
|
||||
highp float gamma_scaled = gamma * v_gamma_scale;
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
#define SDF_PX 8.0
|
||||
#define EDGE_GAMMA 0.105
|
||||
attribute vec3 a_Position;
|
||||
attribute vec2 a_tex;
|
||||
attribute vec2 a_offset;
|
||||
attribute vec4 a_color;
|
||||
attribute float a_size;
|
||||
attribute vec2 a_textOffsets;
|
||||
attribute vec4 a_Color;
|
||||
attribute float a_Size;
|
||||
|
||||
uniform vec2 u_sdf_map_size;
|
||||
uniform vec2 u_viewport_size;
|
||||
|
||||
uniform float u_activeId : 0;
|
||||
uniform vec4 u_activeColor : [1.0, 0.0, 0.0, 1.0];
|
||||
uniform mat4 u_ModelMatrix;
|
||||
|
||||
varying vec2 v_uv;
|
||||
varying float v_gamma_scale;
|
||||
|
@ -17,18 +16,18 @@ varying vec4 v_color;
|
|||
#pragma include "projection"
|
||||
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
v_color = a_Color;
|
||||
v_uv = a_tex / u_sdf_map_size;
|
||||
|
||||
// 文本缩放比例
|
||||
float fontScale = a_size / 24.;
|
||||
float fontScale = a_Size / 24.;
|
||||
|
||||
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||
|
||||
vec4 projected_position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0));
|
||||
|
||||
gl_Position = vec4(projected_position.xy / projected_position.w
|
||||
+ a_offset * fontScale / u_viewport_size * 2., 0.0, 1.0);
|
||||
+ a_textOffsets * fontScale / u_ViewportSize * 2., 0.0, 1.0);
|
||||
v_gamma_scale = gl_Position.w;
|
||||
|
||||
|
||||
|
|
|
@ -1,177 +0,0 @@
|
|||
import { AttributeType, gl, IEncodeFeature } from '@antv/l7-core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import { getGlyphQuads, shapeText } from '../utils/symbol-layout';
|
||||
import textFrag from './shaders/text_frag.glsl';
|
||||
import textVert from './shaders/text_vert.glsl';
|
||||
interface IPointTextLayerStyleOptions {
|
||||
opacity: number;
|
||||
textAnchor: string;
|
||||
textOffset: [number, number];
|
||||
spacing: number;
|
||||
padding: [number, number];
|
||||
stroke: string;
|
||||
strokeWidth: number;
|
||||
strokeOpacity: number;
|
||||
fontWeight: string;
|
||||
fontFamily: string;
|
||||
|
||||
textAllowOverlap: boolean;
|
||||
}
|
||||
export function PointTriangulation(feature: IEncodeFeature) {
|
||||
const coordinates = feature.coordinates as number[];
|
||||
return {
|
||||
vertices: [...coordinates, ...coordinates, ...coordinates, ...coordinates],
|
||||
indices: [0, 1, 2, 2, 3, 0],
|
||||
size: coordinates.length,
|
||||
};
|
||||
}
|
||||
export default class TextLayer extends BaseLayer<IPointTextLayerStyleOptions> {
|
||||
public type: string = 'PointLayer';
|
||||
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
const { opacity } = this.getLayerConfig();
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_opacity: opacity || 1.0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected buildModels() {
|
||||
this.registerBuiltinAttributes();
|
||||
this.models = [
|
||||
this.buildLayerModel({
|
||||
moduleName: 'pointText',
|
||||
vertexShader: textVert,
|
||||
fragmentShader: textFrag,
|
||||
triangulation: PointTriangulation,
|
||||
depth: { enable: false },
|
||||
blend: {
|
||||
enable: true,
|
||||
func: {
|
||||
srcRGB: gl.SRC_ALPHA,
|
||||
srcAlpha: 1,
|
||||
dstRGB: gl.ONE_MINUS_SRC_ALPHA,
|
||||
dstAlpha: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private registerBuiltinAttributes() {
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'textOffsets',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_textOffsets',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.STATIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 2,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
const extrude = [-1, -1, 1, -1, 1, 1, -1, 1];
|
||||
const extrudeIndex = (attributeIdx % 4) * 2;
|
||||
return [extrude[extrudeIndex], extrude[extrudeIndex + 1]];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'size',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Size',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
const { size } = feature;
|
||||
return Array.isArray(size) ? [size[0]] : [size as number];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'shape',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Shape',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
const { shape = 2 } = feature;
|
||||
const shape2d = this.getLayerConfig().shape2d as string[];
|
||||
const shapeIndex = shape2d.indexOf(shape as string);
|
||||
return [shapeIndex];
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private initTextFont() {
|
||||
const { fontWeight = 'normal', fontFamily } = this.getLayerConfig();
|
||||
const data = this.getEncodedData();
|
||||
const characterSet: string[] = [];
|
||||
data.forEach((item: IEncodeFeature) => {
|
||||
let { shape = '' } = item;
|
||||
shape = shape.toString();
|
||||
for (const char of shape) {
|
||||
// 去重
|
||||
if (characterSet.indexOf(char) === -1) {
|
||||
characterSet.push(char);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.fontService.setFontOptions({
|
||||
characterSet,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
export interface ICollisionBox {
|
||||
x1: number;
|
||||
y1: number;
|
||||
x2: number;
|
||||
y2: number;
|
||||
anchorPointX: number;
|
||||
anchorPointY: number;
|
||||
}
|
||||
// @mapbox/grid-index 并没有类似 hitTest 的单纯获取碰撞检测结果的方法,query 将导致计算大量多余的包围盒结果,因此使用改良版
|
||||
import { mat4, vec4 } from 'gl-matrix';
|
||||
import GridIndex from './grid-index';
|
||||
|
||||
// 为 viewport 加上 buffer,避免边缘处的文本无法显示
|
||||
const viewportPadding = 100;
|
||||
|
||||
/**
|
||||
* 基于网格实现文本避让,大幅提升包围盒碰撞检测效率
|
||||
* @see https://zhuanlan.zhihu.com/p/74373214
|
||||
*/
|
||||
export default class CollisionIndex {
|
||||
private width: number;
|
||||
private height: number;
|
||||
private grid: GridIndex;
|
||||
private screenRightBoundary: number;
|
||||
private screenBottomBoundary: number;
|
||||
private gridRightBoundary: number;
|
||||
private gridBottomBoundary: number;
|
||||
constructor(width: number, height: number) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
// 创建网格索引
|
||||
this.grid = new GridIndex(
|
||||
width + 2 * viewportPadding,
|
||||
height + 2 * viewportPadding,
|
||||
25,
|
||||
);
|
||||
|
||||
this.screenRightBoundary = width + viewportPadding;
|
||||
this.screenBottomBoundary = height + viewportPadding;
|
||||
this.gridRightBoundary = width + 2 * viewportPadding;
|
||||
this.gridBottomBoundary = height + 2 * viewportPadding;
|
||||
}
|
||||
|
||||
public placeCollisionBox(collisionBox: ICollisionBox, mvpMatrix: mat4) {
|
||||
const projectedPoint = this.project(
|
||||
mvpMatrix,
|
||||
collisionBox.anchorPointX,
|
||||
collisionBox.anchorPointY,
|
||||
);
|
||||
|
||||
const tlX = collisionBox.x1 + projectedPoint.x;
|
||||
const tlY = collisionBox.y1 + projectedPoint.y;
|
||||
const brX = collisionBox.x2 + projectedPoint.x;
|
||||
const brY = collisionBox.y2 + projectedPoint.y;
|
||||
|
||||
if (
|
||||
!this.isInsideGrid(tlX, tlY, brX, brY) ||
|
||||
this.grid.hitTest(tlX, tlY, brX, brY)
|
||||
) {
|
||||
return {
|
||||
box: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
box: [tlX, tlY, brX, brY],
|
||||
};
|
||||
}
|
||||
|
||||
public insertCollisionBox(box: number[], featureIndex: number) {
|
||||
const key = { featureIndex };
|
||||
this.grid.insert(key, box[0], box[1], box[2], box[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 后续碰撞检测都需要投影到 viewport 坐标系
|
||||
* @param {THREE.Matrix4} mvpMatrix mvp矩阵
|
||||
* @param {number} x P20 平面坐标X
|
||||
* @param {number} y P20 平面坐标Y
|
||||
* @return {Point} projectedPoint
|
||||
*/
|
||||
public project(mvpMatrix: mat4, x: number, y: number) {
|
||||
const point = vec4.fromValues(x, y, 0, 1);
|
||||
const out = vec4.create();
|
||||
vec4.transformMat4(out, point, mvpMatrix);
|
||||
// GL 坐标系[-1, 1] -> viewport 坐标系[width, height]
|
||||
return {
|
||||
x: ((out[0] / out[3] + 1) / 2) * this.width + viewportPadding,
|
||||
y: ((-out[1] / out[3] + 1) / 2) * this.height + viewportPadding,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断包围盒是否在整个网格内,需要加上 buffer
|
||||
* @param {number} x1 x1
|
||||
* @param {number} y1 y1
|
||||
* @param {number} x2 x2
|
||||
* @param {number} y2 y2
|
||||
* @return {Point} isInside
|
||||
*/
|
||||
public isInsideGrid(x1: number, y1: number, x2: number, y2: number) {
|
||||
return (
|
||||
x2 >= 0 &&
|
||||
x1 < this.gridRightBoundary &&
|
||||
y2 >= 0 &&
|
||||
y1 < this.gridBottomBoundary
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
interface IQueryArgs {
|
||||
hitTest: boolean;
|
||||
seenUids: { box: any; circle: any };
|
||||
}
|
||||
type CallBack = (...args: any[]) => any;
|
||||
/**
|
||||
* 网格索引,相比 @mapbox/grid-index,在简单计算碰撞检测结果时效率更高
|
||||
* @see https://zhuanlan.zhihu.com/p/74373214
|
||||
*/
|
||||
class GridIndex {
|
||||
private boxCells: number[][];
|
||||
private xCellCount: number;
|
||||
private yCellCount: number;
|
||||
private boxKeys: string[];
|
||||
private bboxes: number[];
|
||||
private width: number;
|
||||
private height: number;
|
||||
private xScale: number;
|
||||
private yScale: number;
|
||||
private boxUid: number;
|
||||
|
||||
constructor(width: number, height: number, cellSize: number) {
|
||||
const boxCells = this.boxCells;
|
||||
|
||||
this.xCellCount = Math.ceil(width / cellSize);
|
||||
this.yCellCount = Math.ceil(height / cellSize);
|
||||
|
||||
for (let i = 0; i < this.xCellCount * this.yCellCount; i++) {
|
||||
boxCells.push([]);
|
||||
}
|
||||
this.boxKeys = [];
|
||||
this.bboxes = [];
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.xScale = this.xCellCount / width;
|
||||
this.yScale = this.yCellCount / height;
|
||||
this.boxUid = 0;
|
||||
}
|
||||
|
||||
public insert(key: any, x1: number, y1: number, x2: number, y2: number) {
|
||||
this.forEachCell(x1, y1, x2, y2, this.insertBoxCell, this.boxUid++);
|
||||
this.boxKeys.push(key);
|
||||
this.bboxes.push(x1);
|
||||
this.bboxes.push(y1);
|
||||
this.bboxes.push(x2);
|
||||
this.bboxes.push(y2);
|
||||
}
|
||||
|
||||
public query(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
predicate?: CallBack,
|
||||
) {
|
||||
return this.queryHitTest(x1, y1, x2, y2, false, predicate);
|
||||
}
|
||||
|
||||
public hitTest(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
predicate?: CallBack,
|
||||
) {
|
||||
return this.queryHitTest(x1, y1, x2, y2, true, predicate);
|
||||
}
|
||||
|
||||
private insertBoxCell(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
cellIndex: number,
|
||||
uid: number,
|
||||
) {
|
||||
this.boxCells[cellIndex].push(uid);
|
||||
}
|
||||
|
||||
private queryHitTest(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
hitTest: boolean,
|
||||
predicate?: CallBack,
|
||||
) {
|
||||
if (x2 < 0 || x1 > this.width || y2 < 0 || y1 > this.height) {
|
||||
return hitTest ? false : [];
|
||||
}
|
||||
const result: any[] = [];
|
||||
if (x1 <= 0 && y1 <= 0 && this.width <= x2 && this.height <= y2) {
|
||||
// 这一步是高效的关键,后续精确碰撞检测结果在计算文本可见性时并不需要
|
||||
if (hitTest) {
|
||||
return true;
|
||||
}
|
||||
for (let boxUid = 0; boxUid < this.boxKeys.length; boxUid++) {
|
||||
result.push({
|
||||
key: this.boxKeys[boxUid],
|
||||
x1: this.bboxes[boxUid * 4],
|
||||
y1: this.bboxes[boxUid * 4 + 1],
|
||||
x2: this.bboxes[boxUid * 4 + 2],
|
||||
y2: this.bboxes[boxUid * 4 + 3],
|
||||
});
|
||||
}
|
||||
return predicate ? result.filter(predicate) : result;
|
||||
}
|
||||
|
||||
const queryArgs = {
|
||||
hitTest,
|
||||
seenUids: { box: {}, circle: {} },
|
||||
};
|
||||
this.forEachCell(
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
this.queryCell,
|
||||
result,
|
||||
queryArgs,
|
||||
predicate,
|
||||
);
|
||||
return hitTest ? result.length > 0 : result;
|
||||
}
|
||||
|
||||
private queryCell(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
cellIndex: number,
|
||||
result: any[],
|
||||
queryArgs?: any,
|
||||
predicate?: CallBack,
|
||||
) {
|
||||
const seenUids = queryArgs.seenUids;
|
||||
const boxCell = this.boxCells[cellIndex];
|
||||
if (boxCell !== null) {
|
||||
const bboxes = this.bboxes;
|
||||
for (const boxUid of boxCell) {
|
||||
if (!seenUids.box[boxUid]) {
|
||||
seenUids.box[boxUid] = true;
|
||||
const offset = boxUid * 4;
|
||||
if (
|
||||
x1 <= bboxes[offset + 2] &&
|
||||
y1 <= bboxes[offset + 3] &&
|
||||
x2 >= bboxes[offset + 0] &&
|
||||
y2 >= bboxes[offset + 1] &&
|
||||
(!predicate || predicate(this.boxKeys[boxUid]))
|
||||
) {
|
||||
if (queryArgs.hitTest) {
|
||||
result.push(true);
|
||||
return true;
|
||||
}
|
||||
result.push({
|
||||
key: this.boxKeys[boxUid],
|
||||
x1: bboxes[offset],
|
||||
y1: bboxes[offset + 1],
|
||||
x2: bboxes[offset + 2],
|
||||
y2: bboxes[offset + 3],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private forEachCell(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
fn: CallBack,
|
||||
arg1: any[] | number,
|
||||
arg2?: IQueryArgs,
|
||||
predicate?: CallBack,
|
||||
) {
|
||||
const cx1 = this.convertToXCellCoord(x1);
|
||||
const cy1 = this.convertToYCellCoord(y1);
|
||||
const cx2 = this.convertToXCellCoord(x2);
|
||||
const cy2 = this.convertToYCellCoord(y2);
|
||||
|
||||
for (let x = cx1; x <= cx2; x++) {
|
||||
for (let y = cy1; y <= cy2; y++) {
|
||||
const cellIndex = this.xCellCount * y + x;
|
||||
if (fn.call(this, x1, y1, x2, y2, cellIndex, arg1, arg2, predicate)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private convertToXCellCoord(x: number) {
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(this.xCellCount - 1, Math.floor(x * this.xScale)),
|
||||
);
|
||||
}
|
||||
|
||||
private convertToYCellCoord(y: number) {
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(this.yCellCount - 1, Math.floor(y * this.yScale)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default GridIndex;
|
|
@ -1,3 +1,22 @@
|
|||
interface IPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
export interface IGlyphQuad {
|
||||
tr: IPoint;
|
||||
tl: IPoint;
|
||||
bl: IPoint;
|
||||
br: IPoint;
|
||||
tex: {
|
||||
x: number;
|
||||
y: number;
|
||||
height: number;
|
||||
width: number;
|
||||
advance: number;
|
||||
};
|
||||
glyphOffset: [number, number];
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文本相对锚点位置
|
||||
* @param {string} anchor 锚点位置
|
||||
|
@ -181,7 +200,7 @@ export function shapeText(
|
|||
textAnchor: string,
|
||||
textJustify: string,
|
||||
spacing: number,
|
||||
translate: [number, number],
|
||||
translate: [number, number] = [0, 0],
|
||||
) {
|
||||
// TODO:处理换行
|
||||
const lines = text.split('\n');
|
||||
|
@ -215,11 +234,11 @@ export function shapeText(
|
|||
|
||||
export function getGlyphQuads(
|
||||
shaping: any,
|
||||
textOffset: [number, number],
|
||||
textOffset: [number, number] = [0, 0],
|
||||
alongLine: boolean,
|
||||
) {
|
||||
): IGlyphQuad[] {
|
||||
const { positionedGlyphs } = shaping;
|
||||
const quads = [];
|
||||
const quads: IGlyphQuad[] = [];
|
||||
|
||||
for (const positionedGlyph of positionedGlyphs) {
|
||||
const rect = positionedGlyph.metrics;
|
||||
|
@ -229,7 +248,7 @@ export function getGlyphQuads(
|
|||
|
||||
const halfAdvance = (rect.advance * positionedGlyph.scale) / 2;
|
||||
|
||||
const glyphOffset = alongLine
|
||||
const glyphOffset: [number, number] = alongLine
|
||||
? [positionedGlyph.x + halfAdvance, positionedGlyph.y]
|
||||
: [0, 0];
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import PointImage from './components/PointImage';
|
|||
import Polygon3D from './components/Polygon3D';
|
||||
import ImageLayerDemo from './components/RasterImage';
|
||||
import RasterLayerDemo from './components/RasterLayer';
|
||||
import TextLayerDemo from './components/Text';
|
||||
|
||||
// @ts-ignore
|
||||
storiesOf('图层', module)
|
||||
|
@ -22,6 +23,7 @@ storiesOf('图层', module)
|
|||
.add('数据更新', () => <DataUpdate />)
|
||||
.add('亮度图', () => <LightDemo />)
|
||||
.add('3D点', () => <Point3D />)
|
||||
.add('文字', () => <TextLayerDemo />)
|
||||
.add('Column', () => <Column />)
|
||||
.add('图片标注', () => <PointImage />)
|
||||
.add('面3d图层', () => <Polygon3D />)
|
||||
|
|
|
@ -12,40 +12,30 @@ export default class LineDemo extends React.Component {
|
|||
|
||||
public async componentDidMount() {
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json',
|
||||
'https://arcgis.github.io/arcgis-samples-javascript/sample-data/custom-gl-animated-lines/lines.json',
|
||||
);
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new Mapbox({
|
||||
center: [102.602992, 23.107329],
|
||||
pitch: 0,
|
||||
style: 'mapbox://styles/mapbox/dark-v9',
|
||||
zoom: 13,
|
||||
center: [-74.006, 40.7128],
|
||||
zoom: 15,
|
||||
style: 'dark',
|
||||
}),
|
||||
});
|
||||
const lineLayer = new LineLayer()
|
||||
.source(await response.json())
|
||||
.size(1)
|
||||
.source(await response.json(), {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'path',
|
||||
},
|
||||
})
|
||||
.size(3)
|
||||
.shape('line')
|
||||
.color(
|
||||
'ELEV',
|
||||
[
|
||||
'#E8FCFF',
|
||||
'#CFF6FF',
|
||||
'#A1E9ff',
|
||||
'#65CEF7',
|
||||
'#3CB1F0',
|
||||
'#2894E0',
|
||||
'#1772c2',
|
||||
'#105CB3',
|
||||
'#0D408C',
|
||||
'#002466',
|
||||
].reverse(),
|
||||
)
|
||||
.color('red')
|
||||
.animate({
|
||||
interval: 0.1,
|
||||
trailLength: 0.05,
|
||||
duration: 2,
|
||||
interval: 0.5,
|
||||
trailLength: 0.2,
|
||||
duration: 4,
|
||||
})
|
||||
.style({
|
||||
lineType: 'solid',
|
||||
|
|
|
@ -36,6 +36,7 @@ export default class Point3D extends React.Component {
|
|||
type: 'quantile',
|
||||
})
|
||||
.size('point_count', [5, 10, 15, 20, 25])
|
||||
.animate(false)
|
||||
.color('yellow')
|
||||
.style({
|
||||
opacity: 0.5,
|
||||
|
|
|
@ -1,61 +1,82 @@
|
|||
import { PointLayer, Scene } from '@antv/l7';
|
||||
import { GaodeMap, Mapbox } from '@antv/l7-maps';
|
||||
import * as React from 'react';
|
||||
// @ts-ignore
|
||||
import data from '../data/data.json';
|
||||
export default class Point3D extends React.Component {
|
||||
export default class TextLayerDemo extends React.Component {
|
||||
// @ts-ignore
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const scene = new Scene({
|
||||
center: [120.19382669582967, 30.258134],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'mapbox',
|
||||
style: 'mapbox://styles/mapbox/streets-v9',
|
||||
zoom: 1,
|
||||
});
|
||||
const pointLayer = new PointLayer({});
|
||||
const p1 = {
|
||||
public async componentDidMount() {
|
||||
const data = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
properties: {
|
||||
name: '中华人民共和国',
|
||||
},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [83.671875, 44.84029065139799],
|
||||
coordinates: [103.0078125, 36.03133177633187],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
name: '中华人民共和国',
|
||||
},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [122.6953125, 10.833305983642491],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
pointLayer
|
||||
.source(data)
|
||||
.color('name', [
|
||||
'#FFF5B8',
|
||||
'#FFDC7D',
|
||||
'#FFAB5C',
|
||||
'#F27049',
|
||||
'#D42F31',
|
||||
'#730D1C',
|
||||
])
|
||||
.shape('subregion', [
|
||||
'circle',
|
||||
'triangle',
|
||||
'square',
|
||||
'pentagon',
|
||||
'hexagon',
|
||||
'octogon',
|
||||
'hexagram',
|
||||
'rhombus',
|
||||
'vesica',
|
||||
])
|
||||
.size('scalerank', [2, 4, 6, 8, 10]);
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/rmsportal/oVTMqfzuuRFKiDwhPSFL.json',
|
||||
);
|
||||
const pointsData = await response.json();
|
||||
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [120.19382669582967, 30.258134],
|
||||
pitch: 0,
|
||||
style: 'dark',
|
||||
zoom: 3,
|
||||
}),
|
||||
});
|
||||
// scene.on('loaded', () => {
|
||||
const pointLayer = new PointLayer({})
|
||||
.source(pointsData.list, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'j',
|
||||
y: 'w',
|
||||
},
|
||||
})
|
||||
.shape('m', 'text')
|
||||
.size(24)
|
||||
.color('#fff')
|
||||
.style({
|
||||
fontWeight: 800,
|
||||
textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
|
||||
textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
|
||||
spacing: 2, // 字符间距
|
||||
padding: [4, 4], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
|
||||
strokeColor: 'white', // 描边颜色
|
||||
strokeWidth: 4, // 描边宽度
|
||||
strokeOpacity: 1.0,
|
||||
});
|
||||
scene.addLayer(pointLayer);
|
||||
scene.render();
|
||||
|
||||
this.scene = scene;
|
||||
// });
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
Loading…
Reference in New Issue