leaflet-rotate 0.4.0 → 0.5.1
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 +85 -5
- package/dist/leaflet-rotate-src.js +1325 -254
- 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/lib/debug.js +62 -88
- package/lib/debug.min.js +16 -1
- package/misc/leaflet-heat-rotate.js +7 -0
- package/package.json +1 -1
- package/src/control/Rotate.js +160 -103
- 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 +4 -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 +38 -3
- package/src/layer/vector/Renderer.js +88 -34
- package/src/map/Map.js +578 -116
- package/src/map/handler/CompassBearing.js +34 -1
- package/src/map/handler/ContainerMutation.js +18 -1
- package/src/map/handler/ShiftKeyRotate.js +204 -13
- package/src/map/handler/TouchGestures.js +108 -10
- package/src/map/handler/TouchPitch.js +53 -0
- package/src/map/handler/TouchRotate.js +18 -1
- package/src/map/handler/TouchZoom.js +21 -1
- package/test/features/example.feature +8 -4
- package/test/features/gestures.feature +4 -0
- package/test/features/rotation.feature +60 -0
- package/test/index.html +342 -186
- package/test/steps/bearing.steps.js +535 -0
- package/test/steps/example.steps.js +56 -29
- package/test/steps/gestures.steps.js +55 -0
- package/test/support/hooks.js +4 -16
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Sets map bearing from device orientation when the browser exposes compass data.
|
|
3
3
|
*
|
|
4
4
|
* @typedef L.Map.CompassBearing
|
|
5
|
+
* @property {L.Map.CompassBearing} compassBearing Map handler, enabled to follow device heading.
|
|
6
|
+
*
|
|
7
|
+
* @since leaflet-rotate (v0.1.0)
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
L.Map.CompassBearing = L.Handler.extend({
|
|
8
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @param {L.Map} map Map whose bearing follows device orientation.
|
|
14
|
+
*
|
|
15
|
+
* @since leaflet-rotate (v0.1.0)
|
|
16
|
+
*/
|
|
9
17
|
initialize(map) {
|
|
10
18
|
this._map = map;
|
|
11
19
|
/** @see https://caniuse.com/?search=DeviceOrientation */
|
|
@@ -17,9 +25,21 @@ L.Map.CompassBearing = L.Handler.extend({
|
|
|
17
25
|
this._throttled = L.Util.throttle(this._onDeviceOrientation, 100, this);
|
|
18
26
|
},
|
|
19
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Subscribe to the supported device-orientation event, if available.
|
|
30
|
+
* On iOS, permission is requested first (call `enable()` from a user gesture).
|
|
31
|
+
*
|
|
32
|
+
* @since leaflet-rotate (v0.1.0)
|
|
33
|
+
*/
|
|
20
34
|
addHooks() {
|
|
21
35
|
if (this._map._rotate && this.__deviceOrientationEvent) {
|
|
22
36
|
L.DomEvent.on(window, this.__deviceOrientationEvent, this._throttled, this);
|
|
37
|
+
const DOE = window.DeviceOrientationEvent;
|
|
38
|
+
if (DOE && DOE.requestPermission) {
|
|
39
|
+
DOE.requestPermission()
|
|
40
|
+
.then(state => 'denied' === state && this.enabled() && this.disable())
|
|
41
|
+
.catch(() => {});
|
|
42
|
+
}
|
|
23
43
|
} else {
|
|
24
44
|
// L.Map.CompassBearing handler will be automatically
|
|
25
45
|
// disabled if device orientation is not supported.
|
|
@@ -27,6 +47,11 @@ L.Map.CompassBearing = L.Handler.extend({
|
|
|
27
47
|
}
|
|
28
48
|
},
|
|
29
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Unsubscribe from device orientation when the handler is disabled.
|
|
52
|
+
*
|
|
53
|
+
* @since leaflet-rotate (v0.1.0)
|
|
54
|
+
*/
|
|
30
55
|
removeHooks() {
|
|
31
56
|
if (this._map._rotate && this.__deviceOrientationEvent) {
|
|
32
57
|
L.DomEvent.off(window, this.__deviceOrientationEvent, this._throttled, this);
|
|
@@ -34,6 +59,8 @@ L.Map.CompassBearing = L.Handler.extend({
|
|
|
34
59
|
},
|
|
35
60
|
|
|
36
61
|
/**
|
|
62
|
+
* Apply a device orientation reading to the map bearing.
|
|
63
|
+
*
|
|
37
64
|
* `DeviceOrientationEvent.absolute` - Indicates whether the device is providing absolute
|
|
38
65
|
* orientation values (relatives to Magnetic North) or
|
|
39
66
|
* using some arbitrary frame determined by the device.
|
|
@@ -50,6 +77,10 @@ L.Map.CompassBearing = L.Handler.extend({
|
|
|
50
77
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/absolute
|
|
51
78
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/alpha
|
|
52
79
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/orientation
|
|
80
|
+
*
|
|
81
|
+
* @param {DeviceOrientationEvent} e Orientation reading from the browser.
|
|
82
|
+
*
|
|
83
|
+
* @since leaflet-rotate (v0.1.0)
|
|
53
84
|
*/
|
|
54
85
|
_onDeviceOrientation(e) {
|
|
55
86
|
let angle = e.webkitCompassHeading != null ? e.webkitCompassHeading : e.alpha;
|
|
@@ -76,5 +107,7 @@ L.Map.CompassBearing = L.Handler.extend({
|
|
|
76
107
|
* Add Compass bearing handler to L.Map (disabled unless `window.DeviceOrientationEvent` is set).
|
|
77
108
|
*
|
|
78
109
|
* @property {L.Map.CompassBearing} compassBearing
|
|
110
|
+
*
|
|
111
|
+
* @since leaflet-rotate (v0.1.0)
|
|
79
112
|
*/
|
|
80
113
|
L.Map.addInitHook('addHandler', 'compassBearing', L.Map.CompassBearing);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Calls `invalidateSize()` when the map container's inline style changes.
|
|
3
3
|
*
|
|
4
4
|
* @typedef L.Map.ContainerMutation
|
|
5
|
+
* @property {boolean} [trackContainerMutation=false] Observe inline style changes on the map container.
|
|
6
|
+
*
|
|
7
|
+
* @since leaflet-rotate (v0.1.0)
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
/**
|
|
@@ -19,6 +22,8 @@ L.Map.mergeOptions({
|
|
|
19
22
|
*
|
|
20
23
|
* @type {Boolean}
|
|
21
24
|
*
|
|
25
|
+
* @since leaflet-rotate (v0.1.0)
|
|
26
|
+
*
|
|
22
27
|
* @see https://developer.mozilla.org/docs/Web/API/MutationObserver
|
|
23
28
|
*/
|
|
24
29
|
trackContainerMutation: false
|
|
@@ -27,6 +32,11 @@ L.Map.mergeOptions({
|
|
|
27
32
|
|
|
28
33
|
L.Map.ContainerMutation = L.Handler.extend({
|
|
29
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Start observing the map container's style attribute.
|
|
37
|
+
*
|
|
38
|
+
* @since leaflet-rotate (v0.1.0)
|
|
39
|
+
*/
|
|
30
40
|
addHooks() {
|
|
31
41
|
// if (!L.Browser.mutation) { return; }
|
|
32
42
|
if (!this._observer) {
|
|
@@ -41,6 +51,11 @@ L.Map.ContainerMutation = L.Handler.extend({
|
|
|
41
51
|
});
|
|
42
52
|
},
|
|
43
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Stop observing container mutations.
|
|
56
|
+
*
|
|
57
|
+
* @since leaflet-rotate (v0.1.0)
|
|
58
|
+
*/
|
|
44
59
|
removeHooks() {
|
|
45
60
|
// if (!L.Browser.mutation) { return; }
|
|
46
61
|
this._observer.disconnect();
|
|
@@ -52,5 +67,7 @@ L.Map.ContainerMutation = L.Handler.extend({
|
|
|
52
67
|
* Add Container mutation handler to L.Map (disabled unless `trackContainerMutation` is set).
|
|
53
68
|
*
|
|
54
69
|
* @property {L.Map.ContainerMutation} trackContainerMutation
|
|
70
|
+
*
|
|
71
|
+
* @since leaflet-rotate (v0.1.0)
|
|
55
72
|
*/
|
|
56
73
|
L.Map.addInitHook('addHandler', 'trackContainerMutation', L.Map.ContainerMutation);
|
|
@@ -1,55 +1,246 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Handles Shift+wheel bearing changes and Ctrl+drag / right-drag bearing/pitch changes.
|
|
4
4
|
*
|
|
5
5
|
* @typedef L.Map.ShiftKeyRotate
|
|
6
|
+
* @property {boolean} [shiftKeyRotate=true] Enable Shift+wheel bearing rotation.
|
|
7
|
+
*
|
|
8
|
+
* @since leaflet-rotate (v0.1.0)
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
L.Map.mergeOptions({
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
|
-
* Whether the map
|
|
14
|
+
* Whether Shift+wheel changes the map bearing.
|
|
15
|
+
*
|
|
12
16
|
* @type {Boolean}
|
|
17
|
+
*
|
|
18
|
+
* @since leaflet-rotate (v0.1.0)
|
|
13
19
|
*/
|
|
14
20
|
shiftKeyRotate: true,
|
|
15
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Whether Ctrl+drag (or right-button drag) changes bearing and pitch.
|
|
24
|
+
*
|
|
25
|
+
* @type {Boolean}
|
|
26
|
+
*
|
|
27
|
+
* @since leaflet-rotate (0.5.0)
|
|
28
|
+
*/
|
|
29
|
+
pitchDrag: true,
|
|
30
|
+
|
|
16
31
|
});
|
|
17
32
|
|
|
18
33
|
L.Map.ShiftKeyRotate = L.Handler.extend({
|
|
19
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {L.Map} map Map controlled by this handler.
|
|
37
|
+
*
|
|
38
|
+
* @since leaflet-rotate (v0.1.0)
|
|
39
|
+
*/
|
|
40
|
+
initialize(map) {
|
|
41
|
+
this._map = map;
|
|
42
|
+
this._onMouseDownBound = this._onMouseDown.bind(this);
|
|
43
|
+
this._onMouseMoveBound = this._onMouseMove.bind(this);
|
|
44
|
+
this._onMouseUpBound = this._onMouseUp.bind(this);
|
|
45
|
+
this._onContextMenuBound = this._onContextMenu.bind(this);
|
|
46
|
+
this._applyBound = this._apply.bind(this);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Attach enabled wheel and pointer/mouse gesture listeners.
|
|
51
|
+
*
|
|
52
|
+
* @since leaflet-rotate (v0.1.0)
|
|
53
|
+
*/
|
|
20
54
|
addHooks() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
55
|
+
if (this._map.options.shiftKeyRotate) {
|
|
56
|
+
L.DomEvent.on(this._map._container, "wheel", this._handleShiftScroll, this);
|
|
57
|
+
}
|
|
58
|
+
if (this._map.options.pitchDrag) {
|
|
59
|
+
this.addPitchHooks();
|
|
60
|
+
}
|
|
24
61
|
},
|
|
25
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Detach gesture listeners and cancel any active drag.
|
|
65
|
+
*
|
|
66
|
+
* @since leaflet-rotate (v0.1.0)
|
|
67
|
+
*/
|
|
26
68
|
removeHooks() {
|
|
27
69
|
L.DomEvent.off(this._map._container, "wheel", this._handleShiftScroll, this);
|
|
28
|
-
this.
|
|
70
|
+
this.removePitchHooks();
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Listen for the start of Ctrl+drag pitch gestures.
|
|
75
|
+
*
|
|
76
|
+
* @since leaflet-rotate (0.5.0)
|
|
77
|
+
*/
|
|
78
|
+
addPitchHooks() {
|
|
79
|
+
if (this._pitchHooksAdded) { return; }
|
|
80
|
+
this._pitchEvents = L.version.startsWith('2.')
|
|
81
|
+
? ['pointerdown', 'pointermove', 'pointerup pointercancel']
|
|
82
|
+
: ['mousedown', 'mousemove', 'mouseup'];
|
|
83
|
+
this._map._container.addEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
|
|
84
|
+
this._map._container.addEventListener('contextmenu', this._onContextMenuBound, true);
|
|
85
|
+
this._pitchHooksAdded = true;
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Stop listening for pitch gestures and remove active document listeners.
|
|
90
|
+
*
|
|
91
|
+
* @since leaflet-rotate (0.5.0)
|
|
92
|
+
*/
|
|
93
|
+
removePitchHooks() {
|
|
94
|
+
if (!this._pitchHooksAdded) { return; }
|
|
95
|
+
this._map._container.removeEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
|
|
96
|
+
this._map._container.removeEventListener('contextmenu', this._onContextMenuBound, true);
|
|
97
|
+
this._onMouseUp();
|
|
98
|
+
delete this._pitchEvents;
|
|
99
|
+
this._pitchHooksAdded = false;
|
|
29
100
|
},
|
|
30
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Rotate the map in fixed increments while Shift is held during wheel input.
|
|
104
|
+
*
|
|
105
|
+
* @param {WheelEvent} e Wheel event from the map container.
|
|
106
|
+
*
|
|
107
|
+
* @since leaflet-rotate (v0.1.0)
|
|
108
|
+
*/
|
|
31
109
|
_handleShiftScroll(e) {
|
|
32
110
|
if (e.shiftKey) {
|
|
33
111
|
e.preventDefault();
|
|
34
112
|
this._map.scrollWheelZoom.disable();
|
|
35
|
-
this._map.setBearing(
|
|
113
|
+
this._map.setBearing(this._map.getBearing() + Math.sign(e.deltaY) * 5);
|
|
36
114
|
} else {
|
|
37
115
|
this._map.scrollWheelZoom.enable();
|
|
38
116
|
}
|
|
39
117
|
},
|
|
40
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Start Ctrl+drag (or right-button drag) and capture subsequent movement outside the map.
|
|
121
|
+
*
|
|
122
|
+
* @param {MouseEvent|PointerEvent} e Gesture start event.
|
|
123
|
+
*
|
|
124
|
+
* @since leaflet-rotate (0.5.0)
|
|
125
|
+
*/
|
|
126
|
+
_onMouseDown(e) {
|
|
127
|
+
if (this._dragging) { return; }
|
|
128
|
+
this._moved = false;
|
|
129
|
+
if (!this._map._rotate || !this._map.options.pitchDrag || !(e.button === 2 || (e.ctrlKey && e.button === 0))) { return; }
|
|
130
|
+
|
|
131
|
+
const scrollWheelZoom = this._map.scrollWheelZoom;
|
|
132
|
+
if (scrollWheelZoom && scrollWheelZoom._timer) {
|
|
133
|
+
clearTimeout(scrollWheelZoom._timer);
|
|
134
|
+
scrollWheelZoom._timer = null;
|
|
135
|
+
scrollWheelZoom._delta = 0;
|
|
136
|
+
scrollWheelZoom._startTime = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
e.preventDefault();
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
e.stopImmediatePropagation();
|
|
142
|
+
this._map._stopPitchBearingAnimation();
|
|
143
|
+
this._dragging = true;
|
|
144
|
+
this._lastX = e.clientX;
|
|
145
|
+
this._lastY = e.clientY;
|
|
146
|
+
this._pitchDelta = this._bearingDelta = 0;
|
|
147
|
+
document.addEventListener(this._pitchEvents[1], this._onMouseMoveBound);
|
|
148
|
+
for (const event of this._pitchEvents[2].split(' ')) {
|
|
149
|
+
document.addEventListener(event, this._onMouseUpBound);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Accumulate pointer movement as pitch (vertical) and bearing (horizontal).
|
|
155
|
+
* Far from the horizontal center line the bearing follows the pointer angle
|
|
156
|
+
* around the map center.
|
|
157
|
+
*
|
|
158
|
+
* @param {MouseEvent|PointerEvent} e Gesture movement event.
|
|
159
|
+
*
|
|
160
|
+
* @since leaflet-rotate (0.5.0)
|
|
161
|
+
*/
|
|
162
|
+
_onMouseMove(e) {
|
|
163
|
+
const rect = this._map._container.getBoundingClientRect(),
|
|
164
|
+
cx = rect.left + rect.width / 2,
|
|
165
|
+
cy = rect.top + rect.height / 2,
|
|
166
|
+
x = e.clientX,
|
|
167
|
+
y = e.clientY;
|
|
168
|
+
|
|
169
|
+
this._pitchDelta += (this._lastY - y) * 0.5;
|
|
170
|
+
this._bearingDelta += Math.abs(cy - this._lastY) > 100
|
|
171
|
+
? (Math.atan2(y - cy, x - cx) - Math.atan2(y - cy, this._lastX - cx)) * (180 / Math.PI)
|
|
172
|
+
: (x - this._lastX) * 0.8 * (y < cy ? 1 : -1);
|
|
173
|
+
this._lastX = x;
|
|
174
|
+
this._lastY = y;
|
|
175
|
+
this._moved = true;
|
|
176
|
+
if (!this._frame) {
|
|
177
|
+
this._frame = requestAnimationFrame(this._applyBound);
|
|
178
|
+
}
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Apply accumulated movement once per animation frame.
|
|
184
|
+
*
|
|
185
|
+
* @since leaflet-rotate (0.5.1)
|
|
186
|
+
*/
|
|
187
|
+
_apply() {
|
|
188
|
+
this._frame = null;
|
|
189
|
+
if (!this._pitchDelta && !this._bearingDelta) { return; }
|
|
190
|
+
this._map._setPitchAndBearing(this._map.getPitch() + this._pitchDelta, this._map.getBearing() + this._bearingDelta);
|
|
191
|
+
this._pitchDelta = this._bearingDelta = 0;
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Suppress the context menu that follows a right-button drag.
|
|
196
|
+
*
|
|
197
|
+
* @param {MouseEvent} e Context menu event.
|
|
198
|
+
*
|
|
199
|
+
* @since leaflet-rotate (0.5.1)
|
|
200
|
+
*/
|
|
201
|
+
_onContextMenu(e) {
|
|
202
|
+
if (this._dragging || this._moved) {
|
|
203
|
+
e.preventDefault();
|
|
204
|
+
e.stopPropagation();
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* End Ctrl+drag, flush pending movement and clear document-level listeners.
|
|
210
|
+
*
|
|
211
|
+
* @since leaflet-rotate (0.5.0)
|
|
212
|
+
*/
|
|
213
|
+
_onMouseUp() {
|
|
214
|
+
if (!this._pitchEvents || !this._dragging) { return; }
|
|
215
|
+
document.removeEventListener(this._pitchEvents[1], this._onMouseMoveBound);
|
|
216
|
+
for (const event of this._pitchEvents[2].split(' ')) {
|
|
217
|
+
document.removeEventListener(event, this._onMouseUpBound);
|
|
218
|
+
}
|
|
219
|
+
cancelAnimationFrame(this._frame);
|
|
220
|
+
if (this._map._mapPane) { this._apply(); }
|
|
221
|
+
this._frame = null;
|
|
222
|
+
this._dragging = false;
|
|
223
|
+
delete this._lastX;
|
|
224
|
+
delete this._lastY;
|
|
225
|
+
},
|
|
226
|
+
|
|
41
227
|
});
|
|
42
228
|
|
|
43
229
|
/**
|
|
44
|
-
*
|
|
230
|
+
* Create the handler when either desktop rotation gesture is enabled.
|
|
45
231
|
*
|
|
46
|
-
* @
|
|
232
|
+
* @since leaflet-rotate (v0.1.0)
|
|
47
233
|
*/
|
|
48
|
-
L.Map.addInitHook('addHandler', 'shiftKeyRotate', L.Map.ShiftKeyRotate);
|
|
49
|
-
|
|
50
|
-
// decrease `scrollWheelZoom` handler priority over `shiftKeyRotate` handler
|
|
51
234
|
L.Map.addInitHook(function() {
|
|
52
|
-
if (this.
|
|
235
|
+
if (this.options.shiftKeyRotate) {
|
|
236
|
+
this.shiftKeyRotate = new L.Map.ShiftKeyRotate(this);
|
|
237
|
+
this.shiftKeyRotate.enable();
|
|
238
|
+
} else if (this.options.pitchDrag) {
|
|
239
|
+
this.shiftKeyRotate = new L.Map.ShiftKeyRotate(this);
|
|
240
|
+
this.shiftKeyRotate.addPitchHooks();
|
|
241
|
+
this.on('unload', this.shiftKeyRotate.removePitchHooks, this.shiftKeyRotate);
|
|
242
|
+
}
|
|
243
|
+
if (this.scrollWheelZoom.enabled() && this.options.shiftKeyRotate) {
|
|
53
244
|
this.scrollWheelZoom.disable();
|
|
54
245
|
this.scrollWheelZoom.enable();
|
|
55
246
|
}
|
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared implementation for two-finger pinch zoom and map rotation.
|
|
3
3
|
*
|
|
4
4
|
* @see https://github.com/fnicollet/Leaflet/commit/a77af51a6b10f308d1b9a16552091d1d0aee8834
|
|
5
5
|
* @see https://github.com/Leaflet/Leaflet/blob/v1.9.3/src/map/handler/Map.TouchZoom.js
|
|
6
6
|
*
|
|
7
7
|
* @typedef L.Map.TouchGestures
|
|
8
|
+
* @property {boolean} [bounceAtZoomLimits] Whether pinch zoom may temporarily exceed zoom limits.
|
|
9
|
+
* @property {boolean} zoom Whether pinch zoom is enabled.
|
|
10
|
+
* @property {boolean} rotate Whether touch rotation is enabled.
|
|
11
|
+
*
|
|
12
|
+
* @since leaflet-rotate (v0.1.0)
|
|
8
13
|
*/
|
|
9
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Convert a touch's client coordinates to coordinates relative to the map container.
|
|
17
|
+
*
|
|
18
|
+
* @param {L.Map} map
|
|
19
|
+
* @param {Touch} touch
|
|
20
|
+
*
|
|
21
|
+
* @returns {L.Point}
|
|
22
|
+
*
|
|
23
|
+
* @since leaflet-rotate (v0.1.0)
|
|
24
|
+
*/
|
|
10
25
|
function touchToContainerPoint(map, touch) {
|
|
11
26
|
const container = map.getContainer(),
|
|
12
27
|
rect = container.getBoundingClientRect();
|
|
@@ -20,11 +35,11 @@ function touchToContainerPoint(map, touch) {
|
|
|
20
35
|
L.Map.mergeOptions({
|
|
21
36
|
|
|
22
37
|
/**
|
|
23
|
-
*
|
|
24
|
-
* zoom beyond min/max zoom and then bounce back
|
|
25
|
-
* when pinch-zooming.
|
|
38
|
+
* Whether pinch zoom may temporarily exceed min/max zoom and bounce back.
|
|
26
39
|
*
|
|
27
40
|
* @type {Boolean}
|
|
41
|
+
*
|
|
42
|
+
* @since leaflet-rotate (v0.1.0)
|
|
28
43
|
*/
|
|
29
44
|
bounceAtZoomLimits: true,
|
|
30
45
|
|
|
@@ -32,29 +47,62 @@ L.Map.mergeOptions({
|
|
|
32
47
|
|
|
33
48
|
L.Map.TouchGestures = L.Handler.extend({
|
|
34
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Initialize the shared handler from the map's touch zoom/rotation options.
|
|
52
|
+
*
|
|
53
|
+
* @param {L.Map} map Map whose touch gestures are handled.
|
|
54
|
+
*
|
|
55
|
+
* @since leaflet-rotate (v0.1.0)
|
|
56
|
+
*/
|
|
35
57
|
initialize(map) {
|
|
36
58
|
this._map = map;
|
|
37
59
|
this.rotate = !!this._map.options.touchRotate;
|
|
38
60
|
this.zoom = !!this._map.options.touchZoom;
|
|
61
|
+
this.pitch = !!this._map.options.touchPitch;
|
|
39
62
|
},
|
|
40
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Listen for the start of two-finger gestures on the map container.
|
|
66
|
+
*
|
|
67
|
+
* @since leaflet-rotate (v0.1.0)
|
|
68
|
+
*/
|
|
41
69
|
addHooks() {
|
|
42
70
|
L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
|
|
43
71
|
},
|
|
44
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Remove the touch-start listener.
|
|
75
|
+
*
|
|
76
|
+
* @since leaflet-rotate (v0.1.0)
|
|
77
|
+
*/
|
|
45
78
|
removeHooks() {
|
|
46
79
|
L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
|
|
47
80
|
},
|
|
48
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Record initial touch points and start tracking a two-finger gesture.
|
|
84
|
+
*
|
|
85
|
+
* @param {TouchEvent} e Touch-start event.
|
|
86
|
+
*
|
|
87
|
+
* @since leaflet-rotate (v0.1.0)
|
|
88
|
+
*/
|
|
49
89
|
_onTouchStart(e) {
|
|
50
90
|
const map = this._map;
|
|
51
91
|
|
|
52
|
-
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) { return; }
|
|
92
|
+
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating || this._gesture) { return; }
|
|
53
93
|
|
|
54
94
|
const p1 = touchToContainerPoint(map, e.touches[0]),
|
|
55
95
|
p2 = touchToContainerPoint(map, e.touches[1]),
|
|
56
96
|
vector = p1.subtract(p2);
|
|
57
97
|
|
|
98
|
+
// Two horizontally aligned fingers moving vertically together change the pitch
|
|
99
|
+
const canPitch = this.pitch && map._rotate && Math.abs(vector.x) > Math.abs(vector.y);
|
|
100
|
+
if (!canPitch && !this.zoom && !this.rotate) { return; }
|
|
101
|
+
this._gesture = canPitch ? 'pending' : 'pinch';
|
|
102
|
+
this._startP1 = p1;
|
|
103
|
+
this._startP2 = p2;
|
|
104
|
+
map._stopPitchBearingAnimation();
|
|
105
|
+
|
|
58
106
|
this._centerPoint = map.getSize()._divideBy(2);
|
|
59
107
|
this._startLatLng = map.containerPointToLatLng(this._centerPoint);
|
|
60
108
|
|
|
@@ -89,8 +137,15 @@ L.Map.TouchGestures = L.Handler.extend({
|
|
|
89
137
|
L.DomEvent.preventDefault(e);
|
|
90
138
|
},
|
|
91
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Update bearing and/or zoom while the two touch points move.
|
|
142
|
+
*
|
|
143
|
+
* @param {TouchEvent} e Touch-move event.
|
|
144
|
+
*
|
|
145
|
+
* @since leaflet-rotate (v0.1.0)
|
|
146
|
+
*/
|
|
92
147
|
_onTouchMove(e) {
|
|
93
|
-
if (!e.touches || e.touches.length !== 2 || !
|
|
148
|
+
if (!e.touches || e.touches.length !== 2 || !this._gesture) { return; }
|
|
94
149
|
|
|
95
150
|
const map = this._map,
|
|
96
151
|
p1 = touchToContainerPoint(map, e.touches[0]),
|
|
@@ -99,6 +154,33 @@ L.Map.TouchGestures = L.Handler.extend({
|
|
|
99
154
|
scale = p1.distanceTo(p2) / this._startDist;
|
|
100
155
|
let delta;
|
|
101
156
|
|
|
157
|
+
if (this._gesture === 'pending') {
|
|
158
|
+
const d1 = p1.subtract(this._startP1),
|
|
159
|
+
d2 = p2.subtract(this._startP2);
|
|
160
|
+
if (Math.max(d1.x * d1.x + d1.y * d1.y, d2.x * d2.x + d2.y * d2.y) < 25) {
|
|
161
|
+
L.DomEvent.preventDefault(e);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const vertical = d => Math.abs(d.y) > Math.abs(d.x);
|
|
165
|
+
if (vertical(d1) && vertical(d2) && Math.sign(d1.y) === Math.sign(d2.y)) {
|
|
166
|
+
this._gesture = 'pitch';
|
|
167
|
+
this._lastPitchY = (this._startP1.y + this._startP2.y) / 2;
|
|
168
|
+
this._zooming = this._rotating = false;
|
|
169
|
+
} else {
|
|
170
|
+
this._gesture = 'pinch';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this._gesture === 'pitch') {
|
|
175
|
+
const y = (p1.y + p2.y) / 2;
|
|
176
|
+
map._setPitchAndBearing(map.getPitch() - (y - this._lastPitchY) * 0.5, map.getBearing());
|
|
177
|
+
this._lastPitchY = y;
|
|
178
|
+
L.DomEvent.preventDefault(e);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!(this._zooming || this._rotating)) { return; }
|
|
183
|
+
|
|
102
184
|
if (this._rotating) {
|
|
103
185
|
const theta = Math.atan(vector.x / vector.y);
|
|
104
186
|
let bearingDelta = (theta - this._startTheta) * (180 / Math.PI);
|
|
@@ -133,9 +215,12 @@ L.Map.TouchGestures = L.Handler.extend({
|
|
|
133
215
|
delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
|
|
134
216
|
if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
|
|
135
217
|
|
|
136
|
-
const
|
|
218
|
+
const mapDelta = map._pitch
|
|
219
|
+
? map.containerPointToLayerPoint(this._centerPoint.add(delta))
|
|
220
|
+
.subtract(map.containerPointToLayerPoint(this._centerPoint))
|
|
221
|
+
: delta.rotate(-map.getBearing() * (Math.PI / 180));
|
|
137
222
|
|
|
138
|
-
this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(
|
|
223
|
+
this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(mapDelta));
|
|
139
224
|
}
|
|
140
225
|
|
|
141
226
|
}
|
|
@@ -159,9 +244,20 @@ L.Map.TouchGestures = L.Handler.extend({
|
|
|
159
244
|
L.DomEvent.preventDefault(e);
|
|
160
245
|
},
|
|
161
246
|
|
|
247
|
+
/**
|
|
248
|
+
* Finish the gesture, settle the zoom and remove document listeners.
|
|
249
|
+
*
|
|
250
|
+
* @since leaflet-rotate (v0.1.0)
|
|
251
|
+
*/
|
|
162
252
|
_onTouchEnd() {
|
|
163
|
-
|
|
253
|
+
const gesture = this._gesture;
|
|
254
|
+
delete this._gesture;
|
|
255
|
+
if (gesture === 'pitch' || gesture === 'pending' || !this._moved || !(this._zooming || this._rotating)) {
|
|
164
256
|
this._zooming = false;
|
|
257
|
+
this._rotating = false;
|
|
258
|
+
L.DomEvent
|
|
259
|
+
.off(document, 'touchmove', this._onTouchMove, this)
|
|
260
|
+
.off(document, 'touchend touchcancel', this._onTouchEnd, this);
|
|
165
261
|
return;
|
|
166
262
|
}
|
|
167
263
|
|
|
@@ -184,8 +280,10 @@ L.Map.TouchGestures = L.Handler.extend({
|
|
|
184
280
|
});
|
|
185
281
|
|
|
186
282
|
/**
|
|
187
|
-
* Add
|
|
283
|
+
* Add the shared touch gesture handler to the map.
|
|
188
284
|
*
|
|
189
285
|
* @property {L.Map.TouchGestures} touchGestures
|
|
286
|
+
*
|
|
287
|
+
* @since leaflet-rotate (v0.1.0)
|
|
190
288
|
*/
|
|
191
289
|
L.Map.addInitHook('addHandler', 'touchGestures', L.Map.TouchGestures);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enables MapLibre-like two-finger pitch (both fingers dragged vertically) through the shared touch-gesture handler.
|
|
3
|
+
*
|
|
4
|
+
* @typedef L.Map.TouchPitch
|
|
5
|
+
* @property {boolean} [touchPitch=false] Enable two-finger map pitch (vertical drag).
|
|
6
|
+
*
|
|
7
|
+
* @since leaflet-rotate (0.5.1)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
L.Map.mergeOptions({
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether the map can be pitched by dragging two fingers vertically
|
|
14
|
+
*
|
|
15
|
+
* @type {Boolean}
|
|
16
|
+
*
|
|
17
|
+
* @since leaflet-rotate (0.5.1)
|
|
18
|
+
*/
|
|
19
|
+
touchPitch: false,
|
|
20
|
+
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
L.Map.TouchPitch = L.Handler.extend({
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Enable pitch handling in the map's shared touch gesture handler.
|
|
27
|
+
*
|
|
28
|
+
* @since leaflet-rotate (0.5.1)
|
|
29
|
+
*/
|
|
30
|
+
addHooks() {
|
|
31
|
+
this._map.touchGestures.enable();
|
|
32
|
+
this._map.touchGestures.pitch = true;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Disable pitch handling without affecting pinch zoom and rotation.
|
|
37
|
+
*
|
|
38
|
+
* @since leaflet-rotate (0.5.1)
|
|
39
|
+
*/
|
|
40
|
+
removeHooks() {
|
|
41
|
+
this._map.touchGestures.pitch = false;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Add Touch Pitch handler (disabled unless `touchGestures` is set).
|
|
48
|
+
*
|
|
49
|
+
* @property {L.Map.TouchGestures} touchGestures
|
|
50
|
+
*
|
|
51
|
+
* @since leaflet-rotate (0.5.1)
|
|
52
|
+
*/
|
|
53
|
+
L.Map.addInitHook('addHandler', 'touchPitch', L.Map.TouchPitch);
|