diff --git a/package-lock.json b/package-lock.json index 27fef15..445e8c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "devDependencies": { "@deck.gl/aggregation-layers": "^8.9.23", "@deck.gl/core": "^8.9.23", + "@deck.gl/geo-layers": "^8.9.23", "@deck.gl/layers": "^8.9.23", "@math.gl/polygon": "^3.6.2", "@rollup/plugin-terser": "^0.4.3", @@ -29,6 +30,7 @@ "peerDependencies": { "@deck.gl/aggregation-layers": "^8.9.23", "@deck.gl/core": "^8.9.23", + "@deck.gl/geo-layers": "^8.9.23", "@deck.gl/layers": "^8.9.23", "@math.gl/polygon": "^3.6.2", "apache-arrow": "^13.0.0" diff --git a/package.json b/package.json index 57573b5..3980926 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "peerDependencies": { "@deck.gl/aggregation-layers": "^8.9.23", "@deck.gl/core": "^8.9.23", + "@deck.gl/geo-layers": "^8.9.23", "@deck.gl/layers": "^8.9.23", "@math.gl/polygon": "^3.6.2", "apache-arrow": "^13.0.0" @@ -46,6 +47,7 @@ "devDependencies": { "@deck.gl/aggregation-layers": "^8.9.23", "@deck.gl/core": "^8.9.23", + "@deck.gl/geo-layers": "^8.9.23", "@deck.gl/layers": "^8.9.23", "@math.gl/polygon": "^3.6.2", "@rollup/plugin-terser": "^0.4.3", diff --git a/src/arc-layer.ts b/src/arc-layer.ts index d4b4273..399119d 100644 --- a/src/arc-layer.ts +++ b/src/arc-layer.ts @@ -42,7 +42,7 @@ export type GeoArrowArcLayerProps = Omit< /** Properties added by GeoArrowArcLayer */ type _GeoArrowArcLayerProps = { - data?: arrow.Table; + data: arrow.Table; /** * Method called to retrieve the source position of each object. @@ -139,6 +139,8 @@ export class GeoArrowArcLayer< } } + // Note: below we iterate over table batches anyways, so this layer won't + // work as-is if data/table is null validatePointType(sourcePosition.type); validatePointType(targetPosition.type); if (table) { diff --git a/src/column-layer.ts b/src/column-layer.ts new file mode 100644 index 0000000..ff048bc --- /dev/null +++ b/src/column-layer.ts @@ -0,0 +1,234 @@ +import { + CompositeLayer, + CompositeLayerProps, + DefaultProps, + GetPickingInfoParams, + Layer, + LayersList, +} from "@deck.gl/core/typed"; +import { ColumnLayer } from "@deck.gl/layers/typed"; +import type { ColumnLayerProps } from "@deck.gl/layers/typed"; +import * as arrow from "apache-arrow"; +import { + assignAccessor, + getGeometryVector, + getPointChild, + isPointVector, + validateColorVector, + validatePointType, + validateVectorAccessors, +} from "./utils.js"; +import { + ColorAccessor, + FloatAccessor, + GeoArrowPickingInfo, + PointVector, +} from "./types.js"; +import { EXTENSION_NAME } from "./constants.js"; +import { getPickingInfo } from "./picking.js"; + +/** All properties supported by GeoArrowColumnLayer */ +export type GeoArrowColumnLayerProps = Omit< + ColumnLayerProps, + | "data" + | "getPosition" + | "getFillColor" + | "getLineColor" + | "getElevation" + | "getLineWidth" +> & + _GeoArrowColumnLayerProps & + CompositeLayerProps; + +/** Properties added by GeoArrowColumnLayer */ +type _GeoArrowColumnLayerProps = { + data: arrow.Table; + + /** + * Method called to retrieve the position of each column. + * @default object => object.position + */ + getPosition?: PointVector; + + /** + * Fill color value or accessor. + * @default [0, 0, 0, 255] + */ + getFillColor?: ColorAccessor; + + /** + * Line color value or accessor. + * + * @default [0, 0, 0, 255] + */ + getLineColor?: ColorAccessor; + + /** + * The elevation of each cell in meters. + * @default 1000 + */ + getElevation?: FloatAccessor; + + /** + * The width of the outline of the column, in units specified by `lineWidthUnits`. + * + * @default 1 + */ + getLineWidth?: FloatAccessor; + + /** + * If `true`, validate the arrays provided (e.g. chunk lengths) + * @default true + */ + _validate?: boolean; +}; + +// Remove data and getPosition from the upstream default props +const { + data: _data, + getPosition: _getPosition, + ..._defaultProps +} = ColumnLayer.defaultProps; + +const defaultProps: DefaultProps = { + ..._defaultProps, + _validate: true, +}; + +/** + * Render extruded cylinders (tessellated regular polygons) at given + * coordinates. + */ +export class GeoArrowColumnLayer< + ExtraProps extends {} = {} +> extends CompositeLayer & ExtraProps> { + static defaultProps = defaultProps; + static layerName = "GeoArrowColumnLayer"; + + getPickingInfo(params: GetPickingInfoParams): 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 (isPointVector(geometryColumn)) { + return this._renderLayersPoint(geometryColumn); + } + + throw new Error("geometryColumn not point"); + } + + _renderLayersPoint( + geometryColumn: PointVector + ): Layer<{}> | LayersList | null { + const { data: table } = this.props; + + if (this.props._validate) { + const vectorAccessors: arrow.Vector[] = [geometryColumn]; + for (const accessor of [ + this.props.getFillColor, + this.props.getLineColor, + this.props.getLineWidth, + this.props.getElevation, + ]) { + if (accessor instanceof arrow.Vector) { + vectorAccessors.push(accessor); + } + } + + validatePointType(geometryColumn.type); + validateVectorAccessors(table, vectorAccessors); + + if (this.props.getFillColor instanceof arrow.Vector) { + validateColorVector(this.props.getFillColor); + } + if (this.props.getLineColor instanceof arrow.Vector) { + validateColorVector(this.props.getLineColor); + } + } + + const layers: ColumnLayer[] = []; + for ( + let recordBatchIdx = 0; + recordBatchIdx < table.batches.length; + recordBatchIdx++ + ) { + const geometryData = geometryColumn.data[recordBatchIdx]; + const flatCoordsData = getPointChild(geometryData); + const flatCoordinateArray = flatCoordsData.values; + + const props: ColumnLayerProps = { + // @ts-expect-error used for picking purposes + recordBatchIdx, + + id: `${this.props.id}-geoarrow-column-${recordBatchIdx}`, + + diskResolution: this.props.diskResolution, + radius: this.props.radius, + angle: this.props.angle, + vertices: this.props.vertices, + offset: this.props.offset, + coverage: this.props.coverage, + elevationScale: this.props.elevationScale, + filled: this.props.filled, + stroked: this.props.stroked, + extruded: this.props.extruded, + wireframe: this.props.wireframe, + flatShading: this.props.flatShading, + radiusUnits: this.props.radiusUnits, + lineWidthUnits: this.props.lineWidthUnits, + lineWidthScale: this.props.lineWidthScale, + lineWidthMinPixels: this.props.lineWidthMinPixels, + lineWidthMaxPixels: this.props.lineWidthMaxPixels, + material: this.props.material, + + data: { + length: geometryData.length, + attributes: { + getPosition: { + value: flatCoordinateArray, + size: geometryData.type.listSize, + }, + }, + }, + }; + + assignAccessor({ + props, + propName: "getFillColor", + propInput: this.props.getFillColor, + chunkIdx: recordBatchIdx, + }); + assignAccessor({ + props, + propName: "getLineColor", + propInput: this.props.getLineColor, + chunkIdx: recordBatchIdx, + }); + assignAccessor({ + props, + propName: "getElevation", + propInput: this.props.getElevation, + chunkIdx: recordBatchIdx, + }); + assignAccessor({ + props, + propName: "getLineWidth", + propInput: this.props.getLineWidth, + chunkIdx: recordBatchIdx, + }); + + const layer = new ColumnLayer(this.getSubLayerProps(props)); + layers.push(layer); + } + + return layers; + } +} diff --git a/src/h3-hexagon-layer.ts b/src/h3-hexagon-layer.ts new file mode 100644 index 0000000..f417047 --- /dev/null +++ b/src/h3-hexagon-layer.ts @@ -0,0 +1,147 @@ +import { + CompositeLayer, + CompositeLayerProps, + DefaultProps, + GetPickingInfoParams, + Layer, + LayersList, +} from "@deck.gl/core/typed"; +import { H3HexagonLayer } from "@deck.gl/geo-layers/typed"; +import type { H3HexagonLayerProps } from "@deck.gl/geo-layers/typed"; +import * as arrow from "apache-arrow"; +import { + assignAccessor, + extractAccessorsFromProps, + validateColorVector, + validateVectorAccessors, +} from "./utils.js"; +import { GeoArrowPickingInfo } from "./types.js"; +import { getPickingInfo } from "./picking.js"; + +/** All properties supported by GeoArrowH3HexagonLayer */ +export type GeoArrowH3HexagonLayerProps = Omit< + H3HexagonLayerProps, + "data" | "getHexagon" +> & + _GeoArrowH3HexagonLayerProps & + CompositeLayerProps; + +/** Props added by the GeoArrowH3HexagonLayer */ +type _GeoArrowH3HexagonLayerProps = { + data: arrow.Table; + + /** + * Called for each data object to retrieve the quadkey string identifier. + */ + getHexagon: arrow.Vector; + + /** + * If `true`, validate the arrays provided (e.g. chunk lengths) + * @default true + */ + _validate?: boolean; +}; + +// Remove data from the upstream default props +const { + data: _data, + getHexagon: _getHexagon, + ..._defaultProps +} = H3HexagonLayer.defaultProps; + +const defaultProps: DefaultProps = { + ..._defaultProps, + _validate: true, +}; + +export class GeoArrowH3HexagonLayer< + ExtraProps extends {} = {} +> extends CompositeLayer & ExtraProps> { + static defaultProps = defaultProps; + static layerName = "GeoArrowH3HexagonLayer"; + + getPickingInfo(params: GetPickingInfoParams): GeoArrowPickingInfo { + return getPickingInfo(params, this.props.data); + } + + renderLayers(): Layer<{}> | LayersList | null { + return this._renderLayersPoint(); + } + + _renderLayersPoint(): Layer<{}> | LayersList | null { + const { data: table, getHexagon: hexagonColumn } = this.props; + + if (this.props._validate) { + const vectorAccessors: arrow.Vector[] = []; + const colorVectorAccessors: arrow.Vector[] = []; + for (const [accessorName, accessorValue] of Object.entries(this.props)) { + // Is it an accessor + if (accessorName.startsWith("get")) { + // Is it a vector accessor + if (accessorValue instanceof arrow.Vector) { + vectorAccessors.push(accessorValue); + + // Is it a color vector accessor + if (accessorName.endsWith("Color")) { + colorVectorAccessors.push(accessorValue); + } + } + } + } + + validateVectorAccessors(table, vectorAccessors); + for (const colorVectorAccessor of colorVectorAccessors) { + validateColorVector(colorVectorAccessor); + } + } + + const layers: H3HexagonLayer[] = []; + for ( + let recordBatchIdx = 0; + recordBatchIdx < table.batches.length; + recordBatchIdx++ + ) { + const hexData = hexagonColumn.data[recordBatchIdx]; + const hexValues = hexData.values; + + // Exclude manually-set accessors + const [accessors, otherProps] = extractAccessorsFromProps(this.props, [ + "getHexagon", + ]); + + const props: H3HexagonLayerProps = { + ...otherProps, + + // @ts-expect-error used for picking purposes + recordBatchIdx, + + id: `${this.props.id}-geoarrow-arc-${recordBatchIdx}`, + + data: { + length: hexData.length, + attributes: { + getHexagon: { + value: hexValues, + // h3 cells should always be 15 characters...? + size: 15, + }, + }, + }, + }; + + for (const [propName, propInput] of Object.entries(accessors)) { + assignAccessor({ + props, + propName, + propInput, + chunkIdx: recordBatchIdx, + }); + } + + const layer = new H3HexagonLayer(this.getSubLayerProps(props)); + layers.push(layer); + } + + return layers; + } +} diff --git a/src/heatmap-layer.ts b/src/heatmap-layer.ts index b64eccc..8a4e908 100644 --- a/src/heatmap-layer.ts +++ b/src/heatmap-layer.ts @@ -30,7 +30,7 @@ export type GeoArrowHeatmapLayerProps = Omit< /** Properties added by GeoArrowHeatmapLayer */ type _GeoArrowHeatmapLayerProps = { - data?: arrow.Table; + data: arrow.Table; /** * Method called to retrieve the position of each object. diff --git a/src/index.ts b/src/index.ts index 0625551..0e56c0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,14 @@ export { GeoArrowArcLayer } from "./arc-layer.js"; +export { GeoArrowColumnLayer } from "./column-layer.js"; +export { GeoArrowH3HexagonLayer as _GeoArrowH3HexagonLayer } from "./h3-hexagon-layer.js"; export { GeoArrowHeatmapLayer } from "./heatmap-layer.js"; export { GeoArrowPathLayer } from "./path-layer.js"; export { GeoArrowScatterplotLayer } from "./scatterplot-layer.js"; export { GeoArrowSolidPolygonLayer } from "./solid-polygon-layer.js"; export type { GeoArrowArcLayerProps } from "./arc-layer.js"; +export type { GeoArrowColumnLayerProps } from "./column-layer.js"; +export type { GeoArrowH3HexagonLayerProps as _GeoArrowH3HexagonLayerProps } from "./h3-hexagon-layer.js"; export type { GeoArrowHeatmapLayerProps } from "./heatmap-layer.js"; export type { GeoArrowPathLayerProps } from "./path-layer.js"; export type { GeoArrowScatterplotLayerProps } from "./scatterplot-layer.js";