@deck.gl/core 9.3.0-alpha.1 → 9.3.0-alpha.3
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/debug.min.js +1 -1
- package/dist/controllers/controller.d.ts +5 -4
- package/dist/controllers/controller.d.ts.map +1 -1
- package/dist/controllers/controller.js +18 -7
- package/dist/controllers/controller.js.map +1 -1
- package/dist/controllers/first-person-controller.d.ts +3 -2
- package/dist/controllers/first-person-controller.d.ts.map +1 -1
- package/dist/controllers/first-person-controller.js +13 -5
- package/dist/controllers/first-person-controller.js.map +1 -1
- package/dist/controllers/globe-controller.d.ts +1 -0
- package/dist/controllers/globe-controller.d.ts.map +1 -1
- package/dist/controllers/globe-controller.js +66 -5
- package/dist/controllers/globe-controller.js.map +1 -1
- package/dist/controllers/map-controller.d.ts +7 -18
- package/dist/controllers/map-controller.d.ts.map +1 -1
- package/dist/controllers/map-controller.js +94 -50
- package/dist/controllers/map-controller.js.map +1 -1
- package/dist/controllers/orbit-controller.d.ts +12 -4
- package/dist/controllers/orbit-controller.d.ts.map +1 -1
- package/dist/controllers/orbit-controller.js +118 -10
- package/dist/controllers/orbit-controller.js.map +1 -1
- package/dist/controllers/orthographic-controller.d.ts +117 -9
- package/dist/controllers/orthographic-controller.d.ts.map +1 -1
- package/dist/controllers/orthographic-controller.js +302 -37
- package/dist/controllers/orthographic-controller.js.map +1 -1
- package/dist/controllers/terrain-controller.d.ts +29 -0
- package/dist/controllers/terrain-controller.d.ts.map +1 -0
- package/dist/controllers/terrain-controller.js +108 -0
- package/dist/controllers/terrain-controller.js.map +1 -0
- package/dist/controllers/view-state.d.ts +2 -1
- package/dist/controllers/view-state.d.ts.map +1 -1
- package/dist/controllers/view-state.js +2 -1
- package/dist/controllers/view-state.js.map +1 -1
- package/dist/debug/loggers.d.ts.map +1 -1
- package/dist/debug/loggers.js +1 -4
- package/dist/debug/loggers.js.map +1 -1
- package/dist/dist.dev.js +3800 -1675
- package/dist/effects/lighting/lighting-effect.d.ts +1 -0
- package/dist/effects/lighting/lighting-effect.d.ts.map +1 -1
- package/dist/effects/lighting/lighting-effect.js +14 -5
- package/dist/effects/lighting/lighting-effect.js.map +1 -1
- package/dist/index.cjs +775 -123
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/attribute/attribute-manager.d.ts.map +1 -1
- package/dist/lib/attribute/attribute-manager.js +2 -0
- package/dist/lib/attribute/attribute-manager.js.map +1 -1
- package/dist/lib/deck-picker.d.ts +6 -1
- package/dist/lib/deck-picker.d.ts.map +1 -1
- package/dist/lib/deck-picker.js +15 -3
- package/dist/lib/deck-picker.js.map +1 -1
- package/dist/lib/deck-renderer.d.ts +6 -1
- package/dist/lib/deck-renderer.d.ts.map +1 -1
- package/dist/lib/deck-renderer.js +14 -2
- package/dist/lib/deck-renderer.js.map +1 -1
- package/dist/lib/deck.d.ts +5 -0
- package/dist/lib/deck.d.ts.map +1 -1
- package/dist/lib/deck.js +13 -3
- package/dist/lib/deck.js.map +1 -1
- package/dist/lib/init.js +2 -2
- package/dist/lib/layer.d.ts.map +1 -1
- package/dist/lib/layer.js +1 -0
- package/dist/lib/layer.js.map +1 -1
- package/dist/passes/draw-layers-pass.d.ts +2 -0
- package/dist/passes/draw-layers-pass.d.ts.map +1 -1
- package/dist/passes/draw-layers-pass.js +3 -0
- package/dist/passes/draw-layers-pass.js.map +1 -1
- package/dist/passes/layers-pass.d.ts +2 -1
- package/dist/passes/layers-pass.d.ts.map +1 -1
- package/dist/passes/layers-pass.js +3 -0
- package/dist/passes/layers-pass.js.map +1 -1
- package/dist/passes/pick-layers-pass.d.ts +5 -2
- package/dist/passes/pick-layers-pass.d.ts.map +1 -1
- package/dist/passes/pick-layers-pass.js +3 -2
- package/dist/passes/pick-layers-pass.js.map +1 -1
- package/dist/shaderlib/project/project.glsl.d.ts.map +1 -1
- package/dist/shaderlib/project/project.glsl.js +3 -0
- package/dist/shaderlib/project/project.glsl.js.map +1 -1
- package/dist/utils/deep-merge.d.ts +5 -0
- package/dist/utils/deep-merge.d.ts.map +1 -0
- package/dist/utils/deep-merge.js +31 -0
- package/dist/utils/deep-merge.js.map +1 -0
- package/dist/utils/math-utils.d.ts +4 -0
- package/dist/utils/math-utils.d.ts.map +1 -1
- package/dist/utils/math-utils.js +8 -0
- package/dist/utils/math-utils.js.map +1 -1
- package/dist/viewports/globe-viewport.d.ts +1 -0
- package/dist/viewports/globe-viewport.d.ts.map +1 -1
- package/dist/viewports/globe-viewport.js +1 -1
- package/dist/viewports/globe-viewport.js.map +1 -1
- package/dist/viewports/orbit-viewport.d.ts.map +1 -1
- package/dist/viewports/orbit-viewport.js +7 -2
- package/dist/viewports/orbit-viewport.js.map +1 -1
- package/dist/viewports/orthographic-viewport.d.ts +8 -2
- package/dist/viewports/orthographic-viewport.d.ts.map +1 -1
- package/dist/viewports/orthographic-viewport.js.map +1 -1
- package/dist/views/orthographic-view.d.ts +38 -4
- package/dist/views/orthographic-view.d.ts.map +1 -1
- package/dist/views/orthographic-view.js.map +1 -1
- package/dist/views/view.d.ts.map +1 -1
- package/dist/views/view.js +2 -8
- package/dist/views/view.js.map +1 -1
- package/dist.min.js +220 -144
- package/package.json +9 -9
- package/src/controllers/controller.ts +23 -9
- package/src/controllers/first-person-controller.ts +18 -8
- package/src/controllers/globe-controller.ts +89 -5
- package/src/controllers/map-controller.ts +105 -56
- package/src/controllers/orbit-controller.ts +147 -13
- package/src/controllers/orthographic-controller.ts +417 -41
- package/src/controllers/terrain-controller.ts +146 -0
- package/src/controllers/view-state.ts +8 -1
- package/src/debug/loggers.ts +1 -5
- package/src/effects/lighting/lighting-effect.ts +20 -8
- package/src/index.ts +1 -0
- package/src/lib/attribute/attribute-manager.ts +1 -0
- package/src/lib/deck-picker.ts +18 -4
- package/src/lib/deck-renderer.ts +17 -3
- package/src/lib/deck.ts +19 -3
- package/src/lib/layer.ts +1 -0
- package/src/passes/draw-layers-pass.ts +5 -0
- package/src/passes/layers-pass.ts +5 -1
- package/src/passes/pick-layers-pass.ts +8 -4
- package/src/shaderlib/project/project.glsl.ts +3 -0
- package/src/utils/deep-merge.ts +33 -0
- package/src/utils/math-utils.ts +12 -0
- package/src/viewports/globe-viewport.ts +1 -1
- package/src/viewports/orbit-viewport.ts +8 -2
- package/src/viewports/orthographic-viewport.ts +8 -2
- package/src/views/orthographic-view.ts +38 -4
- package/src/views/view.ts +2 -8
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "deck.gl core library",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"version": "9.3.0-alpha.
|
|
6
|
+
"version": "9.3.0-alpha.3",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"prepublishOnly": "npm run build-debugger && npm run build-bundle && npm run build-bundle -- --env=dev"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@loaders.gl/core": "^4.4.0-alpha.
|
|
44
|
-
"@loaders.gl/images": "^4.4.0-alpha.
|
|
45
|
-
"@luma.gl/constants": "^9.3.0-alpha.
|
|
46
|
-
"@luma.gl/core": "^9.3.0-alpha.
|
|
47
|
-
"@luma.gl/engine": "^9.3.0-alpha.
|
|
48
|
-
"@luma.gl/shadertools": "^9.3.0-alpha.
|
|
49
|
-
"@luma.gl/webgl": "^9.3.0-alpha.
|
|
43
|
+
"@loaders.gl/core": "^4.4.0-alpha.18",
|
|
44
|
+
"@loaders.gl/images": "^4.4.0-alpha.18",
|
|
45
|
+
"@luma.gl/constants": "^9.3.0-alpha.6",
|
|
46
|
+
"@luma.gl/core": "^9.3.0-alpha.6",
|
|
47
|
+
"@luma.gl/engine": "^9.3.0-alpha.6",
|
|
48
|
+
"@luma.gl/shadertools": "^9.3.0-alpha.6",
|
|
49
|
+
"@luma.gl/webgl": "^9.3.0-alpha.6",
|
|
50
50
|
"@math.gl/core": "^4.1.0",
|
|
51
51
|
"@math.gl/sun": "^4.1.0",
|
|
52
52
|
"@math.gl/types": "^4.1.0",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"gl-matrix": "^3.0.0",
|
|
59
59
|
"mjolnir.js": "^3.0.0"
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "25b39cfc26abd50aa900796fae75c2b5fedada69"
|
|
62
62
|
}
|
|
@@ -7,6 +7,7 @@ import TransitionManager, {TransitionProps} from './transition-manager';
|
|
|
7
7
|
import LinearInterpolator from '../transitions/linear-interpolator';
|
|
8
8
|
import {IViewState} from './view-state';
|
|
9
9
|
import {ConstructorOf} from '../types/types';
|
|
10
|
+
import {deepEqual} from '../utils/deep-equal';
|
|
10
11
|
|
|
11
12
|
import type Viewport from '../viewports/viewport';
|
|
12
13
|
|
|
@@ -65,6 +66,8 @@ export type ControllerOptions = {
|
|
|
65
66
|
dragMode?: 'pan' | 'rotate';
|
|
66
67
|
/** Enable inertia after panning/pinching. If a number is provided, indicates the duration of time over which the velocity reduces to zero, in milliseconds. Default `false`. */
|
|
67
68
|
inertia?: boolean | number;
|
|
69
|
+
/** Bounding box of content that the controller is constrained in */
|
|
70
|
+
maxBounds?: [min: [number, number], max: [number, number]] | [min: [number, number, number], max: [number, number, number]] | null;
|
|
68
71
|
};
|
|
69
72
|
|
|
70
73
|
export type ControllerProps = {
|
|
@@ -122,6 +125,7 @@ export default abstract class Controller<ControllerState extends IViewState<Cont
|
|
|
122
125
|
protected onViewStateChange: (params: ViewStateChangeParameters) => void;
|
|
123
126
|
protected onStateChange: (state: InteractionState) => void;
|
|
124
127
|
protected makeViewport: (opts: Record<string, any>) => Viewport;
|
|
128
|
+
protected pickPosition?: (x: number, y: number) => {coordinate?: number[]} | null;
|
|
125
129
|
|
|
126
130
|
private _controllerState?: ControllerState;
|
|
127
131
|
private _events: Record<string, boolean> = {};
|
|
@@ -171,6 +175,7 @@ export default abstract class Controller<ControllerState extends IViewState<Cont
|
|
|
171
175
|
this.onViewStateChange = opts.onViewStateChange || (() => {});
|
|
172
176
|
this.onStateChange = opts.onStateChange || (() => {});
|
|
173
177
|
this.makeViewport = opts.makeViewport;
|
|
178
|
+
this.pickPosition = opts.pickPosition;
|
|
174
179
|
}
|
|
175
180
|
|
|
176
181
|
set events(customEvents) {
|
|
@@ -240,7 +245,7 @@ export default abstract class Controller<ControllerState extends IViewState<Cont
|
|
|
240
245
|
...this.props,
|
|
241
246
|
...this.state
|
|
242
247
|
});
|
|
243
|
-
return this._controllerState
|
|
248
|
+
return this._controllerState;
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
getCenter(event: MjolnirGestureEvent | MjolnirWheelEvent) : [number, number] {
|
|
@@ -291,6 +296,7 @@ export default abstract class Controller<ControllerState extends IViewState<Cont
|
|
|
291
296
|
if (props.dragMode) {
|
|
292
297
|
this.dragMode = props.dragMode;
|
|
293
298
|
}
|
|
299
|
+
const oldProps = this.props;
|
|
294
300
|
this.props = props;
|
|
295
301
|
|
|
296
302
|
if (!('transitionInterpolator' in props)) {
|
|
@@ -332,6 +338,19 @@ export default abstract class Controller<ControllerState extends IViewState<Cont
|
|
|
332
338
|
this.touchZoom = touchZoom;
|
|
333
339
|
this.touchRotate = touchRotate;
|
|
334
340
|
this.keyboard = keyboard;
|
|
341
|
+
|
|
342
|
+
// Normalize view state if maxBounds is defined
|
|
343
|
+
const dimensionChanged = !oldProps || oldProps.height !== props.height || oldProps.width !== props.width || oldProps.maxBounds !== props.maxBounds;
|
|
344
|
+
if (dimensionChanged && props.maxBounds) {
|
|
345
|
+
// Dimensions changed, try re-normalize the props
|
|
346
|
+
const controllerState = new this.ControllerState({...props, makeViewport: this.makeViewport});
|
|
347
|
+
const normalizedProps = controllerState.getViewportProps();
|
|
348
|
+
const changed = Object.keys(normalizedProps).some(key => !deepEqual(normalizedProps[key], props[key], 1));
|
|
349
|
+
if (changed) {
|
|
350
|
+
// some props are updated after normalization
|
|
351
|
+
this.updateViewport(controllerState);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
335
354
|
}
|
|
336
355
|
|
|
337
356
|
updateTransition() {
|
|
@@ -400,19 +419,14 @@ export default abstract class Controller<ControllerState extends IViewState<Cont
|
|
|
400
419
|
alternateMode = !alternateMode;
|
|
401
420
|
}
|
|
402
421
|
|
|
403
|
-
const newControllerState = alternateMode
|
|
404
|
-
|
|
405
|
-
|
|
422
|
+
const newControllerState = this.controllerState[alternateMode ? 'panStart' : 'rotateStart']({
|
|
423
|
+
pos
|
|
424
|
+
});
|
|
406
425
|
this._panMove = alternateMode;
|
|
407
426
|
this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
|
|
408
427
|
return true;
|
|
409
428
|
}
|
|
410
429
|
|
|
411
|
-
/** Returns parameters for rotateStart. Override to add extra params (e.g. altitude). */
|
|
412
|
-
protected _getRotateStartParams(pos: [number, number]): {pos: [number, number]} {
|
|
413
|
-
return {pos};
|
|
414
|
-
}
|
|
415
|
-
|
|
416
430
|
// Default handler for the `panmove` and `panend` event.
|
|
417
431
|
protected _onPan(event: MjolnirGestureEvent): boolean {
|
|
418
432
|
if (!this.isDragging()) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import Controller from './controller';
|
|
5
|
+
import Controller, {ControllerProps} from './controller';
|
|
6
6
|
import ViewState from './view-state';
|
|
7
7
|
import {mod} from '../utils/math-utils';
|
|
8
8
|
import type Viewport from '../viewports/viewport';
|
|
@@ -27,6 +27,8 @@ type FirstPersonStateProps = {
|
|
|
27
27
|
|
|
28
28
|
maxPitch?: number;
|
|
29
29
|
minPitch?: number;
|
|
30
|
+
|
|
31
|
+
maxBounds?: ControllerProps['maxBounds'];
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
type FirstPersonStateInternal = {
|
|
@@ -43,8 +45,6 @@ class FirstPersonState extends ViewState<
|
|
|
43
45
|
FirstPersonStateProps,
|
|
44
46
|
FirstPersonStateInternal
|
|
45
47
|
> {
|
|
46
|
-
makeViewport: (props: Record<string, any>) => Viewport;
|
|
47
|
-
|
|
48
48
|
constructor(
|
|
49
49
|
options: FirstPersonStateProps &
|
|
50
50
|
FirstPersonStateInternal & {
|
|
@@ -69,6 +69,8 @@ class FirstPersonState extends ViewState<
|
|
|
69
69
|
maxPitch = 90,
|
|
70
70
|
minPitch = -90,
|
|
71
71
|
|
|
72
|
+
maxBounds = null,
|
|
73
|
+
|
|
72
74
|
// Model state when the rotate operation first started
|
|
73
75
|
startRotatePos,
|
|
74
76
|
startBearing,
|
|
@@ -88,7 +90,8 @@ class FirstPersonState extends ViewState<
|
|
|
88
90
|
longitude,
|
|
89
91
|
latitude,
|
|
90
92
|
maxPitch,
|
|
91
|
-
minPitch
|
|
93
|
+
minPitch,
|
|
94
|
+
maxBounds
|
|
92
95
|
},
|
|
93
96
|
{
|
|
94
97
|
startRotatePos,
|
|
@@ -97,10 +100,9 @@ class FirstPersonState extends ViewState<
|
|
|
97
100
|
startZoomPosition,
|
|
98
101
|
startPanPos,
|
|
99
102
|
startPanPosition
|
|
100
|
-
}
|
|
103
|
+
},
|
|
104
|
+
options.makeViewport
|
|
101
105
|
);
|
|
102
|
-
|
|
103
|
-
this.makeViewport = options.makeViewport;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
/* Public API */
|
|
@@ -366,7 +368,7 @@ class FirstPersonState extends ViewState<
|
|
|
366
368
|
// Apply any constraints (mathematical or defined by _viewportProps) to map state
|
|
367
369
|
applyConstraints(props: Required<FirstPersonStateProps>): Required<FirstPersonStateProps> {
|
|
368
370
|
// Ensure pitch and zoom are within specified range
|
|
369
|
-
const {pitch, maxPitch, minPitch, longitude, bearing} = props;
|
|
371
|
+
const {pitch, maxPitch, minPitch, longitude, position, bearing, maxBounds} = props;
|
|
370
372
|
props.pitch = clamp(pitch, minPitch, maxPitch);
|
|
371
373
|
|
|
372
374
|
// Normalize degrees
|
|
@@ -376,6 +378,14 @@ class FirstPersonState extends ViewState<
|
|
|
376
378
|
if (bearing < -180 || bearing > 180) {
|
|
377
379
|
props.bearing = mod(bearing + 180, 360) - 180;
|
|
378
380
|
}
|
|
381
|
+
if (maxBounds) {
|
|
382
|
+
const x = clamp(position[0], maxBounds[0][0], maxBounds[1][0]);
|
|
383
|
+
const y = clamp(position[1], maxBounds[0][1], maxBounds[1][1]);
|
|
384
|
+
const z = clamp(position[2] ?? 0, maxBounds[0][2] ?? 0, maxBounds[1][2] ?? 0);
|
|
385
|
+
if (x !== position[0] || y !== position[1] || z !== position[2]) {
|
|
386
|
+
props.position = [x, y, z];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
379
389
|
|
|
380
390
|
return props;
|
|
381
391
|
}
|
|
@@ -9,10 +9,24 @@ import {MapState, MapStateProps} from './map-controller';
|
|
|
9
9
|
import type {MapStateInternal} from './map-controller';
|
|
10
10
|
import {mod} from '../utils/math-utils';
|
|
11
11
|
import LinearInterpolator from '../transitions/linear-interpolator';
|
|
12
|
-
import {zoomAdjust} from '../viewports/globe-viewport';
|
|
12
|
+
import {zoomAdjust, GLOBE_RADIUS} from '../viewports/globe-viewport';
|
|
13
13
|
|
|
14
14
|
import {MAX_LATITUDE} from '@math.gl/web-mercator';
|
|
15
15
|
|
|
16
|
+
const DEGREES_TO_RADIANS = Math.PI / 180;
|
|
17
|
+
const RADIANS_TO_DEGREES = 180 / Math.PI;
|
|
18
|
+
|
|
19
|
+
function degreesToPixels(angle: number, zoom: number = 0): number {
|
|
20
|
+
const radians = Math.min(180, angle) * DEGREES_TO_RADIANS;
|
|
21
|
+
const size = GLOBE_RADIUS * 2 * Math.sin(radians / 2);
|
|
22
|
+
return size * Math.pow(2, zoom);
|
|
23
|
+
}
|
|
24
|
+
function pixelsToDegrees(pixels: number, zoom: number = 0): number {
|
|
25
|
+
const size = pixels / Math.pow(2, zoom);
|
|
26
|
+
const radians = Math.asin(Math.min(1, size / GLOBE_RADIUS / 2)) * 2;
|
|
27
|
+
return radians * RADIANS_TO_DEGREES;
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
type GlobeStateInternal = MapStateInternal & {
|
|
17
31
|
startPanPos?: [number, number];
|
|
18
32
|
};
|
|
@@ -25,6 +39,7 @@ class GlobeState extends MapState {
|
|
|
25
39
|
}
|
|
26
40
|
) {
|
|
27
41
|
const {startPanPos, ...mapStateOptions} = options;
|
|
42
|
+
mapStateOptions.normalize = false; // disable MapState default normalization
|
|
28
43
|
super(mapStateOptions);
|
|
29
44
|
|
|
30
45
|
if (startPanPos !== undefined) {
|
|
@@ -71,19 +86,88 @@ class GlobeState extends MapState {
|
|
|
71
86
|
|
|
72
87
|
applyConstraints(props: Required<MapStateProps>): Required<MapStateProps> {
|
|
73
88
|
// Ensure zoom is within specified range
|
|
74
|
-
const {longitude, latitude,
|
|
89
|
+
const {longitude, latitude, maxBounds} = props;
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
const zoomAdjustment = zoomAdjust(latitude) - ZOOM0;
|
|
78
|
-
props.zoom = clamp(zoom, minZoom + zoomAdjustment, maxZoom + zoomAdjustment);
|
|
91
|
+
props.zoom = this._constrainZoom(props.zoom, props);
|
|
79
92
|
|
|
80
93
|
if (longitude < -180 || longitude > 180) {
|
|
81
94
|
props.longitude = mod(longitude + 180, 360) - 180;
|
|
82
95
|
}
|
|
83
96
|
props.latitude = clamp(latitude, -MAX_LATITUDE, MAX_LATITUDE);
|
|
97
|
+
if (maxBounds) {
|
|
98
|
+
props.longitude = clamp(props.longitude, maxBounds[0][0], maxBounds[1][0]);
|
|
99
|
+
props.latitude = clamp(props.latitude, maxBounds[0][1], maxBounds[1][1]);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (maxBounds) {
|
|
103
|
+
// calculate center and zoom ranges at pitch=0 and bearing=0
|
|
104
|
+
// to maintain visual stability when rotating
|
|
105
|
+
const effectiveZoom = props.zoom - zoomAdjust(latitude);
|
|
106
|
+
const lngSpan = maxBounds[1][0] - maxBounds[0][0];
|
|
107
|
+
const latSpan = maxBounds[1][1] - maxBounds[0][1];
|
|
108
|
+
if (latSpan > 0 && latSpan < MAX_LATITUDE * 2) {
|
|
109
|
+
const halfHeightDegrees =
|
|
110
|
+
Math.min(pixelsToDegrees(props.height, effectiveZoom), latSpan) / 2;
|
|
111
|
+
props.latitude = clamp(
|
|
112
|
+
props.latitude,
|
|
113
|
+
maxBounds[0][1] + halfHeightDegrees,
|
|
114
|
+
maxBounds[1][1] - halfHeightDegrees
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (lngSpan > 0 && lngSpan < 360) {
|
|
118
|
+
const halfWidthDegrees =
|
|
119
|
+
Math.min(
|
|
120
|
+
pixelsToDegrees(
|
|
121
|
+
props.width / Math.cos(props.latitude * DEGREES_TO_RADIANS),
|
|
122
|
+
effectiveZoom
|
|
123
|
+
),
|
|
124
|
+
lngSpan
|
|
125
|
+
) / 2;
|
|
126
|
+
props.longitude = clamp(
|
|
127
|
+
props.longitude,
|
|
128
|
+
maxBounds[0][0] + halfWidthDegrees,
|
|
129
|
+
maxBounds[1][0] - halfWidthDegrees
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (props.latitude !== latitude) {
|
|
134
|
+
props.zoom += zoomAdjust(props.latitude) - zoomAdjust(latitude);
|
|
135
|
+
}
|
|
84
136
|
|
|
85
137
|
return props;
|
|
86
138
|
}
|
|
139
|
+
|
|
140
|
+
_constrainZoom(zoom: number, props?: Required<MapStateProps>): number {
|
|
141
|
+
props ||= this.getViewportProps();
|
|
142
|
+
const {latitude, maxZoom, maxBounds} = props;
|
|
143
|
+
let {minZoom} = props;
|
|
144
|
+
const ZOOM0 = zoomAdjust(0);
|
|
145
|
+
const zoomAdjustment = zoomAdjust(latitude) - ZOOM0;
|
|
146
|
+
|
|
147
|
+
const shouldApplyMaxBounds = maxBounds !== null && props.width > 0 && props.height > 0;
|
|
148
|
+
if (shouldApplyMaxBounds) {
|
|
149
|
+
const minLatitude = maxBounds[0][1];
|
|
150
|
+
const maxLatitude = maxBounds[1][1];
|
|
151
|
+
// latitude at which the bounding box is the widest
|
|
152
|
+
const fitLatitude =
|
|
153
|
+
Math.sign(minLatitude) === Math.sign(maxLatitude)
|
|
154
|
+
? Math.min(Math.abs(minLatitude), Math.abs(maxLatitude))
|
|
155
|
+
: 0;
|
|
156
|
+
const w =
|
|
157
|
+
degreesToPixels(maxBounds[1][0] - maxBounds[0][0]) *
|
|
158
|
+
Math.cos(fitLatitude * DEGREES_TO_RADIANS);
|
|
159
|
+
const h = degreesToPixels(maxBounds[1][1] - maxBounds[0][1]);
|
|
160
|
+
if (w > 0) {
|
|
161
|
+
minZoom = Math.max(minZoom, Math.log2(props.width / w) + ZOOM0);
|
|
162
|
+
}
|
|
163
|
+
if (h > 0) {
|
|
164
|
+
minZoom = Math.max(minZoom, Math.log2(props.height / h) + ZOOM0);
|
|
165
|
+
}
|
|
166
|
+
if (minZoom > maxZoom) minZoom = maxZoom;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return clamp(zoom, minZoom + zoomAdjustment, maxZoom + zoomAdjustment);
|
|
170
|
+
}
|
|
87
171
|
}
|
|
88
172
|
|
|
89
173
|
export default class GlobeController extends Controller<MapState> {
|
|
@@ -5,14 +5,34 @@
|
|
|
5
5
|
import {clamp} from '@math.gl/core';
|
|
6
6
|
import Controller, {ControllerProps, InteractionState} from './controller';
|
|
7
7
|
import ViewState from './view-state';
|
|
8
|
-
import {
|
|
8
|
+
import {worldToLngLat, lngLatToWorld as _lngLatToWorld} from '@math.gl/web-mercator';
|
|
9
9
|
import assert from '../utils/assert';
|
|
10
|
+
import {mod} from '../utils/math-utils';
|
|
10
11
|
|
|
11
12
|
import LinearInterpolator from '../transitions/linear-interpolator';
|
|
12
13
|
import type Viewport from '../viewports/viewport';
|
|
13
14
|
|
|
14
15
|
const PITCH_MOUSE_THRESHOLD = 5;
|
|
15
16
|
const PITCH_ACCEL = 1.2;
|
|
17
|
+
const WEB_MERCATOR_TILE_SIZE = 512;
|
|
18
|
+
const WEB_MERCATOR_MAX_BOUNDS = [
|
|
19
|
+
[-Infinity, -90],
|
|
20
|
+
[Infinity, 90]
|
|
21
|
+
] satisfies ControllerProps['maxBounds'];
|
|
22
|
+
|
|
23
|
+
/** The web mercator utility `lngLatToWorld` throws if invalid coordinates are provided.
|
|
24
|
+
* This wrapper clamps user input to calculate common positions safely. */
|
|
25
|
+
function lngLatToWorld([lng, lat]: number[]): number[] {
|
|
26
|
+
if (Math.abs(lat) > 90) {
|
|
27
|
+
lat = Math.sign(lat) * 90;
|
|
28
|
+
}
|
|
29
|
+
if (Number.isFinite(lng)) {
|
|
30
|
+
const [x, y] = _lngLatToWorld([lng, lat]);
|
|
31
|
+
return [x, clamp(y, 0, WEB_MERCATOR_TILE_SIZE)];
|
|
32
|
+
}
|
|
33
|
+
const [, y] = _lngLatToWorld([0, lat]);
|
|
34
|
+
return [lng, clamp(y, 0, WEB_MERCATOR_TILE_SIZE)];
|
|
35
|
+
}
|
|
16
36
|
|
|
17
37
|
export type MapStateProps = {
|
|
18
38
|
/** Mapbox viewport properties */
|
|
@@ -47,6 +67,8 @@ export type MapStateProps = {
|
|
|
47
67
|
|
|
48
68
|
/** Normalize viewport props to fit map height into viewport. Default `true` */
|
|
49
69
|
normalize?: boolean;
|
|
70
|
+
|
|
71
|
+
maxBounds?: ControllerProps['maxBounds'];
|
|
50
72
|
};
|
|
51
73
|
|
|
52
74
|
export type MapStateInternal = {
|
|
@@ -70,12 +92,18 @@ export type MapStateInternal = {
|
|
|
70
92
|
/* Utils */
|
|
71
93
|
|
|
72
94
|
export class MapState extends ViewState<MapState, MapStateProps, MapStateInternal> {
|
|
73
|
-
|
|
95
|
+
/* get optional altitude for rotation pivot
|
|
96
|
+
* - undefined: rotate around viewport center (no pivot point)
|
|
97
|
+
* - 0: rotate around pointer position at ground level
|
|
98
|
+
* - other value: rotate around pointer position at specified altitude
|
|
99
|
+
*/
|
|
100
|
+
getAltitude?: (pos: [number, number]) => number | undefined;
|
|
74
101
|
|
|
75
102
|
constructor(
|
|
76
103
|
options: MapStateProps &
|
|
77
104
|
MapStateInternal & {
|
|
78
105
|
makeViewport: (props: Record<string, any>) => Viewport;
|
|
106
|
+
getAltitude?: (pos: [number, number]) => number | undefined;
|
|
79
107
|
}
|
|
80
108
|
) {
|
|
81
109
|
const {
|
|
@@ -133,6 +161,8 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
133
161
|
assert(Number.isFinite(latitude)); // `latitude` must be supplied
|
|
134
162
|
assert(Number.isFinite(zoom)); // `zoom` must be supplied
|
|
135
163
|
|
|
164
|
+
const maxBounds = options.maxBounds || (normalize ? WEB_MERCATOR_MAX_BOUNDS : null);
|
|
165
|
+
|
|
136
166
|
super(
|
|
137
167
|
{
|
|
138
168
|
width,
|
|
@@ -148,7 +178,8 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
148
178
|
maxPitch,
|
|
149
179
|
minPitch,
|
|
150
180
|
normalize,
|
|
151
|
-
position
|
|
181
|
+
position,
|
|
182
|
+
maxBounds
|
|
152
183
|
},
|
|
153
184
|
{
|
|
154
185
|
startPanLngLat,
|
|
@@ -158,10 +189,11 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
158
189
|
startBearing,
|
|
159
190
|
startPitch,
|
|
160
191
|
startZoom
|
|
161
|
-
}
|
|
192
|
+
},
|
|
193
|
+
options.makeViewport
|
|
162
194
|
);
|
|
163
195
|
|
|
164
|
-
this.
|
|
196
|
+
this.getAltitude = options.getAltitude;
|
|
165
197
|
}
|
|
166
198
|
|
|
167
199
|
/**
|
|
@@ -206,12 +238,10 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
206
238
|
/**
|
|
207
239
|
* Start rotating
|
|
208
240
|
* @param {[Number, Number]} pos - position on screen where the center is
|
|
209
|
-
* @param {Number} altitude - optional altitude for rotation pivot
|
|
210
|
-
* - undefined: rotate around viewport center (no pivot point)
|
|
211
|
-
* - 0: rotate around pointer position at ground level
|
|
212
|
-
* - other value: rotate around pointer position at specified altitude
|
|
213
241
|
*/
|
|
214
|
-
rotateStart({pos
|
|
242
|
+
rotateStart({pos}: {pos: [number, number]}): MapState {
|
|
243
|
+
const altitude = this.getAltitude?.(pos);
|
|
244
|
+
|
|
215
245
|
return this._getUpdatedState({
|
|
216
246
|
startRotatePos: pos,
|
|
217
247
|
startRotateLngLat: altitude !== undefined ? this._unproject3D(pos, altitude) : undefined,
|
|
@@ -323,10 +353,7 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
323
353
|
return this;
|
|
324
354
|
}
|
|
325
355
|
|
|
326
|
-
const
|
|
327
|
-
let zoom = (startZoom as number) + Math.log2(scale);
|
|
328
|
-
zoom = clamp(zoom, minZoom, maxZoom);
|
|
329
|
-
|
|
356
|
+
const zoom = this._constrainZoom((startZoom as number) + Math.log2(scale));
|
|
330
357
|
const zoomedViewport = this.makeViewport({...this.getViewportProps(), zoom});
|
|
331
358
|
|
|
332
359
|
return this._getUpdatedState({
|
|
@@ -411,18 +438,33 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
411
438
|
|
|
412
439
|
// Apply any constraints (mathematical or defined by _viewportProps) to map state
|
|
413
440
|
applyConstraints(props: Required<MapStateProps>): Required<MapStateProps> {
|
|
414
|
-
// Ensure zoom is within specified range
|
|
415
|
-
const {maxZoom, minZoom, zoom} = props;
|
|
416
|
-
props.zoom = clamp(zoom, minZoom, maxZoom);
|
|
417
|
-
|
|
418
441
|
// Ensure pitch is within specified range
|
|
419
|
-
const {maxPitch, minPitch, pitch} = props;
|
|
420
|
-
props.pitch = clamp(pitch, minPitch, maxPitch);
|
|
442
|
+
const {maxPitch, minPitch, pitch, longitude, bearing, normalize, maxBounds} = props;
|
|
421
443
|
|
|
422
|
-
// Normalize viewport props to fit map height into viewport
|
|
423
|
-
const {normalize = true} = props;
|
|
424
444
|
if (normalize) {
|
|
425
|
-
|
|
445
|
+
if (longitude < -180 || longitude > 180) {
|
|
446
|
+
props.longitude = mod(longitude + 180, 360) - 180;
|
|
447
|
+
}
|
|
448
|
+
if (bearing < -180 || bearing > 180) {
|
|
449
|
+
props.bearing = mod(bearing + 180, 360) - 180;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
props.pitch = clamp(pitch, minPitch, maxPitch);
|
|
453
|
+
|
|
454
|
+
props.zoom = this._constrainZoom(props.zoom, props);
|
|
455
|
+
|
|
456
|
+
if (maxBounds) {
|
|
457
|
+
const bl = lngLatToWorld(maxBounds[0]);
|
|
458
|
+
const tr = lngLatToWorld(maxBounds[1]);
|
|
459
|
+
// calculate center and zoom ranges at pitch=0 and bearing=0
|
|
460
|
+
// to maintain visual stability when rotating
|
|
461
|
+
const scale = 2 ** props.zoom;
|
|
462
|
+
const halfWidth = props.width / 2 / scale;
|
|
463
|
+
const halfHeight = props.height / 2 / scale;
|
|
464
|
+
const [minLng, minLat] = worldToLngLat([bl[0] + halfWidth, bl[1] + halfHeight]);
|
|
465
|
+
const [maxLng, maxLat] = worldToLngLat([tr[0] - halfWidth, tr[1] - halfHeight]);
|
|
466
|
+
props.longitude = clamp(props.longitude, minLng, maxLng);
|
|
467
|
+
props.latitude = clamp(props.latitude, minLat, maxLat);
|
|
426
468
|
}
|
|
427
469
|
|
|
428
470
|
return props;
|
|
@@ -430,6 +472,30 @@ export class MapState extends ViewState<MapState, MapStateProps, MapStateInterna
|
|
|
430
472
|
|
|
431
473
|
/* Private methods */
|
|
432
474
|
|
|
475
|
+
_constrainZoom(zoom: number, props?: Required<MapStateProps>): number {
|
|
476
|
+
props ||= this.getViewportProps();
|
|
477
|
+
const {maxZoom, maxBounds} = props;
|
|
478
|
+
|
|
479
|
+
const shouldApplyMaxBounds = maxBounds !== null && props.width > 0 && props.height > 0;
|
|
480
|
+
let {minZoom} = props;
|
|
481
|
+
|
|
482
|
+
if (shouldApplyMaxBounds) {
|
|
483
|
+
const bl = lngLatToWorld(maxBounds[0]);
|
|
484
|
+
const tr = lngLatToWorld(maxBounds[1]);
|
|
485
|
+
const w = tr[0] - bl[0];
|
|
486
|
+
const h = tr[1] - bl[1];
|
|
487
|
+
// ignore bound size of 0 or Infinity
|
|
488
|
+
if (Number.isFinite(w) && w > 0) {
|
|
489
|
+
minZoom = Math.max(minZoom, Math.log2(props.width / w));
|
|
490
|
+
}
|
|
491
|
+
if (Number.isFinite(h) && h > 0) {
|
|
492
|
+
minZoom = Math.max(minZoom, Math.log2(props.height / h));
|
|
493
|
+
}
|
|
494
|
+
if (minZoom > maxZoom) minZoom = maxZoom;
|
|
495
|
+
}
|
|
496
|
+
return clamp(zoom, minZoom, maxZoom);
|
|
497
|
+
}
|
|
498
|
+
|
|
433
499
|
_zoomFromCenter(scale) {
|
|
434
500
|
const {width, height} = this.getViewportProps();
|
|
435
501
|
return this.zoom({
|
|
@@ -542,36 +608,23 @@ export default class MapController extends Controller<MapState> {
|
|
|
542
608
|
*/
|
|
543
609
|
protected rotationPivot: 'center' | '2d' | '3d' = 'center';
|
|
544
610
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
this.pickPosition = opts.pickPosition;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
setProps(props: ControllerProps & MapStateProps & {rotationPivot?: 'center' | '2d' | '3d'}) {
|
|
611
|
+
setProps(
|
|
612
|
+
props: ControllerProps &
|
|
613
|
+
MapStateProps & {
|
|
614
|
+
rotationPivot?: 'center' | '2d' | '3d';
|
|
615
|
+
getAltitude?: (pos: [number, number]) => number | undefined;
|
|
616
|
+
}
|
|
617
|
+
) {
|
|
556
618
|
if ('rotationPivot' in props) {
|
|
557
619
|
this.rotationPivot = props.rotationPivot || 'center';
|
|
558
620
|
}
|
|
621
|
+
// this will be passed to MapState constructor
|
|
622
|
+
props.getAltitude = this._getAltitude;
|
|
559
623
|
props.position = props.position || [0, 0, 0];
|
|
560
|
-
|
|
624
|
+
props.maxBounds =
|
|
625
|
+
props.maxBounds || (props.normalize === false ? null : WEB_MERCATOR_MAX_BOUNDS);
|
|
561
626
|
|
|
562
627
|
super.setProps(props);
|
|
563
|
-
|
|
564
|
-
const dimensionChanged = !oldProps || oldProps.height !== props.height;
|
|
565
|
-
if (dimensionChanged) {
|
|
566
|
-
// Dimensions changed, normalize the props
|
|
567
|
-
this.updateViewport(
|
|
568
|
-
new this.ControllerState({
|
|
569
|
-
makeViewport: this.makeViewport,
|
|
570
|
-
...props,
|
|
571
|
-
...this.state
|
|
572
|
-
})
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
628
|
}
|
|
576
629
|
|
|
577
630
|
protected updateViewport(
|
|
@@ -595,22 +648,18 @@ export default class MapController extends Controller<MapState> {
|
|
|
595
648
|
}
|
|
596
649
|
|
|
597
650
|
/** Add altitude to rotateStart params based on rotationPivot mode */
|
|
598
|
-
protected
|
|
599
|
-
pos: [number, number];
|
|
600
|
-
altitude?: number;
|
|
601
|
-
} {
|
|
602
|
-
let altitude: number | undefined;
|
|
651
|
+
protected _getAltitude = (pos: [number, number]): number | undefined => {
|
|
603
652
|
if (this.rotationPivot === '2d') {
|
|
604
|
-
|
|
653
|
+
return 0;
|
|
605
654
|
} else if (this.rotationPivot === '3d') {
|
|
606
655
|
if (this.pickPosition) {
|
|
607
656
|
const {x, y} = this.props;
|
|
608
657
|
const pickResult = this.pickPosition(x + pos[0], y + pos[1]);
|
|
609
658
|
if (pickResult && pickResult.coordinate && pickResult.coordinate.length >= 3) {
|
|
610
|
-
|
|
659
|
+
return pickResult.coordinate[2];
|
|
611
660
|
}
|
|
612
661
|
}
|
|
613
662
|
}
|
|
614
|
-
return
|
|
615
|
-
}
|
|
663
|
+
return undefined;
|
|
664
|
+
};
|
|
616
665
|
}
|