* feat: 补充雷达图

* style: lint style
This commit is contained in:
YiQianYao 2022-03-10 19:03:37 +08:00 committed by GitHub
parent e1aca744b3
commit cf0ac84a23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 598 additions and 17 deletions

View File

@ -74,7 +74,7 @@ export interface IPointLayerStyleOptions {
maskInside?: boolean;
rotation?: number; // angle
speed?: number;
animateOption: IAnimateOption;
}

View File

@ -57,6 +57,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
normal: {
blend: 'additive',
},
radar: {},
simplePoint: {},
fill: { blend: 'normal' },
extrude: {},
@ -73,6 +74,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
const PointTypes = [
'fillImage',
'fill',
'radar',
'image',
'normal',
'simplePoint',
@ -102,6 +104,9 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
if (shape === 'simple') {
return 'simplePoint';
}
if (shape === 'radar') {
return 'radar';
}
if (shape === 'fillImage') {
return 'fillImage';
}

View File

@ -4,12 +4,14 @@ import FillImageModel from './fillmage';
import IconModel from './icon-font';
import IMageModel from './image';
import NormalModel from './normal';
import Radar from './radar';
import SimplePopint from './simplePoint';
import TextModel from './text';
export type PointType =
| 'fillImage'
| 'fill'
| 'radar'
| 'image'
| 'normal'
| 'simplePoint'
@ -20,6 +22,7 @@ export type PointType =
const PointModels: { [key in PointType]: any } = {
fillImage: FillImageModel,
fill: FillModel,
radar: Radar,
image: IMageModel,
normal: NormalModel,
simplePoint: SimplePopint,

View File

@ -0,0 +1,305 @@
import {
AttributeType,
gl,
IAnimateOption,
IAttribute,
IElements,
IEncodeFeature,
IModel,
IModelUniform,
} from '@antv/l7-core';
import { getMask } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { IPointLayerStyleOptions } from '../../core/interface';
import { PointFillTriangulation } from '../../core/triangulation';
import pointFillFrag from '../shaders/radar/radar_frag.glsl';
import pointFillVert from '../shaders/radar/radar_vert.glsl';
import { isNumber } from 'lodash';
import { Version } from '@antv/l7-maps';
export default class RadarModel extends BaseModel {
public meter2coord: number = 1;
private isMeter: boolean = false;
public getUninforms(): IModelUniform {
const {
opacity = 1,
strokeOpacity = 1,
strokeWidth = 0,
stroke = 'rgba(0,0,0,0)',
offsets = [0, 0],
blend,
speed = 1,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
if (
this.dataTextureTest &&
this.dataTextureNeedUpdate({
opacity,
strokeOpacity,
strokeWidth,
stroke,
offsets,
})
) {
// 判断当前的样式中哪些是需要进行数据映射的,哪些是常量,同时计算用于构建数据纹理的一些中间变量
this.judgeStyleAttributes({
opacity,
strokeOpacity,
strokeWidth,
stroke,
offsets,
});
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_isMeter: Number(this.isMeter),
u_speed: speed,
u_additive: blend === 'additive' ? 1.0 : 0.0,
u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1]
u_cellTypeLayout: this.getCellTypeLayout(),
u_opacity: isNumber(opacity) ? opacity : 1.0,
u_offsets: this.isOffsetStatic(offsets)
? (offsets as [number, number])
: [0, 0],
};
}
public getAnimateUniforms(): IModelUniform {
const {
animateOption = { enable: false },
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
return {
u_aimate: this.animateOption2Array(animateOption),
u_time: this.layer.getLayerAnimateTime(),
};
}
public getAttribute(): {
attributes: {
[attributeName: string]: IAttribute;
};
elements: IElements;
} {
return this.styleAttributeService.createAttributesAndIndices(
this.layer.getEncodedData(),
PointFillTriangulation,
);
}
public initModels(): IModel[] {
const {
unit = 'l7size',
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
const { version } = this.mapService;
if (
unit === 'meter' &&
version !== Version.L7MAP &&
version !== Version.GLOBEL
) {
this.isMeter = true;
this.calMeter2Coord();
}
return this.buildModels();
}
/**
* unit meter
* @returns
*/
public calMeter2Coord() {
// @ts-ignore
const [minLng, minLat, maxLng, maxLat] = this.layer.getSource().extent;
const center = [(minLng + maxLng) / 2, (minLat + maxLat) / 2];
const { version } = this.mapService;
if (version === Version.MAPBOX && window.mapboxgl.MercatorCoordinate) {
const coord = window.mapboxgl.MercatorCoordinate.fromLngLat(
{ lng: center[0], lat: center[1] },
0,
);
const offsetInMeters = 1;
const offsetInMercatorCoordinateUnits =
offsetInMeters * coord.meterInMercatorCoordinateUnits();
const westCoord = new window.mapboxgl.MercatorCoordinate(
coord.x - offsetInMercatorCoordinateUnits,
coord.y,
coord.z,
);
const westLnglat = westCoord.toLngLat();
this.meter2coord = center[0] - westLnglat.lng;
return;
}
// @ts-ignore
const m1 = this.mapService.meterToCoord(center, [minLng, minLat]);
// @ts-ignore
const m2 = this.mapService.meterToCoord(center, [
maxLng === minLng ? maxLng + 0.1 : maxLng,
maxLat === minLat ? minLat + 0.1 : maxLat,
]);
this.meter2coord = (m1 + m2) / 2;
if (!Boolean(this.meter2coord)) {
// Tip: 兼容单个数据导致的 m1、m2 为 NaN
this.meter2coord = 7.70681090738883;
}
}
public buildModels(): IModel[] {
const {
mask = false,
maskInside = true,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
const { frag, vert, type } = this.getShaders();
return [
this.layer.buildLayerModel({
moduleName: 'pointfill_' + type,
vertexShader: vert,
fragmentShader: frag,
triangulation: PointFillTriangulation,
depth: { enable: false },
blend: this.getBlend(),
stencil: getMask(mask, maskInside),
}),
];
}
/**
* animateOption shader
* @returns
*/
public getShaders(): { frag: string; vert: string; type: string } {
return {
frag: pointFillFrag,
vert: pointFillVert,
type: 'radar',
};
}
public clearModels() {
this.dataTexture?.destroy();
}
// overwrite baseModel func
protected animateOption2Array(option: IAnimateOption): number[] {
return [option.enable ? 0 : 1.0, option.speed || 1, option.rings || 3, 0];
}
protected registerBuiltinAttributes() {
this.styleAttributeService.registerStyleAttribute({
name: 'extrude',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Extrude',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 3,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
let extrude;
extrude = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0];
const extrudeIndex = (attributeIdx % 4) * 3;
return [
extrude[extrudeIndex],
extrude[extrudeIndex + 1],
extrude[extrudeIndex + 2],
];
},
},
});
// point layer size;
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: 1,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
const { size = 5 } = feature;
// console.log('featureIdx', featureIdx, feature)
return Array.isArray(size)
? [size[0] * this.meter2coord]
: [(size as number) * this.meter2coord];
},
},
});
// point layer size;
this.styleAttributeService.registerStyleAttribute({
name: 'shape',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Shape',
buffer: {
// give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 1,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
const { shape = 2 } = feature;
const shape2d = this.layer.getLayerConfig().shape2d as string[];
const shapeIndex = shape2d.indexOf(shape as string);
return [shapeIndex];
},
},
});
}
}

View File

@ -0,0 +1,66 @@
uniform float u_additive;
varying mat4 styleMappingMat; // 传递从片元中传递的映射数据
varying vec4 v_data;
varying vec4 v_color;
varying float v_radius;
#pragma include "sdf_2d"
#pragma include "picking"
void main() {
int shape = int(floor(v_data.w + 0.5));
vec4 textrueStroke = vec4(
styleMappingMat[1][0],
styleMappingMat[1][1],
styleMappingMat[1][2],
styleMappingMat[1][3]
);
float opacity = styleMappingMat[0][0];
lowp float antialiasblur = v_data.z;
float r = v_radius / (v_radius);
float outer_df = sdCircle(v_data.xy, 1.0);
float inner_df = sdCircle(v_data.xy, r);
float opacity_t = smoothstep(0.0, antialiasblur, outer_df);
gl_FragColor = vec4(v_color.rgb, v_color.a * opacity);
if(u_additive > 0.0) {
gl_FragColor *= opacity_t;
} else {
gl_FragColor.a *= opacity_t;
}
if(gl_FragColor.a > 0.0) {
gl_FragColor = filterColor(gl_FragColor);
}
vec2 extrude = styleMappingMat[2].ba;
vec2 dir = normalize(extrude);
vec2 baseDir = vec2(1.0, 0.0);
float pi = 3.14159265359;
// full circle
// float rades = dot(dir, baseDir);
// float flag = sign(dir.y);
// float radar_v = (flag - 1.0) * -0.5 + flag * acos(rades)/pi/2.0;
// half circle
float flag = sign(dir.y);
float rades = dot(dir, baseDir);
float radar_v = (flag - 1.0) * -0.5 * acos(rades)/pi;
// simple AA
if(radar_v > 0.99) {
radar_v = 1.0 - (radar_v - 0.99)/0.01;
}
gl_FragColor.a *= radar_v;
}

View File

@ -0,0 +1,131 @@
attribute vec4 a_Color;
attribute vec3 a_Position;
attribute vec3 a_Extrude;
attribute float a_Size;
attribute float a_Shape;
uniform float u_speed: 1.0;
uniform float u_time;
varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样式值传递给片元
uniform float u_globel;
uniform mat4 u_ModelMatrix;
uniform mat4 u_Mvp;
uniform float u_isMeter;
varying vec4 v_data;
varying vec4 v_color;
varying float v_radius;
uniform float u_opacity : 1;
uniform float u_stroke_opacity : 1;
uniform float u_stroke_width : 2;
uniform vec4 u_stroke_color : [0.0, 0.0, 0.0, 0.0];
uniform vec2 u_offsets;
uniform float u_blur : 0.0;
#pragma include "styleMapping"
#pragma include "styleMappingCalOpacity"
#pragma include "projection"
#pragma include "picking"
void main() {
vec3 extrude = a_Extrude;
float shape_type = a_Shape;
float newSize = setPickingSize(a_Size);
// cal style mapping - 数据纹理映射部分的计算
styleMappingMat = mat4(
0.0, 0.0, 0.0, 0.0, // opacity - empty - empty - empty
0.0, 0.0, 0.0, 0.0, // empty - empty - empty - empty
0.0, 0.0, 0.0, 0.0, // offsets[0] - offsets[1] - a_Extrude.x - a_Extrude.y
0.0, 0.0, 0.0, 0.0 //
);
float time = u_time * u_speed;
mat2 rotateMatrix = mat2(
cos(time), sin(time),
-sin(time), cos(time)
);
styleMappingMat[2].ba = rotateMatrix * a_Extrude.xy;
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 -> empty -> empty ...
// 按顺序从 cell 中取值、若没有则自动往下取值
float textureOffset = 0.0; // 在 cell 中取值的偏移量
vec2 opacityAndOffset = calOpacityAndOffset(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset, columnWidth, rowHeight);
styleMappingMat[0][0] = opacityAndOffset.r;
textureOffset = opacityAndOffset.g;
vec2 textrueOffsets = vec2(0.0, 0.0);
if(hasOffsets()) {
vec2 valueXPos = nextPos(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset);
textrueOffsets.r = pos2value(valueXPos, columnWidth, rowHeight); // x
textureOffset += 1.0;
vec2 valueYPos = nextPos(cellCurrentRow, cellCurrentColumn, columnCount, textureOffset);
textrueOffsets.g = pos2value(valueYPos, columnWidth, rowHeight); // x
textureOffset += 1.0;
} else {
textrueOffsets = u_offsets;
}
// cal style mapping
// unpack color(vec2)
v_color = a_Color;
// radius(16-bit)
v_radius = newSize;
// TODO: billboard
// anti-alias
// float antialiased_blur = -max(u_blur, antialiasblur);
float antialiasblur = -max(2.0 / u_DevicePixelRatio / a_Size, u_blur);
vec2 offset = (extrude.xy * (newSize + u_stroke_width) + textrueOffsets);
vec3 aPosition = a_Position;
if(u_isMeter < 1.0) {
// 不以米为实际单位
offset = project_pixel(offset);
} else {
// 以米为实际单位
antialiasblur *= pow(19.0 - u_Zoom, 2.0);
antialiasblur = max(antialiasblur, -0.01);
// offset *= 0.5;
if(u_CoordinateSystem == COORDINATE_SYSTEM_LNGLAT || u_CoordinateSystem == COORDINATE_SYSTEM_LNGLAT_OFFSET) {
aPosition.xy += offset;
offset.x = 0.0;
offset.y = 0.0;
}
}
v_data = vec4(extrude.x, extrude.y, antialiasblur,shape_type);
vec4 project_pos = project_position(vec4(aPosition.xy, 0.0, 1.0));
if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
gl_Position = u_Mvp * vec4(project_pos.xy + offset, 0.0, 1.0);
} else {
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, project_pixel(setPickingOrder(0.0)), 1.0));
}
if(u_globel > 0.0) {
gl_Position = u_ViewProjectionMatrix * vec4(a_Position + extrude * newSize * 0.1, 1.0);
}
setPickingColor(a_PickingColor);
}

View File

@ -0,0 +1,65 @@
import { PointLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as React from 'react';
export default class Amap2demo_polygon extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
pitch: 0,
center: [120, 30],
zoom: 13,
}),
});
this.scene = scene;
const layer = new PointLayer()
.source(
[
{
lng: 120,
lat: 30,
},
],
{
parser: {
type: 'json',
x: 'lng',
y: 'lat',
},
},
)
.shape('radar')
.size(100)
.color('#d00')
.style({
// rotation: 90
speed: 5,
})
.animate(true);
scene.on('loaded', () => {
scene.addLayer(layer);
});
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -2,7 +2,9 @@ import { storiesOf } from '@storybook/react';
import * as React from 'react';
import Water from './components/water';
import Taifong from './components/taifeng'
import Radar from './components/radar';
storiesOf('Object', module)
.add('water', () => <Water />)
.add('Taifong', () => <Taifong />)
.add('Taifong', () => <Taifong />)
.add('Radar', () => <Radar/>)

View File

@ -19,28 +19,32 @@ export default class ScaleComponent extends React.Component {
scene.on('loaded', () => {
fetch(
'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json',
// 'https://gw.alipayobjects.com/os/alisis/geo-data-v0.1.1/choropleth-data/country/100000_country_province.json'
)
.then((res) => res.json())
.then((data) => {
let layer = new PolygonLayer({ blend: 'normal' }) // autoFit: true
.source(data)
.size('name', [0, 10000, 50000, 30000, 100000])
.color('name1', [
'#2E8AE6',
'#69D1AB',
'#DAF291',
'#FFD591',
'#FF7A45',
'#CF1D49',
])
.shape('fill')
.select(true)
// .size('name', [0, 10000, 50000, 30000, 100000])
.size(1)
// .color('name1', [
// '#2E8AE6',
// '#69D1AB',
// '#DAF291',
// '#FFD591',
// '#FF7A45',
// '#CF1D49',
// ])
.color('#000')
// .shape('fill')
.shape('line')
// .select(true)
.style({
opacity: 0.8,
opacityLinear: {
enable: true,
dir: 'in', // in - out
},
// opacityLinear: {
// enable: true,
// dir: 'in', // in - out
// },
});
layer.setBottomColor('#f00');