|
| 1 | +import { |
| 2 | + CompositeLayer, |
| 3 | + CompositeLayerProps, |
| 4 | + DefaultProps, |
| 5 | + GetPickingInfoParams, |
| 6 | + Layer, |
| 7 | + LayersList, |
| 8 | + assert, |
| 9 | + Unit, |
| 10 | + Material, |
| 11 | +} from "@deck.gl/core/typed"; |
| 12 | +import { PointCloudLayer } from "@deck.gl/layers/typed"; |
| 13 | +import type { PointCloudLayerProps } from "@deck.gl/layers/typed"; |
| 14 | +import * as arrow from "apache-arrow"; |
| 15 | +import * as ga from "@geoarrow/geoarrow-js"; |
| 16 | +import { |
| 17 | + assignAccessor, |
| 18 | + extractAccessorsFromProps, |
| 19 | + getGeometryVector, |
| 20 | +} from "./utils.js"; |
| 21 | +import { |
| 22 | + GeoArrowExtraPickingProps, |
| 23 | + computeChunkOffsets, |
| 24 | + getPickingInfo, |
| 25 | +} from "./picking.js"; |
| 26 | +import { ColorAccessor, GeoArrowPickingInfo, NormalAccessor } from "./types.js"; |
| 27 | +import { EXTENSION_NAME } from "./constants.js"; |
| 28 | +import { validateAccessors } from "./validate.js"; |
| 29 | + |
| 30 | +/* All properties supported by GeoArrowPointCloudLayer */ |
| 31 | +export type GeoArrowPointCloudLayerProps = Omit< |
| 32 | + PointCloudLayerProps<arrow.Table>, |
| 33 | + "data" | "getPosition" | "getNormal" | "getColor" |
| 34 | +> & |
| 35 | + _GeoArrowPointCloudLayerProps & |
| 36 | + CompositeLayerProps; |
| 37 | + |
| 38 | +/* All properties added by GeoArrowPointCloudLayer */ |
| 39 | +type _GeoArrowPointCloudLayerProps = { |
| 40 | + // data |
| 41 | + data: arrow.Table; |
| 42 | + |
| 43 | + /** |
| 44 | + * If `true`, validate the arrays provided (e.g. chunk lengths) |
| 45 | + * @default true |
| 46 | + */ |
| 47 | + _validate?: boolean; |
| 48 | + |
| 49 | + /** |
| 50 | + * Center position accessor. |
| 51 | + * If not provided, will be inferred by finding a column with extension type |
| 52 | + * `"geoarrow.point"` |
| 53 | + */ |
| 54 | + getPosition?: ga.vector.PointVector; |
| 55 | + |
| 56 | + /** |
| 57 | + * The normal of each object, in `[nx, ny, nz]`. |
| 58 | + * @default [0,0,1] |
| 59 | + */ |
| 60 | + getNormal?: NormalAccessor; |
| 61 | + |
| 62 | + /** |
| 63 | + * The rgba color is in the format of `[r, g, b, [a]]` |
| 64 | + * @default [0,0,0,225] |
| 65 | + */ |
| 66 | + getColor?: ColorAccessor; |
| 67 | +}; |
| 68 | + |
| 69 | +// Remove data nd get Position from the upstream default props |
| 70 | +const { |
| 71 | + data: _data, |
| 72 | + getPosition: _getPosition, |
| 73 | + ..._upstreamDefaultProps |
| 74 | +} = PointCloudLayer.defaultProps; |
| 75 | + |
| 76 | +// Default props added by us |
| 77 | +const ourDefaultProps = { |
| 78 | + _validate: true, |
| 79 | +}; |
| 80 | + |
| 81 | +// @ts-expect-error Type error in merging default props with ours |
| 82 | +const defaultProps: DefaultProps<GeoArrowPointCloudLayerProps> = { |
| 83 | + ..._upstreamDefaultProps, |
| 84 | + ...ourDefaultProps, |
| 85 | +}; |
| 86 | + |
| 87 | +export class GeoArrowPointCloudLayer< |
| 88 | + ExtraProps extends {} = {}, |
| 89 | +> extends CompositeLayer<GeoArrowPointCloudLayerProps & ExtraProps> { |
| 90 | + static defaultProps = defaultProps; |
| 91 | + static layerName = "GeoArrowPointCloudLayer"; |
| 92 | + |
| 93 | + getPickingInfo( |
| 94 | + params: GetPickingInfoParams & { |
| 95 | + sourceLayer: { props: GeoArrowExtraPickingProps }; |
| 96 | + }, |
| 97 | + ): GeoArrowPickingInfo { |
| 98 | + return getPickingInfo(params, this.props.data); |
| 99 | + } |
| 100 | + |
| 101 | + renderLayers(): Layer<{}> | LayersList | null { |
| 102 | + const { data: table } = this.props; |
| 103 | + |
| 104 | + const pointVector = getGeometryVector(table, EXTENSION_NAME.POINT); |
| 105 | + if (pointVector !== null) { |
| 106 | + return this._renderLayersPoint(pointVector); |
| 107 | + } |
| 108 | + |
| 109 | + const geometryColumn = this.props.getPosition; |
| 110 | + if ( |
| 111 | + geometryColumn !== undefined && |
| 112 | + ga.vector.isPointVector(geometryColumn) |
| 113 | + ) { |
| 114 | + return this._renderLayersPoint(geometryColumn); |
| 115 | + } |
| 116 | + |
| 117 | + throw new Error("geometryColumn not GeoArrow point"); |
| 118 | + } |
| 119 | + |
| 120 | + _renderLayersPoint( |
| 121 | + geometryColumn: ga.vector.PointVector, |
| 122 | + ): Layer<{}> | LayersList | null { |
| 123 | + const { data: table } = this.props; |
| 124 | + |
| 125 | + if (this.props._validate) { |
| 126 | + assert( |
| 127 | + ga.vector.isPointVector(geometryColumn), |
| 128 | + "The geometry column is not a valid PointVector.", |
| 129 | + ); |
| 130 | + assert( |
| 131 | + geometryColumn.type.listSize === 3, |
| 132 | + "Points of a PointCloudLayer in the geometry column must be three-dimensional.", |
| 133 | + ); |
| 134 | + validateAccessors(this.props, table); |
| 135 | + } |
| 136 | + |
| 137 | + // Exclude manually-set accessors |
| 138 | + const [accessors, otherProps] = extractAccessorsFromProps(this.props, [ |
| 139 | + "getPosition", |
| 140 | + ]); |
| 141 | + const tableOffsets = computeChunkOffsets(table.data); |
| 142 | + |
| 143 | + const layers: PointCloudLayer[] = []; |
| 144 | + for ( |
| 145 | + let recordBatchIdx = 0; |
| 146 | + recordBatchIdx < table.batches.length; |
| 147 | + recordBatchIdx++ |
| 148 | + ) { |
| 149 | + const geometryData = geometryColumn.data[recordBatchIdx]; |
| 150 | + const flatCoordsData = ga.child.getPointChild(geometryData); |
| 151 | + const flatCoordinateArray = flatCoordsData.values; |
| 152 | + |
| 153 | + const props: PointCloudLayerProps = { |
| 154 | + // Note: because this is a composite layer and not doing the rendering |
| 155 | + // itself, we still have to pass in our defaultProps |
| 156 | + ...ourDefaultProps, |
| 157 | + ...otherProps, |
| 158 | + |
| 159 | + // used for picking purposes |
| 160 | + recordBatchIdx, |
| 161 | + tableOffsets, |
| 162 | + |
| 163 | + id: `${this.props.id}-geoarrow-pointcloud-${recordBatchIdx}`, |
| 164 | + data: { |
| 165 | + // @ts-expect-error passed through to enable use by function accessors |
| 166 | + data: table.batches[recordBatchIdx], |
| 167 | + length: geometryData.length, |
| 168 | + attributes: { |
| 169 | + getPosition: { |
| 170 | + value: flatCoordinateArray, |
| 171 | + size: geometryData.type.listSize, |
| 172 | + }, |
| 173 | + }, |
| 174 | + }, |
| 175 | + }; |
| 176 | + for (const [propName, propInput] of Object.entries(accessors)) { |
| 177 | + assignAccessor({ |
| 178 | + props, |
| 179 | + propName, |
| 180 | + propInput, |
| 181 | + chunkIdx: recordBatchIdx, |
| 182 | + }); |
| 183 | + } |
| 184 | + const layer = new PointCloudLayer(this.getSubLayerProps(props)); |
| 185 | + layers.push(layer); |
| 186 | + } |
| 187 | + return layers; |
| 188 | + } |
| 189 | +} |
0 commit comments