mirror of https://gitee.com/antv-l7/antv-l7
feat(layer): add point line polygon image layer
This commit is contained in:
parent
83d6dcc4a4
commit
c0289113cf
|
@ -45,6 +45,7 @@ export * from './services/camera/ICameraService';
|
||||||
export * from './services/config/IConfigService';
|
export * from './services/config/IConfigService';
|
||||||
export * from './services/scene/ISceneService';
|
export * from './services/scene/ISceneService';
|
||||||
export * from './services/shader/IShaderModuleService';
|
export * from './services/shader/IShaderModuleService';
|
||||||
|
export * from './services/asset/IIconService';
|
||||||
|
|
||||||
/** 全部渲染服务接口 */
|
/** 全部渲染服务接口 */
|
||||||
export * from './services/renderer/IAttribute';
|
export * from './services/renderer/IAttribute';
|
||||||
|
|
|
@ -6,6 +6,7 @@ import getDecorators from 'inversify-inject-decorators';
|
||||||
import { TYPES } from './types';
|
import { TYPES } from './types';
|
||||||
|
|
||||||
/** Service interfaces */
|
/** Service interfaces */
|
||||||
|
import { IIconService} from './services/asset/IIconService';
|
||||||
import { ICameraService } from './services/camera/ICameraService';
|
import { ICameraService } from './services/camera/ICameraService';
|
||||||
import { IGlobalConfigService } from './services/config/IConfigService';
|
import { IGlobalConfigService } from './services/config/IConfigService';
|
||||||
import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService';
|
import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService';
|
||||||
|
@ -14,6 +15,7 @@ import { ILogService } from './services/log/ILogService';
|
||||||
import { IShaderModuleService } from './services/shader/IShaderModuleService';
|
import { IShaderModuleService } from './services/shader/IShaderModuleService';
|
||||||
|
|
||||||
/** Service implements */
|
/** Service implements */
|
||||||
|
import IconService from './services/asset/IconService';
|
||||||
import CameraService from './services/camera/CameraService';
|
import CameraService from './services/camera/CameraService';
|
||||||
import GlobalConfigService from './services/config/ConfigService';
|
import GlobalConfigService from './services/config/ConfigService';
|
||||||
import CoordinateSystemService from './services/coordinate/CoordinateSystemService';
|
import CoordinateSystemService from './services/coordinate/CoordinateSystemService';
|
||||||
|
@ -44,6 +46,10 @@ container
|
||||||
.bind<ICoordinateSystemService>(TYPES.ICoordinateSystemService)
|
.bind<ICoordinateSystemService>(TYPES.ICoordinateSystemService)
|
||||||
.to(CoordinateSystemService)
|
.to(CoordinateSystemService)
|
||||||
.inSingletonScope();
|
.inSingletonScope();
|
||||||
|
container
|
||||||
|
.bind<IIconService>(TYPES.IIconService)
|
||||||
|
.to(IconService)
|
||||||
|
.inSingletonScope();
|
||||||
container
|
container
|
||||||
.bind<IShaderModuleService>(TYPES.IShaderModuleService)
|
.bind<IShaderModuleService>(TYPES.IShaderModuleService)
|
||||||
.to(ShaderModuleService)
|
.to(ShaderModuleService)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
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 {
|
||||||
|
addImage(id: string, image: IImage): void;
|
||||||
|
getTexture(): ITexture2D;
|
||||||
|
getIconMap(): IICONMap;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { inject, injectable } from 'inversify';
|
||||||
|
import { buildIconMaping } from '../../utils/font_util';
|
||||||
|
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 {
|
||||||
|
private canvas: HTMLCanvasElement;
|
||||||
|
private iconData: IIcon[];
|
||||||
|
private iconMap: IICONMap;
|
||||||
|
private canvasHeigth: number;
|
||||||
|
private textrure: ITexture2D;
|
||||||
|
private ctx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.iconData = [];
|
||||||
|
this.iconMap = {};
|
||||||
|
this.canvas = document.createElement('canvas');
|
||||||
|
// this.texture =
|
||||||
|
this.ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addImage(id: string, image: IImage) {
|
||||||
|
const imagedata = (await this.loadImage(image)) 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.canvasHeigth = canvasHeight;
|
||||||
|
this.updateIconAtlas();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTexture(): ITexture2D {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIconMap() {
|
||||||
|
return this.iconMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateIconAtlas() {
|
||||||
|
this.canvas.width = MAX_CANVAS_WIDTH;
|
||||||
|
this.canvas.height = this.canvasHeigth;
|
||||||
|
Object.keys(this.iconMap).forEach((item: string) => {
|
||||||
|
const { x, y, image } = this.iconMap[item];
|
||||||
|
this.ctx.drawImage(image, x, y, imageSize, imageSize);
|
||||||
|
});
|
||||||
|
// this.texture.magFilter = THREE.LinearFilter;
|
||||||
|
// this.texture.minFilter = THREE.LinearFilter;
|
||||||
|
// this.texture.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadImage(url: IImage) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (url instanceof HTMLImageElement) {
|
||||||
|
resolve(url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const image = new Image();
|
||||||
|
image.onload = () => {
|
||||||
|
resolve(image);
|
||||||
|
};
|
||||||
|
image.onerror = () => {
|
||||||
|
reject(new Error('Could not load image at ' + url));
|
||||||
|
};
|
||||||
|
image.src = url instanceof File ? URL.createObjectURL(url) : url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,4 +45,4 @@ export default class GlobalConfigService implements IGlobalConfigService {
|
||||||
public reset() {
|
public reset() {
|
||||||
this.config = defaultGlobalConfig;
|
this.config = defaultGlobalConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ export interface IStyleScale {
|
||||||
scale: any;
|
scale: any;
|
||||||
field: string;
|
field: string;
|
||||||
type: StyleScaleType;
|
type: StyleScaleType;
|
||||||
|
option: IScaleOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILayerGlobalConfig {
|
export interface ILayerGlobalConfig {
|
||||||
|
@ -44,12 +45,13 @@ export interface ILayerGlobalConfig {
|
||||||
type CallBack = (...args: any[]) => any;
|
type CallBack = (...args: any[]) => any;
|
||||||
export type StyleAttributeField = string | string[];
|
export type StyleAttributeField = string | string[];
|
||||||
export type StyleAttributeOption = string | number | boolean | any[] | CallBack;
|
export type StyleAttributeOption = string | number | boolean | any[] | CallBack;
|
||||||
|
export type StyleAttrField = string | string[] | number | number[];
|
||||||
export interface ILayerStyleAttribute {
|
export interface ILayerStyleAttribute {
|
||||||
type: string;
|
type: string;
|
||||||
names: string[];
|
names: string[];
|
||||||
field: StyleAttributeField;
|
field: StyleAttributeField;
|
||||||
values?: any[];
|
values?: any[];
|
||||||
scales?: any[];
|
scales?: IStyleScale[];
|
||||||
setScales: (scales: IStyleScale[]) => void;
|
setScales: (scales: IStyleScale[]) => void;
|
||||||
callback?: (...args: any[]) => [];
|
callback?: (...args: any[]) => [];
|
||||||
mapping?(...params: unknown[]): unknown[];
|
mapping?(...params: unknown[]): unknown[];
|
||||||
|
@ -77,9 +79,9 @@ export interface ILayer {
|
||||||
};
|
};
|
||||||
multiPassRenderer: IMultiPassRenderer;
|
multiPassRenderer: IMultiPassRenderer;
|
||||||
init(): ILayer;
|
init(): ILayer;
|
||||||
size(field: string, value?: StyleAttributeOption): ILayer;
|
size(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||||
color(field: string, value?: StyleAttributeOption): ILayer;
|
color(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||||
shape(field: string, value?: StyleAttributeOption): ILayer;
|
shape(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||||
// pattern(field: string, value: StyleAttributeOption): ILayer;
|
// pattern(field: string, value: StyleAttributeOption): ILayer;
|
||||||
// filter(field: string, value: StyleAttributeOption): ILayer;
|
// filter(field: string, value: StyleAttributeOption): ILayer;
|
||||||
// active(option: ActiveOption): ILayer;
|
// active(option: ActiveOption): ILayer;
|
||||||
|
|
|
@ -9,6 +9,7 @@ const TYPES = {
|
||||||
IMapService: Symbol.for('IMapService'),
|
IMapService: Symbol.for('IMapService'),
|
||||||
IRendererService: Symbol.for('IRendererService'),
|
IRendererService: Symbol.for('IRendererService'),
|
||||||
IShaderModuleService: Symbol.for('IShaderModuleService'),
|
IShaderModuleService: Symbol.for('IShaderModuleService'),
|
||||||
|
IIconService: Symbol.for('IIconService'),
|
||||||
|
|
||||||
/** multi-pass */
|
/** multi-pass */
|
||||||
ClearPass: Symbol.for('ClearPass'),
|
ClearPass: Symbol.for('ClearPass'),
|
||||||
|
|
|
@ -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,7 +22,9 @@
|
||||||
"@l7/core": "^0.0.1",
|
"@l7/core": "^0.0.1",
|
||||||
"@l7/source": "^0.0.1",
|
"@l7/source": "^0.0.1",
|
||||||
"@turf/meta": "^6.0.2",
|
"@turf/meta": "^6.0.2",
|
||||||
|
"@types/d3-color": "^1.2.2",
|
||||||
"d3-array": "^2.3.1",
|
"d3-array": "^2.3.1",
|
||||||
|
"d3-color": "^1.4.0",
|
||||||
"d3-scale": "^3.1.0",
|
"d3-scale": "^3.1.0",
|
||||||
"earcut": "^2.2.1",
|
"earcut": "^2.2.1",
|
||||||
"eventemitter3": "^3.1.0",
|
"eventemitter3": "^3.1.0",
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
|
import { ILayerStyleOptions } from '@l7/core';
|
||||||
|
import { lngLatToMeters } from '@l7/utils';
|
||||||
|
import { vec3 } from 'gl-matrix';
|
||||||
interface IBufferCfg {
|
interface IBufferCfg {
|
||||||
data: unknown[];
|
data: unknown[];
|
||||||
imagePos?: unknown;
|
imagePos?: unknown;
|
||||||
uv?: boolean;
|
style?: ILayerStyleOptions;
|
||||||
}
|
}
|
||||||
type Position = number[];
|
export type Position = number[];
|
||||||
type Color = [number, number, number, number];
|
type Color = [number, number, number, number];
|
||||||
import { lngLatToMeters } from '@l7/utils';
|
|
||||||
import { vec3 } from 'gl-matrix';
|
|
||||||
export interface IBufferInfo {
|
export interface IBufferInfo {
|
||||||
vertices?: any;
|
vertices?: any;
|
||||||
indexArray?: any;
|
indexArray?: any;
|
||||||
indexOffset: any;
|
indexOffset: any;
|
||||||
verticesOffset: any;
|
verticesOffset: number;
|
||||||
faceNum?: any;
|
faceNum?: any;
|
||||||
dimensions: number;
|
dimensions: number;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export interface IEncodeFeature {
|
export interface IEncodeFeature {
|
||||||
color?: Color;
|
color?: Color;
|
||||||
|
@ -21,8 +23,8 @@ export interface IEncodeFeature {
|
||||||
shape?: string | number;
|
shape?: string | number;
|
||||||
pattern?: string;
|
pattern?: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
coordinates: Position[][];
|
coordinates: unknown;
|
||||||
bufferInfo: IBufferInfo;
|
bufferInfo: unknown;
|
||||||
}
|
}
|
||||||
export default class Buffer {
|
export default class Buffer {
|
||||||
public attributes: {
|
public attributes: {
|
||||||
|
@ -34,20 +36,23 @@ export default class Buffer {
|
||||||
|
|
||||||
protected data: unknown[];
|
protected data: unknown[];
|
||||||
protected imagePos: unknown;
|
protected imagePos: unknown;
|
||||||
protected uv: boolean;
|
protected style: any;
|
||||||
|
|
||||||
constructor({ data, imagePos, uv }: IBufferCfg) {
|
constructor({ data, imagePos, style }: IBufferCfg) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.imagePos = imagePos;
|
this.imagePos = imagePos;
|
||||||
this.uv = !!uv;
|
this.style = style;
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
public computeVertexNormals() {
|
public computeVertexNormals(
|
||||||
|
field: string = 'positions',
|
||||||
|
flag: boolean = true,
|
||||||
|
) {
|
||||||
const normals = (this.attributes.normals = new Float32Array(
|
const normals = (this.attributes.normals = new Float32Array(
|
||||||
this.verticesCount * 3,
|
this.verticesCount * 3,
|
||||||
));
|
));
|
||||||
const indexArray = this.indexArray;
|
const indexArray = this.indexArray;
|
||||||
const { positions } = this.attributes;
|
const positions = this.attributes[field];
|
||||||
let vA;
|
let vA;
|
||||||
let vB;
|
let vB;
|
||||||
let vC;
|
let vC;
|
||||||
|
@ -58,11 +63,17 @@ export default class Buffer {
|
||||||
vA = indexArray[i + 0] * 3;
|
vA = indexArray[i + 0] * 3;
|
||||||
vB = indexArray[i + 1] * 3;
|
vB = indexArray[i + 1] * 3;
|
||||||
vC = indexArray[i + 2] * 3;
|
vC = indexArray[i + 2] * 3;
|
||||||
const [ax, ay] = lngLatToMeters([positions[vA], positions[vA + 1]]);
|
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 pA = vec3.fromValues(ax, ay, positions[vA + 2]);
|
||||||
const [bx, by] = lngLatToMeters([positions[vB], positions[vB + 1]]);
|
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 pB = vec3.fromValues(bx, by, positions[vB + 2]);
|
||||||
const [cx, cy] = lngLatToMeters([positions[vC], positions[vC + 1]]);
|
const [cx, cy] = flag
|
||||||
|
? lngLatToMeters([positions[vC], positions[vC + 1]])
|
||||||
|
: [positions[vC], positions[vC + 1]];
|
||||||
const pC = vec3.fromValues(cx, cy, positions[vC + 2]);
|
const pC = vec3.fromValues(cx, cy, positions[vC + 2]);
|
||||||
vec3.sub(cb, pC, pB);
|
vec3.sub(cb, pC, pB);
|
||||||
vec3.sub(ab, pA, pB);
|
vec3.sub(ab, pA, pB);
|
||||||
|
@ -113,7 +124,8 @@ export default class Buffer {
|
||||||
}
|
}
|
||||||
protected encodeArray(feature: IEncodeFeature, num: number) {
|
protected encodeArray(feature: IEncodeFeature, num: number) {
|
||||||
const { color, id, pattern, size } = feature;
|
const { color, id, pattern, size } = feature;
|
||||||
const { verticesOffset } = feature.bufferInfo;
|
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||||
|
const { verticesOffset } = bufferInfo;
|
||||||
const imagePos = this.imagePos;
|
const imagePos = this.imagePos;
|
||||||
const start1 = verticesOffset;
|
const start1 = verticesOffset;
|
||||||
for (let i = 0; i < num; i++) {
|
for (let i = 0; i < num; i++) {
|
||||||
|
@ -130,7 +142,7 @@ export default class Buffer {
|
||||||
let size2: number[] = [];
|
let size2: number[] = [];
|
||||||
if (Array.isArray(size) && size.length === 2) {
|
if (Array.isArray(size) && size.length === 2) {
|
||||||
// TODO 多维size支持
|
// TODO 多维size支持
|
||||||
size2 = [size[0]];
|
size2 = [size[0], size[0], size[1]];
|
||||||
}
|
}
|
||||||
if (!Array.isArray(size)) {
|
if (!Array.isArray(size)) {
|
||||||
size2 = [size];
|
size2 = [size];
|
||||||
|
@ -145,90 +157,24 @@ export default class Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected calculateWall(feature: IEncodeFeature) {
|
protected initAttributes() {
|
||||||
const size = feature.size || 0;
|
|
||||||
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 * 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,
|
|
||||||
);
|
|
||||||
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();
|
|
||||||
// 拼接成一个 attribute
|
|
||||||
this.initAttributes();
|
|
||||||
this.buildFeatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initAttributes() {
|
|
||||||
this.attributes.positions = new Float32Array(this.verticesCount * 3);
|
this.attributes.positions = new Float32Array(this.verticesCount * 3);
|
||||||
this.attributes.colors = new Float32Array(this.verticesCount * 4);
|
this.attributes.colors = new Float32Array(this.verticesCount * 4);
|
||||||
this.attributes.pickingIds = new Float32Array(this.verticesCount);
|
this.attributes.pickingIds = new Float32Array(this.verticesCount);
|
||||||
this.attributes.sizes = new Float32Array(this.verticesCount);
|
this.attributes.sizes = new Float32Array(this.verticesCount);
|
||||||
this.attributes.pickingIds = 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);
|
this.indexArray = new Uint32Array(this.indexCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
// 1. 计算 attribute 长度
|
||||||
|
this.calculateFeatures();
|
||||||
|
// 2. 初始化 attribute
|
||||||
|
this.initAttributes();
|
||||||
|
// 3. 拼接attribute
|
||||||
|
this.buildFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
private normalizeNormals() {
|
private normalizeNormals() {
|
||||||
const { normals } = this.attributes;
|
const { normals } = this.attributes;
|
||||||
for (let i = 0, li = normals.length; i < li; i += 3) {
|
for (let i = 0, li = normals.length; i < li; i += 3) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
IGlobalConfigService,
|
IGlobalConfigService,
|
||||||
|
IIconService,
|
||||||
ILayer,
|
ILayer,
|
||||||
ILayerInitializationOptions,
|
ILayerInitializationOptions,
|
||||||
ILayerPlugin,
|
ILayerPlugin,
|
||||||
|
@ -50,7 +51,9 @@ export default class BaseLayer implements ILayer {
|
||||||
data: any;
|
data: any;
|
||||||
options?: ISourceCFG;
|
options?: ISourceCFG;
|
||||||
};
|
};
|
||||||
public styleOption: ILayerStyleOptions;
|
public styleOption: ILayerStyleOptions = {
|
||||||
|
opacity: 1.0,
|
||||||
|
};
|
||||||
// 样式属性
|
// 样式属性
|
||||||
public styleAttributes: {
|
public styleAttributes: {
|
||||||
[key: string]: Required<ILayerStyleAttribute>;
|
[key: string]: Required<ILayerStyleAttribute>;
|
||||||
|
@ -67,6 +70,9 @@ export default class BaseLayer implements ILayer {
|
||||||
@lazyInject(TYPES.IRendererService)
|
@lazyInject(TYPES.IRendererService)
|
||||||
private readonly rendererService: IRendererService;
|
private readonly rendererService: IRendererService;
|
||||||
|
|
||||||
|
@lazyInject(TYPES.IIconService)
|
||||||
|
private readonly iconService: IIconService;
|
||||||
|
|
||||||
constructor(initializationOptions: Partial<ILayerInitializationOptions>) {
|
constructor(initializationOptions: Partial<ILayerInitializationOptions>) {
|
||||||
this.initializationOptions = initializationOptions;
|
this.initializationOptions = initializationOptions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export default class ScaleController {
|
||||||
field,
|
field,
|
||||||
scale: undefined,
|
scale: undefined,
|
||||||
type: StyleScaleType.VARIABLE,
|
type: StyleScaleType.VARIABLE,
|
||||||
|
option: scaleOption,
|
||||||
};
|
};
|
||||||
if (!data || !data.length) {
|
if (!data || !data.length) {
|
||||||
// 数据为空
|
// 数据为空
|
||||||
|
@ -69,6 +70,7 @@ export default class ScaleController {
|
||||||
Object.assign(cfg, scaleOption);
|
Object.assign(cfg, scaleOption);
|
||||||
scaleOption = cfg; // 更新scale配置
|
scaleOption = cfg; // 更新scale配置
|
||||||
scale.scale = this.generateScale(type, cfg);
|
scale.scale = this.generateScale(type, cfg);
|
||||||
|
scale.option = scaleOption;
|
||||||
}
|
}
|
||||||
return scale;
|
return scale;
|
||||||
}
|
}
|
||||||
|
@ -100,10 +102,10 @@ export default class ScaleController {
|
||||||
|
|
||||||
private generateScale(type: ScaleTypes, scaleOption: IScaleOption) {
|
private generateScale(type: ScaleTypes, scaleOption: IScaleOption) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const scale = scaleMap[type]();
|
let scale = scaleMap[type]();
|
||||||
if (scaleOption.hasOwnProperty('domain')) {
|
if (scaleOption.hasOwnProperty('domain')) {
|
||||||
// 处理同一字段映射不同视觉通道的问题
|
// 处理同一字段映射不同视觉通道的问题
|
||||||
scale.copy().domain(scaleOption.domain);
|
scale = scale.copy().domain(scaleOption.domain);
|
||||||
}
|
}
|
||||||
// TODO 其他属性支持
|
// TODO 其他属性支持
|
||||||
return scale;
|
return scale;
|
||||||
|
|
|
@ -22,13 +22,6 @@ export default class StyleAttribute implements ILayerStyleAttribute {
|
||||||
this.scales = scales;
|
this.scales = scales;
|
||||||
this.values = values;
|
this.values = values;
|
||||||
this.names = this.parseFields(field) || [];
|
this.names = this.parseFields(field) || [];
|
||||||
// 设置 range TODO 2维映射
|
|
||||||
// this.scales.forEach((scale) => {
|
|
||||||
// scale.scale.range(values);
|
|
||||||
// if (scale.type === StyleScaleType.VARIABLE) {
|
|
||||||
// this.type = StyleScaleType.VARIABLE;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
this.type = StyleScaleType.VARIABLE;
|
this.type = StyleScaleType.VARIABLE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import BaseLayer from './core/BaseLayer';
|
import BaseLayer from './core/BaseLayer';
|
||||||
import PointLayer from './point';
|
import PointLayer from './point';
|
||||||
|
import Point from './point/point';
|
||||||
import PolygonLayer from './polygon';
|
import PolygonLayer from './polygon';
|
||||||
|
export { BaseLayer, PointLayer, PolygonLayer, Point };
|
||||||
export { BaseLayer, PointLayer, PolygonLayer };
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
import BufferBase, { IEncodeFeature, Position } from '../../core/BaseBuffer';
|
||||||
|
interface IBufferInfo {
|
||||||
|
normals: number[];
|
||||||
|
arrayIndex: number[];
|
||||||
|
positions: number[];
|
||||||
|
attrDistance: number[];
|
||||||
|
miters: number[];
|
||||||
|
verticesOffset: number;
|
||||||
|
indexOffset: number;
|
||||||
|
}
|
||||||
|
export default class FillBuffer 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) => {
|
||||||
|
let { coordinates } = feature;
|
||||||
|
if (Array.isArray(coordinates[0][0])) {
|
||||||
|
coordinates = coordinates[0];
|
||||||
|
}
|
||||||
|
const { normals, attrIndex, attrPos, attrDistance, miters } = getNormals(
|
||||||
|
coordinates,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
||||||
const attribute = layer.styleAttributes[attributeName];
|
const attribute = layer.styleAttributes[attributeName];
|
||||||
const scales: any[] = [];
|
const scales: any[] = [];
|
||||||
attribute.names.forEach((field: string) => {
|
attribute.names.forEach((field: string) => {
|
||||||
scales.push(this.getOrCreateScale(attribute, dataArray));
|
scales.push(this.getOrCreateScale(attribute, field, dataArray));
|
||||||
});
|
});
|
||||||
attribute.setScales(scales);
|
attribute.setScales(scales);
|
||||||
});
|
});
|
||||||
|
@ -54,9 +54,9 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
||||||
|
|
||||||
private getOrCreateScale(
|
private getOrCreateScale(
|
||||||
attribute: ILayerStyleAttribute,
|
attribute: ILayerStyleAttribute,
|
||||||
|
field: string,
|
||||||
data: any[],
|
data: any[],
|
||||||
): IStyleScale {
|
): IStyleScale {
|
||||||
const { field } = attribute;
|
|
||||||
let scale = this.scaleCache[field as string];
|
let scale = this.scaleCache[field as string];
|
||||||
if (!scale) {
|
if (!scale) {
|
||||||
scale = this.scaleController.createScale(field as string, data);
|
scale = this.scaleController.createScale(field as string, data);
|
||||||
|
@ -84,10 +84,10 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
||||||
// TODO: 数据过滤
|
// TODO: 数据过滤
|
||||||
Object.keys(attributes).forEach((attributeName: string) => {
|
Object.keys(attributes).forEach((attributeName: string) => {
|
||||||
const attribute = attributes[attributeName];
|
const attribute = attributes[attributeName];
|
||||||
const { type } = attribute;
|
// const { type } = attribute; // TODO: 支持常量 或变量
|
||||||
if (type === StyleScaleType.CONSTANT) {
|
// if (type === StyleScaleType.CONSTANT) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
let values = this.getAttrValue(attribute, record);
|
let values = this.getAttrValue(attribute, record);
|
||||||
if (attributeName === 'color') {
|
if (attributeName === 'color') {
|
||||||
values = values.map((c: unknown) => {
|
values = values.map((c: unknown) => {
|
||||||
|
@ -109,7 +109,7 @@ export default class DataEncodePlugin implements ILayerPlugin {
|
||||||
const params: unknown[] = [];
|
const params: unknown[] = [];
|
||||||
|
|
||||||
scales.forEach((scale) => {
|
scales.forEach((scale) => {
|
||||||
const { field, type, value } = scale;
|
const { field, type } = scale;
|
||||||
if (type === StyleScaleType.CONSTANT) {
|
if (type === StyleScaleType.CONSTANT) {
|
||||||
params.push(scale.field);
|
params.push(scale.field);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import BaseBuffer, {
|
||||||
|
IBufferInfo,
|
||||||
|
IEncodeFeature,
|
||||||
|
Position,
|
||||||
|
} from '../../core/BaseBuffer';
|
||||||
|
import extrudePolygon, { IExtrudeGeomety } from '../shape/extrude';
|
||||||
|
import { geometryShape, ShapeType } 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 ShapeType);
|
||||||
|
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 ShapeType);
|
||||||
|
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: ShapeType): 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,21 @@
|
||||||
|
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[];
|
||||||
|
layerData.forEach((item: IEncodeFeature, index: number) => {
|
||||||
|
const { color = [0, 0, 0, 0], size, id, shape, coordinates } = item;
|
||||||
|
const { x, y } = this.imagePos[shape];
|
||||||
|
const coor = coordinates as Position;
|
||||||
|
this.attributes.vertices.set([coor[0], coor[1], coor[2] || 0], index * 3);
|
||||||
|
this.attributes.colors.set(color, index * 4);
|
||||||
|
this.attributes.pickingIds.set([id], index);
|
||||||
|
this.attributes.sizes.set([size as number], index); //
|
||||||
|
this.attributes.uv.set([x, y], index * 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
import {
|
||||||
|
gl,
|
||||||
|
IRendererService,
|
||||||
|
IShaderModuleService,
|
||||||
|
lazyInject,
|
||||||
|
TYPES,
|
||||||
|
} from '@l7/core';
|
||||||
|
import BaseLayer from '../core/BaseLayer';
|
||||||
|
import ExtrudeBuffer from './buffers/ExtrudeBuffer';
|
||||||
|
import extrude_frag from './shaders/extrude_frag.glsl';
|
||||||
|
import extrude_vert from './shaders/extrude_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.models = [];
|
||||||
|
const { vs, fs, uniforms } = this.shaderModule.getModule('point');
|
||||||
|
const buffer = new ExtrudeBuffer({
|
||||||
|
data: this.getEncodedData(),
|
||||||
|
});
|
||||||
|
buffer.computeVertexNormals('miters', false);
|
||||||
|
console.log(buffer); // TODO: normal
|
||||||
|
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: 3,
|
||||||
|
}),
|
||||||
|
a_shape: createAttribute({
|
||||||
|
buffer: createBuffer({
|
||||||
|
data: buffer.attributes.miters,
|
||||||
|
type: gl.FLOAT,
|
||||||
|
}),
|
||||||
|
size: 3,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
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,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,13 @@
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
varying vec4 v_color;
|
||||||
|
void main(){
|
||||||
|
vec2 pos=v_uv+gl_PointCoord / 512.*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,15 @@
|
||||||
|
precision highp float;
|
||||||
|
attribute vec3 a_Position;
|
||||||
|
attribute vec4 a_color;
|
||||||
|
attribute float a_size;
|
||||||
|
attribute float a_shape;
|
||||||
|
varying vec4 v_color;
|
||||||
|
varying vec2 v_uv;
|
||||||
|
#pragma include "projection"
|
||||||
|
void main() {
|
||||||
|
v_color = a_color;
|
||||||
|
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||||
|
gl_Position = project_common_position_to_clipspace(vec4(project_pos, 1.0));
|
||||||
|
gl_PointSize = a_size;
|
||||||
|
v_uv = uv;
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
type IPosition = [number, number, number];
|
||||||
|
export type IPath = IPosition[];
|
||||||
|
export enum ShapeType {
|
||||||
|
CIRCLE = 'cylinder',
|
||||||
|
SQUARE = 'squareColumn',
|
||||||
|
TRIANGLE = 'triangleColumn',
|
||||||
|
HEXAGON = 'hexagonColumn',
|
||||||
|
PENTAGON = 'pentagonColumn',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成规则多边形顶点个数
|
||||||
|
* @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 = {
|
||||||
|
[ShapeType.CIRCLE]: circle,
|
||||||
|
[ShapeType.HEXAGON]: hexagon,
|
||||||
|
[ShapeType.TRIANGLE]: triangle,
|
||||||
|
[ShapeType.SQUARE]: square,
|
||||||
|
[ShapeType.PENTAGON]: pentagon,
|
||||||
|
};
|
|
@ -0,0 +1,62 @@
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
import earcut from 'earcut';
|
import earcut from 'earcut';
|
||||||
import BufferBase, { IBufferInfo, IEncodeFeature } from '../../core/BaseBuffer';
|
import BufferBase, {
|
||||||
|
IBufferInfo,
|
||||||
|
IEncodeFeature,
|
||||||
|
Position,
|
||||||
|
} from '../../core/BaseBuffer';
|
||||||
export default class ExtrudeBuffer extends BufferBase {
|
export default class ExtrudeBuffer extends BufferBase {
|
||||||
public buildFeatures() {
|
public buildFeatures() {
|
||||||
const layerData = this.data as IEncodeFeature[];
|
const layerData = this.data as IEncodeFeature[];
|
||||||
|
@ -14,7 +18,7 @@ export default class ExtrudeBuffer extends BufferBase {
|
||||||
const layerData = this.data as IEncodeFeature[];
|
const layerData = this.data as IEncodeFeature[];
|
||||||
// 计算长
|
// 计算长
|
||||||
layerData.forEach((feature: IEncodeFeature) => {
|
layerData.forEach((feature: IEncodeFeature) => {
|
||||||
const { coordinates } = feature;
|
const coordinates = feature.coordinates as Position[][];
|
||||||
const flattengeo = earcut.flatten(coordinates);
|
const flattengeo = earcut.flatten(coordinates);
|
||||||
const n = this.checkIsClosed(coordinates)
|
const n = this.checkIsClosed(coordinates)
|
||||||
? coordinates[0].length - 1
|
? coordinates[0].length - 1
|
||||||
|
@ -36,15 +40,45 @@ export default class ExtrudeBuffer extends BufferBase {
|
||||||
feature.bufferInfo = bufferInfo;
|
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) {
|
private calculateTop(feature: IEncodeFeature) {
|
||||||
const size = feature.size || 1;
|
const size = feature.size || 1;
|
||||||
|
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||||
const {
|
const {
|
||||||
indexArray,
|
indexArray,
|
||||||
vertices,
|
vertices,
|
||||||
indexOffset,
|
indexOffset,
|
||||||
verticesOffset,
|
verticesOffset,
|
||||||
dimensions,
|
dimensions,
|
||||||
} = feature.bufferInfo;
|
} = bufferInfo;
|
||||||
const pointCount = vertices.length / dimensions;
|
const pointCount = vertices.length / dimensions;
|
||||||
this.encodeArray(feature, vertices.length / dimensions);
|
this.encodeArray(feature, vertices.length / dimensions);
|
||||||
// 添加顶点
|
// 添加顶点
|
||||||
|
@ -54,14 +88,50 @@ export default class ExtrudeBuffer extends BufferBase {
|
||||||
(verticesOffset + i) * 3,
|
(verticesOffset + i) * 3,
|
||||||
);
|
);
|
||||||
// 顶部文理坐标计算
|
// 顶部文理坐标计算
|
||||||
if (this.uv) {
|
// if (this.uv) {
|
||||||
// TODO 用过BBox计算纹理坐标
|
// // TODO 用过BBox计算纹理坐标
|
||||||
this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2);
|
// this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
feature.bufferInfo.verticesOffset += pointCount;
|
bufferInfo.verticesOffset += pointCount;
|
||||||
// 添加顶点索引
|
// 添加顶点索引
|
||||||
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
|
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
|
||||||
feature.bufferInfo.indexOffset += indexArray.length;
|
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 earcut from 'earcut';
|
||||||
import BufferBase, { IBufferInfo, IEncodeFeature } from '../../core/BaseBuffer';
|
import BufferBase, {
|
||||||
|
IBufferInfo,
|
||||||
|
IEncodeFeature,
|
||||||
|
Position,
|
||||||
|
} from '../../core/BaseBuffer';
|
||||||
export default class FillBuffer extends BufferBase {
|
export default class FillBuffer extends BufferBase {
|
||||||
protected buildFeatures() {
|
protected buildFeatures() {
|
||||||
const layerData = this.data as IEncodeFeature[];
|
const layerData = this.data as IEncodeFeature[];
|
||||||
|
@ -14,7 +18,7 @@ export default class FillBuffer extends BufferBase {
|
||||||
// 计算长
|
// 计算长
|
||||||
layerData.forEach((feature: IEncodeFeature) => {
|
layerData.forEach((feature: IEncodeFeature) => {
|
||||||
const { coordinates } = feature;
|
const { coordinates } = feature;
|
||||||
const flattengeo = earcut.flatten(coordinates);
|
const flattengeo = earcut.flatten(coordinates as Position[][]);
|
||||||
const { vertices, dimensions, holes } = flattengeo;
|
const { vertices, dimensions, holes } = flattengeo;
|
||||||
const indexArray = earcut(vertices, holes, dimensions).map(
|
const indexArray = earcut(vertices, holes, dimensions).map(
|
||||||
(v) => this.verticesCount + v,
|
(v) => this.verticesCount + v,
|
||||||
|
@ -33,13 +37,14 @@ export default class FillBuffer extends BufferBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateFill(feature: IEncodeFeature) {
|
private calculateFill(feature: IEncodeFeature) {
|
||||||
|
const bufferInfo = feature.bufferInfo as IBufferInfo;
|
||||||
const {
|
const {
|
||||||
indexArray,
|
indexArray,
|
||||||
vertices,
|
vertices,
|
||||||
indexOffset,
|
indexOffset,
|
||||||
verticesOffset,
|
verticesOffset,
|
||||||
dimensions = 3,
|
dimensions = 3,
|
||||||
} = feature.bufferInfo;
|
} = bufferInfo;
|
||||||
const pointCount = vertices.length / dimensions;
|
const pointCount = vertices.length / dimensions;
|
||||||
this.encodeArray(feature, pointCount);
|
this.encodeArray(feature, pointCount);
|
||||||
// 添加顶点
|
// 添加顶点
|
||||||
|
@ -48,15 +53,16 @@ export default class FillBuffer extends BufferBase {
|
||||||
[vertices[i * dimensions], vertices[i * dimensions + 1], 0],
|
[vertices[i * dimensions], vertices[i * dimensions + 1], 0],
|
||||||
(verticesOffset + i) * 3,
|
(verticesOffset + i) * 3,
|
||||||
);
|
);
|
||||||
if (this.uv) {
|
// if (this.uv) {
|
||||||
// TODO 用过BBox计算纹理坐标
|
// // TODO 用过BBox计算纹理坐标
|
||||||
this.attributes.uv.set(
|
// this.attributes.uv.set(
|
||||||
[0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0],
|
// [0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0],
|
||||||
(verticesOffset + i) * 3,
|
// (verticesOffset + i) * 3,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
feature.bufferInfo.verticesOffset += pointCount;
|
bufferInfo.verticesOffset += pointCount;
|
||||||
|
feature.bufferInfo = bufferInfo;
|
||||||
// 添加顶点索引
|
// 添加顶点索引
|
||||||
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
|
this.indexArray.set(indexArray, indexOffset); // 顶部坐标
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,15 +39,13 @@ export default class PolygonLayer extends BaseLayer {
|
||||||
|
|
||||||
this.models = [];
|
this.models = [];
|
||||||
const { vs, fs, uniforms } = this.shaderModule.getModule('polygon');
|
const { vs, fs, uniforms } = this.shaderModule.getModule('polygon');
|
||||||
const buffer = new ExtrudeBuffer({
|
// const buffer = new ExtrudeBuffer({
|
||||||
|
// data: this.getEncodedData(),
|
||||||
|
// });
|
||||||
|
// buffer.computeVertexNormals();
|
||||||
|
const buffer = new FillBuffer({
|
||||||
data: this.getEncodedData(),
|
data: this.getEncodedData(),
|
||||||
});
|
});
|
||||||
buffer.computeVertexNormals();
|
|
||||||
const buffer2 = new FillBuffer({
|
|
||||||
data: this.getEncodedData(),
|
|
||||||
});
|
|
||||||
console.log(buffer);
|
|
||||||
console.log(buffer2);
|
|
||||||
const {
|
const {
|
||||||
createAttribute,
|
createAttribute,
|
||||||
createBuffer,
|
createBuffer,
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import BaseBuffer, { IEncodeFeature, Position } from '../../core/BaseBuffer';
|
||||||
|
interface IImageFeature extends IEncodeFeature {
|
||||||
|
images: any[];
|
||||||
|
}
|
||||||
|
export default class ImageBuffer extends BaseBuffer {
|
||||||
|
protected calculateFeatures() {
|
||||||
|
const layerData = this.data as IImageFeature[];
|
||||||
|
this.verticesCount = 4;
|
||||||
|
this.indexCount = 6;
|
||||||
|
}
|
||||||
|
protected buildFeatures() {
|
||||||
|
const layerData = this.data as IImageFeature[];
|
||||||
|
const coordinates = layerData[0].coordinates as Position[];
|
||||||
|
const images = layerData[0].images;
|
||||||
|
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,8 @@
|
||||||
|
precision mediump float;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_opacity;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture2D(u_texture,vec2(v_texCoord.x,1.0-v_texCoord.y));
|
||||||
|
gl_FragColor = color * u_opacity;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
precision highp float;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
uniform mat4 u_ModelMatrix;
|
||||||
|
attribute vec3 a_Position;
|
||||||
|
void main() {
|
||||||
|
v_texCoord = uv;
|
||||||
|
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||||
|
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0));
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
|
import * as d3 from 'd3-color';
|
||||||
export function rgb2arr(str: string) {
|
export function rgb2arr(str: string) {
|
||||||
const arr = [];
|
const color = d3.color(str) as d3.RGBColor;
|
||||||
if (str.length === 4) {
|
const arr = [0, 0, 0, 0];
|
||||||
str = `#${str[1]}${str[1]}${str[2]}${str[2]}${str[3]}${str[3]}`;
|
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;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
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) {
|
||||||
|
vec2.sub(out, a, b);
|
||||||
|
vec2.normalize(out, out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function extrusions(
|
||||||
|
positions: number[],
|
||||||
|
out: vec2,
|
||||||
|
miters: vec2,
|
||||||
|
point: vec2,
|
||||||
|
normal: vec2,
|
||||||
|
scale,
|
||||||
|
) {
|
||||||
|
addNext(out, miters, normal, -scale);
|
||||||
|
addNext(out, miters, normal, scale);
|
||||||
|
positions.push(...point);
|
||||||
|
positions.push(...point);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNext(out, miters, normal, length) {
|
||||||
|
out.push(normal[0], normal[1], 0);
|
||||||
|
miters.push(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lineSegmentDistance(end, start) {
|
||||||
|
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 + dz * dz);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPointEqual(a, b) {
|
||||||
|
return a[0] === b[0] && a[1] === b[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(points, closed, indexOffset) {
|
||||||
|
const lineA = vec2.fromValues(0, 0);
|
||||||
|
const lineB = vec2.fromValues(0, 0);
|
||||||
|
const tangent = vec2.fromValues(0, 0);
|
||||||
|
const miter = vec2.fromValues(0, 0);
|
||||||
|
let _started = false;
|
||||||
|
let _normal = null;
|
||||||
|
const tmp = vec2.create();
|
||||||
|
let count = indexOffset || 0;
|
||||||
|
const miterLimit = 3;
|
||||||
|
|
||||||
|
const out = [];
|
||||||
|
const attrPos = [];
|
||||||
|
const attrIndex = [];
|
||||||
|
const miters = [];
|
||||||
|
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 = points[i - 1];
|
||||||
|
const cur = points[i];
|
||||||
|
let next = i < points.length - 1 ? points[i + 1] : null;
|
||||||
|
// 如果当前点和前一点相同,跳过
|
||||||
|
if (isPointEqual(last, cur)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
let nextIndex = i + 1;
|
||||||
|
// 找到不相同的下一点
|
||||||
|
while (next && isPointEqual(cur, next)) {
|
||||||
|
next = nextIndex < points.length - 1 ? points[++nextIndex] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lineDistance = lineSegmentDistance(cur, last);
|
||||||
|
const d = lineDistance + attrDistance[attrDistance.length - 1];
|
||||||
|
|
||||||
|
direction(lineA, cur, last);
|
||||||
|
|
||||||
|
if (!_normal) {
|
||||||
|
_normal = [0, 0];
|
||||||
|
computeNormal(_normal, lineA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_started) {
|
||||||
|
_started = true;
|
||||||
|
extrusions(attrPos, out, miters, last, _normal, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
attrIndex.push(index + 0, index + 2, index + 1);
|
||||||
|
|
||||||
|
// no miter, simple segment
|
||||||
|
if (!next) {
|
||||||
|
// reset normal
|
||||||
|
computeNormal(_normal, lineA);
|
||||||
|
extrusions(attrPos, out, miters, cur, _normal, 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, miter, lineA, lineB, 1);
|
||||||
|
|
||||||
|
// get orientation
|
||||||
|
const flip = vec2.dot(tangent, _normal) < 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, _normal, 1);
|
||||||
|
attrIndex.push(index + 1, index + 2, index + 3);
|
||||||
|
attrIndex.push(index + 2, index + 4, index + 3);
|
||||||
|
computeNormal(tmp, lineB);
|
||||||
|
vec2.copy(_normal, tmp); // store normal for next round
|
||||||
|
|
||||||
|
extrusions(attrPos, out, miters, cur, _normal, 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, _normal, 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(_normal, tmp); // store normal for next round
|
||||||
|
|
||||||
|
extrusions(attrPos, out, miters, cur, _normal, 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, _normal, 1);
|
||||||
|
attrIndex.push(index + 1, index + 2, index + 3);
|
||||||
|
|
||||||
|
// now add the miter triangles
|
||||||
|
addNext(out, miters, miter, miterLen * -flip);
|
||||||
|
attrPos.push(...cur);
|
||||||
|
attrIndex.push(index + 2, index + 4, index + 3);
|
||||||
|
attrIndex.push(index + 4, index + 5, index + 6);
|
||||||
|
computeNormal(tmp, lineB);
|
||||||
|
vec2.copy(_normal, tmp); // store normal for next round
|
||||||
|
|
||||||
|
extrusions(attrPos, out, miters, cur, _normal, 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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@l7/utils": "0.0.1",
|
"@l7/utils": "0.0.1",
|
||||||
|
"@l7/core": "0.0.1",
|
||||||
"@mapbox/geojson-rewind": "^0.4.0",
|
"@mapbox/geojson-rewind": "^0.4.0",
|
||||||
"@turf/helpers": "^6.1.4",
|
"@turf/helpers": "^6.1.4",
|
||||||
"@turf/invariant": "^6.1.2",
|
"@turf/invariant": "^6.1.2",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { IParserCfg, ITransform } from '@l7/core';
|
||||||
import { IParserData } from './interface';
|
import { IParserData } from './interface';
|
||||||
type ParserFunction = (data: any, cfg?: any) => IParserData;
|
type ParserFunction = (data: any, cfg?: any) => IParserData;
|
||||||
type transformFunction = (data: IParserData, cfg?: object) => IParserData;
|
type transformFunction = (data: IParserData, cfg?: any) => IParserData;
|
||||||
const TRANSFORMS: {
|
const TRANSFORMS: {
|
||||||
[type: string]: transformFunction;
|
[type: string]: transformFunction;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
|
@ -4,11 +4,15 @@ import geojson from './parser/geojson';
|
||||||
import image from './parser/image';
|
import image from './parser/image';
|
||||||
import json from './parser/json';
|
import json from './parser/json';
|
||||||
import Source from './source';
|
import Source from './source';
|
||||||
|
import { cluster } from './transform/cluster';
|
||||||
|
import { aggregatorToGrid } from './transform/grid';
|
||||||
export default Source;
|
export default Source;
|
||||||
registerParser('geojson', geojson);
|
registerParser('geojson', geojson);
|
||||||
registerParser('image', image);
|
registerParser('image', image);
|
||||||
registerParser('csv', csv);
|
registerParser('csv', csv);
|
||||||
registerParser('json', json);
|
registerParser('json', json);
|
||||||
|
registerTransform('cluster', cluster);
|
||||||
|
registerTransform('grid', aggregatorToGrid);
|
||||||
export {
|
export {
|
||||||
getTransform,
|
getTransform,
|
||||||
registerTransform,
|
registerTransform,
|
||||||
|
|
|
@ -3,7 +3,10 @@ import { IParserData } from '../interface';
|
||||||
interface IImageCfg {
|
interface IImageCfg {
|
||||||
extent: [number, number, number, number];
|
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 { extent } = cfg;
|
||||||
|
|
||||||
const resultData: IParserData = {
|
const resultData: IParserData = {
|
||||||
|
|
|
@ -1,41 +1,65 @@
|
||||||
|
import { IParserCfg, IParserData, ISourceCFG, ITransform } from '@l7/core';
|
||||||
import { extent } from '@l7/utils';
|
import { extent } from '@l7/utils';
|
||||||
import { BBox, FeatureCollection, Geometries, Properties } from '@turf/helpers';
|
import { BBox, FeatureCollection, Geometries, Properties } from '@turf/helpers';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { getParser } from './';
|
import { SyncHook } from 'tapable';
|
||||||
import { IDictionary, IParserData, ISourceCFG } from './interface';
|
import { getParser, getTransform } from './';
|
||||||
export default class Source extends EventEmitter {
|
export default class Source extends EventEmitter {
|
||||||
public data: IParserData;
|
public data: IParserData;
|
||||||
|
|
||||||
// 数据范围
|
// 数据范围
|
||||||
public extent: BBox;
|
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;
|
private originData: any;
|
||||||
constructor(data: any, cfg?: ISourceCFG) {
|
constructor(data: any, cfg?: ISourceCFG) {
|
||||||
super();
|
super();
|
||||||
this.set('data', data);
|
this.data = cloneDeep(data);
|
||||||
Object.assign(this.attrs, cfg);
|
this.originData = data;
|
||||||
this.originData = cloneDeep(this.get('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.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get(name: string): any {
|
|
||||||
return this.attrs[name];
|
|
||||||
}
|
|
||||||
public set(name: string, value: any) {
|
|
||||||
this.attrs[name] = value;
|
|
||||||
}
|
|
||||||
private excuteParser(): void {
|
private excuteParser(): void {
|
||||||
const parser = this.get('parser') || {};
|
const parser = this.parser;
|
||||||
const type: string = parser.type || 'geojson';
|
const type: string = parser.type || 'geojson';
|
||||||
const sourceParser = getParser(type);
|
const sourceParser = getParser(type);
|
||||||
this.data = sourceParser(this.originData, parser);
|
this.data = sourceParser(this.originData, parser);
|
||||||
// 计算范围
|
// 计算范围
|
||||||
this.extent = extent(this.data.dataArray);
|
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() {
|
private init() {
|
||||||
this.excuteParser(); // 数据解析
|
this.hooks.init.call(this);
|
||||||
|
// this.excuteParser(); // 数据解析
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.xOffset / 360) * (256 << 20)) / 2,
|
||||||
|
xOffset: ((gridOffset.xOffset / 360) * (256 << 20)) / 2,
|
||||||
|
radius: ((gridOffset.xOffset / 360) * (256 << 20)) / 2,
|
||||||
|
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 };
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
import AMap from './components/AMap';
|
import AMap from './components/AMap';
|
||||||
import Mapbox from './components/Mapbox';
|
import Mapbox from './components/Mapbox';
|
||||||
import Polygon from './components/Polygon';
|
import Polygon from './components/Polygon';
|
||||||
|
import Point3D from './components/Point3D';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import notes from './Map.md';
|
import notes from './Map.md';
|
||||||
|
|
||||||
|
@ -13,4 +14,5 @@ storiesOf('地图底图测试', module)
|
||||||
.add('Mapbox', () => <Mapbox />, {
|
.add('Mapbox', () => <Mapbox />, {
|
||||||
notes: { markdown: notes },
|
notes: { markdown: notes },
|
||||||
})
|
})
|
||||||
.add('Polygon', () => <Polygon />);
|
.add('Polygon', () => <Polygon />)
|
||||||
|
.add('Point3D', () => <Point3D />);
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
// function run() {
|
||||||
|
// scene.render();
|
||||||
|
// requestAnimationFrame(run);
|
||||||
|
// }
|
||||||
|
// requestAnimationFrame(run);
|
||||||
|
scene.render();
|
||||||
|
this.scene = scene;
|
||||||
|
console.log(pointLayer);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
window.layer = pointLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="map"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,7 +75,11 @@ export default class Mapbox extends React.Component {
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
});
|
});
|
||||||
scene.addLayer(layer);
|
scene.addLayer(layer);
|
||||||
scene.render();
|
function run() {
|
||||||
|
scene.render();
|
||||||
|
requestAnimationFrame(run);
|
||||||
|
}
|
||||||
|
requestAnimationFrame(run);
|
||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
console.log(layer);
|
console.log(layer);
|
||||||
/*** 运行时修改样式属性 ***/
|
/*** 运行时修改样式属性 ***/
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -2754,6 +2754,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.0.0.tgz#a0d63a296a2d8435a9ec59393dcac746c6174a96"
|
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.0.0.tgz#a0d63a296a2d8435a9ec59393dcac746c6174a96"
|
||||||
integrity sha512-rGqfPVowNDTszSFvwoZIXvrPG7s/qKzm9piCRIH6xwTTRu7pPZ3ootULFnPkTt74B6i5lN0FpLQL24qGOw1uZA==
|
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":
|
"@types/d3-dsv@^1.0.36":
|
||||||
version "1.0.36"
|
version "1.0.36"
|
||||||
resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-1.0.36.tgz#e91129d7c02b1b814838d001e921e8b9a67153d0"
|
resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-1.0.36.tgz#e91129d7c02b1b814838d001e921e8b9a67153d0"
|
||||||
|
@ -5557,6 +5562,11 @@ d3-color@1:
|
||||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.3.0.tgz#675818359074215b020dc1d41d518136dcb18fa9"
|
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.3.0.tgz#675818359074215b020dc1d41d518136dcb18fa9"
|
||||||
integrity sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg==
|
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:
|
d3-dsv@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.1.1.tgz#aaa830ecb76c4b5015572c647cc6441e3c7bb701"
|
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.1.1.tgz#aaa830ecb76c4b5015572c647cc6441e3c7bb701"
|
||||||
|
|
Loading…
Reference in New Issue