Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start implementation of point cloud layer #96

Merged
merged 11 commits into from
Feb 2, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ docs_build/
*.zip
*.feather
*.parquet
*.code-workspace

*.yarn

Expand Down
188 changes: 188 additions & 0 deletions src/point-cloud-layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import {
CompositeLayer,
CompositeLayerProps,
DefaultProps,
GetPickingInfoParams,
Layer,
LayersList,
assert,
Unit,
Material,
} from "@deck.gl/core/typed";
import { PointCloudLayer } from "@deck.gl/layers/typed";
import type { PointCloudLayerProps } from "@deck.gl/layers/typed";
import * as arrow from "apache-arrow";
import * as ga from "@geoarrow/geoarrow-js";
import {
assignAccessor,
extractAccessorsFromProps,
getGeometryVector,
} from "./utils.js";
// TODO which accessors are actually needed for a pointcloud layer
naomatheus marked this conversation as resolved.
Show resolved Hide resolved
import {
GeoArrowExtraPickingProps,
computeChunkOffsets,
getPickingInfo,
} from "./picking.js";
import { ColorAccessor, GeoArrowPickingInfo, NormalAccessor } from "./types.js";
import { EXTENSION_NAME } from "./constants.js";
import { validateAccessors } from "./validate.js";

/* All properties supported by GeoArrowPointCloudLayer */
export type GeoArrowPointCloudLayerProps = Omit<
PointCloudLayerProps<arrow.Table>,
"data" | "getPosition" | "getNormal" | "getColor"
> &
_GeoArrowPointCloudLayerProps &
CompositeLayerProps;

/* All properties added by GeoArrowPointCloudLayer */
type _GeoArrowPointCloudLayerProps = {
// data
data: arrow.Table;

/**
* If `true`, validate the arrays provided (e.g. chunk lengths)
* @default true
*/
_validate?: boolean;

/**
* Center position accessor.
* If not provided, will be inferred by finding a column with extension type
* `"geoarrow.point"`
*/
getPosition?: ga.vector.PointVector;

/**
* The normal of each object, in `[nx, ny, nz]`.
* @default [0,0,1]
*/
getNormal?: NormalAccessor;

/**
* The rgba color is in the format of `[r, g, b, [a]]`
* @default [0,0,0,225]
*/
getColor?: ColorAccessor;
};

// Remove data nd get Position from the upstream default props
const {
data: _data,
getPosition: _getPosition,
..._upstreamDefaultProps
} = PointCloudLayer.defaultProps;

// Default props added by us
const ourDefaultProps = {
_validate: true,
};

// @ts-expect-error Type error in merging default props with ours
const defaultProps: DefaultProps<GeoArrowPointCloudLayerProps> = {
..._upstreamDefaultProps,
...ourDefaultProps,
};

export class GeoArrowPointCloudLayer<
ExtraProps extends {} = {},
> extends CompositeLayer<GeoArrowPointCloudLayerProps & ExtraProps> {
static defaultProps = defaultProps;
static layerName = "GeoArrowPointCloudLayer";

getPickingInfo(
params: GetPickingInfoParams & {
sourceLayer: { props: GeoArrowExtraPickingProps };
},
): GeoArrowPickingInfo {
return getPickingInfo(params, this.props.data);
}

renderLayers(): Layer<{}> | LayersList | null {
const { data: table } = this.props;

const pointVector = getGeometryVector(table, EXTENSION_NAME.POINT);
if (pointVector !== null) {
return this._renderLayersPoint(pointVector);
}

const geometryColumn = this.props.getPosition;
if (
geometryColumn !== undefined &&
ga.vector.isPointVector(geometryColumn)
) {
return this._renderLayersPoint(geometryColumn);
}

throw new Error("geometryColumn not GeoArrow point");
}

_renderLayersPoint(
geometryColumn: ga.vector.PointVector,
): Layer<{}> | LayersList | null {
const { data: table } = this.props;

if (this.props._validate) {
assert(
ga.vector.isPointVector(geometryColumn),
"The geometry column is not a valid PointVector.",
);
assert(
geometryColumn.type.listSize === 3,
"Points of a PointCloudLayer in the geometry column must be three-dimensional.",
);
validateAccessors(this.props, table);
}

// Exclude manually-set accessors
const [accessors, otherProps] = extractAccessorsFromProps(this.props, [
"getPosition",
]);
const tableOffsets = computeChunkOffsets(table.data);

const layers: PointCloudLayer[] = [];
for (
let recordBatchIdx = 0;
recordBatchIdx < table.batches.length;
recordBatchIdx++
) {
const geometryData = geometryColumn.data[recordBatchIdx];
const flatCoordsData = ga.child.getPointChild(geometryData);
const flatCoordinateArray = flatCoordsData.values;

const props: PointCloudLayerProps = {
// Note: because this is a composite layer and not doing the rendering
// itself, we still have to pass in our defaultProps
...ourDefaultProps,
...otherProps,

// @ts-expect-error used for picking purposes
recordBatchIdx,
tableOffsets,

id: `${this.props.id}-geoarrow-pointcloud-${recordBatchIdx}`,
data: {
length: geometryData.length,
naomatheus marked this conversation as resolved.
Show resolved Hide resolved
attributes: {
getPosition: {
value: flatCoordinateArray,
size: geometryData.type.listSize,
},
},
},
};
for (const [propName, propInput] of Object.entries(accessors)) {
assignAccessor({
props,
propName,
propInput,
chunkIdx: recordBatchIdx,
});
}
const layer = new PointCloudLayer(this.getSubLayerProps(props));
layers.push(layer);
}
return layers;
}
}
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ export type TimestampAccessor = arrow.Vector<arrow.List<arrow.Float>>;
export type ColorAccessor =
| arrow.Vector<arrow.FixedSizeList<arrow.Uint8>>
| Accessor<arrow.RecordBatch, Color | Color[]>;
export type NormalAccessor =
| arrow.Vector<arrow.FixedSizeList<arrow.Float32>>
| Accessor<arrow.Table, arrow.Vector<arrow.FixedSizeList<arrow.Float32>>>;
Loading