-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add pitch argument to cameraThatFits functions #12213
Conversation
src/mbgl/map/map.cpp
Outdated
CameraOptions Map::cameraForLatLngs(const std::vector<LatLng>& latLngs, const EdgeInsets& padding, optional<double> bearing) const { | ||
if(bearing) { | ||
CameraOptions Map::cameraForLatLngs(const std::vector<LatLng>& latLngs, const EdgeInsets& padding, optional<double> bearing, optional<double> pitch) const { | ||
if (bearing && pitch) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will provide incorrect results if only one of bearing
or pitch
is provided. The if
clause would need to handle each of bearing
and pitch
individually.
src/mbgl/map/map.cpp
Outdated
options.angle = transform.getAngle(); | ||
} | ||
if (pitch) { | ||
options.angle = transform.getAngle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should set pitch here.
src/mbgl/map/map.cpp
Outdated
|
||
CameraOptions options = mbgl::cameraForLatLngs(latLngs, transform, padding); | ||
|
||
if (bearing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Angle and pitch can be set on options
without the additional if blocks.
test/map/map.test.cpp
Outdated
@@ -84,7 +84,7 @@ TEST(Map, LatLngBoundsToCameraWithAngle) { | |||
CameraOptions virtualCamera = test.map.cameraForLatLngBounds(bounds, {}, 35); | |||
ASSERT_TRUE(bounds.contains(*virtualCamera.center)); | |||
EXPECT_NEAR(*virtualCamera.zoom, 1.21385, 1e-5); | |||
EXPECT_DOUBLE_EQ(virtualCamera.angle.value_or(0), -35 * util::DEG2RAD); | |||
EXPECT_NEAR(virtualCamera.angle.value_or(0), -35 * util::DEG2RAD, 1e-6); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May need this on Line 96 below as well. While you're at it, could you also add a test with pitch and bearing, and oe with just pitch.
transform.setAngle(angle); | ||
} | ||
if (pitch) { | ||
double pitchAsRadian = *pitch * util::DEG2RAD; // Convert to radians |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike bearing, negative does not work here. Does this look right to you @asheemmamoowala?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes thats correct. Bearing is counter clockwise from North,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR implements #12238. Please make any corresponding changes to the macOS implementation of MGLMapView, then add a blurb to the iOS and macOS map SDKs noting the addition of these methods.
platform/ios/src/MGLMapView.h
Outdated
@@ -926,6 +926,23 @@ MGL_EXPORT IB_DESIGNABLE | |||
*/ | |||
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets; | |||
|
|||
/** | |||
Returns the camera that best fits the given coordinate bounds, optionally with | |||
some additional padding on each side. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence should indicate that the method lets the developer specify a direction and pitch.
platform/ios/src/MGLMapView.h
Outdated
@return A camera object centered on the shape's center with zoom level as high | ||
(close to the ground) as possible while still including the entire shape. | ||
*/ | ||
- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction pitch:(CGFloat)pitch edgePadding:(UIEdgeInsets)insets; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some problems with the design of this API:
- This represents the first time we’re exposing pitch as an option in MGLMapView as opposed to MGLMapCamera. Up to now, we’ve deliberately excluded pitch parameters from MGLMapView, because they make more sense in MGLMapCamera’s 3D context than in MGLMapView’s largely 2D context. For example, MGLMapCamera offers two frames of reference (viewer and focal point), whereas MGLMapView can only express the focal point.
- We’re trying to limit the number of viewport-related methods in MGLMapView, to avoid combinatorial explosion. What if the developer wants to specify a direction but pitch but not a direction?
- From the method name (and even from the documentation), the relationship between the shape and the direction is unclear, and so is the relationship between the shape and the pitch.
+[MGLMapCamera cameraLookingAtCenterCoordinate:fromDistance:pitch:heading:]
does a better job of describing the relationship between the center coordinate and the other parameters. Can we do something similar for shapes and coordinate bounds? Maybe something that explicitly returns a copy of an MGLMapCamera, like -[MGLMapView camera:fittingShape:edgePadding:]
? We could deprecate -[MGLMapView cameraThatFitsShape:direction:edgePadding:]
at the same time, since MGLMapCamera has a convention where values like −1 are ignored.
platform/ios/src/MGLMapView.mm
Outdated
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); | ||
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset); | ||
mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does mbgl::Map::cameraForLatLngBounds()
correctly handle the situation where either direction
or pitch
is negative? A negative value in an MGLMapCamera property normally means that the property is ignored.
mapbox-gl-native/platform/ios/src/MGLMapView.mm
Lines 3400 to 3407 in 3946841
if (camera.heading >= 0) | |
{ | |
options.angle = MGLRadiansFromDegrees(-camera.heading); | |
} | |
if (camera.pitch >= 0) | |
{ | |
options.pitch = MGLRadiansFromDegrees(camera.pitch); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the mbgl::Map::cameraFor...
methods end up in mbgl::Map::cameraForLatLngs
. The input values are wrapped and clamped appropriately before use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before clamping, we need to check for < 0
, which means “ignore” only on iOS and macOS, but not on other platforms.
@1ec5 updated and changed this PR to add a single new function, |
platform/ios/src/MGLMapView.h
Outdated
`0` results in a camera pointed straight down at the map. Angles greater | ||
than `0` result in a camera angled toward the horizon. | ||
@param camera The camera that the return camera should adhere to. All values | ||
on this camera will be manipulated except for pitch and direction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: missing a period at the end.
platform/ios/src/MGLMapView.mm
Outdated
return [self cameraForCameraOptions:cameraOptions]; | ||
|
||
CGFloat pitch = camera.pitch; | ||
CLLocationDirection direction = camera.heading; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When pitch
or heading
is negative, it should be ignored in favor of the most appropriate value (which can be the current value).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm this brings up a good point. I feel like the lowest level function should be responsible for normalizing the direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the lowest-level method at which the normalization is relevant. The negative-means-ignored behavior is specific to Apple platforms.
platform/ios/src/MGLMapView.h
Outdated
*/ | ||
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds direction:(CLLocationDirection)direction pitch:(CGFloat)pitch edgePadding:(UIEdgeInsets)insets; | ||
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(UIEdgeInsets)insets; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a need for a -camera:fittingCoordinateBounds:edgePadding:
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will add.
Also, remember to make any corresponding changes to the macOS implementation of MGLMapView, then add a blurb to the iOS and macOS map SDKs noting the addition of these methods. |
platform/ios/CHANGELOG.md
Outdated
@@ -2,6 +2,10 @@ | |||
|
|||
Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started. | |||
|
|||
# master | |||
|
|||
* Added `-[MGLMapView camera:fittingShape:edgePadding:]` and `-[MGLMapView camera:fittingCoordinateBounds:edgePadding:]` allowing you specify the pitch and direction for the calculated camera. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also add a link to this PR in parentheses.
platform/ios/src/MGLMapView.mm
Outdated
return [self cameraForCameraOptions:cameraOptions]; | ||
|
||
CGFloat pitch = camera.pitch; | ||
CLLocationDirection direction = camera.heading; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the lowest-level method at which the normalization is relevant. The negative-means-ignored behavior is specific to Apple platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One minor change to make, but otherwise good to go.
platform/macos/src/MGLMapView.mm
Outdated
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); | ||
|
||
CGFloat pitch = camera.pitch < 0 ? _mbglMap->getPitch() : camera.pitch; | ||
CLLocationDirection direction = camera.heading < 0 ? _mbglMap->getBearing() : camera.heading; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When possible, use existing property getters to ensure that normalization is applied consistently:
MGLMapCamera *currentCamera = self.camera;
CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch;
CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading;
(There’s also an MGLMapView.direction
, but we have to call -camera
anyways to get the current pitch.)
×4
In the navigation sdk, we use
MGLMapView.cameraThatFitsShape(_:direction: edgePadding)
to go from navigation mode to overhead mode. However, since this function does not provide a pitch argument, the function relies on the mapview's current pitch to calculate the new camera. This does not work well because we want to end up overhead, looking straight down.Because of this, we have to jarringly set the pitch to 0, then find the appropriate camera that fits the current route line. If we could pass in the desired pitch, this first camera animation would not be required.
This change allows for developers to pass in a desired pitch for a few of the
cameraThatFites...
functions./cc @asheemmamoowala @1ec5 @frederoni
todo: