Adding a PinchZoom control for smooth zooming on multi-touch devices. p=bbinet,me r=crschmidt (closes #3105)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@11544 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Tim Schaub
2011-02-26 02:22:45 +00:00
parent 1425c2b0e6
commit f785153bca
7 changed files with 326 additions and 9 deletions

View File

@@ -182,6 +182,7 @@
"OpenLayers/Control/ZoomToMaxExtent.js",
"OpenLayers/Control/DragPan.js",
"OpenLayers/Control/Navigation.js",
"OpenLayers/Control/PinchZoom.js",
"OpenLayers/Control/TouchNavigation.js",
"OpenLayers/Control/MouseDefaults.js",
"OpenLayers/Control/MousePosition.js",

View File

@@ -0,0 +1,192 @@
/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the Clear BSD license.
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Handler/Pinch.js
*/
/**
* Class: OpenLayers.Control.PinchZoom
*
* Inherits:
* - <OpenLayers.Control>
*/
OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
/**
* Property: type
* {OpenLayers.Control.TYPES}
*/
type: OpenLayers.Control.TYPE_TOOL,
/**
* Property: containerOrigin
* {Object} Cached object representing the layer container origin (in pixels).
*/
containerOrigin: null,
/**
* Property: pinchOrigin
* {Object} Cached object representing the pinch start (in pixels).
*/
pinchOrigin: null,
/**
* Property: currentCenter
* {Object} Cached object representing the latest pinch center (in pixels).
*/
currentCenter: null,
/**
* APIProperty: autoActivate
* {Boolean} Activate the control when it is added to a map. Default is
* true.
*/
autoActivate: true,
/**
* Constructor: OpenLayers.Control.PinchZoom
* Create a control for zooming with pinch gestures. This works on devices
* with multi-touch support.
*
* Parameters:
* options - {Object} An optional object whose properties will be set on
* the control
*/
initialize: function(options) {
OpenLayers.Control.prototype.initialize.apply(this, arguments);
this.handler = new OpenLayers.Handler.Pinch(this, {
start: this.pinchStart,
move: this.pinchMove,
done: this.pinchDone
}, this.handlerOptions);
},
/**
* APIMethod: activate
* Activate this control. Must be called after the control is added to a
* map.
*
* Returns:
* {Boolean} The control was successfully activated.
*/
activate: function() {
var activated = OpenLayers.Control.prototype.activate.apply(this,arguments);
if (activated) {
this.map.events.on({
moveend: this.updateContainerOrigin,
scope: this
});
this.updateContainerOrigin();
}
return activated;
},
/**
* APIMethod: deactivate
* Deactivate this control.
*
* Returns:
* {Boolean} The control was successfully deactivated.
*/
deactivate: function() {
var deactivated = OpenLayers.Control.prototype.deactivate.apply(this,arguments);
if (this.map && this.map.events) {
this.map.events.un({
moveend: this.updateContainerOrigin,
scope: this
});
}
return deactivated;
},
/**
* Method: updateContainerOrigin
* Must be called each time the layer container origin changes.
*/
updateContainerOrigin: function() {
var container = this.map.layerContainerDiv;
this.containerOrigin = {
x: parseInt(container.style.left, 10),
y: parseInt(container.style.top, 10)
};
},
/**
* Method: pinchStart
*
* Parameters:
* evt - {Event}
* pinchData - {Object} pinch data object related to the current touchmove
* of the pinch gesture. This give us the current scale of the pinch.
*/
pinchStart: function(evt, pinchData) {
this.pinchOrigin = evt.xy;
},
/**
* Method: pinchMove
*
* Parameters:
* evt - {Event}
* pinchData - {Object} pinch data object related to the current touchmove
* of the pinch gesture. This give us the current scale of the pinch.
*/
pinchMove: function(evt, pinchData) {
var scale = pinchData.scale;
var containerOrigin = this.containerOrigin;
var pinchOrigin = this.pinchOrigin;
var current = evt.xy;
var dx = Math.round((current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
var dy = Math.round((current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
this.applyTransform(
"translate(" + dx + "px, " + dy + "px) scale(" + scale + ")"
);
this.currentCenter = current;
},
/**
* Method: applyTransform
* Applies the given transform to layers.
*/
applyTransform: function(transform) {
var style = this.map.layerContainerDiv.style;
style['-webkit-transform'] = transform;
style['-moz-transform'] = transform;
},
/**
* Method: pinchDone
*
* Parameters:
* evt - {Event}
* start - {Object} pinch data object related to the touchstart event that
* started the pinch gesture.
* last - {Object} pinch data object related to the last touchmove event
* of the pinch gesture. This give us the final scale of the pinch.
*/
pinchDone: function(evt, start, last) {
var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
var resolution = this.map.getResolutionForZoom(zoom);
var location = this.map.getLonLatFromPixel(this.pinchOrigin);
var zoomPixel = this.currentCenter;
var size = this.map.getSize();
location.lon += resolution * ((size.w / 2) - zoomPixel.x);
location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
this.map.setCenter(location, zoom);
var style = this.map.layerContainerDiv.style;
style['-webkit-transform'] = "";
style['-moz-transform'] = "";
},
CLASS_NAME: "OpenLayers.Control.PinchZoom"
});

View File

@@ -5,14 +5,15 @@
/**
* @requires OpenLayers/Control/DragPan.js
* @requires OpenLayers/Control/PinchZoom.js
* @requires OpenLayers/Handler/Click.js
*/
/**
* Class: OpenLayers.Control.TouchNavigation
* The navigation control handles map browsing with touch events (dragging,
* double-tapping, and tap with two fingers). Create a new navigation
* control with the <OpenLayers.Control.TouchNavigation> control.
* double-tapping, tap with two fingers, and pinch zoom). Create a new
* control with the <OpenLayers.Control.TouchNavigation> constructor.
*
* Inherits:
* - <OpenLayers.Control>
@@ -31,6 +32,18 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
*/
dragPanOptions: null,
/**
* Property: pinchZoom
* {<OpenLayers.Control.PinchZoom>}
*/
pinchZoom: null,
/**
* APIProprety: pinchZoomOptions
* {Object} Options passed to the PinchZoom control.
*/
pinchZoomOptions: null,
/**
* APIProperty: documentDrag
* {Boolean} Allow panning of the map by dragging outside map viewport.
@@ -70,6 +83,10 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
this.dragPan.destroy();
}
this.dragPan = null;
if (this.pinchZoom) {
this.pinchZoom.destroy();
delete this.pinchZoom;
}
OpenLayers.Control.prototype.destroy.apply(this,arguments);
},
@@ -80,6 +97,7 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
this.dragPan.activate();
this.handlers.click.activate();
this.pinchZoom.activate();
return true;
}
return false;
@@ -92,6 +110,7 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
this.dragPan.deactivate();
this.handlers.click.deactivate();
this.pinchZoom.deactivate();
return true;
}
return false;
@@ -119,6 +138,9 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
}, this.dragPanOptions)
);
this.dragPan.draw();
this.pinchZoom = new OpenLayers.Control.PinchZoom(
OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
);
},
/**

View File

@@ -821,10 +821,20 @@ OpenLayers.Events = OpenLayers.Class({
// noone's listening, bail out
return;
}
// add clientX & clientY to all events - only corresponds to the first touch
if (evt.touches && evt.touches[0]) {
evt.clientX = evt.touches[0].clientX;
evt.clientY = evt.touches[0].clientY;
// add clientX & clientY to all events - corresponds to average x, y
var touches = evt.touches;
if (touches && touches[0]) {
var x = 0;
var y = 0;
var num = touches.length;
var touch;
for (var i=0; i<num; ++i) {
touch = touches[i];
x += touch.clientX;
y += touch.clientY;
}
evt.clientX = x / num;
evt.clientY = y / num;
}
if (this.includeXY) {
evt.xy = this.getMousePosition(evt);

View File

@@ -120,13 +120,15 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
* {Boolean} Let the event propagate.
*/
touchmove: function(evt) {
var propagate = true;
if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
this.pinching = true;
var current = this.getPinchData(evt);
this.callback("move", [evt, current]);
this.last = current;
propagate = false;
}
return true;
return propagate;
},
/**
@@ -140,14 +142,16 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
* {Boolean} Let the event propagate.
*/
touchend: function(evt) {
var propagate = true;
if (this.started) {
this.started = false;
this.pinching = false;
this.callback("done", [evt, this.start, this.last]);
this.start = null;
this.last = null;
propagate = false;
}
return true;
return propagate;
},
/**

View File

@@ -0,0 +1,87 @@
<html>
<head>
<script src="../OLLoader.js"></script>
<script type="text/javascript">
function test_constructor(t) {
t.plan(2);
var control = new OpenLayers.Control.PinchZoom();
t.ok(control instanceof OpenLayers.Control.PinchZoom, "got an instance");
t.ok(control.handler instanceof OpenLayers.Handler.Pinch, "control has pinch handler");
control.destroy();
}
function test_destroy(t) {
t.plan(1);
var control = new OpenLayers.Control.PinchZoom();
control.destroy();
t.ok(!control.handler, "handler destroyed");
}
function test_activate(t) {
t.plan(3);
var control = new OpenLayers.Control.PinchZoom();
t.ok(!control.active, "control not activated after construction");
var map = new OpenLayers.Map({
div: "map",
controls: [control]
});
t.ok(control.active, "control activated after being added to the map");
control.deactivate();
t.ok(!control.active, "control deactivated");
map.destroy();
}
function test_pinchMove(t) {
var control = new OpenLayers.Control.PinchZoom();
var map = new OpenLayers.Map({
div: "map",
controls: [control]
});
var log = [];
control.applyTransform = function(transform) {
log.push(transform);
}
control.containerOrigin = {
x: 0, y: 0
};
control.pinchOrigin = {
x: 100, y: 50
};
var cases = [
{x: 100, y: 60, scale: 1, transform: "translate(0px, 10px) scale(1)"},
{x: 150, y: 60, scale: 1, transform: "translate(50px, 10px) scale(1)"},
{x: 150, y: 60, scale: 2, transform: "translate(-50px, -40px) scale(2)"},
{x: 50, y: 20, scale: 2.5, transform: "translate(-200px, -105px) scale(2.5)"},
{x: 150, y: 60, scale: 2, transform: "translate(-50px, -40px) scale(2)"},
{x: 50, y: 20, scale: 0.25, transform: "translate(25px, 8px) scale(0.25)"}
];
var len = cases.length;
t.plan(len*2);
var c;
for (var i=0; i<len; ++i) {
c = cases[i];
control.pinchMove({xy: {x: c.x, y: c.y}}, {scale: c.scale});
t.eq(log.length, i+1, i + " called once");
t.eq(log[i], c.transform, i + " correct transform");
}
}
</script>
</head>
<body>
<div id="map" style="width: 256px; height: 256px;"></div>
</body>
</html>

View File

@@ -32,6 +32,7 @@
<li>Control/PanZoom.html</li>
<li>Control/PanZoomBar.html</li>
<li>Control/Permalink.html</li>
<li>Control/PinchZoom.html</li>
<li>Control/Scale.html</li>
<li>Control/ScaleLine.html</li>
<li>Control/SelectFeature.html</li>