leaflet-rotate
Version:
A Leaflet plugin that allows to add rotation functionality to map tiles
178 lines (141 loc) • 6.06 kB
JavaScript
/**
* TouchGestures is both TouchZoom plus TouchRotate
*
* @see https://github.com/fnicollet/Leaflet/commit/a77af51a6b10f308d1b9a16552091d1d0aee8834
* @see https://github.com/Leaflet/Leaflet/blob/v1.9.3/src/map/handler/Map.TouchZoom.js
*
* @typedef L.Map.TouchGestures
*/
L.Map.mergeOptions({
/**
* Set it to false if you don't want the map to
* zoom beyond min/max zoom and then bounce back
* when pinch-zooming.
*
* @type {Boolean}
*/
bounceAtZoomLimits: true,
});
L.Map.TouchGestures = L.Handler.extend({
initialize: function(map) {
this._map = map;
this.rotate = !!this._map.options.touchRotate;
this.zoom = !!this._map.options.touchZoom;
},
addHooks: function() {
L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
},
removeHooks: function() {
L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
},
_onTouchStart: function(e) {
var map = this._map;
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) { return; }
var p1 = map.mouseEventToContainerPoint(e.touches[0]),
p2 = map.mouseEventToContainerPoint(e.touches[1]),
vector = p1.subtract(p2);
this._centerPoint = map.getSize()._divideBy(2);
this._startLatLng = map.containerPointToLatLng(this._centerPoint);
if (this.zoom) {
if (map.options.touchZoom !== 'center') {
this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
}
this._startDist = p1.distanceTo(p2);
this._startZoom = map.getZoom();
this._zooming = true;
} else {
this._zooming = false;
}
if (this.rotate) {
this._startTheta = Math.atan(vector.x / vector.y);
this._startBearing = map.getBearing();
if (vector.y < 0) { this._startBearing += 180; }
this._rotating = true;
} else {
this._rotating = false;
}
this._moved = false;
map._stop();
L.DomEvent
.on(document, 'touchmove', this._onTouchMove, this)
.on(document, 'touchend touchcancel', this._onTouchEnd, this);
L.DomEvent.preventDefault(e);
},
_onTouchMove: function(e) {
if (!e.touches || e.touches.length !== 2 || !(this._zooming || this._rotating)) { return; }
var map = this._map,
p1 = map.mouseEventToContainerPoint(e.touches[0]),
p2 = map.mouseEventToContainerPoint(e.touches[1]),
vector = p1.subtract(p2),
scale = p1.distanceTo(p2) / this._startDist,
delta;
if (this._rotating) {
var theta = Math.atan(vector.x / vector.y);
var bearingDelta = (theta - this._startTheta) * L.DomUtil.RAD_TO_DEG;
if (vector.y < 0) { bearingDelta += 180; }
if (bearingDelta) {
/**
* @TODO the pivot should be the last touch point,
* but zoomAnimation manages to overwrite the rotate
* pane position. Maybe related to #3529.
*
* @see https://github.com/Leaflet/Leaflet/pull/3529
* @see https://github.com/fnicollet/Leaflet/commit/a77af51a6b10f308d1b9a16552091d1d0aee8834
*/
map.setBearing(this._startBearing - bearingDelta);
}
}
if (this._zooming) {
this._zoom = map.getScaleZoom(scale, this._startZoom);
if (!map.options.bounceAtZoomLimits && (
(this._zoom < map.getMinZoom() && scale < 1) ||
(this._zoom > map.getMaxZoom() && scale > 1))) {
this._zoom = map._limitZoom(this._zoom);
}
if (map.options.touchZoom === 'center') {
this._center = this._startLatLng;
if (scale === 1) { return; }
} else {
// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
var alpha = -map.getBearing() * L.DomUtil.DEG_TO_RAD;
this._center = map.unproject(map.project(this._pinchStartLatLng).subtract(delta.rotate(alpha)));
}
}
if (!this._moved) {
map._moveStart(true, false);
this._moved = true;
}
L.Util.cancelAnimFrame(this._animRequest);
var moveFn = map._move.bind(map, this._center, this._zoom, { pinch: true, round: false }, undefined);
this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
L.DomEvent.preventDefault(e);
},
_onTouchEnd: function() {
if (!this._moved || !(this._zooming || this._rotating)) {
this._zooming = false;
return;
}
this._zooming = false;
this._rotating = false;
L.Util.cancelAnimFrame(this._animRequest);
L.DomEvent
.off(document, 'touchmove', this._onTouchMove, this)
.off(document, 'touchend touchcancel', this._onTouchEnd, this);
if (this.zoom) {
// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
if (this._map.options.zoomAnimation) {
this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
} else {
this._map._resetView(this._center, this._map._limitZoom(this._zoom));
}
}
},
});
/**
* Add Touch Gestures handler (enabled unless `touchGestures` is unset).
*
* @property {L.Map.TouchGestures} touchGestures
*/
L.Map.addInitHook('addHandler', 'touchGestures', L.Map.TouchGestures);