Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Add pitch argument to cameraThatFits functions #12213

Merged
merged 18 commits into from
Jul 2, 2018
6 changes: 3 additions & 3 deletions include/mbgl/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ class Map : private util::noncopyable {
void jumpTo(const CameraOptions&);
void easeTo(const CameraOptions&, const AnimationOptions&);
void flyTo(const CameraOptions&, const AnimationOptions&);
CameraOptions cameraForLatLngBounds(const LatLngBounds&, const EdgeInsets&, optional<double> bearing = {}) const;
CameraOptions cameraForLatLngs(const std::vector<LatLng>&, const EdgeInsets&, optional<double> bearing = {}) const;
CameraOptions cameraForGeometry(const Geometry<double>&, const EdgeInsets&, optional<double> bearing = {}) const;
CameraOptions cameraForLatLngBounds(const LatLngBounds&, const EdgeInsets&, optional<double> bearing = {}, optional<double> pitch = {}) const;
CameraOptions cameraForLatLngs(const std::vector<LatLng>&, const EdgeInsets&, optional<double> bearing = {}, optional<double> pitch = {}) const;
CameraOptions cameraForGeometry(const Geometry<double>&, const EdgeInsets&, optional<double> bearing = {}, optional<double> pitch = {}) const;
LatLngBounds latLngBoundsForCamera(const CameraOptions&) const;

// Position
Expand Down
4 changes: 4 additions & 0 deletions platform/ios/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. ([#12213](https://github.com/mapbox/mapbox-gl-native/pull/12213))

## 4.2.0

### Packaging
Expand Down
31 changes: 31 additions & 0 deletions platform/ios/src/MGLMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,37 @@ MGL_EXPORT IB_DESIGNABLE
*/
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets;

/**
Returns the camera that best fits the given coordinate bounds, with the specified camera,
optionally with some additional padding on each side.

@param camera The camera that the return camera should adhere to. All values
on this camera will be manipulated except for pitch and direction.
@param bounds The coordinate bounds to fit to the receiver’s viewport.
@param insets The minimum padding (in screen points) that would be visible
around the returned camera object if it were set as the receiver’s camera.
@return A camera object centered on the same location as the coordinate bounds
with zoom level as high (close to the ground) as possible while still
including the entire coordinate bounds. The initial camera's pitch and
direction will be honored.
*/
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets;

/**
Returns the camera that best fits the given shape, with the specified camera,
optionally with some additional padding on each side.

@param camera The camera that the return camera should adhere to. All values
on this camera will be manipulated except for pitch and direction.
@param shape The shape to fit to the receiver’s viewport.
@param insets The minimum padding (in screen points) that would be visible
around the returned camera object if it were set as the receiver’s camera.
@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. The
initial camera's pitch and direction will be honored.
*/
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(UIEdgeInsets)insets;
Copy link
Contributor

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?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will add.


/**
Returns the camera that best fits the given shape, with the specified direction,
optionally with some additional padding on each side.
Expand Down
29 changes: 26 additions & 3 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3348,14 +3348,37 @@ - (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edg
return [self cameraForCameraOptions:cameraOptions];
}

- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets {
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets
{
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);

CGFloat pitch = camera.pitch < 0 ? _mbglMap->getPitch() : camera.pitch;
CLLocationDirection direction = camera.heading < 0 ? _mbglMap->getBearing() : camera.heading;

mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch);
Copy link
Contributor

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.

if (camera.heading >= 0)
{
options.angle = MGLRadiansFromDegrees(-camera.heading);
}
if (camera.pitch >= 0)
{
options.pitch = MGLRadiansFromDegrees(camera.pitch);
}

Copy link
Contributor

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.

Copy link
Contributor

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.

return [self cameraForCameraOptions:cameraOptions];
}

mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction);
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(UIEdgeInsets)insets {
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);

CGFloat pitch = camera.pitch < 0 ? _mbglMap->getPitch() : camera.pitch;
CLLocationDirection direction = camera.heading < 0 ? _mbglMap->getBearing() : camera.heading;

mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction, pitch);

return [self cameraForCameraOptions: cameraOptions];
}

- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets {
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);

mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction);

return [self cameraForCameraOptions:cameraOptions];

}

- (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions
Expand Down
4 changes: 4 additions & 0 deletions platform/macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog for Mapbox Maps SDK for macOS

# master

* Added `-[MGLMapView camera:fittingShape:edgePadding:]` and `-[MGLMapView camera:fittingCoordinateBounds:edgePadding:]` allowing you specify the pitch and direction for the calculated camera. ([#12213](https://github.com/mapbox/mapbox-gl-native/pull/12213))

## 0.7.2 - June 22, 2018

* Fixed a crash in `-[MGLStyle localizeLabelsIntoLocale:]` on macOS 10.11. ([#12123](https://github.com/mapbox/mapbox-gl-native/pull/12123))
Expand Down
31 changes: 31 additions & 0 deletions platform/macos/src/MGLMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,37 @@ MGL_EXPORT IB_DESIGNABLE
*/
- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets;

/**
Returns the camera that best fits the given coordinate bounds, with the specified camera,
optionally with some additional padding on each side.

@param camera The camera that the return camera should adhere to. All values
on this camera will be manipulated except for pitch and direction.
@param bounds The coordinate bounds to fit to the receiver’s viewport.
@param insets The minimum padding (in screen points) that would be visible
around the returned camera object if it were set as the receiver’s camera.
@return A camera object centered on the same location as the coordinate bounds
with zoom level as high (close to the ground) as possible while still
including the entire coordinate bounds. The initial camera's pitch and
direction will be honored.
*/
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets;

/**
Returns the camera that best fits the given shape, with the specified camera,
optionally with some additional padding on each side.

@param camera The camera that the return camera should adhere to. All values
on this camera will be manipulated except for pitch and direction.
@param shape The shape to fit to the receiver’s viewport.
@param insets The minimum padding (in screen points) that would be visible
around the returned camera object if it were set as the receiver’s camera.
@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. The
initial camera's pitch and direction will be honored.
*/
- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(NSEdgeInsets)insets;

/**
Returns the camera that best fits the given shape, with the specified direction,
optionally with some additional padding on each side.
Expand Down
24 changes: 24 additions & 0 deletions platform/macos/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,30 @@ - (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edg
return [self cameraForCameraOptions:cameraOptions];
}

- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets
{
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);

CGFloat pitch = camera.pitch < 0 ? _mbglMap->getPitch() : camera.pitch;
CLLocationDirection direction = camera.heading < 0 ? _mbglMap->getBearing() : camera.heading;

mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch);
return [self cameraForCameraOptions:cameraOptions];
}

- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(NSEdgeInsets)insets {
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);

CGFloat pitch = camera.pitch < 0 ? _mbglMap->getPitch() : camera.pitch;
CLLocationDirection direction = camera.heading < 0 ? _mbglMap->getBearing() : camera.heading;
Copy link
Contributor

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


mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction, pitch);

return [self cameraForCameraOptions: cameraOptions];
}

- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets {
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
Expand Down
37 changes: 24 additions & 13 deletions src/mbgl/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,13 +364,13 @@ void Map::setLatLngZoom(const LatLng& latLng, double zoom, const EdgeInsets& pad
impl->onUpdate();
}

CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, const EdgeInsets& padding, optional<double> bearing) const {
CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, const EdgeInsets& padding, optional<double> bearing, optional<double> pitch) const {
return cameraForLatLngs({
bounds.northwest(),
bounds.southwest(),
bounds.southeast(),
bounds.northeast(),
}, padding, bearing);
}, padding, bearing, pitch);
}

CameraOptions cameraForLatLngs(const std::vector<LatLng>& latLngs, const Transform& transform, const EdgeInsets& padding) {
Expand Down Expand Up @@ -426,26 +426,37 @@ CameraOptions cameraForLatLngs(const std::vector<LatLng>& latLngs, const Transfo
return options;
}

CameraOptions Map::cameraForLatLngs(const std::vector<LatLng>& latLngs, const EdgeInsets& padding, optional<double> bearing) const {
if(bearing) {
double angle = -*bearing * util::DEG2RAD; // Convert to radians
Transform transform(impl->transform.getState());
transform.setAngle(angle);
CameraOptions options = mbgl::cameraForLatLngs(latLngs, transform, padding);
options.angle = angle;
return options;
} else {
CameraOptions Map::cameraForLatLngs(const std::vector<LatLng>& latLngs, const EdgeInsets& padding, optional<double> bearing, optional<double> pitch) const {

if (!bearing && !pitch) {
return mbgl::cameraForLatLngs(latLngs, impl->transform, padding);
}

Transform transform(impl->transform.getState());

if (bearing) {
double angle = -*bearing * util::DEG2RAD; // Convert to radians
transform.setAngle(angle);
}
if (pitch) {
double pitchAsRadian = *pitch * util::DEG2RAD; // Convert to radians
Copy link
Author

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?

Copy link
Contributor

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,

transform.setPitch(pitchAsRadian);
}

CameraOptions options = mbgl::cameraForLatLngs(latLngs, transform, padding);
options.angle = transform.getAngle();
options.pitch = transform.getPitch();

return options;
}

CameraOptions Map::cameraForGeometry(const Geometry<double>& geometry, const EdgeInsets& padding, optional<double> bearing) const {
CameraOptions Map::cameraForGeometry(const Geometry<double>& geometry, const EdgeInsets& padding, optional<double> bearing, optional<double> pitch) const {

std::vector<LatLng> latLngs;
forEachPoint(geometry, [&](const Point<double>& pt) {
latLngs.push_back({ pt.y, pt.x });
});
return cameraForLatLngs(latLngs, padding, bearing);
return cameraForLatLngs(latLngs, padding, bearing, pitch);
}

LatLngBounds Map::latLngBoundsForCamera(const CameraOptions& camera) const {
Expand Down
32 changes: 30 additions & 2 deletions test/map/map.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,21 @@ 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-5);
}

TEST(Map, LatLngBoundsToCameraWithAngleAndPitch) {
MapTest<> test;

test.map.setLatLngZoom({ 40.712730, -74.005953 }, 16.0);

LatLngBounds bounds = LatLngBounds::hull({15.68169,73.499857}, {53.560711, 134.77281});

CameraOptions virtualCamera = test.map.cameraForLatLngBounds(bounds, {}, 35, 20);
ASSERT_TRUE(bounds.contains(*virtualCamera.center));
EXPECT_NEAR(*virtualCamera.zoom, 13.66272, 1e-5);
ASSERT_DOUBLE_EQ(*virtualCamera.pitch, 20 * util::DEG2RAD);
EXPECT_NEAR(virtualCamera.angle.value_or(0), -35 * util::DEG2RAD, 1e-5);
}

TEST(Map, LatLngsToCamera) {
Expand All @@ -93,12 +107,26 @@ TEST(Map, LatLngsToCamera) {
std::vector<LatLng> latLngs{{ 40.712730, 74.005953 }, {15.68169,73.499857}, {30.82678, 83.4082}};

CameraOptions virtualCamera = test.map.cameraForLatLngs(latLngs, {}, 23);
EXPECT_DOUBLE_EQ(virtualCamera.angle.value_or(0), -23 * util::DEG2RAD);
EXPECT_NEAR(virtualCamera.angle.value_or(0), -23 * util::DEG2RAD, 1e-5);
EXPECT_NEAR(virtualCamera.zoom.value_or(0), 2.75434, 1e-5);
EXPECT_NEAR(virtualCamera.center->latitude(), 28.49288, 1e-5);
EXPECT_NEAR(virtualCamera.center->longitude(), 74.97437, 1e-5);
}

TEST(Map, LatLngsToCameraWithAngleAndPitch) {
MapTest<> test;

std::vector<LatLng> latLngs{{ 40.712730, 74.005953 }, {15.68169,73.499857}, {30.82678, 83.4082}};

CameraOptions virtualCamera = test.map.cameraForLatLngs(latLngs, {}, 23, 20);
EXPECT_NEAR(virtualCamera.angle.value_or(0), -23 * util::DEG2RAD, 1e-5);
EXPECT_NEAR(virtualCamera.zoom.value_or(0), 3.04378, 1e-5);
EXPECT_NEAR(virtualCamera.center->latitude(), 28.53718, 1e-5);
EXPECT_NEAR(virtualCamera.center->longitude(), 74.31746, 1e-5);
ASSERT_DOUBLE_EQ(*virtualCamera.pitch, 20 * util::DEG2RAD);
}


TEST(Map, CameraToLatLngBounds) {
MapTest<> test;

Expand Down