Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

uber / deck.gl / 13127

29 Aug 2019 - 17:33 coverage increased (+2.5%) to 83.552%
13127

Pull #3507

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
update the component-wrapping-rfc.md
Pull Request #3507: update the component-wrapping-rfc.md

3393 of 4570 branches covered (74.25%)

Branch coverage included in aggregate %.

7066 of 7948 relevant lines covered (88.9%)

3403.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.78
/modules/aggregation-layers/src/screen-grid-layer/screen-grid-layer.js
1
// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
// of this software and associated documentation files (the "Software"), to deal
5
// in the Software without restriction, including without limitation the rights
6
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
// copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in
11
// all copies or substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
// THE SOFTWARE.
20

21
import {Layer, WebMercatorViewport, createIterable, log, experimental} from '@deck.gl/core';
22
const {count} = experimental;
1×
23
import {defaultColorRange, colorRangeToFlatArray} from '../utils/color-utils';
24
import GPUGridAggregator from '../utils/gpu-grid-aggregation/gpu-grid-aggregator';
25
import {AGGREGATION_OPERATION} from '../utils/aggregation-operation-utils';
26

27
import GL from '@luma.gl/constants';
28
import {Model, Geometry, Buffer, isWebGL2} from '@luma.gl/core';
29

30
import vs from './screen-grid-layer-vertex.glsl';
31
import vs_WebGL1 from './screen-grid-layer-vertex-webgl1.glsl';
32
import fs from './screen-grid-layer-fragment.glsl';
33
import fs_WebGL1 from './screen-grid-layer-fragment-webgl1.glsl';
34

35
const DEFAULT_MINCOLOR = [0, 0, 0, 0];
19×
36
const DEFAULT_MAXCOLOR = [0, 255, 0, 255];
1×
37
const AGGREGATION_DATA_UBO_INDEX = 0;
1×
38
const COLOR_PROPS = [`minColor`, `maxColor`, `colorRange`, `colorDomain`];
1×
39

40
const defaultProps = {
1×
41
  cellSizePixels: {value: 100, min: 1},
42
  cellMarginPixels: {value: 2, min: 0, max: 5},
43

44
  colorDomain: null,
45
  colorRange: defaultColorRange,
46

47
  getPosition: {type: 'accessor', value: d => d.position},
1×
48
  getWeight: {type: 'accessor', value: d => [1, 0, 0]},
1×
49

50
  gpuAggregation: true,
51
  aggregation: 'SUM'
52
};
53

54
export default class ScreenGridLayer extends Layer {
55
  getShaders() {
56
    const shaders = isWebGL2(this.context.gl) ? {vs, fs} : {vs: vs_WebGL1, fs: fs_WebGL1};
Branches [[0, 0]] missed. 1×
57
    shaders.modules = ['picking'];
2×
58
    return super.getShaders(shaders);
2×
59
  }
60

61
  initializeState() {
62
    const attributeManager = this.getAttributeManager();
1×
63
    const {gl} = this.context;
3×
64

65
    /* eslint-disable max-len */
66
    attributeManager.addInstanced({
1×
67
      instancePositions: {size: 3, update: this.calculateInstancePositions},
68
      instanceCounts: {
69
        size: 4,
70
        transition: true,
71
        accessor: ['getPosition', 'getWeight'],
72
        update: this.calculateInstanceCounts,
73
        noAlloc: true
74
      }
75
    });
76
    /* eslint-disable max-len */
77

78
    const options = {
1×
79
      id: `${this.id}-aggregator`,
80
      shaderCache: this.context.shaderCache
81
    };
82
    const maxBuffer = this._getMaxCountBuffer(gl);
1×
83
    const weights = {
1×
84
      color: {
85
        size: 1,
86
        operation: AGGREGATION_OPERATION.SUM,
87
        needMax: true,
88
        maxBuffer
89
      }
90
    };
91
    this.setState({
1×
92
      model: this._getModel(gl),
93
      gpuGridAggregator: new GPUGridAggregator(gl, options),
94
      maxBuffer,
95
      weights
96
    });
97

98
    this._setupUniformBuffer();
1×
99
  }
100

101
  shouldUpdateState({changeFlags}) {
102
    return changeFlags.somethingChanged;
1×
103
  }
104

105
  updateState(opts) {
106
    super.updateState(opts);
1×
107

108
    this._updateUniforms(opts);
1×
109

110
    if (opts.changeFlags.dataChanged) {
1×
111
      this._processData();
2×
112
    }
113

114
    const changeFlags = this._getAggregationChangeFlags(opts);
1×
115

116
    if (changeFlags) {
2×
117
      if (changeFlags.cellSizeChanged || changeFlags.viewportChanged) {
1×
118
        this._updateGridParams();
2×
119
      }
120
      const {pointCount} = this.state;
1×
121
      if (pointCount > 0) {
2×
122
        this._updateAggregation(changeFlags);
1×
123
      }
124
    }
125
  }
126

127
  finalizeState() {
128
    super.finalizeState();
1×
129

130
    const {aggregationBuffer, maxBuffer, gpuGridAggregator} = this.state;
1×
131
    gpuGridAggregator.delete();
1×
132
    if (aggregationBuffer) {
Branches [[6, 1]] missed. 1×
133
      aggregationBuffer.delete();
1×
134
    }
UNCOV
135
    if (maxBuffer) {
Branches [[7, 1]] missed. !
136
      maxBuffer.delete();
10,092×
137
    }
138
  }
139

140
  draw({uniforms}) {
141
    const {gl} = this.context;
2×
142
    const {parameters = {}} = this.props;
Branches [[8, 0]] missed. 2×
143
    const minColor = this.props.minColor || DEFAULT_MINCOLOR;
2×
144
    const maxColor = this.props.maxColor || DEFAULT_MAXCOLOR;
2×
145

146
    // If colorDomain not specified we use default domain [1, maxCount]
147
    // maxCount value will be deduced from aggregated buffer in the vertex shader.
148
    const colorDomain = this.props.colorDomain || [1, 0];
2×
149
    const {model, maxBuffer, cellScale, shouldUseMinMax, colorRange, maxWeight} = this.state;
2×
150
    const layerUniforms = {
2×
151
      minColor,
152
      maxColor,
153
      cellScale,
154
      colorRange,
155
      colorDomain,
156
      shouldUseMinMax
157
    };
158

159
    if (isWebGL2(gl)) {
Branches [[12, 0]] missed. 2×
160
      maxBuffer.bind({target: GL.UNIFORM_BUFFER});
2×
161
    } else {
162
      layerUniforms.maxWeight = maxWeight;
2×
163
    }
164
    uniforms = Object.assign(layerUniforms, uniforms);
2×
165
    model.draw({
17×
166
      uniforms,
167
      parameters: Object.assign(
168
        {
169
          depthTest: false,
170
          depthMask: false
171
        },
172
        parameters
173
      )
174
    });
175
    if (isWebGL2(gl)) {
Branches [[13, 0]] missed. 15×
176
      maxBuffer.unbind();
15×
177
    }
178
  }
179

180
  calculateInstancePositions(attribute, {numInstances}) {
181
    const {width, height} = this.context.viewport;
15×
182
    const {cellSizePixels} = this.props;
10×
183
    const {numCol} = this.state;
15×
184
    const {value, size} = attribute;
15×
185

186
    for (let i = 0; i < numInstances; i++) {
14×
187
      const x = i % numCol;
14×
188
      const y = Math.floor(i / numCol);
14×
189
      value[i * size + 0] = ((x * cellSizePixels) / width) * 2 - 1;
12×
190
      value[i * size + 1] = 1 - ((y * cellSizePixels) / height) * 2;
2×
191
      value[i * size + 2] = 0;
2×
192
    }
193
  }
194

195
  calculateInstanceCounts(attribute, {numInstances}) {
196
    const {aggregationBuffer} = this.state;
2×
197
    attribute.update({
2×
198
      buffer: aggregationBuffer
199
    });
200
  }
201

202
  getPickingInfo({info, mode}) {
203
    const {index} = info;
2×
204
    if (index >= 0) {
Branches [[14, 0], [14, 1]] missed. 2×
205
      const {gpuGridAggregator} = this.state;
2×
206
      // Get color aggregation results
207
      const aggregationResults = gpuGridAggregator.getData('color');
17×
208

209
      // Each instance (one cell) is aggregated into single pixel,
210
      // Get current instance's aggregation details.
211
      info.object = GPUGridAggregator.getAggregationData(
17×
212
        Object.assign({pixelIndex: index}, aggregationResults)
213
      );
214
    }
215

216
    return info;
17×
217
  }
218

219
  // HELPER Methods
220

221
  _getAggregationChangeFlags({oldProps, props, changeFlags}) {
222
    const cellSizeChanged =
223
      props.cellSizePixels !== oldProps.cellSizePixels ||
17×
224
      props.cellMarginPixels !== oldProps.cellMarginPixels;
225
    const dataChanged = changeFlags.dataChanged || props.aggregation !== oldProps.aggregation;
17×
226
    const viewportChanged = changeFlags.viewportChanged;
17×
227

228
    if (cellSizeChanged || dataChanged || viewportChanged) {
17×
229
      return {cellSizeChanged, dataChanged, viewportChanged};
17×
230
    }
231

UNCOV
232
    return null;
!
233
  }
234

235
  _getModel(gl) {
236
    return new Model(
17×
237
      gl,
238
      Object.assign({}, this.getShaders(), {
239
        id: this.props.id,
240
        geometry: new Geometry({
241
          drawMode: GL.TRIANGLE_FAN,
242
          attributes: {
243
            positions: new Float32Array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0])
244
          }
245
        }),
246
        isInstanced: true,
247
        shaderCache: this.context.shaderCache
248
      })
249
    );
250
  }
251

252
  // Creates and returns a Uniform Buffer object to hold maxCount value.
253
  _getMaxCountBuffer(gl) {
254
    return new Buffer(gl, {
17×
255
      byteLength: 4 * 4, // Four floats
256
      index: AGGREGATION_DATA_UBO_INDEX,
257
      accessor: {
258
        size: 4
259
      }
260
    });
261
  }
262

263
  // Process 'data' and build positions and weights Arrays.
264
  _processData() {
265
    const {data, getPosition, getWeight} = this.props;
17×
266
    const pointCount = count(data);
17×
UNCOV
267
    const positions = new Float64Array(pointCount * 2);
!
268
    const colorWeights = new Float32Array(pointCount * 3);
14×
269
    const {weights} = this.state;
14×
270

271
    const {iterable, objectInfo} = createIterable(data);
14×
272
    for (const object of iterable) {
14×
273
      objectInfo.index++;
14×
274
      const position = getPosition(object, objectInfo);
14×
275
      const weight = getWeight(object, objectInfo);
40,010×
276
      const {index} = objectInfo;
40,010×
277

278
      positions[index * 2] = position[0];
40,010×
279
      positions[index * 2 + 1] = position[1];
40,010×
280

281
      if (Array.isArray(weight)) {
Branches [[19, 1]] missed. 40,010×
282
        colorWeights[index * 3] = weight[0];
14×
283
        colorWeights[index * 3 + 1] = weight[1];
14×
UNCOV
284
        colorWeights[index * 3 + 2] = weight[2];
!
285
      } else {
286
        // backward compitability
287
        colorWeights[index * 3] = weight;
!
288
      }
289
    }
UNCOV
290
    weights.color.values = colorWeights;
!
UNCOV
291
    this.setState({positions, pointCount});
!
292
  }
293

294
  // Set a binding point for the aggregation uniform block index
295
  _setupUniformBuffer() {
UNCOV
296
    const gl = this.context.gl;
!
297
    // For WebGL1, uniform buffer is not used.
UNCOV
298
    if (!isWebGL2(gl)) {
Branches [[20, 1]] missed. !
299
      return;
15×
300
    }
301
    const programHandle = this.state.model.program.handle;
15×
302

303
    // TODO: Replace with luma.gl api when ready.
304
    const uniformBlockIndex = gl.getUniformBlockIndex(programHandle, 'AggregationData');
15×
305
    gl.uniformBlockBinding(programHandle, uniformBlockIndex, AGGREGATION_DATA_UBO_INDEX);
15×
306
  }
307

308
  _shouldUseMinMax() {
309
    const {minColor, maxColor, colorDomain, colorRange} = this.props;
14×
310
    if (minColor || maxColor) {
Branches [[21, 0]] missed. 1×
311
      log.deprecated('ScreenGridLayer props: minColor and maxColor', 'colorRange, colorDomain')();
2×
312
      return true;
2×
313
    }
314
    // minColor and maxColor not supplied, check if colorRange or colorDomain supplied.
315
    // NOTE: colorDomain and colorRange are experimental features, use them only when supplied.
316
    if (colorDomain || colorRange) {
Branches [[23, 1]] missed. 10×
317
      return false;
10×
318
    }
319
    // None specified, use default minColor and maxColor
320
    return true;
10×
321
  }
322

323
  _updateAggregation(changeFlags) {
324
    const attributeManager = this.getAttributeManager();
10×
325
    const {cellSizePixels, gpuAggregation} = this.props;
10×
326

327
    const {positions, weights} = this.state;
10×
328
    const {viewport} = this.context;
10×
329

330
    weights.color.operation =
10,092×
331
      AGGREGATION_OPERATION[this.props.aggregation.toUpperCase()] || AGGREGATION_OPERATION.SUM;
Branches [[25, 1]] missed.
332

333
    let projectPoints = false;
10,092×
334
    let gridTransformMatrix = null;
10,092×
335

336
    if (this.context.viewport instanceof WebMercatorViewport) {
Branches [[26, 1]] missed. 10,092×
337
      // project points from world space (lng/lat) to viewport (screen) space.
338
      projectPoints = true;
10,092×
339
    } else {
340
      projectPoints = false;
10,092×
341
      // Use pixelProjectionMatrix to transform points to viewport (screen) space.
342
      gridTransformMatrix = viewport.pixelProjectionMatrix;
10,092×
343
    }
344
    const results = this.state.gpuGridAggregator.run({
10,092×
345
      positions,
346
      weights,
347
      cellSize: [cellSizePixels, cellSizePixels],
348
      viewport,
349
      changeFlags,
350
      useGPU: gpuAggregation,
351
      projectPoints,
352
      gridTransformMatrix
353
    });
354

355
    const maxWeight =
356
      results.color.maxData && Number.isFinite(results.color.maxData[0])
10,092×
357
        ? results.color.maxData[0]
358
        : 0;
359

360
    this.setState({
10,092×
361
      maxWeight // uniform to use under WebGL1
362
    });
363

UNCOV
364
    attributeManager.invalidate('instanceCounts');
!
365
  }
366

367
  _updateUniforms({oldProps, props, changeFlags}) {
368
    const newState = {};
10×
369
    if (COLOR_PROPS.some(key => oldProps[key] !== props[key])) {
10×
370
      newState.shouldUseMinMax = this._shouldUseMinMax();
2×
371
    }
372

373
    if (oldProps.colorRange !== props.colorRange) {
2×
UNCOV
374
      newState.colorRange = colorRangeToFlatArray(props.colorRange);
!
375
    }
376

UNCOV
377
    if (
!
378
      oldProps.cellMarginPixels !== props.cellMarginPixels ||
379
      oldProps.cellSizePixels !== props.cellSizePixels ||
380
      changeFlags.viewportChanged
381
    ) {
UNCOV
382
      const {width, height} = this.context.viewport;
!
383
      const {cellSizePixels, cellMarginPixels} = this.props;
2×
384
      const margin = cellSizePixels > cellMarginPixels ? cellMarginPixels : 0;
2×
385

UNCOV
386
      newState.cellScale = new Float32Array([
!
387
        ((cellSizePixels - margin) / width) * 2,
388
        (-(cellSizePixels - margin) / height) * 2,
389
        1
390
      ]);
391
    }
UNCOV
392
    this.setState(newState);
!
393
  }
394

395
  _updateGridParams() {
396
    const attributeManager = this.getAttributeManager();
2×
397
    attributeManager.invalidateAll();
2×
UNCOV
398
    const {width, height} = this.context.viewport;
!
399
    const {cellSizePixels} = this.props;
12×
400
    const {gl} = this.context;
12×
401

402
    const numCol = Math.ceil(width / cellSizePixels);
12×
403
    const numRow = Math.ceil(height / cellSizePixels);
12×
404
    const numInstances = numCol * numRow;
12×
405
    const dataBytes = numInstances * 4 * 4;
12×
406
    let aggregationBuffer = this.state.aggregationBuffer;
12×
407
    if (aggregationBuffer) {
12×
408
      aggregationBuffer.delete();
12×
409
    }
410

UNCOV
411
    aggregationBuffer = new Buffer(gl, {
!
412
      byteLength: dataBytes,
413
      accessor: {
414
        size: 4,
415
        type: GL.FLOAT,
416
        divisor: 1
417
      }
418
    });
UNCOV
419
    this.state.weights.color.aggregationBuffer = aggregationBuffer;
!
420
    this.setState({
12×
421
      numCol,
422
      numRow,
423
      numInstances,
424
      aggregationBuffer
425
    });
426
  }
427
}
428

429
ScreenGridLayer.layerName = 'ScreenGridLayer';
12×
430
ScreenGridLayer.defaultProps = defaultProps;
12×
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC