From aa2e9540de407bdb4f2c6ae3a3d004cda65a1130 Mon Sep 17 00:00:00 2001 From: YiQianYao <42212176+2912401452@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:36:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BE=E5=B1=82=E6=96=B0=E5=A2=9E=20?= =?UTF-8?q?creatModelData=20=E4=B8=8E=20updateModelData=20=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20(#1064)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 支持提前计算 attribute/elements 并更新 models * style: lint style * feat: 支持 lineLayer 的 models 更新 attributes/elements * style: lint style * chore: update version 2.8.30 -> 2.8.31 * feat: 完善 geometry 加载地形的 lod * style: lint style * feat: rename func updateMudelsData to updateModelData * feat: pointLayer simple 支持 initModelData/updateModelData * feat: 增加顶点更新 demo * style: lint style * feat: reset func name => initModelData => createModelData --- gatsby-browser.js | 2 + package.json | 2 + .../core/src/services/layer/ILayerService.ts | 8 + packages/core/src/services/renderer/IModel.ts | 4 + packages/layers/src/Geometry/models/plane.ts | 258 +++++++++++------- packages/layers/src/core/BaseLayer.ts | 51 ++++ packages/layers/src/line/models/line.ts | 1 + packages/layers/src/point/models/fill.ts | 3 + .../layers/src/point/models/simplePoint.ts | 1 + packages/layers/src/polygon/models/fill.ts | 3 + packages/layers/src/utils/layerData.ts | 220 +++++++++++++++ packages/renderer/src/regl/ReglModel.ts | 22 ++ packages/source/src/parser/geojson.ts | 6 + .../components/updataPointsTimeLine.tsx | 199 ++++++++++++++ .../components/updateAttrAndEle.tsx | 105 +++++++ .../components/updateAttrAndEle_line.tsx | 132 +++++++++ .../updateAttrAndEle_planeGeometry.tsx | 121 ++++++++ .../components/updateAttrAndEle_polygon.tsx | 116 ++++++++ stories/MapPerformance/map.stories.tsx | 12 +- stories/Object/components/planeTerrain.tsx | 2 +- 20 files changed, 1167 insertions(+), 101 deletions(-) create mode 100644 packages/layers/src/utils/layerData.ts create mode 100644 stories/MapPerformance/components/updataPointsTimeLine.tsx create mode 100644 stories/MapPerformance/components/updateAttrAndEle.tsx create mode 100644 stories/MapPerformance/components/updateAttrAndEle_line.tsx create mode 100644 stories/MapPerformance/components/updateAttrAndEle_planeGeometry.tsx create mode 100644 stories/MapPerformance/components/updateAttrAndEle_polygon.tsx diff --git a/gatsby-browser.js b/gatsby-browser.js index 99b1aba888..552fda12e6 100644 --- a/gatsby-browser.js +++ b/gatsby-browser.js @@ -19,3 +19,5 @@ window.react = require('react'); window.popmotion = require('popmotion'); window.reactDom = require('react-dom'); window.antd = require('antd'); +window.d3Dsv = require('d3-dsv'); +window.materialUI = require('@material-ui') diff --git a/package.json b/package.json index e5fbada87d..9f64d0206a 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,8 @@ "husky": "^3.0.9", "jest": "^24.9.0", "jest-electron": "^0.1.11", + "@material-ui/core": "^4.10.2", + "@material-ui/icons": "^4.9.1", "jest-styled-components": "^6.2.1", "lerna": "^3.16.4", "lint-staged": "^9.2.4", diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index c26e111d08..64859e37a3 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -78,6 +78,7 @@ export interface ILayerModel { // earth mode setEarthTime?(time: number): void; + createModelData?(options?: any): any; } export interface IModelUniform { [key: string]: IUniform; @@ -110,6 +111,11 @@ export interface ILegendClassificaItem { // 图层图例 export type LegendItems = ILegendSegmentItem[] | ILegendClassificaItem[]; +export interface IAttrubuteAndElements { + attributes: any; + elements: any; +} + export interface ILayer { id: string; // 一个场景中同一类型 Layer 可能存在多个 type: string; // 代表 Layer 的类型 @@ -150,6 +156,7 @@ export interface ILayer { // 初始化 layer 的时候指定 layer type 类型()兼容空数据的情况 layerType?: string | undefined; isLayerGroup: boolean; + triangulation?: Triangulation | undefined; /** * threejs 适配兼容相关的方法 * @param lnglat @@ -161,6 +168,7 @@ export interface ILayer { threeRenderService?: any; getShaderPickStat: () => boolean; + updateModelData(data: IAttrubuteAndElements): void; addMaskLayer(maskLayer: ILayer): void; removeMaskLayer(maskLayer: ILayer): void; diff --git a/packages/core/src/services/renderer/IModel.ts b/packages/core/src/services/renderer/IModel.ts index 24cabfcce7..cb0e31ddad 100644 --- a/packages/core/src/services/renderer/IModel.ts +++ b/packages/core/src/services/renderer/IModel.ts @@ -244,6 +244,10 @@ export interface IModelDrawOptions { */ export interface IModel { updateAttributes(attributes: { [key: string]: IAttribute }): void; + updateAttributesAndElements( + attributes: { [key: string]: IAttribute }, + elements: IElements, + ): void; addUniforms(uniforms: { [key: string]: IUniform }): void; draw(options: IModelDrawOptions, pick?: boolean): void; destroy(): void; diff --git a/packages/layers/src/Geometry/models/plane.ts b/packages/layers/src/Geometry/models/plane.ts index 27bef9f4b9..1eb6ab8d8a 100644 --- a/packages/layers/src/Geometry/models/plane.ts +++ b/packages/layers/src/Geometry/models/plane.ts @@ -1,6 +1,7 @@ import { AttributeType, gl, + IAttrubuteAndElements, IEncodeFeature, IModelUniform, ITexture2D, @@ -15,9 +16,9 @@ import planeVert from '../shaders/plane_vert.glsl'; export default class PlaneModel extends BaseModel { protected texture: ITexture2D; + protected terrainImage: HTMLImageElement; + protected terrainImageLoaded: boolean = false; protected mapTexture: string | undefined; - protected positions: number[]; - protected indices: number[]; public initPlane( width = 1, @@ -88,7 +89,6 @@ export default class PlaneModel extends BaseModel { center = [120, 30], terrainTexture, } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; - const { indices, positions } = this.initPlane( width, height, @@ -96,12 +96,9 @@ export default class PlaneModel extends BaseModel { heightSegments, ...center, ); - this.positions = positions; - this.indices = indices; - if (terrainTexture) { // 存在地形贴图的时候会根据地形贴图对顶点进行偏移 - this.loadTerrainTexture(); + this.loadTerrainTexture(positions, indices); } return { @@ -110,13 +107,6 @@ export default class PlaneModel extends BaseModel { size: 5, }; }; - public planeGeometryUpdateTriangulation = () => { - return { - vertices: this.positions, - indices: this.indices, - size: 5, - }; - }; public getUninforms(): IModelUniform { const { @@ -135,14 +125,12 @@ export default class PlaneModel extends BaseModel { u_mapFlag: mapTexture ? 1 : 0, u_terrainClipHeight: terrainTexture ? terrainClipHeight : -1, u_texture: this.texture, - // u_ModelMatrix: mat4.translate(mat4.create(), mat4.create(), [1, 0, 0]) - // u_ModelMatrix: mat4.rotateZ(mat4.create(), mat4.create(), 10) - // u_ModelMatrix: mat4.rotateZ(mat4.create(), mat4.create(), 10) - // u_ModelMatrix: this.rotateZ() }; } public clearModels(): void { + // @ts-ignore + this.terrainImage = null; this.texture?.destroy(); } @@ -161,7 +149,6 @@ export default class PlaneModel extends BaseModel { }); this.updateTexture(mapTexture); - return [ this.layer.buildLayerModel({ moduleName: 'geometry_plane', @@ -169,6 +156,7 @@ export default class PlaneModel extends BaseModel { fragmentShader: planeFrag, triangulation: this.planeGeometryTriangulation, primitive: gl.TRIANGLES, + // primitive: gl.LINES, depth: { enable: true }, blend: this.getBlend(), stencil: getMask(mask, maskInside), @@ -180,90 +168,41 @@ export default class PlaneModel extends BaseModel { ]; } - public getImageData(img: HTMLImageElement) { - const canvas: HTMLCanvasElement = document.createElement('canvas'); - const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; - const { width, height } = img; - canvas.width = width; - canvas.height = height; - - ctx.drawImage(img, 0, 0, width, height); - const imageData = ctx.getImageData(0, 0, width, height); - - return imageData; - } - - /** - * load terrain texture & offset attribute z - */ - public loadTerrainTexture(): void { - const { - mask = false, - maskInside = true, - widthSegments = 1, - heightSegments = 1, - terrainTexture, - rgb2height = (r: number, g: number, b: number) => r + g + b, - } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; - const terrainImage = new Image(); - terrainImage.crossOrigin = 'anonymous'; - terrainImage.onload = () => { - const imgWidth = terrainImage.width; - const imgHeight = terrainImage.height; - - const imageData = this.getImageData(terrainImage).data; - - const gridX = Math.floor(widthSegments); - const gridY = Math.floor(heightSegments); - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - const widthStep = imgWidth / gridX; - const heihgtStep = imgHeight / gridY; - - for (let iy = 0; iy < gridY1; iy++) { - const imgIndexY = Math.floor(iy * heihgtStep); - const imgLen = imgIndexY * imgWidth; - - for (let ix = 0; ix < gridX1; ix++) { - const imgIndexX = Math.floor(ix * widthStep); - const imgDataIndex = (imgLen + imgIndexX) * 4; - - const r = imageData[imgDataIndex]; - const g = imageData[imgDataIndex + 1]; - const b = imageData[imgDataIndex + 2]; - - const z = (iy * gridX1 + ix) * 5 + 2; - this.positions[z] = rgb2height(r, g, b); - } - } - - this.layer.models = [ - this.layer.buildLayerModel({ - moduleName: 'geometry_plane', - vertexShader: planeVert, - fragmentShader: planeFrag, - triangulation: this.planeGeometryUpdateTriangulation, - primitive: gl.TRIANGLES, - depth: { enable: true }, - blend: this.getBlend(), - stencil: getMask(mask, maskInside), - cull: { - enable: true, - face: gl.BACK, - }, - }), - ]; - this.layerService.renderLayers(); - }; - terrainImage.src = terrainTexture as string; - } - public buildModels() { return this.initModels(); } + public createModelData(options?: any) { + if (options) { + const { + widthSegments: oldwidthSegments, + heightSegments: oldheightSegments, + width: oldwidth, + height: oldheight, + } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; + const { + widthSegments, + heightSegments, + width, + height, + } = options as IGeometryLayerStyleOptions; + this.layer.style({ + widthSegments: + widthSegments !== undefined ? widthSegments : oldwidthSegments, + heightSegments: + heightSegments !== undefined ? heightSegments : oldheightSegments, + width: width !== undefined ? width : oldwidth, + height: height !== undefined ? height : oldheight, + }); + } + const oldFeatures = this.layer.getEncodedData(); + const res = this.styleAttributeService.createAttributesAndIndices( + oldFeatures, + this.planeGeometryTriangulation, + ); + return res; + } + public updateTexture(mapTexture: string | undefined): void { const { createTexture2D } = this.rendererService; @@ -290,6 +229,127 @@ export default class PlaneModel extends BaseModel { } } + protected getImageData(img: HTMLImageElement) { + const canvas: HTMLCanvasElement = document.createElement('canvas'); + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; + const { width, height } = img; + canvas.width = width; + canvas.height = height; + + ctx.drawImage(img, 0, 0, width, height); + const imageData = ctx.getImageData(0, 0, width, height); + + return imageData; + } + + protected translateVertex( + positions: number[], + indices: number[], + image: HTMLImageElement, + widthSegments: number, + heightSegments: number, + rgb2height: (r: number, g: number, b: number) => number, + ) { + const imgWidth = image.width; + const imgHeight = image.height; + const imageData = this.getImageData(image).data; + + const gridX = Math.floor(widthSegments); + const gridY = Math.floor(heightSegments); + + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; + + const widthStep = imgWidth / gridX; + const heihgtStep = imgHeight / gridY; + + for (let iy = 0; iy < gridY1; iy++) { + const imgIndexY = Math.floor(iy * heihgtStep); + const imgLen = imgIndexY * imgWidth; + + for (let ix = 0; ix < gridX1; ix++) { + const imgIndexX = Math.floor(ix * widthStep); + const imgDataIndex = (imgLen + imgIndexX) * 4; + + const r = imageData[imgDataIndex]; + const g = imageData[imgDataIndex + 1]; + const b = imageData[imgDataIndex + 2]; + + const z = (iy * gridX1 + ix) * 5 + 2; + positions[z] = rgb2height(r, g, b); + } + } + + const oldFeatures = this.layer.getEncodedData(); + const modelData = this.styleAttributeService.createAttributesAndIndices( + oldFeatures, + () => { + return { + vertices: positions, + indices, + size: 5, + }; + }, + ); + this.layer.updateModelData(modelData as IAttrubuteAndElements); + this.layerService.renderLayers(); + } + + /** + * load terrain texture & offset attribute z + */ + protected loadTerrainTexture(positions: number[], indices: number[]) { + const { + widthSegments = 1, + heightSegments = 1, + terrainTexture, + rgb2height = (r: number, g: number, b: number) => r + g + b, + } = this.layer.getLayerConfig() as IGeometryLayerStyleOptions; + if (this.terrainImage) { + // 若当前已经存在 image,直接进行偏移计算(LOD) + if (this.terrainImageLoaded) { + this.translateVertex( + positions, + indices, + this.terrainImage, + widthSegments, + heightSegments, + rgb2height, + ); + } else { + this.terrainImage.onload = () => { + this.translateVertex( + positions, + indices, + this.terrainImage, + widthSegments, + heightSegments, + rgb2height, + ); + }; + } + } else { + // 加载地形贴图、根据地形贴图对 planeGeometry 进行偏移 + const terrainImage = new Image(); + this.terrainImage = terrainImage; + terrainImage.crossOrigin = 'anonymous'; + terrainImage.onload = () => { + this.terrainImageLoaded = true; + // 图片加载完,触发事件,可以进行地形图的顶点计算存储 + setTimeout(() => this.layer.emit('terrainImageLoaded', null)); + this.translateVertex( + positions, + indices, + terrainImage, + widthSegments, + heightSegments, + rgb2height, + ); + }; + terrainImage.src = terrainTexture as string; + } + } + protected getConfigSchema() { return { properties: { diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index e809cbef1b..1dd109a32b 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -5,6 +5,7 @@ import { gl, IActiveOption, IAnimateOption, + IAttrubuteAndElements, ICameraService, ICoordinateSystemService, IDataState, @@ -44,6 +45,7 @@ import { ScaleTypes, StyleAttributeField, StyleAttributeOption, + Triangulation, TYPES, } from '@antv/l7-core'; import Source from '@antv/l7-source'; @@ -53,6 +55,7 @@ import { Container } from 'inversify'; import { isEqual, isFunction, isObject, isUndefined } from 'lodash'; import { BlendTypes } from '../utils/blend'; import { handleStyleDataMapping } from '../utils/dataMappingStyle'; +import { calculateData } from '../utils/layerData'; import { createMultiPassRenderer, normalizePasses, @@ -81,6 +84,7 @@ export default class BaseLayer extends EventEmitter public clusterZoom: number = 0; // 聚合等级标记 public layerType?: string | undefined; public isLayerGroup: boolean = false; + public triangulation?: Triangulation | undefined; public dataState: IDataState = { dataSourceNeedUpdate: false, @@ -399,8 +403,55 @@ export default class BaseLayer extends EventEmitter target: this, type: 'add', }); + return this; } + + public updateModelData(data: IAttrubuteAndElements) { + if (data.attributes && data.elements) { + this.models.map((m) => { + m.updateAttributesAndElements(data.attributes, data.elements); + }); + } else { + console.warn('data error'); + } + } + + public createModelData(data: any, option?: ISourceCFG) { + if (this.layerModel.createModelData) { + // 在某些特殊图层中单独构建 attribute & elements + return this.layerModel.createModelData(option); + } + const calEncodeData = this.calculateEncodeData(data, option); + const triangulation = this.triangulation; + if (calEncodeData && triangulation) { + return this.styleAttributeService.createAttributesAndIndices( + calEncodeData, + triangulation, + ); + } else { + return { + attributes: undefined, + elements: undefined, + }; + } + } + + public calculateEncodeData(data: any, option?: ISourceCFG) { + if (this.inited) { + return calculateData( + this, + this.fontService, + this.mapService, + this.styleAttributeService, + data, + option, + ); + } else { + console.warn('layer not inited!'); + return null; + } + } /** * Model初始化前需要更新Model样式 */ diff --git a/packages/layers/src/line/models/line.ts b/packages/layers/src/line/models/line.ts index 7da6950598..dad289eaf5 100644 --- a/packages/layers/src/line/models/line.ts +++ b/packages/layers/src/line/models/line.ts @@ -165,6 +165,7 @@ export default class LineModel extends BaseModel { depth = false, } = this.layer.getLayerConfig() as ILineLayerStyleOptions; const { frag, vert, type } = this.getShaders(); + this.layer.triangulation = LineTriangulation; return [ this.layer.buildLayerModel({ moduleName: 'line_' + type, diff --git a/packages/layers/src/point/models/fill.ts b/packages/layers/src/point/models/fill.ts index 43e1e7867a..250c71f85f 100644 --- a/packages/layers/src/point/models/fill.ts +++ b/packages/layers/src/point/models/fill.ts @@ -201,6 +201,9 @@ export default class FillModel extends BaseModel { // TODO: 判断当前的点图层的模型是普通地图模式还是地球模式 const isGlobel = this.mapService.version === 'GLOBEL'; + this.layer.triangulation = isGlobel + ? GlobelPointFillTriangulation + : PointFillTriangulation; return [ this.layer.buildLayerModel({ moduleName: 'pointfill_' + type, diff --git a/packages/layers/src/point/models/simplePoint.ts b/packages/layers/src/point/models/simplePoint.ts index c18d74d813..12b37d9236 100644 --- a/packages/layers/src/point/models/simplePoint.ts +++ b/packages/layers/src/point/models/simplePoint.ts @@ -103,6 +103,7 @@ export default class SimplePointModel extends BaseModel { mask = false, maskInside = true, } = this.layer.getLayerConfig() as IPointLayerStyleOptions; + this.layer.triangulation = PointTriangulation; return [ this.layer.buildLayerModel({ moduleName: 'simplepoint', diff --git a/packages/layers/src/polygon/models/fill.ts b/packages/layers/src/polygon/models/fill.ts index b7ff5ce79e..a460521940 100644 --- a/packages/layers/src/polygon/models/fill.ts +++ b/packages/layers/src/polygon/models/fill.ts @@ -77,6 +77,9 @@ export default class FillModel extends BaseModel { mask = false, maskInside = true, } = this.layer.getLayerConfig() as IPolygonLayerStyleOptions; + this.layer.triangulation = opacityLinear.enable + ? polygonTriangulationWithCenter + : polygonTriangulation; return [ this.layer.buildLayerModel({ moduleName: 'polygon', diff --git a/packages/layers/src/utils/layerData.ts b/packages/layers/src/utils/layerData.ts new file mode 100644 index 0000000000..15a2cc2a47 --- /dev/null +++ b/packages/layers/src/utils/layerData.ts @@ -0,0 +1,220 @@ +import { + IEncodeFeature, + IFontService, + ILayer, + IMapService, + IParseDataItem, + ISourceCFG, + IStyleAttribute, + IStyleAttributeService, + Position, +} from '@antv/l7-core'; +import { Version } from '@antv/l7-maps'; +import Source from '@antv/l7-source'; +import { isColor, normalize, rgb2arr } from '@antv/l7-utils'; +import { ILineLayerStyleOptions } from '../core/interface'; + +function getArrowPoints(p1: Position, p2: Position) { + const dir = [p2[0] - p1[0], p2[1] - p1[1]]; + const normalizeDir = normalize(dir); + const arrowPoint = [ + p1[0] + normalizeDir[0] * 0.0001, + p1[1] + normalizeDir[1] * 0.0001, + ]; + return arrowPoint; +} + +function adjustData2Amap2Coordinates( + mappedData: IEncodeFeature[], + mapService: IMapService, +) { + // 根据地图的类型判断是否需要对点位数据进行处理, 若是高德2.0则需要对坐标进行相对偏移 + if (mappedData.length > 0 && mapService.version === Version['GAODE2.x']) { + if (typeof mappedData[0].coordinates[0] === 'number') { + // 单个的点数据 + // @ts-ignore + mappedData + // TODO: 避免经纬度被重复计算导致坐标位置偏移 + .filter((d) => !d.originCoordinates) + .map((d) => { + d.version = Version['GAODE2.x']; + // @ts-ignore + d.originCoordinates = cloneDeep(d.coordinates); // 为了兼容高德1.x 需要保存一份原始的经纬度坐标数据(许多上层逻辑依赖经纬度数据) + // @ts-ignore + d.coordinates = this.mapService.lngLatToCoord(d.coordinates); + }); + } else { + // 连续的线、面数据 + // @ts-ignore + mappedData + // TODO: 避免经纬度被重复计算导致坐标位置偏移 + .filter((d) => !d.originCoordinates) + .map((d) => { + d.version = Version['GAODE2.x']; + // @ts-ignore + d.originCoordinates = cloneDeep(d.coordinates); // 为了兼容高德1.x 需要保存一份原始的经纬度坐标数据(许多上层逻辑依赖经纬度数据) + // @ts-ignore + d.coordinates = this.mapService.lngLatToCoords(d.coordinates); + }); + } + } +} + +function adjustData2SimpleCoordinates( + mappedData: IEncodeFeature[], + mapService: IMapService, +) { + if (mappedData.length > 0 && mapService.version === Version.SIMPLE) { + mappedData.map((d) => { + if (!d.simpleCoordinate) { + d.coordinates = unProjectCoordinates(d.coordinates, mapService); + d.simpleCoordinate = true; + } + }); + } +} + +function unProjectCoordinates(coordinates: any, mapService: IMapService) { + if (typeof coordinates[0] === 'number') { + return mapService.simpleMapCoord.unproject(coordinates as [number, number]); + } + + if (coordinates[0] && coordinates[0][0] instanceof Array) { + // @ts-ignore + const coords = []; + coordinates.map((coord: any) => { + // @ts-ignore + const c1 = []; + coord.map((co: any) => { + c1.push(mapService.simpleMapCoord.unproject(co as [number, number])); + }); + // @ts-ignore + coords.push(c1); + }); + // @ts-ignore + return coords; + } else { + // @ts-ignore + const coords = []; + // @ts-ignore + coordinates.map((coord) => { + coords.push( + mapService.simpleMapCoord.unproject(coord as [number, number]), + ); + }); + // @ts-ignore + return coords; + } +} + +function applyAttributeMapping( + attribute: IStyleAttribute, + record: { [key: string]: unknown }, + minimumColor?: string, +) { + if (!attribute.scale) { + return []; + } + const scalers = attribute?.scale?.scalers || []; + const params: unknown[] = []; + + scalers.forEach(({ field }) => { + if (record.hasOwnProperty(field) || attribute.scale?.type === 'variable') { + // TODO:多字段,常量 + params.push(record[field]); + } + }); + + const mappingResult = attribute.mapping ? attribute.mapping(params) : []; + if (attribute.name === 'color' && !isColor(mappingResult[0])) { + return [minimumColor]; + } + return mappingResult; +} + +function mapping( + attributes: IStyleAttribute[], + data: IParseDataItem[], + fontService: IFontService, + mapService: IMapService, + minimumColor?: string, + layer?: ILayer, +): IEncodeFeature[] { + const { + arrow = { + enable: false, + }, + } = layer?.getLayerConfig() as ILineLayerStyleOptions; + const mappedData = data.map((record: IParseDataItem) => { + const encodeRecord: IEncodeFeature = { + id: record._id, + coordinates: record.coordinates, + }; + + attributes + .filter((attribute) => attribute.scale !== undefined) + .forEach((attribute: IStyleAttribute) => { + let values = applyAttributeMapping(attribute, record, minimumColor); + + attribute.needRemapping = false; + + // TODO: 支持每个属性配置 postprocess + if (attribute.name === 'color') { + values = values.map((c: unknown) => { + return rgb2arr(c as string); + }); + } + // @ts-ignore + encodeRecord[attribute.name] = + Array.isArray(values) && values.length === 1 ? values[0] : values; + + // 增加对 layer/text/iconfont unicode 映射的解析 + if (attribute.name === 'shape') { + encodeRecord.shape = fontService.getIconFontKey( + encodeRecord[attribute.name] as string, + ); + } + }); + + if (encodeRecord.shape === 'line' && arrow.enable) { + // 只有在线图层且支持配置箭头的时候进行插入顶点的处理 + const coords = encodeRecord.coordinates as Position[]; + const arrowPoint = getArrowPoints(coords[0], coords[1]); + encodeRecord.coordinates.splice(1, 0, arrowPoint, arrowPoint); + } + return encodeRecord; + }) as IEncodeFeature[]; + // 调整数据兼容 Amap2.0 + adjustData2Amap2Coordinates(mappedData, mapService); + + // 调整数据兼容 SimpleCoordinates + adjustData2SimpleCoordinates(mappedData, mapService); + + return mappedData; +} + +export function calculateData( + layer: ILayer, + fontService: IFontService, + mapService: IMapService, + styleAttributeService: IStyleAttributeService, + data: any, + options: ISourceCFG | undefined, +): IEncodeFeature[] { + const source = new Source(data, options); + const bottomColor = layer.getBottomColor(); + const attributes = styleAttributeService.getLayerStyleAttributes() || []; + const { dataArray } = source.data; + const filterData = dataArray; + + const mappedEncodeData = mapping( + attributes, + filterData, + fontService, + mapService, + bottomColor, + layer, + ); + source.destroy(); + return mappedEncodeData; +} diff --git a/packages/renderer/src/regl/ReglModel.ts b/packages/renderer/src/regl/ReglModel.ts index d634fa2327..257c6ece81 100644 --- a/packages/renderer/src/regl/ReglModel.ts +++ b/packages/renderer/src/regl/ReglModel.ts @@ -1,6 +1,7 @@ import { gl, IAttribute, + IElements, IModel, IModelDrawOptions, IModelInitializationOptions, @@ -107,6 +108,27 @@ export default class ReglModel implements IModel { this.drawParams = drawParams; } + public updateAttributesAndElements( + attributes: { [key: string]: IAttribute }, + elements: IElements, + ) { + const reglAttributes: { [key: string]: regl.Attribute } = {}; + Object.keys(attributes).forEach((name: string) => { + reglAttributes[name] = (attributes[name] as ReglAttribute).get(); + }); + this.drawParams.attributes = reglAttributes; + this.drawParams.elements = (elements as ReglElements).get(); + + this.drawCommand = this.reGl(this.drawParams); + const pickDrawParams = cloneDeep(this.drawParams); + pickDrawParams.blend = { + ...pickDrawParams.blend, + enable: false, + }; + + this.drawPickCommand = this.reGl(pickDrawParams); + } + public updateAttributes(attributes: { [key: string]: IAttribute }) { const reglAttributes: { [key: string]: regl.Attribute } = {}; Object.keys(attributes).forEach((name: string) => { diff --git a/packages/source/src/parser/geojson.ts b/packages/source/src/parser/geojson.ts index bebb05b874..f82fe5cc12 100644 --- a/packages/source/src/parser/geojson.ts +++ b/packages/source/src/parser/geojson.ts @@ -23,6 +23,12 @@ export default function geoJSON( ): IParserData { const resultData: IParseDataItem[] = []; const featureKeys: IFeatureKey = {}; + if (!data.features) { + data.features = []; + return { + dataArray: [], + }; + } data.features = data.features.filter((item: Feature) => { const geometry: Geometry | null = item.geometry as Geometry; return ( diff --git a/stories/MapPerformance/components/updataPointsTimeLine.tsx b/stories/MapPerformance/components/updataPointsTimeLine.tsx new file mode 100644 index 0000000000..d7c0183fa1 --- /dev/null +++ b/stories/MapPerformance/components/updataPointsTimeLine.tsx @@ -0,0 +1,199 @@ +// @ts-nocheck +import { Scene, json } from '@antv/l7'; +import { PointLayer } from '@antv/l7-layers'; +import { GaodeMap } from '@antv/l7-maps'; +import * as React from 'react'; +import { csvParse } from 'd3-dsv'; +import { styled, withStyles } from '@material-ui/core/styles'; +import Slider from '@material-ui/core/Slider'; + +export default class Demo extends React.Component { + private scene: Scene; + private layer: any; + + constructor() { + this.state = { + currentYear: 50, + modelDatas: undefined, + }; + } + public componentWillUnmount() { + this.scene.destroy(); + } + + public getSortedData(dataList: { DateTime: string }[]) { + const res = {}, + years = []; + dataList.map((data) => { + const { DateTime } = data; + const year = DateTime.slice(0, 4); + if (res[year]) { + res[year].push({ + Latitude: Number(data.Latitude), + Longitude: Number(data.Longitude), + Depth: Number(data.Depth), + Magnitude: Number(data.Magnitude), + }); + } else { + years.push(year); + res[year] = []; + res[year].push({ + Latitude: Number(data.Latitude), + Longitude: Number(data.Longitude), + Depth: Number(data.Depth), + Magnitude: Number(data.Magnitude), + }); + } + }); + return { + res, + years, + }; + } + + public getModelDatas(layer, sortedData, years, parser) { + const modelDatas = {}; + years.map((year) => { + modelDatas[year] = layer.createModelData(sortedData[year], parser); + }); + + this.setState({ + modelDatas, + }); + } + + public generateData(size) { + let data = []; + for (let i = 0; i < size; i++) { + data.push({ + lng: Math.random() * 180, + lat: Math.random() * 80 - 40, + c: Math.random() > 0.5 ? '#f00' : '#ff0', + }); + } + return data; + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + center: [-120, 36], + pitch: 0, + zoom: 6, + }), + }); + this.scene = scene; + + scene.on('loaded', () => { + fetch( + 'https://gw.alipayobjects.com/os/bmw-prod/6b15fe03-9d5b-4779-831d-ec30aa2e4738.csv', + ) + .then((res) => res.text()) + .then((res) => { + const originData = csvParse(res); + const { res: sortedData, years } = this.getSortedData(originData); + const parser = { + parser: { + type: 'json', + x: 'Longitude', + y: 'Latitude', + }, + }; + + let layer = new PointLayer() + .source(sortedData[years[0]], parser) + .shape('simple') + .size('Magnitude', (v) => Math.pow(v, 2)) + .color('Magnitude', [ + '#ffffb2', + '#fed976', + '#feb24c', + '#fd8d3c', + '#f03b20', + '#bd0026', + ]) + .style({ + opacity: 0.5, + }); + + scene.addLayer(layer); + this.layer = layer; + + this.getModelDatas(layer, sortedData, years, parser); + }); + }); + } + + public timelinechange(time) { + if (time !== this.state.currentYear) { + this.layer.updateModelData(this.state.modelDatas[time]); + this.scene.render(); + this.setState({ + currentYear: time, + }); + } + } + + public render() { + return ( +
+ {this.state.modelDatas !== undefined && ( + this.timelinechange(e)} + /> + )} +
+ ); + } +} + +const PositionContainer = styled('div')({ + position: 'absolute', + zIndex: 1, + bottom: '40px', + width: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}); + +const SliderInput = withStyles({ + root: { + marginLeft: 12, + width: '40%', + }, + valueLabel: { + '& span': { + background: 'none', + color: '#000', + }, + }, +})(Slider); + +function RangeInput({ min, max, value, onChange }) { + return ( + + onChange(newValue)} + valueLabelDisplay="auto" + valueLabelFormat={(t) => t} + /> + + ); +} diff --git a/stories/MapPerformance/components/updateAttrAndEle.tsx b/stories/MapPerformance/components/updateAttrAndEle.tsx new file mode 100644 index 0000000000..0746efbaef --- /dev/null +++ b/stories/MapPerformance/components/updateAttrAndEle.tsx @@ -0,0 +1,105 @@ +// @ts-nocheck +// @ts-ignore +import { Scene } from '@antv/l7'; +import { PointLayer } from '@antv/l7-layers'; +import { GaodeMap } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class Demo extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public generateData(size) { + let data = []; + for (let i = 0; i < size; i++) { + data.push({ + lng: Math.random() * 180, + lat: Math.random() * 80 - 40, + c: Math.random() > 0.5 ? '#f00' : '#ff0', + }); + } + return data; + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + center: [120, 30], + pitch: 0, + zoom: 2, + }), + }); + + const data1 = this.generateData(1000); + const data2 = this.generateData(10000); + + const layer = new PointLayer() + .source(data1, { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }) + .size(10) + // .color('#f00') + .color('c', (v) => v) + // .shape('circle') + .shape('simple') + .active(true); + + scene.on('loaded', () => { + scene.addLayer(layer); + + let data1cache = layer.createModelData(data1, { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }); + + console.log(data1cache); + + let data2cache = layer.createModelData(data2, { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }); + + let c = 0; + setInterval(() => { + if (c === 0) { + c = 1; + layer.updateModelData(data2cache); + scene.render(); + } else { + c = 0; + layer.updateModelData(data1cache); + scene.render(); + } + }, 1000); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/MapPerformance/components/updateAttrAndEle_line.tsx b/stories/MapPerformance/components/updateAttrAndEle_line.tsx new file mode 100644 index 0000000000..ca4fa20fda --- /dev/null +++ b/stories/MapPerformance/components/updateAttrAndEle_line.tsx @@ -0,0 +1,132 @@ +// @ts-nocheck +// @ts-ignore +import { Scene } from '@antv/l7'; +import { PointLayer, LineLayer } from '@antv/l7-layers'; +import { GaodeMap } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class Demo extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public generateData(size) { + let data = []; + for (let i = 0; i < size; i++) { + data.push({ + lng: Math.random() * 180, + lat: Math.random() * 80 - 40, + c: Math.random() > 0.5 ? '#f00' : '#ff0', + }); + } + return data; + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + center: [110, 30], + pitch: 0, + zoom: 4, + }), + }); + + // const data1 = this.generateData(1000); + // const data2 = this.generateData(10000); + const data1 = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + c: '#f00', + }, + geometry: { + type: 'LineString', + coordinates: [ + [100.37109375, 32.32427558887655], + [101.689453125, 28.844673680771795], + [104.853515625, 30.524413269923986], + ], + }, + }, + ], + }; + + const data2 = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: { + c: '#ff0', + }, + geometry: { + type: 'LineString', + coordinates: [ + [109.6875, 39.90973623453719], + [115.75195312499999, 39.90973623453719], + [109.3359375, 37.579412513438385], + [115.57617187499999, 36.80928470205937], + ], + }, + }, + ], + }; + + const layer = new LineLayer() + .source(data1) + .size(2) + .color('c', (v) => v) + .shape('line') + .active(true); + + scene.on('loaded', () => { + scene.addLayer(layer); + + // let data1cache = layer.createModelData(data1, {}); + + // console.log(data1cache); + + // let data2cache = layer.createModelData(data2, {}); + // console.log('data2cache', data2cache) + + // let c = 0; + // setInterval(() => { + // if (c === 0) { + // c = 1; + // layer.updateModelData(data2cache); + // scene.render(); + // } else { + // c = 0; + // layer.updateModelData(data1cache); + // scene.render(); + // } + // }, 1000); + + setTimeout(() => { + let data2cache = layer.createModelData(data2); + layer.updateModelData(data2cache); + scene.render(); + }, 1000); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/MapPerformance/components/updateAttrAndEle_planeGeometry.tsx b/stories/MapPerformance/components/updateAttrAndEle_planeGeometry.tsx new file mode 100644 index 0000000000..5baec377a6 --- /dev/null +++ b/stories/MapPerformance/components/updateAttrAndEle_planeGeometry.tsx @@ -0,0 +1,121 @@ +// @ts-nocheck +// @ts-ignore +import { Scene } from '@antv/l7'; +import { + PointLayer, + LineLayer, + PolygonLayer, + GeometryLayer, +} from '@antv/l7-layers'; +import { GaodeMap } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class Demo extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public generateData(size) { + let data = []; + for (let i = 0; i < size; i++) { + data.push({ + lng: Math.random() * 180, + lat: Math.random() * 80 - 40, + c: Math.random() > 0.5 ? '#f00' : '#ff0', + }); + } + return data; + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + pitch: 60, + center: [120.1025, 30.2594], + rotation: 160, + zoom: 14, + }), + }); + + const layer = new GeometryLayer() + .style({ + width: 0.074, + height: 0.061, + center: [120.1025, 30.2594], + widthSegments: 100, + heightSegments: 100, + // widthSegments: 10, + // heightSegments: 10, + mapTexture: + 'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*gA0NRbuOF5cAAAAAAAAAAAAAARQnAQ', + terrainTexture: + 'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*eYFaRYlnnOUAAAAAAAAAAAAAARQnAQ', + rgb2height: (r: number, g: number, b: number) => { + let h = + -10000.0 + + (r * 255.0 * 256.0 * 256.0 + g * 255.0 * 256.0 + b * 255.0) * 0.1; + h = h / 20 - 127600; + h = Math.max(0, h); + return h; + }, + }) + .color('#f00'); + + scene.on('loaded', () => { + scene.addLayer(layer); + + let cache10 = null, + cache100 = null; + + layer.on('terrainImageLoaded', () => { + console.log('terrainImageLoaded'); + + cache10 = layer.createModelData([], { + widthSegments: 10, + heightSegments: 10, + }); + + cache100 = layer.createModelData([], { + widthSegments: 100, + heightSegments: 100, + }); + }); + + let currentCache = 'cache100'; + scene.on('zoom', ({ value }) => { + if (!cache10 || !cache100) return; + if (value < 14.5) { + if (currentCache !== 'cache10') { + console.log('set cache10'); + layer.updateModelData(cache10); + currentCache = 'cache10'; + } + } else { + if (currentCache !== 'cache100') { + console.log('set cache100'); + layer.updateModelData(cache100); + currentCache = 'cache100'; + } + } + }); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/MapPerformance/components/updateAttrAndEle_polygon.tsx b/stories/MapPerformance/components/updateAttrAndEle_polygon.tsx new file mode 100644 index 0000000000..41f4842518 --- /dev/null +++ b/stories/MapPerformance/components/updateAttrAndEle_polygon.tsx @@ -0,0 +1,116 @@ +// @ts-nocheck +// @ts-ignore +import { Scene } from '@antv/l7'; +import { PointLayer, LineLayer, PolygonLayer } from '@antv/l7-layers'; +import { GaodeMap } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class Demo extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public generateData(size) { + let data = []; + for (let i = 0; i < size; i++) { + data.push({ + lng: Math.random() * 180, + lat: Math.random() * 80 - 40, + c: Math.random() > 0.5 ? '#f00' : '#ff0', + }); + } + return data; + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new GaodeMap({ + center: [110, 30], + pitch: 0, + zoom: 4, + }), + }); + + const data1 = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [98.4375, 35.746512259918504], + [98.173828125, 30.14512718337613], + [104.94140625, 30.600093873550072], + [98.4375, 35.746512259918504], + ], + ], + }, + }, + ], + }; + + const data2 = { + type: 'FeatureCollection', + features: [ + { + type: 'Feature', + properties: {}, + geometry: { + type: 'Polygon', + coordinates: [ + [ + [110.390625, 38.06539235133249], + [106.435546875, 33.50475906922609], + [112.763671875, 32.47269502206151], + [109.072265625, 27.21555620902969], + [117.24609374999999, 27.605670826465445], + [118.037109375, 30.372875188118016], + [115.927734375, 34.30714385628804], + [111.97265625, 36.87962060502676], + [110.390625, 38.06539235133249], + ], + ], + }, + }, + ], + }; + + const layer = new PolygonLayer() + .source(data1) + .size(2) + .color('#f00') + .shape('fill') + .active(true); + + scene.on('loaded', () => { + scene.addLayer(layer); + + setTimeout(() => { + let data2cache = layer.createModelData(data2); + layer.updateModelData(data2cache); + scene.render(); + }, 1000); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/MapPerformance/map.stories.tsx b/stories/MapPerformance/map.stories.tsx index 654a1259b7..b000beff5f 100644 --- a/stories/MapPerformance/map.stories.tsx +++ b/stories/MapPerformance/map.stories.tsx @@ -1,11 +1,21 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; +import UpdateAttrAndEle from './components/updateAttrAndEle'; +import UpdateAttrAndEle_line from './components/updateAttrAndEle_line'; +import UpdateAttrAndEle_polygon from './components/updateAttrAndEle_polygon'; +import UpdateAttrAndEle_planeGeometry from './components/updateAttrAndEle_planeGeometry'; +import UpdateAttrTimeLine from './components/updataPointsTimeLine'; import PointTest from './components/Map'; import BigLine from './components/BigLine'; import DataUpdate from './components/DataUpdate'; -// @ts-ignore + storiesOf('地图性能检测', module) + .add('更新数据 update point attr&ele', () => ) + .add('更新数据 update line attr&ele', () => ) + .add('更新数据 update polygon attr&ele', () => ) + .add('更新数据 update plane geometry attr&ele', () => ) + .add('更新数据 update updateAttrTimeLine', () => ) .add('点', () => ) .add('BigLine', () => ) .add('DataUpdate', () => ); diff --git a/stories/Object/components/planeTerrain.tsx b/stories/Object/components/planeTerrain.tsx index 03ff518099..fb904627d9 100644 --- a/stories/Object/components/planeTerrain.tsx +++ b/stories/Object/components/planeTerrain.tsx @@ -31,7 +31,7 @@ export default class Demo extends React.Component { center: [120.1025, 30.2594], widthSegments: 200, heightSegments: 200, - terrainClipHeight: 1, + // terrainClipHeight: 1, mapTexture: 'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*gA0NRbuOF5cAAAAAAAAAAAAAARQnAQ', terrainTexture: