@deck.gl/carto 9.0.11 → 9.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.0.11",
6
+ "version": "9.0.13",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -48,6 +48,8 @@
48
48
  "@loaders.gl/schema": "^4.2.0",
49
49
  "@loaders.gl/tiles": "^4.2.0",
50
50
  "@luma.gl/constants": "^9.0.11",
51
+ "@luma.gl/core": "^9.0.11",
52
+ "@luma.gl/shadertools": "^9.0.11",
51
53
  "@math.gl/web-mercator": "^4.0.0",
52
54
  "@types/d3-array": "^3.0.2",
53
55
  "@types/d3-color": "^1.4.2",
@@ -69,8 +71,7 @@
69
71
  "@deck.gl/extensions": "^9.0.0",
70
72
  "@deck.gl/geo-layers": "^9.0.0",
71
73
  "@deck.gl/layers": "^9.0.0",
72
- "@loaders.gl/core": "^4.2.0",
73
- "@luma.gl/core": "^9.0.0"
74
+ "@loaders.gl/core": "^4.2.0"
74
75
  },
75
- "gitHead": "0f342da92b42dce68b647687111fcca332e954c0"
76
+ "gitHead": "39b76f3cd7d6041dd36b93dc3e91b740ed2c57f9"
76
77
  }
package/src/index.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import {default as H3TileLayer} from './layers/h3-tile-layer';
2
2
  import {default as _PointLabelLayer} from './layers/point-label-layer';
3
3
  import {default as QuadbinTileLayer} from './layers/quadbin-tile-layer';
4
+ import {default as QuadbinHeatmapTileLayer} from './layers/quadbin-heatmap-tile-layer';
4
5
  import {default as RasterTileLayer} from './layers/raster-tile-layer';
5
6
  import {default as VectorTileLayer} from './layers/vector-tile-layer';
6
7
  const CARTO_LAYERS = {
7
8
  H3TileLayer,
8
9
  _PointLabelLayer,
9
10
  QuadbinTileLayer,
11
+ QuadbinHeatmapTileLayer,
10
12
  RasterTileLayer,
11
13
  VectorTileLayer
12
14
  };
@@ -15,6 +17,7 @@ export {
15
17
  H3TileLayer,
16
18
  _PointLabelLayer,
17
19
  QuadbinTileLayer,
20
+ QuadbinHeatmapTileLayer,
18
21
  RasterTileLayer,
19
22
  VectorTileLayer
20
23
  };
@@ -0,0 +1,196 @@
1
+ import type {ShaderPass} from '@luma.gl/shadertools';
2
+ import {random} from '@luma.gl/shadertools';
3
+ import {Color} from '@deck.gl/core';
4
+ const glsl = (s: TemplateStringsArray) => `${s}`;
5
+
6
+ /**
7
+ * @filter Heatmap
8
+ * @param radiusPixels Blur radius in pixels, controls smoothness of heatmap
9
+ * @param colorDomain Domain to apply to values prior to applying color scale
10
+ * @param color1-6 Colors to use in color scale
11
+ */
12
+
13
+ const fs = glsl`\
14
+ uniform heatmapUniforms {
15
+ float radiusPixels;
16
+ vec2 colorDomain;
17
+ vec3 color1;
18
+ vec3 color2;
19
+ vec3 color3;
20
+ vec3 color4;
21
+ vec3 color5;
22
+ vec3 color6;
23
+ float opacity;
24
+ } heatmap;
25
+
26
+ // Controls quality of heatmap, larger values increase quality at expense of performance
27
+ const float SUPPORT = 8.0;
28
+ const vec4 STOPS = vec4(0.2, 0.4, 0.6, 0.8);
29
+
30
+ vec3 colorGradient(float value) {
31
+ vec3 c1;
32
+ vec3 c2;
33
+ vec2 range;
34
+ if (value < STOPS.x) {
35
+ range = vec2(0.0, STOPS.x);
36
+ c1 = heatmap.color1; c2 = heatmap.color2;
37
+ } else if (value < STOPS.y) {
38
+ range = STOPS.xy;
39
+ c1 = heatmap.color2; c2 = heatmap.color3;
40
+ } else if (value < STOPS.z) {
41
+ range = STOPS.yz;
42
+ c1 = heatmap.color3; c2 = heatmap.color4;
43
+ } else if (value < STOPS.w) {
44
+ range = STOPS.zw;
45
+ c1 = heatmap.color4; c2 = heatmap.color5;
46
+ } else if (value < 1.0 ) {
47
+ range = vec2(STOPS.w, 1.0);
48
+ c1 = heatmap.color5; c2 = heatmap.color6;
49
+ } else {
50
+ // Fade out to white
51
+ range = vec2(1.0, 10.0);
52
+ c1 = heatmap.color6; c2 = vec3(255.0);
53
+ }
54
+
55
+ float f = (value - range.x) / (range.y - range.x);
56
+ return mix(c1, c2, f) / 255.0;
57
+ }
58
+
59
+ const vec3 SHIFT = vec3(1.0, 256.0, 256.0 * 256.0);
60
+ float unpack(vec3 color) {
61
+ return 255.0 * dot(color, SHIFT);
62
+ }
63
+
64
+ vec4 heatmap_sampleColor(sampler2D source, vec2 texSize, vec2 texCoord) {
65
+ float accumulator = 0.0;
66
+
67
+ // Randomize the lookup values to hide the fixed number of samples
68
+ float offset = 0.5 * random(vec3(12.9898, 78.233, 151.7182), 0.0);
69
+
70
+ // Gaussian normalization parameters
71
+ const float sigma = SUPPORT / 3.0;
72
+ const float a = -0.5 / (sigma * sigma);
73
+ const float w0 = 1.0 / (2.0 * 3.141592653589793 * sigma * sigma); // 2D normalization
74
+
75
+ for (float t = -SUPPORT; t <= SUPPORT; t++) {
76
+ for (float s = -SUPPORT; s <= SUPPORT; s++) {
77
+ vec2 percent = (vec2(s, t) + offset - 0.5) / SUPPORT;
78
+ vec2 delta = percent * heatmap.radiusPixels / texSize;
79
+ vec4 offsetColor = texture(source, texCoord + delta);
80
+
81
+ // Unpack float
82
+ float value = unpack(offsetColor.rgb);
83
+
84
+ // Gaussian
85
+ float weight = w0 * exp(a * (s * s + t * t));
86
+
87
+ accumulator += value * weight;
88
+ }
89
+ }
90
+
91
+ // Apply domain
92
+ float f = (accumulator - heatmap.colorDomain[0]) / (heatmap.colorDomain[1] - heatmap.colorDomain[0]);
93
+
94
+ // Color map
95
+ vec4 color = vec4(0.0);
96
+ color.rgb = colorGradient(f);
97
+
98
+ color.a = smoothstep(0.0, 0.1, f);
99
+ color.a = pow(color.a, 1.0 / 2.2);
100
+ color.a *= heatmap.opacity;
101
+
102
+ return color;
103
+ }
104
+ `;
105
+
106
+ const defaultColorRange: Color[] = [
107
+ [255, 255, 178],
108
+ [254, 217, 118],
109
+ [254, 178, 76],
110
+ [253, 141, 60],
111
+ [240, 59, 32],
112
+ [189, 0, 38]
113
+ ];
114
+
115
+ export type HeatmapProps = {
116
+ /**
117
+ * Radius of the circle in pixels, to which the weight of an object is distributed.
118
+ *
119
+ * @default 30
120
+ */
121
+ radiusPixels?: number;
122
+ /**
123
+ * Controls how weight values are mapped to the `colorRange`, as an array of two numbers [`minValue`, `maxValue`].
124
+ *
125
+ * @default [0, 1]
126
+ */
127
+ colorDomain?: [number, number];
128
+ /**
129
+ * Specified as an array of colors [color1, color2, ...].
130
+ *
131
+ * @default `6-class YlOrRd` - [colorbrewer](http://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=6)
132
+ */
133
+ colorRange: Color[];
134
+ opacity: number;
135
+ };
136
+
137
+ export type HeatmapUniforms = {
138
+ radiusPixels?: number;
139
+ colorDomain?: [number, number];
140
+ color1?: [number, number, number];
141
+ color2?: [number, number, number];
142
+ color3?: [number, number, number];
143
+ color4?: [number, number, number];
144
+ color5?: [number, number, number];
145
+ color6?: [number, number, number];
146
+ opacity?: number;
147
+ };
148
+
149
+ export const heatmap: ShaderPass<HeatmapProps, HeatmapUniforms> = {
150
+ name: 'heatmap',
151
+ uniformPropTypes: {
152
+ radiusPixels: {value: 20, min: 0, softMax: 100},
153
+ colorDomain: {value: [0, 1]},
154
+ color1: {value: [0, 0, 0]},
155
+ color2: {value: [0, 0, 0]},
156
+ color3: {value: [0, 0, 0]},
157
+ color4: {value: [0, 0, 0]},
158
+ color5: {value: [0, 0, 0]},
159
+ color6: {value: [0, 0, 0]},
160
+ opacity: {value: 1, min: 0, max: 1}
161
+ },
162
+ uniformTypes: {
163
+ radiusPixels: 'f32',
164
+ colorDomain: 'vec2<f32>',
165
+ color1: 'vec3<f32>',
166
+ color2: 'vec3<f32>',
167
+ color3: 'vec3<f32>',
168
+ color4: 'vec3<f32>',
169
+ color5: 'vec3<f32>',
170
+ color6: 'vec3<f32>',
171
+ opacity: 'f32'
172
+ },
173
+ getUniforms: opts => {
174
+ const {
175
+ colorRange = defaultColorRange,
176
+ radiusPixels = 20,
177
+ colorDomain = [0, 1],
178
+ opacity = 1
179
+ } = opts as HeatmapProps;
180
+ const [color1, color2, color3, color4, color5, color6] = colorRange;
181
+ return {
182
+ color1,
183
+ color2,
184
+ color3,
185
+ color4,
186
+ color5,
187
+ color6,
188
+ radiusPixels,
189
+ colorDomain,
190
+ opacity
191
+ };
192
+ },
193
+ dependencies: [random],
194
+ fs,
195
+ passes: [{sampler: true}]
196
+ };
@@ -0,0 +1,180 @@
1
+ import {Framebuffer, TextureProps} from '@luma.gl/core';
2
+ import {
3
+ CompositeLayer,
4
+ Layer,
5
+ LayerContext,
6
+ LayerProps,
7
+ LayersList,
8
+ PostProcessEffect,
9
+ PostRenderOptions
10
+ } from '@deck.gl/core';
11
+
12
+ const TEXTURE_PROPS: TextureProps = {
13
+ format: 'rgba8unorm',
14
+ mipmaps: false,
15
+ sampler: {
16
+ minFilter: 'linear',
17
+ magFilter: 'linear',
18
+ addressModeU: 'clamp-to-edge',
19
+ addressModeV: 'clamp-to-edge'
20
+ }
21
+ };
22
+
23
+ interface IPostProcessLayer {
24
+ applyPostProcess: () => void;
25
+ enableRTT: Layer['draw'];
26
+ disableRTT: () => void;
27
+ props: LayerProps;
28
+ }
29
+
30
+ type Constructor<T> = (new (...args: any[]) => T) & {layerName: string};
31
+ type DrawableCompositeLayer = CompositeLayer & {
32
+ renderLayers(): Layer<{}> | null | LayersList;
33
+ };
34
+
35
+ function getPostProcessLayer(layer: any): IPostProcessLayer {
36
+ while (layer.parent && !layer.applyPostProcess) {
37
+ layer = layer.parent;
38
+ }
39
+ return layer as unknown as IPostProcessLayer;
40
+ }
41
+
42
+ /**
43
+ * Dummy Layer that draws nothing, just calls back to root Layer
44
+ */
45
+ class DrawCallbackLayer extends Layer {
46
+ static layerName = 'DrawCallbackLayer';
47
+
48
+ initializeState(this: DrawCallbackLayer): void {
49
+ this.id = `draw-callback-${getPostProcessLayer(this).props.id}`;
50
+ }
51
+
52
+ _drawLayer(this: DrawCallbackLayer) {
53
+ getPostProcessLayer(this).applyPostProcess();
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Modifier that marks a layer for Render-to-Target rendering.
59
+ * Resulting layer must be used as a sublayer of a layer created
60
+ * with `PostProcessModifier`
61
+ */
62
+ export function RTTModifier(BaseLayer) {
63
+ return class RTTLayer extends BaseLayer {
64
+ static layerName = `RTT-${BaseLayer.layerName}`;
65
+
66
+ draw(this: RTTLayer, opts: any) {
67
+ const {moduleParameters} = opts;
68
+ const {picking} = moduleParameters;
69
+ const postProcessLayer = getPostProcessLayer(this);
70
+
71
+ if (!picking.isActive) {
72
+ postProcessLayer.enableRTT(opts);
73
+ }
74
+
75
+ // Draw actual layer
76
+ super.draw(opts);
77
+
78
+ if (!picking.isActive) {
79
+ postProcessLayer.disableRTT();
80
+ }
81
+ }
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Modifier that returns the a modified Layer, which applies a
87
+ * postprocess effect to all subLayers created using `RTTModifier`
88
+ */
89
+ export function PostProcessModifier<T extends Constructor<DrawableCompositeLayer>>(
90
+ BaseLayer: T,
91
+ effect: any
92
+ ): T {
93
+ return class PostProcessLayer extends BaseLayer {
94
+ static layerName = `PostProcess${BaseLayer.layerName}`;
95
+
96
+ internalState: any;
97
+
98
+ initializeState(this: PostProcessLayer, context: LayerContext): void {
99
+ super.initializeState(context);
100
+
101
+ this._createTextures();
102
+ this.internalState.postProcess = new PostProcessEffect(effect, this.props);
103
+ this.internalState.postProcess.setup(context);
104
+ }
105
+
106
+ updateState(this: PostProcessLayer, params: any): void {
107
+ super.updateState(params);
108
+ this.internalState.postProcess.setProps(this.props);
109
+ }
110
+
111
+ renderLayers(): PostProcessLayer | null | LayersList {
112
+ let subLayers = super.renderLayers();
113
+ if (!subLayers) {
114
+ return null;
115
+ }
116
+ subLayers = Array.isArray(subLayers) ? subLayers : [subLayers];
117
+ return [...subLayers, new DrawCallbackLayer()];
118
+ }
119
+
120
+ _createTextures(this: PostProcessLayer) {
121
+ const {device} = this.context;
122
+ this.internalState.renderBuffers = [0, 1].map(i => {
123
+ return device.createFramebuffer({
124
+ id: `layer-fbo-${i}`,
125
+ colorAttachments: [device.createTexture(TEXTURE_PROPS)],
126
+ depthStencilAttachment: 'depth16unorm'
127
+ });
128
+ });
129
+ }
130
+
131
+ _resizeBuffers(this: PostProcessLayer, opts: any) {
132
+ // TODO we could likely render to a smaller buffer for better perf
133
+ const {moduleParameters} = opts;
134
+ const {viewport} = this.context;
135
+ const width = moduleParameters.devicePixelRatio * viewport.width;
136
+ const height = moduleParameters.devicePixelRatio * viewport.height;
137
+ this.internalState.renderBuffers.forEach((fbo: Framebuffer) => fbo.resize({width, height}));
138
+ }
139
+
140
+ enableRTT(this: PostProcessLayer, opts: any) {
141
+ this._resizeBuffers(opts);
142
+ this.internalState.originalRenderPass = this.context.renderPass;
143
+
144
+ const [framebuffer] = this.internalState.renderBuffers;
145
+
146
+ // Create new render pass for RTT
147
+ this.internalState.internalRenderPass = this.context.device.beginRenderPass({
148
+ framebuffer,
149
+ parameters: {viewport: [0, 0, framebuffer.width, framebuffer.height]},
150
+ // Only clear on first render
151
+ clearColor: this.internalState.renderInProgress ? false : [0, 0, 0, 0]
152
+ });
153
+ this.internalState.renderInProgress = true;
154
+ this.context.renderPass = this.internalState.internalRenderPass;
155
+ }
156
+
157
+ disableRTT(this: PostProcessLayer) {
158
+ // End render pass, and reinstate original
159
+ this.internalState.internalRenderPass.end();
160
+ this.context.renderPass = this.internalState.originalRenderPass;
161
+ }
162
+
163
+ applyPostProcess(this: PostProcessLayer) {
164
+ if (!this.internalState.renderInProgress) {
165
+ return;
166
+ }
167
+
168
+ // Apply post process effect
169
+ const [inputBuffer, swapBuffer] = this.internalState.renderBuffers;
170
+ const {framebuffer: target} = this.context.renderPass.props;
171
+ this.internalState.postProcess.postRender({
172
+ inputBuffer,
173
+ swapBuffer,
174
+ target
175
+ } as PostRenderOptions);
176
+
177
+ this.internalState.renderInProgress = false;
178
+ }
179
+ };
180
+ }
@@ -0,0 +1,122 @@
1
+ import {getResolution} from 'quadbin';
2
+
3
+ import {Accessor, CompositeLayer, DefaultProps, Layer} from '@deck.gl/core';
4
+ import {SolidPolygonLayer} from '@deck.gl/layers';
5
+
6
+ import {HeatmapProps, heatmap} from './heatmap';
7
+ import {RTTModifier, PostProcessModifier} from './post-process-utils';
8
+ import QuadbinTileLayer, {QuadbinTileLayerProps} from './quadbin-tile-layer';
9
+
10
+ // Modified polygon layer to draw offscreen and output value expected by heatmap
11
+ class RTTSolidPolygonLayer extends RTTModifier(SolidPolygonLayer) {
12
+ static layerName = 'RTTSolidPolygonLayer';
13
+
14
+ getShaders(type) {
15
+ const shaders = super.getShaders(type);
16
+ shaders.inject = {
17
+ 'vs:#decl': 'uniform float cellResolution;',
18
+ 'vs:DECKGL_FILTER_COLOR': `
19
+ const vec3 SHIFT = vec3(1.0, 256.0, 256.0 * 256.0);
20
+ float value = 255.0 * dot(SHIFT, color.rgb);
21
+
22
+ // Keep "power" delivered to screen constant when tiles update
23
+ float relativeZoom = log2(project_uScale) - cellResolution + 3.5; // range 0-1
24
+ float relativeArea = pow(2.0, -2.0 * relativeZoom); // range 0.25-1
25
+ value *= relativeArea;
26
+
27
+ // Pack float into 3 channels to pass to heatmap shader
28
+ color = vec4(mod(vec3(value, floor(value / SHIFT.yz)), 256.0), 255.0) / 255.0;
29
+ `
30
+ };
31
+ return shaders;
32
+ }
33
+
34
+ draw(this, opts: any) {
35
+ const cell0 = this.props!.data[0];
36
+ const cellResolution = Number(getResolution(cell0.id));
37
+ for (const model of this.state.models) {
38
+ model.setUniforms({cellResolution});
39
+ }
40
+
41
+ super.draw(opts);
42
+ }
43
+ }
44
+
45
+ // Modify QuadbinTileLayer to apply heatmap post process effect
46
+ const PostProcessQuadbinTileLayer = PostProcessModifier(QuadbinTileLayer, heatmap);
47
+
48
+ function encodeWeight(w: number) {
49
+ return [w % 256, Math.floor(w / 256), Math.floor(w / (256 * 256))];
50
+ }
51
+
52
+ const defaultProps: DefaultProps<QuadbinHeatmapTileLayerProps> = {
53
+ ...QuadbinTileLayer.defaultProps,
54
+
55
+ getWeight: {type: 'accessor', value: 1}
56
+ };
57
+
58
+ /** All properties supported by QuadbinHeatmapTileLayer. */
59
+ export type QuadbinHeatmapTileLayerProps<DataT = unknown> = _QuadbinHeatmapTileLayerProps<DataT> &
60
+ QuadbinTileLayerProps;
61
+
62
+ /** Properties added by QuadbinHeatmapTileLayer. */
63
+ type _QuadbinHeatmapTileLayerProps<DataT> = QuadbinTileLayerProps<DataT> &
64
+ HeatmapProps & {
65
+ /**
66
+ * The weight of each object.
67
+ *
68
+ * @default 1
69
+ */
70
+ getWeight?: Accessor<DataT, number>;
71
+ };
72
+
73
+ class QuadbinHeatmapTileLayer<DataT = any, ExtraProps extends {} = {}> extends CompositeLayer<
74
+ ExtraProps & Required<_QuadbinHeatmapTileLayerProps<DataT>>
75
+ > {
76
+ static layerName = 'QuadbinHeatmapTileLayer';
77
+ static defaultProps = defaultProps;
78
+
79
+ renderLayers(): Layer {
80
+ const {getWeight, colorDomain, colorRange, radiusPixels, _subLayerProps} = this.props;
81
+
82
+ // Inject modified polygon layer as sublayer into TileLayer
83
+ const subLayerProps = {
84
+ ..._subLayerProps,
85
+ cell: {
86
+ ..._subLayerProps?.cell,
87
+ _subLayerProps: {
88
+ ..._subLayerProps?.cell?._subLayerProps,
89
+ fill: {
90
+ ..._subLayerProps?.cell?._subLayerProps?.fill,
91
+ type: RTTSolidPolygonLayer
92
+ }
93
+ }
94
+ }
95
+ };
96
+
97
+ const getFillColor =
98
+ typeof getWeight === 'function'
99
+ ? (d, info) => encodeWeight(getWeight(d, info))
100
+ : encodeWeight(getWeight);
101
+
102
+ return new PostProcessQuadbinTileLayer(
103
+ this.getSubLayerProps({
104
+ id: 'heatmap',
105
+ data: this.props.data,
106
+
107
+ getFillColor,
108
+
109
+ colorDomain,
110
+ colorRange,
111
+ radiusPixels,
112
+ _subLayerProps: subLayerProps,
113
+
114
+ updateTriggers: {
115
+ getPosition: this.props.updateTriggers.getWeight
116
+ }
117
+ })
118
+ );
119
+ }
120
+ }
121
+
122
+ export default QuadbinHeatmapTileLayer;