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:
YiQianYao 2022-08-30 15:35:41 +08:00 committed by GitHub
parent 7837021908
commit f345fba72c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 822 additions and 91 deletions

View File

@ -0,0 +1,2 @@
### Test Tile
<code src="./testTile.tsx"></code>

View File

@ -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',
}}
/>
);
};

View File

@ -265,6 +265,10 @@ export interface ILayer {
masks: ILayer[]; // 图层的 mask 列表 masks: ILayer[]; // 图层的 mask 列表
sceneContainer: Container | undefined; sceneContainer: Container | undefined;
dataState: IDataState; // 数据流状态 dataState: IDataState; // 数据流状态
defaultSourceConfig: {
data: any[],
options: ISourceCFG | undefined,
},
pickedFeatureID: number | null; pickedFeatureID: number | null;
hooks: { hooks: {
init: SyncBailHook; init: SyncBailHook;

View File

@ -81,6 +81,11 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
public layerType?: string | undefined; public layerType?: string | undefined;
public triangulation?: Triangulation | undefined; public triangulation?: Triangulation | undefined;
public defaultSourceConfig: {
data: any[];
options: ISourceCFG | undefined;
};
public dataState: IDataState = { public dataState: IDataState = {
dataSourceNeedUpdate: false, dataSourceNeedUpdate: false,
dataMappingNeedUpdate: false, dataMappingNeedUpdate: false,

View File

@ -16,6 +16,8 @@ import EarthLayer from './earth';
import MaskLayer from './mask'; import MaskLayer from './mask';
import WindLayer from './wind'; import WindLayer from './wind';
import TileDebugLayer from './tile/tileTest';
// import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin'; // 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';
@ -151,6 +153,7 @@ export {
EarthLayer, EarthLayer,
WindLayer, WindLayer,
MaskLayer, MaskLayer,
TileDebugLayer
}; };
export * from './core/interface'; export * from './core/interface';

View File

@ -5,6 +5,25 @@ import { isVectorTile } from '../tile/utils';
export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> { export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
public type: string = 'LineLayer'; 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() { public buildModels() {
const shape = this.getModelType(); const shape = this.getModelType();

View File

@ -4,6 +4,14 @@ import MaskModels, { MaskModelType } from './models';
export default class MaskLayer extends BaseLayer<IMaskLayerStyleOptions> { export default class MaskLayer extends BaseLayer<IMaskLayerStyleOptions> {
public type: string = 'MaskLayer'; public type: string = 'MaskLayer';
public defaultSourceConfig: {
data: [];
options: {
parser: {
type: 'geojson';
};
};
};
public buildModels() { public buildModels() {
const shape = this.getModelType(); const shape = this.getModelType();
this.layerModel = new MaskModels[shape](this); this.layerModel = new MaskModels[shape](this);

View File

@ -1,9 +1,5 @@
import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core'; import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
import Source, { import Source from '@antv/l7-source';
DEFAULT_DATA,
DEFAULT_PARSER,
DEFAULT_SOURCE,
} from '@antv/l7-source';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import 'reflect-metadata'; import 'reflect-metadata';
@ -15,14 +11,9 @@ export default class DataSourcePlugin implements ILayerPlugin {
layer.hooks.init.tap('DataSourcePlugin', () => { layer.hooks.init.tap('DataSourcePlugin', () => {
let source = layer.getSource(); let source = layer.getSource();
if (!source) { if (!source) {
// TODO: 允许用户不使用 layer 的 source 方法,在这里传入一个默认的替换的默认数据 // Tip: 用户没有传入 source 的时候使用图层的默认数据
const defaultSourceConfig = DEFAULT_SOURCE[ const { data, options } =
layer.type as 'PointLayer' | 'LineLayer' layer.sourceOption || layer.defaultSourceConfig;
] || {
data: DEFAULT_DATA,
options: DEFAULT_PARSER,
};
const { data, options } = layer.sourceOption || defaultSourceConfig;
source = new Source(data, options); source = new Source(data, options);
layer.setSource(source); layer.setSource(source);
} }
@ -31,7 +22,6 @@ export default class DataSourcePlugin implements ILayerPlugin {
} else { } else {
source.once('sourceUpdate', () => { source.once('sourceUpdate', () => {
this.updateClusterData(layer); this.updateClusterData(layer);
// TODO: layer.hooks.init.call();
}); });
} }
// this.updateClusterData(layer); // this.updateClusterData(layer);

View File

@ -6,6 +6,17 @@ import { isVectorTile } from '../tile/utils';
export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> { export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
public type: string = 'PointLayer'; public type: string = 'PointLayer';
public defaultSourceConfig = {
data: [],
options: {
parser: {
type: 'json',
x: 'lng',
y: 'lat',
},
},
};
public buildModels() { public buildModels() {
const modelType = this.getModelType(); const modelType = this.getModelType();
this.layerModel = new PointModels[modelType](this); this.layerModel = new PointModels[modelType](this);
@ -26,7 +37,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
public getModelTypeWillEmptyData(): PointType { public getModelTypeWillEmptyData(): PointType {
if (this.shapeOption) { if (this.shapeOption) {
const { field, values } = this.shapeOption; const { field, values } = this.shapeOption;
const { shape2d, shape3d } = this.getLayerConfig(); const { shape2d } = this.getLayerConfig();
const iconMap = this.iconService.getIconMap(); const iconMap = this.iconService.getIconMap();
@ -72,6 +83,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
}, },
vectorpoint: {}, vectorpoint: {},
tile: {}, tile: {},
tileText: {},
earthFill: {}, earthFill: {},
earthExtrude: {}, earthExtrude: {},
}; };
@ -79,20 +91,6 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
} }
protected getModelType(): PointType { protected getModelType(): PointType {
const PointTypes = [
'fillImage',
'fill',
'radar',
'image',
'normal',
'simplePoint',
'extrude',
'text',
'vectorpoint',
'tile',
'earthFill',
'earthExtrude',
];
const parserType = this.layerSource.getParserType(); const parserType = this.layerSource.getParserType();
if (isVectorTile(parserType)) { if (isVectorTile(parserType)) {
return 'vectorpoint'; return 'vectorpoint';

View File

@ -10,6 +10,7 @@ import NormalModel from './normal';
import Radar from './radar'; import Radar from './radar';
import SimplePopint from './simplePoint'; import SimplePopint from './simplePoint';
import TextModel from './text'; import TextModel from './text';
import TileTextModel from './tileText';
import TileFillModel from './tile'; import TileFillModel from './tile';
export type PointType = export type PointType =
@ -23,6 +24,7 @@ export type PointType =
| 'text' | 'text'
| 'vectorpoint' | 'vectorpoint'
| 'tile' | 'tile'
| 'tileText'
| 'earthFill' | 'earthFill'
| 'earthExtrude'; | 'earthExtrude';
@ -37,6 +39,7 @@ const PointModels: { [key in PointType]: any } = {
text: TextModel, text: TextModel,
vectorpoint: PointTileModel, vectorpoint: PointTileModel,
tile: TileFillModel, tile: TileFillModel,
tileText: TileTextModel,
earthFill: EarthFillModel, earthFill: EarthFillModel,
earthExtrude: EarthExtrudeModel, earthExtrude: EarthExtrudeModel,
}; };

View File

@ -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) {
// TODOfeatureIndex
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 = [];
});
}
}

View File

@ -6,6 +6,14 @@ import { isVectorTile } from '../tile/utils';
export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> { export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
public type: string = 'PolygonLayer'; public type: string = 'PolygonLayer';
public defaultSourceConfig: {
data: [];
options: {
parser: {
type: 'geojson';
};
};
};
public buildModels() { public buildModels() {
const shape = this.getModelType(); const shape = this.getModelType();
this.layerModel = new PolygonModels[shape](this); this.layerModel = new PolygonModels[shape](this);

View File

@ -184,13 +184,14 @@ export class TileLayerManager implements ITileLayerManager {
this.rampColorsData = generateColorRamp(rampColors as IColorRamp); this.rampColorsData = generateColorRamp(rampColors as IColorRamp);
} }
this.initOptions = { this.initOptions = {
layerType: this.parent.type, layerType: this.parent.type,
transforms: this.transforms, transforms: this.transforms,
shape: layerShape, shape: layerShape,
zIndex, zIndex,
opacity, opacity,
sourceLayer: parentParserType === 'geojsonvt' ? 'geojsonvt' : sourceLayer, sourceLayer: this.getSourceLayer(parentParserType, sourceLayer),
coords, coords,
featureId, featureId,
color: colorValue, 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() { private setConfigListener() {
// RasterLayer PolygonLayer LineLayer PointLayer // RasterLayer PolygonLayer LineLayer PointLayer
// All Tile Layer Need Listen // All Tile Layer Need Listen

View File

@ -1,7 +1,7 @@
import { IModelUniform } from '@antv/l7-core'; import { IModelUniform } from '@antv/l7-core';
import BaseModel from '../../core/BaseModel'; import BaseModel from '../../core/BaseModel';
import { TMSTileLayer } from '../tmsTileLayer'; import { TMSTileLayer } from '../tmsTileLayer';
export default class RasterTileModel extends BaseModel { export default class TileModel extends BaseModel {
public getUninforms(): IModelUniform { public getUninforms(): IModelUniform {
return {}; return {};
} }

View File

@ -4,12 +4,14 @@ import VectorPointLayer from './point';
import VectorPolygonTile from './polygon'; import VectorPolygonTile from './polygon';
import RasterTileFactory from './raster'; import RasterTileFactory from './raster';
import RasterDataFactory from './rasterData'; import RasterDataFactory from './rasterData';
import TestTile from './test';
export type TileType = export type TileType =
| 'PolygonLayer' | 'PolygonLayer'
| 'PointLayer' | 'PointLayer'
| 'LineLayer' | 'LineLayer'
| 'RasterLayer'; | 'RasterLayer'
| 'TileDebugLayer';
export function getTileFactory(tileType: TileType, parser: IParserCfg) { export function getTileFactory(tileType: TileType, parser: IParserCfg) {
switch (tileType) { switch (tileType) {
@ -19,6 +21,8 @@ export function getTileFactory(tileType: TileType, parser: IParserCfg) {
return VectorLineTile; return VectorLineTile;
case 'PointLayer': case 'PointLayer':
return VectorPointLayer; return VectorPointLayer;
case 'TileDebugLayer':
return TestTile;
case 'RasterLayer': case 'RasterLayer':
if (parser.dataType === 'arraybuffer') { if (parser.dataType === 'arraybuffer') {
return RasterDataFactory; return RasterDataFactory;

View File

@ -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],
};
}
}

View File

@ -8,7 +8,7 @@ import {
import lineFillModel from '../../line/models/tile'; import lineFillModel from '../../line/models/tile';
import lineSimpleModel from '../../line/models/simpleLine'; 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 pointFillModel from '../../point/models/tile';
import polygonFillModel from '../../polygon/models/tile'; import polygonFillModel from '../../polygon/models/tile';

View File

@ -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();
});
}
}

View File

@ -7,7 +7,7 @@ import {
import { DOM, Tile } from '@antv/l7-utils'; import { DOM, Tile } from '@antv/l7-utils';
import { Container } from 'inversify'; import { Container } from 'inversify';
export const tileVectorParser = ['mvt', 'geojsonvt']; export const tileVectorParser = ['mvt', 'geojsonvt', 'testTile'];
export function isVectorTile(parserType: string) { export function isVectorTile(parserType: string) {
return tileVectorParser.indexOf(parserType) >= 0; return tileVectorParser.indexOf(parserType) >= 0;

View File

@ -2,11 +2,12 @@ import { registerParser, registerTransform } from './factory';
import csv from './parser/csv'; import csv from './parser/csv';
import geojson from './parser/geojson'; import geojson from './parser/geojson';
import image from './parser/image'; import image from './parser/image';
import json, { defaultData, defaultParser, defaultSource } from './parser/json'; import json from './parser/json';
import mapboxVectorTile from './parser/mvt'; import mapboxVectorTile from './parser/mvt';
import geojsonVTTile from './parser/geojsonvt'; import geojsonVTTile from './parser/geojsonvt';
import raster from './parser/raster'; import raster from './parser/raster';
import rasterTile from './parser/raster-tile'; import rasterTile from './parser/raster-tile';
import testTile from './parser/testTile';
import Source from './source'; import Source from './source';
import { cluster } from './transform/cluster'; import { cluster } from './transform/cluster';
import { filter } from './transform/filter'; import { filter } from './transform/filter';
@ -18,6 +19,7 @@ import { map } from './transform/map';
registerParser('rasterTile', rasterTile); registerParser('rasterTile', rasterTile);
registerParser('mvt', mapboxVectorTile); registerParser('mvt', mapboxVectorTile);
registerParser('geojsonvt', geojsonVTTile); registerParser('geojsonvt', geojsonVTTile);
registerParser('testTile', testTile);
registerParser('geojson', geojson); registerParser('geojson', geojson);
registerParser('image', image); registerParser('image', image);
registerParser('csv', csv); registerParser('csv', csv);
@ -39,8 +41,4 @@ export {
export * from './interface'; export * from './interface';
export const DEFAULT_SOURCE = defaultSource;
export const DEFAULT_DATA = defaultData;
export const DEFAULT_PARSER = defaultParser;
export default Source; export default Source;

View File

@ -84,55 +84,3 @@ export default function json(data: IJsonData, cfg: IParserCfg): IParserData {
dataArray: resultData, 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',
},
};

View File

@ -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,
};
}