Skip to content

Commit

Permalink
Allow pitch override in camera apis (#12367)
Browse files Browse the repository at this point in the history
* Expose pitch on camera fit APIs

* Add unit tests

* Revert unnecessary change

* Apply review feedback
  • Loading branch information
karimnaaji authored Nov 11, 2022
1 parent 58026fd commit ced4597
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 14 deletions.
8 changes: 8 additions & 0 deletions debug/padding.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<button id="getBounds">getBounds</button>
<button id="setBounds">setBounds</button>
<button id="maintainBearing">Maintain Bearing: false</button>
<button id="maintainPitch">Maintain Pitch: false</button>
</div>

<script src='../dist/mapbox-gl-dev.js'></script>
Expand Down Expand Up @@ -60,6 +61,7 @@

var bounds;
var maintainBearing = false;
var maintainPitch = false;
document.getElementById('getBounds').addEventListener('click', function () {
bounds = map.getBounds();
map.getSource('bounds').setData({
Expand All @@ -81,13 +83,19 @@
map.fitBounds(bounds, {
duration: 0,
bearing: maintainBearing ? map.getBearing() : 0,
pitch: maintainPitch ? map.getPitch() : 0
});
});
const maintainBearingElement = document.getElementById('maintainBearing');
maintainBearingElement.addEventListener('click', function() {
maintainBearing = !maintainBearing;
maintainBearingElement.innerHTML = `Maintain Bearing: ${maintainBearing}`;
});
const maintainPitchElement = document.getElementById('maintainPitch');
maintainPitchElement.addEventListener('click', function() {
maintainPitch = !maintainPitch;
maintainPitchElement.innerHTML = `Maintain Pitch: ${maintainPitch}`;
});
</script>
</body>
</html>
40 changes: 26 additions & 14 deletions src/ui/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ class Camera extends Evented {
* @param {CameraOptions | null} options Options object.
* @param {number | PaddingOptions} [options.padding] The amount of padding in pixels to add to the given bounds.
* @param {number} [options.bearing=0] Desired map bearing at end of animation, in degrees.
* @param {number} [options.pitch=0] Desired map pitch at end of animation, in degrees.
* @param {PointLike} [options.offset=[0, 0]] The center of the given bounds relative to the map's center, measured in pixels.
* @param {number} [options.maxZoom] The maximum zoom level to allow when the camera would transition to the specified bounds.
* @returns {CameraOptions | void} If map is able to fit to provided bounds, returns `CameraOptions` with
Expand All @@ -605,9 +606,10 @@ class Camera extends Evented {
cameraForBounds(bounds: LngLatBoundsLike, options?: CameraOptions): ?EasingOptions {
bounds = LngLatBounds.convert(bounds);
const bearing = (options && options.bearing) || 0;
const pitch = (options && options.pitch) || 0;
const lnglat0 = bounds.getNorthWest();
const lnglat1 = bounds.getSouthEast();
return this._cameraForBounds(this.transform, lnglat0, lnglat1, bearing, options);
return this._cameraForBounds(this.transform, lnglat0, lnglat1, bearing, pitch, options);
}

_extendCameraOptions(options?: CameraOptions): FullCameraOptions {
Expand Down Expand Up @@ -649,11 +651,12 @@ class Camera extends Evented {
return minimumDistance;
}

_cameraForBoundsOnGlobe(transform: Transform, p0: LngLatLike, p1: LngLatLike, bearing: number, options?: CameraOptions): ?EasingOptions {
_cameraForBoundsOnGlobe(transform: Transform, p0: LngLatLike, p1: LngLatLike, bearing: number, pitch: number, options?: CameraOptions): ?EasingOptions {
const tr = transform.clone();
const eOptions = this._extendCameraOptions(options);

tr.bearing = bearing;
tr.pitch = pitch;

const coord0 = LngLat.convert(p0);
const coord1 = LngLat.convert(p1);
Expand Down Expand Up @@ -734,10 +737,10 @@ class Camera extends Evented {
if (zoom > halfZoomTransition) {
tr.setProjection({name: 'mercator'});
tr.zoom = zoom;
return this._cameraForBounds(tr, p0, p1, bearing, options);
return this._cameraForBounds(tr, p0, p1, bearing, pitch, options);
}

return {center: tr.center, zoom, bearing};
return {center: tr.center, zoom, bearing, pitch};
}

/**
Expand Down Expand Up @@ -773,6 +776,7 @@ class Camera extends Evented {
* @param {LngLatLike} p0 First point
* @param {LngLatLike} p1 Second point
* @param {number} bearing Desired map bearing at end of animation, in degrees
* @param {number} pitch Desired map pitch at end of animation, in degrees
* @param {CameraOptions | null} options
* @param {number | PaddingOptions} [options.padding] The amount of padding in pixels to add to the given bounds.
* @param {PointLike} [options.offset=[0, 0]] The center of the given bounds relative to the map's center, measured in pixels.
Expand All @@ -784,20 +788,21 @@ class Camera extends Evented {
* var p0 = [-79, 43];
* var p1 = [-73, 45];
* var bearing = 90;
* var newCameraTransform = map._cameraForBounds(p0, p1, bearing, {
* var newCameraTransform = map._cameraForBounds(p0, p1, bearing, pitch, {
* padding: {top: 10, bottom:25, left: 15, right: 5}
* });
*/
_cameraForBounds(transform: Transform, p0: LngLatLike, p1: LngLatLike, bearing: number, options?: CameraOptions): ?EasingOptions {
_cameraForBounds(transform: Transform, p0: LngLatLike, p1: LngLatLike, bearing: number, pitch: number, options?: CameraOptions): ?EasingOptions {
if (transform.projection.name === 'globe') {
return this._cameraForBoundsOnGlobe(transform, p0, p1, bearing, options);
return this._cameraForBoundsOnGlobe(transform, p0, p1, bearing, pitch, options);
}

const tr = transform.clone();
const eOptions = this._extendCameraOptions(options);
const edgePadding = tr.padding;

tr.bearing = bearing;
tr.pitch = pitch;

const coord0 = LngLat.convert(p0);
const coord1 = LngLat.convert(p1);
Expand Down Expand Up @@ -884,22 +889,23 @@ class Camera extends Evented {
if (tr.mercatorFromTransition && zoom < halfZoomTransition) {
tr.setProjection({name: 'globe'});
tr.zoom = zoom;
return this._cameraForBounds(tr, p0, p1, bearing, options);
return this._cameraForBounds(tr, p0, p1, bearing, pitch, options);
}

return {center, zoom, bearing};
return {center, zoom, bearing, pitch};
}

/**
* Pans and zooms the map to contain its visible area within the specified geographical bounds.
* This function will also reset the map's bearing to 0 if bearing is nonzero.
* If a padding is set on the map, the bounds are fit to the inset.
*
* @memberof Map#
* @param {LngLatBoundsLike} bounds Center these bounds in the viewport and use the highest
* zoom level up to and including `Map#getMaxZoom()` that fits them in the viewport.
* @param {Object} [options] Options supports all properties from {@link AnimationOptions} and {@link CameraOptions} in addition to the fields below.
* @param {number | PaddingOptions} [options.padding] The amount of padding in pixels to add to the given bounds.
* @param {number} [options.pitch=0] Desired map pitch at end of animation, in degrees.
* @param {number} [options.bearing=0] Desired map bearing at end of animation, in degrees.
* @param {boolean} [options.linear=false] If `true`, the map transitions using
* {@link Map#easeTo}. If `false`, the map transitions using {@link Map#flyTo}. See
* those functions and {@link AnimationOptions} for information about options available.
Expand Down Expand Up @@ -930,12 +936,14 @@ class Camera extends Evented {
* @memberof Map#
* @param {PointLike} p0 First point on screen, in pixel coordinates.
* @param {PointLike} p1 Second point on screen, in pixel coordinates.
* @param {number} bearing Desired map bearing at end of animation, in degrees. This value is ignored if the map has non-zero pitch.
* @param {CameraOptions | null} options Options object.
* @param {number} bearing Desired map bearing at end of animation, in degrees.
* @param {EasingOptions | null} options Options object.
* Accepts {@link CameraOptions} and {@link AnimationOptions}.
* @param {number | PaddingOptions} [options.padding] The amount of padding in pixels to add to the given bounds.
* @param {boolean} [options.linear=false] If `true`, the map transitions using
* {@link Map#easeTo}. If `false`, the map transitions using {@link Map#flyTo}. See
* those functions and {@link AnimationOptions} for information about options available.
* @param {number} [options.pitch=0] Desired map pitch at end of animation, in degrees.
* @param {Function} [options.easing] An easing function for the animated transition. See {@link AnimationOptions}.
* @param {PointLike} [options.offset=[0, 0]] The center of the given bounds relative to the map's center, measured in pixels.
* @param {number} [options.maxZoom] The maximum zoom level to allow when the map view transitions to the specified bounds.
Expand All @@ -958,7 +966,9 @@ class Camera extends Evented {
const min = new Point(Math.min(screen0.x, screen1.x), Math.min(screen0.y, screen1.y));
const max = new Point(Math.max(screen0.x, screen1.x), Math.max(screen0.y, screen1.y));

if (this.transform.projection.name === 'mercator' && this.transform.anyCornerOffEdge(screen0, screen1)) return this;
if (this.transform.projection.name === 'mercator' && this.transform.anyCornerOffEdge(screen0, screen1)) {
return this;
}

const lnglat0 = this.transform.pointLocation3D(min);
const lnglat1 = this.transform.pointLocation3D(max);
Expand All @@ -974,7 +984,9 @@ class Camera extends Evented {
Math.max(lnglat0.lat, lnglat1.lat, lnglat2.lat, lnglat3.lat),
];

const cameraPlacement = this._cameraForBounds(this.transform, p0coord, p1coord, bearing, options);
const pitch = options && options.pitch ? options.pitch : this.getPitch();

const cameraPlacement = this._cameraForBounds(this.transform, p0coord, p1coord, bearing, pitch, options);
return this._fitInternal(cameraPlacement, options, eventData);
}

Expand Down
37 changes: 37 additions & 0 deletions test/unit/ui/camera.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,18 @@ test('camera', (t) => {
t.end();
});

t.test('bearing and pitch', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];

const transform = camera.cameraForBounds(bb, {bearing: 175, pitch: 40});
t.deepEqual(fixedLngLat(transform.center, 4), {lng: -100.5, lat: 34.7171}, 'correctly calculates coordinates for new bounds');
t.equal(fixedNum(transform.zoom, 3), 2.197);
t.equal(transform.bearing, 175);
t.equal(transform.pitch, 40);
t.end();
});

t.test('bearing negative number', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];
Expand Down Expand Up @@ -2268,6 +2280,16 @@ test('camera', (t) => {
t.end();
});

t.test('padding object with pitch', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];

camera.fitBounds(bb, {padding: {top: 10, right: 75, bottom: 50, left: 25}, duration:0, pitch: 30});
t.deepEqual(fixedLngLat(camera.getCenter(), 4), {lng: -96.5558, lat: 32.4408}, 'pans to coordinates based on fitBounds with padding option as object applied');
t.equal(camera.getPitch(), 30);
t.end();
});

t.test('padding does not get propagated to transform.padding', (t) => {
const camera = createCamera();
const bb = [[-133, 16], [-68, 50]];
Expand Down Expand Up @@ -2315,6 +2337,21 @@ test('camera', (t) => {
t.end();
});

t.test('bearing 225, pitch 30 and 60 at end of animation', (t) => {
const pitch = 30;
const camera = createCamera({pitch});
const p0 = [200, 500];
const p1 = [210, 510];
const bearing = 225;

camera.fitScreenCoordinates(p0, p1, bearing, {duration:0, pitch: 60});
t.deepEqual(fixedLngLat(camera.getCenter(), 4), {lng: -30.215, lat: -84.1374}, 'centers, rotates 225 degrees, pitch 30 degrees, and zooms based on screen coordinates');
t.equal(fixedNum(camera.getZoom(), 3), 5.056);
t.equal(camera.getBearing(), -135);
t.equal(camera.getPitch(), 60);
t.end();
});

t.test('bearing 225, pitch 80, over horizon', (t) => {
const pitch = 80;
const camera = createCamera({pitch});
Expand Down

0 comments on commit ced4597

Please sign in to comment.