Merge pull request #9137 from jahow/add-view-constrain-extent-2
Refactor the way view constraints are handled & add a view extent constraint
This commit is contained in:
503
src/ol/View.js
503
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<number>} [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<string, *>}
|
||||
*/
|
||||
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};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<number>} 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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user