mirror of https://gitee.com/antv-l7/antv-l7
commit
1b6dd0f14e
|
@ -39,6 +39,7 @@ export {
|
|||
};
|
||||
|
||||
/** 暴露服务接口供其他 packages 实现 */
|
||||
export * from './services/layer/ILayerStyleService';
|
||||
export * from './services/layer/ILayerService';
|
||||
export * from './services/source/ISourceService';
|
||||
export * from './services/map/IMapService';
|
||||
|
@ -48,6 +49,7 @@ export * from './services/camera/ICameraService';
|
|||
export * from './services/config/IConfigService';
|
||||
export * from './services/scene/ISceneService';
|
||||
export * from './services/shader/IShaderModuleService';
|
||||
export * from './services/asset/IIconService';
|
||||
|
||||
/** 全部渲染服务接口 */
|
||||
export * from './services/renderer/IAttribute';
|
||||
|
|
|
@ -6,6 +6,7 @@ import getDecorators from 'inversify-inject-decorators';
|
|||
import { TYPES } from './types';
|
||||
|
||||
/** Service interfaces */
|
||||
import { IIconService } from './services/asset/IIconService';
|
||||
import { ICameraService } from './services/camera/ICameraService';
|
||||
import { IGlobalConfigService } from './services/config/IConfigService';
|
||||
import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService';
|
||||
|
@ -15,6 +16,7 @@ import { ILogService } from './services/log/ILogService';
|
|||
import { IShaderModuleService } from './services/shader/IShaderModuleService';
|
||||
|
||||
/** Service implements */
|
||||
import IconService from './services/asset/IconService';
|
||||
import CameraService from './services/camera/CameraService';
|
||||
import GlobalConfigService from './services/config/ConfigService';
|
||||
import CoordinateSystemService from './services/coordinate/CoordinateSystemService';
|
||||
|
@ -46,6 +48,10 @@ container
|
|||
.bind<ICoordinateSystemService>(TYPES.ICoordinateSystemService)
|
||||
.to(CoordinateSystemService)
|
||||
.inSingletonScope();
|
||||
container
|
||||
.bind<IIconService>(TYPES.IIconService)
|
||||
.to(IconService)
|
||||
.inSingletonScope();
|
||||
container
|
||||
.bind<IShaderModuleService>(TYPES.IShaderModuleService)
|
||||
.to(ShaderModuleService)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { ITexture2D } from '../renderer/ITexture2D';
|
||||
export type IImage = HTMLImageElement | File | string;
|
||||
export interface IIconValue {
|
||||
x: number;
|
||||
y: number;
|
||||
image: HTMLImageElement;
|
||||
}
|
||||
export interface IIcon {
|
||||
id: string;
|
||||
image: HTMLImageElement;
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
export interface IICONMap {
|
||||
[key: string]: IIconValue;
|
||||
}
|
||||
export interface IIconService {
|
||||
canvasHeight: number;
|
||||
init(): void;
|
||||
addImage(id: string, image: IImage): void;
|
||||
getTexture(): ITexture2D;
|
||||
getIconMap(): IICONMap;
|
||||
getCanvas(): HTMLCanvasElement;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import { inject, injectable } from 'inversify';
|
||||
import { TYPES } from '../../types';
|
||||
import { buildIconMaping } from '../../utils/font_util';
|
||||
import { gl } from '../renderer/gl';
|
||||
import { IRendererService } from '../renderer/IRendererService';
|
||||
import { ITexture2D } from '../renderer/ITexture2D';
|
||||
import {
|
||||
IIcon,
|
||||
IICONMap,
|
||||
IIconService,
|
||||
IIconValue,
|
||||
IImage,
|
||||
} from './IIconService';
|
||||
const BUFFER = 3;
|
||||
const MAX_CANVAS_WIDTH = 1024;
|
||||
const imageSize = 64;
|
||||
@injectable()
|
||||
export default class IconService implements IIconService {
|
||||
public canvasHeight: number;
|
||||
private textrure: ITexture2D;
|
||||
private canvas: HTMLCanvasElement;
|
||||
private iconData: IIcon[];
|
||||
private iconMap: IICONMap;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
public init() {
|
||||
this.iconData = [];
|
||||
this.iconMap = {};
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||
}
|
||||
|
||||
public addImage(id: string, image: IImage) {
|
||||
let imagedata = new Image();
|
||||
this.loadImage(image).then((img) => {
|
||||
imagedata = img as HTMLImageElement;
|
||||
});
|
||||
this.iconData.push({
|
||||
id,
|
||||
image: imagedata,
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
});
|
||||
const { mapping, canvasHeight } = buildIconMaping(
|
||||
this.iconData,
|
||||
BUFFER,
|
||||
MAX_CANVAS_WIDTH,
|
||||
);
|
||||
this.iconMap = mapping;
|
||||
this.canvasHeight = canvasHeight;
|
||||
this.updateIconAtlas();
|
||||
}
|
||||
|
||||
public getTexture(): ITexture2D {
|
||||
return this.textrure;
|
||||
}
|
||||
|
||||
public getIconMap() {
|
||||
return this.iconMap;
|
||||
}
|
||||
public getCanvas() {
|
||||
return this.canvas;
|
||||
}
|
||||
|
||||
private updateIconAtlas() {
|
||||
this.canvas.width = MAX_CANVAS_WIDTH;
|
||||
this.canvas.height = this.canvasHeight;
|
||||
Object.keys(this.iconMap).forEach((item: string) => {
|
||||
const { x, y, image } = this.iconMap[item];
|
||||
this.ctx.drawImage(image, x, y, imageSize, imageSize);
|
||||
});
|
||||
// const { createTexture2D } = this.rendererService;
|
||||
// this.textrure = createTexture2D({
|
||||
// data: this.canvas,
|
||||
// width: this.canvas.width,
|
||||
// height: this.canvasHeight,
|
||||
// mag: gl.LINEAR,
|
||||
// });
|
||||
}
|
||||
|
||||
private loadImage(url: IImage) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (url instanceof HTMLImageElement) {
|
||||
resolve(url);
|
||||
return;
|
||||
}
|
||||
const image = new Image();
|
||||
image.crossOrigin = 'anonymous';
|
||||
image.onload = () => {
|
||||
resolve(image);
|
||||
};
|
||||
image.onerror = () => {
|
||||
reject(new Error('Could not load image at ' + url));
|
||||
};
|
||||
image.src = url instanceof File ? URL.createObjectURL(url) : url;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ export const CameraUniform = {
|
|||
Zoom: 'u_Zoom',
|
||||
ZoomScale: 'u_ZoomScale',
|
||||
FocalDistance: 'u_FocalDistance',
|
||||
CameraPosition: 'u_CameraPosition',
|
||||
};
|
||||
|
||||
export interface IViewport {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ISourceCFG } from '@l7/source';
|
||||
import { ISourceCFG } from '@l7/core';
|
||||
import { AsyncParallelHook, SyncHook } from 'tapable';
|
||||
import { IModel } from '../renderer/IModel';
|
||||
import { IMultiPassRenderer } from '../renderer/IMultiPassRenderer';
|
||||
import { ISource } from '../source/ISourceService';
|
||||
|
||||
import { ILayerStyleOptions } from './ILayerStyleService';
|
||||
export enum ScaleTypes {
|
||||
LINEAR = 'linear',
|
||||
POWER = 'power',
|
||||
|
@ -15,8 +15,11 @@ export enum ScaleTypes {
|
|||
THRESHOLD = 'threshold',
|
||||
CAT = 'cat',
|
||||
}
|
||||
|
||||
export interface IScale {
|
||||
export enum StyleScaleType {
|
||||
CONSTANT = 'constant',
|
||||
VARIABLE = 'variable',
|
||||
}
|
||||
export interface IScaleOption {
|
||||
field?: string;
|
||||
type: ScaleTypes;
|
||||
ticks?: any[];
|
||||
|
@ -24,23 +27,32 @@ export interface IScale {
|
|||
format?: () => any;
|
||||
domain?: any[];
|
||||
}
|
||||
export interface IStyleScale {
|
||||
scale: any;
|
||||
field: string;
|
||||
type: StyleScaleType;
|
||||
option: IScaleOption;
|
||||
}
|
||||
|
||||
export interface ILayerGlobalConfig {
|
||||
colors: string[];
|
||||
size: number;
|
||||
shape: string;
|
||||
scales: {
|
||||
[key: string]: IScale;
|
||||
[key: string]: IScaleOption;
|
||||
};
|
||||
}
|
||||
|
||||
type CallBack = (...args: any[]) => any;
|
||||
export type StyleAttributeField = string | string[];
|
||||
export type StyleAttributeOption = string | number | boolean | any[] | CallBack;
|
||||
export type StyleAttrField = string | string[] | number | number[];
|
||||
export interface ILayerStyleAttribute {
|
||||
type?: string;
|
||||
names?: string[];
|
||||
field?: StyleAttributeField;
|
||||
type: string;
|
||||
names: string[];
|
||||
field: StyleAttributeField;
|
||||
values?: any[];
|
||||
scales?: any[];
|
||||
scales?: IStyleScale[];
|
||||
setScales: (scales: IStyleScale[]) => void;
|
||||
callback?: (...args: any[]) => [];
|
||||
mapping?(...params: unknown[]): unknown[];
|
||||
}
|
||||
|
@ -61,15 +73,19 @@ export interface ILayer {
|
|||
styleAttributes: {
|
||||
[attributeName: string]: ILayerStyleAttribute;
|
||||
};
|
||||
sourceOption: {
|
||||
data: any;
|
||||
options?: ISourceCFG;
|
||||
};
|
||||
multiPassRenderer: IMultiPassRenderer;
|
||||
init(): ILayer;
|
||||
// size(field: string, value: AttrOption): ILayer;
|
||||
// color(field: string, value: AttrOption): ILayer;
|
||||
// shape(field: string, value: AttrOption): ILayer;
|
||||
// pattern(field: string, value: AttrOption): ILayer;
|
||||
// filter(field: string, value: AttrOption): ILayer;
|
||||
size(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
color(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
shape(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
// pattern(field: string, value: StyleAttributeOption): ILayer;
|
||||
// filter(field: string, value: StyleAttributeOption): ILayer;
|
||||
// active(option: ActiveOption): ILayer;
|
||||
// style(options: ILayerStyleOptions): ILayer;
|
||||
style(options: ILayerStyleOptions): ILayer;
|
||||
// hide(): ILayer;
|
||||
// show(): ILayer;
|
||||
// animate(field: string, option: any): ILayer;
|
||||
|
@ -78,6 +94,7 @@ export interface ILayer {
|
|||
source(data: any, option?: ISourceCFG): ILayer;
|
||||
addPlugin(plugin: ILayerPlugin): ILayer;
|
||||
getSource(): ISource;
|
||||
setSource(source: ISource): void;
|
||||
setEncodedData(encodedData: Array<{ [key: string]: unknown }>): void;
|
||||
getEncodedData(): Array<{ [key: string]: unknown }>;
|
||||
getInitializationOptions(): Partial<ILayerInitializationOptions>;
|
||||
|
|
|
@ -33,6 +33,8 @@ export interface ITexture2DInitializationOptions {
|
|||
*/
|
||||
data?:
|
||||
| undefined
|
||||
| HTMLCanvasElement
|
||||
| HTMLImageElement
|
||||
| number[]
|
||||
| number[][]
|
||||
| Uint8Array
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { IImage } from '../asset/IIconService';
|
||||
import { ILayer } from '../layer/ILayerService';
|
||||
import { IMapConfig } from '../map/IMapService';
|
||||
import { IRenderConfig } from '../renderer/IRendererService';
|
||||
|
||||
|
||||
export interface ISceneService {
|
||||
init(config: IMapConfig & IRenderConfig): void;
|
||||
addLayer(layer: ILayer): void;
|
||||
addImage(id: string, image: IImage): void;
|
||||
render(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { inject, injectable } from 'inversify';
|
|||
import { AsyncParallelHook, AsyncSeriesHook } from 'tapable';
|
||||
import { TYPES } from '../../types';
|
||||
import { createRendererContainer } from '../../utils/dom';
|
||||
import { IIconService, IImage } from '../asset/IIconService';
|
||||
import { ICameraService, IViewport } from '../camera/ICameraService';
|
||||
import { IGlobalConfig, IGlobalConfigService } from '../config/IConfigService';
|
||||
import { IInteractionService } from '../interaction/IInteractionService';
|
||||
|
@ -12,7 +13,6 @@ import { IMapCamera, IMapService } from '../map/IMapService';
|
|||
import { IRendererService } from '../renderer/IRendererService';
|
||||
import { IShaderModuleService } from '../shader/IShaderModuleService';
|
||||
import { ISceneService } from './ISceneService';
|
||||
|
||||
/**
|
||||
* will emit `loaded` `resize` `destroy` event
|
||||
*/
|
||||
|
@ -45,6 +45,9 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
@inject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@inject(TYPES.IIconService)
|
||||
private readonly iconService: IIconService;
|
||||
|
||||
/**
|
||||
* 是否首次渲染
|
||||
*/
|
||||
|
@ -76,6 +79,10 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
|
||||
public init(globalConfig: IGlobalConfig) {
|
||||
this.configService.setAndCheckConfig(globalConfig);
|
||||
|
||||
// 初始化资源管理 字体,图片
|
||||
this.iconService.init();
|
||||
|
||||
/**
|
||||
* 初始化底图
|
||||
*/
|
||||
|
@ -151,6 +158,9 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.interactionService.destroy();
|
||||
window.removeEventListener('resize', this.handleWindowResized, false);
|
||||
}
|
||||
public addImage(id: string, img: IImage) {
|
||||
this.iconService.addImage(id, img);
|
||||
}
|
||||
|
||||
private handleWindowResized = () => {
|
||||
this.emit('resize');
|
||||
|
@ -174,7 +184,6 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.render();
|
||||
}
|
||||
};
|
||||
|
||||
private handleMapCameraChanged = (viewport: IViewport) => {
|
||||
this.cameraService.update(viewport);
|
||||
this.render();
|
||||
|
|
|
@ -4,6 +4,7 @@ import { extractUniforms } from '../../utils/shader-module';
|
|||
import { IModuleParams, IShaderModuleService } from './IShaderModuleService';
|
||||
|
||||
import decode from '../../shaders/decode.glsl';
|
||||
import lighting from '../../shaders/lighting.glsl';
|
||||
import projection from '../../shaders/projection.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('projection', { vs: projection, fs: '' });
|
||||
this.registerModule('sdf_2d', { vs: '', fs: sdf2d });
|
||||
this.registerModule('lighting', { vs: lighting, fs: '' });
|
||||
}
|
||||
|
||||
public registerModule(moduleName: string, moduleParams: IModuleParams) {
|
||||
|
|
|
@ -12,7 +12,7 @@ type CallBack = (...args: any[]) => any;
|
|||
export interface ITransform {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
callback: CallBack;
|
||||
callback?: CallBack;
|
||||
}
|
||||
|
||||
export interface ISourceCFG {
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Blinn-Phong model
|
||||
// apply lighting in vertex shader instead of fragment shader
|
||||
// @see https://learnopengl.com/Advanced-Lighting/Advanced-Lighting
|
||||
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;
|
||||
}
|
|
@ -9,6 +9,7 @@ const TYPES = {
|
|||
IMapService: Symbol.for('IMapService'),
|
||||
IRendererService: Symbol.for('IRendererService'),
|
||||
IShaderModuleService: Symbol.for('IShaderModuleService'),
|
||||
IIconService: Symbol.for('IIconService'),
|
||||
IInteractionService: Symbol.for('IInteractionService'),
|
||||
|
||||
/** multi-pass */
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import {
|
||||
IIcon,
|
||||
IICONMap,
|
||||
IIconService,
|
||||
IIconValue,
|
||||
IImage,
|
||||
} from '../services/asset/IIconService';
|
||||
export function buildIconMaping(
|
||||
icons: IIcon[],
|
||||
buffer: number,
|
||||
maxCanvasWidth: number,
|
||||
) {
|
||||
let xOffset = 0;
|
||||
let yOffset = 0;
|
||||
let rowHeight = 0;
|
||||
let columns = [];
|
||||
const mapping: IICONMap = {};
|
||||
for (const icon of icons) {
|
||||
if (!mapping[icon.id]) {
|
||||
const { height, width } = icon;
|
||||
|
||||
// fill one row
|
||||
if (xOffset + width + buffer > maxCanvasWidth) {
|
||||
buildRowMapping(mapping, columns, yOffset);
|
||||
|
||||
xOffset = 0;
|
||||
yOffset = rowHeight + yOffset + buffer;
|
||||
rowHeight = 0;
|
||||
columns = [];
|
||||
}
|
||||
|
||||
columns.push({
|
||||
icon,
|
||||
xOffset,
|
||||
});
|
||||
|
||||
xOffset = xOffset + width + buffer;
|
||||
rowHeight = Math.max(rowHeight, height);
|
||||
}
|
||||
}
|
||||
|
||||
if (columns.length > 0) {
|
||||
buildRowMapping(mapping, columns, yOffset);
|
||||
}
|
||||
|
||||
const canvasHeight = nextPowOfTwo(rowHeight + yOffset + buffer);
|
||||
|
||||
return {
|
||||
mapping,
|
||||
canvasHeight,
|
||||
};
|
||||
}
|
||||
function buildRowMapping(
|
||||
mapping: IICONMap,
|
||||
columns: Array<{
|
||||
icon: IIcon;
|
||||
xOffset: number;
|
||||
}>,
|
||||
yOffset: number,
|
||||
) {
|
||||
for (const column of columns) {
|
||||
const { icon, xOffset } = column;
|
||||
mapping[icon.id] = { ...icon, x: xOffset, y: yOffset, image: icon.image };
|
||||
}
|
||||
}
|
||||
export function nextPowOfTwo(num: number) {
|
||||
return Math.pow(2, Math.ceil(Math.log2(num)));
|
||||
}
|
|
@ -22,11 +22,16 @@
|
|||
"@l7/core": "^0.0.1",
|
||||
"@l7/source": "^0.0.1",
|
||||
"@turf/meta": "^6.0.2",
|
||||
"@types/d3-color": "^1.2.2",
|
||||
"d3-array": "^2.3.1",
|
||||
"d3-color": "^1.4.0",
|
||||
"d3-scale": "^3.1.0",
|
||||
"earcut": "^2.2.1",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"gl-matrix": "^3.1.0",
|
||||
"gl-vec2": "^1.3.0",
|
||||
"lodash": "^4.17.15",
|
||||
"polyline-miter-util": "^1.0.1",
|
||||
"tapable": "^2.0.0-beta.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import { IICONMap, ILayerStyleOptions } from '@l7/core';
|
||||
import { lngLatToMeters } from '@l7/utils';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
import { IExtrudeGeomety } from '../point/shape/extrude';
|
||||
interface IBufferCfg {
|
||||
data: unknown[];
|
||||
imagePos?: unknown;
|
||||
uv?: boolean;
|
||||
iconMap?: IICONMap;
|
||||
style?: ILayerStyleOptions;
|
||||
}
|
||||
type Position = number[];
|
||||
export type Position = number[];
|
||||
type Color = [number, number, number, number];
|
||||
export interface IBufferInfo {
|
||||
vertices?: any;
|
||||
indexArray?: any;
|
||||
indexOffset: any;
|
||||
verticesOffset: any;
|
||||
verticesOffset: number;
|
||||
faceNum?: any;
|
||||
dimensions: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface IEncodeFeature {
|
||||
color?: Color;
|
||||
|
@ -19,9 +24,10 @@ export interface IEncodeFeature {
|
|||
shape?: string | number;
|
||||
pattern?: string;
|
||||
id?: number;
|
||||
coordinates: Position[][];
|
||||
bufferInfo: IBufferInfo;
|
||||
coordinates: unknown;
|
||||
bufferInfo: unknown;
|
||||
}
|
||||
|
||||
export default class Buffer {
|
||||
public attributes: {
|
||||
[key: string]: Float32Array;
|
||||
|
@ -29,17 +35,64 @@ export default class Buffer {
|
|||
public verticesCount: number = 0;
|
||||
public indexArray: Uint32Array = new Uint32Array(0);
|
||||
public indexCount: number = 0;
|
||||
|
||||
public instanceGeometry: IExtrudeGeomety;
|
||||
protected data: unknown[];
|
||||
protected imagePos: unknown;
|
||||
protected uv: boolean;
|
||||
protected iconMap: IICONMap;
|
||||
protected style: any;
|
||||
|
||||
constructor({ data, imagePos, uv }: IBufferCfg) {
|
||||
constructor({ data, iconMap, style }: IBufferCfg) {
|
||||
this.data = data;
|
||||
this.imagePos = imagePos;
|
||||
this.uv = !!uv;
|
||||
this.iconMap = iconMap as IICONMap;
|
||||
this.style = style;
|
||||
this.init();
|
||||
}
|
||||
public computeVertexNormals(
|
||||
field: string = 'positions',
|
||||
flag: boolean = true,
|
||||
) {
|
||||
const normals = (this.attributes.normals = new Float32Array(
|
||||
this.verticesCount * 3,
|
||||
));
|
||||
const indexArray = this.indexArray;
|
||||
const positions = this.attributes[field];
|
||||
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] = flag
|
||||
? lngLatToMeters([positions[vA], positions[vA + 1]])
|
||||
: [positions[vA], positions[vA + 1]];
|
||||
const pA = vec3.fromValues(ax, ay, positions[vA + 2]);
|
||||
const [bx, by] = flag
|
||||
? lngLatToMeters([positions[vB], positions[vB + 1]])
|
||||
: [positions[vB], positions[vB + 1]];
|
||||
const pB = vec3.fromValues(bx, by, positions[vB + 2]);
|
||||
const [cx, cy] = flag
|
||||
? lngLatToMeters([positions[vC], positions[vC + 1]])
|
||||
: [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() {
|
||||
throw new Error('Method not implemented.');
|
||||
|
@ -47,11 +100,13 @@ export default class Buffer {
|
|||
protected buildFeatures() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
protected checkIsClosed(points: Position[][]) {
|
||||
const p1 = points[0][0];
|
||||
const p2 = points[0][points[0].length - 1];
|
||||
return p1[0] === p2[0] && p1[1] === p2[1];
|
||||
}
|
||||
|
||||
protected concat(arrayType: Float32Array, arrays: any) {
|
||||
let totalLength = 0;
|
||||
for (const arr of arrays) {
|
||||
|
@ -71,8 +126,9 @@ export default class Buffer {
|
|||
}
|
||||
protected encodeArray(feature: IEncodeFeature, num: number) {
|
||||
const { color, id, pattern, size } = feature;
|
||||
const { verticesOffset } = feature.bufferInfo;
|
||||
const imagePos = this.imagePos;
|
||||
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||
const { verticesOffset } = bufferInfo;
|
||||
const imagePos = this.iconMap;
|
||||
const start1 = verticesOffset;
|
||||
for (let i = 0; i < num; i++) {
|
||||
if (color) {
|
||||
|
@ -88,7 +144,7 @@ export default class Buffer {
|
|||
let size2: number[] = [];
|
||||
if (Array.isArray(size) && size.length === 2) {
|
||||
// TODO 多维size支持
|
||||
size2 = [size[0]];
|
||||
size2 = [size[0], size[0], size[1]];
|
||||
}
|
||||
if (!Array.isArray(size)) {
|
||||
size2 = [size];
|
||||
|
@ -103,82 +159,35 @@ export default class Buffer {
|
|||
}
|
||||
}
|
||||
}
|
||||
protected calculateWall(feature: IEncodeFeature) {
|
||||
const size = feature.size;
|
||||
const {
|
||||
vertices,
|
||||
indexOffset,
|
||||
verticesOffset,
|
||||
faceNum,
|
||||
dimensions,
|
||||
} = feature.bufferInfo;
|
||||
this.encodeArray(feature, faceNum * 4);
|
||||
for (let i = 0; i < faceNum; i++) {
|
||||
const prePoint = vertices.slice(i * 3, i * 3 + 3);
|
||||
const nextPoint = vertices.slice(i * 3 + 3, i * 3 + 6);
|
||||
this.calculateExtrudeFace(
|
||||
prePoint,
|
||||
nextPoint,
|
||||
verticesOffset + i * 4,
|
||||
indexOffset + i * 6,
|
||||
size as number,
|
||||
);
|
||||
feature.bufferInfo.verticesOffset += 4;
|
||||
feature.bufferInfo.indexOffset += 6;
|
||||
}
|
||||
}
|
||||
|
||||
protected calculateExtrudeFace(
|
||||
prePoint: number[],
|
||||
nextPoint: number[],
|
||||
positionOffset: number,
|
||||
indexOffset: number | undefined,
|
||||
size: number,
|
||||
) {
|
||||
this.attributes.positions.set(
|
||||
[
|
||||
prePoint[0],
|
||||
prePoint[1],
|
||||
size,
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
size,
|
||||
prePoint[0],
|
||||
prePoint[1],
|
||||
0,
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
0,
|
||||
],
|
||||
positionOffset * 3,
|
||||
);
|
||||
const indexArray = [1, 2, 0, 3, 2, 1].map((v) => {
|
||||
return v + positionOffset;
|
||||
});
|
||||
if (this.uv) {
|
||||
this.attributes.uv.set(
|
||||
[0.1, 0, 0, 0, 0.1, size / 2000, 0, size / 2000],
|
||||
positionOffset * 2,
|
||||
);
|
||||
}
|
||||
this.indexArray.set(indexArray, indexOffset);
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.calculateFeatures();
|
||||
this.initAttributes();
|
||||
this.buildFeatures();
|
||||
}
|
||||
|
||||
private initAttributes() {
|
||||
protected initAttributes() {
|
||||
this.attributes.positions = new Float32Array(this.verticesCount * 3);
|
||||
this.attributes.colors = new Float32Array(this.verticesCount * 4);
|
||||
this.attributes.pickingIds = new Float32Array(this.verticesCount);
|
||||
this.attributes.sizes = new Float32Array(this.verticesCount);
|
||||
this.attributes.pickingIds = new Float32Array(this.verticesCount);
|
||||
if (this.uv) {
|
||||
this.attributes.uv = new Float32Array(this.verticesCount * 2);
|
||||
}
|
||||
this.indexArray = new Uint32Array(this.indexCount);
|
||||
}
|
||||
|
||||
private init() {
|
||||
// 1. 计算 attribute 长度
|
||||
this.calculateFeatures();
|
||||
// 2. 初始化 attribute
|
||||
this.initAttributes();
|
||||
// 3. 拼接attribute
|
||||
this.buildFeatures();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
import {
|
||||
IGlobalConfigService,
|
||||
IIconService,
|
||||
ILayer,
|
||||
ILayerInitializationOptions,
|
||||
ILayerPlugin,
|
||||
ILayerStyleAttribute,
|
||||
ILayerStyleOptions,
|
||||
IModel,
|
||||
IMultiPassRenderer,
|
||||
IRendererService,
|
||||
ISource,
|
||||
ISourceCFG,
|
||||
lazyInject,
|
||||
StyleAttributeField,
|
||||
StyleAttributeOption,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import Source, { ISourceCFG } from '@l7/source';
|
||||
import Source from '@l7/source';
|
||||
import { isFunction } from 'lodash';
|
||||
import { SyncHook } from 'tapable';
|
||||
import DataEncodePlugin from '../plugins/DataEncodePlugin';
|
||||
import DataSourcePlugin from '../plugins/DataSourcePlugin';
|
||||
import MultiPassRendererPlugin from '../plugins/MultiPassRendererPlugin';
|
||||
import ShaderUniformPlugin from '../plugins/ShaderUniformPlugin';
|
||||
import StyleAttribute from './StyleAttribute';
|
||||
|
@ -37,15 +43,24 @@ export default class BaseLayer implements ILayer {
|
|||
|
||||
// 插件集
|
||||
public plugins: ILayerPlugin[] = [
|
||||
new DataSourcePlugin(),
|
||||
new DataEncodePlugin(),
|
||||
new MultiPassRendererPlugin(),
|
||||
new ShaderUniformPlugin(),
|
||||
];
|
||||
|
||||
public sourceOption: {
|
||||
data: any;
|
||||
options?: ISourceCFG;
|
||||
};
|
||||
public styleOption: ILayerStyleOptions = {
|
||||
opacity: 1.0,
|
||||
};
|
||||
// 样式属性
|
||||
public styleAttributes: {
|
||||
[key: string]: Partial<ILayerStyleAttribute>;
|
||||
[key: string]: Required<ILayerStyleAttribute>;
|
||||
} = {};
|
||||
@lazyInject(TYPES.IIconService)
|
||||
protected readonly iconService: IIconService;
|
||||
|
||||
protected layerSource: Source;
|
||||
|
||||
|
@ -77,8 +92,10 @@ export default class BaseLayer implements ILayer {
|
|||
this.buildModels();
|
||||
return this;
|
||||
}
|
||||
|
||||
public color(field: StyleAttributeField, values?: any) {
|
||||
public color(
|
||||
field: StyleAttributeField,
|
||||
values?: StyleAttributeOption,
|
||||
): ILayer {
|
||||
this.createStyleAttribute(
|
||||
'color',
|
||||
field,
|
||||
|
@ -88,7 +105,10 @@ export default class BaseLayer implements ILayer {
|
|||
return this;
|
||||
}
|
||||
|
||||
public size(field: StyleAttributeField, values?: any) {
|
||||
public size(
|
||||
field: StyleAttributeField,
|
||||
values?: StyleAttributeOption,
|
||||
): ILayer {
|
||||
this.createStyleAttribute(
|
||||
'size',
|
||||
field,
|
||||
|
@ -98,7 +118,10 @@ export default class BaseLayer implements ILayer {
|
|||
return this;
|
||||
}
|
||||
|
||||
public shape(field: StyleAttributeField, values?: any) {
|
||||
public shape(
|
||||
field: StyleAttributeField,
|
||||
values?: StyleAttributeOption,
|
||||
): ILayer {
|
||||
this.createStyleAttribute(
|
||||
'shape',
|
||||
field,
|
||||
|
@ -108,15 +131,17 @@ export default class BaseLayer implements ILayer {
|
|||
return this;
|
||||
}
|
||||
|
||||
public source(data: any, options?: ISourceCFG) {
|
||||
this.layerSource = new Source(data, options);
|
||||
public source(data: any, options?: ISourceCFG): ILayer {
|
||||
this.sourceOption = {
|
||||
data,
|
||||
options,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public style(styleAttributes: any) {
|
||||
//
|
||||
public style(options: ILayerStyleOptions): ILayer {
|
||||
this.styleOption = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public render(): ILayer {
|
||||
if (this.multiPassRenderer && this.multiPassRenderer.getRenderFlag()) {
|
||||
this.multiPassRenderer.render();
|
||||
|
@ -131,11 +156,14 @@ export default class BaseLayer implements ILayer {
|
|||
}
|
||||
|
||||
public getStyleAttributes(): {
|
||||
[key: string]: Partial<ILayerStyleAttribute>;
|
||||
[key: string]: Required<ILayerStyleAttribute>;
|
||||
} {
|
||||
return this.styleAttributes;
|
||||
}
|
||||
|
||||
public setSource(source: Source) {
|
||||
this.layerSource = source;
|
||||
}
|
||||
public getSource() {
|
||||
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 * as d3 from 'd3-scale';
|
||||
import { isNil, isNumber, isString, uniq } from 'lodash';
|
||||
|
@ -19,27 +24,28 @@ const scaleMap = {
|
|||
|
||||
export default class ScaleController {
|
||||
private scaleOptions: {
|
||||
[field: string]: IScale;
|
||||
[field: string]: IScaleOption;
|
||||
};
|
||||
constructor(cfg: { [field: string]: IScale }) {
|
||||
constructor(cfg: { [field: string]: IScaleOption }) {
|
||||
this.scaleOptions = cfg;
|
||||
}
|
||||
|
||||
public createScale(
|
||||
field: string,
|
||||
data?: any[],
|
||||
): { field: string; scale: any } {
|
||||
let scaleOption: IScale = this.scaleOptions[field];
|
||||
const scale: { field: string; scale: any } = {
|
||||
public createScale(field: string, data?: any[]): IStyleScale {
|
||||
let scaleOption: IScaleOption = this.scaleOptions[field];
|
||||
const scale: IStyleScale = {
|
||||
field,
|
||||
scale: undefined,
|
||||
type: StyleScaleType.VARIABLE,
|
||||
option: scaleOption,
|
||||
};
|
||||
if (!data || !data.length) {
|
||||
// 数据为空
|
||||
scale.scale =
|
||||
scaleOption && scaleOption.type
|
||||
? this.generateScale(scaleOption.type, scaleOption)
|
||||
: d3.scaleOrdinal([field]);
|
||||
if (scaleOption && scaleOption.type) {
|
||||
scale.scale = this.generateScale(scaleOption.type, scaleOption);
|
||||
} else {
|
||||
scale.scale = d3.scaleOrdinal([field]);
|
||||
scale.type = StyleScaleType.CONSTANT;
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
let firstValue = null;
|
||||
|
@ -53,6 +59,7 @@ export default class ScaleController {
|
|||
// 常量 Scale
|
||||
if (isNumber(field) || (isNil(firstValue) && !scaleOption)) {
|
||||
scale.scale = d3.scaleOrdinal([field]);
|
||||
scale.type = StyleScaleType.CONSTANT;
|
||||
} else {
|
||||
// 根据数据类型判断 默认等分位,时间,和枚举类型
|
||||
const type =
|
||||
|
@ -61,8 +68,9 @@ export default class ScaleController {
|
|||
: this.getDefaultType(field, firstValue);
|
||||
const cfg = this.getScaleCfg(type, field, data);
|
||||
Object.assign(cfg, scaleOption);
|
||||
scaleOption = cfg;
|
||||
scaleOption = cfg; // 更新scale配置
|
||||
scale.scale = this.generateScale(type, cfg);
|
||||
scale.option = scaleOption;
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
@ -78,7 +86,7 @@ export default class ScaleController {
|
|||
}
|
||||
|
||||
private getScaleCfg(type: ScaleTypes, field: string, data: any[]) {
|
||||
const cfg: IScale = {
|
||||
const cfg: IScaleOption = {
|
||||
field,
|
||||
type,
|
||||
};
|
||||
|
@ -92,11 +100,12 @@ export default class ScaleController {
|
|||
return cfg;
|
||||
}
|
||||
|
||||
private generateScale(type: ScaleTypes, scaleOption: IScale) {
|
||||
private generateScale(type: ScaleTypes, scaleOption: IScaleOption) {
|
||||
// @ts-ignore
|
||||
const scale = scaleMap[type]();
|
||||
let scale = scaleMap[type]();
|
||||
if (scaleOption.hasOwnProperty('domain')) {
|
||||
scale.domain(scaleOption.domain);
|
||||
// 处理同一字段映射不同视觉通道的问题
|
||||
scale = scale.copy().domain(scaleOption.domain);
|
||||
}
|
||||
// TODO 其他属性支持
|
||||
return scale;
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { ILayerStyleAttribute } from '@l7/core';
|
||||
import { isNil } from 'lodash';
|
||||
import { ILayerStyleAttribute, IStyleScale, StyleScaleType } from '@l7/core';
|
||||
import { isNil, isString } from 'lodash';
|
||||
|
||||
type CallBack = (...args: any[]) => any;
|
||||
|
||||
export default class StyleAttribute implements ILayerStyleAttribute {
|
||||
public type: string;
|
||||
public type: StyleScaleType;
|
||||
public names: string[];
|
||||
public scales: any[] = [];
|
||||
public scales: IStyleScale[] = [];
|
||||
public values: any[] = [];
|
||||
public field: string;
|
||||
constructor(cfg: any) {
|
||||
const {
|
||||
type = 'base',
|
||||
names = [],
|
||||
type = StyleScaleType.CONSTANT,
|
||||
scales = [],
|
||||
values = [],
|
||||
callback,
|
||||
|
@ -22,11 +21,10 @@ export default class StyleAttribute implements ILayerStyleAttribute {
|
|||
this.type = type;
|
||||
this.scales = scales;
|
||||
this.values = values;
|
||||
this.names = names;
|
||||
// 设置 range TODO 2维映射
|
||||
this.scales.forEach((scale) => {
|
||||
scale.scale.range(values);
|
||||
});
|
||||
this.names = this.parseFields(field) || [];
|
||||
if (callback) {
|
||||
this.type = StyleScaleType.VARIABLE;
|
||||
}
|
||||
this.callback = (...params: any[]): any[] => {
|
||||
/**
|
||||
* 当用户设置的 callback 返回 null 时, 应该返回默认 callback 中的值
|
||||
|
@ -44,7 +42,22 @@ export default class StyleAttribute implements ILayerStyleAttribute {
|
|||
};
|
||||
}
|
||||
public callback: CallBack = () => [];
|
||||
|
||||
public setScales(scales: IStyleScale[]): void {
|
||||
if (scales.some((scale) => scale.type === StyleScaleType.VARIABLE)) {
|
||||
this.type = StyleScaleType.VARIABLE;
|
||||
scales.forEach((scale) => {
|
||||
if (this.values.length > 0) {
|
||||
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[] {
|
||||
return this.callback.apply(this, params);
|
||||
}
|
||||
|
@ -60,4 +73,18 @@ export default class StyleAttribute implements ILayerStyleAttribute {
|
|||
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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer';
|
||||
import extrudePolygon, {
|
||||
fillPolygon,
|
||||
IExtrudeGeomety,
|
||||
} from '../../point/shape/extrude';
|
||||
import {
|
||||
geometryShape,
|
||||
ShapeType2D,
|
||||
ShapeType3D,
|
||||
} from '../../point/shape/Path';
|
||||
export default class GridHeatMapBuffer extends BufferBase {
|
||||
private verticesOffset: number = 0;
|
||||
protected buildFeatures() {
|
||||
this.verticesOffset = 0;
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
layerData.forEach((feature: IEncodeFeature) => {
|
||||
this.calculateFill(feature);
|
||||
});
|
||||
}
|
||||
protected calculateFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
const shape = layerData[0].shape as ShapeType3D | ShapeType2D;
|
||||
this.verticesCount = layerData.length;
|
||||
this.indexCount = 0;
|
||||
this.instanceGeometry = this.getGeometry(shape as
|
||||
| ShapeType2D
|
||||
| ShapeType3D);
|
||||
}
|
||||
protected calculateFill(feature: IEncodeFeature) {
|
||||
feature.bufferInfo = { verticesOffset: this.verticesOffset };
|
||||
const coordinates = feature.coordinates as Position;
|
||||
this.encodeArray(feature, 1);
|
||||
this.attributes.positions.set([...coordinates, 1], this.verticesOffset * 3);
|
||||
this.verticesOffset++;
|
||||
}
|
||||
private getGeometry(shape: ShapeType2D | ShapeType3D): IExtrudeGeomety {
|
||||
const path = geometryShape[shape]
|
||||
? geometryShape[shape]()
|
||||
: geometryShape.circle();
|
||||
// const geometry = ShapeType2D[str as ShapeType2D]
|
||||
// ? fillPolygon([path])
|
||||
// : extrudePolygon([path]);
|
||||
const geometry = fillPolygon([path]);
|
||||
return geometry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import {
|
||||
gl,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
lazyInject,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import GridHeatMapBuffer from './buffers/GridBuffer';
|
||||
import hexagon_frag from './shaders/hexagon_frag.glsl';
|
||||
import hexagon_vert from './shaders/hexagon_vert.glsl';
|
||||
|
||||
export default class HeatMapLayer extends BaseLayer {
|
||||
public name: string = 'HeatMapLayer';
|
||||
|
||||
@lazyInject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
private readonly renderer: IRendererService;
|
||||
|
||||
protected renderModels() {
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected buildModels(): void {
|
||||
this.shaderModule.registerModule('grid', {
|
||||
vs: hexagon_vert,
|
||||
fs: hexagon_frag,
|
||||
});
|
||||
this.models = [];
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('grid');
|
||||
const buffer = new GridHeatMapBuffer({
|
||||
data: this.getEncodedData(),
|
||||
});
|
||||
console.log(this.getSource());
|
||||
console.log(buffer);
|
||||
const {
|
||||
createAttribute,
|
||||
createBuffer,
|
||||
createElements,
|
||||
createModel,
|
||||
} = this.renderer;
|
||||
|
||||
this.models.push(
|
||||
createModel({
|
||||
attributes: {
|
||||
a_miter: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.instanceGeometry.positions,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
divisor: 0,
|
||||
}),
|
||||
// a_normal: createAttribute({
|
||||
// buffer: createBuffer({
|
||||
// data: buffer.attributes.normals,
|
||||
// type: gl.FLOAT,
|
||||
// }),
|
||||
// size: 3,
|
||||
// }),
|
||||
a_color: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.colors,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 4,
|
||||
divisor: 1,
|
||||
}),
|
||||
// a_size: createAttribute({
|
||||
// buffer: createBuffer({
|
||||
// data: buffer.attributes.sizes,
|
||||
// type: gl.FLOAT,
|
||||
// }),
|
||||
// size: 1,
|
||||
// divisor: 1,
|
||||
// }),
|
||||
a_Position: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.positions,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
divisor: 1,
|
||||
}),
|
||||
},
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
u_opacity: (this.styleOption.opacity as number) || 1.0,
|
||||
u_radius: [
|
||||
this.getSource().data.xOffset,
|
||||
this.getSource().data.yOffset,
|
||||
],
|
||||
},
|
||||
fs,
|
||||
vs,
|
||||
count: buffer.instanceGeometry.index.length,
|
||||
instances: buffer.verticesCount,
|
||||
elements: createElements({
|
||||
data: buffer.instanceGeometry.index,
|
||||
type: gl.UNSIGNED_INT,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
precision highp float;
|
||||
varying vec4 v_color;
|
||||
uniform float u_opacity: 0.1;
|
||||
void main() {
|
||||
gl_FragColor = v_color;
|
||||
gl_FragColor.a *= u_opacity;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
precision highp float;
|
||||
attribute vec3 a_Position;
|
||||
attribute vec3 a_miter;
|
||||
attribute float a_size;
|
||||
attribute vec4 a_color;
|
||||
uniform vec2 u_radius;
|
||||
uniform float u_coverage: 1.;
|
||||
uniform float u_angle: 0;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
varying vec4 v_color;
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
mat2 rotationMatrix = mat2(cos(u_angle), sin(u_angle), -sin(u_angle), cos(u_angle));
|
||||
vec2 offset =(vec2(a_miter.xy * u_radius * u_coverage * rotationMatrix));
|
||||
vec4 project_pos = project_position(vec4(a_Position.xy + offset, 0, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy, 0., 1.0));
|
||||
}
|
|
@ -1,5 +1,16 @@
|
|||
import BaseLayer from './core/BaseLayer';
|
||||
import HeatMapLayer from './heatmap';
|
||||
import Line from './line';
|
||||
import PointLayer from './point';
|
||||
import Point from './point/point';
|
||||
import PolygonLayer from './polygon';
|
||||
|
||||
export { BaseLayer, PointLayer, PolygonLayer };
|
||||
import ImageLayer from './raster';
|
||||
export {
|
||||
BaseLayer,
|
||||
PointLayer,
|
||||
PolygonLayer,
|
||||
Point,
|
||||
Line,
|
||||
ImageLayer,
|
||||
HeatMapLayer,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import { lngLatToMeters, Point } from '@l7/utils';
|
||||
import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer';
|
||||
import getNormals from '../../utils/polylineNormal';
|
||||
interface IBufferInfo {
|
||||
normals: number[];
|
||||
arrayIndex: number[];
|
||||
positions: number[];
|
||||
attrDistance: number[];
|
||||
miters: number[];
|
||||
verticesOffset: number;
|
||||
indexOffset: number;
|
||||
}
|
||||
export default class LineBuffer extends BufferBase {
|
||||
private hasPattern: boolean;
|
||||
protected buildFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
layerData.forEach((feature: IEncodeFeature) => {
|
||||
this.calculateLine(feature);
|
||||
delete feature.bufferInfo;
|
||||
});
|
||||
this.hasPattern = layerData.some((feature: IEncodeFeature) => {
|
||||
return feature.pattern;
|
||||
});
|
||||
}
|
||||
protected initAttributes() {
|
||||
super.initAttributes();
|
||||
this.attributes.dashArray = new Float32Array(this.verticesCount);
|
||||
this.attributes.attrDistance = new Float32Array(this.verticesCount);
|
||||
this.attributes.totalDistances = new Float32Array(this.verticesCount);
|
||||
this.attributes.patterns = new Float32Array(this.verticesCount * 2);
|
||||
this.attributes.miters = new Float32Array(this.verticesCount);
|
||||
this.attributes.normals = new Float32Array(this.verticesCount * 3);
|
||||
}
|
||||
protected calculateFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
// 计算长
|
||||
layerData.forEach((feature: IEncodeFeature, index: number) => {
|
||||
let coordinates = feature.coordinates as Position[] | Position[][];
|
||||
if (Array.isArray(coordinates[0][0])) {
|
||||
coordinates = coordinates[0] as Position[];
|
||||
}
|
||||
// @ts-ignore
|
||||
const projectCoord: number[][] = coordinates.map((item: Position[]) => {
|
||||
// @ts-ignore
|
||||
const p: Point = [...item];
|
||||
return lngLatToMeters(p);
|
||||
});
|
||||
const { normals, attrIndex, attrPos, attrDistance, miters } = getNormals(
|
||||
coordinates as number[][],
|
||||
false,
|
||||
this.verticesCount,
|
||||
);
|
||||
const bufferInfo: IBufferInfo = {
|
||||
normals,
|
||||
arrayIndex: attrIndex,
|
||||
positions: attrPos,
|
||||
attrDistance,
|
||||
miters,
|
||||
verticesOffset: this.verticesCount,
|
||||
indexOffset: this.indexCount,
|
||||
};
|
||||
this.verticesCount += attrPos.length / 3;
|
||||
this.indexCount += attrIndex.length;
|
||||
feature.bufferInfo = bufferInfo;
|
||||
});
|
||||
}
|
||||
private calculateLine(feature: IEncodeFeature) {
|
||||
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||
const {
|
||||
normals,
|
||||
arrayIndex,
|
||||
positions,
|
||||
attrDistance,
|
||||
miters,
|
||||
verticesOffset,
|
||||
indexOffset,
|
||||
} = bufferInfo;
|
||||
const { dashArray = 200 } = this.style;
|
||||
|
||||
this.encodeArray(feature, positions.length / 3);
|
||||
const totalLength = attrDistance[attrDistance.length - 1];
|
||||
// 增加长度
|
||||
const totalDistances = Array(positions.length / 3).fill(totalLength);
|
||||
// 虚线比例
|
||||
const ratio = dashArray / totalLength;
|
||||
const dashArrays = Array(positions.length / 3).fill(ratio);
|
||||
this.attributes.positions.set(positions, verticesOffset * 3);
|
||||
this.indexArray.set(arrayIndex, indexOffset);
|
||||
this.attributes.miters.set(miters, verticesOffset);
|
||||
this.attributes.normals.set(normals, verticesOffset * 3);
|
||||
this.attributes.attrDistance.set(attrDistance, verticesOffset);
|
||||
this.attributes.totalDistances.set(totalDistances, verticesOffset);
|
||||
this.attributes.dashArray.set(dashArrays, verticesOffset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
import {
|
||||
gl,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
lazyInject,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import LineBuffer from './buffers/line';
|
||||
import line_frag from './shaders/line_frag.glsl';
|
||||
import line_vert from './shaders/line_vert.glsl';
|
||||
export default class LineLayer extends BaseLayer {
|
||||
public name: string = 'LineLayer';
|
||||
@lazyInject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
private readonly renderer: IRendererService;
|
||||
|
||||
protected renderModels() {
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
protected buildModels(): void {
|
||||
this.shaderModule.registerModule('line', {
|
||||
vs: line_vert,
|
||||
fs: line_frag,
|
||||
});
|
||||
|
||||
this.models = [];
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('line');
|
||||
const buffer = new LineBuffer({
|
||||
data: this.getEncodedData(),
|
||||
style: this.styleOption,
|
||||
});
|
||||
console.log(buffer);
|
||||
const {
|
||||
createAttribute,
|
||||
createBuffer,
|
||||
createElements,
|
||||
createModel,
|
||||
} = this.renderer;
|
||||
|
||||
this.models.push(
|
||||
createModel({
|
||||
attributes: {
|
||||
a_Position: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.positions,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_normal: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.normals,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_color: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.colors,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 4,
|
||||
}),
|
||||
a_size: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.sizes,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 1,
|
||||
}),
|
||||
a_miter: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.miters,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 1,
|
||||
}),
|
||||
},
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
u_opacity: this.styleOption.opacity as number,
|
||||
},
|
||||
fs,
|
||||
vs,
|
||||
count: buffer.indexArray.length,
|
||||
elements: createElements({
|
||||
data: buffer.indexArray,
|
||||
type: gl.UNSIGNED_INT,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
uniform float u_blur : 0.99;
|
||||
varying vec4 v_color;
|
||||
varying vec3 v_normal;
|
||||
void main() {
|
||||
gl_FragColor = v_color;
|
||||
// anti-alias
|
||||
float blur = smoothstep(u_blur, 1., length(v_normal.xy));
|
||||
gl_FragColor.a *= blur;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
attribute float a_miter;
|
||||
attribute vec4 a_color;
|
||||
attribute float a_size;
|
||||
attribute float a_distance;
|
||||
attribute float a_dash_array;
|
||||
attribute float a_total_distance;
|
||||
attribute vec3 a_normal;
|
||||
attribute vec3 a_Position;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
|
||||
varying vec4 v_color;
|
||||
varying float v_dash_array;
|
||||
varying vec3 v_normal;
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
v_normal = a_normal;
|
||||
v_color = a_color;
|
||||
vec3 size = a_miter * a_size * v_normal;
|
||||
vec2 offset = project_pixel(size.xy);
|
||||
vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0, 1.0));
|
||||
}
|
|
@ -4,7 +4,9 @@ import {
|
|||
ILayerPlugin,
|
||||
ILayerStyleAttribute,
|
||||
IParseDataItem,
|
||||
IStyleScale,
|
||||
lazyInject,
|
||||
StyleScaleType,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import { isString } from 'lodash';
|
||||
|
@ -18,10 +20,7 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
|||
private scaleController: ScaleController;
|
||||
|
||||
private scaleCache: {
|
||||
[fieldName: string]: {
|
||||
field: string;
|
||||
scale: any;
|
||||
};
|
||||
[fieldName: string]: IStyleScale;
|
||||
} = {};
|
||||
|
||||
public apply(layer: ILayer) {
|
||||
|
@ -39,14 +38,12 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
|||
// create scales by source data & config
|
||||
Object.keys(layer.styleAttributes).forEach((attributeName) => {
|
||||
const attribute = layer.styleAttributes[attributeName];
|
||||
const fields = this.parseFields(attribute.field || '');
|
||||
const scales: any[] = [];
|
||||
fields.forEach((field: string) => {
|
||||
scales.push(this.getOrCreateScale(attribute, dataArray));
|
||||
attribute.names.forEach((field: string) => {
|
||||
scales.push(this.getOrCreateScale(attribute, field, dataArray));
|
||||
});
|
||||
attribute.scales = scales;
|
||||
attribute.setScales(scales);
|
||||
});
|
||||
|
||||
// mapping with source data
|
||||
layer.setEncodedData(this.mapping(layer.styleAttributes, dataArray));
|
||||
});
|
||||
|
@ -55,33 +52,28 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
|||
// layer.hooks.beforeRender.tap()
|
||||
}
|
||||
|
||||
private getOrCreateScale(attribute: ILayerStyleAttribute, data: any[]) {
|
||||
const { field } = attribute;
|
||||
private getOrCreateScale(
|
||||
attribute: ILayerStyleAttribute,
|
||||
field: string,
|
||||
data: any[],
|
||||
): IStyleScale {
|
||||
let scale = this.scaleCache[field as string];
|
||||
if (!scale) {
|
||||
scale = this.scaleController.createScale(field as string, data);
|
||||
scale.scale.range(attribute.values);
|
||||
if (
|
||||
scale.type === StyleScaleType.VARIABLE &&
|
||||
attribute.values &&
|
||||
attribute.values.length > 0
|
||||
) {
|
||||
scale.scale.range(attribute.values);
|
||||
}
|
||||
this.scaleCache[field as string] = scale;
|
||||
}
|
||||
// scale: scale.scale.copy(),
|
||||
return this.scaleCache[field as string];
|
||||
return {
|
||||
...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(
|
||||
attributes: {
|
||||
[attributeName: string]: ILayerStyleAttribute;
|
||||
|
@ -93,11 +85,13 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
|||
id: record._id,
|
||||
coordinates: record.coordinates,
|
||||
};
|
||||
// TODO 数据过滤
|
||||
Object.keys(attributes).forEach((attributeName: string) => {
|
||||
const attribute = attributes[attributeName];
|
||||
// const { type } = attribute;
|
||||
// if (type === StyleScaleType.CONSTANT) {
|
||||
// return;
|
||||
// }
|
||||
let values = this.getAttrValue(attribute, record);
|
||||
|
||||
if (attributeName === 'color') {
|
||||
values = values.map((c: unknown) => {
|
||||
return rgb2arr(c as string);
|
||||
|
@ -117,8 +111,11 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
|||
const scales = attribute.scales || [];
|
||||
const params: unknown[] = [];
|
||||
|
||||
scales.forEach(({ field }) => {
|
||||
if (record[field]) {
|
||||
scales.forEach((scale) => {
|
||||
const { field, type } = scale;
|
||||
if (type === StyleScaleType.CONSTANT) {
|
||||
params.push(scale.field);
|
||||
} else {
|
||||
params.push(record[field]);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import {
|
||||
IGlobalConfigService,
|
||||
ILayer,
|
||||
ILayerPlugin,
|
||||
ILayerStyleAttribute,
|
||||
IParseDataItem,
|
||||
IStyleScale,
|
||||
lazyInject,
|
||||
StyleScaleType,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import { ISourceCFG } from '@l7/core';
|
||||
import Source 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));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
import BaseBuffer, {
|
||||
IBufferInfo,
|
||||
IEncodeFeature,
|
||||
Position,
|
||||
} from '../../core/BaseBuffer';
|
||||
import extrudePolygon, { IExtrudeGeomety } from '../shape/extrude';
|
||||
import { geometryShape, ShapeType2D, ShapeType3D } from '../shape/Path';
|
||||
interface IGeometryCache {
|
||||
[key: string]: IExtrudeGeomety;
|
||||
}
|
||||
export default class ExtrudeBuffer extends BaseBuffer {
|
||||
private indexOffset: number = 0;
|
||||
private verticesOffset: number = 0;
|
||||
private geometryCache: IGeometryCache;
|
||||
public buildFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
layerData.forEach((feature: IEncodeFeature) => {
|
||||
this.calculateFill(feature);
|
||||
});
|
||||
}
|
||||
|
||||
protected calculateFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
this.geometryCache = {};
|
||||
this.verticesOffset = 0;
|
||||
this.indexOffset = 0;
|
||||
layerData.forEach((feature: IEncodeFeature) => {
|
||||
const { shape } = feature;
|
||||
const { positions, index } = this.getGeometry(shape as ShapeType3D);
|
||||
this.verticesCount += positions.length / 3;
|
||||
this.indexCount += index.length;
|
||||
});
|
||||
}
|
||||
protected initAttributes() {
|
||||
super.initAttributes();
|
||||
this.attributes.miters = new Float32Array(this.verticesCount * 3);
|
||||
this.attributes.normals = new Float32Array(this.verticesCount * 3);
|
||||
this.attributes.sizes = new Float32Array(this.verticesCount * 3);
|
||||
}
|
||||
private calculateFill(feature: IEncodeFeature) {
|
||||
const { coordinates, shape } = feature;
|
||||
const instanceGeometry = this.getGeometry(shape as ShapeType3D);
|
||||
const numPoint = instanceGeometry.positions.length / 3;
|
||||
feature.bufferInfo = {
|
||||
verticesOffset: this.verticesOffset,
|
||||
indexOffset: this.indexOffset,
|
||||
dimensions: 3,
|
||||
};
|
||||
this.encodeArray(feature, numPoint);
|
||||
this.attributes.miters.set(
|
||||
instanceGeometry.positions,
|
||||
this.verticesOffset * 3,
|
||||
);
|
||||
const indexArray = instanceGeometry.index.map((v) => {
|
||||
return v + this.verticesOffset;
|
||||
});
|
||||
this.indexArray.set(indexArray, this.indexOffset);
|
||||
const position: number[] = [];
|
||||
for (let i = 0; i < numPoint; i++) {
|
||||
const coor = coordinates as Position;
|
||||
position.push(coor[0], coor[1], coor[2] || 0);
|
||||
}
|
||||
this.attributes.positions.set(position, this.verticesOffset * 3);
|
||||
this.verticesOffset += numPoint;
|
||||
this.indexOffset += indexArray.length;
|
||||
}
|
||||
|
||||
private getGeometry(shape: ShapeType3D): IExtrudeGeomety {
|
||||
if (this.geometryCache && this.geometryCache[shape]) {
|
||||
return this.geometryCache[shape];
|
||||
}
|
||||
const path = geometryShape[shape]
|
||||
? geometryShape[shape]()
|
||||
: geometryShape.cylinder();
|
||||
const geometry = extrudePolygon([path]);
|
||||
this.geometryCache[shape] = geometry;
|
||||
return geometry;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer';
|
||||
export default class ImageBuffer extends BaseBuffer {
|
||||
protected calculateFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
this.verticesCount = layerData.length;
|
||||
this.indexCount = layerData.length;
|
||||
}
|
||||
protected buildFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
this.attributes.uv = new Float32Array(this.verticesCount * 2);
|
||||
layerData.forEach((item: IEncodeFeature, index: number) => {
|
||||
const { color = [0, 0, 0, 0], size, id, shape, coordinates } = item;
|
||||
const { x, y } = this.iconMap[shape as string] || { x: 0, y: 0 };
|
||||
const coor = coordinates as Position;
|
||||
this.attributes.positions.set(coor, index * 3);
|
||||
this.attributes.colors.set(color, index * 4);
|
||||
this.attributes.pickingIds.set([id as number], index);
|
||||
this.attributes.sizes.set([size as number], index); //
|
||||
this.attributes.uv.set([x, y], index * 2);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
gl,
|
||||
ILayer,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
lazyInject,
|
||||
|
@ -50,15 +51,15 @@ export default class PointLayer extends BaseLayer {
|
|||
|
||||
private pointFeatures: IPointFeature[] = [];
|
||||
|
||||
public style(options: Partial<IPointLayerStyleOptions>) {
|
||||
// this.layerStyleService.update(options);
|
||||
// this.styleOptions = {
|
||||
// ...this.styleOptions,
|
||||
// ...options,
|
||||
// };
|
||||
}
|
||||
// public style(options: Partial<IPointLayerStyleOptions>) {
|
||||
// // this.layerStyleService.update(options);
|
||||
// // this.styleOptions = {
|
||||
// // ...this.styleOptions,
|
||||
// // ...options,
|
||||
// // };
|
||||
// }
|
||||
|
||||
public render() {
|
||||
public render(): ILayer {
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
import {
|
||||
gl,
|
||||
IIconService,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
lazyInject,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import ExtrudeBuffer from './buffers/ExtrudeBuffer';
|
||||
import ImageBuffer from './buffers/ImageBuffer';
|
||||
import extrude_frag from './shaders/extrude_frag.glsl';
|
||||
import extrude_vert from './shaders/extrude_vert.glsl';
|
||||
import image_frag from './shaders/image_frag.glsl';
|
||||
import image_vert from './shaders/image_vert.glsl';
|
||||
|
||||
export default class PointLayer extends BaseLayer {
|
||||
public name: string = 'PointLayer';
|
||||
|
||||
@lazyInject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
private readonly renderer: IRendererService;
|
||||
|
||||
|
||||
protected renderModels() {
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected buildModels(): void {
|
||||
this.shaderModule.registerModule('point', {
|
||||
vs: extrude_vert,
|
||||
fs: extrude_frag,
|
||||
});
|
||||
this.shaderModule.registerModule('pointImage', {
|
||||
vs: image_vert,
|
||||
fs: image_frag,
|
||||
});
|
||||
|
||||
this.models = [];
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('pointImage');
|
||||
// const buffer = new ExtrudeBuffer({
|
||||
// data: this.getEncodedData(),
|
||||
// });
|
||||
// buffer.computeVertexNormals('miters', false);
|
||||
const {
|
||||
createAttribute,
|
||||
createBuffer,
|
||||
createElements,
|
||||
createTexture2D,
|
||||
createModel,
|
||||
} = this.renderer;
|
||||
const buffer = new ImageBuffer({
|
||||
data: this.getEncodedData(),
|
||||
iconMap: this.iconService.getIconMap(),
|
||||
});
|
||||
this.models.push(
|
||||
createModel({
|
||||
attributes: {
|
||||
a_Position: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.positions,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_normal: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.normals,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_color: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.colors,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 4,
|
||||
}),
|
||||
a_size: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.sizes,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 1,
|
||||
}),
|
||||
a_uv: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.uv,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 2,
|
||||
}),
|
||||
// a_shape: createAttribute({
|
||||
// buffer: createBuffer({
|
||||
// data: buffer.attributes.miters,
|
||||
// type: gl.FLOAT,
|
||||
// }),
|
||||
// size: 3,
|
||||
// }),
|
||||
},
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
u_opacity: this.styleOption.opacity as number,
|
||||
u_texture: createTexture2D({
|
||||
data: this.iconService.getCanvas(),
|
||||
width: 1024,
|
||||
height: this.iconService.canvasHeight,
|
||||
}),
|
||||
},
|
||||
fs,
|
||||
vs,
|
||||
primitive: gl.POINTS,
|
||||
count: buffer.verticesCount,
|
||||
// elements: createElements({
|
||||
// data: buffer.indexArray,
|
||||
// type: gl.UNSIGNED_INT,
|
||||
// }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
varying vec4 v_color;
|
||||
uniform float u_opacity: 1.0;
|
||||
void main() {
|
||||
gl_FragColor = v_color;
|
||||
gl_FragColor.a *= u_opacity;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
precision highp float;
|
||||
attribute vec3 a_Position;
|
||||
attribute vec4 a_color;
|
||||
attribute vec3 a_size;
|
||||
attribute vec3 a_shape;
|
||||
attribute vec3 a_normal;
|
||||
|
||||
uniform mat4 u_ModelMatrix;
|
||||
varying vec4 v_color;
|
||||
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
vec3 size = a_size * a_shape;
|
||||
v_color = vec4(a_normal,1.0);
|
||||
vec2 offset = project_pixel(size.xy);
|
||||
vec4 project_pos = project_position(vec4(a_Position.xy, 0, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, size.z, 1.0));
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
uniform sampler2D u_texture;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_uv;
|
||||
void main(){
|
||||
vec2 pos= v_uv + gl_PointCoord / vec2(1024.,128.)*64.;
|
||||
pos.y= 1.- pos.y;
|
||||
vec4 textureColor=texture2D(u_texture,pos);
|
||||
if(v_color == vec4(0.)){
|
||||
gl_FragColor= textureColor;
|
||||
}else {
|
||||
gl_FragColor= step(0.01, textureColor.x) * v_color;
|
||||
}
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
precision highp float;
|
||||
attribute vec3 a_Position;
|
||||
attribute vec4 a_color;
|
||||
attribute vec2 a_uv;
|
||||
attribute float a_size;
|
||||
attribute float a_shape;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_uv;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
v_uv = a_uv;
|
||||
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0));
|
||||
gl_PointSize = a_size;
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
type IPosition = [number, number, number];
|
||||
export type IPath = IPosition[];
|
||||
export enum ShapeType3D {
|
||||
CYLINDER = 'cylinder',
|
||||
SQUARECOLUMN = 'squareColumn',
|
||||
TRIANGLECOLUMN = 'triangleColumn',
|
||||
HEXAGONCOLUMN = 'hexagonColumn',
|
||||
PENTAGONCOLUMN = 'pentagonColumn',
|
||||
}
|
||||
export enum ShapeType2D {
|
||||
CIRCLE = 'circle',
|
||||
SQUARE = 'square',
|
||||
TRIANGLE = 'triangle',
|
||||
HEXAGON = 'hexagon',
|
||||
PENTAGON = 'pentagon',
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成规则多边形顶点个数
|
||||
* @param pointCount 顶点个数 3 => 三角形
|
||||
* @param start 顶点起始角度 调整图形的方向
|
||||
*/
|
||||
export function polygonPath(pointCount: number, start: number = 0): IPath {
|
||||
const step = (Math.PI * 2) / pointCount;
|
||||
const line = [];
|
||||
for (let i = 0; i < pointCount; i++) {
|
||||
line.push(step * i + (start * Math.PI) / 12);
|
||||
}
|
||||
const path: IPath = line.map((t) => {
|
||||
const x = Math.sin(t + Math.PI / 4);
|
||||
const y = Math.cos(t + Math.PI / 4);
|
||||
return [x, y, 0];
|
||||
});
|
||||
// path.push(path[0]);
|
||||
return path;
|
||||
}
|
||||
|
||||
export function circle(): IPath {
|
||||
return polygonPath(30);
|
||||
}
|
||||
export function square(): IPath {
|
||||
return polygonPath(4);
|
||||
}
|
||||
export function triangle(): IPath {
|
||||
return polygonPath(3);
|
||||
}
|
||||
export function hexagon(): IPath {
|
||||
return polygonPath(6);
|
||||
}
|
||||
export function pentagon(): IPath {
|
||||
return polygonPath(5);
|
||||
}
|
||||
|
||||
export const geometryShape = {
|
||||
[ShapeType2D.CIRCLE]: circle,
|
||||
[ShapeType2D.HEXAGON]: hexagon,
|
||||
[ShapeType2D.TRIANGLE]: triangle,
|
||||
[ShapeType2D.SQUARE]: square,
|
||||
[ShapeType2D.PENTAGON]: pentagon,
|
||||
[ShapeType3D.CYLINDER]: circle,
|
||||
[ShapeType3D.HEXAGONCOLUMN]: hexagon,
|
||||
[ShapeType3D.TRIANGLECOLUMN]: triangle,
|
||||
[ShapeType3D.SQUARECOLUMN]: square,
|
||||
[ShapeType3D.PENTAGONCOLUMN]: pentagon,
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
import earcut from 'earcut';
|
||||
import { IPath } from './Path';
|
||||
export interface IExtrudeGeomety {
|
||||
positions: number[];
|
||||
index: number[];
|
||||
}
|
||||
/**
|
||||
* 拉伸多边形顶点,返回拉伸后的顶点信息
|
||||
* @param paths 路径数据组
|
||||
* @param extrude 是否拉伸
|
||||
*/
|
||||
export default function extrudePolygon(path: IPath[]): IExtrudeGeomety {
|
||||
const p1 = path[0][0];
|
||||
const p2 = path[0][path[0].length - 1];
|
||||
if (p1[0] === p2[0] && p1[1] === p2[1]) {
|
||||
path[0] = path[0].slice(0, path[0].length - 1);
|
||||
}
|
||||
const n = path[0].length;
|
||||
const flattengeo = earcut.flatten(path);
|
||||
const positions = [];
|
||||
const indexArray = [];
|
||||
const normals = [];
|
||||
// 设置顶部z值
|
||||
for (let j = 0; j < flattengeo.vertices.length / 3; j++) {
|
||||
flattengeo.vertices[j * 3 + 2] = 1;
|
||||
normals.push(0, 0, 1);
|
||||
}
|
||||
positions.push(...flattengeo.vertices);
|
||||
const triangles = earcut(
|
||||
flattengeo.vertices,
|
||||
flattengeo.holes,
|
||||
flattengeo.dimensions,
|
||||
);
|
||||
indexArray.push(...triangles);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const prePoint = flattengeo.vertices.slice(i * 3, i * 3 + 3);
|
||||
let nextPoint = flattengeo.vertices.slice(i * 3 + 3, i * 3 + 6);
|
||||
if (nextPoint.length === 0) {
|
||||
nextPoint = flattengeo.vertices.slice(0, 3);
|
||||
}
|
||||
const indexOffset = positions.length / 3;
|
||||
positions.push(
|
||||
prePoint[0],
|
||||
prePoint[1],
|
||||
1,
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
1,
|
||||
prePoint[0],
|
||||
prePoint[1],
|
||||
0,
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
0,
|
||||
);
|
||||
indexArray.push(...[1, 2, 0, 3, 2, 1].map((v) => v + indexOffset));
|
||||
}
|
||||
return {
|
||||
positions,
|
||||
index: indexArray,
|
||||
};
|
||||
}
|
||||
export function fillPolygon(points: IPath[]) {
|
||||
const flattengeo = earcut.flatten(points);
|
||||
const triangles = earcut(
|
||||
flattengeo.vertices,
|
||||
flattengeo.holes,
|
||||
flattengeo.dimensions,
|
||||
);
|
||||
return {
|
||||
positions: flattengeo.vertices,
|
||||
index: triangles,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
import earcut from 'earcut';
|
||||
import BufferBase, {
|
||||
IBufferInfo,
|
||||
IEncodeFeature,
|
||||
Position,
|
||||
} 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.coordinates as Position[][];
|
||||
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;
|
||||
});
|
||||
}
|
||||
protected calculateWall(feature: IEncodeFeature) {
|
||||
const size = feature.size || 0;
|
||||
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||
const {
|
||||
vertices,
|
||||
indexOffset,
|
||||
verticesOffset,
|
||||
faceNum,
|
||||
dimensions,
|
||||
} = bufferInfo;
|
||||
this.encodeArray(feature, faceNum * 4);
|
||||
for (let i = 0; i < faceNum; i++) {
|
||||
const prePoint = vertices.slice(i * dimensions, (i + 1) * dimensions);
|
||||
const nextPoint = vertices.slice(
|
||||
(i + 1) * dimensions,
|
||||
(i + 2) * dimensions,
|
||||
);
|
||||
this.calculateExtrudeFace(
|
||||
prePoint,
|
||||
nextPoint,
|
||||
verticesOffset + i * 4,
|
||||
indexOffset + i * 6,
|
||||
size as number,
|
||||
);
|
||||
bufferInfo.verticesOffset += 4;
|
||||
bufferInfo.indexOffset += 6;
|
||||
feature.bufferInfo = bufferInfo;
|
||||
}
|
||||
}
|
||||
private calculateTop(feature: IEncodeFeature) {
|
||||
const size = feature.size || 1;
|
||||
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||
const {
|
||||
indexArray,
|
||||
vertices,
|
||||
indexOffset,
|
||||
verticesOffset,
|
||||
dimensions,
|
||||
} = 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);
|
||||
// }
|
||||
}
|
||||
bufferInfo.verticesOffset += pointCount;
|
||||
// 添加顶点索引
|
||||
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
|
||||
bufferInfo.indexOffset += indexArray.length;
|
||||
feature.bufferInfo = bufferInfo;
|
||||
}
|
||||
private calculateExtrudeFace(
|
||||
prePoint: number[],
|
||||
nextPoint: number[],
|
||||
positionOffset: number,
|
||||
indexOffset: number | undefined,
|
||||
size: number,
|
||||
) {
|
||||
this.attributes.positions.set(
|
||||
[
|
||||
prePoint[0],
|
||||
prePoint[1],
|
||||
size,
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
size,
|
||||
prePoint[0],
|
||||
prePoint[1],
|
||||
0,
|
||||
nextPoint[0],
|
||||
nextPoint[1],
|
||||
0,
|
||||
],
|
||||
positionOffset * 3,
|
||||
);
|
||||
const indexArray = [1, 2, 0, 3, 2, 1].map((v) => {
|
||||
return v + positionOffset;
|
||||
});
|
||||
// if (this.uv) {
|
||||
// this.attributes.uv.set(
|
||||
// [0.1, 0, 0, 0, 0.1, size / 2000, 0, size / 2000],
|
||||
// positionOffset * 2,
|
||||
// );
|
||||
// }
|
||||
this.indexArray.set(indexArray, indexOffset);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,9 @@
|
|||
import earcut from 'earcut';
|
||||
import BufferBase, { IBufferInfo, IEncodeFeature } from '../../core/BaseBuffer';
|
||||
import BufferBase, {
|
||||
IBufferInfo,
|
||||
IEncodeFeature,
|
||||
Position,
|
||||
} from '../../core/BaseBuffer';
|
||||
export default class FillBuffer extends BufferBase {
|
||||
protected buildFeatures() {
|
||||
const layerData = this.data as IEncodeFeature[];
|
||||
|
@ -14,7 +18,7 @@ export default class FillBuffer extends BufferBase {
|
|||
// 计算长
|
||||
layerData.forEach((feature: IEncodeFeature) => {
|
||||
const { coordinates } = feature;
|
||||
const flattengeo = earcut.flatten(coordinates);
|
||||
const flattengeo = earcut.flatten(coordinates as Position[][]);
|
||||
const { vertices, dimensions, holes } = flattengeo;
|
||||
const indexArray = earcut(vertices, holes, dimensions).map(
|
||||
(v) => this.verticesCount + v,
|
||||
|
@ -33,13 +37,14 @@ export default class FillBuffer extends BufferBase {
|
|||
}
|
||||
|
||||
private calculateFill(feature: IEncodeFeature) {
|
||||
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||
const {
|
||||
indexArray,
|
||||
vertices,
|
||||
indexOffset,
|
||||
verticesOffset,
|
||||
dimensions = 3,
|
||||
} = feature.bufferInfo;
|
||||
} = bufferInfo;
|
||||
const pointCount = vertices.length / dimensions;
|
||||
this.encodeArray(feature, pointCount);
|
||||
// 添加顶点
|
||||
|
@ -48,15 +53,16 @@ export default class FillBuffer extends BufferBase {
|
|||
[vertices[i * dimensions], vertices[i * dimensions + 1], 0],
|
||||
(verticesOffset + i) * 3,
|
||||
);
|
||||
if (this.uv) {
|
||||
// TODO 用过BBox计算纹理坐标
|
||||
this.attributes.uv.set(
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0],
|
||||
(verticesOffset + i) * 3,
|
||||
);
|
||||
}
|
||||
// if (this.uv) {
|
||||
// // TODO 用过BBox计算纹理坐标
|
||||
// this.attributes.uv.set(
|
||||
// [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0],
|
||||
// (verticesOffset + i) * 3,
|
||||
// );
|
||||
// }
|
||||
}
|
||||
feature.bufferInfo.verticesOffset += pointCount;
|
||||
bufferInfo.verticesOffset += pointCount;
|
||||
feature.bufferInfo = bufferInfo;
|
||||
// 添加顶点索引
|
||||
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
} from '@l7/core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import ExtrudeBuffer from './buffers/ExtrudeBuffer';
|
||||
import FillBuffer from './buffers/FillBuffer';
|
||||
import polygon_frag from './shaders/polygon_frag.glsl';
|
||||
import polygon_vert from './shaders/polygon_vert.glsl';
|
||||
|
@ -38,10 +39,13 @@ export default class PolygonLayer extends BaseLayer {
|
|||
|
||||
this.models = [];
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('polygon');
|
||||
// const buffer = new ExtrudeBuffer({
|
||||
// data: this.getEncodedData(),
|
||||
// });
|
||||
// buffer.computeVertexNormals();
|
||||
const buffer = new FillBuffer({
|
||||
data: this.getEncodedData(),
|
||||
});
|
||||
|
||||
const {
|
||||
createAttribute,
|
||||
createBuffer,
|
||||
|
@ -59,6 +63,13 @@ export default class PolygonLayer extends BaseLayer {
|
|||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_normal: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.normals,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_color: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.colors,
|
||||
|
@ -67,7 +78,10 @@ export default class PolygonLayer extends BaseLayer {
|
|||
size: 4,
|
||||
}),
|
||||
},
|
||||
uniforms,
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
u_opacity: this.styleOption.opacity as number,
|
||||
},
|
||||
fs,
|
||||
vs,
|
||||
count: buffer.indexArray.length,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
varying vec4 v_color;
|
||||
uniform float u_opacity: 1.0;
|
||||
void main() {
|
||||
gl_FragColor = v_color;
|
||||
gl_FragColor.a *= u_opacity;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
attribute vec4 a_color;
|
||||
attribute vec3 a_Position;
|
||||
|
||||
attribute vec3 a_normal;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
|
||||
varying vec4 v_color;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer';
|
||||
interface IImageFeature extends IEncodeFeature {
|
||||
images: any[];
|
||||
}
|
||||
export default class ImageBuffer extends BaseBuffer {
|
||||
protected calculateFeatures() {
|
||||
this.verticesCount = 6;
|
||||
this.indexCount = 6;
|
||||
}
|
||||
protected buildFeatures() {
|
||||
this.attributes.uv = new Float32Array(this.verticesCount * 2);
|
||||
const layerData = this.data as IImageFeature[];
|
||||
const coordinates = layerData[0].coordinates as Position[];
|
||||
const positions: number[] = [
|
||||
...coordinates[0],
|
||||
0,
|
||||
coordinates[1][0],
|
||||
coordinates[0][1],
|
||||
0,
|
||||
...coordinates[1],
|
||||
0,
|
||||
...coordinates[0],
|
||||
0,
|
||||
...coordinates[1],
|
||||
0,
|
||||
coordinates[0][0],
|
||||
coordinates[1][1],
|
||||
0,
|
||||
];
|
||||
this.attributes.positions.set(positions, 0);
|
||||
this.attributes.uv.set([0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0], 0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import {
|
||||
gl,
|
||||
IRendererService,
|
||||
IShaderModuleService,
|
||||
ITexture2D,
|
||||
lazyInject,
|
||||
TYPES,
|
||||
} from '@l7/core';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
import ImageBuffer from './buffers/ImageBuffer';
|
||||
import image_frag from './shaders/image_frag.glsl';
|
||||
import image_vert from './shaders/image_vert.glsl';
|
||||
export default class ImageLayer extends BaseLayer {
|
||||
public name: string = 'imageLayer';
|
||||
@lazyInject(TYPES.IShaderModuleService)
|
||||
private readonly shaderModule: IShaderModuleService;
|
||||
|
||||
@lazyInject(TYPES.IRendererService)
|
||||
private readonly renderer: IRendererService;
|
||||
|
||||
protected renderModels() {
|
||||
this.models.forEach((model) =>
|
||||
model.draw({
|
||||
uniforms: {
|
||||
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
},
|
||||
}),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
protected buildModels() {
|
||||
const {
|
||||
createAttribute,
|
||||
createBuffer,
|
||||
createElements,
|
||||
createTexture2D,
|
||||
createModel,
|
||||
} = this.renderer;
|
||||
this.shaderModule.registerModule('image', {
|
||||
vs: image_vert,
|
||||
fs: image_frag,
|
||||
});
|
||||
|
||||
this.models = [];
|
||||
const { vs, fs, uniforms } = this.shaderModule.getModule('image');
|
||||
const source = this.getSource();
|
||||
// const imageData = await source.data.images;
|
||||
const buffer = new ImageBuffer({
|
||||
data: this.getEncodedData(),
|
||||
});
|
||||
source.data.images.then((imageData: HTMLImageElement[]) => {
|
||||
const texture: ITexture2D = createTexture2D({
|
||||
data: imageData[0],
|
||||
width: imageData[0].width,
|
||||
height: imageData[0].height,
|
||||
});
|
||||
this.models.push(
|
||||
createModel({
|
||||
attributes: {
|
||||
a_Position: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.positions,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 3,
|
||||
}),
|
||||
a_uv: createAttribute({
|
||||
buffer: createBuffer({
|
||||
data: buffer.attributes.uv,
|
||||
type: gl.FLOAT,
|
||||
}),
|
||||
size: 2,
|
||||
}),
|
||||
},
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
u_texture: texture,
|
||||
u_opacity: 1.0,
|
||||
},
|
||||
fs,
|
||||
vs,
|
||||
count: buffer.verticesCount,
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
precision mediump float;
|
||||
uniform float u_opacity: 1.0;
|
||||
uniform sampler2D u_texture;
|
||||
varying vec2 v_texCoord;
|
||||
void main() {
|
||||
vec4 color = texture2D(u_texture,vec2(v_texCoord.x,v_texCoord.y));
|
||||
gl_FragColor = color;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
precision highp float;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
attribute vec3 a_Position;
|
||||
attribute vec2 a_uv;
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
v_texCoord = a_uv;
|
||||
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
import * as d3 from 'd3-color';
|
||||
export function rgb2arr(str: string) {
|
||||
const arr = [];
|
||||
if (str.length === 4) {
|
||||
str = `#${str[1]}${str[1]}${str[2]}${str[2]}${str[3]}${str[3]}`;
|
||||
const color = d3.color(str) as d3.RGBColor;
|
||||
const arr = [0, 0, 0, 0];
|
||||
if (color != null) {
|
||||
arr[0] = color.r / 255;
|
||||
arr[1] = color.g / 255;
|
||||
arr[2] = color.b / 255;
|
||||
arr[3] = color.opacity;
|
||||
}
|
||||
arr.push(parseInt(str.substr(1, 2), 16) / 255);
|
||||
arr.push(parseInt(str.substr(3, 2), 16) / 255);
|
||||
arr.push(parseInt(str.substr(5, 2), 16) / 255);
|
||||
arr.push(1.0);
|
||||
return arr;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
import { aProjectFlat, lngLatToMeters, Point } from '@l7/utils';
|
||||
import { vec2 } from 'gl-matrix';
|
||||
export function computeMiter(
|
||||
tangent: vec2,
|
||||
miter: vec2,
|
||||
lineA: vec2,
|
||||
lineB: vec2,
|
||||
halfThick: number,
|
||||
) {
|
||||
vec2.add(tangent, lineA, lineB);
|
||||
vec2.normalize(tangent, tangent);
|
||||
miter = vec2.fromValues(-tangent[1], tangent[0]);
|
||||
const tmp = vec2.fromValues(-lineA[1], lineA[0]);
|
||||
return halfThick / vec2.dot(miter, tmp);
|
||||
}
|
||||
export function computeNormal(out: vec2, dir: vec2) {
|
||||
return vec2.set(out, -dir[1], dir[0]);
|
||||
}
|
||||
export function direction(out: vec2, a: vec2, b: vec2) {
|
||||
const a1 = aProjectFlat([a[0], a[1]]);
|
||||
const b1 = aProjectFlat([b[0], b[1]]);
|
||||
vec2.sub(out, a1, b1);
|
||||
vec2.normalize(out, out);
|
||||
return out;
|
||||
}
|
||||
function extrusions(
|
||||
positions: number[],
|
||||
out: number[],
|
||||
miters: number[],
|
||||
point: vec2,
|
||||
normal: vec2,
|
||||
scale: number,
|
||||
) {
|
||||
addNext(out, miters, normal, -scale);
|
||||
addNext(out, miters, normal, scale);
|
||||
positions.push(point[0], point[1], 0);
|
||||
positions.push(point[0], point[1], 0);
|
||||
}
|
||||
|
||||
function addNext(
|
||||
out: number[],
|
||||
miters: number[],
|
||||
normal: vec2,
|
||||
length: number,
|
||||
) {
|
||||
out.push(normal[0], normal[1], 0);
|
||||
miters.push(length);
|
||||
}
|
||||
|
||||
function lineSegmentDistance(end: vec2, start: vec2) {
|
||||
const dx = start[0] - end[0];
|
||||
const dy = start[1] - end[1];
|
||||
// const dz = start[2] - end[2];
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
function isPointEqual(a: vec2, b: vec2) {
|
||||
return a[0] === b[0] && a[1] === b[1];
|
||||
}
|
||||
|
||||
export default function(
|
||||
points: number[][],
|
||||
closed: boolean,
|
||||
indexOffset: number,
|
||||
) {
|
||||
const lineA = vec2.fromValues(0, 0);
|
||||
const lineB = vec2.fromValues(0, 0);
|
||||
const tangent = vec2.fromValues(0, 0);
|
||||
const miter: vec2 = vec2.create();
|
||||
let started = false;
|
||||
let lineNormal = null;
|
||||
const tmp = vec2.create();
|
||||
let count = indexOffset || 0;
|
||||
const miterLimit = 3;
|
||||
|
||||
const out: number[] = [];
|
||||
const attrPos: number[] = [];
|
||||
const attrIndex: number[] = [];
|
||||
const miters: number[] = [];
|
||||
const attrDistance = [0, 0];
|
||||
if (closed) {
|
||||
points = points.slice();
|
||||
points.push(points[0]);
|
||||
}
|
||||
|
||||
const total = points.length;
|
||||
|
||||
for (let i = 1; i < total; i++) {
|
||||
const index = count;
|
||||
const last = vec2.fromValues(points[i - 1][0], points[i - 1][1]);
|
||||
const cur = vec2.fromValues(points[i][0], points[i][1]);
|
||||
let next =
|
||||
i < points.length - 1
|
||||
? vec2.fromValues(points[i + 1][0], points[i + 1][1])
|
||||
: null;
|
||||
// 如果当前点和前一点相同,跳过
|
||||
if (isPointEqual(last, cur)) {
|
||||
continue;
|
||||
}
|
||||
if (next) {
|
||||
let nextIndex = i + 1;
|
||||
// 找到不相同的下一点
|
||||
while (next && isPointEqual(cur, next)) {
|
||||
next =
|
||||
nextIndex < points.length - 1
|
||||
? vec2.fromValues(points[++nextIndex][0], points[nextIndex][1])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
const lineDistance = lineSegmentDistance(cur, last);
|
||||
const d = lineDistance + attrDistance[attrDistance.length - 1];
|
||||
direction(lineA, cur, last);
|
||||
if (!lineNormal) {
|
||||
lineNormal = vec2.create();
|
||||
computeNormal(lineNormal, lineA);
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
started = true;
|
||||
extrusions(attrPos, out, miters, last, lineNormal, 1);
|
||||
}
|
||||
|
||||
attrIndex.push(index + 0, index + 2, index + 1);
|
||||
|
||||
// no miter, simple segment
|
||||
if (!next) {
|
||||
// reset normal
|
||||
computeNormal(lineNormal, lineA);
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
attrDistance.push(d, d);
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
count += 2;
|
||||
} else {
|
||||
// get unit dir of next line
|
||||
direction(lineB, next, cur);
|
||||
|
||||
// stores tangent & miter
|
||||
let miterLen = computeMiter(
|
||||
tangent,
|
||||
vec2.fromValues(miter[0], miter[1]),
|
||||
lineA,
|
||||
lineB,
|
||||
1,
|
||||
);
|
||||
|
||||
// get orientation
|
||||
const flip = vec2.dot(tangent, lineNormal) < 0 ? -1 : 1;
|
||||
const bevel = Math.abs(miterLen) > miterLimit;
|
||||
|
||||
// 处理前后两条线段重合的情况,这种情况不需要使用任何接头(miter/bevel)。
|
||||
// 理论上这种情况下 miterLen = Infinity,本应通过 isFinite(miterLen) 判断,
|
||||
// 但是 AMap 投影变换后丢失精度,只能通过一个阈值(1000)判断。
|
||||
|
||||
if (Math.abs(miterLen) > 1000) {
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
attrIndex.push(index + 2, index + 4, index + 3);
|
||||
computeNormal(tmp, lineB);
|
||||
vec2.copy(lineNormal, tmp); // store normal for next round
|
||||
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
attrDistance.push(d, d, d, d);
|
||||
|
||||
// the miter is now the normal for our next join
|
||||
count += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bevel) {
|
||||
miterLen = miterLimit;
|
||||
|
||||
// next two points in our first segment
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
|
||||
// now add the bevel triangle
|
||||
attrIndex.push(
|
||||
...(flip === 1
|
||||
? [index + 2, index + 4, index + 5]
|
||||
: [index + 4, index + 5, index + 3]),
|
||||
);
|
||||
|
||||
computeNormal(tmp, lineB);
|
||||
vec2.copy(lineNormal, tmp); // store normal for next round
|
||||
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
attrDistance.push(d, d, d, d);
|
||||
|
||||
// the miter is now the normal for our next join
|
||||
count += 4;
|
||||
} else {
|
||||
// next two points in our first segment
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
attrIndex.push(index + 1, index + 2, index + 3);
|
||||
|
||||
// now add the miter triangles
|
||||
addNext(out, miters, lineNormal, miterLen * -flip);
|
||||
attrPos.push(cur[0], cur[1], 0);
|
||||
attrIndex.push(index + 2, index + 4, index + 3);
|
||||
attrIndex.push(index + 4, index + 5, index + 6);
|
||||
computeNormal(tmp, lineB);
|
||||
vec2.copy(lineNormal, tmp); // store normal for next round
|
||||
|
||||
extrusions(attrPos, out, miters, cur, lineNormal, 1);
|
||||
attrDistance.push(d, d, d, d, d);
|
||||
|
||||
// the miter is now the normal for our next join
|
||||
count += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
normals: out,
|
||||
attrIndex,
|
||||
attrPos,
|
||||
attrDistance,
|
||||
miters,
|
||||
};
|
||||
}
|
|
@ -44,8 +44,8 @@ export default class ReglModel implements IModel {
|
|||
blend,
|
||||
stencil,
|
||||
cull,
|
||||
instances,
|
||||
} = options;
|
||||
|
||||
const reglUniforms: { [key: string]: IUniform } = {};
|
||||
if (uniforms) {
|
||||
this.uniforms = uniforms;
|
||||
|
@ -60,7 +60,6 @@ export default class ReglModel implements IModel {
|
|||
Object.keys(attributes).forEach((name: string) => {
|
||||
reglAttributes[name] = (attributes[name] as ReglAttribute).get();
|
||||
});
|
||||
|
||||
const drawParams: regl.DrawConfig = {
|
||||
attributes: reglAttributes,
|
||||
frag: fs,
|
||||
|
@ -70,6 +69,9 @@ export default class ReglModel implements IModel {
|
|||
primitiveMap[primitive === undefined ? gl.TRIANGLES : primitive],
|
||||
count,
|
||||
};
|
||||
if (instances) {
|
||||
drawParams.instances = instances;
|
||||
}
|
||||
|
||||
if (elements) {
|
||||
drawParams.elements = (elements as ReglElements).get();
|
||||
|
|
|
@ -56,6 +56,7 @@ export default class ReglRendererService implements IRendererService {
|
|||
'EXT_SRGB', // baseColor emmisive
|
||||
'OES_texture_float', // shadow map
|
||||
'WEBGL_depth_texture',
|
||||
'angle_instanced_arrays',
|
||||
'EXT_texture_filter_anisotropic', // VSM shadow map
|
||||
],
|
||||
optionalExtensions: ['oes_texture_float_linear'],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
container,
|
||||
IImage,
|
||||
ILayer,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
|
@ -73,10 +74,14 @@ class Scene {
|
|||
public render(): void {
|
||||
this.sceneService.render();
|
||||
}
|
||||
|
||||
public addImage(id: string, img: IImage) {
|
||||
this.sceneService.addImage(id, img);
|
||||
}
|
||||
public destroy() {
|
||||
this.sceneService.destroy();
|
||||
}
|
||||
|
||||
// 资源管理
|
||||
}
|
||||
|
||||
export { Scene };
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@l7/utils": "0.0.1",
|
||||
"@l7/core": "0.0.1",
|
||||
"@mapbox/geojson-rewind": "^0.4.0",
|
||||
"@turf/helpers": "^6.1.4",
|
||||
"@turf/invariant": "^6.1.2",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { IParserCfg, ITransform } from '@l7/core';
|
||||
import { IParserData } from './interface';
|
||||
type ParserFunction = (data: any, cfg?: any) => IParserData;
|
||||
type transformFunction = (data: IParserData, cfg?: object) => IParserData;
|
||||
type transformFunction = (data: IParserData, cfg?: any) => IParserData;
|
||||
const TRANSFORMS: {
|
||||
[type: string]: transformFunction;
|
||||
} = {};
|
||||
|
|
|
@ -4,11 +4,15 @@ import geojson from './parser/geojson';
|
|||
import image from './parser/image';
|
||||
import json from './parser/json';
|
||||
import Source from './source';
|
||||
import { cluster } from './transform/cluster';
|
||||
import { aggregatorToGrid } from './transform/grid';
|
||||
export default Source;
|
||||
registerParser('geojson', geojson);
|
||||
registerParser('image', image);
|
||||
registerParser('csv', csv);
|
||||
registerParser('json', json);
|
||||
registerTransform('cluster', cluster);
|
||||
registerTransform('grid', aggregatorToGrid);
|
||||
export {
|
||||
getTransform,
|
||||
registerTransform,
|
||||
|
|
|
@ -1,24 +1,4 @@
|
|||
export type DataType = string | object[] | object;
|
||||
export interface IParserCfg {
|
||||
type: string;
|
||||
x?: string;
|
||||
y?: string;
|
||||
x1?: string;
|
||||
y1?: string;
|
||||
coordinates?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
type CallBack = (...args: any[]) => any;
|
||||
export interface ITransform {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
callback: CallBack;
|
||||
}
|
||||
|
||||
export interface ISourceCFG {
|
||||
parser?: IParserCfg;
|
||||
transforms?: ITransform[];
|
||||
}
|
||||
export interface IDictionary<TValue> {
|
||||
[key: string]: TValue;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { csvParse } from 'd3-dsv';
|
||||
import { IJsonData, IParserCfg, IParserData } from '../interface';
|
||||
import { IJsonData, IParserCfg, IParserData } from '@l7/core';
|
||||
import json from './json';
|
||||
export default function csv(data: string, cfg: IParserCfg): IParserData {
|
||||
const csvData: IJsonData = csvParse(data);
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
import { getImage } from '@l7/utils';
|
||||
import { IParserData } from '../interface';
|
||||
|
||||
interface IImageCfg {
|
||||
extent: [number, number, number, number];
|
||||
}
|
||||
export default function image(data: string | [], cfg: IImageCfg): IParserData {
|
||||
export default function image(
|
||||
data: string | string[],
|
||||
cfg: IImageCfg,
|
||||
): IParserData {
|
||||
const { extent } = cfg;
|
||||
|
||||
const images = new Promise((resolve) => {
|
||||
loadData(data, (res: any) => {
|
||||
resolve(res);
|
||||
});
|
||||
});
|
||||
const resultData: IParserData = {
|
||||
images: loadData(data),
|
||||
images,
|
||||
_id: 1,
|
||||
dataArray: [
|
||||
{
|
||||
|
@ -18,16 +25,26 @@ export default function image(data: string | [], cfg: IImageCfg): IParserData {
|
|||
};
|
||||
return resultData;
|
||||
}
|
||||
function loadData(data: string | string[]): Promise<Response | Response[]> {
|
||||
function loadData(data: string | string[], done: any) {
|
||||
const url = data;
|
||||
const imageDatas: HTMLImageElement[] = [];
|
||||
if (typeof url === 'string') {
|
||||
const imageRequest = new Request(url);
|
||||
return fetch(imageRequest);
|
||||
} else {
|
||||
const fetchs = url.map((item: string) => {
|
||||
const imageRequest = new Request(item);
|
||||
return fetch(imageRequest);
|
||||
getImage({ url }, (err: string, img: HTMLImageElement) => {
|
||||
imageDatas.push(img);
|
||||
done(imageDatas);
|
||||
});
|
||||
} else {
|
||||
const imageCount = url.length;
|
||||
let imageindex = 0;
|
||||
url.forEach((item) => {
|
||||
getImage({ url: item }, (err: any, img: HTMLImageElement) => {
|
||||
imageindex++;
|
||||
imageDatas.push(img);
|
||||
if (imageindex === imageCount) {
|
||||
done(imageDatas);
|
||||
}
|
||||
});
|
||||
});
|
||||
return Promise.all(fetchs);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
IParseDataItem,
|
||||
IParserCfg,
|
||||
IParserData,
|
||||
} from '../interface';
|
||||
} from '@l7/core';
|
||||
export default function json(data: IJsonData, cfg: IParserCfg): IParserData {
|
||||
const { x, y, x1, y1, coordinates } = cfg;
|
||||
const resultData: IParseDataItem[] = [];
|
||||
|
|
|
@ -1,41 +1,67 @@
|
|||
import { IParserCfg, IParserData, ISourceCFG, ITransform } from '@l7/core';
|
||||
import { extent } from '@l7/utils';
|
||||
import { BBox, FeatureCollection, Geometries, Properties } from '@turf/helpers';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getParser } from './';
|
||||
import { IDictionary, IParserData, ISourceCFG } from './interface';
|
||||
import { SyncHook } from 'tapable';
|
||||
import { getParser, getTransform } from './';
|
||||
export default class Source extends EventEmitter {
|
||||
public data: IParserData;
|
||||
|
||||
// 数据范围
|
||||
public extent: BBox;
|
||||
private attrs: IDictionary<any> = {};
|
||||
// 生命周期钩子
|
||||
public hooks = {
|
||||
init: new SyncHook(['source']),
|
||||
layout: new SyncHook(['source']),
|
||||
update: new SyncHook(['source']),
|
||||
};
|
||||
public parser: IParserCfg = { type: 'geojson' };
|
||||
public transforms: ITransform[] = [];
|
||||
|
||||
// 原始数据
|
||||
private originData: any;
|
||||
constructor(data: any, cfg?: ISourceCFG) {
|
||||
super();
|
||||
this.set('data', data);
|
||||
Object.assign(this.attrs, cfg);
|
||||
this.originData = cloneDeep(this.get('data'));
|
||||
this.data = cloneDeep(data);
|
||||
this.originData = data;
|
||||
if (cfg) {
|
||||
if (cfg.parser) {
|
||||
this.parser = cfg.parser;
|
||||
}
|
||||
if (cfg.transforms) {
|
||||
this.transforms = cfg.transforms;
|
||||
}
|
||||
}
|
||||
this.hooks.init.tap('parser', () => {
|
||||
this.excuteParser();
|
||||
});
|
||||
this.hooks.init.tap('transform', () => {
|
||||
this.executeTrans();
|
||||
});
|
||||
this.init();
|
||||
}
|
||||
|
||||
public get(name: string): any {
|
||||
return this.attrs[name];
|
||||
}
|
||||
public set(name: string, value: any) {
|
||||
this.attrs[name] = value;
|
||||
}
|
||||
private excuteParser(): void {
|
||||
const parser = this.get('parser') || {};
|
||||
const parser = this.parser;
|
||||
const type: string = parser.type || 'geojson';
|
||||
const sourceParser = getParser(type);
|
||||
this.data = sourceParser(this.originData, parser);
|
||||
// 计算范围
|
||||
this.extent = extent(this.data.dataArray);
|
||||
}
|
||||
/**
|
||||
* 数据统计
|
||||
*/
|
||||
private executeTrans() {
|
||||
const trans = this.transforms;
|
||||
trans.forEach((tran: ITransform) => {
|
||||
const { type } = tran;
|
||||
const data = getTransform(type)(this.data, tran);
|
||||
Object.assign(this.data, data);
|
||||
});
|
||||
}
|
||||
private init() {
|
||||
this.excuteParser(); // 数据解析
|
||||
this.hooks.init.call(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import { IParserCfg, IParserData, ISourceCFG, ITransform } from '@l7/core';
|
||||
import Supercluster from 'supercluster';
|
||||
export function cluster(data: IParserData, option: ITransform): IParserData {
|
||||
const { radius = 80, maxZoom = 18, minZoom = 0, field, zoom = 2 } = option;
|
||||
if (data.pointIndex) {
|
||||
const clusterData = data.pointIndex.getClusters(data.extent, zoom);
|
||||
data.dataArray = formatData(clusterData);
|
||||
return data;
|
||||
}
|
||||
const pointIndex = new Supercluster({
|
||||
radius,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
map: (props) => ({ sum: props[field] }), // 根据指定字段求和
|
||||
reduce: (accumulated, props) => {
|
||||
accumulated.sum += props.sum;
|
||||
},
|
||||
});
|
||||
const geojson: {
|
||||
type: string;
|
||||
features: any[];
|
||||
} = {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
};
|
||||
geojson.features = data.dataArray.map((item) => {
|
||||
return {
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
[field]: item[field],
|
||||
},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: item.coordinates,
|
||||
},
|
||||
};
|
||||
});
|
||||
pointIndex.load(geojson.features);
|
||||
const clusterPoint = pointIndex.getClusters(data.extent, zoom);
|
||||
const resultData = clusterPoint.map((point, index) => {
|
||||
return {
|
||||
coordinates: point.geometry.coordinates,
|
||||
_id: index + 1,
|
||||
...point.properties,
|
||||
};
|
||||
});
|
||||
data.dataArray = resultData;
|
||||
data.pointIndex = pointIndex;
|
||||
return data;
|
||||
}
|
||||
export function formatData(clusterPoint: any[]) {
|
||||
return clusterPoint.map((point, index) => {
|
||||
return {
|
||||
coordinates: point.geometry.coordinates,
|
||||
_id: index + 1,
|
||||
...point.properties,
|
||||
};
|
||||
});
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* 生成四边形热力图
|
||||
*/
|
||||
import { IParserCfg, IParserData, ISourceCFG, ITransform } from '@l7/core';
|
||||
import { max, mean, min, sum } from './statistics';
|
||||
const statMap: { [key: string]: any } = {
|
||||
min,
|
||||
max,
|
||||
mean,
|
||||
sum,
|
||||
};
|
||||
interface IGridHash {
|
||||
[key: string]: any;
|
||||
}
|
||||
interface IGridOffset {
|
||||
yOffset: number;
|
||||
xOffset: number;
|
||||
}
|
||||
const R_EARTH = 6378000;
|
||||
|
||||
export function aggregatorToGrid(data: IParserData, option: ITransform) {
|
||||
const dataArray = data.dataArray;
|
||||
const { size = 10 } = option;
|
||||
const { gridHash, gridOffset } = _pointsGridHash(dataArray, size);
|
||||
const layerData = _getGridLayerDataFromGridHash(gridHash, gridOffset, option);
|
||||
return {
|
||||
yOffset: gridOffset.yOffset / 1.8,
|
||||
xOffset: gridOffset.xOffset / 1.8,
|
||||
radius: gridOffset.xOffset,
|
||||
dataArray: layerData,
|
||||
};
|
||||
}
|
||||
|
||||
function _pointsGridHash(dataArray: any[], size: number) {
|
||||
let latMin = Infinity;
|
||||
let latMax = -Infinity;
|
||||
let pLat;
|
||||
for (const point of dataArray) {
|
||||
pLat = point.coordinates[1];
|
||||
if (Number.isFinite(pLat)) {
|
||||
latMin = pLat < latMin ? pLat : latMin;
|
||||
latMax = pLat > latMax ? pLat : latMax;
|
||||
}
|
||||
}
|
||||
const centerLat = (latMin + latMax) / 2;
|
||||
// const centerLat = 34.54083;
|
||||
const gridOffset = _calculateGridLatLonOffset(size, centerLat);
|
||||
if (gridOffset.xOffset <= 0 || gridOffset.yOffset <= 0) {
|
||||
return { gridHash: {}, gridOffset };
|
||||
}
|
||||
const gridHash: IGridHash = {};
|
||||
for (const point of dataArray) {
|
||||
const lat = point.coordinates[1];
|
||||
const lng = point.coordinates[0];
|
||||
|
||||
if (Number.isFinite(lat) && Number.isFinite(lng)) {
|
||||
const latIdx = Math.floor((lat + 90) / gridOffset.yOffset);
|
||||
const lonIdx = Math.floor((lng + 180) / gridOffset.xOffset);
|
||||
const key = `${latIdx}-${lonIdx}`;
|
||||
|
||||
gridHash[key] = gridHash[key] || { count: 0, points: [] };
|
||||
gridHash[key].count += 1;
|
||||
gridHash[key].points.push(point);
|
||||
}
|
||||
}
|
||||
|
||||
return { gridHash, gridOffset };
|
||||
}
|
||||
// 计算网格偏移量
|
||||
function _calculateGridLatLonOffset(cellSize: number, latitude: number) {
|
||||
const yOffset = _calculateLatOffset(cellSize);
|
||||
const xOffset = _calculateLonOffset(latitude, cellSize);
|
||||
return { yOffset, xOffset };
|
||||
}
|
||||
|
||||
function _calculateLatOffset(dy: number) {
|
||||
return (dy / R_EARTH) * (180 / Math.PI);
|
||||
}
|
||||
|
||||
function _calculateLonOffset(lat: number, dx: number) {
|
||||
return ((dx / R_EARTH) * (180 / Math.PI)) / Math.cos((lat * Math.PI) / 180);
|
||||
}
|
||||
function _getGridLayerDataFromGridHash(
|
||||
gridHash: IGridHash,
|
||||
gridOffset: IGridOffset,
|
||||
option: ITransform,
|
||||
) {
|
||||
return Object.keys(gridHash).reduce((accu, key, i) => {
|
||||
const idxs = key.split('-');
|
||||
const latIdx = parseInt(idxs[0], 10);
|
||||
const lonIdx = parseInt(idxs[1], 10);
|
||||
const item: {
|
||||
[key: string]: any;
|
||||
} = {};
|
||||
if (option.field && option.method) {
|
||||
const columns = getColumn(gridHash[key].points, option.field);
|
||||
item[option.method] = statMap[option.method](columns);
|
||||
}
|
||||
Object.assign(item, {
|
||||
_id: i + 1,
|
||||
coordinates: [
|
||||
-180 + gridOffset.xOffset * lonIdx,
|
||||
-90 + gridOffset.yOffset * latIdx,
|
||||
],
|
||||
count: gridHash[key].count,
|
||||
});
|
||||
// @ts-ignore
|
||||
accu.push(item);
|
||||
return accu;
|
||||
}, []);
|
||||
}
|
||||
function getColumn(data: any[], columnName: string) {
|
||||
return data.map((item) => {
|
||||
return item[columnName];
|
||||
});
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
function max(x: number[]) {
|
||||
if (x.length === 0) {
|
||||
throw new Error('max requires at least one data point');
|
||||
}
|
||||
|
||||
let value = x[0];
|
||||
for (let i = 1; i < x.length; i++) {
|
||||
// On the first iteration of this loop, max is
|
||||
// undefined and is thus made the maximum element in the array
|
||||
if (x[i] > value) {
|
||||
value = x[i];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function min(x: number[]) {
|
||||
if (x.length === 0) {
|
||||
throw new Error('min requires at least one data point');
|
||||
}
|
||||
|
||||
let value = x[0];
|
||||
for (let i = 1; i < x.length; i++) {
|
||||
// On the first iteration of this loop, min is
|
||||
// undefined and is thus made the minimum element in the array
|
||||
if (x[i] < value) {
|
||||
value = x[i];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sum(x: number[]) {
|
||||
// If the array is empty, we needn't bother computing its sum
|
||||
if (x.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initializing the sum as the first number in the array
|
||||
let sumNum = x[0];
|
||||
|
||||
// Keeping track of the floating-point error correction
|
||||
let correction = 0;
|
||||
|
||||
let transition;
|
||||
|
||||
for (let i = 1; i < x.length; i++) {
|
||||
transition = sumNum + x[i];
|
||||
|
||||
// Here we need to update the correction in a different fashion
|
||||
// if the new absolute value is greater than the absolute sum
|
||||
if (Math.abs(sumNum) >= Math.abs(x[i])) {
|
||||
correction += sumNum - transition + x[i];
|
||||
} else {
|
||||
correction += x[i] - transition + sumNum;
|
||||
}
|
||||
|
||||
sumNum = transition;
|
||||
}
|
||||
|
||||
// Returning the corrected sum
|
||||
return sumNum + correction;
|
||||
}
|
||||
function mean(x: number[]) {
|
||||
if (x.length === 0) {
|
||||
throw new Error('mean requires at least one data point');
|
||||
}
|
||||
return sum(x) / x.length;
|
||||
}
|
||||
|
||||
export { sum, max, min, mean };
|
|
@ -0,0 +1,125 @@
|
|||
class AJAXError extends Error {
|
||||
private status: number;
|
||||
private url: string;
|
||||
|
||||
constructor(message: string, status: number, url: string) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
this.url = url;
|
||||
|
||||
// work around for https://github.com/Rich-Harris/buble/issues/40
|
||||
this.name = this.constructor.name;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `${this.name}: ${this.message} (${this.status}): ${this.url}`;
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(requestParameters: any) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('GET', requestParameters.url, true);
|
||||
for (const k in requestParameters.headers) {
|
||||
if (requestParameters.headers.hasOwnProperty(k)) {
|
||||
xhr.setRequestHeader(k, requestParameters.headers[k]);
|
||||
}
|
||||
}
|
||||
xhr.withCredentials = requestParameters.credentials === 'include';
|
||||
return xhr;
|
||||
}
|
||||
|
||||
export const getJSON = (requestParameters: any, callback: any) => {
|
||||
const xhr = makeRequest(requestParameters);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.onerror = () => {
|
||||
callback(new Error(xhr.statusText));
|
||||
};
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300 && xhr.response) {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(xhr.response);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, data);
|
||||
} else {
|
||||
if (xhr.status === 401) {
|
||||
callback(
|
||||
new AJAXError(`${xhr.statusText}`, xhr.status, requestParameters.url),
|
||||
);
|
||||
} else {
|
||||
callback(
|
||||
new AJAXError(xhr.statusText, xhr.status, requestParameters.url),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
return xhr;
|
||||
};
|
||||
|
||||
export const getArrayBuffer = (requestParameters: any, callback: any) => {
|
||||
const xhr = makeRequest(requestParameters);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.onerror = () => {
|
||||
callback(new Error(xhr.statusText));
|
||||
};
|
||||
xhr.onload = () => {
|
||||
const response = xhr.response;
|
||||
if (response.byteLength === 0 && xhr.status === 200) {
|
||||
return callback(new Error('http status 200 returned without content.'));
|
||||
}
|
||||
if (xhr.status >= 200 && xhr.status < 300 && xhr.response) {
|
||||
callback(null, {
|
||||
data: response,
|
||||
cacheControl: xhr.getResponseHeader('Cache-Control'),
|
||||
expires: xhr.getResponseHeader('Expires'),
|
||||
});
|
||||
} else {
|
||||
callback(
|
||||
new AJAXError(xhr.statusText, xhr.status, requestParameters.url),
|
||||
);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
return xhr;
|
||||
};
|
||||
|
||||
function sameOrigin(url: string) {
|
||||
const a = window.document.createElement('a');
|
||||
a.href = url;
|
||||
return (
|
||||
a.protocol === window.document.location.protocol &&
|
||||
a.host === window.document.location.host
|
||||
);
|
||||
}
|
||||
|
||||
const transparentPngUrl =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=';
|
||||
|
||||
export const getImage = (requestParameters: any, callback: any) => {
|
||||
// request the image with XHR to work around caching issues
|
||||
// see https://github.com/mapbox/mapbox-gl-js/issues/1470
|
||||
return getArrayBuffer(requestParameters, (err: string, imgData: any) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else if (imgData) {
|
||||
const img = new window.Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
const URL = window.URL || window.webkitURL;
|
||||
img.onload = () => {
|
||||
callback(null, img);
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
const blob = new window.Blob([new Uint8Array(imgData.data)], {
|
||||
type: 'image/png',
|
||||
});
|
||||
img.src = imgData.data.byteLength
|
||||
? URL.createObjectURL(blob)
|
||||
: transparentPngUrl;
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import { BBox } from '@turf/helpers';
|
||||
|
||||
const originShift = (2 * Math.PI * 6378137) / 2.0;
|
||||
export type Point = [number, number] | [number, number, number];
|
||||
/**
|
||||
* 计算地理数据范围
|
||||
* @param {dataArray} data 地理坐标数据
|
||||
|
@ -46,3 +47,110 @@ function transform(item: any[], cb: (item: any[]) => any): any {
|
|||
}
|
||||
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];
|
||||
}
|
||||
export function aProjectFlat(lnglat: number[]) {
|
||||
const maxs = 85.0511287798;
|
||||
const lat = Math.max(Math.min(maxs, lnglat[1]), -maxs);
|
||||
const scale = 256 << 20;
|
||||
let d = Math.PI / 180;
|
||||
let x = lnglat[0] * d;
|
||||
let y = lat * d;
|
||||
y = Math.log(Math.tan(Math.PI / 4 + y / 2));
|
||||
|
||||
const a = 0.5 / Math.PI;
|
||||
const b = 0.5;
|
||||
const c = -0.5 / Math.PI;
|
||||
d = 0.5;
|
||||
x = scale * (a * x + b) - 215440491;
|
||||
y = scale * (c * y + d) - 106744817;
|
||||
return [parseInt(x.toString(), 10), parseInt(y.toString(), 10)];
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { djb2hash, BKDRHash } from './hash';
|
||||
export { extent, tranfrormCoord } from './geo';
|
||||
export * from './fetchData';
|
||||
export * from './geo';
|
||||
|
|
|
@ -3,6 +3,11 @@ import * as React from 'react';
|
|||
import AMap from './components/AMap';
|
||||
import Mapbox from './components/Mapbox';
|
||||
import Polygon from './components/Polygon';
|
||||
import Point3D from './components/Point3D';
|
||||
import Line from './components/Line';
|
||||
import ImageLayer from './components/Image';
|
||||
import GridHeatMap from './components/GridHeatmap';
|
||||
import PointImage from './components/pointImage';
|
||||
// @ts-ignore
|
||||
import notes from './Map.md';
|
||||
|
||||
|
@ -13,4 +18,9 @@ storiesOf('地图底图测试', module)
|
|||
.add('Mapbox', () => <Mapbox />, {
|
||||
notes: { markdown: notes },
|
||||
})
|
||||
.add('Polygon', () => <Polygon />);
|
||||
.add('Polygon', () => <Polygon />)
|
||||
.add('Point3D', () => <Point3D />)
|
||||
.add('Line', () => <Line />)
|
||||
.add('GridHeatMap', () => <GridHeatMap />)
|
||||
.add('Image', () => <ImageLayer />)
|
||||
.add('pointImage', () => <PointImage />);
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
import { HeatMapLayer } from '@l7/layers';
|
||||
import { Scene } from '@l7/scene';
|
||||
import * as React from 'react';
|
||||
|
||||
export default class GridHeatMap extends React.Component {
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/basement_prod/c3f8bda2-081b-449d-aa9f-9413b779205b.json',
|
||||
);
|
||||
const scene = new Scene({
|
||||
center: [116.49434030056, 39.868073421167621],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'amap',
|
||||
style: 'mapbox://styles/mapbox/streets-v9',
|
||||
zoom: 16,
|
||||
});
|
||||
const layer = new HeatMapLayer({});
|
||||
layer
|
||||
.source(await response.json(), {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
transforms: [
|
||||
{
|
||||
type: 'grid',
|
||||
size: 50,
|
||||
field: 'count',
|
||||
method: 'sum',
|
||||
},
|
||||
],
|
||||
})
|
||||
.size('sum', (value: number) => {
|
||||
return value;
|
||||
})
|
||||
.shape('circle')
|
||||
.style({
|
||||
coverage: 1.2,
|
||||
angle: 0,
|
||||
})
|
||||
.color('count', [
|
||||
'#002466',
|
||||
'#105CB3',
|
||||
'#2894E0',
|
||||
'#CFF6FF',
|
||||
'#FFF5B8',
|
||||
'#FFAB5C',
|
||||
'#F27049',
|
||||
'#730D1C',
|
||||
]);
|
||||
scene.addLayer(layer);
|
||||
scene.render();
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import { ImageLayer } from '@l7/layers';
|
||||
import { Scene } from '@l7/scene';
|
||||
import * as React from 'react';
|
||||
|
||||
export default class ImageLayerDemo extends React.Component {
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const scene = new Scene({
|
||||
center: [121.2680, 30.3628],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'mapbox',
|
||||
style: 'mapbox://styles/mapbox/streets-v9',
|
||||
zoom: 10,
|
||||
});
|
||||
const layer = new ImageLayer({});
|
||||
layer.source(
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
|
||||
{
|
||||
parser: {
|
||||
type: 'image',
|
||||
extent: [121.168, 30.2828, 121.384, 30.4219],
|
||||
},
|
||||
},
|
||||
);
|
||||
// scene.addLayer(layer);
|
||||
scene.render();
|
||||
console.log(scene);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import { Line } from '@l7/layers';
|
||||
import { Scene } from '@l7/scene';
|
||||
import * as React from 'react';
|
||||
|
||||
export default class Point3D extends React.Component {
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/rmsportal/ZVfOvhVCzwBkISNsuKCc.json',
|
||||
);
|
||||
const testdata = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[91.58203125, 34.95799531086792],
|
||||
[96.767578125, 34.379712580462204],
|
||||
[99.228515625, 33.7243396617476],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const scene = new Scene({
|
||||
center: [102.602992, 23.107329],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'mapbox',
|
||||
style: 'mapbox://styles/mapbox/dark-v9',
|
||||
zoom: 13,
|
||||
});
|
||||
const LineLayer = new Line({});
|
||||
|
||||
LineLayer.source(await response.json())
|
||||
.size(1)
|
||||
.shape('line')
|
||||
.color(
|
||||
'ELEV',
|
||||
[
|
||||
'#E8FCFF',
|
||||
'#CFF6FF',
|
||||
'#A1E9ff',
|
||||
'#65CEF7',
|
||||
'#3CB1F0',
|
||||
'#2894E0',
|
||||
'#1772c2',
|
||||
'#105CB3',
|
||||
'#0D408C',
|
||||
'#002466',
|
||||
].reverse(),
|
||||
)
|
||||
.render();
|
||||
scene.addLayer(LineLayer);
|
||||
// function run() {
|
||||
// scene.render();
|
||||
// requestAnimationFrame(run);
|
||||
// }
|
||||
// requestAnimationFrame(run);
|
||||
scene.render();
|
||||
this.scene = scene;
|
||||
console.log(LineLayer);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import { Point } from '@l7/layers';
|
||||
import { Scene } from '@l7/scene';
|
||||
import * as React from 'react';
|
||||
import data from './data.json';
|
||||
|
||||
export default class Point3D extends React.Component {
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const scene = new Scene({
|
||||
center: [120.19382669582967, 30.258134],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'mapbox',
|
||||
style: 'mapbox://styles/mapbox/streets-v9',
|
||||
zoom: 1,
|
||||
});
|
||||
scene.addImage(
|
||||
'00',
|
||||
'https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*kzTMQqS2QdUAAAAAAAAAAABkARQnAQ',
|
||||
);
|
||||
const pointLayer = new Point({});
|
||||
const p1 = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [83.671875, 44.84029065139799],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
pointLayer
|
||||
.source(data)
|
||||
.color('blue')
|
||||
.shape('scalerank', [ 'triangleColumn', 'squareColumn', 'hexagonColumn' ,'cylinder' ])
|
||||
.size([25, 10]);
|
||||
scene.addLayer(pointLayer);
|
||||
scene.render();
|
||||
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,9 @@ export default class Mapbox extends React.Component {
|
|||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
properties: {
|
||||
name: 'test',
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
|
@ -53,21 +55,13 @@ export default class Mapbox extends React.Component {
|
|||
});
|
||||
const layer = new PolygonLayer({
|
||||
enableMultiPassRenderer: true,
|
||||
passes: [
|
||||
'blurH',
|
||||
[
|
||||
'blurV',
|
||||
{
|
||||
blurRadius: 8,
|
||||
},
|
||||
],
|
||||
],
|
||||
enablePicking: true,
|
||||
passes: [],
|
||||
});
|
||||
|
||||
// TODO: new GeoJSONSource()
|
||||
layer
|
||||
.source(await response.json())
|
||||
.size('name', [0, 10000, 50000, 30000, 100000])
|
||||
.color('name', [
|
||||
'#2E8AE6',
|
||||
'#69D1AB',
|
||||
|
@ -75,12 +69,19 @@ export default class Mapbox extends React.Component {
|
|||
'#FFD591',
|
||||
'#FF7A45',
|
||||
'#CF1D49',
|
||||
]);
|
||||
])
|
||||
.shape('fill')
|
||||
.style({
|
||||
opacity: 0.8,
|
||||
});
|
||||
scene.addLayer(layer);
|
||||
scene.render();
|
||||
|
||||
function run() {
|
||||
scene.render();
|
||||
requestAnimationFrame(run);
|
||||
}
|
||||
requestAnimationFrame(run);
|
||||
this.scene = scene;
|
||||
|
||||
console.log(layer);
|
||||
/*** 运行时修改样式属性 ***/
|
||||
// const gui = new dat.GUI();
|
||||
// this.gui = gui;
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import { Point } from '@l7/layers';
|
||||
import { Scene } from '@l7/scene';
|
||||
import * as React from 'react';
|
||||
import data from './data.json';
|
||||
export default class PointImage extends React.Component {
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const scene = new Scene({
|
||||
center: [120.19382669582967, 30.258134],
|
||||
id: 'map',
|
||||
pitch: 0,
|
||||
type: 'mapbox',
|
||||
style: 'mapbox://styles/mapbox/streets-v9',
|
||||
zoom: 1,
|
||||
});
|
||||
scene.addImage(
|
||||
'00',
|
||||
'https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*kzTMQqS2QdUAAAAAAAAAAABkARQnAQ',
|
||||
);
|
||||
const pointLayer = new Point({});
|
||||
const p1 = {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [83.671875, 44.84029065139799],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
pointLayer
|
||||
.source(data)
|
||||
// .color('blue')
|
||||
.shape('00')
|
||||
.size(14);
|
||||
scene.addLayer(pointLayer);
|
||||
scene.render();
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
22
yarn.lock
22
yarn.lock
|
@ -2754,6 +2754,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.0.0.tgz#a0d63a296a2d8435a9ec59393dcac746c6174a96"
|
||||
integrity sha512-rGqfPVowNDTszSFvwoZIXvrPG7s/qKzm9piCRIH6xwTTRu7pPZ3ootULFnPkTt74B6i5lN0FpLQL24qGOw1uZA==
|
||||
|
||||
"@types/d3-color@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.2.2.tgz#80cf7cfff7401587b8f89307ba36fe4a576bc7cf"
|
||||
integrity sha512-6pBxzJ8ZP3dYEQ4YjQ+NVbQaOflfgXq/JbDiS99oLobM2o72uAST4q6yPxHv6FOTCRC/n35ktuo8pvw/S4M7sw==
|
||||
|
||||
"@types/d3-dsv@^1.0.36":
|
||||
version "1.0.36"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-1.0.36.tgz#e91129d7c02b1b814838d001e921e8b9a67153d0"
|
||||
|
@ -5562,6 +5567,11 @@ d3-color@1:
|
|||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.3.0.tgz#675818359074215b020dc1d41d518136dcb18fa9"
|
||||
integrity sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg==
|
||||
|
||||
d3-color@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.0.tgz#89c45a995ed773b13314f06460df26d60ba0ecaf"
|
||||
integrity sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==
|
||||
|
||||
d3-dsv@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.1.1.tgz#aaa830ecb76c4b5015572c647cc6441e3c7bb701"
|
||||
|
@ -7285,6 +7295,11 @@ gl-matrix@^3.0.0, gl-matrix@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.1.0.tgz#f5b2de17d8fed95a79e5025b10cded0ab9ccbed0"
|
||||
integrity sha512-526NA+3EA+ztAQi0IZpSWiM0fyQXIp7IbRvfJ4wS/TjjQD0uv0fVybXwwqqSOlq33UckivI0yMDlVtboWm3k7A==
|
||||
|
||||
gl-vec2@^1.0.0, gl-vec2@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/gl-vec2/-/gl-vec2-1.3.0.tgz#83d472ed46034de8e09cbc857123fb6c81c51199"
|
||||
integrity sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A==
|
||||
|
||||
glob-parent@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
|
||||
|
@ -11377,6 +11392,13 @@ polished@^3.3.1:
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.4.5"
|
||||
|
||||
polyline-miter-util@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/polyline-miter-util/-/polyline-miter-util-1.0.1.tgz#b693f2389ea0ded36a6bcf5ecd2ece4b6917d957"
|
||||
integrity sha1-tpPyOJ6g3tNqa89ezS7OS2kX2Vc=
|
||||
dependencies:
|
||||
gl-vec2 "^1.0.0"
|
||||
|
||||
popper.js@^1.14.4, popper.js@^1.14.7:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
|
||||
|
|
Loading…
Reference in New Issue