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

#2329 Constrain map to user specified bounds #2341

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/mbgl/ios/MGLMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ IB_DESIGNABLE
* @param function A timing function used for the animation. Set this parameter to `nil` for a transition that matches most system animations. If the duration is `0`, this parameter is ignored. */
- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function;

/**
* Constrains map movement within the given coordinates.
*
* @param southWest The south west coordinate to constrain the map to.
* @param northEast The north east coordinate to constrain the map to.
*/
- (void)setUserConstraintsSouthWest:(CLLocationCoordinate2D)southWest andNorthEast:(CLLocationCoordinate2D)northEast;

/** Moves the viewpoint to a different location with respect to the map with an optional transition duration and timing function.
* @param camera The new viewpoint.
* @param duration The amount of time, measured in seconds, that the transition animation should take. Specify `0` to jump to the new viewpoint instantaneously.
Expand Down
3 changes: 3 additions & 0 deletions include/mbgl/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ class Map : private util::noncopyable {
// Size
uint16_t getWidth() const;
uint16_t getHeight() const;

// User constraints
void setUserConstraints(LatLngBounds userConstraints);

// Projection
MetersBounds getWorldBoundsMeters() const;
Expand Down
5 changes: 5 additions & 0 deletions platform/ios/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,11 @@ - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration a
_mbglMap->easeTo(options);
}

- (void)setUserConstraintsSouthWest:(CLLocationCoordinate2D)southWest andNorthEast:(CLLocationCoordinate2D)northEast
{
_mbglMap->setUserConstraints(mbgl::LatLngBounds(mbgl::LatLng(southWest.latitude, southWest.longitude), mbgl::LatLng(northEast.latitude, northEast.longitude)));
}

- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view
{
CGPoint convertedPoint = [self convertPoint:point fromView:view];
Expand Down
5 changes: 5 additions & 0 deletions src/mbgl/map/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,12 @@ uint16_t Map::getHeight() const {
return transform->getState().getHeight();
}

#pragma mark - User constrains

void Map::setUserConstraints(LatLngBounds userConstraints) {
transform->setUserConstraints(userConstraints);
}

#pragma mark - Rotation

void Map::rotateBy(const PrecisionPoint& first, const PrecisionPoint& second, const Duration& duration) {
Expand Down
3 changes: 3 additions & 0 deletions src/mbgl/map/transform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ bool Transform::resize(const std::array<uint16_t, 2> size) {

state.width = size[0];
state.height = size[1];
state.userConstrain(state.scale, state.x, state.y);
state.constrain(state.scale, state.x, state.y);

view.notifyMapChange(MapChangeRegionDidChange);
Expand Down Expand Up @@ -106,6 +107,7 @@ void Transform::_moveBy(const PrecisionPoint& point, const Duration& duration) {
double x = state.x + std::cos(state.angle) * point.x + std::sin( state.angle) * point.y;
double y = state.y + std::cos(state.angle) * point.y + std::sin(-state.angle) * point.x;

state.userConstrain(state.scale, x, y);
state.constrain(state.scale, x, y);

CameraOptions options;
Expand Down Expand Up @@ -240,6 +242,7 @@ void Transform::_easeTo(const CameraOptions& options, double new_scale, double n
double x = xn;
double y = yn;

state.userConstrain(scale, x, y);
state.constrain(scale, x, y);

double angle = _normalizeAngle(new_angle, state.angle);
Expand Down
3 changes: 3 additions & 0 deletions src/mbgl/map/transform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class Transform : private util::noncopyable {
bool isScaling() const { return state.isScaling(); }
bool isPanning() const { return state.isPanning(); }

// User constraints
void setUserConstraints(LatLngBounds userConstraints) { state.userConstraints = userConstraints; }

private:
void _moveBy(const PrecisionPoint&, const Duration& = Duration::zero());
void _setScale(double scale, const PrecisionPoint& center, const Duration& = Duration::zero());
Expand Down
55 changes: 55 additions & 0 deletions src/mbgl/map/transform_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ PrecisionPoint TransformState::latLngToPoint(const LatLng& latLng) const {
return coordinateToPoint(latLngToCoordinate(latLng));
}

PrecisionPoint TransformState::latLngToXY(LatLng latLng) const {
const double m = 1 - 1e-15;
const double f = std::fmin(std::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);

const double s = scale * util::tileSize;
double Bc_ = s / 360;
double Cc_ = s / util::M2PI;

double xn = -latLng.longitude * Bc_;
double yn = 0.5 * Cc_ * std::log((1 + f) / (1 - f));

return {xn, yn};
}

LatLng TransformState::pointToLatLng(const PrecisionPoint& point) const {
return coordinateToLatLng(pointToCoordinate(point));
}
Expand Down Expand Up @@ -318,6 +332,47 @@ mat4 TransformState::getPixelMatrix() const {

#pragma mark - (private helper functions)

void TransformState::userConstrain(double& scale_, double& x_, double& y_) const {
// Calculate a constraining bounding box
double scaleFactor = scale_ / scale;
double max_x = latLngToXY(userConstraints.ne).x * scaleFactor;
double min_x = latLngToXY(userConstraints.sw).x * scaleFactor;
double max_y = latLngToXY(userConstraints.ne).y * scaleFactor;
double min_y = latLngToXY(userConstraints.sw).y * scaleFactor;

// Check if the requested zoom level fits in the constraining box, if not generate a scale factor to adjust the requesed zoom level by
double fitScaleFactor = height / fabs(max_y - min_y);
if (width / fabs(max_x - min_x) > fitScaleFactor) {
fitScaleFactor = width / fabs(max_x - min_x);
}

// Adjust the new zoom level if necessary and update the constrained bounding box to the new scale
if (fitScaleFactor > 1.0) {
scale_ = scale_ * fitScaleFactor;

scaleFactor = scale_ / scale;
max_x = latLngToXY(userConstraints.ne).x * scaleFactor;
min_x = latLngToXY(userConstraints.sw).x * scaleFactor;
max_y = latLngToXY(userConstraints.ne).y * scaleFactor;
min_y = latLngToXY(userConstraints.sw).y * scaleFactor;
}

// Calculate the requested visible bounds to use when checking against the constraints
double x_left = x_ + (width / 2);
double x_right = x_ - (width / 2);
double y_bottom = y_ - (height / 2);
double y_top = y_ + (height / 2);

// Check each side of the bounding box and constrain if necessary
// Only constrain x if necessary to allow continuous scroll if no user constraints are set
if (userConstraints.ne.longitude < 180 || userConstraints.sw.longitude > -180) {
if (x_left > min_x) x_ = min_x - (width / 2);
if (x_right < max_x) x_ = max_x + (width / 2);
}
if (y_bottom < min_y) y_ = min_y + (height / 2);
if (y_top > max_y) y_ = max_y - (height / 2);
}

void TransformState::constrain(double& scale_, double& x_, double& y_) const {
// Constrain minimum scale to avoid zooming out far enough to show off-world areas.
if (constrainMode == ConstrainMode::WidthAndHeight) {
Expand Down
6 changes: 6 additions & 0 deletions src/mbgl/map/transform_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ class TransformState {

// State
bool isChanging() const;

// User constraints
LatLngBounds userConstraints = LatLngBounds(LatLng(-90, -180), LatLng(90, 180));

bool isRotating() const;
bool isScaling() const;
bool isPanning() const;
Expand All @@ -71,10 +75,12 @@ class TransformState {
LatLng coordinateToLatLng(const TileCoordinate&) const;

PrecisionPoint coordinateToPoint(const TileCoordinate&) const;
PrecisionPoint latLngToXY(LatLng latlng) const;
TileCoordinate pointToCoordinate(const PrecisionPoint&) const;

private:
void constrain(double& scale, double& x, double& y) const;
void userConstrain(double& scale_, double& x_, double& y_) const;

// Limit the amount of zooming possible on the map.
double min_scale = std::pow(2, 0);
Expand Down