packages/layers/geojson/src/Polygon/PolygonSurfaceLayer.ts (619 lines of code) (raw):

/** * Copyright (C) 2021 Alibaba Group Holding Limited * All rights reserved. */ import { computeBBox, computeBSphere } from '@gs.i/utils-geometry' /** * 基类。 * 可以使用 Layer,自己添加需要的 view; * 也可以使用 StandardLayer,添加好 threeView 和 htmlView 的 Layer,懒人福音。 */ import { StandardLayer, StandardLayerProps } from '@polaris.gl/gsi' import { Mesh, Geom, Attr, PbrMaterial } from '@gs.i/frontend-sdk' import { Color } from '@gs.i/utils-math' import { LineIndicator } from '@polaris.gl/utils-indicator' /** * 内部逻辑依赖 */ import { FeatureCollection } from '@turf/helpers' import { triangulateGeoJSON, CDTGeojsonWithSubdivision, getColorUint, getFeatureStringKey, featureToLinePositions, OptionalDefault, } from '../utils' import { PolygonMatr } from './PolygonMatr' import { Polaris } from '@polaris.gl/base' import { isDISPOSED } from '@gs.i/schema-scene' import { WorkerManager } from '@polaris.gl/utils-worker-manager' import { createWorkers } from '../workers/PolygonGeomWorkerFactory' /** * 配置项 interface */ export type PolygonSurfaceLayerProps = StandardLayerProps & typeof defaultProps & { /** * Data */ data?: FeatureCollection } /** * 配置项 默认值 */ const defaultProps = { /** * Style related */ useTessellation: false, tessellation: 3, getColor: (feature) => '#689826' as string | number, getOpacity: (feature) => 1, getThickness: (feature) => 0, baseAlt: 0, transparent: false, doubleSide: false, metallic: 0.1, roughness: 1.0, /** * Selection related */ genSelectLines: false, selectLinesHeight: 0, hoverLineLevel: 2 as 1 | 2 | 4, hoverLineWidth: 1, hoverLineColor: '#262626', selectLineLevel: 2 as 1 | 2 | 4, selectLineWidth: 2, selectLineColor: '#FFAE0F', /** * Local storage/cache related */ // storeGeomToLocalDB: true, // clearStorage: false, // maxMemCacheCount: 0, // featureStorageKey: 'properties.adcode', /** * Worker params */ workersCount: 0, } /** * 类 */ export class PolygonSurfaceLayer extends StandardLayer<PolygonSurfaceLayerProps> { props: any declare geom: Geom declare matr: PbrMaterial declare mesh: Mesh /** * Select/Hover objects */ declare hoverIndicator: LineIndicator declare selectIndicator: LineIndicator // 记录Polygon中feature的Index range declare featIndexRangeMap: Map<any, Uint32Array> // 记录Polygon中feature的Color attr range declare featColorRangeMap: Map<any, Uint32Array> // 记录feature的ColorUint信息 declare featColorMap: Map<any, Uint32Array> // 记录LineIndicator中feature的偏移信息 declare featLineInfoMap: Map<any, { offset: number; count: number }[]> /** * Worker Manager */ private declare _workerManager: WorkerManager /** * Mem cache */ private _geomCache: Map<string, GeomCacheType> /** * IndexedDB store name */ private _storeName: string private declare _tessellation: number constructor(props: OptionalDefault<PolygonSurfaceLayerProps, typeof defaultProps> = {}) { const _props = { ...defaultProps, ...props, } super(_props) this.props = _props // Local caches this._storeName = 'Polaris_PolygonSurfaceLayer' this._geomCache = new Map() this.matr = new PolygonMatr() this.onRenderOrderChange = (renderOrder) => { if (this.mesh) { this.mesh.renderOrder = renderOrder } if (this.selectIndicator) { this.selectIndicator.gline.renderOrder = renderOrder } if (this.hoverIndicator) { this.hoverIndicator.gline.renderOrder = renderOrder } } this.onViewChange = (cam, polaris) => { if (this.selectIndicator) { const p = polaris as Polaris this.selectIndicator.updateResolution(p.canvasWidth, p.canvasHeight) } if (this.hoverIndicator) { const p = polaris as Polaris this.hoverIndicator.updateResolution(p.canvasWidth, p.canvasHeight) } } this.addEventListener('init', (e) => { const polaris = e.polaris const projection = e.projection this.listenProps(['tessellation'], (e) => { this._tessellation = this.getProps('tessellation') ?? 0 }) this.listenProps(['transparent', 'doubleSide'], (e) => { this.matr.alphaMode = this.getProps('transparent') ? 'BLEND' : 'OPAQUE' this.matr.side = this.getProps('doubleSide') ? 'double' : 'front' this.matr.version++ }) this.listenProps(['metallic', 'roughness'], (e) => { this.matr.metallicFactor = this.getProps('metallic') this.matr.roughnessFactor = this.getProps('roughness') }) // Init WorkerManager // @TODO: what is 'workers' ? @qianxun // this.listenProps(['workers'], (e, done) => { this.listenProps(['workersCount'], (e) => { if (this._workerManager) { throw new Error('can not change props.workersCount') } else { const count = this.getProps('workersCount') if (count > 0) { const workers: Worker[] = createWorkers(count) this._workerManager = new WorkerManager(workers) } } }) // this.listenProps(['workersCount'], (e, done) => { // const count = this.getProps('workersCount') // if (count > 0) { // const workers: Worker[] = [] // for (let i = 0; i < count; i++) { // workers.push(new GeomWorker()) // } // this._workerManager = new WorkerManager(workers) // } // done() // }) // 数据与配置的应用(包括reaction) this.listenProps( [ 'data', 'getThickness', 'baseAlt', 'useTessellation', 'genSelectLines', 'selectLinesHeight', 'selectLineWidth', 'selectLineLevel', 'selectLineColor', ], (e) => { const data = this.getProps('data') if (!(data && Array.isArray(data.features))) { return } this.createGeom(data, projection, polaris) // const cached = undefined // if (cached) { // this.mesh.geometry = cached.geom // if (this.getProps('genSelectLines')) { // this.selectIndicator = cached.selectIndicator // this.hoverIndicator = cached.hoverIndicator // this.selectIndicator.addToLayer(this) // this.hoverIndicator.addToLayer(this) // } // } else { // this.createGeom(data, projection, polaris) // } } ) // Only update color attributes this.listenProps(['getColor', 'getOpacity'], (e) => { this._updateColorsAttr() }) }) } private async createGeom(data, projection, polaris) { this.featIndexRangeMap = new Map() this.featColorRangeMap = new Map() this.featLineInfoMap = new Map() this.featColorMap = new Map() const getThickness = this.getProps('getThickness') const getColor = this.getProps('getColor') const getOpacity = this.getProps('getOpacity') const baseAlt = this.getProps('baseAlt') const genSelectLines = this.getProps('genSelectLines') const lineHeight = this.getProps('selectLinesHeight') const positions: number[] = [] const colors: number[] = [] const indices: number[] = [] const linePosArr: number[][] = [] let linePosOffset = 0 let offset = 0 let results if (this._workerManager) { // Triangulate geojson using workers const geomPendings: Promise<any>[] = [] data.features.forEach((feature, index) => { feature.index = index // Store feature index range let promise if (this.getProps('useTessellation') && !projection.isPlaneProjection) { promise = this._workerManager.execute({ data: { task: 'tessellation', feature, tessellation: this._tessellation, }, transferables: undefined, }) } else { promise = this._workerManager.execute({ data: { task: 'triangulate', feature, }, transferables: undefined, }) } geomPendings.push(promise) }) results = await Promise.all(geomPendings) } else { // Triangulate geojson in main thread results = [] data.features.forEach((feature, index) => { feature.index = index // Store feature index range let result if (this.getProps('useTessellation') && !projection.isPlaneProjection) { result = CDTGeojsonWithSubdivision(feature, Math.pow(2, this._tessellation)) } else { result = triangulateGeoJSON(feature) } result.index = index results.push(result) }) } results.forEach((result) => { const feature = data.features[result.index] const points = result.points const triangles = result.triangles const indexRange = new Uint32Array([indices.length, 0]) const colorRange = new Uint32Array([offset * 4, 0]) for (let i = 0; i < points.length; i += 2) { const xyz = projection.project(points[i], points[i + 1], baseAlt + getThickness(feature)) positions.push(...xyz) } for (let i = 0; i < triangles.length; i++) { indices.push(triangles[i] + offset) } const count = points.length / 2 const offset4 = offset * 4 const color = new Color(getColor(feature)) const alpha = getOpacity(feature) ?? 1.0 const colorUint = getColorUint(color, alpha) for (let i = 0; i < count; i++) { const i4 = i * 4 colors[i4 + 0 + offset4] = colorUint[0] colors[i4 + 1 + offset4] = colorUint[1] colors[i4 + 2 + offset4] = colorUint[2] colors[i4 + 3 + offset4] = colorUint[3] } this.featColorMap.set(feature, colorUint) offset += count // Store index range for feature indexRange[1] = indices.length - 1 // Store feature vert range colorRange[1] = offset * 4 this.featIndexRangeMap.set(feature, indexRange) this.featColorRangeMap.set(feature, colorRange) // LineIndicator geom info creation if (genSelectLines) { const linePos = featureToLinePositions( feature, projection, baseAlt + getThickness(feature) + lineHeight ) if (linePos) { linePos.forEach((positions) => { linePosArr.push(positions) // Cache offset/count info const info = this.featLineInfoMap.get(feature) if (info) { info.push({ offset: linePosOffset, count: positions.length / 3, }) } else { this.featLineInfoMap.set(feature, [ { offset: linePosOffset, count: positions.length / 3, }, ]) } // Offset linePosOffset += positions.length / 3 }) } } }) this.geom = new Geom() this.geom.attributes.position = new Attr(new Float32Array(positions), 3) this.geom.attributes.color = new Attr(new Uint16Array(colors), 4, false, 'DYNAMIC_DRAW') const indicesArray = offset > 65535 ? new Uint32Array(indices) : new Uint16Array(indices) this.geom.indices = new Attr(indicesArray, 1) // disable auto gpu dispose this.geom.attributes.position.disposable = false this.geom.attributes.color.disposable = false this.geom.indices.disposable = false this.geom.boundingSphere = computeBSphere(this.geom) this.geom.boundingBox = computeBBox(this.geom) // regenerate mesh this._generateMesh() this.mesh.geometry = this.geom // Create selection polyline // Remove if existed this._removeIndicator(this.selectIndicator) this._removeIndicator(this.hoverIndicator) if (genSelectLines) { this._genLineIndicators(polaris, linePosArr) } } private _genLineIndicators(polaris, selectPosArr: number[][]) { // Hover indicator const hoverLineWidth = this.getProp('hoverLineWidth') as number const hoverLineColor = this.getProp('hoverLineColor') const hoverLineLevel = this.getProp('hoverLineLevel') let hoverLevel = hoverLineLevel if (hoverLineWidth > 1 && hoverLineLevel === 1) { hoverLevel = 2 } const hoverLineConfig = { level: hoverLevel, opacity: 1.0, lineWidth: hoverLineWidth, useColors: true, resolution: { x: polaris.canvasWidth ?? polaris.width, y: polaris.canvasHeight ?? polaris.height, }, usePerspective: false, dynamic: true, u: false, texture: undefined, renderOrder: this.getProp('renderOrder'), depthTest: true, transparent: true, // alphaTest: 0.0001, } const hoverIndicator = new LineIndicator(selectPosArr, hoverLineConfig, { defaultColor: new Color(0.0, 0.0, 0.0), defaultAlpha: 0.0, highlightColor: new Color(hoverLineColor), highlightAlpha: 1.0, }) hoverIndicator.addToLayer(this) // Select indicator const selectLineWidth = this.getProp('selectLineWidth') as number const selectLineColor = this.getProp('selectLineColor') const selectLineLevel = this.getProp('selectLineLevel') let selectLevel = selectLineLevel if (selectLineWidth > 1 && selectLineLevel === 1) { selectLevel = 2 } const selectLineConfig = { level: selectLevel, opacity: 1.0, lineWidth: selectLineWidth, useColors: true, resolution: { x: polaris.canvasWidth ?? polaris.width, y: polaris.canvasHeight ?? polaris.height, }, usePerspective: false, dynamic: true, u: false, texture: undefined, renderOrder: this.getProp('renderOrder'), depthTest: true, transparent: true, // alphaTest: 0.0001, } const selectIndicator = new LineIndicator(selectPosArr, selectLineConfig, { defaultColor: new Color(0.0, 0.0, 0.0), defaultAlpha: 0.0, highlightColor: new Color(selectLineColor), highlightAlpha: 1.0, }) selectIndicator.addToLayer(this) this.selectIndicator = selectIndicator this.hoverIndicator = hoverIndicator } /** * 获取feature的geometry index range * @param feature * @returns */ getFeatureIndexRange(feature) { return this.featIndexRangeMap.get(feature) } /** * 获取feature的geometry color attribute range * @param feature * @returns */ getFeatureColorRange(feature) { return this.featColorRangeMap.get(feature) } /** * 获取feature的line indicator信息,包含offset, count * @param feature * @returns */ getFeatureLineInfo(feature) { return this.featLineInfoMap.get(feature) } /** * 获取feature的default color信息 * @param feature * @returns */ getFeatureColor(feature) { return this.featColorMap.get(feature) } /** * 更新feature polygon的填充色 * @param feature * @param color * @param alpha */ updateFeatureColor(feature: any, color: Color, alpha: number) { if (!this.geom) return const range = this.getFeatureColorRange(feature) if (!range) return const attr = this.geom.attributes.color if (!attr) return const array = attr.array if (isDISPOSED(array)) return let needsUpdate = false const colorUint = getColorUint(color, alpha) for (let i = range[0]; i < range[1]; i += 4) { if ( array[i + 0] !== colorUint[0] || array[i + 1] !== colorUint[1] || array[i + 2] !== colorUint[2] || array[i + 3] !== colorUint[3] ) { array[i + 0] = colorUint[0] array[i + 1] = colorUint[1] array[i + 2] = colorUint[2] array[i + 3] = colorUint[3] needsUpdate = true } } if (needsUpdate) { !attr.extensions && (attr.extensions = {}) !attr.extensions.EXT_buffer_partial_update && (attr.extensions.EXT_buffer_partial_update = { updateRanges: [] }) const updateRanges = attr.extensions.EXT_buffer_partial_update.updateRanges updateRanges.push({ start: range[0], count: range[1] - range[0], }) attr.version++ } } /** * 恢复Polygon原始填充色 * @param feature */ restoreFeatureColor(feature: any) { if (!this.geom) return const attr = this.geom.attributes.color if (!attr) return const array = attr.array if (isDISPOSED(array)) return const colorUint = this.getFeatureColor(feature) if (!colorUint) return const range = this.getFeatureColorRange(feature) if (!range) return let needsUpdate = false for (let i = range[0]; i < range[1]; i += 4) { if ( array[i + 0] !== colorUint[0] || array[i + 1] !== colorUint[1] || array[i + 2] !== colorUint[2] || array[i + 3] !== colorUint[3] ) { array[i + 0] = colorUint[0] array[i + 1] = colorUint[1] array[i + 2] = colorUint[2] array[i + 3] = colorUint[3] needsUpdate = true } } if (needsUpdate) { !attr.extensions && (attr.extensions = {}) !attr.extensions.EXT_buffer_partial_update && (attr.extensions.EXT_buffer_partial_update = { updateRanges: [] }) const updateRanges = attr.extensions.EXT_buffer_partial_update.updateRanges updateRanges.push({ start: range[0], count: range[1] - range[0], }) attr.version++ } } /** * 更新SelectLine高亮样式 * @param feature */ updateSelectLineHighlight(feature) { this._updateFeatureHighlight(feature, 'select') } restoreSelectLineHighlight(feature) { this._restoreFeatureHighlight(feature, 'select') } /** * 恢复SelectLine原始Default样式 */ restoreSelectLines() { this._restoreLineColors('select') } /** * 清除SelectLine的updateRanges,即清除之前的更新信息 */ clearSelectLineUpdteRanges() { this._clearLineUpdteRanges('select') } /** * 更新HoverLine高亮样式 * @param feature */ updateHoverLineHighlight(feature) { this._updateFeatureHighlight(feature, 'hover') } restoreHoverLineHighlight(feature) { this._restoreFeatureHighlight(feature, 'hover') } /** * 恢复HoverLine原始Default样式 */ restoreHoverLines() { this._restoreLineColors('hover') } /** * 清除HoverLine的updateRanges,即清除之前的更新信息 */ clearHoverLineUpdteRanges() { this._clearLineUpdteRanges('hover') } private _generateMesh() { if (this.mesh) { this.group.remove(this.mesh) } this.mesh = new Mesh({ name: 'PolygonSurface', material: this.matr }) this.group.add(this.mesh) } private _updateFeatureHighlight(feature, mode: 'select' | 'hover') { const info = this.featLineInfoMap.get(feature) if (info) { let indicator: LineIndicator switch (mode) { case 'select': indicator = this.selectIndicator break case 'hover': indicator = this.hoverIndicator break default: return } info.forEach((item) => { if (indicator) { indicator.updateHighlightByOffsetCount(item.offset, item.count) } }) } } private _restoreFeatureHighlight(feature, mode: 'select' | 'hover') { const info = this.featLineInfoMap.get(feature) if (info) { let indicator: LineIndicator switch (mode) { case 'select': indicator = this.selectIndicator break case 'hover': indicator = this.hoverIndicator break default: return } info.forEach((item) => { if (indicator) { indicator.restoreHighlightByOffsetCount(item.offset, item.count) } }) } } private _restoreLineColors(mode: 'select' | 'hover') { let indicator: LineIndicator switch (mode) { case 'select': indicator = this.selectIndicator break case 'hover': indicator = this.hoverIndicator break default: return } if (indicator) { indicator.restoreDefaultColorAll() } } private _clearLineUpdteRanges(mode: 'select' | 'hover') { let indicator: LineIndicator switch (mode) { case 'select': indicator = this.selectIndicator break case 'hover': indicator = this.hoverIndicator break default: return } if (indicator) { // TODD: why? @qianxun // indicator.clearUpdateRanges() } } private _removeIndicator(indicator: LineIndicator) { indicator && indicator.removeFromLayer() } private _updateColorsAttr() { const data = this.getProp('data') if (!(data && Array.isArray(data.features))) { return } if (!this.geom) return const attr = this.geom.attributes.color if (!attr || !attr.array || isDISPOSED(attr.array)) { console.warn( 'Polaris::PolygonSurfaceLayer - Color attr does not exist, or has been disposed. ' ) return } const getColor = this.getProp('getColor') const getOpacity = this.getProp('getOpacity') data.features.forEach((feature) => { const color = new Color(getColor(feature)) const opacity = getOpacity(feature) ?? 1.0 // Update this.updateFeatureColor(feature, color, opacity) // Update cached colorUint this.featColorMap.set(feature, getColorUint(color, opacity)) }) } } type GeomCacheType = { geom: Geom selectIndicator: LineIndicator hoverIndicator: LineIndicator }