mirror of https://gitee.com/antv-l7/antv-l7
feat(layer): 新增sourceplugin, attribute 增加类型判断
This commit is contained in:
parent
20324f57dd
commit
3a92b02691
|
@ -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';
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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); // 顶部坐标
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export { djb2hash, BKDRHash } from './hash';
|
export { djb2hash, BKDRHash } from './hash';
|
||||||
export { extent, tranfrormCoord } from './geo';
|
export * from './geo';
|
||||||
|
|
|
@ -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);
|
||||||
/*** 运行时修改样式属性 ***/
|
/*** 运行时修改样式属性 ***/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue