diff --git a/packages/layers/src/core/interface.ts b/packages/layers/src/core/interface.ts index 9caef99a61..2c65ea3a08 100644 --- a/packages/layers/src/core/interface.ts +++ b/packages/layers/src/core/interface.ts @@ -43,6 +43,8 @@ export interface ILineLayerStyleOptions { maskInside?: boolean; // 可选参数 控制图层是否显示在蒙层的内部 arrow?: ILineArrow; + + rampColors?: IColorRamp; } export interface IPointLayerStyleOptions { diff --git a/packages/layers/src/line/index.ts b/packages/layers/src/line/index.ts index 10df574d4f..1d6b405083 100644 --- a/packages/layers/src/line/index.ts +++ b/packages/layers/src/line/index.ts @@ -29,6 +29,7 @@ export default class LineLayer extends BaseLayer { const type = this.getModelType(); const defaultConfig = { line: {}, + linearline: {}, simple: {}, wall: {}, arc3d: { blend: 'additive' }, diff --git a/packages/layers/src/line/models/index.ts b/packages/layers/src/line/models/index.ts index dbf1c2e4e1..cb1efab204 100644 --- a/packages/layers/src/line/models/index.ts +++ b/packages/layers/src/line/models/index.ts @@ -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; diff --git a/packages/layers/src/line/models/linearline.ts b/packages/layers/src/line/models/linearline.ts new file mode 100644 index 0000000000..20cbb9ce6a --- /dev/null +++ b/packages/layers/src/line/models/linearline.ts @@ -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, + }); + }; +} diff --git a/packages/layers/src/line/shaders/linearLine/line_linear_frag.glsl b/packages/layers/src/line/shaders/linearLine/line_linear_frag.glsl new file mode 100644 index 0000000000..6396cc2448 --- /dev/null +++ b/packages/layers/src/line/shaders/linearLine/line_linear_frag.glsl @@ -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); +} diff --git a/packages/layers/src/line/shaders/linearLine/line_linear_vert.glsl b/packages/layers/src/line/shaders/linearLine/line_linear_vert.glsl new file mode 100644 index 0000000000..b38aabbcf5 --- /dev/null +++ b/packages/layers/src/line/shaders/linearLine/line_linear_vert.glsl @@ -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); +} diff --git a/stories/Map/components/amap2demo_lineLinear.tsx b/stories/Map/components/amap2demo_lineLinear.tsx index e920c55afe..9e106e790b 100644 --- a/stories/Map/components/amap2demo_lineLinear.tsx +++ b/stories/Map/components/amap2demo_lineLinear.tsx @@ -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); }); }