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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ import {linearRingIsClockwise} from '../geom/flat/orient.js';
import Projection from '../proj/Projection.js'; import Projection from '../proj/Projection.js';
import Units from '../proj/Units.js'; import Units from '../proj/Units.js';
import RenderFeature from '../render/Feature.js'; import RenderFeature from '../render/Feature.js';
import {get} from '../proj.js';
/** /**
@@ -83,12 +84,6 @@ class MVT extends FeatureFormat {
*/ */
this.layers_ = options.layers ? options.layers : null; this.layers_ = options.layers ? options.layers : null;
/**
* @private
* @type {import("../extent.js").Extent}
*/
this.extent_ = null;
} }
/** /**
@@ -159,10 +154,10 @@ class MVT extends FeatureFormat {
* @private * @private
* @param {PBF} pbf PBF * @param {PBF} pbf PBF
* @param {Object} rawFeature Raw Mapbox feature. * @param {Object} rawFeature Raw Mapbox feature.
* @param {import("./Feature.js").ReadOptions=} opt_options Read options. * @param {import("./Feature.js").ReadOptions} options Read options.
* @return {import("../Feature.js").FeatureLike} Feature. * @return {import("../Feature.js").FeatureLike} Feature.
*/ */
createFeature_(pbf, rawFeature, opt_options) { createFeature_(pbf, rawFeature, options) {
const type = rawFeature.type; const type = rawFeature.type;
if (type === 0) { if (type === 0) {
return null; return null;
@@ -181,6 +176,7 @@ class MVT extends FeatureFormat {
if (this.featureClass_ === RenderFeature) { if (this.featureClass_ === RenderFeature) {
feature = new this.featureClass_(geometryType, flatCoordinates, ends, values, id); feature = new this.featureClass_(geometryType, flatCoordinates, ends, values, id);
feature.transform(options.dataProjection, options.featureProjection);
} else { } else {
let geom; let geom;
if (geometryType == GeometryType.POLYGON) { if (geometryType == GeometryType.POLYGON) {
@@ -213,7 +209,7 @@ class MVT extends FeatureFormat {
if (this.geometryName_) { if (this.geometryName_) {
feature.setGeometryName(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.setGeometry(geometry);
feature.setId(id); feature.setId(id);
feature.setProperties(values, true); feature.setProperties(values, true);
@@ -222,14 +218,6 @@ class MVT extends FeatureFormat {
return feature; return feature;
} }
/**
* @inheritDoc
* @api
*/
getLastExtent() {
return this.extent_;
}
/** /**
* @inheritDoc * @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 * @api
*/ */
readFeatures(source, opt_options) { readFeatures(source, opt_options) {
const layers = this.layers_; const layers = this.layers_;
const options = /** @type {import("./Feature.js").ReadOptions} */ (this.adaptOptions(opt_options));
const dataProjection = get(options.dataProjection);
dataProjection.setWorldExtent(options.extent);
options.dataProjection = dataProjection;
const pbf = new PBF(/** @type {ArrayBuffer} */ (source)); const pbf = new PBF(/** @type {ArrayBuffer} */ (source));
const pbfLayers = pbf.readFields(layersPBFReader, {}); const pbfLayers = pbf.readFields(layersPBFReader, {});
/** @type {Array<import("../Feature.js").FeatureLike>} */
const features = []; const features = [];
for (const name in pbfLayers) { for (const name in pbfLayers) {
if (layers && layers.indexOf(name) == -1) { if (layers && layers.indexOf(name) == -1) {
@@ -254,11 +249,13 @@ class MVT extends FeatureFormat {
} }
const pbfLayer = pbfLayers[name]; const pbfLayer = pbfLayers[name];
const extent = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
dataProjection.setExtent(extent);
for (let i = 0, ii = pbfLayer.length; i < ii; ++i) { for (let i = 0, ii = pbfLayer.length; i < ii; ++i) {
const rawFeature = readRawFeature(pbf, pbfLayer, i); const rawFeature = readRawFeature(pbf, pbfLayer, i);
features.push(this.createFeature_(pbf, rawFeature)); features.push(this.createFeature_(pbf, rawFeature, options));
} }
this.extent_ = pbfLayer ? [0, 0, pbfLayer.extent, pbfLayer.extent] : null;
} }
return features; 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 * focus. This affects the `MouseWheelZoom` and `DragPan` interactions and is
* useful when page scroll is desired for maps that do not have the browser's * useful when page scroll is desired for maps that do not have the browser's
* focus. * focus.
* @property {boolean} [constrainResolution=false] Zoom to the closest integer
* zoom level after the wheel/trackpad or pinch gesture ends.
* @property {boolean} [doubleClickZoom=true] Whether double click zoom is * @property {boolean} [doubleClickZoom=true] Whether double click zoom is
* desired. * desired.
* @property {boolean} [keyboard=true] Whether keyboard interaction is desired. * @property {boolean} [keyboard=true] Whether keyboard interaction is desired.
@@ -127,7 +125,6 @@ export function defaults(opt_options) {
const pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true; const pinchZoom = options.pinchZoom !== undefined ? options.pinchZoom : true;
if (pinchZoom) { if (pinchZoom) {
interactions.push(new PinchZoom({ interactions.push(new PinchZoom({
constrainResolution: options.constrainResolution,
duration: options.zoomDuration duration: options.zoomDuration
})); }));
} }
@@ -146,7 +143,6 @@ export function defaults(opt_options) {
if (mouseWheelZoom) { if (mouseWheelZoom) {
interactions.push(new MouseWheelZoom({ interactions.push(new MouseWheelZoom({
condition: options.onFocusOnly ? focus : undefined, condition: options.onFocusOnly ? focus : undefined,
constrainResolution: options.constrainResolution,
duration: options.zoomDuration duration: options.zoomDuration
})); }));
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,37 +2,87 @@
* @module ol/resolutionconstraint * @module ol/resolutionconstraint
*/ */
import {linearFindNearest} from './array.js'; 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 {Array<number>} resolutions Resolutions.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function. * @return {Type} Zoom function.
*/ */
export function createSnapToResolutions(resolutions) { export function createSnapToResolutions(resolutions, opt_smooth, opt_maxExtent) {
return ( return (
/** /**
* @param {number|undefined} resolution Resolution. * @param {number|undefined} resolution Resolution.
* @param {number} delta Delta.
* @param {number} direction Direction. * @param {number} direction Direction.
* @param {import("./size.js").Size} size Viewport size.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Resolution. * @return {number|undefined} Resolution.
*/ */
function(resolution, delta, direction) { function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) { if (resolution !== undefined) {
let z = linearFindNearest(resolutions, resolution, direction); const maxResolution = resolutions[0];
z = clamp(z + delta, 0, resolutions.length - 1); const minResolution = resolutions[resolutions.length - 1];
const index = Math.floor(z); const cappedMaxRes = opt_maxExtent ?
if (z != index && index < resolutions.length - 1) { getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
const power = resolutions[index] / resolutions[index + 1]; maxResolution;
return resolutions[index] / Math.pow(power, z - index);
} else { // during interacting or animating, allow intermediary values
return resolutions[index]; 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 { } else {
return undefined; return undefined;
} }
@@ -44,29 +94,78 @@ export function createSnapToResolutions(resolutions) {
/** /**
* @param {number} power Power. * @param {number} power Power.
* @param {number} maxResolution Maximum resolution. * @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. * @return {Type} Zoom function.
*/ */
export function createSnapToPower(power, maxResolution, opt_maxLevel) { export function createSnapToPower(power, maxResolution, opt_minResolution, opt_smooth, opt_maxExtent) {
return ( return (
/** /**
* @param {number|undefined} resolution Resolution. * @param {number|undefined} resolution Resolution.
* @param {number} delta Delta.
* @param {number} direction Direction. * @param {number} direction Direction.
* @param {import("./size.js").Size} size Viewport size.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Resolution. * @return {number|undefined} Resolution.
*/ */
function(resolution, delta, direction) { function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) { if (resolution !== undefined) {
const offset = -direction / 2 + 0.5; const cappedMaxRes = opt_maxExtent ?
const oldLevel = Math.floor( getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
Math.log(maxResolution / resolution) / Math.log(power) + offset); maxResolution;
let newLevel = Math.max(oldLevel + delta, 0); const minResolution = opt_minResolution !== undefined ? opt_minResolution : 0;
if (opt_maxLevel !== undefined) {
newLevel = Math.min(newLevel, opt_maxLevel); // during interacting or animating, allow intermediary values
if (opt_isMoving) {
const smooth = opt_smooth !== undefined ? opt_smooth : true;
if (!smooth) {
return clamp(resolution, minResolution, cappedMaxRes);
}
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
} }
return maxResolution / Math.pow(power, newLevel);
const offset = -direction * (0.5 - 1e-9) + 0.5;
const capped = Math.min(cappedMaxRes, resolution);
const zoomLevel = Math.floor(
Math.log(maxResolution / capped) / Math.log(power) + offset);
const newResolution = maxResolution / Math.pow(power, zoomLevel);
return clamp(newResolution, minResolution, cappedMaxRes);
} else { } else {
return undefined; return undefined;
} }
}); });
} }
/**
* @param {number} maxResolution Max resolution.
* @param {number} minResolution Min resolution.
* @param {boolean=} opt_smooth If true, the view will be able to slightly exceed resolution limits. Default: true.
* @param {import("./extent.js").Extent=} opt_maxExtent Maximum allowed extent.
* @return {Type} Zoom function.
*/
export function createMinMaxResolution(maxResolution, minResolution, opt_smooth, opt_maxExtent) {
return (
/**
* @param {number|undefined} resolution Resolution.
* @param {number} direction Direction.
* @param {import("./size.js").Size} size Viewport size.
* @param {boolean=} opt_isMoving True if an interaction or animation is in progress.
* @return {number|undefined} Resolution.
*/
function(resolution, direction, size, opt_isMoving) {
if (resolution !== undefined) {
const cappedMaxRes = opt_maxExtent ?
getViewportClampedResolution(maxResolution, opt_maxExtent, size) :
maxResolution;
const smooth = opt_smooth !== undefined ? opt_smooth : true;
if (!smooth || !opt_isMoving) {
return clamp(resolution, minResolution, cappedMaxRes);
}
return getSmoothClampedResolution(resolution, cappedMaxRes, minResolution);
} else {
return undefined;
}
}
);
}

View File

@@ -5,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|undefined} rotation Rotation.
* @param {number} delta Delta.
* @return {number|undefined} Rotation. * @return {number|undefined} Rotation.
*/ */
export function disable(rotation, delta) { export function disable(rotation) {
if (rotation !== undefined) { if (rotation !== undefined) {
return 0; return 0;
} else { } else {
@@ -25,12 +24,11 @@ export function disable(rotation, delta) {
/** /**
* @param {number|undefined} rotation Rotation. * @param {number|undefined} rotation Rotation.
* @param {number} delta Delta.
* @return {number|undefined} Rotation. * @return {number|undefined} Rotation.
*/ */
export function none(rotation, delta) { export function none(rotation) {
if (rotation !== undefined) { if (rotation !== undefined) {
return rotation + delta; return rotation;
} else { } else {
return undefined; return undefined;
} }
@@ -46,12 +44,16 @@ export function createSnapToN(n) {
return ( return (
/** /**
* @param {number|undefined} rotation Rotation. * @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. * @return {number|undefined} Rotation.
*/ */
function(rotation, delta) { function(rotation, opt_isMoving) {
if (opt_isMoving) {
return rotation;
}
if (rotation !== undefined) { if (rotation !== undefined) {
rotation = Math.floor((rotation + delta) / theta + 0.5) * theta; rotation = Math.floor(rotation / theta + 0.5) * theta;
return rotation; return rotation;
} else { } else {
return undefined; return undefined;
@@ -69,15 +71,19 @@ export function createSnapToZero(opt_tolerance) {
return ( return (
/** /**
* @param {number|undefined} rotation Rotation. * @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. * @return {number|undefined} Rotation.
*/ */
function(rotation, delta) { function(rotation, opt_isMoving) {
if (opt_isMoving) {
return rotation;
}
if (rotation !== undefined) { if (rotation !== undefined) {
if (Math.abs(rotation + delta) <= tolerance) { if (Math.abs(rotation) <= tolerance) {
return 0; return 0;
} else { } else {
return rotation + delta; return rotation;
} }
} else { } else {
return undefined; return undefined;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import Map from '../../../../src/ol/Map.js'; import Map from '../../../../src/ol/Map.js';
import View from '../../../../src/ol/View.js';
import EventTarget from '../../../../src/ol/events/Target.js'; import EventTarget from '../../../../src/ol/events/Target.js';
import Interaction, {zoomByDelta} from '../../../../src/ol/interaction/Interaction.js'; import Interaction from '../../../../src/ol/interaction/Interaction.js';
import {FALSE} from '../../../../src/ol/functions.js'; import {FALSE} from '../../../../src/ol/functions.js';
describe('ol.interaction.Interaction', function() { describe('ol.interaction.Interaction', function() {
@@ -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); const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1); expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(MouseWheelZoom); expect(interactions.item(0)).to.be.a(MouseWheelZoom);
expect(interactions.item(0).constrainResolution_).to.eql(false);
expect(interactions.item(0).useAnchor_).to.eql(true); expect(interactions.item(0).useAnchor_).to.eql(true);
interactions.item(0).setMouseAnchor(false); interactions.item(0).setMouseAnchor(false);
expect(interactions.item(0).useAnchor_).to.eql(false); expect(interactions.item(0).useAnchor_).to.eql(false);
@@ -601,21 +600,6 @@ describe('ol.Map', function() {
const interactions = defaultInteractions(options); const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(1); expect(interactions.getLength()).to.eql(1);
expect(interactions.item(0)).to.be.a(PinchZoom); expect(interactions.item(0)).to.be.a(PinchZoom);
expect(interactions.item(0).constrainResolution_).to.eql(false);
});
});
describe('set constrainResolution option', function() {
it('set constrainResolution option', function() {
options.pinchZoom = true;
options.mouseWheelZoom = true;
options.constrainResolution = true;
const interactions = defaultInteractions(options);
expect(interactions.getLength()).to.eql(2);
expect(interactions.item(0)).to.be.a(PinchZoom);
expect(interactions.item(0).constrainResolution_).to.eql(true);
expect(interactions.item(1)).to.be.a(MouseWheelZoom);
expect(interactions.item(1).constrainResolution_).to.eql(true);
}); });
}); });

View File

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

View File

@@ -1,4 +1,5 @@
import {createSnapToResolutions, createSnapToPower} from '../../../src/ol/resolutionconstraint.js'; import {createSnapToResolutions, createSnapToPower} from '../../../src/ol/resolutionconstraint.js';
import {createMinMaxResolution} from '../../../src/ol/resolutionconstraint';
describe('ol.resolutionconstraint', function() { describe('ol.resolutionconstraint', function() {
@@ -12,30 +13,30 @@ describe('ol.resolutionconstraint', function() {
[1000, 500, 250, 100]); [1000, 500, 250, 100]);
}); });
describe('delta 0', function() { describe('direction 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1000, 0, 0)).to.eql(1000); expect(resolutionConstraint(1000, 0)).to.eql(1000);
expect(resolutionConstraint(500, 0, 0)).to.eql(500); expect(resolutionConstraint(500, 0)).to.eql(500);
expect(resolutionConstraint(250, 0, 0)).to.eql(250); expect(resolutionConstraint(250, 0)).to.eql(250);
expect(resolutionConstraint(100, 0, 0)).to.eql(100); expect(resolutionConstraint(100, 0)).to.eql(100);
}); });
}); });
describe('zoom in', function() { describe('direction 1', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1000, 1, 0)).to.eql(500); expect(resolutionConstraint(1000, 1)).to.eql(1000);
expect(resolutionConstraint(500, 1, 0)).to.eql(250); expect(resolutionConstraint(500, 1)).to.eql(500);
expect(resolutionConstraint(250, 1, 0)).to.eql(100); expect(resolutionConstraint(250, 1)).to.eql(250);
expect(resolutionConstraint(100, 1, 0)).to.eql(100); expect(resolutionConstraint(100, 1)).to.eql(100);
}); });
}); });
describe('zoom out', function() { describe('direction -1', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1000, -1, 0)).to.eql(1000); expect(resolutionConstraint(1000, -1)).to.eql(1000);
expect(resolutionConstraint(500, -1, 0)).to.eql(1000); expect(resolutionConstraint(500, -1)).to.eql(500);
expect(resolutionConstraint(250, -1, 0)).to.eql(500); expect(resolutionConstraint(250, -1)).to.eql(250);
expect(resolutionConstraint(100, -1, 0)).to.eql(250); expect(resolutionConstraint(100, -1)).to.eql(100);
}); });
}); });
}); });
@@ -50,42 +51,42 @@ describe('ol.resolutionconstraint', function() {
[1000, 500, 250, 100]); [1000, 500, 250, 100]);
}); });
describe('delta 0', function() { describe('direction 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 0, 0)).to.eql(1000); expect(resolutionConstraint(1050, 0)).to.eql(1000);
expect(resolutionConstraint(950, 0, 0)).to.eql(1000); expect(resolutionConstraint(950, 0)).to.eql(1000);
expect(resolutionConstraint(550, 0, 0)).to.eql(500); expect(resolutionConstraint(550, 0)).to.eql(500);
expect(resolutionConstraint(400, 0, 0)).to.eql(500); expect(resolutionConstraint(400, 0)).to.eql(500);
expect(resolutionConstraint(300, 0, 0)).to.eql(250); expect(resolutionConstraint(300, 0)).to.eql(250);
expect(resolutionConstraint(200, 0, 0)).to.eql(250); expect(resolutionConstraint(200, 0)).to.eql(250);
expect(resolutionConstraint(150, 0, 0)).to.eql(100); expect(resolutionConstraint(150, 0)).to.eql(100);
expect(resolutionConstraint(50, 0, 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() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 1, 0)).to.eql(500); expect(resolutionConstraint(1050, 1)).to.eql(1000);
expect(resolutionConstraint(950, 1, 0)).to.eql(500); expect(resolutionConstraint(950, 1)).to.eql(1000);
expect(resolutionConstraint(550, 1, 0)).to.eql(250); expect(resolutionConstraint(550, 1)).to.eql(1000);
expect(resolutionConstraint(450, 1, 0)).to.eql(250); expect(resolutionConstraint(450, 1)).to.eql(500);
expect(resolutionConstraint(300, 1, 0)).to.eql(100); expect(resolutionConstraint(300, 1)).to.eql(500);
expect(resolutionConstraint(200, 1, 0)).to.eql(100); expect(resolutionConstraint(200, 1)).to.eql(250);
expect(resolutionConstraint(150, 1, 0)).to.eql(100); expect(resolutionConstraint(150, 1)).to.eql(250);
expect(resolutionConstraint(50, 1, 0)).to.eql(100); expect(resolutionConstraint(50, 1)).to.eql(100);
}); });
}); });
describe('zoom out', function() { describe('direction -1', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, -1, 0)).to.eql(1000); expect(resolutionConstraint(1050, -1)).to.eql(1000);
expect(resolutionConstraint(950, -1, 0)).to.eql(1000); expect(resolutionConstraint(950, -1)).to.eql(500);
expect(resolutionConstraint(550, -1, 0)).to.eql(1000); expect(resolutionConstraint(550, -1)).to.eql(500);
expect(resolutionConstraint(450, -1, 0)).to.eql(1000); expect(resolutionConstraint(450, -1)).to.eql(250);
expect(resolutionConstraint(300, -1, 0)).to.eql(500); expect(resolutionConstraint(300, -1)).to.eql(250);
expect(resolutionConstraint(200, -1, 0)).to.eql(500); expect(resolutionConstraint(200, -1)).to.eql(100);
expect(resolutionConstraint(150, -1, 0)).to.eql(250); expect(resolutionConstraint(150, -1)).to.eql(100);
expect(resolutionConstraint(50, -1, 0)).to.eql(250); expect(resolutionConstraint(50, -1)).to.eql(100);
}); });
}); });
}); });
@@ -96,54 +97,54 @@ describe('ol.resolutionconstraint', function() {
beforeEach(function() { beforeEach(function() {
resolutionConstraint = resolutionConstraint =
createSnapToPower(2, 1024, 10); createSnapToPower(2, 1024, 1);
}); });
describe('delta 0', function() { describe('delta 0', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1024, 0, 0)).to.eql(1024); expect(resolutionConstraint(1024, 0)).to.eql(1024);
expect(resolutionConstraint(512, 0, 0)).to.eql(512); expect(resolutionConstraint(512, 0)).to.eql(512);
expect(resolutionConstraint(256, 0, 0)).to.eql(256); expect(resolutionConstraint(256, 0)).to.eql(256);
expect(resolutionConstraint(128, 0, 0)).to.eql(128); expect(resolutionConstraint(128, 0)).to.eql(128);
expect(resolutionConstraint(64, 0, 0)).to.eql(64); expect(resolutionConstraint(64, 0)).to.eql(64);
expect(resolutionConstraint(32, 0, 0)).to.eql(32); expect(resolutionConstraint(32, 0)).to.eql(32);
expect(resolutionConstraint(16, 0, 0)).to.eql(16); expect(resolutionConstraint(16, 0)).to.eql(16);
expect(resolutionConstraint(8, 0, 0)).to.eql(8); expect(resolutionConstraint(8, 0)).to.eql(8);
expect(resolutionConstraint(4, 0, 0)).to.eql(4); expect(resolutionConstraint(4, 0)).to.eql(4);
expect(resolutionConstraint(2, 0, 0)).to.eql(2); expect(resolutionConstraint(2, 0)).to.eql(2);
expect(resolutionConstraint(1, 0, 0)).to.eql(1); expect(resolutionConstraint(1, 0)).to.eql(1);
}); });
}); });
describe('zoom in', function() { describe('direction 1', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1024, 1, 0)).to.eql(512); expect(resolutionConstraint(1024, 1)).to.eql(1024);
expect(resolutionConstraint(512, 1, 0)).to.eql(256); expect(resolutionConstraint(512, 1)).to.eql(512);
expect(resolutionConstraint(256, 1, 0)).to.eql(128); expect(resolutionConstraint(256, 1)).to.eql(256);
expect(resolutionConstraint(128, 1, 0)).to.eql(64); expect(resolutionConstraint(128, 1)).to.eql(128);
expect(resolutionConstraint(64, 1, 0)).to.eql(32); expect(resolutionConstraint(64, 1)).to.eql(64);
expect(resolutionConstraint(32, 1, 0)).to.eql(16); expect(resolutionConstraint(32, 1)).to.eql(32);
expect(resolutionConstraint(16, 1, 0)).to.eql(8); expect(resolutionConstraint(16, 1)).to.eql(16);
expect(resolutionConstraint(8, 1, 0)).to.eql(4); expect(resolutionConstraint(8, 1)).to.eql(8);
expect(resolutionConstraint(4, 1, 0)).to.eql(2); expect(resolutionConstraint(4, 1)).to.eql(4);
expect(resolutionConstraint(2, 1, 0)).to.eql(1); expect(resolutionConstraint(2, 1)).to.eql(2);
expect(resolutionConstraint(1, 1, 0)).to.eql(1); expect(resolutionConstraint(1, 1)).to.eql(1);
}); });
}); });
describe('zoom out', function() { describe('direction -1', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1024, -1, 0)).to.eql(1024); expect(resolutionConstraint(1024, -1)).to.eql(1024);
expect(resolutionConstraint(512, -1, 0)).to.eql(1024); expect(resolutionConstraint(512, -1)).to.eql(512);
expect(resolutionConstraint(256, -1, 0)).to.eql(512); expect(resolutionConstraint(256, -1)).to.eql(256);
expect(resolutionConstraint(128, -1, 0)).to.eql(256); expect(resolutionConstraint(128, -1)).to.eql(128);
expect(resolutionConstraint(64, -1, 0)).to.eql(128); expect(resolutionConstraint(64, -1)).to.eql(64);
expect(resolutionConstraint(32, -1, 0)).to.eql(64); expect(resolutionConstraint(32, -1)).to.eql(32);
expect(resolutionConstraint(16, -1, 0)).to.eql(32); expect(resolutionConstraint(16, -1)).to.eql(16);
expect(resolutionConstraint(8, -1, 0)).to.eql(16); expect(resolutionConstraint(8, -1)).to.eql(8);
expect(resolutionConstraint(4, -1, 0)).to.eql(8); expect(resolutionConstraint(4, -1)).to.eql(4);
expect(resolutionConstraint(2, -1, 0)).to.eql(4); expect(resolutionConstraint(2, -1)).to.eql(2);
expect(resolutionConstraint(1, -1, 0)).to.eql(2); expect(resolutionConstraint(1, -1)).to.eql(1);
}); });
}); });
}); });
@@ -154,88 +155,182 @@ describe('ol.resolutionconstraint', function() {
beforeEach(function() { beforeEach(function() {
resolutionConstraint = 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() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 0, 0)).to.eql(1024); expect(resolutionConstraint(1050, 0)).to.eql(1024);
expect(resolutionConstraint(9050, 0, 0)).to.eql(1024); expect(resolutionConstraint(9050, 0)).to.eql(1024);
expect(resolutionConstraint(550, 0, 0)).to.eql(512); expect(resolutionConstraint(550, 0)).to.eql(512);
expect(resolutionConstraint(450, 0, 0)).to.eql(512); expect(resolutionConstraint(450, 0)).to.eql(512);
expect(resolutionConstraint(300, 0, 0)).to.eql(256); expect(resolutionConstraint(300, 0)).to.eql(256);
expect(resolutionConstraint(250, 0, 0)).to.eql(256); expect(resolutionConstraint(250, 0)).to.eql(256);
expect(resolutionConstraint(150, 0, 0)).to.eql(128); expect(resolutionConstraint(150, 0)).to.eql(128);
expect(resolutionConstraint(100, 0, 0)).to.eql(128); expect(resolutionConstraint(100, 0)).to.eql(128);
expect(resolutionConstraint(75, 0, 0)).to.eql(64); expect(resolutionConstraint(75, 0)).to.eql(64);
expect(resolutionConstraint(50, 0, 0)).to.eql(64); expect(resolutionConstraint(50, 0)).to.eql(64);
expect(resolutionConstraint(40, 0, 0)).to.eql(32); expect(resolutionConstraint(40, 0)).to.eql(32);
expect(resolutionConstraint(30, 0, 0)).to.eql(32); expect(resolutionConstraint(30, 0)).to.eql(32);
expect(resolutionConstraint(20, 0, 0)).to.eql(16); expect(resolutionConstraint(20, 0)).to.eql(16);
expect(resolutionConstraint(12, 0, 0)).to.eql(16); expect(resolutionConstraint(12, 0)).to.eql(16);
expect(resolutionConstraint(9, 0, 0)).to.eql(8); expect(resolutionConstraint(9, 0)).to.eql(8);
expect(resolutionConstraint(7, 0, 0)).to.eql(8); expect(resolutionConstraint(7, 0)).to.eql(8);
expect(resolutionConstraint(5, 0, 0)).to.eql(4); expect(resolutionConstraint(5, 0)).to.eql(4);
expect(resolutionConstraint(3.5, 0, 0)).to.eql(4); expect(resolutionConstraint(3.5, 0)).to.eql(4);
expect(resolutionConstraint(2.1, 0, 0)).to.eql(2); expect(resolutionConstraint(2.1, 0)).to.eql(2);
expect(resolutionConstraint(1.9, 0, 0)).to.eql(2); expect(resolutionConstraint(1.9, 0)).to.eql(2);
expect(resolutionConstraint(1.1, 0, 0)).to.eql(1); expect(resolutionConstraint(1.1, 0)).to.eql(1);
expect(resolutionConstraint(0.9, 0, 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() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 0, 1)).to.eql(1024); expect(resolutionConstraint(1050, 1)).to.eql(1024);
expect(resolutionConstraint(9050, 0, 1)).to.eql(1024); expect(resolutionConstraint(9050, 1)).to.eql(1024);
expect(resolutionConstraint(550, 0, 1)).to.eql(1024); expect(resolutionConstraint(550, 1)).to.eql(1024);
expect(resolutionConstraint(450, 0, 1)).to.eql(512); expect(resolutionConstraint(450, 1)).to.eql(512);
expect(resolutionConstraint(300, 0, 1)).to.eql(512); expect(resolutionConstraint(300, 1)).to.eql(512);
expect(resolutionConstraint(250, 0, 1)).to.eql(256); expect(resolutionConstraint(250, 1)).to.eql(256);
expect(resolutionConstraint(150, 0, 1)).to.eql(256); expect(resolutionConstraint(150, 1)).to.eql(256);
expect(resolutionConstraint(100, 0, 1)).to.eql(128); expect(resolutionConstraint(100, 1)).to.eql(128);
expect(resolutionConstraint(75, 0, 1)).to.eql(128); expect(resolutionConstraint(75, 1)).to.eql(128);
expect(resolutionConstraint(50, 0, 1)).to.eql(64); expect(resolutionConstraint(50, 1)).to.eql(64);
expect(resolutionConstraint(40, 0, 1)).to.eql(64); expect(resolutionConstraint(40, 1)).to.eql(64);
expect(resolutionConstraint(30, 0, 1)).to.eql(32); expect(resolutionConstraint(30, 1)).to.eql(32);
expect(resolutionConstraint(20, 0, 1)).to.eql(32); expect(resolutionConstraint(20, 1)).to.eql(32);
expect(resolutionConstraint(12, 0, 1)).to.eql(16); expect(resolutionConstraint(12, 1)).to.eql(16);
expect(resolutionConstraint(9, 0, 1)).to.eql(16); expect(resolutionConstraint(9, 1)).to.eql(16);
expect(resolutionConstraint(7, 0, 1)).to.eql(8); expect(resolutionConstraint(7, 1)).to.eql(8);
expect(resolutionConstraint(5, 0, 1)).to.eql(8); expect(resolutionConstraint(5, 1)).to.eql(8);
expect(resolutionConstraint(3.5, 0, 1)).to.eql(4); expect(resolutionConstraint(3.5, 1)).to.eql(4);
expect(resolutionConstraint(2.1, 0, 1)).to.eql(4); expect(resolutionConstraint(2.1, 1)).to.eql(4);
expect(resolutionConstraint(1.9, 0, 1)).to.eql(2); expect(resolutionConstraint(1.9, 1)).to.eql(2);
expect(resolutionConstraint(1.1, 0, 1)).to.eql(2); expect(resolutionConstraint(1.1, 1)).to.eql(2);
expect(resolutionConstraint(0.9, 0, 1)).to.eql(1); expect(resolutionConstraint(0.9, 1)).to.eql(1);
}); });
}); });
describe('delta 0, direction < 0', function() { describe('direction -1', function() {
it('returns expected resolution value', function() { it('returns expected resolution value', function() {
expect(resolutionConstraint(1050, 0, -1)).to.eql(1024); expect(resolutionConstraint(1050, -1)).to.eql(1024);
expect(resolutionConstraint(9050, 0, -1)).to.eql(1024); expect(resolutionConstraint(9050, -1)).to.eql(1024);
expect(resolutionConstraint(550, 0, -1)).to.eql(512); expect(resolutionConstraint(550, -1)).to.eql(512);
expect(resolutionConstraint(450, 0, -1)).to.eql(256); expect(resolutionConstraint(450, -1)).to.eql(256);
expect(resolutionConstraint(300, 0, -1)).to.eql(256); expect(resolutionConstraint(300, -1)).to.eql(256);
expect(resolutionConstraint(250, 0, -1)).to.eql(128); expect(resolutionConstraint(250, -1)).to.eql(128);
expect(resolutionConstraint(150, 0, -1)).to.eql(128); expect(resolutionConstraint(150, -1)).to.eql(128);
expect(resolutionConstraint(100, 0, -1)).to.eql(64); expect(resolutionConstraint(100, -1)).to.eql(64);
expect(resolutionConstraint(75, 0, -1)).to.eql(64); expect(resolutionConstraint(75, -1)).to.eql(64);
expect(resolutionConstraint(50, 0, -1)).to.eql(32); expect(resolutionConstraint(50, -1)).to.eql(32);
expect(resolutionConstraint(40, 0, -1)).to.eql(32); expect(resolutionConstraint(40, -1)).to.eql(32);
expect(resolutionConstraint(30, 0, -1)).to.eql(16); expect(resolutionConstraint(30, -1)).to.eql(16);
expect(resolutionConstraint(20, 0, -1)).to.eql(16); expect(resolutionConstraint(20, -1)).to.eql(16);
expect(resolutionConstraint(12, 0, -1)).to.eql(8); expect(resolutionConstraint(12, -1)).to.eql(8);
expect(resolutionConstraint(9, 0, -1)).to.eql(8); expect(resolutionConstraint(9, -1)).to.eql(8);
expect(resolutionConstraint(7, 0, -1)).to.eql(4); expect(resolutionConstraint(7, -1)).to.eql(4);
expect(resolutionConstraint(5, 0, -1)).to.eql(4); expect(resolutionConstraint(5, -1)).to.eql(4);
expect(resolutionConstraint(3.5, 0, -1)).to.eql(2); expect(resolutionConstraint(3.5, -1)).to.eql(2);
expect(resolutionConstraint(2.1, 0, -1)).to.eql(2); expect(resolutionConstraint(2.1, -1)).to.eql(2);
expect(resolutionConstraint(1.9, 0, -1)).to.eql(1); expect(resolutionConstraint(1.9, -1)).to.eql(1);
expect(resolutionConstraint(1.1, 0, -1)).to.eql(1); expect(resolutionConstraint(1.1, -1)).to.eql(1);
expect(resolutionConstraint(0.9, 0, -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() { it('returns expected rotation value', function() {
const rotationConstraint = createSnapToZero(0.3); const rotationConstraint = createSnapToZero(0.3);
expect(rotationConstraint(0.1, 0)).to.eql(0); expect(rotationConstraint(0.1)).to.eql(0);
expect(rotationConstraint(0.2, 0)).to.eql(0); expect(rotationConstraint(0.2)).to.eql(0);
expect(rotationConstraint(0.3, 0)).to.eql(0); expect(rotationConstraint(0.3)).to.eql(0);
expect(rotationConstraint(0.4, 0)).to.eql(0.4); expect(rotationConstraint(0.4)).to.eql(0.4);
expect(rotationConstraint(-0.1, 0)).to.eql(0); expect(rotationConstraint(-0.1)).to.eql(0);
expect(rotationConstraint(-0.2, 0)).to.eql(0); expect(rotationConstraint(-0.2)).to.eql(0);
expect(rotationConstraint(-0.3, 0)).to.eql(0); expect(rotationConstraint(-0.3)).to.eql(0);
expect(rotationConstraint(-0.4, 0)).to.eql(-0.4); expect(rotationConstraint(-0.4)).to.eql(-0.4);
expect(rotationConstraint(1, -0.9)).to.eql(0);
expect(rotationConstraint(1, -0.8)).to.eql(0);
// floating-point arithmetic
expect(rotationConstraint(1, -0.7)).not.to.eql(0);
expect(rotationConstraint(1, -0.6)).to.eql(0.4);
expect(rotationConstraint(-1, 0.9)).to.eql(0);
expect(rotationConstraint(-1, 0.8)).to.eql(0);
// floating-point arithmetic
expect(rotationConstraint(-1, 0.7)).not.to.eql(0);
expect(rotationConstraint(-1, 0.6)).to.eql(-0.4);
}); });
}); });

View File

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

View File

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