diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 647b8a188a..86fd03366e 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -22,6 +22,10 @@ Previously, this options only constrained the view *center*. This behaviour can As a side effect, the view `rotate` method is gone and has been replaced with `adjustRotation` which takes a delta as input. +##### Zoom is constrained so only one world is visible + +Previously, maps showed multiple worlds at low zoom levels. Now, the view is restricted to show only one world. To get the previous behavior, configure the `ol/View` with `multiWorld: true`. + ##### Removal of deprecated methods The `inherits` function that was used to inherit the prototype methods from one constructor into another has been removed. diff --git a/rendering/cases/map-pan/main.js b/rendering/cases/map-pan/main.js index e5569792cb..69334de26c 100644 --- a/rendering/cases/map-pan/main.js +++ b/rendering/cases/map-pan/main.js @@ -20,7 +20,8 @@ const map = new Map({ view: new View({ projection: 'EPSG:4326', center: [0, 0], - resolution: 1 + resolution: 1, + multiWorld: true }) }); map.getView().setCenter([10, 10]); diff --git a/src/ol/View.js b/src/ol/View.js index 839ca4c881..3dee0cefbd 100644 --- a/src/ol/View.js +++ b/src/ol/View.js @@ -120,6 +120,7 @@ import {createMinMaxResolution} from './resolutionconstraint'; * resolution constraint. It is used together with `maxZoom` (or * `minResolution`) and `zoomFactor`. Note that if `maxResolution` is also * provided, it is given precedence over `minZoom`. + * @property {boolean} [multiWorld=false] No more than one world is visible. * @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. @@ -1435,6 +1436,9 @@ export function createResolutionConstraint(options) { const zoomFactor = options.zoomFactor !== undefined ? options.zoomFactor : defaultZoomFactor; + const multiWorld = options.multiWorld !== undefined ? + options.multiWorld : false; + const smooth = options.smoothResolutionConstraint !== undefined ? options.smoothResolutionConstraint : true; @@ -1499,8 +1503,14 @@ export function createResolutionConstraint(options) { zoomFactor, maxResolution, minResolution, smooth, !options.constrainOnlyCenter && options.extent); } else { + let constrainOnlyCenter = options.constrainOnlyCenter; + let extent = options.extent; + if (!multiWorld && !extent && projection.isGlobal()) { + constrainOnlyCenter = false; + extent = projection.getExtent(); + } resolutionConstraint = createMinMaxResolution(maxResolution, minResolution, smooth, - !options.constrainOnlyCenter && options.extent); + !constrainOnlyCenter && extent); } } return {constraint: resolutionConstraint, maxResolution: maxResolution, diff --git a/test/spec/ol/control/scaleline.test.js b/test/spec/ol/control/scaleline.test.js index 2b79613bc8..954cc314b8 100644 --- a/test/spec/ol/control/scaleline.test.js +++ b/test/spec/ol/control/scaleline.test.js @@ -146,6 +146,7 @@ describe('ol.control.ScaleLine', function() { ctrl.setMap(map); map.setView(new View({ center: [0, 0], + multiWorld: true, zoom: 0 })); map.renderSync(); @@ -176,6 +177,7 @@ describe('ol.control.ScaleLine', function() { const ctrl = new ScaleLine(); map.setView(new View({ center: [0, 0], + multiWorld: true, zoom: 0 })); ctrl.setMap(map); @@ -277,6 +279,7 @@ describe('ol.control.ScaleLine', function() { expect(ctrl.element.innerText).to.be('2000 km'); map.setView(new View({ center: [7, 52], + multiWorld: true, zoom: 2, projection: 'EPSG:4326' })); @@ -380,6 +383,7 @@ describe('ol.control.ScaleLine', function() { ctrl.setMap(map); map.setView(new View({ center: [0, 0], + multiWorld: true, zoom: 0, /* min zoom */ projection: 'EPSG:4326' })); @@ -417,7 +421,8 @@ describe('ol.control.ScaleLine', function() { map.setView(new View({ center: [7, 0], zoom: 2, - projection: 'EPSG:4326' + projection: 'EPSG:4326', + multiWorld: true })); map.renderSync(); const innerHtml0 = ctrl.element.innerHTML; @@ -467,7 +472,8 @@ describe('ol.control.ScaleLine', function() { map.setView(new View({ center: [0, 0], zoom: currentZoom, - maxZoom: currentZoom + maxZoom: currentZoom, + multiWorld: true })); mapView = map.getView(); map.renderSync(); diff --git a/test/spec/ol/renderer/layer.test.js b/test/spec/ol/renderer/layer.test.js index 6ca96c5e3d..6a7c9d432b 100644 --- a/test/spec/ol/renderer/layer.test.js +++ b/test/spec/ol/renderer/layer.test.js @@ -100,6 +100,7 @@ describe('ol.renderer.Layer', function() { view = new View({ center: [0, 0], + multiWorld: true, zoom: 0 }); diff --git a/test/spec/ol/view.test.js b/test/spec/ol/view.test.js index 5f4d3e783a..8512e3339e 100644 --- a/test/spec/ol/view.test.js +++ b/test/spec/ol/view.test.js @@ -145,18 +145,20 @@ describe('ol.View', function() { describe('create resolution constraint', function() { describe('with no options', function() { + const size = [200, 200]; it('gives a correct resolution constraint function', function() { const options = {}; const fn = createResolutionConstraint(options).constraint; - expect(fn(156543.03392804097, 0, 0)) + expect(fn(156543.03392804097, 0, size)) .to.roughlyEqual(156543.03392804097, 1e-9); - expect(fn(78271.51696402048, 0, 0)) + expect(fn(78271.51696402048, 0, size)) .to.roughlyEqual(78271.51696402048, 1e-10); }); }); describe('with maxResolution, maxZoom, and zoomFactor options', function() { + const size = [200, 200]; it('gives a correct resolution constraint function', function() { const options = { maxResolution: 81, @@ -169,16 +171,17 @@ describe('ol.View', function() { const minResolution = info.minResolution; expect(minResolution).to.eql(3); const fn = info.constraint; - expect(fn(82, 0, 0)).to.eql(81); - expect(fn(81, 0, 0)).to.eql(81); - expect(fn(27, 0, 0)).to.eql(27); - expect(fn(9, 0, 0)).to.eql(9); - expect(fn(3, 0, 0)).to.eql(3); - expect(fn(2, 0, 0)).to.eql(3); + expect(fn(82, 0, size)).to.eql(81); + expect(fn(81, 0, size)).to.eql(81); + expect(fn(27, 0, size)).to.eql(27); + expect(fn(9, 0, size)).to.eql(9); + expect(fn(3, 0, size)).to.eql(3); + expect(fn(2, 0, size)).to.eql(3); }); }); describe('with resolutions', function() { + const size = [200, 200]; it('gives a correct resolution constraint function', function() { const options = { resolutions: [97, 76, 65, 54, 0.45] @@ -189,17 +192,18 @@ describe('ol.View', function() { const minResolution = info.minResolution; expect(minResolution).to.eql(0.45); const fn = info.constraint; - expect(fn(97, 0, 0)).to.eql(97); - expect(fn(76, 0, 0)).to.eql(76); - expect(fn(65, 0, 0)).to.eql(65); - expect(fn(54, 0, 0)).to.eql(54); - expect(fn(0.45, 0, 0)).to.eql(0.45); + expect(fn(97, 0, size)).to.eql(97); + expect(fn(76, 0, size)).to.eql(76); + expect(fn(65, 0, size)).to.eql(65); + expect(fn(54, 0, size)).to.eql(54); + expect(fn(0.45, 0, size)).to.eql(0.45); }); }); describe('with zoom related options', function() { const defaultMaxRes = 156543.03392804097; + const size = [200, 200]; function getConstraint(options) { return createResolutionConstraint(options).constraint; } @@ -210,10 +214,10 @@ describe('ol.View', function() { maxZoom: maxZoom }); - expect(constraint(defaultMaxRes, 0, 0)).to.roughlyEqual( + expect(constraint(defaultMaxRes, 0, size)).to.roughlyEqual( defaultMaxRes, 1e-9); - expect(constraint(0, 0, 0)).to.roughlyEqual( + expect(constraint(0, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(2, maxZoom), 1e-9); }); @@ -223,10 +227,10 @@ describe('ol.View', function() { minZoom: minZoom }); - expect(constraint(defaultMaxRes, 0, 0)).to.roughlyEqual( + expect(constraint(defaultMaxRes, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(2, minZoom), 1e-9); - expect(constraint(0, 0, 0)).to.roughlyEqual( + expect(constraint(0, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(2, 28), 1e-9); }); @@ -238,10 +242,10 @@ describe('ol.View', function() { maxZoom: maxZoom }); - expect(constraint(defaultMaxRes, 0, 0)).to.roughlyEqual( + expect(constraint(defaultMaxRes, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(2, minZoom), 1e-9); - expect(constraint(0, 0, 0)).to.roughlyEqual( + expect(constraint(0, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(2, maxZoom), 1e-9); }); @@ -255,10 +259,10 @@ describe('ol.View', function() { zoomFactor: zoomFactor }); - expect(constraint(defaultMaxRes, 0, 0)).to.roughlyEqual( + expect(constraint(defaultMaxRes, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(zoomFactor, minZoom), 1e-9); - expect(constraint(0, 0, 0)).to.roughlyEqual( + expect(constraint(0, 0, size)).to.roughlyEqual( defaultMaxRes / Math.pow(zoomFactor, maxZoom), 1e-9); }); @@ -267,6 +271,7 @@ describe('ol.View', function() { describe('with resolution related options', function() { const defaultMaxRes = 156543.03392804097; + const size = [200, 200]; function getConstraint(options) { return createResolutionConstraint(options).constraint; } @@ -274,13 +279,14 @@ describe('ol.View', function() { it('works with only maxResolution', function() { const maxResolution = 10e6; const constraint = getConstraint({ + multiWorld: true, maxResolution: maxResolution }); - expect(constraint(maxResolution * 3, 0, 0)).to.roughlyEqual( + expect(constraint(maxResolution * 3, 0, size)).to.roughlyEqual( maxResolution, 1e-9); - const minResolution = constraint(0, 0, 0); + const minResolution = constraint(0, 0, size); const defaultMinRes = defaultMaxRes / Math.pow(2, 28); expect(minResolution).to.be.greaterThan(defaultMinRes); @@ -293,10 +299,10 @@ describe('ol.View', function() { minResolution: minResolution }); - expect(constraint(defaultMaxRes, 0, 0)).to.roughlyEqual( + expect(constraint(defaultMaxRes, 0, size)).to.roughlyEqual( defaultMaxRes, 1e-9); - const constrainedMinRes = constraint(0, 0, 0); + const constrainedMinRes = constraint(0, 0, size); expect(constrainedMinRes).to.be.greaterThan(minResolution); expect(constrainedMinRes / minResolution).to.be.lessThan(2); }); @@ -308,13 +314,13 @@ describe('ol.View', function() { constrainResolution: true }); - expect(constraint(600, 0, 0)).to.be(500); - expect(constraint(500, 0, 0)).to.be(500); - expect(constraint(400, 0, 0)).to.be(500); - expect(constraint(300, 0, 0)).to.be(250); - expect(constraint(200, 0, 0)).to.be(250); - expect(constraint(100, 0, 0)).to.be(125); - expect(constraint(0, 0, 0)).to.be(125); + expect(constraint(600, 0, size)).to.be(500); + expect(constraint(500, 0, size)).to.be(500); + expect(constraint(400, 0, size)).to.be(500); + expect(constraint(300, 0, size)).to.be(250); + expect(constraint(200, 0, size)).to.be(250); + expect(constraint(100, 0, size)).to.be(125); + expect(constraint(0, 0, size)).to.be(125); }); it('accepts minResolution, maxResolution, and zoomFactor', function() { @@ -325,12 +331,12 @@ describe('ol.View', function() { constrainResolution: true }); - expect(constraint(1000, 0, 0)).to.be(500); - expect(constraint(500, 0, 0)).to.be(500); - expect(constraint(100, 0, 0)).to.be(50); - expect(constraint(50, 0, 0)).to.be(50); - expect(constraint(10, 0, 0)).to.be(5); - expect(constraint(1, 0, 0)).to.be(5); + expect(constraint(1000, 0, size)).to.be(500); + expect(constraint(500, 0, size)).to.be(500); + expect(constraint(100, 0, size)).to.be(50); + expect(constraint(50, 0, size)).to.be(50); + expect(constraint(10, 0, size)).to.be(5); + expect(constraint(1, 0, size)).to.be(5); }); }); @@ -338,6 +344,7 @@ describe('ol.View', function() { describe('overspecified options (prefers resolution)', function() { const defaultMaxRes = 156543.03392804097; + const size = [200, 200]; function getConstraint(options) { return createResolutionConstraint(options).constraint; } @@ -346,14 +353,15 @@ describe('ol.View', function() { const maxResolution = 10e6; const minZoom = 8; const constraint = getConstraint({ + multiWorld: true, maxResolution: maxResolution, minZoom: minZoom }); - expect(constraint(maxResolution * 3, 0, 0)).to.roughlyEqual( + expect(constraint(maxResolution * 3, 0, size)).to.roughlyEqual( maxResolution, 1e-9); - const minResolution = constraint(0, 0, 0); + const minResolution = constraint(0, 0, size); const defaultMinRes = defaultMaxRes / Math.pow(2, 28); expect(minResolution).to.be.greaterThan(defaultMinRes); @@ -368,16 +376,37 @@ describe('ol.View', function() { maxZoom: maxZoom }); - expect(constraint(defaultMaxRes, 0, 0)).to.roughlyEqual( + expect(constraint(defaultMaxRes, 0, size)).to.roughlyEqual( defaultMaxRes, 1e-9); - const constrainedMinRes = constraint(0, 0, 0); + const constrainedMinRes = constraint(0, 0, size); expect(constrainedMinRes).to.be.greaterThan(minResolution); expect(constrainedMinRes / minResolution).to.be.lessThan(2); }); }); + describe('Map views that show more than one world', function() { + + const defaultMaxRes = 156543.03392804097; + const size = [512, 512]; + function getConstraint(options) { + return createResolutionConstraint(options).constraint; + } + + it('are disabled by default', function() { + const fn = getConstraint({}); + expect(fn(defaultMaxRes, 0, size)).to.be(defaultMaxRes / 2); + }); + + it('can be enabled by setting multiWorld to truet', function() { + const fn = getConstraint({ + multiWorld: true + }); + expect(fn(defaultMaxRes, 0, size)).to.be(defaultMaxRes); + }); + }); + }); describe('create rotation constraint', function() {