diff --git a/debug/7517.html b/debug/7517.html new file mode 100644 index 00000000000..0fb7f135714 --- /dev/null +++ b/debug/7517.html @@ -0,0 +1,137 @@ + + + + + Mapbox GL JS debug page + + + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/src/ui/camera.js b/src/ui/camera.js index 3cde087716c..06135aea72d 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -434,17 +434,9 @@ class Camera extends Evented { return; } - // we separate the passed padding option into two parts, the part that does not affect the map's center - // (lateral and vertical padding), and the part that does (paddingOffset). We add the padding offset - // to the options `offset` object where it can alter the map's center in the subsequent calls to - // `easeTo` and `flyTo`. - const paddingOffset = [(options.padding.left - options.padding.right) / 2, (options.padding.top - options.padding.bottom) / 2], - lateralPadding = Math.min(options.padding.right, options.padding.left), - verticalPadding = Math.min(options.padding.top, options.padding.bottom); - options.offset = [options.offset[0] + paddingOffset[0], options.offset[1] + paddingOffset[1]]; - const tr = this.transform; - // we want to calculate the upper right and lower left of the box defined by p0 and p1 + + // We want to calculate the upper right and lower left of the box defined by p0 and p1 // in a coordinate system rotate to match the destination bearing. const p0world = tr.project(LngLat.convert(p0)); const p1world = tr.project(LngLat.convert(p1)); @@ -454,10 +446,10 @@ class Camera extends Evented { const upperRight = new Point(Math.max(p0rotated.x, p1rotated.x), Math.max(p0rotated.y, p1rotated.y)); const lowerLeft = new Point(Math.min(p0rotated.x, p1rotated.x), Math.min(p0rotated.y, p1rotated.y)); - const offset = Point.convert(options.offset), - size = upperRight.sub(lowerLeft), - scaleX = (tr.width - lateralPadding * 2 - Math.abs(offset.x) * 2) / size.x, - scaleY = (tr.height - verticalPadding * 2 - Math.abs(offset.y) * 2) / size.y; + // Calculate zoom: consider the original bbox and padding. + const size = upperRight.sub(lowerLeft); + const scaleX = (tr.width - options.padding.left - options.padding.right) / size.x; + const scaleY = (tr.height - options.padding.top - options.padding.bottom) / size.y; if (scaleY < 0 || scaleX < 0) { warnOnce( @@ -465,11 +457,23 @@ class Camera extends Evented { ); return; } - options.center = tr.unproject(p0world.add(p1world).div(2)); - options.zoom = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom); - options.bearing = bearing; - return options; + const zoom = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), options.maxZoom); + + // Calculate center: apply the zoom, the configured offset, as well as offset that exists as a result of padding. + const offset = Point.convert(options.offset); + const paddingOffsetX = (options.padding.left - options.padding.right) / 2; + const paddingOffsetY = (options.padding.top - options.padding.bottom) / 2; + const offsetAtInitialZoom = new Point(offset.x + paddingOffsetX, offset.y + paddingOffsetY); + const offsetAtFinalZoom = offsetAtInitialZoom.mult(tr.scale / tr.zoomScale(zoom)); + + const center = tr.unproject(p0world.add(p1world).div(2).sub(offsetAtFinalZoom)); + + return { + center, + zoom, + bearing + }; } /** diff --git a/test/unit/ui/camera.test.js b/test/unit/ui/camera.test.js index 3c6f60a8f07..9aa8db43679 100644 --- a/test/unit/ui/camera.test.js +++ b/test/unit/ui/camera.test.js @@ -1714,11 +1714,47 @@ test('camera', (t) => { const camera = createCamera(); const bb = [[-133, 16], [-68, 50]]; - const transform = camera.cameraForBounds(bb, { padding: {top: 10, right: 75, bottom: 50, left: 25}, duration: 0 }); + const transform = camera.cameraForBounds(bb, { padding: {top: 15, right: 15, bottom: 15, left: 15}, duration: 0 }); t.deepEqual(fixedLngLat(transform.center, 4), { lng: -100.5, lat: 34.7171 }, 'correctly calculates coordinates for bounds with padding option as object applied'); t.end(); }); + t.test('asymetrical padding', (t) => { + const camera = createCamera(); + const bb = [[-133, 16], [-68, 50]]; + + const transform = camera.cameraForBounds(bb, { padding: {top: 10, right: 75, bottom: 50, left: 25}, duration: 0 }); + t.deepEqual(fixedLngLat(transform.center, 4), { lng: -96.5558, lat: 32.0833 }, 'correctly calculates coordinates for bounds with padding option as object applied'); + t.end(); + }); + + t.test('offset', (t) => { + const camera = createCamera(); + const bb = [[-133, 16], [-68, 50]]; + + const transform = camera.cameraForBounds(bb, { offset: [0, 100] }); + t.deepEqual(fixedLngLat(transform.center, 4), { lng: -100.5, lat: 44.4717 }, 'correctly calculates coordinates for bounds with padding option as object applied'); + t.end(); + }); + + t.test('offset as object', (t) => { + const camera = createCamera(); + const bb = [[-133, 16], [-68, 50]]; + + const transform = camera.cameraForBounds(bb, { offset: { x: 0, y: 100 } }); + t.deepEqual(fixedLngLat(transform.center, 4), { lng: -100.5, lat: 44.4717 }, 'correctly calculates coordinates for bounds with padding option as object applied'); + t.end(); + }); + + t.test('offset and padding', (t) => { + const camera = createCamera(); + const bb = [[-133, 16], [-68, 50]]; + + const transform = camera.cameraForBounds(bb, { padding: {top: 10, right: 75, bottom: 50, left: 25}, offset: [0, 100] }); + t.deepEqual(fixedLngLat(transform.center, 4), { lng: -96.5558, lat: 44.4189 }, 'correctly calculates coordinates for bounds with padding option as object applied'); + t.end(); + }); + t.end(); });