diff --git a/packages/core/src/services/layer/IStyleAttributeService.ts b/packages/core/src/services/layer/IStyleAttributeService.ts index 4a9a6c6fe7..a33101d141 100644 --- a/packages/core/src/services/layer/IStyleAttributeService.ts +++ b/packages/core/src/services/layer/IStyleAttributeService.ts @@ -111,6 +111,7 @@ export interface IVertexAttributeDescriptor vertex: number[], attributeIdx: number, normal: number[], + vertexIndex?: number, ) => number[]; } @@ -168,6 +169,7 @@ export type Triangulation = ( indices: number[]; size: number; normals?: number[]; + indexes?: number[]; }; export interface IStyleAttributeUpdateOptions { diff --git a/packages/core/src/services/layer/StyleAttributeService.ts b/packages/core/src/services/layer/StyleAttributeService.ts index d7af836380..1e53b0a30d 100644 --- a/packages/core/src/services/layer/StyleAttributeService.ts +++ b/packages/core/src/services/layer/StyleAttributeService.ts @@ -53,6 +53,7 @@ export default class StyleAttributeService implements IStyleAttributeService { vertices: number[]; normals: number[]; offset: number; + indexes?: number[]; }>; } = { sizePerElement: 0, @@ -218,6 +219,7 @@ export default class StyleAttributeService implements IStyleAttributeService { vertices: verticesForCurrentFeature, normals: normalsForCurrentFeature, size: vertexSize, + indexes, } = this.triangulation(feature, segmentNumber); indicesForCurrentFeature.forEach((i) => { indices.push(i + verticesNum); @@ -249,6 +251,12 @@ export default class StyleAttributeService implements IStyleAttributeService { vertexIdx * vertexSize, vertexIdx * vertexSize + vertexSize, ); + + let vertexIndex = 0; + if (indexes && indexes[vertexIdx] !== undefined) { + vertexIndex = indexes[vertexIdx]; + } + descriptors.forEach((descriptor, attributeIdx) => { if (descriptor && descriptor.update) { (descriptor.buffer.data as number[]).push( @@ -258,6 +266,7 @@ export default class StyleAttributeService implements IStyleAttributeService { vertice, vertexIdx, // 当前顶点所在feature索引 normal, + vertexIndex, // TODO: 传入顶点索引 vertexIdx ), ); diff --git a/packages/layers/src/core/interface.ts b/packages/layers/src/core/interface.ts index 705da7c37f..36bfcc50ae 100644 --- a/packages/layers/src/core/interface.ts +++ b/packages/layers/src/core/interface.ts @@ -7,6 +7,12 @@ export enum lineStyleType { 'dash' = 1.0, } +interface ILineArrow { + enable: boolean; + arrowWidth: number; + arrowHeight: number; +} + export interface ILineLayerStyleOptions { opacity: styleSingle; lineType?: keyof typeof lineStyleType; // 可选参数、线类型(all - dash/solid) @@ -34,6 +40,8 @@ export interface ILineLayerStyleOptions { mask?: boolean; // 可选参数 时候允许蒙层 maskInside?: boolean; // 可选参数 控制图层是否显示在蒙层的内部 + + arrow?: ILineArrow; } export interface IPointLayerStyleOptions { diff --git a/packages/layers/src/core/triangulation.ts b/packages/layers/src/core/triangulation.ts index 187f65baa5..cb994eea08 100644 --- a/packages/layers/src/core/triangulation.ts +++ b/packages/layers/src/core/triangulation.ts @@ -139,6 +139,7 @@ export function LineTriangulation(feature: IEncodeFeature) { vertices: linebuffer.positions, // [ x,y,z, distance, miter,total ] indices: linebuffer.indices, normals: linebuffer.normals, + indexes: linebuffer.indexes, size: 6, }; } diff --git a/packages/layers/src/line/models/line.ts b/packages/layers/src/line/models/line.ts index c458cdf560..52a0f0e676 100644 --- a/packages/layers/src/line/models/line.ts +++ b/packages/layers/src/line/models/line.ts @@ -45,6 +45,11 @@ export default class LineModel extends BaseModel { borderColor = '#ccc', raisingHeight = 0, heightfixed = false, + arrow = { + enable: false, + arrowWidth: 2, + arrowHeight: 3, + }, } = this.layer.getLayerConfig() as ILineLayerStyleOptions; if (dashArray.length === 2) { dashArray.push(0, 0); @@ -124,6 +129,11 @@ export default class LineModel extends BaseModel { // 顶点高度 scale u_vertexScale: vertexHeightScale, u_raisingHeight: Number(raisingHeight), + + // arrow + u_arrow: Number(arrow.enable), + u_arrowHeight: arrow.arrowHeight || 3, + u_arrowWidth: arrow.arrowWidth || 2, }; } public getAnimateUniforms(): IModelUniform { @@ -216,14 +226,18 @@ export default class LineModel extends BaseModel { data: [], type: gl.FLOAT, }, - size: 1, + size: 2, update: ( feature: IEncodeFeature, featureIdx: number, vertex: number[], attributeIdx: number, + normal: number[], + vertexIndex?: number, ) => { - return [vertex[3]]; + return vertexIndex === undefined + ? [vertex[3], 10] + : [vertex[3], vertexIndex]; }, }, }); diff --git a/packages/layers/src/line/shaders/line_vert.glsl b/packages/layers/src/line/shaders/line_vert.glsl index 4261f26c1d..4c6592a6d9 100644 --- a/packages/layers/src/line/shaders/line_vert.glsl +++ b/packages/layers/src/line/shaders/line_vert.glsl @@ -11,7 +11,7 @@ attribute vec2 a_iconMapUV; // dash line attribute float a_Total_Distance; -attribute float a_Distance; +attribute vec2 a_Distance; uniform mat4 u_ModelMatrix; uniform mat4 u_Mvp; @@ -32,6 +32,9 @@ varying vec2 v_iconMapUV; uniform float u_linearColor: 0; +uniform float u_arrow: 0.0; +uniform float u_arrowHeight: 3.0; +uniform float u_arrowWidth: 2.0; uniform float u_opacity: 1.0; varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样式值传递给片元 @@ -39,6 +42,48 @@ varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样 #pragma include "styleMapping" #pragma include "styleMappingCalOpacity" +vec2 calculateArrow(vec2 offset) { + /* + * 在支持箭头的时候,第二、第三组顶点是额外插入用于构建顶点的 + */ + float arrowFlag = -1.0; + if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { + // 高德 2.0 的旋转角度不同 + arrowFlag = 1.0; + } + float pi = arrowFlag * 3.1415926/2.; + if(a_Miter < 0.) { + // 根据线的两侧偏移不同、旋转的方向相反 + pi = -pi; + } + highp float angle_sin = sin(pi); + highp float angle_cos = cos(pi); + // 计算垂直与线方向的旋转矩阵 + mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos); + float arrowWidth = u_arrowWidth; + float arrowHeight = u_arrowHeight; + + vec2 arrowOffset = vec2(0.0); + /* + * a_Distance.y 用于标记当前顶点属于哪一组(两个顶点一组,构成线的其实是矩形,最简需要四个顶点、两组顶点构成) + */ + if(a_Distance.y == 0.0) { + // 箭头尖部 + offset = vec2(0.0); + } else if(a_Distance.y == 1.0) { + // 箭头两侧 + arrowOffset = rotation_matrix*(offset * arrowHeight); + offset += arrowOffset; // 沿线偏移 + offset = offset * arrowWidth; // 垂直线向外偏移(是构建箭头两侧的顶点) + } else if(a_Distance.y == 2.0 || a_Distance.y == 3.0 || a_Distance.y == 4.0) { + // 偏移其余的点位(将长度让位给箭头) + arrowOffset = rotation_matrix*(offset * arrowHeight) * arrowWidth; + offset += arrowOffset;// 沿线偏移 + } + + return offset; +} + void main() { // cal style mapping - 数据纹理映射部分的计算 styleMappingMat = mat4( @@ -79,14 +124,19 @@ void main() { vec3 size = a_Miter * setPickingSize(a_Size.x) * reverse_offset_normal(a_Normal); vec2 offset = project_pixel(size.xy); + + if(u_arrow > 0.0) { + // 计算箭头 + offset = calculateArrow(offset); + } 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] = a_Distance / a_Total_Distance;; // 当前点位距离占线总长的比例 - styleMappingMat[3][1] = a_Distance; // 当前顶点的距离 + styleMappingMat[3][0] = a_Distance.x / a_Total_Distance;; // 当前点位距离占线总长的比例 + styleMappingMat[3][1] = a_Distance.x; // 当前顶点的距离 styleMappingMat[3][2] = d_texPixelLen; // 贴图的像素长度,根据地图层级缩放 styleMappingMat[3][3] = texV; // 线图层贴图部分的 v 坐标值 diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts index 6cfebbbbb2..7af982f418 100644 --- a/packages/layers/src/plugins/DataMappingPlugin.ts +++ b/packages/layers/src/plugins/DataMappingPlugin.ts @@ -9,13 +9,15 @@ import { IParseDataItem, IStyleAttribute, IStyleAttributeService, + Position, TYPES, } from '@antv/l7-core'; import { Version } from '@antv/l7-maps'; -import { isColor, rgb2arr, unProjectFlat } from '@antv/l7-utils'; +import { isColor, normalize, rgb2arr, unProjectFlat } from '@antv/l7-utils'; import { inject, injectable } from 'inversify'; import { cloneDeep } from 'lodash'; import 'reflect-metadata'; +import { ILineLayerStyleOptions } from '../core/interface'; @injectable() export default class DataMappingPlugin implements ILayerPlugin { @@ -54,21 +56,24 @@ export default class DataMappingPlugin implements ILayerPlugin { const attributes = styleAttributeService.getLayerStyleAttributes() || []; const filter = styleAttributeService.getLayerStyleAttribute('filter'); const { dataArray } = layer.getSource().data; + const attributesToRemapping = attributes.filter( (attribute) => attribute.needRemapping, // 如果filter变化 ); let filterData = dataArray; + // 数据过滤完 再执行数据映射 if (filter?.needRemapping && filter?.scale) { filterData = dataArray.filter((record: IParseDataItem) => { return this.applyAttributeMapping(filter, record, bottomColor)[0]; }); } + if (attributesToRemapping.length) { // 过滤数据 if (filter?.needRemapping) { layer.setEncodedData( - this.mapping(attributes, filterData, undefined, bottomColor), + this.mapping(attributes, filterData, undefined, bottomColor, layer), ); filter.needRemapping = false; } else { @@ -78,6 +83,7 @@ export default class DataMappingPlugin implements ILayerPlugin { filterData, layer.getEncodedData(), bottomColor, + layer, ), ); } @@ -104,19 +110,34 @@ export default class DataMappingPlugin implements ILayerPlugin { }); } layer.setEncodedData( - this.mapping(attributes, filterData, undefined, bottomColor), + this.mapping(attributes, filterData, undefined, bottomColor, layer), ); // 对外暴露事件 layer.emit('dataUpdate', null); } + private getArrowPoints(p1: Position, p2: Position) { + const dir = [p2[0] - p1[0], p2[1] - p1[1]]; + const normalizeDir = normalize(dir); + const arrowPoint = [ + p1[0] + normalizeDir[0] * 0.0001, + p1[1] + normalizeDir[1] * 0.0001, + ]; + return arrowPoint; + } + private mapping( attributes: IStyleAttribute[], data: IParseDataItem[], predata?: IEncodeFeature[], minimumColor?: string, + layer?: ILayer, ): IEncodeFeature[] { - // console.log('data', data) + const { + arrow = { + enable: false, + }, + } = layer?.getLayerConfig() as ILineLayerStyleOptions; const mappedData = data.map((record: IParseDataItem, i) => { const preRecord = predata ? predata[i] : {}; const encodeRecord: IEncodeFeature = { @@ -124,11 +145,9 @@ export default class DataMappingPlugin implements ILayerPlugin { coordinates: record.coordinates, ...preRecord, }; - // console.log('attributes', attributes) attributes .filter((attribute) => attribute.scale !== undefined) .forEach((attribute: IStyleAttribute) => { - // console.log('attribute', attribute) // console.log('record', record) let values = this.applyAttributeMapping( attribute, @@ -156,6 +175,13 @@ export default class DataMappingPlugin implements ILayerPlugin { ); } }); + + if (encodeRecord.shape === 'line' && arrow.enable) { + // 只有在线图层且支持配置箭头的时候进行插入顶点的处理 + const coords = encodeRecord.coordinates as Position[]; + const arrowPoint = this.getArrowPoints(coords[0], coords[1]); + encodeRecord.coordinates.splice(1, 0, arrowPoint, arrowPoint); + } return encodeRecord; }) as IEncodeFeature[]; // console.log('mappedData', mappedData) @@ -165,7 +191,7 @@ export default class DataMappingPlugin implements ILayerPlugin { // 调整数据兼容 SimpleCoordinates this.adjustData2SimpleCoordinates(mappedData); - // console.log('mappedData', mappedData) + return mappedData; } diff --git a/packages/layers/src/utils/extrude_polyline.ts b/packages/layers/src/utils/extrude_polyline.ts index eb1892043f..c6a6635608 100644 --- a/packages/layers/src/utils/extrude_polyline.ts +++ b/packages/layers/src/utils/extrude_polyline.ts @@ -62,6 +62,7 @@ export default class ExtrudePolyline { indices: number[]; normals: number[]; startIndex: number; + indexes: number[]; }; private join: string; private cap: string; @@ -73,6 +74,7 @@ export default class ExtrudePolyline { private started: boolean = false; private dash: boolean = false; private totalDistance: number = 0; + private currentIndex: number = 0; constructor(opts: Partial = {}) { this.join = opts.join || 'miter'; @@ -85,6 +87,7 @@ export default class ExtrudePolyline { indices: [], normals: [], startIndex: 0, + indexes: [], }; } @@ -313,6 +316,7 @@ export default class ExtrudePolyline { -this.thickness, last[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( last[0], last[1], @@ -321,6 +325,8 @@ export default class ExtrudePolyline { this.thickness, last[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; } else { this.extrusions( positions, @@ -354,6 +360,7 @@ export default class ExtrudePolyline { this.thickness, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( cur[0], cur[1], @@ -362,6 +369,8 @@ export default class ExtrudePolyline { this.thickness, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; } else { this.extrusions( positions, @@ -429,6 +438,7 @@ export default class ExtrudePolyline { -this.thickness * flip, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( cur[0], cur[1], @@ -437,6 +447,8 @@ export default class ExtrudePolyline { this.thickness * flip, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; indices.push( ...(this.lastFlip !== -flip ? [index, index + 2, index + 3] @@ -457,6 +469,8 @@ export default class ExtrudePolyline { -this.thickness * flip, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; count += 3; } else { this.extrusions( @@ -636,6 +650,7 @@ export default class ExtrudePolyline { -this.thickness, last[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( last[0], last[1], @@ -644,7 +659,8 @@ export default class ExtrudePolyline { this.thickness, last[2] | 0, ); - + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; // this.extrusions(positions, normals, last, out, this.thickness); // last = capEnd; } else { @@ -681,6 +697,7 @@ export default class ExtrudePolyline { this.thickness, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( cur[0], cur[1], @@ -689,6 +706,8 @@ export default class ExtrudePolyline { this.thickness, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; } else { this.extrusions( positions, @@ -750,6 +769,7 @@ export default class ExtrudePolyline { -this.thickness * flip, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( cur[0], cur[1], @@ -758,6 +778,8 @@ export default class ExtrudePolyline { this.thickness * flip, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; indices.push( ...(this.lastFlip !== -flip ? [index, index + 2, index + 3] @@ -778,6 +800,8 @@ export default class ExtrudePolyline { -this.thickness * flip, cur[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; count += 3; } else { this.extrusions( @@ -822,6 +846,7 @@ export default class ExtrudePolyline { -thickness, point[2] | 0, ); + this.complex.indexes.push(this.currentIndex); positions.push( point[0], point[1], @@ -830,6 +855,8 @@ export default class ExtrudePolyline { thickness, point[2] | 0, ); + this.complex.indexes.push(this.currentIndex); + this.currentIndex++; } private lineSegmentDistance(b1: vec3, a1: vec3) { const dx = a1[0] - b1[0]; diff --git a/packages/source/src/parser/json.ts b/packages/source/src/parser/json.ts index 9c385fe9bd..5d0596bb01 100644 --- a/packages/source/src/parser/json.ts +++ b/packages/source/src/parser/json.ts @@ -54,12 +54,21 @@ export default function json(data: IJsonData, cfg: IParserCfg): IParserData { } // TODO: 提供默认数据和解析器 -export const defaultData = []; +export const defaultData = [ + { + lng1: 100, + lat1: 30.0, + lng2: 130, + lat2: 30, + }, +]; export const defaultParser = { parser: { type: 'json', - x: 'lng', - y: 'lat', + x: 'lng1', + y: 'lat1', + x1: 'lng2', + y1: 'lat2', }, }; diff --git a/packages/utils/src/geo.ts b/packages/utils/src/geo.ts index aeb7c33cdb..943956306a 100644 --- a/packages/utils/src/geo.ts +++ b/packages/utils/src/geo.ts @@ -251,6 +251,11 @@ export function bBoxToBounds(b1: BBox): IBounds { ]; } +export function normalize(v: Point) { + const len = calDistance(v, [0, 0]); + return [v[0] / len, v[1] / len]; +} + export function calDistance(p1: Point, p2: Point) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); } diff --git a/stories/Object/components/arrow.tsx b/stories/Object/components/arrow.tsx new file mode 100644 index 0000000000..939583f0a3 --- /dev/null +++ b/stories/Object/components/arrow.tsx @@ -0,0 +1,102 @@ +import { GeometryLayer, Scene, LineLayer } from '@antv/l7'; +import { GaodeMap, GaodeMapV2, Mapbox } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class Demo extends React.Component { + // @ts-ignore + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + // map: new GaodeMapV2({ + // map: new Mapbox({ + pitch: 0, + // style: 'dark', + center: [120, 30], + zoom: 3, + }), + }); + this.scene = scene; + + const layer = new LineLayer() + // .source([ + // { + // lng1: 100, + // lat1: 30.0, + // lng2: 105, + // lat2: 30, + // }, + // { + // lng1: 105, + // lat1: 30.0, + // lng2: 130, + // lat2: 30, + // }, + // ], { + // parser: { + // type: 'json', + // x: 'lng1', + // y: 'lat1', + // x1: 'lng2', + // y1: 'lat2', + // } + // }) + .source({ + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'LineString', + coordinates: [ + [100, 30], + [120, 30], + [120, 25], + [125, 25], + ], + }, + }, + ], + }) + .shape('line') + .size(10) + // .color('lng1', ['#f00', '#ff0']) + .color('#f00') + .style({ + opacity: 0.3, + arrow: { + enable: true, + arrowWidth: 2, + arrowHeight: 3, + }, + }); + + scene.on('loaded', () => { + scene.addLayer(layer); + }); + } + + public render() { + return ( + <> +
+ + ); + } +} diff --git a/stories/Object/map.stories.tsx b/stories/Object/map.stories.tsx index 709f1bea67..ff2aaca5d4 100644 --- a/stories/Object/map.stories.tsx +++ b/stories/Object/map.stories.tsx @@ -8,6 +8,7 @@ import CanvasDemo from './components/canvas'; import Plane from './components/plane'; import PlaneTerrain from './components/planeTerrain'; import Cursor from './components/cursor'; +import Arrow from './components/arrow'; storiesOf('Object', module) .add('water', () => ) @@ -17,4 +18,5 @@ storiesOf('Object', module) .add('CanvasDemo', () => ) .add('Plane', () => ) .add('PlaneTerrain', () => ) - .add('Cursor', () => ) \ No newline at end of file + .add('Cursor', () => ) + .add('Arrow', () => ) \ No newline at end of file