From 54f28be495af94a39313b7840c69725be16dc1e2 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Sat, 12 Oct 2019 21:26:54 +0800 Subject: [PATCH] feat(layer): add point line polygon image layer --- packages/core/src/index.ts | 1 + packages/core/src/inversify.config.ts | 6 + .../core/src/services/asset/IIconService.ts | 21 ++ .../core/src/services/asset/IconService.ts | 85 ++++++++ .../core/src/services/config/ConfigService.ts | 2 +- .../core/src/services/layer/ILayerService.ts | 10 +- packages/core/src/types.ts | 1 + packages/core/src/utils/font_util.ts | 68 ++++++ packages/layers/package.json | 2 + packages/layers/src/core/BaseBuffer.ts | 132 ++++-------- packages/layers/src/core/BaseLayer.ts | 8 +- packages/layers/src/core/ScaleController.ts | 6 +- packages/layers/src/core/StyleAttribute.ts | 7 - packages/layers/src/index.ts | 4 +- packages/layers/src/line/buffers/line.ts | 87 ++++++++ .../layers/src/plugins/DataEncodePlugin.ts | 14 +- .../layers/src/point/buffers/ExtrudeBuffer.ts | 79 +++++++ .../layers/src/point/buffers/ImageBuffer.ts | 21 ++ packages/layers/src/point/point.ts | 106 ++++++++++ .../src/point/shaders/extrude_frag.glsl | 6 + .../src/point/shaders/extrude_vert.glsl | 19 ++ .../layers/src/point/shaders/image_frag.glsl | 13 ++ .../layers/src/point/shaders/image_vert.glsl | 15 ++ packages/layers/src/point/shape/Path.ts | 53 +++++ packages/layers/src/point/shape/extrude.ts | 62 ++++++ .../src/polygon/buffers/ExtrudeBuffer.ts | 88 +++++++- .../layers/src/polygon/buffers/FillBuffer.ts | 28 ++- packages/layers/src/polygon/index.ts | 12 +- .../layers/src/raster/buffers/ImageBuffer.ts | 34 +++ .../layers/src/raster/shaders/image_frag.glsl | 8 + .../layers/src/raster/shaders/image_vert.glsl | 9 + packages/layers/src/utils/color.ts | 15 +- packages/layers/src/utils/polylineNormal.ts | 199 ++++++++++++++++++ packages/source/package.json | 1 + packages/source/src/factory.ts | 3 +- packages/source/src/index.ts | 4 + packages/source/src/parser/image.ts | 5 +- packages/source/src/source.ts | 52 +++-- packages/source/src/transform/cluster.ts | 59 ++++++ packages/source/src/transform/grid.ts | 116 ++++++++++ packages/source/src/transform/statistics.ts | 71 +++++++ stories/MapAdaptor/Map.stories.tsx | 4 +- stories/MapAdaptor/components/Point3D.tsx | 69 ++++++ stories/MapAdaptor/components/Polygon.tsx | 6 +- yarn.lock | 10 + 45 files changed, 1452 insertions(+), 169 deletions(-) create mode 100644 packages/core/src/services/asset/IIconService.ts create mode 100644 packages/core/src/services/asset/IconService.ts create mode 100644 packages/core/src/utils/font_util.ts create mode 100644 packages/layers/src/line/buffers/line.ts create mode 100644 packages/layers/src/point/buffers/ExtrudeBuffer.ts create mode 100644 packages/layers/src/point/buffers/ImageBuffer.ts create mode 100644 packages/layers/src/point/point.ts create mode 100644 packages/layers/src/point/shaders/extrude_frag.glsl create mode 100644 packages/layers/src/point/shaders/extrude_vert.glsl create mode 100644 packages/layers/src/point/shaders/image_frag.glsl create mode 100644 packages/layers/src/point/shaders/image_vert.glsl create mode 100644 packages/layers/src/point/shape/Path.ts create mode 100644 packages/layers/src/point/shape/extrude.ts create mode 100644 packages/layers/src/raster/buffers/ImageBuffer.ts create mode 100644 packages/layers/src/raster/shaders/image_frag.glsl create mode 100644 packages/layers/src/raster/shaders/image_vert.glsl create mode 100644 packages/layers/src/utils/polylineNormal.ts create mode 100644 packages/source/src/transform/cluster.ts create mode 100644 packages/source/src/transform/grid.ts create mode 100644 packages/source/src/transform/statistics.ts create mode 100644 stories/MapAdaptor/components/Point3D.tsx diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 793aede385..4c765cb654 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -45,6 +45,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'; diff --git a/packages/core/src/inversify.config.ts b/packages/core/src/inversify.config.ts index 2645d7a932..f76e15dc7e 100644 --- a/packages/core/src/inversify.config.ts +++ b/packages/core/src/inversify.config.ts @@ -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'; @@ -14,6 +15,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'; @@ -44,6 +46,10 @@ container .bind(TYPES.ICoordinateSystemService) .to(CoordinateSystemService) .inSingletonScope(); +container + .bind(TYPES.IIconService) + .to(IconService) + .inSingletonScope(); container .bind(TYPES.IShaderModuleService) .to(ShaderModuleService) diff --git a/packages/core/src/services/asset/IIconService.ts b/packages/core/src/services/asset/IIconService.ts new file mode 100644 index 0000000000..81003ee678 --- /dev/null +++ b/packages/core/src/services/asset/IIconService.ts @@ -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; +} diff --git a/packages/core/src/services/asset/IconService.ts b/packages/core/src/services/asset/IconService.ts new file mode 100644 index 0000000000..71875a6f2f --- /dev/null +++ b/packages/core/src/services/asset/IconService.ts @@ -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; + }); + } +} diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index c36fe0b257..2d6ff3cabf 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -45,4 +45,4 @@ export default class GlobalConfigService implements IGlobalConfigService { public reset() { this.config = defaultGlobalConfig; } -} \ No newline at end of file +} diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index eddba5482b..20d690ea17 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -31,6 +31,7 @@ export interface IStyleScale { scale: any; field: string; type: StyleScaleType; + option: IScaleOption; } export interface ILayerGlobalConfig { @@ -44,12 +45,13 @@ export interface ILayerGlobalConfig { 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; values?: any[]; - scales?: any[]; + scales?: IStyleScale[]; setScales: (scales: IStyleScale[]) => void; callback?: (...args: any[]) => []; mapping?(...params: unknown[]): unknown[]; @@ -77,9 +79,9 @@ export interface ILayer { }; multiPassRenderer: IMultiPassRenderer; init(): ILayer; - size(field: string, value?: StyleAttributeOption): ILayer; - color(field: string, value?: StyleAttributeOption): ILayer; - shape(field: string, value?: StyleAttributeOption): 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; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 19901f97a2..6934825731 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -9,6 +9,7 @@ const TYPES = { IMapService: Symbol.for('IMapService'), IRendererService: Symbol.for('IRendererService'), IShaderModuleService: Symbol.for('IShaderModuleService'), + IIconService: Symbol.for('IIconService'), /** multi-pass */ ClearPass: Symbol.for('ClearPass'), diff --git a/packages/core/src/utils/font_util.ts b/packages/core/src/utils/font_util.ts new file mode 100644 index 0000000000..68b9260645 --- /dev/null +++ b/packages/core/src/utils/font_util.ts @@ -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))); +} diff --git a/packages/layers/package.json b/packages/layers/package.json index 6ad99af8ac..f986aa58e9 100644 --- a/packages/layers/package.json +++ b/packages/layers/package.json @@ -22,7 +22,9 @@ "@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", diff --git a/packages/layers/src/core/BaseBuffer.ts b/packages/layers/src/core/BaseBuffer.ts index 0b542ce15f..bf91cacd84 100644 --- a/packages/layers/src/core/BaseBuffer.ts +++ b/packages/layers/src/core/BaseBuffer.ts @@ -1,19 +1,21 @@ +import { ILayerStyleOptions } from '@l7/core'; +import { lngLatToMeters } from '@l7/utils'; +import { vec3 } from 'gl-matrix'; interface IBufferCfg { data: unknown[]; imagePos?: unknown; - uv?: boolean; + style?: ILayerStyleOptions; } -type Position = number[]; +export type Position = number[]; type Color = [number, number, number, number]; -import { lngLatToMeters } from '@l7/utils'; -import { vec3 } from 'gl-matrix'; 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; @@ -21,8 +23,8 @@ export interface IEncodeFeature { shape?: string | number; pattern?: string; id?: number; - coordinates: Position[][]; - bufferInfo: IBufferInfo; + coordinates: unknown; + bufferInfo: unknown; } export default class Buffer { public attributes: { @@ -34,20 +36,23 @@ export default class Buffer { protected data: unknown[]; protected imagePos: unknown; - protected uv: boolean; + protected style: any; - constructor({ data, imagePos, uv }: IBufferCfg) { + constructor({ data, imagePos, style }: IBufferCfg) { this.data = data; this.imagePos = imagePos; - this.uv = !!uv; + this.style = style; this.init(); } - public computeVertexNormals() { + 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; + const positions = this.attributes[field]; let vA; let vB; let vC; @@ -58,11 +63,17 @@ export default class Buffer { vA = indexArray[i + 0] * 3; vB = indexArray[i + 1] * 3; vC = indexArray[i + 2] * 3; - const [ax, ay] = lngLatToMeters([positions[vA], positions[vA + 1]]); + const [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] = 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 [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]); vec3.sub(cb, pC, pB); vec3.sub(ab, pA, pB); @@ -113,7 +124,8 @@ export default class Buffer { } protected encodeArray(feature: IEncodeFeature, num: number) { const { color, id, pattern, size } = feature; - const { verticesOffset } = feature.bufferInfo; + const bufferInfo = feature.bufferInfo as IBufferInfo; + const { verticesOffset } = bufferInfo; const imagePos = this.imagePos; const start1 = verticesOffset; for (let i = 0; i < num; i++) { @@ -130,7 +142,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]; @@ -145,90 +157,24 @@ export default class Buffer { } } } - protected calculateWall(feature: IEncodeFeature) { - 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() { + 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) { diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 2fe50e92f5..0150220569 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -1,5 +1,6 @@ import { IGlobalConfigService, + IIconService, ILayer, ILayerInitializationOptions, ILayerPlugin, @@ -50,7 +51,9 @@ export default class BaseLayer implements ILayer { data: any; options?: ISourceCFG; }; - public styleOption: ILayerStyleOptions; + public styleOption: ILayerStyleOptions = { + opacity: 1.0, + }; // 样式属性 public styleAttributes: { [key: string]: Required; @@ -67,6 +70,9 @@ export default class BaseLayer implements ILayer { @lazyInject(TYPES.IRendererService) private readonly rendererService: IRendererService; + @lazyInject(TYPES.IIconService) + private readonly iconService: IIconService; + constructor(initializationOptions: Partial) { this.initializationOptions = initializationOptions; } diff --git a/packages/layers/src/core/ScaleController.ts b/packages/layers/src/core/ScaleController.ts index 2a308692d3..849e10ac01 100644 --- a/packages/layers/src/core/ScaleController.ts +++ b/packages/layers/src/core/ScaleController.ts @@ -36,6 +36,7 @@ export default class ScaleController { field, scale: undefined, type: StyleScaleType.VARIABLE, + option: scaleOption, }; if (!data || !data.length) { // 数据为空 @@ -69,6 +70,7 @@ export default class ScaleController { Object.assign(cfg, scaleOption); scaleOption = cfg; // 更新scale配置 scale.scale = this.generateScale(type, cfg); + scale.option = scaleOption; } return scale; } @@ -100,10 +102,10 @@ export default class ScaleController { private generateScale(type: ScaleTypes, scaleOption: IScaleOption) { // @ts-ignore - const scale = scaleMap[type](); + let scale = scaleMap[type](); if (scaleOption.hasOwnProperty('domain')) { // 处理同一字段映射不同视觉通道的问题 - scale.copy().domain(scaleOption.domain); + scale = scale.copy().domain(scaleOption.domain); } // TODO 其他属性支持 return scale; diff --git a/packages/layers/src/core/StyleAttribute.ts b/packages/layers/src/core/StyleAttribute.ts index dbb39d0382..5f080b0deb 100644 --- a/packages/layers/src/core/StyleAttribute.ts +++ b/packages/layers/src/core/StyleAttribute.ts @@ -22,13 +22,6 @@ export default class StyleAttribute implements ILayerStyleAttribute { this.scales = scales; this.values = values; 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) { this.type = StyleScaleType.VARIABLE; } diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index 13d132e024..93e61285ff 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -1,5 +1,5 @@ import BaseLayer from './core/BaseLayer'; import PointLayer from './point'; +import Point from './point/point'; import PolygonLayer from './polygon'; - -export { BaseLayer, PointLayer, PolygonLayer }; +export { BaseLayer, PointLayer, PolygonLayer, Point }; diff --git a/packages/layers/src/line/buffers/line.ts b/packages/layers/src/line/buffers/line.ts new file mode 100644 index 0000000000..d2f16491ba --- /dev/null +++ b/packages/layers/src/line/buffers/line.ts @@ -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); + } +} diff --git a/packages/layers/src/plugins/DataEncodePlugin.ts b/packages/layers/src/plugins/DataEncodePlugin.ts index d86f342e35..b326d63706 100644 --- a/packages/layers/src/plugins/DataEncodePlugin.ts +++ b/packages/layers/src/plugins/DataEncodePlugin.ts @@ -40,7 +40,7 @@ export default class DataEncodePlugin implements ILayerPlugin { const attribute = layer.styleAttributes[attributeName]; const scales: any[] = []; attribute.names.forEach((field: string) => { - scales.push(this.getOrCreateScale(attribute, dataArray)); + scales.push(this.getOrCreateScale(attribute, field, dataArray)); }); attribute.setScales(scales); }); @@ -54,9 +54,9 @@ export default class DataEncodePlugin implements ILayerPlugin { private getOrCreateScale( attribute: ILayerStyleAttribute, + field: string, data: any[], ): IStyleScale { - const { field } = attribute; let scale = this.scaleCache[field as string]; if (!scale) { scale = this.scaleController.createScale(field as string, data); @@ -84,10 +84,10 @@ export default class DataEncodePlugin implements ILayerPlugin { // TODO: 数据过滤 Object.keys(attributes).forEach((attributeName: string) => { const attribute = attributes[attributeName]; - const { type } = attribute; - if (type === StyleScaleType.CONSTANT) { - return; - } + // const { type } = attribute; // TODO: 支持常量 或变量 + // if (type === StyleScaleType.CONSTANT) { + // return; + // } let values = this.getAttrValue(attribute, record); if (attributeName === 'color') { values = values.map((c: unknown) => { @@ -109,7 +109,7 @@ export default class DataEncodePlugin implements ILayerPlugin { const params: unknown[] = []; scales.forEach((scale) => { - const { field, type, value } = scale; + const { field, type } = scale; if (type === StyleScaleType.CONSTANT) { params.push(scale.field); } else { diff --git a/packages/layers/src/point/buffers/ExtrudeBuffer.ts b/packages/layers/src/point/buffers/ExtrudeBuffer.ts new file mode 100644 index 0000000000..6313ee9bf4 --- /dev/null +++ b/packages/layers/src/point/buffers/ExtrudeBuffer.ts @@ -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; + } +} diff --git a/packages/layers/src/point/buffers/ImageBuffer.ts b/packages/layers/src/point/buffers/ImageBuffer.ts new file mode 100644 index 0000000000..cfe6a15254 --- /dev/null +++ b/packages/layers/src/point/buffers/ImageBuffer.ts @@ -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); + }); + } +} diff --git a/packages/layers/src/point/point.ts b/packages/layers/src/point/point.ts new file mode 100644 index 0000000000..36cfea61bd --- /dev/null +++ b/packages/layers/src/point/point.ts @@ -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, + }), + }), + ); + } +} diff --git a/packages/layers/src/point/shaders/extrude_frag.glsl b/packages/layers/src/point/shaders/extrude_frag.glsl new file mode 100644 index 0000000000..6f26ac6083 --- /dev/null +++ b/packages/layers/src/point/shaders/extrude_frag.glsl @@ -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; +} \ No newline at end of file diff --git a/packages/layers/src/point/shaders/extrude_vert.glsl b/packages/layers/src/point/shaders/extrude_vert.glsl new file mode 100644 index 0000000000..32432f1938 --- /dev/null +++ b/packages/layers/src/point/shaders/extrude_vert.glsl @@ -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)); + +} \ No newline at end of file diff --git a/packages/layers/src/point/shaders/image_frag.glsl b/packages/layers/src/point/shaders/image_frag.glsl new file mode 100644 index 0000000000..7f472a1e29 --- /dev/null +++ b/packages/layers/src/point/shaders/image_frag.glsl @@ -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; +} \ No newline at end of file diff --git a/packages/layers/src/point/shaders/image_vert.glsl b/packages/layers/src/point/shaders/image_vert.glsl new file mode 100644 index 0000000000..c62ac0a641 --- /dev/null +++ b/packages/layers/src/point/shaders/image_vert.glsl @@ -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; +} \ No newline at end of file diff --git a/packages/layers/src/point/shape/Path.ts b/packages/layers/src/point/shape/Path.ts new file mode 100644 index 0000000000..d9647ff73b --- /dev/null +++ b/packages/layers/src/point/shape/Path.ts @@ -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, +}; diff --git a/packages/layers/src/point/shape/extrude.ts b/packages/layers/src/point/shape/extrude.ts new file mode 100644 index 0000000000..8e0d8f7620 --- /dev/null +++ b/packages/layers/src/point/shape/extrude.ts @@ -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, + }; +} diff --git a/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts b/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts index 19ec468fab..9b0697d590 100644 --- a/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts +++ b/packages/layers/src/polygon/buffers/ExtrudeBuffer.ts @@ -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 ExtrudeBuffer extends BufferBase { public buildFeatures() { const layerData = this.data as IEncodeFeature[]; @@ -14,7 +18,7 @@ export default class ExtrudeBuffer extends BufferBase { const layerData = this.data as IEncodeFeature[]; // 计算长 layerData.forEach((feature: IEncodeFeature) => { - const { coordinates } = feature; + const coordinates = feature.coordinates as Position[][]; const flattengeo = earcut.flatten(coordinates); const n = this.checkIsClosed(coordinates) ? coordinates[0].length - 1 @@ -36,15 +40,45 @@ export default class ExtrudeBuffer extends BufferBase { 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, - } = feature.bufferInfo; + } = bufferInfo; const pointCount = vertices.length / dimensions; this.encodeArray(feature, vertices.length / dimensions); // 添加顶点 @@ -54,14 +88,50 @@ export default class ExtrudeBuffer extends BufferBase { (verticesOffset + i) * 3, ); // 顶部文理坐标计算 - if (this.uv) { - // TODO 用过BBox计算纹理坐标 - this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2); - } + // if (this.uv) { + // // TODO 用过BBox计算纹理坐标 + // this.attributes.uv.set([-1, -1], (verticesOffset + i) * 2); + // } } - feature.bufferInfo.verticesOffset += pointCount; + bufferInfo.verticesOffset += pointCount; // 添加顶点索引 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); } } diff --git a/packages/layers/src/polygon/buffers/FillBuffer.ts b/packages/layers/src/polygon/buffers/FillBuffer.ts index 3e8df52b15..c1e03a1459 100644 --- a/packages/layers/src/polygon/buffers/FillBuffer.ts +++ b/packages/layers/src/polygon/buffers/FillBuffer.ts @@ -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); // 顶部坐标 } diff --git a/packages/layers/src/polygon/index.ts b/packages/layers/src/polygon/index.ts index 0b9f93ad62..38f355aacf 100644 --- a/packages/layers/src/polygon/index.ts +++ b/packages/layers/src/polygon/index.ts @@ -39,15 +39,13 @@ export default class PolygonLayer extends BaseLayer { this.models = []; 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(), }); - buffer.computeVertexNormals(); - const buffer2 = new FillBuffer({ - data: this.getEncodedData(), - }); - console.log(buffer); - console.log(buffer2); const { createAttribute, createBuffer, diff --git a/packages/layers/src/raster/buffers/ImageBuffer.ts b/packages/layers/src/raster/buffers/ImageBuffer.ts new file mode 100644 index 0000000000..1208b76460 --- /dev/null +++ b/packages/layers/src/raster/buffers/ImageBuffer.ts @@ -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); + } +} diff --git a/packages/layers/src/raster/shaders/image_frag.glsl b/packages/layers/src/raster/shaders/image_frag.glsl new file mode 100644 index 0000000000..a83beb90e7 --- /dev/null +++ b/packages/layers/src/raster/shaders/image_frag.glsl @@ -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; +} \ No newline at end of file diff --git a/packages/layers/src/raster/shaders/image_vert.glsl b/packages/layers/src/raster/shaders/image_vert.glsl new file mode 100644 index 0000000000..778b2c576c --- /dev/null +++ b/packages/layers/src/raster/shaders/image_vert.glsl @@ -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)); +} \ No newline at end of file diff --git a/packages/layers/src/utils/color.ts b/packages/layers/src/utils/color.ts index 12ed54742f..27af9f87d3 100644 --- a/packages/layers/src/utils/color.ts +++ b/packages/layers/src/utils/color.ts @@ -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; } diff --git a/packages/layers/src/utils/polylineNormal.ts b/packages/layers/src/utils/polylineNormal.ts new file mode 100644 index 0000000000..9ad8e482aa --- /dev/null +++ b/packages/layers/src/utils/polylineNormal.ts @@ -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, + }; +} diff --git a/packages/source/package.json b/packages/source/package.json index df1cf147a5..2ee8927270 100644 --- a/packages/source/package.json +++ b/packages/source/package.json @@ -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", diff --git a/packages/source/src/factory.ts b/packages/source/src/factory.ts index abf126e7a4..b7c2b5d644 100644 --- a/packages/source/src/factory.ts +++ b/packages/source/src/factory.ts @@ -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; } = {}; diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index 7ebdc18515..a307b5857a 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -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, diff --git a/packages/source/src/parser/image.ts b/packages/source/src/parser/image.ts index 90a5d5dd9c..72fad48edf 100644 --- a/packages/source/src/parser/image.ts +++ b/packages/source/src/parser/image.ts @@ -3,7 +3,10 @@ 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 resultData: IParserData = { diff --git a/packages/source/src/source.ts b/packages/source/src/source.ts index 1cb2d9c265..e11d737949 100644 --- a/packages/source/src/source.ts +++ b/packages/source/src/source.ts @@ -1,41 +1,65 @@ +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 = {}; + // 生命周期钩子 + 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.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); + // this.excuteParser(); // 数据解析 } } diff --git a/packages/source/src/transform/cluster.ts b/packages/source/src/transform/cluster.ts new file mode 100644 index 0000000000..5ee549286c --- /dev/null +++ b/packages/source/src/transform/cluster.ts @@ -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, + }; + }); +} diff --git a/packages/source/src/transform/grid.ts b/packages/source/src/transform/grid.ts new file mode 100644 index 0000000000..73e834fd3a --- /dev/null +++ b/packages/source/src/transform/grid.ts @@ -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]; + }); +} diff --git a/packages/source/src/transform/statistics.ts b/packages/source/src/transform/statistics.ts new file mode 100644 index 0000000000..e3da89891c --- /dev/null +++ b/packages/source/src/transform/statistics.ts @@ -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 }; diff --git a/stories/MapAdaptor/Map.stories.tsx b/stories/MapAdaptor/Map.stories.tsx index a1f0adc11a..c1ede60b07 100644 --- a/stories/MapAdaptor/Map.stories.tsx +++ b/stories/MapAdaptor/Map.stories.tsx @@ -3,6 +3,7 @@ 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'; // @ts-ignore import notes from './Map.md'; @@ -13,4 +14,5 @@ storiesOf('地图底图测试', module) .add('Mapbox', () => , { notes: { markdown: notes }, }) - .add('Polygon', () => ); + .add('Polygon', () => ) + .add('Point3D', () => ); diff --git a/stories/MapAdaptor/components/Point3D.tsx b/stories/MapAdaptor/components/Point3D.tsx new file mode 100644 index 0000000000..49bd61170a --- /dev/null +++ b/stories/MapAdaptor/components/Point3D.tsx @@ -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 ( +
+ ); + } +} diff --git a/stories/MapAdaptor/components/Polygon.tsx b/stories/MapAdaptor/components/Polygon.tsx index 92d5ba42ac..9078d45975 100644 --- a/stories/MapAdaptor/components/Polygon.tsx +++ b/stories/MapAdaptor/components/Polygon.tsx @@ -75,7 +75,11 @@ export default class Mapbox extends React.Component { opacity: 0.8, }); scene.addLayer(layer); - scene.render(); + function run() { + scene.render(); + requestAnimationFrame(run); + } + requestAnimationFrame(run); this.scene = scene; console.log(layer); /*** 运行时修改样式属性 ***/ diff --git a/yarn.lock b/yarn.lock index f5436fbf7d..9ccbb3e960 100644 --- a/yarn.lock +++ b/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" @@ -5557,6 +5562,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"