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

Commit

Permalink
[core] Offset viewport center when edge insets are specified
Browse files Browse the repository at this point in the history
The change is implemented in TransformState::getProjMatrix, the rest of the code is making sure that existing API contracts stay and there are tests verifyingrendering and render query processing only items within screen and given tolerance around screen edges.

MapView: don't bake edge insets into relalculated camera center. Keep edge insets as property of camera in TransformState (similar to pitch, zoom, bearing) independent from specified camera center. Interpolate edge insets in animation.

iOS Demo app: "Turn On/Off Content Insets" pitch the camera and navigate to convenient location in Denver, where streets are parallel to cardinal directions, to illustrate viewport center offset when edge insets are set.

Tests:
ViewFrustumCulling: although Annotations are deprecated, queryRenderedFeatures related tests in Annotations would need to get ported and decided to add the edge insets related query tests next to them. Verify frustum culling (render+queryRenderedFeatures) With different camera and edge insets setups. TODO: port Annotations tests.

Transform.Padding: Verify that coordinates take proper place on screen after applying edge insets.

LocalGlyphRasterizer: verify text rendering when applying padding.

Related to #11882: both use projection matrix elements [8] and [9].

Alternative approach to this was to increase and offset map origin so that the screen would be a sub-rectangle in larger map viewport. This approach has a drawback of unecessary processing the items that are outside screen area.

Fixes #12107, #12728, navigation-sdks/issues/120
  • Loading branch information
astojilj committed May 28, 2019
1 parent ddb6f74 commit d594019
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 101 deletions.
4 changes: 2 additions & 2 deletions include/mbgl/map/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace mbgl {
*/
struct CameraOptions {
CameraOptions& withCenter(const optional<LatLng>& o) { center = o; return *this; }
CameraOptions& withPadding(const EdgeInsets& p) { padding = p; return *this; }
CameraOptions& withPadding(const optional<EdgeInsets>& p) { padding = p; return *this; }
CameraOptions& withAnchor(const optional<ScreenCoordinate>& o) { anchor = o; return *this; }
CameraOptions& withZoom(const optional<double>& o) { zoom = o; return *this; }
CameraOptions& withBearing(const optional<double>& o) { bearing = o; return *this; }
Expand All @@ -27,7 +27,7 @@ struct CameraOptions {

/** Padding around the interior of the view that affects the frame of
reference for `center`. */
EdgeInsets padding;
optional<EdgeInsets> padding;

/** Point of reference for `zoom` and `angle`, assuming an origin at the
top-left corner of the view. */
Expand Down
77 changes: 77 additions & 0 deletions platform/ios/app/MBXViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
MBXSettingsMiscellaneousShowSnapshots,
MBXSettingsMiscellaneousMissingIcon,
MBXSettingsMiscellaneousShouldLimitCameraChanges,
MBXSettingsMiscellaneousSetContentInsets,
MBXSettingsMiscellaneousShowCustomLocationManager,
MBXSettingsMiscellaneousOrnamentsPlacement,
MBXSettingsMiscellaneousPrintLogFile,
Expand Down Expand Up @@ -209,6 +210,7 @@ @interface MBXViewController () <UITableViewDelegate,
@property (nonatomic) BOOL shouldLimitCameraChanges;
@property (nonatomic) BOOL randomWalk;
@property (nonatomic) NSMutableArray<UIWindow *> *helperWindows;
@property (nonatomic) NSMutableArray<UIView *> *contentInsetsOverlays;

@end

Expand All @@ -219,6 +221,8 @@ @interface MGLMapView (MBXViewController)
@implementation MBXViewController
{
BOOL _isTouringWorld;
BOOL _contentInsetsEnabled;
UIEdgeInsets _originalContentInsets;
}

#pragma mark - Setup & Teardown
Expand Down Expand Up @@ -475,6 +479,7 @@ - (void)dismissSettings:(__unused id)sender
@"Show Snapshots",
@"Missing Icon",
[NSString stringWithFormat:@"%@ Camera Changes", (_shouldLimitCameraChanges ? @"Unlimit" : @"Limit")],
[NSString stringWithFormat:@"Turn %@ Content Insets", (_contentInsetsEnabled ? @"Off" : @"On")],
@"View Route Simulation",
@"Ornaments Placement",
]];
Expand Down Expand Up @@ -739,6 +744,61 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath
}
break;
}
case MBXSettingsMiscellaneousSetContentInsets:
{
if (!_contentInsetsEnabled) {
_originalContentInsets = [self.mapView contentInset];
}
_contentInsetsEnabled = !_contentInsetsEnabled;
self.automaticallyAdjustsScrollViewInsets = !_contentInsetsEnabled;
UIEdgeInsets contentInsets = self.mapView.bounds.size.width > self.mapView.bounds.size.height
? UIEdgeInsetsMake(_originalContentInsets.top, 0.5 * self.mapView.bounds.size.width, _originalContentInsets.bottom, 0.0)
: UIEdgeInsetsMake(0.25 * self.mapView.bounds.size.height, 0.0, _originalContentInsets.bottom, 0.25 * self.mapView.bounds.size.width);
if (_contentInsetsEnabled) {
if (!self.contentInsetsOverlays)
self.contentInsetsOverlays = [NSMutableArray array];
if (![self.contentInsetsOverlays count]) {
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.mapView.bounds.size.width, contentInsets.top)];
view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
[self.contentInsetsOverlays addObject:view];
[self.view addSubview:view];
view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, contentInsets.left, self.mapView.bounds.size.height)];
view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
[self.contentInsetsOverlays addObject:view];
[self.view addSubview:view];
view = [[UIView alloc]initWithFrame:CGRectMake(self.mapView.bounds.size.width - contentInsets.right, 0, contentInsets.right, self.mapView.bounds.size.height)];
view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
[self.contentInsetsOverlays addObject:view];
[self.view addSubview:view];
view = [[UIView alloc]initWithFrame:CGRectMake(0, self.mapView.bounds.size.height - contentInsets.bottom, self.mapView.bounds.size.width, self.mapView.bounds.size.height)];
view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5];
[self.contentInsetsOverlays addObject:view];
[self.view addSubview:view];
}
[self.view bringSubviewToFront:self.contentInsetsOverlays[0]];
[self.view bringSubviewToFront:self.contentInsetsOverlays[1]];
[self.view bringSubviewToFront:self.contentInsetsOverlays[2]];
[self.view bringSubviewToFront:self.contentInsetsOverlays[3]];

// Denver streets parallel to cardinal directions help illustrate
// viewport center offset when edge insets are set.
MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(39.72707, -104.9986)
acrossDistance:100
pitch:60
heading:0];
__weak typeof(self) weakSelf = self;
[self.mapView setCamera:camera withDuration:0.3 animationTimingFunction:nil completionHandler:^{
[weakSelf.mapView setContentInset:contentInsets animated:TRUE];
}];
} else {
[self.view sendSubviewToBack:self.contentInsetsOverlays[0]];
[self.view sendSubviewToBack:self.contentInsetsOverlays[1]];
[self.view sendSubviewToBack:self.contentInsetsOverlays[2]];
[self.view sendSubviewToBack:self.contentInsetsOverlays[3]];
[self.mapView setContentInset:_originalContentInsets animated:TRUE];
}
break;
}
case MBXSettingsMiscellaneousOrnamentsPlacement:
{
MBXOrnamentsViewController *ornamentsViewController = [[MBXOrnamentsViewController alloc] init];
Expand Down Expand Up @@ -2005,6 +2065,23 @@ - (IBAction)locateUser:(id)sender
[sender setAccessibilityValue:nextAccessibilityValue];
}

#pragma mark - UIViewDelegate

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
if (_contentInsetsEnabled)
{
_contentInsetsEnabled = NO;
self.automaticallyAdjustsScrollViewInsets = YES;
[self.mapView setContentInset:UIEdgeInsetsZero];
}
while (self.contentInsetsOverlays && [self.contentInsetsOverlays count]) {
[[self.contentInsetsOverlays lastObject] removeFromSuperview];
[self.contentInsetsOverlays removeLastObject];
}
}

#pragma mark - MGLMapViewDelegate

- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation
Expand Down
19 changes: 5 additions & 14 deletions platform/ios/src/MGLMapView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1076,20 +1076,15 @@ - (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated
return;
}

// After adjusting the content inset, move the center coordinate from the
// old frame of reference to the new one represented by the newly set
// content inset.
CLLocationCoordinate2D oldCenter = self.centerCoordinate;

_contentInset = contentInset;

if (self.userTrackingMode == MGLUserTrackingModeNone)
{
// Don’t call -setCenterCoordinate:, which resets the user tracking mode.
[self _setCenterCoordinate:oldCenter animated:animated];
[self _setCenterCoordinate:self.centerCoordinate edgePadding:contentInset zoomLevel:self.zoomLevel direction:self.direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL];
_contentInset = contentInset;
}
else
{
_contentInset = contentInset;
[self didUpdateLocationWithUserTrackingAnimated:animated];
}

Expand Down Expand Up @@ -3313,10 +3308,6 @@ - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(
[self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
}

- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated {
[self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:self.zoomLevel direction:self.direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL];
}

- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePadding:(UIEdgeInsets)insets zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
if (!_mbglMap)
Expand Down Expand Up @@ -3356,7 +3347,7 @@ - (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePaddin
}

MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions];
if ([self.camera isEqualToMapCamera:camera])
if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets))
{
if (completion)
{
Expand Down Expand Up @@ -3893,7 +3884,7 @@ - (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOpti
return self.residualCamera;
}

mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions();
mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions(cameraOptions.padding.value_or(mbgl::EdgeInsets()));
CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : *mapCamera.center);
double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel;
CLLocationDirection direction = cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction;
Expand Down
2 changes: 1 addition & 1 deletion src/mbgl/layout/symbol_projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ namespace mbgl {
* For horizontal labels we want to do step 1 in the shader for performance reasons (no cpu work).
* This is what `u_label_plane_matrix` is used for.
* For labels aligned with lines we have to steps 1 and 2 on the cpu since we need access to the line geometry.
* This is what `updateLineLabels(...)` does.
* This is what `updateLineLabels(...)` in JS, `reprojectLineLabels()` in gl-native, does.
* Since the conversion is handled on the cpu we just set `u_label_plane_matrix` to an identity matrix.
*
* Steps 3 and 4 are done in the shaders for all labels.
Expand Down
76 changes: 35 additions & 41 deletions src/mbgl/map/transform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ void Transform::jumpTo(const CameraOptions& camera) {
}

/**
* Change any combination of center, zoom, bearing, and pitch, with a smooth animation
* between old and new values. The map will retain the current values for any options
* not included in `options`.
* Change any combination of center, zoom, bearing, pitch and edgeInsets, with a
* smooth animation between old and new values. The map will retain the current
* values for any options not included in `options`.
*/
void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& animation) {
const EdgeInsets& padding = camera.padding;
LatLng startLatLng = getLatLng(padding, LatLng::Unwrapped);
const EdgeInsets& padding = camera.padding.value_or(state.edgeInsets);
LatLng startLatLng = getLatLng(LatLng::Unwrapped);
const LatLng& unwrappedLatLng = camera.center.value_or(startLatLng);
const LatLng& latLng = state.bounds != LatLngBounds::unbounded() ? unwrappedLatLng : unwrappedLatLng.wrapped();
double zoom = camera.zoom.value_or(getZoom());
Expand All @@ -110,9 +110,6 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
const Point<double> startPoint = Projection::project(startLatLng, state.scale);
const Point<double> endPoint = Projection::project(latLng, state.scale);

ScreenCoordinate center = getScreenCoordinate(padding);
center.y = state.size.height - center.y;

// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
const double scale = state.zoomScale(zoom);
Expand All @@ -130,6 +127,7 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
state.panning = unwrappedLatLng != startLatLng;
state.scaling = scale != startScale;
state.rotating = bearing != startBearing;
const EdgeInsets startEdgeInsets = state.edgeInsets;

startTransition(camera, animation, [=](double t) {
Point<double> framePoint = util::interpolate(startPoint, endPoint, t);
Expand All @@ -143,9 +141,14 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
if (pitch != startPitch) {
state.pitch = util::interpolate(startPitch, pitch, t);
}

if (!padding.isFlush()) {
state.moveLatLng(frameLatLng, center);
if (padding != startEdgeInsets) {
// Interpolate edge insets
state.edgeInsets = {
util::interpolate(startEdgeInsets.top(), padding.top(), t),
util::interpolate(startEdgeInsets.left(), padding.left(), t),
util::interpolate(startEdgeInsets.bottom(), padding.bottom(), t),
util::interpolate(startEdgeInsets.right(), padding.right(), t)
};
}
}, duration);
}
Expand All @@ -159,8 +162,8 @@ void Transform::easeTo(const CameraOptions& camera, const AnimationOptions& anim
Where applicable, local variable documentation begins with the associated
variable or function in van Wijk (2003). */
void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &animation) {
const EdgeInsets& padding = camera.padding;
const LatLng& latLng = camera.center.value_or(getLatLng(padding, LatLng::Unwrapped)).wrapped();
const EdgeInsets& padding = camera.padding.value_or(state.edgeInsets);
const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped();
double zoom = camera.zoom.value_or(getZoom());
double bearing = camera.bearing ? -*camera.bearing * util::DEG2RAD : getBearing();
double pitch = camera.pitch ? *camera.pitch * util::DEG2RAD : getPitch();
Expand All @@ -170,15 +173,12 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
}

// Determine endpoints.
LatLng startLatLng = getLatLng(padding, LatLng::Unwrapped).wrapped();
LatLng startLatLng = getLatLng(LatLng::Unwrapped).wrapped();
startLatLng.unwrapForShortestPath(latLng);

const Point<double> startPoint = Projection::project(startLatLng, state.scale);
const Point<double> endPoint = Projection::project(latLng, state.scale);

ScreenCoordinate center = getScreenCoordinate(padding);
center.y = state.size.height - center.y;

// Constrain camera options.
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
pitch = util::clamp(pitch, util::PITCH_MIN, util::PITCH_MAX);
Expand Down Expand Up @@ -278,6 +278,7 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
state.panning = true;
state.scaling = true;
state.rotating = bearing != startBearing;
const EdgeInsets startEdgeInsets = state.edgeInsets;

startTransition(camera, animation, [=](double k) {
/// s: The distance traveled along the flight path, measured in
Expand All @@ -304,38 +305,31 @@ void Transform::flyTo(const CameraOptions &camera, const AnimationOptions &anima
if (pitch != startPitch) {
state.pitch = util::interpolate(startPitch, pitch, k);
}

if (!padding.isFlush()) {
state.moveLatLng(frameLatLng, center);
if (padding != startEdgeInsets) {
// Interpolate edge insets
state.edgeInsets = {
util::interpolate(startEdgeInsets.top(), padding.top(), us),
util::interpolate(startEdgeInsets.left(), padding.left(), us),
util::interpolate(startEdgeInsets.bottom(), padding.bottom(), us),
util::interpolate(startEdgeInsets.right(), padding.right(), us)
};
}
}, duration);
}

#pragma mark - Position

void Transform::moveBy(const ScreenCoordinate& offset, const AnimationOptions& animation) {
ScreenCoordinate centerOffset = { offset.x, -offset.y, };
ScreenCoordinate centerPoint = getScreenCoordinate() - centerOffset;
easeTo(CameraOptions().withCenter(state.screenCoordinateToLatLng(centerPoint)), animation);
}

LatLng Transform::getLatLng(const EdgeInsets& padding, LatLng::WrapMode wrap) const {
if (padding.isFlush()) {
return state.getLatLng(wrap);
} else {
return screenCoordinateToLatLng(padding.getCenter(state.size.width, state.size.height));
}
ScreenCoordinate centerOffset = { offset.x, offset.y };
ScreenCoordinate pointOnScreen = state.edgeInsets.getCenter(state.size.width, state.size.height) - centerOffset;
// Use unwrapped LatLng to carry information about moveBy direction.
easeTo(CameraOptions().withCenter(screenCoordinateToLatLng(pointOnScreen, LatLng::Unwrapped)), animation);
}

ScreenCoordinate Transform::getScreenCoordinate(const EdgeInsets& padding) const {
if (padding.isFlush()) {
return { state.size.width / 2., state.size.height / 2. };
} else {
return padding.getCenter(state.size.width, state.size.height);
}
LatLng Transform::getLatLng(LatLng::WrapMode wrap) const {
return state.getLatLng(wrap);
}


#pragma mark - Zoom

double Transform::getZoom() const {
Expand Down Expand Up @@ -364,7 +358,7 @@ void Transform::setMaxZoom(const double maxZoom) {
#pragma mark - Bearing

void Transform::rotateBy(const ScreenCoordinate& first, const ScreenCoordinate& second, const AnimationOptions& animation) {
ScreenCoordinate center = getScreenCoordinate();
ScreenCoordinate center = state.edgeInsets.getCenter(state.size.width, state.size.height);
const ScreenCoordinate offset = first - center;
const double distance = std::sqrt(std::pow(2, offset.x) + std::pow(2, offset.y));

Expand Down Expand Up @@ -576,10 +570,10 @@ ScreenCoordinate Transform::latLngToScreenCoordinate(const LatLng& latLng) const
return point;
}

LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point) const {
LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng::WrapMode wrapMode) const {
ScreenCoordinate flippedPoint = point;
flippedPoint.y = state.size.height - flippedPoint.y;
return state.screenCoordinateToLatLng(flippedPoint).wrapped();
return state.screenCoordinateToLatLng(flippedPoint, wrapMode);
}

} // namespace mbgl
Loading

0 comments on commit d594019

Please sign in to comment.