Merge pull request #53 from antvis/encode

add heatmaplayer rasterlayer
This commit is contained in:
@thinkinggis 2019-11-04 14:20:24 +08:00 committed by GitHub
commit d49ba3ee42
61 changed files with 25754 additions and 107 deletions

View File

@ -1,10 +1,10 @@
sudo: false
language: node_js
node_js:
- '8'
- '12'
branches:
only:
- next
script:
- yarn test
- yarn coveralls
- yarn coveralls

23669
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -115,5 +115,8 @@
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"dependencies": {
"geotiff": "^1.0.0-beta.6"
}
}

View File

@ -4,6 +4,7 @@ import {
} from '../renderer/IAttribute';
import { IBufferInitializationOptions } from '../renderer/IBuffer';
import { IElements } from '../renderer/IElements';
import { IParseDataItem, IParserData } from '../source/ISourceService';
import { ILayer } from './ILayerService';
/**
@ -64,6 +65,7 @@ export interface IEncodeFeature {
pattern?: string;
id?: number;
coordinates: Position | Position[] | Position[][];
[key: string]: any;
}
export interface IVertexAttributeDescriptor

View File

@ -4,6 +4,7 @@ import { gl } from '../renderer/gl';
import { IAttribute } from '../renderer/IAttribute';
import { IElements } from '../renderer/IElements';
import { IRendererService } from '../renderer/IRendererService';
import { IParseDataItem } from '../source/ISourceService'
import { ILayer } from './ILayerService';
import {
IEncodeFeature,

View File

@ -39,7 +39,8 @@ export interface ITexture2DInitializationOptions {
| number[][]
| Uint8Array
| Uint16Array
| Uint32Array;
| Uint32Array
| Uint8ClampedArray;
/**
*

View File

@ -48,3 +48,16 @@ export type IJsonData = IJsonItem[];
export interface ISource {
data: IParserData;
}
export interface IRasterCfg {
extent: [number, number, number, number];
width: number;
height: number;
max: number;
min: number;
}
export interface IRasterParserDataItem extends IParseDataItem {
data: number[];
width: number;
height: number;
}

View File

@ -22,9 +22,10 @@
"@l7/core": "^0.0.1",
"@l7/source": "^0.0.1",
"@l7/utils": "^0.0.1",
"@mapbox/martini": "^0.1.0",
"reflect-metadata": "^0.1.13",
"@turf/meta": "^6.0.2",
"@types/d3-color": "^1.2.2",
"inversify": "^5.0.1",
"d3-array": "^2.3.1",
"d3-color": "^1.4.0",
"d3-scale": "^3.1.0",
@ -32,6 +33,7 @@
"eventemitter3": "^3.1.0",
"gl-matrix": "^3.1.0",
"gl-vec2": "^1.3.0",
"inversify": "^5.0.1",
"lodash": "^4.17.15",
"merge-json-schemas": "1.0.0",
"polyline-miter-util": "^1.0.1",

View File

@ -1,4 +1,5 @@
import {
ICameraService,
IEncodeFeature,
IFontService,
IGlobalConfigService,
@ -106,6 +107,12 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
@lazyInject(TYPES.IRendererService)
protected readonly rendererService: IRendererService;
@lazyInject(TYPES.IShaderModuleService)
protected readonly shaderModuleService: IShaderModuleService;
@lazyInject(TYPES.IMapService)
protected readonly map: IMapService;
private encodedData: IEncodeFeature[];
private configSchema: object;
@ -117,12 +124,6 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
ILayerInitializationOptions & ChildLayerStyleOptions
>;
@lazyInject(TYPES.IShaderModuleService)
private readonly shaderModuleService: IShaderModuleService;
@lazyInject(TYPES.IMapService)
private readonly map: IMapService;
@lazyInject(TYPES.IInteractionService)
private readonly interactionService: IInteractionService;
@ -343,7 +344,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> implements ILayer {
});
const { vs, fs, uniforms } = this.shaderModuleService.getModule(moduleName);
const { createModel } = this.rendererService;
const parserData = this.getSource().data.dataArray;
const {
attributes,
elements,

View File

@ -136,6 +136,78 @@ export function RasterImageTriangulation(feature: IEncodeFeature) {
size: 5,
};
}
/**
* 3D弧线顶点
* @param feature
* @param segNum 线线
*/
export function LineArcTriangulation(feature: IEncodeFeature) {
const segNum = 30;
const coordinates = feature.coordinates as IPosition[];
const positions = [];
const indexArray = [];
for (let i = 0; i < segNum; i++) {
// 上线两个顶点
// [ x, y, z, sx,sy, tx,ty]
positions.push(
i,
1,
i,
coordinates[0][0],
coordinates[0][1],
coordinates[1][0],
coordinates[1][1],
i,
-1,
i,
coordinates[0][0],
coordinates[0][1],
coordinates[1][0],
coordinates[1][1],
);
if (i !== segNum - 1) {
indexArray.push(
...[0, 1, 2, 1, 3, 2].map((v) => {
return i * 2 + v;
}),
);
}
}
return {
vertices: positions,
indices: indexArray,
size: 7,
};
}
export function HeatmapTriangulation(feature: IEncodeFeature) {
const coordinates = feature.coordinates as number[];
if (coordinates.length === 2) {
coordinates.push(0);
}
const size = feature.size as number;
const dir = addDir(-1, 1);
const dir1 = addDir(1, 1);
const dir2 = addDir(-1, -1);
const dir3 = addDir(1, -1);
// [x,y,z, dirx ,diry, weight]
const positions = [
...coordinates,
...dir,
...coordinates,
...dir2,
...coordinates,
...dir3,
...coordinates,
...dir1,
];
const indexArray = [0, 1, 2, 3, 0, 2];
return {
vertices: positions,
indices: indexArray,
size: 5,
};
}
/**
* 3d geomerty
@ -217,3 +289,9 @@ function getHeatmapGeometry(shape: ShapeType2D | ShapeType3D): IExtrudeGeomety {
const geometry = fillPolygon([path]);
return geometry;
}
// 热力图计算范围
function addDir(dirX: number, dirY: number) {
const x = (dirX + 1) / 2;
const y = (dirY + 1) / 2;
return [x, y];
}

View File

@ -1,46 +0,0 @@
// import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer';
// import extrudePolygon, {
// fillPolygon,
// IExtrudeGeomety,
// } from '../../point/shape/extrude';
// import {
// geometryShape,
// ShapeType2D,
// ShapeType3D,
// } from '../../point/shape/Path';
// export default class GridHeatMapBuffer extends BufferBase {
// private verticesOffset: number = 0;
// protected buildFeatures() {
// this.verticesOffset = 0;
// const layerData = this.data as IEncodeFeature[];
// layerData.forEach((feature: IEncodeFeature) => {
// this.calculateFill(feature);
// });
// }
// protected calculateFeatures() {
// const layerData = this.data as IEncodeFeature[];
// const shape = layerData[0].shape as ShapeType3D | ShapeType2D;
// this.verticesCount = layerData.length;
// this.indexCount = 0;
// this.instanceGeometry = this.getGeometry(shape as
// | ShapeType2D
// | ShapeType3D);
// }
// protected calculateFill(feature: IEncodeFeature) {
// feature.bufferInfo = { verticesOffset: this.verticesOffset };
// const coordinates = feature.coordinates as Position;
// this.encodeArray(feature, 1);
// this.attributes.positions.set([...coordinates, 1], this.verticesOffset * 3);
// this.verticesOffset++;
// }
// private getGeometry(shape: ShapeType2D | ShapeType3D): IExtrudeGeomety {
// const path = geometryShape[shape]
// ? geometryShape[shape]()
// : geometryShape.circle();
// // const geometry = ShapeType2D[str as ShapeType2D]
// // ? fillPolygon([path])
// // : extrudePolygon([path]);
// const geometry = fillPolygon([path]);
// return geometry;
// }
// }

View File

@ -0,0 +1,324 @@
import {
AttributeType,
gl,
ICameraService,
IEncodeFeature,
IFramebuffer,
ILayer,
ILayerPlugin,
ILogService,
IModel,
IStyleAttributeService,
ITexture2D,
lazyInject,
TYPES,
} from '@l7/core';
import { mat4 } from 'gl-matrix';
import BaseLayer from '../core/BaseLayer';
import { HeatmapTriangulation } from '../core/triangulation';
import { generateColorRamp, IColorRamp } from '../utils/color';
import heatmap3DFrag from './shaders/heatmap_3d_frag.glsl';
import heatmap3DVert from './shaders/heatmap_3d_vert.glsl';
import heatmapColorFrag from './shaders/heatmap_frag.glsl';
import heatmapFrag from './shaders/heatmap_framebuffer_frag.glsl';
import heatmapVert from './shaders/heatmap_framebuffer_vert.glsl';
import heatmapColorVert from './shaders/heatmap_vert.glsl';
import { heatMap3DTriangulation } from './triangulation';
interface IHeatMapLayerStyleOptions {
opacity: number;
intensity: number;
radius: number;
rampColors: IColorRamp;
}
export default class HeatMapLayer extends BaseLayer<IHeatMapLayerStyleOptions> {
public name: string = 'HeatMapLayer';
protected texture: ITexture2D;
protected colorTexture: ITexture2D;
@lazyInject(TYPES.ICameraService)
protected readonly camera: ICameraService;
protected heatmapFramerBuffer: IFramebuffer;
private intensityModel: IModel;
private colorModel: IModel;
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
const { clear, useFramebuffer } = this.rendererService;
useFramebuffer(this.heatmapFramerBuffer, () => {
clear({
color: [0, 0, 0, 0],
depth: 1,
stencil: 0,
framebuffer: this.heatmapFramerBuffer,
});
this.drawIntensityMode();
});
this.draw3DHeatMap();
// this.drawIntensityMode();
return this;
}
protected buildModels() {
this.registerBuiltinAttributes(this);
this.intensityModel = this.buildHeatMapIntensity();
this.models = [this.intensityModel];
// this.colorModel = this.buildHeatmapColor();
this.colorModel = this.build3dHeatMap();
this.models.push(this.colorModel);
const { rampColors } = this.getStyleOptions();
const imageData = generateColorRamp(rampColors as IColorRamp);
const {
createFramebuffer,
clear,
getViewportSize,
createTexture2D,
useFramebuffer,
} = this.rendererService;
const { width, height } = getViewportSize();
this.heatmapFramerBuffer = createFramebuffer({
color: createTexture2D({
width,
height,
wrapS: gl.CLAMP_TO_EDGE,
wrapT: gl.CLAMP_TO_EDGE,
min: gl.NEAREST,
mag: gl.NEAREST,
}),
});
this.colorTexture = createTexture2D({
data: 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: true,
});
}
private registerBuiltinAttributes(layer: ILayer) {
layer.styleAttributeService.registerStyleAttribute({
name: 'dir',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Dir',
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,
) => {
return [vertex[3], vertex[4]];
},
},
});
// point layer size;
layer.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 = 2 } = feature;
return [size as number];
},
},
});
}
private buildHeatMapIntensity(): IModel {
return this.buildLayerModel({
moduleName: 'heatmapintensity',
vertexShader: heatmapVert,
fragmentShader: heatmapFrag,
triangulation: HeatmapTriangulation,
depth: {
enable: false,
},
blend: {
enable: true,
func: {
srcRGB: gl.ONE,
srcAlpha: 1,
dstRGB: gl.ONE,
dstAlpha: 1,
},
},
});
}
private buildHeatmapColor(): IModel {
this.shaderModuleService.registerModule('heatmapColor', {
vs: heatmapColorVert,
fs: heatmapColorFrag,
});
const { vs, fs, uniforms } = this.shaderModuleService.getModule(
'heatmapColor',
);
const {
createAttribute,
createElements,
createBuffer,
createModel,
} = this.rendererService;
return createModel({
vs,
fs,
attributes: {
a_Position: createAttribute({
buffer: createBuffer({
data: [-1, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0],
type: gl.FLOAT,
}),
size: 3,
}),
a_Uv: createAttribute({
buffer: createBuffer({
data: [0, 1, 1, 1, 0, 0, 1, 0],
type: gl.FLOAT,
}),
size: 2,
}),
},
uniforms: {
...uniforms,
},
depth: {
enable: false,
},
count: 6,
elements: createElements({
data: [0, 2, 1, 2, 3, 1],
type: gl.UNSIGNED_INT,
count: 6,
}),
});
}
private drawIntensityMode() {
const { opacity, intensity = 10, radius = 5 } = this.getStyleOptions();
this.intensityModel.draw({
uniforms: {
u_Opacity: opacity || 1.0,
u_radius: radius,
u_intensity: intensity,
},
});
}
private drawColorMode() {
const { opacity } = this.getStyleOptions();
this.colorModel.draw({
uniforms: {
u_Opacity: opacity || 1.0,
u_colorTexture: this.colorTexture,
u_texture: this.heatmapFramerBuffer,
},
});
}
private draw3DHeatMap() {
const { opacity } = this.getStyleOptions();
const mapbounds = this.map.getBounds();
const invert = mat4.invert(
mat4.create(),
// @ts-ignore
mat4.fromValues(...this.camera.getViewProjectionMatrix()),
) as mat4;
this.colorModel.draw({
uniforms: {
u_Opacity: opacity || 1.0,
u_colorTexture: this.colorTexture,
u_texture: this.heatmapFramerBuffer,
u_extent: [-179.9476, -60.0959, 179.9778, 79.5651],
u_InverseViewProjectionMatrix: [...invert],
},
});
}
private build3dHeatMap() {
const { getViewportSize } = this.rendererService;
const { width, height } = getViewportSize();
const triangulation = heatMap3DTriangulation(256, 128);
this.shaderModuleService.registerModule('heatmap3dColor', {
vs: heatmap3DVert,
fs: heatmap3DFrag,
});
const { vs, fs, uniforms } = this.shaderModuleService.getModule(
'heatmap3dColor',
);
const {
createAttribute,
createElements,
createBuffer,
createModel,
} = this.rendererService;
return createModel({
vs,
fs,
attributes: {
a_Position: createAttribute({
buffer: createBuffer({
data: triangulation.vertices,
type: gl.FLOAT,
}),
size: 3,
}),
a_Uv: createAttribute({
buffer: createBuffer({
data: triangulation.uvs,
type: gl.FLOAT,
}),
size: 2,
}),
},
primitive: gl.TRIANGLES,
uniforms: {
...uniforms,
},
depth: {
enable: false,
},
elements: createElements({
data: triangulation.indices,
type: gl.UNSIGNED_INT,
count: triangulation.indices.length,
}),
});
}
}

View File

@ -0,0 +1,16 @@
uniform sampler2D u_texture;
uniform sampler2D u_colorTexture;
uniform float u_Opacity;
varying vec2 v_texCoord;
void main(){
float intensity = texture2D(u_texture, v_texCoord).r;
vec2 ramp_pos = vec2(
fract(16.0 * (1.0 - intensity)),
floor(16.0 * (1.0 - intensity)) / 16.0);
// vec4 color = texture2D(u_colorTexture,vec2(0.5,1.0-intensity));
vec4 color = texture2D(u_colorTexture,ramp_pos);
gl_FragColor = color;
// gl_FragColor.a = color.a * smoothstep(0.0,0.12,intensity) * u_Opacity;
}

View File

@ -0,0 +1,42 @@
precision highp float;
attribute vec3 a_Position;
attribute vec2 a_Uv;
uniform sampler2D u_texture;
uniform vec4 u_extent;
varying vec2 v_texCoord;
uniform mat4 u_ModelMatrix;
uniform mat4 u_InverseViewProjectionMatrix;
#pragma include "projection"
void main() {
v_texCoord = a_Uv;
vec2 pos = a_Uv * vec2(2.0) - vec2(1.0);
vec4 n_0 = vec4(pos, 0.0, 1.0) - u_ViewportCenterProjection;
vec4 n_1 = vec4(pos, 1.0, 1.0) - u_ViewportCenterProjection;
vec4 m_0 = u_InverseViewProjectionMatrix * n_0 ;
vec4 m_1 = u_InverseViewProjectionMatrix * n_1;
m_0 = m_0 / m_0.w;
m_1 = m_1 / m_1.w;
float zPos = (0.0 - m_0.z) / (m_1.z - m_0.z);
vec4 mapCoord = m_0 + zPos * (m_1 - m_0);
// vec4 p = u_InverseViewProjectionMatrix * (vec4(pos,0,1) - u_ViewportCenterProjection);
// p = p /p.w;
// pos.y = 1.0 -pos.y;
// vec2 minxy = project_position(vec4(u_extent.xy, 0, 1.0)).xy;
// vec2 maxxy = project_position(vec4(u_extent.zw, 0, 1.0)).xy;
// vec2 step = (maxxy - minxy);
// vec2 pos = minxy + (vec2(a_Position.x, a_Position.y ) + vec2(1.0)) / vec2(2.0) * step;
float intensity = texture2D(u_texture, v_texCoord).r;
gl_Position = project_common_position_to_clipspace(vec4(mapCoord.xy, intensity * 100., 1.0));
// gl_Position = vec4(pos,0.,1.0);
// v_texCoord = (gl_Position.xy + vec2(1.0)) / vec2(2.0) / gl_Position.w;
// v_texCoord.y = 1.0 - v_texCoord.y;
}

View File

@ -0,0 +1,16 @@
uniform sampler2D u_texture;
uniform sampler2D u_colorTexture;
uniform float u_Opacity;
varying vec2 v_texCoord;
void main(){
float intensity = texture2D(u_texture, v_texCoord).r;
vec2 ramp_pos = vec2(
fract(16.0 * (1.0 - intensity)),
floor(16.0 * (1.0 - intensity)) / 16.0);
// vec4 color = texture2D(u_colorTexture,vec2(0.5,1.0-intensity));
vec4 color = texture2D(u_colorTexture,ramp_pos);
gl_FragColor = color;
gl_FragColor.a = color.a * smoothstep(0.1,0.5,intensity) * u_Opacity;
}

View File

@ -0,0 +1,12 @@
precision highp float;
uniform float u_intensity;
varying float v_weight;
varying vec2 v_extrude;
void main(){
float GAUSS_COEF = 0.3989422804014327;
float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude);
float val = v_weight * u_intensity * GAUSS_COEF * exp(d);
gl_FragColor = vec4(val, val, val, val);
}

View File

@ -0,0 +1,27 @@
precision highp float;
attribute vec3 a_Position;
attribute float a_Size;
attribute vec2 a_Dir;
uniform float u_intensity;
uniform float u_radius;
varying vec2 v_extrude;
varying float v_weight;
uniform mat4 u_ModelMatrix;
#pragma include "projection"
void main(){
v_weight = a_Size;
float GAUSS_COEF = 0.3989422804014327;
float ZERO = 1.0 / 255.0 / 16.0;
float extrude_x = a_Dir.x * 2.0 -1.0;
float extrude_y = a_Dir.y * 2.0 -1.0;
vec2 extrude_dir = normalize(vec2(extrude_x,extrude_y));
float S = sqrt(-2.0 * log(ZERO / a_Size / u_intensity / GAUSS_COEF)) / 3.0;
v_extrude = extrude_dir * S;
vec2 offset = project_pixel(v_extrude * u_radius);
vec4 project_pos = project_position(vec4(a_Position.xy, 0.0, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0.0, 1.0));
}

View File

@ -0,0 +1,10 @@
precision highp float;
attribute vec3 a_Position;
attribute vec2 a_Uv;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
v_texCoord = a_Uv;
float intensity = texture2D(u_texture, v_texCoord).r;
gl_Position = vec4(a_Position.xy,intensity -0.5, 1.);
}

View File

@ -0,0 +1,36 @@
import { IEncodeFeature, IParseDataItem } from '@l7/core';
// @ts-ignore
import Martini from '@mapbox/martini';
export function heatMap3DTriangulation(width: number, height: number) {
const indices = [];
const vertices = [];
const uvs = [];
const gridX1 = width + 1;
const gridY1 = height + 1;
const widthHalf = width / 2;
const heightHalf = height / 2;
for (let iy = 0; iy < gridY1; iy++) {
const y = iy - heightHalf;
for (let ix = 0; ix < gridX1; ix++) {
const x = ix - widthHalf;
vertices.push(x / widthHalf, -y / heightHalf, 0);
uvs.push(ix / width);
uvs.push(1 - iy / height);
}
}
for (let iy = 0; iy < height; iy++) {
for (let ix = 0; ix < width; ix++) {
const a = ix + gridX1 * iy;
const b = ix + gridX1 * (iy + 1);
const c = ix + 1 + gridX1 * (iy + 1);
const d = ix + 1 + gridX1 * iy;
indices.push(a, b, d);
indices.push(b, c, d);
}
}
return {
vertices,
indices,
uvs,
};
}

View File

@ -1,14 +1,19 @@
import { container, ILayerPlugin, TYPES } from '@l7/core';
import BaseLayer from './core/BaseLayer';
import HeatMapGridLayer from './heatmap/grid';
import HeatMapLayer from './heatmap/heatmap';
import ArcLineLayer from './line/arc';
import Arc2DLineLayer from './line/arc2d';
import LineLayer from './line/index';
import Point3dLayer from './point/extrude';
import PointImageLayer from './point/image';
import PointLayer from './point/index';
import TextLayer from './point/text';
// import Point from './point/point';
import PolygonLayer from './polygon';
import Polygon3DLayer from './polygon/polygon3D';
import ImageLayer from './raster/image';
import RasterLayer from './raster/raster';
import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin';
import DataMappingPlugin from './plugins/DataMappingPlugin';
@ -77,7 +82,11 @@ export {
Polygon3DLayer,
ImageLayer,
HeatMapGridLayer,
// Line,
ArcLineLayer,
Arc2DLineLayer,
RasterLayer,
HeatMapLayer,
TextLayer,
// ImageLayer,
// HeatMapLayer,
};

View File

@ -0,0 +1,107 @@
import { AttributeType, gl, IEncodeFeature, ILayer } from '@l7/core';
import BaseLayer from '../core/BaseLayer';
import { LineArcTriangulation } from '../core/triangulation';
import line_arc_frag from './shaders/line_arc_frag.glsl';
import line_arc_vert from './shaders/line_arc_vert.glsl';
interface IArcLayerStyleOptions {
opacity: number;
segmentNumber: number;
}
export default class ArcLineLayer extends BaseLayer<IArcLayerStyleOptions> {
public name: string = 'LineLayer';
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
const { opacity } = this.getStyleOptions();
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 1,
segmentNumber: 30,
},
}),
);
return this;
}
protected buildModels() {
this.registerBuiltinAttributes(this);
this.models = [
this.buildLayerModel({
moduleName: 'arcline',
vertexShader: line_arc_vert,
fragmentShader: line_arc_frag,
triangulation: LineArcTriangulation,
blend: {
enable: true,
func: {
srcRGB: gl.ONE,
srcAlpha: 1,
dstRGB: gl.ONE,
dstAlpha: 1,
},
},
}),
];
}
private registerBuiltinAttributes(layer: ILayer) {
// point layer size;
layer.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 } = feature;
return Array.isArray(size) ? [size[0]] : [size as number];
},
},
});
layer.styleAttributeService.registerStyleAttribute({
name: 'instance', // 弧线起始点信息
type: AttributeType.Attribute,
descriptor: {
name: 'a_Instance',
buffer: {
usage: gl.STATIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 4,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
return [vertex[3], vertex[4], vertex[5], vertex[6]];
},
},
});
}
}

View File

@ -0,0 +1,108 @@
import { AttributeType, gl, IEncodeFeature, ILayer } from '@l7/core';
import BaseLayer from '../core/BaseLayer';
import { LineArcTriangulation } from '../core/triangulation';
import line_arc2d_vert from './shaders/line_arc2d_vert.glsl';
import line_arc_frag from './shaders/line_arc_frag.glsl';
interface IArcLayerStyleOptions {
opacity: number;
segmentNumber: number;
}
export default class Arc2DLineLayer extends BaseLayer<IArcLayerStyleOptions> {
public name: string = 'LineLayer';
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
const { opacity } = this.getStyleOptions();
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 1,
segmentNumber: 30,
},
}),
);
return this;
}
protected buildModels() {
this.registerBuiltinAttributes(this);
this.models = [
this.buildLayerModel({
moduleName: 'arc2dline',
vertexShader: line_arc2d_vert,
fragmentShader: line_arc_frag,
triangulation: LineArcTriangulation,
depth: { enable: false },
blend: {
enable: true,
func: {
srcRGB: gl.ONE,
srcAlpha: 1,
dstRGB: gl.ONE,
dstAlpha: 1,
},
},
}),
];
}
private registerBuiltinAttributes(layer: ILayer) {
// point layer size;
layer.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 } = feature;
return Array.isArray(size) ? [size[0]] : [size as number];
},
},
});
layer.styleAttributeService.registerStyleAttribute({
name: 'instance', // 弧线起始点信息
type: AttributeType.Attribute,
descriptor: {
name: 'a_Instance',
buffer: {
usage: gl.STATIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 4,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
attributeIdx: number,
) => {
return [vertex[3], vertex[4], vertex[5], vertex[6]];
},
},
});
}
}

View File

@ -26,7 +26,7 @@ export default class LineLayer extends BaseLayer<IPointLayerStyleOptions> {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 0,
u_Opacity: opacity || 1.0,
},
}),
);

View File

@ -0,0 +1,89 @@
precision mediump float;
attribute vec4 a_Color;
attribute vec3 a_Position;
attribute vec4 a_Instance;
attribute float a_Size;
uniform mat4 u_ModelMatrix;
uniform float segmentNumber;
varying vec4 v_color;
#pragma include "projection"
float maps (float value, float start1, float stop1, float start2, float stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}
float getSegmentRatio(float index) {
return smoothstep(0.0, 1.0, index / (segmentNumber - 1.));
}
float paraboloid(vec2 source, vec2 target, float ratio) {
vec2 x = mix(source, target, ratio);
vec2 center = mix(source, target, 0.5);
float dSourceCenter = distance(source, center);
float dXCenter = distance(x, center);
return (dSourceCenter + dXCenter) * (dSourceCenter - dXCenter);
}
vec3 getPos(vec2 source, vec2 target, float segmentRatio) {
float vertex_height = paraboloid(source, target, segmentRatio);
return vec3(
mix(source, target, segmentRatio),
sqrt(max(0.0, vertex_height))
);
}
vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) {
// normalized direction of the line
vec2 dir_screenspace = normalize(line_clipspace);
// rotate by 90 degrees
dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x);
vec2 offset = dir_screenspace * offset_direction * a_Size / 2.0;
return offset;
}
float getAngularDist (vec2 source, vec2 target) {
vec2 delta = source - target;
vec2 sin_half_delta = sin(delta / 2.0);
float a =
sin_half_delta.y * sin_half_delta.y +
cos(source.y) * cos(target.y) *
sin_half_delta.x * sin_half_delta.x;
return 2.0 * atan(sqrt(a), sqrt(1.0 - a));
}
vec2 interpolate (vec2 source, vec2 target, float angularDist, float t) {
// if the angularDist is PI, linear interpolation is applied. otherwise, use spherical interpolation
if(abs(angularDist - PI) < 0.001) {
return (1.0 - t) * source + t * target;
}
float a = sin((1.0 - t) * angularDist) / sin(angularDist);
float b = sin(t * angularDist) / sin(angularDist);
vec2 sin_source = sin(source);
vec2 cos_source = cos(source);
vec2 sin_target = sin(target);
vec2 cos_target = cos(target);
float x = a * cos_source.y * cos_source.x + b * cos_target.y * cos_target.x;
float y = a * cos_source.y * sin_source.x + b * cos_target.y * sin_target.x;
float z = a * sin_source.y + b * sin_target.y;
return vec2(atan(y, x), atan(z, sqrt(x * x + y * y)));
}
void main() {
v_color = a_Color;
vec2 source = radians(a_Instance.rg);
vec2 target = radians(a_Instance.ba);
float angularDist = getAngularDist(source, target);
float segmentIndex = a_Position.x;
float segmentRatio = getSegmentRatio(segmentIndex);
float indexDir = mix(-1.0, 1.0, step(segmentIndex, 0.0));
float nextSegmentRatio = getSegmentRatio(segmentIndex + indexDir);
vec4 curr = project_position(vec4(degrees(interpolate(source, target, angularDist, segmentRatio)), 0.0, 1.0));
vec4 next = project_position(vec4(degrees(interpolate(source, target, angularDist, nextSegmentRatio)), 0.0, 1.0));
vec2 offset = getExtrusionOffset((next.xy - curr.xy) * indexDir, a_Position.y);
// vec4 project_pos = project_position(vec4(curr.xy, 0, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(curr.xy + offset, 0, 1.0));
}

View File

@ -0,0 +1,10 @@
precision mediump float;
uniform float u_Opacity;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
gl_FragColor.a = v_color.a * u_Opacity;
}

View File

@ -0,0 +1,66 @@
precision mediump float;
attribute vec3 a_Position;
attribute vec4 a_Instance;
attribute vec4 a_Color;
attribute float a_Size;
uniform mat4 u_ModelMatrix;
uniform float segmentNumber;
varying vec4 v_color;
#pragma include "projection"
float maps (float value, float start1, float stop1, float start2, float stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}
float getSegmentRatio(float index) {
return smoothstep(0.0, 1.0, index / (segmentNumber - 1.0));
}
float paraboloid(vec2 source, vec2 target, float ratio) {
vec2 x = mix(source, target, ratio);
vec2 center = mix(source, target, 0.5);
float dSourceCenter = distance(source, center);
float dXCenter = distance(x, center);
return (dSourceCenter + dXCenter) * (dSourceCenter - dXCenter);
}
vec3 getPos(vec2 source, vec2 target, float segmentRatio) {
float vertex_height = paraboloid(source, target, segmentRatio);
return vec3(
mix(source, target, segmentRatio),
sqrt(max(0.0, vertex_height))
);
}
vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) {
// normalized direction of the line
vec2 dir_screenspace = normalize(line_clipspace);
// rotate by 90 degrees
dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x);
vec2 offset = dir_screenspace * offset_direction * a_Size / 2.0;
return offset;
}
void main() {
v_color = a_Color;
vec2 source = project_position(vec4(a_Instance.rg, 0, 0)).xy;
vec2 target = project_position(vec4(a_Instance.ba, 0, 0)).xy;
float segmentIndex = a_Position.x;
float segmentRatio = getSegmentRatio(segmentIndex);
float indexDir = mix(-1.0, 1.0, step(segmentIndex, 0.0));
float nextSegmentRatio = getSegmentRatio(segmentIndex + indexDir);
vec3 curr = getPos(source, target, segmentRatio);
vec3 next = getPos(source, target, nextSegmentRatio);
vec2 offset = getExtrusionOffset((next.xy - curr.xy) * indexDir, a_Position.y);
// vec4 project_pos = project_position(vec4(curr, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(curr.xy + project_pixel(offset), curr.z, 1.0));
}

View File

@ -12,6 +12,7 @@ uniform mat4 u_ModelMatrix;
varying vec4 v_color;
varying float v_dash_array;
varying vec3 v_normal;
#pragma include "projection"
void main() {
v_normal = a_Normal;

View File

@ -193,7 +193,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
}
private getDefaultType(firstValue: unknown) {
let type = ScaleTypes.QUANTIZE;
let type = ScaleTypes.LINEAR;
if (typeof firstValue === 'string') {
type = dateRegex.test(firstValue) ? ScaleTypes.TIME : ScaleTypes.CAT;
}

View File

@ -26,7 +26,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 0,
u_Opacity: opacity || 1.0,
},
}),
);

View File

@ -46,7 +46,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 0,
u_Opacity: opacity || 1.0,
u_texture: createTexture2D({
data: this.iconService.getCanvas(),
width: 1024,

View File

@ -44,7 +44,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 0,
u_Opacity: opacity || 1.0,
},
}),
);
@ -118,8 +118,8 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
vertex: number[],
attributeIdx: number,
) => {
const { size = 2 } = feature;
return [size as number];
const { size } = feature;
return Array.isArray(size) ? [size[0]] : [size as number];
},
},
});

View File

@ -13,7 +13,9 @@ uniform vec4 u_activeColor : [1.0, 0.0, 0.0, 1.0];
varying vec2 v_uv;
varying float v_gamma_scale;
varying vec4 v_color;
#pragma include "projection"
void main() {
v_color = a_color;
v_uv = a_tex / u_sdf_map_size;
@ -24,10 +26,10 @@ void main() {
vec4 project_pos = project_position(vec4(a_Position, 1.0));
vec4 projected_position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0));
gl_Position = vec4(projected_position.xy / projected_position.w
+ a_offset * fontScale / u_viewport_size * 2., 0.0, 1.0);
v_gamma_scale = gl_Position.w;
}

View File

@ -0,0 +1,188 @@
import {
AttributeType,
gl,
IEncodeFeature,
IFontOptions,
ILayer,
ILayerPlugin,
ILogService,
IStyleAttributeService,
lazyInject,
TYPES,
} from '@l7/core';
import BaseLayer from '../core/BaseLayer';
import { getGlyphQuads, shapeText } from '../utils/symbol-layout';
import textFrag from './shaders/text_frag.glsl';
import textVert from './shaders/text_vert.glsl';
interface IPointTextLayerStyleOptions {
opacity: number;
textAnchor: string;
textOffset: [number, number];
spacing: number;
padding: [number, number];
stroke: string;
strokeWidth: number;
strokeOpacity: number;
fontWeight: string;
fontFamily: string;
textAllowOverlap: boolean;
}
export function PointTriangulation(feature: IEncodeFeature) {
const coordinates = feature.coordinates as number[];
return {
vertices: [...coordinates, ...coordinates, ...coordinates, ...coordinates],
indices: [0, 1, 2, 2, 3, 0],
size: coordinates.length,
};
}
export default class TextLayer extends BaseLayer<IPointTextLayerStyleOptions> {
public name: string = 'PointLayer';
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
const { opacity } = this.getStyleOptions();
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 1.0,
},
}),
);
return this;
}
protected buildModels() {
this.registerBuiltinAttributes(this);
this.models = [
this.buildLayerModel({
moduleName: 'pointText',
vertexShader: textVert,
fragmentShader: textFrag,
triangulation: PointTriangulation,
depth: { enable: false },
blend: {
enable: true,
func: {
srcRGB: gl.SRC_ALPHA,
srcAlpha: 1,
dstRGB: gl.ONE_MINUS_SRC_ALPHA,
dstAlpha: 1,
},
},
}),
];
}
private registerBuiltinAttributes(layer: ILayer) {
layer.styleAttributeService.registerStyleAttribute({
name: 'textOffsets',
type: AttributeType.Attribute,
descriptor: {
name: 'a_textOffsets',
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,
) => {
const extrude = [-1, -1, 1, -1, 1, 1, -1, 1];
const extrudeIndex = (attributeIdx % 4) * 2;
return [extrude[extrudeIndex], extrude[extrudeIndex + 1]];
},
},
});
// point layer size;
layer.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 } = feature;
return Array.isArray(size) ? [size[0]] : [size as number];
},
},
});
// point layer size;
layer.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 = layer.configService.getConfig().shape2d as string[];
const shapeIndex = shape2d.indexOf(shape as string);
return [shapeIndex];
},
},
});
}
private iniTextFont() {
const { fontWeight = 'normal', fontFamily } = this.getStyleOptions();
const data = this.getEncodedData();
const characterSet: string[] = [];
data.forEach((item: IEncodeFeature) => {
let { text = '' } = item;
text = text.toString();
for (const char of text) {
// 去重
if (characterSet.indexOf(char) === -1) {
characterSet.push(char);
}
}
});
this.fontService.setFontOptions({
characterSet,
fontWeight,
fontFamily,
});
}
}

View File

@ -40,7 +40,7 @@ export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 0,
u_Opacity: opacity || 1.0,
},
}),
);

View File

@ -26,7 +26,7 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 0,
u_Opacity: opacity || 1.0,
},
}),
);

View File

@ -1,33 +0,0 @@
// import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer';
// interface IImageFeature extends IEncodeFeature {
// images: any[];
// }
// export default class ImageBuffer extends BaseBuffer {
// protected calculateFeatures() {
// this.verticesCount = 6;
// this.indexCount = 6;
// }
// protected buildFeatures() {
// this.attributes.uv = new Float32Array(this.verticesCount * 2);
// const layerData = this.data as IImageFeature[];
// const coordinates = layerData[0].coordinates as Position[];
// const positions: number[] = [
// ...coordinates[0],
// 0,
// coordinates[1][0],
// coordinates[0][1],
// 0,
// ...coordinates[1],
// 0,
// ...coordinates[0],
// 0,
// ...coordinates[1],
// 0,
// coordinates[0][0],
// coordinates[1][1],
// 0,
// ];
// this.attributes.positions.set(positions, 0);
// this.attributes.uv.set([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0], 0);
// }
// }

View File

@ -0,0 +1,22 @@
import { IEncodeFeature, IParseDataItem } from '@l7/core';
// @ts-ignore
import Martini from '@mapbox/martini';
export function RasterTriangulation(parserData: IParseDataItem) {
const { coordinates, data, min, max, width, height } = parserData;
const maxlength = Math.max(width, height);
const gridSize = Math.pow(2, Math.ceil(Math.log2(maxlength))) + 1;
const terrain = new Float32Array(gridSize * gridSize);
for (let i = 0; i < width; i++) {
for (let j = 0; j < height; j++) {
terrain[i * gridSize + j] = data[i * width + j];
}
}
const martini = new Martini(gridSize);
const tile = martini.createTile(terrain);
const mesh = tile.getMesh(gridSize / 2);
return {
vertices: Array.from(mesh.vertices) as number[],
indices: Array.from(mesh.triangles) as number[],
size: 2,
};
}

View File

@ -0,0 +1,128 @@
import {
AttributeType,
gl,
IEncodeFeature,
ILayer,
ILayerPlugin,
ILogService,
IRasterParserDataItem,
IStyleAttributeService,
ITexture2D,
lazyInject,
TYPES,
} from '@l7/core';
import BaseLayer from '../core/BaseLayer';
import { generateColorRamp, IColorRamp } from '../utils/color';
import { RasterTriangulation } from './buffers/triangulation';
import rasterFrag from './shaders/raster_frag.glsl';
import rasterVert from './shaders/raster_vert.glsl';
interface IRasterLayerStyleOptions {
opacity: number;
min: number;
max: number;
extent: [number, number, number, number];
rampColors: IColorRamp;
}
export default class RasterLayer extends BaseLayer<IRasterLayerStyleOptions> {
public name: string = 'e';
protected texture: ITexture2D;
protected colorTexture: ITexture2D;
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
const { opacity } = this.getStyleOptions();
const parserDataItem = this.getSource().data.dataArray[0];
const { coordinates, width, height, min, max } = parserDataItem;
this.models.forEach((model) =>
model.draw({
uniforms: {
u_Opacity: opacity || 1,
u_texture: this.texture,
u_min: min,
u_width: width,
u_height: height,
u_max: max,
u_colorTexture: this.colorTexture,
u_extent: [...coordinates[0], ...coordinates[1]],
},
}),
);
return this;
}
protected buildModels() {
const parserDataItem = this.getSource().data.dataArray[0];
const { createTexture2D } = this.rendererService;
this.texture = createTexture2D({
data: parserDataItem.data,
width: parserDataItem.width,
height: parserDataItem.height,
format: gl.LUMINANCE,
type: gl.FLOAT,
});
const { rampColors } = this.getStyleOptions();
const imageData = generateColorRamp(rampColors as IColorRamp);
this.colorTexture = createTexture2D({
data: imageData.data,
width: imageData.width,
height: imageData.height,
flipY: true,
});
this.models = [this.buildRasterModel()];
}
private buildRasterModel() {
const source = this.getSource();
const sourceFeature = source.data.dataArray[0];
const triangulation = RasterTriangulation(sourceFeature);
this.shaderModuleService.registerModule('raster', {
vs: rasterVert,
fs: rasterFrag,
});
const { vs, fs, uniforms } = this.shaderModuleService.getModule('raster');
const {
createAttribute,
createElements,
createBuffer,
createModel,
} = this.rendererService;
return createModel({
vs,
fs,
attributes: {
a_Position: createAttribute({
buffer: createBuffer({
data: triangulation.vertices,
type: gl.FLOAT,
}),
size: 2,
}),
},
primitive: gl.TRIANGLES,
uniforms: {
...uniforms,
},
depth: {
enable: true,
},
elements: createElements({
data: triangulation.indices,
type: gl.UNSIGNED_INT,
count: triangulation.indices.length,
}),
});
}
}

View File

@ -0,0 +1,9 @@
varying vec4 v_color;
uniform float u_Opacity: 1.0;
#define PI 3.141592653589793
void main() {
gl_FragColor = v_color;
gl_FragColor.a *= u_Opacity;
}

View File

@ -0,0 +1,40 @@
precision highp float;
uniform mat4 u_ModelMatrix;
attribute vec3 a_Position;
uniform vec4 u_extent;
uniform sampler2D u_texture;
uniform sampler2D u_colorTexture;
uniform float u_min;
uniform float u_max;
uniform float u_width;
uniform float u_height;
varying vec2 v_texCoord;
varying vec4 v_color;
#pragma include "projection"
void main() {
vec2 uv = a_Position.xy / vec2(u_width, u_height);
vec2 minxy = project_position(vec4(u_extent.xy, 0, 1.0)).xy;
vec2 maxxy = project_position(vec4(u_extent.zw, 0, 1.0)).xy;
float value = texture2D(u_texture, vec2(uv.x,1.0 - uv.y)).x;
vec2 step = (maxxy - minxy) / vec2(u_width, u_height);
vec2 pos = minxy + vec2(a_Position.x, a_Position.y ) * step;
// v_texCoord = a_Uv;
value = clamp(value,u_min,u_max);
float value1 = (value - u_min) / (u_max -u_min);
vec2 ramp_pos = vec2(
fract(16.0 * (1.0 - value1)),
floor(16.0 * (1.0 - value1)) / 16.0);
v_color = texture2D(u_colorTexture,ramp_pos);
// if(uv.x > 1.0 || uv.y > 1.0) {
// v_color = vec4(0.);
// }
// vec2 range = u_extent.zw - u_extent.xy;
// vec4 project_pos = project_position(vec4(pos, 0, 1.0));
gl_Position = project_common_position_to_clipspace(vec4(pos.xy, project_scale(value) * 10., 1.0));
}

View File

@ -1,4 +1,8 @@
import * as d3 from 'd3-color';
export interface IColorRamp {
positions: number[];
colors: string[];
}
export function rgb2arr(str: string) {
const color = d3.color(str) as d3.RGBColor;
const arr = [0, 0, 0, 0];
@ -10,3 +14,23 @@ export function rgb2arr(str: string) {
}
return arr;
}
export function generateColorRamp(colorRamp: IColorRamp): ImageData {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.width = 256;
canvas.height = 1;
const gradient = ctx.createLinearGradient(0, 0, 256, 0);
let data = null;
const min = colorRamp.positions[0];
const max = colorRamp.positions[colorRamp.positions.length - 1];
for (let i = 0; i < colorRamp.colors.length; ++i) {
const value = (colorRamp.positions[i] - min) / (max - min);
gradient.addColorStop(value, colorRamp.colors[i]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1);
data = new Uint8ClampedArray(ctx.getImageData(0, 0, 256, 1).data);
return new ImageData(data, 16, 16);
}

View File

@ -0,0 +1,260 @@
/**
*
* @param {string} anchor
* @return {alignment} alignment
*/
function getAnchorAlignment(anchor: string) {
let horizontalAlign = 0.5;
let verticalAlign = 0.5;
switch (anchor) {
case 'right':
case 'top-right':
case 'bottom-right':
horizontalAlign = 1;
break;
case 'left':
case 'top-left':
case 'bottom-left':
horizontalAlign = 0;
break;
default:
horizontalAlign = 0.5;
}
switch (anchor) {
case 'bottom':
case 'bottom-right':
case 'bottom-left':
verticalAlign = 1;
break;
case 'top':
case 'top-right':
case 'top-left':
verticalAlign = 0;
break;
default:
verticalAlign = 0.5;
}
return { horizontalAlign, verticalAlign };
}
// justify right = 1, left = 0, center = 0.5
function justifyLine(
positionedGlyphs: any,
glyphMap: any,
start: number,
end: number,
justify: number,
) {
if (!justify) {
return;
}
const lastPositionedGlyph = positionedGlyphs[end];
const glyph = lastPositionedGlyph.glyph;
if (glyph) {
const lastAdvance = glyphMap[glyph].advance * lastPositionedGlyph.scale;
const lineIndent = (positionedGlyphs[end].x + lastAdvance) * justify;
for (let j = start; j <= end; j++) {
positionedGlyphs[j].x -= lineIndent;
}
}
}
// justify right=1 left=0 center=0.5
// horizontalAlign right=1 left=0 center=0.5
// verticalAlign right=1 left=0 center=0.5
function align(
positionedGlyphs: any[],
justify: number,
horizontalAlign: number,
verticalAlign: number,
maxLineLength: number,
lineHeight: number,
lineCount: number,
) {
const shiftX = (justify - horizontalAlign) * maxLineLength;
const shiftY = (-verticalAlign * lineCount + 0.5) * lineHeight;
for (const glyphs of positionedGlyphs) {
glyphs.x += shiftX;
glyphs.y += shiftY;
}
}
function shapeLines(
shaping: any,
glyphMap: any,
lines: any[],
lineHeight: number,
textAnchor: string,
textJustify: string,
spacing: number,
) {
// buffer 为 4
const yOffset = -8;
let x = 0;
let y = yOffset;
let maxLineLength = 0;
const positionedGlyphs = shaping.positionedGlyphs;
const justify =
textJustify === 'right' ? 1 : textJustify === 'left' ? 0 : 0.5;
const lineStartIndex = positionedGlyphs.length;
lines.forEach((line) => {
line.split('').forEach((char: string) => {
const glyph = glyphMap[char];
const baselineOffset = 0;
if (glyph) {
positionedGlyphs.push({
glyph: char,
x,
y: y + baselineOffset,
vertical: false, // TODO目前只支持水平方向
scale: 1,
metrics: glyph,
});
x += glyph.advance + spacing;
}
});
// 左右对齐
if (positionedGlyphs.length !== lineStartIndex) {
const lineLength = x - spacing;
maxLineLength = Math.max(lineLength, maxLineLength);
justifyLine(
positionedGlyphs,
glyphMap,
lineStartIndex,
positionedGlyphs.length - 1,
justify,
);
}
x = 0;
y += lineHeight;
});
const { horizontalAlign, verticalAlign } = getAnchorAlignment(textAnchor);
align(
positionedGlyphs,
justify,
horizontalAlign,
verticalAlign,
maxLineLength,
lineHeight,
lines.length,
);
// 计算包围盒
const height = y - yOffset;
shaping.top += -verticalAlign * height;
shaping.bottom = shaping.top + height;
shaping.left += -horizontalAlign * maxLineLength;
shaping.right = shaping.left + maxLineLength;
}
/**
*
*
* @param {string} text
* @param {*} glyphs mapping
* @param {number} lineHeight
* @param {string} textAnchor
* @param {string} textJustify
* @param {number} spacing
* @param {[number, number]} translate &
* @return {boolean|shaping}
*/
export function shapeText(
text: string,
glyphs: any,
lineHeight: number,
textAnchor: string,
textJustify: string,
spacing: number,
translate: [number, number],
) {
// TODO处理换行
const lines = text.split('\n');
const positionedGlyphs: any[] = [];
const shaping = {
positionedGlyphs,
top: translate[1],
bottom: translate[1],
left: translate[0],
right: translate[0],
lineCount: lines.length,
text,
};
shapeLines(
shaping,
glyphs,
lines,
lineHeight,
textAnchor,
textJustify,
spacing,
);
if (!positionedGlyphs.length) {
return false;
}
return shaping;
}
export function getGlyphQuads(
shaping: any,
textOffset: [number, number],
alongLine: boolean,
) {
const { positionedGlyphs } = shaping;
const quads = [];
for (const positionedGlyph of positionedGlyphs) {
const rect = positionedGlyph.metrics;
// The rects have an addditional buffer that is not included in their size.
const rectBuffer = 4;
const halfAdvance = (rect.advance * positionedGlyph.scale) / 2;
const glyphOffset = alongLine
? [positionedGlyph.x + halfAdvance, positionedGlyph.y]
: [0, 0];
const builtInOffset = alongLine
? [0, 0]
: [
positionedGlyph.x + halfAdvance + textOffset[0],
positionedGlyph.y + textOffset[1],
];
const x1 =
(0 - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset[0];
const y1 = (0 - rectBuffer) * positionedGlyph.scale + builtInOffset[1];
const x2 = x1 + rect.width * positionedGlyph.scale;
const y2 = y1 + rect.height * positionedGlyph.scale;
const tl = { x: x1, y: y1 };
const tr = { x: x2, y: y1 };
const bl = { x: x1, y: y2 };
const br = { x: x2, y: y2 };
// TODO处理字符旋转的情况
quads.push({ tl, tr, bl, br, tex: rect, glyphOffset });
}
return quads;
}

View File

@ -22,6 +22,8 @@
"@l7/core": "^0.0.1",
"inversify": "^5.0.1",
"inversify-logging": "^0.2.1",
"regl": "^1.3.11"
"reflect-metadata": "^0.1.13",
"regl": "^1.3.11",
"gl": "^4.4.0"
}
}

View File

@ -22,6 +22,7 @@ export default class ReglTexture2D implements ITexture2D {
type = gl.UNSIGNED_BYTE,
width,
height,
flipY = false,
format = gl.RGBA,
mipmap = false,
wrapS = gl.CLAMP_TO_EDGE,
@ -46,6 +47,7 @@ export default class ReglTexture2D implements ITexture2D {
mag: filterMap[mag],
min: filterMap[min],
alignment,
flipY,
colorSpace: colorSpaceMap[colorSpace],
premultiplyAlpha,
aniso,

View File

@ -3,6 +3,7 @@ import csv from './parser/csv';
import geojson from './parser/geojson';
import image from './parser/image';
import json from './parser/json';
import raster from './parser/raster';
import Source from './source';
import { cluster } from './transform/cluster';
import { aggregatorToGrid } from './transform/grid';
@ -12,6 +13,7 @@ registerParser('geojson', geojson);
registerParser('image', image);
registerParser('csv', csv);
registerParser('json', json);
registerParser('raster', raster);
registerTransform('cluster', cluster);
registerTransform('grid', aggregatorToGrid);
registerTransform('hexagon', pointToHexbin);

View File

@ -1,5 +1,5 @@
import { IParserData } from '@l7/core';
import { getImage } from '@l7/utils';
import { IParserData } from '../interface';
interface IImageCfg {
extent: [number, number, number, number];
}

View File

@ -0,0 +1,19 @@
import { IParserData, IRasterCfg } from '@l7/core';
export default function raster(data: number[], cfg: IRasterCfg): IParserData {
const { extent, width, height, min, max } = cfg;
const resultData = {
_id: 1,
dataArray: [
{
_id: 1,
data: Array.from(data),
width,
height,
min,
max,
coordinates: [[extent[0], extent[1]], [extent[2], extent[3]]],
},
],
};
return resultData;
}

View File

@ -1,5 +1,8 @@
import { storiesOf } from '@storybook/react';
import * as React from 'react';
import Arc2DLineDemo from './components/Arc2DLine';
import ArcLineDemo from './components/Arcline';
import HeatMapDemo from './components/heatMap';
import GridHeatMap from './components/heatMapgrid';
import LineLayer from './components/Line';
import PointDemo from './components/Point';
@ -7,6 +10,7 @@ import Point3D from './components/Point3D';
import PointImage from './components/pointImage';
import Polygon3D from './components/polygon3D';
import ImageLayerDemo from './components/rasterImage';
import RasterLayerDemo from './components/RasterLayer';
// @ts-ignore
storiesOf('图层', module)
@ -15,5 +19,9 @@ storiesOf('图层', module)
.add('图片标注', () => <PointImage />)
.add('面3d图层', () => <Polygon3D />)
.add('线图层', () => <LineLayer />)
.add('3D弧线', () => <ArcLineDemo />)
.add('2D弧线', () => <Arc2DLineDemo />)
.add('网格热力图', () => <GridHeatMap />)
.add('热力图', () => <HeatMapDemo />)
.add('栅格', () => <RasterLayerDemo />)
.add('图片', () => <ImageLayerDemo />);

View File

@ -0,0 +1,56 @@
import { Arc2DLineLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
import * as React from 'react';
export default class Arc2DLineDemo extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/rmsportal/UEXQMifxtkQlYfChpPwT.txt',
);
const scene = new Scene({
center: [116.2825, 39.9],
id: 'map',
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/dark-v9',
zoom: 2,
});
const lineLayer = new Arc2DLineLayer({})
.source(await response.text(), {
parser: {
type: 'csv',
x: 'lng1',
y: 'lat1',
x1: 'lng2',
y1: 'lat2',
},
})
.size(0.5)
.shape('arc')
.color('rgb(13,64,140)');
scene.addLayer(lineLayer);
scene.render();
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -0,0 +1,56 @@
import { ArcLineLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
import * as React from 'react';
export default class ArcLineDemo extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/rmsportal/UEXQMifxtkQlYfChpPwT.txt',
);
const scene = new Scene({
center: [116.2825, 39.9],
id: 'map',
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/dark-v9',
zoom: 2,
});
const lineLayer = new ArcLineLayer({})
.source(await response.text(), {
parser: {
type: 'csv',
x: 'lng1',
y: 'lat1',
x1: 'lng2',
y1: 'lat2',
},
})
.size(0.5)
.shape('arc')
.color('rgb(13,64,140)');
scene.addLayer(lineLayer);
scene.render();
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -2,7 +2,7 @@ import { LineLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
import * as React from 'react';
export default class Point3D extends React.Component {
export default class LineDemo extends React.Component {
private scene: Scene;
public componentWillUnmount() {

View File

@ -57,6 +57,7 @@ export default class Point3D extends React.Component {
scene.addLayer(pointLayer);
console.log(pointLayer);
scene.render();
this.scene = scene;
}
public render() {

View File

@ -39,6 +39,7 @@ export default class Point3D extends React.Component {
.size([15, 10]);
scene.addLayer(pointLayer);
scene.render();
this.scene = scene;
}
public render() {

View File

@ -0,0 +1,92 @@
import { RasterLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
// @ts-ignore
import * as GeoTIFF from 'geotiff/dist/geotiff.bundle.js';
import * as React from 'react';
export default class ImageLayerDemo extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const scene = new Scene({
center: [121.268, 30.3628],
id: 'map',
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/streets-v9',
zoom: 2,
});
const tiffdata = await this.getTiffData();
const layer = new RasterLayer({});
layer
.source(tiffdata.data, {
parser: {
type: 'raster',
width: tiffdata.width,
height: tiffdata.height,
min: 0,
max: 8000,
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
},
})
.style({
opacity: 0.8,
rampColors: {
colors: [
'#002466',
'#0D408C',
'#105CB3',
'#1A76C7',
'#2894E0',
'#3CB4F0',
'#65CEF7',
'#98E3FA',
'#CFF6FF',
'#E8FCFF',
],
positions: [0, 0.02, 0.05, 0.1, 0.2, 0.3, 0.5, 0.6, 0.8, 1.0],
},
});
scene.addLayer(layer);
console.log(layer);
scene.render();
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
private async getTiffData() {
const response = await fetch(
'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat',
);
const arrayBuffer = await response.arrayBuffer();
const tiff = await GeoTIFF.fromArrayBuffer(arrayBuffer);
const image = await tiff.getImage();
const width = image.getWidth();
const height = image.getHeight();
const values = await image.readRasters();
return {
data: values[0],
width,
height,
min: 0,
max: 8000,
};
}
}

View File

@ -0,0 +1,77 @@
import { PointLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
import * as React from 'react';
import data from '../data/data.json';
export default class Point3D extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public componentDidMount() {
const scene = new Scene({
center: [120.19382669582967, 30.258134],
id: 'map',
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/streets-v9',
zoom: 1,
});
const pointLayer = new PointLayer({});
const p1 = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [83.671875, 44.84029065139799],
},
},
],
};
pointLayer
.source(data)
.color('name', [
'#FFF5B8',
'#FFDC7D',
'#FFAB5C',
'#F27049',
'#D42F31',
'#730D1C',
])
.shape('subregion',[
'circle',
'triangle',
'square',
'pentagon',
'hexagon',
'octogon',
'hexagram',
'rhombus',
'vesica',
])
.size('scalerank', [2, 4, 6, 8, 10]);
scene.addLayer(pointLayer);
console.log(pointLayer);
scene.render();
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -0,0 +1,73 @@
import { HeatMapLayer } from '@l7/layers';
import { Scene } from '@l7/scene';
// @ts-ignore
import * as React from 'react';
export default class HeatMapLayerDemo extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
);
const scene = new Scene({
center: [121.268, 30.3628],
id: 'map',
pitch: 0,
type: 'mapbox',
style: 'mapbox://styles/mapbox/dark-v10',
zoom: 2,
});
const layer = new HeatMapLayer({
enableTAA: true,
});
layer
.source(await response.json())
.size('mag', [0, 1]) // weight映射通道
.style({
intensity: 2,
radius: 20,
opacity: 0.5,
rampColors: {
colors: [
'rgba(0,0,0,0)',
'#2E8AE6',
'#69D1AB',
'#DAF291',
'#FFD591',
'#FF7A45',
'#CF1D49',
],
positions: [0, 0.1, 0.2, 0.4, 0.6, 0.8, 1.0],
},
});
scene.addLayer(layer);
console.log(layer);
// requestAnimationFrame(run);
scene.render();
this.scene = scene;
// function run() {
// scene.render();
// requestAnimationFrame(run);
// }
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -58,6 +58,7 @@ export default class GridHeatMap extends React.Component {
]);
scene.addLayer(layer);
scene.render();
this.scene = scene;
}
public render() {

View File

@ -42,6 +42,7 @@ export default class PointImage extends React.Component {
.size(30);
scene.addLayer(pointLayer);
scene.render();
this.scene = scene;
}
public render() {

View File

@ -56,6 +56,7 @@ export default class Polygon3D extends React.Component {
});
scene.addLayer(layer);
scene.render();
this.scene = scene;
}
public render() {

View File

@ -29,8 +29,8 @@ export default class ImageLayerDemo extends React.Component {
},
);
scene.addLayer(layer);
console.log(layer);
scene.render();
this.scene = scene;
}
public render() {

View File

@ -2129,6 +2129,11 @@
resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.4.1.tgz#c0a03cf75f8b0ad7b57849d6c7e91b0aec4b640f"
integrity sha512-yyKza9S6z3ELKuf6w5n6VNUB0Osu6Z93RXPfMHLIlNWohu3KqxewLOq4lMXseYJ92GwkRAxd207Pr/Z98cwmvw==
"@mapbox/martini@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@mapbox/martini/-/martini-0.1.0.tgz#1801b9234140e1136f37939157ba647d46f1ea30"
integrity sha512-sAk7M4l1Zw0vIRIH1QpT+dy548w0Mh5fMP+r2sNPVzM9q8BV2nur76Qiv7cQ1NJzbYdCX182qUxbRnUljT4grg==
"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2"
@ -7450,6 +7455,14 @@ geojson-vt@^3.2.1:
resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7"
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
geotiff@^1.0.0-beta.6:
version "1.0.0-beta.6"
resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-1.0.0-beta.6.tgz#500f256196a2c23517b73ccb36a45dc82a1f7a70"
integrity sha512-xdZ/MLcnrv1+6wQlQZQIs11zNJywylnV1pXqDw7Ao7bmLRpM421a39dXP5e6SG+vio0mnDUZkL2XknKbqppFzw==
dependencies:
pako "^1.0.3"
xmldom "0.1.*"
get-caller-file@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
@ -11527,7 +11540,7 @@ p-waterfall@^1.0.0:
dependencies:
p-reduce "^1.0.0"
pako@~1.0.5:
pako@^1.0.3, pako@~1.0.5:
version "1.0.10"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732"
integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==
@ -15995,6 +16008,11 @@ xml-name-validator@^3.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
xmldom@0.1.*:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"