summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--platform/darwin/MGLGeometry.mm25
-rw-r--r--platform/darwin/MGLGeometry_Private.h18
-rw-r--r--platform/ios/src/MGLMapView.mm110
-rw-r--r--platform/osx/src/MGLMapView.mm99
-rw-r--r--src/mbgl/map/transform.cpp3
6 files changed, 71 insertions, 185 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23d594fe76..3c9e5e687c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,7 @@ Known issues:
- You can now modify an annotation’s image after adding the annotation to the map. ([#3146](https://github.com/mapbox/mapbox-gl-native/pull/3146))
- Tapping now selects annotations more reliably. Tapping near the top of a large annotation image now selects that annotation. An annotation image’s alignment insets influence how far away the user can tap and still select the annotation. For example, if your annotation image has a large shadow, you can keep that shadow from being tappable by excluding it from the image’s alignment rect. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261))
- A new method on MGLMapView, `-flyToCamera:withDuration:completionHandler:`, lets you transition between viewpoints along an arc as if by aircraft. ([#3171](https://github.com/mapbox/mapbox-gl-native/pull/3171), [#3301](https://github.com/mapbox/mapbox-gl-native/pull/3301))
+- MGLMapCamera’s `altitude` values now match those of MKMapCamera. ([#3362](https://github.com/mapbox/mapbox-gl-native/pull/3362))
- The user dot’s callout view is now centered above the user dot. It was previously offset slightly to the left. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261))
## iOS 3.0.1
diff --git a/platform/darwin/MGLGeometry.mm b/platform/darwin/MGLGeometry.mm
index b80203d142..70f00afd2f 100644
--- a/platform/darwin/MGLGeometry.mm
+++ b/platform/darwin/MGLGeometry.mm
@@ -1,5 +1,15 @@
#import "MGLGeometry_Private.h"
+#import <mbgl/util/projection.hpp>
+
+/** Vertical field of view, measured in degrees, for determining the altitude
+ of the viewpoint.
+
+ TransformState::getProjMatrix() assumes a vertical field of view of
+ 2 arctan ⅓ rad ≈ 36.9°, but MapKit uses a vertical field of view of 30°.
+ flyTo() assumes a field of view of 2 arctan ½ rad. */
+const CLLocationDegrees MGLAngularFieldOfView = 30;
+
const MGLCoordinateSpan MGLCoordinateSpanZero = {0, 0};
CGRect MGLExtendRect(CGRect rect, CGPoint point) {
@@ -19,3 +29,18 @@ CGRect MGLExtendRect(CGRect rect, CGPoint point) {
}
return rect;
}
+
+CLLocationDistance MGLAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
+ CLLocationDistance metersPerPixel = mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
+ CLLocationDistance metersTall = metersPerPixel * size.height;
+ CLLocationDistance altitude = metersTall / 2 / std::tan(MGLRadiansFromDegrees(MGLAngularFieldOfView) / 2.);
+ return altitude * std::sin(M_PI_2 - MGLRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
+}
+
+double MGLZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
+ CLLocationDistance eyeAltitude = altitude / std::sin(M_PI_2 - MGLRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
+ CLLocationDistance metersTall = eyeAltitude * 2 * std::tan(MGLRadiansFromDegrees(MGLAngularFieldOfView) / 2.);
+ CLLocationDistance metersPerPixel = metersTall / size.height;
+ CGFloat mapPixelWidthAtZoom = std::cos(MGLRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel;
+ return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize);
+}
diff --git a/platform/darwin/MGLGeometry_Private.h b/platform/darwin/MGLGeometry_Private.h
index 49a306701d..bf5bc4e0ff 100644
--- a/platform/darwin/MGLGeometry_Private.h
+++ b/platform/darwin/MGLGeometry_Private.h
@@ -44,3 +44,21 @@ NS_INLINE mbgl::EdgeInsets MGLEdgeInsetsFromNSEdgeInsets(NSEdgeInsets insets) {
return { insets.top, insets.left, insets.bottom, insets.right };
}
#endif
+
+/** Converts a map zoom level to a camera altitude.
+
+ @param zoomLevel The zoom level to convert.
+ @param pitch The camera pitch, measured in degrees.
+ @param latitude The latitude of the point at the center of the viewport.
+ @param size The size of the viewport.
+ @return An altitude measured in meters. */
+CLLocationDistance MGLAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size);
+
+/** Converts a camera altitude to a map zoom level.
+
+ @param altitude The altitude to convert, measured in meters.
+ @param pitch The camera pitch, measured in degrees.
+ @param latitude The latitude of the point at the center of the viewport.
+ @param size The size of the viewport.
+ @return A zero-based zoom level. */
+double MGLZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size);
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 23c027b2a1..7dbce7d2e3 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -55,9 +55,6 @@ NSString *const MGLMapboxSetupDocumentationURLDisplayString = @"mapbox.com/help/
const NSTimeInterval MGLAnimationDuration = 0.3;
const CGSize MGLAnnotationUpdateViewportOutset = {150, 150};
const CGFloat MGLMinimumZoom = 3;
-const CGFloat MGLMinimumPitch = 0;
-const CGFloat MGLMaximumPitch = 60;
-const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.;
const NSUInteger MGLTargetFrameInterval = 1; //Target FPS will be 60 divided by this value
/// Reuse identifier and file name of the default point annotation image.
@@ -1285,7 +1282,7 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
CGFloat currentPitch = _mbglMap->getPitch();
CGFloat slowdown = 20.0;
- CGFloat pitchNew = mbgl::util::clamp(currentPitch - (gestureDistance / slowdown), MGLMinimumPitch, MGLMaximumPitch);
+ CGFloat pitchNew = currentPitch - (gestureDistance / slowdown);
_mbglMap->setPitch(pitchNew);
@@ -1726,48 +1723,11 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
- (MGLMapCamera *)camera
{
- CGRect frame = self.frame;
- CGPoint edgePoint;
- // Constrain by the shorter of the two axes.
- if (frame.size.width > frame.size.height) // landscape
- {
- edgePoint = CGPointMake(0, frame.size.height / 2.);
- }
- else // portrait
- {
- edgePoint = CGPointMake(frame.size.width / 2., 0);
- }
- CLLocationCoordinate2D edgeCoordinate = [self convertPoint:edgePoint toCoordinateFromView:self];
- mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(edgeCoordinate));
-
- // Because we constrain the zoom level vertically in portrait orientation,
- // the visible medial span is affected by pitch: the distance from the
- // center point to the near edge is less than than distance from the center
- // point to the far edge. Average the two distances.
- mbgl::ProjectedMeters nearEdgeMeters;
- if (frame.size.width > frame.size.height)
- {
- nearEdgeMeters = edgeMeters;
- }
- else
- {
- CGPoint nearEdgePoint = CGPointMake(frame.size.width / 2., frame.size.height);
- CLLocationCoordinate2D nearEdgeCoordinate = [self convertPoint:nearEdgePoint toCoordinateFromView:self];
- nearEdgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(nearEdgeCoordinate));
- }
-
- // The opposite side is the distance between the center and one edge.
- CLLocationCoordinate2D centerCoordinate = self.centerCoordinate;
- mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate));
- CLLocationDistance centerToEdge = std::hypot(centerMeters.easting - edgeMeters.easting,
- centerMeters.northing - edgeMeters.northing);
- CLLocationDistance centerToNearEdge = std::hypot(centerMeters.easting - nearEdgeMeters.easting,
- centerMeters.northing - nearEdgeMeters.northing);
- CLLocationDistance altitude = (centerToEdge + centerToNearEdge) / 2 / std::tan(MGLAngularFieldOfView / 2.);
-
CGFloat pitch = _mbglMap->getPitch();
-
- return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
+ CLLocationDistance altitude = MGLAltitudeForZoomLevel(self.zoomLevel, pitch,
+ self.centerCoordinate.latitude,
+ self.frame.size);
+ return [MGLMapCamera cameraLookingAtCenterCoordinate:self.centerCoordinate
fromDistance:altitude
pitch:pitch
heading:self.direction];
@@ -1840,62 +1800,20 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
/// Returns a CameraOptions object that specifies parameters for animating to
/// the given camera.
-- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera {
- // The opposite side is the distance between the center and one edge.
- mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
- mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng);
- CLLocationDistance centerToEdge = camera.altitude * std::tan(MGLAngularFieldOfView / 2.);
-
- double angle = -1;
+- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera
+{
+ mbgl::CameraOptions options;
+ options.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
+ options.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch,
+ camera.centerCoordinate.latitude,
+ self.frame.size);
if (camera.heading >= 0)
{
- angle = MGLRadiansFromDegrees(-camera.heading);
+ options.angle = MGLRadiansFromDegrees(-camera.heading);
}
- double pitch = -1;
if (camera.pitch >= 0)
{
- pitch = MGLRadiansFromDegrees(mbgl::util::clamp(camera.pitch, MGLMinimumPitch, MGLMaximumPitch));
- }
-
- // Make a visible bounds that extends in the constrained direction (the
- // shorter of the two axes).
- CGRect frame = self.frame;
- mbgl::LatLng sw, ne;
- if (frame.size.width > frame.size.height) // landscape
- {
- sw = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing - centerToEdge * std::sin(angle),
- centerMeters.easting - centerToEdge * std::cos(angle),
- });
- ne = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing + centerToEdge * std::sin(angle),
- centerMeters.easting + centerToEdge * std::cos(angle),
- });
- }
- else // portrait
- {
- sw = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing - centerToEdge * std::cos(-angle) + centerToEdge * std::cos(-angle) * std::sin(pitch) / 2,
- centerMeters.easting - centerToEdge * std::sin(-angle) + centerToEdge * std::sin(-angle) * std::sin(pitch) / 2,
- });
- ne = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing + centerToEdge * std::cos(-angle) - centerToEdge * std::cos(-angle) * std::sin(pitch) / 2,
- centerMeters.easting + centerToEdge * std::sin(-angle) - centerToEdge * std::sin(-angle) * std::sin(pitch) / 2,
- });
- }
-
- // Fit the viewport to the bounds. Correct the center in case pitch should
- // cause the visual center to lie above the screen center.
- mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {});
- options.center = centerLatLng;
-
- if (camera.heading >= 0)
- {
- options.angle = angle;
- }
- if (pitch >= 0)
- {
- options.pitch = pitch;
+ options.pitch = MGLRadiansFromDegrees(camera.pitch);
}
return options;
}
diff --git a/platform/osx/src/MGLMapView.mm b/platform/osx/src/MGLMapView.mm
index c8145a67dc..e0d44a2717 100644
--- a/platform/osx/src/MGLMapView.mm
+++ b/platform/osx/src/MGLMapView.mm
@@ -49,15 +49,6 @@ const CGFloat MGLOrnamentOpacity = 0.9;
/// Default duration for programmatic animations.
const NSTimeInterval MGLAnimationDuration = 0.3;
-/// Angular field of view for determining altitude of viewpoint.
-const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.;
-
-/// Minimum allowed pitch in degrees.
-const CGFloat MGLMinimumPitch = 0;
-
-/// Maximum allowed pitch in degrees.
-const CGFloat MGLMaximumPitch = 60;
-
/// Distance in points that a single press of the panning keyboard shortcut pans the map by.
const CGFloat MGLKeyPanningIncrement = 150;
@@ -935,41 +926,10 @@ public:
}
- (MGLMapCamera *)camera {
- CGRect frame = self.frame;
- CGPoint edgePoint;
- // Constrain by the shorter of the two axes.
- if (frame.size.width > frame.size.height) { // landscape
- edgePoint = CGPointMake(0, frame.size.height / 2.);
- } else { // portrait
- edgePoint = CGPointMake(frame.size.width / 2., 0);
- }
- mbgl::LatLng edgeLatLng = [self convertPoint:edgePoint toLatLngFromView:self];
- mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(edgeLatLng);
-
- // Because we constrain the zoom level vertically in portrait orientation,
- // the visible medial span is affected by pitch: the distance from the
- // center point to the near edge is less than than distance from the center
- // point to the far edge. Average the two distances.
- mbgl::ProjectedMeters nearEdgeMeters;
- if (frame.size.width > frame.size.height) {
- nearEdgeMeters = edgeMeters;
- } else {
- CGPoint nearEdgePoint = CGPointMake(frame.size.width / 2., frame.size.height);
- mbgl::LatLng nearEdgeLatLng = [self convertPoint:nearEdgePoint toLatLngFromView:self];
- nearEdgeMeters = _mbglMap->projectedMetersForLatLng(nearEdgeLatLng);
- }
-
- // The opposite side is the distance between the center and one edge.
- mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(self.centerCoordinate);
- mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng);
- CLLocationDistance centerToEdge = std::hypot(centerMeters.easting - edgeMeters.easting,
- centerMeters.northing - edgeMeters.northing);
- CLLocationDistance centerToNearEdge = std::hypot(centerMeters.easting - nearEdgeMeters.easting,
- centerMeters.northing - nearEdgeMeters.northing);
- CLLocationDistance altitude = (centerToEdge + centerToNearEdge) / 2 / std::tan(MGLAngularFieldOfView / 2.);
-
CGFloat pitch = _mbglMap->getPitch();
-
+ CLLocationDistance altitude = MGLAltitudeForZoomLevel(self.zoomLevel, pitch,
+ self.centerCoordinate.latitude,
+ self.frame.size);
return [MGLMapCamera cameraLookingAtCenterCoordinate:self.centerCoordinate
fromDistance:altitude
pitch:pitch
@@ -1038,55 +998,16 @@ public:
/// Returns a CameraOptions object that specifies parameters for animating to
/// the given camera.
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera {
- // The opposite side is the distance between the center and one edge.
- mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
- mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng);
- CLLocationDistance centerToEdge = camera.altitude * std::tan(MGLAngularFieldOfView / 2.);
-
- double angle = -1;
+ mbgl::CameraOptions options;
+ options.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
+ options.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch,
+ camera.centerCoordinate.latitude,
+ self.frame.size);
if (camera.heading >= 0) {
- angle = MGLRadiansFromDegrees(-camera.heading);
+ options.angle = MGLRadiansFromDegrees(-camera.heading);
}
- double pitch = -1;
if (camera.pitch >= 0) {
- pitch = MGLRadiansFromDegrees(mbgl::util::clamp(camera.pitch, MGLMinimumPitch, MGLMaximumPitch));
- }
-
- // Make a visible bounds that extends in the constrained direction (the
- // shorter of the two axes).
- CGRect frame = self.frame;
- mbgl::LatLng sw, ne;
- if (frame.size.width > frame.size.height) { // landscape
- sw = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing - centerToEdge * std::sin(angle),
- centerMeters.easting - centerToEdge * std::cos(angle),
- });
- ne = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing + centerToEdge * std::sin(angle),
- centerMeters.easting + centerToEdge * std::cos(angle),
- });
- }
- else { // portrait
- sw = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing - centerToEdge * std::cos(-angle) + centerToEdge * std::cos(-angle) * std::sin(pitch) / 2,
- centerMeters.easting - centerToEdge * std::sin(-angle) + centerToEdge * std::sin(-angle) * std::sin(pitch) / 2,
- });
- ne = _mbglMap->latLngForProjectedMeters({
- centerMeters.northing + centerToEdge * std::cos(-angle) - centerToEdge * std::cos(-angle) * std::sin(pitch) / 2,
- centerMeters.easting + centerToEdge * std::sin(-angle) - centerToEdge * std::sin(-angle) * std::sin(pitch) / 2,
- });
- }
-
- // Fit the viewport to the bounds. Correct the center in case pitch should
- // cause the visual center to lie above the screen center.
- mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {});
- options.center = centerLatLng;
-
- if (camera.heading >= 0) {
- options.angle = angle;
- }
- if (pitch >= 0) {
- options.pitch = pitch;
+ options.pitch = MGLRadiansFromDegrees(camera.pitch);
}
return options;
}
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
index 86d204f58e..04c0f8bda6 100644
--- a/src/mbgl/map/transform.cpp
+++ b/src/mbgl/map/transform.cpp
@@ -364,6 +364,8 @@ void Transform::flyTo(const CameraOptions &options) {
state.latY(latLng.latitude),
};
+ zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
+
// Minimize rotation by taking the shorter path around the circle.
double normalizedAngle = _normalizeAngle(angle, state.angle);
state.angle = _normalizeAngle(state.angle, normalizedAngle);
@@ -395,6 +397,7 @@ void Transform::flyTo(const CameraOptions &options) {
double rho = 1.42;
if (flyOptions.minZoom) {
double minZoom = util::min(*flyOptions.minZoom, startZoom, zoom);
+ minZoom = util::clamp(minZoom, state.getMinZoom(), state.getMaxZoom());
/// w<sub>m</sub>: Maximum visible span, measured in pixels with respect
/// to the initial scale.
double wMax = w0 / state.zoomScale(minZoom - startZoom);