Skip to content

Commit

Permalink
impl gpu brush with react
Browse files Browse the repository at this point in the history
  • Loading branch information
lixun910 committed Sep 29, 2023
1 parent a56dd8b commit 107ec62
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 36 deletions.
27 changes: 22 additions & 5 deletions src/deckgl-layers/src/geojson-layer/brush-geojson-layer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import {Layer, LayerExtension } from '@deck.gl/core';
import {LayerContext} from '@deck.gl/core/lib/layer';
import GL from '@luma.gl/constants';

import shaderModule from './brush-shader-module';

const defaultProps = {
getCenter: {type: 'accessor', value: [0, 0]},
enableBrushing: false,
brushRectangle: [0, 0, 0, 0],
brushPolygon: []
};

export type BrushGeoJsonExtensionProps = {
getCenter?: () => [number, number];
enableBrushing?: boolean;
brushRectangle?: [number, number, number, number];
brushPolygon?: number[];
};

// Write an extension to brush geojson layer using the drawn polygon:
// an instanced attribute 'instanceHighlighted' is added to the layer to indicate whether the feature is highlighted
// the shader module is modified to discard the feature if instanceHighlighted is 0
// the accessor getHighlighted is used to get the value of instanceHighlighted based on the search result in GeoJsonlayer
// From a test, deck: Updated attributes for 7314969 instances in azfyr45-polygons-fill in 162ms
// From a test, gl deck: Updated attributes for 7314969 instances in azfyr45-polygons-fill in 162ms
export default class BrushGeoJsonExtension extends LayerExtension {
static defaultProps = defaultProps;
static extensionName = 'BrushGeoJsonExtension';
Expand All @@ -28,10 +37,18 @@ export default class BrushGeoJsonExtension extends LayerExtension {
initializeState(this: Layer<BrushGeoJsonExtensionProps>, context: LayerContext, extension: this) {
const attributeManager = this.getAttributeManager();
if (attributeManager) {
attributeManager.addInstanced({
instanceHighlighted: {
size: 1,
accessor: 'getHighlighted'
attributeManager.add({
center: {
size: 2,
accessor: 'getCenter',
shaderAttributes: {
center: {
divisor: 0
},
instanceCenter: {
divisor: 1
}
},
}
});
}
Expand Down
79 changes: 66 additions & 13 deletions src/deckgl-layers/src/geojson-layer/brush-shader-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,68 @@ import {project} from '@deck.gl/core';

import type {BrushGeoJsonExtensionProps} from './brush-geojson-layer';

const vs = ``;
const vs = `
#ifdef NON_INSTANCED_MODEL
#define BRUSH_GEOJSON_ATTRIB center
#else
#define BRUSH_GEOJSON_ATTRIB instanceCenter
#endif
attribute vec2 BRUSH_GEOJSON_ATTRIB;
uniform vec4 brush_rectangle;
uniform vec2 brush_polygon[516];
uniform int brush_polygon_length;
uniform bool brushing_enabled;
float center_in_polygon(vec2 point, vec2 poly[516]) {
float inside = 0.;
float x = point.x, y = point.y;
// for (int i = 0, j = brush_polygon_length - 1; i < brush_polygon_length; j = i++) {
// float xi = poly[i].x;
// float yi = poly[i].y;
// float xj = poly[j].x;
// float yj = poly[j].y;
// if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
// inside = 1. - inside;
// }
// }
return inside;
}
float center_in_rectangle(vec2 point, vec4 rectangle) {
if (point.x >= rectangle.x && point.x <= rectangle.z && point.y >= rectangle.y && point.y <= rectangle.w) {
return 1.;
}
return 0.;
}
`;

const fs = ``;

const inject = {
'vs:#decl': `
attribute float instanceHighlighted;
varying float vHighlighted;
varying float is_visible;
`,
'vs:#main-end': `
if (instanceHighlighted == 0.) {
gl_Position = vec4(0.);
is_visible = 0.;
if (brushing_enabled) {
is_visible = center_in_rectangle(BRUSH_GEOJSON_ATTRIB, brush_rectangle);
// // if (brush_polygon_length > 0 && is_visible == 1.) {
// // is_visible = center_in_polygon(BRUSH_GEOJSON_ATTRIB, brush_polygon);
// // }
// // position the current vertex out of screen
// if (is_visible == 0.) {
// gl_Position = vec4(0.);
// }
}
vHighlighted = instanceHighlighted;
`,
'fs:#decl': `
varying float vHighlighted;
varying float is_visible;
uniform bool brushing_enabled;
`,
'fs:DECKGL_FILTER_COLOR': `
if (vHighlighted == 0.) {
// abandon the fragments if brush_enabled and it is not highlighted
if (brushing_enabled && is_visible == 0.) {
discard;
}
`
Expand All @@ -30,13 +72,24 @@ const inject = {
export default {
name: 'brush-geojson',
dependencies: [project],
vs,
fs,
inject,
vs: vs,
fs: fs,
inject: inject,
getUniforms: (opts?: BrushGeoJsonExtensionProps): Record<string, any> => {
if (!opts) {
if (!opts || !('extensions' in opts)) {
return {};
}
return {};
const {
enableBrushing = false,
brushRectangle = [0, 0, 0, 0],
brushPolygon = []
} = opts;

return {
brushing_enabled: enableBrushing,
brush_rectangle: brushRectangle,
brush_polygon: brushPolygon,
brush_polygon_length: brushPolygon ? brushPolygon.length : 0
};
}
}
6 changes: 2 additions & 4 deletions src/layers/src/base-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import keymirror from 'keymirror';
import {DataFilterExtension} from '@deck.gl/extensions';
import {COORDINATE_SYSTEM} from '@deck.gl/core';
import {TextLayer} from '@deck.gl/layers';
import {BrushGeoJsonExtension} from '@kepler.gl/deckgl-layers';

import DefaultLayerIcon from './default-layer-icon';
import {diffUpdateTriggers} from './layer-update';
Expand Down Expand Up @@ -203,8 +202,7 @@ export const LAYER_ID_LENGTH = 6;

const MAX_SAMPLE_SIZE = 5000;
const defaultDomain: [number, number] = [0, 1];
const dataFilterExtension = new DataFilterExtension({filterSize: MAX_GPU_FILTERS});
const brushGeoJsonExtension = new BrushGeoJsonExtension();
const dataFilterExtension = new DataFilterExtension({ filterSize: MAX_GPU_FILTERS });
const defaultDataAccessor = dc => d => d;
const defaultGetFieldValue = (field, d) => field.valueAccessor(d);

Expand Down Expand Up @@ -1310,7 +1308,7 @@ class Layer {
opacity: this.config.visConfig.opacity,
highlightColor: this.config.highlightColor,
// data filtering
extensions: [dataFilterExtension, brushGeoJsonExtension],
extensions: [dataFilterExtension],
filterRange: gpuFilter ? gpuFilter.filterRange : undefined,

// layer should be visible and if splitMap, shown in to one of panel
Expand Down
37 changes: 27 additions & 10 deletions src/layers/src/geojson-layer/geojson-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import {
} from '@kepler.gl/types';
import {KeplerTable} from '@kepler.gl/table';
import {DataContainerInterface} from '@kepler.gl/utils';
import {BrushGeoJsonExtension} from '@kepler.gl/deckgl-layers';


const SUPPORTED_ANALYZER_TYPES = {
[DATA_TYPES.GEOMETRY]: true,
Expand Down Expand Up @@ -347,8 +349,14 @@ export default class GeoJsonLayer extends Layer {
return null;
}

calculateDataAttribute({dataContainer, filteredIndex}, getPosition) {
return filteredIndex.map(i => this.dataToFeature[i]).filter(d => d);
calculateDataAttribute({ dataContainer, filteredIndex }, getPosition) {
return filteredIndex.map(i => {
const feat = this.dataToFeature[i];
if (feat && feat.properties) {
feat.properties.centroid = this.centroids[i];
}
return feat;
}).filter(d => d);
}

formatLayerData(datasets, oldLayerData) {
Expand All @@ -358,7 +366,10 @@ export default class GeoJsonLayer extends Layer {
const {gpuFilter, dataContainer} = datasets[this.config.dataId];
const {data} = this.updateData(datasets, oldLayerData);

const customFilterValueAccessor = (dc, d, fieldIndex) => {
const customFilterValueAccessor = (dc, d, fieldIndex, filter) => {
if (filter && filter.type === FILTER_TYPES.polygon) {
return d.properties.centroid[0];
}
return dc.valueAt(d.properties.index, fieldIndex);
};
const indexAccessor = f => f.properties.index;
Expand All @@ -367,8 +378,7 @@ export default class GeoJsonLayer extends Layer {
const accessors = this.getAttributeAccessors({dataAccessor, dataContainer});

const queryResultAccessor = d => {
// return (this?.queryBounds && this?.queryBounds?.length === 0) || d.properties.selected ? 1 : 0;
return Math.round(Math.random())
return d.properties.centroid;
};

return {
Expand All @@ -377,7 +387,10 @@ export default class GeoJsonLayer extends Layer {
indexAccessor,
customFilterValueAccessor
),
getHighlighted: queryResultAccessor,
enableBrushing: this.queryBounds.length > 0,
brushRectangle: this.queryBounds.length > 0 ? this.queryBounds : [0, 0, 0, 0],
brushPolygon: [],
getCenter: queryResultAccessor,
...accessors
};
}
Expand Down Expand Up @@ -423,9 +436,13 @@ export default class GeoJsonLayer extends Layer {
getCentroids(): number[][] {
if (this.centroids.length === 0) {
this.centroids = getGeojsonMeanCenters(this.dataToFeature);
console.time('build spatial index');
for (let i = 0; i < this.centroids.length; i++) {
const feat = this.dataToFeature[i];
if (feat && feat.properties) {
feat.properties.centroid = this.centroids[i];
}
}
this.getSpatialIndex();
console.timeEnd('build spatial index');
}
return this.centroids;
}
Expand Down Expand Up @@ -508,8 +525,7 @@ export default class GeoJsonLayer extends Layer {

const updateTriggers = {
...this.getVisualChannelUpdateTriggers(),
getFilterValue: gpuFilter.filterValueUpdateTriggers,
getHighlighted: this.queryBounds
getFilterValue: gpuFilter.filterValueUpdateTriggers
};

const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
Expand Down Expand Up @@ -537,6 +553,7 @@ export default class GeoJsonLayer extends Layer {
capRounded: true,
jointRounded: true,
updateTriggers,
extensions: [...defaultLayerProps.extensions, new BrushGeoJsonExtension()],
_subLayerProps: {
...(featureTypes?.polygon ? {'polygons-stroke': opaOverwrite} : {}),
...(featureTypes?.line ? {linestrings: opaOverwrite} : {}),
Expand Down
6 changes: 2 additions & 4 deletions src/table/src/gpu-filter-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,8 @@ export function getGpuFilterProps(filters: Filter[], dataId: string, fields: Fie
);

if (filter?.type === FILTER_TYPES.polygon) {
filterRange[i][0] = 1;
// get a number that is larger than 1 and changed when bbox changes
filterRange[i][1] = 1 + Math.abs(filter.value.properties.bbox.reduce((accu, d) => accu + d, 0));
console.log(filterRange)
filterRange[i][0] = filter.value.properties.bbox[0];
filterRange[i][1] = filter.value.properties.bbox[2];
triggers[`gpuFilter_${i}`] = filter.id;
} else {
filterRange[i][0] = filter ? filter.value[0] - filter.domain?.[0] : 0;
Expand Down

0 comments on commit 107ec62

Please sign in to comment.