Skip to content

Commit bfde800

Browse files
Start implementation of point cloud layer (#96)
* DRAFT: initial commit for pointcloud layer * test push * base pointcloud layer. TODO: verify accessors in PointCloudLayerProps, TODO: verify accessor methods in PointCloudLayer's render methods * update accessors, error handling, remove multipoint support * allows custom normalization function taking vector of fixed size lists * run prettier and typecheck * correct merge and pass typecheck * remove unused imports * omit PointCloudLayerProps overrides, remove TODO comments * remove comment, add data attr to PointCloudLayerProps props --------- Co-authored-by: Kyle Barron <[email protected]>
1 parent 4887ce8 commit bfde800

File tree

4 files changed

+195
-2
lines changed

4 files changed

+195
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ docs_build/
55
*.zip
66
*.feather
77
*.parquet
8+
*.code-workspace
89

910
*.yarn
1011

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/point-cloud-layer.ts

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
}

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ export type TimestampAccessor = arrow.Vector<arrow.List<arrow.Float>>;
6363
export type ColorAccessor =
6464
| arrow.Vector<arrow.FixedSizeList<arrow.Uint8>>
6565
| Accessor<arrow.RecordBatch, Color | Color[]>;
66+
export type NormalAccessor =
67+
| arrow.Vector<arrow.FixedSizeList<arrow.Float32>>
68+
| Accessor<arrow.Table, arrow.Vector<arrow.FixedSizeList<arrow.Float32>>>;

0 commit comments

Comments
 (0)