Allow constraining camera to maximum bounds (#2475)
This commit is contained in:
@@ -12,6 +12,7 @@ namespace mbgl {
|
|||||||
*/
|
*/
|
||||||
struct BoundOptions {
|
struct BoundOptions {
|
||||||
/// Sets the latitude and longitude bounds to which the camera center are constrained
|
/// Sets the latitude and longitude bounds to which the camera center are constrained
|
||||||
|
/// If ConstrainMode is set to Screen these bounds describe what can be shown on screen.
|
||||||
BoundOptions& withLatLngBounds(LatLngBounds b) {
|
BoundOptions& withLatLngBounds(LatLngBounds b) {
|
||||||
bounds = b;
|
bounds = b;
|
||||||
return *this;
|
return *this;
|
||||||
@@ -38,6 +39,7 @@ struct BoundOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Constrain the center of the camera to be within these bounds.
|
/// Constrain the center of the camera to be within these bounds.
|
||||||
|
/// If ConstrainMode is set to Screen these bounds describe what can be shown on screen.
|
||||||
std::optional<LatLngBounds> bounds;
|
std::optional<LatLngBounds> bounds;
|
||||||
|
|
||||||
/// Maximum zoom level allowed.
|
/// Maximum zoom level allowed.
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ enum class MapMode : EnumType {
|
|||||||
Tile ///< a once-off still image of a single tile
|
Tile ///< a once-off still image of a single tile
|
||||||
};
|
};
|
||||||
|
|
||||||
/// We can choose to constrain the map both horizontally or vertically, or only
|
/// We can choose to constrain the map both horizontally or vertically, only
|
||||||
/// vertically e.g. while panning.
|
/// vertically e.g. while panning, or screen to the specified bounds.
|
||||||
enum class ConstrainMode : EnumType {
|
enum class ConstrainMode : EnumType {
|
||||||
None,
|
None,
|
||||||
HeightOnly,
|
HeightOnly,
|
||||||
WidthAndHeight,
|
WidthAndHeight,
|
||||||
|
Screen,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Satisfies embedding platforms that requires the viewport coordinate systems
|
/// Satisfies embedding platforms that requires the viewport coordinate systems
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ struct MapLibreNavigationView: View {
|
|||||||
NavigationLink("BlockingGesturesExample") {
|
NavigationLink("BlockingGesturesExample") {
|
||||||
BlockingGesturesExample()
|
BlockingGesturesExample()
|
||||||
}
|
}
|
||||||
|
NavigationLink("MaximumScreenBoundsExample") {
|
||||||
|
MaximumScreenBoundsExample()
|
||||||
|
}
|
||||||
NavigationLink("LineStyleLayerExample") {
|
NavigationLink("LineStyleLayerExample") {
|
||||||
LineStyleLayerExampleUIViewControllerRepresentable()
|
LineStyleLayerExampleUIViewControllerRepresentable()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import MapLibre
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// Denver, Colorado
|
||||||
|
private let center = CLLocationCoordinate2D(latitude: 39.748947, longitude: -104.995882)
|
||||||
|
|
||||||
|
// Colorado’s bounds
|
||||||
|
private let colorado = MLNCoordinateBounds(
|
||||||
|
sw: CLLocationCoordinate2D(latitude: 36.986207, longitude: -109.049896),
|
||||||
|
ne: CLLocationCoordinate2D(latitude: 40.989329, longitude: -102.062592)
|
||||||
|
)
|
||||||
|
|
||||||
|
struct MaximumScreenBoundsExample: UIViewRepresentable {
|
||||||
|
func makeUIView(context _: Context) -> MLNMapView {
|
||||||
|
let mapView = MLNMapView(frame: .zero, styleURL: VERSATILES_COLORFUL_STYLE)
|
||||||
|
mapView.setCenter(center, zoomLevel: 10, direction: 0, animated: false)
|
||||||
|
mapView.maximumScreenBounds = MLNCoordinateBounds(sw: colorado.sw, ne: colorado.ne)
|
||||||
|
|
||||||
|
return mapView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_: MLNMapView, context _: Context) {}
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -915,6 +915,13 @@ vertically on the map.
|
|||||||
*/
|
*/
|
||||||
@property (nonatomic) double maximumZoomLevel;
|
@property (nonatomic) double maximumZoomLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum bounds of the map that can be shown on screen.
|
||||||
|
*
|
||||||
|
* @param MLNCoordinateBounds the bounds to constrain the screen to.
|
||||||
|
*/
|
||||||
|
@property (nonatomic) MLNCoordinateBounds maximumScreenBounds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The heading of the map, measured in degrees clockwise from true north.
|
The heading of the map, measured in degrees clockwise from true north.
|
||||||
|
|
||||||
|
|||||||
@@ -3867,6 +3867,21 @@ static void *windowScreenContext = &windowScreenContext;
|
|||||||
return *self.mbglMap.getBounds().maxZoom;
|
return *self.mbglMap.getBounds().maxZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setMaximumScreenBounds:(MLNCoordinateBounds)maximumScreenBounds
|
||||||
|
{
|
||||||
|
mbgl::LatLng sw = {maximumScreenBounds.sw.latitude, maximumScreenBounds.sw.longitude};
|
||||||
|
mbgl::LatLng ne = {maximumScreenBounds.ne.latitude, maximumScreenBounds.ne.longitude};
|
||||||
|
mbgl::BoundOptions newBounds = mbgl::BoundOptions().withLatLngBounds(mbgl::LatLngBounds::hull(sw, ne));
|
||||||
|
|
||||||
|
self.mbglMap.setBounds(newBounds);
|
||||||
|
self.mbglMap.setConstrainMode(mbgl::ConstrainMode::Screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (MLNCoordinateBounds)maximumScreenBounds
|
||||||
|
{
|
||||||
|
return MLNCoordinateBoundsFromLatLngBounds(*self.mbglMap.getBounds().bounds);;
|
||||||
|
}
|
||||||
|
|
||||||
- (CGFloat)minimumPitch
|
- (CGFloat)minimumPitch
|
||||||
{
|
{
|
||||||
return *self.mbglMap.getBounds().minPitch;
|
return *self.mbglMap.getBounds().minPitch;
|
||||||
|
|||||||
@@ -60,6 +60,23 @@ void Transform::resize(const Size size) {
|
|||||||
double scale{state.getScale()};
|
double scale{state.getScale()};
|
||||||
double x{state.getX()};
|
double x{state.getX()};
|
||||||
double y{state.getY()};
|
double y{state.getY()};
|
||||||
|
|
||||||
|
double lat;
|
||||||
|
double lon;
|
||||||
|
if (state.constrainScreen(scale, lat, lon)) {
|
||||||
|
// Turns out that if you resize during a transition any changes made to the state will be ignored :(
|
||||||
|
// So if we have to constrain because of a resize and a transition is in progress - cancel the transition!
|
||||||
|
if (inTransition()) {
|
||||||
|
cancelTransitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// It also turns out that state.setProperties isn't enough if you change the center, you need to set Cc and Bc
|
||||||
|
// too, which setLatLngZoom does.
|
||||||
|
state.setLatLngZoom(mbgl::LatLng{lat, lon}, state.scaleZoom(scale));
|
||||||
|
observer.onCameraDidChange(MapObserver::CameraChangeMode::Immediate);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
state.constrain(scale, x, y);
|
state.constrain(scale, x, y);
|
||||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
|
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
|
||||||
|
|
||||||
@@ -86,17 +103,23 @@ void Transform::jumpTo(const CameraOptions& camera) {
|
|||||||
* smooth animation between old and new values. The map will retain the current
|
* smooth animation between old and new values. The map will retain the current
|
||||||
* values for any options not included in `options`.
|
* values for any options not included in `options`.
|
||||||
*/
|
*/
|
||||||
void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& animation) {
|
void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions& animation) {
|
||||||
|
CameraOptions camera = inputCamera;
|
||||||
|
|
||||||
Duration duration = animation.duration.value_or(Duration::zero());
|
Duration duration = animation.duration.value_or(Duration::zero());
|
||||||
if (state.getLatLngBounds() == LatLngBounds() && !isGestureInProgress() && duration != Duration::zero()) {
|
if (state.getLatLngBounds() == LatLngBounds() && !isGestureInProgress() && duration != Duration::zero()) {
|
||||||
// reuse flyTo, without exaggerated animation, to achieve constant ground speed.
|
// reuse flyTo, without exaggerated animation, to achieve constant ground speed.
|
||||||
return flyTo(camera, animation, true);
|
return flyTo(camera, animation, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double zoom = camera.zoom.value_or(getZoom());
|
||||||
|
state.constrainCameraAndZoomToBounds(camera, zoom);
|
||||||
|
|
||||||
const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets());
|
const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets());
|
||||||
LatLng startLatLng = getLatLng(LatLng::Unwrapped);
|
LatLng startLatLng = getLatLng(LatLng::Unwrapped);
|
||||||
const LatLng& unwrappedLatLng = camera.center.value_or(startLatLng);
|
const LatLng& unwrappedLatLng = camera.center.value_or(startLatLng);
|
||||||
const LatLng& latLng = state.getLatLngBounds() != LatLngBounds() ? unwrappedLatLng : unwrappedLatLng.wrapped();
|
const LatLng& latLng = state.getLatLngBounds() != LatLngBounds() ? unwrappedLatLng : unwrappedLatLng.wrapped();
|
||||||
double zoom = camera.zoom.value_or(getZoom());
|
|
||||||
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
|
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
|
||||||
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
|
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
|
||||||
|
|
||||||
@@ -176,10 +199,17 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
|
|||||||
|
|
||||||
Where applicable, local variable documentation begins with the associated
|
Where applicable, local variable documentation begins with the associated
|
||||||
variable or function in van Wijk (2003). */
|
variable or function in van Wijk (2003). */
|
||||||
void Transform::flyTo(const CameraOptions& camera, const AnimationOptions& animation, bool linearZoomInterpolation) {
|
void Transform::flyTo(const CameraOptions& inputCamera,
|
||||||
|
const AnimationOptions& animation,
|
||||||
|
bool linearZoomInterpolation) {
|
||||||
|
CameraOptions camera = inputCamera;
|
||||||
|
|
||||||
|
double zoom = camera.zoom.value_or(getZoom());
|
||||||
|
state.constrainCameraAndZoomToBounds(camera, zoom);
|
||||||
|
|
||||||
const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets());
|
const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets());
|
||||||
const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped();
|
const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped();
|
||||||
double zoom = camera.zoom.value_or(getZoom());
|
|
||||||
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
|
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
|
||||||
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
|
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
|
||||||
|
|
||||||
|
|||||||
@@ -748,8 +748,27 @@ bool TransformState::rotatedNorth() const {
|
|||||||
return (orientation == NO::Leftwards || orientation == NO::Rightwards);
|
return (orientation == NO::Leftwards || orientation == NO::Rightwards);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TransformState::constrainScreen(double& scale_, double& lat, double& lon) const {
|
||||||
|
if (constrainMode == ConstrainMode::Screen) {
|
||||||
|
double zoom = scaleZoom(scale_);
|
||||||
|
CameraOptions options = CameraOptions();
|
||||||
|
constrainCameraAndZoomToBounds(options, zoom);
|
||||||
|
|
||||||
|
scale_ = zoomScale(zoom);
|
||||||
|
|
||||||
|
if (options.center) {
|
||||||
|
LatLng center = options.center.value();
|
||||||
|
lat = center.latitude();
|
||||||
|
lon = center.longitude();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void TransformState::constrain(double& scale_, double& x_, double& y_) const {
|
void TransformState::constrain(double& scale_, double& x_, double& y_) const {
|
||||||
if (constrainMode == ConstrainMode::None) {
|
if (constrainMode == ConstrainMode::None || constrainMode == ConstrainMode::Screen) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,6 +787,131 @@ void TransformState::constrain(double& scale_, double& x_, double& y_) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TransformState::constrainCameraAndZoomToBounds(CameraOptions& requestedCamera, double& requestedZoom) const {
|
||||||
|
if (constrainMode != ConstrainMode::Screen || getLatLngBounds() == LatLngBounds()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LatLng centerLatLng = getLatLng();
|
||||||
|
|
||||||
|
if (requestedCamera.center) {
|
||||||
|
centerLatLng = requestedCamera.center.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
Point<double> anchorOffset{0, 0};
|
||||||
|
double requestedScale = zoomScale(requestedZoom);
|
||||||
|
|
||||||
|
// Since the transition calculations will include any specified anchor in the result
|
||||||
|
// we need to do the same when testing if the requested center and zoom is outside the bounds or not.
|
||||||
|
if (requestedCamera.anchor) {
|
||||||
|
ScreenCoordinate anchor = requestedCamera.anchor.value();
|
||||||
|
anchor.y = getSize().height - anchor.y;
|
||||||
|
LatLng anchorLatLng = screenCoordinateToLatLng(anchor);
|
||||||
|
|
||||||
|
// The screenCoordinateToLatLng function requires the matrices inside the state to reflect
|
||||||
|
// the requested scale. So we create a copy and set the requested zoom before the conversion.
|
||||||
|
// This will give us the same result as the transition calculations.
|
||||||
|
TransformState state{*this};
|
||||||
|
state.setLatLngZoom(getLatLng(), scaleZoom(requestedScale));
|
||||||
|
LatLng screenLatLng = state.screenCoordinateToLatLng(anchor);
|
||||||
|
|
||||||
|
auto latLngCoord = Projection::project(anchorLatLng, requestedScale);
|
||||||
|
auto anchorCoord = Projection::project(screenLatLng, requestedScale);
|
||||||
|
anchorOffset = latLngCoord - anchorCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbgl::LatLngBounds currentBounds = getLatLngBounds();
|
||||||
|
mbgl::ScreenCoordinate neBounds = Projection::project(currentBounds.northeast(), requestedScale);
|
||||||
|
mbgl::ScreenCoordinate swBounds = Projection::project(currentBounds.southwest(), requestedScale);
|
||||||
|
mbgl::ScreenCoordinate center = Projection::project(centerLatLng, requestedScale);
|
||||||
|
mbgl::ScreenCoordinate currentCenter = Projection::project(getLatLng(), requestedScale);
|
||||||
|
|
||||||
|
double minY = neBounds.y;
|
||||||
|
double maxY = swBounds.y;
|
||||||
|
double minX = swBounds.x;
|
||||||
|
double maxX = neBounds.x;
|
||||||
|
|
||||||
|
double startX = center.x;
|
||||||
|
double startY = center.y;
|
||||||
|
|
||||||
|
double resultX = startX;
|
||||||
|
double resultY = startY;
|
||||||
|
|
||||||
|
uint32_t screenWidth = getSize().width;
|
||||||
|
uint32_t screenHeight = getSize().height;
|
||||||
|
|
||||||
|
double h2 = screenHeight / 2.0;
|
||||||
|
if (startY - h2 + anchorOffset.y < minY) {
|
||||||
|
resultY = minY + h2;
|
||||||
|
}
|
||||||
|
if (startY + anchorOffset.y + h2 > maxY) {
|
||||||
|
resultY = maxY - h2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double w2 = screenWidth / 2.0;
|
||||||
|
if (startX + anchorOffset.x - w2 < minX) {
|
||||||
|
resultX = minX + w2;
|
||||||
|
}
|
||||||
|
if (startX + anchorOffset.x + w2 > maxX) {
|
||||||
|
resultX = maxX - w2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double scaleY = 0;
|
||||||
|
if (maxY - minY < screenHeight) {
|
||||||
|
scaleY = screenHeight / (maxY - minY);
|
||||||
|
resultY = (maxY + minY) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double scaleX = 0;
|
||||||
|
if (maxX - minX < screenWidth) {
|
||||||
|
scaleX = screenWidth / (maxX - minX);
|
||||||
|
resultX = (maxX + minX) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double maxScale = scaleX > scaleY ? scaleX : scaleY;
|
||||||
|
|
||||||
|
// Max scale will be 1 when the screen is exactly the same size as the max bounds in either the X or Y direction.
|
||||||
|
// To avoid numerical instabilities we add small amount to the check to make sure we don't try to scale when we
|
||||||
|
// don't actually need it.
|
||||||
|
if (maxScale > 1.000001) {
|
||||||
|
requestedZoom += scaleZoom(maxScale);
|
||||||
|
|
||||||
|
if (scaleY > scaleX) {
|
||||||
|
// If we scaled the y direction we want the resulting x position to be the same as the current x position.
|
||||||
|
resultX = currentCenter.x;
|
||||||
|
} else {
|
||||||
|
// If we scaled the x direction we want the resulting y position to be the same as the current y position.
|
||||||
|
resultY = currentCenter.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we changed the scale, we might display something outside the bounds.
|
||||||
|
// When checking we need to take into consideration that we just changed the scale,
|
||||||
|
// since the resultX and minX were calculated with the requested scale, and not the scale we
|
||||||
|
// just calculated to make sure we stay inside the bounds.
|
||||||
|
if (resultX * maxScale - w2 <= minX * maxScale) {
|
||||||
|
resultX = minX * maxScale + w2;
|
||||||
|
resultX /= maxScale;
|
||||||
|
} else if (resultX * maxScale + w2 >= maxX * maxScale) {
|
||||||
|
resultX = maxX * maxScale - w2;
|
||||||
|
resultX /= maxScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultY * maxScale - h2 <= minY * maxScale) {
|
||||||
|
resultY = minY * maxScale + h2;
|
||||||
|
resultY /= maxScale;
|
||||||
|
} else if (resultY * maxScale + h2 >= maxY * maxScale) {
|
||||||
|
resultY = maxY * maxScale - h2;
|
||||||
|
resultY /= maxScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultX != startX || resultY != startY) {
|
||||||
|
// If we made changes just drop any anchor point
|
||||||
|
requestedCamera.anchor.reset();
|
||||||
|
requestedCamera.center = std::optional(Projection::unproject({resultX, resultY}, requestedScale));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ScreenCoordinate TransformState::getCenterOffset() const {
|
ScreenCoordinate TransformState::getCenterOffset() const {
|
||||||
return {0.5 * (edgeInsets.left() - edgeInsets.right()), 0.5 * (edgeInsets.top() - edgeInsets.bottom())};
|
return {0.5 * (edgeInsets.left() - edgeInsets.right()), 0.5 * (edgeInsets.top() - edgeInsets.bottom())};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,6 +216,9 @@ public:
|
|||||||
void setLatLngZoom(const LatLng& latLng, double zoom);
|
void setLatLngZoom(const LatLng& latLng, double zoom);
|
||||||
|
|
||||||
void constrain(double& scale, double& x, double& y) const;
|
void constrain(double& scale, double& x, double& y) const;
|
||||||
|
bool constrainScreen(double& scale_, double& x_, double& y_) const;
|
||||||
|
void constrainCameraAndZoomToBounds(CameraOptions& camera, double& zoom) const;
|
||||||
|
|
||||||
const mat4& getProjectionMatrix() const;
|
const mat4& getProjectionMatrix() const;
|
||||||
const mat4& getInvProjectionMatrix() const;
|
const mat4& getInvProjectionMatrix() const;
|
||||||
|
|
||||||
|
|||||||
@@ -611,6 +611,26 @@ TEST(Transform, DefaultTransform) {
|
|||||||
point = state.latLngToScreenCoordinate(nullIsland);
|
point = state.latLngToScreenCoordinate(nullIsland);
|
||||||
ASSERT_DOUBLE_EQ(point.x, center.x);
|
ASSERT_DOUBLE_EQ(point.x, center.x);
|
||||||
ASSERT_DOUBLE_EQ(point.y, center.y);
|
ASSERT_DOUBLE_EQ(point.y, center.y);
|
||||||
|
|
||||||
|
// Constrain to screen while resizing
|
||||||
|
transform.resize({1000, 500});
|
||||||
|
transform.setLatLngBounds(LatLngBounds::hull({40.0, -10.0}, {70.0, 40.0}));
|
||||||
|
transform.setConstrainMode(ConstrainMode::Screen);
|
||||||
|
|
||||||
|
// Request impossible zoom
|
||||||
|
AnimationOptions easeOptions(Seconds(1));
|
||||||
|
transform.easeTo(CameraOptions().withCenter(LatLng{56, 11}).withZoom(1), easeOptions);
|
||||||
|
ASSERT_TRUE(transform.inTransition());
|
||||||
|
transform.updateTransitions(transform.getTransitionStart() + Milliseconds(250));
|
||||||
|
|
||||||
|
// Rotate the screen during a transition (resize it)
|
||||||
|
transform.resize({500, 1000});
|
||||||
|
|
||||||
|
// The resize while constraining to screen should have stopped the transition and updated the state
|
||||||
|
ASSERT_FALSE(transform.inTransition());
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 8.22103, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 46.6905, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getState().getScale(), 38.1529, 1e-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Transform, LatLngBounds) {
|
TEST(Transform, LatLngBounds) {
|
||||||
@@ -803,6 +823,60 @@ TEST(Transform, LatLngBounds) {
|
|||||||
ASSERT_DOUBLE_EQ(transform.getLatLng().longitude(), 120.0);
|
ASSERT_DOUBLE_EQ(transform.getLatLng().longitude(), 120.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Transform, ConstrainScreenToBounds) {
|
||||||
|
Transform transform;
|
||||||
|
|
||||||
|
transform.resize({500, 500});
|
||||||
|
transform.setLatLngBounds(LatLngBounds::hull({40.0, -10.0}, {70.0, 40.0}));
|
||||||
|
transform.setConstrainMode(ConstrainMode::Screen);
|
||||||
|
|
||||||
|
// Request impossible zoom
|
||||||
|
transform.easeTo(CameraOptions().withCenter(LatLng{56, 11}).withZoom(1));
|
||||||
|
ASSERT_NEAR(transform.getZoom(), 2.81378, 1e-4);
|
||||||
|
|
||||||
|
// Request impossible center left
|
||||||
|
transform.easeTo(CameraOptions().withCenter(LatLng{56, -65}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 0.98632, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 56.0, 1e-4);
|
||||||
|
|
||||||
|
// Request impossible center top
|
||||||
|
transform.easeTo(CameraOptions().withCenter(LatLng{80, 11}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 11.0, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 65.88603, 1e-4);
|
||||||
|
|
||||||
|
// Request impossible center right
|
||||||
|
transform.easeTo(CameraOptions().withCenter(LatLng{56, 50}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 29.01367, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 56.0, 1e-4);
|
||||||
|
|
||||||
|
// Request impossible center bottom
|
||||||
|
transform.easeTo(CameraOptions().withCenter(LatLng{30, 11}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 11.0, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 47.89217, 1e-4);
|
||||||
|
|
||||||
|
// Request impossible center with anchor
|
||||||
|
transform.easeTo(CameraOptions().withAnchor(ScreenCoordinate{250, 250}).withCenter(LatLng{56, -65}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 0.98632, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 56.0, 1e-4);
|
||||||
|
|
||||||
|
// Request impossible center (anchor)
|
||||||
|
transform.easeTo(CameraOptions().withAnchor(ScreenCoordinate{250, 250}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 0.98632, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 56.0, 1e-4);
|
||||||
|
|
||||||
|
// Fly to impossible center
|
||||||
|
transform.flyTo(CameraOptions().withCenter(LatLng{56, -65}).withZoom(4));
|
||||||
|
ASSERT_NEAR(transform.getZoom(), 4.0, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 0.98632, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 56.0, 1e-4);
|
||||||
|
|
||||||
|
// Fly to impossible center and zoom
|
||||||
|
transform.flyTo(CameraOptions().withCenter(LatLng{56, -65}).withZoom(2));
|
||||||
|
ASSERT_NEAR(transform.getZoom(), 4.0, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().longitude(), 0.98632, 1e-4);
|
||||||
|
ASSERT_NEAR(transform.getLatLng().latitude(), 56.0, 1e-4);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Transform, InvalidPitch) {
|
TEST(Transform, InvalidPitch) {
|
||||||
Transform transform;
|
Transform transform;
|
||||||
transform.resize({1, 1});
|
transform.resize({1, 1});
|
||||||
|
|||||||
Reference in New Issue
Block a user