Merge pull request #800 from ahocevar/transform

Use GPU where available; animated zooming. r=@elemoine
This commit is contained in:
ahocevar
2013-02-14 01:15:18 -08:00
23 changed files with 491 additions and 173 deletions
+5 -15
View File
@@ -245,8 +245,7 @@ OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
* evt - {Event}
*/
defaultDblClick: function (evt) {
var newCenter = this.map.getLonLatFromViewPortPx( evt.xy );
this.map.setCenter(newCenter, this.map.zoom + 1);
this.map.zoomTo(this.map.zoom + 1, evt.xy);
},
/**
@@ -256,8 +255,7 @@ OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
* evt - {Event}
*/
defaultDblRightClick: function (evt) {
var newCenter = this.map.getLonLatFromViewPortPx( evt.xy );
this.map.setCenter(newCenter, this.map.zoom - 1);
this.map.zoomTo(this.map.zoom - 1, evt.xy);
},
/**
@@ -271,22 +269,14 @@ OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
if (!this.map.fractionalZoom) {
deltaZ = Math.round(deltaZ);
}
var currentZoom = this.map.getZoom();
var newZoom = this.map.getZoom() + deltaZ;
var currentZoom = this.map.getZoom(),
newZoom = currentZoom + deltaZ;
newZoom = Math.max(newZoom, 0);
newZoom = Math.min(newZoom, this.map.getNumZoomLevels());
if (newZoom === currentZoom) {
return;
}
var size = this.map.getSize();
var deltaX = size.w/2 - evt.xy.x;
var deltaY = evt.xy.y - size.h/2;
var newRes = this.map.baseLayer.getResolutionForZoom(newZoom);
var zoomPoint = this.map.getLonLatFromPixel(evt.xy);
var newCenter = new OpenLayers.LonLat(
zoomPoint.lon + deltaX * newRes,
zoomPoint.lat + deltaY * newRes );
this.map.setCenter( newCenter, newZoom );
this.map.zoomTo(newZoom, evt.xy);
},
/**
+5 -20
View File
@@ -4,7 +4,6 @@
* full text of the license. */
/**
* @requires OpenLayers/Util/vendorPrefix.js
* @requires OpenLayers/Handler/Pinch.js
*/
@@ -105,27 +104,13 @@ OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
var current = (this.preserveCenter) ?
this.map.getPixelFromLonLat(this.map.getCenter()) : 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));
var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
this.applyTransform(
"translate(" + dx + "px, " + dy + "px) scale(" + scale + ")"
);
this.map.applyTransform(dx, dy, scale);
this.currentCenter = current;
},
/**
* Method: applyTransform
* Applies the given transform to layers.
*/
applyTransform: function(transform) {
var style = this.map.layerContainerDiv.style;
var transformProperty = OpenLayers.Util.vendorPrefix.style("transform");
if (transformProperty) {
style[transformProperty] = transform;
}
},
/**
* Method: pinchDone
*
@@ -137,7 +122,7 @@ OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
* of the pinch gesture. This give us the final scale of the pinch.
*/
pinchDone: function(evt, start, last) {
this.applyTransform("");
this.map.applyTransform();
var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
var resolution = this.map.getResolutionForZoom(zoom);
+1 -2
View File
@@ -175,8 +175,7 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
* evt - {Event}
*/
defaultDblClick: function (evt) {
var newCenter = this.map.getLonLatFromViewPortPx(evt.xy);
this.map.setCenter(newCenter, this.map.zoom + 1);
this.map.zoomTo(this.map.zoom + 1, evt.xy);
},
CLASS_NAME: "OpenLayers.Control.TouchNavigation"
+18 -9
View File
@@ -69,7 +69,8 @@ OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
*/
zoomBox: function (position) {
if (position instanceof OpenLayers.Bounds) {
var bounds;
var bounds,
targetCenterPx = position.getCenterPixel();
if (!this.out) {
var minXY = this.map.getLonLatFromPixel({
x: position.left,
@@ -87,8 +88,7 @@ OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
var zoomFactor = Math.min((this.map.size.h / pixHeight),
(this.map.size.w / pixWidth));
var extent = this.map.getExtent();
var center = this.map.getLonLatFromPixel(
position.getCenterPixel());
var center = this.map.getLonLatFromPixel(targetCenterPx);
var xmin = center.lon - (extent.getWidth()/2)*zoomFactor;
var xmax = center.lon + (extent.getWidth()/2)*zoomFactor;
var ymin = center.lat - (extent.getHeight()/2)*zoomFactor;
@@ -96,18 +96,27 @@ OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax);
}
// always zoom in/out
var lastZoom = this.map.getZoom();
this.map.zoomToExtent(bounds);
var lastZoom = this.map.getZoom(),
size = this.map.getSize(),
centerPx = {x: size.w / 2, y: size.h / 2},
zoom = this.map.getZoomForExtent(bounds),
oldRes = this.map.getResolution(),
newRes = this.map.getResolutionForZoom(zoom),
zoomOriginPx = {
x: targetCenterPx.x +
(targetCenterPx.x - centerPx.x) * newRes / oldRes,
y: targetCenterPx.y +
(targetCenterPx.y - centerPx.y) * newRes / oldRes
};
this.map.zoomTo(zoom, zoomOriginPx);
if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){
this.map.zoomTo(lastZoom + (this.out ? -1 : 1));
}
} else if (this.zoomOnClick) { // it's a pixel
if (!this.out) {
this.map.setCenter(this.map.getLonLatFromPixel(position),
this.map.getZoom() + 1);
this.map.zoomTo(this.map.getZoom() + 1, position);
} else {
this.map.setCenter(this.map.getLonLatFromPixel(position),
this.map.getZoom() - 1);
this.map.zoomTo(this.map.getZoom() - 1, position);
}
}
},
+8 -2
View File
@@ -650,7 +650,11 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
if(!backBuffer) {
return;
}
this.div.insertBefore(backBuffer, this.div.firstChild);
if (resolution === this.gridResolution) {
this.div.insertBefore(backBuffer, this.div.firstChild);
} else {
this.map.layerContainerDiv.insertBefore(backBuffer, this.map.baseLayer.div);
}
this.backBuffer = backBuffer;
// set some information in the instance for subsequent
@@ -731,7 +735,9 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
delete this._transitionElement;
}
if(this.backBuffer) {
this.div.removeChild(this.backBuffer);
if (this.backBuffer.parentNode) {
this.backBuffer.parentNode.removeChild(this.backBuffer);
}
this.backBuffer = null;
this.backBufferResolution = null;
if(this.backBufferTimerId !== null) {
+205 -28
View File
@@ -6,6 +6,7 @@
/**
* @requires OpenLayers/BaseTypes/Class.js
* @requires OpenLayers/Util.js
* @requires OpenLayers/Util/vendorPrefix.js
* @requires OpenLayers/Events.js
* @requires OpenLayers/Tween.js
* @requires OpenLayers/Projection.js
@@ -395,12 +396,6 @@ OpenLayers.Map = OpenLayers.Class({
*/
autoUpdateSize: true,
/**
* Property: panTween
* {<OpenLayers.Tween>} Animated panning tween object, see panTo()
*/
panTween: null,
/**
* APIProperty: eventListeners
* {Object} If set as an option at construction, the eventListeners
@@ -410,6 +405,12 @@ OpenLayers.Map = OpenLayers.Class({
*/
eventListeners: null,
/**
* Property: panTween
* {<OpenLayers.Tween>} Animated panning tween object, see panTo()
*/
panTween: null,
/**
* APIProperty: panMethod
* {Function} The Easing function to be used for tweening. Default is
@@ -427,6 +428,28 @@ OpenLayers.Map = OpenLayers.Class({
*/
panDuration: 50,
/**
* Property: zoomTween
* {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
*/
zoomTween: null,
/**
* APIProperty: zoomMethod
* {Function} The Easing function to be used for tweening. Default is
* OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
* animated zooming.
*/
zoomMethod: OpenLayers.Easing.Quad.easeOut,
/**
* Property: zoomDuration
* {Integer} The number of steps to be passed to the
* OpenLayers.Tween.start() method when the map is zoomed.
* Default is 20.
*/
zoomDuration: 20,
/**
* Property: paddingForPopups
* {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent
@@ -598,6 +621,7 @@ OpenLayers.Map = OpenLayers.Class({
this.layerContainerDiv = OpenLayers.Util.createDiv(id);
this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
this.layerContainerOriginPx = {x: 0, y: 0};
this.applyTransform();
this.viewPortDiv.appendChild(this.layerContainerDiv);
@@ -693,6 +717,13 @@ OpenLayers.Map = OpenLayers.Class({
this.setCenter(options.center, options.zoom);
}
}
if (this.panMethod) {
this.panTween = new OpenLayers.Tween(this.panMethod);
}
if (this.zoomMethod && this.applyTransform.transform) {
this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
}
},
/**
@@ -761,6 +792,11 @@ OpenLayers.Map = OpenLayers.Class({
this.panTween.stop();
this.panTween = null;
}
// make sure zooming doesn't continue after destruction
if(this.zoomTween) {
this.zoomTween.stop();
this.zoomTween = null;
}
// map has been destroyed. dont do it again!
OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
@@ -1681,10 +1717,7 @@ OpenLayers.Map = OpenLayers.Class({
* lonlat - {<OpenLayers.LonLat>}
*/
panTo: function(lonlat) {
if (this.panMethod && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
if (!this.panTween) {
this.panTween = new OpenLayers.Tween(this.panMethod);
}
if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
var center = this.getCachedCenter();
// center will not change, don't do nothing
@@ -1735,7 +1768,12 @@ OpenLayers.Map = OpenLayers.Class({
* TBD: reconsider forceZoomChange in 3.0
*/
setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
this.panTween && this.panTween.stop();
if (this.panTween) {
this.panTween.stop();
}
if (this.zoomTween) {
this.zoomTween.stop();
}
this.moveTo(lonlat, zoom, {
'dragging': dragging,
'forceZoomChange': forceZoomChange
@@ -1776,17 +1814,16 @@ OpenLayers.Map = OpenLayers.Class({
}
this.center = null;
if (dx) {
this.layerContainerDiv.style.left =
(this.layerContainerOriginPx.x -= dx) + "px";
this.layerContainerOriginPx.x -= dx;
this.minPx.x -= dx;
this.maxPx.x -= dx;
}
if (dy) {
this.layerContainerDiv.style.top =
(this.layerContainerOriginPx.y -= dy) + "px";
this.layerContainerOriginPx.y -= dy;
this.minPx.y -= dy;
this.maxPx.y -= dy;
}
this.applyTransform();
var layer, i, len;
for (i=0, len=this.layers.length; i<len; ++i) {
layer = this.layers[i];
@@ -1946,11 +1983,9 @@ OpenLayers.Map = OpenLayers.Class({
// (re)set the layerContainerDiv's location
if (zoomChanged || this.layerContainerOrigin == null) {
this.layerContainerOrigin = this.getCachedCenter();
var style = this.layerContainerDiv.style;
style.left = "0px";
style.top = "0px";
this.layerContainerOriginPx.x = 0;
this.layerContainerOriginPx.y = 0;
this.applyTransform();
var maxExtent = this.getMaxExtent({restricted: true});
var maxExtentCenter = maxExtent.getCenterLonLat();
var lonDelta = this.center.lon - maxExtentCenter.lon;
@@ -2038,10 +2073,9 @@ OpenLayers.Map = OpenLayers.Class({
var oldTop = this.layerContainerOriginPx.y;
var newLeft = Math.round(originPx.x - newPx.x);
var newTop = Math.round(originPx.y - newPx.y);
this.layerContainerDiv.style.left =
(this.layerContainerOriginPx.x = newLeft) + "px";
this.layerContainerDiv.style.top =
(this.layerContainerOriginPx.y = newTop) + "px";
this.applyTransform(
(this.layerContainerOriginPx.x = newLeft),
(this.layerContainerOriginPx.y = newTop));
var dx = oldLeft - newLeft;
var dy = oldTop - newTop;
this.minPx.x -= dx;
@@ -2340,17 +2374,65 @@ OpenLayers.Map = OpenLayers.Class({
/**
* APIMethod: zoomTo
* Zoom to a specific zoom level
* Zoom to a specific zoom level. Zooming will be animated unless the map
* is configured with {zoomMethod: null}. To zoom without animation, use
* <setCenter> without a lonlat argument.
*
* Parameters:
* zoom - {Integer}
*/
zoomTo: function(zoom) {
if (this.isValidZoomLevel(zoom)) {
this.setCenter(null, zoom);
zoomTo: function(zoom, xy) {
// non-API arguments:
// xy - {<OpenLayers.Pixel>} optional zoom origin
var map = this;
if (map.isValidZoomLevel(zoom)) {
if (map.baseLayer.wrapDateLine) {
zoom = map.adjustZoom(zoom);
}
if (map.zoomTween) {
var currentRes = map.getResolution(),
targetRes = map.getResolutionForZoom(zoom),
start = {scale: 1},
end = {scale: currentRes / targetRes};
if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
// update the end scale, and reuse the running zoomTween
map.zoomTween.finish = {
scale: map.zoomTween.finish.scale * end.scale
};
} else {
if (!xy) {
var size = map.getSize();
xy = {x: size.w / 2, y: size.h / 2};
}
map.zoomTween.start(start, end, map.zoomDuration, {
minFrameRate: 50, // don't spend much time zooming
callbacks: {
eachStep: function(data) {
var containerOrigin = map.layerContainerOriginPx,
scale = data.scale,
dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
},
done: function(data) {
map.applyTransform();
var resolution = map.getResolution() / data.scale,
zoom = map.getZoomForResolution(resolution, true)
map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
}
}
});
}
} else {
var center = xy ?
map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
null;
map.setCenter(center, zoom);
}
}
},
/**
* APIMethod: zoomIn
*
@@ -2508,7 +2590,32 @@ OpenLayers.Map = OpenLayers.Class({
return px;
},
/**
* Method: getZoomTargetCenter
*
* Parameters:
* xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
* resolution - {Float} The resolution we want to get the center for
*
* Returns:
* {<OpenLayers.LonLat>} The location of the map center after the
* transformation described by the origin xy and the target resolution.
*/
getZoomTargetCenter: function (xy, resolution) {
var lonlat = null,
size = this.getSize(),
deltaX = size.w/2 - xy.x,
deltaY = xy.y - size.h/2,
zoomPoint = this.getLonLatFromPixel(xy);
if (zoomPoint) {
lonlat = new OpenLayers.LonLat(
zoomPoint.lon + deltaX * resolution,
zoomPoint.lat + deltaY * resolution
);
}
return lonlat;
},
//
// CONVENIENCE TRANSLATION FUNCTIONS FOR API
//
@@ -2668,6 +2775,76 @@ OpenLayers.Map = OpenLayers.Class({
return this.getLayerPxFromViewPortPx(px);
},
/**
* Method: applyTransform
* Applies the given transform to the <layerContainerDiv>. This method has
* a 2-stage fallback from translate3d/scale3d via translate/scale to plain
* style.left/style.top, in which case no scaling is supported.
*
* Parameters:
* x - {Number} x parameter for the translation. Defaults to the x value of
* the map's <layerContainerOriginPx>
* y - {Number} y parameter for the translation. Defaults to the y value of
* the map's <layerContainerOriginPx>
* scale - {Number} scale. Defaults to 1 if not provided.
*/
applyTransform: function(x, y, scale) {
scale = scale || 1;
var origin = this.layerContainerOriginPx,
needTransform = scale !== 1;
x = x || origin.x;
y = y || origin.y;
var style = this.layerContainerDiv.style,
transform = this.applyTransform.transform,
template = this.applyTransform.template;
if (transform === undefined) {
transform = OpenLayers.Util.vendorPrefix.style('transform');
this.applyTransform.transform = transform;
if (transform) {
// Try translate3d, but only if the viewPortDiv has a transform
// defined in a stylesheet
var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
OpenLayers.Util.vendorPrefix.css('transform'));
if (!computedStyle || computedStyle !== 'none') {
template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
style[transform] = [template[0], '0,0', template[1]].join('');
}
// If no transform is defined in the stylesheet or translate3d
// does not stick, use translate and scale
if (!template || !~style[transform].indexOf(template[0])) {
template = ['translate(', ') ', 'scale(', ')'];
}
this.applyTransform.template = template;
}
}
// If we do 3d transforms, we always want to use them. If we do 2d
// transforms, we only use them when we need to.
if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
// Our 2d transforms are combined with style.left and style.top, so
// adjust x and y values and set the origin as left and top
if (needTransform === true && template[0] === 'translate(') {
x -= origin.x;
y -= origin.y;
style.left = origin.x + 'px';
style.top = origin.y + 'px';
}
style[transform] = [
template[0], x, 'px,', y, 'px', template[1],
template[2], scale, ',', scale, template[3]
].join('');
} else {
style.left = x + 'px';
style.top = y + 'px';
// We previously might have had needTransform, so remove transform
if (transform !== null) {
style[transform] = '';
}
}
},
CLASS_NAME: "OpenLayers.Map"
});