feat(layer): 新增sourceplugin, attribute 增加类型判断

This commit is contained in:
thinkinggis 2019-10-10 11:45:18 +08:00
parent 20324f57dd
commit 3a92b02691
22 changed files with 550 additions and 188 deletions

View File

@ -35,6 +35,7 @@ export {
}; };
/** 暴露服务接口供其他 packages 实现 */ /** 暴露服务接口供其他 packages 实现 */
export * from './services/layer/ILayerStyleService';
export * from './services/layer/ILayerService'; export * from './services/layer/ILayerService';
export * from './services/source/ISourceService'; export * from './services/source/ISourceService';
export * from './services/map/IMapService'; export * from './services/map/IMapService';

View File

@ -8,6 +8,7 @@ export const CameraUniform = {
Zoom: 'u_Zoom', Zoom: 'u_Zoom',
ZoomScale: 'u_ZoomScale', ZoomScale: 'u_ZoomScale',
FocalDistance: 'u_FocalDistance', FocalDistance: 'u_FocalDistance',
CameraPosition: 'u_CameraPosition',
}; };
export interface IViewport { export interface IViewport {

View File

@ -45,4 +45,4 @@ export default class GlobalConfigService implements IGlobalConfigService {
public reset() { public reset() {
this.config = defaultGlobalConfig; this.config = defaultGlobalConfig;
} }
} }

View File

@ -3,7 +3,7 @@ import { AsyncParallelHook, SyncHook } from 'tapable';
import { IModel } from '../renderer/IModel'; import { IModel } from '../renderer/IModel';
import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer'; import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer';
import { ISource } from '../source/ISourceService'; import { ISource } from '../source/ISourceService';
import { ILayerStyleOptions } from './ILayerStyleService';
export enum ScaleTypes { export enum ScaleTypes {
LINEAR = 'linear', LINEAR = 'linear',
POWER = 'power', POWER = 'power',
@ -15,8 +15,11 @@ export enum ScaleTypes {
THRESHOLD = 'threshold', THRESHOLD = 'threshold',
CAT = 'cat', CAT = 'cat',
} }
export enum StyleScaleType {
export interface IScale { CONSTANT = 'constant',
VARIABLE = 'variable',
}
export interface IScaleOption {
field?: string; field?: string;
type: ScaleTypes; type: ScaleTypes;
ticks?: any[]; ticks?: any[];
@ -24,23 +27,30 @@ export interface IScale {
format?: () => any; format?: () => any;
domain?: any[]; domain?: any[];
} }
export interface IStyleScale {
scale: any;
field: string;
type: StyleScaleType;
}
export interface ILayerGlobalConfig { export interface ILayerGlobalConfig {
colors: string[]; colors: string[];
size: number; size: number;
shape: string; shape: string;
scales: { scales: {
[key: string]: IScale; [key: string]: IScaleOption;
}; };
} }
type CallBack = (...args: any[]) => any;
export type StyleAttributeField = string | string[]; export type StyleAttributeField = string | string[];
export type StyleAttributeOption = string | number | boolean | any[] | CallBack;
export interface ILayerStyleAttribute { export interface ILayerStyleAttribute {
type?: string; type: string;
names?: string[]; names: string[];
field?: StyleAttributeField; field: StyleAttributeField;
values?: any[]; values?: any[];
scales?: any[]; scales?: any[];
setScales: (scales: IStyleScale[]) => void;
callback?: (...args: any[]) => []; callback?: (...args: any[]) => [];
mapping?(...params: unknown[]): unknown[]; mapping?(...params: unknown[]): unknown[];
} }
@ -61,15 +71,19 @@ export interface ILayer {
styleAttributes: { styleAttributes: {
[attributeName: string]: ILayerStyleAttribute; [attributeName: string]: ILayerStyleAttribute;
}; };
sourceOption: {
data: any;
options?: ISourceCFG;
};
multiPassRenderer: IMultiPassRenderer; multiPassRenderer: IMultiPassRenderer;
init(): ILayer; init(): ILayer;
// size(field: string, value: AttrOption): ILayer; size(field: string, value?: StyleAttributeOption): ILayer;
// color(field: string, value: AttrOption): ILayer; color(field: string, value?: StyleAttributeOption): ILayer;
// shape(field: string, value: AttrOption): ILayer; shape(field: string, value?: StyleAttributeOption): ILayer;
// pattern(field: string, value: AttrOption): ILayer; // pattern(field: string, value: StyleAttributeOption): ILayer;
// filter(field: string, value: AttrOption): ILayer; // filter(field: string, value: StyleAttributeOption): ILayer;
// active(option: ActiveOption): ILayer; // active(option: ActiveOption): ILayer;
// style(options: ILayerStyleOptions): ILayer; style(options: ILayerStyleOptions): ILayer;
// hide(): ILayer; // hide(): ILayer;
// show(): ILayer; // show(): ILayer;
// animate(field: string, option: any): ILayer; // animate(field: string, option: any): ILayer;
@ -78,6 +92,7 @@ export interface ILayer {
source(data: any, option?: ISourceCFG): ILayer; source(data: any, option?: ISourceCFG): ILayer;
addPlugin(plugin: ILayerPlugin): ILayer; addPlugin(plugin: ILayerPlugin): ILayer;
getSource(): ISource; getSource(): ISource;
setSource(source: ISource): void;
setEncodedData(encodedData: Array<{ [key: string]: unknown }>): void; setEncodedData(encodedData: Array<{ [key: string]: unknown }>): void;
getEncodedData(): Array<{ [key: string]: unknown }>; getEncodedData(): Array<{ [key: string]: unknown }>;
getInitializationOptions(): Partial<ILayerInitializationOptions>; getInitializationOptions(): Partial<ILayerInitializationOptions>;

View File

@ -4,6 +4,7 @@ import { extractUniforms } from '../../utils/shader-module';
import { IModuleParams, IShaderModuleService } from './IShaderModuleService'; import { IModuleParams, IShaderModuleService } from './IShaderModuleService';
import decode from '../../shaders/decode.glsl'; import decode from '../../shaders/decode.glsl';
import lighting from '../../shaders/lighting.glsl';
import projection from '../../shaders/projection.glsl'; import projection from '../../shaders/projection.glsl';
import sdf2d from '../../shaders/sdf_2d.glsl'; import sdf2d from '../../shaders/sdf_2d.glsl';
@ -21,6 +22,7 @@ export default class ShaderModuleService implements IShaderModuleService {
this.registerModule('decode', { vs: decode, fs: '' }); this.registerModule('decode', { vs: decode, fs: '' });
this.registerModule('projection', { vs: projection, fs: '' }); this.registerModule('projection', { vs: projection, fs: '' });
this.registerModule('sdf_2d', { vs: '', fs: sdf2d }); this.registerModule('sdf_2d', { vs: '', fs: sdf2d });
this.registerModule('lighting', { vs: lighting, fs: '' });
} }
public registerModule(moduleName: string, moduleParams: IModuleParams) { public registerModule(moduleName: string, moduleParams: IModuleParams) {

View File

@ -0,0 +1,98 @@
// Blinn-Phong model
// apply lighting in vertex shader instead of fragment shader
// @see https://learnopengl.com/Advanced-Lighting/Advanced-Lighting
// TODO: support point light、spot light & sun light
uniform float u_ambient : 1.0;
uniform float u_diffuse : 1.0;
uniform float u_specular : 1.0;
uniform int u_num_of_directional_lights : 1;
uniform int u_num_of_spot_lights : 0;
#define SHININESS 32.0
#define MAX_NUM_OF_DIRECTIONAL_LIGHTS 3
#define MAX_NUM_OF_SPOT_LIGHTS 3
struct DirectionalLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
struct SpotLight {
vec3 position;
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
float angle;
float blur;
float exponent;
};
uniform DirectionalLight u_directional_lights[MAX_NUM_OF_DIRECTIONAL_LIGHTS];
uniform SpotLight u_spot_lights[MAX_NUM_OF_SPOT_LIGHTS];
vec3 calc_directional_light(DirectionalLight light, vec3 normal, vec3 viewDir) {
vec3 lightDir = normalize(light.direction);
// diffuse shading
float diff = max(dot(normal, lightDir), 0.0);
// Blinn-Phong specular shading
vec3 halfwayDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(normal, halfwayDir), 0.0), SHININESS);
vec3 ambient = light.ambient * u_ambient;
vec3 diffuse = light.diffuse * diff * u_diffuse;
vec3 specular = light.specular * spec * u_specular;
return ambient + diffuse + specular;
}
// vec3 calc_spot_light(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) {
// vec3 lightDir = normalize(light.position - fragPos);
// // diffuse shading
// float diff = max(dot(normal, lightDir), 0.0);
// // specular shading
// vec3 reflectDir = reflect(-lightDir, normal);
// float spec = pow(max(dot(viewDir, reflectDir), 0.0), SHININESS);
// // attenuation
// float distance = length(light.position - fragPos);
// float attenuation = 1.0 / (light.constant + light.linear * distance +
// light.quadratic * (distance * distance));
// vec3 ambient = light.ambient * u_ambient;
// vec3 diffuse = light.diffuse * diff * u_diffuse;
// vec3 specular = light.specular * spec * u_specular;
// float spotEffect = dot(normalize(light.direction), -lightDir);
// float spotCosCutoff = cos(light.angle / 180.0 * PI);
// float spotCosOuterCutoff = cos((light.angle + light.blur) / 180.0 * PI);
// float spotCosInnerCutoff = cos((light.angle - light.blur) / 180.0 * PI);
// if (spotEffect > spotCosCutoff) {
// spotEffect = pow(smoothstep(spotCosOuterCutoff, spotCosInnerCutoff, spotEffect), light.exponent);
// } else {
// spotEffect = 0.0;
// }
// return ambient + attenuation * (spotEffect * diffuse + specular);
// }
vec3 calc_lighting(vec3 position, vec3 normal, vec3 viewDir) {
vec3 weight = vec3(0.0);
for (int i = 0; i < MAX_NUM_OF_DIRECTIONAL_LIGHTS; i++) {
if (i >= u_num_of_directional_lights) {
break;
}
weight += calc_directional_light(u_directional_lights[i], normal, viewDir);
}
// for (int i = 0; i < MAX_NUM_OF_SPOT_LIGHTS; i++) {
// if (i >= u_num_of_spot_lights) {
// break;
// }
// weight += calc_spot_light(u_spot_lights[i], normal, position, viewDir);
// }
return weight;
}

View File

@ -26,6 +26,7 @@
"d3-scale": "^3.1.0", "d3-scale": "^3.1.0",
"earcut": "^2.2.1", "earcut": "^2.2.1",
"eventemitter3": "^3.1.0", "eventemitter3": "^3.1.0",
"gl-matrix": "^3.1.0",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"tapable": "^2.0.0-beta.8" "tapable": "^2.0.0-beta.8"
}, },

View File

@ -5,6 +5,8 @@ interface IBufferCfg {
} }
type Position = number[]; type Position = number[];
type Color = [number, number, number, number]; type Color = [number, number, number, number];
import { lngLatToMeters } from '@l7/utils';
import { vec3 } from 'gl-matrix';
export interface IBufferInfo { export interface IBufferInfo {
vertices?: any; vertices?: any;
indexArray?: any; indexArray?: any;
@ -40,6 +42,44 @@ export default class Buffer {
this.uv = !!uv; this.uv = !!uv;
this.init(); this.init();
} }
public computeVertexNormals() {
const normals = (this.attributes.normals = new Float32Array(
this.verticesCount * 3,
));
const indexArray = this.indexArray;
const { positions } = this.attributes;
let vA;
let vB;
let vC;
const cb = vec3.create();
const ab = vec3.create();
const normal = vec3.create();
for (let i = 0, li = indexArray.length; i < li; i += 3) {
vA = indexArray[i + 0] * 3;
vB = indexArray[i + 1] * 3;
vC = indexArray[i + 2] * 3;
const [ax, ay] = lngLatToMeters([positions[vA], positions[vA + 1]]);
const pA = vec3.fromValues(ax, ay, positions[vA + 2]);
const [bx, by] = lngLatToMeters([positions[vB], positions[vB + 1]]);
const pB = vec3.fromValues(bx, by, positions[vB + 2]);
const [cx, cy] = lngLatToMeters([positions[vC], positions[vC + 1]]);
const pC = vec3.fromValues(cx, cy, positions[vC + 2]);
vec3.sub(cb, pC, pB);
vec3.sub(ab, pA, pB);
vec3.cross(normal, cb, ab);
normals[vA] += cb[0];
normals[vA + 1] += cb[1];
normals[vA + 2] += cb[2];
normals[vB] += cb[0];
normals[vB + 1] += cb[1];
normals[vB + 2] += cb[2];
normals[vC] += cb[0];
normals[vC + 1] += cb[1];
normals[vC + 2] += cb[2];
}
this.normalizeNormals();
}
// 计算每个要素顶点个数,记录索引位置 // 计算每个要素顶点个数,记录索引位置
protected calculateFeatures() { protected calculateFeatures() {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@ -47,11 +87,13 @@ export default class Buffer {
protected buildFeatures() { protected buildFeatures() {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
protected checkIsClosed(points: Position[][]) { protected checkIsClosed(points: Position[][]) {
const p1 = points[0][0]; const p1 = points[0][0];
const p2 = points[0][points[0].length - 1]; const p2 = points[0][points[0].length - 1];
return p1[0] === p2[0] && p1[1] === p2[1]; return p1[0] === p2[0] && p1[1] === p2[1];
} }
protected concat(arrayType: Float32Array, arrays: any) { protected concat(arrayType: Float32Array, arrays: any) {
let totalLength = 0; let totalLength = 0;
for (const arr of arrays) { for (const arr of arrays) {
@ -104,7 +146,7 @@ export default class Buffer {
} }
} }
protected calculateWall(feature: IEncodeFeature) { protected calculateWall(feature: IEncodeFeature) {
const size = feature.size; const size = feature.size || 0;
const { const {
vertices, vertices,
indexOffset, indexOffset,
@ -114,8 +156,11 @@ export default class Buffer {
} = feature.bufferInfo; } = feature.bufferInfo;
this.encodeArray(feature, faceNum * 4); this.encodeArray(feature, faceNum * 4);
for (let i = 0; i < faceNum; i++) { for (let i = 0; i < faceNum; i++) {
const prePoint = vertices.slice(i * 3, i * 3 + 3); const prePoint = vertices.slice(i * dimensions, (i + 1) * dimensions);
const nextPoint = vertices.slice(i * 3 + 3, i * 3 + 6); const nextPoint = vertices.slice(
(i + 1) * dimensions,
(i + 2) * dimensions,
);
this.calculateExtrudeFace( this.calculateExtrudeFace(
prePoint, prePoint,
nextPoint, nextPoint,
@ -165,7 +210,9 @@ export default class Buffer {
} }
private init() { private init() {
// 将每个多边形三角化,存储顶点坐标和索引坐标
this.calculateFeatures(); this.calculateFeatures();
// 拼接成一个 attribute
this.initAttributes(); this.initAttributes();
this.buildFeatures(); this.buildFeatures();
} }
@ -181,4 +228,18 @@ export default class Buffer {
} }
this.indexArray = new Uint32Array(this.indexCount); this.indexArray = new Uint32Array(this.indexCount);
} }
private normalizeNormals() {
const { normals } = this.attributes;
for (let i = 0, li = normals.length; i < li; i += 3) {
const normal = vec3.fromValues(
normals[i],
normals[i + 1],
normals[i + 2],
);
const newNormal = vec3.create();
vec3.normalize(newNormal, normal);
normals.set(newNormal, i);
}
}
} }

View File

@ -4,17 +4,21 @@ import {
ILayerInitializationOptions, ILayerInitializationOptions,
ILayerPlugin, ILayerPlugin,
ILayerStyleAttribute, ILayerStyleAttribute,
ILayerStyleOptions,
IModel, IModel,
IMultiPassRenderer, IMultiPassRenderer,
IRendererService, IRendererService,
ISource,
lazyInject, lazyInject,
StyleAttributeField, StyleAttributeField,
StyleAttributeOption,
TYPES, TYPES,
} from '@l7/core'; } from '@l7/core';
import Source, { ISourceCFG } from '@l7/source'; import Source, { ISourceCFG } from '@l7/source';
import { isFunction } from 'lodash'; import { isFunction } from 'lodash';
import { SyncHook } from 'tapable'; import { SyncHook } from 'tapable';
import DataEncodePlugin from '../plugins/DataEncodePlugin'; import DataEncodePlugin from '../plugins/DataEncodePlugin';
import DataSourcePlugin from '../plugins/DataSourcePlugin';
import MultiPassRendererPlugin from '../plugins/MultiPassRendererPlugin'; import MultiPassRendererPlugin from '../plugins/MultiPassRendererPlugin';
import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin'; import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin';
import StyleAttribute from './StyleAttribute'; import StyleAttribute from './StyleAttribute';
@ -37,14 +41,19 @@ export default class BaseLayer implements ILayer {
// 插件集 // 插件集
public plugins: ILayerPlugin[] = [ public plugins: ILayerPlugin[] = [
new DataSourcePlugin(),
new DataEncodePlugin(), new DataEncodePlugin(),
new MultiPassRendererPlugin(), new MultiPassRendererPlugin(),
new ShaderUniformPlugin(), new ShaderUniformPlugin(),
]; ];
public sourceOption: {
data: any;
options?: ISourceCFG;
};
public styleOption: ILayerStyleOptions;
// 样式属性 // 样式属性
public styleAttributes: { public styleAttributes: {
[key: string]: Partial<ILayerStyleAttribute>; [key: string]: Required<ILayerStyleAttribute>;
} = {}; } = {};
protected layerSource: Source; protected layerSource: Source;
@ -77,7 +86,10 @@ export default class BaseLayer implements ILayer {
this.buildModels(); this.buildModels();
return this; return this;
} }
public color(field: StyleAttributeField, values?: any) { public color(
field: StyleAttributeField,
values?: StyleAttributeOption,
): ILayer {
this.createStyleAttribute( this.createStyleAttribute(
'color', 'color',
field, field,
@ -86,7 +98,11 @@ export default class BaseLayer implements ILayer {
); );
return this; return this;
} }
public size(field: StyleAttributeField, values?: any) {
public size(
field: StyleAttributeField,
values?: StyleAttributeOption,
): ILayer {
this.createStyleAttribute( this.createStyleAttribute(
'size', 'size',
field, field,
@ -95,7 +111,11 @@ export default class BaseLayer implements ILayer {
); );
return this; return this;
} }
public shape(field: StyleAttributeField, values?: any) {
public shape(
field: StyleAttributeField,
values?: StyleAttributeOption,
): ILayer {
this.createStyleAttribute( this.createStyleAttribute(
'shape', 'shape',
field, field,
@ -105,11 +125,17 @@ export default class BaseLayer implements ILayer {
return this; return this;
} }
public source(data: any, options?: ISourceCFG) { public source(data: any, options?: ISourceCFG): ILayer {
this.layerSource = new Source(data, options); this.sourceOption = {
data,
options,
};
return this;
}
public style(options: ILayerStyleOptions): ILayer {
this.styleOption = options; // TODO: merge 默认同类型
return this; return this;
} }
public render(): ILayer { public render(): ILayer {
if (this.multiPassRenderer && this.multiPassRenderer.getRenderFlag()) { if (this.multiPassRenderer && this.multiPassRenderer.getRenderFlag()) {
this.multiPassRenderer.render(); this.multiPassRenderer.render();
@ -124,11 +150,14 @@ export default class BaseLayer implements ILayer {
} }
public getStyleAttributes(): { public getStyleAttributes(): {
[key: string]: Partial<ILayerStyleAttribute>; [key: string]: Required<ILayerStyleAttribute>;
} { } {
return this.styleAttributes; return this.styleAttributes;
} }
public setSource(source: Source) {
this.layerSource = source;
}
public getSource() { public getSource() {
return this.layerSource; return this.layerSource;
} }

View File

@ -1,4 +1,9 @@
import { IScale, ScaleTypes } from '@l7/core'; import {
IScaleOption,
IStyleScale,
ScaleTypes,
StyleScaleType,
} from '@l7/core';
import { extent } from 'd3-array'; import { extent } from 'd3-array';
import * as d3 from 'd3-scale'; import * as d3 from 'd3-scale';
import { isNil, isNumber, isString, uniq } from 'lodash'; import { isNil, isNumber, isString, uniq } from 'lodash';
@ -19,27 +24,27 @@ const scaleMap = {
export default class ScaleController { export default class ScaleController {
private scaleOptions: { private scaleOptions: {
[field: string]: IScale; [field: string]: IScaleOption;
}; };
constructor(cfg: { [field: string]: IScale }) { constructor(cfg: { [field: string]: IScaleOption }) {
this.scaleOptions = cfg; this.scaleOptions = cfg;
} }
public createScale( public createScale(field: string, data?: any[]): IStyleScale {
field: string, let scaleOption: IScaleOption = this.scaleOptions[field];
data?: any[], const scale: IStyleScale = {
): { field: string; scale: any } {
let scaleOption: IScale = this.scaleOptions[field];
const scale: { field: string; scale: any } = {
field, field,
scale: undefined, scale: undefined,
type: StyleScaleType.VARIABLE,
}; };
if (!data || !data.length) { if (!data || !data.length) {
// 数据为空 // 数据为空
scale.scale = if (scaleOption && scaleOption.type) {
scaleOption && scaleOption.type scale.scale = this.generateScale(scaleOption.type, scaleOption);
? this.generateScale(scaleOption.type, scaleOption) } else {
: d3.scaleOrdinal([field]); scale.scale = d3.scaleOrdinal([field]);
scale.type = StyleScaleType.CONSTANT;
}
return scale; return scale;
} }
let firstValue = null; let firstValue = null;
@ -53,6 +58,7 @@ export default class ScaleController {
// 常量 Scale // 常量 Scale
if (isNumber(field) || (isNil(firstValue) && !scaleOption)) { if (isNumber(field) || (isNil(firstValue) && !scaleOption)) {
scale.scale = d3.scaleOrdinal([field]); scale.scale = d3.scaleOrdinal([field]);
scale.type = StyleScaleType.CONSTANT;
} else { } else {
// 根据数据类型判断 默认等分位,时间,和枚举类型 // 根据数据类型判断 默认等分位,时间,和枚举类型
const type = const type =
@ -61,7 +67,7 @@ export default class ScaleController {
: this.getDefaultType(field, firstValue); : this.getDefaultType(field, firstValue);
const cfg = this.getScaleCfg(type, field, data); const cfg = this.getScaleCfg(type, field, data);
Object.assign(cfg, scaleOption); Object.assign(cfg, scaleOption);
scaleOption = cfg; scaleOption = cfg; // 更新scale配置
scale.scale = this.generateScale(type, cfg); scale.scale = this.generateScale(type, cfg);
} }
return scale; return scale;
@ -78,7 +84,7 @@ export default class ScaleController {
} }
private getScaleCfg(type: ScaleTypes, field: string, data: any[]) { private getScaleCfg(type: ScaleTypes, field: string, data: any[]) {
const cfg: IScale = { const cfg: IScaleOption = {
field, field,
type, type,
}; };
@ -92,11 +98,12 @@ export default class ScaleController {
return cfg; return cfg;
} }
private generateScale(type: ScaleTypes, scaleOption: IScale) { private generateScale(type: ScaleTypes, scaleOption: IScaleOption) {
// @ts-ignore // @ts-ignore
const scale = scaleMap[type](); const scale = scaleMap[type]();
if (scaleOption.hasOwnProperty('domain')) { if (scaleOption.hasOwnProperty('domain')) {
scale.domain(scaleOption.domain); // 处理同一字段映射不同视觉通道的问题
scale.copy().domain(scaleOption.domain);
} }
// TODO 其他属性支持 // TODO 其他属性支持
return scale; return scale;

View File

@ -1,18 +1,17 @@
import { ILayerStyleAttribute } from '@l7/core'; import { ILayerStyleAttribute, IStyleScale, StyleScaleType } from '@l7/core';
import { isNil } from 'lodash'; import { isNil, isString } from 'lodash';
type CallBack = (...args: any[]) => any; type CallBack = (...args: any[]) => any;
export default class StyleAttribute implements ILayerStyleAttribute { export default class StyleAttribute implements ILayerStyleAttribute {
public type: string; public type: StyleScaleType;
public names: string[]; public names: string[];
public scales: any[] = []; public scales: IStyleScale[] = [];
public values: any[] = []; public values: any[] = [];
public field: string; public field: string;
constructor(cfg: any) { constructor(cfg: any) {
const { const {
type = 'base', type = StyleScaleType.CONSTANT,
names = [],
scales = [], scales = [],
values = [], values = [],
callback, callback,
@ -22,11 +21,17 @@ export default class StyleAttribute implements ILayerStyleAttribute {
this.type = type; this.type = type;
this.scales = scales; this.scales = scales;
this.values = values; this.values = values;
this.names = names; this.names = this.parseFields(field) || [];
// 设置 range TODO 2维映射 // 设置 range TODO 2维映射
this.scales.forEach((scale) => { // this.scales.forEach((scale) => {
scale.scale.range(values); // scale.scale.range(values);
}); // if (scale.type === StyleScaleType.VARIABLE) {
// this.type = StyleScaleType.VARIABLE;
// }
// });
if (callback) {
this.type = StyleScaleType.VARIABLE;
}
this.callback = (...params: any[]): any[] => { this.callback = (...params: any[]): any[] => {
/** /**
* callback null , callback * callback null , callback
@ -44,7 +49,20 @@ export default class StyleAttribute implements ILayerStyleAttribute {
}; };
} }
public callback: CallBack = () => []; public callback: CallBack = () => [];
public setScales(scales: IStyleScale[]): void {
if (scales.some((scale) => scale.type === StyleScaleType.VARIABLE)) {
this.type = StyleScaleType.VARIABLE;
scales.forEach((scale) => {
scale.scale.range(this.values);
});
} else {
// 设置attribute 常量值
this.values = scales.map((scale, index) => {
return scale.scale(this.names[index]);
});
}
this.scales = scales;
}
public mapping(...params: unknown[]): unknown[] { public mapping(...params: unknown[]): unknown[] {
return this.callback.apply(this, params); return this.callback.apply(this, params);
} }
@ -60,4 +78,18 @@ export default class StyleAttribute implements ILayerStyleAttribute {
return value; return value;
}); });
} }
/**
* @example
* 'w*h' => ['w', 'h']
* 'w' => ['w']
*/
private parseFields(field: string[] | string): string[] {
if (Array.isArray(field)) {
return field;
}
if (isString(field)) {
return field.split('*');
}
return [field];
}
} }

View File

@ -4,7 +4,9 @@ import {
ILayerPlugin, ILayerPlugin,
ILayerStyleAttribute, ILayerStyleAttribute,
IParseDataItem, IParseDataItem,
IStyleScale,
lazyInject, lazyInject,
StyleScaleType,
TYPES, TYPES,
} from '@l7/core'; } from '@l7/core';
import { isString } from 'lodash'; import { isString } from 'lodash';
@ -18,10 +20,7 @@ export default class DataEncodePlugin implements ILayerPlugin {
private scaleController: ScaleController; private scaleController: ScaleController;
private scaleCache: { private scaleCache: {
[fieldName: string]: { [fieldName: string]: IStyleScale;
field: string;
scale: any;
};
} = {}; } = {};
public apply(layer: ILayer) { public apply(layer: ILayer) {
@ -39,14 +38,12 @@ export default class DataEncodePlugin implements ILayerPlugin {
// create scales by source data & config // create scales by source data & config
Object.keys(layer.styleAttributes).forEach((attributeName) => { Object.keys(layer.styleAttributes).forEach((attributeName) => {
const attribute = layer.styleAttributes[attributeName]; const attribute = layer.styleAttributes[attributeName];
const fields = this.parseFields(attribute.field || '');
const scales: any[] = []; const scales: any[] = [];
fields.forEach((field: string) => { attribute.names.forEach((field: string) => {
scales.push(this.getOrCreateScale(attribute, dataArray)); scales.push(this.getOrCreateScale(attribute, dataArray));
}); });
attribute.scales = scales; attribute.setScales(scales);
}); });
// mapping with source data // mapping with source data
layer.setEncodedData(this.mapping(layer.styleAttributes, dataArray)); layer.setEncodedData(this.mapping(layer.styleAttributes, dataArray));
}); });
@ -55,33 +52,24 @@ export default class DataEncodePlugin implements ILayerPlugin {
// layer.hooks.beforeRender.tap() // layer.hooks.beforeRender.tap()
} }
private getOrCreateScale(attribute: ILayerStyleAttribute, data: any[]) { private getOrCreateScale(
attribute: ILayerStyleAttribute,
data: any[],
): IStyleScale {
const { field } = attribute; const { field } = attribute;
let scale = this.scaleCache[field as string]; let scale = this.scaleCache[field as string];
if (!scale) { if (!scale) {
scale = this.scaleController.createScale(field as string, data); scale = this.scaleController.createScale(field as string, data);
scale.scale.range(attribute.values); if (scale.type === StyleScaleType.VARIABLE) {
scale.scale.range(attribute.values);
}
this.scaleCache[field as string] = scale; this.scaleCache[field as string] = scale;
} }
// scale: scale.scale.copy(), return {
return this.scaleCache[field as string]; ...scale,
scale: scale.scale.copy(), // 存在相同字段映射不同通道的情况
};
} }
/**
* @example
* 'w*h' => ['w', 'h']
* 'w' => ['w']
*/
private parseFields(field: string[] | string): string[] {
if (Array.isArray(field)) {
return field;
}
if (isString(field)) {
return field.split('*');
}
return [field];
}
private mapping( private mapping(
attributes: { attributes: {
[attributeName: string]: ILayerStyleAttribute; [attributeName: string]: ILayerStyleAttribute;
@ -93,11 +81,14 @@ export default class DataEncodePlugin implements ILayerPlugin {
id: record._id, id: record._id,
coordinates: record.coordinates, coordinates: record.coordinates,
}; };
// TODO 数据过滤 // TODO: 数据过滤
Object.keys(attributes).forEach((attributeName: string) => { Object.keys(attributes).forEach((attributeName: string) => {
const attribute = attributes[attributeName]; const attribute = attributes[attributeName];
const { type } = attribute;
if (type === StyleScaleType.CONSTANT) {
return;
}
let values = this.getAttrValue(attribute, record); let values = this.getAttrValue(attribute, record);
if (attributeName === 'color') { if (attributeName === 'color') {
values = values.map((c: unknown) => { values = values.map((c: unknown) => {
return rgb2arr(c as string); return rgb2arr(c as string);
@ -117,8 +108,11 @@ export default class DataEncodePlugin implements ILayerPlugin {
const scales = attribute.scales || []; const scales = attribute.scales || [];
const params: unknown[] = []; const params: unknown[] = [];
scales.forEach(({ field }) => { scales.forEach((scale) => {
if (record[field]) { const { field, type, value } = scale;
if (type === StyleScaleType.CONSTANT) {
params.push(scale.field);
} else {
params.push(record[field]); params.push(record[field]);
} }
}); });

View File

@ -0,0 +1,20 @@
import {
IGlobalConfigService,
ILayer,
ILayerPlugin,
ILayerStyleAttribute,
IParseDataItem,
IStyleScale,
lazyInject,
StyleScaleType,
TYPES,
} from '@l7/core';
import Source, { ISourceCFG } from '@l7/source';
export default class DataSourcePlugin implements ILayerPlugin {
public apply(layer: ILayer) {
layer.hooks.init.tap('DataSourcePlugin', () => {
const { data, options } = layer.sourceOption;
layer.setSource(new Source(data, options));
});
}
}

View File

@ -1,5 +1,6 @@
import { import {
gl, gl,
ILayer,
IRendererService, IRendererService,
IShaderModuleService, IShaderModuleService,
lazyInject, lazyInject,
@ -50,15 +51,15 @@ export default class PointLayer extends BaseLayer {
private pointFeatures: IPointFeature[] = []; private pointFeatures: IPointFeature[] = [];
public style(options: Partial<IPointLayerStyleOptions>) { // public style(options: Partial<IPointLayerStyleOptions>) {
// this.layerStyleService.update(options); // // this.layerStyleService.update(options);
// this.styleOptions = { // // this.styleOptions = {
// ...this.styleOptions, // // ...this.styleOptions,
// ...options, // // ...options,
// }; // // };
} // }
public render() { public render(): ILayer {
this.models.forEach((model) => this.models.forEach((model) =>
model.draw({ model.draw({
uniforms: { uniforms: {

View File

@ -0,0 +1,67 @@
import earcut from 'earcut';
import BufferBase, { IBufferInfo, IEncodeFeature } from '../../core/BaseBuffer';
export default class ExtrudeBuffer extends BufferBase {
public buildFeatures() {
const layerData = this.data as IEncodeFeature[];
layerData.forEach((feature: IEncodeFeature) => {
this.calculateTop(feature);
this.calculateWall(feature);
delete feature.bufferInfo;
});
}
public calculateFeatures() {
const layerData = this.data as IEncodeFeature[];
// 计算长
layerData.forEach((feature: IEncodeFeature) => {
const { coordinates } = feature;
const flattengeo = earcut.flatten(coordinates);
const n = this.checkIsClosed(coordinates)
? coordinates[0].length - 1
: coordinates[0].length;
const { vertices, dimensions, holes } = flattengeo;
const indexArray = earcut(vertices, holes, dimensions).map(
(v) => this.verticesCount + v,
);
const bufferInfo: IBufferInfo = {
dimensions,
vertices,
indexArray,
verticesOffset: this.verticesCount + 0,
indexOffset: this.indexCount + 0,
faceNum: n,
};
this.indexCount += indexArray.length + n * 6;
this.verticesCount += vertices.length / dimensions + n * 4;
feature.bufferInfo = bufferInfo;
});
}
private calculateTop(feature: IEncodeFeature) {
const size = feature.size || 1;
const {
indexArray,
vertices,
indexOffset,
verticesOffset,
dimensions,
} = feature.bufferInfo;
const pointCount = vertices.length / dimensions;
this.encodeArray(feature, vertices.length / dimensions);
// 添加顶点
for (let i = 0; i < pointCount; i++) {
this.attributes.positions.set(
[vertices[i * dimensions], vertices[i * dimensions + 1], size],
(verticesOffset + i) * 3,
);
// 顶部文理坐标计算
if (this.uv) {
// TODO 用过BBox计算纹理坐标
this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2);
}
}
feature.bufferInfo.verticesOffset += pointCount;
// 添加顶点索引
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
feature.bufferInfo.indexOffset += indexArray.length;
}
}

View File

@ -1,66 +0,0 @@
// import earcut from 'earcut';
// import BufferBase, { IBufferInfo, IEncodeFeature } from '../../core/buffer';
// export default class ExtrudeButffer extends BufferBase {
// public _buildFeatures() {
// const layerData = this.get('data');
// layerData.forEach((feature: IEncodeFeature) => {
// this.calculateTop(feature);
// this.calculateWall(feature);
// delete feature.bufferInfo;
// });
// }
// public _calculateFeatures() {
// const layerData = this.get('data');
// // 计算长
// layerData.forEach((feature: IEncodeFeature) => {
// const { coordinates } = feature;
// const flattengeo = earcut.flatten(coordinates);
// const n = this.checkIsClosed(coordinates)
// ? coordinates[0].length - 1
// : coordinates[0].length;
// const { vertices, dimensions, holes } = flattengeo;
// const indexArray = earcut(vertices, holes, dimensions).map(
// (v) => this.verticesCount + v,
// );
// const bufferInfo: IBufferInfo = {
// dimensions,
// vertices,
// indexArray,
// verticesOffset: this.verticesCount + 0,
// indexOffset: this.indexCount + 0,
// faceNum: n,
// };
// this.indexCount += indexArray.length + n * 6;
// this.verticesCount += vertices.length / dimensions + n * 4;
// feature.bufferInfo = bufferInfo;
// });
// }
// private calculateTop(feature: IEncodeFeature) {
// const size = feature.size;
// const {
// indexArray,
// vertices,
// indexOffset,
// verticesOffset,
// dimensions,
// } = feature.bufferInfo;
// const pointCount = vertices.length / dimensions;
// this.encodeArray(feature, dimensions);
// // 添加顶点
// for (let i = 0; i < pointCount; i++) {
// this.attributes.positions.set(
// [vertices[i * 3], vertices[i * 3 + 1], size],
// (verticesOffset + i) * 3,
// );
// // 顶部文理坐标计算
// if (this.get('uv')) {
// // TODO 用过BBox计算纹理坐标
// this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2);
// }
// }
// feature.bufferInfo.verticesOffset += pointCount;
// // 添加顶点索引
// this.indexArray.set(indexArray, indexOffset); // 顶部坐标
// }
// }

View File

@ -6,6 +6,7 @@ import {
TYPES, TYPES,
} from '@l7/core'; } from '@l7/core';
import BaseLayer from '../core/BaseLayer'; import BaseLayer from '../core/BaseLayer';
import ExtrudeBuffer from './buffers/ExtrudeBuffer';
import FillBuffer from './buffers/FillBuffer'; import FillBuffer from './buffers/FillBuffer';
import polygon_frag from './shaders/polygon_frag.glsl'; import polygon_frag from './shaders/polygon_frag.glsl';
import polygon_vert from './shaders/polygon_vert.glsl'; import polygon_vert from './shaders/polygon_vert.glsl';
@ -19,14 +20,6 @@ export default class PolygonLayer extends BaseLayer {
@lazyInject(TYPES.IRendererService) @lazyInject(TYPES.IRendererService)
private readonly renderer: IRendererService; private readonly renderer: IRendererService;
public style(options: any) {
// this.layerStyleService.update(options);
// this.styleOptions = {
// ...this.styleOptions,
// ...options,
// };
}
protected renderModels() { protected renderModels() {
this.models.forEach((model) => this.models.forEach((model) =>
model.draw({ model.draw({
@ -46,10 +39,15 @@ export default class PolygonLayer extends BaseLayer {
this.models = []; this.models = [];
const { vs, fs, uniforms } = this.shaderModule.getModule('polygon'); const { vs, fs, uniforms } = this.shaderModule.getModule('polygon');
const buffer = new FillBuffer({ const buffer = new ExtrudeBuffer({
data: this.getEncodedData(), data: this.getEncodedData(),
}); });
buffer.computeVertexNormals();
const buffer2 = new FillBuffer({
data: this.getEncodedData(),
});
console.log(buffer);
console.log(buffer2);
const { const {
createAttribute, createAttribute,
createBuffer, createBuffer,
@ -67,6 +65,13 @@ export default class PolygonLayer extends BaseLayer {
}), }),
size: 3, size: 3,
}), }),
a_normal: createAttribute({
buffer: createBuffer({
data: buffer.attributes.normals,
type: gl.FLOAT,
}),
size: 3,
}),
a_color: createAttribute({ a_color: createAttribute({
buffer: createBuffer({ buffer: createBuffer({
data: buffer.attributes.colors, data: buffer.attributes.colors,
@ -75,7 +80,10 @@ export default class PolygonLayer extends BaseLayer {
size: 4, size: 4,
}), }),
}, },
uniforms, uniforms: {
...uniforms,
u_opacity: this.styleOption.opacity as number,
},
fs, fs,
vs, vs,
count: buffer.indexArray.length, count: buffer.indexArray.length,

View File

@ -1,4 +1,6 @@
varying vec4 v_color; varying vec4 v_color;
uniform float u_opacity: 1.0;
void main() { void main() {
gl_FragColor = v_color; gl_FragColor = v_color;
gl_FragColor.a *= u_opacity;
} }

View File

@ -1,6 +1,6 @@
attribute vec4 a_color; attribute vec4 a_color;
attribute vec3 a_Position; attribute vec3 a_Position;
attribute vec3 a_normal;
uniform mat4 u_ModelMatrix; uniform mat4 u_ModelMatrix;
varying vec4 v_color; varying vec4 v_color;

View File

@ -1,5 +1,6 @@
import { BBox } from '@turf/helpers'; import { BBox } from '@turf/helpers';
const originShift = (2 * Math.PI * 6378137) / 2.0;
type Point = [number, number] | [number, number, number];
/** /**
* *
* @param {dataArray} data * @param {dataArray} data
@ -46,3 +47,93 @@ function transform(item: any[], cb: (item: any[]) => any): any {
} }
return cb(item); return cb(item);
} }
export function lngLatToMeters(lnglat: Point): Point;
export function lngLatToMeters(
lnglat: Point,
validate: boolean = true,
accuracy = { enable: true, decimal: 1 },
) {
lnglat = validateLngLat(lnglat, validate);
const lng = lnglat[0];
const lat = lnglat[1];
let x = (lng * originShift) / 180.0;
let y =
Math.log(Math.tan(((90 + lat) * Math.PI) / 360.0)) / (Math.PI / 180.0);
y = (y * originShift) / 180.0;
if (accuracy.enable) {
x = Number(x.toFixed(accuracy.decimal));
y = Number(y.toFixed(accuracy.decimal));
}
return lnglat.length === 3 ? [x, y, lnglat[2]] : [x, y];
}
export function metersToLngLat(meters: Point, decimal = 6) {
const x = meters[0];
const y = meters[1];
let lng = (x / originShift) * 180.0;
let lat = (y / originShift) * 180.0;
lat =
(180 / Math.PI) *
(2 * Math.atan(Math.exp((lat * Math.PI) / 180.0)) - Math.PI / 2.0);
if (decimal !== undefined && decimal !== null) {
lng = Number(lng.toFixed(decimal));
lat = Number(lat.toFixed(decimal));
}
return meters.length === 3 ? [lng, lat, meters[2]] : [lng, lat];
}
export function longitude(lng: number) {
if (lng === undefined || lng === null) {
throw new Error('lng is required');
}
// lngitudes cannot extends beyond +/-90 degrees
if (lng > 180 || lng < -180) {
lng = lng % 360;
if (lng > 180) {
lng = -360 + lng;
}
if (lng < -180) {
lng = 360 + lng;
}
if (lng === 0) {
lng = 0;
}
}
return lng;
}
export function latitude(lat: number) {
if (lat === undefined || lat === null) {
throw new Error('lat is required');
}
if (lat > 90 || lat < -90) {
lat = lat % 180;
if (lat > 90) {
lat = -180 + lat;
}
if (lat < -90) {
lat = 180 + lat;
}
if (lat === 0) {
lat = 0;
}
}
return lat;
}
export function validateLngLat(lnglat: Point, validate: boolean): Point {
if (validate === false) {
return lnglat;
}
const lng = longitude(lnglat[0]);
let lat = latitude(lnglat[1]);
// Global Mercator does not support latitudes within 85 to 90 degrees
if (lat > 85) {
lat = 85;
}
if (lat < -85) {
lat = -85;
}
return lnglat.length === 3 ? [lng, lat, lnglat[2]] : [lng, lat];
}

View File

@ -1,2 +1,2 @@
export { djb2hash, BKDRHash } from './hash'; export { djb2hash, BKDRHash } from './hash';
export { extent, tranfrormCoord } from './geo'; export * from './geo';

View File

@ -27,7 +27,9 @@ export default class Mapbox extends React.Component {
features: [ features: [
{ {
type: 'Feature', type: 'Feature',
properties: {}, properties: {
name: 'test',
},
geometry: { geometry: {
type: 'Polygon', type: 'Polygon',
coordinates: [ coordinates: [
@ -53,20 +55,13 @@ export default class Mapbox extends React.Component {
}); });
const layer = new PolygonLayer({ const layer = new PolygonLayer({
enableMultiPassRenderer: true, enableMultiPassRenderer: true,
passes: [ passes: [],
'blurH',
[
'blurV',
{
blurRadius: 8,
},
],
],
}); });
// TODO: new GeoJSONSource() // TODO: new GeoJSONSource()
layer layer
.source(await response.json()) .source(await response.json())
.size('name', [0, 10000, 50000, 30000, 100000])
.color('name', [ .color('name', [
'#2E8AE6', '#2E8AE6',
'#69D1AB', '#69D1AB',
@ -74,12 +69,15 @@ export default class Mapbox extends React.Component {
'#FFD591', '#FFD591',
'#FF7A45', '#FF7A45',
'#CF1D49', '#CF1D49',
]); ])
.shape('fill')
.style({
opacity: 0.8,
});
scene.addLayer(layer); scene.addLayer(layer);
scene.render(); scene.render();
this.scene = scene; this.scene = scene;
console.log(layer);
/*** 运行时修改样式属性 ***/ /*** 运行时修改样式属性 ***/
} }