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',
);
const data = await response.json();
const newData = data.map((item: any) => {
item.type = ['00', '01', '02'][Math.floor(Math.random() * 3)];
const newData = data.map((item: any,index: number) => {
item.type = ['00', '01', '02',''][index % 4];
return item;
});
const imageLayer = new PointLayer({
autoFit:false
})
.source(newData, {
.source(newData.slice(0,4), {
parser: {
type: 'json',
x: 'longitude',
@ -52,25 +52,9 @@ export default () => {
})
.active(false)
.size(20);
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() {
return 'key';
const {
fontFamily,
fontWeight,
fontSize,
buffer,
sdf,
radius,
cutoff,
} = this.fontOptions;
if (sdf) {
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer} ${radius} ${cutoff} `;
}
return `${fontFamily} ${fontWeight} ${fontSize} ${buffer}`;
const { fontFamily, fontWeight } = this.fontOptions;
return `${fontFamily}_${fontWeight}`;
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,19 @@
// @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 './geo';
export * from './lru_cache';
export * from './event';
export * from './color';
export * from './anchor';
export * from './stencli';
export * from './worker-helper';
export * from './color';
export * from './cull';
export * as DOM from './dom';
export * from './env';
export * from './tileset-manager';
export * from './workers/triangulation';
export * from './event';
export * from './geo';
export { BKDRHash, djb2hash } from './hash';
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';