diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md
index 1ba1139f5a..04559b5ef2 100644
--- a/changelog/upgrade-notes.md
+++ b/changelog/upgrade-notes.md
@@ -4,6 +4,24 @@
#### Backwards incompatible changes
+##### The `setCenter`, `setZoom`, `setResolution` and `setRotation` methods on `ol/View` do not bypass constraints anymore
+
+Previously, these methods allowed setting values that were inconsistent with the given view constraints.
+This is no longer the case and all changes to the view state now follow the same logic:
+target values are provided and constraints are applied on these to determine the actual values to be used.
+
+##### Removal of the `constrainResolution` option on `View.fit`, `PinchZoom`, `MouseWheelZoom` and `ol/interaction.js`
+
+The `constrainResolution` option is now only supported by the `View` class. A `View.setResolutionConstrained` method was added as well.
+
+Generally, the responsibility of applying center/rotation/resolutions constraints was moved from interactions and controls to the `View` class.
+
+##### The view `extent` option now applies to the whole viewport
+
+Previously, this options only constrained the view *center*. This behaviour can still be obtained by specifying `constrainCenterOnly` in the view options.
+
+As a side effect, the view `rotate` method is gone and has been replaced with `adjustRotation` which takes a delta as input.
+
##### Removal of deprecated methods
The `inherits` function that was used to inherit the prototype methods from one constructor into another has been removed.
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/extent-constrained.html b/examples/extent-constrained.html
new file mode 100644
index 0000000000..c28d8d5120
--- /dev/null
+++ b/examples/extent-constrained.html
@@ -0,0 +1,9 @@
+---
+layout: example.html
+title: Constrained Extent
+shortdesc: Example of a view with a constrained extent.
+docs: >
+ This map has a view that is constrained in an extent. This is done using the `extent` view option. Please note that specifying `constrainOnlyCenter: true` would only apply the extent restriction to the view center.
+tags: "view, extent, constrain, restrict"
+---
+
diff --git a/examples/extent-constrained.js b/examples/extent-constrained.js
new file mode 100644
index 0000000000..8fc6e68c77
--- /dev/null
+++ b/examples/extent-constrained.js
@@ -0,0 +1,25 @@
+import Map from '../src/ol/Map.js';
+import View from '../src/ol/View.js';
+import TileLayer from '../src/ol/layer/Tile.js';
+import OSM from '../src/ol/source/OSM.js';
+import {defaults as defaultControls} from '../src/ol/control/util';
+import ZoomSlider from '../src/ol/control/ZoomSlider';
+
+const view = new View({
+ center: [328627.563458, 5921296.662223],
+ zoom: 8,
+ extent: [-572513.341856, 5211017.966314,
+ 916327.095083, 6636950.728974]
+});
+
+new Map({
+ layers: [
+ new TileLayer({
+ source: new OSM()
+ })
+ ],
+ keyboardEventTarget: document,
+ target: 'map',
+ view: view,
+ controls: defaultControls().extend([new ZoomSlider()])
+});
diff --git a/examples/interaction-options.html b/examples/interaction-options.html
index 8c07ee8505..b0be6f0d4b 100644
--- a/examples/interaction-options.html
+++ b/examples/interaction-options.html
@@ -4,16 +4,11 @@ title: Interaction Options
shortdesc: Shows interaction options for custom scroll and zoom behavior.
docs: >
This example uses a custom `ol/interaction/defaults` configuration:
-
- * By default, wheel/trackpad zoom and drag panning is always active, which
- can be unexpected on pages with a lot of scrollable content and an embedded
- map. To perform wheel/trackpad zoom and drag-pan actions only when the map
- has the focus, set `onFocusOnly: true` as option. This requires a map div
- with a `tabindex` attribute set.
- * By default, pinch-zoom and wheel/trackpad zoom interactions can leave the
- map at fractional zoom levels. If instead you want to constrain
- wheel/trackpad zooming to integer zoom levels, set
- `constrainResolution: true`.
+ by default, wheel/trackpad zoom and drag panning is always active, which
+ can be unexpected on pages with a lot of scrollable content and an embedded
+ map. To perform wheel/trackpad zoom and drag-pan actions only when the map
+ has the focus, set `onFocusOnly: true` as option. This requires a map div
+ with a `tabindex` attribute set.
tags: "trackpad, mousewheel, zoom, scroll, interaction, fractional"
---
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/mapbox-layer.js b/examples/mapbox-layer.js
index aa2fbadcf4..e717328572 100644
--- a/examples/mapbox-layer.js
+++ b/examples/mapbox-layer.js
@@ -203,7 +203,11 @@ const map = new Map({
target: 'map',
view: new View({
center: [-10997148, 4569099],
- zoom: 4
+ zoom: 4,
+ minZoom: 1,
+ extent: [-Infinity, -20048966.10, Infinity, 20048966.10],
+ smoothExtentConstraint: false,
+ smoothResolutionConstraint: false
})
});
diff --git a/examples/pinch-zoom.html b/examples/pinch-zoom.html
index ab61eef6fc..e955ad7dac 100644
--- a/examples/pinch-zoom.html
+++ b/examples/pinch-zoom.html
@@ -5,7 +5,7 @@ shortdesc: Restrict pinch zooming to integer zoom levels.
docs: >
By default, the `ol/interaction/PinchZoom` can leave the map at fractional zoom levels.
If instead you want to constrain pinch zooming to integer zoom levels, set
- constrainResolution: true when constructing the interaction.
+ constrainResolution: true when constructing the view.
tags: "pinch, zoom, interaction"
---
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 0b33692e0c..6342888e37 100644
--- a/src/ol/View.js
+++ b/src/ol/View.js
@@ -21,6 +21,9 @@ import {clamp, modulo} from './math.js';
import {assign} from './obj.js';
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';
/**
@@ -58,9 +61,8 @@ import Units from './proj/Units.js';
* @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.
@@ -92,7 +94,12 @@ import Units from './proj/Units.js';
* used. The `constrainRotation` option has no effect if `enableRotation` is
* `false`.
* @property {import("./extent.js").Extent} [extent] The extent that constrains the
- * center, in other words, center cannot be set outside this extent.
+ * view, in other words, nothing outside of this extent can be visible on the map.
+ * @property {boolean} [constrainOnlyCenter=false] If true, the extent
+ * constraint will only apply to the view center and not the whole extent.
+ * @property {boolean} [smoothExtentConstraint=true] If true, the extent
+ * constraint will be applied smoothly, i.e. allow the view to go slightly outside
+ * of the given `extent`.
* @property {number} [maxResolution] The maximum resolution used to determine
* the resolution constraint. It is used together with `minResolution` (or
* `maxZoom`) and `zoomFactor`. If unspecified it is calculated in such a way
@@ -113,6 +120,12 @@ import Units from './proj/Units.js';
* 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=false] If true, the view will always
+ * animate to the closest zoom level after an interaction; false means
+ * intermediary zoom levels are allowed.
+ * @property {boolean} [smoothResolutionConstraint=true] If true, the resolution
+ * min/max values will be applied smoothly, i. e. allow the view to exceed slightly
+ * the given resolution or zoom bounds.
* @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
@@ -126,10 +139,9 @@ import Units from './proj/Units.js';
* @property {number} [rotation=0] The initial rotation for the view in radians
* (positive rotation clockwise, 0 means North).
* @property {number} [zoom] Only used if `resolution` is not defined. Zoom
- * level used to calculate the initial resolution for the view. The initial
- * resolution is determined using the {@link #constrainResolution} method.
- * @property {number} [zoomFactor=2] The zoom factor used to determine the
- * resolution constraint.
+ * level used to calculate the initial resolution for the view.
+ * @property {number} [zoomFactor=2] The zoom factor used to compute the
+ * corresponding resolution.
*/
@@ -143,7 +155,7 @@ import Units from './proj/Units.js';
* of the animation. If `zoom` is also provided, this option will be ignored.
* @property {number} [rotation] The rotation of the view at the end of
* the animation.
- * @property {import("./coordinate.js").Coordinate} [anchor] Optional anchor to remained fixed
+ * @property {import("./coordinate.js").Coordinate} [anchor] Optional anchor to remain fixed
* during a rotation or resolution animation.
* @property {number} [duration=1000] The duration of the animation in milliseconds.
* @property {function(number):number} [easing] The easing function used
@@ -184,7 +196,12 @@ const DEFAULT_MIN_ZOOM = 0;
* and `rotation`. Each state has a corresponding getter and setter, e.g.
* `getCenter` and `setCenter` for the `center` state.
*
- * An View has a `projection`. The projection determines the
+ * The `zoom` state is actually not saved on the view: all computations
+ * internally use the `resolution` state. Still, the `setZoom` and `getZoom`
+ * methods are available, as well as `getResolutionForZoom` and
+ * `getZoomForResolution` to switch from one system to the other.
+ *
+ * A View has a `projection`. The projection determines the
* coordinate system of the center, and its units determine the units of the
* resolution (projection units per pixel). The default projection is
* Spherical Mercator (EPSG:3857).
@@ -192,28 +209,19 @@ const DEFAULT_MIN_ZOOM = 0;
* ### The constraints
*
* `setCenter`, `setResolution` and `setRotation` can be used to change the
- * states of the view. Any value can be passed to the setters. And the value
- * that is passed to a setter will effectively be the value set in the view,
- * and returned by the corresponding getter.
+ * states of the view, but any constraint defined in the constructor will
+ * be applied along the way.
*
- * But a View object also has a *resolution constraint*, a
- * *rotation constraint* and a *center constraint*.
+ * A View object can have a *resolution constraint*, a *rotation constraint*
+ * and a *center constraint*.
*
- * As said above, no constraints are applied when the setters are used to set
- * new states for the view. Applying constraints is done explicitly through
- * the use of the `constrain*` functions (`constrainResolution` and
- * `constrainRotation` and `constrainCenter`).
- *
- * The main users of the constraints are the interactions and the
- * controls. For example, double-clicking on the map changes the view to
- * the "next" resolution. And releasing the fingers after pinch-zooming
- * snaps to the closest resolution (with an animation).
- *
- * The *resolution constraint* snaps to specific resolutions. It is
- * determined by the following options: `resolutions`, `maxResolution`,
- * `maxZoom`, and `zoomFactor`. If `resolutions` is set, the other three
- * options are ignored. See documentation for each option for more
- * information.
+ * The *resolution constraint* typically restricts min/max values and
+ * snaps to specific resolutions. It is determined by the following
+ * options: `resolutions`, `maxResolution`, `maxZoom`, and `zoomFactor`.
+ * If `resolutions` is set, the other three options are ignored. See
+ * documentation for each option for more information. By default, the view
+ * only has a min/max restriction and allow intermediary zoom levels when
+ * pinch-zooming for example.
*
* The *rotation constraint* snaps to specific angles. It is determined
* by the following options: `enableRotation` and `constrainRotation`.
@@ -221,9 +229,31 @@ const DEFAULT_MIN_ZOOM = 0;
* horizontal.
*
* The *center constraint* is determined by the `extent` option. By
- * default the center is not constrained at all.
+ * default the view center is not constrained at all.
*
- * @api
+ * ### Changing the view state
+ *
+ * It is important to note that `setZoom`, `setResolution`, `setCenter` and
+ * `setRotation` are subject to the above mentioned constraints. As such, it
+ * may sometimes not be possible to know in advance the resulting state of the
+ * View. For example, calling `setResolution(10)` does not guarantee that
+ * `getResolution()` will return `10`.
+ *
+ * A consequence of this is that, when applying a delta on the view state, one
+ * should use `adjustCenter`, `adjustRotation`, `adjustZoom` and `adjustResolution`
+ * rather than the corresponding setters. This will let view do its internal
+ * computations. Besides, the `adjust*` methods also take an `opt_anchor`
+ * argument which allows specifying an origin for the transformation.
+ *
+ * ### Interacting with the view
+ *
+ * View constraints are usually only applied when the view is *at rest*, meaning that
+ * no interaction or animation is ongoing. As such, if the user puts the view in a
+ * state that is not equivalent to a constrained one (e.g. rotating the view when
+ * the snap angle is 0), an animation will be triggered at the interaction end to
+ * put back the view to a stable state;
+ *
+ * @api
*/
class View extends BaseObject {
@@ -262,6 +292,24 @@ class View extends BaseObject {
*/
this.projection_ = createProjection(options.projection, 'EPSG:3857');
+ /**
+ * @private
+ * @type {import("./coordinate.js").Coordinate|undefined}
+ */
+ this.targetCenter_ = null;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.targetResolution_;
+
+ /**
+ * @private
+ * @type {number|undefined}
+ */
+ this.targetRotation_;
+
this.applyOptions_(options);
}
@@ -275,8 +323,6 @@ class View extends BaseObject {
* @type {Object}
*/
const properties = {};
- properties[ViewProperty.CENTER] = options.center !== undefined ?
- options.center : null;
const resolutionConstraintInfo = createResolutionConstraint(options);
@@ -324,19 +370,20 @@ class View extends BaseObject {
rotation: rotationConstraint
};
+ this.setRotation(options.rotation !== undefined ? options.rotation : 0);
+ this.setCenter(options.center !== undefined ? options.center : null);
if (options.resolution !== undefined) {
- properties[ViewProperty.RESOLUTION] = options.resolution;
+ this.setResolution(options.resolution);
} else if (options.zoom !== undefined) {
- properties[ViewProperty.RESOLUTION] = this.constrainResolution(
- this.maxResolution_, options.zoom - this.minZoom_);
-
if (this.resolutions_) { // in case map zoom is out of min/max zoom range
- properties[ViewProperty.RESOLUTION] = clamp(
- Number(this.getResolution() || properties[ViewProperty.RESOLUTION]),
- this.minResolution_, this.maxResolution_);
+ const resolution = this.getResolutionForZoom(options.zoom);
+ this.setResolution(clamp(resolution,
+ this.minResolution_, this.maxResolution_));
+ } else {
+ this.setZoom(options.zoom);
}
}
- properties[ViewProperty.ROTATION] = options.rotation !== undefined ? options.rotation : 0;
+
this.setProperties(properties);
/**
@@ -432,9 +479,9 @@ class View extends BaseObject {
return;
}
let start = Date.now();
- let center = this.getCenter().slice();
- let resolution = this.getResolution();
- let rotation = this.getRotation();
+ let center = this.targetCenter_.slice();
+ let resolution = this.targetResolution_;
+ let rotation = this.targetRotation_;
const series = [];
for (let i = 0; i < animationCount; ++i) {
const options = /** @type {AnimationOptions} */ (arguments[i]);
@@ -450,14 +497,13 @@ class View extends BaseObject {
if (options.center) {
animation.sourceCenter = center;
- animation.targetCenter = options.center;
+ animation.targetCenter = options.center.slice();
center = animation.targetCenter;
}
if (options.zoom !== undefined) {
animation.sourceResolution = resolution;
- animation.targetResolution = this.constrainResolution(
- this.maxResolution_, options.zoom - this.minZoom_, 0);
+ animation.targetResolution = this.getResolutionForZoom(options.zoom);
resolution = animation.targetResolution;
} else if (options.resolution) {
animation.sourceResolution = resolution;
@@ -556,28 +602,31 @@ class View extends BaseObject {
const y1 = animation.targetCenter[1];
const x = x0 + progress * (x1 - x0);
const y = y0 + progress * (y1 - y0);
- this.set(ViewProperty.CENTER, [x, y]);
+ this.targetCenter_ = [x, y];
}
if (animation.sourceResolution && animation.targetResolution) {
const resolution = progress === 1 ?
animation.targetResolution :
animation.sourceResolution + progress * (animation.targetResolution - animation.sourceResolution);
if (animation.anchor) {
- this.set(ViewProperty.CENTER,
- this.calculateCenterZoom(resolution, animation.anchor));
+ const size = this.getSizeFromViewport_(this.getRotation());
+ const constrainedResolution = this.constraints_.resolution(resolution, 0, size, true);
+ this.targetCenter_ = this.calculateCenterZoom(constrainedResolution, animation.anchor);
}
- this.set(ViewProperty.RESOLUTION, resolution);
+ this.targetResolution_ = resolution;
+ this.applyTargetState_(true);
}
if (animation.sourceRotation !== undefined && animation.targetRotation !== undefined) {
const rotation = progress === 1 ?
modulo(animation.targetRotation + Math.PI, 2 * Math.PI) - Math.PI :
animation.sourceRotation + progress * (animation.targetRotation - animation.sourceRotation);
if (animation.anchor) {
- this.set(ViewProperty.CENTER,
- this.calculateCenterRotate(rotation, animation.anchor));
+ const constrainedRotation = this.constraints_.rotation(rotation, true);
+ this.targetCenter_ = this.calculateCenterRotate(constrainedRotation, animation.anchor);
}
- this.set(ViewProperty.ROTATION, rotation);
+ this.targetRotation_ = rotation;
}
+ this.applyTargetState_(true);
more = true;
if (!animation.complete) {
break;
@@ -597,6 +646,10 @@ class View extends BaseObject {
if (more && this.updateAnimationKey_ === undefined) {
this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_);
}
+
+ if (!this.getAnimating()) {
+ setTimeout(this.resolveConstraints_.bind(this), 0);
+ }
}
/**
@@ -634,9 +687,10 @@ class View extends BaseObject {
/**
* @private
+ * @param {number=} opt_rotation Take into account the rotation of the viewport when giving the size
* @return {import("./size.js").Size} Viewport size or `[100, 100]` when no viewport is found.
*/
- getSizeFromViewport_() {
+ getSizeFromViewport_(opt_rotation) {
const size = [100, 100];
const selector = '.ol-viewport[data-view="' + getUid(this) + '"]';
const element = document.querySelector(selector);
@@ -645,45 +699,15 @@ class View extends BaseObject {
size[0] = parseInt(metrics.width, 10);
size[1] = parseInt(metrics.height, 10);
}
+ if (opt_rotation) {
+ const w = size[0];
+ const h = size[1];
+ size[0] = Math.abs(w * Math.cos(opt_rotation)) + Math.abs(h * Math.sin(opt_rotation));
+ size[1] = Math.abs(w * Math.sin(opt_rotation)) + Math.abs(h * Math.cos(opt_rotation));
+ }
return size;
}
- /**
- * Get the constrained center of this view.
- * @param {import("./coordinate.js").Coordinate|undefined} center Center.
- * @return {import("./coordinate.js").Coordinate|undefined} Constrained center.
- * @api
- */
- constrainCenter(center) {
- return this.constraints_.center(center);
- }
-
- /**
- * Get the constrained resolution of this view.
- * @param {number|undefined} resolution Resolution.
- * @param {number=} opt_delta Delta. Default is `0`.
- * @param {number=} opt_direction Direction. Default is `0`.
- * @return {number|undefined} Constrained resolution.
- * @api
- */
- constrainResolution(resolution, opt_delta, opt_direction) {
- const delta = opt_delta || 0;
- const direction = opt_direction || 0;
- return this.constraints_.resolution(resolution, delta, direction);
- }
-
- /**
- * Get the constrained rotation of this view.
- * @param {number|undefined} rotation Rotation.
- * @param {number=} opt_delta Delta. Default is `0`.
- * @return {number|undefined} Constrained rotation.
- * @api
- */
- constrainRotation(rotation, opt_delta) {
- const delta = opt_delta || 0;
- return this.constraints_.rotation(rotation, delta);
- }
-
/**
* Get the view center.
* @return {import("./coordinate.js").Coordinate|undefined} The center of the view.
@@ -793,6 +817,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.
@@ -914,9 +947,9 @@ class View extends BaseObject {
}
/**
- * Get the current zoom level. If you configured your view with a resolutions
- * array (this is rare), this method may return non-integer zoom levels (so
- * the zoom level is not safe to use as an index into a resolutions array).
+ * Get the current zoom level. This method may return non-integer zoom levels
+ * if the view does not constrain the resolution, or if an interaction or
+ * animation is underway.
* @return {number|undefined} Zoom.
* @api
*/
@@ -961,8 +994,16 @@ class View extends BaseObject {
* @api
*/
getResolutionForZoom(zoom) {
- return /** @type {number} */ (this.constrainResolution(
- this.maxResolution_, zoom - this.minZoom_, 0));
+ if (this.resolutions_) {
+ if (this.resolutions_.length <= 1) {
+ return 0;
+ }
+ const baseLevel = clamp(Math.floor(zoom), 0, this.resolutions_.length - 2);
+ const zoomFactor = this.resolutions_[baseLevel] / this.resolutions_[baseLevel + 1];
+ return this.resolutions_[baseLevel] / Math.pow(zoomFactor, clamp(zoom - baseLevel, 0, 1));
+ } else {
+ return this.maxResolution_ / Math.pow(this.zoomFactor_, zoom - this.minZoom_);
+ }
}
/**
@@ -998,15 +1039,12 @@ 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) {
minResolution = options.minResolution;
} else if (options.maxZoom !== undefined) {
- minResolution = this.constrainResolution(
- this.maxResolution_, options.maxZoom - this.minZoom_, 0);
+ minResolution = this.getResolutionForZoom(options.maxZoom);
} else {
minResolution = 0;
}
@@ -1036,14 +1074,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) {
- let constrainedResolution = this.constrainResolution(resolution, 0, 0);
- if (!nearest && constrainedResolution < resolution) {
- constrainedResolution = this.constrainResolution(
- constrainedResolution, -1, 0);
- }
- resolution = constrainedResolution;
- }
+ resolution = this.getConstrainedResolution(resolution, nearest ? 0 : 1);
// calculate center
sinAngle = -sinAngle; // go back to original rotation
@@ -1059,13 +1090,14 @@ class View extends BaseObject {
if (options.duration !== undefined) {
this.animate({
resolution: resolution,
- center: center,
+ center: this.getConstrainedCenter(center, resolution),
duration: options.duration,
easing: options.easing
}, callback);
} else {
- this.setResolution(resolution);
- this.setCenter(center);
+ this.targetResolution_ = resolution;
+ this.targetCenter_ = center;
+ this.applyTargetState_(false, true);
animationCallback(callback, true);
}
}
@@ -1104,30 +1136,74 @@ class View extends BaseObject {
}
/**
- * Rotate the view around a given coordinate.
- * @param {number} rotation New rotation value for the view.
- * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center.
+ * Adds relative coordinates to the center of the view. Any extent constraint will apply.
+ * @param {import("./coordinate.js").Coordinate} deltaCoordinates Relative value to add.
* @api
*/
- rotate(rotation, opt_anchor) {
- if (opt_anchor !== undefined) {
- const center = this.calculateCenterRotate(rotation, opt_anchor);
- this.setCenter(center);
- }
- this.setRotation(rotation);
+ adjustCenter(deltaCoordinates) {
+ const center = this.targetCenter_;
+ this.setCenter([center[0] + deltaCoordinates[0], center[1] + deltaCoordinates[1]]);
}
/**
- * Set the center of the current view.
+ * Multiply the view resolution by a ratio, optionally using an anchor. Any resolution
+ * constraint will apply.
+ * @param {number} ratio The ratio to apply on the view resolution.
+ * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation.
+ * @observable
+ * @api
+ */
+ adjustResolution(ratio, opt_anchor) {
+ const isMoving = this.getAnimating() || this.getInteracting();
+ const size = this.getSizeFromViewport_(this.getRotation());
+ const newResolution = this.constraints_.resolution(this.targetResolution_ * ratio, 0, size, isMoving);
+
+ if (opt_anchor !== undefined) {
+ this.targetCenter_ = this.calculateCenterZoom(newResolution, opt_anchor);
+ }
+
+ this.targetResolution_ *= ratio;
+ this.applyTargetState_();
+ }
+
+ /**
+ * Adds a value to the view zoom level, optionally using an anchor. Any resolution
+ * constraint will apply.
+ * @param {number} delta Relative value to add to the zoom level.
+ * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation.
+ * @api
+ */
+ adjustZoom(delta, opt_anchor) {
+ this.adjustResolution(Math.pow(this.zoomFactor_, -delta), opt_anchor);
+ }
+
+ /**
+ * Adds a value to the view rotation, optionally using an anchor. Any rotation
+ * constraint will apply.
+ * @param {number} delta Relative value to add to the zoom rotation, in radians.
+ * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center.
+ * @observable
+ * @api
+ */
+ adjustRotation(delta, opt_anchor) {
+ const isMoving = this.getAnimating() || this.getInteracting();
+ const newRotation = this.constraints_.rotation(this.targetRotation_ + delta, isMoving);
+ if (opt_anchor !== undefined) {
+ this.targetCenter_ = this.calculateCenterRotate(newRotation, opt_anchor);
+ }
+ this.targetRotation_ += delta;
+ this.applyTargetState_();
+ }
+
+ /**
+ * Set the center of the current view. Any extent constraint will apply.
* @param {import("./coordinate.js").Coordinate|undefined} center The center of the view.
* @observable
* @api
*/
setCenter(center) {
- this.set(ViewProperty.CENTER, center);
- if (this.getAnimating()) {
- this.cancelAnimations();
- }
+ this.targetCenter_ = center;
+ this.applyTargetState_();
}
/**
@@ -1142,39 +1218,166 @@ class View extends BaseObject {
}
/**
- * Set the resolution for this view.
+ * Set the resolution for this view. Any resolution constraint will apply.
* @param {number|undefined} resolution The resolution of the view.
* @observable
* @api
*/
setResolution(resolution) {
- this.set(ViewProperty.RESOLUTION, resolution);
- if (this.getAnimating()) {
- this.cancelAnimations();
- }
+ this.targetResolution_ = resolution;
+ this.applyTargetState_();
}
/**
- * Set the rotation for this view.
+ * Set the rotation for this view. Any rotation constraint will apply.
* @param {number} rotation The rotation of the view in radians.
* @observable
* @api
*/
setRotation(rotation) {
- this.set(ViewProperty.ROTATION, rotation);
- if (this.getAnimating()) {
- this.cancelAnimations();
- }
+ this.targetRotation_ = rotation;
+ this.applyTargetState_();
}
/**
- * Zoom to a specific zoom level.
+ * Zoom to a specific zoom level. Any resolution constrain will apply.
* @param {number} zoom Zoom level.
* @api
*/
setZoom(zoom) {
this.setResolution(this.getResolutionForZoom(zoom));
}
+
+ /**
+ * Recompute rotation/resolution/center based on target values.
+ * Note: we have to compute rotation first, then resolution and center considering that
+ * parameters can influence one another in case a view extent constraint is present.
+ * @param {boolean=} opt_doNotCancelAnims Do not cancel animations.
+ * @param {boolean=} opt_forceMoving Apply constraints as if the view is moving.
+ * @private
+ */
+ applyTargetState_(opt_doNotCancelAnims, opt_forceMoving) {
+ const isMoving = this.getAnimating() || this.getInteracting() || opt_forceMoving;
+
+ // compute rotation
+ const newRotation = this.constraints_.rotation(this.targetRotation_, isMoving);
+ const size = this.getSizeFromViewport_(newRotation);
+ const newResolution = this.constraints_.resolution(this.targetResolution_, 0, size, isMoving);
+ const newCenter = this.constraints_.center(this.targetCenter_, newResolution, size, isMoving);
+
+ if (this.get(ViewProperty.ROTATION) !== newRotation) {
+ this.set(ViewProperty.ROTATION, newRotation);
+ }
+ if (this.get(ViewProperty.RESOLUTION) !== newResolution) {
+ this.set(ViewProperty.RESOLUTION, newResolution);
+ }
+ if (!this.get(ViewProperty.CENTER) || !equals(this.get(ViewProperty.CENTER), newCenter)) {
+ this.set(ViewProperty.CENTER, newCenter);
+ }
+
+ if (this.getAnimating() && !opt_doNotCancelAnims) {
+ this.cancelAnimations();
+ }
+ }
+
+ /**
+ * If any constraints need to be applied, an animation will be triggered.
+ * This is typically done on interaction end.
+ * @param {number=} opt_duration The animation duration in ms.
+ * @param {number=} opt_resolutionDirection Which direction to zoom.
+ * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation.
+ * @private
+ */
+ resolveConstraints_(opt_duration, opt_resolutionDirection, opt_anchor) {
+ const duration = opt_duration !== undefined ? opt_duration : 200;
+ const direction = opt_resolutionDirection || 0;
+
+ const newRotation = this.constraints_.rotation(this.targetRotation_);
+ const size = this.getSizeFromViewport_(newRotation);
+ const newResolution = this.constraints_.resolution(this.targetResolution_, direction, size);
+ const newCenter = this.constraints_.center(this.targetCenter_, newResolution, size);
+
+ if (this.getResolution() !== newResolution ||
+ this.getRotation() !== newRotation ||
+ !this.getCenter() ||
+ !equals(this.getCenter(), newCenter)) {
+
+ if (this.getAnimating()) {
+ this.cancelAnimations();
+ }
+
+ this.animate({
+ rotation: newRotation,
+ center: newCenter,
+ resolution: newResolution,
+ duration: duration,
+ easing: easeOut,
+ anchor: opt_anchor
+ });
+ }
+ }
+
+ /**
+ * Notify the View that an interaction has started.
+ * @api
+ */
+ beginInteraction() {
+ this.setHint(ViewHint.INTERACTING, 1);
+ }
+
+ /**
+ * Notify the View that an interaction has ended. The view state will be resolved
+ * to a stable one if needed (depending on its constraints).
+ * @param {number=} opt_duration Animation duration in ms.
+ * @param {number=} opt_resolutionDirection Which direction to zoom.
+ * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation.
+ * @api
+ */
+ endInteraction(opt_duration, opt_resolutionDirection, opt_anchor) {
+ this.setHint(ViewHint.INTERACTING, -1);
+
+ this.resolveConstraints_(opt_duration, opt_resolutionDirection, opt_anchor);
+ }
+
+ /**
+ * Get a valid position for the view center according to the current constraints.
+ * @param {import("./coordinate.js").Coordinate|undefined} targetCenter Target center position.
+ * @param {number=} opt_targetResolution Target resolution. If not supplied, the current one will be used.
+ * This is useful to guess a valid center position at a different zoom level.
+ * @return {import("./coordinate.js").Coordinate|undefined} Valid center position.
+ */
+ getConstrainedCenter(targetCenter, opt_targetResolution) {
+ const size = this.getSizeFromViewport_(this.getRotation());
+ return this.constraints_.center(targetCenter, opt_targetResolution || this.getResolution(), size);
+ }
+
+ /**
+ * Get a valid zoom level according to the current view constraints.
+ * @param {number|undefined} targetZoom Target zoom.
+ * @param {number=} opt_direction Direction. Default is `0`. Specify `-1` or `1` to return
+ * the available value respectively lower or greater than the target one. Leaving `0` will simply choose
+ * the nearest available value.
+ * @return {number|undefined} Valid zoom level.
+ */
+ getConstrainedZoom(targetZoom, opt_direction) {
+ const targetRes = this.getResolutionForZoom(targetZoom);
+ return this.getZoomForResolution(this.getConstrainedResolution(targetRes));
+ }
+
+ /**
+ * Get a valid resolution according to the current view constraints.
+ * @param {number|undefined} targetResolution Target resolution.
+ * @param {number=} opt_direction Direction. Default is `0`. Specify `-1` or `1` to return
+ * the available value respectively lower or greater than the target one. Leaving `0` will simply choose
+ * the nearest available value.
+ * @return {number|undefined} Valid resolution.
+ */
+ getConstrainedResolution(targetResolution, opt_direction) {
+ const direction = opt_direction || 0;
+ const size = this.getSizeFromViewport_(this.getRotation());
+
+ return this.constraints_.resolution(targetResolution, direction, size);
+ }
}
@@ -1195,7 +1398,8 @@ function animationCallback(callback, returnValue) {
*/
export function createCenterConstraint(options) {
if (options.extent !== undefined) {
- return createExtent(options.extent);
+ return createExtent(options.extent, options.constrainOnlyCenter,
+ options.smoothExtentConstraint !== undefined ? options.smoothExtentConstraint : true);
} else {
return centerNone;
}
@@ -1226,13 +1430,22 @@ export function createResolutionConstraint(options) {
const zoomFactor = options.zoomFactor !== undefined ?
options.zoomFactor : defaultZoomFactor;
+ const smooth =
+ options.smoothResolutionConstraint !== undefined ? options.smoothResolutionConstraint : true;
+
if (options.resolutions !== undefined) {
const resolutions = options.resolutions;
maxResolution = resolutions[minZoom];
minResolution = resolutions[maxZoom] !== undefined ?
resolutions[maxZoom] : resolutions[resolutions.length - 1];
- resolutionConstraint = createSnapToResolutions(
- resolutions);
+
+ if (options.constrainResolution) {
+ resolutionConstraint = createSnapToResolutions(resolutions, smooth,
+ !options.constrainOnlyCenter && options.extent);
+ } else {
+ resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
+ !options.constrainOnlyCenter && options.extent);
+ }
} else {
// calculate the default min and max resolution
const projection = createProjection(options.projection, 'EPSG:3857');
@@ -1276,8 +1489,14 @@ export function createResolutionConstraint(options) {
Math.log(maxResolution / minResolution) / Math.log(zoomFactor));
minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom);
- resolutionConstraint = createSnapToPower(
- zoomFactor, maxResolution, maxZoom - minZoom);
+ if (options.constrainResolution) {
+ resolutionConstraint = createSnapToPower(
+ zoomFactor, maxResolution, minResolution, smooth,
+ !options.constrainOnlyCenter && options.extent);
+ } else {
+ resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
+ !options.constrainOnlyCenter && options.extent);
+ }
}
return {constraint: resolutionConstraint, maxResolution: maxResolution,
minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor};
diff --git a/src/ol/centerconstraint.js b/src/ol/centerconstraint.js
index 3d24463c8b..1bf5480c6f 100644
--- a/src/ol/centerconstraint.js
+++ b/src/ol/centerconstraint.js
@@ -5,26 +5,57 @@ import {clamp} from './math.js';
/**
- * @typedef {function((import("./coordinate.js").Coordinate|undefined)): (import("./coordinate.js").Coordinate|undefined)} Type
+ * @typedef {function((import("./coordinate.js").Coordinate|undefined), number, import("./size.js").Size, boolean=): (import("./coordinate.js").Coordinate|undefined)} Type
*/
/**
* @param {import("./extent.js").Extent} extent Extent.
+ * @param {boolean} onlyCenter If true, the constraint will only apply to the view center.
+ * @param {boolean} smooth If true, the view will be able to go slightly out of the given extent
+ * (only during interaction and animation).
* @return {Type} The constraint.
*/
-export function createExtent(extent) {
+export function createExtent(extent, onlyCenter, smooth) {
return (
/**
- * @param {import("./coordinate.js").Coordinate=} center Center.
+ * @param {import("./coordinate.js").Coordinate|undefined} center Center.
+ * @param {number} resolution Resolution.
+ * @param {import("./size.js").Size} size Viewport size; unused if `onlyCenter` was specified.
+ * @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {import("./coordinate.js").Coordinate|undefined} Center.
*/
- function(center) {
+ function(center, resolution, size, opt_isMoving) {
if (center) {
- return [
- clamp(center[0], extent[0], extent[2]),
- clamp(center[1], extent[1], extent[3])
- ];
+ const viewWidth = onlyCenter ? 0 : size[0] * resolution;
+ const viewHeight = onlyCenter ? 0 : size[1] * resolution;
+ let minX = extent[0] + viewWidth / 2;
+ let maxX = extent[2] - viewWidth / 2;
+ let minY = extent[1] + viewHeight / 2;
+ let maxY = extent[3] - viewHeight / 2;
+
+ // note: when zooming out of bounds, min and max values for x and y may
+ // end up inverted (min > max); this has to be accounted for
+ if (minX > maxX) {
+ minX = maxX = (maxX + minX) / 2;
+ }
+ if (minY > maxY) {
+ minY = maxY = (maxY + minY) / 2;
+ }
+
+ let x = clamp(center[0], minX, maxX);
+ let y = clamp(center[1], minY, maxY);
+ const ratio = 30 * resolution;
+
+ // during an interaction, allow some overscroll
+ if (opt_isMoving && smooth) {
+ x += -ratio * Math.log(1 + Math.max(0, minX - center[0]) / ratio) +
+ ratio * Math.log(1 + Math.max(0, center[0] - maxX) / ratio);
+ y += -ratio * Math.log(1 + Math.max(0, minY - center[1]) / ratio) +
+ ratio * Math.log(1 + Math.max(0, center[1] - maxY) / ratio);
+ }
+
+ return [x, y];
} else {
return undefined;
}
diff --git a/src/ol/control/Zoom.js b/src/ol/control/Zoom.js
index 5e016950a9..379e703fc4 100644
--- a/src/ol/control/Zoom.js
+++ b/src/ol/control/Zoom.js
@@ -114,20 +114,20 @@ class Zoom extends Control {
// upon it
return;
}
- const currentResolution = view.getResolution();
- if (currentResolution) {
- const newResolution = view.constrainResolution(currentResolution, delta);
+ const currentZoom = view.getZoom();
+ if (currentZoom !== undefined) {
+ const newZoom = view.getConstrainedZoom(currentZoom + delta);
if (this.duration_ > 0) {
if (view.getAnimating()) {
view.cancelAnimations();
}
view.animate({
- resolution: newResolution,
+ zoom: newZoom,
duration: this.duration_,
easing: easeOut
});
} else {
- view.setResolution(newResolution);
+ view.setZoom(newZoom);
}
}
}
diff --git a/src/ol/control/ZoomSlider.js b/src/ol/control/ZoomSlider.js
index fa0b3560cb..050cb0d021 100644
--- a/src/ol/control/ZoomSlider.js
+++ b/src/ol/control/ZoomSlider.js
@@ -1,7 +1,6 @@
/**
* @module ol/control/ZoomSlider
*/
-import ViewHint from '../ViewHint.js';
import Control from './Control.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE} from '../css.js';
import {easeOut} from '../easing.js';
@@ -102,13 +101,13 @@ class ZoomSlider extends Control {
* @type {number|undefined}
* @private
*/
- this.previousX_;
+ this.startX_;
/**
* @type {number|undefined}
* @private
*/
- this.previousY_;
+ this.startY_;
/**
* The calculated thumb size (border box plus margins). Set when initSlider_
@@ -218,9 +217,10 @@ class ZoomSlider extends Control {
event.offsetY - this.thumbSize_[1] / 2);
const resolution = this.getResolutionForPosition_(relativePosition);
+ const zoom = view.getConstrainedZoom(view.getZoomForResolution(resolution));
view.animate({
- resolution: view.constrainResolution(resolution),
+ zoom: zoom,
duration: this.duration_,
easing: easeOut
});
@@ -233,9 +233,10 @@ class ZoomSlider extends Control {
*/
handleDraggerStart_(event) {
if (!this.dragging_ && event.originalEvent.target === this.element.firstElementChild) {
- this.getMap().getView().setHint(ViewHint.INTERACTING, 1);
- this.previousX_ = event.clientX;
- this.previousY_ = event.clientY;
+ const element = /** @type {HTMLElement} */ (this.element.firstElementChild);
+ this.getMap().getView().beginInteraction();
+ this.startX_ = event.clientX - parseFloat(element.style.left);
+ this.startY_ = event.clientY - parseFloat(element.style.top);
this.dragging_ = true;
if (this.dragListenerKeys_.length === 0) {
@@ -259,15 +260,11 @@ class ZoomSlider extends Control {
*/
handleDraggerDrag_(event) {
if (this.dragging_) {
- const element = /** @type {HTMLElement} */ (this.element.firstElementChild);
- const deltaX = event.clientX - this.previousX_ + parseFloat(element.style.left);
- const deltaY = event.clientY - this.previousY_ + parseFloat(element.style.top);
+ const deltaX = event.clientX - this.startX_;
+ const deltaY = event.clientY - this.startY_;
const relativePosition = this.getRelativePosition_(deltaX, deltaY);
this.currentResolution_ = this.getResolutionForPosition_(relativePosition);
this.getMap().getView().setResolution(this.currentResolution_);
- this.setThumbPosition_(this.currentResolution_);
- this.previousX_ = event.clientX;
- this.previousY_ = event.clientY;
}
}
@@ -279,17 +276,11 @@ class ZoomSlider extends Control {
handleDraggerEnd_(event) {
if (this.dragging_) {
const view = this.getMap().getView();
- view.setHint(ViewHint.INTERACTING, -1);
-
- view.animate({
- resolution: view.constrainResolution(this.currentResolution_),
- duration: this.duration_,
- easing: easeOut
- });
+ view.endInteraction();
this.dragging_ = false;
- this.previousX_ = undefined;
- this.previousY_ = undefined;
+ this.startX_ = undefined;
+ this.startY_ = undefined;
this.dragListenerKeys_.forEach(unlistenByKey);
this.dragListenerKeys_.length = 0;
}
@@ -356,7 +347,7 @@ class ZoomSlider extends Control {
*/
getPositionForResolution_(res) {
const fn = this.getMap().getView().getValueForResolutionFunction();
- return 1 - fn(res);
+ return clamp(1 - fn(res), 0, 1);
}
}
@@ -375,10 +366,8 @@ export function render(mapEvent) {
this.initSlider_();
}
const res = mapEvent.frameState.viewState.resolution;
- if (res !== this.currentResolution_) {
- this.currentResolution_ = res;
- this.setThumbPosition_(res);
- }
+ this.currentResolution_ = res;
+ this.setThumbPosition_(res);
}
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/DragPan.js b/src/ol/interaction/DragPan.js
index d889830f33..cfcb9c1530 100644
--- a/src/ol/interaction/DragPan.js
+++ b/src/ol/interaction/DragPan.js
@@ -1,8 +1,7 @@
/**
* @module ol/interaction/DragPan
*/
-import ViewHint from '../ViewHint.js';
-import {scale as scaleCoordinate, rotate as rotateCoordinate, add as addCoordinate} from '../coordinate.js';
+import {scale as scaleCoordinate, rotate as rotateCoordinate} from '../coordinate.js';
import {easeOut} from '../easing.js';
import {noModifierKeys} from '../events/condition.js';
import {FALSE} from '../functions.js';
@@ -74,10 +73,6 @@ class DragPan extends PointerInteraction {
* @inheritDoc
*/
handleDragEvent(mapBrowserEvent) {
- if (!this.panning_) {
- this.panning_ = true;
- this.getMap().getView().setHint(ViewHint.INTERACTING, 1);
- }
const targetPointers = this.targetPointers;
const centroid = centroidFromPointers(targetPointers);
if (targetPointers.length == this.lastPointersCount_) {
@@ -85,16 +80,15 @@ class DragPan extends PointerInteraction {
this.kinetic_.update(centroid[0], centroid[1]);
}
if (this.lastCentroid) {
- const deltaX = this.lastCentroid[0] - centroid[0];
- const deltaY = centroid[1] - this.lastCentroid[1];
+ const delta = [
+ this.lastCentroid[0] - centroid[0],
+ centroid[1] - this.lastCentroid[1]
+ ];
const map = mapBrowserEvent.map;
const view = map.getView();
- let center = [deltaX, deltaY];
- scaleCoordinate(center, view.getResolution());
- rotateCoordinate(center, view.getRotation());
- addCoordinate(center, view.getCenter());
- center = view.constrainCenter(center);
- view.setCenter(center);
+ scaleCoordinate(delta, view.getResolution());
+ rotateCoordinate(delta, view.getRotation());
+ view.adjustCenter(delta);
}
} else if (this.kinetic_) {
// reset so we don't overestimate the kinetic energy after
@@ -122,14 +116,14 @@ class DragPan extends PointerInteraction {
centerpx[1] - distance * Math.sin(angle)
]);
view.animate({
- center: view.constrainCenter(dest),
+ center: view.getConstrainedCenter(dest),
duration: 500,
easing: easeOut
});
}
if (this.panning_) {
this.panning_ = false;
- view.setHint(ViewHint.INTERACTING, -1);
+ view.endInteraction();
}
return false;
} else {
@@ -153,7 +147,11 @@ class DragPan extends PointerInteraction {
this.lastCentroid = null;
// stop any current animation
if (view.getAnimating()) {
- view.setCenter(mapBrowserEvent.frameState.viewState.center);
+ view.cancelAnimations();
+ }
+ if (!this.panning_) {
+ this.panning_ = true;
+ this.getMap().getView().beginInteraction();
}
if (this.kinetic_) {
this.kinetic_.begin();
diff --git a/src/ol/interaction/DragRotate.js b/src/ol/interaction/DragRotate.js
index 68995ccf08..d7fc11cfd6 100644
--- a/src/ol/interaction/DragRotate.js
+++ b/src/ol/interaction/DragRotate.js
@@ -2,10 +2,8 @@
* @module ol/interaction/DragRotate
*/
import {disable} from '../rotationconstraint.js';
-import ViewHint from '../ViewHint.js';
import {altShiftKeysOnly, mouseOnly, mouseActionButton} from '../events/condition.js';
import {FALSE} from '../functions.js';
-import {rotate, rotateWithoutConstraints} from './Interaction.js';
import PointerInteraction from './Pointer.js';
@@ -80,8 +78,7 @@ class DragRotate extends PointerInteraction {
Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2);
if (this.lastAngle_ !== undefined) {
const delta = theta - this.lastAngle_;
- const rotation = view.getRotation();
- rotateWithoutConstraints(view, rotation - delta);
+ view.adjustRotation(-delta);
}
this.lastAngle_ = theta;
}
@@ -97,9 +94,7 @@ class DragRotate extends PointerInteraction {
const map = mapBrowserEvent.map;
const view = map.getView();
- view.setHint(ViewHint.INTERACTING, -1);
- const rotation = view.getRotation();
- rotate(view, rotation, undefined, this.duration_);
+ view.endInteraction(this.duration_);
return false;
}
@@ -114,7 +109,7 @@ class DragRotate extends PointerInteraction {
if (mouseActionButton(mapBrowserEvent) && this.condition_(mapBrowserEvent)) {
const map = mapBrowserEvent.map;
- map.getView().setHint(ViewHint.INTERACTING, 1);
+ map.getView().beginInteraction();
this.lastAngle_ = undefined;
return true;
} else {
diff --git a/src/ol/interaction/DragRotateAndZoom.js b/src/ol/interaction/DragRotateAndZoom.js
index cf04606327..3e750df327 100644
--- a/src/ol/interaction/DragRotateAndZoom.js
+++ b/src/ol/interaction/DragRotateAndZoom.js
@@ -1,10 +1,7 @@
/**
* @module ol/interaction/DragRotateAndZoom
*/
-import {disable} from '../rotationconstraint.js';
-import ViewHint from '../ViewHint.js';
import {shiftKeyOnly, mouseOnly} from '../events/condition.js';
-import {rotate, rotateWithoutConstraints, zoom, zoomWithoutConstraints} from './Interaction.js';
import PointerInteraction from './Pointer.js';
@@ -88,14 +85,13 @@ class DragRotateAndZoom extends PointerInteraction {
const theta = Math.atan2(deltaY, deltaX);
const magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const view = map.getView();
- if (view.getConstraints().rotation !== disable && this.lastAngle_ !== undefined) {
- const angleDelta = theta - this.lastAngle_;
- rotateWithoutConstraints(view, view.getRotation() - angleDelta);
+ if (this.lastAngle_ !== undefined) {
+ const angleDelta = this.lastAngle_ - theta;
+ view.adjustRotation(angleDelta);
}
this.lastAngle_ = theta;
if (this.lastMagnitude_ !== undefined) {
- const resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
- zoomWithoutConstraints(view, resolution);
+ view.adjustResolution(this.lastMagnitude_ / magnitude);
}
if (this.lastMagnitude_ !== undefined) {
this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
@@ -113,10 +109,8 @@ class DragRotateAndZoom extends PointerInteraction {
const map = mapBrowserEvent.map;
const view = map.getView();
- view.setHint(ViewHint.INTERACTING, -1);
- const direction = this.lastScaleDelta_ - 1;
- rotate(view, view.getRotation());
- zoom(view, view.getResolution(), undefined, this.duration_, direction);
+ const direction = this.lastScaleDelta_ > 1 ? 1 : -1;
+ view.endInteraction(this.duration_, direction);
this.lastScaleDelta_ = 0;
return false;
}
@@ -130,7 +124,7 @@ class DragRotateAndZoom extends PointerInteraction {
}
if (this.condition_(mapBrowserEvent)) {
- mapBrowserEvent.map.getView().setHint(ViewHint.INTERACTING, 1);
+ mapBrowserEvent.map.getView().beginInteraction();
this.lastAngle_ = undefined;
this.lastMagnitude_ = undefined;
return true;
diff --git a/src/ol/interaction/DragZoom.js b/src/ol/interaction/DragZoom.js
index 83b70c5b47..4050193eb1 100644
--- a/src/ol/interaction/DragZoom.js
+++ b/src/ol/interaction/DragZoom.js
@@ -80,11 +80,8 @@ function onBoxEnd() {
extent = mapExtent;
}
- const resolution = view.constrainResolution(
- view.getResolutionForExtent(extent, size));
-
- let center = getCenter(extent);
- center = view.constrainCenter(center);
+ const resolution = view.getConstrainedResolution(view.getResolutionForExtent(extent, size));
+ const center = view.getConstrainedCenter(getCenter(extent), resolution);
view.animate({
resolution: resolution,
diff --git a/src/ol/interaction/Interaction.js b/src/ol/interaction/Interaction.js
index 6a13a03857..80a9644758 100644
--- a/src/ol/interaction/Interaction.js
+++ b/src/ol/interaction/Interaction.js
@@ -4,7 +4,6 @@
import BaseObject from '../Object.js';
import {easeOut, linear} from '../easing.js';
import InteractionProperty from './Property.js';
-import {clamp} from '../math.js';
/**
@@ -111,77 +110,15 @@ class Interaction extends BaseObject {
export function pan(view, delta, opt_duration) {
const currentCenter = view.getCenter();
if (currentCenter) {
- const center = view.constrainCenter(
- [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]);
- if (opt_duration) {
- view.animate({
- duration: opt_duration,
- easing: linear,
- center: center
- });
- } else {
- view.setCenter(center);
- }
+ const center = [currentCenter[0] + delta[0], currentCenter[1] + delta[1]];
+ view.animate({
+ duration: opt_duration !== undefined ? opt_duration : 250,
+ easing: linear,
+ center: view.getConstrainedCenter(center)
+ });
}
}
-
-/**
- * @param {import("../View.js").default} view View.
- * @param {number|undefined} rotation Rotation.
- * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-export function rotate(view, rotation, opt_anchor, opt_duration) {
- rotation = view.constrainRotation(rotation, 0);
- rotateWithoutConstraints(view, rotation, opt_anchor, opt_duration);
-}
-
-
-/**
- * @param {import("../View.js").default} view View.
- * @param {number|undefined} rotation Rotation.
- * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-export function rotateWithoutConstraints(view, rotation, opt_anchor, opt_duration) {
- if (rotation !== undefined) {
- const currentRotation = view.getRotation();
- const currentCenter = view.getCenter();
- if (currentRotation !== undefined && currentCenter && opt_duration > 0) {
- view.animate({
- rotation: rotation,
- anchor: opt_anchor,
- duration: opt_duration,
- easing: easeOut
- });
- } else {
- view.rotate(rotation, opt_anchor);
- }
- }
-}
-
-
-/**
- * @param {import("../View.js").default} view View.
- * @param {number|undefined} resolution Resolution to go to.
- * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- * @param {number=} opt_direction Zooming direction; > 0 indicates
- * zooming out, in which case the constraints system will select
- * the largest nearest resolution; < 0 indicates zooming in, in
- * which case the constraints system will select the smallest
- * nearest resolution; == 0 indicates that the zooming direction
- * is unknown/not relevant, in which case the constraints system
- * will select the nearest resolution. If not defined 0 is
- * assumed.
- */
-export function zoom(view, resolution, opt_anchor, opt_duration, opt_direction) {
- resolution = view.constrainResolution(resolution, 0, opt_direction);
- zoomWithoutConstraints(view, resolution, opt_anchor, opt_duration);
-}
-
-
/**
* @param {import("../View.js").default} view View.
* @param {number} delta Delta from previous zoom level.
@@ -189,63 +126,24 @@ export function zoom(view, resolution, opt_anchor, opt_duration, opt_direction)
* @param {number=} opt_duration Duration.
*/
export function zoomByDelta(view, delta, opt_anchor, opt_duration) {
- const currentResolution = view.getResolution();
- let resolution = view.constrainResolution(currentResolution, delta, 0);
+ const currentZoom = view.getZoom();
- if (resolution !== undefined) {
- const resolutions = view.getResolutions();
- resolution = clamp(
- resolution,
- view.getMinResolution() || resolutions[resolutions.length - 1],
- view.getMaxResolution() || resolutions[0]);
+ if (currentZoom === undefined) {
+ return;
}
- // If we have a constraint on center, we need to change the anchor so that the
- // new center is within the extent. We first calculate the new center, apply
- // the constraint to it, and then calculate back the anchor
- if (opt_anchor && resolution !== undefined && resolution !== currentResolution) {
- const currentCenter = view.getCenter();
- let center = view.calculateCenterZoom(resolution, opt_anchor);
- center = view.constrainCenter(center);
+ const newZoom = view.getConstrainedZoom(currentZoom + delta);
+ const newResolution = view.getResolutionForZoom(newZoom);
- opt_anchor = [
- (resolution * currentCenter[0] - currentResolution * center[0]) /
- (resolution - currentResolution),
- (resolution * currentCenter[1] - currentResolution * center[1]) /
- (resolution - currentResolution)
- ];
- }
-
- zoomWithoutConstraints(view, resolution, opt_anchor, opt_duration);
-}
-
-
-/**
- * @param {import("../View.js").default} view View.
- * @param {number|undefined} resolution Resolution to go to.
- * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate.
- * @param {number=} opt_duration Duration.
- */
-export function zoomWithoutConstraints(view, resolution, opt_anchor, opt_duration) {
- if (resolution) {
- const currentResolution = view.getResolution();
- const currentCenter = view.getCenter();
- if (currentResolution !== undefined && currentCenter &&
- resolution !== currentResolution && opt_duration) {
- view.animate({
- resolution: resolution,
- anchor: opt_anchor,
- duration: opt_duration,
- easing: easeOut
- });
- } else {
- if (opt_anchor) {
- const center = view.calculateCenterZoom(resolution, opt_anchor);
- view.setCenter(center);
- }
- view.setResolution(resolution);
- }
+ if (view.getAnimating()) {
+ view.cancelAnimations();
}
+ view.animate({
+ resolution: newResolution,
+ anchor: opt_anchor,
+ duration: opt_duration !== undefined ? opt_duration : 250,
+ easing: easeOut
+ });
}
export default Interaction;
diff --git a/src/ol/interaction/MouseWheelZoom.js b/src/ol/interaction/MouseWheelZoom.js
index 726ee1ba62..30548d2587 100644
--- a/src/ol/interaction/MouseWheelZoom.js
+++ b/src/ol/interaction/MouseWheelZoom.js
@@ -1,9 +1,7 @@
/**
* @module ol/interaction/MouseWheelZoom
*/
-import ViewHint from '../ViewHint.js';
import {always} from '../events/condition.js';
-import {easeOut} from '../easing.js';
import EventType from '../events/EventType.js';
import {DEVICE_PIXEL_RATIO, FIREFOX, SAFARI} from '../has.js';
import Interaction, {zoomByDelta} from './Interaction.js';
@@ -34,9 +32,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.
@@ -62,7 +57,13 @@ class MouseWheelZoom extends Interaction {
* @private
* @type {number}
*/
- this.delta_ = 0;
+ this.totalDelta_ = 0;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.lastDelta_ = 0;
/**
* @private
@@ -82,12 +83,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}
@@ -137,22 +132,15 @@ class MouseWheelZoom extends Interaction {
*/
this.trackpadDeltaPerZoom_ = 300;
- /**
- * The zoom factor by which scroll zooming is allowed to exceed the limits.
- * @private
- * @type {number}
- */
- this.trackpadZoomBuffer_ = 1.5;
-
}
/**
* @private
*/
- decrementInteractingHint_() {
+ endInteraction_() {
this.trackpadTimeoutId_ = undefined;
const view = this.getMap().getView();
- view.setHint(ViewHint.INTERACTING, -1);
+ view.endInteraction(undefined, Math.sign(this.lastDelta_), this.lastAnchor_);
}
/**
@@ -199,6 +187,8 @@ class MouseWheelZoom extends Interaction {
if (delta === 0) {
return false;
+ } else {
+ this.lastDelta_ = delta;
}
const now = Date.now();
@@ -218,55 +208,15 @@ class MouseWheelZoom extends Interaction {
if (this.trackpadTimeoutId_) {
clearTimeout(this.trackpadTimeoutId_);
} else {
- view.setHint(ViewHint.INTERACTING, 1);
- }
- this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_);
- let resolution = view.getResolution() * Math.pow(2, delta / this.trackpadDeltaPerZoom_);
- const minResolution = view.getMinResolution();
- const maxResolution = view.getMaxResolution();
- let rebound = 0;
- if (resolution < minResolution) {
- resolution = Math.max(resolution, minResolution / this.trackpadZoomBuffer_);
- rebound = 1;
- } else if (resolution > maxResolution) {
- resolution = Math.min(resolution, maxResolution * this.trackpadZoomBuffer_);
- rebound = -1;
- }
- if (this.lastAnchor_) {
- const center = view.calculateCenterZoom(resolution, this.lastAnchor_);
- view.setCenter(view.constrainCenter(center));
- }
- view.setResolution(resolution);
-
- if (rebound === 0 && this.constrainResolution_) {
- view.animate({
- resolution: view.constrainResolution(resolution, delta > 0 ? -1 : 1),
- easing: easeOut,
- anchor: this.lastAnchor_,
- duration: this.duration_
- });
- }
-
- if (rebound > 0) {
- view.animate({
- resolution: minResolution,
- easing: easeOut,
- anchor: this.lastAnchor_,
- duration: 500
- });
- } else if (rebound < 0) {
- view.animate({
- resolution: maxResolution,
- easing: easeOut,
- anchor: this.lastAnchor_,
- duration: 500
- });
+ view.beginInteraction();
}
+ this.trackpadTimeoutId_ = setTimeout(this.endInteraction_.bind(this), this.trackpadEventGap_);
+ view.adjustZoom(-delta / this.trackpadDeltaPerZoom_, this.lastAnchor_);
this.startTime_ = now;
return false;
}
- this.delta_ += delta;
+ this.totalDelta_ += delta;
const timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0);
@@ -286,10 +236,10 @@ class MouseWheelZoom extends Interaction {
view.cancelAnimations();
}
const maxDelta = MAX_DELTA;
- const delta = clamp(this.delta_, -maxDelta, maxDelta);
+ const delta = clamp(this.totalDelta_, -maxDelta, maxDelta);
zoomByDelta(view, -delta, this.lastAnchor_, this.duration_);
this.mode_ = undefined;
- this.delta_ = 0;
+ this.totalDelta_ = 0;
this.lastAnchor_ = null;
this.startTime_ = undefined;
this.timeoutId_ = undefined;
diff --git a/src/ol/interaction/PinchRotate.js b/src/ol/interaction/PinchRotate.js
index 82f8c254e5..c1143ba312 100644
--- a/src/ol/interaction/PinchRotate.js
+++ b/src/ol/interaction/PinchRotate.js
@@ -1,9 +1,7 @@
/**
* @module ol/interaction/PinchRotate
*/
-import ViewHint from '../ViewHint.js';
import {FALSE} from '../functions.js';
-import {rotate, rotateWithoutConstraints} from './Interaction.js';
import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js';
import {disable} from '../rotationconstraint.js';
@@ -118,9 +116,8 @@ class PinchRotate extends PointerInteraction {
// rotate
if (this.rotating_) {
- const rotation = view.getRotation();
map.render();
- rotateWithoutConstraints(view, rotation + rotationDelta, this.anchor_);
+ view.adjustRotation(rotationDelta, this.anchor_);
}
}
@@ -131,11 +128,7 @@ class PinchRotate extends PointerInteraction {
if (this.targetPointers.length < 2) {
const map = mapBrowserEvent.map;
const view = map.getView();
- view.setHint(ViewHint.INTERACTING, -1);
- if (this.rotating_) {
- const rotation = view.getRotation();
- rotate(view, rotation, this.anchor_, this.duration_);
- }
+ view.endInteraction(this.duration_);
return false;
} else {
return true;
@@ -153,7 +146,7 @@ class PinchRotate extends PointerInteraction {
this.rotating_ = false;
this.rotationDelta_ = 0.0;
if (!this.handlingDownUpSequence) {
- map.getView().setHint(ViewHint.INTERACTING, 1);
+ map.getView().beginInteraction();
}
return true;
} else {
diff --git a/src/ol/interaction/PinchZoom.js b/src/ol/interaction/PinchZoom.js
index ed9446e318..9d352bafe6 100644
--- a/src/ol/interaction/PinchZoom.js
+++ b/src/ol/interaction/PinchZoom.js
@@ -1,17 +1,13 @@
/**
* @module ol/interaction/PinchZoom
*/
-import ViewHint from '../ViewHint.js';
import {FALSE} from '../functions.js';
-import {zoom, zoomWithoutConstraints} from './Interaction.js';
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 +33,6 @@ class PinchZoom extends PointerInteraction {
super(pointerOptions);
- /**
- * @private
- * @type {boolean}
- */
- this.constrainResolution_ = options.constrainResolution || false;
-
/**
* @private
* @type {import("../coordinate.js").Coordinate}
@@ -91,17 +81,6 @@ class PinchZoom extends PointerInteraction {
const map = mapBrowserEvent.map;
const view = map.getView();
- const resolution = view.getResolution();
- const maxResolution = view.getMaxResolution();
- const minResolution = view.getMinResolution();
- let newResolution = resolution * scaleDelta;
- if (newResolution > maxResolution) {
- scaleDelta = maxResolution / resolution;
- newResolution = maxResolution;
- } else if (newResolution < minResolution) {
- scaleDelta = minResolution / resolution;
- newResolution = minResolution;
- }
if (scaleDelta != 1.0) {
this.lastScaleDelta_ = scaleDelta;
@@ -116,7 +95,7 @@ class PinchZoom extends PointerInteraction {
// scale, bypass the resolution constraint
map.render();
- zoomWithoutConstraints(view, newResolution, this.anchor_);
+ view.adjustResolution(scaleDelta, this.anchor_);
}
/**
@@ -126,17 +105,8 @@ class PinchZoom extends PointerInteraction {
if (this.targetPointers.length < 2) {
const map = mapBrowserEvent.map;
const view = map.getView();
- view.setHint(ViewHint.INTERACTING, -1);
- const resolution = view.getResolution();
- if (this.constrainResolution_ ||
- resolution < view.getMinResolution() ||
- resolution > view.getMaxResolution()) {
- // Zoom to final resolution, with an animation, and provide a
- // direction not to zoom out/in if user was pinching in/out.
- // Direction is > 0 if pinching out, and < 0 if pinching in.
- const direction = this.lastScaleDelta_ - 1;
- zoom(view, resolution, this.anchor_, this.duration_, direction);
- }
+ const direction = this.lastScaleDelta_ > 1 ? 1 : -1;
+ view.endInteraction(this.duration_, direction);
return false;
} else {
return true;
@@ -153,7 +123,7 @@ class PinchZoom extends PointerInteraction {
this.lastDistance_ = undefined;
this.lastScaleDelta_ = 1;
if (!this.handlingDownUpSequence) {
- map.getView().setHint(ViewHint.INTERACTING, 1);
+ map.getView().beginInteraction();
}
return true;
} else {
diff --git a/src/ol/resolutionconstraint.js b/src/ol/resolutionconstraint.js
index 8e85618832..2e12a1a69d 100644
--- a/src/ol/resolutionconstraint.js
+++ b/src/ol/resolutionconstraint.js
@@ -2,37 +2,87 @@
* @module ol/resolutionconstraint
*/
import {linearFindNearest} from './array.js';
-import {clamp} from './math.js';
+import {getHeight, getWidth} from './extent';
+import {clamp} from './math';
/**
- * @typedef {function((number|undefined), number, number): (number|undefined)} Type
+ * @typedef {function((number|undefined), number, import("./size.js").Size, boolean=): (number|undefined)} Type
*/
+/**
+ * Returns a modified resolution taking into acocunt the viewport size and maximum
+ * allowed extent.
+ * @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 getViewportClampedResolution(resolution, maxExtent, viewportSize) {
+ const xResolution = getWidth(maxExtent) / viewportSize[0];
+ const yResolution = getHeight(maxExtent) / viewportSize[1];
+ return Math.min(resolution, Math.min(xResolution, yResolution));
+}
+
+/**
+ * Returns a modified resolution to be between maxResolution and minResolution while
+ * still allowing the value to be slightly out of bounds.
+ * Note: the computation is based on the logarithm function (ln):
+ * - at 1, ln(x) is 0
+ * - above 1, ln(x) keeps increasing but at a much slower pace than x
+ * The final result is clamped to prevent getting too far away from bounds.
+ * @param {number} resolution Resolution.
+ * @param {number} maxResolution Max resolution.
+ * @param {number} minResolution Min resolution.
+ * @return {number} Smoothed resolution.
+ */
+function getSmoothClampedResolution(resolution, maxResolution, minResolution) {
+ let result = Math.min(resolution, maxResolution);
+ const ratio = 50;
+
+ result *= Math.log(1 + ratio * Math.max(0, resolution / maxResolution - 1)) / ratio + 1;
+ if (minResolution) {
+ result = Math.max(result, minResolution);
+ result /= Math.log(1 + ratio * Math.max(0, minResolution / resolution - 1)) / ratio + 1;
+ }
+ return clamp(result, minResolution / 2, maxResolution * 2);
+}
/**
* @param {Array} resolutions Resolutions.
+ * @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
+ * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function.
*/
-export function createSnapToResolutions(resolutions) {
+export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
- * @param {number} delta Delta.
* @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, delta, direction) {
+ function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
- let z = linearFindNearest(resolutions, resolution, direction);
- z = clamp(z + delta, 0, resolutions.length - 1);
- const index = Math.floor(z);
- if (z != index && index < resolutions.length - 1) {
- const power = resolutions[index] / resolutions[index + 1];
- return resolutions[index] / Math.pow(power, z - index);
- } else {
- return resolutions[index];
+ const maxResolution = resolutions[0];
+ const minResolution = resolutions[resolutions.length - 1];
+ const cappedMaxRes = opt_maxExtent ?
+ getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
+ maxResolution;
+
+ // during interacting or animating, allow intermediary values
+ if (opt_isMoving) {
+ const smooth = opt_smooth !== undefined ? opt_smooth : true;
+ if (!smooth) {
+ return clamp(resolution, minResolution, cappedMaxRes);
+ }
+ return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
}
+
+ const capped = Math.min(cappedMaxRes, resolution);
+ const z = Math.floor(linearFindNearest(resolutions, capped, direction));
+ return resolutions[z];
} else {
return undefined;
}
@@ -44,29 +94,78 @@ export function createSnapToResolutions(resolutions) {
/**
* @param {number} power Power.
* @param {number} maxResolution Maximum resolution.
- * @param {number=} opt_maxLevel Maximum level.
+ * @param {number=} opt_minResolution Minimum resolution.
+ * @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
+ * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function.
*/
-export function createSnapToPower(power, maxResolution, opt_maxLevel) {
+export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
- * @param {number} delta Delta.
* @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, delta, direction) {
+ function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
- const offset = -direction / 2 + 0.5;
- const oldLevel = Math.floor(
- Math.log(maxResolution / resolution) / Math.log(power) + offset);
- let newLevel = Math.max(oldLevel + delta, 0);
- if (opt_maxLevel !== undefined) {
- newLevel = Math.min(newLevel, opt_maxLevel);
+ const cappedMaxRes = opt_maxExtent ?
+ getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
+ maxResolution;
+ const minResolution = opt_minResolution !== undefined ? opt_minResolution : 0;
+
+ // during interacting or animating, allow intermediary values
+ if (opt_isMoving) {
+ const smooth = opt_smooth !== undefined ? opt_smooth : true;
+ if (!smooth) {
+ return clamp(resolution, minResolution, cappedMaxRes);
+ }
+ return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
}
- return maxResolution / Math.pow(power, newLevel);
+
+ const offset = -direction * (0.5 - 1e-9) + 0.5;
+ const capped = Math.min(cappedMaxRes, resolution);
+ const zoomLevel = Math.floor(
+ Math.log(maxResolution / capped) / Math.log(power) + offset);
+ const newResolution = maxResolution / Math.pow(power, zoomLevel);
+ return clamp(newResolution, minResolution, cappedMaxRes);
} else {
return undefined;
}
});
}
+
+/**
+ * @param {number} maxResolution Max resolution.
+ * @param {number} minResolution Min resolution.
+ * @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
+ * @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
+ * @return {Type} Zoom function.
+ */
+export function createMinMaxResolution(maxResolution, minResolution, opt_smooth, 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 cappedMaxRes = opt_maxExtent ?
+ getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
+ maxResolution;
+ const smooth = opt_smooth !== undefined ? opt_smooth : true;
+
+ if (!smooth || !opt_isMoving) {
+ return clamp(resolution, minResolution, cappedMaxRes);
+ }
+ return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
+ } else {
+ return undefined;
+ }
+ }
+ );
+}
diff --git a/src/ol/rotationconstraint.js b/src/ol/rotationconstraint.js
index 6abc294e42..3da582e459 100644
--- a/src/ol/rotationconstraint.js
+++ b/src/ol/rotationconstraint.js
@@ -5,16 +5,15 @@ import {toRadians} from './math.js';
/**
- * @typedef {function((number|undefined), number): (number|undefined)} Type
+ * @typedef {function((number|undefined), boolean=): (number|undefined)} Type
*/
/**
* @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
* @return {number|undefined} Rotation.
*/
-export function disable(rotation, delta) {
+export function disable(rotation) {
if (rotation !== undefined) {
return 0;
} else {
@@ -25,12 +24,11 @@ export function disable(rotation, delta) {
/**
* @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
* @return {number|undefined} Rotation.
*/
-export function none(rotation, delta) {
+export function none(rotation) {
if (rotation !== undefined) {
- return rotation + delta;
+ return rotation;
} else {
return undefined;
}
@@ -46,12 +44,16 @@ export function createSnapToN(n) {
return (
/**
* @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
+ * @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Rotation.
*/
- function(rotation, delta) {
+ function(rotation, opt_isMoving) {
+ if (opt_isMoving) {
+ return rotation;
+ }
+
if (rotation !== undefined) {
- rotation = Math.floor((rotation + delta) / theta + 0.5) * theta;
+ rotation = Math.floor(rotation / theta + 0.5) * theta;
return rotation;
} else {
return undefined;
@@ -69,15 +71,19 @@ export function createSnapToZero(opt_tolerance) {
return (
/**
* @param {number|undefined} rotation Rotation.
- * @param {number} delta Delta.
+ * @param {boolean} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Rotation.
*/
- function(rotation, delta) {
+ function(rotation, opt_isMoving) {
+ if (opt_isMoving) {
+ return rotation;
+ }
+
if (rotation !== undefined) {
- if (Math.abs(rotation + delta) <= tolerance) {
+ if (Math.abs(rotation) <= tolerance) {
return 0;
} else {
- return rotation + delta;
+ return rotation;
}
} else {
return undefined;
diff --git a/test/spec/ol/interaction/dragrotateandzoom.test.js b/test/spec/ol/interaction/dragrotateandzoom.test.js
index 7a9249182e..47bc94cab4 100644
--- a/test/spec/ol/interaction/dragrotateandzoom.test.js
+++ b/test/spec/ol/interaction/dragrotateandzoom.test.js
@@ -62,13 +62,18 @@ describe('ol.interaction.DragRotateAndZoom', function() {
true);
interaction.lastAngle_ = Math.PI;
- let view = map.getView();
- let spy = sinon.spy(view, 'rotate');
- interaction.handleDragEvent(event);
- expect(spy.callCount).to.be(1);
- expect(interaction.lastAngle_).to.be(-0.8308214428190254);
- view.rotate.restore();
+ let callCount = 0;
+ let view = map.getView();
+ view.on('change:rotation', function() {
+ callCount++;
+ });
+
+ interaction.handleDragEvent(event);
+ expect(callCount).to.be(1);
+ expect(interaction.lastAngle_).to.be(-0.8308214428190254);
+
+ callCount = 0;
view = new View({
projection: 'EPSG:4326',
center: [0, 0],
@@ -76,15 +81,16 @@ describe('ol.interaction.DragRotateAndZoom', function() {
enableRotation: false
});
map.setView(view);
+ view.on('change:rotation', function() {
+ callCount++;
+ });
event = new MapBrowserPointerEvent('pointermove', map,
new PointerEvent('pointermove', {clientX: 24, clientY: 16}, {pointerType: 'mouse'}),
true);
- spy = sinon.spy(view, 'rotate');
interaction.handleDragEvent(event);
- expect(spy.callCount).to.be(0);
- view.rotate.restore();
+ expect(callCount).to.be(0);
});
});
diff --git a/test/spec/ol/interaction/dragzoom.test.js b/test/spec/ol/interaction/dragzoom.test.js
index de9f3ec6af..9746ee25ca 100644
--- a/test/spec/ol/interaction/dragzoom.test.js
+++ b/test/spec/ol/interaction/dragzoom.test.js
@@ -103,7 +103,7 @@ describe('ol.interaction.DragZoom', function() {
setTimeout(function() {
const view = map.getView();
const resolution = view.getResolution();
- expect(resolution).to.eql(view.constrainResolution(0.5));
+ expect(resolution).to.eql(view.getConstrainedResolution(0.5));
done();
}, 50);
}, 50);
diff --git a/test/spec/ol/interaction/interaction.test.js b/test/spec/ol/interaction/interaction.test.js
index 000a575eda..b83f949edb 100644
--- a/test/spec/ol/interaction/interaction.test.js
+++ b/test/spec/ol/interaction/interaction.test.js
@@ -1,7 +1,6 @@
import Map from '../../../../src/ol/Map.js';
-import View from '../../../../src/ol/View.js';
import EventTarget from '../../../../src/ol/events/Target.js';
-import Interaction, {zoomByDelta} from '../../../../src/ol/interaction/Interaction.js';
+import Interaction from '../../../../src/ol/interaction/Interaction.js';
import {FALSE} from '../../../../src/ol/functions.js';
describe('ol.interaction.Interaction', function() {
@@ -87,67 +86,4 @@ describe('ol.interaction.Interaction', function() {
});
- describe('zoomByDelta()', function() {
-
- it('changes view resolution', function() {
- const view = new View({
- resolution: 1,
- resolutions: [4, 2, 1, 0.5, 0.25]
- });
-
- zoomByDelta(view, 1);
- expect(view.getResolution()).to.be(0.5);
-
- zoomByDelta(view, -1);
- expect(view.getResolution()).to.be(1);
-
- zoomByDelta(view, 2);
- expect(view.getResolution()).to.be(0.25);
-
- zoomByDelta(view, -2);
- expect(view.getResolution()).to.be(1);
- });
-
- it('changes view resolution and center relative to the anchor', function() {
- const view = new View({
- center: [0, 0],
- resolution: 1,
- resolutions: [4, 2, 1, 0.5, 0.25]
- });
-
- zoomByDelta(view, 1, [10, 10]);
- expect(view.getCenter()).to.eql([5, 5]);
-
- zoomByDelta(view, -1, [0, 0]);
- expect(view.getCenter()).to.eql([10, 10]);
-
- zoomByDelta(view, 2, [0, 0]);
- expect(view.getCenter()).to.eql([2.5, 2.5]);
-
- zoomByDelta(view, -2, [0, 0]);
- expect(view.getCenter()).to.eql([10, 10]);
- });
-
- it('changes view resolution and center relative to the anchor, while respecting the extent', function() {
- const view = new View({
- center: [0, 0],
- extent: [-2.5, -2.5, 2.5, 2.5],
- resolution: 1,
- resolutions: [4, 2, 1, 0.5, 0.25]
- });
-
- zoomByDelta(view, 1, [10, 10]);
- expect(view.getCenter()).to.eql([2.5, 2.5]);
-
- zoomByDelta(view, -1, [0, 0]);
- expect(view.getCenter()).to.eql([2.5, 2.5]);
-
- zoomByDelta(view, 2, [10, 10]);
- expect(view.getCenter()).to.eql([2.5, 2.5]);
-
- zoomByDelta(view, -2, [0, 0]);
- expect(view.getCenter()).to.eql([2.5, 2.5]);
- });
- });
-
});
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/resolutionconstraint.test.js b/test/spec/ol/resolutionconstraint.test.js
index c48ea0fe10..2fc2649d77 100644
--- a/test/spec/ol/resolutionconstraint.test.js
+++ b/test/spec/ol/resolutionconstraint.test.js
@@ -1,4 +1,5 @@
import {createSnapToResolutions, createSnapToPower} from '../../../src/ol/resolutionconstraint.js';
+import {createMinMaxResolution} from '../../../src/ol/resolutionconstraint';
describe('ol.resolutionconstraint', function() {
@@ -12,30 +13,30 @@ describe('ol.resolutionconstraint', function() {
[1000, 500, 250, 100]);
});
- describe('delta 0', function() {
+ describe('direction 0', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1000, 0, 0)).to.eql(1000);
- expect(resolutionConstraint(500, 0, 0)).to.eql(500);
- expect(resolutionConstraint(250, 0, 0)).to.eql(250);
- expect(resolutionConstraint(100, 0, 0)).to.eql(100);
+ expect(resolutionConstraint(1000, 0)).to.eql(1000);
+ expect(resolutionConstraint(500, 0)).to.eql(500);
+ expect(resolutionConstraint(250, 0)).to.eql(250);
+ expect(resolutionConstraint(100, 0)).to.eql(100);
});
});
- describe('zoom in', function() {
+ describe('direction 1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1000, 1, 0)).to.eql(500);
- expect(resolutionConstraint(500, 1, 0)).to.eql(250);
- expect(resolutionConstraint(250, 1, 0)).to.eql(100);
- expect(resolutionConstraint(100, 1, 0)).to.eql(100);
+ expect(resolutionConstraint(1000, 1)).to.eql(1000);
+ expect(resolutionConstraint(500, 1)).to.eql(500);
+ expect(resolutionConstraint(250, 1)).to.eql(250);
+ expect(resolutionConstraint(100, 1)).to.eql(100);
});
});
- describe('zoom out', function() {
+ describe('direction -1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1000, -1, 0)).to.eql(1000);
- expect(resolutionConstraint(500, -1, 0)).to.eql(1000);
- expect(resolutionConstraint(250, -1, 0)).to.eql(500);
- expect(resolutionConstraint(100, -1, 0)).to.eql(250);
+ expect(resolutionConstraint(1000, -1)).to.eql(1000);
+ expect(resolutionConstraint(500, -1)).to.eql(500);
+ expect(resolutionConstraint(250, -1)).to.eql(250);
+ expect(resolutionConstraint(100, -1)).to.eql(100);
});
});
});
@@ -50,42 +51,42 @@ describe('ol.resolutionconstraint', function() {
[1000, 500, 250, 100]);
});
- describe('delta 0', function() {
+ describe('direction 0', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1050, 0, 0)).to.eql(1000);
- expect(resolutionConstraint(950, 0, 0)).to.eql(1000);
- expect(resolutionConstraint(550, 0, 0)).to.eql(500);
- expect(resolutionConstraint(400, 0, 0)).to.eql(500);
- expect(resolutionConstraint(300, 0, 0)).to.eql(250);
- expect(resolutionConstraint(200, 0, 0)).to.eql(250);
- expect(resolutionConstraint(150, 0, 0)).to.eql(100);
- expect(resolutionConstraint(50, 0, 0)).to.eql(100);
+ expect(resolutionConstraint(1050, 0)).to.eql(1000);
+ expect(resolutionConstraint(950, 0)).to.eql(1000);
+ expect(resolutionConstraint(550, 0)).to.eql(500);
+ expect(resolutionConstraint(400, 0)).to.eql(500);
+ expect(resolutionConstraint(300, 0)).to.eql(250);
+ expect(resolutionConstraint(200, 0)).to.eql(250);
+ expect(resolutionConstraint(150, 0)).to.eql(100);
+ expect(resolutionConstraint(50, 0)).to.eql(100);
});
});
- describe('zoom in', function() {
+ describe('direction 1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1050, 1, 0)).to.eql(500);
- expect(resolutionConstraint(950, 1, 0)).to.eql(500);
- expect(resolutionConstraint(550, 1, 0)).to.eql(250);
- expect(resolutionConstraint(450, 1, 0)).to.eql(250);
- expect(resolutionConstraint(300, 1, 0)).to.eql(100);
- expect(resolutionConstraint(200, 1, 0)).to.eql(100);
- expect(resolutionConstraint(150, 1, 0)).to.eql(100);
- expect(resolutionConstraint(50, 1, 0)).to.eql(100);
+ expect(resolutionConstraint(1050, 1)).to.eql(1000);
+ expect(resolutionConstraint(950, 1)).to.eql(1000);
+ expect(resolutionConstraint(550, 1)).to.eql(1000);
+ expect(resolutionConstraint(450, 1)).to.eql(500);
+ expect(resolutionConstraint(300, 1)).to.eql(500);
+ expect(resolutionConstraint(200, 1)).to.eql(250);
+ expect(resolutionConstraint(150, 1)).to.eql(250);
+ expect(resolutionConstraint(50, 1)).to.eql(100);
});
});
- describe('zoom out', function() {
+ describe('direction -1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1050, -1, 0)).to.eql(1000);
- expect(resolutionConstraint(950, -1, 0)).to.eql(1000);
- expect(resolutionConstraint(550, -1, 0)).to.eql(1000);
- expect(resolutionConstraint(450, -1, 0)).to.eql(1000);
- expect(resolutionConstraint(300, -1, 0)).to.eql(500);
- expect(resolutionConstraint(200, -1, 0)).to.eql(500);
- expect(resolutionConstraint(150, -1, 0)).to.eql(250);
- expect(resolutionConstraint(50, -1, 0)).to.eql(250);
+ expect(resolutionConstraint(1050, -1)).to.eql(1000);
+ expect(resolutionConstraint(950, -1)).to.eql(500);
+ expect(resolutionConstraint(550, -1)).to.eql(500);
+ expect(resolutionConstraint(450, -1)).to.eql(250);
+ expect(resolutionConstraint(300, -1)).to.eql(250);
+ expect(resolutionConstraint(200, -1)).to.eql(100);
+ expect(resolutionConstraint(150, -1)).to.eql(100);
+ expect(resolutionConstraint(50, -1)).to.eql(100);
});
});
});
@@ -96,54 +97,54 @@ describe('ol.resolutionconstraint', function() {
beforeEach(function() {
resolutionConstraint =
- createSnapToPower(2, 1024, 10);
+ createSnapToPower(2, 1024, 1);
});
describe('delta 0', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1024, 0, 0)).to.eql(1024);
- expect(resolutionConstraint(512, 0, 0)).to.eql(512);
- expect(resolutionConstraint(256, 0, 0)).to.eql(256);
- expect(resolutionConstraint(128, 0, 0)).to.eql(128);
- expect(resolutionConstraint(64, 0, 0)).to.eql(64);
- expect(resolutionConstraint(32, 0, 0)).to.eql(32);
- expect(resolutionConstraint(16, 0, 0)).to.eql(16);
- expect(resolutionConstraint(8, 0, 0)).to.eql(8);
- expect(resolutionConstraint(4, 0, 0)).to.eql(4);
- expect(resolutionConstraint(2, 0, 0)).to.eql(2);
- expect(resolutionConstraint(1, 0, 0)).to.eql(1);
+ expect(resolutionConstraint(1024, 0)).to.eql(1024);
+ expect(resolutionConstraint(512, 0)).to.eql(512);
+ expect(resolutionConstraint(256, 0)).to.eql(256);
+ expect(resolutionConstraint(128, 0)).to.eql(128);
+ expect(resolutionConstraint(64, 0)).to.eql(64);
+ expect(resolutionConstraint(32, 0)).to.eql(32);
+ expect(resolutionConstraint(16, 0)).to.eql(16);
+ expect(resolutionConstraint(8, 0)).to.eql(8);
+ expect(resolutionConstraint(4, 0)).to.eql(4);
+ expect(resolutionConstraint(2, 0)).to.eql(2);
+ expect(resolutionConstraint(1, 0)).to.eql(1);
});
});
- describe('zoom in', function() {
+ describe('direction 1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1024, 1, 0)).to.eql(512);
- expect(resolutionConstraint(512, 1, 0)).to.eql(256);
- expect(resolutionConstraint(256, 1, 0)).to.eql(128);
- expect(resolutionConstraint(128, 1, 0)).to.eql(64);
- expect(resolutionConstraint(64, 1, 0)).to.eql(32);
- expect(resolutionConstraint(32, 1, 0)).to.eql(16);
- expect(resolutionConstraint(16, 1, 0)).to.eql(8);
- expect(resolutionConstraint(8, 1, 0)).to.eql(4);
- expect(resolutionConstraint(4, 1, 0)).to.eql(2);
- expect(resolutionConstraint(2, 1, 0)).to.eql(1);
- expect(resolutionConstraint(1, 1, 0)).to.eql(1);
+ expect(resolutionConstraint(1024, 1)).to.eql(1024);
+ expect(resolutionConstraint(512, 1)).to.eql(512);
+ expect(resolutionConstraint(256, 1)).to.eql(256);
+ expect(resolutionConstraint(128, 1)).to.eql(128);
+ expect(resolutionConstraint(64, 1)).to.eql(64);
+ expect(resolutionConstraint(32, 1)).to.eql(32);
+ expect(resolutionConstraint(16, 1)).to.eql(16);
+ expect(resolutionConstraint(8, 1)).to.eql(8);
+ expect(resolutionConstraint(4, 1)).to.eql(4);
+ expect(resolutionConstraint(2, 1)).to.eql(2);
+ expect(resolutionConstraint(1, 1)).to.eql(1);
});
});
- describe('zoom out', function() {
+ describe('direction -1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1024, -1, 0)).to.eql(1024);
- expect(resolutionConstraint(512, -1, 0)).to.eql(1024);
- expect(resolutionConstraint(256, -1, 0)).to.eql(512);
- expect(resolutionConstraint(128, -1, 0)).to.eql(256);
- expect(resolutionConstraint(64, -1, 0)).to.eql(128);
- expect(resolutionConstraint(32, -1, 0)).to.eql(64);
- expect(resolutionConstraint(16, -1, 0)).to.eql(32);
- expect(resolutionConstraint(8, -1, 0)).to.eql(16);
- expect(resolutionConstraint(4, -1, 0)).to.eql(8);
- expect(resolutionConstraint(2, -1, 0)).to.eql(4);
- expect(resolutionConstraint(1, -1, 0)).to.eql(2);
+ expect(resolutionConstraint(1024, -1)).to.eql(1024);
+ expect(resolutionConstraint(512, -1)).to.eql(512);
+ expect(resolutionConstraint(256, -1)).to.eql(256);
+ expect(resolutionConstraint(128, -1)).to.eql(128);
+ expect(resolutionConstraint(64, -1)).to.eql(64);
+ expect(resolutionConstraint(32, -1)).to.eql(32);
+ expect(resolutionConstraint(16, -1)).to.eql(16);
+ expect(resolutionConstraint(8, -1)).to.eql(8);
+ expect(resolutionConstraint(4, -1)).to.eql(4);
+ expect(resolutionConstraint(2, -1)).to.eql(2);
+ expect(resolutionConstraint(1, -1)).to.eql(1);
});
});
});
@@ -154,88 +155,182 @@ describe('ol.resolutionconstraint', function() {
beforeEach(function() {
resolutionConstraint =
- createSnapToPower(2, 1024, 10);
+ createSnapToPower(2, 1024, 1);
});
- describe('delta 0, direction 0', function() {
+ describe('direction 0', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1050, 0, 0)).to.eql(1024);
- expect(resolutionConstraint(9050, 0, 0)).to.eql(1024);
- expect(resolutionConstraint(550, 0, 0)).to.eql(512);
- expect(resolutionConstraint(450, 0, 0)).to.eql(512);
- expect(resolutionConstraint(300, 0, 0)).to.eql(256);
- expect(resolutionConstraint(250, 0, 0)).to.eql(256);
- expect(resolutionConstraint(150, 0, 0)).to.eql(128);
- expect(resolutionConstraint(100, 0, 0)).to.eql(128);
- expect(resolutionConstraint(75, 0, 0)).to.eql(64);
- expect(resolutionConstraint(50, 0, 0)).to.eql(64);
- expect(resolutionConstraint(40, 0, 0)).to.eql(32);
- expect(resolutionConstraint(30, 0, 0)).to.eql(32);
- expect(resolutionConstraint(20, 0, 0)).to.eql(16);
- expect(resolutionConstraint(12, 0, 0)).to.eql(16);
- expect(resolutionConstraint(9, 0, 0)).to.eql(8);
- expect(resolutionConstraint(7, 0, 0)).to.eql(8);
- expect(resolutionConstraint(5, 0, 0)).to.eql(4);
- expect(resolutionConstraint(3.5, 0, 0)).to.eql(4);
- expect(resolutionConstraint(2.1, 0, 0)).to.eql(2);
- expect(resolutionConstraint(1.9, 0, 0)).to.eql(2);
- expect(resolutionConstraint(1.1, 0, 0)).to.eql(1);
- expect(resolutionConstraint(0.9, 0, 0)).to.eql(1);
+ expect(resolutionConstraint(1050, 0)).to.eql(1024);
+ expect(resolutionConstraint(9050, 0)).to.eql(1024);
+ expect(resolutionConstraint(550, 0)).to.eql(512);
+ expect(resolutionConstraint(450, 0)).to.eql(512);
+ expect(resolutionConstraint(300, 0)).to.eql(256);
+ expect(resolutionConstraint(250, 0)).to.eql(256);
+ expect(resolutionConstraint(150, 0)).to.eql(128);
+ expect(resolutionConstraint(100, 0)).to.eql(128);
+ expect(resolutionConstraint(75, 0)).to.eql(64);
+ expect(resolutionConstraint(50, 0)).to.eql(64);
+ expect(resolutionConstraint(40, 0)).to.eql(32);
+ expect(resolutionConstraint(30, 0)).to.eql(32);
+ expect(resolutionConstraint(20, 0)).to.eql(16);
+ expect(resolutionConstraint(12, 0)).to.eql(16);
+ expect(resolutionConstraint(9, 0)).to.eql(8);
+ expect(resolutionConstraint(7, 0)).to.eql(8);
+ expect(resolutionConstraint(5, 0)).to.eql(4);
+ expect(resolutionConstraint(3.5, 0)).to.eql(4);
+ expect(resolutionConstraint(2.1, 0)).to.eql(2);
+ expect(resolutionConstraint(1.9, 0)).to.eql(2);
+ expect(resolutionConstraint(1.1, 0)).to.eql(1);
+ expect(resolutionConstraint(0.9, 0)).to.eql(1);
});
});
- describe('delta 0, direction > 0', function() {
+ describe('direction 1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1050, 0, 1)).to.eql(1024);
- expect(resolutionConstraint(9050, 0, 1)).to.eql(1024);
- expect(resolutionConstraint(550, 0, 1)).to.eql(1024);
- expect(resolutionConstraint(450, 0, 1)).to.eql(512);
- expect(resolutionConstraint(300, 0, 1)).to.eql(512);
- expect(resolutionConstraint(250, 0, 1)).to.eql(256);
- expect(resolutionConstraint(150, 0, 1)).to.eql(256);
- expect(resolutionConstraint(100, 0, 1)).to.eql(128);
- expect(resolutionConstraint(75, 0, 1)).to.eql(128);
- expect(resolutionConstraint(50, 0, 1)).to.eql(64);
- expect(resolutionConstraint(40, 0, 1)).to.eql(64);
- expect(resolutionConstraint(30, 0, 1)).to.eql(32);
- expect(resolutionConstraint(20, 0, 1)).to.eql(32);
- expect(resolutionConstraint(12, 0, 1)).to.eql(16);
- expect(resolutionConstraint(9, 0, 1)).to.eql(16);
- expect(resolutionConstraint(7, 0, 1)).to.eql(8);
- expect(resolutionConstraint(5, 0, 1)).to.eql(8);
- expect(resolutionConstraint(3.5, 0, 1)).to.eql(4);
- expect(resolutionConstraint(2.1, 0, 1)).to.eql(4);
- expect(resolutionConstraint(1.9, 0, 1)).to.eql(2);
- expect(resolutionConstraint(1.1, 0, 1)).to.eql(2);
- expect(resolutionConstraint(0.9, 0, 1)).to.eql(1);
+ expect(resolutionConstraint(1050, 1)).to.eql(1024);
+ expect(resolutionConstraint(9050, 1)).to.eql(1024);
+ expect(resolutionConstraint(550, 1)).to.eql(1024);
+ expect(resolutionConstraint(450, 1)).to.eql(512);
+ expect(resolutionConstraint(300, 1)).to.eql(512);
+ expect(resolutionConstraint(250, 1)).to.eql(256);
+ expect(resolutionConstraint(150, 1)).to.eql(256);
+ expect(resolutionConstraint(100, 1)).to.eql(128);
+ expect(resolutionConstraint(75, 1)).to.eql(128);
+ expect(resolutionConstraint(50, 1)).to.eql(64);
+ expect(resolutionConstraint(40, 1)).to.eql(64);
+ expect(resolutionConstraint(30, 1)).to.eql(32);
+ expect(resolutionConstraint(20, 1)).to.eql(32);
+ expect(resolutionConstraint(12, 1)).to.eql(16);
+ expect(resolutionConstraint(9, 1)).to.eql(16);
+ expect(resolutionConstraint(7, 1)).to.eql(8);
+ expect(resolutionConstraint(5, 1)).to.eql(8);
+ expect(resolutionConstraint(3.5, 1)).to.eql(4);
+ expect(resolutionConstraint(2.1, 1)).to.eql(4);
+ expect(resolutionConstraint(1.9, 1)).to.eql(2);
+ expect(resolutionConstraint(1.1, 1)).to.eql(2);
+ expect(resolutionConstraint(0.9, 1)).to.eql(1);
});
});
- describe('delta 0, direction < 0', function() {
+ describe('direction -1', function() {
it('returns expected resolution value', function() {
- expect(resolutionConstraint(1050, 0, -1)).to.eql(1024);
- expect(resolutionConstraint(9050, 0, -1)).to.eql(1024);
- expect(resolutionConstraint(550, 0, -1)).to.eql(512);
- expect(resolutionConstraint(450, 0, -1)).to.eql(256);
- expect(resolutionConstraint(300, 0, -1)).to.eql(256);
- expect(resolutionConstraint(250, 0, -1)).to.eql(128);
- expect(resolutionConstraint(150, 0, -1)).to.eql(128);
- expect(resolutionConstraint(100, 0, -1)).to.eql(64);
- expect(resolutionConstraint(75, 0, -1)).to.eql(64);
- expect(resolutionConstraint(50, 0, -1)).to.eql(32);
- expect(resolutionConstraint(40, 0, -1)).to.eql(32);
- expect(resolutionConstraint(30, 0, -1)).to.eql(16);
- expect(resolutionConstraint(20, 0, -1)).to.eql(16);
- expect(resolutionConstraint(12, 0, -1)).to.eql(8);
- expect(resolutionConstraint(9, 0, -1)).to.eql(8);
- expect(resolutionConstraint(7, 0, -1)).to.eql(4);
- expect(resolutionConstraint(5, 0, -1)).to.eql(4);
- expect(resolutionConstraint(3.5, 0, -1)).to.eql(2);
- expect(resolutionConstraint(2.1, 0, -1)).to.eql(2);
- expect(resolutionConstraint(1.9, 0, -1)).to.eql(1);
- expect(resolutionConstraint(1.1, 0, -1)).to.eql(1);
- expect(resolutionConstraint(0.9, 0, -1)).to.eql(1);
+ expect(resolutionConstraint(1050, -1)).to.eql(1024);
+ expect(resolutionConstraint(9050, -1)).to.eql(1024);
+ expect(resolutionConstraint(550, -1)).to.eql(512);
+ expect(resolutionConstraint(450, -1)).to.eql(256);
+ expect(resolutionConstraint(300, -1)).to.eql(256);
+ expect(resolutionConstraint(250, -1)).to.eql(128);
+ expect(resolutionConstraint(150, -1)).to.eql(128);
+ expect(resolutionConstraint(100, -1)).to.eql(64);
+ expect(resolutionConstraint(75, -1)).to.eql(64);
+ expect(resolutionConstraint(50, -1)).to.eql(32);
+ expect(resolutionConstraint(40, -1)).to.eql(32);
+ expect(resolutionConstraint(30, -1)).to.eql(16);
+ expect(resolutionConstraint(20, -1)).to.eql(16);
+ expect(resolutionConstraint(12, -1)).to.eql(8);
+ expect(resolutionConstraint(9, -1)).to.eql(8);
+ expect(resolutionConstraint(7, -1)).to.eql(4);
+ expect(resolutionConstraint(5, -1)).to.eql(4);
+ expect(resolutionConstraint(3.5, -1)).to.eql(2);
+ expect(resolutionConstraint(2.1, -1)).to.eql(2);
+ expect(resolutionConstraint(1.9, -1)).to.eql(1);
+ expect(resolutionConstraint(1.1, -1)).to.eql(1);
+ expect(resolutionConstraint(0.9, -1)).to.eql(1);
});
});
});
+
+ describe('SnapToPower smooth constraint', function() {
+
+ describe('snap to power, smooth constraint on', function() {
+ it('returns expected resolution value', function() {
+ const resolutionConstraint = createSnapToPower(2, 128, 16, true);
+
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.greaterThan(128);
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.lessThan(150);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.greaterThan(128);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.lessThan(130);
+ expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.greaterThan(15);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.lessThan(16);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.greaterThan(10);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.lessThan(16);
+ });
+ });
+
+ describe('snap to power, smooth constraint off', function() {
+ it('returns expected resolution value', function() {
+ const resolutionConstraint = createSnapToPower(2, 128, 16, false);
+
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.eql(16);
+ });
+ });
+
+ describe('snap to resolutions, smooth constraint on', function() {
+ it('returns expected resolution value', function() {
+ const resolutionConstraint = createSnapToResolutions([128, 64, 32, 16], true);
+
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.greaterThan(128);
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.lessThan(150);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.greaterThan(128);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.lessThan(130);
+ expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.greaterThan(15);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.lessThan(16);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.greaterThan(10);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.lessThan(16);
+ });
+ });
+
+ describe('snap to resolutions, smooth constraint off', function() {
+ it('returns expected resolution value', function() {
+ const resolutionConstraint = createSnapToResolutions([128, 64, 32, 16], false);
+
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.eql(16);
+ });
+ });
+
+ describe('min/max, smooth constraint on', function() {
+ it('returns expected resolution value', function() {
+ const resolutionConstraint = createMinMaxResolution(128, 16, true);
+
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.greaterThan(128);
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.lessThan(150);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.greaterThan(128);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.lessThan(130);
+ expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.greaterThan(15);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.lessThan(16);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.greaterThan(10);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.lessThan(16);
+ });
+ });
+
+ describe('min/max, smooth constraint off', function() {
+ it('returns expected resolution value', function() {
+ const resolutionConstraint = createMinMaxResolution(128, 16, false);
+
+ expect(resolutionConstraint(150, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(130, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
+ expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(15, 0, [100, 100], true)).to.eql(16);
+ expect(resolutionConstraint(10, 0, [100, 100], true)).to.eql(16);
+ });
+ });
+ });
+
});
diff --git a/test/spec/ol/rotationconstraint.test.js b/test/spec/ol/rotationconstraint.test.js
index f3e19a6119..c042ad4307 100644
--- a/test/spec/ol/rotationconstraint.test.js
+++ b/test/spec/ol/rotationconstraint.test.js
@@ -8,27 +8,15 @@ describe('ol.rotationconstraint', function() {
it('returns expected rotation value', function() {
const rotationConstraint = createSnapToZero(0.3);
- expect(rotationConstraint(0.1, 0)).to.eql(0);
- expect(rotationConstraint(0.2, 0)).to.eql(0);
- expect(rotationConstraint(0.3, 0)).to.eql(0);
- expect(rotationConstraint(0.4, 0)).to.eql(0.4);
+ expect(rotationConstraint(0.1)).to.eql(0);
+ expect(rotationConstraint(0.2)).to.eql(0);
+ expect(rotationConstraint(0.3)).to.eql(0);
+ expect(rotationConstraint(0.4)).to.eql(0.4);
- expect(rotationConstraint(-0.1, 0)).to.eql(0);
- expect(rotationConstraint(-0.2, 0)).to.eql(0);
- expect(rotationConstraint(-0.3, 0)).to.eql(0);
- expect(rotationConstraint(-0.4, 0)).to.eql(-0.4);
-
- expect(rotationConstraint(1, -0.9)).to.eql(0);
- expect(rotationConstraint(1, -0.8)).to.eql(0);
- // floating-point arithmetic
- expect(rotationConstraint(1, -0.7)).not.to.eql(0);
- expect(rotationConstraint(1, -0.6)).to.eql(0.4);
-
- expect(rotationConstraint(-1, 0.9)).to.eql(0);
- expect(rotationConstraint(-1, 0.8)).to.eql(0);
- // floating-point arithmetic
- expect(rotationConstraint(-1, 0.7)).not.to.eql(0);
- expect(rotationConstraint(-1, 0.6)).to.eql(-0.4);
+ expect(rotationConstraint(-0.1)).to.eql(0);
+ expect(rotationConstraint(-0.2)).to.eql(0);
+ expect(rotationConstraint(-0.3)).to.eql(0);
+ expect(rotationConstraint(-0.4)).to.eql(-0.4);
});
});
diff --git a/test/spec/ol/view.test.js b/test/spec/ol/view.test.js
index 321c69667c..3fc8c4e9ce 100644
--- a/test/spec/ol/view.test.js
+++ b/test/spec/ol/view.test.js
@@ -42,15 +42,36 @@ describe('ol.View', function() {
});
});
+ describe('with extent option and center only', function() {
+ it('gives a correct center constraint function', function() {
+ const options = {
+ extent: [0, 0, 1, 1],
+ constrainOnlyCenter: true
+ };
+ const fn = createCenterConstraint(options);
+ expect(fn([0, 0])).to.eql([0, 0]);
+ expect(fn([-10, 0])).to.eql([0, 0]);
+ expect(fn([100, 100])).to.eql([1, 1]);
+ });
+ });
+
describe('with extent option', function() {
it('gives a correct center constraint function', function() {
const options = {
extent: [0, 0, 1, 1]
};
const fn = createCenterConstraint(options);
- expect(fn([0, 0])).to.eql([0, 0]);
- expect(fn([-10, 0])).to.eql([0, 0]);
- expect(fn([100, 100])).to.eql([1, 1]);
+ const res = 1;
+ const size = [0.15, 0.1];
+ expect(fn([0, 0], res, size)).to.eql([0.075, 0.05]);
+ expect(fn([0.5, 0.5], res, size)).to.eql([0.5, 0.5]);
+ expect(fn([10, 10], res, size)).to.eql([0.925, 0.95]);
+
+ const overshootCenter = fn([10, 10], res, size, true);
+ expect(overshootCenter[0] > 0.925).to.eql(true);
+ expect(overshootCenter[1] > 0.95).to.eql(true);
+ expect(overshootCenter[0] < 9).to.eql(true);
+ expect(overshootCenter[1] < 9).to.eql(true);
});
});
@@ -218,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);
@@ -234,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);
@@ -365,6 +388,7 @@ describe('ol.View', function() {
it('applies the current resolution if resolution was originally supplied', function() {
const view = new View({
center: [0, 0],
+ maxResolution: 2000,
resolution: 1000
});
view.setResolution(500);
@@ -964,7 +988,8 @@ describe('ol.View', function() {
let view;
beforeEach(function() {
view = new View({
- resolutions: [512, 256, 128, 64, 32, 16]
+ resolutions: [1024, 512, 256, 128, 64, 32, 16, 8],
+ smoothResolutionConstraint: false
});
});
@@ -973,30 +998,31 @@ describe('ol.View', function() {
expect(view.getZoom()).to.be(undefined);
view.setResolution(513);
- expect(view.getZoom()).to.roughlyEqual(Math.log(512 / 513) / Math.LN2, 1e-9);
+ expect(view.getZoom()).to.roughlyEqual(Math.log(1024 / 513) / Math.LN2, 1e-9);
view.setResolution(512);
- expect(view.getZoom()).to.be(0);
+ expect(view.getZoom()).to.be(1);
view.setResolution(100);
- expect(view.getZoom()).to.roughlyEqual(2.35614, 1e-5);
+ expect(view.getZoom()).to.roughlyEqual(3.35614, 1e-5);
view.setResolution(65);
- expect(view.getZoom()).to.roughlyEqual(2.97763, 1e-5);
+ expect(view.getZoom()).to.roughlyEqual(3.97763, 1e-5);
view.setResolution(64);
- expect(view.getZoom()).to.be(3);
+ expect(view.getZoom()).to.be(4);
view.setResolution(16);
- expect(view.getZoom()).to.be(5);
+ expect(view.getZoom()).to.be(6);
view.setResolution(15);
- expect(view.getZoom()).to.roughlyEqual(Math.log(512 / 15) / Math.LN2, 1e-9);
+ expect(view.getZoom()).to.roughlyEqual(Math.log(1024 / 15) / Math.LN2, 1e-9);
});
it('works for resolution arrays with variable zoom factors', function() {
const view = new View({
- resolutions: [10, 5, 2, 1]
+ resolutions: [10, 5, 2, 1],
+ smoothResolutionConstraint: false
});
view.setZoom(1);
@@ -1021,7 +1047,8 @@ describe('ol.View', function() {
it('returns correct zoom levels', function() {
const view = new View({
minZoom: 10,
- maxZoom: 20
+ maxZoom: 20,
+ smoothResolutionConstraint: false
});
view.setZoom(5);
@@ -1097,12 +1124,16 @@ describe('ol.View', function() {
describe('#getResolutionForZoom', function() {
it('returns correct zoom resolution', function() {
- const view = new View();
+ const view = new View({
+ smoothResolutionConstraint: false
+ });
const max = view.getMaxZoom();
const min = view.getMinZoom();
expect(view.getResolutionForZoom(max)).to.be(view.getMinResolution());
+ expect(view.getResolutionForZoom(max + 1)).to.be(view.getMinResolution() / 2);
expect(view.getResolutionForZoom(min)).to.be(view.getMaxResolution());
+ expect(view.getResolutionForZoom(min - 1)).to.be(view.getMaxResolution() * 2);
});
it('returns correct zoom levels for specifically configured resolutions', function() {
@@ -1110,11 +1141,30 @@ describe('ol.View', function() {
resolutions: [10, 8, 6, 4, 2]
});
+ expect(view.getResolutionForZoom(-1)).to.be(10);
expect(view.getResolutionForZoom(0)).to.be(10);
expect(view.getResolutionForZoom(1)).to.be(8);
expect(view.getResolutionForZoom(2)).to.be(6);
expect(view.getResolutionForZoom(3)).to.be(4);
expect(view.getResolutionForZoom(4)).to.be(2);
+ expect(view.getResolutionForZoom(5)).to.be(2);
+ });
+
+ it('returns correct zoom levels for resolutions with variable zoom levels', function() {
+ const view = new View({
+ resolutions: [50, 10, 5, 2.5, 1.25, 0.625]
+ });
+
+ expect(view.getResolutionForZoom(-1)).to.be(50);
+ expect(view.getResolutionForZoom(0)).to.be(50);
+ expect(view.getResolutionForZoom(0.5)).to.be(50 / Math.pow(5, 0.5));
+ expect(view.getResolutionForZoom(1)).to.be(10);
+ expect(view.getResolutionForZoom(2)).to.be(5);
+ expect(view.getResolutionForZoom(2.75)).to.be(5 / Math.pow(2, 0.75));
+ expect(view.getResolutionForZoom(3)).to.be(2.5);
+ expect(view.getResolutionForZoom(4)).to.be(1.25);
+ expect(view.getResolutionForZoom(5)).to.be(0.625);
+ expect(view.getResolutionForZoom(6)).to.be(0.625);
});
});
@@ -1246,8 +1296,14 @@ describe('ol.View', function() {
document.body.removeChild(target);
});
it('calculates the size correctly', function() {
- const size = map.getView().getSizeFromViewport_();
+ let size = map.getView().getSizeFromViewport_();
expect(size).to.eql([200, 150]);
+ size = map.getView().getSizeFromViewport_(Math.PI / 2);
+ expect(size[0]).to.roughlyEqual(150, 1e-9);
+ expect(size[1]).to.roughlyEqual(200, 1e-9);
+ size = map.getView().getSizeFromViewport_(Math.PI);
+ expect(size[0]).to.roughlyEqual(200, 1e-9);
+ expect(size[1]).to.roughlyEqual(150, 1e-9);
});
});
@@ -1278,14 +1334,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]});
@@ -1314,30 +1396,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);
@@ -1360,7 +1420,6 @@ describe('ol.View', function() {
{
size: [200, 200],
padding: [100, 0, 0, 100],
- constrainResolution: false,
duration: 25
});
@@ -1422,6 +1481,235 @@ describe('ol.View', function() {
expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9);
});
});
+
+ describe('#beginInteraction() and endInteraction()', function() {
+ let view;
+ beforeEach(function() {
+ view = new View();
+ });
+
+ it('correctly changes the view hint', function() {
+ view.beginInteraction();
+ expect(view.getHints()[1]).to.be(1);
+ view.beginInteraction();
+ expect(view.getHints()[1]).to.be(2);
+ view.endInteraction();
+ view.endInteraction();
+ expect(view.getHints()[1]).to.be(0);
+ });
+ });
+
+ describe('#getConstrainedZoom()', function() {
+ let view;
+
+ it('works correctly without constraint', function() {
+ view = new View({
+ zoom: 0
+ });
+ expect(view.getConstrainedZoom(3)).to.be(3);
+ });
+ it('works correctly with resolution constraints', function() {
+ view = new View({
+ zoom: 4,
+ minZoom: 4,
+ maxZoom: 8
+ });
+ expect(view.getConstrainedZoom(3)).to.be(4);
+ expect(view.getConstrainedZoom(10)).to.be(8);
+ });
+ it('works correctly with a specific resolution set', function() {
+ view = new View({
+ zoom: 0,
+ resolutions: [512, 256, 128, 64, 32, 16, 8]
+ });
+ expect(view.getConstrainedZoom(0)).to.be(0);
+ expect(view.getConstrainedZoom(4)).to.be(4);
+ expect(view.getConstrainedZoom(8)).to.be(6);
+ });
+ });
+
+ describe('#getConstrainedResolution()', function() {
+ let view;
+ const defaultMaxRes = 156543.03392804097;
+
+ it('works correctly by snapping to power of 2', function() {
+ view = new View();
+ expect(view.getConstrainedResolution(1000000)).to.be(defaultMaxRes);
+ expect(view.getConstrainedResolution(defaultMaxRes / 8)).to.be(defaultMaxRes / 8);
+ });
+ it('works correctly by snapping to a custom zoom factor', function() {
+ view = new View({
+ maxResolution: 2500,
+ zoomFactor: 5,
+ maxZoom: 4,
+ constrainResolution: true
+ });
+ expect(view.getConstrainedResolution(90, 1)).to.be(100);
+ expect(view.getConstrainedResolution(90, -1)).to.be(20);
+ expect(view.getConstrainedResolution(20)).to.be(20);
+ expect(view.getConstrainedResolution(5)).to.be(4);
+ expect(view.getConstrainedResolution(1)).to.be(4);
+ });
+ it('works correctly with a specific resolution set', function() {
+ view = new View({
+ zoom: 0,
+ resolutions: [512, 256, 128, 64, 32, 16, 8],
+ constrainResolution: true
+ });
+ expect(view.getConstrainedResolution(1000, 1)).to.be(512);
+ expect(view.getConstrainedResolution(260, 1)).to.be(512);
+ expect(view.getConstrainedResolution(260)).to.be(256);
+ expect(view.getConstrainedResolution(30)).to.be(32);
+ expect(view.getConstrainedResolution(30, -1)).to.be(16);
+ expect(view.getConstrainedResolution(4, -1)).to.be(8);
+ });
+ });
+
+ describe('#adjustRotation()', function() {
+ it('changes view rotation with anchor', function() {
+ const view = new View({
+ resolution: 1,
+ center: [0, 0]
+ });
+
+ view.adjustRotation(Math.PI / 2);
+ expect(view.getRotation()).to.be(Math.PI / 2);
+ expect(view.getCenter()).to.eql([0, 0]);
+
+ view.adjustRotation(-Math.PI);
+ expect(view.getRotation()).to.be(-Math.PI / 2);
+ expect(view.getCenter()).to.eql([0, 0]);
+
+ view.adjustRotation(Math.PI / 3, [50, 0]);
+ expect(view.getRotation()).to.roughlyEqual(-Math.PI / 6, 1e-9);
+ expect(view.getCenter()[0]).to.roughlyEqual(50 * (1 - Math.cos(Math.PI / 3)), 1e-9);
+ expect(view.getCenter()[1]).to.roughlyEqual(-50 * Math.sin(Math.PI / 3), 1e-9);
+ });
+
+ it('does not change view parameters if rotation is disabled', function() {
+ const view = new View({
+ resolution: 1,
+ enableRotation: false,
+ center: [0, 0]
+ });
+
+ view.adjustRotation(Math.PI / 2);
+ expect(view.getRotation()).to.be(0);
+ expect(view.getCenter()).to.eql([0, 0]);
+
+ view.adjustRotation(-Math.PI * 3, [-50, 0]);
+ expect(view.getRotation()).to.be(0);
+ expect(view.getCenter()).to.eql([0, 0]);
+ });
+ });
+
+ describe('#adjustZoom()', function() {
+
+ it('changes view resolution', function() {
+ const view = new View({
+ resolution: 1,
+ resolutions: [4, 2, 1, 0.5, 0.25]
+ });
+
+ view.adjustZoom(1);
+ expect(view.getResolution()).to.be(0.5);
+
+ view.adjustZoom(-1);
+ expect(view.getResolution()).to.be(1);
+
+ view.adjustZoom(2);
+ expect(view.getResolution()).to.be(0.25);
+
+ view.adjustZoom(-2);
+ expect(view.getResolution()).to.be(1);
+ });
+
+ it('changes view resolution and center relative to the anchor', function() {
+ const view = new View({
+ center: [0, 0],
+ resolution: 1,
+ resolutions: [4, 2, 1, 0.5, 0.25]
+ });
+
+ view.adjustZoom(1, [10, 10]);
+ expect(view.getCenter()).to.eql([5, 5]);
+
+ view.adjustZoom(-1, [0, 0]);
+ expect(view.getCenter()).to.eql([10, 10]);
+
+ view.adjustZoom(2, [0, 0]);
+ expect(view.getCenter()).to.eql([2.5, 2.5]);
+
+ view.adjustZoom(-2, [0, 0]);
+ expect(view.getCenter()).to.eql([10, 10]);
+ });
+
+ it('changes view resolution and center relative to the anchor, while respecting the extent (center only)', function() {
+ const view = new View({
+ center: [0, 0],
+ extent: [-2.5, -2.5, 2.5, 2.5],
+ constrainOnlyCenter: true,
+ resolution: 1,
+ resolutions: [4, 2, 1, 0.5, 0.25]
+ });
+
+ view.adjustZoom(1, [10, 10]);
+ expect(view.getCenter()).to.eql([2.5, 2.5]);
+
+ view.adjustZoom(-1, [0, 0]);
+ expect(view.getCenter()).to.eql([2.5, 2.5]);
+
+ view.adjustZoom(2, [10, 10]);
+ expect(view.getCenter()).to.eql([2.5, 2.5]);
+
+ view.adjustZoom(-2, [0, 0]);
+ expect(view.getCenter()).to.eql([2.5, 2.5]);
+ });
+
+ it('changes view resolution and center relative to the anchor, while respecting the extent', function() {
+ const map = new Map({});
+ const view = new View({
+ center: [50, 50],
+ extent: [0, 0, 100, 100],
+ resolution: 1,
+ resolutions: [4, 2, 1, 0.5, 0.25, 0.125]
+ });
+ map.setView(view);
+
+ view.adjustZoom(1, [100, 100]);
+ expect(view.getCenter()).to.eql([75, 75]);
+
+ view.adjustZoom(-1, [75, 75]);
+ expect(view.getCenter()).to.eql([50, 50]);
+
+ view.adjustZoom(2, [100, 100]);
+ expect(view.getCenter()).to.eql([87.5, 87.5]);
+
+ view.adjustZoom(-3, [0, 0]);
+ expect(view.getCenter()).to.eql([50, 50]);
+ expect(view.getResolution()).to.eql(1);
+ });
+
+ it('changes view resolution and center relative to the anchor, while respecting the extent (rotated)', function() {
+ const map = new Map({});
+ const view = new View({
+ center: [50, 50],
+ extent: [-100, -100, 100, 100],
+ resolution: 1,
+ resolutions: [2, 1, 0.5, 0.25, 0.125],
+ rotation: Math.PI / 4
+ });
+ map.setView(view);
+ const halfSize = 100 * Math.SQRT1_2;
+
+ view.adjustZoom(1, [100, 100]);
+ expect(view.getCenter()).to.eql([100 - halfSize / 2, 100 - halfSize / 2]);
+
+ view.setCenter([0, 50]);
+ view.adjustZoom(-1, [0, 0]);
+ expect(view.getCenter()).to.eql([0, 100 - halfSize]);
+ });
+ });
});
describe('ol.View.isNoopAnimation()', function() {