diff --git a/src/ui/camera.js b/src/ui/camera.js index ffe2f775c4d..668d629b588 100644 --- a/src/ui/camera.js +++ b/src/ui/camera.js @@ -709,6 +709,8 @@ class Camera extends Evented { aabb = Aabb.applyTransform(aabb, mat4.multiply([], worldToCamera, aabbOrientation)); + aabb = this._extendAABBWithPaddings(aabb, eOptions, tr, bearing); + vec3.transformMat4(center, center, worldToCamera); const aabbHalfExtentZ = (aabb.max[2] - aabb.min[2]) * 0.5; @@ -744,6 +746,42 @@ class Camera extends Evented { return {center: tr.center, zoom, bearing, pitch}; } + _extendAABBWithPaddings(aabb: Aabb, eOptions: FullCameraOptions, tr: Transform, bearing: number): Aabb { + const size = vec3.sub([], aabb.max, aabb.min); + + const screenPadL = tr.padding.left || 0; + const screenPadR = tr.padding.right || 0; + const screenPadB = tr.padding.bottom || 0; + const screenPadT = tr.padding.top || 0; + + const {left: padL, right: padR, top: padT, bottom: padB} = eOptions.padding; + + const halfScreenPadX = (screenPadL + screenPadR) * 0.5; + const halfScreenPadY = (screenPadT + screenPadB) * 0.5; + + const scaleX = (tr.width - (screenPadL + screenPadR + padL + padR)) / size[0]; + const scaleY = (tr.height - (screenPadB + screenPadT + padB + padT)) / size[1]; + + const zoomRef = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), eOptions.maxZoom); + + const scaleRatio = tr.scale / tr.zoomScale(zoomRef); + + aabb = new Aabb( + [aabb.min[0] - (padL + halfScreenPadX) * scaleRatio, aabb.min[1] - (padB + halfScreenPadY) * scaleRatio, aabb.min[2]], + [aabb.max[0] + (padR + halfScreenPadX) * scaleRatio, aabb.max[1] + (padT + halfScreenPadY) * scaleRatio, aabb.max[2]]); + + const centerOffset = (typeof eOptions.offset.x === 'number' && typeof eOptions.offset.y === 'number') ? + new Point(eOptions.offset.x, eOptions.offset.y) : + Point.convert(eOptions.offset); + + const rotatedOffset = centerOffset.rotate(-degToRad(bearing)); + + aabb.center[0] -= rotatedOffset.x * scaleRatio; + aabb.center[1] += rotatedOffset.y * scaleRatio; + + return aabb; + } + /** @section {Querying features} */ /** @@ -777,6 +815,7 @@ class Camera extends Evented { * the highest zoom level up to and including `Map#getMaxZoom()` that fits * the points in the viewport at the specified bearing. * @memberof Map# + * @param transform The current transform * @param {LngLatLike} p0 First point * @param {LngLatLike} p1 Second point * @param {number} bearing Desired map bearing at end of animation, in degrees @@ -803,7 +842,6 @@ class Camera extends Evented { const tr = transform.clone(); const eOptions = this._extendCameraOptions(options); - const edgePadding = tr.padding; tr.bearing = bearing; tr.pitch = pitch; @@ -833,29 +871,9 @@ class Camera extends Evented { aabb = Aabb.applyTransform(aabb, worldToCamera); - const size = vec3.sub([], aabb.max, aabb.min); - - const screenPadL = edgePadding.left || 0; - const screenPadR = edgePadding.right || 0; - const screenPadB = edgePadding.bottom || 0; - const screenPadT = edgePadding.top || 0; - - const {left: padL, right: padR, top: padT, bottom: padB} = eOptions.padding; - - const halfScreenPadX = (screenPadL + screenPadR) * 0.5; - const halfScreenPadY = (screenPadT + screenPadB) * 0.5; - - const scaleX = (tr.width - (screenPadL + screenPadR + padL + padR)) / size[0]; - const scaleY = (tr.height - (screenPadB + screenPadT + padB + padT)) / size[1]; - - const zoomRef = Math.min(tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY)), eOptions.maxZoom); - - const scaleRatio = tr.scale / tr.zoomScale(zoomRef); - - aabb = new Aabb( - [aabb.min[0] - (padL + halfScreenPadX) * scaleRatio, aabb.min[1] - (padB + halfScreenPadY) * scaleRatio, aabb.min[2]], - [aabb.max[0] + (padR + halfScreenPadX) * scaleRatio, aabb.max[1] + (padT + halfScreenPadY) * scaleRatio, aabb.max[2]]); + aabb = this._extendAABBWithPaddings(aabb, eOptions, tr, bearing); + const size = vec3.sub([], aabb.max, aabb.min); const aabbHalfExtentZ = size[2] * 0.5; const frustumDistance = this._minimumAABBFrustumDistance(tr, aabb); @@ -867,15 +885,6 @@ class Camera extends Evented { const offset = vec3.scale([], normalZ, frustumDistance + aabbHalfExtentZ); const cameraPosition = vec3.add([], aabb.center, offset); - const centerOffset = (typeof eOptions.offset.x === 'number' && typeof eOptions.offset.y === 'number') ? - new Point(eOptions.offset.x, eOptions.offset.y) : - Point.convert(eOptions.offset); - - const rotatedOffset = centerOffset.rotate(-degToRad(bearing)); - - aabb.center[0] -= rotatedOffset.x * scaleRatio; - aabb.center[1] += rotatedOffset.y * scaleRatio; - vec3.transformMat4(aabb.center, aabb.center, cameraToWorld); vec3.transformMat4(cameraPosition, cameraPosition, cameraToWorld); diff --git a/test/unit/ui/camera.test.js b/test/unit/ui/camera.test.js index 2145ada9521..23cf162f725 100644 --- a/test/unit/ui/camera.test.js +++ b/test/unit/ui/camera.test.js @@ -2300,6 +2300,15 @@ describe('camera', () => { expect(fixedLngLat(transform.center, 4)).toEqual({lng: 180, lat: 80}); expect(fixedNum(transform.zoom, 3)).toEqual(1.072); }); + + test('entire longitude range: -180 to 180 with asymmetrical padding', () => { + const camera = createCamera({projection: {name: 'globe'}}); + const bb = [[-180, 10], [180, 50]]; + + const transform = camera.cameraForBounds(bb, {padding:{top: 10, right: 75, bottom: 50, left: 25}}); + expect(fixedLngLat(transform.center, 4)).toEqual({lng: 180, lat: 80}); + expect(fixedNum(transform.zoom, 3)).toEqual(0.892); + }); }); describe('#fitBounds', () => {