From 211a2834de89b0a9ff82a16ca2ce7f4559642178 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 4 Feb 2008 06:23:54 +0000 Subject: [PATCH] Adding fractionalZoom property to the map. This allows zooming to an arbitrary level, making it possible to have non-discrete resolutions for layers that support it. This property should not be set to true for layers with fixed zoom levels (commercial layers or others with cached tiles). r=elemoine,crschmidt,ahocevar (closes #1243) git-svn-id: http://svn.openlayers.org/trunk/openlayers@5982 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/fractional-zoom.html | 69 +++++++++++++++++++++++++ lib/OpenLayers/Control/Navigation.js | 2 +- lib/OpenLayers/Layer.js | 75 +++++++++++++++++++++++----- lib/OpenLayers/Map.js | 33 ++++++++++-- tests/test_Layer.html | 50 ++++++++++++++++++- tests/test_Map.html | 16 ++++++ 6 files changed, 225 insertions(+), 20 deletions(-) create mode 100644 examples/fractional-zoom.html 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 );