diff --git a/examples/fractional-zoom.html b/examples/fractional-zoom.html
new file mode 100644
index 0000000000..69c5e1918e
--- /dev/null
+++ b/examples/fractional-zoom.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
Fractional Zoom Example
+
+
+
+
+ Shows the use of a map with fractional (or non-discrete) zoom levels.
+
+
+
+
+
+ (zoom: )
+
+
+
+ Setting the map.fractionalZoom property to true allows zooming to
+ an arbitrary level (between the min and max resolutions). This
+ can be demonstrated by shift-dragging a box to zoom to an arbitrary
+ extent.
+
+
+
+
+
+
+
+
diff --git a/lib/OpenLayers/Control/Navigation.js b/lib/OpenLayers/Control/Navigation.js
index 8f913766d5..e85b48880b 100644
--- a/lib/OpenLayers/Control/Navigation.js
+++ b/lib/OpenLayers/Control/Navigation.js
@@ -137,7 +137,7 @@ OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
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.resolutions[newZoom];
+ var newRes = this.map.baseLayer.getResolutionForZoom(newZoom);
var zoomPoint = this.map.getLonLatFromPixel(evt.xy);
var newCenter = new OpenLayers.LonLat(
zoomPoint.lon + deltaX * newRes,
diff --git a/lib/OpenLayers/Layer.js b/lib/OpenLayers/Layer.js
index b037391c88..8c452b1b05 100644
--- a/lib/OpenLayers/Layer.js
+++ b/lib/OpenLayers/Layer.js
@@ -758,7 +758,7 @@ OpenLayers.Layer = OpenLayers.Class({
*/
getResolution: function() {
var zoom = this.map.getZoom();
- return this.resolutions[zoom];
+ return this.getResolutionForZoom(zoom);
},
/**
@@ -812,6 +812,29 @@ OpenLayers.Layer = OpenLayers.Class({
//to be implemented by subclasses
},
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameter:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom.
+ */
+ getResolutionForZoom: function(zoom) {
+ zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
+ var resolution;
+ if(this.map.fractionalZoom) {
+ var low = Math.floor(zoom);
+ var high = Math.ceil(zoom);
+ resolution = this.resolutions[high] +
+ ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
+ } else {
+ resolution = this.resolutions[Math.round(zoom)];
+ }
+ return resolution;
+ },
+
/**
* APIMethod: getZoomForResolution
*
@@ -831,22 +854,50 @@ OpenLayers.Layer = OpenLayers.Class({
* value and the 'closest' specification.
*/
getZoomForResolution: function(resolution, closest) {
- var diff;
- var minDiff = Number.POSITIVE_INFINITY;
- for(var i=0; i < this.resolutions.length; i++) {
- if (closest) {
- diff = Math.abs(this.resolutions[i] - resolution);
- if (diff > minDiff) {
- break;
+ var zoom;
+ if(this.map.fractionalZoom) {
+ var lowZoom = 0;
+ var highZoom = this.resolutions.length - 1;
+ var highRes = this.resolutions[lowZoom];
+ var lowRes = this.resolutions[highZoom];
+ var res;
+ for(var i=0; i= resolution) {
+ highRes = res;
+ lowZoom = i;
}
- minDiff = diff;
- } else {
- if (this.resolutions[i] < resolution) {
+ if(res <= resolution) {
+ lowRes = res;
+ highZoom = i;
break;
}
}
+ var dRes = highRes - lowRes;
+ if(dRes > 0) {
+ zoom = lowZoom + ((resolution - lowRes) / dRes);
+ } else {
+ zoom = lowZoom;
+ }
+ } else {
+ var diff;
+ var minDiff = Number.POSITIVE_INFINITY;
+ for(var i=0; i < this.resolutions.length; i++) {
+ if (closest) {
+ diff = Math.abs(this.resolutions[i] - resolution);
+ if (diff > minDiff) {
+ break;
+ }
+ minDiff = diff;
+ } else {
+ if (this.resolutions[i] < resolution) {
+ break;
+ }
+ }
+ }
+ zoom = Math.max(0, i-1);
}
- return Math.max(0, i-1);
+ return zoom;
},
/**
diff --git a/lib/OpenLayers/Map.js b/lib/OpenLayers/Map.js
index 2236a185aa..f051ad9b3b 100644
--- a/lib/OpenLayers/Map.js
+++ b/lib/OpenLayers/Map.js
@@ -66,6 +66,14 @@ OpenLayers.Map = OpenLayers.Class({
*/
id: null,
+ /**
+ * Property: fractionalZoom
+ * {Boolean} For a base layer that supports it, allow the map resolution
+ * to be set to a value between one of the values in the resolutions
+ * array. Default is false.
+ */
+ fractionalZoom: false,
+
/**
* APIProperty: events
* {} An events object that handles all
@@ -1266,10 +1274,7 @@ OpenLayers.Map = OpenLayers.Class({
if(zoom == null) {
zoom = this.getZoom();
}
- var resolution = null;
- if(this.baseLayer != null) {
- resolution = this.baseLayer.resolutions[zoom];
- }
+ var resolution = this.getResolutionForZoom(zoom);
var extent = this.calculateBounds(lonlat, resolution);
if(!this.restrictedExtent.containsBounds(extent)) {
var maxCenter = this.restrictedExtent.getCenterLonLat();
@@ -1326,7 +1331,7 @@ OpenLayers.Map = OpenLayers.Class({
if (zoomChanged) {
this.zoom = zoom;
- this.resolution = this.baseLayer.getResolution();
+ this.resolution = this.getResolutionForZoom(zoom);
// zoom level has changed, increment viewRequestID.
this.viewRequestID++;
}
@@ -1594,6 +1599,24 @@ OpenLayers.Map = OpenLayers.Class({
return zoom;
},
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameter:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom. If no baselayer
+ * is set, returns null.
+ */
+ getResolutionForZoom: function(zoom) {
+ var resolution = null;
+ if(this.baseLayer) {
+ resolution = this.baseLayer.getResolutionForZoom(zoom);
+ }
+ return resolution;
+ },
+
/**
* APIMethod: getZoomForResolution
*
diff --git a/tests/test_Layer.html b/tests/test_Layer.html
index 8498ba179a..eecc579392 100644
--- a/tests/test_Layer.html
+++ b/tests/test_Layer.html
@@ -176,10 +176,11 @@
function test_06_Layer_getZoomForResolution(t) {
- t.plan(8);
+ t.plan(12);
var layer = new OpenLayers.Layer('Test Layer');
-
+ layer.map = {};
+
//make some dummy resolutions
layer.resolutions = [128, 64, 32, 16, 8, 4, 2];
@@ -193,6 +194,16 @@
t.eq(layer.getZoomForResolution(65, true), 1, "closest res");
t.eq(layer.getZoomForResolution(63, true), 1, "closest res");
+
+ layer.map.fractionalZoom = true;
+ t.eq(layer.getZoomForResolution(64), 1,
+ "(fractionalZoom) correct zoom for res in array");
+ t.eq(layer.getZoomForResolution(48).toPrecision(6), (1.5).toPrecision(6),
+ "(fractionalZoom) linear scaling for res between entries");
+ t.eq(layer.getZoomForResolution(200).toPrecision(6), (0).toPrecision(6),
+ "(fractionalZoom) doesn't return zoom below zero");
+ t.eq(layer.getZoomForResolution(1).toPrecision(6), (layer.resolutions.length - 1).toPrecision(6),
+ "(fractionalZoom) doesn't return zoom above highest index");
}
@@ -301,6 +312,41 @@
t.ok(layer.imageOffset.equals(desiredImageOffset), "image offset correctly calculated");
t.ok(layer.imageSize.equals(desiredImageSize), "image size correctly calculated");
}
+
+ function test_Layer_getResolution(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer("test");
+ layer.map = {
+ getZoom: function() {return "foo";}
+ };
+ layer.getResolutionForZoom = function(zoom) {
+ t.eq(zoom, "foo", "getResolution calls getResolutionForZoom");
+ }
+ layer.getResolution();
+ layer.map = null;
+ layer.destroy();
+ }
+
+ function test_Layer_getResolutionForZoom(t) {
+ t.plan(5);
+ var layer = new OpenLayers.Layer("test");
+ layer.map = {fractionalZoom: false};
+ layer.resolutions = ["zero", "one", "two"];
+ t.eq(layer.getResolutionForZoom(0), "zero",
+ "(fractionalZoom false) returns resolution for given index");
+ t.eq(layer.getResolutionForZoom(0.9), "one",
+ "(fractionalZoom false) returns resolution for float index");
+
+ layer.resolutions = [2, 4, 6, 8];
+ layer.map.fractionalZoom = true;
+ t.eq(layer.getResolutionForZoom(1).toPrecision(6), (4).toPrecision(6),
+ "(fractionalZoom true) returns resolution for integer zoom");
+ t.eq(layer.getResolutionForZoom(1.5).toPrecision(6), (5).toPrecision(6),
+ "(fractionalZoom true) returns resolution for float zoom");
+ t.eq(layer.getResolutionForZoom(3.5).toPrecision(6), (8).toPrecision(6),
+ "(fractionalZoom true) returns resolution for zoom beyond res length - 1");
+
+ }
diff --git a/tests/test_Map.html b/tests/test_Map.html
index 8ae2fdffce..32a67371f9 100644
--- a/tests/test_Map.html
+++ b/tests/test_Map.html
@@ -958,6 +958,22 @@
se.toString(),
"map extent not restricted with null restrictedExtent for se");
}
+
+ function test_Map_getResolutionForZoom(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var res = map.getResolutionForZoom();
+ t.eq(res, null, "getResolutionForZoom returns null for no base layer");
+ map.fractionalZoom = true;
+ var layer = new OpenLayers.Layer("test", {isBaseLayer: true});
+ layer.getResolutionForZoom = function() {
+ t.ok(true, "getResolutionForZoom calls base layer getResolutionForZoom");
+ }
+ map.addLayer(layer);
+ var res = map.getResolutionForZoom();
+ layer.destroy();
+ map.destroy();
+ }
function test_99_Map_destroy (t) {
t.plan( 3 );