diff --git a/dev-demos/features/tile/testTile.md b/dev-demos/features/tile/testTile.md new file mode 100644 index 0000000000..79ed00771f --- /dev/null +++ b/dev-demos/features/tile/testTile.md @@ -0,0 +1,2 @@ +### Test Tile + \ No newline at end of file diff --git a/dev-demos/features/tile/testTile.tsx b/dev-demos/features/tile/testTile.tsx new file mode 100644 index 0000000000..394ed4a59d --- /dev/null +++ b/dev-demos/features/tile/testTile.tsx @@ -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 ( +
+ ); +}; diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 1b4cbd0b49..e7b392e578 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -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; diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 89ab6a53d9..20b12f35e6 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -81,6 +81,11 @@ export default class BaseLayer public layerType?: string | undefined; public triangulation?: Triangulation | undefined; + public defaultSourceConfig: { + data: any[]; + options: ISourceCFG | undefined; + }; + public dataState: IDataState = { dataSourceNeedUpdate: false, dataMappingNeedUpdate: false, diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index 2007fd66ef..6fead23f06 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -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'; diff --git a/packages/layers/src/line/index.ts b/packages/layers/src/line/index.ts index ec2098619f..c77306789d 100644 --- a/packages/layers/src/line/index.ts +++ b/packages/layers/src/line/index.ts @@ -5,6 +5,25 @@ import { isVectorTile } from '../tile/utils'; export default class LineLayer extends BaseLayer { 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(); diff --git a/packages/layers/src/mask/index.ts b/packages/layers/src/mask/index.ts index f6955905b8..a8513d4fe9 100644 --- a/packages/layers/src/mask/index.ts +++ b/packages/layers/src/mask/index.ts @@ -4,6 +4,14 @@ import MaskModels, { MaskModelType } from './models'; export default class MaskLayer extends BaseLayer { public type: string = 'MaskLayer'; + public defaultSourceConfig: { + data: []; + options: { + parser: { + type: 'geojson'; + }; + }; + }; public buildModels() { const shape = this.getModelType(); this.layerModel = new MaskModels[shape](this); diff --git a/packages/layers/src/plugins/DataSourcePlugin.ts b/packages/layers/src/plugins/DataSourcePlugin.ts index 821b14104b..1b248f9e05 100644 --- a/packages/layers/src/plugins/DataSourcePlugin.ts +++ b/packages/layers/src/plugins/DataSourcePlugin.ts @@ -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); diff --git a/packages/layers/src/point/index.ts b/packages/layers/src/point/index.ts index 4e33ea0548..92236959b3 100644 --- a/packages/layers/src/point/index.ts +++ b/packages/layers/src/point/index.ts @@ -6,6 +6,17 @@ import { isVectorTile } from '../tile/utils'; export default class PointLayer extends BaseLayer { 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 { 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 { }, vectorpoint: {}, tile: {}, + tileText: {}, earthFill: {}, earthExtrude: {}, }; @@ -79,20 +91,6 @@ export default class PointLayer extends BaseLayer { } 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'; diff --git a/packages/layers/src/point/models/index.ts b/packages/layers/src/point/models/index.ts index 217a7fbe76..cf13432193 100644 --- a/packages/layers/src/point/models/index.ts +++ b/packages/layers/src/point/models/index.ts @@ -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, }; diff --git a/packages/layers/src/point/models/tileText.ts b/packages/layers/src/point/models/tileText.ts new file mode 100644 index 0000000000..dd4a75ad3e --- /dev/null +++ b/packages/layers/src/point/models/tileText.ts @@ -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 = {}; + 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 = []; + }); + } +} diff --git a/packages/layers/src/polygon/index.ts b/packages/layers/src/polygon/index.ts index 3afdb4eed0..5bb4fbde47 100644 --- a/packages/layers/src/polygon/index.ts +++ b/packages/layers/src/polygon/index.ts @@ -6,6 +6,14 @@ import { isVectorTile } from '../tile/utils'; export default class PolygonLayer extends BaseLayer { public type: string = 'PolygonLayer'; + public defaultSourceConfig: { + data: []; + options: { + parser: { + type: 'geojson'; + }; + }; + }; public buildModels() { const shape = this.getModelType(); this.layerModel = new PolygonModels[shape](this); diff --git a/packages/layers/src/tile/manager/tileLayerManager.ts b/packages/layers/src/tile/manager/tileLayerManager.ts index 89f5a68130..922d88b10b 100644 --- a/packages/layers/src/tile/manager/tileLayerManager.ts +++ b/packages/layers/src/tile/manager/tileLayerManager.ts @@ -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 diff --git a/packages/layers/src/tile/models/tileModel.ts b/packages/layers/src/tile/models/tileModel.ts index ae82665ef4..4d02e86366 100644 --- a/packages/layers/src/tile/models/tileModel.ts +++ b/packages/layers/src/tile/models/tileModel.ts @@ -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 {}; } diff --git a/packages/layers/src/tile/tileFactory/index.ts b/packages/layers/src/tile/tileFactory/index.ts index cd383826a8..2ee234f2f5 100644 --- a/packages/layers/src/tile/tileFactory/index.ts +++ b/packages/layers/src/tile/tileFactory/index.ts @@ -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; diff --git a/packages/layers/src/tile/tileFactory/test.ts b/packages/layers/src/tile/tileFactory/test.ts new file mode 100644 index 0000000000..b94c4de25f --- /dev/null +++ b/packages/layers/src/tile/tileFactory/test.ts @@ -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], + }; + } +} diff --git a/packages/layers/src/tile/tileFactory/vectorLayer.ts b/packages/layers/src/tile/tileFactory/vectorLayer.ts index 3ddc2d5a02..d01d1bb694 100644 --- a/packages/layers/src/tile/tileFactory/vectorLayer.ts +++ b/packages/layers/src/tile/tileFactory/vectorLayer.ts @@ -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'; diff --git a/packages/layers/src/tile/tileTest.ts b/packages/layers/src/tile/tileTest.ts new file mode 100644 index 0000000000..bc51b6f933 --- /dev/null +++ b/packages/layers/src/tile/tileTest.ts @@ -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 { + 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(); + }); + } +} diff --git a/packages/layers/src/tile/utils.ts b/packages/layers/src/tile/utils.ts index 91b0ff17f9..ab7c8e5fb6 100644 --- a/packages/layers/src/tile/utils.ts +++ b/packages/layers/src/tile/utils.ts @@ -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; diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index 2ddaf700a8..579aceac1f 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -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; diff --git a/packages/source/src/parser/json.ts b/packages/source/src/parser/json.ts index 1f316e5d2b..3a71f434c5 100644 --- a/packages/source/src/parser/json.ts +++ b/packages/source/src/parser/json.ts @@ -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', - }, -}; diff --git a/packages/source/src/parser/testTile.ts b/packages/source/src/parser/testTile.ts new file mode 100644 index 0000000000..eb9b10f38a --- /dev/null +++ b/packages/source/src/parser/testTile.ts @@ -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 = { + tileSize: 256, + minZoom: 0, + maxZoom: Infinity, + zoomOffset: 0, +}; + +export type MapboxVectorTile = { + layers: { [_: string]: VectorTileLayer & { features: Feature[] } }; +}; + +const getVectorTile = async (tile: Tile): Promise => { + 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, + }; +}