feat: 栅格表达式添加 min/max/log10/log2/计算逻辑&文本更新避让能力完善 (#1594)

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

* fix: lint format

* fix: 文本避让

* fix: utils some error

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

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

* chore: fillImange 默认shape 为透明
This commit is contained in:
@thinkinggis 2023-02-13 11:07:47 +08:00 committed by GitHub
parent faf6c7719f
commit ffee682445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 3715 additions and 462 deletions

View File

@ -33,14 +33,14 @@ export default () => {
'https://gw.alipayobjects.com/zos/bmw-prod/904d047a-16a5-461b-a921-98fa537fc04a.svg', 'https://gw.alipayobjects.com/zos/bmw-prod/904d047a-16a5-461b-a921-98fa537fc04a.svg',
); );
const data = await response.json(); const data = await response.json();
const newData = data.map((item: any) => { const newData = data.map((item: any,index: number) => {
item.type = ['00', '01', '02'][Math.floor(Math.random() * 3)]; item.type = ['00', '01', '02',''][index % 4];
return item; return item;
}); });
const imageLayer = new PointLayer({ const imageLayer = new PointLayer({
autoFit:false autoFit:false
}) })
.source(newData, { .source(newData.slice(0,4), {
parser: { parser: {
type: 'json', type: 'json',
x: 'longitude', x: 'longitude',
@ -52,25 +52,9 @@ export default () => {
}) })
.active(false) .active(false)
.size(20); .size(20);
scene.addLayer(imageLayer); scene.addLayer(imageLayer);
setInterval(()=>{
scene.addImage(
'00',
'https://gw.alipayobjects.com/mdn/rms_fcd5b3/afts/img/A*g8cUQ7pPT9YAAAAAAAAAAAAAARQnAQ',
);
scene.addImage(
'01',
'https://gw.alipayobjects.com/mdn/rms_fcd5b3/afts/img/A*LTcXTLBM7kYAAAAAAAAAAAAAARQnAQ',
);
scene.addImage(
'02',
'https://gw.alipayobjects.com/zos/bmw-prod/904d047a-16a5-461b-a921-98fa537fc04a.svg',
);
const data = newData.slice(0,5+ Math.round(Math.random()*10));
imageLayer.setData(data)
console.log(imageLayer)
console.log('更新')
},3000)
}, []); }, []);

File diff suppressed because it is too large Load Diff

View File

@ -298,20 +298,8 @@ export default class FontService extends EventEmitter implements IFontService {
} }
private getKey() { private getKey() {
return 'key'; const { fontFamily, fontWeight } = this.fontOptions;
const { return `${fontFamily}_${fontWeight}`;
fontFamily,
fontWeight,
fontSize,
buffer,
sdf,
radius,
cutoff,
} = this.fontOptions;
if (sdf) {
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer} ${radius} ${cutoff} `;
}
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer}`;
} }
/** /**

View File

@ -5,12 +5,7 @@ import 'reflect-metadata';
import { buildIconMaping } from '../../utils/font_util'; import { buildIconMaping } from '../../utils/font_util';
import { ITexture2D } from '../renderer/ITexture2D'; import { ITexture2D } from '../renderer/ITexture2D';
import { ISceneService } from '../scene/ISceneService'; import { ISceneService } from '../scene/ISceneService';
import { import { IIcon, IICONMap, IIconService, IImage } from './IIconService';
IIcon,
IICONMap,
IIconService,
IImage,
} from './IIconService';
const BUFFER = 3; const BUFFER = 3;
const MAX_CANVAS_WIDTH = 1024; const MAX_CANVAS_WIDTH = 1024;
const imageSize = 64; const imageSize = 64;
@ -47,8 +42,8 @@ export default class IconService extends EventEmitter implements IIconService {
size: imageSize, size: imageSize,
}); });
} }
this.updateIconMap();// 先存储 ID this.updateIconMap(); // 先存储 ID
imagedata = await this.loadImage(image) as HTMLImageElement; imagedata = (await this.loadImage(image)) as HTMLImageElement;
const iconImage = this.iconData.find((icon: IIcon) => { const iconImage = this.iconData.find((icon: IIcon) => {
return icon.id === id; return icon.id === id;
}); });
@ -58,9 +53,8 @@ export default class IconService extends EventEmitter implements IIconService {
iconImage.height = imagedata.height; iconImage.height = imagedata.height;
} }
this.update(); this.update();
} }
/** /**
* *
* @param id * @param id

View File

@ -12,17 +12,14 @@ import {
import { getMask, PointFillTriangulation } from '@antv/l7-utils'; import { getMask, PointFillTriangulation } from '@antv/l7-utils';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import BaseModel from '../../core/BaseModel'; import BaseModel from '../../core/BaseModel';
import { IPointLayerStyleOptions } from '../../core/interface'; import { IPointLayerStyleOptions, SizeUnitType } from '../../core/interface';
// animate pointLayer shader - support animate // animate pointLayer shader - support animate
import waveFillFrag from '../shaders/animate/wave_frag.glsl'; import waveFillFrag from '../shaders/animate/wave_frag.glsl';
// static pointLayer shader - not support animate // static pointLayer shader - not support animate
import pointFillFrag from '../shaders/fill_frag.glsl'; import pointFillFrag from '../shaders/fill_frag.glsl';
import pointFillVert from '../shaders/fill_vert.glsl'; import pointFillVert from '../shaders/fill_vert.glsl';
import { SizeUnitType } from '../../core/interface'
export default class FillModel extends BaseModel { export default class FillModel extends BaseModel {
public getUninforms(): IModelUniform { public getUninforms(): IModelUniform {
const { const {
opacity = 1, opacity = 1,
@ -100,9 +97,8 @@ export default class FillModel extends BaseModel {
}; };
} }
public getAnimateUniforms(): IModelUniform { public getAnimateUniforms(): IModelUniform {
const { const { animateOption = { enable: false } } =
animateOption = { enable: false }, this.layer.getLayerConfig() as ILayerConfig;
} = this.layer.getLayerConfig() as ILayerConfig;
return { return {
u_animate: this.animateOption2Array(animateOption), u_animate: this.animateOption2Array(animateOption),
u_time: this.layer.getLayerAnimateTime(), u_time: this.layer.getLayerAnimateTime(),
@ -122,10 +118,10 @@ export default class FillModel extends BaseModel {
} }
public async initModels(): Promise<IModel[]> { public async initModels(): Promise<IModel[]> {
return await this.buildModels(); return this.buildModels();
} }
public async buildModels():Promise<IModel[]> { public async buildModels(): Promise<IModel[]> {
const { const {
mask = false, mask = false,
maskInside = true, maskInside = true,
@ -139,32 +135,33 @@ export default class FillModel extends BaseModel {
const { frag, vert, type } = this.getShaders(animateOption); const { frag, vert, type } = this.getShaders(animateOption);
this.layer.triangulation = PointFillTriangulation; this.layer.triangulation = PointFillTriangulation;
const model = await this.layer const model = await this.layer.buildLayerModel({
.buildLayerModel({ moduleName: type,
moduleName: type, vertexShader: vert,
vertexShader: vert, fragmentShader: frag,
fragmentShader: frag, triangulation: PointFillTriangulation,
triangulation: PointFillTriangulation, depth: { enable: false },
depth: { enable: false }, blend: this.getBlend(),
blend: this.getBlend(), stencil: getMask(mask, maskInside),
stencil: getMask(mask, maskInside), workerEnabled,
workerEnabled, workerOptions: {
workerOptions: { modelType: type,
modelType: type, enablePicking,
enablePicking, shape2d,
shape2d, },
}, });
}); return [model];
return [model];
} }
/** /**
* animateOption shader * animateOption shader
* @returns * @returns
*/ */
public getShaders( public getShaders(animateOption: Partial<IAnimateOption>): {
animateOption: Partial<IAnimateOption>, frag: string;
): { frag: string; vert: string; type: string } { vert: string;
type: string;
} {
if (animateOption.enable) { if (animateOption.enable) {
switch (animateOption.type) { switch (animateOption.type) {
case 'wave': case 'wave':
@ -242,9 +239,7 @@ export default class FillModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { size = 5 } = feature; const { size = 5 } = feature;
return Array.isArray(size) ? [size[0]] : [size]; return Array.isArray(size) ? [size[0]] : [size];
}, },
@ -264,9 +259,7 @@ export default class FillModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { shape = 2 } = feature; const { shape = 2 } = feature;
const shapeIndex = shape2d.indexOf(shape as string); const shapeIndex = shape2d.indexOf(shape as string);
return [shapeIndex]; return [shapeIndex];
@ -274,6 +267,4 @@ export default class FillModel extends BaseModel {
}, },
}); });
} }
} }

View File

@ -11,12 +11,11 @@ import {
import { getCullFace, getMask } from '@antv/l7-utils'; import { getCullFace, getMask } from '@antv/l7-utils';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import BaseModel from '../../core/BaseModel'; import BaseModel from '../../core/BaseModel';
import { IPointLayerStyleOptions } from '../../core/interface'; import { IPointLayerStyleOptions, SizeUnitType } from '../../core/interface';
import { PointFillTriangulation } from '../../core/triangulation'; import { PointFillTriangulation } from '../../core/triangulation';
// static pointLayer shader - not support animate // static pointLayer shader - not support animate
import pointFillFrag from '../shaders/image/fillImage_frag.glsl'; import pointFillFrag from '../shaders/image/fillImage_frag.glsl';
import pointFillVert from '../shaders/image/fillImage_vert.glsl'; import pointFillVert from '../shaders/image/fillImage_vert.glsl';
import { SizeUnitType } from '../../core/interface'
export default class FillImageModel extends BaseModel { export default class FillImageModel extends BaseModel {
private meter2coord: number = 1; private meter2coord: number = 1;
@ -132,35 +131,29 @@ export default class FillImageModel extends BaseModel {
); );
} }
public async initModels():Promise<IModel[]> { public async initModels(): Promise<IModel[]> {
this.iconService.on('imageUpdate', this.updateTexture); this.iconService.on('imageUpdate', this.updateTexture);
this.updateTexture(); this.updateTexture();
return await this.buildModels(); return this.buildModels();
} }
public async buildModels(): Promise<IModel[]> {
const { mask = false, maskInside = true } =
public async buildModels():Promise<IModel[]> { this.layer.getLayerConfig() as IPointLayerStyleOptions;
const { const model = await this.layer.buildLayerModel({
mask = false, moduleName: 'pointFillImage',
maskInside = true, vertexShader: pointFillVert,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions; fragmentShader: pointFillFrag,
const model = await this.layer triangulation: PointFillTriangulation,
.buildLayerModel({ depth: { enable: false },
moduleName: 'pointFillImage', blend: this.getBlend(),
vertexShader: pointFillVert, stencil: getMask(mask, maskInside),
fragmentShader: pointFillFrag, cull: {
triangulation: PointFillTriangulation, enable: true,
depth: { enable: false }, face: getCullFace(this.mapService.version),
blend: this.getBlend(), },
stencil: getMask(mask, maskInside), });
cull: { return [model];
enable: true,
face: getCullFace(this.mapService.version),
},
});
return [model]
} }
public clearModels() { public clearModels() {
@ -182,9 +175,7 @@ export default class FillImageModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { rotate = 0 } = feature; const { rotate = 0 } = feature;
return Array.isArray(rotate) ? [rotate[0]] : [rotate as number]; return Array.isArray(rotate) ? [rotate[0]] : [rotate as number];
}, },
@ -202,12 +193,10 @@ export default class FillImageModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 2, size: 2,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const iconMap = this.iconService.getIconMap(); const iconMap = this.iconService.getIconMap();
const { shape } = feature; const { shape } = feature;
const { x, y } = iconMap[shape as string] || { x: 0, y: 0 }; const { x, y } = iconMap[shape as string] || { x: -64, y: -64 };
return [x, y]; return [x, y];
}, },
}, },
@ -256,13 +245,9 @@ export default class FillImageModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { size = 5 } = feature; const { size = 5 } = feature;
return Array.isArray(size) return Array.isArray(size) ? [size[0]] : [size as number];
? [size[0]]
: [(size as number)];
}, },
}, },
}); });

View File

@ -48,21 +48,21 @@ export default class ImageModel extends BaseModel {
this.dataTexture = this.dataTexture =
this.cellLength > 0 && data.length > 0 this.cellLength > 0 && data.length > 0
? this.createTexture2D({ ? this.createTexture2D({
flipY: true, flipY: true,
data, data,
format: gl.LUMINANCE, format: gl.LUMINANCE,
type: gl.FLOAT, type: gl.FLOAT,
width, width,
height, height,
}) })
: this.createTexture2D({ : this.createTexture2D({
flipY: true, flipY: true,
data: [1], data: [1],
format: gl.LUMINANCE, format: gl.LUMINANCE,
type: gl.FLOAT, type: gl.FLOAT,
width: 1, width: 1,
height: 1, height: 1,
}); });
} }
return { return {
u_raisingHeight: Number(raisingHeight), u_raisingHeight: Number(raisingHeight),
@ -83,7 +83,7 @@ export default class ImageModel extends BaseModel {
public async initModels(): Promise<IModel[]> { public async initModels(): Promise<IModel[]> {
this.iconService.on('imageUpdate', this.updateTexture); this.iconService.on('imageUpdate', this.updateTexture);
this.updateTexture(); this.updateTexture();
return await this.buildModels(); return this.buildModels();
} }
public clearModels() { public clearModels() {
@ -93,25 +93,21 @@ export default class ImageModel extends BaseModel {
} }
public async buildModels(): Promise<IModel[]> { public async buildModels(): Promise<IModel[]> {
const { const { mask = false, maskInside = true } =
mask = false, this.layer.getLayerConfig() as IPointLayerStyleOptions;
maskInside = true,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
const model = await this.layer const model = await this.layer.buildLayerModel({
.buildLayerModel({ moduleName: 'pointImage',
moduleName: 'pointImage', vertexShader: pointImageVert,
vertexShader: pointImageVert, fragmentShader: pointImageFrag,
fragmentShader: pointImageFrag, triangulation: PointImageTriangulation,
triangulation: PointImageTriangulation, depth: { enable: false },
depth: { enable: false }, primitive: gl.POINTS,
primitive: gl.POINTS, blend: this.getBlend(),
blend: this.getBlend(), stencil: getMask(mask, maskInside),
stencil: getMask(mask, maskInside), });
});
return [model]
return [model];
} }
protected registerBuiltinAttributes() { protected registerBuiltinAttributes() {
// point layer size; // point layer size;
@ -127,9 +123,7 @@ export default class ImageModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { size = 5 } = feature; const { size = 5 } = feature;
return Array.isArray(size) ? [size[0]] : [size as number]; return Array.isArray(size) ? [size[0]] : [size as number];
}, },
@ -149,12 +143,10 @@ export default class ImageModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 2, size: 2,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const iconMap = this.iconService.getIconMap(); const iconMap = this.iconService.getIconMap();
const { shape } = feature; const { shape } = feature;
const { x, y } = iconMap[shape as string] || { x: 0, y: 0 }; const { x, y } = iconMap[shape as string] || { x: -64, y: -64 }; // 非画布区域,默认的图标改为透明
return [x, y]; return [x, y];
}, },
}, },
@ -172,9 +164,10 @@ export default class ImageModel extends BaseModel {
}); });
// 更新完纹理后在更新的图层的时候需要更新所有的图层 // 更新完纹理后在更新的图层的时候需要更新所有的图层
// this.layer.layerModelNeedUpdate = true; // this.layer.layerModelNeedUpdate = true;
setTimeout(() => { // 延迟渲染 setTimeout(() => {
// 延迟渲染
this.layerService.throttleRenderLayers(); this.layerService.throttleRenderLayers();
}) });
return; return;
} }

View File

@ -12,7 +12,7 @@ import {
getMask, getMask,
padBounds, padBounds,
} from '@antv/l7-utils'; } from '@antv/l7-utils';
import { isNumber } from 'lodash'; import { isEqual, isNumber } from 'lodash';
import BaseModel from '../../core/BaseModel'; import BaseModel from '../../core/BaseModel';
import { IPointLayerStyleOptions } from '../../core/interface'; import { IPointLayerStyleOptions } from '../../core/interface';
import CollisionIndex from '../../utils/collision-index'; import CollisionIndex from '../../utils/collision-index';
@ -102,9 +102,6 @@ export default class TextModel extends BaseModel {
opacity = 1.0, opacity = 1.0,
stroke = '#fff', stroke = '#fff',
strokeWidth = 0, strokeWidth = 0,
textAnchor = 'center',
textOffset,
textAllowOverlap = false,
halo = 0.5, halo = 0.5,
gamma = 2.0, gamma = 2.0,
raisingHeight = 0, raisingHeight = 0,
@ -116,11 +113,7 @@ export default class TextModel extends BaseModel {
this.textCount = Object.keys(mapping).length; this.textCount = Object.keys(mapping).length;
} }
this.preTextStyle = { this.preTextStyle = this.getTextStyle();
textAnchor,
textAllowOverlap,
textOffset,
};
if ( if (
this.dataTextureTest && this.dataTextureTest &&
@ -176,85 +169,96 @@ export default class TextModel extends BaseModel {
u_sdf_map: this.texture, u_sdf_map: this.texture,
u_halo_blur: halo, u_halo_blur: halo,
u_gamma_scale: gamma, u_gamma_scale: gamma,
u_sdf_map_size: [canvas?.width || 1, canvas?.height ||1], u_sdf_map_size: [canvas?.width || 1, canvas?.height || 1],
}; };
} }
public async initModels():Promise<IModel[]> { public async initModels(): Promise<IModel[]> {
// 绑定事件 // 绑定事件
this.bindEvent(); this.bindEvent();
this.extent = this.textExtent(); this.extent = this.textExtent();
const { this.preTextStyle = this.getTextStyle();
textAnchor = 'center', return this.buildModels();
textAllowOverlap = true,
textOffset,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
this.preTextStyle = {
textAnchor,
textAllowOverlap,
textOffset
};
return await this.buildModels();
} }
public async buildModels():Promise<IModel[]> { public async buildModels(): Promise<IModel[]> {
const { const {
mask = false, mask = false,
maskInside = true, maskInside = true,
textAllowOverlap = false textAllowOverlap = false,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions; } = this.layer.getLayerConfig() as IPointLayerStyleOptions;
// this.mapping(); 重复调用
// this.mapping(); 重复调用 this.initGlyph(); //
this.initGlyph(); // this.updateTexture();
this.updateTexture(); if (!textAllowOverlap) {
if(!textAllowOverlap) { this.filterGlyphs();
this.filterGlyphs(); }
} const model = await this.layer.buildLayerModel({
const model = await this.layer moduleName: 'pointText',
.buildLayerModel({ vertexShader: textVert,
moduleName: 'pointText', fragmentShader: textFrag,
vertexShader: textVert, triangulation: TextTriangulation.bind(this),
fragmentShader: textFrag, depth: { enable: false },
triangulation: TextTriangulation.bind(this), blend: this.getBlend(),
depth: { enable: false }, stencil: getMask(mask, maskInside),
blend: this.getBlend(), });
stencil: getMask(mask, maskInside), return [model];
});
return [model]
} }
// 需要更新的场景
public async needUpdate():Promise<boolean> { // 1. 文本偏移量发生改变
// 2. 文本锚点发生改变
// 3. 文本允许重叠发生改变
// 4. 文本字体发生改变
// 5. 文本字体粗细发生改变
public async needUpdate(): Promise<boolean> {
const { const {
textAllowOverlap = false, textAllowOverlap = false,
textAnchor = 'center', textAnchor = 'center',
textOffset textOffset,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions; padding,
const data = this.layer.getEncodedData(); fontFamily,
if(JSON.stringify(textOffset) !==JSON.stringify(this.preTextStyle.textOffset) ||textAnchor!==this.preTextStyle.textAnchor ) { fontWeight,
} = this.getTextStyle() as IPointLayerStyleOptions;
if (
!isEqual(padding, this.preTextStyle.padding) ||
!isEqual(textOffset, this.preTextStyle.textOffset) ||
!isEqual(textAnchor, this.preTextStyle.textAnchor) ||
!isEqual(fontFamily, this.preTextStyle.fontFamily) ||
!isEqual(fontWeight, this.preTextStyle.fontWeight)
) {
await this.mapping(); await this.mapping();
return true; return true;
} }
if(data.length < 5 || textAllowOverlap) { // 小于不做避让
// if (
// JSON.stringify(textOffset) !==
// JSON.stringify(this.preTextStyle.textOffset) ||
// textAnchor !== this.preTextStyle.textAnchor
// ) {
// await this.mapping();
// return true;
// }
if (textAllowOverlap) {
// 小于不做避让
return false; return false;
} }
// textAllowOverlap 发生改变 // textAllowOverlap 发生改变
const zoom = this.mapService.getZoom(); const zoom = this.mapService.getZoom();
const extent = this.mapService.getBounds(); const extent = this.mapService.getBounds();
const flag = boundsContains(this.extent, extent); const flag = boundsContains(this.extent, extent);
// 文本不能压盖则进行过滤 // 文本不能压盖则进行过滤
if ( if (
((Math.abs(this.currentZoom - zoom) > 1 || !flag)) || Math.abs(this.currentZoom - zoom) > 0.5 ||
!flag ||
textAllowOverlap !== this.preTextStyle.textAllowOverlap textAllowOverlap !== this.preTextStyle.textAllowOverlap
) { ) {
// TODO this.mapping 数据未变化,避让 // TODO this.mapping 数据未变化,避让
await this.reBuildModel(); await this.reBuildModel();
return true; return true;
} }
return false; return false;
} }
@ -277,9 +281,7 @@ export default class TextModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { rotate = 0 } = feature; const { rotate = 0 } = feature;
return Array.isArray(rotate) ? [rotate[0]] : [rotate as number]; return Array.isArray(rotate) ? [rotate[0]] : [rotate as number];
}, },
@ -320,9 +322,7 @@ export default class TextModel extends BaseModel {
type: gl.FLOAT, type: gl.FLOAT,
}, },
size: 1, size: 1,
update: ( update: (feature: IEncodeFeature) => {
feature: IEncodeFeature,
) => {
const { size = 12 } = feature; const { size = 12 } = feature;
return Array.isArray(size) ? [size[0]] : [size as number]; return Array.isArray(size) ? [size[0]] : [size as number];
}, },
@ -352,17 +352,17 @@ export default class TextModel extends BaseModel {
} }
private bindEvent() { private bindEvent() {
if(!this.layer.isTileLayer) { if (!this.layer.isTileLayer) {
// 重新绑定 // 重新绑定
this.layer.on('remapping', this.mapping); this.layer.on('remapping', this.mapping);
} }
} }
private mapping = async(): Promise<void> =>{ private mapping = async (): Promise<void> => {
this.initGlyph(); // this.initGlyph(); //
this.updateTexture(); this.updateTexture();
await this.reBuildModel(); await this.reBuildModel();
} };
private textExtent(): [[number, number], [number, number]] { private textExtent(): [[number, number], [number, number]] {
const bounds = this.mapService.getBounds(); const bounds = this.mapService.getBounds();
@ -372,10 +372,7 @@ export default class TextModel extends BaseModel {
* *
*/ */
private initTextFont() { private initTextFont() {
const { const { fontWeight, fontFamily } = this.getTextStyle();
fontWeight = '400',
fontFamily = 'sans-serif',
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
const data = this.layer.getEncodedData(); const data = this.layer.getEncodedData();
const characterSet: string[] = []; const characterSet: string[] = [];
data.forEach((item: IEncodeFeature) => { data.forEach((item: IEncodeFeature) => {
@ -400,10 +397,7 @@ export default class TextModel extends BaseModel {
* iconfont * iconfont
*/ */
private initIconFontTex() { private initIconFontTex() {
const { const { fontWeight, fontFamily } = this.getTextStyle();
fontWeight = '400',
fontFamily = 'sans-serif',
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
const data = this.layer.getEncodedData(); const data = this.layer.getEncodedData();
const characterSet: string[] = []; const characterSet: string[] = [];
data.forEach((item: IEncodeFeature) => { data.forEach((item: IEncodeFeature) => {
@ -421,6 +415,33 @@ export default class TextModel extends BaseModel {
}); });
} }
private getTextStyle() {
const {
fontWeight = '400',
fontFamily = 'sans-serif',
textAllowOverlap = false,
padding = [0, 0],
textAnchor = 'center',
textOffset = [0, 0],
opacity = 1,
strokeOpacity = 1,
strokeWidth = 0,
stroke = '#000',
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
return {
fontWeight,
fontFamily,
textAllowOverlap,
padding,
textAnchor,
textOffset,
opacity,
strokeOpacity,
strokeWidth,
stroke,
};
}
/** /**
* *
*/ */
@ -435,7 +456,7 @@ export default class TextModel extends BaseModel {
const data = this.layer.getEncodedData(); const data = this.layer.getEncodedData();
this.glyphInfo = data.map((feature: IEncodeFeature) => { this.glyphInfo = data.map((feature: IEncodeFeature) => {
const { shape = '', id, size = 1, } = feature; const { shape = '', id, size = 1 } = feature;
const shaping = shapeText( const shaping = shapeText(
shape.toString(), shape.toString(),
@ -445,7 +466,7 @@ export default class TextModel extends BaseModel {
textAnchor, textAnchor,
'left', 'left',
spacing, spacing,
textOffset || feature.textOffset || [0,0], textOffset || feature.textOffset || [0, 0],
iconfont, iconfont,
); );
const glyphQuads = getGlyphQuads(shaping, textOffset, false); const glyphQuads = getGlyphQuads(shaping, textOffset, false);
@ -473,13 +494,10 @@ export default class TextModel extends BaseModel {
* depend on originCentorid * depend on originCentorid
*/ */
private filterGlyphs() { private filterGlyphs() {
const { const { padding = [0, 0], textAllowOverlap = false } =
padding = [0, 0], this.layer.getLayerConfig() as IPointLayerStyleOptions;
textAllowOverlap = false,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
if (textAllowOverlap) { if (textAllowOverlap) {
// 如果允许文本覆盖 // 如果允许文本覆盖
// this.layer.setEncodedData(this.glyphInfo);
return; return;
} }
this.glyphInfoMap = {}; this.glyphInfoMap = {};
@ -491,9 +509,11 @@ export default class TextModel extends BaseModel {
const { shaping, id = 0 } = feature; const { shaping, id = 0 } = feature;
// const centroid = feature.centroid as [number, number]; // const centroid = feature.centroid as [number, number];
// const centroid = feature.originCentroid as [number, number]; // const centroid = feature.originCentroid as [number, number];
const centroid = (feature.version === 'GAODE2.x' const centroid = (
? feature.originCentroid feature.version === 'GAODE2.x'
: feature.centroid) as [number, number]; ? feature.originCentroid
: feature.centroid
) as [number, number];
const size = feature.size as number; const size = feature.size as number;
const fontScale: number = size / 16; const fontScale: number = size / 16;
const pixels = this.mapService.lngLatToContainer(centroid); const pixels = this.mapService.lngLatToContainer(centroid);
@ -525,8 +545,6 @@ export default class TextModel extends BaseModel {
const { iconfont = false } = this.layer.getLayerConfig(); const { iconfont = false } = this.layer.getLayerConfig();
// 1.生成文字纹理(或是生成 iconfont // 1.生成文字纹理(或是生成 iconfont
iconfont ? this.initIconFontTex() : this.initTextFont(); iconfont ? this.initIconFontTex() : this.initTextFont();
// this.initTextFont();
// 2.生成文字布局 // 2.生成文字布局
this.generateGlyphLayout(iconfont); this.generateGlyphLayout(iconfont);
} }
@ -550,22 +568,19 @@ export default class TextModel extends BaseModel {
} }
private async reBuildModel() { private async reBuildModel() {
const { const { mask = false, maskInside = true } =
mask = false, this.layer.getLayerConfig() as IPointLayerStyleOptions;
maskInside = true, this.filterGlyphs();
} = this.layer.getLayerConfig() as IPointLayerStyleOptions; const model = await this.layer.buildLayerModel({
this.filterGlyphs(); moduleName: 'pointText',
const model = await this.layer vertexShader: textVert,
.buildLayerModel({ fragmentShader: textFrag,
moduleName: 'pointText', triangulation: TextTriangulation.bind(this),
vertexShader: textVert, depth: { enable: false },
fragmentShader: textFrag, blend: this.getBlend(),
triangulation: TextTriangulation.bind(this), stencil: getMask(mask, maskInside),
depth: { enable: false }, });
blend: this.getBlend(), // TODO 渲染流程待修改
stencil: getMask(mask, maskInside), this.layer.models = [model];
});
// TODO 渲染流程待修改
this.layer.models = [model];
} }
} }

View File

@ -212,7 +212,7 @@ layer.scale('value'); // L7 能够自动推断为 identify
```ts ```ts
pointLayer.size('type', (type) => { pointLayer.filter('type', (type) => {
// 回调函数 // 回调函数
if (type === 'a') { if (type === 'a') {
return false; return false;

View File

@ -1,133 +1,159 @@
import { IRasterData } from '../../interface'; import { IRasterData } from '../../interface';
/** /**
* * * Math operators: * * * Math operators:
* `['*', value1, value2]` * `['*', value1, value2]`
* `['/', value1, value2]` * `['/', value1, value2]`
* `['+', value1, value2]` * `['+', value1, value2]`
* `['-', value1, value2]` * `['-', value1, value2]`
* `['%', value1, value2]` * `['%', value1, value2]`
* `['^', value1, value2]` * `['^', value1, value2]`
* `['abs', value1]` * `['abs', value1]`
* `['floor', value1]` * `['floor', value1]`
* `['round', value1]` * `['round', value1]`
* `['ceil', value1]` * `['ceil', value1]`
* `['sin', value1]` * `['sin', value1]`
* `['cos', value1]` * `['cos', value1]`
* `['atan', value1, value2]` * `['atan', value1, value2]`
*/ */
export function mathematical(symbol: string, n1: number, n2: number) { export function mathematical(symbol: string, n1: number, n2: number) {
switch(symbol) { switch (symbol) {
case '+': return n1 + n2; case '+':
case '-': return n1 - n2; return n1 + n2;
case '*': return n1 * n2; case '-':
case '/': return n1 / n2; return n1 - n2;
case '%': return n1 % n2; case '*':
return n1 * n2;
case '/':
return n1 / n2;
case '%':
return n1 % n2;
case '^': return Math.pow(n1, n2); case '^':
case 'abs': return Math.abs(n1); return Math.pow(n1, n2);
case 'floor': return Math.floor(n1); case 'abs':
case 'round': return Math.round(n1); return Math.abs(n1);
case 'ceil': return Math.ceil(n1); case 'floor':
case 'sin': return Math.sin(n1); return Math.floor(n1);
case 'cos': return Math.cos(n1); case 'round':
case 'atan': return (n2 === -1) ? Math.atan(n1): Math.atan2(n1, n2); return Math.round(n1);
case 'ceil':
default: return Math.ceil(n1);
console.warn('Calculate symbol err! Return default 0'); case 'sin':
return 0; return Math.sin(n1);
} case 'cos':
return Math.cos(n1);
case 'atan':
return n2 === -1 ? Math.atan(n1) : Math.atan2(n1, n2);
case 'min':
return Math.min(n1, n2);
case 'max':
return Math.max(n1, n2);
case 'log10':
return Math.log(n1);
case 'log2':
return Math.log2(n1);
default:
console.warn('Calculate symbol err! Return default 0');
return 0;
}
} }
/** /**
* *
* @param express * @param express
* @param bandsData * @param bandsData
*/ */
export function calculate(express: any[], bandsData: IRasterData[]) { export function calculate(express: any[], bandsData: IRasterData[]) {
const {width, height} = bandsData[0]; const { width, height } = bandsData[0];
const dataArray = bandsData.map(band => band.rasterData) as Uint8Array[]; const dataArray = bandsData.map((band) => band.rasterData) as Uint8Array[];
const length = width * height; const length = width * height;
const rasterData = []; const rasterData = [];
const originExp = JSON.stringify(express); const originExp = JSON.stringify(express);
for(let i = 0;i < length; i++) { for (let i = 0; i < length; i++) {
const exp = JSON.parse(originExp); const exp = JSON.parse(originExp);
// 将表达式中的 ['band', 0]、['band', 1] 等替换为实际的栅格数据 // 将表达式中的 ['band', 0]、['band', 1] 等替换为实际的栅格数据
const expResult = spellExpress(exp, dataArray, i); const expResult = spellExpress(exp, dataArray, i);
if(typeof expResult === 'number') { if (typeof expResult === 'number') {
// exp: ['band', 0] => exp: 2 ... // exp: ['band', 0] => exp: 2 ...
// exp 直接指定了波段值,替换完后直接就是数值了,无需计算 // exp 直接指定了波段值,替换完后直接就是数值了,无需计算
rasterData.push(expResult); rasterData.push(expResult);
} else { } else {
const result = calculateExpress(exp); const result = calculateExpress(exp);
rasterData.push(result); rasterData.push(result);
}
} }
return rasterData as unknown as Uint8Array; }
return rasterData as unknown as Uint8Array;
} }
type IExpress = any[]; type IExpress = any[];
/** /**
* *
* @param express * @param express
* @param dataArray * @param dataArray
* @param index * @param index
*/ */
export function spellExpress(express: IExpress, dataArray: Uint8Array[], index: number) { export function spellExpress(
/** express: IExpress,
* dataArray: Uint8Array[],
*/ index: number,
if(express.length === 2 && express[0] === 'band' && typeof express[1] === 'number') { ) {
try { /**
return dataArray[express[1]][index]; *
} catch(err) { */
console.warn('Raster Data err!'); if (
return 0; express.length === 2 &&
} express[0] === 'band' &&
typeof express[1] === 'number'
) {
try {
return dataArray[express[1]][index];
} catch (err) {
console.warn('Raster Data err!');
return 0;
} }
express.map((e, i) => { }
if(Array.isArray(e) && e.length > 0) { express.map((e, i) => {
switch(e[0]) { if (Array.isArray(e) && e.length > 0) {
case 'band': switch (e[0]) {
try { case 'band':
express[i] = dataArray[e[1]][index]; try {
} catch(err) { express[i] = dataArray[e[1]][index];
console.warn('Raster Data err!'); } catch (err) {
express[i] = 0; console.warn('Raster Data err!');
} express[i] = 0;
break; }
default: break;
spellExpress(e, dataArray, index); default:
} spellExpress(e, dataArray, index);
} }
}) }
});
} }
export function formatExpress(express: IExpress) { export function formatExpress(express: IExpress) {
const [symbol1, symbol2 = -1, symbol3 = -1] = express; const [symbol1, symbol2 = -1, symbol3 = -1] = express;
if(symbol1 === undefined) { if (symbol1 === undefined) {
console.warn('Express err!') console.warn('Express err!');
return ['+', 0, 0]; return ['+', 0, 0];
} }
const symbol = symbol1.replace(/\s+/g, ''); const symbol = symbol1.replace(/\s+/g, '');
return [symbol, symbol2, symbol3]; return [symbol, symbol2, symbol3];
} }
export function calculateExpress(express: IExpress) { export function calculateExpress(express: IExpress) {
const formatExp = formatExpress(express); const formatExp = formatExpress(express);
const str = formatExp[0]; const str = formatExp[0];
let left = formatExp[1]; let left = formatExp[1];
let right = formatExp[2]; let right = formatExp[2];
if(Array.isArray(left)) { if (Array.isArray(left)) {
left = calculateExpress(express[1]); left = calculateExpress(express[1]);
} }
if(Array.isArray(right)) { if (Array.isArray(right)) {
right = calculateExpress(express[2]); right = calculateExpress(express[2]);
} }
return mathematical(str, left, right); return mathematical(str, left, right);
} }

View File

@ -2,7 +2,7 @@ import * as d3 from 'd3-color';
import { Context } from 'vm'; import { Context } from 'vm';
import { $window, isMini } from './mini-adapter'; import { $window, isMini } from './mini-adapter';
export interface IColorRamp { export interface IColorRamp {
type?: 'cat' | 'linear' | 'quantize' | 'custom' type?: 'cat' | 'linear' | 'quantize' | 'custom';
positions: number[]; positions: number[];
colors: string[]; colors: string[];
} }
@ -74,7 +74,6 @@ export function generateColorRamp(
ctx.fillStyle = gradient; ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1); ctx.fillRect(0, 0, 256, 1);
if (!isMini) { if (!isMini) {
data = ctx.getImageData(0, 0, 256, 1).data; data = ctx.getImageData(0, 0, 256, 1).data;
// 使用 createImageData 替代 new ImageData、兼容 IE11 // 使用 createImageData 替代 new ImageData、兼容 IE11
@ -114,8 +113,7 @@ export function generateLinearRamp(
const step = domain[1] - domain[0]; const step = domain[1] - domain[0];
for (let i = 0; i < colorRamp.colors.length; ++i) { for (let i = 0; i < colorRamp.colors.length; ++i) {
const value = Math.max((colorRamp.positions[i] - domain[0]) / step,0); const value = Math.max((colorRamp.positions[i] - domain[0]) / step, 0);
console.log(value)
gradient.addColorStop(value, colorRamp.colors[i]); gradient.addColorStop(value, colorRamp.colors[i]);
} }
ctx.fillStyle = gradient; ctx.fillStyle = gradient;
@ -127,16 +125,11 @@ export function generateLinearRamp(
canvas = null; canvas = null;
// @ts-ignore // @ts-ignore
ctx = null; ctx = null;
return imageData return imageData;
} }
// 枚举类型 // 枚举类型
export function generateCatRamp( export function generateCatRamp(colorRamp: IColorRamp): ImageData | IImagedata {
colorRamp: IColorRamp,
): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas'); let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D; let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.width = 256; canvas.width = 256;
@ -144,12 +137,12 @@ export function generateCatRamp(
const imageData = ctx.createImageData(256, 1); const imageData = ctx.createImageData(256, 1);
imageData.data.fill(0); imageData.data.fill(0);
colorRamp.positions.forEach((p: number, index: number) => { colorRamp.positions.forEach((p: number, index: number) => {
const colorArray = rgb2arr(colorRamp.colors[index]) const colorArray = rgb2arr(colorRamp.colors[index]);
imageData.data[p * 4 + 0] = colorArray[0] * 255; imageData.data[p * 4 + 0] = colorArray[0] * 255;
imageData.data[p * 4 + 1] = colorArray[1] * 255; imageData.data[p * 4 + 1] = colorArray[1] * 255;
imageData.data[p * 4 + 2] = colorArray[2] * 255; imageData.data[p * 4 + 2] = colorArray[2] * 255;
imageData.data[p * 4 + 3] = colorArray[3] * 255; imageData.data[p * 4 + 3] = colorArray[3] * 255;
}) });
// @ts-ignore // @ts-ignore
canvas = null; canvas = null;
// @ts-ignore // @ts-ignore
@ -163,10 +156,10 @@ export function generateQuantizeRamp(
): ImageData | IImagedata { ): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas'); let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D; let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx.globalAlpha = 1.0 ctx.globalAlpha = 1.0;
canvas.width = 256; canvas.width = 256;
canvas.height = 1; canvas.height = 1;
const step = 256 / colorRamp.colors.length;// TODO 精度问题 const step = 256 / colorRamp.colors.length; // TODO 精度问题
// draw linear color // draw linear color
for (let i = 0; i < colorRamp.colors.length; i++) { for (let i = 0; i < colorRamp.colors.length; i++) {
ctx.beginPath(); ctx.beginPath();
@ -176,14 +169,12 @@ export function generateQuantizeRamp(
ctx.moveTo(i * step, 0); // positioned at 50,25 ctx.moveTo(i * step, 0); // positioned at 50,25
ctx.lineTo((i + 1) * step, 0); ctx.lineTo((i + 1) * step, 0);
ctx.stroke(); ctx.stroke();
} }
const data = ctx.getImageData(0, 0, 256, 1).data; const data = ctx.getImageData(0, 0, 256, 1).data;
// 使用 createImageData 替代 new ImageData、兼容 IE11 // 使用 createImageData 替代 new ImageData、兼容 IE11
const imageData = toIEIMageData(ctx, data); const imageData = toIEIMageData(ctx, data);
// @ts-ignore // @ts-ignore
canvas = null; canvas = null;
// @ts-ignore // @ts-ignore
@ -197,25 +188,25 @@ export function generateCustomRamp(
colorRamp: IColorRamp, colorRamp: IColorRamp,
domain: [number, number], domain: [number, number],
): ImageData | IImagedata { ): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas'); let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D; let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx.globalAlpha = 1.0 ctx.globalAlpha = 1.0;
canvas.width = 256; canvas.width = 256;
canvas.height = 1; canvas.height = 1;
const step = domain[1] - domain[0]; const step = domain[1] - domain[0];
if(colorRamp.positions.length - colorRamp.colors.length !==1) { if (colorRamp.positions.length - colorRamp.colors.length !== 1) {
console.warn('positions 的数字个数应当比 colors 的样式多一个,poisitions 的首尾值一般为数据的最大最新值') console.warn(
'positions 的数字个数应当比 colors 的样式多一个,poisitions 的首尾值一般为数据的最大最新值',
);
} }
for (let i = 0; i < colorRamp.colors.length; i++) { for (let i = 0; i < colorRamp.colors.length; i++) {
ctx.beginPath(); ctx.beginPath();
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.strokeStyle = colorRamp.colors[i]; ctx.strokeStyle = colorRamp.colors[i];
ctx.moveTo((colorRamp.positions[i] - domain[0]) / step * 255, 0); // positioned at 50,25 ctx.moveTo(((colorRamp.positions[i] - domain[0]) / step) * 255, 0); // positioned at 50,25
ctx.lineTo((colorRamp.positions[i + 1]- domain[0]) / step * 255, 0); ctx.lineTo(((colorRamp.positions[i + 1] - domain[0]) / step) * 255, 0);
ctx.stroke(); ctx.stroke();
} }
const data = ctx.getImageData(0, 0, 256, 1).data; const data = ctx.getImageData(0, 0, 256, 1).data;
const imageData = toIEIMageData(ctx, data); const imageData = toIEIMageData(ctx, data);
@ -233,15 +224,15 @@ function toIEIMageData(ctx: Context, data: Uint8ClampedArray) {
imageData.data[i + 2] = data[i + 2]; imageData.data[i + 2] = data[i + 2];
imageData.data[i + 3] = data[i + 3]; imageData.data[i + 3] = data[i + 3];
} }
return imageData return imageData;
} }
export function getDefaultDomain(rampColors:IColorRamp) { export function getDefaultDomain(rampColors: IColorRamp) {
switch (rampColors.type) { switch (rampColors.type) {
case 'cat' : case 'cat':
return [0,255] return [0, 255];
default: default:
[0,1] [0, 1];
} }
}
}

View File

@ -1,22 +1,19 @@
// @ts-ignore // @ts-ignore
export { djb2hash, BKDRHash } from './hash';
import * as DOM from './dom';
import * as Satistics from './statistics';
export { DOM, Satistics };
export * from './mini-adapter/index';
export * from './ajax'; export * from './ajax';
export * from './geo';
export * from './lru_cache';
export * from './event';
export * from './color';
export * from './anchor'; export * from './anchor';
export * from './stencli'; export * from './color';
export * from './worker-helper';
export * from './cull'; export * from './cull';
export * as DOM from './dom';
export * from './env'; export * from './env';
export * from './tileset-manager'; export * from './event';
export * from './workers/triangulation'; export * from './geo';
export { BKDRHash, djb2hash } from './hash';
export * from './lineAtOffset'; export * from './lineAtOffset';
export * from './lru_cache';
export * from './mini-adapter/index';
export * as Satistics from './statistics';
export * from './stencli';
export * from './tileset-manager';
export * from './worker-helper';
export * from './workers/triangulation';