UNPKG

@deck.gl/core

Version:

deck.gl core library

274 lines (242 loc) 9.14 kB
// deck.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import Layer, {UpdateParameters} from './layer'; import debug from '../debug/index'; import {flatten} from '../utils/flatten'; import type AttributeManager from './attribute/attribute-manager'; import type {PickingInfo, GetPickingInfoParams} from './picking/pick-info'; import type {FilterContext} from '../passes/layers-pass'; import type {LayersList, LayerContext} from './layer-manager'; import type {CompositeLayerProps, Accessor, AccessorContext} from '../types/layer-props'; import {ConstructorOf} from '../types/types'; import {PROP_TYPES_SYMBOL} from '../lifecycle/constants'; const TRACE_RENDER_LAYERS = 'compositeLayer.renderLayers'; export default abstract class CompositeLayer<PropsT extends {} = {}> extends Layer< PropsT & Required<CompositeLayerProps> > { static layerName: string = 'CompositeLayer'; /** `true` if this layer renders other layers */ get isComposite(): boolean { return true; } /** `true` if the layer renders to screen */ get isDrawable(): boolean { return false; } /** Returns true if all async resources are loaded */ get isLoaded(): boolean { return super.isLoaded && this.getSubLayers().every(layer => layer.isLoaded); } /** Return last rendered sub layers */ getSubLayers(): Layer[] { return (this.internalState && this.internalState.subLayers) || []; } // initializeState is usually not needed for composite layers // Provide empty definition to disable check for missing definition // eslint-disable-next-line @typescript-eslint/no-empty-function initializeState(context: LayerContext): void {} /** Updates selected state members and marks the composite layer to need rerender */ setState(updateObject: any): void { super.setState(updateObject); // Trigger a layer update // Although conceptually layer.draw and compositeLayer.renderLayers are equivalent, // they are executed during different lifecycles. // draw can be called without calling updateState (e.g. most viewport changes), // while renderLayers can only be called during a recursive layer update. this.setNeedsUpdate(); } /** called to augment the info object that is bubbled up from a sublayer override Layer.getPickingInfo() because decoding / setting uniform do not apply to a composite layer. */ getPickingInfo({info}: GetPickingInfoParams): PickingInfo { const {object} = info; const isDataWrapped = object && object.__source && object.__source.parent && object.__source.parent.id === this.id; if (!isDataWrapped) { return info; } // override object with picked data info.object = object.__source.object; info.index = object.__source.index; return info; } // Implement to generate subLayers abstract renderLayers(): Layer | null | LayersList; /** * Filters sub layers at draw time. Return true if the sub layer should be drawn. */ filterSubLayer(context: FilterContext): boolean { return true; } /** Returns true if sub layer needs to be rendered */ protected shouldRenderSubLayer(subLayerId: string, data: any): boolean { return data && data.length; } /** Returns sub layer class for a specific sublayer */ protected getSubLayerClass<T extends Layer>( subLayerId: string, DefaultLayerClass: ConstructorOf<T> ): ConstructorOf<T> { const {_subLayerProps: overridingProps} = this.props; return ( (overridingProps && overridingProps[subLayerId] && (overridingProps[subLayerId].type as ConstructorOf<T>)) || DefaultLayerClass ); } /** When casting user data into another format to pass to sublayers, add reference to the original object and object index */ protected getSubLayerRow<T>(row: T, sourceObject: any, sourceObjectIndex: number): T { // @ts-ignore (TS2339) adding undefined property row.__source = { parent: this, object: sourceObject, index: sourceObjectIndex }; return row; } /** Some composite layers cast user data into another format before passing to sublayers We need to unwrap them before calling the accessor so that they see the original data objects */ protected getSubLayerAccessor<In, Out>(accessor: Accessor<In, Out>): Accessor<In, Out> { if (typeof accessor === 'function') { const objectInfo: AccessorContext<In> = { index: -1, // @ts-ignore accessing resolved data data: this.props.data, target: [] }; return (x: any, i: AccessorContext<In>) => { if (x && x.__source) { objectInfo.index = x.__source.index; // @ts-ignore (TS2349) Out is never a function return accessor(x.__source.object as In, objectInfo); } // @ts-ignore (TS2349) Out is never a function return accessor(x as In, i); }; } return accessor; } /** Returns sub layer props for a specific sublayer */ // eslint-disable-next-line complexity protected getSubLayerProps( sublayerProps: { id?: string; updateTriggers?: Record<string, any>; [propName: string]: any; } = {} ): any { const { opacity, pickable, visible, parameters, getPolygonOffset, highlightedObjectIndex, autoHighlight, highlightColor, coordinateSystem, coordinateOrigin, wrapLongitude, positionFormat, modelMatrix, extensions, fetch, operation, _subLayerProps: overridingProps } = this.props; const newProps = { id: '', updateTriggers: {}, opacity, pickable, visible, parameters, getPolygonOffset, highlightedObjectIndex, autoHighlight, highlightColor, coordinateSystem, coordinateOrigin, wrapLongitude, positionFormat, modelMatrix, extensions, fetch, operation }; const overridingSublayerProps = overridingProps && sublayerProps.id && overridingProps[sublayerProps.id]; const overridingSublayerTriggers = overridingSublayerProps && overridingSublayerProps.updateTriggers; const sublayerId = sublayerProps.id || 'sublayer'; if (overridingSublayerProps) { const propTypes = this.props[PROP_TYPES_SYMBOL]; const subLayerPropTypes = sublayerProps.type ? sublayerProps.type._propTypes : {}; for (const key in overridingSublayerProps) { const propType = subLayerPropTypes[key] || propTypes[key]; // eslint-disable-next-line if (propType && propType.type === 'accessor') { overridingSublayerProps[key] = this.getSubLayerAccessor(overridingSublayerProps[key]); } } } Object.assign( newProps, sublayerProps, // experimental feature that allows users to override sublayer props via parent layer prop overridingSublayerProps ); newProps.id = `${this.props.id}-${sublayerId}`; newProps.updateTriggers = { all: this.props.updateTriggers?.all, ...sublayerProps.updateTriggers, ...overridingSublayerTriggers }; // Pass through extension props // @ts-ignore (TS2532) extensions is always defined after merging with default props for (const extension of extensions) { const passThroughProps = extension.getSubLayerProps.call(this, extension); if (passThroughProps) { Object.assign(newProps, passThroughProps, { updateTriggers: Object.assign(newProps.updateTriggers, passThroughProps.updateTriggers) }); } } return newProps; } /** Update sub layers to highlight the hovered object */ protected _updateAutoHighlight(info: PickingInfo): void { for (const layer of this.getSubLayers()) { layer.updateAutoHighlight(info); } } /** Override base Layer method */ protected _getAttributeManager(): AttributeManager | null { return null; } /** (Internal) Called after an update to rerender sub layers */ protected _postUpdate(updateParams: UpdateParameters<this>, forceUpdate: boolean) { // @ts-ignore (TS2531) this method is only called internally when internalState is defined let subLayers = this.internalState.subLayers as Layer[]; const shouldUpdate = !subLayers || this.needsUpdate(); if (shouldUpdate) { const subLayersList = this.renderLayers(); // Flatten the returned array, removing any null, undefined or false // this allows layers to render sublayers conditionally // (see CompositeLayer.renderLayers docs) subLayers = flatten(subLayersList, Boolean) as Layer[]; // @ts-ignore (TS2531) this method is only called internally when internalState is defined this.internalState.subLayers = subLayers; } debug(TRACE_RENDER_LAYERS, this, shouldUpdate, subLayers); // populate reference to parent layer (this layer) // NOTE: needs to be done even when reusing layers as the parent may have changed for (const layer of subLayers) { layer.parent = this; } } }