View / add a 'smoothResolutionConstraint' options
When enabled (true by default), the resolution min/max values will be applied with a smoothing effect for a better user experience.
This commit is contained in:
@@ -123,6 +123,9 @@ import {createMinMaxResolution} from './resolutionconstraint';
|
||||
* @property {boolean} [constrainResolution] If true, the view will always
|
||||
* animate to the closest zoom level after an interaction; false means
|
||||
* intermediary zoom levels are allowed. Default is false.
|
||||
* @property {boolean} [smoothResolutionConstraint] 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. Default is true.
|
||||
* @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
|
||||
@@ -978,8 +981,7 @@ class View extends BaseObject {
|
||||
const zoomFactor = this.resolutions_[baseLevel] / this.resolutions_[baseLevel + 1];
|
||||
return this.resolutions_[baseLevel] / Math.pow(zoomFactor, clamp(zoom - baseLevel, 0, 1));
|
||||
} else {
|
||||
return clamp(this.maxResolution_ / Math.pow(this.zoomFactor_, zoom - this.minZoom_),
|
||||
this.minResolution_, this.maxResolution_);
|
||||
return this.maxResolution_ / Math.pow(this.zoomFactor_, zoom - this.minZoom_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,7 +1205,7 @@ class View extends BaseObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rotation for this view.
|
||||
* Set the rotation for this view using an anchor.
|
||||
* @param {number} rotation The rotation of the view in radians.
|
||||
* @observable
|
||||
* @api
|
||||
@@ -1214,7 +1216,7 @@ class View extends BaseObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to a specific zoom level.
|
||||
* Zoom to a specific zoom level using an anchor
|
||||
* @param {number} zoom Zoom level.
|
||||
* @api
|
||||
*/
|
||||
@@ -1263,7 +1265,7 @@ class View extends BaseObject {
|
||||
* @private
|
||||
*/
|
||||
resolveConstraints_(opt_duration, opt_resolutionDirection) {
|
||||
const duration = opt_duration || 200;
|
||||
const duration = opt_duration !== undefined ? opt_duration : 200;
|
||||
const direction = opt_resolutionDirection || 0;
|
||||
|
||||
const newRotation = this.constraints_.rotation(this.targetRotation_);
|
||||
@@ -1289,7 +1291,7 @@ class View extends BaseObject {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Notify the View that an interaction has started.
|
||||
* @api
|
||||
@@ -1391,6 +1393,9 @@ 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];
|
||||
@@ -1398,10 +1403,10 @@ export function createResolutionConstraint(options) {
|
||||
resolutions[maxZoom] : resolutions[resolutions.length - 1];
|
||||
|
||||
if (options.constrainResolution) {
|
||||
resolutionConstraint = createSnapToResolutions(resolutions,
|
||||
resolutionConstraint = createSnapToResolutions(resolutions, smooth,
|
||||
!options.constrainOnlyCenter && options.extent);
|
||||
} else {
|
||||
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution,
|
||||
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
|
||||
!options.constrainOnlyCenter && options.extent);
|
||||
}
|
||||
} else {
|
||||
@@ -1449,10 +1454,10 @@ export function createResolutionConstraint(options) {
|
||||
|
||||
if (options.constrainResolution) {
|
||||
resolutionConstraint = createSnapToPower(
|
||||
zoomFactor, maxResolution, minResolution,
|
||||
zoomFactor, maxResolution, minResolution, smooth,
|
||||
!options.constrainOnlyCenter && options.extent);
|
||||
} else {
|
||||
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution,
|
||||
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
|
||||
!options.constrainOnlyCenter && options.extent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ export function createExtent(extent, onlyCenter, smooth) {
|
||||
const maxX = extent[2] - viewWidth / 2;
|
||||
const minY = extent[1] + viewHeight / 2;
|
||||
const maxY = extent[3] - viewHeight / 2;
|
||||
let x = clamp(center[0], minX, maxX);
|
||||
let y = clamp(center[1], minY, maxY);
|
||||
let ratio = 30 * resolution;
|
||||
let x = minX > maxX ? (maxX + minX) / 2 : clamp(center[0], minX, maxX);
|
||||
let y = minY > maxY ? (maxY + minY) / 2 : clamp(center[1], minY, maxY);
|
||||
const ratio = 30 * resolution;
|
||||
|
||||
// during an interaction, allow some overscroll
|
||||
if (opt_isMoving && smooth) {
|
||||
|
||||
@@ -102,13 +102,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_
|
||||
@@ -234,9 +234,10 @@ class ZoomSlider extends Control {
|
||||
*/
|
||||
handleDraggerStart_(event) {
|
||||
if (!this.dragging_ && event.originalEvent.target === this.element.firstElementChild) {
|
||||
const element = /** @type {HTMLElement} */ (this.element.firstElementChild);
|
||||
this.getMap().getView().beginInteraction();
|
||||
this.previousX_ = event.clientX;
|
||||
this.previousY_ = event.clientY;
|
||||
this.startX_ = event.clientX - parseFloat(element.style.left);
|
||||
this.startY_ = event.clientY - parseFloat(element.style.top);
|
||||
this.dragging_ = true;
|
||||
|
||||
if (this.dragListenerKeys_.length === 0) {
|
||||
@@ -260,15 +261,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,18 +279,9 @@ class ZoomSlider extends Control {
|
||||
const view = this.getMap().getView();
|
||||
view.endInteraction();
|
||||
|
||||
const zoom = view.getValidZoomLevel(
|
||||
view.getZoomForResolution(this.currentResolution_));
|
||||
|
||||
view.animate({
|
||||
zoom: zoom,
|
||||
duration: this.duration_,
|
||||
easing: easeOut
|
||||
});
|
||||
|
||||
this.dragging_ = false;
|
||||
this.previousX_ = undefined;
|
||||
this.previousY_ = undefined;
|
||||
this.startX_ = undefined;
|
||||
this.startY_ = undefined;
|
||||
this.dragListenerKeys_.forEach(unlistenByKey);
|
||||
this.dragListenerKeys_.length = 0;
|
||||
}
|
||||
@@ -360,7 +348,7 @@ class ZoomSlider extends Control {
|
||||
*/
|
||||
getPositionForResolution_(res) {
|
||||
const fn = this.getMap().getView().getValueForResolutionFunction();
|
||||
return 1 - fn(res);
|
||||
return clamp(1 - fn(res), 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,10 +367,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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -128,19 +128,12 @@ 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.endInteraction();
|
||||
@@ -211,7 +204,7 @@ class MouseWheelZoom extends Interaction {
|
||||
} else {
|
||||
view.beginInteraction();
|
||||
}
|
||||
this.trackpadTimeoutId_ = setTimeout(this.decrementInteractingHint_.bind(this), this.trackpadEventGap_);
|
||||
this.trackpadTimeoutId_ = setTimeout(this.endInteraction_.bind(this), this.trackpadEventGap_);
|
||||
view.adjustZoom(-delta / this.trackpadDeltaPerZoom_, this.lastAnchor_);
|
||||
this.startTime_ = now;
|
||||
return false;
|
||||
|
||||
@@ -11,24 +11,45 @@ import {getHeight, getWidth} from './extent';
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 getCappedResolution(resolution, maxExtent, viewportSize) {
|
||||
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.
|
||||
* @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);
|
||||
|
||||
result *= Math.log(Math.max(1, resolution / maxResolution)) * 0.1 + 1;
|
||||
if (minResolution) {
|
||||
result = Math.max(result, minResolution);
|
||||
result /= Math.log(Math.max(1, minResolution / resolution)) * 0.1 + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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, opt_maxExtent) {
|
||||
export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent) {
|
||||
return (
|
||||
/**
|
||||
* @param {number|undefined} resolution Resolution.
|
||||
@@ -39,16 +60,23 @@ export function createSnapToResolutions(resolutions, opt_maxExtent) {
|
||||
*/
|
||||
function(resolution, direction, size, opt_isMoving) {
|
||||
if (resolution !== undefined) {
|
||||
const cappedRes = opt_maxExtent ? getCappedResolution(resolution, opt_maxExtent, size) : resolution;
|
||||
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 maxResolution = resolutions[0];
|
||||
const minResolution = resolutions[resolutions.length - 1];
|
||||
return clamp(cappedRes, minResolution, maxResolution);
|
||||
const smooth = opt_smooth !== undefined ? opt_smooth : true;
|
||||
if (!smooth) {
|
||||
return clamp(resolution, minResolution, cappedMaxRes);
|
||||
}
|
||||
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
|
||||
}
|
||||
|
||||
let z = Math.floor(linearFindNearest(resolutions, cappedRes, direction));
|
||||
const capped = Math.min(cappedMaxRes, resolution);
|
||||
const z = Math.floor(linearFindNearest(resolutions, capped, direction));
|
||||
return resolutions[z];
|
||||
} else {
|
||||
return undefined;
|
||||
@@ -62,10 +90,11 @@ export function createSnapToResolutions(resolutions, opt_maxExtent) {
|
||||
* @param {number} power Power.
|
||||
* @param {number} maxResolution Maximum resolution.
|
||||
* @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_minResolution, opt_maxExtent) {
|
||||
export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent) {
|
||||
return (
|
||||
/**
|
||||
* @param {number|undefined} resolution Resolution.
|
||||
@@ -76,20 +105,26 @@ export function createSnapToPower(power, maxResolution, opt_minResolution, opt_m
|
||||
*/
|
||||
function(resolution, direction, size, opt_isMoving) {
|
||||
if (resolution !== undefined) {
|
||||
const cappedRes = opt_maxExtent ? getCappedResolution(resolution, opt_maxExtent, size) : resolution;
|
||||
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) {
|
||||
return opt_minResolution !== undefined ? Math.max(opt_minResolution, cappedRes) : cappedRes;
|
||||
const smooth = opt_smooth !== undefined ? opt_smooth : true;
|
||||
if (!smooth) {
|
||||
return clamp(resolution, minResolution, cappedMaxRes);
|
||||
}
|
||||
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
|
||||
}
|
||||
|
||||
const offset = -direction * (0.5 - 1e-9) + 0.5;
|
||||
const capped = Math.min(cappedMaxRes, resolution);
|
||||
const zoomLevel = Math.floor(
|
||||
Math.log(maxResolution / cappedRes) / Math.log(power) + offset);
|
||||
Math.log(maxResolution / capped) / Math.log(power) + offset);
|
||||
let newResolution = maxResolution / Math.pow(power, zoomLevel);
|
||||
return opt_minResolution !== undefined ?
|
||||
clamp(newResolution, opt_minResolution, maxResolution) :
|
||||
Math.min(maxResolution, newResolution);
|
||||
return clamp(newResolution, minResolution, cappedMaxRes);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
@@ -99,10 +134,11 @@ export function createSnapToPower(power, maxResolution, opt_minResolution, opt_m
|
||||
/**
|
||||
* @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_maxExtent) {
|
||||
export function createMinMaxResolution(maxResolution, minResolution, opt_smooth, opt_maxExtent) {
|
||||
return (
|
||||
/**
|
||||
* @param {number|undefined} resolution Resolution.
|
||||
@@ -113,8 +149,15 @@ export function createMinMaxResolution(maxResolution, minResolution, opt_maxExte
|
||||
*/
|
||||
function(resolution, direction, size, opt_isMoving) {
|
||||
if (resolution !== undefined) {
|
||||
const cappedRes = opt_maxExtent ? getCappedResolution(resolution, opt_maxExtent, size) : resolution;
|
||||
return clamp(cappedRes, minResolution, maxResolution);
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user