leaflet-rotate 0.5.1 → 0.5.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/README.md +2 -2
- package/build/server.js +19 -3
- package/dist/leaflet-rotate-src.js +313 -73
- package/dist/leaflet-rotate-src.js.map +4 -4
- package/dist/leaflet-rotate.js +1 -1
- package/dist/leaflet-rotate.js.map +4 -4
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/layer/marker/Marker.js +24 -11
- package/src/layer/tile/GridLayer.js +16 -1
- package/src/map/Map.js +270 -61
- package/src/map/handler/Drag.js +59 -0
- package/test/features/navigation-bounds.feature +33 -0
- package/test/features/overlays.feature +9 -0
- package/test/features/pitch.feature +19 -0
- package/test/features/rotation.feature +5 -0
- package/test/index.html +312 -29
- package/test/steps/navigation-bounds.steps.js +195 -0
- package/test/steps/overlays.steps.js +44 -0
- package/test/steps/pitch.steps.js +264 -0
- package/test/steps/{bearing.steps.js → rotation.steps.js} +147 -19
- package/test/support/hooks.js +19 -7
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.
|
|
29
|
+
<script src="https://unpkg.com/leaflet-rotate@0.5.3/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.
|
|
38
|
+
<script src="https://unpkg.com/leaflet-rotate@0.5.3/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
|
-
|
|
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);
|
|
@@ -478,6 +478,24 @@ var leafletRotate = (() => {
|
|
|
478
478
|
if (this._map._rotate) {
|
|
479
479
|
pos = this._map.rotatedPointToMapPanePoint(pos);
|
|
480
480
|
}
|
|
481
|
+
const bearing = this._getIconBearing();
|
|
482
|
+
if (this._icon) {
|
|
483
|
+
L.DomUtil.setPosition(this._icon, pos, bearing, pos, this.options.scale);
|
|
484
|
+
}
|
|
485
|
+
if (this._shadow) {
|
|
486
|
+
L.DomUtil.setPosition(this._shadow, pos, bearing, pos, this.options.scale);
|
|
487
|
+
}
|
|
488
|
+
this._zIndex = pos.y + this.options.zIndexOffset;
|
|
489
|
+
this._resetZIndex();
|
|
490
|
+
},
|
|
491
|
+
/**
|
|
492
|
+
* Compute the on-screen icon rotation from `rotation`, `rotateWithView` and `keepUpright`.
|
|
493
|
+
*
|
|
494
|
+
* @returns {number} Angle in radians.
|
|
495
|
+
*
|
|
496
|
+
* @since leaflet-rotate (0.5.3)
|
|
497
|
+
*/
|
|
498
|
+
_getIconBearing() {
|
|
481
499
|
let bearing = this.options.rotation || 0;
|
|
482
500
|
if (this.options.rotateWithView) {
|
|
483
501
|
bearing += this._map._bearing;
|
|
@@ -489,14 +507,7 @@ var leafletRotate = (() => {
|
|
|
489
507
|
}
|
|
490
508
|
bearing %= 2 * Math.PI;
|
|
491
509
|
}
|
|
492
|
-
|
|
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();
|
|
510
|
+
return bearing;
|
|
500
511
|
},
|
|
501
512
|
// _updateZIndex(offset) {
|
|
502
513
|
// if (!this._map._rotate) {
|
|
@@ -541,9 +552,24 @@ var leafletRotate = (() => {
|
|
|
541
552
|
}
|
|
542
553
|
return events;
|
|
543
554
|
},
|
|
555
|
+
/**
|
|
556
|
+
* Preserve the 3D context on tile containers without making individual tile levels 3D.
|
|
557
|
+
*
|
|
558
|
+
* @since leaflet-rotate (0.5.3)
|
|
559
|
+
*/
|
|
560
|
+
_initContainer() {
|
|
561
|
+
gridLayerProto._initContainer.apply(this, arguments);
|
|
562
|
+
if (!this._map._rotate || !this._container) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
for (let pane = this._container; pane && pane !== this._map._rotatePane; pane = pane.parentElement) {
|
|
566
|
+
pane.style.transformStyle = "preserve-3d";
|
|
567
|
+
}
|
|
568
|
+
},
|
|
544
569
|
/**
|
|
545
570
|
* Keep tile levels aligned when a bearing/pitch change shifts the pixel origin
|
|
546
571
|
* (eg. when the rotation pivot is not the view center), then refresh tiles (throttled).
|
|
572
|
+
* The first event also synchronizes levels created before any accumulated pans.
|
|
547
573
|
*
|
|
548
574
|
* @listens L.Map~rotate
|
|
549
575
|
*
|
|
@@ -555,7 +581,7 @@ var leafletRotate = (() => {
|
|
|
555
581
|
return;
|
|
556
582
|
}
|
|
557
583
|
const origin = map.getPixelOrigin();
|
|
558
|
-
if (this._rotateOrigin
|
|
584
|
+
if ((!this._rotateOrigin || !this._rotateOrigin.equals(origin)) && !map._animatingZoom && this._levels) {
|
|
559
585
|
this._setZoomTransforms(map.getCenter(), map.getZoom());
|
|
560
586
|
}
|
|
561
587
|
this._rotateOrigin = origin;
|
|
@@ -796,6 +822,19 @@ var leafletRotate = (() => {
|
|
|
796
822
|
pitchMaxDistance: 3
|
|
797
823
|
});
|
|
798
824
|
L.Map.include({
|
|
825
|
+
/**
|
|
826
|
+
* Rebuild perspective before Leaflet calculates the origin of a reset view.
|
|
827
|
+
* A reset clears the outer pane translation, unlike a completed pan.
|
|
828
|
+
*/
|
|
829
|
+
_resetView() {
|
|
830
|
+
if (this._pitch) {
|
|
831
|
+
delete this._pitchPan;
|
|
832
|
+
L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
|
|
833
|
+
this._updatePitchTransform();
|
|
834
|
+
arguments[0] = this._limitCenter(arguments[0], arguments[1], this.options.maxBounds);
|
|
835
|
+
}
|
|
836
|
+
return mapProto._resetView.apply(this, arguments);
|
|
837
|
+
},
|
|
799
838
|
/**
|
|
800
839
|
* Apply an immediate animated-zoom frame without waiting for a transition delay.
|
|
801
840
|
*
|
|
@@ -831,7 +870,11 @@ var leafletRotate = (() => {
|
|
|
831
870
|
mapProto.initialize.apply(this, arguments);
|
|
832
871
|
if (this.options.rotate) {
|
|
833
872
|
this.on("resize", this._updatePitchTransform, this);
|
|
873
|
+
this.on("movestart dragstart", this._startPitchPan, this);
|
|
874
|
+
this.on("move", this._updatePitchPan, this);
|
|
875
|
+
this.on("moveend", this._endPitchPan, this);
|
|
834
876
|
this.on("move", this._updatePitchSky, this);
|
|
877
|
+
this.on("rotate", this._panInsideMaxBounds, this);
|
|
835
878
|
this.setBearing(this.options.bearing);
|
|
836
879
|
this.setPitch(this.options.pitch);
|
|
837
880
|
}
|
|
@@ -951,7 +994,8 @@ var leafletRotate = (() => {
|
|
|
951
994
|
/**
|
|
952
995
|
* Return the geographic bounds of the current viewport, accounting for rotation and pitch.
|
|
953
996
|
*
|
|
954
|
-
* The result
|
|
997
|
+
* The result encloses the four corners of the visible ground rectangle. With pitch,
|
|
998
|
+
* the rectangle stops at the configured far plane, excluding the sky.
|
|
955
999
|
*
|
|
956
1000
|
* @returns {L.LatLngBounds} Geographic bounds visible in the map viewport.
|
|
957
1001
|
*
|
|
@@ -1170,26 +1214,38 @@ var leafletRotate = (() => {
|
|
|
1170
1214
|
this.fire("rotate");
|
|
1171
1215
|
},
|
|
1172
1216
|
/**
|
|
1173
|
-
* Transform a point with a
|
|
1217
|
+
* Transform a ground-plane point with a DOMMatrix, or reverse its screen projection.
|
|
1218
|
+
* Both branches divide by the homogeneous coordinate after applying the matrix.
|
|
1174
1219
|
*
|
|
1175
1220
|
* @param {L.Point|number[]} point Input pixel coordinate.
|
|
1176
|
-
* @param {DOMMatrix|Object} matrix Forward or inverse
|
|
1221
|
+
* @param {DOMMatrix|Object} matrix Forward CSS matrix or inverse homography with
|
|
1222
|
+
* row-major coefficients `a` through `i`.
|
|
1223
|
+
* @param {L.Point} [anchor] Keep inverse pan coordinates on this anchor's side of the horizon.
|
|
1177
1224
|
*
|
|
1178
1225
|
* @returns {L.Point} Transformed pixel coordinate.
|
|
1179
1226
|
*
|
|
1180
1227
|
* @since leaflet-rotate (0.5.0)
|
|
1181
1228
|
*/
|
|
1182
|
-
_transformPitchPoint(point, matrix) {
|
|
1229
|
+
_transformPitchPoint(point, matrix, anchor) {
|
|
1183
1230
|
point = toPoint(point);
|
|
1184
1231
|
if (!(matrix instanceof DOMMatrix)) {
|
|
1185
|
-
|
|
1232
|
+
if (anchor) {
|
|
1233
|
+
const anchorW = matrix.g * anchor.x + matrix.h * anchor.y + matrix.i, pointW = matrix.g * point.x + matrix.h * point.y + matrix.i;
|
|
1234
|
+
if (pointW * anchorW <= 0) {
|
|
1235
|
+
point = anchor.add(point.subtract(anchor).multiplyBy(anchorW / (anchorW - pointW) * (1 - 1e-6)));
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
const denominator2 = matrix.g * point.x + matrix.h * point.y + matrix.i;
|
|
1186
1239
|
return new L.Point(
|
|
1187
|
-
(matrix.a * point.x + matrix.b * point.y + matrix.c) /
|
|
1188
|
-
(matrix.d * point.x + matrix.e * point.y + matrix.f) /
|
|
1240
|
+
(matrix.a * point.x + matrix.b * point.y + matrix.c) / denominator2,
|
|
1241
|
+
(matrix.d * point.x + matrix.e * point.y + matrix.f) / denominator2
|
|
1189
1242
|
);
|
|
1190
1243
|
}
|
|
1191
|
-
const
|
|
1192
|
-
return new L.Point(
|
|
1244
|
+
const denominator = matrix.m14 * point.x + matrix.m24 * point.y + matrix.m44;
|
|
1245
|
+
return new L.Point(
|
|
1246
|
+
(matrix.m11 * point.x + matrix.m21 * point.y + matrix.m41) / denominator,
|
|
1247
|
+
(matrix.m12 * point.x + matrix.m22 * point.y + matrix.m42) / denominator
|
|
1248
|
+
);
|
|
1193
1249
|
},
|
|
1194
1250
|
/**
|
|
1195
1251
|
* Apply the current pitch/bearing CSS transform and cache its forward and inverse matrices.
|
|
@@ -1201,9 +1257,25 @@ var leafletRotate = (() => {
|
|
|
1201
1257
|
return;
|
|
1202
1258
|
}
|
|
1203
1259
|
const pivot = this._getPixelCenter(), size = this.getSize(), perspective = Math.max(1, size.y * 1.5), position = this._rotatePane._leaflet_pos || new L.Point(0, 0), transform = `translate3d(${pivot.x}px,${pivot.y}px,0) perspective(${perspective}px) rotateX(${this.getPitch()}deg) rotateZ(${this.getBearing()}deg) translate3d(${position.x - pivot.x}px,${position.y - pivot.y}px,0)`;
|
|
1260
|
+
this._setPitchTransform(transform);
|
|
1261
|
+
},
|
|
1262
|
+
/**
|
|
1263
|
+
* Apply the CSS transform and cache matching forward/inverse point conversions.
|
|
1264
|
+
*
|
|
1265
|
+
* Ground points have z = 0, so the 4x4 CSS matrix reduces to the homography
|
|
1266
|
+
* [a b c; d e f; g h i]. Its inverse maps screen points back onto that plane;
|
|
1267
|
+
* inverting the full 4x4 matrix at screen z = 0 would not undo perspective.
|
|
1268
|
+
*
|
|
1269
|
+
* @param {string} transform CSS transform list, including a serialized DOMMatrix.
|
|
1270
|
+
*
|
|
1271
|
+
* @since leaflet-rotate (0.5.2)
|
|
1272
|
+
*/
|
|
1273
|
+
_setPitchTransform(transform) {
|
|
1204
1274
|
this._rotatePane.style.transformOrigin = "0 0";
|
|
1205
|
-
this._rotatePane.style.transform = transform;
|
|
1206
1275
|
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);
|
|
1276
|
+
const ground = DOMMatrix.fromMatrix(matrix);
|
|
1277
|
+
ground.m13 = ground.m23 = ground.m43 = 0;
|
|
1278
|
+
this._rotatePane.style.transform = `matrix3d(${Array.from(ground.toFloat64Array(), (value) => value * 1e3).join(",")})`;
|
|
1207
1279
|
this._pitchInverseTransform = {
|
|
1208
1280
|
a: (e * i - f * h) / determinant,
|
|
1209
1281
|
b: (c * h - b * i) / determinant,
|
|
@@ -1217,9 +1289,76 @@ var leafletRotate = (() => {
|
|
|
1217
1289
|
};
|
|
1218
1290
|
this._updatePitchSky();
|
|
1219
1291
|
},
|
|
1292
|
+
/**
|
|
1293
|
+
* Capture a fixed projection baseline for a pitched drag or programmatic pan.
|
|
1294
|
+
*
|
|
1295
|
+
* `movestart` uses the viewport center; `dragstart` replaces it with the original
|
|
1296
|
+
* pointer position. Store both the screen anchor and its ground-plane coordinate
|
|
1297
|
+
* so subsequent frames can keep the grabbed geographic point under the pointer.
|
|
1298
|
+
* Pane positions are in screen pixels and the baseline matrices stay unchanged.
|
|
1299
|
+
*
|
|
1300
|
+
* @param {L.Event} e Leaflet `movestart` or `dragstart` event.
|
|
1301
|
+
*
|
|
1302
|
+
* @since leaflet-rotate (0.5.2)
|
|
1303
|
+
*/
|
|
1304
|
+
_startPitchPan(e) {
|
|
1305
|
+
if (!this._pitch || !this._loaded) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
const draggable = this.dragging && this.dragging._draggable, anchor = e.type === "dragstart" && draggable ? (this.pointerEventToContainerPoint || this.mouseEventToContainerPoint).call(this, {
|
|
1309
|
+
clientX: draggable._startPoint.x,
|
|
1310
|
+
clientY: draggable._startPoint.y
|
|
1311
|
+
}) : this.getSize().divideBy(2), panePos = this._getMapPanePos().clone();
|
|
1312
|
+
this._pitchPan = {
|
|
1313
|
+
anchor,
|
|
1314
|
+
panePos,
|
|
1315
|
+
lastPanePos: panePos,
|
|
1316
|
+
matrix: this._pitchTransform,
|
|
1317
|
+
inverse: this._pitchInverseTransform,
|
|
1318
|
+
layerAnchor: this.containerPointToLayerPoint(anchor),
|
|
1319
|
+
zoom: this._zoom
|
|
1320
|
+
};
|
|
1321
|
+
},
|
|
1322
|
+
/**
|
|
1323
|
+
* Translate the ground plane without moving the camera's vanishing point.
|
|
1324
|
+
*
|
|
1325
|
+
* For pane displacement delta, invert the displaced anchor with the baseline
|
|
1326
|
+
* projection to find the required layer-space shift. Compose
|
|
1327
|
+
* T(-delta) * baseline * T(shift): the left translation cancels Leaflet's outer
|
|
1328
|
+
* pane movement, while the right translation moves geography before perspective.
|
|
1329
|
+
* Always derive this from the baseline, not the previous frame, to avoid drift.
|
|
1330
|
+
* Zoom frames are handled by Leaflet's zoom/origin updates instead.
|
|
1331
|
+
*
|
|
1332
|
+
* @since leaflet-rotate (0.5.2)
|
|
1333
|
+
*/
|
|
1334
|
+
_updatePitchPan() {
|
|
1335
|
+
const pan = this._pitchPan;
|
|
1336
|
+
if (!this._pitch || !pan || this._animatingZoom || this._zoom !== pan.zoom) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const panePos = this._getMapPanePos();
|
|
1340
|
+
if (panePos.equals(pan.lastPanePos)) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const delta = panePos.subtract(pan.panePos), target = this._transformPitchPoint(pan.anchor.add(delta).subtract(pan.panePos), pan.inverse), shift = target.subtract(pan.layerAnchor);
|
|
1344
|
+
const matrix = new DOMMatrix().translate(-delta.x, -delta.y).multiply(pan.matrix).translate(shift.x, shift.y);
|
|
1345
|
+
this._setPitchTransform(matrix.toString());
|
|
1346
|
+
pan.lastPanePos = panePos.clone();
|
|
1347
|
+
delete this._lastCenter;
|
|
1348
|
+
},
|
|
1349
|
+
/**
|
|
1350
|
+
* Discard the baseline only at `moveend`, after any drag inertia has completed.
|
|
1351
|
+
* Keep the final transform and pixel origin: resetting the view here would
|
|
1352
|
+
* reproject layers abruptly and cause a visible jump on release.
|
|
1353
|
+
*
|
|
1354
|
+
* @since leaflet-rotate (0.5.2)
|
|
1355
|
+
*/
|
|
1356
|
+
_endPitchPan() {
|
|
1357
|
+
delete this._pitchPan;
|
|
1358
|
+
},
|
|
1220
1359
|
/**
|
|
1221
1360
|
* Fill the area above the far plane (where no tiles are loaded) with a sky gradient.
|
|
1222
|
-
* Style it through
|
|
1361
|
+
* Style it through `.leaflet-pitch-sky` or the `--leaflet-sky-background` CSS property.
|
|
1223
1362
|
*
|
|
1224
1363
|
* @since leaflet-rotate (0.5.1)
|
|
1225
1364
|
*/
|
|
@@ -1240,7 +1379,7 @@ var leafletRotate = (() => {
|
|
|
1240
1379
|
width: "100%",
|
|
1241
1380
|
zIndex: 0,
|
|
1242
1381
|
pointerEvents: "none",
|
|
1243
|
-
background: "linear-gradient(#9ec5e8, #dfe9f0)"
|
|
1382
|
+
background: "var(--leaflet-sky-background, linear-gradient(#9ec5e8, #dfe9f0))"
|
|
1244
1383
|
});
|
|
1245
1384
|
}
|
|
1246
1385
|
this._pitchSky.style.display = "";
|
|
@@ -1398,6 +1537,7 @@ var leafletRotate = (() => {
|
|
|
1398
1537
|
L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
|
|
1399
1538
|
if (this._rotate) {
|
|
1400
1539
|
this._rotatePane = this.createPane("rotatePane", this._mapPane);
|
|
1540
|
+
this._mapPane.style.transformStyle = this._rotatePane.style.transformStyle = "preserve-3d";
|
|
1401
1541
|
this._norotatePane = this.createPane("norotatePane", this._mapPane);
|
|
1402
1542
|
if (L.version.startsWith("2.")) {
|
|
1403
1543
|
this._rotatePane.style.transformOrigin = "0 0";
|
|
@@ -1454,53 +1594,87 @@ var leafletRotate = (() => {
|
|
|
1454
1594
|
return this;
|
|
1455
1595
|
},
|
|
1456
1596
|
/**
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1597
|
+
* Constrain the visible ground footprint, including its asymmetric pitched far side.
|
|
1598
|
+
* When it is larger than maxBounds, Leaflet's rebound centers the excess on each axis.
|
|
1599
|
+
*/
|
|
1600
|
+
_limitCenter(center, zoom, bounds) {
|
|
1601
|
+
if (!this._rotate || !bounds) {
|
|
1602
|
+
return mapProto._limitCenter.apply(this, arguments);
|
|
1603
|
+
}
|
|
1604
|
+
const centerPoint = this.project(center, zoom), layerCenter = this.containerPointToLayerPoint(this.getSize().divideBy(2)), footprint = this._getPaddedPixelBounds(0, false), viewBounds = new L.Bounds(
|
|
1605
|
+
centerPoint.add(footprint.min.subtract(layerCenter)),
|
|
1606
|
+
centerPoint.add(footprint.max.subtract(layerCenter))
|
|
1607
|
+
), offset = this._getBoundsOffset(viewBounds, bounds, zoom);
|
|
1608
|
+
if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
|
|
1609
|
+
return center;
|
|
1610
|
+
}
|
|
1611
|
+
return this.unproject(centerPoint.add(offset), zoom);
|
|
1612
|
+
},
|
|
1613
|
+
/**
|
|
1614
|
+
* Reapply configured maximum bounds after a map transform.
|
|
1463
1615
|
*
|
|
1464
|
-
* @
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1616
|
+
* @since leaflet-rotate (0.5.3)
|
|
1617
|
+
*/
|
|
1618
|
+
_panInsideMaxBounds() {
|
|
1619
|
+
if (this.options.maxBounds) {
|
|
1620
|
+
return mapProto._panInsideMaxBounds.apply(this, arguments);
|
|
1621
|
+
}
|
|
1622
|
+
},
|
|
1623
|
+
/**
|
|
1624
|
+
* Keep the visible ground footprint inside the given bounds.
|
|
1625
|
+
*
|
|
1626
|
+
* @param {L.LatLngBounds|Array} bounds Geographic bounds.
|
|
1627
|
+
* @param {Object} [options] Pan options passed to Leaflet.
|
|
1469
1628
|
* @returns {L.Map} This map instance.
|
|
1470
|
-
*
|
|
1471
|
-
* @since leaflet-rotate (
|
|
1629
|
+
*
|
|
1630
|
+
* @since leaflet-rotate (0.5.3)
|
|
1472
1631
|
*/
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1632
|
+
panInsideBounds(bounds, options) {
|
|
1633
|
+
if (this._rotate) {
|
|
1634
|
+
bounds = bounds instanceof L.LatLngBounds ? bounds : new L.LatLngBounds(bounds);
|
|
1635
|
+
const center = this.getCenter(), limited = this._limitCenter(center, this._zoom, bounds);
|
|
1636
|
+
const offset = this._getCenterOffset(limited);
|
|
1637
|
+
if (!center.equals(limited) && Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
|
|
1638
|
+
this._enforcingBounds = true;
|
|
1639
|
+
this.setView(limited, this._zoom, { reset: true });
|
|
1640
|
+
this._enforcingBounds = false;
|
|
1641
|
+
return this;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return mapProto.panInsideBounds.apply(this, arguments);
|
|
1645
|
+
},
|
|
1646
|
+
/**
|
|
1647
|
+
* Leaflet supplies inertia offsets in screen pixels, not projected CRS pixels.
|
|
1648
|
+
* Keep the drag's moving anchor when inertia continues a pitched pan.
|
|
1649
|
+
*/
|
|
1650
|
+
_limitOffset(offset, bounds) {
|
|
1651
|
+
if (!this._rotate || !bounds) {
|
|
1652
|
+
return mapProto._limitOffset.apply(this, arguments);
|
|
1653
|
+
}
|
|
1654
|
+
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(
|
|
1655
|
+
footprint.min.add(origin),
|
|
1656
|
+
footprint.max.add(origin)
|
|
1657
|
+
), bounds);
|
|
1658
|
+
return anchor.subtract(this.layerPointToContainerPoint(layerAnchor.add(shift).subtract(correction)));
|
|
1659
|
+
},
|
|
1660
|
+
/**
|
|
1661
|
+
* Pan by a screen-pixel offset, constraining large immediate pans to maxBounds.
|
|
1662
|
+
*
|
|
1663
|
+
* @param {L.Point|number[]} offset Pan offset in pixels.
|
|
1664
|
+
* @param {Object} [options] Pan options passed to Leaflet.
|
|
1665
|
+
* @returns {L.Map} This map instance.
|
|
1666
|
+
*
|
|
1667
|
+
* @since leaflet-rotate (0.5.3)
|
|
1668
|
+
*/
|
|
1669
|
+
panBy(offset, options) {
|
|
1670
|
+
offset = toPoint(offset);
|
|
1671
|
+
if (this._rotate && this._loaded && !this.getSize().contains(offset) && !(options && options.animate)) {
|
|
1672
|
+
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));
|
|
1673
|
+
this._resetView(this._limitCenter(center, this._zoom, this.options.maxBounds), this._zoom);
|
|
1674
|
+
return this;
|
|
1675
|
+
}
|
|
1676
|
+
return mapProto.panBy.call(this, offset, options);
|
|
1677
|
+
},
|
|
1504
1678
|
// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
|
|
1505
1679
|
// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
|
|
1506
1680
|
// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
|
|
@@ -1565,7 +1739,10 @@ var leafletRotate = (() => {
|
|
|
1565
1739
|
// return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
|
|
1566
1740
|
// },
|
|
1567
1741
|
/**
|
|
1568
|
-
* Return the
|
|
1742
|
+
* Return the screen-space pan offset needed to center the given geographic point.
|
|
1743
|
+
* Under perspective, projecting a negated layer offset is not equivalent to
|
|
1744
|
+
* negating its screen projection. Reflect the target about the layer center
|
|
1745
|
+
* to match the opposite anchor displacement performed by Leaflet's `panBy`.
|
|
1569
1746
|
*
|
|
1570
1747
|
* @param {L.LatLng} latlng Geographic point.
|
|
1571
1748
|
*
|
|
@@ -1577,6 +1754,10 @@ var leafletRotate = (() => {
|
|
|
1577
1754
|
if (!this._rotate) {
|
|
1578
1755
|
return mapProto._getCenterOffset.apply(this, arguments);
|
|
1579
1756
|
}
|
|
1757
|
+
if (this._pitch) {
|
|
1758
|
+
const center = this.getSize().divideBy(2), layerCenter = this.containerPointToLayerPoint(center), opposite = layerCenter.multiplyBy(2).subtract(this.project(latlng).subtract(this._pixelOrigin));
|
|
1759
|
+
return center.subtract(this.layerPointToContainerPoint(opposite));
|
|
1760
|
+
}
|
|
1580
1761
|
return this.latLngToContainerPoint(latlng)._subtract(this.getSize()._divideBy(2));
|
|
1581
1762
|
},
|
|
1582
1763
|
/**
|
|
@@ -1636,9 +1817,12 @@ var leafletRotate = (() => {
|
|
|
1636
1817
|
return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
|
|
1637
1818
|
},
|
|
1638
1819
|
/**
|
|
1820
|
+
* Return the viewport center before the outer map-pane translation.
|
|
1821
|
+
* Used as the bearing/pitch pivot in map-pane coordinates, not layer coordinates.
|
|
1822
|
+
*
|
|
1639
1823
|
* @since leaflet-rotate (v0.2.0)
|
|
1640
1824
|
*
|
|
1641
|
-
* @returns {L.Point} Map pivot
|
|
1825
|
+
* @returns {L.Point} Map pivot in map-pane pixels.
|
|
1642
1826
|
*/
|
|
1643
1827
|
_getPixelCenter() {
|
|
1644
1828
|
if (!this._rotate && mapProto._getPixelCenter) {
|
|
@@ -1647,25 +1831,32 @@ var leafletRotate = (() => {
|
|
|
1647
1831
|
return this.getSize()._divideBy(2)._subtract(this._getMapPanePos());
|
|
1648
1832
|
},
|
|
1649
1833
|
/**
|
|
1834
|
+
* Convert the padded visible ground rectangle to layer bounds for vector renderers.
|
|
1835
|
+
* Clip its far edge before reversing perspective to avoid unbounded render areas.
|
|
1836
|
+
*
|
|
1650
1837
|
* @see src\layer\vector\Renderer::_update()
|
|
1651
1838
|
*
|
|
1652
1839
|
* @param {number} padding Bounds padding as a fraction of map size.
|
|
1840
|
+
* @param {boolean} [round=true] Round renderer bounds; navigation needs exact ground coordinates.
|
|
1653
1841
|
*
|
|
1654
1842
|
* @returns {L.Bounds} Padded bounds in layer-point coordinates.
|
|
1655
1843
|
*
|
|
1656
1844
|
* @since leaflet-rotate (v0.2.0)
|
|
1657
1845
|
*/
|
|
1658
|
-
_getPaddedPixelBounds(padding) {
|
|
1846
|
+
_getPaddedPixelBounds(padding, round = true) {
|
|
1659
1847
|
if (!this._rotate && mapProto._getPaddedPixelBounds) {
|
|
1660
1848
|
return mapProto._getPaddedPixelBounds.apply(this, arguments);
|
|
1661
1849
|
}
|
|
1662
1850
|
const [padMin, padMax] = this._getPitchedContainerBounds(padding);
|
|
1663
1851
|
return new L.Bounds([
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
])
|
|
1852
|
+
[padMin.x, padMin.y],
|
|
1853
|
+
[padMin.x, padMax.y],
|
|
1854
|
+
[padMax.x, padMin.y],
|
|
1855
|
+
[padMax.x, padMax.y]
|
|
1856
|
+
].map((point) => {
|
|
1857
|
+
const projected = this.containerPointToLayerPoint(point);
|
|
1858
|
+
return round ? projected.floor() : projected;
|
|
1859
|
+
}));
|
|
1669
1860
|
},
|
|
1670
1861
|
/**
|
|
1671
1862
|
* Build and fire Leaflet's location-found event, including the device heading.
|
|
@@ -1846,6 +2037,55 @@ var leafletRotate = (() => {
|
|
|
1846
2037
|
});
|
|
1847
2038
|
L.Map.addInitHook("addHandler", "trackContainerMutation", L.Map.ContainerMutation);
|
|
1848
2039
|
|
|
2040
|
+
// src/map/handler/Drag.js
|
|
2041
|
+
var dragProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Map.Drag.prototype));
|
|
2042
|
+
L.Map.Drag.include({
|
|
2043
|
+
/**
|
|
2044
|
+
* Capture the ground footprint and pointer anchor before a constrained drag.
|
|
2045
|
+
*/
|
|
2046
|
+
_onDragStart() {
|
|
2047
|
+
dragProto._onDragStart.apply(this, arguments);
|
|
2048
|
+
const map = this._map;
|
|
2049
|
+
this._rotateLimit = null;
|
|
2050
|
+
if (!map._rotate || !map.options.maxBounds || !map.options.maxBoundsViscosity) {
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
const anchor = (map.pointerEventToContainerPoint || map.mouseEventToContainerPoint).call(map, {
|
|
2054
|
+
clientX: this._draggable._startPoint.x,
|
|
2055
|
+
clientY: this._draggable._startPoint.y
|
|
2056
|
+
}), footprint = map._getPaddedPixelBounds(0, false), origin = map.getPixelOrigin();
|
|
2057
|
+
this._rotateLimit = {
|
|
2058
|
+
anchor,
|
|
2059
|
+
layerAnchor: map.containerPointToLayerPoint(anchor),
|
|
2060
|
+
panePos: map._getMapPanePos().clone(),
|
|
2061
|
+
footprint: new L.Bounds(footprint.min.add(origin), footprint.max.add(origin)),
|
|
2062
|
+
matrix: map._pitch ? map._pitchTransform : null,
|
|
2063
|
+
inverse: map._pitchInverseTransform,
|
|
2064
|
+
bearing: map._bearing
|
|
2065
|
+
};
|
|
2066
|
+
},
|
|
2067
|
+
/**
|
|
2068
|
+
* Apply max-bounds viscosity in ground coordinates during a rotated drag.
|
|
2069
|
+
*/
|
|
2070
|
+
_onPreDragLimit() {
|
|
2071
|
+
const limit = this._rotateLimit;
|
|
2072
|
+
if (!this._map._rotate || !limit) {
|
|
2073
|
+
return dragProto._onPreDragLimit.apply(this, arguments);
|
|
2074
|
+
}
|
|
2075
|
+
const map = this._map, draggable = this._draggable, offset = draggable._newPos.subtract(draggable._startPos), shift = limit.matrix ? map._transformPitchPoint(
|
|
2076
|
+
limit.anchor.add(offset).subtract(limit.panePos),
|
|
2077
|
+
limit.inverse,
|
|
2078
|
+
limit.anchor.subtract(limit.panePos)
|
|
2079
|
+
).subtract(limit.layerAnchor) : offset.rotate(-limit.bearing), correction = map._getBoundsOffset(new L.Bounds(
|
|
2080
|
+
limit.footprint.min.subtract(shift),
|
|
2081
|
+
limit.footprint.max.subtract(shift)
|
|
2082
|
+
), map.options.maxBounds);
|
|
2083
|
+
shift._subtract(correction.multiplyBy(this._viscosity));
|
|
2084
|
+
const limitedOffset = limit.matrix ? map._transformPitchPoint(limit.layerAnchor.add(shift), limit.matrix).add(limit.panePos).subtract(limit.anchor) : shift.rotate(limit.bearing);
|
|
2085
|
+
draggable._newPos = draggable._startPos.add(limitedOffset);
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
|
|
1849
2089
|
// src/map/handler/TouchGestures.js
|
|
1850
2090
|
function touchToContainerPoint(map, touch) {
|
|
1851
2091
|
const container = map.getContainer(), rect = container.getBoundingClientRect();
|