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
package/src/map/Map.js
CHANGED
|
@@ -1,17 +1,73 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Extends Leaflet maps with bearing rotation and 3D pitch.
|
|
3
|
+
*
|
|
2
4
|
* @external L.Map
|
|
3
5
|
*
|
|
6
|
+
* @since leaflet-rotate (v0.1.0)
|
|
7
|
+
*
|
|
4
8
|
* @see https://github.com/Leaflet/Leaflet/blob/v1.9.3/src/map/Map.js
|
|
5
9
|
*/
|
|
6
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @event L.Map#rotate Fired after the map bearing changes.
|
|
13
|
+
*
|
|
14
|
+
* @since leaflet-rotate (v0.1.0)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @event L.Map#pitch Fired after the map pitch changes.
|
|
19
|
+
*
|
|
20
|
+
* @since leaflet-rotate (0.5.0)
|
|
21
|
+
*/
|
|
22
|
+
|
|
7
23
|
import { toPoint } from '../geometry/Point';
|
|
8
24
|
|
|
9
25
|
const mapProto = Object.defineProperties({}, Object.getOwnPropertyDescriptors(L.Map.prototype));
|
|
10
26
|
|
|
11
|
-
L.Map.mergeOptions({
|
|
27
|
+
L.Map.mergeOptions({
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @type {boolean} Enable bearing and pitch transformations.
|
|
31
|
+
*
|
|
32
|
+
* @since leaflet-rotate (v0.1.0)
|
|
33
|
+
*/
|
|
34
|
+
rotate: false,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @type {number} Initial map bearing in degrees.
|
|
38
|
+
*
|
|
39
|
+
* @since leaflet-rotate (v0.1.0)
|
|
40
|
+
*/
|
|
41
|
+
bearing: 0,
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @type {number} Initial map pitch in degrees, clamped to 0–85.
|
|
45
|
+
*
|
|
46
|
+
* @since leaflet-rotate (0.5.0)
|
|
47
|
+
*/
|
|
48
|
+
pitch: 0,
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @type {number} Maximum distance of the pitched map plane from the map center, in multiples
|
|
52
|
+
* of the map height. Tiles, vectors and `getBounds()` stop there (and always
|
|
53
|
+
* before the horizon); a `.leaflet-pitch-sky` element fills the area above.
|
|
54
|
+
*
|
|
55
|
+
* @since leaflet-rotate (0.5.1)
|
|
56
|
+
*/
|
|
57
|
+
pitchMaxDistance: 3,
|
|
58
|
+
|
|
59
|
+
});
|
|
12
60
|
|
|
13
61
|
L.Map.include({
|
|
14
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Apply an immediate animated-zoom frame without waiting for a transition delay.
|
|
65
|
+
*
|
|
66
|
+
* @param {L.LatLng} center Target center.
|
|
67
|
+
* @param {number} zoom Target zoom level.
|
|
68
|
+
*
|
|
69
|
+
* @since leaflet-rotate (v0.1.0)
|
|
70
|
+
*/
|
|
15
71
|
_animateZoomNoDelay(center, zoom) {
|
|
16
72
|
if (!this._mapPane) { return; }
|
|
17
73
|
|
|
@@ -23,17 +79,25 @@ L.Map.include({
|
|
|
23
79
|
},
|
|
24
80
|
|
|
25
81
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
82
|
+
* Initialize the Leaflet map and apply its initial bearing and pitch.
|
|
83
|
+
*
|
|
84
|
+
* @param {HTMLElement|string} id Map container element or its ID.
|
|
85
|
+
* @param {Object} [options={}] Leaflet map options, including `rotate`, `bearing`, and `pitch`.
|
|
86
|
+
*
|
|
87
|
+
* @since leaflet-rotate (v0.1.0)
|
|
28
88
|
*/
|
|
29
89
|
initialize(id, options) {
|
|
30
90
|
if (options.rotate) {
|
|
31
91
|
this._rotate = true;
|
|
32
92
|
this._bearing = 0;
|
|
93
|
+
this._pitch = 0;
|
|
33
94
|
}
|
|
34
95
|
mapProto.initialize.apply(this, arguments);
|
|
35
|
-
if(this.options.rotate){
|
|
36
|
-
|
|
96
|
+
if (this.options.rotate) {
|
|
97
|
+
this.on('resize', this._updatePitchTransform, this);
|
|
98
|
+
this.on('move', this._updatePitchSky, this);
|
|
99
|
+
this.setBearing(this.options.bearing);
|
|
100
|
+
this.setPitch(this.options.pitch);
|
|
37
101
|
}
|
|
38
102
|
},
|
|
39
103
|
|
|
@@ -42,13 +106,22 @@ L.Map.include({
|
|
|
42
106
|
* returns the corresponding pixel coordinate relative to
|
|
43
107
|
* the [origin pixel](#map-getpixelorigin).
|
|
44
108
|
*
|
|
45
|
-
* @param {L.Point} point
|
|
46
|
-
*
|
|
109
|
+
* @param {L.Point|number[]} point Pixel coordinate in the map container.
|
|
110
|
+
*
|
|
111
|
+
* @returns {L.Point} Corresponding layer-point coordinate.
|
|
112
|
+
*
|
|
113
|
+
* @since leaflet-rotate (v0.1.0)
|
|
47
114
|
*/
|
|
48
115
|
containerPointToLayerPoint(point) {
|
|
49
116
|
if (!this._rotate) {
|
|
50
117
|
return mapProto.containerPointToLayerPoint.apply(this, arguments);
|
|
51
118
|
}
|
|
119
|
+
if (this._pitch) {
|
|
120
|
+
return this._transformPitchPoint(
|
|
121
|
+
toPoint(point).subtract(this._getMapPanePos()),
|
|
122
|
+
this._pitchInverseTransform
|
|
123
|
+
);
|
|
124
|
+
}
|
|
52
125
|
return toPoint(point)
|
|
53
126
|
.subtract(this._getMapPanePos())
|
|
54
127
|
.rotateFrom(-this._bearing, this._getRotatePanePos())
|
|
@@ -59,13 +132,19 @@ L.Map.include({
|
|
|
59
132
|
* Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
|
|
60
133
|
* returns the corresponding pixel coordinate relative to the map container.
|
|
61
134
|
*
|
|
62
|
-
* @param {L.Point} point
|
|
63
|
-
*
|
|
135
|
+
* @param {L.Point|number[]} point Pixel coordinate in layer-point space.
|
|
136
|
+
*
|
|
137
|
+
* @returns {L.Point} Corresponding pixel coordinate in the map container.
|
|
138
|
+
*
|
|
139
|
+
* @since leaflet-rotate (v0.1.0)
|
|
64
140
|
*/
|
|
65
141
|
layerPointToContainerPoint(point) {
|
|
66
142
|
if (!this._rotate) {
|
|
67
143
|
return mapProto.layerPointToContainerPoint.apply(this, arguments);
|
|
68
144
|
}
|
|
145
|
+
if (this._pitch) {
|
|
146
|
+
return this._transformPitchPoint(point, this._pitchTransform).add(this._getMapPanePos());
|
|
147
|
+
}
|
|
69
148
|
return toPoint(point)
|
|
70
149
|
.add(this._getRotatePanePos())
|
|
71
150
|
.rotateFrom(this._bearing, this._getRotatePanePos())
|
|
@@ -79,12 +158,16 @@ L.Map.include({
|
|
|
79
158
|
* (rotatePane) --> (mapPane)
|
|
80
159
|
* (rotatePane) --> (norotatePane)
|
|
81
160
|
*
|
|
82
|
-
* @param {L.Point} point
|
|
83
|
-
* @returns {L.Point}
|
|
161
|
+
* @param {L.Point|number[]} point Point in the rotated pane.
|
|
84
162
|
*
|
|
85
|
-
* @
|
|
163
|
+
* @returns {L.Point} Corresponding point in map-pane coordinates.
|
|
164
|
+
*
|
|
165
|
+
* @since leaflet-rotate (v0.1.0)
|
|
86
166
|
*/
|
|
87
167
|
rotatedPointToMapPanePoint(point) {
|
|
168
|
+
if (this._pitch) {
|
|
169
|
+
return this._transformPitchPoint(point, this._pitchTransform);
|
|
170
|
+
}
|
|
88
171
|
return toPoint(point)
|
|
89
172
|
.rotate(this._bearing)
|
|
90
173
|
._add(this._getRotatePanePos());
|
|
@@ -97,11 +180,16 @@ L.Map.include({
|
|
|
97
180
|
* (mapPane) --> (rotatePane)
|
|
98
181
|
* (norotatePane) --> (rotatePane)
|
|
99
182
|
*
|
|
100
|
-
* @param {L.Point} point
|
|
183
|
+
* @param {L.Point|number[]} point Point in map-pane coordinates.
|
|
184
|
+
*
|
|
185
|
+
* @returns {L.Point} Corresponding point in the rotated pane.
|
|
101
186
|
*
|
|
102
|
-
* @since leaflet-rotate (v0.1)
|
|
187
|
+
* @since leaflet-rotate (v0.1.0)
|
|
103
188
|
*/
|
|
104
189
|
mapPanePointToRotatedPoint(point) {
|
|
190
|
+
if (this._pitch) {
|
|
191
|
+
return this._transformPitchPoint(point, this._pitchInverseTransform);
|
|
192
|
+
}
|
|
105
193
|
return toPoint(point)
|
|
106
194
|
._subtract(this._getRotatePanePos())
|
|
107
195
|
.rotate(-this._bearing);
|
|
@@ -122,10 +210,11 @@ L.Map.include({
|
|
|
122
210
|
*
|
|
123
211
|
* @see https://github.com/ronikar/Leaflet/blob/5c480ef959b947c3beed7065425a5a36c486262b/src/map/Map.js#L1114-L1135
|
|
124
212
|
*
|
|
125
|
-
* @param {L.LatLngBounds} bounds
|
|
126
|
-
* @returns {L.Bounds}
|
|
213
|
+
* @param {L.LatLngBounds} bounds Geographic bounds to project.
|
|
127
214
|
*
|
|
128
|
-
* @
|
|
215
|
+
* @returns {L.Bounds} Axis-aligned bounds in container-pixel coordinates.
|
|
216
|
+
*
|
|
217
|
+
* @since leaflet-rotate (v0.2.0)
|
|
129
218
|
*/
|
|
130
219
|
mapBoundsToContainerBounds(bounds) {
|
|
131
220
|
if (!this._rotate && mapProto.mapBoundsToContainerBounds) {
|
|
@@ -151,14 +240,13 @@ L.Map.include({
|
|
|
151
240
|
},
|
|
152
241
|
|
|
153
242
|
/**
|
|
154
|
-
*
|
|
243
|
+
* Return the geographic bounds of the current viewport, accounting for rotation and pitch.
|
|
155
244
|
*
|
|
156
|
-
*
|
|
157
|
-
* function should match the `rotatePane` or `norotatePane` bounds
|
|
245
|
+
* The result is the smallest geographic bounds containing all four viewport corners.
|
|
158
246
|
*
|
|
159
|
-
* @
|
|
247
|
+
* @returns {L.LatLngBounds} Geographic bounds visible in the map viewport.
|
|
160
248
|
*
|
|
161
|
-
* @
|
|
249
|
+
* @since leaflet-rotate (v0.1.0)
|
|
162
250
|
*/
|
|
163
251
|
getBounds() {
|
|
164
252
|
if (!this._rotate) {
|
|
@@ -175,15 +263,66 @@ L.Map.include({
|
|
|
175
263
|
|
|
176
264
|
// LatLngBounds' constructor automatically
|
|
177
265
|
// extends the bounds to fit the passed points
|
|
178
|
-
const
|
|
266
|
+
const [min, max] = this._getPitchedContainerBounds(0);
|
|
179
267
|
return new L.LatLngBounds([
|
|
180
|
-
this.containerPointToLatLng([
|
|
181
|
-
this.containerPointToLatLng([
|
|
182
|
-
this.containerPointToLatLng([
|
|
183
|
-
this.containerPointToLatLng([
|
|
268
|
+
this.containerPointToLatLng([min.x, min.y]), // topleft
|
|
269
|
+
this.containerPointToLatLng([max.x, min.y]), // topright
|
|
270
|
+
this.containerPointToLatLng([max.x, max.y]), // bottomright
|
|
271
|
+
this.containerPointToLatLng([min.x, max.y]), // bottomleft
|
|
184
272
|
]);
|
|
185
273
|
},
|
|
186
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Return the container rectangle covering the visible map plane, optionally padded by a
|
|
277
|
+
* fraction of the map size. When pitched, its top is cut at the far plane (see `pitchMaxDistance`)
|
|
278
|
+
* so that it never reaches the horizon, where the plane would extend to infinity.
|
|
279
|
+
*
|
|
280
|
+
* @param {number} padding Padding as a fraction of the map size.
|
|
281
|
+
*
|
|
282
|
+
* @returns {L.Point[]} Top-left and bottom-right container points.
|
|
283
|
+
*
|
|
284
|
+
* @since leaflet-rotate (0.5.1)
|
|
285
|
+
*/
|
|
286
|
+
_getPitchedContainerBounds(padding) {
|
|
287
|
+
const size = this.getSize(),
|
|
288
|
+
min = size.multiplyBy(-padding),
|
|
289
|
+
max = size.multiplyBy(1 + padding);
|
|
290
|
+
if (this._pitch) {
|
|
291
|
+
min.y = Math.min(Math.max(min.y, this._getPitchFarY()), max.y);
|
|
292
|
+
}
|
|
293
|
+
return [min, max];
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Return the container y coordinate of the far plane: points above it are farther than
|
|
298
|
+
* `pitchMaxDistance` from the map center, or beyond the horizon.
|
|
299
|
+
*
|
|
300
|
+
* @returns {number} Container y coordinate (`-Infinity` when the whole map is close enough).
|
|
301
|
+
*
|
|
302
|
+
* @since leaflet-rotate (0.5.1)
|
|
303
|
+
*/
|
|
304
|
+
_getPitchFarY() {
|
|
305
|
+
const size = this.getSize(),
|
|
306
|
+
inverse = this._pitchInverseTransform,
|
|
307
|
+
panePos = this._getMapPanePos(),
|
|
308
|
+
x = size.x / 2 - panePos.x,
|
|
309
|
+
centerY = size.y / 2 - panePos.y,
|
|
310
|
+
maxDistance = this.options.pitchMaxDistance * size.y,
|
|
311
|
+
w = y => inverse.g * x + inverse.h * y + inverse.i,
|
|
312
|
+
centerW = w(centerY),
|
|
313
|
+
center = this._transformPitchPoint([x, centerY], inverse),
|
|
314
|
+
// the horizon is horizontal on screen (bearing is applied before the pitch)
|
|
315
|
+
isNear = y => w(y) * centerW > 0 && this._transformPitchPoint([x, y], inverse).distanceTo(center) <= maxDistance;
|
|
316
|
+
|
|
317
|
+
let far = -size.y - panePos.y, near = centerY;
|
|
318
|
+
if (isNear(far)) { return -Infinity; }
|
|
319
|
+
for (let i = 0; i < 20; i++) {
|
|
320
|
+
const mid = (far + near) / 2;
|
|
321
|
+
if (isNear(mid)) { near = mid; } else { far = mid; }
|
|
322
|
+
}
|
|
323
|
+
return near + panePos.y;
|
|
324
|
+
},
|
|
325
|
+
|
|
187
326
|
/**
|
|
188
327
|
* Returns the bounds of the current map view in projected pixel
|
|
189
328
|
* coordinates (sometimes useful in layer and overlay implementations).
|
|
@@ -202,86 +341,358 @@ L.Map.include({
|
|
|
202
341
|
// },
|
|
203
342
|
|
|
204
343
|
/**
|
|
205
|
-
*
|
|
344
|
+
* Set the map bearing, optionally animating to the new value.
|
|
345
|
+
*
|
|
346
|
+
* @param {number} theta Target bearing in degrees; normalized to [0, 360).
|
|
347
|
+
* @param {Object} [options] Animation options.
|
|
348
|
+
* @param {boolean} [options.animate=false] Animate the change.
|
|
349
|
+
* @param {number} [options.duration=0.25] Animation duration in seconds.
|
|
350
|
+
* @param {number} [options.easeLinearity=0.5] Easing curve linearity.
|
|
206
351
|
*
|
|
207
|
-
* @
|
|
208
|
-
* @param {Object} [options] animation options
|
|
209
|
-
* @param {boolean} [options.animate=false]
|
|
210
|
-
* @param {number} [options.duration=0.25] animation duration in seconds
|
|
211
|
-
* @param {number} [options.easeLinearity=0.5]
|
|
352
|
+
* @returns {void}
|
|
212
353
|
*
|
|
213
|
-
* @
|
|
354
|
+
* @fires L.Map#rotate
|
|
355
|
+
*
|
|
356
|
+
* @since leaflet-rotate (v0.1.0)
|
|
214
357
|
*/
|
|
215
358
|
setBearing(theta, options) {
|
|
216
359
|
if (!this._rotate) { return; }
|
|
217
360
|
|
|
218
361
|
if (options && options.animate) {
|
|
219
|
-
this.
|
|
220
|
-
const animationId = this._bearingAnimationId;
|
|
221
|
-
if (this._bearingAnimationFrame) {
|
|
222
|
-
cancelAnimationFrame(this._bearingAnimationFrame);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const startBearing = this.getBearing(),
|
|
226
|
-
endBearing = L.Util.wrapNum(theta, [0, 360]),
|
|
227
|
-
delta = ((endBearing - startBearing + 540) % 360) - 180,
|
|
228
|
-
duration = Math.max(0, options.duration == null ? 0.25 : options.duration) * 1000,
|
|
229
|
-
easeLinearity = Math.max(options.easeLinearity || 0.5, 0.2),
|
|
230
|
-
startTime = performance.now();
|
|
231
|
-
|
|
232
|
-
const animate = function() {
|
|
233
|
-
if (!this._mapPane) {
|
|
234
|
-
delete this._bearingAnimationFrame;
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const progress = duration ? Math.min((performance.now() - startTime) / duration, 1) : 1,
|
|
239
|
-
eased = 1 - Math.pow(1 - progress, 1 / easeLinearity);
|
|
240
|
-
|
|
241
|
-
this._setBearing(progress === 1 ? endBearing : startBearing + delta * eased);
|
|
242
|
-
if (this._bearingAnimationId !== animationId) { return; }
|
|
243
|
-
if (progress < 1) {
|
|
244
|
-
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
245
|
-
} else {
|
|
246
|
-
delete this._bearingAnimationFrame;
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
362
|
+
this._animatePitchBearing(this.getPitch(), theta, options);
|
|
251
363
|
return;
|
|
252
364
|
}
|
|
253
365
|
|
|
366
|
+
this._stopPitchBearingAnimation();
|
|
367
|
+
this._setBearing(theta);
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Animate pitch and bearing together (bearing follows the shortest path).
|
|
372
|
+
*
|
|
373
|
+
* @param {number} pitch Target pitch in degrees.
|
|
374
|
+
* @param {number} bearing Target bearing in degrees.
|
|
375
|
+
* @param {Object} [options] Animation options.
|
|
376
|
+
* @param {number} [options.duration=0.25] Animation duration in seconds.
|
|
377
|
+
* @param {number} [options.easeLinearity=0.5] Easing curve linearity.
|
|
378
|
+
*
|
|
379
|
+
* @since leaflet-rotate (0.5.1)
|
|
380
|
+
*/
|
|
381
|
+
_animatePitchBearing(pitch, bearing, options) {
|
|
382
|
+
this._stopPitchBearingAnimation();
|
|
383
|
+
const animationId = this._bearingAnimationId;
|
|
384
|
+
|
|
385
|
+
const startBearing = this.getBearing(),
|
|
386
|
+
startPitch = this.getPitch(),
|
|
387
|
+
endBearing = L.Util.wrapNum(bearing, [0, 360]),
|
|
388
|
+
endPitch = Math.max(0, Math.min(pitch, 85)),
|
|
389
|
+
delta = ((endBearing - startBearing + 540) % 360) - 180,
|
|
390
|
+
duration = Math.max(0, options.duration == null ? 0.25 : options.duration) * 1000,
|
|
391
|
+
easeLinearity = Math.max(options.easeLinearity || 0.5, 0.2),
|
|
392
|
+
startTime = performance.now();
|
|
393
|
+
|
|
394
|
+
const animate = function() {
|
|
395
|
+
if (!this._mapPane || this._bearingAnimationId !== animationId) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const progress = duration ? Math.min((performance.now() - startTime) / duration, 1) : 1,
|
|
400
|
+
eased = 1 - Math.pow(1 - progress, 1 / easeLinearity);
|
|
401
|
+
|
|
402
|
+
if (progress < 1) {
|
|
403
|
+
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
404
|
+
} else {
|
|
405
|
+
delete this._bearingAnimationFrame;
|
|
406
|
+
}
|
|
407
|
+
this._setPitchAndBearing(
|
|
408
|
+
progress === 1 ? endPitch : startPitch + (endPitch - startPitch) * eased,
|
|
409
|
+
progress === 1 ? endBearing : startBearing + delta * eased
|
|
410
|
+
);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Cancel a running pitch/bearing animation.
|
|
418
|
+
*
|
|
419
|
+
* @since leaflet-rotate (0.5.1)
|
|
420
|
+
*/
|
|
421
|
+
_stopPitchBearingAnimation() {
|
|
254
422
|
this._bearingAnimationId = (this._bearingAnimationId || 0) + 1;
|
|
255
423
|
if (this._bearingAnimationFrame) {
|
|
256
424
|
cancelAnimationFrame(this._bearingAnimationFrame);
|
|
257
425
|
delete this._bearingAnimationFrame;
|
|
258
426
|
}
|
|
427
|
+
},
|
|
259
428
|
|
|
260
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Rotate the map back to north (bearing 0), animated by default.
|
|
431
|
+
*
|
|
432
|
+
* @param {Object} [options] Animation options (`animate`, `duration`, `easeLinearity`).
|
|
433
|
+
*
|
|
434
|
+
* @returns {L.Map} This map instance.
|
|
435
|
+
*
|
|
436
|
+
* @since leaflet-rotate (0.5.1)
|
|
437
|
+
*/
|
|
438
|
+
resetNorth(options) {
|
|
439
|
+
options = Object.assign({ animate: true, duration: 0.5 }, options);
|
|
440
|
+
if (options.animate) {
|
|
441
|
+
this._animatePitchBearing(this.getPitch(), 0, options);
|
|
442
|
+
} else {
|
|
443
|
+
this.setBearing(0);
|
|
444
|
+
}
|
|
445
|
+
return this;
|
|
261
446
|
},
|
|
262
447
|
|
|
263
|
-
|
|
448
|
+
/**
|
|
449
|
+
* Rotate the map back to north and remove the pitch, animated by default.
|
|
450
|
+
*
|
|
451
|
+
* @param {Object} [options] Animation options (`animate`, `duration`, `easeLinearity`).
|
|
452
|
+
*
|
|
453
|
+
* @returns {L.Map} This map instance.
|
|
454
|
+
*
|
|
455
|
+
* @since leaflet-rotate (0.5.1)
|
|
456
|
+
*/
|
|
457
|
+
resetNorthPitch(options) {
|
|
458
|
+
options = Object.assign({ animate: true, duration: 0.5 }, options);
|
|
459
|
+
if (options.animate) {
|
|
460
|
+
this._animatePitchBearing(0, 0, options);
|
|
461
|
+
} else {
|
|
462
|
+
this._stopPitchBearingAnimation();
|
|
463
|
+
this._setPitchAndBearing(0, 0);
|
|
464
|
+
}
|
|
465
|
+
return this;
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Apply a bearing immediately and update the rotated pane and map coordinates.
|
|
470
|
+
*
|
|
471
|
+
* @param {number} theta Bearing in degrees.
|
|
472
|
+
* @param {L.LatLng} [mapCenter] Center to preserve while applying the change.
|
|
473
|
+
*
|
|
474
|
+
* @fires L.Map#rotate
|
|
475
|
+
*
|
|
476
|
+
* @since leaflet-rotate (v0.1.0)
|
|
477
|
+
*/
|
|
478
|
+
_setBearing(theta, mapCenter) {
|
|
479
|
+
if (!mapCenter && this._loaded) {
|
|
480
|
+
mapCenter = this.getCenter();
|
|
481
|
+
}
|
|
482
|
+
|
|
264
483
|
const bearing = L.Util.wrapNum(theta, [0, 360]) * (Math.PI / 180),
|
|
265
484
|
center = this._getPixelCenter(),
|
|
266
485
|
oldPos = this._getRotatePanePos().rotateFrom(-this._bearing, center),
|
|
267
486
|
newPos = oldPos.rotateFrom(bearing, center);
|
|
268
487
|
|
|
269
|
-
// CSS transform
|
|
270
|
-
L.DomUtil.setPosition(this._rotatePane, oldPos, bearing, center);
|
|
271
|
-
|
|
272
488
|
this._pivot = center;
|
|
273
489
|
this._bearing = bearing;
|
|
274
490
|
this._rotatePanePos = newPos;
|
|
275
491
|
|
|
492
|
+
if (this._pitch) {
|
|
493
|
+
this._rotatePane._leaflet_pos = oldPos;
|
|
494
|
+
this._updatePitchTransform();
|
|
495
|
+
} else {
|
|
496
|
+
L.DomUtil.setPosition(this._rotatePane, oldPos, bearing, center);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (mapCenter) {
|
|
500
|
+
this._pixelOrigin = this.project(mapCenter, this._zoom)
|
|
501
|
+
.subtract(this.containerPointToLayerPoint(this.getSize().divideBy(2)));
|
|
502
|
+
delete this._lastCenter;
|
|
503
|
+
this.fire('move');
|
|
504
|
+
}
|
|
505
|
+
|
|
276
506
|
this.fire('rotate');
|
|
277
507
|
},
|
|
278
508
|
|
|
279
509
|
/**
|
|
280
|
-
*
|
|
510
|
+
* Transform a point with a projective matrix, accepting DOMMatrix or its 2D inverse.
|
|
511
|
+
*
|
|
512
|
+
* @param {L.Point|number[]} point Input pixel coordinate.
|
|
513
|
+
* @param {DOMMatrix|Object} matrix Forward or inverse transformation matrix.
|
|
514
|
+
*
|
|
515
|
+
* @returns {L.Point} Transformed pixel coordinate.
|
|
516
|
+
*
|
|
517
|
+
* @since leaflet-rotate (0.5.0)
|
|
518
|
+
*/
|
|
519
|
+
_transformPitchPoint(point, matrix) {
|
|
520
|
+
point = toPoint(point);
|
|
521
|
+
if (!(matrix instanceof DOMMatrix)) {
|
|
522
|
+
const denominator = matrix.g * point.x + matrix.h * point.y + matrix.i;
|
|
523
|
+
return new L.Point(
|
|
524
|
+
(matrix.a * point.x + matrix.b * point.y + matrix.c) / denominator,
|
|
525
|
+
(matrix.d * point.x + matrix.e * point.y + matrix.f) / denominator
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
const transformed = new DOMPoint(point.x, point.y).matrixTransform(matrix);
|
|
529
|
+
return new L.Point(transformed.x / transformed.w, transformed.y / transformed.w);
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Apply the current pitch/bearing CSS transform and cache its forward and inverse matrices.
|
|
534
|
+
*
|
|
535
|
+
* @since leaflet-rotate (0.5.0)
|
|
536
|
+
*/
|
|
537
|
+
_updatePitchTransform() {
|
|
538
|
+
if (!this._pitch || !this._rotatePane) { return; }
|
|
539
|
+
|
|
540
|
+
const pivot = this._getPixelCenter(),
|
|
541
|
+
size = this.getSize(),
|
|
542
|
+
perspective = Math.max(1, size.y * 1.5),
|
|
543
|
+
position = this._rotatePane._leaflet_pos || new L.Point(0, 0),
|
|
544
|
+
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)`;
|
|
545
|
+
|
|
546
|
+
this._rotatePane.style.transformOrigin = '0 0';
|
|
547
|
+
this._rotatePane.style.transform = transform;
|
|
548
|
+
// parse the transform directly: getComputedStyle() would force a style recalculation on every frame
|
|
549
|
+
const matrix = this._pitchTransform = new DOMMatrix(transform),
|
|
550
|
+
a = matrix.m11, b = matrix.m21, c = matrix.m41,
|
|
551
|
+
d = matrix.m12, e = matrix.m22, f = matrix.m42,
|
|
552
|
+
g = matrix.m14, h = matrix.m24, i = matrix.m44,
|
|
553
|
+
determinant = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
|
554
|
+
|
|
555
|
+
this._pitchInverseTransform = {
|
|
556
|
+
a: (e * i - f * h) / determinant,
|
|
557
|
+
b: (c * h - b * i) / determinant,
|
|
558
|
+
c: (b * f - c * e) / determinant,
|
|
559
|
+
d: (f * g - d * i) / determinant,
|
|
560
|
+
e: (a * i - c * g) / determinant,
|
|
561
|
+
f: (c * d - a * f) / determinant,
|
|
562
|
+
g: (d * h - e * g) / determinant,
|
|
563
|
+
h: (b * g - a * h) / determinant,
|
|
564
|
+
i: (a * e - b * d) / determinant,
|
|
565
|
+
};
|
|
566
|
+
this._updatePitchSky();
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Fill the area above the far plane (where no tiles are loaded) with a sky gradient.
|
|
571
|
+
* Style it through the `.leaflet-pitch-sky` class.
|
|
572
|
+
*
|
|
573
|
+
* @since leaflet-rotate (0.5.1)
|
|
574
|
+
*/
|
|
575
|
+
_updatePitchSky() {
|
|
576
|
+
const farY = this._pitch && this._pitchInverseTransform ? this._getPitchFarY() : -Infinity;
|
|
577
|
+
if (farY <= 0) {
|
|
578
|
+
if (this._pitchSky) { this._pitchSky.style.display = 'none'; }
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (!this._pitchSky) {
|
|
582
|
+
this._pitchSky = L.DomUtil.create('div', 'leaflet-pitch-sky', this._container);
|
|
583
|
+
Object.assign(this._pitchSky.style, {
|
|
584
|
+
position: 'absolute', left: 0, top: 0, width: '100%', zIndex: 0, pointerEvents: 'none',
|
|
585
|
+
background: 'linear-gradient(#9ec5e8, #dfe9f0)',
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
this._pitchSky.style.display = '';
|
|
589
|
+
// overlap the far edge slightly to hide antialiasing gaps
|
|
590
|
+
this._pitchSky.style.height = `${Math.ceil(farY) + 1}px`;
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Set the map pitch in degrees. Values are clamped to the range 0–85.
|
|
595
|
+
*
|
|
596
|
+
* @param {number} pitch Pitch angle in degrees; clamped to [0, 85].
|
|
597
|
+
* @param {Object} [options] Animation options (`animate`, `duration`, `easeLinearity`).
|
|
598
|
+
*
|
|
599
|
+
* @returns {L.Map} This map instance.
|
|
600
|
+
*
|
|
601
|
+
* @fires L.Map#pitch
|
|
602
|
+
*
|
|
603
|
+
* @since leaflet-rotate (0.5.0)
|
|
604
|
+
*/
|
|
605
|
+
setPitch(pitch, options) {
|
|
606
|
+
if (options && options.animate) {
|
|
607
|
+
this._animatePitchBearing(pitch, this.getBearing(), options);
|
|
608
|
+
return this;
|
|
609
|
+
}
|
|
610
|
+
this._stopPitchBearingAnimation();
|
|
611
|
+
this._setPitchAndBearing(pitch, this.getBearing());
|
|
612
|
+
return this;
|
|
613
|
+
},
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Update pitch and bearing together, preserving the current map center.
|
|
617
|
+
* Used by the Ctrl+drag handler to avoid applying two separate transforms.
|
|
618
|
+
*
|
|
619
|
+
* @param {number} pitch Pitch angle in degrees.
|
|
620
|
+
* @param {number} bearing Bearing angle in degrees.
|
|
621
|
+
*
|
|
622
|
+
* @fires L.Map#pitch
|
|
623
|
+
* @fires L.Map#rotate
|
|
624
|
+
*
|
|
625
|
+
* @since leaflet-rotate (0.5.0)
|
|
626
|
+
*/
|
|
627
|
+
_setPitchAndBearing(pitch, bearing) {
|
|
628
|
+
if (!this._rotate) { return; }
|
|
629
|
+
|
|
630
|
+
const clampedPitch = Math.max(0, Math.min(pitch, 85)) * (Math.PI / 180),
|
|
631
|
+
clampedBearing = L.Util.wrapNum(bearing, [0, 360]) * (Math.PI / 180),
|
|
632
|
+
pitchChanged = clampedPitch !== this._pitch;
|
|
633
|
+
if (!pitchChanged && clampedBearing === this._bearing) { return; }
|
|
634
|
+
|
|
635
|
+
const mapCenter = this._loaded ? this.getCenter() : undefined;
|
|
636
|
+
if (pitchChanged) {
|
|
637
|
+
this._pitch = clampedPitch;
|
|
638
|
+
this._rotatePane.style.transformOrigin = L.version.startsWith('2.') ? '0 0' : '';
|
|
639
|
+
}
|
|
640
|
+
this._setBearing(bearing, mapCenter);
|
|
641
|
+
if (pitchChanged) {
|
|
642
|
+
if (!this._pitch) { this._updatePitchSky(); }
|
|
643
|
+
this.fire('pitch');
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Get the current map pitch in degrees.
|
|
649
|
+
*
|
|
650
|
+
* @returns {number} Pitch angle in degrees.
|
|
651
|
+
*
|
|
652
|
+
* @since leaflet-rotate (0.5.0)
|
|
653
|
+
*/
|
|
654
|
+
getPitch() {
|
|
655
|
+
return (this._pitch || 0) * (180 / Math.PI);
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Zoom while keeping the given point fixed on screen, also when the map is pitched.
|
|
660
|
+
*
|
|
661
|
+
* Leaflet interpolates in container space, which is not linear under perspective;
|
|
662
|
+
* interpolating in layer space keeps the point under the cursor.
|
|
663
|
+
*
|
|
664
|
+
* @param {L.LatLng|L.Point} latlng Geographic point or container point to keep fixed.
|
|
665
|
+
* @param {number} zoom Target zoom level.
|
|
666
|
+
* @param {Object} [options] Zoom options.
|
|
667
|
+
*
|
|
668
|
+
* @returns {L.Map} This map instance.
|
|
669
|
+
*
|
|
670
|
+
* @since leaflet-rotate (0.5.1)
|
|
671
|
+
*/
|
|
672
|
+
setZoomAround(latlng, zoom, options) {
|
|
673
|
+
if (!this._pitch) {
|
|
674
|
+
return mapProto.setZoomAround.apply(this, arguments);
|
|
675
|
+
}
|
|
676
|
+
const scale = this.getZoomScale(zoom),
|
|
677
|
+
size = this.getSize(),
|
|
678
|
+
center = this.containerPointToLayerPoint(size.divideBy(2)),
|
|
679
|
+
containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
|
|
680
|
+
point = this.containerPointToLayerPoint(containerPoint),
|
|
681
|
+
newCenter = this.layerPointToLatLng(center.add(point.subtract(center).multiplyBy(1 - 1 / scale)));
|
|
682
|
+
options = Object.assign({}, options);
|
|
683
|
+
// Leaflet skips the animation when the origin is "far" in layer space, which is always the case near the horizon.
|
|
684
|
+
if (options.animate === undefined && new L.Bounds(new L.Point(0, 0), size).contains(containerPoint)) {
|
|
685
|
+
options.animate = true;
|
|
686
|
+
}
|
|
687
|
+
return this.setView(newCenter, zoom, { zoom: options });
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Get the current map bearing. Enable the `rotate` map option before calling.
|
|
281
692
|
*
|
|
282
|
-
* @returns {number}
|
|
693
|
+
* @returns {number} Bearing in degrees, normalized to [0, 360).
|
|
283
694
|
*
|
|
284
|
-
* @since leaflet-rotate (v0.1)
|
|
695
|
+
* @since leaflet-rotate (v0.1.0)
|
|
285
696
|
*/
|
|
286
697
|
getBearing() {
|
|
287
698
|
return this._bearing * (180 / Math.PI);
|
|
@@ -294,7 +705,10 @@ L.Map.include({
|
|
|
294
705
|
*
|
|
295
706
|
* @param {String} name leaflet pane
|
|
296
707
|
* @param {HTMLElement} [container] parent element
|
|
708
|
+
*
|
|
297
709
|
* @returns {HTMLElement} pane container
|
|
710
|
+
*
|
|
711
|
+
* @since leaflet-rotate (v0.1.0)
|
|
298
712
|
*/
|
|
299
713
|
// createPane(name, container) {
|
|
300
714
|
// if (!this._rotate || name == 'mapPane') {
|
|
@@ -325,6 +739,12 @@ L.Map.include({
|
|
|
325
739
|
* - tooltipPane [HTMLElement = 650] - Pane for tooltips.
|
|
326
740
|
* - popupPane [HTMLElement = 700] - Pane for popups.
|
|
327
741
|
*/
|
|
742
|
+
/**
|
|
743
|
+
* Create the map's panes, placing tile/vector layers in the transformed pane
|
|
744
|
+
* and markers/overlays in the pane that remains upright.
|
|
745
|
+
*
|
|
746
|
+
* @since leaflet-rotate (v0.1.0)
|
|
747
|
+
*/
|
|
328
748
|
_initPanes() {
|
|
329
749
|
const panes = this._panes = {};
|
|
330
750
|
this._paneRenderers = {};
|
|
@@ -369,13 +789,15 @@ L.Map.include({
|
|
|
369
789
|
*
|
|
370
790
|
* @see https://github.com/Raruto/leaflet-rotate/issues/18
|
|
371
791
|
*
|
|
372
|
-
* @param {L.LatLng} latlng
|
|
373
|
-
* @param {Object} [options={}] padding
|
|
792
|
+
* @param {L.LatLng} latlng Geographic point to keep visible.
|
|
793
|
+
* @param {Object} [options={}] Padding options (`padding`, `paddingTopLeft`, `paddingBottomRight`).
|
|
374
794
|
*
|
|
375
795
|
* @returns {L.Map} current map instance
|
|
796
|
+
*
|
|
797
|
+
* @since leaflet-rotate (v0.1.4)
|
|
376
798
|
*/
|
|
377
799
|
panInside(latlng, options) {
|
|
378
|
-
if (!this._rotate || Math.abs(this._bearing).toFixed(1) < 0.1) {
|
|
800
|
+
if (!this._rotate || (!this._pitch && Math.abs(this._bearing).toFixed(1) < 0.1)) {
|
|
379
801
|
return mapProto.panInside.apply(this, arguments);
|
|
380
802
|
}
|
|
381
803
|
|
|
@@ -419,9 +841,12 @@ L.Map.include({
|
|
|
419
841
|
*
|
|
420
842
|
* @see https://github.com/fnicollet/Leaflet/issues/7
|
|
421
843
|
*
|
|
422
|
-
* @param {L.LatLngBounds} bounds
|
|
423
|
-
* @param {Object} [options]
|
|
424
|
-
*
|
|
844
|
+
* @param {L.LatLngBounds} bounds Geographic bounds to fit inside the viewport.
|
|
845
|
+
* @param {Object} [options] Pan animation options.
|
|
846
|
+
*
|
|
847
|
+
* @returns {L.Map} This map instance.
|
|
848
|
+
*
|
|
849
|
+
* @since leaflet-rotate (v0.1.0)
|
|
425
850
|
*/
|
|
426
851
|
// panInsideBounds(bounds, options) {
|
|
427
852
|
// this._enforcingBounds = true;
|
|
@@ -490,14 +915,16 @@ L.Map.include({
|
|
|
490
915
|
* instead returns the minimum zoom level on which the map view fits into
|
|
491
916
|
* the given bounds in its entirety.
|
|
492
917
|
*
|
|
493
|
-
* @param {L.LatLngBounds} bounds
|
|
494
|
-
* @param {
|
|
495
|
-
* @param {L.Point} [padding=[0,0]]
|
|
918
|
+
* @param {L.LatLngBounds} bounds Geographic bounds to fit.
|
|
919
|
+
* @param {boolean} [inside=false] Fit the viewport inside bounds instead of bounds inside the viewport.
|
|
920
|
+
* @param {L.Point|number[]} [padding=[0,0]] Pixel padding around the viewport.
|
|
921
|
+
*
|
|
922
|
+
* @returns {number} Zoom level satisfying the fit.
|
|
496
923
|
*
|
|
497
|
-
* @
|
|
924
|
+
* @since leaflet-rotate (v0.1.0)
|
|
498
925
|
*/
|
|
499
926
|
getBoundsZoom(bounds, inside, padding) {
|
|
500
|
-
if (!this._rotate || Math.abs(this._bearing).toFixed(1) < 0.1) {
|
|
927
|
+
if (!this._rotate || (!this._pitch && Math.abs(this._bearing).toFixed(1) < 0.1)) {
|
|
501
928
|
return mapProto.getBoundsZoom.apply(this, arguments);
|
|
502
929
|
}
|
|
503
930
|
|
|
@@ -539,20 +966,27 @@ L.Map.include({
|
|
|
539
966
|
// },
|
|
540
967
|
|
|
541
968
|
/**
|
|
542
|
-
*
|
|
969
|
+
* Return the pixel offset from the current center to the given geographic point.
|
|
543
970
|
*
|
|
544
|
-
* @param {L.LatLng} latlng
|
|
971
|
+
* @param {L.LatLng} latlng Geographic point.
|
|
972
|
+
*
|
|
973
|
+
* @returns {L.Point} Offset in container pixels (the map pane is panned in screen space), adjusted for bearing and pitch.
|
|
974
|
+
*
|
|
975
|
+
* @since leaflet-rotate (v0.1.0)
|
|
545
976
|
*/
|
|
546
977
|
_getCenterOffset(latlng) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
centerOffset = centerOffset.rotate(this._bearing);
|
|
978
|
+
if (!this._rotate) {
|
|
979
|
+
return mapProto._getCenterOffset.apply(this, arguments);
|
|
550
980
|
}
|
|
551
|
-
return
|
|
981
|
+
return this.latLngToContainerPoint(latlng)._subtract(this.getSize()._divideBy(2));
|
|
552
982
|
},
|
|
553
983
|
|
|
554
984
|
/**
|
|
555
|
-
*
|
|
985
|
+
* Return the current position of the rotated pane.
|
|
986
|
+
*
|
|
987
|
+
* @returns {L.Point} Rotated-pane position in map-pane coordinates.
|
|
988
|
+
*
|
|
989
|
+
* @since leaflet-rotate (v0.1.0)
|
|
556
990
|
*/
|
|
557
991
|
_getRotatePanePos() {
|
|
558
992
|
return this._rotatePanePos || new L.Point(0, 0);
|
|
@@ -564,22 +998,36 @@ L.Map.include({
|
|
|
564
998
|
// return this.project(latlng, zoom)._subtract(topLeft);
|
|
565
999
|
//},
|
|
566
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* Calculate the pixel origin for a target view while preserving bearing-aware alignment.
|
|
1003
|
+
*
|
|
1004
|
+
* @param {L.LatLng} center Target center.
|
|
1005
|
+
* @param {number} zoom Target zoom level.
|
|
1006
|
+
*
|
|
1007
|
+
* @returns {L.Point} New pixel origin.
|
|
1008
|
+
*
|
|
1009
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1010
|
+
*/
|
|
567
1011
|
_getNewPixelOrigin(center, zoom) {
|
|
568
|
-
if (!this._rotate
|
|
1012
|
+
if (!this._rotate) {
|
|
569
1013
|
return mapProto._getNewPixelOrigin.apply(this, arguments);
|
|
570
1014
|
}
|
|
571
|
-
|
|
1015
|
+
// the layer point under the view center doesn't depend on zoom (pane transforms only rotate/pitch around the pivot),
|
|
1016
|
+
// so this matches `_setBearing` also when the pivot is no longer the view center (eg. after panning)
|
|
572
1017
|
return this.project(center, zoom)
|
|
573
|
-
.
|
|
574
|
-
._subtract(viewHalf)
|
|
575
|
-
._add(this._getMapPanePos())
|
|
576
|
-
._add(this._getRotatePanePos())
|
|
577
|
-
.rotate(-this._bearing)
|
|
1018
|
+
._subtract(this.containerPointToLayerPoint(this.getSize()._divideBy(2)))
|
|
578
1019
|
._round();
|
|
579
1020
|
},
|
|
580
1021
|
|
|
581
1022
|
/**
|
|
582
|
-
*
|
|
1023
|
+
* Calculate the pixel bounds needed to cover the transformed viewport.
|
|
1024
|
+
*
|
|
1025
|
+
* @param {L.LatLng} [center=this.getCenter()] Target center.
|
|
1026
|
+
* @param {number} [zoom=this.getZoom()] Target zoom level.
|
|
1027
|
+
*
|
|
1028
|
+
* @returns {L.Bounds} Pixel bounds in projected map coordinates.
|
|
1029
|
+
*
|
|
1030
|
+
* @since leaflet-rotate (v0.2.0)
|
|
583
1031
|
*
|
|
584
1032
|
* @see src\layer\tile\GridLayer::_getTiledPixelBounds()
|
|
585
1033
|
*/
|
|
@@ -592,21 +1040,25 @@ L.Map.include({
|
|
|
592
1040
|
const mapZoom = this._animatingZoom ? Math.max(this._animateToZoom, this.getZoom()) : this.getZoom(),
|
|
593
1041
|
scale = this.getZoomScale(mapZoom, zoom),
|
|
594
1042
|
pixelCenter = this.project(center, zoom).floor(),
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
1043
|
+
[min, max] = this._getPitchedContainerBounds(0),
|
|
1044
|
+
corners = [[min.x, min.y], [max.x, min.y], [min.x, max.y], [max.x, max.y]]
|
|
1045
|
+
.map(point => this.containerPointToLayerPoint(point));
|
|
1046
|
+
|
|
1047
|
+
if (this._pitch) {
|
|
1048
|
+
// the pitched view is not symmetric: the far side covers much more than the near one
|
|
1049
|
+
const viewCenter = this.containerPointToLayerPoint(this.getSize()._divideBy(2));
|
|
1050
|
+
return new L.Bounds(corners.map(point => pixelCenter.add(point.subtract(viewCenter).divideBy(scale))));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const halfSize = new L.Bounds(corners.map(point => point.floor())).getSize().divideBy(scale * 2);
|
|
602
1054
|
|
|
603
1055
|
return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
|
|
604
1056
|
},
|
|
605
1057
|
|
|
606
1058
|
/**
|
|
607
|
-
* @since leaflet-rotate (v0.2)
|
|
1059
|
+
* @since leaflet-rotate (v0.2.0)
|
|
608
1060
|
*
|
|
609
|
-
* @
|
|
1061
|
+
* @returns {L.Point} Map pivot point in pixel coordinates.
|
|
610
1062
|
*/
|
|
611
1063
|
_getPixelCenter() {
|
|
612
1064
|
if (!this._rotate && mapProto._getPixelCenter) {
|
|
@@ -616,19 +1068,19 @@ L.Map.include({
|
|
|
616
1068
|
},
|
|
617
1069
|
|
|
618
1070
|
/**
|
|
619
|
-
* @since leaflet-rotate (v0.2)
|
|
620
|
-
*
|
|
621
1071
|
* @see src\layer\vector\Renderer::_update()
|
|
1072
|
+
*
|
|
1073
|
+
* @param {number} padding Bounds padding as a fraction of map size.
|
|
1074
|
+
*
|
|
1075
|
+
* @returns {L.Bounds} Padded bounds in layer-point coordinates.
|
|
1076
|
+
*
|
|
1077
|
+
* @since leaflet-rotate (v0.2.0)
|
|
622
1078
|
*/
|
|
623
1079
|
_getPaddedPixelBounds(padding) {
|
|
624
1080
|
if (!this._rotate && mapProto._getPaddedPixelBounds) {
|
|
625
1081
|
return mapProto._getPaddedPixelBounds.apply(this, arguments);
|
|
626
1082
|
}
|
|
627
|
-
const
|
|
628
|
-
size = this.getSize(),
|
|
629
|
-
padMin = size.multiplyBy(-p),
|
|
630
|
-
padMax = size.multiplyBy(1 + p);
|
|
631
|
-
//min = this.containerPointToLayerPoint(size.multiplyBy(-p)).round();
|
|
1083
|
+
const [padMin, padMax] = this._getPitchedContainerBounds(padding);
|
|
632
1084
|
|
|
633
1085
|
return new L.Bounds([
|
|
634
1086
|
this.containerPointToLayerPoint([padMin.x, padMin.y]).floor(),
|
|
@@ -638,6 +1090,15 @@ L.Map.include({
|
|
|
638
1090
|
]);
|
|
639
1091
|
},
|
|
640
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Build and fire Leaflet's location-found event, including the device heading.
|
|
1095
|
+
*
|
|
1096
|
+
* @param {GeolocationPosition} pos Browser geolocation result.
|
|
1097
|
+
*
|
|
1098
|
+
* @fires L.Map#locationfound
|
|
1099
|
+
*
|
|
1100
|
+
* @since leaflet-rotate (v0.1.0)
|
|
1101
|
+
*/
|
|
641
1102
|
_handleGeolocationResponse(pos) {
|
|
642
1103
|
if (!this._container._leaflet_id) { return; }
|
|
643
1104
|
|
|
@@ -678,6 +1139,7 @@ L.Map.include({
|
|
|
678
1139
|
* @see https://github.com/ronikar/Leaflet/blob/5c480ef959b947c3beed7065425a5a36c486262b/src/geo/LatLngBounds.js#L253-L264
|
|
679
1140
|
*
|
|
680
1141
|
* @param {L.Bounds} points
|
|
1142
|
+
*
|
|
681
1143
|
* @returns {L.Bounds}
|
|
682
1144
|
*/
|
|
683
1145
|
// toCircumscribedBounds(points) {
|