leaflet-rotate 0.5.2 → 0.5.4

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/README.md CHANGED
@@ -26,7 +26,7 @@ Load Leaflet first, then the plugin. For example, using the published CDN build:
26
26
  ```html
27
27
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css">
28
28
  <script src="https://unpkg.com/leaflet@1.9/dist/leaflet.js"></script>
29
- <script src="https://unpkg.com/leaflet-rotate@0.5.2/dist/leaflet-rotate.js"></script>
29
+ <script src="https://unpkg.com/leaflet-rotate@0.5.4/dist/leaflet-rotate.js"></script>
30
30
  ```
31
31
 
32
32
  Or if you want to test it with [`2.0.0-alpha.1`](https://github.com/Leaflet/Leaflet/releases/tag/v2.0.0-alpha.1):
@@ -35,7 +35,7 @@ Or if you want to test it with [`2.0.0-alpha.1`](https://github.com/Leaflet/Leaf
35
35
  <link rel="stylesheet" href="https://unpkg.com/leaflet@2.0.0-alpha.1/dist/leaflet.css">
36
36
  <script src="https://unpkg.com/leaflet@2.0.0-alpha.1/dist/leaflet-global-src.js"></script>
37
37
  <script>globalThis.L = globalThis.leaflet;</script>
38
- <script src="https://unpkg.com/leaflet-rotate@0.5.2/dist/leaflet-rotate.js"></script>
38
+ <script src="https://unpkg.com/leaflet-rotate@0.5.4/dist/leaflet-rotate.js"></script>
39
39
  ```
40
40
 
41
41
  Now, you can enable rotation in the map options. Bearing and pitch values are in degrees:
package/build/server.js CHANGED
@@ -72,11 +72,27 @@ export function startServer(port = 8080) {
72
72
  response.writeHead(404).end();
73
73
  return;
74
74
  }
75
- response.writeHead(200, {
75
+ const headers = {
76
+ 'Accept-Ranges': 'bytes',
76
77
  'Content-Type': types[extname(target).toLowerCase()] ?? 'application/octet-stream',
77
- });
78
+ };
79
+ // Serve a single byte range per request.
80
+ const range = /^bytes=(\d*)-(\d*)$/.exec(request.headers.range ?? '');
81
+ let start = 0;
82
+ let end = content.size - 1;
83
+ if (range && (range[1] || range[2])) {
84
+ start = range[1] ? Number(range[1]) : Math.max(0, content.size - Number(range[2]));
85
+ end = range[1] && range[2] ? Math.min(Number(range[2]), end) : end;
86
+ if (start > end) {
87
+ response.writeHead(416, { 'Content-Range': `bytes */${content.size}` }).end();
88
+ return;
89
+ }
90
+ response.writeHead(206, { ...headers, 'Content-Length': end - start + 1, 'Content-Range': `bytes ${start}-${end}/${content.size}` });
91
+ } else {
92
+ response.writeHead(200, { ...headers, 'Content-Length': content.size });
93
+ }
78
94
  if (request.method === 'HEAD') response.end();
79
- else createReadStream(target).on('error', error => {
95
+ else createReadStream(target, content.size ? { start, end } : undefined).on('error', error => {
80
96
  console.error(error);
81
97
  response.destroy(error);
82
98
  }).pipe(response);
@@ -84,11 +84,25 @@ var leafletRotate = (() => {
84
84
  });
85
85
 
86
86
  // src/dom/Draggable.js
87
+ var draggableProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Draggable.prototype));
87
88
  L.Draggable.include({
88
- /** @TODO */
89
- // updateMapBearing(mapBearing) {
90
- // this._mapBearing = mapBearing;
91
- // },
89
+ _onMove(e) {
90
+ const pointer = e.touches ? e.touches[0] : e;
91
+ if (this._enabled && this._moving && (!e.touches || e.touches.length === 1)) {
92
+ const offset = new L.Point(pointer.clientX, pointer.clientY).subtract(this._startPoint);
93
+ if (!offset.x && !offset.y || Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) {
94
+ L.DomEvent.preventDefault(e);
95
+ this._newPos = this._startPos.add(new L.Point(
96
+ offset.x / this._parentScale.x,
97
+ offset.y / this._parentScale.y
98
+ ));
99
+ this._lastEvent = e;
100
+ this._updatePosition();
101
+ return;
102
+ }
103
+ }
104
+ return draggableProto._onMove.apply(this, arguments);
105
+ }
92
106
  });
93
107
 
94
108
  // src/geometry/Point.js
@@ -478,6 +492,24 @@ var leafletRotate = (() => {
478
492
  if (this._map._rotate) {
479
493
  pos = this._map.rotatedPointToMapPanePoint(pos);
480
494
  }
495
+ const bearing = this._getIconBearing();
496
+ if (this._icon) {
497
+ L.DomUtil.setPosition(this._icon, pos, bearing, pos, this.options.scale);
498
+ }
499
+ if (this._shadow) {
500
+ L.DomUtil.setPosition(this._shadow, pos, bearing, pos, this.options.scale);
501
+ }
502
+ this._zIndex = pos.y + this.options.zIndexOffset;
503
+ this._resetZIndex();
504
+ },
505
+ /**
506
+ * Compute the on-screen icon rotation from `rotation`, `rotateWithView` and `keepUpright`.
507
+ *
508
+ * @returns {number} Angle in radians.
509
+ *
510
+ * @since leaflet-rotate (0.5.3)
511
+ */
512
+ _getIconBearing() {
481
513
  let bearing = this.options.rotation || 0;
482
514
  if (this.options.rotateWithView) {
483
515
  bearing += this._map._bearing;
@@ -489,14 +521,7 @@ var leafletRotate = (() => {
489
521
  }
490
522
  bearing %= 2 * Math.PI;
491
523
  }
492
- if (this._icon) {
493
- L.DomUtil.setPosition(this._icon, pos, bearing, pos, this.options.scale);
494
- }
495
- if (this._shadow) {
496
- L.DomUtil.setPosition(this._shadow, pos, bearing, pos, this.options.scale);
497
- }
498
- this._zIndex = pos.y + this.options.zIndexOffset;
499
- this._resetZIndex();
524
+ return bearing;
500
525
  },
501
526
  // _updateZIndex(offset) {
502
527
  // if (!this._map._rotate) {
@@ -541,9 +566,24 @@ var leafletRotate = (() => {
541
566
  }
542
567
  return events;
543
568
  },
569
+ /**
570
+ * Preserve the 3D context on tile containers without making individual tile levels 3D.
571
+ *
572
+ * @since leaflet-rotate (0.5.3)
573
+ */
574
+ _initContainer() {
575
+ gridLayerProto._initContainer.apply(this, arguments);
576
+ if (!this._map._rotate || !this._container) {
577
+ return;
578
+ }
579
+ for (let pane = this._container; pane && pane !== this._map._rotatePane; pane = pane.parentElement) {
580
+ pane.style.transformStyle = "preserve-3d";
581
+ }
582
+ },
544
583
  /**
545
584
  * Keep tile levels aligned when a bearing/pitch change shifts the pixel origin
546
585
  * (eg. when the rotation pivot is not the view center), then refresh tiles (throttled).
586
+ * The first event also synchronizes levels created before any accumulated pans.
547
587
  *
548
588
  * @listens L.Map~rotate
549
589
  *
@@ -555,7 +595,7 @@ var leafletRotate = (() => {
555
595
  return;
556
596
  }
557
597
  const origin = map.getPixelOrigin();
558
- if (this._rotateOrigin && !this._rotateOrigin.equals(origin) && !map._animatingZoom && this._levels) {
598
+ if ((!this._rotateOrigin || !this._rotateOrigin.equals(origin)) && !map._animatingZoom && this._levels) {
559
599
  this._setZoomTransforms(map.getCenter(), map.getZoom());
560
600
  }
561
601
  this._rotateOrigin = origin;
@@ -796,6 +836,19 @@ var leafletRotate = (() => {
796
836
  pitchMaxDistance: 3
797
837
  });
798
838
  L.Map.include({
839
+ /**
840
+ * Rebuild perspective before Leaflet calculates the origin of a reset view.
841
+ * A reset clears the outer pane translation, unlike a completed pan.
842
+ */
843
+ _resetView() {
844
+ if (this._pitch) {
845
+ delete this._pitchPan;
846
+ L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
847
+ this._updatePitchTransform();
848
+ arguments[0] = this._limitCenter(arguments[0], arguments[1], this.options.maxBounds);
849
+ }
850
+ return mapProto._resetView.apply(this, arguments);
851
+ },
799
852
  /**
800
853
  * Apply an immediate animated-zoom frame without waiting for a transition delay.
801
854
  *
@@ -835,6 +888,7 @@ var leafletRotate = (() => {
835
888
  this.on("move", this._updatePitchPan, this);
836
889
  this.on("moveend", this._endPitchPan, this);
837
890
  this.on("move", this._updatePitchSky, this);
891
+ this.on("rotate", this._panInsideMaxBounds, this);
838
892
  this.setBearing(this.options.bearing);
839
893
  this.setPitch(this.options.pitch);
840
894
  }
@@ -1180,22 +1234,32 @@ var leafletRotate = (() => {
1180
1234
  * @param {L.Point|number[]} point Input pixel coordinate.
1181
1235
  * @param {DOMMatrix|Object} matrix Forward CSS matrix or inverse homography with
1182
1236
  * row-major coefficients `a` through `i`.
1237
+ * @param {L.Point} [anchor] Keep inverse pan coordinates on this anchor's side of the horizon.
1183
1238
  *
1184
1239
  * @returns {L.Point} Transformed pixel coordinate.
1185
1240
  *
1186
1241
  * @since leaflet-rotate (0.5.0)
1187
1242
  */
1188
- _transformPitchPoint(point, matrix) {
1243
+ _transformPitchPoint(point, matrix, anchor) {
1189
1244
  point = toPoint(point);
1190
1245
  if (!(matrix instanceof DOMMatrix)) {
1191
- const denominator = matrix.g * point.x + matrix.h * point.y + matrix.i;
1246
+ if (anchor) {
1247
+ const anchorW = matrix.g * anchor.x + matrix.h * anchor.y + matrix.i, pointW = matrix.g * point.x + matrix.h * point.y + matrix.i;
1248
+ if (pointW * anchorW <= 0) {
1249
+ point = anchor.add(point.subtract(anchor).multiplyBy(anchorW / (anchorW - pointW) * (1 - 1e-6)));
1250
+ }
1251
+ }
1252
+ const denominator2 = matrix.g * point.x + matrix.h * point.y + matrix.i;
1192
1253
  return new L.Point(
1193
- (matrix.a * point.x + matrix.b * point.y + matrix.c) / denominator,
1194
- (matrix.d * point.x + matrix.e * point.y + matrix.f) / denominator
1254
+ (matrix.a * point.x + matrix.b * point.y + matrix.c) / denominator2,
1255
+ (matrix.d * point.x + matrix.e * point.y + matrix.f) / denominator2
1195
1256
  );
1196
1257
  }
1197
- const transformed = new DOMPoint(point.x, point.y).matrixTransform(matrix);
1198
- return new L.Point(transformed.x / transformed.w, transformed.y / transformed.w);
1258
+ const denominator = matrix.m14 * point.x + matrix.m24 * point.y + matrix.m44;
1259
+ return new L.Point(
1260
+ (matrix.m11 * point.x + matrix.m21 * point.y + matrix.m41) / denominator,
1261
+ (matrix.m12 * point.x + matrix.m22 * point.y + matrix.m42) / denominator
1262
+ );
1199
1263
  },
1200
1264
  /**
1201
1265
  * Apply the current pitch/bearing CSS transform and cache its forward and inverse matrices.
@@ -1222,8 +1286,10 @@ var leafletRotate = (() => {
1222
1286
  */
1223
1287
  _setPitchTransform(transform) {
1224
1288
  this._rotatePane.style.transformOrigin = "0 0";
1225
- this._rotatePane.style.transform = transform;
1226
1289
  const matrix = this._pitchTransform = new DOMMatrix(transform), a = matrix.m11, b = matrix.m21, c = matrix.m41, d = matrix.m12, e = matrix.m22, f = matrix.m42, g = matrix.m14, h = matrix.m24, i = matrix.m44, determinant = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
1290
+ const ground = DOMMatrix.fromMatrix(matrix);
1291
+ ground.m13 = ground.m23 = ground.m43 = 0;
1292
+ this._rotatePane.style.transform = `matrix3d(${Array.from(ground.toFloat64Array(), (value) => value * 1e3).join(",")})`;
1227
1293
  this._pitchInverseTransform = {
1228
1294
  a: (e * i - f * h) / determinant,
1229
1295
  b: (c * h - b * i) / determinant,
@@ -1306,7 +1372,7 @@ var leafletRotate = (() => {
1306
1372
  },
1307
1373
  /**
1308
1374
  * Fill the area above the far plane (where no tiles are loaded) with a sky gradient.
1309
- * Style it through the `.leaflet-pitch-sky` class.
1375
+ * Style it through `.leaflet-pitch-sky` or the `--leaflet-sky-background` CSS property.
1310
1376
  *
1311
1377
  * @since leaflet-rotate (0.5.1)
1312
1378
  */
@@ -1327,7 +1393,7 @@ var leafletRotate = (() => {
1327
1393
  width: "100%",
1328
1394
  zIndex: 0,
1329
1395
  pointerEvents: "none",
1330
- background: "linear-gradient(#9ec5e8, #dfe9f0)"
1396
+ background: "var(--leaflet-sky-background, linear-gradient(#9ec5e8, #dfe9f0))"
1331
1397
  });
1332
1398
  }
1333
1399
  this._pitchSky.style.display = "";
@@ -1485,6 +1551,7 @@ var leafletRotate = (() => {
1485
1551
  L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
1486
1552
  if (this._rotate) {
1487
1553
  this._rotatePane = this.createPane("rotatePane", this._mapPane);
1554
+ this._mapPane.style.transformStyle = this._rotatePane.style.transformStyle = "preserve-3d";
1488
1555
  this._norotatePane = this.createPane("norotatePane", this._mapPane);
1489
1556
  if (L.version.startsWith("2.")) {
1490
1557
  this._rotatePane.style.transformOrigin = "0 0";
@@ -1524,7 +1591,7 @@ var leafletRotate = (() => {
1524
1591
  * @since leaflet-rotate (v0.1.4)
1525
1592
  */
1526
1593
  panInside(latlng, options) {
1527
- if (!this._rotate || !this._pitch && Math.abs(this._bearing).toFixed(1) < 0.1) {
1594
+ if (!this._rotate) {
1528
1595
  return mapProto.panInside.apply(this, arguments);
1529
1596
  }
1530
1597
  options = options || {};
@@ -1541,53 +1608,87 @@ var leafletRotate = (() => {
1541
1608
  return this;
1542
1609
  },
1543
1610
  /**
1544
- * Pans the map to the closest view that would lie inside the given bounds
1545
- * (if it's not already), controlling the animation using the options specific,
1546
- * if any.
1547
- *
1548
- * @TODO check if map bounds calculated by `L.Map::panInsideBounds()`
1549
- * function should match the `rotatePane` or `norotatePane` bounds
1611
+ * Constrain the visible ground footprint, including its asymmetric pitched far side.
1612
+ * When it is larger than maxBounds, Leaflet's rebound centers the excess on each axis.
1613
+ */
1614
+ _limitCenter(center, zoom, bounds) {
1615
+ if (!this._rotate || !bounds) {
1616
+ return mapProto._limitCenter.apply(this, arguments);
1617
+ }
1618
+ const centerPoint = this.project(center, zoom), layerCenter = this.containerPointToLayerPoint(this.getSize().divideBy(2)), footprint = this._getPaddedPixelBounds(0, false), viewBounds = new L.Bounds(
1619
+ centerPoint.add(footprint.min.subtract(layerCenter)),
1620
+ centerPoint.add(footprint.max.subtract(layerCenter))
1621
+ ), offset = this._getBoundsOffset(viewBounds, bounds, zoom);
1622
+ if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
1623
+ return center;
1624
+ }
1625
+ return this.unproject(centerPoint.add(offset), zoom);
1626
+ },
1627
+ /**
1628
+ * Reapply configured maximum bounds after a map transform.
1550
1629
  *
1551
- * @see https://github.com/fnicollet/Leaflet/issues/7
1552
- *
1553
- * @param {L.LatLngBounds} bounds Geographic bounds to fit inside the viewport.
1554
- * @param {Object} [options] Pan animation options.
1555
- *
1630
+ * @since leaflet-rotate (0.5.3)
1631
+ */
1632
+ _panInsideMaxBounds() {
1633
+ if (this.options.maxBounds) {
1634
+ return mapProto._panInsideMaxBounds.apply(this, arguments);
1635
+ }
1636
+ },
1637
+ /**
1638
+ * Keep the visible ground footprint inside the given bounds.
1639
+ *
1640
+ * @param {L.LatLngBounds|Array} bounds Geographic bounds.
1641
+ * @param {Object} [options] Pan options passed to Leaflet.
1556
1642
  * @returns {L.Map} This map instance.
1557
- *
1558
- * @since leaflet-rotate (v0.1.0)
1643
+ *
1644
+ * @since leaflet-rotate (0.5.3)
1559
1645
  */
1560
- // panInsideBounds(bounds, options) {
1561
- // this._enforcingBounds = true;
1562
- // const center = this.getCenter(),
1563
- // newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
1564
- //
1565
- // if (!center.equals(newCenter)) {
1566
- // this.panTo(newCenter, options);
1567
- // }
1568
- //
1569
- // this._enforcingBounds = false;
1570
- // return this;
1571
- // },
1572
- // adjust center for view to get inside bounds
1573
- // _limitCenter(center, zoom, bounds) {
1574
- //
1575
- // if (!bounds) { return center; }
1576
- //
1577
- // const centerPoint = this.project(center, zoom),
1578
- // viewHalf = this.getSize().divideBy(2),
1579
- // viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
1580
- // offset = this._getBoundsOffset(viewBounds, bounds, zoom);
1581
- //
1582
- // // If offset is less than a pixel, ignore.
1583
- // // This prevents unstable projections from getting into
1584
- // // an infinite loop of tiny offsets.
1585
- // if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
1586
- // return center;
1587
- // }
1588
- //
1589
- // return this.unproject(centerPoint.add(offset), zoom);
1590
- // },
1646
+ panInsideBounds(bounds, options) {
1647
+ if (this._rotate) {
1648
+ bounds = bounds instanceof L.LatLngBounds ? bounds : new L.LatLngBounds(bounds);
1649
+ const center = this.getCenter(), limited = this._limitCenter(center, this._zoom, bounds);
1650
+ const offset = this._getCenterOffset(limited);
1651
+ if (!center.equals(limited) && Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
1652
+ this._enforcingBounds = true;
1653
+ this.setView(limited, this._zoom, { reset: true });
1654
+ this._enforcingBounds = false;
1655
+ return this;
1656
+ }
1657
+ }
1658
+ return mapProto.panInsideBounds.apply(this, arguments);
1659
+ },
1660
+ /**
1661
+ * Leaflet supplies inertia offsets in screen pixels, not projected CRS pixels.
1662
+ * Keep the drag's moving anchor when inertia continues a pitched pan.
1663
+ */
1664
+ _limitOffset(offset, bounds) {
1665
+ if (!this._rotate || !bounds) {
1666
+ return mapProto._limitOffset.apply(this, arguments);
1667
+ }
1668
+ const pan = this._pitchPan, anchor = pan ? pan.anchor.add(this._getMapPanePos().subtract(pan.panePos)) : this.getSize().divideBy(2), layerAnchor = this.containerPointToLayerPoint(anchor), panePos = this._getMapPanePos(), target = anchor.subtract(offset).subtract(panePos), shift = (this._pitch ? this._transformPitchPoint(target, this._pitchInverseTransform, anchor.subtract(panePos)) : this.containerPointToLayerPoint(anchor.subtract(offset))).subtract(layerAnchor), footprint = this._getPaddedPixelBounds(0, false), origin = this.getPixelOrigin().subtract(shift), correction = this._getBoundsOffset(new L.Bounds(
1669
+ footprint.min.add(origin),
1670
+ footprint.max.add(origin)
1671
+ ), bounds);
1672
+ return anchor.subtract(this.layerPointToContainerPoint(layerAnchor.add(shift).subtract(correction)));
1673
+ },
1674
+ /**
1675
+ * Pan by a screen-pixel offset, constraining large immediate pans to maxBounds.
1676
+ *
1677
+ * @param {L.Point|number[]} offset Pan offset in pixels.
1678
+ * @param {Object} [options] Pan options passed to Leaflet.
1679
+ * @returns {L.Map} This map instance.
1680
+ *
1681
+ * @since leaflet-rotate (0.5.3)
1682
+ */
1683
+ panBy(offset, options) {
1684
+ offset = toPoint(offset);
1685
+ if (this._rotate && this._loaded && !this.getSize().contains(offset) && !(options && options.animate)) {
1686
+ const anchor = this.getSize().divideBy(2), panePos = this._getMapPanePos(), layerAnchor = this.containerPointToLayerPoint(anchor), target = anchor.subtract(offset).subtract(panePos), shift = (this._pitch ? this._transformPitchPoint(target, this._pitchInverseTransform, anchor.subtract(panePos)) : this.containerPointToLayerPoint(anchor.subtract(offset))).subtract(layerAnchor), center = this.unproject(this.project(this.getCenter()).subtract(shift));
1687
+ this._resetView(this._limitCenter(center, this._zoom, this.options.maxBounds), this._zoom);
1688
+ return this;
1689
+ }
1690
+ return mapProto.panBy.call(this, offset, options);
1691
+ },
1591
1692
  // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
1592
1693
  // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
1593
1694
  // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
@@ -1671,7 +1772,7 @@ var leafletRotate = (() => {
1671
1772
  const center = this.getSize().divideBy(2), layerCenter = this.containerPointToLayerPoint(center), opposite = layerCenter.multiplyBy(2).subtract(this.project(latlng).subtract(this._pixelOrigin));
1672
1773
  return center.subtract(this.layerPointToContainerPoint(opposite));
1673
1774
  }
1674
- return this.latLngToContainerPoint(latlng)._subtract(this.getSize()._divideBy(2));
1775
+ return this.layerPointToContainerPoint(this.project(latlng).subtract(this._pixelOrigin))._subtract(this.getSize()._divideBy(2));
1675
1776
  },
1676
1777
  /**
1677
1778
  * Return the current position of the rotated pane.
@@ -1701,7 +1802,7 @@ var leafletRotate = (() => {
1701
1802
  if (!this._rotate) {
1702
1803
  return mapProto._getNewPixelOrigin.apply(this, arguments);
1703
1804
  }
1704
- return this.project(center, zoom)._subtract(this.containerPointToLayerPoint(this.getSize()._divideBy(2)))._round();
1805
+ return this.project(center, zoom)._subtract(this.containerPointToLayerPoint(this.getSize()._divideBy(2)));
1705
1806
  },
1706
1807
  /**
1707
1808
  * Calculate the pixel bounds needed to cover the transformed viewport.
@@ -1750,22 +1851,26 @@ var leafletRotate = (() => {
1750
1851
  * @see src\layer\vector\Renderer::_update()
1751
1852
  *
1752
1853
  * @param {number} padding Bounds padding as a fraction of map size.
1854
+ * @param {boolean} [round=true] Round renderer bounds; navigation needs exact ground coordinates.
1753
1855
  *
1754
1856
  * @returns {L.Bounds} Padded bounds in layer-point coordinates.
1755
1857
  *
1756
1858
  * @since leaflet-rotate (v0.2.0)
1757
1859
  */
1758
- _getPaddedPixelBounds(padding) {
1860
+ _getPaddedPixelBounds(padding, round = true) {
1759
1861
  if (!this._rotate && mapProto._getPaddedPixelBounds) {
1760
1862
  return mapProto._getPaddedPixelBounds.apply(this, arguments);
1761
1863
  }
1762
1864
  const [padMin, padMax] = this._getPitchedContainerBounds(padding);
1763
1865
  return new L.Bounds([
1764
- this.containerPointToLayerPoint([padMin.x, padMin.y]).floor(),
1765
- this.containerPointToLayerPoint([padMin.x, padMax.y]).floor(),
1766
- this.containerPointToLayerPoint([padMax.x, padMin.y]).floor(),
1767
- this.containerPointToLayerPoint([padMax.x, padMax.y]).floor()
1768
- ]);
1866
+ [padMin.x, padMin.y],
1867
+ [padMin.x, padMax.y],
1868
+ [padMax.x, padMin.y],
1869
+ [padMax.x, padMax.y]
1870
+ ].map((point) => {
1871
+ const projected = this.containerPointToLayerPoint(point);
1872
+ return round ? projected.floor() : projected;
1873
+ }));
1769
1874
  },
1770
1875
  /**
1771
1876
  * Build and fire Leaflet's location-found event, including the device heading.
@@ -1946,6 +2051,55 @@ var leafletRotate = (() => {
1946
2051
  });
1947
2052
  L.Map.addInitHook("addHandler", "trackContainerMutation", L.Map.ContainerMutation);
1948
2053
 
2054
+ // src/map/handler/Drag.js
2055
+ var dragProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Map.Drag.prototype));
2056
+ L.Map.Drag.include({
2057
+ /**
2058
+ * Capture the ground footprint and pointer anchor before a constrained drag.
2059
+ */
2060
+ _onDragStart() {
2061
+ dragProto._onDragStart.apply(this, arguments);
2062
+ const map = this._map;
2063
+ this._rotateLimit = null;
2064
+ if (!map._rotate || !map.options.maxBounds || !map.options.maxBoundsViscosity) {
2065
+ return;
2066
+ }
2067
+ const anchor = (map.pointerEventToContainerPoint || map.mouseEventToContainerPoint).call(map, {
2068
+ clientX: this._draggable._startPoint.x,
2069
+ clientY: this._draggable._startPoint.y
2070
+ }), footprint = map._getPaddedPixelBounds(0, false), origin = map.getPixelOrigin();
2071
+ this._rotateLimit = {
2072
+ anchor,
2073
+ layerAnchor: map.containerPointToLayerPoint(anchor),
2074
+ panePos: map._getMapPanePos().clone(),
2075
+ footprint: new L.Bounds(footprint.min.add(origin), footprint.max.add(origin)),
2076
+ matrix: map._pitch ? map._pitchTransform : null,
2077
+ inverse: map._pitchInverseTransform,
2078
+ bearing: map._bearing
2079
+ };
2080
+ },
2081
+ /**
2082
+ * Apply max-bounds viscosity in ground coordinates during a rotated drag.
2083
+ */
2084
+ _onPreDragLimit() {
2085
+ const limit = this._rotateLimit;
2086
+ if (!this._map._rotate || !limit) {
2087
+ return dragProto._onPreDragLimit.apply(this, arguments);
2088
+ }
2089
+ const map = this._map, draggable = this._draggable, offset = draggable._newPos.subtract(draggable._startPos), shift = limit.matrix ? map._transformPitchPoint(
2090
+ limit.anchor.add(offset).subtract(limit.panePos),
2091
+ limit.inverse,
2092
+ limit.anchor.subtract(limit.panePos)
2093
+ ).subtract(limit.layerAnchor) : offset.rotate(-limit.bearing), correction = map._getBoundsOffset(new L.Bounds(
2094
+ limit.footprint.min.subtract(shift),
2095
+ limit.footprint.max.subtract(shift)
2096
+ ), map.options.maxBounds);
2097
+ shift._subtract(correction.multiplyBy(this._viscosity));
2098
+ const limitedOffset = limit.matrix ? map._transformPitchPoint(limit.layerAnchor.add(shift), limit.matrix).add(limit.panePos).subtract(limit.anchor) : shift.rotate(limit.bearing);
2099
+ draggable._newPos = draggable._startPos.add(limitedOffset);
2100
+ }
2101
+ });
2102
+
1949
2103
  // src/map/handler/TouchGestures.js
1950
2104
  function touchToContainerPoint(map, touch) {
1951
2105
  const container = map.getContainer(), rect = container.getBoundingClientRect();