Fix 矢量瓦片拾取&线纹理&一些lint fixed (#1598)

* feat: 栅格表示添加 min/max/log10/log2/计算逻辑

* fix: lint format

* fix: 文本避让

* fix: utils some error

* fix: 文本支持 fontFamily,fontweight,padding 更新

* chore: 图片标注图层空数据改为空图标

* chore: fillImange 默认shape 为透明

* fix: 瓦片拾取featureID 兼容

* fix: line 纹理事件

* chore: ts lint fixed
This commit is contained in:
@thinkinggis 2023-02-14 10:25:00 +08:00 committed by GitHub
parent a0b79e89cd
commit 5a96e01ad5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 261 additions and 163 deletions

View File

@ -0,0 +1,77 @@
// @ts-ignore
import { Scene, PolygonLayer, PointLayer } from '@antv/l7';
// @ts-ignore
import { Map } from '@antv/l7-maps';
import React, { useEffect } from 'react';
export default () => {
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new Map({
center: [119.586579, 39.942531],
zoom: 8,
minZoom: 7,
maxZoom: 18,
}),
});
const layer = new PolygonLayer({
featureId: 'id',
sourceLayer: 'qhd_bianhua_jiance',// woods hillshade contour ecoregions ecoregions2 city
});
const url =
'//114.116.251.141:2183/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=ellip:qhd_bianhua_jiance&STYLE=&TILEMATRIX=EPSG:3857:{z}&TILEMATRIXSET=EPSG:3857&FORMAT=application/vnd.mapbox-vector-tile&TILECOL={x}&TILEROW={y}';
layer
.source(
url,
{
parser: {
type: 'mvt',
tileSize: 256,
extent: [-180, -85.051129, 179, 85.051129],
},
},
)
.shape('fill')
.color('red')
// .active(true)
.select(true)
.style({
// opacity: 0.3
});
scene.on('loaded', () => {
scene.addLayer(layer);
console.log(layer)
// layer.on('inited', () => {
// console.log(
// 'layer.getLayerConfig().enableHighlight',
// layer.getLayerConfig().enableHighlight,
// );
// });
layer.on('click', (e) => {
console.log(e);
});
});
}, []);
return (
<div
id="map"
style={{
height: '60vh',
position: 'relative',
}}
/>
);
};

View File

@ -0,0 +1,13 @@
---
group:
path: 'vector'
title: '矢量瓦片'
order: 1
title: 瓦片交互
order: 0
---
### 瓦片交互
<code src="./demos/event.tsx"></code>

View File

@ -44,15 +44,12 @@ export default class PickingService implements IPickingService {
private pickBufferScale: number = 1.0;
public init(id: string) {
const {
createTexture2D,
createFramebuffer,
getContainer,
} = this.rendererService;
const { createTexture2D, createFramebuffer, getContainer } =
this.rendererService;
let { width, height } = this.getContainerSize(
getContainer() as HTMLCanvasElement | HTMLElement,
);
);
width *= DOM.DPR;
height *= DOM.DPR;
this.pickBufferScale =
@ -134,7 +131,8 @@ export default class PickingService implements IPickingService {
const color = pickedColors.slice(i * 4, i * 4 + 4);
const pickedFeatureIdx = decodePickingColor(color);
if (pickedFeatureIdx !== -1 && !featuresIdMap[pickedFeatureIdx]) {
const rawFeature = layer.layerPickService.getFeatureById(pickedFeatureIdx);
const rawFeature =
layer.layerPickService.getFeatureById(pickedFeatureIdx);
features.push({
// @ts-ignore
...rawFeature,
@ -217,9 +215,10 @@ export default class PickingService implements IPickingService {
pickedColors[2] !== 0
) {
const pickedFeatureIdx = decodePickingColor(pickedColors);
// 瓦片数据获取性能问题需要优化
const rawFeature = layer.layerPickService.getFeatureById(pickedFeatureIdx);
const rawFeature =
layer.layerPickService.getFeatureById(pickedFeatureIdx);
if (
pickedFeatureIdx !== layer.getCurrentPickId() &&
type === 'mousemove'
@ -276,7 +275,6 @@ export default class PickingService implements IPickingService {
type === 'click' &&
pickedColors?.toString() !== [0, 0, 0, 0].toString()
) {
const selectedId = decodePickingColor(pickedColors);
if (
layer.getCurrentSelectedId() === null ||
@ -305,7 +303,8 @@ export default class PickingService implements IPickingService {
}
private async pickingAllLayer(target: IInteractionTarget) {
// 判断是否进行拾取操作
if (!this.layerService.needPick(target.type) ||!this.isPickingAllLayer()) return;
if (!this.layerService.needPick(target.type) || !this.isPickingAllLayer())
return;
this.alreadyInPicking = true;
await this.pickingLayers(target);
this.layerService.renderLayers();
@ -314,13 +313,13 @@ export default class PickingService implements IPickingService {
private isPickingAllLayer() {
// this.alreadyInPicking 避免多次重复拾取
if (this.alreadyInPicking) return false;
if (this.alreadyInPicking) { return false; }
// this.layerService.alreadyInRendering 一个渲染序列中只进行一次拾取操作
if (this.layerService.alreadyInRendering) return false;
if (this.layerService.alreadyInRendering) { return false; }
// this.interactionService.dragging amap2 在点击操作的时候同时会触发 dragging 的情况(避免舍去)
if (this.interactionService.indragging) return false;
if (this.interactionService.indragging) { return false; }
// 判断当前进行 shader pick 拾取判断
if (!this.layerService.getShaderPickStat()) return false;
if (!this.layerService.getShaderPickStat()) { return false; }
// 进行拾取
return true;
@ -349,13 +348,13 @@ export default class PickingService implements IPickingService {
useFramebuffer(this.pickingFBO, () => {
const layers = this.layerService.getRenderList();
layers
.filter((layer) => {
return layer.needPick(target.type)})
return layer.needPick(target.type);
})
.reverse()
.some((layer) => {
clear({
framebuffer: this.pickingFBO,
color: [0, 0, 0, 0],
@ -389,5 +388,4 @@ export default class PickingService implements IPickingService {
layer.emit(target.type, target);
}
}
}

View File

@ -31,8 +31,9 @@ const lineStyleObj: { [key: string]: number } = {
dash: 1.0,
};
export default class LineModel extends BaseModel {
private textureEventFlag: boolean = false;
protected texture: ITexture2D = this.createTexture2D({
data: [0,0,0,0],
data: [0, 0, 0, 0],
mag: gl.NEAREST,
min: gl.NEAREST,
premultiplyAlpha: false,
@ -68,7 +69,6 @@ export default class LineModel extends BaseModel {
}
if (this.rendererService.getDirty()) {
this.texture && this.texture.bind();
}
@ -92,7 +92,7 @@ export default class LineModel extends BaseModel {
);
this.rowCount = height; // 当前数据纹理有多少行
this.dataTexture =
this.cellLength > 0 && data.length > 0
? this.createTexture2D({
@ -123,7 +123,7 @@ export default class LineModel extends BaseModel {
u_blur: blur,
// 纹理支持参数
u_texture: this.texture, // 贴图
u_texture: this.texture, // 贴图
u_line_texture: lineTexture ? 1.0 : 0.0, // 传入线的标识
u_icon_step: iconStep,
u_textSize: [1024, this.iconService.canvasHeight || 128],
@ -160,10 +160,15 @@ export default class LineModel extends BaseModel {
};
}
public async initModels():Promise<IModel[]>{
public async initModels(): Promise<IModel[]> {
// this.updateTexture();
// this.iconService.on('imageUpdate', this.updateTexture);
return await this.buildModels();
if (!this.textureEventFlag) {
this.textureEventFlag = true;
this.updateTexture();
this.iconService.on('imageUpdate', this.updateTexture);
}
return this.buildModels();
}
public clearModels() {
@ -206,11 +211,8 @@ export default class LineModel extends BaseModel {
* @returns
*/
public getShaders(): { frag: string; vert: string; type: string } {
const {
sourceColor,
targetColor,
lineType,
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
const { sourceColor, targetColor, lineType } =
this.layer.getLayerConfig() as ILineLayerStyleOptions;
if (lineType === 'dash') {
return {
@ -374,7 +376,6 @@ export default class LineModel extends BaseModel {
}
private updateTexture = () => {
const { createTexture2D } = this.rendererService;
if (this.texture) {
this.texture.update({

View File

@ -1,4 +1,12 @@
import { ILayerService, ITile, ITilePickService, IInteractionTarget, ILayer, IPickingService, TYPES } from '@antv/l7-core';
import {
IInteractionTarget,
ILayer,
ILayerService,
IPickingService,
ITile,
ITilePickService,
TYPES,
} from '@antv/l7-core';
import { decodePickingColor, encodePickingColor } from '@antv/l7-utils';
import { TileLayerService } from './TileLayerService';
import { TileSourceService } from './TileSourceService';
@ -10,26 +18,29 @@ export interface ITilePickServiceOptions {
const SELECT = 'select';
const ACTIVE = 'active';
export class TilePickService implements ITilePickService{
export class TilePickService implements ITilePickService {
private layerService: ILayerService;
private tileLayerService: TileLayerService;
private tileSourceService: TileSourceService;
private parent: ILayer;
private tilePickID = new Map();
constructor({ layerService, tileLayerService, parent }: ITilePickServiceOptions) {
constructor({
layerService,
tileLayerService,
parent,
}: ITilePickServiceOptions) {
this.layerService = layerService;
this.tileLayerService = tileLayerService;
this.parent = parent;
this.tileSourceService = new TileSourceService();
}
pickRender(target: IInteractionTarget) {
public pickRender(target: IInteractionTarget) {
// 一个 TileLayer 有多个 Tile但是会同时触发事件的只有一个 Tile
const tile = this.tileLayerService.getVisibleTileBylngLat(target.lngLat);
if (tile) {
// TODO 多图层拾取
const pickLayer = tile.getMainLayer();
pickLayer?.layerPickService.pickRender(target)
pickLayer?.layerPickService.pickRender(target);
}
}
@ -38,21 +49,24 @@ export class TilePickService implements ITilePickService{
const pickingService = container.get<IPickingService>(
TYPES.IPickingService,
);
if(layer.type === 'RasterLayer') {
if (layer.type === 'RasterLayer') {
const tile = this.tileLayerService.getVisibleTileBylngLat(target.lngLat);
if (tile && tile.getMainLayer() !== undefined) {
const pickLayer = tile.getMainLayer() as ILayer;
return pickLayer.layerPickService.pickRasterLayer(pickLayer, target, this.parent);
return pickLayer.layerPickService.pickRasterLayer(
pickLayer,
target,
this.parent,
);
}
return false;
}
this.pickRender(target);
return pickingService.pickFromPickingFBO(layer, target);
}
selectFeature(pickedColors: Uint8Array | undefined) {
public selectFeature(pickedColors: Uint8Array | undefined) {
// @ts-ignore
const [r, g, b] = pickedColors;
const id = this.color2PickId(r, g, b);
@ -60,7 +74,7 @@ export class TilePickService implements ITilePickService{
this.updateHighLight(r, g, b, SELECT);
}
highlightPickedFeature(pickedColors: Uint8Array | undefined) {
public highlightPickedFeature(pickedColors: Uint8Array | undefined) {
// @ts-ignore
const [r, g, b] = pickedColors;
const id = this.color2PickId(r, g, b);
@ -68,65 +82,70 @@ export class TilePickService implements ITilePickService{
this.updateHighLight(r, g, b, ACTIVE);
}
updateHighLight(r: number, g: number, b: number, type: string){
public updateHighLight(r: number, g: number, b: number, type: string) {
this.tileLayerService.tiles.map((tile: ITile) => {
const layer = tile.getMainLayer();
switch(type) {
case SELECT:
layer?.hooks.beforeSelect.call([r, g, b]);
break;
case ACTIVE:
layer?.hooks.beforeHighlight.call([r, g, b]);
break;
}
switch (type) {
case SELECT:
layer?.hooks.beforeSelect.call([r, g, b]);
break;
case ACTIVE:
layer?.hooks.beforeHighlight.call([r, g, b]);
break;
}
});
}
setPickState() {
const selectColor = this.tilePickID.get(SELECT)
const activeColor = this.tilePickID.get(ACTIVE)
if(selectColor) {
public setPickState() {
const selectColor = this.tilePickID.get(SELECT);
const activeColor = this.tilePickID.get(ACTIVE);
if (selectColor) {
const [r, g, b] = this.pickId2Color(selectColor);
this.updateHighLight(r, g, b, SELECT);
return;
}
if(activeColor) {
if (activeColor) {
const [r, g, b] = this.pickId2Color(activeColor);
this.updateHighLight(r, g, b, ACTIVE);
return;
}
}
private color2PickId (r: number, g: number, b: number){
return decodePickingColor(new Uint8Array([r,g,b]))
private color2PickId(r: number, g: number, b: number) {
return decodePickingColor(new Uint8Array([r, g, b]));
}
private pickId2Color(str: number){
return encodePickingColor(str )
private pickId2Color(str: number) {
return encodePickingColor(str);
}
/** 从瓦片中根据数据 */
getFeatureById(pickedFeatureIdx: number) {
public getFeatureById(pickedFeatureIdx: number) {
// 提取当前可见瓦片
const tiles = this.tileLayerService.getTiles().filter(tile => tile.visible);
const tiles = this.tileLayerService
.getTiles()
.filter((tile) => tile.visible);
// 提取当前可见瓦片中匹配 ID 的 feature 列表
const features: any[] = [];
tiles.forEach(tile => {
tiles.forEach((tile) => {
features.push(...tile.getFeatureById(pickedFeatureIdx));
})
});
// 将 feature 列表合并后返回
// 统一返回成 polygon 的格式 点、线、面可以通用
// const data = this.tileSourceService.getCombineFeature(features);
return features
return features;
}
// Tip: for interface define
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public pickRasterLayer(layer: ILayer, target: IInteractionTarget, parent?: ILayer) {
public pickRasterLayer(
layer: ILayer,
target: IInteractionTarget,
parent?: ILayer,
) {
return false;
}
}

View File

@ -6,21 +6,21 @@ import union from '@turf/union';
* Tile
*/
export class TileSourceService {
public getCombineFeature(features: IParseDataItem[]) {
let p: any = null;
const properties = features[0];
features.map((feature) => {
const polygon = turf.polygon(feature.coordinates);
if (p === null) {
p = polygon;
} else {
p = union(p, polygon);
}
});
public getCombineFeature(features: IParseDataItem[]) {
let p: any = null;
const properties = features[0];
features.map((feature) => {
const polygon = turf.polygon(feature.coordinates);
if (p === null) {
p = polygon;
} else {
p = union(p, polygon);
}
});
if (properties) {
p.properties = { ...properties };
}
return p;
if (properties) {
p.properties = { ...properties };
}
}
return p;
}
}

View File

@ -1,8 +1,8 @@
import { ILayer, ILayerAttributesOption } from '@antv/l7-core';
import { VectorSource } from '@antv/l7-source'
import MaskLayer from '../../mask';
import Tile from './Tile';
import { getTileLayer } from './util';
import MaskLayer from '../../mask';
import { VectorSource } from '@antv/l7-source'
export default class VectorTile extends Tile {
public async initTileLayer(): Promise<void> {
@ -11,15 +11,14 @@ export default class VectorTile extends Tile {
const vectorLayer = getTileLayer(this.parent.type);
const sourceOptions = this.getSourceOption();
if(!sourceOptions){
if (!sourceOptions) {
this.isLoaded = true;
return
return;
}
const layer = new vectorLayer({...layerOptions}).source(
const layer = new vectorLayer({ ...layerOptions }).source(
sourceOptions.data,
sourceOptions.options,
);
// 初始化数据映射
Object.keys(attributes).forEach((type) => {
@ -27,8 +26,8 @@ export default class VectorTile extends Tile {
// @ts-ignore
layer[attr](attributes[attr]?.field, attributes[attr]?.values);
});
if(layerOptions.mask ) {
await this.addTileMask(layer)
if (layerOptions.mask) {
await this.addTileMask(layer);
}
await this.addLayer(layer);
@ -37,29 +36,29 @@ export default class VectorTile extends Tile {
}
// Todo 校验数据有效性
protected async addTileMask(layer: ILayer) {
const mask = new MaskLayer({layerType: "MaskLayer"})
.source({
type: 'FeatureCollection',
features: [
this.sourceTile.bboxPolygon
],
}, {
parser: {
type: 'geojson',
featureId: 'id'
}
})
await this.addMask(layer, mask)
const mask = new MaskLayer({ layerType: 'MaskLayer' }).source(
{
type: 'FeatureCollection',
features: [this.sourceTile.bboxPolygon],
},
{
parser: {
type: 'geojson',
featureId: 'id',
},
},
);
await this.addMask(layer, mask);
}
protected getSourceOption() {
const rawSource = this.parent.getSource();
const { sourceLayer = 'defaultLayer', featureId = 'id'} = this.parent.getLayerConfig<{
featureId: string;
}>();
const features = this.getFeatures(sourceLayer)
const { sourceLayer = 'defaultLayer', featureId = 'id' } =
this.parent.getLayerConfig<{
featureId: string;
}>();
const features = this.getFeatures(sourceLayer);
return {
data: {
type: 'FeatureCollection',
@ -77,34 +76,32 @@ export default class VectorTile extends Tile {
protected setLayerMinMaxZoom(layer: ILayer) {
// 文本图层设置,可见范围
if(layer.getModelType() === 'text') {
if (layer.getModelType() === 'text') {
layer.updateLayerConfig({
maxZoom: this.z +1,
minZoom: this.z - 1
maxZoom: this.z + 1,
minZoom: this.z - 1,
});
}
}
// 获取瓦片数据
public getFeatures(sourceLayer: string){
const source = this.sourceTile.data as VectorSource;
return source.getTileData(sourceLayer);
public getFeatures(sourceLayer: string) {
const source = this.sourceTile.data as VectorSource;
return source.getTileData(sourceLayer);
}
/**
* Tile ID feature
* @param id
* @returns
* @param id
* @returns
*/
public getFeatureById(id: number) {
const layer = this.getMainLayer();
if (!layer) {
return [];
}
const res = layer.getSource().data.dataArray.filter(d => d._id === id);
return res
console.log(layer.getSource().data.dataArray,id)
const res = layer.getSource().data.dataArray.filter((d) => d._id === id);
return res;
}
}

View File

@ -5,7 +5,7 @@ order: 1
<embed src="@/docs/common/style.md"></embed>
### sourceLayer
#### sourceLayer
<description> _string_ **required** </description>
@ -19,7 +19,7 @@ const layer = new PointLayer({
});
```
### featureId
#### featureId
<description> _string_ **optional** _default:_ 自动数字编号</description>

View File

@ -1,13 +1,13 @@
import { registerParser, registerTransform } from './factory';
import csv from './parser/csv';
import geojson from './parser/geojson';
import geojsonVTTile from './parser/geojsonvt';
import image from './parser/image';
import json from './parser/json';
import mapboxVectorTile from './parser/mvt';
import geojsonVTTile from './parser/geojsonvt';
import raster from './parser/raster';
import rasterRgb from './parser/rasterRgb';
import rasterTile, { rasterDataTypes } from './parser/raster-tile';
import rasterRgb from './parser/rasterRgb';
import testTile from './parser/testTile';
import Source from './source';
import { cluster } from './transform/cluster';
@ -16,7 +16,15 @@ import { aggregatorToGrid } from './transform/grid';
import { pointToHexbin } from './transform/hexagon';
import { join } from './transform/join';
import { map } from './transform/map';
export {
getParser,
getTransform,
registerParser,
registerTransform,
} from './factory';
export * from './interface';
export * from './source/index';
export { rasterDataTypes };
registerParser('rasterTile', rasterTile);
registerParser('mvt', mapboxVectorTile);
@ -35,16 +43,4 @@ registerTransform('map', map);
registerTransform('grid', aggregatorToGrid);
registerTransform('hexagon', pointToHexbin);
export {
rasterDataTypes,
}
export {
getTransform,
registerTransform,
getParser,
registerParser,
} from './factory';
export * from './interface';
export default Source;

View File

@ -36,11 +36,11 @@ function getFeatureID(feature: Feature<Geometries, Properties>, key?: string) {
if (key === undefined) {
return null;
}
// @ts-ignore
if (feature.properties[key]) {
// 单独指定要素
if (typeof feature.properties[key] * 1 === 'number') {
// @ts-ignore
return feature.properties[key];
return feature.properties[key] * 1;
}
if (feature.properties && feature.properties[key]) {

View File

@ -4,10 +4,10 @@ import {
IClusterOptions,
IParseDataItem,
IParserCfg,
ITileParserCFG,
IParserData,
ISource,
ISourceCFG,
ITileParserCFG,
ITransform,
} from '@antv/l7-core';
import {
@ -48,7 +48,7 @@ export default class Source extends EventEmitter implements ISource {
public getSourceCfg() {
return this.cfg;
}
public parser: IParserCfg | ITileParserCFG = { type: 'geojson' } ;
public parser: IParserCfg | ITileParserCFG = { type: 'geojson' };
public transforms: ITransform[] = [];
public cluster: boolean = false;
public clusterOptions: Partial<IClusterOptions> = {
@ -70,7 +70,7 @@ export default class Source extends EventEmitter implements ISource {
protected originData: any;
protected rawData: any;
private cfg: Partial<ISourceCFG> = {
autoRender: true
autoRender: true,
};
private clusterIndex: Supercluster;
@ -81,11 +81,11 @@ export default class Source extends EventEmitter implements ISource {
this.originData = data;
this.initCfg(cfg);
this.init().then(()=>{
this.init().then(() => {
this.inited = true;
this.emit('update',{
type: 'inited'
})
this.emit('update', {
type: 'inited',
});
});
}
@ -142,7 +142,6 @@ export default class Source extends EventEmitter implements ISource {
}
public getFeatureById(id: number): unknown {
const { type = 'geojson', geometry } = this.parser as IParserCfg;
if (type === 'geojson' && !this.cluster) {
const feature =
@ -151,7 +150,6 @@ export default class Source extends EventEmitter implements ISource {
: 'null';
const newFeature = cloneDeep(feature);
if (
newFeature?.properties &&
(this.transforms.length !== 0 || this.dataArrayChanged)
@ -186,8 +184,8 @@ export default class Source extends EventEmitter implements ISource {
},
);
this.dataArrayChanged = true;
this.emit('update',{
type: 'update'
this.emit('update', {
type: 'update',
});
}
@ -202,13 +200,12 @@ export default class Source extends EventEmitter implements ISource {
this.originData = data;
this.dataArrayChanged = false;
this.initCfg(options);
this.init().then(()=>{
this.emit('update',{
type: 'update'
})
});
this.init().then(() => {
this.emit('update', {
type: 'update',
});
});
}
public destroy() {
@ -221,7 +218,7 @@ export default class Source extends EventEmitter implements ISource {
}
private async processData() {
return await new Promise((resolve, reject) => {
return new Promise((resolve, reject) => {
try {
this.excuteParser();
this.initCluster();
@ -280,7 +277,7 @@ export default class Source extends EventEmitter implements ISource {
this.tileset = this.initTileset();
// 判断当前 source 是否需要计算范围
if (parser.cancelExtent) return;
if (parser.cancelExtent) { return; }
// 计算范围
this.extent = extent(this.data.dataArray);