@deck.gl/widgets 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
@@ -15,7 +15,7 @@ var __copyProps = (to, from, except, desc) => {
15
15
  }
16
16
  return to;
17
17
  };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
19
19
 
20
20
  // dist/index.js
21
21
  var index_exports = {};
@@ -126,6 +126,10 @@ var import_core = require("@math.gl/core");
126
126
  function createMat4() {
127
127
  return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
128
128
  }
129
+ function mod(value, divisor) {
130
+ const modulus = value % divisor;
131
+ return modulus < 0 ? divisor + modulus : modulus;
132
+ }
129
133
  function getCameraPosition(viewMatrixInverse) {
130
134
  return [viewMatrixInverse[12], viewMatrixInverse[13], viewMatrixInverse[14]];
131
135
  }
@@ -246,8 +250,8 @@ var _Viewport = class _Viewport {
246
250
  }
247
251
  return PROJECTION_MODE.IDENTITY;
248
252
  }
249
- // Two viewports are equal if width and height are identical, and if
250
- // their view and projection matrices are (approximately) equal.
253
+ // Two viewports are equal if their dimensions, common-space scale, and
254
+ // view and projection matrices are (approximately) equal.
251
255
  equals(viewport) {
252
256
  if (!(viewport instanceof _Viewport)) {
253
257
  return false;
@@ -255,7 +259,7 @@ var _Viewport = class _Viewport {
255
259
  if (this === viewport) {
256
260
  return true;
257
261
  }
258
- return viewport.width === this.width && viewport.height === this.height && viewport.scale === this.scale && (0, import_core2.equals)(viewport.projectionMatrix, this.projectionMatrix) && (0, import_core2.equals)(viewport.viewMatrix, this.viewMatrix);
262
+ return viewport.width === this.width && viewport.height === this.height && viewport.scale === this.scale && viewport.projectionMode === this.projectionMode && viewport.resolution === this.resolution && (0, import_core2.equals)(viewport.distanceScales.unitsPerMeter, this.distanceScales.unitsPerMeter) && (0, import_core2.equals)(viewport.projectionMatrix, this.projectionMatrix) && (0, import_core2.equals)(viewport.viewMatrix, this.viewMatrix);
259
263
  }
260
264
  /**
261
265
  * Projects xyz (possibly latitude and longitude) to pixel coordinates in window
@@ -592,6 +596,10 @@ var _WebMercatorViewport = class _WebMercatorViewport extends Viewport {
592
596
  }
593
597
  return this._subViewports;
594
598
  }
599
+ /** Returns whether two Web Mercator viewports use the same projection settings. */
600
+ equals(viewport) {
601
+ return viewport instanceof _WebMercatorViewport && viewport._pseudoMeters === this._pseudoMeters && super.equals(viewport);
602
+ }
595
603
  projectPosition(xyz) {
596
604
  if (this._pseudoMeters) {
597
605
  return super.projectPosition(xyz);
@@ -1310,185 +1318,7 @@ var TransitionInterpolator = class {
1310
1318
  };
1311
1319
 
1312
1320
  // ../core/src/transitions/linear-interpolator.ts
1313
- var import_core7 = require("@math.gl/core");
1314
-
1315
- // ../core/src/viewports/globe-viewport.ts
1316
1321
  var import_core5 = require("@math.gl/core");
1317
- var import_web_mercator3 = require("@math.gl/web-mercator");
1318
- var import_core6 = require("@math.gl/core");
1319
- var import_web_mercator4 = require("@math.gl/web-mercator");
1320
- var DEGREES_TO_RADIANS2 = Math.PI / 180;
1321
- var RADIANS_TO_DEGREES = 180 / Math.PI;
1322
- var EARTH_RADIUS = 6370972;
1323
- var GLOBE_RADIUS = 256;
1324
- function getDistanceScales2() {
1325
- const unitsPerMeter2 = GLOBE_RADIUS / EARTH_RADIUS;
1326
- const unitsPerDegree = Math.PI / 180 * GLOBE_RADIUS;
1327
- return {
1328
- unitsPerMeter: [unitsPerMeter2, unitsPerMeter2, unitsPerMeter2],
1329
- unitsPerMeter2: [0, 0, 0],
1330
- metersPerUnit: [1 / unitsPerMeter2, 1 / unitsPerMeter2, 1 / unitsPerMeter2],
1331
- unitsPerDegree: [unitsPerDegree, unitsPerDegree, unitsPerMeter2],
1332
- unitsPerDegree2: [0, 0, 0],
1333
- degreesPerUnit: [1 / unitsPerDegree, 1 / unitsPerDegree, 1 / unitsPerMeter2]
1334
- };
1335
- }
1336
- var GlobeViewport = class extends Viewport {
1337
- constructor(opts = {}) {
1338
- const {
1339
- longitude = 0,
1340
- bearing = 0,
1341
- pitch = 0,
1342
- zoom = 0,
1343
- // Matches Maplibre defaults
1344
- // https://github.com/maplibre/maplibre-gl-js/blob/f8ab4b48d59ab8fe7b068b102538793bbdd4c848/src/geo/projection/globe_transform.ts#L632-L633
1345
- nearZMultiplier = 0.5,
1346
- farZMultiplier = 1,
1347
- resolution = 10
1348
- } = opts;
1349
- let { latitude = 0, height, altitude = 1.5, fovy } = opts;
1350
- latitude = Math.max(Math.min(latitude, 90), -90);
1351
- height = height || 1;
1352
- if (fovy) {
1353
- altitude = (0, import_web_mercator3.fovyToAltitude)(fovy);
1354
- } else {
1355
- fovy = (0, import_web_mercator3.altitudeToFovy)(altitude);
1356
- }
1357
- const scaleLatitude = Math.max(Math.min(latitude, import_web_mercator4.MAX_LATITUDE), -import_web_mercator4.MAX_LATITUDE);
1358
- const scale = Math.pow(2, zoom - zoomAdjust(scaleLatitude));
1359
- const pitchRadians = pitch * DEGREES_TO_RADIANS2;
1360
- const nearZ = opts.nearZ ?? nearZMultiplier;
1361
- const farZ = opts.farZ ?? (altitude + GLOBE_RADIUS * 2 * scale / height / Math.max(Math.cos(pitchRadians), 0.1)) * farZMultiplier;
1362
- const viewMatrix2 = new import_core5.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);
1363
- super({
1364
- ...opts,
1365
- // x, y, width,
1366
- height,
1367
- // view matrix
1368
- viewMatrix: viewMatrix2,
1369
- longitude,
1370
- latitude,
1371
- zoom,
1372
- // projection matrix parameters
1373
- distanceScales: getDistanceScales2(),
1374
- fovy,
1375
- focalDistance: altitude,
1376
- near: nearZ,
1377
- far: farZ
1378
- });
1379
- this.scale = scale;
1380
- this.latitude = latitude;
1381
- this.longitude = longitude;
1382
- this.bearing = bearing;
1383
- this.pitch = pitch;
1384
- this.fovy = fovy;
1385
- this.resolution = resolution;
1386
- }
1387
- get projectionMode() {
1388
- return PROJECTION_MODE.GLOBE;
1389
- }
1390
- getDistanceScales() {
1391
- return this.distanceScales;
1392
- }
1393
- getBounds(options = {}) {
1394
- const unprojectOption = { targetZ: options.z || 0 };
1395
- const left = this.unproject([0, this.height / 2], unprojectOption);
1396
- const top = this.unproject([this.width / 2, 0], unprojectOption);
1397
- const right = this.unproject([this.width, this.height / 2], unprojectOption);
1398
- const bottom = this.unproject([this.width / 2, this.height], unprojectOption);
1399
- if (right[0] < this.longitude) right[0] += 360;
1400
- if (left[0] > this.longitude) left[0] -= 360;
1401
- return [
1402
- Math.min(left[0], right[0], top[0], bottom[0]),
1403
- Math.min(left[1], right[1], top[1], bottom[1]),
1404
- Math.max(left[0], right[0], top[0], bottom[0]),
1405
- Math.max(left[1], right[1], top[1], bottom[1])
1406
- ];
1407
- }
1408
- unproject(xyz, { topLeft = true, targetZ } = {}) {
1409
- const [x, y, z] = xyz;
1410
- const y2 = topLeft ? y : this.height - y;
1411
- const { pixelUnprojectionMatrix } = this;
1412
- let coord;
1413
- if (Number.isFinite(z)) {
1414
- coord = transformVector(pixelUnprojectionMatrix, [x, y2, z, 1]);
1415
- } else {
1416
- const coord0 = transformVector(pixelUnprojectionMatrix, [x, y2, -1, 1]);
1417
- const coord1 = transformVector(pixelUnprojectionMatrix, [x, y2, 1, 1]);
1418
- const lt = ((targetZ || 0) / EARTH_RADIUS + 1) * GLOBE_RADIUS;
1419
- const lSqr = import_core6.vec3.sqrLen(import_core6.vec3.sub([], coord0, coord1));
1420
- const l0Sqr = import_core6.vec3.sqrLen(coord0);
1421
- const l1Sqr = import_core6.vec3.sqrLen(coord1);
1422
- const sSqr = (4 * l0Sqr * l1Sqr - (lSqr - l0Sqr - l1Sqr) ** 2) / 16;
1423
- const dSqr = 4 * sSqr / lSqr;
1424
- const r0 = Math.sqrt(l0Sqr - dSqr);
1425
- const dr = Math.sqrt(Math.max(0, lt * lt - dSqr));
1426
- const t = (r0 - dr) / Math.sqrt(lSqr);
1427
- coord = import_core6.vec3.lerp([], coord0, coord1, t);
1428
- }
1429
- const [X, Y, Z] = this.unprojectPosition(coord);
1430
- if (Number.isFinite(z)) {
1431
- return [X, Y, Z];
1432
- }
1433
- return Number.isFinite(targetZ) ? [X, Y, targetZ] : [X, Y];
1434
- }
1435
- projectPosition(xyz) {
1436
- const [lng, lat, Z = 0] = xyz;
1437
- const lambda = lng * DEGREES_TO_RADIANS2;
1438
- const phi = lat * DEGREES_TO_RADIANS2;
1439
- const cosPhi = Math.cos(phi);
1440
- const D = (Z / EARTH_RADIUS + 1) * GLOBE_RADIUS;
1441
- return [Math.sin(lambda) * cosPhi * D, -Math.cos(lambda) * cosPhi * D, Math.sin(phi) * D];
1442
- }
1443
- unprojectPosition(xyz) {
1444
- const [x, y, z] = xyz;
1445
- const D = import_core6.vec3.len(xyz);
1446
- const phi = Math.asin(z / D);
1447
- const lambda = Math.atan2(x, -y);
1448
- const lng = lambda * RADIANS_TO_DEGREES;
1449
- const lat = phi * RADIANS_TO_DEGREES;
1450
- const Z = (D / GLOBE_RADIUS - 1) * EARTH_RADIUS;
1451
- return [lng, lat, Z];
1452
- }
1453
- projectFlat(xyz) {
1454
- return xyz;
1455
- }
1456
- unprojectFlat(xyz) {
1457
- return xyz;
1458
- }
1459
- /**
1460
- * Pan the globe using delta-based movement
1461
- * @param coords - the geographic coordinates where the pan started
1462
- * @param pixel - the current screen position
1463
- * @param startPixel - the screen position where the pan started
1464
- * @returns updated viewport options with new longitude/latitude
1465
- */
1466
- panByPosition([startLng, startLat, startZoom], pixel, startPixel) {
1467
- const scale = Math.pow(2, this.zoom - zoomAdjust(this.latitude));
1468
- const rotationSpeed = 0.25 / scale;
1469
- const longitude = startLng + rotationSpeed * (startPixel[0] - pixel[0]);
1470
- let latitude = startLat - rotationSpeed * (startPixel[1] - pixel[1]);
1471
- latitude = Math.max(Math.min(latitude, 90), -90);
1472
- const out = { longitude, latitude, zoom: startZoom - zoomAdjust(startLat) };
1473
- out.zoom += zoomAdjust(out.latitude);
1474
- return out;
1475
- }
1476
- };
1477
- GlobeViewport.displayName = "GlobeViewport";
1478
- function zoomAdjust(latitude, clampToPoles) {
1479
- if (clampToPoles) {
1480
- latitude = Math.max(Math.min(latitude, import_web_mercator4.MAX_LATITUDE), -import_web_mercator4.MAX_LATITUDE);
1481
- }
1482
- const scaleAdjust = Math.PI * Math.cos(latitude * Math.PI / 180);
1483
- return Math.log2(scaleAdjust);
1484
- }
1485
- function transformVector(matrix, vector) {
1486
- const result = import_core6.vec4.transformMat4([], vector, matrix);
1487
- import_core6.vec4.scale(result, result, 1 / result[3]);
1488
- return result;
1489
- }
1490
-
1491
- // ../core/src/transitions/linear-interpolator.ts
1492
1322
  var DEFAULT_PROPS = ["longitude", "latitude", "zoom", "bearing", "pitch"];
1493
1323
  var DEFAULT_REQUIRED_PROPS = ["longitude", "latitude", "zoom"];
1494
1324
  var LinearInterpolator = class extends TransitionInterpolator {
@@ -1515,28 +1345,23 @@ var LinearInterpolator = class extends TransitionInterpolator {
1515
1345
  const result = super.initializeProps(startProps, endProps);
1516
1346
  const { makeViewport, around } = this.opts;
1517
1347
  if (makeViewport && around) {
1518
- const TestViewport = makeViewport(startProps);
1519
- if (TestViewport instanceof GlobeViewport) {
1520
- log_default.warn("around not supported in GlobeView")();
1521
- } else {
1522
- const startViewport = makeViewport(startProps);
1523
- const endViewport = makeViewport(endProps);
1524
- const aroundPosition = startViewport.unproject(around);
1525
- result.start.around = around;
1526
- Object.assign(result.end, {
1527
- around: endViewport.project(aroundPosition),
1528
- aroundPosition,
1529
- width: endProps.width,
1530
- height: endProps.height
1531
- });
1532
- }
1348
+ const startViewport = makeViewport(startProps);
1349
+ const endViewport = makeViewport(endProps);
1350
+ const aroundPosition = startViewport.unproject(around);
1351
+ result.start.around = around;
1352
+ Object.assign(result.end, {
1353
+ around: endViewport.project(aroundPosition),
1354
+ aroundPosition,
1355
+ width: endProps.width,
1356
+ height: endProps.height
1357
+ });
1533
1358
  }
1534
1359
  return result;
1535
1360
  }
1536
1361
  interpolateProps(startProps, endProps, t) {
1537
1362
  const propsInTransition = {};
1538
1363
  for (const key of this._propsToExtract) {
1539
- propsInTransition[key] = (0, import_core7.lerp)(startProps[key] || 0, endProps[key] || 0, t);
1364
+ propsInTransition[key] = (0, import_core5.lerp)(startProps[key] || 0, endProps[key] || 0, t);
1540
1365
  }
1541
1366
  if (endProps.aroundPosition && this.opts.makeViewport) {
1542
1367
  const viewport = this.opts.makeViewport({ ...endProps, ...propsInTransition });
@@ -1545,7 +1370,7 @@ var LinearInterpolator = class extends TransitionInterpolator {
1545
1370
  viewport.panByPosition(
1546
1371
  endProps.aroundPosition,
1547
1372
  // anchor point in current screen coordinates
1548
- (0, import_core7.lerp)(startProps.around, endProps.around, t)
1373
+ (0, import_core5.lerp)(startProps.around, endProps.around, t)
1549
1374
  )
1550
1375
  );
1551
1376
  }
@@ -1554,6 +1379,7 @@ var LinearInterpolator = class extends TransitionInterpolator {
1554
1379
  };
1555
1380
 
1556
1381
  // ../core/src/controllers/controller.ts
1382
+ var import_web_mercator3 = require("@math.gl/web-mercator");
1557
1383
  var NO_TRANSITION_PROPS = {
1558
1384
  transitionDuration: 0
1559
1385
  };
@@ -1602,6 +1428,7 @@ var Controller = class {
1602
1428
  this.touchRotate = false;
1603
1429
  this.multiTouchDrag = null;
1604
1430
  this.trackpadGesture = false;
1431
+ this.zoomAround = "pointer";
1605
1432
  this.keyboard = true;
1606
1433
  this.transitionManager = new TransitionManager({
1607
1434
  ...opts,
@@ -1696,6 +1523,15 @@ var Controller = class {
1696
1523
  const { offsetCenter } = event;
1697
1524
  return [offsetCenter.x - x, offsetCenter.y - y];
1698
1525
  }
1526
+ /** Resolves the zoom anchor for pointer-based controls. */
1527
+ getZoomPosition(position) {
1528
+ if (this.zoomAround === "pointer") {
1529
+ return position;
1530
+ }
1531
+ const viewport = this.makeViewport(this.controllerState.getViewportProps());
1532
+ const [centerX, centerY] = (0, import_web_mercator3.worldToPixels)(viewport.center, viewport.pixelProjectionMatrix);
1533
+ return [centerX, centerY];
1534
+ }
1699
1535
  isPointInBounds(pos, event) {
1700
1536
  const { width, height } = this.props;
1701
1537
  if (event && event.handled) {
@@ -1753,6 +1589,7 @@ var Controller = class {
1753
1589
  touchRotate = false,
1754
1590
  multiTouchDrag = touchRotate ? "rotate" : null,
1755
1591
  trackpadGesture = false,
1592
+ zoomAround = "pointer",
1756
1593
  keyboard = true
1757
1594
  } = props;
1758
1595
  const isInteractive = Boolean(this.onViewStateChange);
@@ -1775,6 +1612,7 @@ var Controller = class {
1775
1612
  this.touchRotate = multiTouchDrag === "rotate";
1776
1613
  this.multiTouchDrag = multiTouchDrag;
1777
1614
  this.trackpadGesture = trackpadGesture;
1615
+ this.zoomAround = zoomAround;
1778
1616
  this.keyboard = keyboard;
1779
1617
  const constraintChanged = !oldProps || oldProps.height !== props.height || oldProps.width !== props.width || oldProps.maxBounds !== props.maxBounds || oldProps.maxBoundsPadding !== props.maxBoundsPadding;
1780
1618
  if (constraintChanged && props.maxBounds) {
@@ -2008,8 +1846,9 @@ var Controller = class {
2008
1846
  if (delta < 0 && scale !== 0) {
2009
1847
  scale = 1 / scale;
2010
1848
  }
2011
- const transitionProps = smooth ? { ...this._getTransitionProps({ around: pos }), transitionDuration: 250 } : NO_TRANSITION_PROPS;
2012
- const newControllerState = this.controllerState.zoom({ pos, scale });
1849
+ const zoomPosition = this.getZoomPosition(pos);
1850
+ const transitionProps = smooth ? { ...this._getTransitionProps({ around: zoomPosition }), transitionDuration: 250 } : NO_TRANSITION_PROPS;
1851
+ const newControllerState = this.controllerState.zoom({ pos: zoomPosition, scale });
2013
1852
  this.updateViewport(newControllerState, transitionProps, {
2014
1853
  isZooming: true,
2015
1854
  isPanning: true
@@ -2095,7 +1934,7 @@ var Controller = class {
2095
1934
  if (!this.isPointInBounds(pos, event)) {
2096
1935
  return false;
2097
1936
  }
2098
- const newControllerState = this.controllerState.zoomStart({ pos }, this._getConstraintContext("zoom", "start")).rotateStart({ pos }, this._getConstraintContext("rotate", "start"));
1937
+ const newControllerState = this.controllerState.zoomStart({ pos: this.getZoomPosition(pos) }, this._getConstraintContext("zoom", "start")).rotateStart({ pos }, this._getConstraintContext("rotate", "start"));
2099
1938
  pinchEventWorkaround._startPinchRotation = event.rotation;
2100
1939
  pinchEventWorkaround._lastPinchEvent = event;
2101
1940
  this.updateViewport(newControllerState, NO_TRANSITION_PROPS, { isDragging: true });
@@ -2114,7 +1953,7 @@ var Controller = class {
2114
1953
  const { scale } = event;
2115
1954
  const pos = this.getCenter(event);
2116
1955
  newControllerState = newControllerState.zoom(
2117
- { pos, scale },
1956
+ { pos: this.getZoomPosition(pos), scale },
2118
1957
  this._getConstraintContext("zoom", "update")
2119
1958
  );
2120
1959
  }
@@ -2142,15 +1981,16 @@ var Controller = class {
2142
1981
  const { _lastPinchEvent } = pinchEventWorkaround;
2143
1982
  if (this.touchZoom && inertia && _lastPinchEvent && event.scale !== _lastPinchEvent.scale) {
2144
1983
  const pos = this.getCenter(event);
1984
+ const zoomPosition = this.getZoomPosition(pos);
2145
1985
  let newControllerState = this.controllerState.rotateEnd();
2146
1986
  const z = Math.log2(event.scale);
2147
1987
  const velocityZ = (z - Math.log2(_lastPinchEvent.scale)) / (event.deltaTime - _lastPinchEvent.deltaTime);
2148
1988
  const endScale = Math.pow(2, z + velocityZ * inertia / 2);
2149
- newControllerState = newControllerState.zoom({ pos, scale: endScale }).zoomEnd();
1989
+ newControllerState = newControllerState.zoom({ pos: zoomPosition, scale: endScale }).zoomEnd();
2150
1990
  this.updateViewport(
2151
1991
  newControllerState,
2152
1992
  {
2153
- ...this._getTransitionProps({ around: pos }),
1993
+ ...this._getTransitionProps({ around: zoomPosition }),
2154
1994
  transitionDuration: inertia,
2155
1995
  transitionEasing: INERTIA_EASING
2156
1996
  },
@@ -2195,8 +2035,12 @@ var Controller = class {
2195
2035
  return false;
2196
2036
  }
2197
2037
  const isZoomOut = this.isFunctionKeyPressed(event);
2198
- const newControllerState = this.controllerState.zoom({ pos, scale: isZoomOut ? 0.5 : 2 });
2199
- this.updateViewport(newControllerState, this._getTransitionProps({ around: pos }), {
2038
+ const zoomPosition = this.getZoomPosition(pos);
2039
+ const newControllerState = this.controllerState.zoom({
2040
+ pos: zoomPosition,
2041
+ scale: isZoomOut ? 0.5 : 2
2042
+ });
2043
+ this.updateViewport(newControllerState, this._getTransitionProps({ around: zoomPosition }), {
2200
2044
  isZooming: true,
2201
2045
  isPanning: true
2202
2046
  });
@@ -2213,14 +2057,14 @@ var Controller = class {
2213
2057
  this._doubleClickDragAnchor = null;
2214
2058
  return false;
2215
2059
  }
2216
- this._doubleClickDragAnchor = pos;
2060
+ this._doubleClickDragAnchor = this.getZoomPosition(pos);
2217
2061
  let newControllerState = this.controllerState.zoomStart(
2218
- { pos },
2062
+ { pos: this._doubleClickDragAnchor },
2219
2063
  this._getConstraintContext("zoom", "start")
2220
2064
  );
2221
2065
  if (event.scale !== 1) {
2222
2066
  newControllerState = newControllerState.zoom(
2223
- { pos, scale: event.scale },
2067
+ { pos: this._doubleClickDragAnchor, scale: event.scale },
2224
2068
  this._getConstraintContext("zoom", "update")
2225
2069
  );
2226
2070
  }
@@ -2528,10 +2372,279 @@ Widget.defaultProps = {
2528
2372
  className: ""
2529
2373
  };
2530
2374
 
2375
+ // ../core/src/viewports/globe-viewport.ts
2376
+ var import_core6 = require("@math.gl/core");
2377
+ var import_web_mercator4 = require("@math.gl/web-mercator");
2378
+ var DEGREES_TO_RADIANS2 = Math.PI / 180;
2379
+ var RADIANS_TO_DEGREES = 180 / Math.PI;
2380
+ var NORTH_UP_BEARING_THRESHOLD = 1;
2381
+ var EARTH_RADIUS = 6370972;
2382
+ var GLOBE_RADIUS = 256;
2383
+ var GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO = 0.75;
2384
+ var GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO = 1.15;
2385
+ function isGlobeNorthUp(bearing) {
2386
+ const normalizedBearing = mod(bearing + 180, 360) - 180;
2387
+ return Math.abs(normalizedBearing) < NORTH_UP_BEARING_THRESHOLD;
2388
+ }
2389
+ function getDistanceScales2() {
2390
+ const unitsPerMeter2 = GLOBE_RADIUS / EARTH_RADIUS;
2391
+ const unitsPerDegree = Math.PI / 180 * GLOBE_RADIUS;
2392
+ return {
2393
+ unitsPerMeter: [unitsPerMeter2, unitsPerMeter2, unitsPerMeter2],
2394
+ unitsPerMeter2: [0, 0, 0],
2395
+ metersPerUnit: [1 / unitsPerMeter2, 1 / unitsPerMeter2, 1 / unitsPerMeter2],
2396
+ unitsPerDegree: [unitsPerDegree, unitsPerDegree, unitsPerMeter2],
2397
+ unitsPerDegree2: [0, 0, 0],
2398
+ degreesPerUnit: [1 / unitsPerDegree, 1 / unitsPerDegree, 1 / unitsPerMeter2]
2399
+ };
2400
+ }
2401
+ var GlobeViewport = class extends Viewport {
2402
+ constructor(opts = {}) {
2403
+ const {
2404
+ longitude = 0,
2405
+ bearing = 0,
2406
+ pitch = 0,
2407
+ zoom = 0,
2408
+ // Matches Maplibre defaults
2409
+ // https://github.com/maplibre/maplibre-gl-js/blob/f8ab4b48d59ab8fe7b068b102538793bbdd4c848/src/geo/projection/globe_transform.ts#L632-L633
2410
+ nearZMultiplier = 0.5,
2411
+ farZMultiplier = 1,
2412
+ resolution = 10
2413
+ } = opts;
2414
+ let { latitude = 0, height, altitude = 1.5, fovy } = opts;
2415
+ latitude = Math.max(Math.min(latitude, 90), -90);
2416
+ height = height || 1;
2417
+ if (fovy) {
2418
+ altitude = (0, import_web_mercator4.fovyToAltitude)(fovy);
2419
+ } else {
2420
+ fovy = (0, import_web_mercator4.altitudeToFovy)(altitude);
2421
+ }
2422
+ const scaleLatitude = Math.max(Math.min(latitude, import_web_mercator4.MAX_LATITUDE), -import_web_mercator4.MAX_LATITUDE);
2423
+ const scale = Math.pow(2, zoom - zoomAdjust(scaleLatitude));
2424
+ const pitchRadians = pitch * DEGREES_TO_RADIANS2;
2425
+ const nearZ = opts.nearZ ?? nearZMultiplier;
2426
+ const farZ = opts.farZ ?? (altitude + GLOBE_RADIUS * 2 * scale / height / Math.max(Math.cos(pitchRadians), 0.1)) * farZMultiplier;
2427
+ const viewMatrix2 = new import_core6.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);
2428
+ super({
2429
+ ...opts,
2430
+ // x, y, width,
2431
+ height,
2432
+ // view matrix
2433
+ viewMatrix: viewMatrix2,
2434
+ longitude,
2435
+ latitude,
2436
+ zoom,
2437
+ // projection matrix parameters
2438
+ distanceScales: getDistanceScales2(),
2439
+ fovy,
2440
+ focalDistance: altitude,
2441
+ near: nearZ,
2442
+ far: farZ
2443
+ });
2444
+ this.scale = scale;
2445
+ this.latitude = latitude;
2446
+ this.longitude = longitude;
2447
+ this.bearing = bearing;
2448
+ this.pitch = pitch;
2449
+ this.fovy = fovy;
2450
+ this.resolution = resolution;
2451
+ }
2452
+ get projectionMode() {
2453
+ return PROJECTION_MODE.GLOBE;
2454
+ }
2455
+ getDistanceScales() {
2456
+ return this.distanceScales;
2457
+ }
2458
+ getBounds(options = {}) {
2459
+ const unprojectOption = { targetZ: options.z || 0 };
2460
+ const left = this.unproject([0, this.height / 2], unprojectOption);
2461
+ const top = this.unproject([this.width / 2, 0], unprojectOption);
2462
+ const right = this.unproject([this.width, this.height / 2], unprojectOption);
2463
+ const bottom = this.unproject([this.width / 2, this.height], unprojectOption);
2464
+ if (right[0] < this.longitude) right[0] += 360;
2465
+ if (left[0] > this.longitude) left[0] -= 360;
2466
+ return [
2467
+ Math.min(left[0], right[0], top[0], bottom[0]),
2468
+ Math.min(left[1], right[1], top[1], bottom[1]),
2469
+ Math.max(left[0], right[0], top[0], bottom[0]),
2470
+ Math.max(left[1], right[1], top[1], bottom[1])
2471
+ ];
2472
+ }
2473
+ /**
2474
+ * Builds the screen-pixel → globe-center ray and the intermediate ray/sphere
2475
+ * math reused by `unproject` and anchored zoom. One function so the same
2476
+ * pixelUnprojectionMatrix work isn't duplicated.
2477
+ */
2478
+ _getRayToGlobe(screenPosition, { topLeft = true, targetZ } = {}) {
2479
+ const [screenX, screenY] = screenPosition;
2480
+ const adjustedScreenY = topLeft ? screenY : this.height - screenY;
2481
+ const { pixelUnprojectionMatrix } = this;
2482
+ const rayStartPosition = transformVector(pixelUnprojectionMatrix, [
2483
+ screenX,
2484
+ adjustedScreenY,
2485
+ -1,
2486
+ 1
2487
+ ]);
2488
+ const rayEndPosition = transformVector(pixelUnprojectionMatrix, [
2489
+ screenX,
2490
+ adjustedScreenY,
2491
+ 1,
2492
+ 1
2493
+ ]);
2494
+ const radius = ((targetZ || 0) / EARTH_RADIUS + 1) * GLOBE_RADIUS;
2495
+ const rayLengthSquared = import_core6.vec3.sqrLen(import_core6.vec3.sub([], rayStartPosition, rayEndPosition));
2496
+ const rayStartDistanceSquared = import_core6.vec3.sqrLen(rayStartPosition);
2497
+ const rayEndDistanceSquared = import_core6.vec3.sqrLen(rayEndPosition);
2498
+ const triangleAreaSquared = (4 * rayStartDistanceSquared * rayEndDistanceSquared - (rayLengthSquared - rayStartDistanceSquared - rayEndDistanceSquared) ** 2) / 16;
2499
+ const distanceToCenterSquared = 4 * triangleAreaSquared / rayLengthSquared;
2500
+ return {
2501
+ rayStartPosition,
2502
+ rayEndPosition,
2503
+ radius,
2504
+ rayLengthSquared,
2505
+ rayStartDistanceSquared,
2506
+ distanceToCenterSquared
2507
+ };
2508
+ }
2509
+ _getRayDistanceToGlobeCenterRatio(screenPosition, options) {
2510
+ const { distanceToCenterSquared, radius } = this._getRayToGlobe(screenPosition, options);
2511
+ return Math.sqrt(Math.max(0, distanceToCenterSquared)) / radius;
2512
+ }
2513
+ /**
2514
+ * Returns how strongly a screen position should anchor zoom on the visible globe.
2515
+ * A value of `0` means that the controller should fall back to center zoom.
2516
+ * @param screenPosition - Screen position to evaluate.
2517
+ * @returns Anchor strength from `0` to `1`.
2518
+ */
2519
+ getZoomAnchorStrength(screenPosition) {
2520
+ const distanceRatio = this._getRayDistanceToGlobeCenterRatio(screenPosition);
2521
+ if (distanceRatio >= GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO) {
2522
+ return 0;
2523
+ }
2524
+ const edgeProgress = Math.max(
2525
+ 0,
2526
+ Math.min(
2527
+ 1,
2528
+ (distanceRatio - GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO) / (GLOBE_ZOOM_ANCHOR_MAX_DISTANCE_RATIO - GLOBE_ZOOM_ANCHOR_DAMPING_START_RATIO)
2529
+ )
2530
+ );
2531
+ const smoothProgress = edgeProgress * edgeProgress * (3 - 2 * edgeProgress);
2532
+ return 1 - smoothProgress;
2533
+ }
2534
+ unproject(xyz, { topLeft = true, targetZ } = {}) {
2535
+ const [x, y, z] = xyz;
2536
+ const y2 = topLeft ? y : this.height - y;
2537
+ const { pixelUnprojectionMatrix } = this;
2538
+ let coord;
2539
+ if (Number.isFinite(z)) {
2540
+ coord = transformVector(pixelUnprojectionMatrix, [x, y2, z, 1]);
2541
+ } else {
2542
+ const {
2543
+ rayStartPosition,
2544
+ rayEndPosition,
2545
+ radius,
2546
+ rayLengthSquared,
2547
+ rayStartDistanceSquared,
2548
+ distanceToCenterSquared
2549
+ } = this._getRayToGlobe(xyz, { topLeft, targetZ });
2550
+ const rayStartToClosestApproach = Math.sqrt(
2551
+ rayStartDistanceSquared - distanceToCenterSquared
2552
+ );
2553
+ const closestApproachToIntersection = Math.sqrt(
2554
+ Math.max(0, radius * radius - distanceToCenterSquared)
2555
+ );
2556
+ const intersectionRatio = (rayStartToClosestApproach - closestApproachToIntersection) / Math.sqrt(rayLengthSquared);
2557
+ coord = import_core6.vec3.lerp([], rayStartPosition, rayEndPosition, intersectionRatio);
2558
+ }
2559
+ const [X, Y, Z] = this.unprojectPosition(coord);
2560
+ if (Number.isFinite(z)) {
2561
+ return [X, Y, Z];
2562
+ }
2563
+ return Number.isFinite(targetZ) ? [X, Y, targetZ] : [X, Y];
2564
+ }
2565
+ projectPosition(xyz) {
2566
+ const [lng, lat, Z = 0] = xyz;
2567
+ const lambda = lng * DEGREES_TO_RADIANS2;
2568
+ const phi = lat * DEGREES_TO_RADIANS2;
2569
+ const cosPhi = Math.cos(phi);
2570
+ const D = (Z / EARTH_RADIUS + 1) * GLOBE_RADIUS;
2571
+ return [Math.sin(lambda) * cosPhi * D, -Math.cos(lambda) * cosPhi * D, Math.sin(phi) * D];
2572
+ }
2573
+ unprojectPosition(xyz) {
2574
+ const [x, y, z] = xyz;
2575
+ const D = import_core6.vec3.len(xyz);
2576
+ const phi = Math.asin(z / D);
2577
+ const lambda = Math.atan2(x, -y);
2578
+ const lng = lambda * RADIANS_TO_DEGREES;
2579
+ const lat = phi * RADIANS_TO_DEGREES;
2580
+ const Z = (D / GLOBE_RADIUS - 1) * EARTH_RADIUS;
2581
+ return [lng, lat, Z];
2582
+ }
2583
+ projectFlat(xyz) {
2584
+ return xyz;
2585
+ }
2586
+ unprojectFlat(xyz) {
2587
+ return xyz;
2588
+ }
2589
+ /**
2590
+ * Pan the globe to place geographic coordinates at a screen pixel.
2591
+ * When `dragStartPosition` is supplied, applies the delta-based movement used by globe dragging.
2592
+ * @param coordinates - Geographic anchor, or the starting longitude, latitude and zoom.
2593
+ * @param screenPosition - Current screen position.
2594
+ * @param dragStartPosition - Screen position where a drag started.
2595
+ * @returns Updated viewport options.
2596
+ */
2597
+ panByPosition(coordinates, screenPosition, dragStartPosition) {
2598
+ if (!dragStartPosition) {
2599
+ let anchorStrength = this.getZoomAnchorStrength(screenPosition);
2600
+ if (anchorStrength === 0) {
2601
+ return { longitude: this.longitude, latitude: this.latitude };
2602
+ }
2603
+ const currentCoordinates = this.unproject(screenPosition);
2604
+ const longitudeDelta = mod(coordinates[0] - currentCoordinates[0] + 180, 360) - 180;
2605
+ const latitudeDelta = coordinates[1] - currentCoordinates[1];
2606
+ const crossesPole = Math.abs(currentCoordinates[1]) > import_web_mercator4.MAX_LATITUDE || Math.abs(longitudeDelta) > 90;
2607
+ if (isGlobeNorthUp(this.bearing) && crossesPole) {
2608
+ return { longitude: this.longitude, latitude: this.latitude };
2609
+ }
2610
+ if (isGlobeNorthUp(this.bearing) && latitudeDelta !== 0) {
2611
+ const latitudeLimit = latitudeDelta > 0 ? import_web_mercator4.MAX_LATITUDE : -import_web_mercator4.MAX_LATITUDE;
2612
+ const latitudeConstraintStrength = (latitudeLimit - this.latitude) / latitudeDelta;
2613
+ anchorStrength = Math.min(anchorStrength, Math.max(0, latitudeConstraintStrength));
2614
+ }
2615
+ const longitude2 = this.longitude + longitudeDelta * anchorStrength;
2616
+ const latitude2 = Math.max(Math.min(this.latitude + latitudeDelta * anchorStrength, 90), -90);
2617
+ return { longitude: longitude2, latitude: latitude2 };
2618
+ }
2619
+ const [startLongitude, startLatitude, startZoom] = coordinates;
2620
+ const scale = Math.pow(2, this.zoom - zoomAdjust(this.latitude));
2621
+ const rotationSpeed = 0.25 / scale;
2622
+ const longitude = startLongitude + rotationSpeed * (dragStartPosition[0] - screenPosition[0]);
2623
+ let latitude = startLatitude - rotationSpeed * (dragStartPosition[1] - screenPosition[1]);
2624
+ latitude = Math.max(Math.min(latitude, 90), -90);
2625
+ const nextViewState = { longitude, latitude, zoom: startZoom - zoomAdjust(startLatitude) };
2626
+ nextViewState.zoom += zoomAdjust(nextViewState.latitude);
2627
+ return nextViewState;
2628
+ }
2629
+ };
2630
+ GlobeViewport.displayName = "GlobeViewport";
2631
+ function zoomAdjust(latitude, clampToPoles) {
2632
+ if (clampToPoles) {
2633
+ latitude = Math.max(Math.min(latitude, import_web_mercator4.MAX_LATITUDE), -import_web_mercator4.MAX_LATITUDE);
2634
+ }
2635
+ const scaleAdjust = Math.PI * Math.cos(latitude * Math.PI / 180);
2636
+ return Math.log2(scaleAdjust);
2637
+ }
2638
+ function transformVector(matrix, vector) {
2639
+ const result = import_core6.vec4.transformMat4([], vector, matrix);
2640
+ import_core6.vec4.scale(result, result, 1 / result[3]);
2641
+ return result;
2642
+ }
2643
+
2531
2644
  // ../core/src/viewports/orthographic-viewport.ts
2532
- var import_core8 = require("@math.gl/core");
2645
+ var import_core7 = require("@math.gl/core");
2533
2646
  var import_web_mercator5 = require("@math.gl/web-mercator");
2534
- var viewMatrix = new import_core8.Matrix4().lookAt({ eye: [0, 0, 1] });
2647
+ var viewMatrix = new import_core7.Matrix4().lookAt({ eye: [0, 0, 1] });
2535
2648
  function getProjectionMatrix({
2536
2649
  width,
2537
2650
  height,
@@ -2545,14 +2658,14 @@ function getProjectionMatrix({
2545
2658
  let top = height / 2;
2546
2659
  if (padding) {
2547
2660
  const { left: l = 0, right: r = 0, top: t = 0, bottom: b = 0 } = padding;
2548
- const offsetX = (0, import_core8.clamp)((l + width - r) / 2, 0, width) - width / 2;
2549
- const offsetY = (0, import_core8.clamp)((t + height - b) / 2, 0, height) - height / 2;
2661
+ const offsetX = (0, import_core7.clamp)((l + width - r) / 2, 0, width) - width / 2;
2662
+ const offsetY = (0, import_core7.clamp)((t + height - b) / 2, 0, height) - height / 2;
2550
2663
  left -= offsetX;
2551
2664
  right -= offsetX;
2552
2665
  bottom += offsetY;
2553
2666
  top += offsetY;
2554
2667
  }
2555
- return new import_core8.Matrix4().ortho({
2668
+ return new import_core7.Matrix4().ortho({
2556
2669
  left,
2557
2670
  right,
2558
2671
  bottom,
@@ -2620,15 +2733,15 @@ var OrthographicViewport = class extends Viewport {
2620
2733
  panByPosition(coords, pixel, startPixel) {
2621
2734
  const fromLocation = (0, import_web_mercator5.pixelsToWorld)(pixel, this.pixelUnprojectionMatrix);
2622
2735
  const toLocation = this.projectFlat(coords);
2623
- const translate = import_core8.vec2.add([], toLocation, import_core8.vec2.negate([], fromLocation));
2624
- const newCenter = import_core8.vec2.add([], this.center, translate);
2736
+ const translate = import_core7.vec2.add([], toLocation, import_core7.vec2.negate([], fromLocation));
2737
+ const newCenter = import_core7.vec2.add([], this.center, translate);
2625
2738
  return { target: this.unprojectFlat(newCenter) };
2626
2739
  }
2627
2740
  };
2628
2741
  OrthographicViewport.displayName = "OrthographicViewport";
2629
2742
 
2630
2743
  // ../core/src/controllers/orthographic-controller.ts
2631
- var import_core9 = require("@math.gl/core");
2744
+ var import_core8 = require("@math.gl/core");
2632
2745
  var ZOOM_RUBBER_BAND_RANGE = 1;
2633
2746
  function normalizeZoom({
2634
2747
  zoom = 0,
@@ -2647,7 +2760,7 @@ function getAxisBounds(maxBounds, index, negativeExtent, positiveExtent, target)
2647
2760
  minimum,
2648
2761
  maximum,
2649
2762
  midpoint,
2650
- settledTarget: Number.isFinite(negativeExtent) && Number.isFinite(positiveExtent) && minimum <= maximum ? (0, import_core9.clamp)(target, minimum, maximum) : midpoint
2763
+ settledTarget: Number.isFinite(negativeExtent) && Number.isFinite(positiveExtent) && minimum <= maximum ? (0, import_core8.clamp)(target, minimum, maximum) : midpoint
2651
2764
  };
2652
2765
  }
2653
2766
  var OrthographicState = class extends ViewState {
@@ -2965,7 +3078,7 @@ var OrthographicState = class extends ViewState {
2965
3078
  constrainedTarget[index] = midpoint;
2966
3079
  continue;
2967
3080
  }
2968
- const constrained = rubberBand ? settledTarget : (0, import_core9.clamp)(target[index], minimum, maximum);
3081
+ const constrained = rubberBand ? settledTarget : (0, import_core8.clamp)(target[index], minimum, maximum);
2969
3082
  constrainedTarget[index] = (constraintContext == null ? void 0 : constraintContext.mode) === "preserve" ? target[index] : shouldRubberBand ? applyRubberBand(
2970
3083
  target[index],
2971
3084
  constrained,
@@ -3000,10 +3113,10 @@ var OrthographicState = class extends ViewState {
3000
3113
  }
3001
3114
  switch (zoomAxis) {
3002
3115
  case "X":
3003
- zoomX = (0, import_core9.clamp)(zoomX, minZoomX, maxZoomX);
3116
+ zoomX = (0, import_core8.clamp)(zoomX, minZoomX, maxZoomX);
3004
3117
  break;
3005
3118
  case "Y":
3006
- zoomY = (0, import_core9.clamp)(zoomY, minZoomY, maxZoomY);
3119
+ zoomY = (0, import_core8.clamp)(zoomY, minZoomY, maxZoomY);
3007
3120
  break;
3008
3121
  default:
3009
3122
  let delta = Math.min(maxZoomX - zoomX, maxZoomY - zoomY, 0);
@@ -3055,7 +3168,7 @@ var OrthographicView = class extends View {
3055
3168
  OrthographicView.displayName = "OrthographicView";
3056
3169
 
3057
3170
  // ../core/src/transitions/fly-to-interpolator.ts
3058
- var import_core10 = require("@math.gl/core");
3171
+ var import_core9 = require("@math.gl/core");
3059
3172
  var import_web_mercator6 = require("@math.gl/web-mercator");
3060
3173
  var LINEARLY_INTERPOLATED_PROPS = {
3061
3174
  bearing: 0,
@@ -3078,7 +3191,7 @@ var FlyToInterpolator = class extends TransitionInterpolator {
3078
3191
  interpolateProps(startProps, endProps, t) {
3079
3192
  const viewport = (0, import_web_mercator6.flyToViewport)(startProps, endProps, t, this.opts);
3080
3193
  for (const key in LINEARLY_INTERPOLATED_PROPS) {
3081
- viewport[key] = (0, import_core10.lerp)(
3194
+ viewport[key] = (0, import_core9.lerp)(
3082
3195
  startProps[key] || LINEARLY_INTERPOLATED_PROPS[key],
3083
3196
  endProps[key] || LINEARLY_INTERPOLATED_PROPS[key],
3084
3197
  t
@@ -6188,7 +6301,7 @@ LoadingWidget.defaultProps = {
6188
6301
 
6189
6302
  // dist/stats-widget.js
6190
6303
  var import_jsx_runtime27 = require("preact/jsx-runtime");
6191
- var import_core34 = require("@luma.gl/core");
6304
+ var import_core33 = require("@luma.gl/core");
6192
6305
  var import_preact20 = require("preact");
6193
6306
  var import_hooks8 = require("preact/hooks");
6194
6307
  var DEFAULT_COUNT_FORMATTER = (stat) => `${stat.name}: ${stat.count}`;
@@ -6296,7 +6409,7 @@ var StatsWidget = class extends Widget {
6296
6409
  const metrics = ((_a = this.deck) == null ? void 0 : _a.metrics) ?? {};
6297
6410
  return Object.entries(metrics);
6298
6411
  case "luma":
6299
- return Array.from(import_core34.luma.stats.stats.values())[0];
6412
+ return Array.from(import_core33.luma.stats.stats.values())[0];
6300
6413
  case "device":
6301
6414
  const device = (_b = this.deck) == null ? void 0 : _b.device;
6302
6415
  const stats = device == null ? void 0 : device.statsManager.stats.values();