Skip to content

Commit

Permalink
polygon layer (#79)
Browse files Browse the repository at this point in the history
* wip: polygon layer

* Updates for latest main

* lint

* remove own copy of exterior

* special case for getPolygonOffset

* updates

* updates

* add comment

* remove unused imports
  • Loading branch information
kylebarron authored Jan 22, 2024
1 parent b128f31 commit f45c0a5
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 8 deletions.
23 changes: 17 additions & 6 deletions examples/polygon/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React, { useState, useEffect } from "react";
import { createRoot } from "react-dom/client";
import { StaticMap, MapContext, NavigationControl } from "react-map-gl";
import DeckGL, { Layer, PickingInfo } from "deck.gl/typed";
import { GeoArrowSolidPolygonLayer } from "@geoarrow/deck.gl-layers";
import { GeoArrowPolygonLayer } from "@geoarrow/deck.gl-layers";
import * as arrow from "apache-arrow";

const GEOARROW_POLYGON_DATA = "http://localhost:8080/utah.feather";
const GEOARROW_POLYGON_DATA = "http://localhost:8080/small.feather";

const INITIAL_VIEW_STATE = {
latitude: 40.63403641639511,
Expand Down Expand Up @@ -38,7 +38,9 @@ function Root() {
const data = await fetch(GEOARROW_POLYGON_DATA);
const buffer = await data.arrayBuffer();
const table = arrow.tableFromIPC(buffer);
setTable(table);
const table2 = new arrow.Table(table.batches.slice(0, 10));
window.table = table2;
setTable(table2);
};

if (!table) {
Expand All @@ -50,11 +52,20 @@ function Root() {

table &&
layers.push(
new GeoArrowSolidPolygonLayer({
new GeoArrowPolygonLayer({
id: "geoarrow-polygons",
stroked: true,
filled: true,
data: table,
getFillColor: [0, 100, 60, 255],
pickable: true,
getFillColor: [0, 100, 60, 160],
getLineColor: [255, 0, 0],
lineWidthMinPixels: 0.1,
extruded: false,
wireframe: true,
// getElevation: 0,
pickable: false,
positionFormat: "XY",
_normalize: false,
autoHighlight: true,
earcutWorkerUrl: new URL(
"https://cdn.jsdelivr.net/npm/@geoarrow/[email protected]/dist/earcut-worker.min.js",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 { GeoArrowPolygonLayer } from "./polygon-layer.js";
export { GeoArrowScatterplotLayer } from "./scatterplot-layer.js";
export { GeoArrowSolidPolygonLayer } from "./solid-polygon-layer.js";
export { GeoArrowTextLayer as _GeoArrowTextLayer } from "./text-layer.js";
Expand All @@ -13,6 +14,7 @@ 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 { GeoArrowPolygonLayerProps } from "./polygon-layer.js";
export type { GeoArrowScatterplotLayerProps } from "./scatterplot-layer.js";
export type { GeoArrowSolidPolygonLayerProps } from "./solid-polygon-layer.js";
export type { GeoArrowTextLayerProps as _GeoArrowTextLayerProps } from "./text-layer.js";
Expand Down
291 changes: 291 additions & 0 deletions src/polygon-layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import {
CompositeLayer,
CompositeLayerProps,
DefaultProps,
Layer,
LayersList,
GetPickingInfoParams,
assert,
} from "@deck.gl/core/typed";
import { PolygonLayer } from "@deck.gl/layers/typed";
import type { PolygonLayerProps } from "@deck.gl/layers/typed";
import * as arrow from "apache-arrow";
import * as ga from "@geoarrow/geoarrow-js";
import { getGeometryVector } from "./utils.js";
import { GeoArrowExtraPickingProps, getPickingInfo } from "./picking.js";
import { ColorAccessor, FloatAccessor, GeoArrowPickingInfo } from "./types.js";
import { EXTENSION_NAME } from "./constants.js";
import { validateAccessors } from "./validate.js";
import { GeoArrowSolidPolygonLayer } from "./solid-polygon-layer.js";
import { GeoArrowPathLayer } from "./path-layer.js";

/** All properties supported by GeoArrowPolygonLayer */
export type GeoArrowPolygonLayerProps = Omit<
PolygonLayerProps,
| "data"
| "getPolygon"
| "getFillColor"
| "getLineColor"
| "getLineWidth"
| "getElevation"
> &
_GeoArrowPolygonLayerProps &
CompositeLayerProps;

/** Properties added by GeoArrowPolygonLayer */
type _GeoArrowPolygonLayerProps = {
data: arrow.Table;

/** Polygon geometry accessor. */
getPolygon?: ga.vector.PolygonVector | ga.vector.MultiPolygonVector;
/** Fill color accessor.
* @default [0, 0, 0, 255]
*/
getFillColor?: ColorAccessor;
/** Stroke color accessor.
* @default [0, 0, 0, 255]
*/
getLineColor?: ColorAccessor;
/**
* Line width value of accessor.
* @default 1
*/
getLineWidth?: FloatAccessor;
/**
* Elevation value of accessor.
*
* Only used if `extruded: true`.
*
* @default 1000
*/
getElevation?: FloatAccessor;

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

// Remove data and getPolygon from the upstream default props
const {
data: _data,
getPolygon: _getPolygon,
..._defaultProps
} = PolygonLayer.defaultProps;

// Default props added by us
const ourDefaultProps: Pick<
GeoArrowPolygonLayerProps,
"_normalize" | "_windingOrder" | "_validate"
> = {
// Note: this diverges from upstream, where here we default to no
// normalization
_normalize: false,
// Note: this diverges from upstream, where here we default to CCW
_windingOrder: "CCW",

_validate: true,
};

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

const defaultLineColor: [number, number, number, number] = [0, 0, 0, 255];
const defaultFillColor: [number, number, number, number] = [0, 0, 0, 255];

export class GeoArrowPolygonLayer<
ExtraProps extends {} = {},
> extends CompositeLayer<Required<GeoArrowPolygonLayerProps> & ExtraProps> {
static defaultProps = defaultProps;
static layerName = "GeoArrowPolygonLayer";

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

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

const polygonVector = getGeometryVector(table, EXTENSION_NAME.POLYGON);
if (polygonVector !== null) {
return this._renderLayers(polygonVector);
}

const MultiPolygonVector = getGeometryVector(
table,
EXTENSION_NAME.MULTIPOLYGON,
);
if (MultiPolygonVector !== null) {
return this._renderLayers(MultiPolygonVector);
}

const geometryColumn = this.props.getPolygon;
if (ga.vector.isPolygonVector(geometryColumn)) {
return this._renderLayers(geometryColumn);
}

if (ga.vector.isMultiPolygonVector(geometryColumn)) {
return this._renderLayers(geometryColumn);
}

throw new Error("geometryColumn not Polygon or MultiPolygon");
}

// NOTE: Here we shouldn't need a split for handling both multi- and single-
// geometries, because the underlying SolidPolygonLayer and PathLayer both
// support multi-* and single- geometries.
_renderLayers(
geometryColumn: ga.vector.PolygonVector | ga.vector.MultiPolygonVector,
): Layer<{}> | LayersList | null {
const { data: table } = this.props;

if (this.props._validate) {
assert(ga.vector.isPolygonVector(geometryColumn));
validateAccessors(this.props, table);
}

let getPath: ga.vector.LineStringVector | ga.vector.MultiLineStringVector;
if (ga.vector.isPolygonVector(geometryColumn)) {
getPath = ga.algorithm.getPolygonExterior(geometryColumn);
} else if (ga.vector.isMultiPolygonVector(geometryColumn)) {
getPath = ga.algorithm.getMultiPolygonExterior(geometryColumn);
} else {
assert(false);
}

// Layer composition props
const {
data,
_dataDiff,
stroked,
filled,
extruded,
wireframe,
_normalize,
_windingOrder,
elevationScale,
transitions,
positionFormat,
} = this.props;

// Rendering props underlying layer
const {
lineWidthUnits,
lineWidthScale,
lineWidthMinPixels,
lineWidthMaxPixels,
lineJointRounded,
lineMiterLimit,
lineDashJustified,
} = this.props;

// Accessor props for underlying layers
const {
getFillColor,
getLineColor,
getLineWidth,
getElevation,
getPolygon,
updateTriggers,
material,
} = this.props;

const FillLayer = this.getSubLayerClass("fill", GeoArrowSolidPolygonLayer);
const StrokeLayer = this.getSubLayerClass("stroke", GeoArrowPathLayer);

// Filled Polygon Layer
const polygonLayer = new FillLayer(
{
// _dataDiff,
extruded,
elevationScale,

filled,
wireframe,
_normalize,
_windingOrder,

getElevation,
getFillColor,
getLineColor: extruded && wireframe ? getLineColor : defaultLineColor,

material,
transitions,
},
this.getSubLayerProps({
id: "fill",
updateTriggers: updateTriggers && {
getPolygon: updateTriggers.getPolygon,
getElevation: updateTriggers.getElevation,
getFillColor: updateTriggers.getFillColor,
getLineColor: updateTriggers.getLineColor,
},
}),
{
data,
positionFormat,
getPolygon,
},
);

// Polygon line layer
const polygonLineLayer =
!extruded &&
stroked &&
new StrokeLayer(
{
// _dataDiff,
widthUnits: lineWidthUnits,
widthScale: lineWidthScale,
widthMinPixels: lineWidthMinPixels,
widthMaxPixels: lineWidthMaxPixels,
jointRounded: lineJointRounded,
miterLimit: lineMiterLimit,
dashJustified: lineDashJustified,

// Already normalized, and since they had been polygons, we know that
// the lines are a loop.
_pathType: "loop",

transitions: transitions && {
getWidth: transitions.getLineWidth,
getColor: transitions.getLineColor,
getPath: transitions.getPolygon,
},

getColor: this.getSubLayerAccessor(getLineColor),
getWidth: this.getSubLayerAccessor(getLineWidth),
},
this.getSubLayerProps({
id: "stroke",
updateTriggers: updateTriggers && {
getWidth: updateTriggers.getLineWidth,
getColor: updateTriggers.getLineColor,
getDashArray: updateTriggers.getLineDashArray,
},
}),
{
data: table,
positionFormat,
getPath,
},
);

const layers = [
// If not extruded: flat fill layer is drawn below outlines
!extruded && polygonLayer,
polygonLineLayer,
// If extruded: draw fill layer last for correct blending behavior
extruded && polygonLayer,
];
return layers;
}
}
10 changes: 8 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,14 @@ export function assignAccessor(args: AssignAccessorProps) {
};
}
} else if (typeof propInput === "function") {
props[propName] = <In>(_object: null, objectInfo: AccessorContext<In>) =>
wrapAccessorFunction(objectInfo, propInput);
props[propName] = <In>(object: any, objectInfo: AccessorContext<In>) => {
// Special case that doesn't have the same parameters
if (propName === "getPolygonOffset") {
return propInput(object, objectInfo);
}

return wrapAccessorFunction(objectInfo, propInput);
};
} else {
props[propName] = propInput;
}
Expand Down

0 comments on commit f45c0a5

Please sign in to comment.