diff --git a/src/ol/interaction/dragpaninteraction.js b/src/ol/interaction/dragpaninteraction.js index 3fcc4d4157..4598cabec1 100644 --- a/src/ol/interaction/dragpaninteraction.js +++ b/src/ol/interaction/dragpaninteraction.js @@ -7,6 +7,7 @@ goog.require('ol.Coordinate'); goog.require('ol.MapBrowserEvent'); goog.require('ol.View2D'); goog.require('ol.ViewHint'); +goog.require('ol.animation'); goog.require('ol.interaction.ConditionType'); goog.require('ol.interaction.Drag'); @@ -16,8 +17,9 @@ goog.require('ol.interaction.Drag'); * @constructor * @extends {ol.interaction.Drag} * @param {ol.interaction.ConditionType} condition Condition. + * @param {ol.Kinetic=} opt_kinetic Kinetic object. */ -ol.interaction.DragPan = function(condition) { +ol.interaction.DragPan = function(condition, opt_kinetic) { goog.base(this); @@ -27,6 +29,12 @@ ol.interaction.DragPan = function(condition) { */ this.condition_ = condition; + /** + * @private + * @type {ol.Kinetic|undefined} + */ + this.kinetic_ = opt_kinetic; + }; goog.inherits(ol.interaction.DragPan, ol.interaction.Drag); @@ -35,6 +43,9 @@ goog.inherits(ol.interaction.DragPan, ol.interaction.Drag); * @inheritDoc */ ol.interaction.DragPan.prototype.handleDrag = function(mapBrowserEvent) { + if (this.kinetic_) { + this.kinetic_.update(mapBrowserEvent.browserEvent); + } var map = mapBrowserEvent.map; // FIXME works for View2D only var view = map.getView(); @@ -55,9 +66,31 @@ ol.interaction.DragPan.prototype.handleDrag = function(mapBrowserEvent) { * @inheritDoc */ ol.interaction.DragPan.prototype.handleDragEnd = function(mapBrowserEvent) { + + // FIXME works for View2D only + var map = mapBrowserEvent.map; - map.requestRenderFrame(); - map.getView().setHint(ol.ViewHint.PANNING, -1); + var view = map.getView(); + view.setHint(ol.ViewHint.PANNING, -1); + + if (this.kinetic_ && this.kinetic_.end()) { + var distance = this.kinetic_.getDistance(); + var angle = this.kinetic_.getAngle(); + var center = view.getCenter(); + var kineticPanFrom = ol.animation.createPanFrom({ + source: center, + duration: this.kinetic_.getDuration(), + easing: this.kinetic_.getEasingFn() + }); + map.addPreRenderFunction(kineticPanFrom); + + 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); + } }; @@ -67,6 +100,9 @@ ol.interaction.DragPan.prototype.handleDragEnd = function(mapBrowserEvent) { ol.interaction.DragPan.prototype.handleDragStart = function(mapBrowserEvent) { var browserEvent = mapBrowserEvent.browserEvent; if (this.condition_(browserEvent)) { + if (this.kinetic_) { + this.kinetic_.begin(browserEvent); + } var map = mapBrowserEvent.map; map.requestRenderFrame(); map.getView().setHint(ol.ViewHint.PANNING, 1); diff --git a/src/ol/kinetic.js b/src/ol/kinetic.js new file mode 100644 index 0000000000..909fbb4936 --- /dev/null +++ b/src/ol/kinetic.js @@ -0,0 +1,143 @@ + +goog.provide('ol.Kinetic'); + +goog.require('goog.array'); +goog.require('ol.Pixel'); + + +/** + * @typedef {{x: number, + * y: number, + * t: number}} + */ +ol.KineticPoint; + + + +/** + * @constructor + * @param {number} decay Rate of decay (must be negative). + * @param {number} v_min Minimum velocity (pixels/millisecond). + * @param {number} delay Delay to consider to calculate the kinetic + * initial values (milliseconds). + */ +ol.Kinetic = function(decay, v_min, delay) { + + /** + * @private + * @type {number} + */ + this.decay_ = decay; + + /** + * @private + * @type {number} + */ + this.v_min_ = v_min; + + /** + * @private + * @type {number} + */ + this.delay_ = delay; + + /** + * @private + * @type {Array.} + */ + this.points_ = []; + + /** + * @private + * @type {number} + */ + this.angle_ = 0; + + /** + * @private + * @type {number} + */ + this.v_0_ = 0; +}; + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + */ +ol.Kinetic.prototype.begin = function(browserEvent) { + this.points_.length = 0; + this.angle_ = 0; + this.v_0_ = 0; + this.update(browserEvent); +}; + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + */ +ol.Kinetic.prototype.update = function(browserEvent) { + this.points_.push({ + x: browserEvent.clientX, + y: browserEvent.clientY, + t: goog.now() + }); +}; + + +/** + * @return {boolean} Whether we should do kinetic animation. + */ +ol.Kinetic.prototype.end = function() { + var now = goog.now(); + var index = Math.abs(goog.array.binarySelect(this.points_, function(elt) { + return elt.t < now - this.delay_; + }, this)); + if (index < this.points_.length - 1) { + var first = this.points_[index]; + var last = this.points_[this.points_.length - 1]; + var dx = last.x - first.x; + var dy = last.y - first.y; + this.angle_ = Math.atan2(dy, dx); + this.v_0_ = Math.sqrt(dx * dx + dy * dy) / (last.t - first.t); + return this.v_0_ > this.v_min_; + } + return false; +}; + + +/** + * @return {function(number): number} Easing function for kinetic animation. + */ +ol.Kinetic.prototype.getEasingFn = function() { + var decay = this.decay_; + var v_0 = this.v_0_; + var v_min = this.v_min_; + var duration = this.getDuration(); + return function(t) { + return v_0 * (Math.exp((decay * t) * duration) - 1) / (v_min - v_0); + }; +}; + + +/** + * @return {number} Duration of animation (milliseconds). + */ +ol.Kinetic.prototype.getDuration = function() { + return Math.log(this.v_min_ / this.v_0_) / this.decay_; +}; + + +/** + * @return {number} Total distance travelled (pixels). + */ +ol.Kinetic.prototype.getDistance = function() { + return (this.v_min_ - this.v_0_) / this.decay_; +}; + + +/** + * @return {number} Angle of the kinetic panning animation (radians). + */ +ol.Kinetic.prototype.getAngle = function() { + return this.angle_; +};