diff --git a/examples/center.html b/examples/center.html
index 34d6b8c4e3..11eae3ce7d 100644
--- a/examples/center.html
+++ b/examples/center.html
@@ -21,8 +21,6 @@ tags: "center, rotation, openstreetmap"
- (best fit),
- (respect resolution constraint).
- (nearest),
+ (best fit).
(with min resolution),
diff --git a/examples/center.js b/examples/center.js
index 48602a94df..e1f2e029d5 100644
--- a/examples/center.js
+++ b/examples/center.js
@@ -47,29 +47,14 @@ const map = new Map({
view: view
});
-const zoomtoswitzerlandbest = document.getElementById('zoomtoswitzerlandbest');
-zoomtoswitzerlandbest.addEventListener('click', function() {
- const feature = source.getFeatures()[0];
- const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry());
- view.fit(polygon, {padding: [170, 50, 30, 150], constrainResolution: false});
-}, false);
-
-const zoomtoswitzerlandconstrained =
- document.getElementById('zoomtoswitzerlandconstrained');
-zoomtoswitzerlandconstrained.addEventListener('click', function() {
+const zoomtoswitzerland =
+ document.getElementById('zoomtoswitzerland');
+zoomtoswitzerland.addEventListener('click', function() {
const feature = source.getFeatures()[0];
const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry());
view.fit(polygon, {padding: [170, 50, 30, 150]});
}, false);
-const zoomtoswitzerlandnearest =
- document.getElementById('zoomtoswitzerlandnearest');
-zoomtoswitzerlandnearest.addEventListener('click', function() {
- const feature = source.getFeatures()[0];
- const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry());
- view.fit(polygon, {padding: [170, 50, 30, 150], nearest: true});
-}, false);
-
const zoomtolausanne = document.getElementById('zoomtolausanne');
zoomtolausanne.addEventListener('click', function() {
const feature = source.getFeatures()[1];
diff --git a/examples/interaction-options.js b/examples/interaction-options.js
index bc3cc90621..63fd71cc4b 100644
--- a/examples/interaction-options.js
+++ b/examples/interaction-options.js
@@ -7,7 +7,7 @@ import OSM from '../src/ol/source/OSM.js';
const map = new Map({
interactions: defaultInteractions({
- constrainResolution: true, onFocusOnly: true
+ onFocusOnly: true
}),
layers: [
new TileLayer({
diff --git a/examples/pinch-zoom.js b/examples/pinch-zoom.js
index b8f6dd1a2c..0fba1794c9 100644
--- a/examples/pinch-zoom.js
+++ b/examples/pinch-zoom.js
@@ -6,10 +6,8 @@ import OSM from '../src/ol/source/OSM.js';
const map = new Map({
- interactions: defaultInteractions({pinchZoom: false}).extend([
- new PinchZoom({
- constrainResolution: true // force zooming to a integer zoom
- })
+ interactions: defaultInteractions().extend([
+ new PinchZoom()
]),
layers: [
new TileLayer({
@@ -19,6 +17,7 @@ const map = new Map({
target: 'map',
view: new View({
center: [0, 0],
- zoom: 2
+ zoom: 2,
+ constrainResolution: true
})
});
diff --git a/src/ol/View.js b/src/ol/View.js
index f71ae3625f..caed07efbb 100644
--- a/src/ol/View.js
+++ b/src/ol/View.js
@@ -23,6 +23,7 @@ import {createProjection, METERS_PER_UNIT} from './proj.js';
import Units from './proj/Units.js';
import {equals} from './coordinate';
import {easeOut} from './easing';
+import {createMinMaxResolution} from './resolutionconstraint';
/**
@@ -60,9 +61,8 @@ import {easeOut} from './easing';
* @property {!Array} [padding=[0, 0, 0, 0]] Padding (in pixels) to be
* cleared inside the view. Values in the array are top, right, bottom and left
* padding.
- * @property {boolean} [constrainResolution=true] Constrain the resolution.
- * @property {boolean} [nearest=false] If `constrainResolution` is `true`, get
- * the nearest extent instead of the closest that actually fits the view.
+ * @property {boolean} [nearest=false] If the view `constrainResolution` option is `true`,
+ * get the nearest extent instead of the closest that actually fits the view.
* @property {number} [minResolution=0] Minimum resolution that we zoom to.
* @property {number} [maxZoom] Maximum zoom level that we zoom to. If
* `minResolution` is given, this property is ignored.
@@ -120,6 +120,9 @@ import {easeOut} from './easing';
* resolution constraint. It is used together with `maxZoom` (or
* `minResolution`) and `zoomFactor`. Note that if `maxResolution` is also
* provided, it is given precedence over `minZoom`.
+ * @property {boolean} [constrainResolution] If true, the view will always
+ * animate to the closest zoom level after an interaction; false means
+ * intermediary zoom levels are allowed. Default is false.
* @property {import("./proj.js").ProjectionLike} [projection='EPSG:3857'] The
* projection. The default is Spherical Mercator.
* @property {number} [resolution] The initial resolution for the view. The
@@ -621,7 +624,7 @@ class View extends BaseObject {
}
if (!this.getAnimating()) {
- this.resolveConstraints_();
+ setTimeout(this.resolveConstraints_.bind(this), 0);
}
}
@@ -790,6 +793,15 @@ class View extends BaseObject {
this.applyOptions_(this.getUpdatedOptions_({minZoom: zoom}));
}
+ /**
+ * Set whether the view shoud allow intermediary zoom levels.
+ * @param {boolean} enabled Whether the resolution is constrained.
+ * @api
+ */
+ setConstrainResolution(enabled) {
+ this.applyOptions_(this.getUpdatedOptions_({constrainResolution: enabled}));
+ }
+
/**
* Get the view projection.
* @return {import("./proj/Projection.js").default} The projection of the view.
@@ -1001,8 +1013,6 @@ class View extends BaseObject {
}
const padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0];
- const constrainResolution = options.constrainResolution !== undefined ?
- options.constrainResolution : true;
const nearest = options.nearest !== undefined ? options.nearest : false;
let minResolution;
if (options.minResolution !== undefined) {
@@ -1038,9 +1048,7 @@ class View extends BaseObject {
[size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]);
resolution = isNaN(resolution) ? minResolution :
Math.max(resolution, minResolution);
- if (constrainResolution) {
- resolution = this.getValidResolution(resolution, nearest ? 0 : 1);
- }
+ resolution = this.getValidResolution(resolution, nearest ? 0 : 1);
// calculate center
sinAngle = -sinAngle; // go back to original rotation
@@ -1346,8 +1354,14 @@ export function createResolutionConstraint(options) {
maxResolution = resolutions[minZoom];
minResolution = resolutions[maxZoom] !== undefined ?
resolutions[maxZoom] : resolutions[resolutions.length - 1];
- resolutionConstraint = createSnapToResolutions(resolutions,
- !options.constrainOnlyCenter && options.extent);
+
+ if (options.constrainResolution) {
+ resolutionConstraint = createSnapToResolutions(resolutions,
+ !options.constrainOnlyCenter && options.extent);
+ } else {
+ resolutionConstraint = createMinMaxResolution(maxResolution, minResolution,
+ !options.constrainOnlyCenter && options.extent);
+ }
} else {
// calculate the default min and max resolution
const projection = createProjection(options.projection, 'EPSG:3857');
@@ -1391,9 +1405,14 @@ export function createResolutionConstraint(options) {
Math.log(maxResolution / minResolution) / Math.log(zoomFactor));
minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom);
- resolutionConstraint = createSnapToPower(
- zoomFactor, maxResolution, minResolution,
- !options.constrainOnlyCenter && options.extent);
+ if (options.constrainResolution) {
+ resolutionConstraint = createSnapToPower(
+ zoomFactor, maxResolution, minResolution,
+ !options.constrainOnlyCenter && options.extent);
+ } else {
+ resolutionConstraint = createMinMaxResolution(maxResolution, minResolution,
+ !options.constrainOnlyCenter && options.extent);
+ }
}
return {constraint: resolutionConstraint, maxResolution: maxResolution,
minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor};
diff --git a/src/ol/interaction.js b/src/ol/interaction.js
index 7a3c8b449e..3f79f9dac6 100644
--- a/src/ol/interaction.js
+++ b/src/ol/interaction.js
@@ -44,8 +44,6 @@ export {default as Translate} from './interaction/Translate.js';
* focus. This affects the `MouseWheelZoom` and `DragPan` interactions and is
* useful when page scroll is desired for maps that do not have the browser's
* focus.
- * @property {boolean} [constrainResolution=false] Zoom to the closest integer
- * zoom level after the wheel/trackpad or pinch gesture ends.
* @property {boolean} [doubleClickZoom=true] Whether double click zoom is
* desired.
* @property {boolean} [keyboard=true] Whether keyboard interaction is desired.
@@ -127,7 +125,6 @@ export function defaults(opt_options) {
const pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true;
if (pinchZoom) {
interactions.push(new PinchZoom({
- constrainResolution: options.constrainResolution,
duration: options.zoomDuration
}));
}
@@ -146,7 +143,6 @@ export function defaults(opt_options) {
if (mouseWheelZoom) {
interactions.push(new MouseWheelZoom({
condition: options.onFocusOnly ? focus : undefined,
- constrainResolution: options.constrainResolution,
duration: options.zoomDuration
}));
}
diff --git a/src/ol/interaction/MouseWheelZoom.js b/src/ol/interaction/MouseWheelZoom.js
index 120d4e42c0..8ba3c68fd0 100644
--- a/src/ol/interaction/MouseWheelZoom.js
+++ b/src/ol/interaction/MouseWheelZoom.js
@@ -34,9 +34,6 @@ export const Mode = {
* {@link module:ol/events/condition~always}.
* @property {number} [duration=250] Animation duration in milliseconds.
* @property {number} [timeout=80] Mouse wheel timeout duration in milliseconds.
- * @property {boolean} [constrainResolution=false] When using a trackpad or
- * magic mouse, zoom to the closest integer zoom level after the scroll gesture
- * ends.
* @property {boolean} [useAnchor=true] Enable zooming using the mouse's
* location as the anchor. When set to `false`, zooming in and out will zoom to
* the center of the screen instead of zooming on the mouse's location.
@@ -82,12 +79,6 @@ class MouseWheelZoom extends Interaction {
*/
this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
- /**
- * @private
- * @type {boolean}
- */
- this.constrainResolution_ = options.constrainResolution || false;
-
/**
* @private
* @type {import("../events/condition.js").Condition}
@@ -238,7 +229,7 @@ class MouseWheelZoom extends Interaction {
}
view.setResolution(resolution);
- if (rebound === 0 && this.constrainResolution_) {
+ if (rebound === 0) {
const zoomDelta = delta > 0 ? -1 : 1;
const newZoom = view.getValidZoomLevel(view.getZoom() + zoomDelta);
view.animate({
diff --git a/src/ol/interaction/PinchZoom.js b/src/ol/interaction/PinchZoom.js
index 282d543138..b3c1e16c90 100644
--- a/src/ol/interaction/PinchZoom.js
+++ b/src/ol/interaction/PinchZoom.js
@@ -10,8 +10,6 @@ import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js
/**
* @typedef {Object} Options
* @property {number} [duration=400] Animation duration in milliseconds.
- * @property {boolean} [constrainResolution=false] Zoom to the closest integer
- * zoom level after the pinch gesture ends.
*/
@@ -37,12 +35,6 @@ class PinchZoom extends PointerInteraction {
super(pointerOptions);
- /**
- * @private
- * @type {boolean}
- */
- this.constrainResolution_ = options.constrainResolution || false;
-
/**
* @private
* @type {import("../coordinate.js").Coordinate}
diff --git a/src/ol/resolutionconstraint.js b/src/ol/resolutionconstraint.js
index 575d603286..c7198e9c4d 100644
--- a/src/ol/resolutionconstraint.js
+++ b/src/ol/resolutionconstraint.js
@@ -4,13 +4,24 @@
import {linearFindNearest} from './array.js';
import {clamp} from './math.js';
import {getHeight, getWidth} from './extent';
-import {clamp} from './math';
/**
* @typedef {function((number|undefined), number, import("./size.js").Size, boolean=): (number|undefined)} Type
*/
+/**
+ * @param {number} resolution Resolution
+ * @param {import("./extent.js").Extent=} maxExtent Maximum allowed extent.
+ * @param {import("./size.js").Size} viewportSize Viewport size.
+ * @return {number} Capped resolution.
+ */
+function getCappedResolution(resolution, maxExtent, viewportSize) {
+ const xResolution = getWidth(maxExtent) / viewportSize[0];
+ const yResolution = getHeight(maxExtent) / viewportSize[1];
+ return Math.min(resolution, Math.min(xResolution, yResolution));
+}
+
/**
* @param {Array} resolutions Resolutions.
@@ -28,14 +39,7 @@ export function createSnapToResolutions(resolutions, opt_maxExtent) {
*/
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
- let cappedRes = resolution;
-
- // apply constraint related to max extent
- if (opt_maxExtent) {
- const xResolution = getWidth(opt_maxExtent) / size[0];
- const yResolution = getHeight(opt_maxExtent) / size[1];
- cappedRes = Math.min(cappedRes, Math.min(xResolution, yResolution));
- }
+ const cappedRes = opt_maxExtent ? getCappedResolution(resolution, opt_maxExtent, size) : resolution;
// during interacting or animating, allow intermediary values
if (opt_isMoving) {
@@ -72,14 +76,7 @@ export function createSnapToPower(power, maxResolution, opt_minResolution, opt_m
*/
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
- let cappedRes = Math.min(resolution, maxResolution);
-
- // apply constraint related to max extent
- if (opt_maxExtent) {
- const xResolution = getWidth(opt_maxExtent) / size[0];
- const yResolution = getHeight(opt_maxExtent) / size[1];
- cappedRes = Math.min(cappedRes, Math.min(xResolution, yResolution));
- }
+ const cappedRes = opt_maxExtent ? getCappedResolution(resolution, opt_maxExtent, size) : resolution;
// during interacting or animating, allow intermediary values
if (opt_isMoving) {
@@ -98,3 +95,29 @@ export function createSnapToPower(power, maxResolution, opt_minResolution, opt_m
}
});
}
+
+/**
+ * @param {number} maxResolution Max resolution.
+ * @param {number} minResolution Min resolution.
+ * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
+ * @return {Type} Zoom function.
+ */
+export function createMinMaxResolution(maxResolution, minResolution, opt_maxExtent) {
+ return (
+ /**
+ * @param {number|undefined} resolution Resolution.
+ * @param {number} direction Direction.
+ * @param {import("./size.js").Size} size Viewport size.
+ * @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
+ * @return {number|undefined} Resolution.
+ */
+ function(resolution, direction, size, opt_isMoving) {
+ if (resolution !== undefined) {
+ const cappedRes = opt_maxExtent ? getCappedResolution(resolution, opt_maxExtent, size) : resolution;
+ return clamp(cappedRes, minResolution, maxResolution);
+ } else {
+ return undefined;
+ }
+ }
+ );
+}
diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js
index 8d145682b7..9a929bc24e 100644
--- a/test/spec/ol/map.test.js
+++ b/test/spec/ol/map.test.js
@@ -565,7 +565,6 @@ describe('ol.Map', function() {
const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(MouseWheelZoom);
- expect(interactions.item(0).constrainResolution_).to.eql(false);
expect(interactions.item(0).useAnchor_).to.eql(true);
interactions.item(0).setMouseAnchor(false);
expect(interactions.item(0).useAnchor_).to.eql(false);
@@ -601,21 +600,6 @@ describe('ol.Map', function() {
const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(PinchZoom);
- expect(interactions.item(0).constrainResolution_).to.eql(false);
- });
- });
-
- describe('set constrainResolution option', function() {
- it('set constrainResolution option', function() {
- options.pinchZoom = true;
- options.mouseWheelZoom = true;
- options.constrainResolution = true;
- const interactions = defaultInteractions(options);
- expect(interactions.getLength()).to.eql(2);
- expect(interactions.item(0)).to.be.a(PinchZoom);
- expect(interactions.item(0).constrainResolution_).to.eql(true);
- expect(interactions.item(1)).to.be.a(MouseWheelZoom);
- expect(interactions.item(1).constrainResolution_).to.eql(true);
});
});
diff --git a/test/spec/ol/view.test.js b/test/spec/ol/view.test.js
index 796e320386..669a12ab7c 100644
--- a/test/spec/ol/view.test.js
+++ b/test/spec/ol/view.test.js
@@ -239,7 +239,8 @@ describe('ol.View', function() {
it('works with minResolution and maxResolution', function() {
const constraint = getConstraint({
maxResolution: 500,
- minResolution: 100
+ minResolution: 100,
+ constrainResolution: true
});
expect(constraint(600, 0, 0)).to.be(500);
@@ -255,7 +256,8 @@ describe('ol.View', function() {
const constraint = getConstraint({
maxResolution: 500,
minResolution: 1,
- zoomFactor: 10
+ zoomFactor: 10,
+ constrainResolution: true
});
expect(constraint(1000, 0, 0)).to.be(500);
@@ -1327,14 +1329,40 @@ describe('ol.View', function() {
zoom: 5
});
});
- it('fits correctly to the geometry', function() {
+ it('fits correctly to the geometry (with unconstrained resolution)', function() {
view.fit(
new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
- {size: [200, 200], padding: [100, 0, 0, 100], constrainResolution: false});
+ {size: [200, 200], padding: [100, 0, 0, 100]});
expect(view.getResolution()).to.be(11);
expect(view.getCenter()[0]).to.be(5950);
expect(view.getCenter()[1]).to.be(47100);
+ view.fit(
+ new Circle([6000, 46000], 1000),
+ {size: [200, 200]});
+ expect(view.getResolution()).to.be(10);
+ expect(view.getCenter()[0]).to.be(6000);
+ expect(view.getCenter()[1]).to.be(46000);
+
+ view.setRotation(Math.PI / 8);
+ view.fit(
+ new Circle([6000, 46000], 1000),
+ {size: [200, 200]});
+ expect(view.getResolution()).to.roughlyEqual(10, 1e-9);
+ expect(view.getCenter()[0]).to.roughlyEqual(6000, 1e-9);
+ expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9);
+
+ view.setRotation(Math.PI / 4);
+ view.fit(
+ new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
+ {size: [200, 200], padding: [100, 0, 0, 100]});
+ expect(view.getResolution()).to.roughlyEqual(14.849242404917458, 1e-9);
+ expect(view.getCenter()[0]).to.roughlyEqual(5200, 1e-9);
+ expect(view.getCenter()[1]).to.roughlyEqual(46300, 1e-9);
+ });
+ it('fits correctly to the geometry', function() {
+ view.setConstrainResolution(true);
+
view.fit(
new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
{size: [200, 200], padding: [100, 0, 0, 100]});
@@ -1363,30 +1391,8 @@ describe('ol.View', function() {
expect(view.getZoom()).to.be(6);
expect(view.getCenter()[0]).to.be(5900);
expect(view.getCenter()[1]).to.be(46100);
-
- view.fit(
- new Circle([6000, 46000], 1000),
- {size: [200, 200], constrainResolution: false});
- expect(view.getResolution()).to.be(10);
- expect(view.getCenter()[0]).to.be(6000);
- expect(view.getCenter()[1]).to.be(46000);
-
- view.setRotation(Math.PI / 8);
- view.fit(
- new Circle([6000, 46000], 1000),
- {size: [200, 200], constrainResolution: false});
- expect(view.getResolution()).to.roughlyEqual(10, 1e-9);
- expect(view.getCenter()[0]).to.roughlyEqual(6000, 1e-9);
- expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9);
-
- view.setRotation(Math.PI / 4);
- view.fit(
- new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
- {size: [200, 200], padding: [100, 0, 0, 100], constrainResolution: false});
- expect(view.getResolution()).to.roughlyEqual(14.849242404917458, 1e-9);
- expect(view.getCenter()[0]).to.roughlyEqual(5200, 1e-9);
- expect(view.getCenter()[1]).to.roughlyEqual(46300, 1e-9);
});
+
it('fits correctly to the extent', function() {
view.fit([1000, 1000, 2000, 2000], {size: [200, 200]});
expect(view.getResolution()).to.be(5);
@@ -1409,7 +1415,6 @@ describe('ol.View', function() {
{
size: [200, 200],
padding: [100, 0, 0, 100],
- constrainResolution: false,
duration: 25
});
@@ -1531,7 +1536,8 @@ describe('ol.View', function() {
view = new View({
maxResolution: 2500,
zoomFactor: 5,
- maxZoom: 4
+ maxZoom: 4,
+ constrainResolution: true
});
expect(view.getValidResolution(90, 1)).to.be(100);
expect(view.getValidResolution(90, -1)).to.be(20);
@@ -1542,7 +1548,8 @@ describe('ol.View', function() {
it('works correctly with a specific resolution set', function() {
view = new View({
zoom: 0,
- resolutions: [512, 256, 128, 64, 32, 16, 8]
+ resolutions: [512, 256, 128, 64, 32, 16, 8],
+ constrainResolution: true
});
expect(view.getValidResolution(1000, 1)).to.be(512);
expect(view.getValidResolution(260, 1)).to.be(512);