Compare commits

...

50 Commits

Author SHA1 Message Date
ahocevar
937b57c7d9 Update package version to 6.0.0-beta.3 2019-03-11 10:44:51 +01:00
Andreas Hocevar
dfabcc0c66 Merge pull request #9137 from jahow/add-view-constrain-extent-2
Refactor the way view constraints are handled & add a view extent constraint
2019-03-11 10:38:31 +01:00
Andreas Hocevar
3282c74de6 Merge pull request #9311 from openlayers/greenkeeper/pbf-3.2.0
Update pbf to the latest version 🚀
2019-03-11 10:29:17 +01:00
Andreas Hocevar
dee36639bd Merge pull request #9313 from ahocevar/interpolate-tests
Re-enable and fix disabled tests
2019-03-11 10:18:00 +01:00
Andreas Hocevar
5a578c00a7 Merge pull request #9314 from ahocevar/fix-transpile
Fix transpilation
2019-03-11 10:17:32 +01:00
ahocevar
e8243b73b5 Fix transpilation 2019-03-11 10:11:05 +01:00
ahocevar
404b04ae59 Re-enable and fix disabled tests 2019-03-11 09:30:04 +01:00
Andreas Hocevar
8a02a6f9f1 Merge pull request #9308 from ahocevar/vectortile-loader-projection
Simplify vector tile projection handling
2019-03-11 09:28:48 +01:00
Bart van den Eijnden
4a67bd86a8 Merge pull request #9312 from bartvde/stamen-minzoom
Update zoom levels for Stamen source
2019-03-11 09:17:50 +01:00
Frédéric Junod
feb1a9f31b Merge pull request #9310 from fredj/stamen_transition
Add transition options to Stamen source
2019-03-11 09:11:38 +01:00
greenkeeper[bot]
8a5720e8cb fix(package): update pbf to version 3.2.0 2019-03-11 07:51:32 +00:00
bartvde
9ad2924f00 Update zoom levels for Stamen source 2019-03-11 08:50:28 +01:00
Frederic Junod
0401aed6e3 Add transition options to Stamen source 2019-03-11 08:34:42 +01:00
ahocevar
b2722542fe Simplify vector tile projection handling 2019-03-10 09:37:58 +01:00
Andreas Hocevar
7002053678 Merge pull request #9307 from ahocevar/line-height
Take line-height into account when measuring text height
2019-03-09 10:07:16 +01:00
Frederic Junod
425cd19922 Add a new font line height option in vector-label example 2019-03-09 09:59:57 +01:00
ahocevar
4a7b3cde56 Take line-height into account when measuring text height 2019-03-09 08:25:09 +01:00
Frédéric Junod
9c1c71b8e5 Merge pull request #9306 from fredj/dispose_layer_renderer
Set the canvas size to 0 on dispose
2019-03-09 08:00:35 +01:00
Frederic Junod
6de566d95b Set the canvas size to 0 on dispose 2019-03-08 13:25:49 +01:00
Andreas Hocevar
a372ca0569 Merge pull request #9295 from ahocevar/update-ol-mapbox-style
Update ol-mapbox-style to fix broken mapbox-style example
2019-03-07 10:10:08 +01:00
Marc Jansen
1e06dd979e Merge pull request #9292 from KaiVolland/downgrade-puppeteer
Downgrades puppeteer to `~1.11.0`
2019-03-06 19:15:35 +01:00
ahocevar
c44e6ebe1d Update ol-mapbox-style to fix broken mapbox-style example 2019-03-06 19:13:33 +01:00
Kai Volland
c8fab42d75 Downgrades puppeteer to ~1.11.0
This downgrades puppeteer to version `~1.11.0`.
This will download the stable version of chromium
(72.0.3582.0). The current puppeteer version
(`^1.12.2`) downloads the beta version of
chromium (73.0.3679.0).
2019-03-06 16:53:45 +01:00
Andreas Hocevar
0995f95ef1 Fix typo in extent-constrained example
Co-Authored-By: jahow <olivier.guyot@camptocamp.com>
2019-03-04 18:11:15 +01:00
Olivier Guyot
447266cbe8 Better smoothing of resolution and center constraints
Previously the formula for the resolution constraint allowed going way past
the minimum zoom.
Also adjusted the center constraint to avoid a zigzag effect when going out
of resolution bounds.
2019-03-04 10:14:06 +01:00
Andreas Hocevar
d25a534dea Merge pull request #9285 from openlayers/greenkeeper/globby-9.1.0
Update globby to the latest version 🚀
2019-03-04 09:53:25 +01:00
greenkeeper[bot]
90749cd80f chore(package): update globby to version 9.1.0 2019-03-03 13:21:03 +00:00
Olivier Guyot
f67baa0dc0 Interactions / fix zoom level when a zoom interaction ends 2019-02-22 15:04:54 +01:00
Olivier Guyot
75c379beeb View / allow specifying an anchor on interaction end
This also fixes a bug where the animation anchor would be lost when outside
the allowed resolution.
2019-02-22 15:04:54 +01:00
Olivier Guyot
405e206717 View / better names for getValid* and applyParameters_ methods 2019-02-22 15:04:54 +01:00
Olivier Guyot
c2af03f152 Update the View documentation & document breaking changes 2019-02-22 15:04:54 +01:00
Olivier Guyot
7835869582 Add an extent restriction on the mapbox layer example
This fixes a bug where the OL map would allow much larger resolution than
mapbox.
2019-02-22 15:01:30 +01:00
Olivier Guyot
78e8f23df5 View / add getValidCenter method to improve interactions
The DragPan, KeyboardPan and DragZoom interactions now make sure to
animate to a valid center/resolution target to avoid
a chained "resolve" animation which looks weird.

The `View.fit` method was also fixed to use this.
2019-02-22 15:01:30 +01:00
Olivier Guyot
433bccd207 Linting and fixes for unit tests 2019-02-22 15:01:30 +01:00
Olivier Guyot
ef6d17d817 View / add a 'smoothResolutionConstraint' options
When enabled (true by default), the resolution min/max values will be applied with
a smoothing effect for a better user experience.
2019-02-22 15:01:30 +01:00
Olivier Guyot
e023c144bb View / add adjust* methods to manipulate the view more easily
API changes:
* (breaking) the `rotate` method is gone
* the `adjustRotation`, `adjustResolution` and `adjustZoom` methods are now
  available and allow using an anchor.

This means interactions do not have to do the anchor computation themselves
and this also fix anchor computation when constraints must be applied.
2019-02-22 15:01:30 +01:00
Olivier Guyot
b5273babb5 View / handle resolutions array with length=1 2019-02-22 15:01:30 +01:00
Olivier Guyot
49662b019c View / add a constrainResolution option
This introduces a breaking change.

This options replaces the various `constrainResolution` options on interactions
and the `fit` method.

Since constraints are now the responsibility of the View, the fact that intermediary
zoom levels are allowed or not is now set in the view options.

By default, the view resolution is unconstrained.
2019-02-22 15:01:30 +01:00
jahow
48ad1ffcbf View / implement a smooth rebound effect when a max extent is given
This is done by applying the center constraint differently when we're in the
middle of an interaction/animation or not.

When the view is moving, the center constraint will restrain the given value
in an "elastic" way, using a logarithmic function.

This can be disabled using the `smoothCenterConstrain` view parameter.
2019-02-22 15:01:30 +01:00
jahow
cd186ada7f Add new tests for View & Interaction w/ fixes 2019-02-22 15:01:30 +01:00
Olivier Guyot
a6f65df8c4 View / add a resolveConstraints method to end interactions
This will help making sure that the view will come back to a "rested" state
once the interactions are over.

Interactions no longer need to handle the animation back to a rested state,
they simply call `endInteraction` with the desired duration and direction.
2019-02-22 15:01:30 +01:00
Olivier Guyot
1c5fd62e43 View / refactor how zoom and resolution are computed
This commit aims to simplify the computation of zoom and resolution in the
View class.

Previously zoom levels and resolution computations were mixed in different places,
ie resolution constraints, initial values, etc.

Now the View class only has the `getZoomForResolution` and `getResolutionForZoom`
methods to convert from one system to another.

Other than that, most computations use the resolution system internally.

The `constrainResolution` method also does not exist anymore, and is replaced
by `getValidResolution` and `getValidZoomLevel` public methods.
2019-02-22 15:01:30 +01:00
Olivier Guyot
1f379a06a4 View / add support for viewport extent constraint
This introduces a breaking change:

The `extent` view option now constrains the whole viewport and not just the
view center.
The option `constrainOnlyCenter` was added to keep the previous behaviour.

Constraining the whole viewport and not only the view center means
that the center and resolution constraints must be applied with a knowledge of
the viewport size.
2019-02-22 15:01:30 +01:00
jahow
e52fab636c View / apply constraints automatically based on hints
All constraints can now function differently if they are applied during
interaction or animation.
2019-02-22 15:01:30 +01:00
jahow
d991dfa54a View / remove constrainCenter method 2019-02-22 15:01:30 +01:00
jahow
c2c1aa01d3 View / removed the constrainRotation method 2019-02-22 15:01:30 +01:00
Olivier Guyot
e6c4b2ffd1 View / make the constrainResolution function private
Other classes should not need to worry about constraining the resolution
or not, as the View will eventually do this on its own.
2019-02-22 15:01:30 +01:00
Olivier Guyot
3c1e3779e2 View / add a method to compute a valid zoom level
The `getValidZoomLevel` apply the current resolution constraint to return
a value that is guaranteed valid.

This is used for interactions & controls which need a target value to work:
the +/- buttons, the zoom clider, the dragbox zoom and the mouse wheel zoom.
2019-02-22 15:01:30 +01:00
Olivier Guyot
4e1ece16ed View / implemented begin- and endInteraction methods 2019-02-22 15:01:30 +01:00
Olivier Guyot
1cb934dbe3 View / implement intermediate values for center/rot/res
The view now has targetCenter, targetRotation and targetResolution members.

These hold the new values given by set* methods. The actual view parameters are then changed by
calling `applyParameters_`.
2019-02-22 15:01:30 +01:00
52 changed files with 1515 additions and 1048 deletions

View File

@@ -4,6 +4,24 @@
#### Backwards incompatible changes
##### The `setCenter`, `setZoom`, `setResolution` and `setRotation` methods on `ol/View` do not bypass constraints anymore
Previously, these methods allowed setting values that were inconsistent with the given view constraints.
This is no longer the case and all changes to the view state now follow the same logic:
target values are provided and constraints are applied on these to determine the actual values to be used.
##### Removal of the `constrainResolution` option on `View.fit`, `PinchZoom`, `MouseWheelZoom` and `ol/interaction.js`
The `constrainResolution` option is now only supported by the `View` class. A `View.setResolutionConstrained` method was added as well.
Generally, the responsibility of applying center/rotation/resolutions constraints was moved from interactions and controls to the `View` class.
##### The view `extent` option now applies to the whole viewport
Previously, this options only constrained the view *center*. This behaviour can still be obtained by specifying `constrainCenterOnly` in the view options.
As a side effect, the view `rotate` method is gone and has been replaced with `adjustRotation` which takes a delta as input.
##### Removal of deprecated methods
The `inherits` function that was used to inherit the prototype methods from one constructor into another has been removed.
@@ -99,6 +117,58 @@ 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.
##### `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
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. */
/* 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. */
// "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. */

View File

@@ -21,8 +21,6 @@ tags: "center, rotation, openstreetmap"
<div class="padding-bottom"></div>
<div class="center"></div>
</div>
<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="zoomtoswitzerland">Zoom to Switzerland</button> (best fit).<br/>
<button id="zoomtolausanne">Zoom to Lausanne</button> (with min resolution),<br/>
<button id="centerlausanne">Center on Lausanne</button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,10 +24,10 @@ class VectorTile extends Tile {
this.consumers = 0;
/**
* @private
* Extent of this tile; set by the source.
* @type {import("./extent.js").Extent}
*/
this.extent_ = null;
this.extent = null;
/**
* @private
@@ -48,11 +48,16 @@ class VectorTile extends Tile {
this.loader_;
/**
* Data projection
* @private
* Feature projection of this tile; set by the source.
* @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
@@ -76,15 +81,6 @@ class VectorTile extends Tile {
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.
* @return {import("./format/Feature.js").default} Feature format.
@@ -95,8 +91,7 @@ class VectorTile extends Tile {
}
/**
* Get the features for this tile. Geometries will be in the projection returned
* by {@link module:ol/VectorTile~VectorTile#getProjection}.
* Get the features for this tile. Geometries will be in the view projection.
* @return {Array<import("./Feature.js").FeatureLike>} Features.
* @api
*/
@@ -111,16 +106,6 @@ class VectorTile extends Tile {
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
*/
@@ -128,7 +113,7 @@ class VectorTile extends Tile {
if (this.state == TileState.IDLE) {
this.setState(TileState.LOADING);
this.tileLoadFunction_(this, this.url_);
this.loader_(null, NaN, null);
this.loader_(this.extent, this.resolution, this.projection);
}
}
@@ -136,11 +121,8 @@ class VectorTile extends Tile {
* Handler for successful tile load.
* @param {Array<import("./Feature.js").default>} features The loaded features.
* @param {import("./proj/Projection.js").default} dataProjection Data projection.
* @param {import("./extent.js").Extent} extent Extent.
*/
onLoad(features, dataProjection, extent) {
this.setProjection(dataProjection);
this.setExtent(extent);
onLoad(features, dataProjection) {
this.setFeatures(features);
}
@@ -151,22 +133,6 @@ class VectorTile extends Tile {
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`.
* Sets the features for the tile.
@@ -178,17 +144,6 @@ class VectorTile extends Tile {
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.
* @param {import("./featureloader.js").FeatureLoader} loader Feature loader.

View File

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

View File

@@ -5,26 +5,57 @@ import {clamp} from './math.js';
/**
* @typedef {function((import("./coordinate.js").Coordinate|undefined)): (import("./coordinate.js").Coordinate|undefined)} Type
* @typedef {function((import("./coordinate.js").Coordinate|undefined), number, import("./size.js").Size, boolean=): (import("./coordinate.js").Coordinate|undefined)} Type
*/
/**
* @param {import("./extent.js").Extent} extent Extent.
* @param {boolean} onlyCenter If true, the constraint will only apply to the view center.
* @param {boolean} smooth If true, the view will be able to go slightly out of the given extent
* (only during interaction and animation).
* @return {Type} The constraint.
*/
export function createExtent(extent) {
export function createExtent(extent, onlyCenter, smooth) {
return (
/**
* @param {import("./coordinate.js").Coordinate=} center Center.
* @param {import("./coordinate.js").Coordinate|undefined} center Center.
* @param {number} resolution Resolution.
* @param {import("./size.js").Size} size Viewport size; unused if `onlyCenter` was specified.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {import("./coordinate.js").Coordinate|undefined} Center.
*/
function(center) {
function(center, resolution, size, opt_isMoving) {
if (center) {
return [
clamp(center[0], extent[0], extent[2]),
clamp(center[1], extent[1], extent[3])
];
const viewWidth = onlyCenter ? 0 : size[0] * resolution;
const viewHeight = onlyCenter ? 0 : size[1] * resolution;
let minX = extent[0] + viewWidth / 2;
let maxX = extent[2] - viewWidth / 2;
let minY = extent[1] + viewHeight / 2;
let maxY = extent[3] - viewHeight / 2;
// note: when zooming out of bounds, min and max values for x and y may
// end up inverted (min > max); this has to be accounted for
if (minX > maxX) {
minX = maxX = (maxX + minX) / 2;
}
if (minY > maxY) {
minY = maxY = (maxY + minY) / 2;
}
let x = clamp(center[0], minX, maxX);
let y = clamp(center[1], minY, maxY);
const ratio = 30 * resolution;
// during an interaction, allow some overscroll
if (opt_isMoving && smooth) {
x += -ratio * Math.log(1 + Math.max(0, minX - center[0]) / ratio) +
ratio * Math.log(1 + Math.max(0, center[0] - maxX) / ratio);
y += -ratio * Math.log(1 + Math.max(0, minY - center[1]) / ratio) +
ratio * Math.log(1 + Math.max(0, center[1] - maxY) / ratio);
}
return [x, y];
} else {
return undefined;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,6 @@ import EventType from '../../events/EventType.js';
import rbush from 'rbush';
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.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 {labelCache} from '../../render/canvas.js';
import CanvasBuilderGroup from '../../render/canvas/BuilderGroup.js';
@@ -152,6 +150,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
*/
disposeInternal() {
unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
this.overlayContext_.canvas.width = this.overlayContext_.canvas.height = 0;
super.disposeInternal();
}
@@ -263,12 +262,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
const sharedExtent = getIntersection(tileExtent, sourceTileExtent);
const bufferedExtent = equals(sourceTileExtent, sharedExtent) ? null :
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;
const builderGroup = new CanvasBuilderGroup(0, sharedExtent, resolution,
pixelRatio, !!this.declutterTree_);
@@ -297,15 +290,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
}
for (let i = 0, ii = features.length; i < ii; ++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())) {
render.call(this, feature);
}

View File

@@ -2,37 +2,87 @@
* @module ol/resolutionconstraint
*/
import {linearFindNearest} from './array.js';
import {clamp} from './math.js';
import {getHeight, getWidth} from './extent';
import {clamp} from './math';
/**
* @typedef {function((number|undefined), number, number): (number|undefined)} Type
* @typedef {function((number|undefined), number, import("./size.js").Size, boolean=): (number|undefined)} Type
*/
/**
* Returns a modified resolution taking into acocunt the viewport size and maximum
* allowed extent.
* @param {number} resolution Resolution
* @param {import("./extent.js").Extent=} maxExtent Maximum allowed extent.
* @param {import("./size.js").Size} viewportSize Viewport size.
* @return {number} Capped resolution.
*/
function getViewportClampedResolution(resolution, maxExtent, viewportSize) {
const xResolution = getWidth(maxExtent) / viewportSize[0];
const yResolution = getHeight(maxExtent) / viewportSize[1];
return Math.min(resolution, Math.min(xResolution, yResolution));
}
/**
* Returns a modified resolution to be between maxResolution and minResolution while
* still allowing the value to be slightly out of bounds.
* Note: the computation is based on the logarithm function (ln):
* - at 1, ln(x) is 0
* - above 1, ln(x) keeps increasing but at a much slower pace than x
* The final result is clamped to prevent getting too far away from bounds.
* @param {number} resolution Resolution.
* @param {number} maxResolution Max resolution.
* @param {number} minResolution Min resolution.
* @return {number} Smoothed resolution.
*/
function getSmoothClampedResolution(resolution, maxResolution, minResolution) {
let result = Math.min(resolution, maxResolution);
const ratio = 50;
result *= Math.log(1 + ratio * Math.max(0, resolution / maxResolution - 1)) / ratio + 1;
if (minResolution) {
result = Math.max(result, minResolution);
result /= Math.log(1 + ratio * Math.max(0, minResolution / resolution - 1)) / ratio + 1;
}
return clamp(result, minResolution / 2, maxResolution * 2);
}
/**
* @param {Array<number>} resolutions Resolutions.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function.
*/
export function createSnapToResolutions(resolutions) {
export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
* @param {number} delta Delta.
* @param {number} direction Direction.
* @param {import("./size.js").Size} size Viewport size.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Resolution.
*/
function(resolution, delta, direction) {
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
let z = linearFindNearest(resolutions, resolution, direction);
z = clamp(z + delta, 0, resolutions.length - 1);
const index = Math.floor(z);
if (z != index && index < resolutions.length - 1) {
const power = resolutions[index] / resolutions[index + 1];
return resolutions[index] / Math.pow(power, z - index);
} else {
return resolutions[index];
const maxResolution = resolutions[0];
const minResolution = resolutions[resolutions.length - 1];
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
maxResolution;
// during interacting or animating, allow intermediary values
if (opt_isMoving) {
const smooth = opt_smooth !== undefined ? opt_smooth : true;
if (!smooth) {
return clamp(resolution, minResolution, cappedMaxRes);
}
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
}
const capped = Math.min(cappedMaxRes, resolution);
const z = Math.floor(linearFindNearest(resolutions, capped, direction));
return resolutions[z];
} else {
return undefined;
}
@@ -44,29 +94,78 @@ export function createSnapToResolutions(resolutions) {
/**
* @param {number} power Power.
* @param {number} maxResolution Maximum resolution.
* @param {number=} opt_maxLevel Maximum level.
* @param {number=} opt_minResolution Minimum resolution.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function.
*/
export function createSnapToPower(power, maxResolution, opt_maxLevel) {
export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
* @param {number} delta Delta.
* @param {number} direction Direction.
* @param {import("./size.js").Size} size Viewport size.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Resolution.
*/
function(resolution, delta, direction) {
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
const offset = -direction / 2 + 0.5;
const oldLevel = Math.floor(
Math.log(maxResolution / resolution) / Math.log(power) + offset);
let newLevel = Math.max(oldLevel + delta, 0);
if (opt_maxLevel !== undefined) {
newLevel = Math.min(newLevel, opt_maxLevel);
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
maxResolution;
const minResolution = opt_minResolution !== undefined ? opt_minResolution : 0;
// during interacting or animating, allow intermediary values
if (opt_isMoving) {
const smooth = opt_smooth !== undefined ? opt_smooth : true;
if (!smooth) {
return clamp(resolution, minResolution, cappedMaxRes);
}
return maxResolution / Math.pow(power, newLevel);
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
}
const offset = -direction * (0.5 - 1e-9) + 0.5;
const capped = Math.min(cappedMaxRes, resolution);
const zoomLevel = Math.floor(
Math.log(maxResolution / capped) / Math.log(power) + offset);
const newResolution = maxResolution / Math.pow(power, zoomLevel);
return clamp(newResolution, minResolution, cappedMaxRes);
} else {
return undefined;
}
});
}
/**
* @param {number} maxResolution Max resolution.
* @param {number} minResolution Min resolution.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function.
*/
export function createMinMaxResolution(maxResolution, minResolution, opt_smooth, opt_maxExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
* @param {number} direction Direction.
* @param {import("./size.js").Size} size Viewport size.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Resolution.
*/
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
maxResolution;
const smooth = opt_smooth !== undefined ? opt_smooth : true;
if (!smooth || !opt_isMoving) {
return clamp(resolution, minResolution, cappedMaxRes);
}
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
} else {
return undefined;
}
}
);
}

View File

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

View File

@@ -74,7 +74,7 @@ const LayerConfig = {
*/
const ProviderConfig = {
'terrain': {
minZoom: 4,
minZoom: 0,
maxZoom: 18
},
'toner': {
@@ -82,8 +82,8 @@ const ProviderConfig = {
maxZoom: 20
},
'watercolor': {
minZoom: 1,
maxZoom: 16
minZoom: 0,
maxZoom: 18
}
};
@@ -103,6 +103,8 @@ const ProviderConfig = {
* 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 {boolean} [wrapX=true] Whether to wrap the world horizontally.
*/
@@ -137,6 +139,7 @@ class Stamen extends XYZ {
opaque: layerConfig.opaque,
reprojectionErrorThreshold: options.reprojectionErrorThreshold,
tileLoadFunction: options.tileLoadFunction,
transition: options.transition,
url: url,
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
* boundaries or TopoJSON sources) allows the renderer to optimise fill and
* stroke operations.
* @property {import("../proj.js").ProjectionLike} [projection] Projection. Default is the view projection.
* @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Projection of the tile grid.
* @property {import("./State.js").default} [state] Source state.
* @property {typeof import("../VectorTile.js").default} [tileClass] Class used to instantiate image tiles.
* Default is {@link module:ol/VectorTile}.
@@ -35,18 +35,20 @@ import {equals} from '../array.js';
* @property {number|import("../size.js").Size} [tileSize=512] Optional tile size.
* @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
* @property {import("../Tile.js").LoadFunction} [tileLoadFunction]
* Optional function to load a tile given a URL. Could look like this:
* Optional function to load a tile given a URL. Could look like this for pbf tiles:
* ```js
* function(tile, url) {
* tile.setLoader(function() {
* var data = // ... fetch data
* var format = tile.getFormat();
* tile.setProjection(format.readProjection(data));
* tile.setExtent(format.getLastExtent()); // line only required for ol/format/MVT
* tile.setFeatures(format.readFeatures(data, {
* // featureProjection is not required for ol/format/MVT
* featureProjection: map.getView().getProjection()
* }));
* tile.setLoader(function(extent, resolution, projection) {
* fetch(url).then(function(response) {
* response.arrayBuffer().then(function(data) {
* const format = tile.getFormat() // ol/format/MVT configured as source format
* const features = format.readFeatures(data, {
* extent: extent,
* featureProjection: projection
* });
* tile.setFeatures(features);
* });
* });
* }
* });
* ```
@@ -187,9 +189,9 @@ class VectorTile extends UrlTile {
const sourceTileGrid = this.tileGrid;
const sourceExtent = sourceTileGrid.getExtent();
if (sourceExtent) {
getIntersection(extent, sourceTileGrid.getExtent(), extent);
getIntersection(extent, sourceExtent, extent);
}
const sourceZ = sourceTileGrid.getZForResolution(resolution);
const sourceZ = sourceTileGrid.getZForResolution(resolution/*, 1*/);
const minZoom = sourceTileGrid.getMinZoom();
let loadedZ = sourceZ + 1;
@@ -215,6 +217,9 @@ class VectorTile extends UrlTile {
tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
tileUrl == undefined ? '' : tileUrl,
this.format_, this.tileLoadFunction);
sourceTile.extent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
sourceTile.projection = projection;
sourceTile.resolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
this.sourceTiles_[tileKey] = sourceTile;
empty = empty && sourceTile.getState() === TileState.EMPTY;
listen(sourceTile, EventType.CHANGE, this.handleTileChange, this);

View File

@@ -76,7 +76,7 @@ main() {
npm install
npm run build-package
cd ${BUILT_PACKAGE}
npm publish
npm publish --tag beta
}
if test ${#} -ne 1; then

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,6 +76,14 @@ 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() {
it('rotates a canvas at an offset point', function() {

View File

@@ -9,8 +9,7 @@ import {getCenter} from '../../../../../src/ol/extent.js';
import MVT from '../../../../../src/ol/format/MVT.js';
import Point from '../../../../../src/ol/geom/Point.js';
import VectorTileLayer from '../../../../../src/ol/layer/VectorTile.js';
import {get as getProjection, fromLonLat} from '../../../../../src/ol/proj.js';
import Projection from '../../../../../src/ol/proj/Projection.js';
import {get as getProjection} from '../../../../../src/ol/proj.js';
import {checkedFonts} from '../../../../../src/ol/render/canvas.js';
import RenderFeature from '../../../../../src/ol/render/Feature.js';
import CanvasVectorTileLayerRenderer from '../../../../../src/ol/renderer/canvas/VectorTileLayer.js';
@@ -64,7 +63,6 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
constructor() {
super(...arguments);
this.setFeatures([feature1, feature2, feature3]);
this.setProjection(getProjection('EPSG:4326'));
this.setState(TileState.LOADED);
tileCallback(this);
}
@@ -188,30 +186,6 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
}, 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() {
const layer2 = new VectorTileLayer({
source: source,
@@ -240,7 +214,6 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
})
});
const sourceTile = new VectorTile([0, 0, 0], 2);
sourceTile.setProjection(getProjection('EPSG:3857'));
sourceTile.features_ = [];
sourceTile.getImage = function() {
return document.createElement('canvas');
@@ -299,7 +272,6 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
beforeEach(function() {
const sourceTile = new VectorTile([0, 0, 0]);
sourceTile.setState(TileState.LOADED);
sourceTile.setProjection(getProjection('EPSG:3857'));
source = new VectorTileSource({
tileClass: TileClass,
tileGrid: createXYZ()

View File

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

View File

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

View File

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