mirror of https://gitee.com/antv-l7/antv-l7
feat: 矢量文本计算优化、性能优化 (#1310)
* feat: 新增测试瓦片图层 * style: lint style * feat: 矢量文本图层性能优化 * chore: change tiletestlayer demo * style: lint style * feat: 封装 TileDebugLayer 的source 模块,优化图层默认数据的配置,测试图层样式调整 * style: line style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com>
This commit is contained in:
parent
7837021908
commit
f345fba72c
|
@ -0,0 +1,2 @@
|
|||
### Test Tile
|
||||
<code src="./testTile.tsx"></code>
|
|
@ -0,0 +1,33 @@
|
|||
import { Scene, TileDebugLayer } from '@antv/l7';
|
||||
import { Mapbox } from '@antv/l7-maps';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
export default () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
// stencil: true,
|
||||
map: new Mapbox({
|
||||
center: [121.268, 30.3628],
|
||||
pitch: 0,
|
||||
// style: 'blank',
|
||||
zoom: 4,
|
||||
}),
|
||||
});
|
||||
|
||||
const layer = new TileDebugLayer();
|
||||
|
||||
scene.on('loaded', () => {
|
||||
scene.addLayer(layer);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -265,6 +265,10 @@ export interface ILayer {
|
|||
masks: ILayer[]; // 图层的 mask 列表
|
||||
sceneContainer: Container | undefined;
|
||||
dataState: IDataState; // 数据流状态
|
||||
defaultSourceConfig: {
|
||||
data: any[],
|
||||
options: ISourceCFG | undefined,
|
||||
},
|
||||
pickedFeatureID: number | null;
|
||||
hooks: {
|
||||
init: SyncBailHook;
|
||||
|
|
|
@ -81,6 +81,11 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
|
|||
public layerType?: string | undefined;
|
||||
public triangulation?: Triangulation | undefined;
|
||||
|
||||
public defaultSourceConfig: {
|
||||
data: any[];
|
||||
options: ISourceCFG | undefined;
|
||||
};
|
||||
|
||||
public dataState: IDataState = {
|
||||
dataSourceNeedUpdate: false,
|
||||
dataMappingNeedUpdate: false,
|
||||
|
|
|
@ -16,6 +16,8 @@ import EarthLayer from './earth';
|
|||
import MaskLayer from './mask';
|
||||
import WindLayer from './wind';
|
||||
|
||||
import TileDebugLayer from './tile/tileTest';
|
||||
|
||||
// import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin';
|
||||
import DataMappingPlugin from './plugins/DataMappingPlugin';
|
||||
import DataSourcePlugin from './plugins/DataSourcePlugin';
|
||||
|
@ -151,6 +153,7 @@ export {
|
|||
EarthLayer,
|
||||
WindLayer,
|
||||
MaskLayer,
|
||||
TileDebugLayer
|
||||
};
|
||||
|
||||
export * from './core/interface';
|
||||
|
|
|
@ -5,6 +5,25 @@ import { isVectorTile } from '../tile/utils';
|
|||
|
||||
export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
|
||||
public type: string = 'LineLayer';
|
||||
public defaultSourceConfig = {
|
||||
data: [
|
||||
{
|
||||
lng1: 100,
|
||||
lat1: 30.0,
|
||||
lng2: 130,
|
||||
lat2: 30,
|
||||
},
|
||||
],
|
||||
options: {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng1',
|
||||
y: 'lat1',
|
||||
x1: 'lng2',
|
||||
y1: 'lat2',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
public buildModels() {
|
||||
const shape = this.getModelType();
|
||||
|
|
|
@ -4,6 +4,14 @@ import MaskModels, { MaskModelType } from './models';
|
|||
|
||||
export default class MaskLayer extends BaseLayer<IMaskLayerStyleOptions> {
|
||||
public type: string = 'MaskLayer';
|
||||
public defaultSourceConfig: {
|
||||
data: [];
|
||||
options: {
|
||||
parser: {
|
||||
type: 'geojson';
|
||||
};
|
||||
};
|
||||
};
|
||||
public buildModels() {
|
||||
const shape = this.getModelType();
|
||||
this.layerModel = new MaskModels[shape](this);
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
|
||||
import Source, {
|
||||
DEFAULT_DATA,
|
||||
DEFAULT_PARSER,
|
||||
DEFAULT_SOURCE,
|
||||
} from '@antv/l7-source';
|
||||
import Source from '@antv/l7-source';
|
||||
import { injectable } from 'inversify';
|
||||
import 'reflect-metadata';
|
||||
|
||||
|
@ -15,14 +11,9 @@ export default class DataSourcePlugin implements ILayerPlugin {
|
|||
layer.hooks.init.tap('DataSourcePlugin', () => {
|
||||
let source = layer.getSource();
|
||||
if (!source) {
|
||||
// TODO: 允许用户不使用 layer 的 source 方法,在这里传入一个默认的替换的默认数据
|
||||
const defaultSourceConfig = DEFAULT_SOURCE[
|
||||
layer.type as 'PointLayer' | 'LineLayer'
|
||||
] || {
|
||||
data: DEFAULT_DATA,
|
||||
options: DEFAULT_PARSER,
|
||||
};
|
||||
const { data, options } = layer.sourceOption || defaultSourceConfig;
|
||||
// Tip: 用户没有传入 source 的时候使用图层的默认数据
|
||||
const { data, options } =
|
||||
layer.sourceOption || layer.defaultSourceConfig;
|
||||
source = new Source(data, options);
|
||||
layer.setSource(source);
|
||||
}
|
||||
|
@ -31,7 +22,6 @@ export default class DataSourcePlugin implements ILayerPlugin {
|
|||
} else {
|
||||
source.once('sourceUpdate', () => {
|
||||
this.updateClusterData(layer);
|
||||
// TODO: layer.hooks.init.call();
|
||||
});
|
||||
}
|
||||
// this.updateClusterData(layer);
|
||||
|
|
|
@ -6,6 +6,17 @@ import { isVectorTile } from '../tile/utils';
|
|||
|
||||
export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
|
||||
public type: string = 'PointLayer';
|
||||
public defaultSourceConfig = {
|
||||
data: [],
|
||||
options: {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
public buildModels() {
|
||||
const modelType = this.getModelType();
|
||||
this.layerModel = new PointModels[modelType](this);
|
||||
|
@ -26,7 +37,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
|
|||
public getModelTypeWillEmptyData(): PointType {
|
||||
if (this.shapeOption) {
|
||||
const { field, values } = this.shapeOption;
|
||||
const { shape2d, shape3d } = this.getLayerConfig();
|
||||
const { shape2d } = this.getLayerConfig();
|
||||
|
||||
const iconMap = this.iconService.getIconMap();
|
||||
|
||||
|
@ -72,6 +83,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
|
|||
},
|
||||
vectorpoint: {},
|
||||
tile: {},
|
||||
tileText: {},
|
||||
earthFill: {},
|
||||
earthExtrude: {},
|
||||
};
|
||||
|
@ -79,20 +91,6 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
|
|||
}
|
||||
|
||||
protected getModelType(): PointType {
|
||||
const PointTypes = [
|
||||
'fillImage',
|
||||
'fill',
|
||||
'radar',
|
||||
'image',
|
||||
'normal',
|
||||
'simplePoint',
|
||||
'extrude',
|
||||
'text',
|
||||
'vectorpoint',
|
||||
'tile',
|
||||
'earthFill',
|
||||
'earthExtrude',
|
||||
];
|
||||
const parserType = this.layerSource.getParserType();
|
||||
if (isVectorTile(parserType)) {
|
||||
return 'vectorpoint';
|
||||
|
|
|
@ -10,6 +10,7 @@ import NormalModel from './normal';
|
|||
import Radar from './radar';
|
||||
import SimplePopint from './simplePoint';
|
||||
import TextModel from './text';
|
||||
import TileTextModel from './tileText';
|
||||
import TileFillModel from './tile';
|
||||
|
||||
export type PointType =
|
||||
|
@ -23,6 +24,7 @@ export type PointType =
|
|||
| 'text'
|
||||
| 'vectorpoint'
|
||||
| 'tile'
|
||||
| 'tileText'
|
||||
| 'earthFill'
|
||||
| 'earthExtrude';
|
||||
|
||||
|
@ -37,6 +39,7 @@ const PointModels: { [key in PointType]: any } = {
|
|||
text: TextModel,
|
||||
vectorpoint: PointTileModel,
|
||||
tile: TileFillModel,
|
||||
tileText: TileTextModel,
|
||||
earthFill: EarthFillModel,
|
||||
earthExtrude: EarthExtrudeModel,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,530 @@
|
|||
import {
|
||||
AttributeType,
|
||||
gl,
|
||||
IEncodeFeature,
|
||||
IModel,
|
||||
IModelUniform,
|
||||
ITexture2D,
|
||||
} from '@antv/l7-core';
|
||||
import {
|
||||
calculateCentroid,
|
||||
getMask,
|
||||
padBounds,
|
||||
} from '@antv/l7-utils';
|
||||
import { isNumber } from 'lodash';
|
||||
import BaseModel from '../../core/BaseModel';
|
||||
import { IPointLayerStyleOptions } from '../../core/interface';
|
||||
import CollisionIndex from '../../utils/collision-index';
|
||||
import {
|
||||
getGlyphQuads,
|
||||
IGlyphQuad,
|
||||
shapeText,
|
||||
} from '../../utils/symbol-layout';
|
||||
import textFrag from '../shaders/text_frag.glsl';
|
||||
import textVert from '../shaders/text_vert.glsl';
|
||||
|
||||
export function TextTriangulation(feature: IEncodeFeature) {
|
||||
// @ts-ignore
|
||||
const that = this as TextModel;
|
||||
const id = feature.id as number;
|
||||
const vertices: number[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
if (!that.glyphInfoMap || !that.glyphInfoMap[id]) {
|
||||
return {
|
||||
vertices: [], // [ x, y, z, tex.x,tex.y, offset.x. offset.y]
|
||||
indices: [],
|
||||
size: 7,
|
||||
};
|
||||
}
|
||||
const centroid = that.glyphInfoMap[id].centroid as number[]; // 计算中心点
|
||||
const coord =
|
||||
centroid.length === 2 ? [centroid[0], centroid[1], 0] : centroid;
|
||||
that.glyphInfoMap[id].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 TextModel extends BaseModel {
|
||||
public glyphInfo: IEncodeFeature[];
|
||||
public glyphInfoMap: {
|
||||
[key: string]: {
|
||||
shaping: any;
|
||||
glyphQuads: IGlyphQuad[];
|
||||
centroid: number[];
|
||||
};
|
||||
} = {};
|
||||
private texture: ITexture2D;
|
||||
private currentZoom: number = -1;
|
||||
private extent: [[number, number], [number, number]];
|
||||
private textureHeight: number = 0;
|
||||
private textCount: number = 0;
|
||||
private preTextStyle: Partial<IPointLayerStyleOptions> = {};
|
||||
public getUninforms(): IModelUniform {
|
||||
const {
|
||||
opacity = 1.0,
|
||||
stroke = '#fff',
|
||||
strokeWidth = 0,
|
||||
textAnchor = 'center',
|
||||
textAllowOverlap = false,
|
||||
halo = 0.5,
|
||||
gamma = 2.0,
|
||||
raisingHeight = 0,
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
const { canvas, mapping } = this.fontService;
|
||||
if (Object.keys(mapping).length !== this.textCount) {
|
||||
this.updateTexture();
|
||||
this.textCount = Object.keys(mapping).length;
|
||||
}
|
||||
this.preTextStyle = {
|
||||
textAnchor,
|
||||
textAllowOverlap,
|
||||
};
|
||||
|
||||
if (
|
||||
this.dataTextureTest &&
|
||||
this.dataTextureNeedUpdate({
|
||||
opacity,
|
||||
strokeWidth,
|
||||
stroke,
|
||||
})
|
||||
) {
|
||||
this.judgeStyleAttributes({
|
||||
opacity,
|
||||
strokeWidth,
|
||||
stroke,
|
||||
});
|
||||
|
||||
const encodeData = this.layer.getEncodedData();
|
||||
const { data, width, height } = this.calDataFrame(
|
||||
this.cellLength,
|
||||
encodeData,
|
||||
this.cellProperties,
|
||||
);
|
||||
this.rowCount = height; // 当前数据纹理有多少行
|
||||
|
||||
this.dataTexture =
|
||||
this.cellLength > 0 && data.length > 0
|
||||
? this.createTexture2D({
|
||||
flipY: true,
|
||||
data,
|
||||
format: gl.LUMINANCE,
|
||||
type: gl.FLOAT,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
: this.createTexture2D({
|
||||
flipY: true,
|
||||
data: [1],
|
||||
format: gl.LUMINANCE,
|
||||
type: gl.FLOAT,
|
||||
width: 1,
|
||||
height: 1,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1]
|
||||
u_cellTypeLayout: this.getCellTypeLayout(),
|
||||
u_raisingHeight: Number(raisingHeight),
|
||||
|
||||
u_opacity: isNumber(opacity) ? opacity : 1.0,
|
||||
u_stroke_width: isNumber(strokeWidth) ? strokeWidth : 1.0,
|
||||
u_stroke_color: this.getStrokeColor(stroke),
|
||||
|
||||
u_sdf_map: this.texture,
|
||||
u_halo_blur: halo,
|
||||
u_gamma_scale: gamma,
|
||||
u_sdf_map_size: [canvas.width, canvas.height],
|
||||
};
|
||||
}
|
||||
|
||||
public initModels(callbackModel: (models: IModel[]) => void) {
|
||||
this.layer.on('remapping', this.mapping);
|
||||
this.extent = this.textExtent();
|
||||
const {
|
||||
textAnchor = 'center',
|
||||
textAllowOverlap = true,
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
this.preTextStyle = {
|
||||
textAnchor,
|
||||
textAllowOverlap,
|
||||
};
|
||||
this.buildModels(callbackModel);
|
||||
}
|
||||
|
||||
public buildModels = async (callbackModel: (models: IModel[]) => void) => {
|
||||
const {
|
||||
mask = false,
|
||||
maskInside = true,
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
this.mapping();
|
||||
|
||||
this.layer
|
||||
.buildLayerModel({
|
||||
moduleName: 'pointText',
|
||||
vertexShader: textVert,
|
||||
fragmentShader: textFrag,
|
||||
triangulation: TextTriangulation.bind(this),
|
||||
depth: { enable: false },
|
||||
blend: this.getBlend(),
|
||||
stencil: getMask(mask, maskInside),
|
||||
})
|
||||
.then((model) => {
|
||||
callbackModel([model]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(err);
|
||||
callbackModel([]);
|
||||
});
|
||||
};
|
||||
|
||||
public clearModels() {
|
||||
this.texture?.destroy();
|
||||
this.dataTexture?.destroy();
|
||||
this.layer.off('remapping', this.mapping);
|
||||
}
|
||||
protected registerBuiltinAttributes() {
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'rotate',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Rotate',
|
||||
buffer: {
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
const { rotate = 0 } = feature;
|
||||
return Array.isArray(rotate) ? [rotate[0]] : [rotate as number];
|
||||
},
|
||||
},
|
||||
});
|
||||
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 = 12 } = 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 mapping = () => {
|
||||
this.initGlyph();
|
||||
this.updateTexture();
|
||||
this.filterGlyphs();
|
||||
this.reBuildModel();
|
||||
};
|
||||
private textExtent(): [[number, number], [number, number]] {
|
||||
const bounds = this.mapService.getBounds();
|
||||
return padBounds(bounds, 0.5);
|
||||
}
|
||||
/**
|
||||
* 生成文字纹理(生成文字纹理字典)
|
||||
*/
|
||||
private initTextFont() {
|
||||
const {
|
||||
fontWeight = '400',
|
||||
fontFamily = 'sans-serif',
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
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,
|
||||
iconfont: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 iconfont 纹理字典
|
||||
*/
|
||||
private initIconFontTex() {
|
||||
const {
|
||||
fontWeight = '400',
|
||||
fontFamily = 'sans-serif',
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
const data = this.layer.getEncodedData();
|
||||
const characterSet: string[] = [];
|
||||
data.forEach((item: IEncodeFeature) => {
|
||||
let { shape = '' } = item;
|
||||
shape = `${shape}`;
|
||||
if (characterSet.indexOf(shape) === -1) {
|
||||
characterSet.push(shape);
|
||||
}
|
||||
});
|
||||
this.fontService.setFontOptions({
|
||||
characterSet,
|
||||
fontWeight,
|
||||
fontFamily,
|
||||
iconfont: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文字布局(对照文字纹理字典提取对应文字的位置很好信息)
|
||||
*/
|
||||
private generateGlyphLayout(iconfont: boolean) {
|
||||
// TODO:更新文字布局
|
||||
const { mapping } = this.fontService;
|
||||
const {
|
||||
spacing = 2,
|
||||
textAnchor = 'center',
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
const data = this.layer.getEncodedData();
|
||||
|
||||
this.glyphInfo = data.map((feature: IEncodeFeature) => {
|
||||
const { shape = '', id, size = 1, textOffset = [0, 0] } = feature;
|
||||
|
||||
const shaping = shapeText(
|
||||
shape.toString(),
|
||||
mapping,
|
||||
// @ts-ignore
|
||||
size,
|
||||
textAnchor,
|
||||
'left',
|
||||
spacing,
|
||||
textOffset,
|
||||
iconfont,
|
||||
);
|
||||
const glyphQuads = getGlyphQuads(shaping, textOffset, false);
|
||||
feature.shaping = shaping;
|
||||
feature.glyphQuads = glyphQuads;
|
||||
|
||||
feature.centroid = calculateCentroid(feature.coordinates);
|
||||
|
||||
// 此时地图高德2.0 originCentroid == centroid
|
||||
feature.originCentroid =
|
||||
feature.version === 'GAODE2.x'
|
||||
? calculateCentroid(feature.originCoordinates)
|
||||
: (feature.originCentroid = feature.centroid);
|
||||
|
||||
this.glyphInfoMap[id as number] = {
|
||||
shaping,
|
||||
glyphQuads,
|
||||
centroid: calculateCentroid(feature.coordinates),
|
||||
};
|
||||
return feature;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 文字避让 depend on originCentorid
|
||||
*/
|
||||
private filterGlyphs() {
|
||||
const {
|
||||
padding = [4, 4],
|
||||
textAllowOverlap = false,
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
if (textAllowOverlap) {
|
||||
// 如果允许文本覆盖
|
||||
return;
|
||||
}
|
||||
this.glyphInfoMap = {};
|
||||
this.currentZoom = this.mapService.getZoom();
|
||||
this.extent = this.textExtent();
|
||||
const { width, height } = this.rendererService.getViewportSize();
|
||||
const collisionIndex = new CollisionIndex(width, height);
|
||||
const filterData = this.glyphInfo.filter((feature: IEncodeFeature) => {
|
||||
const { shaping, id = 0 } = feature;
|
||||
const centroid = (feature.version === 'GAODE2.x'
|
||||
? feature.originCentroid
|
||||
: feature.centroid) as [number, number];
|
||||
const size = feature.size as number;
|
||||
const fontScale: number = size / 24;
|
||||
const pixels = this.mapService.lngLatToContainer(centroid);
|
||||
const { box } = collisionIndex.placeCollisionBox({
|
||||
x1: shaping.left * fontScale - padding[0],
|
||||
x2: shaping.right * fontScale + padding[0],
|
||||
y1: shaping.top * fontScale - padding[1],
|
||||
y2: shaping.bottom * fontScale + padding[1],
|
||||
anchorPointX: pixels.x,
|
||||
anchorPointY: pixels.y,
|
||||
});
|
||||
if (box && box.length) {
|
||||
// TODO:featureIndex
|
||||
collisionIndex.insertCollisionBox(box, id);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
filterData.forEach((item) => {
|
||||
// @ts-ignore
|
||||
this.glyphInfoMap[item.id as number] = item;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 初始化文字布局
|
||||
*/
|
||||
private initGlyph() {
|
||||
const { iconfont = false } = this.layer.getLayerConfig();
|
||||
// 1.生成文字纹理(或是生成 iconfont)
|
||||
iconfont ? this.initIconFontTex() : this.initTextFont();
|
||||
|
||||
// 2.生成文字布局
|
||||
this.generateGlyphLayout(iconfont);
|
||||
}
|
||||
/**
|
||||
* 更新文字纹理
|
||||
*/
|
||||
private updateTexture() {
|
||||
const { createTexture2D } = this.rendererService;
|
||||
const { canvas } = this.fontService;
|
||||
this.textureHeight = canvas.height;
|
||||
if (this.texture) {
|
||||
this.texture.destroy();
|
||||
}
|
||||
|
||||
this.texture = createTexture2D({
|
||||
data: canvas,
|
||||
mag: gl.LINEAR,
|
||||
min: gl.LINEAR,
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
});
|
||||
}
|
||||
|
||||
private reBuildModel() {
|
||||
const {
|
||||
mask = false,
|
||||
maskInside = true,
|
||||
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
|
||||
this.filterGlyphs();
|
||||
this.layer
|
||||
.buildLayerModel({
|
||||
moduleName: 'pointTileText',
|
||||
vertexShader: textVert,
|
||||
fragmentShader: textFrag,
|
||||
triangulation: TextTriangulation.bind(this),
|
||||
depth: { enable: false },
|
||||
blend: this.getBlend(),
|
||||
stencil: getMask(mask, maskInside),
|
||||
})
|
||||
.then((model) => {
|
||||
this.layer.models = [model];
|
||||
this.layer.renderLayers();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn(err);
|
||||
this.layer.models = [];
|
||||
});
|
||||
}
|
||||
}
|
|
@ -6,6 +6,14 @@ import { isVectorTile } from '../tile/utils';
|
|||
|
||||
export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
|
||||
public type: string = 'PolygonLayer';
|
||||
public defaultSourceConfig: {
|
||||
data: [];
|
||||
options: {
|
||||
parser: {
|
||||
type: 'geojson';
|
||||
};
|
||||
};
|
||||
};
|
||||
public buildModels() {
|
||||
const shape = this.getModelType();
|
||||
this.layerModel = new PolygonModels[shape](this);
|
||||
|
|
|
@ -184,13 +184,14 @@ export class TileLayerManager implements ITileLayerManager {
|
|||
this.rampColorsData = generateColorRamp(rampColors as IColorRamp);
|
||||
}
|
||||
|
||||
|
||||
this.initOptions = {
|
||||
layerType: this.parent.type,
|
||||
transforms: this.transforms,
|
||||
shape: layerShape,
|
||||
zIndex,
|
||||
opacity,
|
||||
sourceLayer: parentParserType === 'geojsonvt' ? 'geojsonvt' : sourceLayer,
|
||||
sourceLayer: this.getSourceLayer(parentParserType, sourceLayer),
|
||||
coords,
|
||||
featureId,
|
||||
color: colorValue,
|
||||
|
@ -216,6 +217,16 @@ export class TileLayerManager implements ITileLayerManager {
|
|||
};
|
||||
}
|
||||
|
||||
private getSourceLayer(parentParserType: string, sourceLayer: string|undefined) {
|
||||
if(parentParserType === 'geojsonvt') {
|
||||
return 'geojsonvt';
|
||||
} else if(parentParserType === 'testTile') {
|
||||
return 'testTile';
|
||||
} else {
|
||||
return sourceLayer;
|
||||
}
|
||||
}
|
||||
|
||||
private setConfigListener() {
|
||||
// RasterLayer PolygonLayer LineLayer PointLayer
|
||||
// All Tile Layer Need Listen
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IModelUniform } from '@antv/l7-core';
|
||||
import BaseModel from '../../core/BaseModel';
|
||||
import { TMSTileLayer } from '../tmsTileLayer';
|
||||
export default class RasterTileModel extends BaseModel {
|
||||
export default class TileModel extends BaseModel {
|
||||
public getUninforms(): IModelUniform {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ import VectorPointLayer from './point';
|
|||
import VectorPolygonTile from './polygon';
|
||||
import RasterTileFactory from './raster';
|
||||
import RasterDataFactory from './rasterData';
|
||||
import TestTile from './test';
|
||||
|
||||
export type TileType =
|
||||
| 'PolygonLayer'
|
||||
| 'PointLayer'
|
||||
| 'LineLayer'
|
||||
| 'RasterLayer';
|
||||
| 'RasterLayer'
|
||||
| 'TileDebugLayer';
|
||||
|
||||
export function getTileFactory(tileType: TileType, parser: IParserCfg) {
|
||||
switch (tileType) {
|
||||
|
@ -19,6 +21,8 @@ export function getTileFactory(tileType: TileType, parser: IParserCfg) {
|
|||
return VectorLineTile;
|
||||
case 'PointLayer':
|
||||
return VectorPointLayer;
|
||||
case 'TileDebugLayer':
|
||||
return TestTile;
|
||||
case 'RasterLayer':
|
||||
if (parser.dataType === 'arraybuffer') {
|
||||
return RasterDataFactory;
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { ILayer, ISubLayerInitOptions } from '@antv/l7-core';
|
||||
import { Tile } from '@antv/l7-utils';
|
||||
import { ITileFactoryOptions } from '../interface';
|
||||
import TileFactory from './base';
|
||||
import VectorLayer from './vectorLayer';
|
||||
import {
|
||||
registerLayers,
|
||||
} from '../utils';
|
||||
|
||||
export default class TestTile extends TileFactory {
|
||||
public parentLayer: ILayer;
|
||||
|
||||
constructor(option: ITileFactoryOptions) {
|
||||
super(option);
|
||||
this.parentLayer = option.parent;
|
||||
}
|
||||
|
||||
public createTile(tile: Tile, initOptions: ISubLayerInitOptions) {
|
||||
const { sourceLayer } = initOptions;
|
||||
if (!sourceLayer) {
|
||||
return {
|
||||
layers: [],
|
||||
layerIDList: [],
|
||||
};
|
||||
}
|
||||
const vectorTileLayer = tile.data.layers[sourceLayer];
|
||||
const features = vectorTileLayer?.features;
|
||||
|
||||
if (features.length === 0) {
|
||||
return {
|
||||
layers: [],
|
||||
layerIDList: [],
|
||||
};
|
||||
}
|
||||
|
||||
const properties = features[0].properties;
|
||||
|
||||
const text = new VectorLayer({ layerType: 'PointLayer' })
|
||||
.source([properties], {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'textLng',
|
||||
y: 'textLat'
|
||||
}
|
||||
})
|
||||
.shape('key', 'text')
|
||||
.size(20)
|
||||
.color('#000')
|
||||
.style({
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2
|
||||
})
|
||||
|
||||
const line = new VectorLayer({ layerType: 'LineLayer' })
|
||||
.source({
|
||||
type: 'FeatureCollection',
|
||||
features: features,
|
||||
})
|
||||
.shape('simple')
|
||||
.color('#000')
|
||||
|
||||
registerLayers(this.parentLayer, [line, text]);
|
||||
|
||||
return {
|
||||
layers: [line, text],
|
||||
layerIDList: [line.id, text.id],
|
||||
};
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ import {
|
|||
import lineFillModel from '../../line/models/tile';
|
||||
import lineSimpleModel from '../../line/models/simpleLine';
|
||||
|
||||
import pointTextModel from '../../point/models/text';
|
||||
import pointTextModel from '../../point/models/tileText';
|
||||
import pointFillModel from '../../point/models/tile';
|
||||
import polygonFillModel from '../../polygon/models/tile';
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import BaseLayer from '../core/BaseLayer';
|
||||
import { IBaseLayerStyleOptions } from '../core/interface';
|
||||
import TileModel from './models/tileModel';
|
||||
|
||||
export default class TileDebugLayer extends BaseLayer<IBaseLayerStyleOptions> {
|
||||
public type: string = 'TileDebugLayer';
|
||||
public defaultSourceConfig = {
|
||||
data: [],
|
||||
options: {
|
||||
parser: {
|
||||
type: 'testTile',
|
||||
},
|
||||
},
|
||||
};
|
||||
public buildModels() {
|
||||
this.layerModel = new TileModel(this);
|
||||
this.layerModel.initModels((models) => {
|
||||
this.models = models;
|
||||
this.renderLayers();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import {
|
|||
import { DOM, Tile } from '@antv/l7-utils';
|
||||
import { Container } from 'inversify';
|
||||
|
||||
export const tileVectorParser = ['mvt', 'geojsonvt'];
|
||||
export const tileVectorParser = ['mvt', 'geojsonvt', 'testTile'];
|
||||
|
||||
export function isVectorTile(parserType: string) {
|
||||
return tileVectorParser.indexOf(parserType) >= 0;
|
||||
|
|
|
@ -2,11 +2,12 @@ import { registerParser, registerTransform } from './factory';
|
|||
import csv from './parser/csv';
|
||||
import geojson from './parser/geojson';
|
||||
import image from './parser/image';
|
||||
import json, { defaultData, defaultParser, defaultSource } from './parser/json';
|
||||
import json from './parser/json';
|
||||
import mapboxVectorTile from './parser/mvt';
|
||||
import geojsonVTTile from './parser/geojsonvt';
|
||||
import raster from './parser/raster';
|
||||
import rasterTile from './parser/raster-tile';
|
||||
import testTile from './parser/testTile';
|
||||
import Source from './source';
|
||||
import { cluster } from './transform/cluster';
|
||||
import { filter } from './transform/filter';
|
||||
|
@ -18,6 +19,7 @@ import { map } from './transform/map';
|
|||
registerParser('rasterTile', rasterTile);
|
||||
registerParser('mvt', mapboxVectorTile);
|
||||
registerParser('geojsonvt', geojsonVTTile);
|
||||
registerParser('testTile', testTile);
|
||||
registerParser('geojson', geojson);
|
||||
registerParser('image', image);
|
||||
registerParser('csv', csv);
|
||||
|
@ -39,8 +41,4 @@ export {
|
|||
|
||||
export * from './interface';
|
||||
|
||||
export const DEFAULT_SOURCE = defaultSource;
|
||||
export const DEFAULT_DATA = defaultData;
|
||||
export const DEFAULT_PARSER = defaultParser;
|
||||
|
||||
export default Source;
|
||||
|
|
|
@ -84,55 +84,3 @@ export default function json(data: IJsonData, cfg: IParserCfg): IParserData {
|
|||
dataArray: resultData,
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultSource = {
|
||||
PointLayer: {
|
||||
data: [],
|
||||
options: {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
},
|
||||
},
|
||||
LineLayer: {
|
||||
data: [
|
||||
{
|
||||
lng1: 100,
|
||||
lat1: 30.0,
|
||||
lng2: 130,
|
||||
lat2: 30,
|
||||
},
|
||||
],
|
||||
options: {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng1',
|
||||
y: 'lat1',
|
||||
x1: 'lng2',
|
||||
y1: 'lat2',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: 提供默认数据和解析器
|
||||
export const defaultData = [
|
||||
{
|
||||
lng1: 100,
|
||||
lat1: 30.0,
|
||||
lng2: 130,
|
||||
lat2: 30,
|
||||
},
|
||||
];
|
||||
|
||||
export const defaultParser = {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng1',
|
||||
y: 'lat1',
|
||||
x1: 'lng2',
|
||||
y1: 'lat2',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import { Tile, TilesetManagerOptions } from '@antv/l7-utils';
|
||||
import { VectorTileLayer } from '@mapbox/vector-tile';
|
||||
import { Feature } from '@turf/helpers';
|
||||
import { IParserData, ITileParserCFG } from '../interface';
|
||||
|
||||
const DEFAULT_CONFIG: Partial<TilesetManagerOptions> = {
|
||||
tileSize: 256,
|
||||
minZoom: 0,
|
||||
maxZoom: Infinity,
|
||||
zoomOffset: 0,
|
||||
};
|
||||
|
||||
export type MapboxVectorTile = {
|
||||
layers: { [_: string]: VectorTileLayer & { features: Feature[] } };
|
||||
};
|
||||
|
||||
const getVectorTile = async (tile: Tile): Promise<MapboxVectorTile> => {
|
||||
return new Promise((resolve) => {
|
||||
const [minLng, minLat, maxLng, maxLat] = tile.bounds;
|
||||
// minLng/maxLat ---- maxLng/maxLat
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// minLng/minLat --- maxLng/minLat
|
||||
|
||||
const vectorTile = {
|
||||
layers: {
|
||||
// Tip: fixed SourceLayer Name
|
||||
testTile: ({
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
key: tile.x + '/' + tile.y + '/' + tile.z,
|
||||
textLng: (minLng + maxLng) / 2,
|
||||
textLat: (minLat + maxLat) / 2,
|
||||
},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[maxLng, maxLat],
|
||||
[maxLng, minLat],
|
||||
[minLng, minLat],
|
||||
[minLng, minLat],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown) as VectorTileLayer & {
|
||||
features: Feature[];
|
||||
},
|
||||
},
|
||||
} as MapboxVectorTile;
|
||||
|
||||
resolve(vectorTile);
|
||||
});
|
||||
};
|
||||
|
||||
export default function mapboxVectorTile(
|
||||
data: string | string[],
|
||||
cfg?: ITileParserCFG,
|
||||
): IParserData {
|
||||
const getTileData = (tile: Tile) => getVectorTile(tile);
|
||||
const tilesetOptions = {
|
||||
...DEFAULT_CONFIG,
|
||||
...cfg,
|
||||
getTileData,
|
||||
};
|
||||
|
||||
return {
|
||||
data,
|
||||
dataArray: [],
|
||||
tilesetOptions,
|
||||
isTile: true,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue