leaflet-rotate 0.4.0 → 0.5.0
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 +73 -4
- package/dist/leaflet-rotate-src.js +836 -103
- package/dist/leaflet-rotate-src.js.map +2 -2
- package/dist/leaflet-rotate.js +1 -1
- package/dist/leaflet-rotate.js.map +3 -3
- package/misc/leaflet-heat-rotate.js +7 -0
- package/package.json +1 -1
- package/src/control/Rotate.js +78 -5
- package/src/dom/DomUtil.js +23 -12
- package/src/dom/Draggable.js +5 -0
- package/src/geo/LatLngBounds.js +5 -0
- package/src/geometry/Bounds.js +5 -0
- package/src/geometry/Point.js +17 -3
- package/src/index.js +3 -1
- package/src/layer/DivOverlay.js +34 -5
- package/src/layer/Popup.js +14 -3
- package/src/layer/Tooltip.js +16 -0
- package/src/layer/marker/Icon.js +12 -0
- package/src/layer/marker/Marker.js +86 -7
- package/src/layer/tile/GridLayer.js +18 -2
- package/src/layer/vector/Renderer.js +57 -6
- package/src/map/Map.js +338 -56
- package/src/map/handler/CompassBearing.js +27 -1
- package/src/map/handler/ContainerMutation.js +18 -1
- package/src/map/handler/ShiftKeyRotate.js +153 -13
- package/src/map/handler/TouchGestures.js +63 -7
- package/src/map/handler/TouchRotate.js +18 -1
- package/src/map/handler/TouchZoom.js +21 -1
- package/test/features/rotation.feature +30 -0
- package/test/index.html +2 -1
- package/test/steps/bearing.steps.js +342 -0
- package/test/support/hooks.js +4 -16
|
@@ -30,11 +30,15 @@ var leafletRotate = (() => {
|
|
|
30
30
|
* Resets the 3D CSS transform of `el` so it is translated by `offset` pixels,
|
|
31
31
|
* optionally scaled by `scale`, and rotated around `pivot` when `bearing` is set.
|
|
32
32
|
*
|
|
33
|
-
* @param {HTMLElement} el
|
|
34
|
-
* @param {L.Point} offset
|
|
35
|
-
* @param {
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {L.Point} pivot
|
|
33
|
+
* @param {HTMLElement} el Element to transform.
|
|
34
|
+
* @param {L.Point} [offset] Translation in pixels; defaults to the origin.
|
|
35
|
+
* @param {number} [scale] Optional scale factor.
|
|
36
|
+
* @param {number} [bearing] Optional rotation in radians.
|
|
37
|
+
* @param {L.Point} [pivot] Point in pixels around which to rotate.
|
|
38
|
+
*
|
|
39
|
+
* @returns {void}
|
|
40
|
+
*
|
|
41
|
+
* @since leaflet-rotate (v0.1.0)
|
|
38
42
|
*/
|
|
39
43
|
setTransform(el, offset, scale, bearing, pivot) {
|
|
40
44
|
let pos = offset || new L.Point(0, 0);
|
|
@@ -48,11 +52,15 @@ var leafletRotate = (() => {
|
|
|
48
52
|
* Sets the position of `el` to `point`, using CSS transforms or top/left positioning
|
|
49
53
|
* depending on browser support, and optionally rotating it around `pivot`.
|
|
50
54
|
*
|
|
51
|
-
* @param {HTMLElement} el
|
|
52
|
-
* @param {L.Point} point
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {L.Point} pivot
|
|
55
|
-
* @param {
|
|
55
|
+
* @param {HTMLElement} el Element to position.
|
|
56
|
+
* @param {L.Point} point Position in pixels.
|
|
57
|
+
* @param {number} [bearing] Rotation in radians; zero or omitted uses Leaflet positioning.
|
|
58
|
+
* @param {L.Point} [pivot] Point in pixels around which to rotate.
|
|
59
|
+
* @param {number} [scale] Optional scale factor.
|
|
60
|
+
*
|
|
61
|
+
* @returns {void}
|
|
62
|
+
*
|
|
63
|
+
* @since leaflet-rotate (v0.1.0)
|
|
56
64
|
*/
|
|
57
65
|
setPosition(el, point, bearing, pivot, scale) {
|
|
58
66
|
if (!bearing) {
|
|
@@ -64,8 +72,11 @@ var leafletRotate = (() => {
|
|
|
64
72
|
/**
|
|
65
73
|
* Returns the position assigned to `el`.
|
|
66
74
|
*
|
|
67
|
-
* @param {HTMLElement} el
|
|
68
|
-
*
|
|
75
|
+
* @param {HTMLElement} el Element whose Leaflet position is requested.
|
|
76
|
+
*
|
|
77
|
+
* @returns {L.Point} Stored Leaflet position, or the position measured by Leaflet.
|
|
78
|
+
*
|
|
79
|
+
* @since leaflet-rotate (v0.1.0)
|
|
69
80
|
*/
|
|
70
81
|
getPosition(el) {
|
|
71
82
|
return el._leaflet_pos || domUtilProto.getPosition(el);
|
|
@@ -88,7 +99,10 @@ var leafletRotate = (() => {
|
|
|
88
99
|
* ⎡ x' ⎤ = ⎡ cos θ -sin θ ⎤ ⎡ x ⎤
|
|
89
100
|
* ⎣ y' ⎦ ⎣ sin θ cos θ ⎦ ⎣ y ⎦
|
|
90
101
|
*
|
|
91
|
-
* @param theta
|
|
102
|
+
* @param {number} theta Rotation angle in radians, counterclockwise.
|
|
103
|
+
* @returns {L.Point} Rotated point (or this point when theta is zero).
|
|
104
|
+
*
|
|
105
|
+
* @since leaflet-rotate (v0.1.0)
|
|
92
106
|
*/
|
|
93
107
|
rotate(theta) {
|
|
94
108
|
return this.rotateFrom(theta, new L.Point(0, 0));
|
|
@@ -102,10 +116,12 @@ var leafletRotate = (() => {
|
|
|
102
116
|
*
|
|
103
117
|
* same as `this.subtract(pivot).rotate(theta).add(pivot)`
|
|
104
118
|
*
|
|
105
|
-
* @param {
|
|
106
|
-
* @param {L.Point} pivot
|
|
119
|
+
* @param {number} theta Rotation angle in radians, counterclockwise.
|
|
120
|
+
* @param {L.Point} pivot Point to rotate around.
|
|
107
121
|
*
|
|
108
122
|
* @returns {L.Point}
|
|
123
|
+
*
|
|
124
|
+
* @since leaflet-rotate (v0.1.0)
|
|
109
125
|
*/
|
|
110
126
|
rotateFrom(theta, pivot) {
|
|
111
127
|
if (!theta) {
|
|
@@ -132,17 +148,41 @@ var leafletRotate = (() => {
|
|
|
132
148
|
var divOverlayProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.DivOverlay.prototype));
|
|
133
149
|
L.DivOverlay.include({
|
|
134
150
|
/**
|
|
135
|
-
*
|
|
136
|
-
* the map is moved by calling `map.setBearing(theta)`
|
|
151
|
+
* Extend overlay event subscriptions to refresh position after map rotation and pitch.
|
|
137
152
|
*
|
|
138
153
|
* @listens L.Map~rotate
|
|
154
|
+
* @listens L.Map~pitch
|
|
155
|
+
*
|
|
156
|
+
* @returns {Object} Leaflet event map for the overlay.
|
|
157
|
+
*
|
|
158
|
+
* @since leaflet-rotate (v0.1.0)
|
|
139
159
|
*/
|
|
140
160
|
getEvents() {
|
|
141
|
-
|
|
161
|
+
const events = Object.assign(divOverlayProto.getEvents.apply(this, arguments), {
|
|
162
|
+
rotate: this._updatePosition,
|
|
163
|
+
pitch: this._updatePitchEvents
|
|
164
|
+
});
|
|
165
|
+
if (this._map && this._map._pitch) {
|
|
166
|
+
events.move = this._updatePosition;
|
|
167
|
+
}
|
|
168
|
+
return events;
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* Listen to map movement only while pitch is non-zero.
|
|
172
|
+
*
|
|
173
|
+
* @since leaflet-rotate (0.5.0)
|
|
174
|
+
*/
|
|
175
|
+
_updatePitchEvents() {
|
|
176
|
+
if (this._map._pitch) {
|
|
177
|
+
this._map.on("move", this._updatePosition, this);
|
|
178
|
+
} else {
|
|
179
|
+
this._map.off("move", this._updatePosition, this);
|
|
180
|
+
}
|
|
142
181
|
},
|
|
143
182
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
183
|
+
* Reposition the overlay after map movement, applying rotation/pitch-aware coordinates.
|
|
184
|
+
*
|
|
185
|
+
* @since leaflet-rotate (v0.1.0)
|
|
146
186
|
*/
|
|
147
187
|
_updatePosition() {
|
|
148
188
|
if (!this._map) {
|
|
@@ -170,8 +210,11 @@ var leafletRotate = (() => {
|
|
|
170
210
|
var popupProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Popup.prototype));
|
|
171
211
|
L.Popup.include({
|
|
172
212
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
213
|
+
* Update the popup position during zoom animation and transform it into map-pane coordinates.
|
|
214
|
+
*
|
|
215
|
+
* @param {Object} e Zoom animation event.
|
|
216
|
+
*
|
|
217
|
+
* @since leaflet-rotate (v0.1.0)
|
|
175
218
|
*/
|
|
176
219
|
_animateZoom(e) {
|
|
177
220
|
popupProto._animateZoom.apply(this, arguments);
|
|
@@ -182,9 +225,13 @@ var leafletRotate = (() => {
|
|
|
182
225
|
}
|
|
183
226
|
},
|
|
184
227
|
/**
|
|
185
|
-
*
|
|
228
|
+
* Pan the map when a popup with auto-pan enabled would extend beyond the viewport.
|
|
186
229
|
*
|
|
187
230
|
* @see https://github.com/fnicollet/Leaflet/pull/21
|
|
231
|
+
*
|
|
232
|
+
* @fires L.Map#autopanstart
|
|
233
|
+
*
|
|
234
|
+
* @since leaflet-rotate (v0.1.0)
|
|
188
235
|
*/
|
|
189
236
|
_adjustPan() {
|
|
190
237
|
if (!this.options.autoPan || this._map._panAnim && this._map._panAnim._inProgress) {
|
|
@@ -222,6 +269,13 @@ var leafletRotate = (() => {
|
|
|
222
269
|
// src/layer/Tooltip.js
|
|
223
270
|
var tooltipProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Tooltip.prototype));
|
|
224
271
|
L.Tooltip.include({
|
|
272
|
+
/**
|
|
273
|
+
* Reposition the tooltip during zoom animation using transformed map coordinates.
|
|
274
|
+
*
|
|
275
|
+
* @param {Object} e Zoom animation event.
|
|
276
|
+
*
|
|
277
|
+
* @since leaflet-rotate (v0.1.0)
|
|
278
|
+
*/
|
|
225
279
|
_animateZoom(e) {
|
|
226
280
|
if (!this._map._rotate) {
|
|
227
281
|
return tooltipProto._animateZoom.apply(this, arguments);
|
|
@@ -230,6 +284,11 @@ var leafletRotate = (() => {
|
|
|
230
284
|
pos = this._map.rotatedPointToMapPanePoint(pos);
|
|
231
285
|
this._setPosition(pos);
|
|
232
286
|
},
|
|
287
|
+
/**
|
|
288
|
+
* Recompute tooltip screen position after map movement.
|
|
289
|
+
*
|
|
290
|
+
* @since leaflet-rotate (v0.1.0)
|
|
291
|
+
*/
|
|
233
292
|
_updatePosition() {
|
|
234
293
|
if (!this._map._rotate) {
|
|
235
294
|
return tooltipProto._updatePosition.apply(this, arguments);
|
|
@@ -243,6 +302,14 @@ var leafletRotate = (() => {
|
|
|
243
302
|
// src/layer/marker/Icon.js
|
|
244
303
|
var iconProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Icon.prototype));
|
|
245
304
|
L.Icon.include({
|
|
305
|
+
/**
|
|
306
|
+
* Set marker icon dimensions and anchor styles, including the rotation origin.
|
|
307
|
+
*
|
|
308
|
+
* @param {HTMLImageElement} img Icon or shadow element.
|
|
309
|
+
* @param {string} name Icon style key (`icon` or `shadow`).
|
|
310
|
+
*
|
|
311
|
+
* @since leaflet-rotate (v0.1.0)
|
|
312
|
+
*/
|
|
246
313
|
_setIconStyles(img, name) {
|
|
247
314
|
const options = this.options;
|
|
248
315
|
let sizeOption = options[name + "Size"];
|
|
@@ -267,27 +334,38 @@ var leafletRotate = (() => {
|
|
|
267
334
|
var markerProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Marker.prototype));
|
|
268
335
|
L.Marker.mergeOptions({
|
|
269
336
|
/**
|
|
270
|
-
* Rotation of
|
|
337
|
+
* Rotation of the marker icon in radians.
|
|
271
338
|
*
|
|
272
339
|
* @type {Number}
|
|
340
|
+
*
|
|
341
|
+
* @since leaflet-rotate (v0.1.0)
|
|
273
342
|
*/
|
|
274
343
|
rotation: 0,
|
|
275
344
|
/**
|
|
276
|
-
* Rotate
|
|
345
|
+
* Rotate the marker icon when the map bearing changes.
|
|
346
|
+
*
|
|
347
|
+
* Combine with `keepUpright: true` to keep the marker readable while
|
|
348
|
+
* preserving its line alignment.
|
|
277
349
|
*
|
|
278
350
|
* @type {Boolean}
|
|
351
|
+
*
|
|
352
|
+
* @since leaflet-rotate (v0.1.0)
|
|
279
353
|
*/
|
|
280
354
|
rotateWithView: false,
|
|
281
355
|
/**
|
|
282
|
-
* Flip
|
|
356
|
+
* Flip the icon when needed to keep it upright while map-aligned.
|
|
283
357
|
*
|
|
284
358
|
* @type {Boolean}
|
|
359
|
+
*
|
|
360
|
+
* @since leaflet-rotate (v0.1.0)
|
|
285
361
|
*/
|
|
286
362
|
keepUpright: false,
|
|
287
363
|
/**
|
|
288
|
-
*
|
|
364
|
+
* Optional scale of the marker icon and shadow.
|
|
289
365
|
*
|
|
290
366
|
* @type {Number}
|
|
367
|
+
*
|
|
368
|
+
* @since leaflet-rotate (v0.1.0)
|
|
291
369
|
*/
|
|
292
370
|
scale: void 0
|
|
293
371
|
});
|
|
@@ -299,6 +377,13 @@ var leafletRotate = (() => {
|
|
|
299
377
|
// }
|
|
300
378
|
// this._draggable.updateMapBearing(this._marker._map._bearing);
|
|
301
379
|
// },
|
|
380
|
+
/**
|
|
381
|
+
* Update a dragged marker's geographic position from its transformed icon position.
|
|
382
|
+
*
|
|
383
|
+
* @param {Object} e Marker drag event with `latlng` and `oldLatLng` fields.
|
|
384
|
+
*
|
|
385
|
+
* @since leaflet-rotate (v0.1.0)
|
|
386
|
+
*/
|
|
302
387
|
_onDrag(e) {
|
|
303
388
|
const marker = this._marker, rotated_marker = marker.options.rotation || marker.options.rotateWithView, shadow = marker._shadow;
|
|
304
389
|
let iconPos = L.DomUtil.getPosition(marker._icon);
|
|
@@ -316,6 +401,13 @@ var leafletRotate = (() => {
|
|
|
316
401
|
else marker.fire("move", e);
|
|
317
402
|
marker.fire("drag", e);
|
|
318
403
|
},
|
|
404
|
+
/**
|
|
405
|
+
* Reposition the marker after a drag ends, then run Leaflet's drag-end logic.
|
|
406
|
+
*
|
|
407
|
+
* @param {Object} e Marker drag-end event.
|
|
408
|
+
*
|
|
409
|
+
* @since leaflet-rotate (v0.1.0)
|
|
410
|
+
*/
|
|
319
411
|
_onDragEnd(e) {
|
|
320
412
|
if (this._marker._map._rotate) {
|
|
321
413
|
this._marker.update();
|
|
@@ -325,14 +417,42 @@ var leafletRotate = (() => {
|
|
|
325
417
|
};
|
|
326
418
|
L.Marker.include({
|
|
327
419
|
/**
|
|
328
|
-
*
|
|
329
|
-
* is moved by calling `map.setBearing(theta)`
|
|
420
|
+
* Extend marker event subscriptions to reposition its icon after bearing or pitch changes.
|
|
330
421
|
*
|
|
331
422
|
* @listens L.Map~rotate
|
|
423
|
+
* @listens L.Map~pitch
|
|
424
|
+
*
|
|
425
|
+
* @returns {Object} Leaflet event map for the marker.
|
|
426
|
+
*
|
|
427
|
+
* @since leaflet-rotate (v0.1.0)
|
|
332
428
|
*/
|
|
333
429
|
getEvents() {
|
|
334
|
-
|
|
430
|
+
const events = Object.assign(markerProto.getEvents.apply(this, arguments), {
|
|
431
|
+
rotate: this.update,
|
|
432
|
+
pitch: this._updatePitchEvents
|
|
433
|
+
});
|
|
434
|
+
if (this._map && this._map._pitch) {
|
|
435
|
+
events.move = this.update;
|
|
436
|
+
}
|
|
437
|
+
return events;
|
|
335
438
|
},
|
|
439
|
+
/**
|
|
440
|
+
* Subscribe to map movement while pitch is active to keep the icon aligned.
|
|
441
|
+
*
|
|
442
|
+
* @since leaflet-rotate (0.5.0)
|
|
443
|
+
*/
|
|
444
|
+
_updatePitchEvents() {
|
|
445
|
+
if (this._map._pitch) {
|
|
446
|
+
this._map.on("move", this.update, this);
|
|
447
|
+
} else {
|
|
448
|
+
this._map.off("move", this.update, this);
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
/**
|
|
452
|
+
* Install rotation-aware drag callbacks when marker dragging is enabled.
|
|
453
|
+
*
|
|
454
|
+
* @since leaflet-rotate (v0.1.0)
|
|
455
|
+
*/
|
|
336
456
|
_initInteraction() {
|
|
337
457
|
const ret = markerProto._initInteraction.apply(this, arguments);
|
|
338
458
|
if (this.dragging && this.dragging.enabled() && this._map && this._map._rotate) {
|
|
@@ -347,6 +467,13 @@ var leafletRotate = (() => {
|
|
|
347
467
|
}
|
|
348
468
|
return ret;
|
|
349
469
|
},
|
|
470
|
+
/**
|
|
471
|
+
* Position and rotate the marker icon and shadow in map-pane coordinates.
|
|
472
|
+
*
|
|
473
|
+
* @param {L.Point} pos Marker anchor in layer-point coordinates.
|
|
474
|
+
*
|
|
475
|
+
* @since leaflet-rotate (v0.1.0)
|
|
476
|
+
*/
|
|
350
477
|
_setPos(pos) {
|
|
351
478
|
if (this._map._rotate) {
|
|
352
479
|
pos = this._map.rotatedPointToMapPanePoint(pos);
|
|
@@ -377,6 +504,15 @@ var leafletRotate = (() => {
|
|
|
377
504
|
// }
|
|
378
505
|
// this._icon.style.zIndex = Math.round(this._zIndex + offset);
|
|
379
506
|
// },
|
|
507
|
+
/**
|
|
508
|
+
* Set the marker icon's rotation and redraw it.
|
|
509
|
+
*
|
|
510
|
+
* @param {number} rotation Angle in radians.
|
|
511
|
+
*
|
|
512
|
+
* @returns {void}
|
|
513
|
+
*
|
|
514
|
+
* @since leaflet-rotate (v0.1.0)
|
|
515
|
+
*/
|
|
380
516
|
setRotation(rotation) {
|
|
381
517
|
this.options.rotation = rotation;
|
|
382
518
|
this.update();
|
|
@@ -387,10 +523,13 @@ var leafletRotate = (() => {
|
|
|
387
523
|
var gridLayerProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.GridLayer.prototype));
|
|
388
524
|
L.GridLayer.include({
|
|
389
525
|
/**
|
|
390
|
-
*
|
|
391
|
-
* moved by just calling `map.setBearing(theta)`
|
|
526
|
+
* Add a throttled tile refresh after map bearing changes.
|
|
392
527
|
*
|
|
393
528
|
* @listens L.Map~rotate
|
|
529
|
+
*
|
|
530
|
+
* @returns {Object} Leaflet event map for the grid layer.
|
|
531
|
+
*
|
|
532
|
+
* @since leaflet-rotate (v0.1.0)
|
|
394
533
|
*/
|
|
395
534
|
getEvents() {
|
|
396
535
|
const events = gridLayerProto.getEvents.apply(this, arguments);
|
|
@@ -402,6 +541,15 @@ var leafletRotate = (() => {
|
|
|
402
541
|
}
|
|
403
542
|
return events;
|
|
404
543
|
},
|
|
544
|
+
/**
|
|
545
|
+
* Return tile pixel bounds that cover the rotated viewport.
|
|
546
|
+
*
|
|
547
|
+
* @param {L.LatLng} center View center used to calculate bounds.
|
|
548
|
+
*
|
|
549
|
+
* @returns {L.Bounds} Pixel bounds covered by the layer's tiles.
|
|
550
|
+
*
|
|
551
|
+
* @since leaflet-rotate (v0.1.0)
|
|
552
|
+
*/
|
|
405
553
|
_getTiledPixelBounds(center) {
|
|
406
554
|
if (!this._map._rotate) {
|
|
407
555
|
return gridLayerProto._getTiledPixelBounds.apply(this, arguments);
|
|
@@ -414,21 +562,45 @@ var leafletRotate = (() => {
|
|
|
414
562
|
var rendererProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Renderer.prototype));
|
|
415
563
|
var rendererBase = Object.getPrototypeOf(L.Renderer.prototype);
|
|
416
564
|
var rendererGetEvents = rendererProto.getEvents || rendererBase.getEvents;
|
|
565
|
+
var rendererOnMoveEnd = rendererProto._onMoveEnd || rendererBase._onMoveEnd;
|
|
417
566
|
L.Renderer.include({
|
|
418
567
|
/**
|
|
419
|
-
*
|
|
420
|
-
* map is moved by just calling `map.setBearing(theta)`
|
|
568
|
+
* Subscribe the renderer to map rotation so its visible bounds are recalculated.
|
|
421
569
|
*
|
|
422
570
|
* @listens L.Map~rotate
|
|
571
|
+
*
|
|
572
|
+
* @returns {Object} Leaflet event map for the renderer.
|
|
573
|
+
*
|
|
574
|
+
* @since leaflet-rotate (v0.1.0)
|
|
423
575
|
*/
|
|
424
576
|
getEvents() {
|
|
425
577
|
const events = rendererGetEvents.apply(this, arguments);
|
|
426
|
-
return
|
|
578
|
+
return Object.assign(events, { rotate: this._onRotate });
|
|
427
579
|
},
|
|
428
580
|
/**
|
|
429
|
-
*
|
|
581
|
+
* Reproject vector paths and refresh renderer bounds after bearing changes.
|
|
582
|
+
*
|
|
583
|
+
* @since leaflet-rotate (v0.1.0)
|
|
584
|
+
*/
|
|
585
|
+
_onRotate() {
|
|
586
|
+
if (!this._map._rotate) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
this._onZoomEnd();
|
|
590
|
+
if (L.version.startsWith("2.")) {
|
|
591
|
+
this._onMoveEnd();
|
|
592
|
+
} else {
|
|
593
|
+
this._update();
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
/**
|
|
597
|
+
* Add the zoom-animation class needed for correct renderer transforms in Leaflet 1.
|
|
598
|
+
*
|
|
599
|
+
* Fixes `map.flyTo()` when `map.options.zoomAnimation` is false.
|
|
430
600
|
*
|
|
431
601
|
* @see https://github.com/Leaflet/Leaflet/pull/8794
|
|
602
|
+
*
|
|
603
|
+
* @since leaflet-rotate (v0.2.0)
|
|
432
604
|
*/
|
|
433
605
|
onAdd() {
|
|
434
606
|
rendererProto.onAdd.apply(this, arguments);
|
|
@@ -437,6 +609,8 @@ var leafletRotate = (() => {
|
|
|
437
609
|
}
|
|
438
610
|
},
|
|
439
611
|
/**
|
|
612
|
+
* Update the renderer transform during zoom animation, accounting for rotated coordinates.
|
|
613
|
+
*
|
|
440
614
|
* @FIXME layer drifts on `map.setZoom()` (eg. zoom during animation)
|
|
441
615
|
*
|
|
442
616
|
* the main cause seems to be related to `this._updateTransform(path._center, path._zoom))`
|
|
@@ -451,9 +625,14 @@ var leafletRotate = (() => {
|
|
|
451
625
|
* @see https://github.com/Leaflet/Leaflet/pull/8103
|
|
452
626
|
* @see https://github.com/Leaflet/Leaflet/issues/7466
|
|
453
627
|
*
|
|
454
|
-
* @TODO
|
|
628
|
+
* @TODO recheck these changes against leaflet@v1.9.3
|
|
455
629
|
*
|
|
456
630
|
* @see https://github.com/Leaflet/Leaflet/compare/v1.7.0...v1.9.3
|
|
631
|
+
*
|
|
632
|
+
* @param {L.LatLng} center Target map center.
|
|
633
|
+
* @param {number} zoom Target zoom level.
|
|
634
|
+
*
|
|
635
|
+
* @since leaflet-rotate (v0.1.0)
|
|
457
636
|
*/
|
|
458
637
|
_updateTransform(center, zoom) {
|
|
459
638
|
if (L.version.startsWith("2.")) {
|
|
@@ -473,9 +652,16 @@ var leafletRotate = (() => {
|
|
|
473
652
|
const scale = this._map.getZoomScale(zoom, this._zoom), offset = this._map._latLngToNewLayerPoint(this._topLeft, zoom, center);
|
|
474
653
|
L.DomUtil.setTransform(this._container, offset, scale);
|
|
475
654
|
},
|
|
655
|
+
/**
|
|
656
|
+
* Recalculate bounds and finish renderer updates after movement on Leaflet 2.
|
|
657
|
+
*
|
|
658
|
+
* @param {Object} ev Map movement event.
|
|
659
|
+
*
|
|
660
|
+
* @since leaflet-rotate (v0.4.0)
|
|
661
|
+
*/
|
|
476
662
|
_onMoveEnd(ev) {
|
|
477
663
|
if (!L.version.startsWith("2.") || !this._map._rotate) {
|
|
478
|
-
return
|
|
664
|
+
return rendererOnMoveEnd.apply(this, arguments);
|
|
479
665
|
}
|
|
480
666
|
this._bounds = this._map._getPaddedPixelBounds(this.options.padding);
|
|
481
667
|
this._topLeft = this._map.layerPointToLatLng(this._bounds.min);
|
|
@@ -486,6 +672,11 @@ var leafletRotate = (() => {
|
|
|
486
672
|
this._resizeContainer();
|
|
487
673
|
this._onSettled(ev);
|
|
488
674
|
},
|
|
675
|
+
/**
|
|
676
|
+
* Resize the renderer container to cover the pitch-aware map bounds.
|
|
677
|
+
*
|
|
678
|
+
* @since leaflet-rotate (v0.4.0)
|
|
679
|
+
*/
|
|
489
680
|
_resizeContainer() {
|
|
490
681
|
if (!L.version.startsWith("2.") || !this._map._rotate || !this._bounds) {
|
|
491
682
|
return rendererBase._resizeContainer.apply(this, arguments);
|
|
@@ -530,6 +721,11 @@ var leafletRotate = (() => {
|
|
|
530
721
|
// this._layers[id]._update();
|
|
531
722
|
// }
|
|
532
723
|
// },
|
|
724
|
+
/**
|
|
725
|
+
* Recalculate renderer bounds and cached view state.
|
|
726
|
+
*
|
|
727
|
+
* @since leaflet-rotate (v0.1.0)
|
|
728
|
+
*/
|
|
533
729
|
_update() {
|
|
534
730
|
if (!this._map._rotate) {
|
|
535
731
|
return rendererProto._update.apply(this, arguments);
|
|
@@ -543,8 +739,35 @@ var leafletRotate = (() => {
|
|
|
543
739
|
|
|
544
740
|
// src/map/Map.js
|
|
545
741
|
var mapProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Map.prototype));
|
|
546
|
-
L.Map.mergeOptions({
|
|
742
|
+
L.Map.mergeOptions({
|
|
743
|
+
/**
|
|
744
|
+
* @type {boolean} Enable bearing and pitch transformations.
|
|
745
|
+
*
|
|
746
|
+
* @since leaflet-rotate (v0.1.0)
|
|
747
|
+
*/
|
|
748
|
+
rotate: false,
|
|
749
|
+
/**
|
|
750
|
+
* @type {number} Initial map bearing in degrees.
|
|
751
|
+
*
|
|
752
|
+
* @since leaflet-rotate (v0.1.0)
|
|
753
|
+
*/
|
|
754
|
+
bearing: 0,
|
|
755
|
+
/**
|
|
756
|
+
* @type {number} Initial map pitch in degrees, clamped to 0–85.
|
|
757
|
+
*
|
|
758
|
+
* @since leaflet-rotate (0.5.0)
|
|
759
|
+
*/
|
|
760
|
+
pitch: 0
|
|
761
|
+
});
|
|
547
762
|
L.Map.include({
|
|
763
|
+
/**
|
|
764
|
+
* Apply an immediate animated-zoom frame without waiting for a transition delay.
|
|
765
|
+
*
|
|
766
|
+
* @param {L.LatLng} center Target center.
|
|
767
|
+
* @param {number} zoom Target zoom level.
|
|
768
|
+
*
|
|
769
|
+
* @since leaflet-rotate (v0.1.0)
|
|
770
|
+
*/
|
|
548
771
|
_animateZoomNoDelay(center, zoom) {
|
|
549
772
|
if (!this._mapPane) {
|
|
550
773
|
return;
|
|
@@ -556,17 +779,24 @@ var leafletRotate = (() => {
|
|
|
556
779
|
this._onZoomTransitionEnd();
|
|
557
780
|
},
|
|
558
781
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
782
|
+
* Initialize the Leaflet map and apply its initial bearing and pitch.
|
|
783
|
+
*
|
|
784
|
+
* @param {HTMLElement|string} id Map container element or its ID.
|
|
785
|
+
* @param {Object} [options={}] Leaflet map options, including `rotate`, `bearing`, and `pitch`.
|
|
786
|
+
*
|
|
787
|
+
* @since leaflet-rotate (v0.1.0)
|
|
561
788
|
*/
|
|
562
789
|
initialize(id, options) {
|
|
563
790
|
if (options.rotate) {
|
|
564
791
|
this._rotate = true;
|
|
565
792
|
this._bearing = 0;
|
|
793
|
+
this._pitch = 0;
|
|
566
794
|
}
|
|
567
795
|
mapProto.initialize.apply(this, arguments);
|
|
568
796
|
if (this.options.rotate) {
|
|
797
|
+
this.on("resize", this._updatePitchTransform, this);
|
|
569
798
|
this.setBearing(this.options.bearing);
|
|
799
|
+
this.setPitch(this.options.pitch);
|
|
570
800
|
}
|
|
571
801
|
},
|
|
572
802
|
/**
|
|
@@ -574,26 +804,41 @@ var leafletRotate = (() => {
|
|
|
574
804
|
* returns the corresponding pixel coordinate relative to
|
|
575
805
|
* the [origin pixel](#map-getpixelorigin).
|
|
576
806
|
*
|
|
577
|
-
* @param {L.Point} point
|
|
578
|
-
*
|
|
807
|
+
* @param {L.Point|number[]} point Pixel coordinate in the map container.
|
|
808
|
+
*
|
|
809
|
+
* @returns {L.Point} Corresponding layer-point coordinate.
|
|
810
|
+
*
|
|
811
|
+
* @since leaflet-rotate (v0.1.0)
|
|
579
812
|
*/
|
|
580
813
|
containerPointToLayerPoint(point) {
|
|
581
814
|
if (!this._rotate) {
|
|
582
815
|
return mapProto.containerPointToLayerPoint.apply(this, arguments);
|
|
583
816
|
}
|
|
817
|
+
if (this._pitch) {
|
|
818
|
+
return this._transformPitchPoint(
|
|
819
|
+
toPoint(point).subtract(this._getMapPanePos()),
|
|
820
|
+
this._pitchInverseTransform
|
|
821
|
+
);
|
|
822
|
+
}
|
|
584
823
|
return toPoint(point).subtract(this._getMapPanePos()).rotateFrom(-this._bearing, this._getRotatePanePos()).subtract(this._getRotatePanePos());
|
|
585
824
|
},
|
|
586
825
|
/**
|
|
587
826
|
* Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
|
|
588
827
|
* returns the corresponding pixel coordinate relative to the map container.
|
|
589
828
|
*
|
|
590
|
-
* @param {L.Point} point
|
|
591
|
-
*
|
|
829
|
+
* @param {L.Point|number[]} point Pixel coordinate in layer-point space.
|
|
830
|
+
*
|
|
831
|
+
* @returns {L.Point} Corresponding pixel coordinate in the map container.
|
|
832
|
+
*
|
|
833
|
+
* @since leaflet-rotate (v0.1.0)
|
|
592
834
|
*/
|
|
593
835
|
layerPointToContainerPoint(point) {
|
|
594
836
|
if (!this._rotate) {
|
|
595
837
|
return mapProto.layerPointToContainerPoint.apply(this, arguments);
|
|
596
838
|
}
|
|
839
|
+
if (this._pitch) {
|
|
840
|
+
return this._transformPitchPoint(point, this._pitchTransform).add(this._getMapPanePos());
|
|
841
|
+
}
|
|
597
842
|
return toPoint(point).add(this._getRotatePanePos()).rotateFrom(this._bearing, this._getRotatePanePos()).add(this._getMapPanePos());
|
|
598
843
|
},
|
|
599
844
|
/**
|
|
@@ -603,12 +848,16 @@ var leafletRotate = (() => {
|
|
|
603
848
|
* (rotatePane) --> (mapPane)
|
|
604
849
|
* (rotatePane) --> (norotatePane)
|
|
605
850
|
*
|
|
606
|
-
* @param {L.Point} point
|
|
607
|
-
*
|
|
851
|
+
* @param {L.Point|number[]} point Point in the rotated pane.
|
|
852
|
+
*
|
|
853
|
+
* @returns {L.Point} Corresponding point in map-pane coordinates.
|
|
608
854
|
*
|
|
609
|
-
* @since leaflet-rotate (v0.1)
|
|
855
|
+
* @since leaflet-rotate (v0.1.0)
|
|
610
856
|
*/
|
|
611
857
|
rotatedPointToMapPanePoint(point) {
|
|
858
|
+
if (this._pitch) {
|
|
859
|
+
return this._transformPitchPoint(point, this._pitchTransform);
|
|
860
|
+
}
|
|
612
861
|
return toPoint(point).rotate(this._bearing)._add(this._getRotatePanePos());
|
|
613
862
|
},
|
|
614
863
|
/**
|
|
@@ -618,11 +867,16 @@ var leafletRotate = (() => {
|
|
|
618
867
|
* (mapPane) --> (rotatePane)
|
|
619
868
|
* (norotatePane) --> (rotatePane)
|
|
620
869
|
*
|
|
621
|
-
* @param {L.Point} point
|
|
870
|
+
* @param {L.Point|number[]} point Point in map-pane coordinates.
|
|
622
871
|
*
|
|
623
|
-
* @
|
|
872
|
+
* @returns {L.Point} Corresponding point in the rotated pane.
|
|
873
|
+
*
|
|
874
|
+
* @since leaflet-rotate (v0.1.0)
|
|
624
875
|
*/
|
|
625
876
|
mapPanePointToRotatedPoint(point) {
|
|
877
|
+
if (this._pitch) {
|
|
878
|
+
return this._transformPitchPoint(point, this._pitchInverseTransform);
|
|
879
|
+
}
|
|
626
880
|
return toPoint(point)._subtract(this._getRotatePanePos()).rotate(-this._bearing);
|
|
627
881
|
},
|
|
628
882
|
// latLngToLayerPoint(latlng) {
|
|
@@ -638,10 +892,11 @@ var leafletRotate = (() => {
|
|
|
638
892
|
*
|
|
639
893
|
* @see https://github.com/ronikar/Leaflet/blob/5c480ef959b947c3beed7065425a5a36c486262b/src/map/Map.js#L1114-L1135
|
|
640
894
|
*
|
|
641
|
-
* @param {L.LatLngBounds} bounds
|
|
642
|
-
* @returns {L.Bounds}
|
|
895
|
+
* @param {L.LatLngBounds} bounds Geographic bounds to project.
|
|
643
896
|
*
|
|
644
|
-
* @
|
|
897
|
+
* @returns {L.Bounds} Axis-aligned bounds in container-pixel coordinates.
|
|
898
|
+
*
|
|
899
|
+
* @since leaflet-rotate (v0.2.0)
|
|
645
900
|
*/
|
|
646
901
|
mapBoundsToContainerBounds(bounds) {
|
|
647
902
|
if (!this._rotate && mapProto.mapBoundsToContainerBounds) {
|
|
@@ -657,14 +912,13 @@ var leafletRotate = (() => {
|
|
|
657
912
|
]);
|
|
658
913
|
},
|
|
659
914
|
/**
|
|
660
|
-
*
|
|
915
|
+
* Return the geographic bounds of the current viewport, accounting for rotation and pitch.
|
|
661
916
|
*
|
|
662
|
-
*
|
|
663
|
-
* function should match the `rotatePane` or `norotatePane` bounds
|
|
917
|
+
* The result is the smallest geographic bounds containing all four viewport corners.
|
|
664
918
|
*
|
|
665
|
-
* @
|
|
919
|
+
* @returns {L.LatLngBounds} Geographic bounds visible in the map viewport.
|
|
666
920
|
*
|
|
667
|
-
* @
|
|
921
|
+
* @since leaflet-rotate (v0.1.0)
|
|
668
922
|
*/
|
|
669
923
|
getBounds() {
|
|
670
924
|
if (!this._rotate) {
|
|
@@ -699,15 +953,19 @@ var leafletRotate = (() => {
|
|
|
699
953
|
// return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
|
|
700
954
|
// },
|
|
701
955
|
/**
|
|
702
|
-
*
|
|
956
|
+
* Set the map bearing, optionally animating to the new value.
|
|
957
|
+
*
|
|
958
|
+
* @param {number} theta Target bearing in degrees; normalized to [0, 360).
|
|
959
|
+
* @param {Object} [options] Animation options.
|
|
960
|
+
* @param {boolean} [options.animate=false] Animate the change.
|
|
961
|
+
* @param {number} [options.duration=0.25] Animation duration in seconds.
|
|
962
|
+
* @param {number} [options.easeLinearity=0.5] Easing curve linearity.
|
|
963
|
+
*
|
|
964
|
+
* @returns {void}
|
|
703
965
|
*
|
|
704
|
-
* @
|
|
705
|
-
* @param {Object} [options] animation options
|
|
706
|
-
* @param {boolean} [options.animate=false]
|
|
707
|
-
* @param {number} [options.duration=0.25] animation duration in seconds
|
|
708
|
-
* @param {number} [options.easeLinearity=0.5]
|
|
966
|
+
* @fires L.Map#rotate
|
|
709
967
|
*
|
|
710
|
-
* @since leaflet-rotate (v0.1)
|
|
968
|
+
* @since leaflet-rotate (v0.1.0)
|
|
711
969
|
*/
|
|
712
970
|
setBearing(theta, options) {
|
|
713
971
|
if (!this._rotate) {
|
|
@@ -746,20 +1004,145 @@ var leafletRotate = (() => {
|
|
|
746
1004
|
}
|
|
747
1005
|
this._setBearing(theta);
|
|
748
1006
|
},
|
|
749
|
-
|
|
1007
|
+
/**
|
|
1008
|
+
* Apply a bearing immediately and update the rotated pane and map coordinates.
|
|
1009
|
+
*
|
|
1010
|
+
* @param {number} theta Bearing in degrees.
|
|
1011
|
+
* @param {L.LatLng} [mapCenter] Center to preserve while applying the change.
|
|
1012
|
+
*
|
|
1013
|
+
* @fires L.Map#rotate
|
|
1014
|
+
*
|
|
1015
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1016
|
+
*/
|
|
1017
|
+
_setBearing(theta, mapCenter) {
|
|
1018
|
+
if (!mapCenter && this._loaded) {
|
|
1019
|
+
mapCenter = this.getCenter();
|
|
1020
|
+
}
|
|
750
1021
|
const bearing = L.Util.wrapNum(theta, [0, 360]) * (Math.PI / 180), center = this._getPixelCenter(), oldPos = this._getRotatePanePos().rotateFrom(-this._bearing, center), newPos = oldPos.rotateFrom(bearing, center);
|
|
751
|
-
L.DomUtil.setPosition(this._rotatePane, oldPos, bearing, center);
|
|
752
1022
|
this._pivot = center;
|
|
753
1023
|
this._bearing = bearing;
|
|
754
1024
|
this._rotatePanePos = newPos;
|
|
1025
|
+
if (this._pitch) {
|
|
1026
|
+
this._rotatePane._leaflet_pos = oldPos;
|
|
1027
|
+
this._updatePitchTransform();
|
|
1028
|
+
} else {
|
|
1029
|
+
L.DomUtil.setPosition(this._rotatePane, oldPos, bearing, center);
|
|
1030
|
+
}
|
|
1031
|
+
if (mapCenter) {
|
|
1032
|
+
this._pixelOrigin = this.project(mapCenter, this._zoom).subtract(this.containerPointToLayerPoint(this.getSize().divideBy(2)));
|
|
1033
|
+
delete this._lastCenter;
|
|
1034
|
+
this.fire("move");
|
|
1035
|
+
}
|
|
755
1036
|
this.fire("rotate");
|
|
756
1037
|
},
|
|
757
1038
|
/**
|
|
758
|
-
*
|
|
1039
|
+
* Transform a point with a projective matrix, accepting DOMMatrix or its 2D inverse.
|
|
1040
|
+
*
|
|
1041
|
+
* @param {L.Point|number[]} point Input pixel coordinate.
|
|
1042
|
+
* @param {DOMMatrix|Object} matrix Forward or inverse transformation matrix.
|
|
1043
|
+
*
|
|
1044
|
+
* @returns {L.Point} Transformed pixel coordinate.
|
|
1045
|
+
*
|
|
1046
|
+
* @since leaflet-rotate (0.5.0)
|
|
1047
|
+
*/
|
|
1048
|
+
_transformPitchPoint(point, matrix) {
|
|
1049
|
+
point = toPoint(point);
|
|
1050
|
+
if (!(matrix instanceof DOMMatrix)) {
|
|
1051
|
+
const denominator = matrix.g * point.x + matrix.h * point.y + matrix.i;
|
|
1052
|
+
return new L.Point(
|
|
1053
|
+
(matrix.a * point.x + matrix.b * point.y + matrix.c) / denominator,
|
|
1054
|
+
(matrix.d * point.x + matrix.e * point.y + matrix.f) / denominator
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
const transformed = new DOMPoint(point.x, point.y).matrixTransform(matrix);
|
|
1058
|
+
return new L.Point(transformed.x / transformed.w, transformed.y / transformed.w);
|
|
1059
|
+
},
|
|
1060
|
+
/**
|
|
1061
|
+
* Apply the current pitch/bearing CSS transform and cache its forward and inverse matrices.
|
|
1062
|
+
*
|
|
1063
|
+
* @since leaflet-rotate (0.5.0)
|
|
1064
|
+
*/
|
|
1065
|
+
_updatePitchTransform() {
|
|
1066
|
+
if (!this._pitch || !this._rotatePane) {
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
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)`;
|
|
1070
|
+
this._rotatePane.style.transformOrigin = "0 0";
|
|
1071
|
+
this._rotatePane.style.transform = transform;
|
|
1072
|
+
const matrix = this._pitchTransform = new DOMMatrix(getComputedStyle(this._rotatePane).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);
|
|
1073
|
+
this._pitchInverseTransform = {
|
|
1074
|
+
a: (e * i - f * h) / determinant,
|
|
1075
|
+
b: (c * h - b * i) / determinant,
|
|
1076
|
+
c: (b * f - c * e) / determinant,
|
|
1077
|
+
d: (f * g - d * i) / determinant,
|
|
1078
|
+
e: (a * i - c * g) / determinant,
|
|
1079
|
+
f: (c * d - a * f) / determinant,
|
|
1080
|
+
g: (d * h - e * g) / determinant,
|
|
1081
|
+
h: (b * g - a * h) / determinant,
|
|
1082
|
+
i: (a * e - b * d) / determinant
|
|
1083
|
+
};
|
|
1084
|
+
},
|
|
1085
|
+
/**
|
|
1086
|
+
* Set the map pitch in degrees. Values are clamped to the range 0–85.
|
|
1087
|
+
*
|
|
1088
|
+
* @param {number} pitch Pitch angle in degrees; clamped to [0, 85].
|
|
1089
|
+
*
|
|
1090
|
+
* @returns {L.Map} This map instance.
|
|
1091
|
+
*
|
|
1092
|
+
* @fires L.Map#pitch
|
|
1093
|
+
*
|
|
1094
|
+
* @since leaflet-rotate (0.5.0)
|
|
1095
|
+
*/
|
|
1096
|
+
setPitch(pitch) {
|
|
1097
|
+
this._setPitchAndBearing(pitch, this.getBearing());
|
|
1098
|
+
return this;
|
|
1099
|
+
},
|
|
1100
|
+
/**
|
|
1101
|
+
* Update pitch and bearing together, preserving the current map center.
|
|
1102
|
+
* Used by the Ctrl+drag handler to avoid applying two separate transforms.
|
|
1103
|
+
*
|
|
1104
|
+
* @param {number} pitch Pitch angle in degrees.
|
|
1105
|
+
* @param {number} bearing Bearing angle in degrees.
|
|
1106
|
+
*
|
|
1107
|
+
* @fires L.Map#pitch
|
|
1108
|
+
* @fires L.Map#rotate
|
|
1109
|
+
*
|
|
1110
|
+
* @since leaflet-rotate (0.5.0)
|
|
1111
|
+
*/
|
|
1112
|
+
_setPitchAndBearing(pitch, bearing) {
|
|
1113
|
+
if (!this._rotate) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const clampedPitch = Math.max(0, Math.min(pitch, 85)) * (Math.PI / 180), clampedBearing = L.Util.wrapNum(bearing, [0, 360]) * (Math.PI / 180), pitchChanged = clampedPitch !== this._pitch;
|
|
1117
|
+
if (!pitchChanged && clampedBearing === this._bearing) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const mapCenter = this._loaded ? this.getCenter() : void 0;
|
|
1121
|
+
if (pitchChanged) {
|
|
1122
|
+
this._pitch = clampedPitch;
|
|
1123
|
+
this._rotatePane.style.transformOrigin = L.version.startsWith("2.") ? "0 0" : "";
|
|
1124
|
+
}
|
|
1125
|
+
this._setBearing(bearing, mapCenter);
|
|
1126
|
+
if (pitchChanged) {
|
|
1127
|
+
this.fire("pitch");
|
|
1128
|
+
}
|
|
1129
|
+
},
|
|
1130
|
+
/**
|
|
1131
|
+
* Get the current map pitch in degrees.
|
|
1132
|
+
*
|
|
1133
|
+
* @returns {number} Pitch angle in degrees.
|
|
1134
|
+
*
|
|
1135
|
+
* @since leaflet-rotate (0.5.0)
|
|
1136
|
+
*/
|
|
1137
|
+
getPitch() {
|
|
1138
|
+
return (this._pitch || 0) * (180 / Math.PI);
|
|
1139
|
+
},
|
|
1140
|
+
/**
|
|
1141
|
+
* Get the current map bearing. Enable the `rotate` map option before calling.
|
|
759
1142
|
*
|
|
760
|
-
* @returns {number}
|
|
1143
|
+
* @returns {number} Bearing in degrees, normalized to [0, 360).
|
|
761
1144
|
*
|
|
762
|
-
* @since leaflet-rotate (v0.1)
|
|
1145
|
+
* @since leaflet-rotate (v0.1.0)
|
|
763
1146
|
*/
|
|
764
1147
|
getBearing() {
|
|
765
1148
|
return this._bearing * (180 / Math.PI);
|
|
@@ -771,7 +1154,10 @@ var leafletRotate = (() => {
|
|
|
771
1154
|
*
|
|
772
1155
|
* @param {String} name leaflet pane
|
|
773
1156
|
* @param {HTMLElement} [container] parent element
|
|
1157
|
+
*
|
|
774
1158
|
* @returns {HTMLElement} pane container
|
|
1159
|
+
*
|
|
1160
|
+
* @since leaflet-rotate (v0.1.0)
|
|
775
1161
|
*/
|
|
776
1162
|
// createPane(name, container) {
|
|
777
1163
|
// if (!this._rotate || name == 'mapPane') {
|
|
@@ -801,6 +1187,12 @@ var leafletRotate = (() => {
|
|
|
801
1187
|
* - tooltipPane [HTMLElement = 650] - Pane for tooltips.
|
|
802
1188
|
* - popupPane [HTMLElement = 700] - Pane for popups.
|
|
803
1189
|
*/
|
|
1190
|
+
/**
|
|
1191
|
+
* Create the map's panes, placing tile/vector layers in the transformed pane
|
|
1192
|
+
* and markers/overlays in the pane that remains upright.
|
|
1193
|
+
*
|
|
1194
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1195
|
+
*/
|
|
804
1196
|
_initPanes() {
|
|
805
1197
|
const panes = this._panes = {};
|
|
806
1198
|
this._paneRenderers = {};
|
|
@@ -839,13 +1231,15 @@ var leafletRotate = (() => {
|
|
|
839
1231
|
*
|
|
840
1232
|
* @see https://github.com/Raruto/leaflet-rotate/issues/18
|
|
841
1233
|
*
|
|
842
|
-
* @param {L.LatLng} latlng
|
|
843
|
-
* @param {Object} [options={}] padding
|
|
1234
|
+
* @param {L.LatLng} latlng Geographic point to keep visible.
|
|
1235
|
+
* @param {Object} [options={}] Padding options (`padding`, `paddingTopLeft`, `paddingBottomRight`).
|
|
844
1236
|
*
|
|
845
1237
|
* @returns {L.Map} current map instance
|
|
1238
|
+
*
|
|
1239
|
+
* @since leaflet-rotate (v0.1.4)
|
|
846
1240
|
*/
|
|
847
1241
|
panInside(latlng, options) {
|
|
848
|
-
if (!this._rotate || Math.abs(this._bearing).toFixed(1) < 0.1) {
|
|
1242
|
+
if (!this._rotate || !this._pitch && Math.abs(this._bearing).toFixed(1) < 0.1) {
|
|
849
1243
|
return mapProto.panInside.apply(this, arguments);
|
|
850
1244
|
}
|
|
851
1245
|
options = options || {};
|
|
@@ -871,9 +1265,12 @@ var leafletRotate = (() => {
|
|
|
871
1265
|
*
|
|
872
1266
|
* @see https://github.com/fnicollet/Leaflet/issues/7
|
|
873
1267
|
*
|
|
874
|
-
* @param {L.LatLngBounds} bounds
|
|
875
|
-
* @param {Object} [options]
|
|
876
|
-
*
|
|
1268
|
+
* @param {L.LatLngBounds} bounds Geographic bounds to fit inside the viewport.
|
|
1269
|
+
* @param {Object} [options] Pan animation options.
|
|
1270
|
+
*
|
|
1271
|
+
* @returns {L.Map} This map instance.
|
|
1272
|
+
*
|
|
1273
|
+
* @since leaflet-rotate (v0.1.0)
|
|
877
1274
|
*/
|
|
878
1275
|
// panInsideBounds(bounds, options) {
|
|
879
1276
|
// this._enforcingBounds = true;
|
|
@@ -938,14 +1335,16 @@ var leafletRotate = (() => {
|
|
|
938
1335
|
* instead returns the minimum zoom level on which the map view fits into
|
|
939
1336
|
* the given bounds in its entirety.
|
|
940
1337
|
*
|
|
941
|
-
* @param {L.LatLngBounds} bounds
|
|
942
|
-
* @param {
|
|
943
|
-
* @param {L.Point} [padding=[0,0]]
|
|
1338
|
+
* @param {L.LatLngBounds} bounds Geographic bounds to fit.
|
|
1339
|
+
* @param {boolean} [inside=false] Fit the viewport inside bounds instead of bounds inside the viewport.
|
|
1340
|
+
* @param {L.Point|number[]} [padding=[0,0]] Pixel padding around the viewport.
|
|
1341
|
+
*
|
|
1342
|
+
* @returns {number} Zoom level satisfying the fit.
|
|
944
1343
|
*
|
|
945
|
-
* @
|
|
1344
|
+
* @since leaflet-rotate (v0.1.0)
|
|
946
1345
|
*/
|
|
947
1346
|
getBoundsZoom(bounds, inside, padding) {
|
|
948
|
-
if (!this._rotate || Math.abs(this._bearing).toFixed(1) < 0.1) {
|
|
1347
|
+
if (!this._rotate || !this._pitch && Math.abs(this._bearing).toFixed(1) < 0.1) {
|
|
949
1348
|
return mapProto.getBoundsZoom.apply(this, arguments);
|
|
950
1349
|
}
|
|
951
1350
|
bounds = bounds instanceof L.LatLngBounds ? bounds : new L.LatLngBounds(bounds);
|
|
@@ -968,19 +1367,30 @@ var leafletRotate = (() => {
|
|
|
968
1367
|
// return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
|
|
969
1368
|
// },
|
|
970
1369
|
/**
|
|
971
|
-
*
|
|
1370
|
+
* Return the pixel offset from the current center to the given geographic point.
|
|
972
1371
|
*
|
|
973
|
-
* @param {L.LatLng} latlng
|
|
1372
|
+
* @param {L.LatLng} latlng Geographic point.
|
|
1373
|
+
*
|
|
1374
|
+
* @returns {L.Point} Offset in layer pixels, adjusted for bearing and pitch.
|
|
1375
|
+
*
|
|
1376
|
+
* @since leaflet-rotate (v0.1.0)
|
|
974
1377
|
*/
|
|
975
1378
|
_getCenterOffset(latlng) {
|
|
976
1379
|
let centerOffset = mapProto._getCenterOffset.apply(this, arguments);
|
|
977
|
-
if (this.
|
|
1380
|
+
if (this._pitch) {
|
|
1381
|
+
const center = this.getSize()._divideBy(2);
|
|
1382
|
+
centerOffset = this.containerPointToLayerPoint(center.add(centerOffset)).subtract(this.containerPointToLayerPoint(center));
|
|
1383
|
+
} else if (this._rotate) {
|
|
978
1384
|
centerOffset = centerOffset.rotate(this._bearing);
|
|
979
1385
|
}
|
|
980
1386
|
return centerOffset;
|
|
981
1387
|
},
|
|
982
1388
|
/**
|
|
983
|
-
*
|
|
1389
|
+
* Return the current position of the rotated pane.
|
|
1390
|
+
*
|
|
1391
|
+
* @returns {L.Point} Rotated-pane position in map-pane coordinates.
|
|
1392
|
+
*
|
|
1393
|
+
* @since leaflet-rotate (v0.1.0)
|
|
984
1394
|
*/
|
|
985
1395
|
_getRotatePanePos() {
|
|
986
1396
|
return this._rotatePanePos || new L.Point(0, 0);
|
|
@@ -989,6 +1399,16 @@ var leafletRotate = (() => {
|
|
|
989
1399
|
// const topLeft = this._getNewPixelOrigin(center, zoom);
|
|
990
1400
|
// return this.project(latlng, zoom)._subtract(topLeft);
|
|
991
1401
|
//},
|
|
1402
|
+
/**
|
|
1403
|
+
* Calculate the pixel origin for a target view while preserving bearing-aware alignment.
|
|
1404
|
+
*
|
|
1405
|
+
* @param {L.LatLng} center Target center.
|
|
1406
|
+
* @param {number} zoom Target zoom level.
|
|
1407
|
+
*
|
|
1408
|
+
* @returns {L.Point} New pixel origin.
|
|
1409
|
+
*
|
|
1410
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1411
|
+
*/
|
|
992
1412
|
_getNewPixelOrigin(center, zoom) {
|
|
993
1413
|
if (!this._rotate || L.version.startsWith("2.")) {
|
|
994
1414
|
return mapProto._getNewPixelOrigin.apply(this, arguments);
|
|
@@ -997,7 +1417,14 @@ var leafletRotate = (() => {
|
|
|
997
1417
|
return this.project(center, zoom).rotate(this._bearing)._subtract(viewHalf)._add(this._getMapPanePos())._add(this._getRotatePanePos()).rotate(-this._bearing)._round();
|
|
998
1418
|
},
|
|
999
1419
|
/**
|
|
1000
|
-
*
|
|
1420
|
+
* Calculate the pixel bounds needed to cover the transformed viewport.
|
|
1421
|
+
*
|
|
1422
|
+
* @param {L.LatLng} [center=this.getCenter()] Target center.
|
|
1423
|
+
* @param {number} [zoom=this.getZoom()] Target zoom level.
|
|
1424
|
+
*
|
|
1425
|
+
* @returns {L.Bounds} Pixel bounds in projected map coordinates.
|
|
1426
|
+
*
|
|
1427
|
+
* @since leaflet-rotate (v0.2.0)
|
|
1001
1428
|
*
|
|
1002
1429
|
* @see src\layer\tile\GridLayer::_getTiledPixelBounds()
|
|
1003
1430
|
*/
|
|
@@ -1016,9 +1443,9 @@ var leafletRotate = (() => {
|
|
|
1016
1443
|
return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
|
|
1017
1444
|
},
|
|
1018
1445
|
/**
|
|
1019
|
-
* @since leaflet-rotate (v0.2)
|
|
1446
|
+
* @since leaflet-rotate (v0.2.0)
|
|
1020
1447
|
*
|
|
1021
|
-
* @
|
|
1448
|
+
* @returns {L.Point} Map pivot point in pixel coordinates.
|
|
1022
1449
|
*/
|
|
1023
1450
|
_getPixelCenter() {
|
|
1024
1451
|
if (!this._rotate && mapProto._getPixelCenter) {
|
|
@@ -1027,9 +1454,13 @@ var leafletRotate = (() => {
|
|
|
1027
1454
|
return this.getSize()._divideBy(2)._subtract(this._getMapPanePos());
|
|
1028
1455
|
},
|
|
1029
1456
|
/**
|
|
1030
|
-
* @since leaflet-rotate (v0.2)
|
|
1031
|
-
*
|
|
1032
1457
|
* @see src\layer\vector\Renderer::_update()
|
|
1458
|
+
*
|
|
1459
|
+
* @param {number} padding Bounds padding as a fraction of map size.
|
|
1460
|
+
*
|
|
1461
|
+
* @returns {L.Bounds} Padded bounds in layer-point coordinates.
|
|
1462
|
+
*
|
|
1463
|
+
* @since leaflet-rotate (v0.2.0)
|
|
1033
1464
|
*/
|
|
1034
1465
|
_getPaddedPixelBounds(padding) {
|
|
1035
1466
|
if (!this._rotate && mapProto._getPaddedPixelBounds) {
|
|
@@ -1043,6 +1474,15 @@ var leafletRotate = (() => {
|
|
|
1043
1474
|
this.containerPointToLayerPoint([padMax.x, padMax.y]).floor()
|
|
1044
1475
|
]);
|
|
1045
1476
|
},
|
|
1477
|
+
/**
|
|
1478
|
+
* Build and fire Leaflet's location-found event, including the device heading.
|
|
1479
|
+
*
|
|
1480
|
+
* @param {GeolocationPosition} pos Browser geolocation result.
|
|
1481
|
+
*
|
|
1482
|
+
* @fires L.Map#locationfound
|
|
1483
|
+
*
|
|
1484
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1485
|
+
*/
|
|
1046
1486
|
_handleGeolocationResponse(pos) {
|
|
1047
1487
|
if (!this._container._leaflet_id) {
|
|
1048
1488
|
return;
|
|
@@ -1070,6 +1510,7 @@ var leafletRotate = (() => {
|
|
|
1070
1510
|
* @see https://github.com/ronikar/Leaflet/blob/5c480ef959b947c3beed7065425a5a36c486262b/src/geo/LatLngBounds.js#L253-L264
|
|
1071
1511
|
*
|
|
1072
1512
|
* @param {L.Bounds} points
|
|
1513
|
+
*
|
|
1073
1514
|
* @returns {L.Bounds}
|
|
1074
1515
|
*/
|
|
1075
1516
|
// toCircumscribedBounds(points) {
|
|
@@ -1084,6 +1525,11 @@ var leafletRotate = (() => {
|
|
|
1084
1525
|
|
|
1085
1526
|
// src/map/handler/CompassBearing.js
|
|
1086
1527
|
L.Map.CompassBearing = L.Handler.extend({
|
|
1528
|
+
/**
|
|
1529
|
+
* @param {L.Map} map Map whose bearing follows device orientation.
|
|
1530
|
+
*
|
|
1531
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1532
|
+
*/
|
|
1087
1533
|
initialize(map) {
|
|
1088
1534
|
this._map = map;
|
|
1089
1535
|
if ("ondeviceorientationabsolute" in window) {
|
|
@@ -1093,6 +1539,11 @@ var leafletRotate = (() => {
|
|
|
1093
1539
|
}
|
|
1094
1540
|
this._throttled = L.Util.throttle(this._onDeviceOrientation, 100, this);
|
|
1095
1541
|
},
|
|
1542
|
+
/**
|
|
1543
|
+
* Subscribe to the supported device-orientation event, if available.
|
|
1544
|
+
*
|
|
1545
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1546
|
+
*/
|
|
1096
1547
|
addHooks() {
|
|
1097
1548
|
if (this._map._rotate && this.__deviceOrientationEvent) {
|
|
1098
1549
|
L.DomEvent.on(window, this.__deviceOrientationEvent, this._throttled, this);
|
|
@@ -1100,12 +1551,19 @@ var leafletRotate = (() => {
|
|
|
1100
1551
|
this.disable();
|
|
1101
1552
|
}
|
|
1102
1553
|
},
|
|
1554
|
+
/**
|
|
1555
|
+
* Unsubscribe from device orientation when the handler is disabled.
|
|
1556
|
+
*
|
|
1557
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1558
|
+
*/
|
|
1103
1559
|
removeHooks() {
|
|
1104
1560
|
if (this._map._rotate && this.__deviceOrientationEvent) {
|
|
1105
1561
|
L.DomEvent.off(window, this.__deviceOrientationEvent, this._throttled, this);
|
|
1106
1562
|
}
|
|
1107
1563
|
},
|
|
1108
1564
|
/**
|
|
1565
|
+
* Apply a device orientation reading to the map bearing.
|
|
1566
|
+
*
|
|
1109
1567
|
* `DeviceOrientationEvent.absolute` - Indicates whether the device is providing absolute
|
|
1110
1568
|
* orientation values (relatives to Magnetic North) or
|
|
1111
1569
|
* using some arbitrary frame determined by the device.
|
|
@@ -1122,6 +1580,10 @@ var leafletRotate = (() => {
|
|
|
1122
1580
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/absolute
|
|
1123
1581
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/alpha
|
|
1124
1582
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/orientation
|
|
1583
|
+
*
|
|
1584
|
+
* @param {DeviceOrientationEvent} e Orientation reading from the browser.
|
|
1585
|
+
*
|
|
1586
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1125
1587
|
*/
|
|
1126
1588
|
_onDeviceOrientation(e) {
|
|
1127
1589
|
let angle = e.webkitCompassHeading != null ? e.webkitCompassHeading : e.alpha;
|
|
@@ -1150,11 +1612,18 @@ var leafletRotate = (() => {
|
|
|
1150
1612
|
*
|
|
1151
1613
|
* @type {Boolean}
|
|
1152
1614
|
*
|
|
1615
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1616
|
+
*
|
|
1153
1617
|
* @see https://developer.mozilla.org/docs/Web/API/MutationObserver
|
|
1154
1618
|
*/
|
|
1155
1619
|
trackContainerMutation: false
|
|
1156
1620
|
});
|
|
1157
1621
|
L.Map.ContainerMutation = L.Handler.extend({
|
|
1622
|
+
/**
|
|
1623
|
+
* Start observing the map container's style attribute.
|
|
1624
|
+
*
|
|
1625
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1626
|
+
*/
|
|
1158
1627
|
addHooks() {
|
|
1159
1628
|
if (!this._observer) {
|
|
1160
1629
|
this._observer = new MutationObserver(L.Util.bind(this._map.invalidateSize, this._map));
|
|
@@ -1167,6 +1636,11 @@ var leafletRotate = (() => {
|
|
|
1167
1636
|
attributeFilter: ["style"]
|
|
1168
1637
|
});
|
|
1169
1638
|
},
|
|
1639
|
+
/**
|
|
1640
|
+
* Stop observing container mutations.
|
|
1641
|
+
*
|
|
1642
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1643
|
+
*/
|
|
1170
1644
|
removeHooks() {
|
|
1171
1645
|
this._observer.disconnect();
|
|
1172
1646
|
}
|
|
@@ -1183,26 +1657,50 @@ var leafletRotate = (() => {
|
|
|
1183
1657
|
}
|
|
1184
1658
|
L.Map.mergeOptions({
|
|
1185
1659
|
/**
|
|
1186
|
-
*
|
|
1187
|
-
* zoom beyond min/max zoom and then bounce back
|
|
1188
|
-
* when pinch-zooming.
|
|
1660
|
+
* Whether pinch zoom may temporarily exceed min/max zoom and bounce back.
|
|
1189
1661
|
*
|
|
1190
1662
|
* @type {Boolean}
|
|
1663
|
+
*
|
|
1664
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1191
1665
|
*/
|
|
1192
1666
|
bounceAtZoomLimits: true
|
|
1193
1667
|
});
|
|
1194
1668
|
L.Map.TouchGestures = L.Handler.extend({
|
|
1669
|
+
/**
|
|
1670
|
+
* Initialize the shared handler from the map's touch zoom/rotation options.
|
|
1671
|
+
*
|
|
1672
|
+
* @param {L.Map} map Map whose touch gestures are handled.
|
|
1673
|
+
*
|
|
1674
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1675
|
+
*/
|
|
1195
1676
|
initialize(map) {
|
|
1196
1677
|
this._map = map;
|
|
1197
1678
|
this.rotate = !!this._map.options.touchRotate;
|
|
1198
1679
|
this.zoom = !!this._map.options.touchZoom;
|
|
1199
1680
|
},
|
|
1681
|
+
/**
|
|
1682
|
+
* Listen for the start of two-finger gestures on the map container.
|
|
1683
|
+
*
|
|
1684
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1685
|
+
*/
|
|
1200
1686
|
addHooks() {
|
|
1201
1687
|
L.DomEvent.on(this._map._container, "touchstart", this._onTouchStart, this);
|
|
1202
1688
|
},
|
|
1689
|
+
/**
|
|
1690
|
+
* Remove the touch-start listener.
|
|
1691
|
+
*
|
|
1692
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1693
|
+
*/
|
|
1203
1694
|
removeHooks() {
|
|
1204
1695
|
L.DomEvent.off(this._map._container, "touchstart", this._onTouchStart, this);
|
|
1205
1696
|
},
|
|
1697
|
+
/**
|
|
1698
|
+
* Record initial touch points and start tracking a two-finger gesture.
|
|
1699
|
+
*
|
|
1700
|
+
* @param {TouchEvent} e Touch-start event.
|
|
1701
|
+
*
|
|
1702
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1703
|
+
*/
|
|
1206
1704
|
_onTouchStart(e) {
|
|
1207
1705
|
const map = this._map;
|
|
1208
1706
|
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) {
|
|
@@ -1236,6 +1734,13 @@ var leafletRotate = (() => {
|
|
|
1236
1734
|
L.DomEvent.on(document, "touchmove", this._onTouchMove, this).on(document, "touchend touchcancel", this._onTouchEnd, this);
|
|
1237
1735
|
L.DomEvent.preventDefault(e);
|
|
1238
1736
|
},
|
|
1737
|
+
/**
|
|
1738
|
+
* Update bearing and/or zoom while the two touch points move.
|
|
1739
|
+
*
|
|
1740
|
+
* @param {TouchEvent} e Touch-move event.
|
|
1741
|
+
*
|
|
1742
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1743
|
+
*/
|
|
1239
1744
|
_onTouchMove(e) {
|
|
1240
1745
|
if (!e.touches || e.touches.length !== 2 || !(this._zooming || this._rotating)) {
|
|
1241
1746
|
return;
|
|
@@ -1267,8 +1772,8 @@ var leafletRotate = (() => {
|
|
|
1267
1772
|
if (scale === 1 && delta.x === 0 && delta.y === 0) {
|
|
1268
1773
|
return;
|
|
1269
1774
|
}
|
|
1270
|
-
const
|
|
1271
|
-
this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(
|
|
1775
|
+
const mapDelta = map._pitch ? map.containerPointToLayerPoint(this._centerPoint.add(delta)).subtract(map.containerPointToLayerPoint(this._centerPoint)) : delta.rotate(-map.getBearing() * (Math.PI / 180));
|
|
1776
|
+
this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(mapDelta));
|
|
1272
1777
|
}
|
|
1273
1778
|
}
|
|
1274
1779
|
if (!this._moved) {
|
|
@@ -1286,6 +1791,11 @@ var leafletRotate = (() => {
|
|
|
1286
1791
|
}
|
|
1287
1792
|
L.DomEvent.preventDefault(e);
|
|
1288
1793
|
},
|
|
1794
|
+
/**
|
|
1795
|
+
* Finish the gesture, settle the zoom and remove document listeners.
|
|
1796
|
+
*
|
|
1797
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1798
|
+
*/
|
|
1289
1799
|
_onTouchEnd() {
|
|
1290
1800
|
if (!this._moved || !(this._zooming || this._rotating)) {
|
|
1291
1801
|
this._zooming = false;
|
|
@@ -1311,14 +1821,26 @@ var leafletRotate = (() => {
|
|
|
1311
1821
|
* Whether the map can be rotated with a two-finger rotation gesture
|
|
1312
1822
|
*
|
|
1313
1823
|
* @type {Boolean}
|
|
1824
|
+
*
|
|
1825
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1314
1826
|
*/
|
|
1315
1827
|
touchRotate: false
|
|
1316
1828
|
});
|
|
1317
1829
|
L.Map.TouchRotate = L.Handler.extend({
|
|
1830
|
+
/**
|
|
1831
|
+
* Enable rotation handling in the map's shared touch gesture handler.
|
|
1832
|
+
*
|
|
1833
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1834
|
+
*/
|
|
1318
1835
|
addHooks() {
|
|
1319
1836
|
this._map.touchGestures.enable();
|
|
1320
1837
|
this._map.touchGestures.rotate = true;
|
|
1321
1838
|
},
|
|
1839
|
+
/**
|
|
1840
|
+
* Disable rotation handling without affecting pinch zoom.
|
|
1841
|
+
*
|
|
1842
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1843
|
+
*/
|
|
1322
1844
|
removeHooks() {
|
|
1323
1845
|
this._map.touchGestures.rotate = false;
|
|
1324
1846
|
}
|
|
@@ -1328,33 +1850,170 @@ var leafletRotate = (() => {
|
|
|
1328
1850
|
// src/map/handler/ShiftKeyRotate.js
|
|
1329
1851
|
L.Map.mergeOptions({
|
|
1330
1852
|
/**
|
|
1331
|
-
* Whether the map
|
|
1853
|
+
* Whether Shift+wheel changes the map bearing.
|
|
1854
|
+
*
|
|
1332
1855
|
* @type {Boolean}
|
|
1856
|
+
*
|
|
1857
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1858
|
+
*/
|
|
1859
|
+
shiftKeyRotate: true,
|
|
1860
|
+
/**
|
|
1861
|
+
* Whether Ctrl+drag changes bearing and pitch.
|
|
1862
|
+
*
|
|
1863
|
+
* @type {Boolean}
|
|
1864
|
+
*
|
|
1865
|
+
* @since leaflet-rotate (0.5.0)
|
|
1333
1866
|
*/
|
|
1334
|
-
|
|
1867
|
+
pitchDrag: true
|
|
1335
1868
|
});
|
|
1336
1869
|
L.Map.ShiftKeyRotate = L.Handler.extend({
|
|
1870
|
+
/**
|
|
1871
|
+
* @param {L.Map} map Map controlled by this handler.
|
|
1872
|
+
*
|
|
1873
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1874
|
+
*/
|
|
1875
|
+
initialize(map) {
|
|
1876
|
+
this._map = map;
|
|
1877
|
+
this._onMouseDownBound = this._onMouseDown.bind(this);
|
|
1878
|
+
this._onMouseMoveBound = this._onMouseMove.bind(this);
|
|
1879
|
+
this._onMouseUpBound = this._onMouseUp.bind(this);
|
|
1880
|
+
},
|
|
1881
|
+
/**
|
|
1882
|
+
* Attach enabled wheel and pointer/mouse gesture listeners.
|
|
1883
|
+
*
|
|
1884
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1885
|
+
*/
|
|
1337
1886
|
addHooks() {
|
|
1338
|
-
|
|
1339
|
-
|
|
1887
|
+
if (this._map.options.shiftKeyRotate) {
|
|
1888
|
+
L.DomEvent.on(this._map._container, "wheel", this._handleShiftScroll, this);
|
|
1889
|
+
}
|
|
1890
|
+
if (this._map.options.pitchDrag) {
|
|
1891
|
+
this.addPitchHooks();
|
|
1892
|
+
}
|
|
1340
1893
|
},
|
|
1894
|
+
/**
|
|
1895
|
+
* Detach gesture listeners and cancel any active drag.
|
|
1896
|
+
*
|
|
1897
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1898
|
+
*/
|
|
1341
1899
|
removeHooks() {
|
|
1342
1900
|
L.DomEvent.off(this._map._container, "wheel", this._handleShiftScroll, this);
|
|
1343
|
-
this.
|
|
1901
|
+
this.removePitchHooks();
|
|
1902
|
+
},
|
|
1903
|
+
/**
|
|
1904
|
+
* Listen for the start of Ctrl+drag pitch gestures.
|
|
1905
|
+
*
|
|
1906
|
+
* @since leaflet-rotate (0.5.0)
|
|
1907
|
+
*/
|
|
1908
|
+
addPitchHooks() {
|
|
1909
|
+
if (this._pitchHooksAdded) {
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
this._pitchEvents = L.version.startsWith("2.") ? ["pointerdown", "pointermove", "pointerup pointercancel"] : ["mousedown", "mousemove", "mouseup"];
|
|
1913
|
+
this._map._container.addEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
|
|
1914
|
+
this._pitchHooksAdded = true;
|
|
1344
1915
|
},
|
|
1916
|
+
/**
|
|
1917
|
+
* Stop listening for pitch gestures and remove active document listeners.
|
|
1918
|
+
*
|
|
1919
|
+
* @since leaflet-rotate (0.5.0)
|
|
1920
|
+
*/
|
|
1921
|
+
removePitchHooks() {
|
|
1922
|
+
if (!this._pitchHooksAdded) {
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
this._map._container.removeEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
|
|
1926
|
+
this._onMouseUp();
|
|
1927
|
+
delete this._pitchEvents;
|
|
1928
|
+
this._pitchHooksAdded = false;
|
|
1929
|
+
},
|
|
1930
|
+
/**
|
|
1931
|
+
* Rotate the map in fixed increments while Shift is held during wheel input.
|
|
1932
|
+
*
|
|
1933
|
+
* @param {WheelEvent} e Wheel event from the map container.
|
|
1934
|
+
*
|
|
1935
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1936
|
+
*/
|
|
1345
1937
|
_handleShiftScroll(e) {
|
|
1346
1938
|
if (e.shiftKey) {
|
|
1347
1939
|
e.preventDefault();
|
|
1348
1940
|
this._map.scrollWheelZoom.disable();
|
|
1349
|
-
this._map.setBearing(this._map.
|
|
1941
|
+
this._map.setBearing(this._map.getBearing() + Math.sign(e.deltaY) * 5);
|
|
1350
1942
|
} else {
|
|
1351
1943
|
this._map.scrollWheelZoom.enable();
|
|
1352
1944
|
}
|
|
1945
|
+
},
|
|
1946
|
+
/**
|
|
1947
|
+
* Start Ctrl+drag and capture subsequent movement outside the map.
|
|
1948
|
+
*
|
|
1949
|
+
* @param {MouseEvent|PointerEvent} e Gesture start event.
|
|
1950
|
+
*
|
|
1951
|
+
* @since leaflet-rotate (0.5.0)
|
|
1952
|
+
*/
|
|
1953
|
+
_onMouseDown(e) {
|
|
1954
|
+
if (!this._map._rotate || !this._map.options.pitchDrag || !e.ctrlKey || e.button !== 0) {
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
const scrollWheelZoom = this._map.scrollWheelZoom;
|
|
1958
|
+
if (scrollWheelZoom && scrollWheelZoom._timer) {
|
|
1959
|
+
clearTimeout(scrollWheelZoom._timer);
|
|
1960
|
+
scrollWheelZoom._timer = null;
|
|
1961
|
+
scrollWheelZoom._delta = 0;
|
|
1962
|
+
scrollWheelZoom._startTime = null;
|
|
1963
|
+
}
|
|
1964
|
+
e.preventDefault();
|
|
1965
|
+
e.stopPropagation();
|
|
1966
|
+
e.stopImmediatePropagation();
|
|
1967
|
+
this._lastX = e.clientX;
|
|
1968
|
+
this._lastY = e.clientY;
|
|
1969
|
+
document.addEventListener(this._pitchEvents[1], this._onMouseMoveBound);
|
|
1970
|
+
for (const event of this._pitchEvents[2].split(" ")) {
|
|
1971
|
+
document.addEventListener(event, this._onMouseUpBound);
|
|
1972
|
+
}
|
|
1973
|
+
},
|
|
1974
|
+
/**
|
|
1975
|
+
* Apply pointer movement to pitch (vertical) and bearing (horizontal).
|
|
1976
|
+
*
|
|
1977
|
+
* @param {MouseEvent|PointerEvent} e Gesture movement event.
|
|
1978
|
+
*
|
|
1979
|
+
* @since leaflet-rotate (0.5.0)
|
|
1980
|
+
*/
|
|
1981
|
+
_onMouseMove(e) {
|
|
1982
|
+
this._map._setPitchAndBearing(
|
|
1983
|
+
this._map.getPitch() + (this._lastY - e.clientY) * 0.5,
|
|
1984
|
+
this._map.getBearing() + (e.clientX - this._lastX) * 0.8
|
|
1985
|
+
);
|
|
1986
|
+
this._lastX = e.clientX;
|
|
1987
|
+
this._lastY = e.clientY;
|
|
1988
|
+
e.preventDefault();
|
|
1989
|
+
},
|
|
1990
|
+
/**
|
|
1991
|
+
* End Ctrl+drag and clear document-level listeners.
|
|
1992
|
+
*
|
|
1993
|
+
* @since leaflet-rotate (0.5.0)
|
|
1994
|
+
*/
|
|
1995
|
+
_onMouseUp() {
|
|
1996
|
+
if (!this._pitchEvents) {
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
document.removeEventListener(this._pitchEvents[1], this._onMouseMoveBound);
|
|
2000
|
+
for (const event of this._pitchEvents[2].split(" ")) {
|
|
2001
|
+
document.removeEventListener(event, this._onMouseUpBound);
|
|
2002
|
+
}
|
|
2003
|
+
delete this._lastX;
|
|
2004
|
+
delete this._lastY;
|
|
1353
2005
|
}
|
|
1354
2006
|
});
|
|
1355
|
-
L.Map.addInitHook("addHandler", "shiftKeyRotate", L.Map.ShiftKeyRotate);
|
|
1356
2007
|
L.Map.addInitHook(function() {
|
|
1357
|
-
if (this.
|
|
2008
|
+
if (this.options.shiftKeyRotate) {
|
|
2009
|
+
this.shiftKeyRotate = new L.Map.ShiftKeyRotate(this);
|
|
2010
|
+
this.shiftKeyRotate.enable();
|
|
2011
|
+
} else if (this.options.pitchDrag) {
|
|
2012
|
+
this.shiftKeyRotate = new L.Map.ShiftKeyRotate(this);
|
|
2013
|
+
this.shiftKeyRotate.addPitchHooks();
|
|
2014
|
+
this.on("unload", this.shiftKeyRotate.removePitchHooks, this.shiftKeyRotate);
|
|
2015
|
+
}
|
|
2016
|
+
if (this.scrollWheelZoom.enabled() && this.options.shiftKeyRotate) {
|
|
1358
2017
|
this.scrollWheelZoom.disable();
|
|
1359
2018
|
this.scrollWheelZoom.enable();
|
|
1360
2019
|
}
|
|
@@ -1370,6 +2029,8 @@ var leafletRotate = (() => {
|
|
|
1370
2029
|
* for touch-capable web browsers.
|
|
1371
2030
|
*
|
|
1372
2031
|
* @type {(Boolean|String)}
|
|
2032
|
+
*
|
|
2033
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1373
2034
|
*/
|
|
1374
2035
|
touchZoom: L.Browser.touch,
|
|
1375
2036
|
/**
|
|
@@ -1380,15 +2041,27 @@ var leafletRotate = (() => {
|
|
|
1380
2041
|
* when pinch-zooming.
|
|
1381
2042
|
*
|
|
1382
2043
|
* @type {Boolean}
|
|
2044
|
+
*
|
|
2045
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1383
2046
|
*/
|
|
1384
2047
|
bounceAtZoomLimits: false
|
|
1385
2048
|
});
|
|
1386
2049
|
L.Map.TouchZoom = L.Handler.extend({
|
|
2050
|
+
/**
|
|
2051
|
+
* Enable pinch zoom handling and mark the shared touch handler as active.
|
|
2052
|
+
*
|
|
2053
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2054
|
+
*/
|
|
1387
2055
|
addHooks() {
|
|
1388
2056
|
this._map._container.classList.add("leaflet-touch-zoom");
|
|
1389
2057
|
this._map.touchGestures.enable();
|
|
1390
2058
|
this._map.touchGestures.zoom = true;
|
|
1391
2059
|
},
|
|
2060
|
+
/**
|
|
2061
|
+
* Disable pinch zoom handling while preserving touch rotation state.
|
|
2062
|
+
*
|
|
2063
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2064
|
+
*/
|
|
1392
2065
|
removeHooks() {
|
|
1393
2066
|
this._map._container.classList.remove("leaflet-touch-zoom");
|
|
1394
2067
|
this._map.touchGestures.zoom = false;
|
|
@@ -1402,6 +2075,15 @@ var leafletRotate = (() => {
|
|
|
1402
2075
|
position: "topleft",
|
|
1403
2076
|
closeOnZeroBearing: true
|
|
1404
2077
|
},
|
|
2078
|
+
/**
|
|
2079
|
+
* Create the control element and subscribe to map rotation.
|
|
2080
|
+
*
|
|
2081
|
+
* @param {L.Map} map
|
|
2082
|
+
*
|
|
2083
|
+
* @returns {HTMLElement} Control container.
|
|
2084
|
+
*
|
|
2085
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2086
|
+
*/
|
|
1405
2087
|
onAdd(map) {
|
|
1406
2088
|
const container = this._container = L.DomUtil.create("div", "leaflet-control-rotate leaflet-bar");
|
|
1407
2089
|
const arrow = this._arrow = L.DomUtil.create("span", "leaflet-control-rotate-arrow");
|
|
@@ -1426,9 +2108,23 @@ var leafletRotate = (() => {
|
|
|
1426
2108
|
}
|
|
1427
2109
|
return container;
|
|
1428
2110
|
},
|
|
2111
|
+
/**
|
|
2112
|
+
* Remove the map event listener when the control is detached.
|
|
2113
|
+
*
|
|
2114
|
+
* @param {L.Map} map
|
|
2115
|
+
*
|
|
2116
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2117
|
+
*/
|
|
1429
2118
|
onRemove(map) {
|
|
1430
2119
|
map.off("rotate", this._restyle, this);
|
|
1431
2120
|
},
|
|
2121
|
+
/**
|
|
2122
|
+
* Begin dragging the control's bearing indicator.
|
|
2123
|
+
*
|
|
2124
|
+
* @param {MouseEvent} e Mouse-down event.
|
|
2125
|
+
*
|
|
2126
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2127
|
+
*/
|
|
1432
2128
|
_handleMouseDown(e) {
|
|
1433
2129
|
L.DomEvent.stop(e);
|
|
1434
2130
|
this.dragging = true;
|
|
@@ -1436,11 +2132,25 @@ var leafletRotate = (() => {
|
|
|
1436
2132
|
this.dragstartY = e.pageY;
|
|
1437
2133
|
L.DomEvent.on(document, "mousemove", this._handleMouseDrag, this).on(document, "mouseup", this._handleMouseUp, this);
|
|
1438
2134
|
},
|
|
2135
|
+
/**
|
|
2136
|
+
* End bearing-indicator dragging and remove document listeners.
|
|
2137
|
+
*
|
|
2138
|
+
* @param {MouseEvent} e Mouse-up event.
|
|
2139
|
+
*
|
|
2140
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2141
|
+
*/
|
|
1439
2142
|
_handleMouseUp(e) {
|
|
1440
2143
|
L.DomEvent.stop(e);
|
|
1441
2144
|
this.dragging = false;
|
|
1442
2145
|
L.DomEvent.off(document, "mousemove", this._handleMouseDrag, this).off(document, "mouseup", this._handleMouseUp, this);
|
|
1443
2146
|
},
|
|
2147
|
+
/**
|
|
2148
|
+
* Set bearing from the horizontal drag distance.
|
|
2149
|
+
*
|
|
2150
|
+
* @param {MouseEvent} e Mouse-move event.
|
|
2151
|
+
*
|
|
2152
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2153
|
+
*/
|
|
1444
2154
|
_handleMouseDrag(e) {
|
|
1445
2155
|
if (!this.dragging) {
|
|
1446
2156
|
return;
|
|
@@ -1448,6 +2158,14 @@ var leafletRotate = (() => {
|
|
|
1448
2158
|
const deltaX = e.clientX - this.dragstartX;
|
|
1449
2159
|
this._map.setBearing(deltaX);
|
|
1450
2160
|
},
|
|
2161
|
+
/**
|
|
2162
|
+
* Cycle through touch, compass, and locked rotation modes.
|
|
2163
|
+
* Compass mode may require permission from the browser.
|
|
2164
|
+
*
|
|
2165
|
+
* @param {Event} ev Control click event.
|
|
2166
|
+
*
|
|
2167
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2168
|
+
*/
|
|
1451
2169
|
_cycleState(ev) {
|
|
1452
2170
|
if (!this._map) {
|
|
1453
2171
|
return;
|
|
@@ -1467,6 +2185,13 @@ var leafletRotate = (() => {
|
|
|
1467
2185
|
}
|
|
1468
2186
|
this._restyle();
|
|
1469
2187
|
},
|
|
2188
|
+
/**
|
|
2189
|
+
* Update the control icon and visibility to match the current mode.
|
|
2190
|
+
*
|
|
2191
|
+
* @listens L.Map#rotate
|
|
2192
|
+
*
|
|
2193
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2194
|
+
*/
|
|
1470
2195
|
_restyle() {
|
|
1471
2196
|
if (!this._map.options.rotate) {
|
|
1472
2197
|
this._link.classList.add("leaflet-disabled");
|
|
@@ -1494,6 +2219,14 @@ var leafletRotate = (() => {
|
|
|
1494
2219
|
L.control.rotate = (options) => new L.Control.Rotate(options);
|
|
1495
2220
|
}
|
|
1496
2221
|
L.Map.mergeOptions({
|
|
2222
|
+
/**
|
|
2223
|
+
* Add the rotation control automatically. Set to `false` to disable,
|
|
2224
|
+
* or pass an options object to configure the control.
|
|
2225
|
+
*
|
|
2226
|
+
* @type {boolean|Object}
|
|
2227
|
+
*
|
|
2228
|
+
* @since leaflet-rotate (v0.1.0)
|
|
2229
|
+
*/
|
|
1497
2230
|
rotateControl: true
|
|
1498
2231
|
});
|
|
1499
2232
|
L.Map.addInitHook(function() {
|