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

uber / deck.gl / 13758

17 Sep 2019 - 21:06 coverage increased (+2.9%) to 82.762%
13758

Pull #3621

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
fix CI
Pull Request #3621: Support spring transition in UniformTransitionManager

3406 of 4621 branches covered (73.71%)

Branch coverage included in aggregate %.

59 of 63 new or added lines in 6 files covered. (93.65%)

499 existing lines in 85 files now uncovered.

7200 of 8194 relevant lines covered (87.87%)

4274.03 hits per line

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

82.89
/modules/core/src/controllers/map-controller.js
1
import {clamp} from 'math.gl';
2
import Controller from './controller';
3
import ViewState from './view-state';
4
import WebMercatorViewport, {normalizeViewportProps} from 'viewport-mercator-project';
5
import assert from '../utils/assert';
6
import LinearInterpolator from '../transitions/linear-interpolator';
7
import {TRANSITION_EVENTS} from './transition-manager';
8

9
const PITCH_MOUSE_THRESHOLD = 5;
1×
10
const PITCH_ACCEL = 1.2;
16×
11

12
const LINEAR_TRANSITION_PROPS = {
1×
13
  transitionDuration: 300,
14
  transitionEasing: t => t,
15×
15
  transitionInterpolator: new LinearInterpolator(),
16
  transitionInterruption: TRANSITION_EVENTS.BREAK
17
};
18

19
const NO_TRANSITION_PROPS = {
16×
20
  transitionDuration: 0
21
};
22

23
// MAPBOX LIMITS
24
export const MAPBOX_LIMITS = {
1×
25
  minZoom: 0,
26
  maxZoom: 20,
27
  minPitch: 0,
28
  maxPitch: 60
29
};
30

31
const DEFAULT_STATE = {
1×
32
  pitch: 0,
33
  bearing: 0,
34
  altitude: 1.5
35
};
36

37
/* Utils */
38

39
class MapState extends ViewState {
40
  constructor({
Branches [[0, 0]] missed.
41
    /** Mapbox viewport properties */
42
    /** The width of the viewport */
43
    width,
44
    /** The height of the viewport */
45
    height,
46
    /** The latitude at the center of the viewport */
47
    latitude,
48
    /** The longitude at the center of the viewport */
49
    longitude,
50
    /** The tile zoom level of the map. */
51
    zoom,
52
    /** The bearing of the viewport in degrees */
53
    bearing = DEFAULT_STATE.bearing,
Branches [[1, 0]] missed.
54
    /** The pitch of the viewport in degrees */
55
    pitch = DEFAULT_STATE.pitch,
Branches [[2, 0]] missed.
56
    /**
57
     * Specify the altitude of the viewport camera
58
     * Unit: map heights, default 1.5
59
     * Non-public API, see https://github.com/mapbox/mapbox-gl-js/issues/1137
60
     */
61
    altitude = DEFAULT_STATE.altitude,
62

63
    /** Viewport constraints */
64
    maxZoom = MAPBOX_LIMITS.maxZoom,
65
    minZoom = MAPBOX_LIMITS.minZoom,
66
    maxPitch = MAPBOX_LIMITS.maxPitch,
67
    minPitch = MAPBOX_LIMITS.minPitch,
68

69
    /** Interaction states, required to calculate change during transform */
70
    /* The point on map being grabbed when the operation first started */
71
    startPanLngLat,
72
    /* Center of the zoom when the operation first started */
73
    startZoomLngLat,
74
    /** Bearing when current perspective rotate operation started */
75
    startBearing,
76
    /** Pitch when current perspective rotate operation started */
77
    startPitch,
78
    /** Zoom when current zoom operation started */
79
    startZoom
80
  } = {}) {
81
    assert(Number.isFinite(longitude), '`longitude` must be supplied');
1×
82
    assert(Number.isFinite(latitude), '`latitude` must be supplied');
3×
83
    assert(Number.isFinite(zoom), '`zoom` must be supplied');
1×
84

85
    super({
3×
86
      width,
87
      height,
88
      latitude,
89
      longitude,
90
      zoom,
91
      bearing,
92
      pitch,
93
      altitude,
94
      maxZoom,
95
      minZoom,
96
      maxPitch,
97
      minPitch
98
    });
99

100
    this._interactiveState = {
1×
101
      startPanLngLat,
102
      startZoomLngLat,
103
      startBearing,
104
      startPitch,
105
      startZoom
106
    };
107
  }
108

109
  /* Public API */
110

111
  getViewportProps() {
112
    return this._viewportProps;
1×
113
  }
114

115
  getInteractiveState() {
116
    return this._interactiveState;
1×
117
  }
118

119
  /**
120
   * Start panning
121
   * @param {[Number, Number]} pos - position on screen where the pointer grabs
122
   */
123
  panStart({pos}) {
124
    return this._getUpdatedState({
1×
125
      startPanLngLat: this._unproject(pos)
126
    });
127
  }
128

129
  /**
130
   * Pan
131
   * @param {[Number, Number]} pos - position on screen where the pointer is
132
   * @param {[Number, Number], optional} startPos - where the pointer grabbed at
133
   *   the start of the operation. Must be supplied of `panStart()` was not called
134
   */
135
  pan({pos, startPos}) {
136
    const startPanLngLat = this._interactiveState.startPanLngLat || this._unproject(startPos);
1×
137

138
    if (!startPanLngLat) {
Branches [[9, 0]] missed. 1×
139
      return this;
1×
140
    }
141

142
    const [longitude, latitude] = this._calculateNewLngLat({startPanLngLat, pos});
1×
143

144
    return this._getUpdatedState({
1×
145
      longitude,
146
      latitude
147
    });
148
  }
149

150
  /**
151
   * End panning
152
   * Must call if `panStart()` was called
153
   */
154
  panEnd() {
155
    return this._getUpdatedState({
1×
156
      startPanLngLat: null
157
    });
158
  }
159

160
  /**
161
   * Start rotating
162
   * @param {[Number, Number]} pos - position on screen where the center is
163
   */
164
  rotateStart({pos}) {
165
    return this._getUpdatedState({
1×
166
      startBearing: this._viewportProps.bearing,
167
      startPitch: this._viewportProps.pitch
168
    });
169
  }
170

171
  /**
172
   * Rotate
173
   * @param {Number} deltaScaleX - a number between [-1, 1] specifying the
174
   *   change to bearing.
175
   * @param {Number} deltaScaleY - a number between [-1, 1] specifying the
176
   *   change to pitch. -1 sets to minPitch and 1 sets to maxPitch.
177
   */
178
  rotate({deltaScaleX = 0, deltaScaleY = 0}) {
Branches [[10, 0]] missed.
179
    const {startBearing, startPitch} = this._interactiveState;
1×
180

181
    if (!Number.isFinite(startBearing) || !Number.isFinite(startPitch)) {
Branches [[12, 0]] missed. 12×
182
      return this;
1×
183
    }
184

185
    const {pitch, bearing} = this._calculateNewPitchAndBearing({
1×
186
      deltaScaleX,
187
      deltaScaleY,
188
      startBearing,
189
      startPitch
190
    });
191

192
    return this._getUpdatedState({
1×
193
      bearing,
194
      pitch
195
    });
196
  }
197

198
  /**
199
   * End rotating
200
   * Must call if `rotateStart()` was called
201
   */
202
  rotateEnd() {
203
    return this._getUpdatedState({
145×
204
      startBearing: null,
205
      startPitch: null
206
    });
207
  }
208

209
  /**
210
   * Start zooming
211
   * @param {[Number, Number]} pos - position on screen where the center is
212
   */
213
  zoomStart({pos}) {
214
    return this._getUpdatedState({
145×
215
      startZoomLngLat: this._unproject(pos),
216
      startZoom: this._viewportProps.zoom
217
    });
218
  }
219

220
  /**
221
   * Zoom
222
   * @param {[Number, Number]} pos - position on screen where the current center is
223
   * @param {[Number, Number]} startPos - the center position at
224
   *   the start of the operation. Must be supplied of `zoomStart()` was not called
225
   * @param {Number} scale - a number between [0, 1] specifying the accumulated
226
   *   relative scale.
227
   */
228
  zoom({pos, startPos, scale}) {
229
    assert(scale > 0, '`scale` must be a positive number');
145×
230

231
    // Make sure we zoom around the current mouse position rather than map center
232
    let {startZoom, startZoomLngLat} = this._interactiveState;
145×
233

234
    if (!Number.isFinite(startZoom)) {
145×
235
      // We have two modes of zoom:
236
      // scroll zoom that are discrete events (transform from the current zoom level),
237
      // and pinch zoom that are continuous events (transform from the zoom level when
238
      // pinch started).
239
      // If startZoom state is defined, then use the startZoom state;
240
      // otherwise assume discrete zooming
241
      startZoom = this._viewportProps.zoom;
98×
242
      startZoomLngLat = this._unproject(startPos) || this._unproject(pos);
32×
243
    }
244

245
    // take the start lnglat and put it where the mouse is down.
246
    assert(
4×
247
      startZoomLngLat,
248
      '`startZoomLngLat` prop is required ' +
249
        'for zoom behavior to calculate where to position the map.'
250
    );
251

252
    const zoom = this._calculateNewZoom({scale, startZoom});
5×
253

254
    const zoomedViewport = new WebMercatorViewport(Object.assign({}, this._viewportProps, {zoom}));
5×
UNCOV
255
    const [longitude, latitude] = zoomedViewport.getLocationAtPoint({lngLat: startZoomLngLat, pos});
!
256

257
    return this._getUpdatedState({
5×
258
      zoom,
259
      longitude,
260
      latitude
261
    });
262
  }
263

264
  /**
265
   * End zooming
266
   * Must call if `zoomStart()` was called
267
   */
268
  zoomEnd() {
269
    return this._getUpdatedState({
5×
270
      startZoomLngLat: null,
271
      startZoom: null
272
    });
273
  }
274

275
  zoomIn() {
276
    return this._zoomFromCenter(2);
6×
277
  }
278

279
  zoomOut() {
280
    return this._zoomFromCenter(0.5);
6×
281
  }
282

283
  moveLeft() {
284
    return this._panFromCenter([100, 0]);
2×
285
  }
286

287
  moveRight() {
288
    return this._panFromCenter([-100, 0]);
2×
289
  }
290

291
  moveUp() {
UNCOV
292
    return this._panFromCenter([0, 100]);
!
293
  }
294

295
  moveDown() {
296
    return this._panFromCenter([0, -100]);
2×
297
  }
298

299
  rotateLeft() {
300
    return this._getUpdatedState({
2×
301
      bearing: this._viewportProps.bearing - 15
302
    });
303
  }
304

305
  rotateRight() {
306
    return this._getUpdatedState({
9×
307
      bearing: this._viewportProps.bearing + 15
308
    });
309
  }
310

311
  rotateUp() {
312
    return this._getUpdatedState({
2×
313
      pitch: this._viewportProps.pitch + 10
314
    });
315
  }
316

317
  rotateDown() {
318
    return this._getUpdatedState({
9×
319
      pitch: this._viewportProps.pitch - 10
320
    });
321
  }
322

323
  shortestPathFrom(viewState) {
324
    // const endViewStateProps = new this.ControllerState(endProps).shortestPathFrom(startViewstate);
325
    const fromProps = viewState.getViewportProps();
9×
326
    const props = Object.assign({}, this._viewportProps);
9×
327
    const {bearing, longitude} = props;
8×
328

329
    if (Math.abs(bearing - fromProps.bearing) > 180) {
Branches [[16, 0]] missed. 8×
330
      props.bearing = bearing < 0 ? bearing + 360 : bearing - 360;
Branches [[17, 0], [17, 1]] missed. 9×
331
    }
332
    if (Math.abs(longitude - fromProps.longitude) > 180) {
Branches [[18, 0]] missed. 9×
333
      props.longitude = longitude < 0 ? longitude + 360 : longitude - 360;
Branches [[19, 0], [19, 1]] missed. 9×
334
    }
335
    return props;
9×
336
  }
337

338
  /* Private methods */
339

340
  _zoomFromCenter(scale) {
341
    const {width, height} = this._viewportProps;
9×
342
    return this.zoom({
3×
343
      pos: [width / 2, height / 2],
344
      scale
345
    });
346
  }
347

348
  _panFromCenter(offset) {
349
    const {width, height} = this._viewportProps;
3×
350
    return this.pan({
3×
351
      startPos: [width / 2, height / 2],
352
      pos: [width / 2 + offset[0], height / 2 + offset[1]]
353
    });
354
  }
355

356
  _getUpdatedState(newProps) {
357
    // Update _viewportProps
358
    return new MapState(Object.assign({}, this._viewportProps, this._interactiveState, newProps));
1×
359
  }
360

361
  // Apply any constraints (mathematical or defined by _viewportProps) to map state
362
  _applyConstraints(props) {
363
    // Ensure zoom is within specified range
364
    const {maxZoom, minZoom, zoom} = props;
1×
365
    props.zoom = clamp(zoom, minZoom, maxZoom);
1×
366

367
    // Ensure pitch is within specified range
368
    const {maxPitch, minPitch, pitch} = props;
1×
369
    props.pitch = clamp(pitch, minPitch, maxPitch);
1×
370

371
    Object.assign(props, normalizeViewportProps(props));
1×
372

373
    return props;
1×
374
  }
375

376
  _unproject(pos) {
377
    const viewport = new WebMercatorViewport(this._viewportProps);
1×
378
    return pos && viewport.unproject(pos);
16×
379
  }
380

381
  // Calculate a new lnglat based on pixel dragging position
382
  _calculateNewLngLat({startPanLngLat, pos}) {
383
    const viewport = new WebMercatorViewport(this._viewportProps);
16×
384
    return viewport.getMapCenterByLngLatPosition({lngLat: startPanLngLat, pos});
16×
385
  }
386

387
  // Calculates new zoom
388
  _calculateNewZoom({scale, startZoom}) {
389
    const {maxZoom, minZoom} = this._viewportProps;
16×
UNCOV
390
    const zoom = startZoom + Math.log2(scale);
!
391
    return clamp(zoom, minZoom, maxZoom);
16×
392
  }
393

394
  // Calculates a new pitch and bearing from a position (coming from an event)
395
  _calculateNewPitchAndBearing({deltaScaleX, deltaScaleY, startBearing, startPitch}) {
396
    // clamp deltaScaleY to [-1, 1] so that rotation is constrained between minPitch and maxPitch.
397
    // deltaScaleX does not need to be clamped as bearing does not have constraints.
UNCOV
398
    deltaScaleY = clamp(deltaScaleY, -1, 1);
!
399

400
    const {minPitch, maxPitch} = this._viewportProps;
16×
401

402
    const bearing = startBearing + 180 * deltaScaleX;
6×
403
    let pitch = startPitch;
6×
404
    if (deltaScaleY > 0) {
Branches [[21, 0]] missed. 4×
405
      // Gradually increase pitch
406
      pitch = startPitch + deltaScaleY * (maxPitch - startPitch);
4×
407
    } else if (deltaScaleY < 0) {
Branches [[22, 0]] missed. 50×
408
      // Gradually decrease pitch
409
      pitch = startPitch - deltaScaleY * (minPitch - startPitch);
145×
410
    }
411

412
    return {
145×
413
      pitch,
414
      bearing
415
    };
416
  }
417
}
418

419
export default class MapController extends Controller {
420
  constructor(props) {
421
    super(MapState, props);
145×
422
    this.invertPan = true;
145×
423
  }
424

425
  _getTransitionProps() {
426
    // Enables Transitions on double-tap and key-down events.
427
    return LINEAR_TRANSITION_PROPS;
145×
428
  }
429

430
  _onPanRotate(event) {
431
    if (!this.dragRotate) {
145×
432
      return false;
26×
433
    }
434

435
    const {deltaX, deltaY} = event;
26×
436
    const [, centerY] = this.getCenter(event);
5×
437
    const startY = centerY - deltaY;
5×
438
    const {width, height} = this.controllerState.getViewportProps();
9×
439

440
    const deltaScaleX = deltaX / width;
9×
441
    let deltaScaleY = 0;
9×
442

443
    if (deltaY > 0) {
Branches [[24, 0]] missed. 2×
444
      if (Math.abs(height - startY) > PITCH_MOUSE_THRESHOLD) {
Branches [[25, 0], [25, 1]] missed. 2×
445
        // Move from 0 to -1 as we drag upwards
446
        deltaScaleY = (deltaY / (startY - height)) * PITCH_ACCEL;
2×
447
      }
448
    } else if (deltaY < 0) {
Branches [[26, 0]] missed. 2×
449
      if (startY > PITCH_MOUSE_THRESHOLD) {
Branches [[27, 0], [27, 1]] missed. 2×
450
        // Move from 0 to 1 as we drag upwards
451
        deltaScaleY = 1 - centerY / startY;
!
452
      }
453
    }
454
    deltaScaleY = Math.min(1, Math.max(-1, deltaScaleY));
2×
455

UNCOV
456
    const newControllerState = this.controllerState.rotate({deltaScaleX, deltaScaleY});
!
457
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {
2×
458
      isDragging: true,
459
      isRotating: true
460
    });
461
  }
462
}
463

464
export const testExports = {MapState};
3×
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