feat: 新增支持多渐变色多线图层 (#1086)

* chore: update version 2.8.30 -> 2.8.31

* feat: 新增多渐变色线图层

* style: lint style
This commit is contained in:
YiQianYao 2022-04-27 19:10:08 +08:00 committed by GitHub
parent c40518677d
commit 061935a1ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 448 additions and 46 deletions

View File

@ -43,6 +43,8 @@ export interface ILineLayerStyleOptions {
maskInside?: boolean; // 可选参数 控制图层是否显示在蒙层的内部
arrow?: ILineArrow;
rampColors?: IColorRamp;
}
export interface IPointLayerStyleOptions {

View File

@ -29,6 +29,7 @@ export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
const type = this.getModelType();
const defaultConfig = {
line: {},
linearline: {},
simple: {},
wall: {},
arc3d: { blend: 'additive' },

View File

@ -3,6 +3,7 @@ import Arc3DModel from './arc_3d';
import ArcMiniModel from './arcmini';
import GreatCircleModel from './great_circle';
import LineModel from './line';
import LinearLine from './linearline';
import SimpleLineModel from './simpleLine';
import LineWallModel from './wall';
@ -13,7 +14,8 @@ export type LineModelType =
| 'greatcircle'
| 'wall'
| 'simple'
| 'line';
| 'line'
| 'linearline';
const LineModels: { [key in LineModelType]: any } = {
arc: ArcModel,
@ -23,6 +25,7 @@ const LineModels: { [key in LineModelType]: any } = {
wall: LineWallModel,
line: LineModel,
simple: SimpleLineModel,
linearline: LinearLine,
};
export default LineModels;

View File

@ -0,0 +1,269 @@
import {
AttributeType,
gl,
IAnimateOption,
IEncodeFeature,
IImage,
ILayerConfig,
IModel,
IModelUniform,
ITexture2D,
} from '@antv/l7-core';
import { generateColorRamp, getMask, IColorRamp } from '@antv/l7-utils';
import { isNumber } from 'lodash';
import BaseModel from '../../core/BaseModel';
import { ILineLayerStyleOptions } from '../../core/interface';
import { LineTriangulation } from '../../core/triangulation';
import linear_line_frag from '../shaders/linearLine/line_linear_frag.glsl';
import linear_line_vert from '../shaders/linearLine/line_linear_vert.glsl';
export default class LinearLineModel extends BaseModel {
protected colorTexture: ITexture2D;
public getUninforms(): IModelUniform {
const {
opacity,
vertexHeightScale = 20.0,
raisingHeight = 0,
heightfixed = false,
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
if (this.rendererService.getDirty()) {
this.colorTexture.bind();
}
if (this.dataTextureTest && this.dataTextureNeedUpdate({ opacity })) {
this.judgeStyleAttributes({ opacity });
const encodeData = this.layer.getEncodedData();
const { data, width, height } = this.calDataFrame(
this.cellLength,
encodeData,
this.cellProperties,
);
this.rowCount = height; // 当前数据纹理有多少行
this.dataTexture =
this.cellLength > 0 && data.length > 0
? this.createTexture2D({
flipY: true,
data,
format: gl.LUMINANCE,
type: gl.FLOAT,
width,
height,
})
: this.createTexture2D({
flipY: true,
data: [1],
format: gl.LUMINANCE,
type: gl.FLOAT,
width: 1,
height: 1,
});
}
return {
u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1]
u_cellTypeLayout: this.getCellTypeLayout(),
u_opacity: isNumber(opacity) ? opacity : 1.0,
// 纹理支持参数
u_texture: this.colorTexture, // 贴图
// 是否固定高度
u_heightfixed: Number(heightfixed),
// 顶点高度 scale
u_vertexScale: vertexHeightScale,
u_raisingHeight: Number(raisingHeight),
};
}
public initModels(): IModel[] {
this.updateTexture();
return this.buildModels();
}
public clearModels() {
this.colorTexture?.destroy();
this.dataTexture?.destroy();
}
public buildModels(): IModel[] {
const {
mask = false,
maskInside = true,
depth = false,
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
const { frag, vert, type } = this.getShaders();
this.layer.triangulation = LineTriangulation;
return [
this.layer.buildLayerModel({
moduleName: 'line_' + type,
vertexShader: vert,
fragmentShader: frag,
triangulation: LineTriangulation,
primitive: gl.TRIANGLES,
blend: this.getBlend(),
depth: { enable: depth },
stencil: getMask(mask, maskInside),
}),
];
}
/**
* shader
* @returns
*/
public getShaders(): { frag: string; vert: string; type: string } {
return {
frag: linear_line_frag,
vert: linear_line_vert,
type: 'linear_rampColors',
};
}
protected registerBuiltinAttributes() {
this.styleAttributeService.registerStyleAttribute({
name: 'distanceAndIndex',
type: AttributeType.Attribute,
descriptor: {
name: 'a_DistanceAndIndex',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 2,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
normal: number[],
vertexIndex?: number,
) => {
return vertexIndex === undefined
? [vertex[3], 10]
: [vertex[3], vertexIndex];
},
},
});
this.styleAttributeService.registerStyleAttribute({
name: 'total_distance',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Total_Distance',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 1,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
return [vertex[5]];
},
},
});
this.styleAttributeService.registerStyleAttribute({
name: 'size',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Size',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 2,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
const { size = 1 } = feature;
return Array.isArray(size) ? [size[0], size[1]] : [size as number, 0];
},
},
});
// point layer size;
this.styleAttributeService.registerStyleAttribute({
name: 'normal',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Normal',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 3,
// @ts-ignore
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
normal: number[],
) => {
return normal;
},
},
});
this.styleAttributeService.registerStyleAttribute({
name: 'miter',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Miter',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.STATIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 1,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
return [vertex[4]];
},
},
});
}
private updateTexture = () => {
const { createTexture2D } = this.rendererService;
if (this.colorTexture) {
this.colorTexture.destroy();
}
const {
rampColors,
} = this.layer.getLayerConfig() as ILineLayerStyleOptions;
const imageData = generateColorRamp(rampColors as IColorRamp);
this.colorTexture = createTexture2D({
data: new Uint8Array(imageData.data),
width: imageData.width,
height: imageData.height,
wrapS: gl.CLAMP_TO_EDGE,
wrapT: gl.CLAMP_TO_EDGE,
min: gl.NEAREST,
mag: gl.NEAREST,
flipY: false,
});
};
}

View File

@ -0,0 +1,16 @@
uniform float u_opacity : 1.0;
uniform sampler2D u_texture;
#pragma include "picking"
varying mat4 styleMappingMat;
void main() {
float opacity = styleMappingMat[0][0];
float d_distance_ratio = styleMappingMat[3].r; // 当前点位距离占线总长的比例
gl_FragColor = texture2D(u_texture, vec2(d_distance_ratio, 0.5));
gl_FragColor.a *= opacity; // 全局透明度
gl_FragColor = filterColor(gl_FragColor);
}

View File

@ -0,0 +1,111 @@
attribute float a_Miter;
attribute vec2 a_Size;
attribute vec3 a_Normal;
attribute vec3 a_Position;
// dash line
attribute float a_Total_Distance;
attribute vec2 a_DistanceAndIndex;
uniform mat4 u_ModelMatrix;
uniform mat4 u_Mvp;
uniform float u_heightfixed: 0.0;
uniform float u_vertexScale: 1.0;
uniform float u_raisingHeight: 0.0;
uniform float u_opacity: 1.0;
#pragma include "projection"
#pragma include "picking"
varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样式值传递给片元
#pragma include "styleMapping"
#pragma include "styleMappingCalOpacity"
void main() {
// cal style mapping - 数据纹理映射部分的计算
styleMappingMat = mat4(
0.0, 0.0, 0.0, 0.0, // opacity - strokeOpacity - strokeWidth - empty
0.0, 0.0, 0.0, 0.0, // strokeR - strokeG - strokeB - strokeA
0.0, 0.0, 0.0, 0.0, // offsets[0] - offsets[1]
0.0, 0.0, 0.0, 0.0 // distance_ratio/distance/pixelLen/texV
);
float rowCount = u_cellTypeLayout[0][0]; // 当前的数据纹理有几行
float columnCount = u_cellTypeLayout[0][1]; // 当看到数据纹理有几列
float columnWidth = 1.0/columnCount; // 列宽
float rowHeight = 1.0/rowCount; // 行高
float cellCount = calCellCount(); // opacity - strokeOpacity - strokeWidth - stroke - offsets
float id = a_vertexId; // 第n个顶点
float cellCurrentRow = floor(id * cellCount / columnCount) + 1.0; // 起始点在第几行
float cellCurrentColumn = mod(id * cellCount, columnCount) + 1.0; // 起始点在第几列
// cell 固定顺序 opacity -> strokeOpacity -> strokeWidth -> stroke ...
// 按顺序从 cell 中取值、若没有则自动往下取值
float textureOffset = 0.0; // 在 cell 中取值的偏移量
vec2 opacityAndOffset = calOpacityAndOffset(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset, columnWidth, rowHeight);
styleMappingMat[0][0] = opacityAndOffset.r;
textureOffset = opacityAndOffset.g;
// cal style mapping - 数据纹理映射部分的计算
vec3 size = a_Miter * setPickingSize(a_Size.x) * reverse_offset_normal(a_Normal);
vec2 offset = project_pixel(size.xy);
float lineDistance = a_DistanceAndIndex.x;
float currentLinePointRatio = lineDistance / a_Total_Distance;
float lineOffsetWidth = length(offset + offset * sign(a_Miter)); // 线横向偏移的距离(向两侧偏移的和)
float linePixelSize = project_pixel(a_Size.x) * 2.0; // 定点位置偏移,按地图等级缩放后的距离 单侧 * 2
float texV = lineOffsetWidth/linePixelSize; // 线图层贴图部分的 v 坐标值
// 设置数据集的参数
styleMappingMat[3][0] = currentLinePointRatio; // 当前点位距离占线总长的比例
vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0));
// gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, a_Size.y, 1.0));
float h = float(a_Position.z) * u_vertexScale; // 线顶点的高度 - 兼容不存在第三个数值的情况 vertex height
float lineHeight = a_Size.y; // size 第二个参数代表的高度 [linewidth, lineheight]
if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
lineHeight *= 0.2; // 保持和 amap/mapbox 一致的效果
h *= 0.2;
if(u_heightfixed < 1.0) {
lineHeight = project_pixel(a_Size.y);
}
gl_Position = u_Mvp * (vec4(project_pos.xy + offset, lineHeight + h + u_raisingHeight, 1.0));
} else {
// mapbox - amap
// 兼容 mapbox 在线高度上的效果表现基本一致
if(u_CoordinateSystem == COORDINATE_SYSTEM_LNGLAT || u_CoordinateSystem == COORDINATE_SYSTEM_LNGLAT_OFFSET) {
// mapbox
// 保持高度相对不变
float mapboxZoomScale = 4.0/pow(2.0, 21.0 - u_Zoom);
h *= mapboxZoomScale;
h += u_raisingHeight * mapboxZoomScale;
if(u_heightfixed > 0.0) {
lineHeight *= mapboxZoomScale;
}
} else {
// amap
h += u_raisingHeight;
// lineHeight 顶点偏移高度
if(u_heightfixed < 1.0) {
lineHeight *= pow(2.0, 20.0 - u_Zoom);
}
}
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, lineHeight + h, 1.0));
}
setPickingColor(a_PickingColor);
}

View File

@ -1,3 +1,4 @@
// @ts-ignore
import { LineLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as React from 'react';
@ -14,10 +15,8 @@ export default class Amap2demo_lineLinear extends React.Component {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [120.19382669582967, 30.258134],
pitch: 0,
zoom: 6,
viewMode: '3D',
center: [105, 30.258134],
zoom: 5,
}),
});
this.scene = scene;
@ -33,18 +32,21 @@ export default class Amap2demo_lineLinear extends React.Component {
type: 'Polygon',
coordinates: [
[
[113.8623046875, 30.031055426540206],
[116.3232421875, 30.031055426540206],
[116.3232421875, 31.090574094954192],
[113.8623046875, 31.090574094954192],
[113.8623046875, 30.031055426540206],
[99.228515625, 37.43997405227057],
[99.228515625, 35.02999636902566],
[101.337890625, 32.99023555965106],
[99.052734375, 30.29701788337205],
[100.72265625, 27.994401411046148],
[99.49218749999999, 26.352497858154024],
[100.634765625, 23.725011735951796],
],
[
[117.26806640625, 32.13840869677249],
[118.36669921875, 32.13840869677249],
[118.36669921875, 32.47269502206151],
[117.26806640625, 32.47269502206151],
[117.26806640625, 32.13840869677249],
[108.544921875, 37.71859032558816],
[110.74218749999999, 34.66935854524543],
[110.21484375, 32.76880048488168],
[112.412109375, 32.84267363195431],
[112.1484375, 30.751277776257812],
[114.08203125, 31.42866311735861],
],
],
},
@ -53,38 +55,36 @@ export default class Amap2demo_lineLinear extends React.Component {
};
scene.on('loaded', () => {
fetch(
// 'https://gw.alipayobjects.com/os/basement_prod/40ef2173-df66-4154-a8c0-785e93a5f18e.json',
'https://gw.alipayobjects.com/os/bmw-prod/459591a6-8aa5-4ce7-87a1-0e1e7bce3e60.json',
)
.then((res) => res.json())
.then((data) => {
// @ts-ignore
const layer = new LineLayer({})
// .source(data)
.source(geoData)
.size(5)
.shape('line')
.color('#25d8b7')
// .animate({
// interval: 1, // 间隔
// duration: 1, // 持续时间,延时
// trailLength: 2, // 流线长度
// })
.style({
opacity: 'testOpacity',
// opacity: 0,
// lineTexture: true, // 开启线的贴图功能
// iconStep: 50, // 设置贴图纹理的间距
// lineType: 'dash',
// dashArray: [5, 5],
// textureBlend: 'replace',
// textureBlend: 'normal',
sourceColor: '#f00',
targetColor: '#0f0',
});
scene.addLayer(layer);
// const layer = new LineLayer({})
// .source(geoData)
// .size(5)
// .shape('line')
// .color('#25d8b7')
// .style({
// sourceColor: '#f00',
// targetColor: '#0f0',
// });
// scene.addLayer(layer);
const layer = new LineLayer({})
.source(geoData)
.size(5)
.shape('linearline')
.style({
rampColors: {
colors: [
'#FF4818',
'#F7B74A',
'#FFF598',
'#91EABC',
'#2EA9A1',
'#206C7C',
],
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
// colors: [ '#f00', '#0f0', '#ff0' ],
// positions: [0, 0.1, 1.0]
},
});
scene.addLayer(layer);
});
}