//>>built define("dojox/geo/openlayers/TouchInteractionSupport", ["dojo/_base/kernel", "dojo/_base/declare", "dojo/_base/connect", "dojo/_base/html", "dojo/_base/lang", "dojo/_base/event", "dojo/_base/window"], function(dojo, declare, connect, html, lang, event, window){ return declare("dojox.geo.openlayers.TouchInteractionSupport", null, { // summary: // class to handle touch interactions on a OpenLayers.Map widget // tags: // private _map : null, _centerTouchLocation : null, _touchMoveListener : null, _touchEndListener : null, _initialFingerSpacing : null, _initialScale : null, _tapCount : null, _tapThreshold : null, _lastTap : null, constructor : function(/* OpenLayers.Map */map){ // summary: // Constructs a new TouchInteractionSupport instance // map: OpenLayers.Map // the Map widget this class provides touch navigation for. this._map = map; this._centerTouchLocation = new OpenLayers.LonLat(0, 0); var div = this._map.div; // install touch listeners connect.connect(div, "touchstart", this, this._touchStartHandler); connect.connect(div, "touchmove", this, this._touchMoveHandler); connect.connect(div, "touchend", this, this._touchEndHandler); this._tapCount = 0; this._lastTap = { x : 0, y : 0 }; this._tapThreshold = 100; // square distance in pixels }, _getTouchBarycenter : function(touchEvent){ // summary: // returns the midpoint of the two first fingers (or the first finger location if only one) // touchEvent: Event // a touch event // returns: dojox.gfx.Point // the midpoint // tags: // private var touches = touchEvent.touches; var firstTouch = touches[0]; var secondTouch = null; if (touches.length > 1) { secondTouch = touches[1]; } else { secondTouch = touches[0]; } var marginBox = html.marginBox(this._map.div); var middleX = (firstTouch.pageX + secondTouch.pageX) / 2.0 - marginBox.l; var middleY = (firstTouch.pageY + secondTouch.pageY) / 2.0 - marginBox.t; return { x : middleX, y : middleY }; }, _getFingerSpacing : function(touchEvent){ // summary: // computes the distance between the first two fingers // touchEvent: Event // a touch event // returns: float // a distance. -1 if less that 2 fingers // tags: // private var touches = touchEvent.touches; var spacing = -1; if (touches.length >= 2) { var dx = (touches[1].pageX - touches[0].pageX); var dy = (touches[1].pageY - touches[0].pageY); spacing = Math.sqrt(dx * dx + dy * dy); } return spacing; }, _isDoubleTap : function(touchEvent){ // summary: // checks whether the specified touchStart event is a double tap // (i.e. follows closely a previous touchStart at approximately the same location) // touchEvent: Event // a touch event // returns: boolean // true if this event is considered a double tap // tags: // private var isDoubleTap = false; var touches = touchEvent.touches; if ((this._tapCount > 0) && touches.length == 1) { // test distance from last tap var dx = (touches[0].pageX - this._lastTap.x); var dy = (touches[0].pageY - this._lastTap.y); var distance = dx * dx + dy * dy; if (distance < this._tapThreshold) { isDoubleTap = true; } else { this._tapCount = 0; } } this._tapCount++; this._lastTap.x = touches[0].pageX; this._lastTap.y = touches[0].pageY; setTimeout(lang.hitch(this, function(){ this._tapCount = 0; }), 300); return isDoubleTap; }, _doubleTapHandler : function(touchEvent){ // summary: // action performed on the map when a double tap was triggered // touchEvent: Event // a touch event // tags: // private // perform a basic 2x zoom on touch var touches = touchEvent.touches; var marginBox = html.marginBox(this._map.div); var offX = touches[0].pageX - marginBox.l; var offY = touches[0].pageY - marginBox.t; // clicked map point before zooming var mapPoint = this._map.getLonLatFromPixel(new OpenLayers.Pixel(offX, offY)); // zoom increment power this._map.setCenter(new OpenLayers.LonLat(mapPoint.lon, mapPoint.lat), this._map.getZoom() + 1); }, _touchStartHandler : function(touchEvent){ // summary: // action performed on the map when a touch start was triggered // touchEvent: Event // a touch event // tags: // private event.stop(touchEvent); // test double tap if (this._isDoubleTap(touchEvent)) { this._doubleTapHandler(touchEvent); return; } // compute map midpoint between fingers var middlePoint = this._getTouchBarycenter(touchEvent); this._centerTouchLocation = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); // store initial finger spacing to compute zoom later this._initialFingerSpacing = this._getFingerSpacing(touchEvent); // store initial map scale this._initialScale = this._map.getScale(); // install touch move and up listeners (if not done by other fingers before) if (!this._touchMoveListener) this._touchMoveListener = connect.connect(window.global, "touchmove", this, this._touchMoveHandler); if (!this._touchEndListener) this._touchEndListener = connect.connect(window.global, "touchend", this, this._touchEndHandler); }, _touchEndHandler : function(touchEvent){ // summary: // action performed on the map when a touch end was triggered // touchEvent: Event // a touch event // tags: // private event.stop(touchEvent); var touches = touchEvent.touches; if (touches.length == 0) { // disconnect listeners only when all fingers are up if (this._touchMoveListener) { connect.disconnect(this._touchMoveListener); this._touchMoveListener = null; } if (this._touchEndListener) { connect.disconnect(this._touchEndListener); this._touchEndListener = null; } } else { // recompute touch center var middlePoint = this._getTouchBarycenter(touchEvent); this._centerTouchLocation = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); } }, _touchMoveHandler : function(touchEvent){ // summary: // action performed on the map when a touch move was triggered // touchEvent: Event // a touch event // tags: // private // prevent browser interaction event.stop(touchEvent); var middlePoint = this._getTouchBarycenter(touchEvent); // compute map offset var mapPoint = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); var mapOffsetLon = mapPoint.lon - this._centerTouchLocation.lon; var mapOffsetLat = mapPoint.lat - this._centerTouchLocation.lat; // compute scale factor var scaleFactor = 1; var touches = touchEvent.touches; if (touches.length >= 2) { var fingerSpacing = this._getFingerSpacing(touchEvent); scaleFactor = fingerSpacing / this._initialFingerSpacing; // weird openlayer bug : setting several times the same scale value lead to visual zoom... this._map.zoomToScale(this._initialScale / scaleFactor); } // adjust map center on barycenter var currentMapCenter = this._map.getCenter(); this._map.setCenter(new OpenLayers.LonLat(currentMapCenter.lon - mapOffsetLon, currentMapCenter.lat - mapOffsetLat)); } }); });