From 2974cacff4142ea19df084593db35697a2215117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Mon, 14 Oct 2013 14:36:30 +0200 Subject: [PATCH 1/2] panInsideBounds: fit or center, pass options, remove boundsMinZoom Deltas are calculated on x, y pixel coordinates separately. Options are propagated from setMaxBounds to panBy. No panBy loops. Fixes #1908. --- src/map/Map.js | 70 +++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/map/Map.js b/src/map/Map.js index 4fd5d5fc..d019edb9 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -130,29 +130,19 @@ L.Map = L.Class.extend({ this.options.maxBounds = bounds; if (!bounds) { - this._boundsMinZoom = null; - this.off('moveend', this._panInsideMaxBounds, this); + this.off('moveend', L.bind(this._panInsideMaxBounds, this, options), this); return this; } - - var minZoom = this.getBoundsZoom(bounds, true); - - this._boundsMinZoom = minZoom; - if (this._loaded) { - if (this._zoom < minZoom) { - this.setView(bounds.getCenter(), minZoom, options); - } else { - this.panInsideBounds(bounds); - } + this.panInsideBounds(bounds, options); } - this.on('moveend', this._panInsideMaxBounds, this); - + this.on('moveend', L.bind(this._panInsideMaxBounds, this, options), this); + return this; }, - panInsideBounds: function (bounds) { + panInsideBounds: function (bounds, options) { bounds = L.latLngBounds(bounds); var viewBounds = this.getPixelBounds(), @@ -160,24 +150,34 @@ L.Map = L.Class.extend({ viewNe = viewBounds.getTopRight(), sw = this.project(bounds.getSouthWest()), ne = this.project(bounds.getNorthEast()), - dx = 0, - dy = 0; + dx = 0, dy = 0; - if (viewNe.y < ne.y) { // north - dy = Math.ceil(ne.y - viewNe.y); + function rebound(l, r) { + var s = l + r; + var h = s / 2; + var d = 0; + if (s >= 0) { + if (l < 0) { + d = l; + } else if (r < 0) { + d = -r; + } + } else { + if (l < h) { + d = l - h; + } else if (r < h) { + d = -r + h; + } + } + return Math.round(d); } - if (viewNe.x > ne.x) { // east - dx = Math.floor(ne.x - viewNe.x); - } - if (viewSw.y > sw.y) { // south - dy = Math.floor(sw.y - viewSw.y); - } - if (viewSw.x < sw.x) { // west - dx = Math.ceil(sw.x - viewSw.x); - } - + // set signs so positive l, r means "in bound" + // and positive s + r means the view can fit inside the bounds + dx = rebound(ne.x - viewNe.x, -sw.x + viewSw.x); + dy = rebound(sw.y - viewSw.y, -ne.y + viewNe.y); + if (dx || dy) { - return this.panBy([dx, dy]); + return this.panBy([dx, dy], options); } return this; @@ -363,9 +363,9 @@ L.Map = L.Class.extend({ }, getMinZoom: function () { - var z1 = this._layersMinZoom === undefined ? 0 : this._layersMinZoom, - z2 = this._boundsMinZoom === undefined ? 0 : this._boundsMinZoom; - return this.options.minZoom === undefined ? Math.max(z1, z2) : this.options.minZoom; + return this.options.minZoom === undefined ? + (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) : + this.options.minZoom; }, getMaxZoom: function () { @@ -652,8 +652,8 @@ L.Map = L.Class.extend({ } }, - _panInsideMaxBounds: function () { - this.panInsideBounds(this.options.maxBounds); + _panInsideMaxBounds: function (options) { + this.panInsideBounds(this.options.maxBounds, options); }, _checkIfLoaded: function () { From d7bf010f0b8e30e291ed8193fcc52d7f8fe118fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Lal?= Date: Sat, 10 Aug 2013 17:11:43 +0200 Subject: [PATCH 2/2] test Map#setMaxBounds and Map#panInsideBounds --- spec/suites/map/MapSpec.js | 40 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/spec/suites/map/MapSpec.js b/spec/suites/map/MapSpec.js index 00da85d8..747ce256 100644 --- a/spec/suites/map/MapSpec.js +++ b/spec/suites/map/MapSpec.js @@ -139,6 +139,46 @@ describe("Map", function () { }); }); + describe('#setMaxBounds', function() { + it("aligns pixel-wise map view center with maxBounds center if it cannot move view bounds inside maxBounds (#1908)", function() { + var container = map.getContainer(); + // large view, cannot fit within maxBounds + container.style.width = container.style.height = "1000px"; + document.body.appendChild(container); + // maxBounds + var bounds = L.latLngBounds([51.5, -0.05], [51.55, 0.05]); + map.setMaxBounds(bounds, {animate: false}); + // set view outside + map.setView(L.latLng([53.0, 0.15]), 12, {animate: false}); + // get center of bounds in pixels + var boundsCenter = map.project(bounds.getCenter()); + boundsCenter = {x: Math.round(boundsCenter.x), y: Math.round(boundsCenter.y)}; + expect(map.project(map.getCenter())).to.eql(boundsCenter); + document.body.removeChild(container); + }); + it("moves map view within maxBounds by changing one coordinate", function() { + var container = map.getContainer(); + // small view, can fit within maxBounds + container.style.width = container.style.height = "200px"; + document.body.appendChild(container); + // maxBounds + var bounds = L.latLngBounds([51, -0.2], [52, 0.2]); + map.setMaxBounds(bounds, {animate: false}); + // set view outside maxBounds on one direction only + // leaves untouched the other coordinate (that is not already centered) + var initCenter = [53.0, 0.1]; + map.setView(L.latLng(initCenter), 16, {animate: false}); + // one pixel coordinate hasn't changed, the other has + var pixelCenter = map.project(map.getCenter()); + var pixelInit = map.project(initCenter); + expect(pixelCenter.x).to.eql(Math.round(pixelInit.x)); + expect(pixelCenter.y).not.to.eql(Math.round(pixelInit.y)); + // the view is inside the bounds + expect(bounds.contains(map.getBounds())).to.be(true); + document.body.removeChild(container); + }); + }); + describe("#getMinZoom and #getMaxZoom", function () { describe('#getMinZoom', function () { it('returns 0 if not set by Map options or TileLayer options', function () {