diff --git a/src/objectliterals.exports b/src/objectliterals.exports index 0e24207a11..3c8adf12f3 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -12,6 +12,7 @@ @exportObjectLiteralProperty ol.MapOptions.renderers Array.|undefined @exportObjectLiteralProperty ol.MapOptions.shiftDragZoom boolean|undefined @exportObjectLiteralProperty ol.MapOptions.target Element|string +@exportObjectLiteralProperty ol.MapOptions.touchPan boolean|undefined @exportObjectLiteralProperty ol.MapOptions.view ol.IView|undefined @exportObjectLiteralProperty ol.MapOptions.zoomControl boolean|undefined @exportObjectLiteralProperty ol.MapOptions.zoomDelta number|undefined diff --git a/src/ol/interaction/touchinteraction.js b/src/ol/interaction/touchinteraction.js new file mode 100644 index 0000000000..c71d5997de --- /dev/null +++ b/src/ol/interaction/touchinteraction.js @@ -0,0 +1,119 @@ + +goog.provide('ol.interaction.Touch'); + +goog.require('goog.functions'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.Pixel'); +goog.require('ol.interaction.Interaction'); + + + +/** + * @constructor + * @extends {ol.interaction.Interaction} + */ +ol.interaction.Touch = function() { + + goog.base(this); + + /** + * @type {boolean} + * @private + */ + this.handled_ = false; + + /** + * @type {Object} + * @private + */ + this.trackedTouches_ = {}; + + /** + * @type {Array.} + */ + this.targetTouches = []; + +}; +goog.inherits(ol.interaction.Touch, ol.interaction.Interaction); + + +/** + * @param {Array.} touches TouchEvents. + * @return {ol.Pixel} Centroid pixel. + */ +ol.interaction.Touch.centroid = function(touches) { + var length = touches.length; + var clientX = 0; + var clientY = 0; + for (var i = 0; i < length; i++) { + clientX += touches[i].clientX; + clientY += touches[i].clientY; + } + return new ol.Pixel(clientX / length, clientY / length); +}; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @private + */ +ol.interaction.Touch.prototype.updateTrackedTouches_ = + function(mapBrowserEvent) { + var event = mapBrowserEvent.browserEvent.getBrowserEvent(); + if (goog.isDef(event.targetTouches)) { + // W3C touch events + this.targetTouches = event.targetTouches; + } else { + // IE pointer event + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHEND) { + delete this.trackedTouches_[event.pointerId]; + } else { + this.trackedTouches_[event.pointerId] = event; + } + this.targetTouches = goog.object.getValues(this.trackedTouches_); + } +}; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @protected + */ +ol.interaction.Touch.prototype.handleTouchMove = goog.nullFunction; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @protected + * @return {boolean} Capture dragging. + */ +ol.interaction.Touch.prototype.handleTouchEnd = goog.functions.FALSE; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @protected + * @return {boolean} Capture dragging. + */ +ol.interaction.Touch.prototype.handleTouchStart = goog.functions.FALSE; + + +/** + * @inheritDoc + */ +ol.interaction.Touch.prototype.handleMapBrowserEvent = + function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent.getBrowserEvent(); + this.updateTrackedTouches_(mapBrowserEvent); + if (this.handled_) { + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHMOVE) { + this.handleTouchMove(mapBrowserEvent); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHEND) { + this.handled_ = this.handleTouchEnd(mapBrowserEvent); + } + } + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHSTART) { + this.handled_ = this.handleTouchStart(mapBrowserEvent); + } +}; diff --git a/src/ol/interaction/touchpaninteraction.js b/src/ol/interaction/touchpaninteraction.js new file mode 100644 index 0000000000..1a36caddd5 --- /dev/null +++ b/src/ol/interaction/touchpaninteraction.js @@ -0,0 +1,109 @@ +// FIXME works for View2D only +// FIXME opt_kinetic param +goog.provide('ol.interaction.TouchPan'); + +goog.require('goog.asserts'); +goog.require('ol.Coordinate'); +goog.require('ol.Kinetic'); +goog.require('ol.Pixel'); +goog.require('ol.View'); +goog.require('ol.ViewHint'); +goog.require('ol.interaction.Touch'); + + + +/** + * @constructor + * @extends {ol.interaction.Touch} + */ +ol.interaction.TouchPan = function() { + + goog.base(this); + + /** + * @private + * @type {ol.Kinetic} + */ + this.kinetic_ = new ol.Kinetic(-0.005, 0.05, 100); + + /** + * @type {ol.Pixel} + */ + this.lastCentroid = null; + +}; +goog.inherits(ol.interaction.TouchPan, ol.interaction.Touch); + + +/** + * @inheritDoc + */ +ol.interaction.TouchPan.prototype.handleTouchMove = function(mapBrowserEvent) { + goog.asserts.assert(this.targetTouches.length >= 1); + var centroid = ol.interaction.Touch.centroid(this.targetTouches); + if (!goog.isNull(this.lastCentroid)) { + this.kinetic_.update(centroid.x, centroid.y); + var map = mapBrowserEvent.map; + var view = map.getView(); + var resolution = view.getResolution(); + var rotation = view.getRotation(); + var center = view.getCenter(); + + var deltaX = this.lastCentroid.x - centroid.x; + var deltaY = centroid.y - this.lastCentroid.y; + var newCenter = new ol.Coordinate(deltaX, deltaY) + .scale(view.getResolution()) + .rotate(view.getRotation()); + // FIXME: nice to have center.add(...); + newCenter.x += center.x; + newCenter.y += center.y; + view.setCenter(newCenter); + } + this.lastCentroid = centroid; +}; + + +/** + * @inheritDoc + */ +ol.interaction.TouchPan.prototype.handleTouchEnd = + function(mapBrowserEvent) { + var map = mapBrowserEvent.map; + var view = map.getView(); + if (this.targetTouches.length == 0) { + view.setHint(ol.ViewHint.PANNING, -1); + if (this.kinetic_.end()) { + var distance = this.kinetic_.getDistance(); + var angle = this.kinetic_.getAngle(); + var center = view.getCenter(); + map.addPreRenderFunction(this.kinetic_.pan(center)); + var centerpx = map.getPixelFromCoordinate(center); + var destpx = new ol.Pixel( + centerpx.x - distance * Math.cos(angle), + centerpx.y - distance * Math.sin(angle)); + var dest = map.getCoordinateFromPixel(destpx); + view.setCenter(dest); + } + return false; + } else { + return true; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.TouchPan.prototype.handleTouchStart = + function(mapBrowserEvent) { + if (this.targetTouches.length >= 1) { + var map = mapBrowserEvent.map; + var view = map.getView(); + this.lastCentroid = null; + this.kinetic_.begin(); + view.setHint(ol.ViewHint.PANNING, 1); + return true; + } else { + return false; + } +}; diff --git a/src/ol/map.js b/src/ol/map.js index 2c4ceea383..3e3fdaef13 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -54,6 +54,7 @@ goog.require('ol.interaction.Interaction'); goog.require('ol.interaction.KeyboardPan'); goog.require('ol.interaction.KeyboardZoom'); goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.TouchPan'); goog.require('ol.interaction.condition'); goog.require('ol.layer.Layer'); goog.require('ol.renderer.Map'); @@ -959,6 +960,12 @@ ol.Map.createInteractions_ = function(mapOptions) { interactions.push(new ol.interaction.DblClickZoom(zoomDelta)); } + var touchPan = goog.isDef(mapOptions.touchPan) ? + mapOptions.touchPan : true; + if (touchPan) { + interactions.push(new ol.interaction.TouchPan()); + } + var dragPan = goog.isDef(mapOptions.dragPan) ? mapOptions.dragPan : true; if (dragPan) { diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index e859e7e8e8..2e70b82ba4 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -77,7 +77,8 @@ describe('ol.Map', function() { dragPan: false, keyboard: false, mouseWheelZoom: false, - shiftDragZoom: false + shiftDragZoom: false, + touchPan: false }; });