Compare commits

..

1 Commits

Author SHA1 Message Date
ahocevar
7ca396f727 Version and tag for v6.0.0-beta.2 2019-03-02 16:46:57 +01:00
51 changed files with 1045 additions and 1512 deletions

View File

@@ -4,24 +4,6 @@
#### Backwards incompatible changes #### Backwards incompatible changes
##### The `setCenter`, `setZoom`, `setResolution` and `setRotation` methods on `ol/View` do not bypass constraints anymore
Previously, these methods allowed setting values that were inconsistent with the given view constraints.
This is no longer the case and all changes to the view state now follow the same logic:
target values are provided and constraints are applied on these to determine the actual values to be used.
##### Removal of the `constrainResolution` option on `View.fit`, `PinchZoom`, `MouseWheelZoom` and `ol/interaction.js`
The `constrainResolution` option is now only supported by the `View` class. A `View.setResolutionConstrained` method was added as well.
Generally, the responsibility of applying center/rotation/resolutions constraints was moved from interactions and controls to the `View` class.
##### The view `extent` option now applies to the whole viewport
Previously, this options only constrained the view *center*. This behaviour can still be obtained by specifying `constrainCenterOnly` in the view options.
As a side effect, the view `rotate` method is gone and has been replaced with `adjustRotation` which takes a delta as input.
##### Removal of deprecated methods ##### Removal of deprecated methods
The `inherits` function that was used to inherit the prototype methods from one constructor into another has been removed. The `inherits` function that was used to inherit the prototype methods from one constructor into another has been removed.
@@ -117,58 +99,6 @@ Due to the constraint above (layers can only be added to a single map), the over
Previously, a graticule was not a layer. Now it is. See the graticule example for details on how to add a graticule layer to your map. Previously, a graticule was not a layer. Now it is. See the graticule example for details on how to add a graticule layer to your map.
##### `ol/format/Feature` API change
The `getLastExtent()` method, which was required for custom `tileLoadFunction`s in `ol/source/Vector`, has been removed because it is no longer needed (see below).
##### `ol/VectorTile` API changes
* Removal of the `getProjection()` and `setProjection()` methods. These were used in custom `tileLoadFunction`s on `ol/source/VectorTile`, which work differently now (see below).
* Removal of the `getExtent()` and `setExtent()` methods. These were used in custom `tileLoadFunction`s on `ol/source/VectorTile`, which work differently now (see below).
##### Custom tileLoadFunction on a VectorTile source needs changes
Previously, applications needed to call `setProjection()` and `setExtent()` on the tile in a custom `tileLoadFunction` on `ol/source/VectorTile`. The format's `getLastExtent()` method was used to get the extent. All this is no longer needed. Instead, the `extent` (first argument to the loader function) and `projection` (third argument to the loader function) are simply passed as `extent` and `featureProjection` options to the format's `readFeatures()` method.
Example for an old `tileLoadFunction`:
```js
function(tile, url) {
tile.setLoader(function() {
fetch(url).then(function(response) {
response.arrayBuffer().then(function(data) {
var format = tile.getFormat();
tile.setProjection(format.readProjection(data));
tile.setFeatures(format.readFeatures(data, {
// featureProjection is not required for ol/format/MVT
featureProjection: map.getView().getProjection()
}));
tile.setExtent(format.getLastExtent());
})
})
}
});
```
This function needs to be changed to:
```js
function(tile, url) {
tile.setLoader(function(extent, resolution, projection) {
fetch(url).then(function(response) {
response.arrayBuffer().then(function(data) {
var format = tile.getFormat();
tile.setFeatures(format.readFeatures(data, {
// extent is only required for ol/format/MVT
extent: extent,
featureProjection: projection
}));
})
})
}
});
```
##### Drop of support for the experimental WebGL renderer ##### Drop of support for the experimental WebGL renderer
The WebGL map and layers renderers are gone, replaced by a `WebGLHelper` function that provides a lightweight, The WebGL map and layers renderers are gone, replaced by a `WebGLHelper` function that provides a lightweight,

View File

@@ -37,7 +37,7 @@
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */ /* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */

View File

@@ -21,6 +21,8 @@ tags: "center, rotation, openstreetmap"
<div class="padding-bottom"></div> <div class="padding-bottom"></div>
<div class="center"></div> <div class="center"></div>
</div> </div>
<button id="zoomtoswitzerland">Zoom to Switzerland</button> (best fit).<br/> <button id="zoomtoswitzerlandbest">Zoom to Switzerland</button> (best fit),<br/>
<button id="zoomtoswitzerlandconstrained">Zoom to Switzerland</button> (respect resolution constraint).<br/>
<button id="zoomtoswitzerlandnearest">Zoom to Switzerland</button> (nearest),<br/>
<button id="zoomtolausanne">Zoom to Lausanne</button> (with min resolution),<br/> <button id="zoomtolausanne">Zoom to Lausanne</button> (with min resolution),<br/>
<button id="centerlausanne">Center on Lausanne</button> <button id="centerlausanne">Center on Lausanne</button>

View File

@@ -47,14 +47,29 @@ const map = new Map({
view: view view: view
}); });
const zoomtoswitzerland = const zoomtoswitzerlandbest = document.getElementById('zoomtoswitzerlandbest');
document.getElementById('zoomtoswitzerland'); zoomtoswitzerlandbest.addEventListener('click', function() {
zoomtoswitzerland.addEventListener('click', function() { const feature = source.getFeatures()[0];
const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry());
view.fit(polygon, {padding: [170, 50, 30, 150], constrainResolution: false});
}, false);
const zoomtoswitzerlandconstrained =
document.getElementById('zoomtoswitzerlandconstrained');
zoomtoswitzerlandconstrained.addEventListener('click', function() {
const feature = source.getFeatures()[0]; const feature = source.getFeatures()[0];
const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry()); const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry());
view.fit(polygon, {padding: [170, 50, 30, 150]}); view.fit(polygon, {padding: [170, 50, 30, 150]});
}, false); }, false);
const zoomtoswitzerlandnearest =
document.getElementById('zoomtoswitzerlandnearest');
zoomtoswitzerlandnearest.addEventListener('click', function() {
const feature = source.getFeatures()[0];
const polygon = /** @type {import("../src/ol/geom/SimpleGeometry.js").default} */ (feature.getGeometry());
view.fit(polygon, {padding: [170, 50, 30, 150], nearest: true});
}, false);
const zoomtolausanne = document.getElementById('zoomtolausanne'); const zoomtolausanne = document.getElementById('zoomtolausanne');
zoomtolausanne.addEventListener('click', function() { zoomtolausanne.addEventListener('click', function() {
const feature = source.getFeatures()[1]; const feature = source.getFeatures()[1];

View File

@@ -1,9 +0,0 @@
---
layout: example.html
title: Constrained Extent
shortdesc: Example of a view with a constrained extent.
docs: >
This map has a view that is constrained in an extent. This is done using the `extent` view option. Please note that specifying `constrainOnlyCenter: true` would only apply the extent restriction to the view center.
tags: "view, extent, constrain, restrict"
---
<div id="map" class="map"></div>

View File

@@ -1,25 +0,0 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js';
import OSM from '../src/ol/source/OSM.js';
import {defaults as defaultControls} from '../src/ol/control/util';
import ZoomSlider from '../src/ol/control/ZoomSlider';
const view = new View({
center: [328627.563458, 5921296.662223],
zoom: 8,
extent: [-572513.341856, 5211017.966314,
916327.095083, 6636950.728974]
});
new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
keyboardEventTarget: document,
target: 'map',
view: view,
controls: defaultControls().extend([new ZoomSlider()])
});

View File

@@ -4,11 +4,16 @@ title: Interaction Options
shortdesc: Shows interaction options for custom scroll and zoom behavior. shortdesc: Shows interaction options for custom scroll and zoom behavior.
docs: > docs: >
This example uses a custom `ol/interaction/defaults` configuration: This example uses a custom `ol/interaction/defaults` configuration:
by default, wheel/trackpad zoom and drag panning is always active, which
* By default, wheel/trackpad zoom and drag panning is always active, which
can be unexpected on pages with a lot of scrollable content and an embedded can be unexpected on pages with a lot of scrollable content and an embedded
map. To perform wheel/trackpad zoom and drag-pan actions only when the map map. To perform wheel/trackpad zoom and drag-pan actions only when the map
has the focus, set `onFocusOnly: true` as option. This requires a map div has the focus, set `onFocusOnly: true` as option. This requires a map div
with a `tabindex` attribute set. with a `tabindex` attribute set.
* By default, pinch-zoom and wheel/trackpad zoom interactions can leave the
map at fractional zoom levels. If instead you want to constrain
wheel/trackpad zooming to integer zoom levels, set
`constrainResolution: true`.
tags: "trackpad, mousewheel, zoom, scroll, interaction, fractional" tags: "trackpad, mousewheel, zoom, scroll, interaction, fractional"
--- ---
<div tabindex="1" id="map" class="map"></div> <div tabindex="1" id="map" class="map"></div>

View File

@@ -7,7 +7,7 @@ import OSM from '../src/ol/source/OSM.js';
const map = new Map({ const map = new Map({
interactions: defaultInteractions({ interactions: defaultInteractions({
onFocusOnly: true constrainResolution: true, onFocusOnly: true
}), }),
layers: [ layers: [
new TileLayer({ new TileLayer({

View File

@@ -203,11 +203,7 @@ const map = new Map({
target: 'map', target: 'map',
view: new View({ view: new View({
center: [-10997148, 4569099], center: [-10997148, 4569099],
zoom: 4, zoom: 4
minZoom: 1,
extent: [-Infinity, -20048966.10, Infinity, 20048966.10],
smoothExtentConstraint: false,
smoothResolutionConstraint: false
}) })
}); });

View File

@@ -5,7 +5,7 @@ shortdesc: Restrict pinch zooming to integer zoom levels.
docs: > docs: >
By default, the `ol/interaction/PinchZoom` can leave the map at fractional zoom levels. By default, the `ol/interaction/PinchZoom` can leave the map at fractional zoom levels.
If instead you want to constrain pinch zooming to integer zoom levels, set If instead you want to constrain pinch zooming to integer zoom levels, set
<code>constrainResolution: true</code> when constructing the view. <code>constrainResolution: true</code> when constructing the interaction.
tags: "pinch, zoom, interaction" tags: "pinch, zoom, interaction"
--- ---
<div id="map" class="map"></div> <div id="map" class="map"></div>

View File

@@ -6,8 +6,10 @@ import OSM from '../src/ol/source/OSM.js';
const map = new Map({ const map = new Map({
interactions: defaultInteractions().extend([ interactions: defaultInteractions({pinchZoom: false}).extend([
new PinchZoom() new PinchZoom({
constrainResolution: true // force zooming to a integer zoom
})
]), ]),
layers: [ layers: [
new TileLayer({ new TileLayer({
@@ -17,7 +19,6 @@ const map = new Map({
target: 'map', target: 'map',
view: new View({ view: new View({
center: [0, 0], center: [0, 0],
zoom: 2, zoom: 2
constrainResolution: true
}) })
}); });

View File

@@ -103,9 +103,6 @@ tags: "geojson, vector, openstreetmap, label"
<label>Size: </label> <label>Size: </label>
<input type="text" value="12px" id="points-size" /> <input type="text" value="12px" id="points-size" />
<br /> <br />
<label>Line height: </label>
<input type="text" value="1" id="points-height" />
<br />
<label>Offset X:</label> <label>Offset X:</label>
<input type="text" value="0" id="points-offset-x" /> <input type="text" value="0" id="points-offset-x" />
<br /> <br />
@@ -215,9 +212,6 @@ tags: "geojson, vector, openstreetmap, label"
<label>Size: </label> <label>Size: </label>
<input type="text" value="12px" id="lines-size" /> <input type="text" value="12px" id="lines-size" />
<br /> <br />
<label>Line height: </label>
<input type="text" value="1.2" id="lines-height" />
<br />
<label>Offset X:</label> <label>Offset X:</label>
<input type="text" value="0" id="lines-offset-x" /> <input type="text" value="0" id="lines-offset-x" />
<br /> <br />
@@ -327,9 +321,6 @@ tags: "geojson, vector, openstreetmap, label"
<label>Size: </label> <label>Size: </label>
<input type="text" value="10px" id="polygons-size" /> <input type="text" value="10px" id="polygons-size" />
<br /> <br />
<label>Line height: </label>
<input type="text" value="1" id="polygons-height" />
<br />
<label>Offset X:</label> <label>Offset X:</label>
<input type="text" value="0" id="polygons-offset-x" /> <input type="text" value="0" id="polygons-offset-x" />
<br /> <br />

View File

@@ -16,7 +16,6 @@ const myDom = {
font: document.getElementById('points-font'), font: document.getElementById('points-font'),
weight: document.getElementById('points-weight'), weight: document.getElementById('points-weight'),
size: document.getElementById('points-size'), size: document.getElementById('points-size'),
height: document.getElementById('points-height'),
offsetX: document.getElementById('points-offset-x'), offsetX: document.getElementById('points-offset-x'),
offsetY: document.getElementById('points-offset-y'), offsetY: document.getElementById('points-offset-y'),
color: document.getElementById('points-color'), color: document.getElementById('points-color'),
@@ -35,7 +34,6 @@ const myDom = {
maxangle: document.getElementById('lines-maxangle'), maxangle: document.getElementById('lines-maxangle'),
overflow: document.getElementById('lines-overflow'), overflow: document.getElementById('lines-overflow'),
size: document.getElementById('lines-size'), size: document.getElementById('lines-size'),
height: document.getElementById('lines-height'),
offsetX: document.getElementById('lines-offset-x'), offsetX: document.getElementById('lines-offset-x'),
offsetY: document.getElementById('lines-offset-y'), offsetY: document.getElementById('lines-offset-y'),
color: document.getElementById('lines-color'), color: document.getElementById('lines-color'),
@@ -54,7 +52,6 @@ const myDom = {
maxangle: document.getElementById('polygons-maxangle'), maxangle: document.getElementById('polygons-maxangle'),
overflow: document.getElementById('polygons-overflow'), overflow: document.getElementById('polygons-overflow'),
size: document.getElementById('polygons-size'), size: document.getElementById('polygons-size'),
height: document.getElementById('polygons-height'),
offsetX: document.getElementById('polygons-offset-x'), offsetX: document.getElementById('polygons-offset-x'),
offsetY: document.getElementById('polygons-offset-y'), offsetY: document.getElementById('polygons-offset-y'),
color: document.getElementById('polygons-color'), color: document.getElementById('polygons-color'),
@@ -87,7 +84,6 @@ const createTextStyle = function(feature, resolution, dom) {
const align = dom.align.value; const align = dom.align.value;
const baseline = dom.baseline.value; const baseline = dom.baseline.value;
const size = dom.size.value; const size = dom.size.value;
const height = dom.height.value;
const offsetX = parseInt(dom.offsetX.value, 10); const offsetX = parseInt(dom.offsetX.value, 10);
const offsetY = parseInt(dom.offsetY.value, 10); const offsetY = parseInt(dom.offsetY.value, 10);
const weight = dom.weight.value; const weight = dom.weight.value;
@@ -102,7 +98,7 @@ const createTextStyle = function(feature, resolution, dom) {
document.getElementsByTagName('head')[0].appendChild(openSans); document.getElementsByTagName('head')[0].appendChild(openSans);
openSansAdded = true; openSansAdded = true;
} }
const font = weight + ' ' + size + '/' + height + ' ' + dom.font.value; const font = weight + ' ' + size + ' ' + dom.font.value;
const fillColor = dom.color.value; const fillColor = dom.color.value;
const outlineColor = dom.outline.value; const outlineColor = dom.outline.value;
const outlineWidth = parseInt(dom.outlineWidth.value, 10); const outlineWidth = parseInt(dom.outlineWidth.value, 10);

View File

@@ -1,6 +1,6 @@
{ {
"name": "ol", "name": "ol",
"version": "6.0.0-beta.3", "version": "6.0.0-beta.2",
"description": "OpenLayers mapping library", "description": "OpenLayers mapping library",
"keywords": [ "keywords": [
"map", "map",
@@ -36,7 +36,7 @@
"url": "https://github.com/openlayers/openlayers/issues" "url": "https://github.com/openlayers/openlayers/issues"
}, },
"dependencies": { "dependencies": {
"pbf": "3.2.0", "pbf": "3.1.0",
"pixelworks": "1.1.0", "pixelworks": "1.1.0",
"rbush": "2.0.2" "rbush": "2.0.2"
}, },
@@ -59,7 +59,7 @@
"front-matter": "^3.0.1", "front-matter": "^3.0.1",
"fs-extra": "^7.0.1", "fs-extra": "^7.0.1",
"glob": "^7.1.2", "glob": "^7.1.2",
"globby": "^9.1.0", "globby": "^8.0.1",
"handlebars": "4.1.0", "handlebars": "4.1.0",
"istanbul": "0.4.5", "istanbul": "0.4.5",
"jquery": "3.3.1", "jquery": "3.3.1",
@@ -75,11 +75,11 @@
"loglevelnext": "^3.0.0", "loglevelnext": "^3.0.0",
"marked": "0.6.1", "marked": "0.6.1",
"mocha": "6.0.2", "mocha": "6.0.2",
"ol-mapbox-style": "^4.1.0", "ol-mapbox-style": "^4.0.0",
"pixelmatch": "^4.0.2", "pixelmatch": "^4.0.2",
"pngjs": "^3.3.3", "pngjs": "^3.3.3",
"proj4": "2.5.0", "proj4": "2.5.0",
"puppeteer": "~1.11.0", "puppeteer": "^1.12.2",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",
"shx": "^0.3.2", "shx": "^0.3.2",
"sinon": "^7.2.3", "sinon": "^7.2.3",

View File

@@ -24,10 +24,10 @@ class VectorTile extends Tile {
this.consumers = 0; this.consumers = 0;
/** /**
* Extent of this tile; set by the source. * @private
* @type {import("./extent.js").Extent} * @type {import("./extent.js").Extent}
*/ */
this.extent = null; this.extent_ = null;
/** /**
* @private * @private
@@ -48,16 +48,11 @@ class VectorTile extends Tile {
this.loader_; this.loader_;
/** /**
* Feature projection of this tile; set by the source. * Data projection
* @private
* @type {import("./proj/Projection.js").default} * @type {import("./proj/Projection.js").default}
*/ */
this.projection = null; this.projection_ = null;
/**
* Resolution of this tile; set by the source.
* @type {number}
*/
this.resolution;
/** /**
* @private * @private
@@ -81,6 +76,15 @@ class VectorTile extends Tile {
super.disposeInternal(); super.disposeInternal();
} }
/**
* Gets the extent of the vector tile.
* @return {import("./extent.js").Extent} The extent.
* @api
*/
getExtent() {
return this.extent_;
}
/** /**
* Get the feature format assigned for reading this tile's features. * Get the feature format assigned for reading this tile's features.
* @return {import("./format/Feature.js").default} Feature format. * @return {import("./format/Feature.js").default} Feature format.
@@ -91,7 +95,8 @@ class VectorTile extends Tile {
} }
/** /**
* Get the features for this tile. Geometries will be in the view projection. * Get the features for this tile. Geometries will be in the projection returned
* by {@link module:ol/VectorTile~VectorTile#getProjection}.
* @return {Array<import("./Feature.js").FeatureLike>} Features. * @return {Array<import("./Feature.js").FeatureLike>} Features.
* @api * @api
*/ */
@@ -106,6 +111,16 @@ class VectorTile extends Tile {
return this.url_; return this.url_;
} }
/**
* Get the feature projection of features returned by
* {@link module:ol/VectorTile~VectorTile#getFeatures}.
* @return {import("./proj/Projection.js").default} Feature projection.
* @api
*/
getProjection() {
return this.projection_;
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -113,7 +128,7 @@ class VectorTile extends Tile {
if (this.state == TileState.IDLE) { if (this.state == TileState.IDLE) {
this.setState(TileState.LOADING); this.setState(TileState.LOADING);
this.tileLoadFunction_(this, this.url_); this.tileLoadFunction_(this, this.url_);
this.loader_(this.extent, this.resolution, this.projection); this.loader_(null, NaN, null);
} }
} }
@@ -121,8 +136,11 @@ class VectorTile extends Tile {
* Handler for successful tile load. * Handler for successful tile load.
* @param {Array<import("./Feature.js").default>} features The loaded features. * @param {Array<import("./Feature.js").default>} features The loaded features.
* @param {import("./proj/Projection.js").default} dataProjection Data projection. * @param {import("./proj/Projection.js").default} dataProjection Data projection.
* @param {import("./extent.js").Extent} extent Extent.
*/ */
onLoad(features, dataProjection) { onLoad(features, dataProjection, extent) {
this.setProjection(dataProjection);
this.setExtent(extent);
this.setFeatures(features); this.setFeatures(features);
} }
@@ -133,6 +151,22 @@ class VectorTile extends Tile {
this.setState(TileState.ERROR); this.setState(TileState.ERROR);
} }
/**
* Function for use in an {@link module:ol/source/VectorTile~VectorTile}'s
* `tileLoadFunction`. Sets the extent of the vector tile. This is only required
* for tiles in projections with `tile-pixels` as units. The extent should be
* set to `[0, 0, tilePixelSize, tilePixelSize]`, where `tilePixelSize` is
* calculated by multiplying the tile size with the tile pixel ratio. For
* sources using {@link module:ol/format/MVT~MVT} as feature format, the
* {@link module:ol/format/MVT~MVT#getLastExtent} method will return the correct
* extent.
* @param {import("./extent.js").Extent} extent The extent.
* @api
*/
setExtent(extent) {
this.extent_ = extent;
}
/** /**
* Function for use in an {@link module:ol/source/VectorTile~VectorTile}'s `tileLoadFunction`. * Function for use in an {@link module:ol/source/VectorTile~VectorTile}'s `tileLoadFunction`.
* Sets the features for the tile. * Sets the features for the tile.
@@ -144,6 +178,17 @@ class VectorTile extends Tile {
this.setState(TileState.LOADED); this.setState(TileState.LOADED);
} }
/**
* Function for use in an {@link module:ol/source/VectorTile~VectorTile}'s `tileLoadFunction`.
* Sets the projection of the features that were added with
* {@link module:ol/VectorTile~VectorTile#setFeatures}.
* @param {import("./proj/Projection.js").default} projection Feature projection.
* @api
*/
setProjection(projection) {
this.projection_ = projection;
}
/** /**
* Set the feature loader for reading this tile's features. * Set the feature loader for reading this tile's features.
* @param {import("./featureloader.js").FeatureLoader} loader Feature loader. * @param {import("./featureloader.js").FeatureLoader} loader Feature loader.

View File

@@ -21,9 +21,6 @@ import {clamp, modulo} from './math.js';
import {assign} from './obj.js'; import {assign} from './obj.js';
import {createProjection, METERS_PER_UNIT} from './proj.js'; import {createProjection, METERS_PER_UNIT} from './proj.js';
import Units from './proj/Units.js'; import Units from './proj/Units.js';
import {equals} from './coordinate';
import {easeOut} from './easing';
import {createMinMaxResolution} from './resolutionconstraint';
/** /**
@@ -61,8 +58,9 @@ import {createMinMaxResolution} from './resolutionconstraint';
* @property {!Array<number>} [padding=[0, 0, 0, 0]] Padding (in pixels) to be * @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 * cleared inside the view. Values in the array are top, right, bottom and left
* padding. * padding.
* @property {boolean} [nearest=false] If the view `constrainResolution` option is `true`, * @property {boolean} [constrainResolution=true] Constrain the resolution.
* get the nearest extent instead of the closest that actually fits the view. * @property {boolean} [nearest=false] If `constrainResolution` 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} [minResolution=0] Minimum resolution that we zoom to.
* @property {number} [maxZoom] Maximum zoom level that we zoom to. If * @property {number} [maxZoom] Maximum zoom level that we zoom to. If
* `minResolution` is given, this property is ignored. * `minResolution` is given, this property is ignored.
@@ -94,12 +92,7 @@ import {createMinMaxResolution} from './resolutionconstraint';
* used. The `constrainRotation` option has no effect if `enableRotation` is * used. The `constrainRotation` option has no effect if `enableRotation` is
* `false`. * `false`.
* @property {import("./extent.js").Extent} [extent] The extent that constrains the * @property {import("./extent.js").Extent} [extent] The extent that constrains the
* view, in other words, nothing outside of this extent can be visible on the map. * center, in other words, center cannot be set outside this extent.
* @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 * @property {number} [maxResolution] The maximum resolution used to determine
* the resolution constraint. It is used together with `minResolution` (or * the resolution constraint. It is used together with `minResolution` (or
* `maxZoom`) and `zoomFactor`. If unspecified it is calculated in such a way * `maxZoom`) and `zoomFactor`. If unspecified it is calculated in such a way
@@ -120,12 +113,6 @@ import {createMinMaxResolution} from './resolutionconstraint';
* resolution constraint. It is used together with `maxZoom` (or * resolution constraint. It is used together with `maxZoom` (or
* `minResolution`) and `zoomFactor`. Note that if `maxResolution` is also * `minResolution`) and `zoomFactor`. Note that if `maxResolution` is also
* provided, it is given precedence over `minZoom`. * 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 * @property {import("./proj.js").ProjectionLike} [projection='EPSG:3857'] The
* projection. The default is Spherical Mercator. * projection. The default is Spherical Mercator.
* @property {number} [resolution] The initial resolution for the view. The * @property {number} [resolution] The initial resolution for the view. The
@@ -139,9 +126,10 @@ import {createMinMaxResolution} from './resolutionconstraint';
* @property {number} [rotation=0] The initial rotation for the view in radians * @property {number} [rotation=0] The initial rotation for the view in radians
* (positive rotation clockwise, 0 means North). * (positive rotation clockwise, 0 means North).
* @property {number} [zoom] Only used if `resolution` is not defined. Zoom * @property {number} [zoom] Only used if `resolution` is not defined. Zoom
* level used to calculate the initial resolution for the view. * level used to calculate the initial resolution for the view. The initial
* @property {number} [zoomFactor=2] The zoom factor used to compute the * resolution is determined using the {@link #constrainResolution} method.
* corresponding resolution. * @property {number} [zoomFactor=2] The zoom factor used to determine the
* resolution constraint.
*/ */
@@ -155,7 +143,7 @@ import {createMinMaxResolution} from './resolutionconstraint';
* of the animation. If `zoom` is also provided, this option will be ignored. * 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 * @property {number} [rotation] The rotation of the view at the end of
* the animation. * the animation.
* @property {import("./coordinate.js").Coordinate} [anchor] Optional anchor to remain fixed * @property {import("./coordinate.js").Coordinate} [anchor] Optional anchor to remained fixed
* during a rotation or resolution animation. * during a rotation or resolution animation.
* @property {number} [duration=1000] The duration of the animation in milliseconds. * @property {number} [duration=1000] The duration of the animation in milliseconds.
* @property {function(number):number} [easing] The easing function used * @property {function(number):number} [easing] The easing function used
@@ -196,12 +184,7 @@ const DEFAULT_MIN_ZOOM = 0;
* and `rotation`. Each state has a corresponding getter and setter, e.g. * and `rotation`. Each state has a corresponding getter and setter, e.g.
* `getCenter` and `setCenter` for the `center` state. * `getCenter` and `setCenter` for the `center` state.
* *
* The `zoom` state is actually not saved on the view: all computations * An View has a `projection`. The projection determines the
* 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 * coordinate system of the center, and its units determine the units of the
* resolution (projection units per pixel). The default projection is * resolution (projection units per pixel). The default projection is
* Spherical Mercator (EPSG:3857). * Spherical Mercator (EPSG:3857).
@@ -209,19 +192,28 @@ const DEFAULT_MIN_ZOOM = 0;
* ### The constraints * ### The constraints
* *
* `setCenter`, `setResolution` and `setRotation` can be used to change the * `setCenter`, `setResolution` and `setRotation` can be used to change the
* states of the view, but any constraint defined in the constructor will * states of the view. Any value can be passed to the setters. And the value
* be applied along the way. * that is passed to a setter will effectively be the value set in the view,
* and returned by the corresponding getter.
* *
* A View object can have a *resolution constraint*, a *rotation constraint* * But a View object also has a *resolution constraint*, a
* and a *center constraint*. * *rotation constraint* and a *center constraint*.
* *
* The *resolution constraint* typically restricts min/max values and * As said above, no constraints are applied when the setters are used to set
* snaps to specific resolutions. It is determined by the following * new states for the view. Applying constraints is done explicitly through
* options: `resolutions`, `maxResolution`, `maxZoom`, and `zoomFactor`. * the use of the `constrain*` functions (`constrainResolution` and
* If `resolutions` is set, the other three options are ignored. See * `constrainRotation` and `constrainCenter`).
* documentation for each option for more information. By default, the view *
* only has a min/max restriction and allow intermediary zoom levels when * The main users of the constraints are the interactions and the
* pinch-zooming for example. * 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 *rotation constraint* snaps to specific angles. It is determined * The *rotation constraint* snaps to specific angles. It is determined
* by the following options: `enableRotation` and `constrainRotation`. * by the following options: `enableRotation` and `constrainRotation`.
@@ -229,29 +221,7 @@ const DEFAULT_MIN_ZOOM = 0;
* horizontal. * horizontal.
* *
* The *center constraint* is determined by the `extent` option. By * The *center constraint* is determined by the `extent` option. By
* default the view center is not constrained at all. * default the center is not constrained at all.
*
* ### 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 * @api
*/ */
@@ -292,24 +262,6 @@ class View extends BaseObject {
*/ */
this.projection_ = createProjection(options.projection, 'EPSG:3857'); 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); this.applyOptions_(options);
} }
@@ -323,6 +275,8 @@ class View extends BaseObject {
* @type {Object<string, *>} * @type {Object<string, *>}
*/ */
const properties = {}; const properties = {};
properties[ViewProperty.CENTER] = options.center !== undefined ?
options.center : null;
const resolutionConstraintInfo = createResolutionConstraint(options); const resolutionConstraintInfo = createResolutionConstraint(options);
@@ -370,20 +324,19 @@ class View extends BaseObject {
rotation: rotationConstraint rotation: rotationConstraint
}; };
this.setRotation(options.rotation !== undefined ? options.rotation : 0);
this.setCenter(options.center !== undefined ? options.center : null);
if (options.resolution !== undefined) { if (options.resolution !== undefined) {
this.setResolution(options.resolution); properties[ViewProperty.RESOLUTION] = options.resolution;
} else if (options.zoom !== undefined) { } else if (options.zoom !== undefined) {
if (this.resolutions_) { // in case map zoom is out of min/max zoom range properties[ViewProperty.RESOLUTION] = this.constrainResolution(
const resolution = this.getResolutionForZoom(options.zoom); this.maxResolution_, options.zoom - this.minZoom_);
this.setResolution(clamp(resolution,
this.minResolution_, this.maxResolution_));
} else {
this.setZoom(options.zoom);
}
}
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_);
}
}
properties[ViewProperty.ROTATION] = options.rotation !== undefined ? options.rotation : 0;
this.setProperties(properties); this.setProperties(properties);
/** /**
@@ -479,9 +432,9 @@ class View extends BaseObject {
return; return;
} }
let start = Date.now(); let start = Date.now();
let center = this.targetCenter_.slice(); let center = this.getCenter().slice();
let resolution = this.targetResolution_; let resolution = this.getResolution();
let rotation = this.targetRotation_; let rotation = this.getRotation();
const series = []; const series = [];
for (let i = 0; i < animationCount; ++i) { for (let i = 0; i < animationCount; ++i) {
const options = /** @type {AnimationOptions} */ (arguments[i]); const options = /** @type {AnimationOptions} */ (arguments[i]);
@@ -497,13 +450,14 @@ class View extends BaseObject {
if (options.center) { if (options.center) {
animation.sourceCenter = center; animation.sourceCenter = center;
animation.targetCenter = options.center.slice(); animation.targetCenter = options.center;
center = animation.targetCenter; center = animation.targetCenter;
} }
if (options.zoom !== undefined) { if (options.zoom !== undefined) {
animation.sourceResolution = resolution; animation.sourceResolution = resolution;
animation.targetResolution = this.getResolutionForZoom(options.zoom); animation.targetResolution = this.constrainResolution(
this.maxResolution_, options.zoom - this.minZoom_, 0);
resolution = animation.targetResolution; resolution = animation.targetResolution;
} else if (options.resolution) { } else if (options.resolution) {
animation.sourceResolution = resolution; animation.sourceResolution = resolution;
@@ -602,31 +556,28 @@ class View extends BaseObject {
const y1 = animation.targetCenter[1]; const y1 = animation.targetCenter[1];
const x = x0 + progress * (x1 - x0); const x = x0 + progress * (x1 - x0);
const y = y0 + progress * (y1 - y0); const y = y0 + progress * (y1 - y0);
this.targetCenter_ = [x, y]; this.set(ViewProperty.CENTER, [x, y]);
} }
if (animation.sourceResolution && animation.targetResolution) { if (animation.sourceResolution && animation.targetResolution) {
const resolution = progress === 1 ? const resolution = progress === 1 ?
animation.targetResolution : animation.targetResolution :
animation.sourceResolution + progress * (animation.targetResolution - animation.sourceResolution); animation.sourceResolution + progress * (animation.targetResolution - animation.sourceResolution);
if (animation.anchor) { if (animation.anchor) {
const size = this.getSizeFromViewport_(this.getRotation()); this.set(ViewProperty.CENTER,
const constrainedResolution = this.constraints_.resolution(resolution, 0, size, true); this.calculateCenterZoom(resolution, animation.anchor));
this.targetCenter_ = this.calculateCenterZoom(constrainedResolution, animation.anchor);
} }
this.targetResolution_ = resolution; this.set(ViewProperty.RESOLUTION, resolution);
this.applyTargetState_(true);
} }
if (animation.sourceRotation !== undefined && animation.targetRotation !== undefined) { if (animation.sourceRotation !== undefined && animation.targetRotation !== undefined) {
const rotation = progress === 1 ? const rotation = progress === 1 ?
modulo(animation.targetRotation + Math.PI, 2 * Math.PI) - Math.PI : modulo(animation.targetRotation + Math.PI, 2 * Math.PI) - Math.PI :
animation.sourceRotation + progress * (animation.targetRotation - animation.sourceRotation); animation.sourceRotation + progress * (animation.targetRotation - animation.sourceRotation);
if (animation.anchor) { if (animation.anchor) {
const constrainedRotation = this.constraints_.rotation(rotation, true); this.set(ViewProperty.CENTER,
this.targetCenter_ = this.calculateCenterRotate(constrainedRotation, animation.anchor); this.calculateCenterRotate(rotation, animation.anchor));
} }
this.targetRotation_ = rotation; this.set(ViewProperty.ROTATION, rotation);
} }
this.applyTargetState_(true);
more = true; more = true;
if (!animation.complete) { if (!animation.complete) {
break; break;
@@ -646,10 +597,6 @@ class View extends BaseObject {
if (more && this.updateAnimationKey_ === undefined) { if (more && this.updateAnimationKey_ === undefined) {
this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_); this.updateAnimationKey_ = requestAnimationFrame(this.updateAnimations_);
} }
if (!this.getAnimating()) {
setTimeout(this.resolveConstraints_.bind(this), 0);
}
} }
/** /**
@@ -687,10 +634,9 @@ class View extends BaseObject {
/** /**
* @private * @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. * @return {import("./size.js").Size} Viewport size or `[100, 100]` when no viewport is found.
*/ */
getSizeFromViewport_(opt_rotation) { getSizeFromViewport_() {
const size = [100, 100]; const size = [100, 100];
const selector = '.ol-viewport[data-view="' + getUid(this) + '"]'; const selector = '.ol-viewport[data-view="' + getUid(this) + '"]';
const element = document.querySelector(selector); const element = document.querySelector(selector);
@@ -699,15 +645,45 @@ class View extends BaseObject {
size[0] = parseInt(metrics.width, 10); size[0] = parseInt(metrics.width, 10);
size[1] = parseInt(metrics.height, 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; 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. * Get the view center.
* @return {import("./coordinate.js").Coordinate|undefined} The center of the view. * @return {import("./coordinate.js").Coordinate|undefined} The center of the view.
@@ -817,15 +793,6 @@ class View extends BaseObject {
this.applyOptions_(this.getUpdatedOptions_({minZoom: zoom})); 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. * Get the view projection.
* @return {import("./proj/Projection.js").default} The projection of the view. * @return {import("./proj/Projection.js").default} The projection of the view.
@@ -947,9 +914,9 @@ class View extends BaseObject {
} }
/** /**
* Get the current zoom level. This method may return non-integer zoom levels * Get the current zoom level. If you configured your view with a resolutions
* if the view does not constrain the resolution, or if an interaction or * array (this is rare), this method may return non-integer zoom levels (so
* animation is underway. * the zoom level is not safe to use as an index into a resolutions array).
* @return {number|undefined} Zoom. * @return {number|undefined} Zoom.
* @api * @api
*/ */
@@ -994,16 +961,8 @@ class View extends BaseObject {
* @api * @api
*/ */
getResolutionForZoom(zoom) { getResolutionForZoom(zoom) {
if (this.resolutions_) { return /** @type {number} */ (this.constrainResolution(
if (this.resolutions_.length <= 1) { this.maxResolution_, zoom - this.minZoom_, 0));
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_);
}
} }
/** /**
@@ -1039,12 +998,15 @@ class View extends BaseObject {
} }
const padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0]; 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; const nearest = options.nearest !== undefined ? options.nearest : false;
let minResolution; let minResolution;
if (options.minResolution !== undefined) { if (options.minResolution !== undefined) {
minResolution = options.minResolution; minResolution = options.minResolution;
} else if (options.maxZoom !== undefined) { } else if (options.maxZoom !== undefined) {
minResolution = this.getResolutionForZoom(options.maxZoom); minResolution = this.constrainResolution(
this.maxResolution_, options.maxZoom - this.minZoom_, 0);
} else { } else {
minResolution = 0; minResolution = 0;
} }
@@ -1074,7 +1036,14 @@ class View extends BaseObject {
[size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]); [size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]);
resolution = isNaN(resolution) ? minResolution : resolution = isNaN(resolution) ? minResolution :
Math.max(resolution, minResolution); Math.max(resolution, minResolution);
resolution = this.getConstrainedResolution(resolution, nearest ? 0 : 1); if (constrainResolution) {
let constrainedResolution = this.constrainResolution(resolution, 0, 0);
if (!nearest && constrainedResolution < resolution) {
constrainedResolution = this.constrainResolution(
constrainedResolution, -1, 0);
}
resolution = constrainedResolution;
}
// calculate center // calculate center
sinAngle = -sinAngle; // go back to original rotation sinAngle = -sinAngle; // go back to original rotation
@@ -1090,14 +1059,13 @@ class View extends BaseObject {
if (options.duration !== undefined) { if (options.duration !== undefined) {
this.animate({ this.animate({
resolution: resolution, resolution: resolution,
center: this.getConstrainedCenter(center, resolution), center: center,
duration: options.duration, duration: options.duration,
easing: options.easing easing: options.easing
}, callback); }, callback);
} else { } else {
this.targetResolution_ = resolution; this.setResolution(resolution);
this.targetCenter_ = center; this.setCenter(center);
this.applyTargetState_(false, true);
animationCallback(callback, true); animationCallback(callback, true);
} }
} }
@@ -1136,74 +1104,30 @@ class View extends BaseObject {
} }
/** /**
* Adds relative coordinates to the center of the view. Any extent constraint will apply. * Rotate the view around a given coordinate.
* @param {import("./coordinate.js").Coordinate} deltaCoordinates Relative value to add. * @param {number} rotation New rotation value for the view.
* @api
*/
adjustCenter(deltaCoordinates) {
const center = this.targetCenter_;
this.setCenter([center[0] + deltaCoordinates[0], center[1] + deltaCoordinates[1]]);
}
/**
* 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. * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center.
* @observable
* @api * @api
*/ */
adjustRotation(delta, opt_anchor) { rotate(rotation, opt_anchor) {
const isMoving = this.getAnimating() || this.getInteracting();
const newRotation = this.constraints_.rotation(this.targetRotation_ + delta, isMoving);
if (opt_anchor !== undefined) { if (opt_anchor !== undefined) {
this.targetCenter_ = this.calculateCenterRotate(newRotation, opt_anchor); const center = this.calculateCenterRotate(rotation, opt_anchor);
this.setCenter(center);
} }
this.targetRotation_ += delta; this.setRotation(rotation);
this.applyTargetState_();
} }
/** /**
* Set the center of the current view. Any extent constraint will apply. * Set the center of the current view.
* @param {import("./coordinate.js").Coordinate|undefined} center The center of the view. * @param {import("./coordinate.js").Coordinate|undefined} center The center of the view.
* @observable * @observable
* @api * @api
*/ */
setCenter(center) { setCenter(center) {
this.targetCenter_ = center; this.set(ViewProperty.CENTER, center);
this.applyTargetState_(); if (this.getAnimating()) {
this.cancelAnimations();
}
} }
/** /**
@@ -1218,166 +1142,39 @@ class View extends BaseObject {
} }
/** /**
* Set the resolution for this view. Any resolution constraint will apply. * Set the resolution for this view.
* @param {number|undefined} resolution The resolution of the view. * @param {number|undefined} resolution The resolution of the view.
* @observable * @observable
* @api * @api
*/ */
setResolution(resolution) { setResolution(resolution) {
this.targetResolution_ = resolution; this.set(ViewProperty.RESOLUTION, resolution);
this.applyTargetState_(); if (this.getAnimating()) {
this.cancelAnimations();
}
} }
/** /**
* Set the rotation for this view. Any rotation constraint will apply. * Set the rotation for this view.
* @param {number} rotation The rotation of the view in radians. * @param {number} rotation The rotation of the view in radians.
* @observable * @observable
* @api * @api
*/ */
setRotation(rotation) { setRotation(rotation) {
this.targetRotation_ = rotation; this.set(ViewProperty.ROTATION, rotation);
this.applyTargetState_(); if (this.getAnimating()) {
this.cancelAnimations();
}
} }
/** /**
* Zoom to a specific zoom level. Any resolution constrain will apply. * Zoom to a specific zoom level.
* @param {number} zoom Zoom level. * @param {number} zoom Zoom level.
* @api * @api
*/ */
setZoom(zoom) { setZoom(zoom) {
this.setResolution(this.getResolutionForZoom(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);
}
} }
@@ -1398,8 +1195,7 @@ function animationCallback(callback, returnValue) {
*/ */
export function createCenterConstraint(options) { export function createCenterConstraint(options) {
if (options.extent !== undefined) { if (options.extent !== undefined) {
return createExtent(options.extent, options.constrainOnlyCenter, return createExtent(options.extent);
options.smoothExtentConstraint !== undefined ? options.smoothExtentConstraint : true);
} else { } else {
return centerNone; return centerNone;
} }
@@ -1430,22 +1226,13 @@ export function createResolutionConstraint(options) {
const zoomFactor = options.zoomFactor !== undefined ? const zoomFactor = options.zoomFactor !== undefined ?
options.zoomFactor : defaultZoomFactor; options.zoomFactor : defaultZoomFactor;
const smooth =
options.smoothResolutionConstraint !== undefined ? options.smoothResolutionConstraint : true;
if (options.resolutions !== undefined) { if (options.resolutions !== undefined) {
const resolutions = options.resolutions; const resolutions = options.resolutions;
maxResolution = resolutions[minZoom]; maxResolution = resolutions[minZoom];
minResolution = resolutions[maxZoom] !== undefined ? minResolution = resolutions[maxZoom] !== undefined ?
resolutions[maxZoom] : resolutions[resolutions.length - 1]; resolutions[maxZoom] : resolutions[resolutions.length - 1];
resolutionConstraint = createSnapToResolutions(
if (options.constrainResolution) { resolutions);
resolutionConstraint = createSnapToResolutions(resolutions, smooth,
!options.constrainOnlyCenter && options.extent);
} else {
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
!options.constrainOnlyCenter && options.extent);
}
} else { } else {
// calculate the default min and max resolution // calculate the default min and max resolution
const projection = createProjection(options.projection, 'EPSG:3857'); const projection = createProjection(options.projection, 'EPSG:3857');
@@ -1489,14 +1276,8 @@ export function createResolutionConstraint(options) {
Math.log(maxResolution / minResolution) / Math.log(zoomFactor)); Math.log(maxResolution / minResolution) / Math.log(zoomFactor));
minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom); minResolution = maxResolution / Math.pow(zoomFactor, maxZoom - minZoom);
if (options.constrainResolution) {
resolutionConstraint = createSnapToPower( resolutionConstraint = createSnapToPower(
zoomFactor, maxResolution, minResolution, smooth, zoomFactor, maxResolution, maxZoom - minZoom);
!options.constrainOnlyCenter && options.extent);
} else {
resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth,
!options.constrainOnlyCenter && options.extent);
}
} }
return {constraint: resolutionConstraint, maxResolution: maxResolution, return {constraint: resolutionConstraint, maxResolution: maxResolution,
minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor}; minResolution: minResolution, minZoom: minZoom, zoomFactor: zoomFactor};

View File

@@ -5,57 +5,26 @@ import {clamp} from './math.js';
/** /**
* @typedef {function((import("./coordinate.js").Coordinate|undefined), number, import("./size.js").Size, boolean=): (import("./coordinate.js").Coordinate|undefined)} Type * @typedef {function((import("./coordinate.js").Coordinate|undefined)): (import("./coordinate.js").Coordinate|undefined)} Type
*/ */
/** /**
* @param {import("./extent.js").Extent} extent Extent. * @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. * @return {Type} The constraint.
*/ */
export function createExtent(extent, onlyCenter, smooth) { export function createExtent(extent) {
return ( return (
/** /**
* @param {import("./coordinate.js").Coordinate|undefined} center Center. * @param {import("./coordinate.js").Coordinate=} 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. * @return {import("./coordinate.js").Coordinate|undefined} Center.
*/ */
function(center, resolution, size, opt_isMoving) { function(center) {
if (center) { if (center) {
const viewWidth = onlyCenter ? 0 : size[0] * resolution; return [
const viewHeight = onlyCenter ? 0 : size[1] * resolution; clamp(center[0], extent[0], extent[2]),
let minX = extent[0] + viewWidth / 2; clamp(center[1], extent[1], extent[3])
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 { } else {
return undefined; return undefined;
} }

View File

@@ -114,20 +114,20 @@ class Zoom extends Control {
// upon it // upon it
return; return;
} }
const currentZoom = view.getZoom(); const currentResolution = view.getResolution();
if (currentZoom !== undefined) { if (currentResolution) {
const newZoom = view.getConstrainedZoom(currentZoom + delta); const newResolution = view.constrainResolution(currentResolution, delta);
if (this.duration_ > 0) { if (this.duration_ > 0) {
if (view.getAnimating()) { if (view.getAnimating()) {
view.cancelAnimations(); view.cancelAnimations();
} }
view.animate({ view.animate({
zoom: newZoom, resolution: newResolution,
duration: this.duration_, duration: this.duration_,
easing: easeOut easing: easeOut
}); });
} else { } else {
view.setZoom(newZoom); view.setResolution(newResolution);
} }
} }
} }

View File

@@ -1,6 +1,7 @@
/** /**
* @module ol/control/ZoomSlider * @module ol/control/ZoomSlider
*/ */
import ViewHint from '../ViewHint.js';
import Control from './Control.js'; import Control from './Control.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE} from '../css.js'; import {CLASS_CONTROL, CLASS_UNSELECTABLE} from '../css.js';
import {easeOut} from '../easing.js'; import {easeOut} from '../easing.js';
@@ -101,13 +102,13 @@ class ZoomSlider extends Control {
* @type {number|undefined} * @type {number|undefined}
* @private * @private
*/ */
this.startX_; this.previousX_;
/** /**
* @type {number|undefined} * @type {number|undefined}
* @private * @private
*/ */
this.startY_; this.previousY_;
/** /**
* The calculated thumb size (border box plus margins). Set when initSlider_ * The calculated thumb size (border box plus margins). Set when initSlider_
@@ -217,10 +218,9 @@ class ZoomSlider extends Control {
event.offsetY - this.thumbSize_[1] / 2); event.offsetY - this.thumbSize_[1] / 2);
const resolution = this.getResolutionForPosition_(relativePosition); const resolution = this.getResolutionForPosition_(relativePosition);
const zoom = view.getConstrainedZoom(view.getZoomForResolution(resolution));
view.animate({ view.animate({
zoom: zoom, resolution: view.constrainResolution(resolution),
duration: this.duration_, duration: this.duration_,
easing: easeOut easing: easeOut
}); });
@@ -233,10 +233,9 @@ class ZoomSlider extends Control {
*/ */
handleDraggerStart_(event) { handleDraggerStart_(event) {
if (!this.dragging_ && event.originalEvent.target === this.element.firstElementChild) { if (!this.dragging_ && event.originalEvent.target === this.element.firstElementChild) {
const element = /** @type {HTMLElement} */ (this.element.firstElementChild); this.getMap().getView().setHint(ViewHint.INTERACTING, 1);
this.getMap().getView().beginInteraction(); this.previousX_ = event.clientX;
this.startX_ = event.clientX - parseFloat(element.style.left); this.previousY_ = event.clientY;
this.startY_ = event.clientY - parseFloat(element.style.top);
this.dragging_ = true; this.dragging_ = true;
if (this.dragListenerKeys_.length === 0) { if (this.dragListenerKeys_.length === 0) {
@@ -260,11 +259,15 @@ class ZoomSlider extends Control {
*/ */
handleDraggerDrag_(event) { handleDraggerDrag_(event) {
if (this.dragging_) { if (this.dragging_) {
const deltaX = event.clientX - this.startX_; const element = /** @type {HTMLElement} */ (this.element.firstElementChild);
const deltaY = event.clientY - this.startY_; const deltaX = event.clientX - this.previousX_ + parseFloat(element.style.left);
const deltaY = event.clientY - this.previousY_ + parseFloat(element.style.top);
const relativePosition = this.getRelativePosition_(deltaX, deltaY); const relativePosition = this.getRelativePosition_(deltaX, deltaY);
this.currentResolution_ = this.getResolutionForPosition_(relativePosition); this.currentResolution_ = this.getResolutionForPosition_(relativePosition);
this.getMap().getView().setResolution(this.currentResolution_); this.getMap().getView().setResolution(this.currentResolution_);
this.setThumbPosition_(this.currentResolution_);
this.previousX_ = event.clientX;
this.previousY_ = event.clientY;
} }
} }
@@ -276,11 +279,17 @@ class ZoomSlider extends Control {
handleDraggerEnd_(event) { handleDraggerEnd_(event) {
if (this.dragging_) { if (this.dragging_) {
const view = this.getMap().getView(); const view = this.getMap().getView();
view.endInteraction(); view.setHint(ViewHint.INTERACTING, -1);
view.animate({
resolution: view.constrainResolution(this.currentResolution_),
duration: this.duration_,
easing: easeOut
});
this.dragging_ = false; this.dragging_ = false;
this.startX_ = undefined; this.previousX_ = undefined;
this.startY_ = undefined; this.previousY_ = undefined;
this.dragListenerKeys_.forEach(unlistenByKey); this.dragListenerKeys_.forEach(unlistenByKey);
this.dragListenerKeys_.length = 0; this.dragListenerKeys_.length = 0;
} }
@@ -347,7 +356,7 @@ class ZoomSlider extends Control {
*/ */
getPositionForResolution_(res) { getPositionForResolution_(res) {
const fn = this.getMap().getView().getValueForResolutionFunction(); const fn = this.getMap().getView().getValueForResolutionFunction();
return clamp(1 - fn(res), 0, 1); return 1 - fn(res);
} }
} }
@@ -366,8 +375,10 @@ export function render(mapEvent) {
this.initSlider_(); this.initSlider_();
} }
const res = mapEvent.frameState.viewState.resolution; const res = mapEvent.frameState.viewState.resolution;
if (res !== this.currentResolution_) {
this.currentResolution_ = res; this.currentResolution_ = res;
this.setThumbPosition_(res); this.setThumbPosition_(res);
}
} }

View File

@@ -83,11 +83,9 @@ export function loadFeaturesXhr(url, format, success, failure) {
source = /** @type {ArrayBuffer} */ (xhr.response); source = /** @type {ArrayBuffer} */ (xhr.response);
} }
if (source) { if (source) {
success.call(this, format.readFeatures(source, { success.call(this, format.readFeatures(source,
extent: extent, {featureProjection: projection}),
featureProjection: projection format.readProjection(source), format.getLastExtent());
}),
format.readProjection(source));
} else { } else {
failure.call(this); failure.call(this);
} }

View File

@@ -111,6 +111,14 @@ class FeatureFormat {
}, options); }, options);
} }
/**
* Get the extent from the source of the last {@link readFeatures} call.
* @return {import("../extent.js").Extent} Tile extent.
*/
getLastExtent() {
return null;
}
/** /**
* @abstract * @abstract
* @return {import("./FormatType.js").default} Format. * @return {import("./FormatType.js").default} Format.

View File

@@ -19,7 +19,6 @@ import {linearRingIsClockwise} from '../geom/flat/orient.js';
import Projection from '../proj/Projection.js'; import Projection from '../proj/Projection.js';
import Units from '../proj/Units.js'; import Units from '../proj/Units.js';
import RenderFeature from '../render/Feature.js'; import RenderFeature from '../render/Feature.js';
import {get} from '../proj.js';
/** /**
@@ -84,6 +83,12 @@ class MVT extends FeatureFormat {
*/ */
this.layers_ = options.layers ? options.layers : null; this.layers_ = options.layers ? options.layers : null;
/**
* @private
* @type {import("../extent.js").Extent}
*/
this.extent_ = null;
} }
/** /**
@@ -154,10 +159,10 @@ class MVT extends FeatureFormat {
* @private * @private
* @param {PBF} pbf PBF * @param {PBF} pbf PBF
* @param {Object} rawFeature Raw Mapbox feature. * @param {Object} rawFeature Raw Mapbox feature.
* @param {import("./Feature.js").ReadOptions} options Read options. * @param {import("./Feature.js").ReadOptions=} opt_options Read options.
* @return {import("../Feature.js").FeatureLike} Feature. * @return {import("../Feature.js").FeatureLike} Feature.
*/ */
createFeature_(pbf, rawFeature, options) { createFeature_(pbf, rawFeature, opt_options) {
const type = rawFeature.type; const type = rawFeature.type;
if (type === 0) { if (type === 0) {
return null; return null;
@@ -176,7 +181,6 @@ class MVT extends FeatureFormat {
if (this.featureClass_ === RenderFeature) { if (this.featureClass_ === RenderFeature) {
feature = new this.featureClass_(geometryType, flatCoordinates, ends, values, id); feature = new this.featureClass_(geometryType, flatCoordinates, ends, values, id);
feature.transform(options.dataProjection, options.featureProjection);
} else { } else {
let geom; let geom;
if (geometryType == GeometryType.POLYGON) { if (geometryType == GeometryType.POLYGON) {
@@ -209,7 +213,7 @@ class MVT extends FeatureFormat {
if (this.geometryName_) { if (this.geometryName_) {
feature.setGeometryName(this.geometryName_); feature.setGeometryName(this.geometryName_);
} }
const geometry = transformGeometryWithOptions(geom, false, options); const geometry = transformGeometryWithOptions(geom, false, this.adaptOptions(opt_options));
feature.setGeometry(geometry); feature.setGeometry(geometry);
feature.setId(id); feature.setId(id);
feature.setProperties(values, true); feature.setProperties(values, true);
@@ -218,6 +222,14 @@ class MVT extends FeatureFormat {
return feature; return feature;
} }
/**
* @inheritDoc
* @api
*/
getLastExtent() {
return this.extent_;
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -226,22 +238,15 @@ class MVT extends FeatureFormat {
} }
/** /**
* Read all features. * @inheritDoc
*
* @param {ArrayBuffer} source Source.
* @param {import("./Feature.js").ReadOptions=} opt_options Read options.
* @return {Array<import("../Feature.js").FeatureLike>} Features.
* @api * @api
*/ */
readFeatures(source, opt_options) { readFeatures(source, opt_options) {
const layers = this.layers_; const layers = this.layers_;
const options = /** @type {import("./Feature.js").ReadOptions} */ (this.adaptOptions(opt_options));
const dataProjection = get(options.dataProjection);
dataProjection.setWorldExtent(options.extent);
options.dataProjection = dataProjection;
const pbf = new PBF(/** @type {ArrayBuffer} */ (source)); const pbf = new PBF(/** @type {ArrayBuffer} */ (source));
const pbfLayers = pbf.readFields(layersPBFReader, {}); const pbfLayers = pbf.readFields(layersPBFReader, {});
/** @type {Array<import("../Feature.js").FeatureLike>} */
const features = []; const features = [];
for (const name in pbfLayers) { for (const name in pbfLayers) {
if (layers && layers.indexOf(name) == -1) { if (layers && layers.indexOf(name) == -1) {
@@ -249,13 +254,11 @@ class MVT extends FeatureFormat {
} }
const pbfLayer = pbfLayers[name]; const pbfLayer = pbfLayers[name];
const extent = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
dataProjection.setExtent(extent);
for (let i = 0, ii = pbfLayer.length; i < ii; ++i) { for (let i = 0, ii = pbfLayer.length; i < ii; ++i) {
const rawFeature = readRawFeature(pbf, pbfLayer, i); const rawFeature = readRawFeature(pbf, pbfLayer, i);
features.push(this.createFeature_(pbf, rawFeature, options)); features.push(this.createFeature_(pbf, rawFeature));
} }
this.extent_ = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
} }
return features; return features;

View File

@@ -44,6 +44,8 @@ export {default as Translate} from './interaction/Translate.js';
* focus. This affects the `MouseWheelZoom` and `DragPan` interactions and is * 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 * useful when page scroll is desired for maps that do not have the browser's
* focus. * 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 * @property {boolean} [doubleClickZoom=true] Whether double click zoom is
* desired. * desired.
* @property {boolean} [keyboard=true] Whether keyboard interaction is desired. * @property {boolean} [keyboard=true] Whether keyboard interaction is desired.
@@ -125,6 +127,7 @@ export function defaults(opt_options) {
const pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true; const pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true;
if (pinchZoom) { if (pinchZoom) {
interactions.push(new PinchZoom({ interactions.push(new PinchZoom({
constrainResolution: options.constrainResolution,
duration: options.zoomDuration duration: options.zoomDuration
})); }));
} }
@@ -143,6 +146,7 @@ export function defaults(opt_options) {
if (mouseWheelZoom) { if (mouseWheelZoom) {
interactions.push(new MouseWheelZoom({ interactions.push(new MouseWheelZoom({
condition: options.onFocusOnly ? focus : undefined, condition: options.onFocusOnly ? focus : undefined,
constrainResolution: options.constrainResolution,
duration: options.zoomDuration duration: options.zoomDuration
})); }));
} }

View File

@@ -1,7 +1,8 @@
/** /**
* @module ol/interaction/DragPan * @module ol/interaction/DragPan
*/ */
import {scale as scaleCoordinate, rotate as rotateCoordinate} from '../coordinate.js'; import ViewHint from '../ViewHint.js';
import {scale as scaleCoordinate, rotate as rotateCoordinate, add as addCoordinate} from '../coordinate.js';
import {easeOut} from '../easing.js'; import {easeOut} from '../easing.js';
import {noModifierKeys} from '../events/condition.js'; import {noModifierKeys} from '../events/condition.js';
import {FALSE} from '../functions.js'; import {FALSE} from '../functions.js';
@@ -73,6 +74,10 @@ class DragPan extends PointerInteraction {
* @inheritDoc * @inheritDoc
*/ */
handleDragEvent(mapBrowserEvent) { handleDragEvent(mapBrowserEvent) {
if (!this.panning_) {
this.panning_ = true;
this.getMap().getView().setHint(ViewHint.INTERACTING, 1);
}
const targetPointers = this.targetPointers; const targetPointers = this.targetPointers;
const centroid = centroidFromPointers(targetPointers); const centroid = centroidFromPointers(targetPointers);
if (targetPointers.length == this.lastPointersCount_) { if (targetPointers.length == this.lastPointersCount_) {
@@ -80,15 +85,16 @@ class DragPan extends PointerInteraction {
this.kinetic_.update(centroid[0], centroid[1]); this.kinetic_.update(centroid[0], centroid[1]);
} }
if (this.lastCentroid) { if (this.lastCentroid) {
const delta = [ const deltaX = this.lastCentroid[0] - centroid[0];
this.lastCentroid[0] - centroid[0], const deltaY = centroid[1] - this.lastCentroid[1];
centroid[1] - this.lastCentroid[1]
];
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
const view = map.getView(); const view = map.getView();
scaleCoordinate(delta, view.getResolution()); let center = [deltaX, deltaY];
rotateCoordinate(delta, view.getRotation()); scaleCoordinate(center, view.getResolution());
view.adjustCenter(delta); rotateCoordinate(center, view.getRotation());
addCoordinate(center, view.getCenter());
center = view.constrainCenter(center);
view.setCenter(center);
} }
} else if (this.kinetic_) { } else if (this.kinetic_) {
// reset so we don't overestimate the kinetic energy after // reset so we don't overestimate the kinetic energy after
@@ -116,14 +122,14 @@ class DragPan extends PointerInteraction {
centerpx[1] - distance * Math.sin(angle) centerpx[1] - distance * Math.sin(angle)
]); ]);
view.animate({ view.animate({
center: view.getConstrainedCenter(dest), center: view.constrainCenter(dest),
duration: 500, duration: 500,
easing: easeOut easing: easeOut
}); });
} }
if (this.panning_) { if (this.panning_) {
this.panning_ = false; this.panning_ = false;
view.endInteraction(); view.setHint(ViewHint.INTERACTING, -1);
} }
return false; return false;
} else { } else {
@@ -147,11 +153,7 @@ class DragPan extends PointerInteraction {
this.lastCentroid = null; this.lastCentroid = null;
// stop any current animation // stop any current animation
if (view.getAnimating()) { if (view.getAnimating()) {
view.cancelAnimations(); view.setCenter(mapBrowserEvent.frameState.viewState.center);
}
if (!this.panning_) {
this.panning_ = true;
this.getMap().getView().beginInteraction();
} }
if (this.kinetic_) { if (this.kinetic_) {
this.kinetic_.begin(); this.kinetic_.begin();

View File

@@ -2,8 +2,10 @@
* @module ol/interaction/DragRotate * @module ol/interaction/DragRotate
*/ */
import {disable} from '../rotationconstraint.js'; import {disable} from '../rotationconstraint.js';
import ViewHint from '../ViewHint.js';
import {altShiftKeysOnly, mouseOnly, mouseActionButton} from '../events/condition.js'; import {altShiftKeysOnly, mouseOnly, mouseActionButton} from '../events/condition.js';
import {FALSE} from '../functions.js'; import {FALSE} from '../functions.js';
import {rotate, rotateWithoutConstraints} from './Interaction.js';
import PointerInteraction from './Pointer.js'; import PointerInteraction from './Pointer.js';
@@ -78,7 +80,8 @@ class DragRotate extends PointerInteraction {
Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2); Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2);
if (this.lastAngle_ !== undefined) { if (this.lastAngle_ !== undefined) {
const delta = theta - this.lastAngle_; const delta = theta - this.lastAngle_;
view.adjustRotation(-delta); const rotation = view.getRotation();
rotateWithoutConstraints(view, rotation - delta);
} }
this.lastAngle_ = theta; this.lastAngle_ = theta;
} }
@@ -94,7 +97,9 @@ class DragRotate extends PointerInteraction {
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
const view = map.getView(); const view = map.getView();
view.endInteraction(this.duration_); view.setHint(ViewHint.INTERACTING, -1);
const rotation = view.getRotation();
rotate(view, rotation, undefined, this.duration_);
return false; return false;
} }
@@ -109,7 +114,7 @@ class DragRotate extends PointerInteraction {
if (mouseActionButton(mapBrowserEvent) && this.condition_(mapBrowserEvent)) { if (mouseActionButton(mapBrowserEvent) && this.condition_(mapBrowserEvent)) {
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
map.getView().beginInteraction(); map.getView().setHint(ViewHint.INTERACTING, 1);
this.lastAngle_ = undefined; this.lastAngle_ = undefined;
return true; return true;
} else { } else {

View File

@@ -1,7 +1,10 @@
/** /**
* @module ol/interaction/DragRotateAndZoom * @module ol/interaction/DragRotateAndZoom
*/ */
import {disable} from '../rotationconstraint.js';
import ViewHint from '../ViewHint.js';
import {shiftKeyOnly, mouseOnly} from '../events/condition.js'; import {shiftKeyOnly, mouseOnly} from '../events/condition.js';
import {rotate, rotateWithoutConstraints, zoom, zoomWithoutConstraints} from './Interaction.js';
import PointerInteraction from './Pointer.js'; import PointerInteraction from './Pointer.js';
@@ -85,13 +88,14 @@ class DragRotateAndZoom extends PointerInteraction {
const theta = Math.atan2(deltaY, deltaX); const theta = Math.atan2(deltaY, deltaX);
const magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY); const magnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
const view = map.getView(); const view = map.getView();
if (this.lastAngle_ !== undefined) { if (view.getConstraints().rotation !== disable && this.lastAngle_ !== undefined) {
const angleDelta = this.lastAngle_ - theta; const angleDelta = theta - this.lastAngle_;
view.adjustRotation(angleDelta); rotateWithoutConstraints(view, view.getRotation() - angleDelta);
} }
this.lastAngle_ = theta; this.lastAngle_ = theta;
if (this.lastMagnitude_ !== undefined) { if (this.lastMagnitude_ !== undefined) {
view.adjustResolution(this.lastMagnitude_ / magnitude); const resolution = this.lastMagnitude_ * (view.getResolution() / magnitude);
zoomWithoutConstraints(view, resolution);
} }
if (this.lastMagnitude_ !== undefined) { if (this.lastMagnitude_ !== undefined) {
this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; this.lastScaleDelta_ = this.lastMagnitude_ / magnitude;
@@ -109,8 +113,10 @@ class DragRotateAndZoom extends PointerInteraction {
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
const view = map.getView(); const view = map.getView();
const direction = this.lastScaleDelta_ > 1 ? 1 : -1; view.setHint(ViewHint.INTERACTING, -1);
view.endInteraction(this.duration_, direction); const direction = this.lastScaleDelta_ - 1;
rotate(view, view.getRotation());
zoom(view, view.getResolution(), undefined, this.duration_, direction);
this.lastScaleDelta_ = 0; this.lastScaleDelta_ = 0;
return false; return false;
} }
@@ -124,7 +130,7 @@ class DragRotateAndZoom extends PointerInteraction {
} }
if (this.condition_(mapBrowserEvent)) { if (this.condition_(mapBrowserEvent)) {
mapBrowserEvent.map.getView().beginInteraction(); mapBrowserEvent.map.getView().setHint(ViewHint.INTERACTING, 1);
this.lastAngle_ = undefined; this.lastAngle_ = undefined;
this.lastMagnitude_ = undefined; this.lastMagnitude_ = undefined;
return true; return true;

View File

@@ -80,8 +80,11 @@ function onBoxEnd() {
extent = mapExtent; extent = mapExtent;
} }
const resolution = view.getConstrainedResolution(view.getResolutionForExtent(extent, size)); const resolution = view.constrainResolution(
const center = view.getConstrainedCenter(getCenter(extent), resolution); view.getResolutionForExtent(extent, size));
let center = getCenter(extent);
center = view.constrainCenter(center);
view.animate({ view.animate({
resolution: resolution, resolution: resolution,

View File

@@ -4,6 +4,7 @@
import BaseObject from '../Object.js'; import BaseObject from '../Object.js';
import {easeOut, linear} from '../easing.js'; import {easeOut, linear} from '../easing.js';
import InteractionProperty from './Property.js'; import InteractionProperty from './Property.js';
import {clamp} from '../math.js';
/** /**
@@ -110,15 +111,77 @@ class Interaction extends BaseObject {
export function pan(view, delta, opt_duration) { export function pan(view, delta, opt_duration) {
const currentCenter = view.getCenter(); const currentCenter = view.getCenter();
if (currentCenter) { if (currentCenter) {
const center = [currentCenter[0] + delta[0], currentCenter[1] + delta[1]]; const center = view.constrainCenter(
[currentCenter[0] + delta[0], currentCenter[1] + delta[1]]);
if (opt_duration) {
view.animate({ view.animate({
duration: opt_duration !== undefined ? opt_duration : 250, duration: opt_duration,
easing: linear, easing: linear,
center: view.getConstrainedCenter(center) center: center
}); });
} else {
view.setCenter(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 {import("../View.js").default} view View.
* @param {number} delta Delta from previous zoom level. * @param {number} delta Delta from previous zoom level.
@@ -126,24 +189,63 @@ export function pan(view, delta, opt_duration) {
* @param {number=} opt_duration Duration. * @param {number=} opt_duration Duration.
*/ */
export function zoomByDelta(view, delta, opt_anchor, opt_duration) { export function zoomByDelta(view, delta, opt_anchor, opt_duration) {
const currentZoom = view.getZoom(); const currentResolution = view.getResolution();
let resolution = view.constrainResolution(currentResolution, delta, 0);
if (currentZoom === undefined) { if (resolution !== undefined) {
return; const resolutions = view.getResolutions();
resolution = clamp(
resolution,
view.getMinResolution() || resolutions[resolutions.length - 1],
view.getMaxResolution() || resolutions[0]);
} }
const newZoom = view.getConstrainedZoom(currentZoom + delta); // If we have a constraint on center, we need to change the anchor so that the
const newResolution = view.getResolutionForZoom(newZoom); // 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);
if (view.getAnimating()) { opt_anchor = [
view.cancelAnimations(); (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({ view.animate({
resolution: newResolution, resolution: resolution,
anchor: opt_anchor, anchor: opt_anchor,
duration: opt_duration !== undefined ? opt_duration : 250, duration: opt_duration,
easing: easeOut easing: easeOut
}); });
} else {
if (opt_anchor) {
const center = view.calculateCenterZoom(resolution, opt_anchor);
view.setCenter(center);
}
view.setResolution(resolution);
}
}
} }
export default Interaction; export default Interaction;

View File

@@ -1,7 +1,9 @@
/** /**
* @module ol/interaction/MouseWheelZoom * @module ol/interaction/MouseWheelZoom
*/ */
import ViewHint from '../ViewHint.js';
import {always} from '../events/condition.js'; import {always} from '../events/condition.js';
import {easeOut} from '../easing.js';
import EventType from '../events/EventType.js'; import EventType from '../events/EventType.js';
import {DEVICE_PIXEL_RATIO, FIREFOX, SAFARI} from '../has.js'; import {DEVICE_PIXEL_RATIO, FIREFOX, SAFARI} from '../has.js';
import Interaction, {zoomByDelta} from './Interaction.js'; import Interaction, {zoomByDelta} from './Interaction.js';
@@ -32,6 +34,9 @@ export const Mode = {
* {@link module:ol/events/condition~always}. * {@link module:ol/events/condition~always}.
* @property {number} [duration=250] Animation duration in milliseconds. * @property {number} [duration=250] Animation duration in milliseconds.
* @property {number} [timeout=80] Mouse wheel timeout 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 * @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 * 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. * the center of the screen instead of zooming on the mouse's location.
@@ -57,13 +62,7 @@ class MouseWheelZoom extends Interaction {
* @private * @private
* @type {number} * @type {number}
*/ */
this.totalDelta_ = 0; this.delta_ = 0;
/**
* @private
* @type {number}
*/
this.lastDelta_ = 0;
/** /**
* @private * @private
@@ -83,6 +82,12 @@ class MouseWheelZoom extends Interaction {
*/ */
this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true; this.useAnchor_ = options.useAnchor !== undefined ? options.useAnchor : true;
/**
* @private
* @type {boolean}
*/
this.constrainResolution_ = options.constrainResolution || false;
/** /**
* @private * @private
* @type {import("../events/condition.js").Condition} * @type {import("../events/condition.js").Condition}
@@ -132,15 +137,22 @@ class MouseWheelZoom extends Interaction {
*/ */
this.trackpadDeltaPerZoom_ = 300; this.trackpadDeltaPerZoom_ = 300;
/**
* The zoom factor by which scroll zooming is allowed to exceed the limits.
* @private
* @type {number}
*/
this.trackpadZoomBuffer_ = 1.5;
} }
/** /**
* @private * @private
*/ */
endInteraction_() { decrementInteractingHint_() {
this.trackpadTimeoutId_ = undefined; this.trackpadTimeoutId_ = undefined;
const view = this.getMap().getView(); const view = this.getMap().getView();
view.endInteraction(undefined, Math.sign(this.lastDelta_), this.lastAnchor_); view.setHint(ViewHint.INTERACTING, -1);
} }
/** /**
@@ -187,8 +199,6 @@ class MouseWheelZoom extends Interaction {
if (delta === 0) { if (delta === 0) {
return false; return false;
} else {
this.lastDelta_ = delta;
} }
const now = Date.now(); const now = Date.now();
@@ -208,15 +218,55 @@ class MouseWheelZoom extends Interaction {
if (this.trackpadTimeoutId_) { if (this.trackpadTimeoutId_) {
clearTimeout(this.trackpadTimeoutId_); clearTimeout(this.trackpadTimeoutId_);
} else { } else {
view.beginInteraction(); 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
});
} }
this.trackpadTimeoutId_ = setTimeout(this.endInteraction_.bind(this), this.trackpadEventGap_);
view.adjustZoom(-delta / this.trackpadDeltaPerZoom_, this.lastAnchor_);
this.startTime_ = now; this.startTime_ = now;
return false; return false;
} }
this.totalDelta_ += delta; this.delta_ += delta;
const timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0); const timeLeft = Math.max(this.timeout_ - (now - this.startTime_), 0);
@@ -236,10 +286,10 @@ class MouseWheelZoom extends Interaction {
view.cancelAnimations(); view.cancelAnimations();
} }
const maxDelta = MAX_DELTA; const maxDelta = MAX_DELTA;
const delta = clamp(this.totalDelta_, -maxDelta, maxDelta); const delta = clamp(this.delta_, -maxDelta, maxDelta);
zoomByDelta(view, -delta, this.lastAnchor_, this.duration_); zoomByDelta(view, -delta, this.lastAnchor_, this.duration_);
this.mode_ = undefined; this.mode_ = undefined;
this.totalDelta_ = 0; this.delta_ = 0;
this.lastAnchor_ = null; this.lastAnchor_ = null;
this.startTime_ = undefined; this.startTime_ = undefined;
this.timeoutId_ = undefined; this.timeoutId_ = undefined;

View File

@@ -1,7 +1,9 @@
/** /**
* @module ol/interaction/PinchRotate * @module ol/interaction/PinchRotate
*/ */
import ViewHint from '../ViewHint.js';
import {FALSE} from '../functions.js'; import {FALSE} from '../functions.js';
import {rotate, rotateWithoutConstraints} from './Interaction.js';
import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js'; import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js';
import {disable} from '../rotationconstraint.js'; import {disable} from '../rotationconstraint.js';
@@ -116,8 +118,9 @@ class PinchRotate extends PointerInteraction {
// rotate // rotate
if (this.rotating_) { if (this.rotating_) {
const rotation = view.getRotation();
map.render(); map.render();
view.adjustRotation(rotationDelta, this.anchor_); rotateWithoutConstraints(view, rotation + rotationDelta, this.anchor_);
} }
} }
@@ -128,7 +131,11 @@ class PinchRotate extends PointerInteraction {
if (this.targetPointers.length < 2) { if (this.targetPointers.length < 2) {
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
const view = map.getView(); const view = map.getView();
view.endInteraction(this.duration_); view.setHint(ViewHint.INTERACTING, -1);
if (this.rotating_) {
const rotation = view.getRotation();
rotate(view, rotation, this.anchor_, this.duration_);
}
return false; return false;
} else { } else {
return true; return true;
@@ -146,7 +153,7 @@ class PinchRotate extends PointerInteraction {
this.rotating_ = false; this.rotating_ = false;
this.rotationDelta_ = 0.0; this.rotationDelta_ = 0.0;
if (!this.handlingDownUpSequence) { if (!this.handlingDownUpSequence) {
map.getView().beginInteraction(); map.getView().setHint(ViewHint.INTERACTING, 1);
} }
return true; return true;
} else { } else {

View File

@@ -1,13 +1,17 @@
/** /**
* @module ol/interaction/PinchZoom * @module ol/interaction/PinchZoom
*/ */
import ViewHint from '../ViewHint.js';
import {FALSE} from '../functions.js'; import {FALSE} from '../functions.js';
import {zoom, zoomWithoutConstraints} from './Interaction.js';
import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js'; import PointerInteraction, {centroid as centroidFromPointers} from './Pointer.js';
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {number} [duration=400] Animation duration in milliseconds. * @property {number} [duration=400] Animation duration in milliseconds.
* @property {boolean} [constrainResolution=false] Zoom to the closest integer
* zoom level after the pinch gesture ends.
*/ */
@@ -33,6 +37,12 @@ class PinchZoom extends PointerInteraction {
super(pointerOptions); super(pointerOptions);
/**
* @private
* @type {boolean}
*/
this.constrainResolution_ = options.constrainResolution || false;
/** /**
* @private * @private
* @type {import("../coordinate.js").Coordinate} * @type {import("../coordinate.js").Coordinate}
@@ -81,6 +91,17 @@ class PinchZoom extends PointerInteraction {
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
const view = map.getView(); 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) { if (scaleDelta != 1.0) {
this.lastScaleDelta_ = scaleDelta; this.lastScaleDelta_ = scaleDelta;
@@ -95,7 +116,7 @@ class PinchZoom extends PointerInteraction {
// scale, bypass the resolution constraint // scale, bypass the resolution constraint
map.render(); map.render();
view.adjustResolution(scaleDelta, this.anchor_); zoomWithoutConstraints(view, newResolution, this.anchor_);
} }
/** /**
@@ -105,8 +126,17 @@ class PinchZoom extends PointerInteraction {
if (this.targetPointers.length < 2) { if (this.targetPointers.length < 2) {
const map = mapBrowserEvent.map; const map = mapBrowserEvent.map;
const view = map.getView(); const view = map.getView();
const direction = this.lastScaleDelta_ > 1 ? 1 : -1; view.setHint(ViewHint.INTERACTING, -1);
view.endInteraction(this.duration_, direction); 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);
}
return false; return false;
} else { } else {
return true; return true;
@@ -123,7 +153,7 @@ class PinchZoom extends PointerInteraction {
this.lastDistance_ = undefined; this.lastDistance_ = undefined;
this.lastScaleDelta_ = 1; this.lastScaleDelta_ = 1;
if (!this.handlingDownUpSequence) { if (!this.handlingDownUpSequence) {
map.getView().beginInteraction(); map.getView().setHint(ViewHint.INTERACTING, 1);
} }
return true; return true;
} else { } else {

View File

@@ -288,22 +288,22 @@ function getMeasureContext() {
* @return {import("../size.js").Size} Measurement. * @return {import("../size.js").Size} Measurement.
*/ */
export const measureTextHeight = (function() { export const measureTextHeight = (function() {
let div; let span;
const heights = textHeights; const heights = textHeights;
return function(font) { return function(font) {
let height = heights[font]; let height = heights[font];
if (height == undefined) { if (height == undefined) {
if (!div) { if (!span) {
div = document.createElement('div'); span = document.createElement('span');
div.innerHTML = 'M'; span.textContent = 'M';
div.style.margin = div.style.padding = '0 !important'; span.style.margin = span.style.padding = '0 !important';
div.style.position = 'absolute !important'; span.style.position = 'absolute !important';
div.style.left = '-99999px !important'; span.style.left = '-99999px !important';
} }
div.style.font = font; span.style.font = font;
document.body.appendChild(div); document.body.appendChild(span);
height = heights[font] = div.offsetHeight; height = heights[font] = span.offsetHeight;
document.body.removeChild(div); document.body.removeChild(span);
} }
return height; return height;
}; };

View File

@@ -143,8 +143,6 @@ class ExecutorGroup extends Disposable {
executors[key].disposeInternal(); executors[key].disposeInternal();
} }
} }
const canvas = this.hitDetectionContext_.canvas;
canvas.width = canvas.height = 0;
super.disposeInternal(); super.disposeInternal();
} }

View File

@@ -63,14 +63,6 @@ class CanvasLayerRenderer extends LayerRenderer {
canvas.className = this.getLayer().getClassName(); canvas.className = this.getLayer().getClassName();
} }
/**
* @inheritDoc
*/
disposeInternal() {
this.context.canvas.width = this.context.canvas.height = 0;
super.disposeInternal();
}
/** /**
* @param {CanvasRenderingContext2D} context Context. * @param {CanvasRenderingContext2D} context Context.
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.

View File

@@ -10,6 +10,8 @@ import EventType from '../../events/EventType.js';
import rbush from 'rbush'; import rbush from 'rbush';
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js'; import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js';
import VectorTileRenderType from '../../layer/VectorTileRenderType.js'; import VectorTileRenderType from '../../layer/VectorTileRenderType.js';
import {equivalent as equivalentProjection} from '../../proj.js';
import Units from '../../proj/Units.js';
import ReplayType from '../../render/canvas/BuilderType.js'; import ReplayType from '../../render/canvas/BuilderType.js';
import {labelCache} from '../../render/canvas.js'; import {labelCache} from '../../render/canvas.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js'; import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
@@ -150,7 +152,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
*/ */
disposeInternal() { disposeInternal() {
unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this); unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
this.overlayContext_.canvas.width = this.overlayContext_.canvas.height = 0;
super.disposeInternal(); super.disposeInternal();
} }
@@ -262,6 +263,12 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
const sharedExtent = getIntersection(tileExtent, sourceTileExtent); const sharedExtent = getIntersection(tileExtent, sourceTileExtent);
const bufferedExtent = equals(sourceTileExtent, sharedExtent) ? null : const bufferedExtent = equals(sourceTileExtent, sharedExtent) ? null :
buffer(sharedExtent, layer.getRenderBuffer() * resolution, this.tmpExtent); buffer(sharedExtent, layer.getRenderBuffer() * resolution, this.tmpExtent);
const tileProjection = sourceTile.getProjection();
let reproject = false;
if (!equivalentProjection(projection, tileProjection)) {
reproject = true;
sourceTile.setProjection(projection);
}
builderState.dirty = false; builderState.dirty = false;
const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution, const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution,
pixelRatio, !!this.declutterTree_); pixelRatio, !!this.declutterTree_);
@@ -290,6 +297,15 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
} }
for (let i = 0, ii = features.length; i < ii; ++i) { for (let i = 0, ii = features.length; i < ii; ++i) {
const feature = features[i]; const feature = features[i];
if (reproject) {
if (tileProjection.getUnits() == Units.TILE_PIXELS) {
// projected tile extent
tileProjection.setWorldExtent(sourceTileExtent);
// tile extent in tile pixel space
tileProjection.setExtent(sourceTile.getExtent());
}
feature.getGeometry().transform(tileProjection, projection);
}
if (!bufferedExtent || intersects(bufferedExtent, feature.getGeometry().getExtent())) { if (!bufferedExtent || intersects(bufferedExtent, feature.getGeometry().getExtent())) {
render.call(this, feature); render.call(this, feature);
} }

View File

@@ -2,87 +2,37 @@
* @module ol/resolutionconstraint * @module ol/resolutionconstraint
*/ */
import {linearFindNearest} from './array.js'; import {linearFindNearest} from './array.js';
import {getHeight, getWidth} from './extent'; import {clamp} from './math.js';
import {clamp} from './math';
/** /**
* @typedef {function((number|undefined), number, import("./size.js").Size, boolean=): (number|undefined)} Type * @typedef {function((number|undefined), number, number): (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 {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. * @return {Type} Zoom function.
*/ */
export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent) { export function createSnapToResolutions(resolutions) {
return ( return (
/** /**
* @param {number|undefined} resolution Resolution. * @param {number|undefined} resolution Resolution.
* @param {number} delta Delta.
* @param {number} direction Direction. * @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. * @return {number|undefined} Resolution.
*/ */
function(resolution, direction, size, opt_isMoving) { function(resolution, delta, direction) {
if (resolution !== undefined) { if (resolution !== undefined) {
const maxResolution = resolutions[0]; let z = linearFindNearest(resolutions, resolution, direction);
const minResolution = resolutions[resolutions.length - 1]; z = clamp(z + delta, 0, resolutions.length - 1);
const cappedMaxRes = opt_maxExtent ? const index = Math.floor(z);
getViewportClampedResolution(maxResolution, opt_maxExtent, size) : if (z != index && index < resolutions.length - 1) {
maxResolution; const power = resolutions[index] / resolutions[index + 1];
return resolutions[index] / Math.pow(power, z - index);
// during interacting or animating, allow intermediary values } else {
if (opt_isMoving) { return resolutions[index];
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 { } else {
return undefined; return undefined;
} }
@@ -94,78 +44,29 @@ export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent)
/** /**
* @param {number} power Power. * @param {number} power Power.
* @param {number} maxResolution Maximum resolution. * @param {number} maxResolution Maximum resolution.
* @param {number=} opt_minResolution Minimum resolution. * @param {number=} opt_maxLevel Maximum level.
* @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. * @return {Type} Zoom function.
*/ */
export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent) { export function createSnapToPower(power, maxResolution, opt_maxLevel) {
return ( return (
/** /**
* @param {number|undefined} resolution Resolution. * @param {number|undefined} resolution Resolution.
* @param {number} delta Delta.
* @param {number} direction Direction. * @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. * @return {number|undefined} Resolution.
*/ */
function(resolution, direction, size, opt_isMoving) { function(resolution, delta, direction) {
if (resolution !== undefined) { if (resolution !== undefined) {
const cappedMaxRes = opt_maxExtent ? const offset = -direction / 2 + 0.5;
getViewportClampedResolution(maxResolution, opt_maxExtent, size) : const oldLevel = Math.floor(
maxResolution; Math.log(maxResolution / resolution) / Math.log(power) + offset);
const minResolution = opt_minResolution !== undefined ? opt_minResolution : 0; let newLevel = Math.max(oldLevel + delta, 0);
if (opt_maxLevel !== undefined) {
// during interacting or animating, allow intermediary values newLevel = Math.min(newLevel, opt_maxLevel);
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 { } else {
return undefined; 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;
}
}
);
}

View File

@@ -5,15 +5,16 @@ import {toRadians} from './math.js';
/** /**
* @typedef {function((number|undefined), boolean=): (number|undefined)} Type * @typedef {function((number|undefined), number): (number|undefined)} Type
*/ */
/** /**
* @param {number|undefined} rotation Rotation. * @param {number|undefined} rotation Rotation.
* @param {number} delta Delta.
* @return {number|undefined} Rotation. * @return {number|undefined} Rotation.
*/ */
export function disable(rotation) { export function disable(rotation, delta) {
if (rotation !== undefined) { if (rotation !== undefined) {
return 0; return 0;
} else { } else {
@@ -24,11 +25,12 @@ export function disable(rotation) {
/** /**
* @param {number|undefined} rotation Rotation. * @param {number|undefined} rotation Rotation.
* @param {number} delta Delta.
* @return {number|undefined} Rotation. * @return {number|undefined} Rotation.
*/ */
export function none(rotation) { export function none(rotation, delta) {
if (rotation !== undefined) { if (rotation !== undefined) {
return rotation; return rotation + delta;
} else { } else {
return undefined; return undefined;
} }
@@ -44,16 +46,12 @@ export function createSnapToN(n) {
return ( return (
/** /**
* @param {number|undefined} rotation Rotation. * @param {number|undefined} rotation Rotation.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress. * @param {number} delta Delta.
* @return {number|undefined} Rotation. * @return {number|undefined} Rotation.
*/ */
function(rotation, opt_isMoving) { function(rotation, delta) {
if (opt_isMoving) {
return rotation;
}
if (rotation !== undefined) { if (rotation !== undefined) {
rotation = Math.floor(rotation / theta + 0.5) * theta; rotation = Math.floor((rotation + delta) / theta + 0.5) * theta;
return rotation; return rotation;
} else { } else {
return undefined; return undefined;
@@ -71,19 +69,15 @@ export function createSnapToZero(opt_tolerance) {
return ( return (
/** /**
* @param {number|undefined} rotation Rotation. * @param {number|undefined} rotation Rotation.
* @param {boolean} opt_isMoving True if an interaction or animation is in progress. * @param {number} delta Delta.
* @return {number|undefined} Rotation. * @return {number|undefined} Rotation.
*/ */
function(rotation, opt_isMoving) { function(rotation, delta) {
if (opt_isMoving) {
return rotation;
}
if (rotation !== undefined) { if (rotation !== undefined) {
if (Math.abs(rotation) <= tolerance) { if (Math.abs(rotation + delta) <= tolerance) {
return 0; return 0;
} else { } else {
return rotation; return rotation + delta;
} }
} else { } else {
return undefined; return undefined;

View File

@@ -74,7 +74,7 @@ const LayerConfig = {
*/ */
const ProviderConfig = { const ProviderConfig = {
'terrain': { 'terrain': {
minZoom: 0, minZoom: 4,
maxZoom: 18 maxZoom: 18
}, },
'toner': { 'toner': {
@@ -82,8 +82,8 @@ const ProviderConfig = {
maxZoom: 20 maxZoom: 20
}, },
'watercolor': { 'watercolor': {
minZoom: 0, minZoom: 1,
maxZoom: 18 maxZoom: 16
} }
}; };
@@ -103,8 +103,6 @@ const ProviderConfig = {
* imageTile.getImage().src = src; * imageTile.getImage().src = src;
* }; * };
* ``` * ```
* @property {number} [transition] Duration of the opacity transition for rendering.
* To disable the opacity transition, pass `transition: 0`.
* @property {string} [url] URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders. * @property {string} [url] URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders.
* @property {boolean} [wrapX=true] Whether to wrap the world horizontally. * @property {boolean} [wrapX=true] Whether to wrap the world horizontally.
*/ */
@@ -139,7 +137,6 @@ class Stamen extends XYZ {
opaque: layerConfig.opaque, opaque: layerConfig.opaque,
reprojectionErrorThreshold: options.reprojectionErrorThreshold, reprojectionErrorThreshold: options.reprojectionErrorThreshold,
tileLoadFunction: options.tileLoadFunction, tileLoadFunction: options.tileLoadFunction,
transition: options.transition,
url: url, url: url,
wrapX: options.wrapX wrapX: options.wrapX
}); });

View File

@@ -26,7 +26,7 @@ import {equals} from '../array.js';
* to `false` (e.g. for sources with polygons that represent administrative * to `false` (e.g. for sources with polygons that represent administrative
* boundaries or TopoJSON sources) allows the renderer to optimise fill and * boundaries or TopoJSON sources) allows the renderer to optimise fill and
* stroke operations. * stroke operations.
* @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Projection of the tile grid. * @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection.
* @property {import("./State.js").default} [state] Source state. * @property {import("./State.js").default} [state] Source state.
* @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles. * @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles.
* Default is {@link module:ol/VectorTile}. * Default is {@link module:ol/VectorTile}.
@@ -35,20 +35,18 @@ import {equals} from '../array.js';
* @property {number|import("../size.js").Size} [tileSize=512] Optional tile size. * @property {number|import("../size.js").Size} [tileSize=512] Optional tile size.
* @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid. * @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
* @property {import("../Tile.js").LoadFunction} [tileLoadFunction] * @property {import("../Tile.js").LoadFunction} [tileLoadFunction]
* Optional function to load a tile given a URL. Could look like this for pbf tiles: * Optional function to load a tile given a URL. Could look like this:
* ```js * ```js
* function(tile, url) { * function(tile, url) {
* tile.setLoader(function(extent, resolution, projection) { * tile.setLoader(function() {
* fetch(url).then(function(response) { * var data = // ... fetch data
* response.arrayBuffer().then(function(data) { * var format = tile.getFormat();
* const format = tile.getFormat() // ol/format/MVT configured as source format * tile.setProjection(format.readProjection(data));
* const features = format.readFeatures(data, { * tile.setExtent(format.getLastExtent()); // line only required for ol/format/MVT
* extent: extent, * tile.setFeatures(format.readFeatures(data, {
* featureProjection: projection * // featureProjection is not required for ol/format/MVT
* }); * featureProjection: map.getView().getProjection()
* tile.setFeatures(features); * }));
* });
* });
* } * }
* }); * });
* ``` * ```
@@ -189,9 +187,9 @@ class VectorTile extends UrlTile {
const sourceTileGrid = this.tileGrid; const sourceTileGrid = this.tileGrid;
const sourceExtent = sourceTileGrid.getExtent(); const sourceExtent = sourceTileGrid.getExtent();
if (sourceExtent) { if (sourceExtent) {
getIntersection(extent, sourceExtent, extent); getIntersection(extent, sourceTileGrid.getExtent(), extent);
} }
const sourceZ = sourceTileGrid.getZForResolution(resolution/*, 1*/); const sourceZ = sourceTileGrid.getZForResolution(resolution);
const minZoom = sourceTileGrid.getMinZoom(); const minZoom = sourceTileGrid.getMinZoom();
let loadedZ = sourceZ + 1; let loadedZ = sourceZ + 1;
@@ -217,9 +215,6 @@ class VectorTile extends UrlTile {
tileUrl == undefined ? TileState.EMPTY : TileState.IDLE, tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
tileUrl == undefined ? '' : tileUrl, tileUrl == undefined ? '' : tileUrl,
this.format_, this.tileLoadFunction); this.format_, this.tileLoadFunction);
sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
sourceTile.projection = projection;
sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
this.sourceTiles_[tileKey] = sourceTile; this.sourceTiles_[tileKey] = sourceTile;
empty = empty && sourceTile.getState() === TileState.EMPTY; empty = empty && sourceTile.getState() === TileState.EMPTY;
listen(sourceTile, EventType.CHANGE, this.handleTileChange, this); listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);

View File

@@ -1,4 +1,5 @@
import Feature from '../../../../src/ol/Feature.js'; import Feature from '../../../../src/ol/Feature.js';
import {getWidth} from '../../../../src/ol/extent.js';
import MVT from '../../../../src/ol/format/MVT.js'; import MVT from '../../../../src/ol/format/MVT.js';
import Point from '../../../../src/ol/geom/Point.js'; import Point from '../../../../src/ol/geom/Point.js';
import Polygon from '../../../../src/ol/geom/Polygon.js'; import Polygon from '../../../../src/ol/geom/Polygon.js';
@@ -21,20 +22,15 @@ where('ArrayBuffer.isView').describe('ol.format.MVT', function() {
describe('#readFeatures', function() { describe('#readFeatures', function() {
const options = {
featureProjection: 'EPSG:3857',
extent: [1824704.739223726, 6141868.096770482, 1827150.7241288517, 6144314.081675608]
};
it('uses ol.render.Feature as feature class by default', function() { it('uses ol.render.Feature as feature class by default', function() {
const format = new MVT({layers: ['water']}); const format = new MVT({layers: ['water']});
const features = format.readFeatures(data, options); const features = format.readFeatures(data);
expect(features[0]).to.be.a(RenderFeature); expect(features[0]).to.be.a(RenderFeature);
}); });
it('parses only specified layers', function() { it('parses only specified layers', function() {
const format = new MVT({layers: ['water']}); const format = new MVT({layers: ['water']});
const features = format.readFeatures(data, options); const features = format.readFeatures(data);
expect(features.length).to.be(10); expect(features.length).to.be(10);
}); });
@@ -68,27 +64,29 @@ where('ArrayBuffer.isView').describe('ol.format.MVT', function() {
featureClass: Feature, featureClass: Feature,
layers: ['building'] layers: ['building']
}); });
let features = format.readFeatures(data, options); let features = format.readFeatures(data);
expect(features[0].getId()).to.be(2); expect(features[0].getId()).to.be(2);
// ol.render.Feature // ol.render.Feature
format = new MVT({ format = new MVT({
layers: ['building'] layers: ['building']
}); });
features = format.readFeatures(data, options); features = format.readFeatures(data);
expect(features[0].getId()).to.be(2); expect(features[0].getId()).to.be(2);
}); });
it('sets the extent of the last readFeatures call', function() {
const format = new MVT();
format.readFeatures(data);
const extent = format.getLastExtent();
expect(getWidth(extent)).to.be(4096);
});
}); });
}); });
describe('ol.format.MVT', function() { describe('ol.format.MVT', function() {
const options = {
featureProjection: 'EPSG:3857',
extent: [1824704.739223726, 6141868.096770482, 1827150.7241288517, 6144314.081675608]
};
describe('#createFeature_', function() { describe('#createFeature_', function() {
it('accepts a geometryName', function() { it('accepts a geometryName', function() {
const format = new MVT({ const format = new MVT({
@@ -178,9 +176,7 @@ describe('ol.format.MVT', function() {
ends.push(10, 20); ends.push(10, 20);
createdEnds = ends; createdEnds = ends;
}; };
format.dataProjection.setExtent([0, 0, 4096, 4096]); const feature = format.createFeature_({}, rawFeature);
format.dataProjection.setWorldExtent(options.extent);
const feature = format.createFeature_({}, rawFeature, format.adaptOptions(options));
expect(feature).to.be.a(RenderFeature); expect(feature).to.be.a(RenderFeature);
expect(feature.getType()).to.be('Polygon'); expect(feature.getType()).to.be('Polygon');
expect(feature.getFlatCoordinates()).to.equal(createdFlatCoordinates); expect(feature.getFlatCoordinates()).to.equal(createdFlatCoordinates);

View File

@@ -28,10 +28,10 @@ describe('ol.geom.flat.interpolate', function() {
expect(point).to.eql([2, 3]); expect(point).to.eql([2, 3]);
}); });
it('also when vertices are repeated', function() { xit('also when vertices are repeated', function() {
const flatCoordinates = [0, 1, 2, 3, 2, 3, 4, 5]; const flatCoordinates = [0, 1, 2, 3, 2, 3, 4, 5];
const point = interpolatePoint( const point = interpolatePoint(
flatCoordinates, 0, 8, 2, 0.5); flatCoordinates, 0, 6, 2, 0.5);
expect(point).to.eql([2, 3]); expect(point).to.eql([2, 3]);
}); });
@@ -44,10 +44,10 @@ describe('ol.geom.flat.interpolate', function() {
expect(point).to.eql([3, 4]); expect(point).to.eql([3, 4]);
}); });
it('also when vertices are repeated', function() { xit('also when vertices are repeated', function() {
const flatCoordinates = [0, 1, 2, 3, 2, 3, 4, 5, 6, 7]; const flatCoordinates = [0, 1, 2, 3, 2, 3, 4, 5, 6, 7];
const point = interpolatePoint( const point = interpolatePoint(
flatCoordinates, 0, 10, 2, 0.5); flatCoordinates, 0, 8, 2, 0.5);
expect(point).to.eql([3, 4]); expect(point).to.eql([3, 4]);
}); });
@@ -59,11 +59,11 @@ describe('ol.geom.flat.interpolate', function() {
expect(point).to.eql([3, 4]); expect(point).to.eql([3, 4]);
}); });
it('also when vertices are repeated', xit('also when vertices are repeated',
function() { function() {
const flatCoordinates = [0, 1, 2, 3, 2, 3, 6, 7]; const flatCoordinates = [0, 1, 2, 3, 2, 3, 6, 7];
const point = interpolatePoint( const point = interpolatePoint(
flatCoordinates, 0, 8, 2, 0.5); flatCoordinates, 0, 6, 2, 0.5);
expect(point).to.eql([3, 4]); expect(point).to.eql([3, 4]);
}); });

View File

@@ -62,18 +62,13 @@ describe('ol.interaction.DragRotateAndZoom', function() {
true); true);
interaction.lastAngle_ = Math.PI; interaction.lastAngle_ = Math.PI;
let callCount = 0;
let view = map.getView(); let view = map.getView();
view.on('change:rotation', function() { let spy = sinon.spy(view, 'rotate');
callCount++;
});
interaction.handleDragEvent(event); interaction.handleDragEvent(event);
expect(callCount).to.be(1); expect(spy.callCount).to.be(1);
expect(interaction.lastAngle_).to.be(-0.8308214428190254); expect(interaction.lastAngle_).to.be(-0.8308214428190254);
view.rotate.restore();
callCount = 0;
view = new View({ view = new View({
projection: 'EPSG:4326', projection: 'EPSG:4326',
center: [0, 0], center: [0, 0],
@@ -81,16 +76,15 @@ describe('ol.interaction.DragRotateAndZoom', function() {
enableRotation: false enableRotation: false
}); });
map.setView(view); map.setView(view);
view.on('change:rotation', function() {
callCount++;
});
event = new MapBrowserPointerEvent('pointermove', map, event = new MapBrowserPointerEvent('pointermove', map,
new PointerEvent('pointermove', {clientX: 24, clientY: 16}, {pointerType: 'mouse'}), new PointerEvent('pointermove', {clientX: 24, clientY: 16}, {pointerType: 'mouse'}),
true); true);
spy = sinon.spy(view, 'rotate');
interaction.handleDragEvent(event); interaction.handleDragEvent(event);
expect(callCount).to.be(0); expect(spy.callCount).to.be(0);
view.rotate.restore();
}); });
}); });

View File

@@ -103,7 +103,7 @@ describe('ol.interaction.DragZoom', function() {
setTimeout(function() { setTimeout(function() {
const view = map.getView(); const view = map.getView();
const resolution = view.getResolution(); const resolution = view.getResolution();
expect(resolution).to.eql(view.getConstrainedResolution(0.5)); expect(resolution).to.eql(view.constrainResolution(0.5));
done(); done();
}, 50); }, 50);
}, 50); }, 50);

View File

@@ -1,6 +1,7 @@
import Map from '../../../../src/ol/Map.js'; import Map from '../../../../src/ol/Map.js';
import View from '../../../../src/ol/View.js';
import EventTarget from '../../../../src/ol/events/Target.js'; import EventTarget from '../../../../src/ol/events/Target.js';
import Interaction from '../../../../src/ol/interaction/Interaction.js'; import Interaction, {zoomByDelta} from '../../../../src/ol/interaction/Interaction.js';
import {FALSE} from '../../../../src/ol/functions.js'; import {FALSE} from '../../../../src/ol/functions.js';
describe('ol.interaction.Interaction', function() { describe('ol.interaction.Interaction', function() {
@@ -86,4 +87,67 @@ describe('ol.interaction.Interaction', function() {
}); });
describe('zoomByDelta()', function() {
it('changes view resolution', function() {
const view = new View({
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25]
});
zoomByDelta(view, 1);
expect(view.getResolution()).to.be(0.5);
zoomByDelta(view, -1);
expect(view.getResolution()).to.be(1);
zoomByDelta(view, 2);
expect(view.getResolution()).to.be(0.25);
zoomByDelta(view, -2);
expect(view.getResolution()).to.be(1);
});
it('changes view resolution and center relative to the anchor', function() {
const view = new View({
center: [0, 0],
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25]
});
zoomByDelta(view, 1, [10, 10]);
expect(view.getCenter()).to.eql([5, 5]);
zoomByDelta(view, -1, [0, 0]);
expect(view.getCenter()).to.eql([10, 10]);
zoomByDelta(view, 2, [0, 0]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
zoomByDelta(view, -2, [0, 0]);
expect(view.getCenter()).to.eql([10, 10]);
});
it('changes view resolution and center relative to the anchor, while respecting the extent', function() {
const view = new View({
center: [0, 0],
extent: [-2.5, -2.5, 2.5, 2.5],
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25]
});
zoomByDelta(view, 1, [10, 10]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
zoomByDelta(view, -1, [0, 0]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
zoomByDelta(view, 2, [10, 10]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
zoomByDelta(view, -2, [0, 0]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
});
});
}); });

View File

@@ -565,6 +565,7 @@ describe('ol.Map', function() {
const interactions = defaultInteractions(options); const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1); expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(MouseWheelZoom); expect(interactions.item(0)).to.be.a(MouseWheelZoom);
expect(interactions.item(0).constrainResolution_).to.eql(false);
expect(interactions.item(0).useAnchor_).to.eql(true); expect(interactions.item(0).useAnchor_).to.eql(true);
interactions.item(0).setMouseAnchor(false); interactions.item(0).setMouseAnchor(false);
expect(interactions.item(0).useAnchor_).to.eql(false); expect(interactions.item(0).useAnchor_).to.eql(false);
@@ -600,6 +601,21 @@ describe('ol.Map', function() {
const interactions = defaultInteractions(options); const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1); expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(PinchZoom); expect(interactions.item(0)).to.be.a(PinchZoom);
expect(interactions.item(0).constrainResolution_).to.eql(false);
});
});
describe('set constrainResolution option', function() {
it('set constrainResolution option', function() {
options.pinchZoom = true;
options.mouseWheelZoom = true;
options.constrainResolution = true;
const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(2);
expect(interactions.item(0)).to.be.a(PinchZoom);
expect(interactions.item(0).constrainResolution_).to.eql(true);
expect(interactions.item(1)).to.be.a(MouseWheelZoom);
expect(interactions.item(1).constrainResolution_).to.eql(true);
}); });
}); });

View File

@@ -76,14 +76,6 @@ describe('ol.render.canvas', function() {
}); });
describe('measureTextHeight', function() {
it('respects line-height', function() {
const height = render.measureTextHeight('12px/1.2 sans-serif');
expect(render.measureTextHeight('12px/2.4 sans-serif')).to.be.greaterThan(height);
expect(render.measureTextHeight('12px/0.1 sans-serif')).to.be.lessThan(height);
});
});
describe('rotateAtOffset', function() { describe('rotateAtOffset', function() {
it('rotates a canvas at an offset point', function() { it('rotates a canvas at an offset point', function() {

View File

@@ -9,7 +9,8 @@ import {getCenter} from '../../../../../src/ol/extent.js';
import MVT from '../../../../../src/ol/format/MVT.js'; import MVT from '../../../../../src/ol/format/MVT.js';
import Point from '../../../../../src/ol/geom/Point.js'; import Point from '../../../../../src/ol/geom/Point.js';
import VectorTileLayer from '../../../../../src/ol/layer/VectorTile.js'; import VectorTileLayer from '../../../../../src/ol/layer/VectorTile.js';
import {get as getProjection} from '../../../../../src/ol/proj.js'; import {get as getProjection, fromLonLat} from '../../../../../src/ol/proj.js';
import Projection from '../../../../../src/ol/proj/Projection.js';
import {checkedFonts} from '../../../../../src/ol/render/canvas.js'; import {checkedFonts} from '../../../../../src/ol/render/canvas.js';
import RenderFeature from '../../../../../src/ol/render/Feature.js'; import RenderFeature from '../../../../../src/ol/render/Feature.js';
import CanvasVectorTileLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorTileLayer.js'; import CanvasVectorTileLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorTileLayer.js';
@@ -63,6 +64,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
constructor() { constructor() {
super(...arguments); super(...arguments);
this.setFeatures([feature1, feature2, feature3]); this.setFeatures([feature1, feature2, feature3]);
this.setProjection(getProjection('EPSG:4326'));
this.setState(TileState.LOADED); this.setState(TileState.LOADED);
tileCallback(this); tileCallback(this);
} }
@@ -186,6 +188,30 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
}, 1600); }, 1600);
}); });
it('transforms geometries when tile and view projection are different', function() {
let tile;
tileCallback = function(t) {
tile = t;
};
map.renderSync();
expect(tile.getProjection()).to.equal(getProjection('EPSG:3857'));
expect(feature1.getGeometry().getCoordinates()).to.eql(fromLonLat([1, -1]));
});
it('Geometries are transformed from tile-pixels', function() {
const proj = new Projection({code: 'EPSG:3857', units: 'tile-pixels'});
let tile;
tileCallback = function(t) {
t.setProjection(proj);
t.setExtent([0, 0, 4096, 4096]);
tile = t;
};
map.renderSync();
expect(tile.getProjection()).to.equal(getProjection('EPSG:3857'));
expect(feature1.getGeometry().getCoordinates()).to.eql([-20027724.40316874, 20047292.282409746]);
expect(feature3.flatCoordinates_).to.eql([-20027724.40316874, 20047292.282409746]);
});
it('works for multiple layers that use the same source', function() { it('works for multiple layers that use the same source', function() {
const layer2 = new VectorTileLayer({ const layer2 = new VectorTileLayer({
source: source, source: source,
@@ -214,6 +240,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
}) })
}); });
const sourceTile = new VectorTile([0, 0, 0], 2); const sourceTile = new VectorTile([0, 0, 0], 2);
sourceTile.setProjection(getProjection('EPSG:3857'));
sourceTile.features_ = []; sourceTile.features_ = [];
sourceTile.getImage = function() { sourceTile.getImage = function() {
return document.createElement('canvas'); return document.createElement('canvas');
@@ -272,6 +299,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
beforeEach(function() { beforeEach(function() {
const sourceTile = new VectorTile([0, 0, 0]); const sourceTile = new VectorTile([0, 0, 0]);
sourceTile.setState(TileState.LOADED); sourceTile.setState(TileState.LOADED);
sourceTile.setProjection(getProjection('EPSG:3857'));
source = new VectorTileSource({ source = new VectorTileSource({
tileClass: TileClass, tileClass: TileClass,
tileGrid: createXYZ() tileGrid: createXYZ()

View File

@@ -1,5 +1,4 @@
import {createSnapToResolutions, createSnapToPower} from '../../../src/ol/resolutionconstraint.js'; import {createSnapToResolutions, createSnapToPower} from '../../../src/ol/resolutionconstraint.js';
import {createMinMaxResolution} from '../../../src/ol/resolutionconstraint';
describe('ol.resolutionconstraint', function() { describe('ol.resolutionconstraint', function() {
@@ -13,30 +12,30 @@ describe('ol.resolutionconstraint', function() {
[1000, 500, 250, 100]); [1000, 500, 250, 100]);
}); });
describe('direction 0', function() { describe('delta 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1000, 0)).to.eql(1000); expect(resolutionConstraint(1000, 0, 0)).to.eql(1000);
expect(resolutionConstraint(500, 0)).to.eql(500); expect(resolutionConstraint(500, 0, 0)).to.eql(500);
expect(resolutionConstraint(250, 0)).to.eql(250); expect(resolutionConstraint(250, 0, 0)).to.eql(250);
expect(resolutionConstraint(100, 0)).to.eql(100); expect(resolutionConstraint(100, 0, 0)).to.eql(100);
}); });
}); });
describe('direction 1', function() { describe('zoom in', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1000, 1)).to.eql(1000); expect(resolutionConstraint(1000, 1, 0)).to.eql(500);
expect(resolutionConstraint(500, 1)).to.eql(500); expect(resolutionConstraint(500, 1, 0)).to.eql(250);
expect(resolutionConstraint(250, 1)).to.eql(250); expect(resolutionConstraint(250, 1, 0)).to.eql(100);
expect(resolutionConstraint(100, 1)).to.eql(100); expect(resolutionConstraint(100, 1, 0)).to.eql(100);
}); });
}); });
describe('direction -1', function() { describe('zoom out', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1000, -1)).to.eql(1000); expect(resolutionConstraint(1000, -1, 0)).to.eql(1000);
expect(resolutionConstraint(500, -1)).to.eql(500); expect(resolutionConstraint(500, -1, 0)).to.eql(1000);
expect(resolutionConstraint(250, -1)).to.eql(250); expect(resolutionConstraint(250, -1, 0)).to.eql(500);
expect(resolutionConstraint(100, -1)).to.eql(100); expect(resolutionConstraint(100, -1, 0)).to.eql(250);
}); });
}); });
}); });
@@ -51,42 +50,42 @@ describe('ol.resolutionconstraint', function() {
[1000, 500, 250, 100]); [1000, 500, 250, 100]);
}); });
describe('direction 0', function() { describe('delta 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 0)).to.eql(1000); expect(resolutionConstraint(1050, 0, 0)).to.eql(1000);
expect(resolutionConstraint(950, 0)).to.eql(1000); expect(resolutionConstraint(950, 0, 0)).to.eql(1000);
expect(resolutionConstraint(550, 0)).to.eql(500); expect(resolutionConstraint(550, 0, 0)).to.eql(500);
expect(resolutionConstraint(400, 0)).to.eql(500); expect(resolutionConstraint(400, 0, 0)).to.eql(500);
expect(resolutionConstraint(300, 0)).to.eql(250); expect(resolutionConstraint(300, 0, 0)).to.eql(250);
expect(resolutionConstraint(200, 0)).to.eql(250); expect(resolutionConstraint(200, 0, 0)).to.eql(250);
expect(resolutionConstraint(150, 0)).to.eql(100); expect(resolutionConstraint(150, 0, 0)).to.eql(100);
expect(resolutionConstraint(50, 0)).to.eql(100); expect(resolutionConstraint(50, 0, 0)).to.eql(100);
}); });
}); });
describe('direction 1', function() { describe('zoom in', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 1)).to.eql(1000); expect(resolutionConstraint(1050, 1, 0)).to.eql(500);
expect(resolutionConstraint(950, 1)).to.eql(1000); expect(resolutionConstraint(950, 1, 0)).to.eql(500);
expect(resolutionConstraint(550, 1)).to.eql(1000); expect(resolutionConstraint(550, 1, 0)).to.eql(250);
expect(resolutionConstraint(450, 1)).to.eql(500); expect(resolutionConstraint(450, 1, 0)).to.eql(250);
expect(resolutionConstraint(300, 1)).to.eql(500); expect(resolutionConstraint(300, 1, 0)).to.eql(100);
expect(resolutionConstraint(200, 1)).to.eql(250); expect(resolutionConstraint(200, 1, 0)).to.eql(100);
expect(resolutionConstraint(150, 1)).to.eql(250); expect(resolutionConstraint(150, 1, 0)).to.eql(100);
expect(resolutionConstraint(50, 1)).to.eql(100); expect(resolutionConstraint(50, 1, 0)).to.eql(100);
}); });
}); });
describe('direction -1', function() { describe('zoom out', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, -1)).to.eql(1000); expect(resolutionConstraint(1050, -1, 0)).to.eql(1000);
expect(resolutionConstraint(950, -1)).to.eql(500); expect(resolutionConstraint(950, -1, 0)).to.eql(1000);
expect(resolutionConstraint(550, -1)).to.eql(500); expect(resolutionConstraint(550, -1, 0)).to.eql(1000);
expect(resolutionConstraint(450, -1)).to.eql(250); expect(resolutionConstraint(450, -1, 0)).to.eql(1000);
expect(resolutionConstraint(300, -1)).to.eql(250); expect(resolutionConstraint(300, -1, 0)).to.eql(500);
expect(resolutionConstraint(200, -1)).to.eql(100); expect(resolutionConstraint(200, -1, 0)).to.eql(500);
expect(resolutionConstraint(150, -1)).to.eql(100); expect(resolutionConstraint(150, -1, 0)).to.eql(250);
expect(resolutionConstraint(50, -1)).to.eql(100); expect(resolutionConstraint(50, -1, 0)).to.eql(250);
}); });
}); });
}); });
@@ -97,54 +96,54 @@ describe('ol.resolutionconstraint', function() {
beforeEach(function() { beforeEach(function() {
resolutionConstraint = resolutionConstraint =
createSnapToPower(2, 1024, 1); createSnapToPower(2, 1024, 10);
}); });
describe('delta 0', function() { describe('delta 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1024, 0)).to.eql(1024); expect(resolutionConstraint(1024, 0, 0)).to.eql(1024);
expect(resolutionConstraint(512, 0)).to.eql(512); expect(resolutionConstraint(512, 0, 0)).to.eql(512);
expect(resolutionConstraint(256, 0)).to.eql(256); expect(resolutionConstraint(256, 0, 0)).to.eql(256);
expect(resolutionConstraint(128, 0)).to.eql(128); expect(resolutionConstraint(128, 0, 0)).to.eql(128);
expect(resolutionConstraint(64, 0)).to.eql(64); expect(resolutionConstraint(64, 0, 0)).to.eql(64);
expect(resolutionConstraint(32, 0)).to.eql(32); expect(resolutionConstraint(32, 0, 0)).to.eql(32);
expect(resolutionConstraint(16, 0)).to.eql(16); expect(resolutionConstraint(16, 0, 0)).to.eql(16);
expect(resolutionConstraint(8, 0)).to.eql(8); expect(resolutionConstraint(8, 0, 0)).to.eql(8);
expect(resolutionConstraint(4, 0)).to.eql(4); expect(resolutionConstraint(4, 0, 0)).to.eql(4);
expect(resolutionConstraint(2, 0)).to.eql(2); expect(resolutionConstraint(2, 0, 0)).to.eql(2);
expect(resolutionConstraint(1, 0)).to.eql(1); expect(resolutionConstraint(1, 0, 0)).to.eql(1);
}); });
}); });
describe('direction 1', function() { describe('zoom in', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1024, 1)).to.eql(1024); expect(resolutionConstraint(1024, 1, 0)).to.eql(512);
expect(resolutionConstraint(512, 1)).to.eql(512); expect(resolutionConstraint(512, 1, 0)).to.eql(256);
expect(resolutionConstraint(256, 1)).to.eql(256); expect(resolutionConstraint(256, 1, 0)).to.eql(128);
expect(resolutionConstraint(128, 1)).to.eql(128); expect(resolutionConstraint(128, 1, 0)).to.eql(64);
expect(resolutionConstraint(64, 1)).to.eql(64); expect(resolutionConstraint(64, 1, 0)).to.eql(32);
expect(resolutionConstraint(32, 1)).to.eql(32); expect(resolutionConstraint(32, 1, 0)).to.eql(16);
expect(resolutionConstraint(16, 1)).to.eql(16); expect(resolutionConstraint(16, 1, 0)).to.eql(8);
expect(resolutionConstraint(8, 1)).to.eql(8); expect(resolutionConstraint(8, 1, 0)).to.eql(4);
expect(resolutionConstraint(4, 1)).to.eql(4); expect(resolutionConstraint(4, 1, 0)).to.eql(2);
expect(resolutionConstraint(2, 1)).to.eql(2); expect(resolutionConstraint(2, 1, 0)).to.eql(1);
expect(resolutionConstraint(1, 1)).to.eql(1); expect(resolutionConstraint(1, 1, 0)).to.eql(1);
}); });
}); });
describe('direction -1', function() { describe('zoom out', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1024, -1)).to.eql(1024); expect(resolutionConstraint(1024, -1, 0)).to.eql(1024);
expect(resolutionConstraint(512, -1)).to.eql(512); expect(resolutionConstraint(512, -1, 0)).to.eql(1024);
expect(resolutionConstraint(256, -1)).to.eql(256); expect(resolutionConstraint(256, -1, 0)).to.eql(512);
expect(resolutionConstraint(128, -1)).to.eql(128); expect(resolutionConstraint(128, -1, 0)).to.eql(256);
expect(resolutionConstraint(64, -1)).to.eql(64); expect(resolutionConstraint(64, -1, 0)).to.eql(128);
expect(resolutionConstraint(32, -1)).to.eql(32); expect(resolutionConstraint(32, -1, 0)).to.eql(64);
expect(resolutionConstraint(16, -1)).to.eql(16); expect(resolutionConstraint(16, -1, 0)).to.eql(32);
expect(resolutionConstraint(8, -1)).to.eql(8); expect(resolutionConstraint(8, -1, 0)).to.eql(16);
expect(resolutionConstraint(4, -1)).to.eql(4); expect(resolutionConstraint(4, -1, 0)).to.eql(8);
expect(resolutionConstraint(2, -1)).to.eql(2); expect(resolutionConstraint(2, -1, 0)).to.eql(4);
expect(resolutionConstraint(1, -1)).to.eql(1); expect(resolutionConstraint(1, -1, 0)).to.eql(2);
}); });
}); });
}); });
@@ -155,182 +154,88 @@ describe('ol.resolutionconstraint', function() {
beforeEach(function() { beforeEach(function() {
resolutionConstraint = resolutionConstraint =
createSnapToPower(2, 1024, 1); createSnapToPower(2, 1024, 10);
}); });
describe('direction 0', function() { describe('delta 0, direction 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 0)).to.eql(1024); expect(resolutionConstraint(1050, 0, 0)).to.eql(1024);
expect(resolutionConstraint(9050, 0)).to.eql(1024); expect(resolutionConstraint(9050, 0, 0)).to.eql(1024);
expect(resolutionConstraint(550, 0)).to.eql(512); expect(resolutionConstraint(550, 0, 0)).to.eql(512);
expect(resolutionConstraint(450, 0)).to.eql(512); expect(resolutionConstraint(450, 0, 0)).to.eql(512);
expect(resolutionConstraint(300, 0)).to.eql(256); expect(resolutionConstraint(300, 0, 0)).to.eql(256);
expect(resolutionConstraint(250, 0)).to.eql(256); expect(resolutionConstraint(250, 0, 0)).to.eql(256);
expect(resolutionConstraint(150, 0)).to.eql(128); expect(resolutionConstraint(150, 0, 0)).to.eql(128);
expect(resolutionConstraint(100, 0)).to.eql(128); expect(resolutionConstraint(100, 0, 0)).to.eql(128);
expect(resolutionConstraint(75, 0)).to.eql(64); expect(resolutionConstraint(75, 0, 0)).to.eql(64);
expect(resolutionConstraint(50, 0)).to.eql(64); expect(resolutionConstraint(50, 0, 0)).to.eql(64);
expect(resolutionConstraint(40, 0)).to.eql(32); expect(resolutionConstraint(40, 0, 0)).to.eql(32);
expect(resolutionConstraint(30, 0)).to.eql(32); expect(resolutionConstraint(30, 0, 0)).to.eql(32);
expect(resolutionConstraint(20, 0)).to.eql(16); expect(resolutionConstraint(20, 0, 0)).to.eql(16);
expect(resolutionConstraint(12, 0)).to.eql(16); expect(resolutionConstraint(12, 0, 0)).to.eql(16);
expect(resolutionConstraint(9, 0)).to.eql(8); expect(resolutionConstraint(9, 0, 0)).to.eql(8);
expect(resolutionConstraint(7, 0)).to.eql(8); expect(resolutionConstraint(7, 0, 0)).to.eql(8);
expect(resolutionConstraint(5, 0)).to.eql(4); expect(resolutionConstraint(5, 0, 0)).to.eql(4);
expect(resolutionConstraint(3.5, 0)).to.eql(4); expect(resolutionConstraint(3.5, 0, 0)).to.eql(4);
expect(resolutionConstraint(2.1, 0)).to.eql(2); expect(resolutionConstraint(2.1, 0, 0)).to.eql(2);
expect(resolutionConstraint(1.9, 0)).to.eql(2); expect(resolutionConstraint(1.9, 0, 0)).to.eql(2);
expect(resolutionConstraint(1.1, 0)).to.eql(1); expect(resolutionConstraint(1.1, 0, 0)).to.eql(1);
expect(resolutionConstraint(0.9, 0)).to.eql(1); expect(resolutionConstraint(0.9, 0, 0)).to.eql(1);
}); });
}); });
describe('direction 1', function() { describe('delta 0, direction > 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 1)).to.eql(1024); expect(resolutionConstraint(1050, 0, 1)).to.eql(1024);
expect(resolutionConstraint(9050, 1)).to.eql(1024); expect(resolutionConstraint(9050, 0, 1)).to.eql(1024);
expect(resolutionConstraint(550, 1)).to.eql(1024); expect(resolutionConstraint(550, 0, 1)).to.eql(1024);
expect(resolutionConstraint(450, 1)).to.eql(512); expect(resolutionConstraint(450, 0, 1)).to.eql(512);
expect(resolutionConstraint(300, 1)).to.eql(512); expect(resolutionConstraint(300, 0, 1)).to.eql(512);
expect(resolutionConstraint(250, 1)).to.eql(256); expect(resolutionConstraint(250, 0, 1)).to.eql(256);
expect(resolutionConstraint(150, 1)).to.eql(256); expect(resolutionConstraint(150, 0, 1)).to.eql(256);
expect(resolutionConstraint(100, 1)).to.eql(128); expect(resolutionConstraint(100, 0, 1)).to.eql(128);
expect(resolutionConstraint(75, 1)).to.eql(128); expect(resolutionConstraint(75, 0, 1)).to.eql(128);
expect(resolutionConstraint(50, 1)).to.eql(64); expect(resolutionConstraint(50, 0, 1)).to.eql(64);
expect(resolutionConstraint(40, 1)).to.eql(64); expect(resolutionConstraint(40, 0, 1)).to.eql(64);
expect(resolutionConstraint(30, 1)).to.eql(32); expect(resolutionConstraint(30, 0, 1)).to.eql(32);
expect(resolutionConstraint(20, 1)).to.eql(32); expect(resolutionConstraint(20, 0, 1)).to.eql(32);
expect(resolutionConstraint(12, 1)).to.eql(16); expect(resolutionConstraint(12, 0, 1)).to.eql(16);
expect(resolutionConstraint(9, 1)).to.eql(16); expect(resolutionConstraint(9, 0, 1)).to.eql(16);
expect(resolutionConstraint(7, 1)).to.eql(8); expect(resolutionConstraint(7, 0, 1)).to.eql(8);
expect(resolutionConstraint(5, 1)).to.eql(8); expect(resolutionConstraint(5, 0, 1)).to.eql(8);
expect(resolutionConstraint(3.5, 1)).to.eql(4); expect(resolutionConstraint(3.5, 0, 1)).to.eql(4);
expect(resolutionConstraint(2.1, 1)).to.eql(4); expect(resolutionConstraint(2.1, 0, 1)).to.eql(4);
expect(resolutionConstraint(1.9, 1)).to.eql(2); expect(resolutionConstraint(1.9, 0, 1)).to.eql(2);
expect(resolutionConstraint(1.1, 1)).to.eql(2); expect(resolutionConstraint(1.1, 0, 1)).to.eql(2);
expect(resolutionConstraint(0.9, 1)).to.eql(1); expect(resolutionConstraint(0.9, 0, 1)).to.eql(1);
}); });
}); });
describe('direction -1', function() { describe('delta 0, direction < 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, -1)).to.eql(1024); expect(resolutionConstraint(1050, 0, -1)).to.eql(1024);
expect(resolutionConstraint(9050, -1)).to.eql(1024); expect(resolutionConstraint(9050, 0, -1)).to.eql(1024);
expect(resolutionConstraint(550, -1)).to.eql(512); expect(resolutionConstraint(550, 0, -1)).to.eql(512);
expect(resolutionConstraint(450, -1)).to.eql(256); expect(resolutionConstraint(450, 0, -1)).to.eql(256);
expect(resolutionConstraint(300, -1)).to.eql(256); expect(resolutionConstraint(300, 0, -1)).to.eql(256);
expect(resolutionConstraint(250, -1)).to.eql(128); expect(resolutionConstraint(250, 0, -1)).to.eql(128);
expect(resolutionConstraint(150, -1)).to.eql(128); expect(resolutionConstraint(150, 0, -1)).to.eql(128);
expect(resolutionConstraint(100, -1)).to.eql(64); expect(resolutionConstraint(100, 0, -1)).to.eql(64);
expect(resolutionConstraint(75, -1)).to.eql(64); expect(resolutionConstraint(75, 0, -1)).to.eql(64);
expect(resolutionConstraint(50, -1)).to.eql(32); expect(resolutionConstraint(50, 0, -1)).to.eql(32);
expect(resolutionConstraint(40, -1)).to.eql(32); expect(resolutionConstraint(40, 0, -1)).to.eql(32);
expect(resolutionConstraint(30, -1)).to.eql(16); expect(resolutionConstraint(30, 0, -1)).to.eql(16);
expect(resolutionConstraint(20, -1)).to.eql(16); expect(resolutionConstraint(20, 0, -1)).to.eql(16);
expect(resolutionConstraint(12, -1)).to.eql(8); expect(resolutionConstraint(12, 0, -1)).to.eql(8);
expect(resolutionConstraint(9, -1)).to.eql(8); expect(resolutionConstraint(9, 0, -1)).to.eql(8);
expect(resolutionConstraint(7, -1)).to.eql(4); expect(resolutionConstraint(7, 0, -1)).to.eql(4);
expect(resolutionConstraint(5, -1)).to.eql(4); expect(resolutionConstraint(5, 0, -1)).to.eql(4);
expect(resolutionConstraint(3.5, -1)).to.eql(2); expect(resolutionConstraint(3.5, 0, -1)).to.eql(2);
expect(resolutionConstraint(2.1, -1)).to.eql(2); expect(resolutionConstraint(2.1, 0, -1)).to.eql(2);
expect(resolutionConstraint(1.9, -1)).to.eql(1); expect(resolutionConstraint(1.9, 0, -1)).to.eql(1);
expect(resolutionConstraint(1.1, -1)).to.eql(1); expect(resolutionConstraint(1.1, 0, -1)).to.eql(1);
expect(resolutionConstraint(0.9, -1)).to.eql(1); expect(resolutionConstraint(0.9, 0, -1)).to.eql(1);
}); });
}); });
}); });
describe('SnapToPower smooth constraint', function() {
describe('snap to power, smooth constraint on', function() {
it('returns expected resolution value', function() {
const resolutionConstraint = createSnapToPower(2, 128, 16, true);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.greaterThan(128);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.lessThan(150);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.greaterThan(128);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.lessThan(130);
expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.greaterThan(15);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.lessThan(16);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.greaterThan(10);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.lessThan(16);
});
});
describe('snap to power, smooth constraint off', function() {
it('returns expected resolution value', function() {
const resolutionConstraint = createSnapToPower(2, 128, 16, false);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.eql(16);
});
});
describe('snap to resolutions, smooth constraint on', function() {
it('returns expected resolution value', function() {
const resolutionConstraint = createSnapToResolutions([128, 64, 32, 16], true);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.greaterThan(128);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.lessThan(150);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.greaterThan(128);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.lessThan(130);
expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.greaterThan(15);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.lessThan(16);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.greaterThan(10);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.lessThan(16);
});
});
describe('snap to resolutions, smooth constraint off', function() {
it('returns expected resolution value', function() {
const resolutionConstraint = createSnapToResolutions([128, 64, 32, 16], false);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.eql(16);
});
});
describe('min/max, smooth constraint on', function() {
it('returns expected resolution value', function() {
const resolutionConstraint = createMinMaxResolution(128, 16, true);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.greaterThan(128);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.be.lessThan(150);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.greaterThan(128);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.be.lessThan(130);
expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.greaterThan(15);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.be.lessThan(16);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.greaterThan(10);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.be.lessThan(16);
});
});
describe('min/max, smooth constraint off', function() {
it('returns expected resolution value', function() {
const resolutionConstraint = createMinMaxResolution(128, 16, false);
expect(resolutionConstraint(150, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(130, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(128, 0, [100, 100], true)).to.eql(128);
expect(resolutionConstraint(16, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(15, 0, [100, 100], true)).to.eql(16);
expect(resolutionConstraint(10, 0, [100, 100], true)).to.eql(16);
});
});
});
}); });

View File

@@ -8,15 +8,27 @@ describe('ol.rotationconstraint', function() {
it('returns expected rotation value', function() { it('returns expected rotation value', function() {
const rotationConstraint = createSnapToZero(0.3); const rotationConstraint = createSnapToZero(0.3);
expect(rotationConstraint(0.1)).to.eql(0); expect(rotationConstraint(0.1, 0)).to.eql(0);
expect(rotationConstraint(0.2)).to.eql(0); expect(rotationConstraint(0.2, 0)).to.eql(0);
expect(rotationConstraint(0.3)).to.eql(0); expect(rotationConstraint(0.3, 0)).to.eql(0);
expect(rotationConstraint(0.4)).to.eql(0.4); expect(rotationConstraint(0.4, 0)).to.eql(0.4);
expect(rotationConstraint(-0.1)).to.eql(0); expect(rotationConstraint(-0.1, 0)).to.eql(0);
expect(rotationConstraint(-0.2)).to.eql(0); expect(rotationConstraint(-0.2, 0)).to.eql(0);
expect(rotationConstraint(-0.3)).to.eql(0); expect(rotationConstraint(-0.3, 0)).to.eql(0);
expect(rotationConstraint(-0.4)).to.eql(-0.4); expect(rotationConstraint(-0.4, 0)).to.eql(-0.4);
expect(rotationConstraint(1, -0.9)).to.eql(0);
expect(rotationConstraint(1, -0.8)).to.eql(0);
// floating-point arithmetic
expect(rotationConstraint(1, -0.7)).not.to.eql(0);
expect(rotationConstraint(1, -0.6)).to.eql(0.4);
expect(rotationConstraint(-1, 0.9)).to.eql(0);
expect(rotationConstraint(-1, 0.8)).to.eql(0);
// floating-point arithmetic
expect(rotationConstraint(-1, 0.7)).not.to.eql(0);
expect(rotationConstraint(-1, 0.6)).to.eql(-0.4);
}); });
}); });

View File

@@ -1,43 +1,38 @@
import Feature from '../../../src/ol/Feature.js';
import {defaultLoadFunction} from '../../../src/ol/source/VectorTile.js'; import {defaultLoadFunction} from '../../../src/ol/source/VectorTile.js';
import VectorTile from '../../../src/ol/VectorTile.js'; import VectorTile from '../../../src/ol/VectorTile.js';
import {listen} from '../../../src/ol/events.js'; import {listen} from '../../../src/ol/events.js';
import GeoJSON from '../../../src/ol/format/GeoJSON.js'; import TextFeature from '../../../src/ol/format/TextFeature.js';
import MVT from '../../../src/ol/format/MVT.js';
import {get as getProjection} from '../../../src/ol/proj.js'; import {get as getProjection} from '../../../src/ol/proj.js';
import {createXYZ} from '../../../src/ol/tilegrid.js'; import Projection from '../../../src/ol/proj/Projection.js';
describe('ol.VectorTile', function() { describe('ol.VectorTile', function() {
it('loader reprojects GeoJSON features', function(done) { it('loader sets features on the tile and updates proj units', function(done) {
const format = new GeoJSON(); // mock format that return a tile-pixels feature
const format = new TextFeature();
format.readProjection = function(source) {
return new Projection({
code: '',
units: 'tile-pixels'
});
};
format.readFeatures = function(source, options) {
return [new Feature()];
};
const tile = new VectorTile([0, 0, 0], null, null, format); const tile = new VectorTile([0, 0, 0], null, null, format);
const url = 'spec/ol/data/point.json'; const url = 'spec/ol/data/point.json';
defaultLoadFunction(tile, url); defaultLoadFunction(tile, url);
const loader = tile.loader_; const loader = tile.loader_;
listen(tile, 'change', function(e) { listen(tile, 'change', function(e) {
expect(tile.getFeatures()[0].getGeometry().getFlatCoordinates()).to.eql([-9724792.346778862, 4164041.638405114]); expect(tile.getFeatures().length).to.be.greaterThan(0);
expect(tile.getProjection().getUnits()).to.be('tile-pixels');
done(); done();
}); });
loader.call(tile, [], 1, getProjection('EPSG:3857')); loader.call(tile, [], 1, getProjection('EPSG:3857'));
}); });
it('loader reprojects MVT features', function(done) {
const format = new MVT();
const tileGrid = createXYZ({
tileSize: 512
});
const tile = new VectorTile([14, 8938, 5680], null, null, format);
const url = 'spec/ol/data/14-8938-5680.vector.pbf';
defaultLoadFunction(tile, url);
const loader = tile.loader_;
listen(tile, 'change', function(e) {
expect(tile.getFeatures()[1246].getGeometry().getFlatCoordinates()).to.eql([1827804.0218549764, 6144812.116688028]);
done();
});
const extent = tileGrid.getTileCoordExtent(tile.tileCoord);
loader.call(tile, extent, 1, getProjection('EPSG:3857'));
});
}); });

View File

@@ -42,36 +42,15 @@ describe('ol.View', function() {
}); });
}); });
describe('with extent option and center only', function() {
it('gives a correct center constraint function', function() {
const options = {
extent: [0, 0, 1, 1],
constrainOnlyCenter: true
};
const fn = createCenterConstraint(options);
expect(fn([0, 0])).to.eql([0, 0]);
expect(fn([-10, 0])).to.eql([0, 0]);
expect(fn([100, 100])).to.eql([1, 1]);
});
});
describe('with extent option', function() { describe('with extent option', function() {
it('gives a correct center constraint function', function() { it('gives a correct center constraint function', function() {
const options = { const options = {
extent: [0, 0, 1, 1] extent: [0, 0, 1, 1]
}; };
const fn = createCenterConstraint(options); const fn = createCenterConstraint(options);
const res = 1; expect(fn([0, 0])).to.eql([0, 0]);
const size = [0.15, 0.1]; expect(fn([-10, 0])).to.eql([0, 0]);
expect(fn([0, 0], res, size)).to.eql([0.075, 0.05]); expect(fn([100, 100])).to.eql([1, 1]);
expect(fn([0.5, 0.5], res, size)).to.eql([0.5, 0.5]);
expect(fn([10, 10], res, size)).to.eql([0.925, 0.95]);
const overshootCenter = fn([10, 10], res, size, true);
expect(overshootCenter[0] > 0.925).to.eql(true);
expect(overshootCenter[1] > 0.95).to.eql(true);
expect(overshootCenter[0] < 9).to.eql(true);
expect(overshootCenter[1] < 9).to.eql(true);
}); });
}); });
@@ -239,8 +218,7 @@ describe('ol.View', function() {
it('works with minResolution and maxResolution', function() { it('works with minResolution and maxResolution', function() {
const constraint = getConstraint({ const constraint = getConstraint({
maxResolution: 500, maxResolution: 500,
minResolution: 100, minResolution: 100
constrainResolution: true
}); });
expect(constraint(600, 0, 0)).to.be(500); expect(constraint(600, 0, 0)).to.be(500);
@@ -256,8 +234,7 @@ describe('ol.View', function() {
const constraint = getConstraint({ const constraint = getConstraint({
maxResolution: 500, maxResolution: 500,
minResolution: 1, minResolution: 1,
zoomFactor: 10, zoomFactor: 10
constrainResolution: true
}); });
expect(constraint(1000, 0, 0)).to.be(500); expect(constraint(1000, 0, 0)).to.be(500);
@@ -388,7 +365,6 @@ describe('ol.View', function() {
it('applies the current resolution if resolution was originally supplied', function() { it('applies the current resolution if resolution was originally supplied', function() {
const view = new View({ const view = new View({
center: [0, 0], center: [0, 0],
maxResolution: 2000,
resolution: 1000 resolution: 1000
}); });
view.setResolution(500); view.setResolution(500);
@@ -988,8 +964,7 @@ describe('ol.View', function() {
let view; let view;
beforeEach(function() { beforeEach(function() {
view = new View({ view = new View({
resolutions: [1024, 512, 256, 128, 64, 32, 16, 8], resolutions: [512, 256, 128, 64, 32, 16]
smoothResolutionConstraint: false
}); });
}); });
@@ -998,31 +973,30 @@ describe('ol.View', function() {
expect(view.getZoom()).to.be(undefined); expect(view.getZoom()).to.be(undefined);
view.setResolution(513); view.setResolution(513);
expect(view.getZoom()).to.roughlyEqual(Math.log(1024 / 513) / Math.LN2, 1e-9); expect(view.getZoom()).to.roughlyEqual(Math.log(512 / 513) / Math.LN2, 1e-9);
view.setResolution(512); view.setResolution(512);
expect(view.getZoom()).to.be(1); expect(view.getZoom()).to.be(0);
view.setResolution(100); view.setResolution(100);
expect(view.getZoom()).to.roughlyEqual(3.35614, 1e-5); expect(view.getZoom()).to.roughlyEqual(2.35614, 1e-5);
view.setResolution(65); view.setResolution(65);
expect(view.getZoom()).to.roughlyEqual(3.97763, 1e-5); expect(view.getZoom()).to.roughlyEqual(2.97763, 1e-5);
view.setResolution(64); view.setResolution(64);
expect(view.getZoom()).to.be(4); expect(view.getZoom()).to.be(3);
view.setResolution(16); view.setResolution(16);
expect(view.getZoom()).to.be(6); expect(view.getZoom()).to.be(5);
view.setResolution(15); view.setResolution(15);
expect(view.getZoom()).to.roughlyEqual(Math.log(1024 / 15) / Math.LN2, 1e-9); expect(view.getZoom()).to.roughlyEqual(Math.log(512 / 15) / Math.LN2, 1e-9);
}); });
it('works for resolution arrays with variable zoom factors', function() { it('works for resolution arrays with variable zoom factors', function() {
const view = new View({ const view = new View({
resolutions: [10, 5, 2, 1], resolutions: [10, 5, 2, 1]
smoothResolutionConstraint: false
}); });
view.setZoom(1); view.setZoom(1);
@@ -1047,8 +1021,7 @@ describe('ol.View', function() {
it('returns correct zoom levels', function() { it('returns correct zoom levels', function() {
const view = new View({ const view = new View({
minZoom: 10, minZoom: 10,
maxZoom: 20, maxZoom: 20
smoothResolutionConstraint: false
}); });
view.setZoom(5); view.setZoom(5);
@@ -1124,16 +1097,12 @@ describe('ol.View', function() {
describe('#getResolutionForZoom', function() { describe('#getResolutionForZoom', function() {
it('returns correct zoom resolution', function() { it('returns correct zoom resolution', function() {
const view = new View({ const view = new View();
smoothResolutionConstraint: false
});
const max = view.getMaxZoom(); const max = view.getMaxZoom();
const min = view.getMinZoom(); const min = view.getMinZoom();
expect(view.getResolutionForZoom(max)).to.be(view.getMinResolution()); expect(view.getResolutionForZoom(max)).to.be(view.getMinResolution());
expect(view.getResolutionForZoom(max + 1)).to.be(view.getMinResolution() / 2);
expect(view.getResolutionForZoom(min)).to.be(view.getMaxResolution()); expect(view.getResolutionForZoom(min)).to.be(view.getMaxResolution());
expect(view.getResolutionForZoom(min - 1)).to.be(view.getMaxResolution() * 2);
}); });
it('returns correct zoom levels for specifically configured resolutions', function() { it('returns correct zoom levels for specifically configured resolutions', function() {
@@ -1141,30 +1110,11 @@ describe('ol.View', function() {
resolutions: [10, 8, 6, 4, 2] resolutions: [10, 8, 6, 4, 2]
}); });
expect(view.getResolutionForZoom(-1)).to.be(10);
expect(view.getResolutionForZoom(0)).to.be(10); expect(view.getResolutionForZoom(0)).to.be(10);
expect(view.getResolutionForZoom(1)).to.be(8); expect(view.getResolutionForZoom(1)).to.be(8);
expect(view.getResolutionForZoom(2)).to.be(6); expect(view.getResolutionForZoom(2)).to.be(6);
expect(view.getResolutionForZoom(3)).to.be(4); expect(view.getResolutionForZoom(3)).to.be(4);
expect(view.getResolutionForZoom(4)).to.be(2); expect(view.getResolutionForZoom(4)).to.be(2);
expect(view.getResolutionForZoom(5)).to.be(2);
});
it('returns correct zoom levels for resolutions with variable zoom levels', function() {
const view = new View({
resolutions: [50, 10, 5, 2.5, 1.25, 0.625]
});
expect(view.getResolutionForZoom(-1)).to.be(50);
expect(view.getResolutionForZoom(0)).to.be(50);
expect(view.getResolutionForZoom(0.5)).to.be(50 / Math.pow(5, 0.5));
expect(view.getResolutionForZoom(1)).to.be(10);
expect(view.getResolutionForZoom(2)).to.be(5);
expect(view.getResolutionForZoom(2.75)).to.be(5 / Math.pow(2, 0.75));
expect(view.getResolutionForZoom(3)).to.be(2.5);
expect(view.getResolutionForZoom(4)).to.be(1.25);
expect(view.getResolutionForZoom(5)).to.be(0.625);
expect(view.getResolutionForZoom(6)).to.be(0.625);
}); });
}); });
@@ -1296,14 +1246,8 @@ describe('ol.View', function() {
document.body.removeChild(target); document.body.removeChild(target);
}); });
it('calculates the size correctly', function() { it('calculates the size correctly', function() {
let size = map.getView().getSizeFromViewport_(); const size = map.getView().getSizeFromViewport_();
expect(size).to.eql([200, 150]); expect(size).to.eql([200, 150]);
size = map.getView().getSizeFromViewport_(Math.PI / 2);
expect(size[0]).to.roughlyEqual(150, 1e-9);
expect(size[1]).to.roughlyEqual(200, 1e-9);
size = map.getView().getSizeFromViewport_(Math.PI);
expect(size[0]).to.roughlyEqual(200, 1e-9);
expect(size[1]).to.roughlyEqual(150, 1e-9);
}); });
}); });
@@ -1334,40 +1278,14 @@ describe('ol.View', function() {
zoom: 5 zoom: 5
}); });
}); });
it('fits correctly to the geometry (with unconstrained resolution)', function() { it('fits correctly to the geometry', function() {
view.fit( view.fit(
new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]), new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
{size: [200, 200], padding: [100, 0, 0, 100]}); {size: [200, 200], padding: [100, 0, 0, 100], constrainResolution: false});
expect(view.getResolution()).to.be(11); expect(view.getResolution()).to.be(11);
expect(view.getCenter()[0]).to.be(5950); expect(view.getCenter()[0]).to.be(5950);
expect(view.getCenter()[1]).to.be(47100); expect(view.getCenter()[1]).to.be(47100);
view.fit(
new Circle([6000, 46000], 1000),
{size: [200, 200]});
expect(view.getResolution()).to.be(10);
expect(view.getCenter()[0]).to.be(6000);
expect(view.getCenter()[1]).to.be(46000);
view.setRotation(Math.PI / 8);
view.fit(
new Circle([6000, 46000], 1000),
{size: [200, 200]});
expect(view.getResolution()).to.roughlyEqual(10, 1e-9);
expect(view.getCenter()[0]).to.roughlyEqual(6000, 1e-9);
expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9);
view.setRotation(Math.PI / 4);
view.fit(
new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
{size: [200, 200], padding: [100, 0, 0, 100]});
expect(view.getResolution()).to.roughlyEqual(14.849242404917458, 1e-9);
expect(view.getCenter()[0]).to.roughlyEqual(5200, 1e-9);
expect(view.getCenter()[1]).to.roughlyEqual(46300, 1e-9);
});
it('fits correctly to the geometry', function() {
view.setConstrainResolution(true);
view.fit( view.fit(
new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]), new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
{size: [200, 200], padding: [100, 0, 0, 100]}); {size: [200, 200], padding: [100, 0, 0, 100]});
@@ -1396,8 +1314,30 @@ describe('ol.View', function() {
expect(view.getZoom()).to.be(6); expect(view.getZoom()).to.be(6);
expect(view.getCenter()[0]).to.be(5900); expect(view.getCenter()[0]).to.be(5900);
expect(view.getCenter()[1]).to.be(46100); expect(view.getCenter()[1]).to.be(46100);
});
view.fit(
new Circle([6000, 46000], 1000),
{size: [200, 200], constrainResolution: false});
expect(view.getResolution()).to.be(10);
expect(view.getCenter()[0]).to.be(6000);
expect(view.getCenter()[1]).to.be(46000);
view.setRotation(Math.PI / 8);
view.fit(
new Circle([6000, 46000], 1000),
{size: [200, 200], constrainResolution: false});
expect(view.getResolution()).to.roughlyEqual(10, 1e-9);
expect(view.getCenter()[0]).to.roughlyEqual(6000, 1e-9);
expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9);
view.setRotation(Math.PI / 4);
view.fit(
new LineString([[6000, 46000], [6000, 47100], [7000, 46000]]),
{size: [200, 200], padding: [100, 0, 0, 100], constrainResolution: false});
expect(view.getResolution()).to.roughlyEqual(14.849242404917458, 1e-9);
expect(view.getCenter()[0]).to.roughlyEqual(5200, 1e-9);
expect(view.getCenter()[1]).to.roughlyEqual(46300, 1e-9);
});
it('fits correctly to the extent', function() { it('fits correctly to the extent', function() {
view.fit([1000, 1000, 2000, 2000], {size: [200, 200]}); view.fit([1000, 1000, 2000, 2000], {size: [200, 200]});
expect(view.getResolution()).to.be(5); expect(view.getResolution()).to.be(5);
@@ -1420,6 +1360,7 @@ describe('ol.View', function() {
{ {
size: [200, 200], size: [200, 200],
padding: [100, 0, 0, 100], padding: [100, 0, 0, 100],
constrainResolution: false,
duration: 25 duration: 25
}); });
@@ -1481,235 +1422,6 @@ describe('ol.View', function() {
expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9); expect(view.getCenter()[1]).to.roughlyEqual(46000, 1e-9);
}); });
}); });
describe('#beginInteraction() and endInteraction()', function() {
let view;
beforeEach(function() {
view = new View();
});
it('correctly changes the view hint', function() {
view.beginInteraction();
expect(view.getHints()[1]).to.be(1);
view.beginInteraction();
expect(view.getHints()[1]).to.be(2);
view.endInteraction();
view.endInteraction();
expect(view.getHints()[1]).to.be(0);
});
});
describe('#getConstrainedZoom()', function() {
let view;
it('works correctly without constraint', function() {
view = new View({
zoom: 0
});
expect(view.getConstrainedZoom(3)).to.be(3);
});
it('works correctly with resolution constraints', function() {
view = new View({
zoom: 4,
minZoom: 4,
maxZoom: 8
});
expect(view.getConstrainedZoom(3)).to.be(4);
expect(view.getConstrainedZoom(10)).to.be(8);
});
it('works correctly with a specific resolution set', function() {
view = new View({
zoom: 0,
resolutions: [512, 256, 128, 64, 32, 16, 8]
});
expect(view.getConstrainedZoom(0)).to.be(0);
expect(view.getConstrainedZoom(4)).to.be(4);
expect(view.getConstrainedZoom(8)).to.be(6);
});
});
describe('#getConstrainedResolution()', function() {
let view;
const defaultMaxRes = 156543.03392804097;
it('works correctly by snapping to power of 2', function() {
view = new View();
expect(view.getConstrainedResolution(1000000)).to.be(defaultMaxRes);
expect(view.getConstrainedResolution(defaultMaxRes / 8)).to.be(defaultMaxRes / 8);
});
it('works correctly by snapping to a custom zoom factor', function() {
view = new View({
maxResolution: 2500,
zoomFactor: 5,
maxZoom: 4,
constrainResolution: true
});
expect(view.getConstrainedResolution(90, 1)).to.be(100);
expect(view.getConstrainedResolution(90, -1)).to.be(20);
expect(view.getConstrainedResolution(20)).to.be(20);
expect(view.getConstrainedResolution(5)).to.be(4);
expect(view.getConstrainedResolution(1)).to.be(4);
});
it('works correctly with a specific resolution set', function() {
view = new View({
zoom: 0,
resolutions: [512, 256, 128, 64, 32, 16, 8],
constrainResolution: true
});
expect(view.getConstrainedResolution(1000, 1)).to.be(512);
expect(view.getConstrainedResolution(260, 1)).to.be(512);
expect(view.getConstrainedResolution(260)).to.be(256);
expect(view.getConstrainedResolution(30)).to.be(32);
expect(view.getConstrainedResolution(30, -1)).to.be(16);
expect(view.getConstrainedResolution(4, -1)).to.be(8);
});
});
describe('#adjustRotation()', function() {
it('changes view rotation with anchor', function() {
const view = new View({
resolution: 1,
center: [0, 0]
});
view.adjustRotation(Math.PI / 2);
expect(view.getRotation()).to.be(Math.PI / 2);
expect(view.getCenter()).to.eql([0, 0]);
view.adjustRotation(-Math.PI);
expect(view.getRotation()).to.be(-Math.PI / 2);
expect(view.getCenter()).to.eql([0, 0]);
view.adjustRotation(Math.PI / 3, [50, 0]);
expect(view.getRotation()).to.roughlyEqual(-Math.PI / 6, 1e-9);
expect(view.getCenter()[0]).to.roughlyEqual(50 * (1 - Math.cos(Math.PI / 3)), 1e-9);
expect(view.getCenter()[1]).to.roughlyEqual(-50 * Math.sin(Math.PI / 3), 1e-9);
});
it('does not change view parameters if rotation is disabled', function() {
const view = new View({
resolution: 1,
enableRotation: false,
center: [0, 0]
});
view.adjustRotation(Math.PI / 2);
expect(view.getRotation()).to.be(0);
expect(view.getCenter()).to.eql([0, 0]);
view.adjustRotation(-Math.PI * 3, [-50, 0]);
expect(view.getRotation()).to.be(0);
expect(view.getCenter()).to.eql([0, 0]);
});
});
describe('#adjustZoom()', function() {
it('changes view resolution', function() {
const view = new View({
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25]
});
view.adjustZoom(1);
expect(view.getResolution()).to.be(0.5);
view.adjustZoom(-1);
expect(view.getResolution()).to.be(1);
view.adjustZoom(2);
expect(view.getResolution()).to.be(0.25);
view.adjustZoom(-2);
expect(view.getResolution()).to.be(1);
});
it('changes view resolution and center relative to the anchor', function() {
const view = new View({
center: [0, 0],
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25]
});
view.adjustZoom(1, [10, 10]);
expect(view.getCenter()).to.eql([5, 5]);
view.adjustZoom(-1, [0, 0]);
expect(view.getCenter()).to.eql([10, 10]);
view.adjustZoom(2, [0, 0]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
view.adjustZoom(-2, [0, 0]);
expect(view.getCenter()).to.eql([10, 10]);
});
it('changes view resolution and center relative to the anchor, while respecting the extent (center only)', function() {
const view = new View({
center: [0, 0],
extent: [-2.5, -2.5, 2.5, 2.5],
constrainOnlyCenter: true,
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25]
});
view.adjustZoom(1, [10, 10]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
view.adjustZoom(-1, [0, 0]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
view.adjustZoom(2, [10, 10]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
view.adjustZoom(-2, [0, 0]);
expect(view.getCenter()).to.eql([2.5, 2.5]);
});
it('changes view resolution and center relative to the anchor, while respecting the extent', function() {
const map = new Map({});
const view = new View({
center: [50, 50],
extent: [0, 0, 100, 100],
resolution: 1,
resolutions: [4, 2, 1, 0.5, 0.25, 0.125]
});
map.setView(view);
view.adjustZoom(1, [100, 100]);
expect(view.getCenter()).to.eql([75, 75]);
view.adjustZoom(-1, [75, 75]);
expect(view.getCenter()).to.eql([50, 50]);
view.adjustZoom(2, [100, 100]);
expect(view.getCenter()).to.eql([87.5, 87.5]);
view.adjustZoom(-3, [0, 0]);
expect(view.getCenter()).to.eql([50, 50]);
expect(view.getResolution()).to.eql(1);
});
it('changes view resolution and center relative to the anchor, while respecting the extent (rotated)', function() {
const map = new Map({});
const view = new View({
center: [50, 50],
extent: [-100, -100, 100, 100],
resolution: 1,
resolutions: [2, 1, 0.5, 0.25, 0.125],
rotation: Math.PI / 4
});
map.setView(view);
const halfSize = 100 * Math.SQRT1_2;
view.adjustZoom(1, [100, 100]);
expect(view.getCenter()).to.eql([100 - halfSize / 2, 100 - halfSize / 2]);
view.setCenter([0, 50]);
view.adjustZoom(-1, [0, 0]);
expect(view.getCenter()).to.eql([0, 100 - halfSize]);
});
});
}); });
describe('ol.View.isNoopAnimation()', function() { describe('ol.View.isNoopAnimation()', function() {