@deck.gl/mapbox 9.4.0-beta.1 → 9.4.0-beta.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/dist/index.cjs CHANGED
@@ -820,7 +820,7 @@ fn project_mercator_(lnglat: vec2<f32>) -> vec2<f32> {
820
820
  let y = clamp(lnglat.y, -89.9, 89.9);
821
821
  return vec2<f32>(
822
822
  radians(x) + PI,
823
- PI + log(tan(PI * 0.25 + radians(y) * 0.5))
823
+ PI + log(tan_fp32(PI * 0.25 + radians(y) * 0.5))
824
824
  ) * WORLD_SCALE;
825
825
  }
826
826
 
@@ -2433,8 +2433,8 @@ var _Viewport = class _Viewport {
2433
2433
  }
2434
2434
  return PROJECTION_MODE.IDENTITY;
2435
2435
  }
2436
- // Two viewports are equal if width and height are identical, and if
2437
- // their view and projection matrices are (approximately) equal.
2436
+ // Two viewports are equal if their dimensions, common-space scale, and
2437
+ // view and projection matrices are (approximately) equal.
2438
2438
  equals(viewport) {
2439
2439
  if (!(viewport instanceof _Viewport)) {
2440
2440
  return false;
@@ -2442,7 +2442,7 @@ var _Viewport = class _Viewport {
2442
2442
  if (this === viewport) {
2443
2443
  return true;
2444
2444
  }
2445
- return viewport.width === this.width && viewport.height === this.height && viewport.scale === this.scale && (0, import_core7.equals)(viewport.projectionMatrix, this.projectionMatrix) && (0, import_core7.equals)(viewport.viewMatrix, this.viewMatrix);
2445
+ return viewport.width === this.width && viewport.height === this.height && viewport.scale === this.scale && viewport.projectionMode === this.projectionMode && viewport.resolution === this.resolution && (0, import_core7.equals)(viewport.distanceScales.unitsPerMeter, this.distanceScales.unitsPerMeter) && (0, import_core7.equals)(viewport.projectionMatrix, this.projectionMatrix) && (0, import_core7.equals)(viewport.viewMatrix, this.viewMatrix);
2446
2446
  }
2447
2447
  /**
2448
2448
  * Projects xyz (possibly latitude and longitude) to pixel coordinates in window
@@ -2779,6 +2779,10 @@ var _WebMercatorViewport = class _WebMercatorViewport extends Viewport {
2779
2779
  }
2780
2780
  return this._subViewports;
2781
2781
  }
2782
+ /** Returns whether two Web Mercator viewports use the same projection settings. */
2783
+ equals(viewport) {
2784
+ return viewport instanceof _WebMercatorViewport && viewport._pseudoMeters === this._pseudoMeters && super.equals(viewport);
2785
+ }
2782
2786
  projectPosition(xyz) {
2783
2787
  if (this._pseudoMeters) {
2784
2788
  return super.projectPosition(xyz);
@@ -4161,7 +4165,7 @@ var View = class {
4161
4165
  };
4162
4166
 
4163
4167
  // ../core/src/controllers/map-controller.ts
4164
- var import_core14 = require("@math.gl/core");
4168
+ var import_core12 = require("@math.gl/core");
4165
4169
 
4166
4170
  // ../core/src/transitions/transition.ts
4167
4171
  var Transition = class {
@@ -4473,185 +4477,7 @@ var TransitionInterpolator = class {
4473
4477
  };
4474
4478
 
4475
4479
  // ../core/src/transitions/linear-interpolator.ts
4476
- var import_core13 = require("@math.gl/core");
4477
-
4478
- // ../core/src/viewports/globe-viewport.ts
4479
4480
  var import_core11 = require("@math.gl/core");
4480
- var import_web_mercator4 = require("@math.gl/web-mercator");
4481
- var import_core12 = require("@math.gl/core");
4482
- var import_web_mercator5 = require("@math.gl/web-mercator");
4483
- var DEGREES_TO_RADIANS2 = Math.PI / 180;
4484
- var RADIANS_TO_DEGREES = 180 / Math.PI;
4485
- var EARTH_RADIUS = 6370972;
4486
- var GLOBE_RADIUS = 256;
4487
- function getDistanceScales2() {
4488
- const unitsPerMeter3 = GLOBE_RADIUS / EARTH_RADIUS;
4489
- const unitsPerDegree = Math.PI / 180 * GLOBE_RADIUS;
4490
- return {
4491
- unitsPerMeter: [unitsPerMeter3, unitsPerMeter3, unitsPerMeter3],
4492
- unitsPerMeter2: [0, 0, 0],
4493
- metersPerUnit: [1 / unitsPerMeter3, 1 / unitsPerMeter3, 1 / unitsPerMeter3],
4494
- unitsPerDegree: [unitsPerDegree, unitsPerDegree, unitsPerMeter3],
4495
- unitsPerDegree2: [0, 0, 0],
4496
- degreesPerUnit: [1 / unitsPerDegree, 1 / unitsPerDegree, 1 / unitsPerMeter3]
4497
- };
4498
- }
4499
- var GlobeViewport = class extends Viewport {
4500
- constructor(opts = {}) {
4501
- const {
4502
- longitude = 0,
4503
- bearing = 0,
4504
- pitch = 0,
4505
- zoom = 0,
4506
- // Matches Maplibre defaults
4507
- // https://github.com/maplibre/maplibre-gl-js/blob/f8ab4b48d59ab8fe7b068b102538793bbdd4c848/src/geo/projection/globe_transform.ts#L632-L633
4508
- nearZMultiplier = 0.5,
4509
- farZMultiplier = 1,
4510
- resolution = 10
4511
- } = opts;
4512
- let { latitude = 0, height, altitude = 1.5, fovy } = opts;
4513
- latitude = Math.max(Math.min(latitude, 90), -90);
4514
- height = height || 1;
4515
- if (fovy) {
4516
- altitude = (0, import_web_mercator4.fovyToAltitude)(fovy);
4517
- } else {
4518
- fovy = (0, import_web_mercator4.altitudeToFovy)(altitude);
4519
- }
4520
- const scaleLatitude = Math.max(Math.min(latitude, import_web_mercator5.MAX_LATITUDE), -import_web_mercator5.MAX_LATITUDE);
4521
- const scale = Math.pow(2, zoom - zoomAdjust(scaleLatitude));
4522
- const pitchRadians = pitch * DEGREES_TO_RADIANS2;
4523
- const nearZ = opts.nearZ ?? nearZMultiplier;
4524
- const farZ = opts.farZ ?? (altitude + GLOBE_RADIUS * 2 * scale / height / Math.max(Math.cos(pitchRadians), 0.1)) * farZMultiplier;
4525
- const viewMatrix = new import_core11.Matrix4().lookAt({ eye: [0, -altitude, 0], up: [0, 0, 1] }).rotateX(-pitchRadians).rotateY(-bearing * DEGREES_TO_RADIANS2).rotateX(latitude * DEGREES_TO_RADIANS2).rotateZ(-longitude * DEGREES_TO_RADIANS2).scale(scale / height);
4526
- super({
4527
- ...opts,
4528
- // x, y, width,
4529
- height,
4530
- // view matrix
4531
- viewMatrix,
4532
- longitude,
4533
- latitude,
4534
- zoom,
4535
- // projection matrix parameters
4536
- distanceScales: getDistanceScales2(),
4537
- fovy,
4538
- focalDistance: altitude,
4539
- near: nearZ,
4540
- far: farZ
4541
- });
4542
- this.scale = scale;
4543
- this.latitude = latitude;
4544
- this.longitude = longitude;
4545
- this.bearing = bearing;
4546
- this.pitch = pitch;
4547
- this.fovy = fovy;
4548
- this.resolution = resolution;
4549
- }
4550
- get projectionMode() {
4551
- return PROJECTION_MODE.GLOBE;
4552
- }
4553
- getDistanceScales() {
4554
- return this.distanceScales;
4555
- }
4556
- getBounds(options = {}) {
4557
- const unprojectOption = { targetZ: options.z || 0 };
4558
- const left = this.unproject([0, this.height / 2], unprojectOption);
4559
- const top = this.unproject([this.width / 2, 0], unprojectOption);
4560
- const right = this.unproject([this.width, this.height / 2], unprojectOption);
4561
- const bottom = this.unproject([this.width / 2, this.height], unprojectOption);
4562
- if (right[0] < this.longitude) right[0] += 360;
4563
- if (left[0] > this.longitude) left[0] -= 360;
4564
- return [
4565
- Math.min(left[0], right[0], top[0], bottom[0]),
4566
- Math.min(left[1], right[1], top[1], bottom[1]),
4567
- Math.max(left[0], right[0], top[0], bottom[0]),
4568
- Math.max(left[1], right[1], top[1], bottom[1])
4569
- ];
4570
- }
4571
- unproject(xyz, { topLeft = true, targetZ } = {}) {
4572
- const [x, y, z] = xyz;
4573
- const y2 = topLeft ? y : this.height - y;
4574
- const { pixelUnprojectionMatrix } = this;
4575
- let coord;
4576
- if (Number.isFinite(z)) {
4577
- coord = transformVector(pixelUnprojectionMatrix, [x, y2, z, 1]);
4578
- } else {
4579
- const coord0 = transformVector(pixelUnprojectionMatrix, [x, y2, -1, 1]);
4580
- const coord1 = transformVector(pixelUnprojectionMatrix, [x, y2, 1, 1]);
4581
- const lt = ((targetZ || 0) / EARTH_RADIUS + 1) * GLOBE_RADIUS;
4582
- const lSqr = import_core12.vec3.sqrLen(import_core12.vec3.sub([], coord0, coord1));
4583
- const l0Sqr = import_core12.vec3.sqrLen(coord0);
4584
- const l1Sqr = import_core12.vec3.sqrLen(coord1);
4585
- const sSqr = (4 * l0Sqr * l1Sqr - (lSqr - l0Sqr - l1Sqr) ** 2) / 16;
4586
- const dSqr = 4 * sSqr / lSqr;
4587
- const r0 = Math.sqrt(l0Sqr - dSqr);
4588
- const dr = Math.sqrt(Math.max(0, lt * lt - dSqr));
4589
- const t = (r0 - dr) / Math.sqrt(lSqr);
4590
- coord = import_core12.vec3.lerp([], coord0, coord1, t);
4591
- }
4592
- const [X, Y, Z] = this.unprojectPosition(coord);
4593
- if (Number.isFinite(z)) {
4594
- return [X, Y, Z];
4595
- }
4596
- return Number.isFinite(targetZ) ? [X, Y, targetZ] : [X, Y];
4597
- }
4598
- projectPosition(xyz) {
4599
- const [lng, lat, Z = 0] = xyz;
4600
- const lambda = lng * DEGREES_TO_RADIANS2;
4601
- const phi = lat * DEGREES_TO_RADIANS2;
4602
- const cosPhi = Math.cos(phi);
4603
- const D = (Z / EARTH_RADIUS + 1) * GLOBE_RADIUS;
4604
- return [Math.sin(lambda) * cosPhi * D, -Math.cos(lambda) * cosPhi * D, Math.sin(phi) * D];
4605
- }
4606
- unprojectPosition(xyz) {
4607
- const [x, y, z] = xyz;
4608
- const D = import_core12.vec3.len(xyz);
4609
- const phi = Math.asin(z / D);
4610
- const lambda = Math.atan2(x, -y);
4611
- const lng = lambda * RADIANS_TO_DEGREES;
4612
- const lat = phi * RADIANS_TO_DEGREES;
4613
- const Z = (D / GLOBE_RADIUS - 1) * EARTH_RADIUS;
4614
- return [lng, lat, Z];
4615
- }
4616
- projectFlat(xyz) {
4617
- return xyz;
4618
- }
4619
- unprojectFlat(xyz) {
4620
- return xyz;
4621
- }
4622
- /**
4623
- * Pan the globe using delta-based movement
4624
- * @param coords - the geographic coordinates where the pan started
4625
- * @param pixel - the current screen position
4626
- * @param startPixel - the screen position where the pan started
4627
- * @returns updated viewport options with new longitude/latitude
4628
- */
4629
- panByPosition([startLng, startLat, startZoom], pixel, startPixel) {
4630
- const scale = Math.pow(2, this.zoom - zoomAdjust(this.latitude));
4631
- const rotationSpeed = 0.25 / scale;
4632
- const longitude = startLng + rotationSpeed * (startPixel[0] - pixel[0]);
4633
- let latitude = startLat - rotationSpeed * (startPixel[1] - pixel[1]);
4634
- latitude = Math.max(Math.min(latitude, 90), -90);
4635
- const out = { longitude, latitude, zoom: startZoom - zoomAdjust(startLat) };
4636
- out.zoom += zoomAdjust(out.latitude);
4637
- return out;
4638
- }
4639
- };
4640
- GlobeViewport.displayName = "GlobeViewport";
4641
- function zoomAdjust(latitude, clampToPoles) {
4642
- if (clampToPoles) {
4643
- latitude = Math.max(Math.min(latitude, import_web_mercator5.MAX_LATITUDE), -import_web_mercator5.MAX_LATITUDE);
4644
- }
4645
- const scaleAdjust = Math.PI * Math.cos(latitude * Math.PI / 180);
4646
- return Math.log2(scaleAdjust);
4647
- }
4648
- function transformVector(matrix, vector) {
4649
- const result = import_core12.vec4.transformMat4([], vector, matrix);
4650
- import_core12.vec4.scale(result, result, 1 / result[3]);
4651
- return result;
4652
- }
4653
-
4654
- // ../core/src/transitions/linear-interpolator.ts
4655
4481
  var DEFAULT_PROPS = ["longitude", "latitude", "zoom", "bearing", "pitch"];
4656
4482
  var DEFAULT_REQUIRED_PROPS = ["longitude", "latitude", "zoom"];
4657
4483
  var LinearInterpolator = class extends TransitionInterpolator {
@@ -4678,28 +4504,23 @@ var LinearInterpolator = class extends TransitionInterpolator {
4678
4504
  const result = super.initializeProps(startProps, endProps);
4679
4505
  const { makeViewport, around } = this.opts;
4680
4506
  if (makeViewport && around) {
4681
- const TestViewport = makeViewport(startProps);
4682
- if (TestViewport instanceof GlobeViewport) {
4683
- log_default.warn("around not supported in GlobeView")();
4684
- } else {
4685
- const startViewport = makeViewport(startProps);
4686
- const endViewport = makeViewport(endProps);
4687
- const aroundPosition = startViewport.unproject(around);
4688
- result.start.around = around;
4689
- Object.assign(result.end, {
4690
- around: endViewport.project(aroundPosition),
4691
- aroundPosition,
4692
- width: endProps.width,
4693
- height: endProps.height
4694
- });
4695
- }
4507
+ const startViewport = makeViewport(startProps);
4508
+ const endViewport = makeViewport(endProps);
4509
+ const aroundPosition = startViewport.unproject(around);
4510
+ result.start.around = around;
4511
+ Object.assign(result.end, {
4512
+ around: endViewport.project(aroundPosition),
4513
+ aroundPosition,
4514
+ width: endProps.width,
4515
+ height: endProps.height
4516
+ });
4696
4517
  }
4697
4518
  return result;
4698
4519
  }
4699
4520
  interpolateProps(startProps, endProps, t) {
4700
4521
  const propsInTransition = {};
4701
4522
  for (const key of this._propsToExtract) {
4702
- propsInTransition[key] = (0, import_core13.lerp)(startProps[key] || 0, endProps[key] || 0, t);
4523
+ propsInTransition[key] = (0, import_core11.lerp)(startProps[key] || 0, endProps[key] || 0, t);
4703
4524
  }
4704
4525
  if (endProps.aroundPosition && this.opts.makeViewport) {
4705
4526
  const viewport = this.opts.makeViewport({ ...endProps, ...propsInTransition });
@@ -4708,7 +4529,7 @@ var LinearInterpolator = class extends TransitionInterpolator {
4708
4529
  viewport.panByPosition(
4709
4530
  endProps.aroundPosition,
4710
4531
  // anchor point in current screen coordinates
4711
- (0, import_core13.lerp)(startProps.around, endProps.around, t)
4532
+ (0, import_core11.lerp)(startProps.around, endProps.around, t)
4712
4533
  )
4713
4534
  );
4714
4535
  }
@@ -4717,6 +4538,7 @@ var LinearInterpolator = class extends TransitionInterpolator {
4717
4538
  };
4718
4539
 
4719
4540
  // ../core/src/controllers/controller.ts
4541
+ var import_web_mercator4 = require("@math.gl/web-mercator");
4720
4542
  var NO_TRANSITION_PROPS = {
4721
4543
  transitionDuration: 0
4722
4544
  };
@@ -4765,6 +4587,7 @@ var Controller = class {
4765
4587
  this.touchRotate = false;
4766
4588
  this.multiTouchDrag = null;
4767
4589
  this.trackpadGesture = false;
4590
+ this.zoomAround = "pointer";
4768
4591
  this.keyboard = true;
4769
4592
  this.transitionManager = new TransitionManager({
4770
4593
  ...opts,
@@ -4859,6 +4682,15 @@ var Controller = class {
4859
4682
  const { offsetCenter } = event;
4860
4683
  return [offsetCenter.x - x, offsetCenter.y - y];
4861
4684
  }
4685
+ /** Resolves the zoom anchor for pointer-based controls. */
4686
+ getZoomPosition(position) {
4687
+ if (this.zoomAround === "pointer") {
4688
+ return position;
4689
+ }
4690
+ const viewport = this.makeViewport(this.controllerState.getViewportProps());
4691
+ const [centerX, centerY] = (0, import_web_mercator4.worldToPixels)(viewport.center, viewport.pixelProjectionMatrix);
4692
+ return [centerX, centerY];
4693
+ }
4862
4694
  isPointInBounds(pos, event) {
4863
4695
  const { width, height } = this.props;
4864
4696
  if (event && event.handled) {
@@ -4916,6 +4748,7 @@ var Controller = class {
4916
4748
  touchRotate = false,
4917
4749
  multiTouchDrag = touchRotate ? "rotate" : null,
4918
4750
  trackpadGesture = false,
4751
+ zoomAround = "pointer",
4919
4752
  keyboard = true
4920
4753
  } = props;
4921
4754
  const isInteractive = Boolean(this.onViewStateChange);
@@ -4938,6 +4771,7 @@ var Controller = class {
4938
4771
  this.touchRotate = multiTouchDrag === "rotate";
4939
4772
  this.multiTouchDrag = multiTouchDrag;
4940
4773
  this.trackpadGesture = trackpadGesture;
4774
+ this.zoomAround = zoomAround;
4941
4775
  this.keyboard = keyboard;
4942
4776
  const constraintChanged = !oldProps || oldProps.height !== props.height || oldProps.width !== props.width || oldProps.maxBounds !== props.maxBounds || oldProps.maxBoundsPadding !== props.maxBoundsPadding;
4943
4777
  if (constraintChanged && props.maxBounds) {
@@ -5171,8 +5005,9 @@ var Controller = class {
5171
5005
  if (delta < 0 && scale !== 0) {
5172
5006
  scale = 1 / scale;
5173
5007
  }
5174
- const transitionProps = smooth ? { ...this._getTransitionProps({ around: pos }), transitionDuration: 250 } : NO_TRANSITION_PROPS;
5175
- const newControllerState = this.controllerState.zoom({ pos, scale });
5008
+ const zoomPosition = this.getZoomPosition(pos);
5009
+ const transitionProps = smooth ? { ...this._getTransitionProps({ around: zoomPosition }), transitionDuration: 250 } : NO_TRANSITION_PROPS;
5010
+ const newControllerState = this.controllerState.zoom({ pos: zoomPosition, scale });
5176
5011
  this.updateViewport(newControllerState, transitionProps, {
5177
5012
  isZooming: true,
5178
5013
  isPanning: true
@@ -5258,7 +5093,7 @@ var Controller = class {
5258
5093
  if (!this.isPointInBounds(pos, event)) {
5259
5094
  return false;
5260
5095
  }
5261
- const newControllerState = this.controllerState.zoomStart({ pos }, this._getConstraintContext("zoom", "start")).rotateStart({ pos }, this._getConstraintContext("rotate", "start"));
5096
+ const newControllerState = this.controllerState.zoomStart({ pos: this.getZoomPosition(pos) }, this._getConstraintContext("zoom", "start")).rotateStart({ pos }, this._getConstraintContext("rotate", "start"));
5262
5097
  pinchEventWorkaround._startPinchRotation = event.rotation;
5263
5098
  pinchEventWorkaround._lastPinchEvent = event;
5264
5099
  this.updateViewport(newControllerState, NO_TRANSITION_PROPS, { isDragging: true });
@@ -5277,7 +5112,7 @@ var Controller = class {
5277
5112
  const { scale } = event;
5278
5113
  const pos = this.getCenter(event);
5279
5114
  newControllerState = newControllerState.zoom(
5280
- { pos, scale },
5115
+ { pos: this.getZoomPosition(pos), scale },
5281
5116
  this._getConstraintContext("zoom", "update")
5282
5117
  );
5283
5118
  }
@@ -5305,15 +5140,16 @@ var Controller = class {
5305
5140
  const { _lastPinchEvent } = pinchEventWorkaround;
5306
5141
  if (this.touchZoom && inertia && _lastPinchEvent && event.scale !== _lastPinchEvent.scale) {
5307
5142
  const pos = this.getCenter(event);
5143
+ const zoomPosition = this.getZoomPosition(pos);
5308
5144
  let newControllerState = this.controllerState.rotateEnd();
5309
5145
  const z = Math.log2(event.scale);
5310
5146
  const velocityZ = (z - Math.log2(_lastPinchEvent.scale)) / (event.deltaTime - _lastPinchEvent.deltaTime);
5311
5147
  const endScale = Math.pow(2, z + velocityZ * inertia / 2);
5312
- newControllerState = newControllerState.zoom({ pos, scale: endScale }).zoomEnd();
5148
+ newControllerState = newControllerState.zoom({ pos: zoomPosition, scale: endScale }).zoomEnd();
5313
5149
  this.updateViewport(
5314
5150
  newControllerState,
5315
5151
  {
5316
- ...this._getTransitionProps({ around: pos }),
5152
+ ...this._getTransitionProps({ around: zoomPosition }),
5317
5153
  transitionDuration: inertia,
5318
5154
  transitionEasing: INERTIA_EASING
5319
5155
  },
@@ -5358,8 +5194,12 @@ var Controller = class {
5358
5194
  return false;
5359
5195
  }
5360
5196
  const isZoomOut = this.isFunctionKeyPressed(event);
5361
- const newControllerState = this.controllerState.zoom({ pos, scale: isZoomOut ? 0.5 : 2 });
5362
- this.updateViewport(newControllerState, this._getTransitionProps({ around: pos }), {
5197
+ const zoomPosition = this.getZoomPosition(pos);
5198
+ const newControllerState = this.controllerState.zoom({
5199
+ pos: zoomPosition,
5200
+ scale: isZoomOut ? 0.5 : 2
5201
+ });
5202
+ this.updateViewport(newControllerState, this._getTransitionProps({ around: zoomPosition }), {
5363
5203
  isZooming: true,
5364
5204
  isPanning: true
5365
5205
  });
@@ -5376,14 +5216,14 @@ var Controller = class {
5376
5216
  this._doubleClickDragAnchor = null;
5377
5217
  return false;
5378
5218
  }
5379
- this._doubleClickDragAnchor = pos;
5219
+ this._doubleClickDragAnchor = this.getZoomPosition(pos);
5380
5220
  let newControllerState = this.controllerState.zoomStart(
5381
- { pos },
5221
+ { pos: this._doubleClickDragAnchor },
5382
5222
  this._getConstraintContext("zoom", "start")
5383
5223
  );
5384
5224
  if (event.scale !== 1) {
5385
5225
  newControllerState = newControllerState.zoom(
5386
- { pos, scale: event.scale },
5226
+ { pos: this._doubleClickDragAnchor, scale: event.scale },
5387
5227
  this._getConstraintContext("zoom", "update")
5388
5228
  );
5389
5229
  }
@@ -5552,7 +5392,7 @@ function getMaxBoundsExtents(viewport, target, rect) {
5552
5392
  }
5553
5393
 
5554
5394
  // ../core/src/controllers/map-controller.ts
5555
- var import_web_mercator6 = require("@math.gl/web-mercator");
5395
+ var import_web_mercator5 = require("@math.gl/web-mercator");
5556
5396
  var PITCH_MOUSE_THRESHOLD = 5;
5557
5397
  var PITCH_ACCEL = 1.2;
5558
5398
  var WEB_MERCATOR_TILE_SIZE = 512;
@@ -5566,11 +5406,11 @@ function lngLatToWorld2([lng, lat]) {
5566
5406
  lat = Math.sign(lat) * 90;
5567
5407
  }
5568
5408
  if (Number.isFinite(lng)) {
5569
- const [x, y2] = (0, import_web_mercator6.lngLatToWorld)([lng, lat]);
5570
- return [x, (0, import_core14.clamp)(y2, 0, WEB_MERCATOR_TILE_SIZE)];
5409
+ const [x, y2] = (0, import_web_mercator5.lngLatToWorld)([lng, lat]);
5410
+ return [x, (0, import_core12.clamp)(y2, 0, WEB_MERCATOR_TILE_SIZE)];
5571
5411
  }
5572
- const [, y] = (0, import_web_mercator6.lngLatToWorld)([0, lat]);
5573
- return [lng, (0, import_core14.clamp)(y, 0, WEB_MERCATOR_TILE_SIZE)];
5412
+ const [, y] = (0, import_web_mercator5.lngLatToWorld)([0, lat]);
5413
+ return [lng, (0, import_core12.clamp)(y, 0, WEB_MERCATOR_TILE_SIZE)];
5574
5414
  }
5575
5415
  var MapState = class extends ViewState {
5576
5416
  constructor(options) {
@@ -5879,7 +5719,7 @@ var MapState = class extends ViewState {
5879
5719
  props.bearing = mod(bearing + 180, 360) - 180;
5880
5720
  }
5881
5721
  }
5882
- props.pitch = (0, import_core14.clamp)(pitch, minPitch, maxPitch);
5722
+ props.pitch = (0, import_core12.clamp)(pitch, minPitch, maxPitch);
5883
5723
  const constrainedZoom = this._constrainZoom(props.zoom, props);
5884
5724
  const shouldRubberBand = rubberBand && (constraintContext == null ? void 0 : constraintContext.mode) === "elastic";
5885
5725
  props.zoom = (constraintContext == null ? void 0 : constraintContext.mode) === "preserve" ? props.zoom : shouldRubberBand ? applyRubberBand(props.zoom, constrainedZoom, ZOOM_RUBBER_BAND_RANGE) : constrainedZoom;
@@ -5914,8 +5754,8 @@ var MapState = class extends ViewState {
5914
5754
  ];
5915
5755
  const center = lngLatToWorld2([props.longitude, props.latitude]);
5916
5756
  const constrainedCenter = [
5917
- (0, import_core14.clamp)(center[0], minimumCenter[0], maximumCenter[0]),
5918
- (0, import_core14.clamp)(center[1], minimumCenter[1], maximumCenter[1])
5757
+ (0, import_core12.clamp)(center[0], minimumCenter[0], maximumCenter[0]),
5758
+ (0, import_core12.clamp)(center[1], minimumCenter[1], maximumCenter[1])
5919
5759
  ];
5920
5760
  const displayedCenter = center.slice();
5921
5761
  if (maxBoundsRect.width >= 0) {
@@ -5925,7 +5765,7 @@ var MapState = class extends ViewState {
5925
5765
  displayedCenter[1] = (constraintContext == null ? void 0 : constraintContext.mode) === "preserve" ? center[1] : shouldRubberBand ? applyRubberBand(center[1], constrainedCenter[1], maxBoundsRect.height / 2 / scale) : constrainedCenter[1];
5926
5766
  }
5927
5767
  if (displayedCenter[0] !== center[0] || displayedCenter[1] !== center[1]) {
5928
- const [displayedLongitude, displayedLatitude] = (0, import_web_mercator6.worldToLngLat)(displayedCenter);
5768
+ const [displayedLongitude, displayedLatitude] = (0, import_web_mercator5.worldToLngLat)(displayedCenter);
5929
5769
  if (displayedCenter[0] !== center[0]) {
5930
5770
  props.longitude = displayedLongitude;
5931
5771
  }
@@ -5956,7 +5796,7 @@ var MapState = class extends ViewState {
5956
5796
  }
5957
5797
  if (minZoom > maxZoom) minZoom = maxZoom;
5958
5798
  }
5959
- return (0, import_core14.clamp)(zoom, minZoom, maxZoom);
5799
+ return (0, import_core12.clamp)(zoom, minZoom, maxZoom);
5960
5800
  }
5961
5801
  _zoomFromCenter(scale, constraintContext) {
5962
5802
  const { width, height } = this.getViewportProps();
@@ -6012,7 +5852,7 @@ var MapState = class extends ViewState {
6012
5852
  deltaScaleY = 1 - centerY / startY;
6013
5853
  }
6014
5854
  }
6015
- deltaScaleY = (0, import_core14.clamp)(deltaScaleY, -1, 1);
5855
+ deltaScaleY = (0, import_core12.clamp)(deltaScaleY, -1, 1);
6016
5856
  const { minPitch, maxPitch } = this.getViewportProps();
6017
5857
  const bearing = startBearing + 180 * deltaScaleX;
6018
5858
  let pitch = startPitch;
@@ -6341,7 +6181,7 @@ var DeckRenderer = class {
6341
6181
  };
6342
6182
 
6343
6183
  // ../core/src/lib/deck-picker.ts
6344
- var import_core15 = require("@luma.gl/core");
6184
+ var import_core13 = require("@luma.gl/core");
6345
6185
 
6346
6186
  // ../core/src/lib/picking/query-object.ts
6347
6187
  var NO_PICKED_OBJECT = {
@@ -6610,7 +6450,7 @@ var DeckPicker = class {
6610
6450
  format: "rgba8unorm",
6611
6451
  width: 1,
6612
6452
  height: 1,
6613
- usage: import_core15.Texture.RENDER_ATTACHMENT | import_core15.Texture.COPY_SRC
6453
+ usage: import_core13.Texture.RENDER_ATTACHMENT | import_core13.Texture.COPY_SRC
6614
6454
  });
6615
6455
  this.pickingFBO = this.device.createFramebuffer({
6616
6456
  colorAttachments: [pickingColorTexture],
@@ -6621,7 +6461,7 @@ var DeckPicker = class {
6621
6461
  format: "rgba32float",
6622
6462
  width: 1,
6623
6463
  height: 1,
6624
- usage: import_core15.Texture.RENDER_ATTACHMENT | import_core15.Texture.COPY_SRC
6464
+ usage: import_core13.Texture.RENDER_ATTACHMENT | import_core13.Texture.COPY_SRC
6625
6465
  });
6626
6466
  const depthFBO = this.device.createFramebuffer({
6627
6467
  colorAttachments: [depthColorTexture],
@@ -7139,7 +6979,7 @@ var DeckPicker = class {
7139
6979
  const layout = texture.computeMemoryLayout(options);
7140
6980
  const readBuffer = this.device.createBuffer({
7141
6981
  byteLength: layout.byteLength,
7142
- usage: import_core15.Buffer.COPY_DST | import_core15.Buffer.MAP_READ
6982
+ usage: import_core13.Buffer.COPY_DST | import_core13.Buffer.MAP_READ
7143
6983
  });
7144
6984
  try {
7145
6985
  texture.readBuffer(options, readBuffer);
@@ -7864,7 +7704,7 @@ var CanvasManager = class {
7864
7704
  };
7865
7705
 
7866
7706
  // ../core/src/lib/deck.ts
7867
- var import_core16 = require("@luma.gl/core");
7707
+ var import_core14 = require("@luma.gl/core");
7868
7708
  var import_webgl = require("@luma.gl/webgl");
7869
7709
 
7870
7710
  // ../../node_modules/@luma.gl/webgl/dist/constants/webgl-constants.js
@@ -9482,7 +9322,7 @@ var Deck = class {
9482
9322
  // we must use 'premultiplied' canvas for webgpu to enable transparency and match shaders
9483
9323
  alphaMode: ((_b = this.props.deviceProps) == null ? void 0 : _b.type) === "webgpu" ? "premultiplied" : void 0
9484
9324
  };
9485
- return import_core16.luma.createDevice({
9325
+ return import_core14.luma.createDevice({
9486
9326
  // luma by default throws if a device is already attached
9487
9327
  // asynchronous device creation could happen after finalize() is called
9488
9328
  // TODO - createDevice should support AbortController?
@@ -9763,7 +9603,7 @@ var Deck = class {
9763
9603
  metrics.cpuTime = stats.get("CPU Time").time;
9764
9604
  metrics.gpuTimePerFrame = stats.get("GPU Time").getAverageTime();
9765
9605
  metrics.cpuTimePerFrame = stats.get("CPU Time").getAverageTime();
9766
- const memoryStats = import_core16.luma.stats.get("GPU Time and Memory");
9606
+ const memoryStats = import_core14.luma.stats.get("GPU Time and Memory");
9767
9607
  metrics.bufferMemory = memoryStats.get("Buffer Memory").count;
9768
9608
  metrics.textureMemory = memoryStats.get("Texture Memory").count;
9769
9609
  metrics.renderbufferMemory = memoryStats.get("Renderbuffer Memory").count;
@@ -9775,11 +9615,280 @@ Deck.defaultProps = defaultProps;
9775
9615
  // https://github.com/visgl/deck.gl/issues/3213
9776
9616
  Deck.VERSION = VERSION;
9777
9617
 
9618
+ // ../core/src/viewports/globe-viewport.ts
9619
+ var import_core15 = require("@math.gl/core");
9620
+ var import_web_mercator6 = require("@math.gl/web-mercator");
9621
+ var DEGREES_TO_RADIANS2 = Math.PI / 180;
9622
+ var RADIANS_TO_DEGREES = 180 / Math.PI;
9623
+ var NORTH_UP_BEARING_THRESHOLD = 1;
9624
+ var EARTH_RADIUS = 6370972;
9625
+ var GLOBE_RADIUS = 256;
9626
+ var GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO = 0.75;
9627
+ var GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO = 1.15;
9628
+ function isGlobeNorthUp(bearing) {
9629
+ const normalizedBearing = mod(bearing + 180, 360) - 180;
9630
+ return Math.abs(normalizedBearing) < NORTH_UP_BEARING_THRESHOLD;
9631
+ }
9632
+ function getDistanceScales2() {
9633
+ const unitsPerMeter3 = GLOBE_RADIUS / EARTH_RADIUS;
9634
+ const unitsPerDegree = Math.PI / 180 * GLOBE_RADIUS;
9635
+ return {
9636
+ unitsPerMeter: [unitsPerMeter3, unitsPerMeter3, unitsPerMeter3],
9637
+ unitsPerMeter2: [0, 0, 0],
9638
+ metersPerUnit: [1 / unitsPerMeter3, 1 / unitsPerMeter3, 1 / unitsPerMeter3],
9639
+ unitsPerDegree: [unitsPerDegree, unitsPerDegree, unitsPerMeter3],
9640
+ unitsPerDegree2: [0, 0, 0],
9641
+ degreesPerUnit: [1 / unitsPerDegree, 1 / unitsPerDegree, 1 / unitsPerMeter3]
9642
+ };
9643
+ }
9644
+ var GlobeViewport = class extends Viewport {
9645
+ constructor(opts = {}) {
9646
+ const {
9647
+ longitude = 0,
9648
+ bearing = 0,
9649
+ pitch = 0,
9650
+ zoom = 0,
9651
+ // Matches Maplibre defaults
9652
+ // https://github.com/maplibre/maplibre-gl-js/blob/f8ab4b48d59ab8fe7b068b102538793bbdd4c848/src/geo/projection/globe_transform.ts#L632-L633
9653
+ nearZMultiplier = 0.5,
9654
+ farZMultiplier = 1,
9655
+ resolution = 10
9656
+ } = opts;
9657
+ let { latitude = 0, height, altitude = 1.5, fovy } = opts;
9658
+ latitude = Math.max(Math.min(latitude, 90), -90);
9659
+ height = height || 1;
9660
+ if (fovy) {
9661
+ altitude = (0, import_web_mercator6.fovyToAltitude)(fovy);
9662
+ } else {
9663
+ fovy = (0, import_web_mercator6.altitudeToFovy)(altitude);
9664
+ }
9665
+ const scaleLatitude = Math.max(Math.min(latitude, import_web_mercator6.MAX_LATITUDE), -import_web_mercator6.MAX_LATITUDE);
9666
+ const scale = Math.pow(2, zoom - zoomAdjust(scaleLatitude));
9667
+ const pitchRadians = pitch * DEGREES_TO_RADIANS2;
9668
+ const nearZ = opts.nearZ ?? nearZMultiplier;
9669
+ const farZ = opts.farZ ?? (altitude + GLOBE_RADIUS * 2 * scale / height / Math.max(Math.cos(pitchRadians), 0.1)) * farZMultiplier;
9670
+ const viewMatrix = new import_core15.Matrix4().lookAt({ eye: [0, -altitude, 0], up: [0, 0, 1] }).rotateX(-pitchRadians).rotateY(-bearing * DEGREES_TO_RADIANS2).rotateX(latitude * DEGREES_TO_RADIANS2).rotateZ(-longitude * DEGREES_TO_RADIANS2).scale(scale / height);
9671
+ super({
9672
+ ...opts,
9673
+ // x, y, width,
9674
+ height,
9675
+ // view matrix
9676
+ viewMatrix,
9677
+ longitude,
9678
+ latitude,
9679
+ zoom,
9680
+ // projection matrix parameters
9681
+ distanceScales: getDistanceScales2(),
9682
+ fovy,
9683
+ focalDistance: altitude,
9684
+ near: nearZ,
9685
+ far: farZ
9686
+ });
9687
+ this.scale = scale;
9688
+ this.latitude = latitude;
9689
+ this.longitude = longitude;
9690
+ this.bearing = bearing;
9691
+ this.pitch = pitch;
9692
+ this.fovy = fovy;
9693
+ this.resolution = resolution;
9694
+ }
9695
+ get projectionMode() {
9696
+ return PROJECTION_MODE.GLOBE;
9697
+ }
9698
+ getDistanceScales() {
9699
+ return this.distanceScales;
9700
+ }
9701
+ getBounds(options = {}) {
9702
+ const unprojectOption = { targetZ: options.z || 0 };
9703
+ const left = this.unproject([0, this.height / 2], unprojectOption);
9704
+ const top = this.unproject([this.width / 2, 0], unprojectOption);
9705
+ const right = this.unproject([this.width, this.height / 2], unprojectOption);
9706
+ const bottom = this.unproject([this.width / 2, this.height], unprojectOption);
9707
+ if (right[0] < this.longitude) right[0] += 360;
9708
+ if (left[0] > this.longitude) left[0] -= 360;
9709
+ return [
9710
+ Math.min(left[0], right[0], top[0], bottom[0]),
9711
+ Math.min(left[1], right[1], top[1], bottom[1]),
9712
+ Math.max(left[0], right[0], top[0], bottom[0]),
9713
+ Math.max(left[1], right[1], top[1], bottom[1])
9714
+ ];
9715
+ }
9716
+ /**
9717
+ * Builds the screen-pixel → globe-center ray and the intermediate ray/sphere
9718
+ * math reused by `unproject` and anchored zoom. One function so the same
9719
+ * pixelUnprojectionMatrix work isn't duplicated.
9720
+ */
9721
+ _getRayToGlobe(screenPosition, { topLeft = true, targetZ } = {}) {
9722
+ const [screenX, screenY] = screenPosition;
9723
+ const adjustedScreenY = topLeft ? screenY : this.height - screenY;
9724
+ const { pixelUnprojectionMatrix } = this;
9725
+ const rayStartPosition = transformVector(pixelUnprojectionMatrix, [
9726
+ screenX,
9727
+ adjustedScreenY,
9728
+ -1,
9729
+ 1
9730
+ ]);
9731
+ const rayEndPosition = transformVector(pixelUnprojectionMatrix, [
9732
+ screenX,
9733
+ adjustedScreenY,
9734
+ 1,
9735
+ 1
9736
+ ]);
9737
+ const radius = ((targetZ || 0) / EARTH_RADIUS + 1) * GLOBE_RADIUS;
9738
+ const rayLengthSquared = import_core15.vec3.sqrLen(import_core15.vec3.sub([], rayStartPosition, rayEndPosition));
9739
+ const rayStartDistanceSquared = import_core15.vec3.sqrLen(rayStartPosition);
9740
+ const rayEndDistanceSquared = import_core15.vec3.sqrLen(rayEndPosition);
9741
+ const triangleAreaSquared = (4 * rayStartDistanceSquared * rayEndDistanceSquared - (rayLengthSquared - rayStartDistanceSquared - rayEndDistanceSquared) ** 2) / 16;
9742
+ const distanceToCenterSquared = 4 * triangleAreaSquared / rayLengthSquared;
9743
+ return {
9744
+ rayStartPosition,
9745
+ rayEndPosition,
9746
+ radius,
9747
+ rayLengthSquared,
9748
+ rayStartDistanceSquared,
9749
+ distanceToCenterSquared
9750
+ };
9751
+ }
9752
+ _getRayDistanceToGlobeCenterRatio(screenPosition, options) {
9753
+ const { distanceToCenterSquared, radius } = this._getRayToGlobe(screenPosition, options);
9754
+ return Math.sqrt(Math.max(0, distanceToCenterSquared)) / radius;
9755
+ }
9756
+ /**
9757
+ * Returns how strongly a screen position should anchor zoom on the visible globe.
9758
+ * A value of `0` means that the controller should fall back to center zoom.
9759
+ * @param screenPosition - Screen position to evaluate.
9760
+ * @returns Anchor strength from `0` to `1`.
9761
+ */
9762
+ getZoomAnchorStrength(screenPosition) {
9763
+ const distanceRatio = this._getRayDistanceToGlobeCenterRatio(screenPosition);
9764
+ if (distanceRatio >= GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO) {
9765
+ return 0;
9766
+ }
9767
+ const edgeProgress = Math.max(
9768
+ 0,
9769
+ Math.min(
9770
+ 1,
9771
+ (distanceRatio - GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO) / (GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO - GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO)
9772
+ )
9773
+ );
9774
+ const smoothProgress = edgeProgress * edgeProgress * (3 - 2 * edgeProgress);
9775
+ return 1 - smoothProgress;
9776
+ }
9777
+ unproject(xyz, { topLeft = true, targetZ } = {}) {
9778
+ const [x, y, z] = xyz;
9779
+ const y2 = topLeft ? y : this.height - y;
9780
+ const { pixelUnprojectionMatrix } = this;
9781
+ let coord;
9782
+ if (Number.isFinite(z)) {
9783
+ coord = transformVector(pixelUnprojectionMatrix, [x, y2, z, 1]);
9784
+ } else {
9785
+ const {
9786
+ rayStartPosition,
9787
+ rayEndPosition,
9788
+ radius,
9789
+ rayLengthSquared,
9790
+ rayStartDistanceSquared,
9791
+ distanceToCenterSquared
9792
+ } = this._getRayToGlobe(xyz, { topLeft, targetZ });
9793
+ const rayStartToClosestApproach = Math.sqrt(
9794
+ rayStartDistanceSquared - distanceToCenterSquared
9795
+ );
9796
+ const closestApproachToIntersection = Math.sqrt(
9797
+ Math.max(0, radius * radius - distanceToCenterSquared)
9798
+ );
9799
+ const intersectionRatio = (rayStartToClosestApproach - closestApproachToIntersection) / Math.sqrt(rayLengthSquared);
9800
+ coord = import_core15.vec3.lerp([], rayStartPosition, rayEndPosition, intersectionRatio);
9801
+ }
9802
+ const [X, Y, Z] = this.unprojectPosition(coord);
9803
+ if (Number.isFinite(z)) {
9804
+ return [X, Y, Z];
9805
+ }
9806
+ return Number.isFinite(targetZ) ? [X, Y, targetZ] : [X, Y];
9807
+ }
9808
+ projectPosition(xyz) {
9809
+ const [lng, lat, Z = 0] = xyz;
9810
+ const lambda = lng * DEGREES_TO_RADIANS2;
9811
+ const phi = lat * DEGREES_TO_RADIANS2;
9812
+ const cosPhi = Math.cos(phi);
9813
+ const D = (Z / EARTH_RADIUS + 1) * GLOBE_RADIUS;
9814
+ return [Math.sin(lambda) * cosPhi * D, -Math.cos(lambda) * cosPhi * D, Math.sin(phi) * D];
9815
+ }
9816
+ unprojectPosition(xyz) {
9817
+ const [x, y, z] = xyz;
9818
+ const D = import_core15.vec3.len(xyz);
9819
+ const phi = Math.asin(z / D);
9820
+ const lambda = Math.atan2(x, -y);
9821
+ const lng = lambda * RADIANS_TO_DEGREES;
9822
+ const lat = phi * RADIANS_TO_DEGREES;
9823
+ const Z = (D / GLOBE_RADIUS - 1) * EARTH_RADIUS;
9824
+ return [lng, lat, Z];
9825
+ }
9826
+ projectFlat(xyz) {
9827
+ return xyz;
9828
+ }
9829
+ unprojectFlat(xyz) {
9830
+ return xyz;
9831
+ }
9832
+ /**
9833
+ * Pan the globe to place geographic coordinates at a screen pixel.
9834
+ * When `dragStartPosition` is supplied, applies the delta-based movement used by globe dragging.
9835
+ * @param coordinates - Geographic anchor, or the starting longitude, latitude and zoom.
9836
+ * @param screenPosition - Current screen position.
9837
+ * @param dragStartPosition - Screen position where a drag started.
9838
+ * @returns Updated viewport options.
9839
+ */
9840
+ panByPosition(coordinates, screenPosition, dragStartPosition) {
9841
+ if (!dragStartPosition) {
9842
+ let anchorStrength = this.getZoomAnchorStrength(screenPosition);
9843
+ if (anchorStrength === 0) {
9844
+ return { longitude: this.longitude, latitude: this.latitude };
9845
+ }
9846
+ const currentCoordinates = this.unproject(screenPosition);
9847
+ const longitudeDelta = mod(coordinates[0] - currentCoordinates[0] + 180, 360) - 180;
9848
+ const latitudeDelta = coordinates[1] - currentCoordinates[1];
9849
+ const crossesPole = Math.abs(currentCoordinates[1]) > import_web_mercator6.MAX_LATITUDE || Math.abs(longitudeDelta) > 90;
9850
+ if (isGlobeNorthUp(this.bearing) && crossesPole) {
9851
+ return { longitude: this.longitude, latitude: this.latitude };
9852
+ }
9853
+ if (isGlobeNorthUp(this.bearing) && latitudeDelta !== 0) {
9854
+ const latitudeLimit = latitudeDelta > 0 ? import_web_mercator6.MAX_LATITUDE : -import_web_mercator6.MAX_LATITUDE;
9855
+ const latitudeConstraintStrength = (latitudeLimit - this.latitude) / latitudeDelta;
9856
+ anchorStrength = Math.min(anchorStrength, Math.max(0, latitudeConstraintStrength));
9857
+ }
9858
+ const longitude2 = this.longitude + longitudeDelta * anchorStrength;
9859
+ const latitude2 = Math.max(Math.min(this.latitude + latitudeDelta * anchorStrength, 90), -90);
9860
+ return { longitude: longitude2, latitude: latitude2 };
9861
+ }
9862
+ const [startLongitude, startLatitude, startZoom] = coordinates;
9863
+ const scale = Math.pow(2, this.zoom - zoomAdjust(this.latitude));
9864
+ const rotationSpeed = 0.25 / scale;
9865
+ const longitude = startLongitude + rotationSpeed * (dragStartPosition[0] - screenPosition[0]);
9866
+ let latitude = startLatitude - rotationSpeed * (dragStartPosition[1] - screenPosition[1]);
9867
+ latitude = Math.max(Math.min(latitude, 90), -90);
9868
+ const nextViewState = { longitude, latitude, zoom: startZoom - zoomAdjust(startLatitude) };
9869
+ nextViewState.zoom += zoomAdjust(nextViewState.latitude);
9870
+ return nextViewState;
9871
+ }
9872
+ };
9873
+ GlobeViewport.displayName = "GlobeViewport";
9874
+ function zoomAdjust(latitude, clampToPoles) {
9875
+ if (clampToPoles) {
9876
+ latitude = Math.max(Math.min(latitude, import_web_mercator6.MAX_LATITUDE), -import_web_mercator6.MAX_LATITUDE);
9877
+ }
9878
+ const scaleAdjust = Math.PI * Math.cos(latitude * Math.PI / 180);
9879
+ return Math.log2(scaleAdjust);
9880
+ }
9881
+ function transformVector(matrix, vector) {
9882
+ const result = import_core15.vec4.transformMat4([], vector, matrix);
9883
+ import_core15.vec4.scale(result, result, 1 / result[3]);
9884
+ return result;
9885
+ }
9886
+
9778
9887
  // ../core/src/controllers/globe-controller.ts
9779
- var import_core18 = require("@math.gl/core");
9888
+ var import_core17 = require("@math.gl/core");
9780
9889
 
9781
9890
  // ../core/src/viewports/globe-utils.ts
9782
- var import_core17 = require("@math.gl/core");
9891
+ var import_core16 = require("@math.gl/core");
9783
9892
  var DEGREES_TO_RADIANS3 = Math.PI / 180;
9784
9893
  var RADIANS_TO_DEGREES2 = 180 / Math.PI;
9785
9894
  var Globe = class _Globe {
@@ -9794,7 +9903,7 @@ var Globe = class _Globe {
9794
9903
  static toLngLat(v) {
9795
9904
  return [
9796
9905
  Math.atan2(v[1], v[0]) * RADIANS_TO_DEGREES2,
9797
- Math.asin((0, import_core17.clamp)(v[2], -1, 1)) * RADIANS_TO_DEGREES2
9906
+ Math.asin((0, import_core16.clamp)(v[2], -1, 1)) * RADIANS_TO_DEGREES2
9798
9907
  ];
9799
9908
  }
9800
9909
  /** North and East tangent vectors at a given (lng, lat) */
@@ -9821,7 +9930,7 @@ var Globe = class _Globe {
9821
9930
  /** Bearing (degrees) from a camera up vector at a given lng/lat */
9822
9931
  static bearing(upVector, lng, lat) {
9823
9932
  const { N, E } = _Globe.tangentBasis(lng, lat);
9824
- return Math.atan2(import_core17.vec3.dot(upVector, E), import_core17.vec3.dot(upVector, N)) * RADIANS_TO_DEGREES2;
9933
+ return Math.atan2(import_core16.vec3.dot(upVector, E), import_core16.vec3.dot(upVector, N)) * RADIANS_TO_DEGREES2;
9825
9934
  }
9826
9935
  /** Camera frame for panning at a given position/bearing */
9827
9936
  static cameraFrame(lng, lat, bearing) {
@@ -9835,8 +9944,8 @@ var Globe = class _Globe {
9835
9944
  return {
9836
9945
  position,
9837
9946
  up,
9838
- axisHorizontal: import_core17.vec3.cross([], position, right),
9839
- axisVertical: import_core17.vec3.cross([], position, up),
9947
+ axisHorizontal: import_core16.vec3.cross([], position, right),
9948
+ axisVertical: import_core16.vec3.cross([], position, up),
9840
9949
  longitude: lng,
9841
9950
  latitude: lat,
9842
9951
  bearing
@@ -9846,18 +9955,18 @@ var Globe = class _Globe {
9846
9955
  static angularDistance(a, b) {
9847
9956
  const pa = _Globe.toPosition(a.longitude, a.latitude);
9848
9957
  const pb = _Globe.toPosition(b.longitude, b.latitude);
9849
- return Math.acos((0, import_core17.clamp)(import_core17.vec3.dot(pa, pb), -1, 1));
9958
+ return Math.acos((0, import_core16.clamp)(import_core16.vec3.dot(pa, pb), -1, 1));
9850
9959
  }
9851
9960
  /** Normalized rotation axis of the great circle between two lng/lat points */
9852
9961
  static greatCircleAxis(a, b) {
9853
9962
  const pa = _Globe.toPosition(a.longitude, a.latitude);
9854
9963
  const pb = _Globe.toPosition(b.longitude, b.latitude);
9855
- return import_core17.vec3.normalize([], import_core17.vec3.cross([], pa, pb));
9964
+ return import_core16.vec3.normalize([], import_core16.vec3.cross([], pa, pb));
9856
9965
  }
9857
9966
  /** Rotate a vector around a unit axis by an angle (radians) using quaternions */
9858
9967
  static rotate(v, axis, angle) {
9859
- const q = new import_core17.Quaternion().fromAxisRotation(axis, angle);
9860
- return import_core17.vec3.transformQuat([], v, q);
9968
+ const q = new import_core16.Quaternion().fromAxisRotation(axis, angle);
9969
+ return import_core16.vec3.transformQuat([], v, q);
9861
9970
  }
9862
9971
  /**
9863
9972
  * Rotate a camera frame by horizontal/vertical angles (radians).
@@ -9881,6 +9990,40 @@ var Globe = class _Globe {
9881
9990
  bearing: b
9882
9991
  };
9883
9992
  }
9993
+ /**
9994
+ * Rotate a camera frame so one globe position moves toward another.
9995
+ * The position and camera up vector share one rigid-body rotation, allowing
9996
+ * bearing to evolve continuously as the camera moves around the sphere.
9997
+ */
9998
+ static rotateFrameToMatch(frame, currentCoordinates, targetCoordinates, strength = 1) {
9999
+ const currentPosition = _Globe.toPosition(...currentCoordinates);
10000
+ const targetPosition = _Globe.toPosition(...targetCoordinates);
10001
+ let rotationAxis = import_core16.vec3.cross([], currentPosition, targetPosition);
10002
+ const axisLength = import_core16.vec3.len(rotationAxis);
10003
+ const cosine = (0, import_core16.clamp)(import_core16.vec3.dot(currentPosition, targetPosition), -1, 1);
10004
+ if (axisLength < 1e-12) {
10005
+ if (cosine > 0) {
10006
+ return frame;
10007
+ }
10008
+ rotationAxis = import_core16.vec3.cross([], currentPosition, frame.up);
10009
+ if (import_core16.vec3.len(rotationAxis) < 1e-12) {
10010
+ rotationAxis = import_core16.vec3.cross([], currentPosition, frame.axisVertical);
10011
+ }
10012
+ }
10013
+ import_core16.vec3.normalize(rotationAxis, rotationAxis);
10014
+ const angle = Math.atan2(axisLength, cosine) * (0, import_core16.clamp)(strength, 0, 1);
10015
+ const position = _Globe.rotate(frame.position, rotationAxis, angle);
10016
+ const up = _Globe.rotate(frame.up, rotationAxis, angle);
10017
+ const [longitude, latitude] = _Globe.toLngLat(position);
10018
+ return {
10019
+ ...frame,
10020
+ position,
10021
+ up,
10022
+ longitude,
10023
+ latitude,
10024
+ bearing: _Globe.bearing(up, longitude, latitude)
10025
+ };
10026
+ }
9884
10027
  };
9885
10028
  var INERTIA_DECAY = 5;
9886
10029
  var INERTIA_NORM = 1 / (1 - Math.exp(-INERTIA_DECAY));
@@ -9933,7 +10076,6 @@ var GlobeInertiaInterpolator = class extends TransitionInterpolator {
9933
10076
  };
9934
10077
 
9935
10078
  // ../core/src/controllers/globe-controller.ts
9936
- var import_web_mercator7 = require("@math.gl/web-mercator");
9937
10079
  var DEGREES_TO_RADIANS4 = Math.PI / 180;
9938
10080
  var RADIANS_TO_DEGREES3 = 180 / Math.PI;
9939
10081
  function degreesToPixels(angle, zoom = 0) {
@@ -9948,35 +10090,23 @@ function pixelsToDegrees(pixels, zoom = 0) {
9948
10090
  }
9949
10091
  var GlobeState = class extends MapState {
9950
10092
  constructor(options) {
9951
- const {
9952
- startPanPos,
9953
- startPanCameraFrame,
9954
- startPanAngularRate,
9955
- startPanLockBearing,
9956
- ...mapStateOptions
9957
- } = options;
10093
+ const { startPanPos, startPanCameraFrame, startPanAngularRate, ...mapStateOptions } = options;
9958
10094
  mapStateOptions.normalize = false;
9959
10095
  super(mapStateOptions);
9960
10096
  const s = this._state;
9961
10097
  if (startPanPos !== void 0) s.startPanPos = startPanPos;
9962
10098
  if (startPanCameraFrame !== void 0) s.startPanCameraFrame = startPanCameraFrame;
9963
10099
  if (startPanAngularRate !== void 0) s.startPanAngularRate = startPanAngularRate;
9964
- if (startPanLockBearing !== void 0) s.startPanLockBearing = startPanLockBearing;
9965
10100
  }
9966
10101
  panStart({ pos }) {
9967
10102
  const { latitude, longitude, zoom, bearing = 0 } = this.getViewportProps();
9968
10103
  const cameraFrame = Globe.cameraFrame(longitude, latitude, bearing);
9969
- const lockBearing = Math.abs(bearing) < 1;
9970
- if (lockBearing) {
9971
- cameraFrame.axisHorizontal = [0, 0, 1];
9972
- }
9973
10104
  const scale = Math.pow(2, zoom - zoomAdjust(latitude, true));
9974
10105
  const angularRate = 0.25 / scale * DEGREES_TO_RADIANS4;
9975
10106
  return this._getUpdatedState({
9976
10107
  startPanPos: pos,
9977
10108
  startPanCameraFrame: cameraFrame,
9978
10109
  startPanAngularRate: angularRate,
9979
- startPanLockBearing: lockBearing,
9980
10110
  startZoom: zoom
9981
10111
  });
9982
10112
  }
@@ -9992,17 +10122,9 @@ var GlobeState = class extends MapState {
9992
10122
  }
9993
10123
  const dx = startPanPos[0] - pos[0];
9994
10124
  const dy = startPanPos[1] - pos[1];
9995
- let hAngle = dx * rate;
9996
- let vAngle = -dy * rate;
9997
- const locked = state.startPanLockBearing;
9998
- if (locked) {
9999
- const cosLat = Math.cos(frame.latitude * DEGREES_TO_RADIANS4);
10000
- hAngle = dx * rate / Math.max(cosLat, 0.25);
10001
- const maxUp = (import_web_mercator7.MAX_LATITUDE - frame.latitude) * DEGREES_TO_RADIANS4;
10002
- const maxDown = -(import_web_mercator7.MAX_LATITUDE + frame.latitude) * DEGREES_TO_RADIANS4;
10003
- vAngle = (0, import_core18.clamp)(vAngle, maxDown, maxUp);
10004
- }
10005
- const rotated = Globe.rotateFrame(frame, hAngle, vAngle, locked);
10125
+ const hAngle = dx * rate;
10126
+ const vAngle = -dy * rate;
10127
+ const rotated = Globe.rotateFrame(frame, hAngle, vAngle);
10006
10128
  const zoom = startZoom + zoomAdjust(rotated.latitude, true) - zoomAdjust(frame.latitude, true);
10007
10129
  return this._getUpdatedState({
10008
10130
  longitude: rotated.longitude,
@@ -10016,38 +10138,52 @@ var GlobeState = class extends MapState {
10016
10138
  startPanPos: null,
10017
10139
  startPanCameraFrame: null,
10018
10140
  startPanAngularRate: null,
10019
- startPanLockBearing: null,
10020
10141
  startZoom: null
10021
10142
  });
10022
10143
  }
10023
- zoom({ scale }) {
10024
- const startZoom = this.getState().startZoom || this.getViewportProps().zoom;
10025
- const zoom = startZoom + Math.log2(scale);
10026
- return this._getUpdatedState({ zoom });
10027
- }
10028
10144
  _panFromCenter(offset) {
10029
10145
  const { width, height } = this.getViewportProps();
10030
10146
  const center = [width / 2, height / 2];
10031
10147
  return this.panStart({ pos: center }).pan({ pos: [center[0] + offset[0], center[1] + offset[1]] }).panEnd();
10032
10148
  }
10033
10149
  applyConstraints(props) {
10034
- const { longitude, latitude, maxBounds } = props;
10150
+ const internalProps = props;
10151
+ const constraintAround = internalProps[CONSTRAINT_AROUND];
10152
+ delete internalProps[CONSTRAINT_AROUND];
10153
+ const { latitude, maxBounds } = props;
10035
10154
  props.zoom = this._constrainZoom(props.zoom, props);
10036
- if (longitude < -180 || longitude > 180) {
10037
- props.longitude = mod(longitude + 180, 360) - 180;
10155
+ if (constraintAround) {
10156
+ const viewport = this.makeViewport(props);
10157
+ const anchorStrength = viewport.getZoomAnchorStrength(constraintAround.screenPosition);
10158
+ if (anchorStrength > 0) {
10159
+ const currentCoordinates = viewport.unproject(constraintAround.screenPosition);
10160
+ const cameraFrame = Globe.cameraFrame(props.longitude, props.latitude, props.bearing || 0);
10161
+ const rotatedFrame = Globe.rotateFrameToMatch(
10162
+ cameraFrame,
10163
+ [currentCoordinates[0], currentCoordinates[1]],
10164
+ [constraintAround.position[0], constraintAround.position[1]],
10165
+ anchorStrength
10166
+ );
10167
+ props.longitude = rotatedFrame.longitude;
10168
+ props.latitude = rotatedFrame.latitude;
10169
+ props.bearing = rotatedFrame.bearing;
10170
+ }
10171
+ }
10172
+ if (props.longitude < -180 || props.longitude > 180) {
10173
+ props.longitude = mod(props.longitude + 180, 360) - 180;
10038
10174
  }
10039
- props.latitude = (0, import_core18.clamp)(latitude, -90, 90);
10040
10175
  if (props.bearing < -180 || props.bearing > 180) {
10041
10176
  props.bearing = mod(props.bearing + 180, 360) - 180;
10042
10177
  }
10043
- props.pitch = (0, import_core18.clamp)(props.pitch, props.minPitch, props.maxPitch);
10178
+ props.latitude = (0, import_core17.clamp)(props.latitude, -90, 90);
10179
+ props.pitch = (0, import_core17.clamp)(props.pitch, props.minPitch, props.maxPitch);
10044
10180
  const maxBoundsRect = maxBounds ? getMaxBoundsRect(props.width, props.height, props.maxBoundsPadding) : null;
10045
10181
  if (maxBounds && maxBoundsRect) {
10046
10182
  if (maxBoundsRect.width >= 0) {
10047
- props.longitude = (0, import_core18.clamp)(props.longitude, maxBounds[0][0], maxBounds[1][0]);
10183
+ props.longitude = (0, import_core17.clamp)(props.longitude, maxBounds[0][0], maxBounds[1][0]);
10048
10184
  }
10049
10185
  if (maxBoundsRect.height >= 0) {
10050
- props.latitude = (0, import_core18.clamp)(props.latitude, maxBounds[0][1], maxBounds[1][1]);
10186
+ props.latitude = (0, import_core17.clamp)(props.latitude, maxBounds[0][1], maxBounds[1][1]);
10051
10187
  }
10052
10188
  }
10053
10189
  if (maxBounds && maxBoundsRect) {
@@ -10067,7 +10203,7 @@ var GlobeState = class extends MapState {
10067
10203
  );
10068
10204
  const bottomDegrees = maxBoundsRect.height ? heightDegrees * screenExtents.bottom / maxBoundsRect.height : pixelsToDegrees(screenExtents.bottom, effectiveZoom);
10069
10205
  const topDegrees = maxBoundsRect.height ? heightDegrees * screenExtents.top / maxBoundsRect.height : pixelsToDegrees(screenExtents.top, effectiveZoom);
10070
- props.latitude = (0, import_core18.clamp)(
10206
+ props.latitude = (0, import_core17.clamp)(
10071
10207
  props.latitude,
10072
10208
  maxBounds[0][1] + bottomDegrees,
10073
10209
  maxBounds[1][1] - topDegrees
@@ -10089,13 +10225,14 @@ var GlobeState = class extends MapState {
10089
10225
  screenExtents.right / Math.cos(props.latitude * DEGREES_TO_RADIANS4),
10090
10226
  effectiveZoom
10091
10227
  );
10092
- props.longitude = (0, import_core18.clamp)(
10228
+ props.longitude = (0, import_core17.clamp)(
10093
10229
  props.longitude,
10094
10230
  maxBounds[0][0] + leftDegrees,
10095
10231
  maxBounds[1][0] - rightDegrees
10096
10232
  );
10097
10233
  }
10098
10234
  }
10235
+ props.latitude = (0, import_core17.clamp)(props.latitude, -90, 90);
10099
10236
  if (props.latitude !== latitude) {
10100
10237
  props.zoom += zoomAdjust(props.latitude, true) - zoomAdjust(latitude, true);
10101
10238
  }
@@ -10123,7 +10260,7 @@ var GlobeState = class extends MapState {
10123
10260
  if (minZoom > maxZoom) minZoom = maxZoom;
10124
10261
  }
10125
10262
  const zoomAdjustment = zoomAdjust(props.latitude, true) - zoomAdjust(0, true);
10126
- return (0, import_core18.clamp)(zoom, minZoom + zoomAdjustment, maxZoom + zoomAdjustment);
10263
+ return (0, import_core17.clamp)(zoom, minZoom + zoomAdjustment, maxZoom + zoomAdjustment);
10127
10264
  }
10128
10265
  };
10129
10266
  var GlobeController = class extends Controller {
@@ -10180,40 +10317,24 @@ var GlobeController = class extends Controller {
10180
10317
  const dt = last.timestamp - first.timestamp;
10181
10318
  if (dt > 0) {
10182
10319
  const viewportProps = this.controllerState.getViewportProps();
10183
- const state = this.controllerState.getState();
10184
10320
  const angularDistance = Globe.angularDistance(first, last);
10185
10321
  const angularVelocity = angularDistance / dt;
10186
10322
  if (angularVelocity > 1e-6) {
10187
10323
  const totalAngle = angularVelocity * inertia / 2;
10188
- let interpolator;
10189
- let endLng;
10190
- let endLat;
10191
- if (state.startPanLockBearing) {
10192
- let dLng = last.longitude - first.longitude;
10193
- if (dLng > 180) dLng -= 360;
10194
- else if (dLng < -180) dLng += 360;
10195
- const dLat = last.latitude - first.latitude;
10196
- const vLng = dLng / dt;
10197
- const vLat = dLat / dt;
10198
- endLng = viewportProps.longitude + vLng * inertia / 2;
10199
- endLat = (0, import_core18.clamp)(viewportProps.latitude + vLat * inertia / 2, -90, 90);
10200
- interpolator = new GlobeInertiaInterpolator({ targetLongitude: endLng });
10201
- } else {
10202
- const axis = Globe.greatCircleAxis(first, last);
10203
- const currentFrame = Globe.cameraFrame(
10204
- viewportProps.longitude,
10205
- viewportProps.latitude,
10206
- viewportProps.bearing || 0
10207
- );
10208
- const endFrame = Globe.rotateFrame(
10209
- { ...currentFrame, axisHorizontal: axis },
10210
- totalAngle,
10211
- 0
10212
- );
10213
- endLng = endFrame.longitude;
10214
- endLat = (0, import_core18.clamp)(endFrame.latitude, -90, 90);
10215
- interpolator = new GlobeInertiaInterpolator({ axis, totalAngle });
10216
- }
10324
+ const axis = Globe.greatCircleAxis(first, last);
10325
+ const currentFrame = Globe.cameraFrame(
10326
+ viewportProps.longitude,
10327
+ viewportProps.latitude,
10328
+ viewportProps.bearing || 0
10329
+ );
10330
+ const endFrame = Globe.rotateFrame(
10331
+ { ...currentFrame, axisHorizontal: axis },
10332
+ totalAngle,
10333
+ 0
10334
+ );
10335
+ const endLng = endFrame.longitude;
10336
+ const endLat = (0, import_core17.clamp)(endFrame.latitude, -90, 90);
10337
+ const interpolator = new GlobeInertiaInterpolator({ axis, totalAngle });
10217
10338
  const newControllerState2 = this.controllerState.panEnd();
10218
10339
  this.updateViewport(
10219
10340
  newControllerState2,
@@ -10350,7 +10471,7 @@ function resolveLayerGroups(map, oldLayers, newLayers) {
10350
10471
  }
10351
10472
 
10352
10473
  // dist/deck-utils.js
10353
- var import_web_mercator8 = require("@math.gl/web-mercator");
10474
+ var import_web_mercator7 = require("@math.gl/web-mercator");
10354
10475
  var MAPBOX_VIEW_ID = "mapbox";
10355
10476
  var TILE_SIZE = 512;
10356
10477
  var DEGREES_TO_RADIANS5 = Math.PI / 180;
@@ -10502,7 +10623,7 @@ function centerCameraOnTerrain(map, viewState) {
10502
10623
  const cameraX = position.x * TILE_SIZE;
10503
10624
  const cameraY = (1 - position.y) * TILE_SIZE;
10504
10625
  const cameraZ = position.z * TILE_SIZE;
10505
- const center = (0, import_web_mercator8.lngLatToWorld)([longitude, latitude]);
10626
+ const center = (0, import_web_mercator7.lngLatToWorld)([longitude, latitude]);
10506
10627
  const dx = cameraX - center[0];
10507
10628
  const dy = cameraY - center[1];
10508
10629
  const cameraToCenterDistanceGround = Math.sqrt(dx * dx + dy * dy);
@@ -10515,7 +10636,7 @@ function centerCameraOnTerrain(map, viewState) {
10515
10636
  viewState.zoom = Math.log2(scale);
10516
10637
  const cameraZFromSurface = altitudePixels * Math.cos(pitchRadians) / scale;
10517
10638
  const surfaceElevation = cameraZ - cameraZFromSurface;
10518
- viewState.position = [0, 0, surfaceElevation / (0, import_web_mercator8.unitsPerMeter)(latitude)];
10639
+ viewState.position = [0, 0, surfaceElevation / (0, import_web_mercator7.unitsPerMeter)(latitude)];
10519
10640
  } else if (typeof map.transform.elevation === "number") {
10520
10641
  viewState.position = [0, 0, map.transform.elevation];
10521
10642
  }