diff --git a/CMakeLists.txt b/CMakeLists.txt index 01f62a1a802..1c923b02b64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/mbgl/map/map.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/map/map_observer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/map/map_options.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/map/map_projection.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/map/mode.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/map/projection_mode.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/math/clamp.hpp @@ -375,6 +376,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/mbgl/map/map_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/map_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/map_options.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/map/map_projection.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform_state.cpp diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 3c61140eb88..80907332967 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -23,6 +23,7 @@ namespace mbgl { class RendererFrontend; +class TransformState; namespace style { class Image; @@ -110,6 +111,9 @@ class Map : private util::noncopyable { std::vector pixelsForLatLngs(const std::vector&) const; std::vector latLngsForPixels(const std::vector&) const; + // Transform + TransformState getTransfromState() const; + // Annotations void addAnnotationImage(std::unique_ptr); void removeAnnotationImage(const std::string&); diff --git a/include/mbgl/map/map_projection.hpp b/include/mbgl/map/map_projection.hpp new file mode 100644 index 00000000000..27c9f496580 --- /dev/null +++ b/include/mbgl/map/map_projection.hpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#include + +namespace mbgl { + +class Transform; + +class MapProjection : private util::noncopyable { +public: + explicit MapProjection(const Map&); + ~MapProjection(); + + ScreenCoordinate pixelForLatLng(const LatLng&) const; + LatLng latLngForPixel(const ScreenCoordinate&) const; + + void setCamera(const CameraOptions&); + CameraOptions getCamera() const; + + /// Set the underneath camera so the requested coordinates are visible with the inset. + void setVisibleCoordinates(const std::vector&, const EdgeInsets&); + +private: + std::unique_ptr transform; +}; + +} // namespace mbgl diff --git a/platform/ios/platform/darwin/test/MGLMapProjectionTests.m b/platform/ios/platform/darwin/test/MGLMapProjectionTests.m new file mode 100644 index 00000000000..9278141d229 --- /dev/null +++ b/platform/ios/platform/darwin/test/MGLMapProjectionTests.m @@ -0,0 +1,92 @@ +#import +#import +#import + +#if TARGET_OS_IPHONE + #define MGLEdgeInsets UIEdgeInsets + #define MGLEdgeInsetsMake UIEdgeInsetsMake +#else + #define MGLEdgeInsets NSEdgeInsets + #define MGLEdgeInsetsMake NSEdgeInsetsMake +#endif + +@interface MGLMapProjectionTests : XCTestCase + +@property (nonatomic, retain) MGLMapView *mapView; +@property (nonatomic, retain) MGLMapProjection *mapProjection; + +@end + +@implementation MGLMapProjectionTests + +- (void)setUp { + [super setUp]; + + [MGLSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + self.mapView = [[MGLMapView alloc] initWithFrame:CGRectMake(0, 0, 200, 200) styleURL:styleURL]; + + [self.mapView setVisibleCoordinateBounds:MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(1.0, 1.0), + CLLocationCoordinate2DMake(2.0, 2.0))]; + + self.mapProjection = self.mapView.mapProjection; +} + +- (void)tearDown { + self.mapView = nil; + self.mapProjection = nil; + [MGLSettings setApiKey:nil]; + + [super tearDown]; +} + +- (void)testMapProjectionCamera { + XCTAssertTrue([self.mapProjection.camera isEqualToMapCamera:self.mapView.camera], + @"Map projection camera must be equal to the map view's one"); +} + +- (void)testMapProjectionCameraSet { + MGLCoordinateBounds newBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(3.0, 3.0), + CLLocationCoordinate2DMake(4.0, 4.0)); + MGLEdgeInsets paddings = MGLEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + + MGLMapCamera *newCamera = [self.mapView cameraThatFitsCoordinateBounds:newBounds edgePadding:paddings]; + [self.mapProjection setCamera:newCamera withEdgeInsets:paddings]; + + XCTAssertTrue([self.mapProjection.camera isEqualToMapCamera:newCamera], + @"Map projection camera must be equal to the one just set"); + + CLLocationCoordinate2D topLeftCoordinate = [self.mapProjection convertPoint:CGPointMake(10.0, 10.0)]; + XCTAssertEqualWithAccuracy(topLeftCoordinate.latitude, 4.0, 1e-3); + XCTAssertEqualWithAccuracy(topLeftCoordinate.longitude, 3.0, 1e-3); + + CLLocationCoordinate2D bottomRightCoordinate = [self.mapProjection convertPoint:CGPointMake(190.0, 190.0)]; + XCTAssertEqualWithAccuracy(bottomRightCoordinate.latitude, 3.0, 1e-3); + XCTAssertEqualWithAccuracy(bottomRightCoordinate.longitude, 4.0, 1e-3); + + CLLocationCoordinate2D centerCoordinate = [self.mapProjection convertPoint:CGPointMake(100.0, 100.0)]; + XCTAssertEqualWithAccuracy(centerCoordinate.latitude, 3.5, 1e-3); + XCTAssertEqualWithAccuracy(centerCoordinate.longitude, 3.5, 1e-3); +} + +- (void)testMapProjectionVisibleBoundsSet { + MGLCoordinateBounds newBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(3.0, 3.0), + CLLocationCoordinate2DMake(4.0, 4.0)); + MGLEdgeInsets paddings = MGLEdgeInsetsMake(10.0, 10.0, 10.0, 10.0); + + [self.mapProjection setVisibleCoordinateBounds:newBounds edgePadding:paddings]; + + CLLocationCoordinate2D topLeftCoordinate = [self.mapProjection convertPoint:CGPointMake(10.0, 10.0)]; + XCTAssertEqualWithAccuracy(topLeftCoordinate.latitude, 4.0, 1e-3); + XCTAssertEqualWithAccuracy(topLeftCoordinate.longitude, 3.0, 1e-3); + + CLLocationCoordinate2D bottomRightCoordinate = [self.mapProjection convertPoint:CGPointMake(190.0, 190.0)]; + XCTAssertEqualWithAccuracy(bottomRightCoordinate.latitude, 3.0, 1e-3); + XCTAssertEqualWithAccuracy(bottomRightCoordinate.longitude, 4.0, 1e-3); + + CLLocationCoordinate2D centerCoordinate = [self.mapProjection convertPoint:CGPointMake(100.0, 100.0)]; + XCTAssertEqualWithAccuracy(centerCoordinate.latitude, 3.5, 1e-3); + XCTAssertEqualWithAccuracy(centerCoordinate.longitude, 3.5, 1e-3); +} + +@end diff --git a/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj index 9b9680245d7..6fc167cdad9 100644 --- a/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/platform/ios/ios.xcodeproj/project.pbxproj @@ -415,6 +415,11 @@ CAFB3C15234505D500399265 /* MGLMapSnapshotter_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */; }; CF75A91522D85E860058A5C4 /* MGLLoggingConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */; }; CF75A91622D85E860058A5C4 /* MGLLoggingConfiguration.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */; }; + D42DE1E4275F7FEB00B4446E /* MGLMapProjection.h in Headers */ = {isa = PBXBuildFile; fileRef = D42DE1E2275F7FEB00B4446E /* MGLMapProjection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D42DE1E5275F7FEB00B4446E /* MGLMapProjection.h in Headers */ = {isa = PBXBuildFile; fileRef = D42DE1E2275F7FEB00B4446E /* MGLMapProjection.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D42DE1E6275F7FEB00B4446E /* MGLMapProjection.mm in Sources */ = {isa = PBXBuildFile; fileRef = D42DE1E3275F7FEB00B4446E /* MGLMapProjection.mm */; }; + D42DE1E7275F7FEB00B4446E /* MGLMapProjection.mm in Sources */ = {isa = PBXBuildFile; fileRef = D42DE1E3275F7FEB00B4446E /* MGLMapProjection.mm */; }; + D4D7412E2760EEBA00102513 /* MGLMapProjectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4D7412D2760EEBA00102513 /* MGLMapProjectionTests.m */; }; D717FE7A25F1955A0054E06E /* iosapp_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717FE7925F1955A0054E06E /* iosapp_UITests.swift */; }; D717FECB25F19F520054E06E /* XCTestCase+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717FECA25F19F520054E06E /* XCTestCase+Extensions.swift */; }; D7E7CE8825F2EF6A00C175FC /* bench_UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E7CE8725F2EF6A00C175FC /* bench_UITests.swift */; }; @@ -1069,6 +1074,9 @@ CAE7AD5420F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MGLMapSnapshotterSwiftTests.swift; sourceTree = ""; }; CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter_Private.h; sourceTree = ""; }; CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLoggingConfiguration.mm; sourceTree = ""; }; + D42DE1E2275F7FEB00B4446E /* MGLMapProjection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLMapProjection.h; sourceTree = ""; }; + D42DE1E3275F7FEB00B4446E /* MGLMapProjection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapProjection.mm; sourceTree = ""; }; + D4D7412D2760EEBA00102513 /* MGLMapProjectionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLMapProjectionTests.m; path = ../../darwin/test/MGLMapProjectionTests.m; sourceTree = ""; }; D717FE7725F1955A0054E06E /* iosapp UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iosapp UITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; D717FE7925F1955A0054E06E /* iosapp_UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosapp_UITests.swift; sourceTree = ""; }; D717FE7B25F1955A0054E06E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1903,6 +1911,7 @@ 96381C0122C6F3950053497D /* MGLMapViewPitchTests.m */, 9658C154204761FC00D8A674 /* MGLMapViewScaleBarTests.m */, 076171C22139C70900668A35 /* MGLMapViewTests.m */, + D4D7412D2760EEBA00102513 /* MGLMapProjectionTests.m */, 9686D1BC22D9357700194EA0 /* MGLMapViewZoomTests.mm */, 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */, 96036A0520059BBA00510F3D /* MGLNSOrthographyAdditionsTests.m */, @@ -2016,6 +2025,8 @@ DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, 4BC5769125E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h */, 4BC5769225E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m */, + D42DE1E2275F7FEB00B4446E /* MGLMapProjection.h */, + D42DE1E3275F7FEB00B4446E /* MGLMapProjection.mm */, ); name = Kit; path = src; @@ -2358,6 +2369,7 @@ DA17BE301CC4BAC300402C41 /* MGLMapView_Private.h in Headers */, DAD165781CF4CDFF001FF4B9 /* MGLShapeCollection.h in Headers */, DAED38631D62D0FC00D7640F /* NSURL+MGLAdditions.h in Headers */, + D42DE1E4275F7FEB00B4446E /* MGLMapProjection.h in Headers */, DA88481E1CBAFA6200AB86E3 /* MGLMultiPoint_Private.h in Headers */, 3566C7661D4A77BA008152BC /* MGLShapeSource.h in Headers */, 35CE61821D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */, @@ -2519,6 +2531,7 @@ DABFB8681CBE99E500D62B32 /* MGLPolyline.h in Headers */, 96E516DF200054FB00A02306 /* MGLShape_Private.h in Headers */, DABFB86F1CBE9A0F00D62B32 /* MGLMapView.h in Headers */, + D42DE1E5275F7FEB00B4446E /* MGLMapProjection.h in Headers */, DA6408DC1DA4E7D300908C90 /* MGLVectorStyleLayer.h in Headers */, 353933F31D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */, 558DE7A11E5615E400C7916D /* MGLFoundation_Private.h in Headers */, @@ -3160,6 +3173,7 @@ DA0CD5901CF56F6A00A5F5A5 /* MGLFeatureTests.mm in Sources */, 556660D81E1D085500E2C41B /* MGLVersionNumber.m in Sources */, 4031ACFF1E9FD29F00A3EA26 /* MGLSDKTestHelpers.swift in Sources */, + D4D7412E2760EEBA00102513 /* MGLMapProjectionTests.m in Sources */, 16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3189,6 +3203,7 @@ 967C864D210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */, 400533021DB0862B0069F638 /* NSArray+MGLAdditions.mm in Sources */, 6F018BAE220031B8003E7269 /* UIView+MGLAdditions.m in Sources */, + D42DE1E6275F7FEB00B4446E /* MGLMapProjection.mm in Sources */, 96036A03200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */, 35136D421D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */, 3538AA1F1D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */, @@ -3288,6 +3303,7 @@ 35136D431D42274500C20EFD /* MGLRasterStyleLayer.mm in Sources */, 967C864E210A9D3C004DF794 /* UIDevice+MGLAdditions.m in Sources */, 96036A04200565C700510F3D /* NSOrthography+MGLAdditions.m in Sources */, + D42DE1E7275F7FEB00B4446E /* MGLMapProjection.mm in Sources */, 6F018BB1220031C1003E7269 /* UIView+MGLAdditions.m in Sources */, 3538AA201D542239008EC33D /* MGLForegroundStyleLayer.mm in Sources */, DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */, diff --git a/platform/ios/platform/ios/src/MGLMapProjection.h b/platform/ios/platform/ios/src/MGLMapProjection.h new file mode 100644 index 00000000000..e010460588d --- /dev/null +++ b/platform/ios/platform/ios/src/MGLMapProjection.h @@ -0,0 +1,75 @@ +#import "MGLFoundation.h" +#import "MGLMapView.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + The aim of this class is to provide the functionality of changing the camera state and + converting between map view screen coordinates and geographical coordinates without + changing the actual map view camera state. +*/ +MGL_EXPORT +@interface MGLMapProjection : NSObject + +/** + Initializes and returns the new projection object with the current + camera state from the provided map view. + + @param mapView The map view the camera state to use for the initialization. + @return An initialized map projection. + */ +- (instancetype)initWithMapView:(MGLMapView*)mapView; + +/** + A camera representing the current projection state + */ +@property (readonly, copy) MGLMapCamera *camera; + +/** + Change the projection state with camera and padding values. + + @param camera The new camera to be used in the projection calculation. + @param insets The insets applied on top of the camera be used in the projection calculation. + + @note `MGLMapView` instance frame must not be changed since this projection is initialized, + otherwise the calculation may be wrong. + */ +- (void)setCamera:(MGLMapCamera * _Nonnull)camera withEdgeInsets:(UIEdgeInsets)insets; + +/** + Change the projection state to make the provided bounds visible with the specified inset. + + @param bounds The bounds that the viewport should fit. + @param insets The insets applied on top of the viewport to be used in the projection calculation. + + @note `MGLMapView` instance frame must not be changed since this projection is initialized, + otherwise the calculation may be wrong. + */ +- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets; + +/** + Converts a point in the coordinate system of the map view the projection + was initialized with to the geographical coordinate. + + @param point The point to convert. + @return The geographic coordinate at the given point. + */ +- (CLLocationCoordinate2D)convertPoint:(CGPoint)point; + +/** + Converts a geographic coordinate to a point in the map view's the projection + was initialized with coordinate system. + + @param coordinate The geographic coordinate to convert. + @return The point corresponding to the given geographic coordinate. + */ +- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + The distance in meters spanned by a single point for the current camera. + */ +@property (readonly) CLLocationDistance metersPerPoint; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/ios/platform/ios/src/MGLMapProjection.mm b/platform/ios/platform/ios/src/MGLMapProjection.mm new file mode 100644 index 00000000000..95216c36210 --- /dev/null +++ b/platform/ios/platform/ios/src/MGLMapProjection.mm @@ -0,0 +1,113 @@ +#include +#include +#include +#include + +#import "MGLMapProjection.h" +#import "MGLMapView_Private.h" +#import "MGLGeometry_Private.h" + +@interface MGLMapProjection () + +@property (nonatomic) CGSize mapFrameSize; + +@end + +@implementation MGLMapProjection +{ + std::unique_ptr _mbglProjection; +} + +- (instancetype)initWithMapView:(MGLMapView *)mapView +{ + if (self = [super init]) + { + _mbglProjection = std::make_unique([mapView mbglMap]); + self.mapFrameSize = mapView.frame.size; + } + return self; +} + +- (MGLMapCamera*)camera +{ + mbgl::CameraOptions cameraOptions = _mbglProjection->getCamera(); + + CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(*cameraOptions.center); + double zoomLevel = *cameraOptions.zoom; + CLLocationDirection direction = mbgl::util::wrap(*cameraOptions.bearing, 0., 360.); + CGFloat pitch = *cameraOptions.pitch; + CLLocationDistance altitude = MGLAltitudeForZoomLevel(zoomLevel, pitch, + centerCoordinate.latitude, self.mapFrameSize); + return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate altitude:altitude + pitch:pitch heading:direction]; +} + +- (void)setCamera:(MGLMapCamera * _Nonnull)camera withEdgeInsets:(UIEdgeInsets)insets +{ + mbgl::CameraOptions cameraOptions; + if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) + { + cameraOptions.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); + } + cameraOptions.padding = MGLEdgeInsetsFromNSEdgeInsets(insets); + cameraOptions.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch, + camera.centerCoordinate.latitude, + self.mapFrameSize); + if (camera.heading >= 0) + { + cameraOptions.bearing = camera.heading; + } + if (camera.pitch >= 0) + { + cameraOptions.pitch = camera.pitch; + } + + _mbglProjection->setCamera(cameraOptions); +} + +- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets { + CLLocationCoordinate2D coordinates[] = { + {bounds.ne.latitude, bounds.sw.longitude}, + bounds.sw, + {bounds.sw.latitude, bounds.ne.longitude}, + bounds.ne, + }; + + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); + std::vector latLngs; + latLngs.reserve(4); + for (NSUInteger i = 0; i < 4; i++) + { + latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude}); + } + + _mbglProjection->setVisibleCoordinates(latLngs, padding); +} + +- (CLLocationCoordinate2D)convertPoint:(CGPoint)point +{ + mbgl::ScreenCoordinate screenCoordinate = mbgl::ScreenCoordinate(point.x, point.y); + return MGLLocationCoordinate2DFromLatLng(_mbglProjection->latLngForPixel(screenCoordinate).wrapped()); +} + +- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate +{ + if ( !CLLocationCoordinate2DIsValid(coordinate)) + { + return CGPointMake(NAN, NAN); + } + + mbgl::LatLng latLng = MGLLatLngFromLocationCoordinate2D(coordinate); + mbgl::ScreenCoordinate pixel = _mbglProjection->pixelForLatLng(latLng); + return CGPointMake(pixel.x, pixel.y); +} + +- (CLLocationDistance)metersPerPoint +{ + mbgl::CameraOptions cameraOptions = _mbglProjection->getCamera(); + return mbgl::Projection::getMetersPerPixelAtLatitude(cameraOptions.center->latitude(), + *cameraOptions.zoom); +} + + +@end diff --git a/platform/ios/platform/ios/src/MGLMapView.h b/platform/ios/platform/ios/src/MGLMapView.h index 862ac6f501f..0a6d417c8d6 100644 --- a/platform/ios/platform/ios/src/MGLMapView.h +++ b/platform/ios/platform/ios/src/MGLMapView.h @@ -12,6 +12,7 @@ NS_ASSUME_NONNULL_BEGIN @class MGLAnnotationView; @class MGLAnnotationImage; @class MGLUserLocation; +@class MGLMapProjection; @class MGLPolyline; @class MGLPolygon; @class MGLShape; @@ -1533,6 +1534,12 @@ MGL_EXPORT */ - (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude; +/** + Returns the new map projection instance initialized with the map view, + i.e. with the current camera state. + */ +- (MGLMapProjection*)mapProjection; + #pragma mark Annotating the Map /** diff --git a/platform/ios/platform/ios/src/MGLMapView.mm b/platform/ios/platform/ios/src/MGLMapView.mm index 02f93cf4ac0..507456c5993 100644 --- a/platform/ios/platform/ios/src/MGLMapView.mm +++ b/platform/ios/platform/ios/src/MGLMapView.mm @@ -67,6 +67,7 @@ #import "MGLNetworkConfiguration_Private.h" #import "MGLReachability.h" #import "MGLSettings_Private.h" +#import "MGLMapProjection.h" #include #include @@ -4360,6 +4361,10 @@ - (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude zoomL return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel); } +- (MGLMapProjection*)mapProjection { + return [[MGLMapProjection alloc] initWithMapView:self]; +} + #pragma mark - Camera Change Reason - - (void)resetCameraChangeReason diff --git a/platform/ios/platform/ios/src/MGLMapView_Private.h b/platform/ios/platform/ios/src/MGLMapView_Private.h index a30819df653..a59b01ed7be 100644 --- a/platform/ios/platform/ios/src/MGLMapView_Private.h +++ b/platform/ios/platform/ios/src/MGLMapView_Private.h @@ -54,6 +54,7 @@ FOUNDATION_EXTERN MGL_EXPORT MGLExceptionName const _Nonnull MGLUnderlyingMapUna /// Synchronously render a frame of the map. - (BOOL)renderSync; +- (mbgl::Map &)mbglMap; - (nonnull mbgl::Renderer *)renderer; /** Returns whether the map view is currently loading or processing any assets required to render the map */ diff --git a/platform/ios/platform/ios/src/Mapbox.h b/platform/ios/platform/ios/src/Mapbox.h index 2c1aed27296..91b9b62c125 100644 --- a/platform/ios/platform/ios/src/Mapbox.h +++ b/platform/ios/platform/ios/src/Mapbox.h @@ -36,6 +36,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLLocationManager.h" #import "MGLLoggingConfiguration.h" #import "MGLMapCamera.h" +#import "MGLMapProjection.h" #import "MGLMapSnapshotter.h" #import "MGLMapView.h" #import "MGLMapViewDelegate.h" diff --git a/platform/ios/platform/ios/test/MGLMapViewGestureRecognizerTests.mm b/platform/ios/platform/ios/test/MGLMapViewGestureRecognizerTests.mm index 00f25189315..d028236862d 100644 --- a/platform/ios/platform/ios/test/MGLMapViewGestureRecognizerTests.mm +++ b/platform/ios/platform/ios/test/MGLMapViewGestureRecognizerTests.mm @@ -3,14 +3,13 @@ #import "../../darwin/src/MGLGeometry_Private.h" #import "MGLMockGestureRecognizers.h" +#import "MGLMapView_Private.h" #include #include @interface MGLMapView (MGLMapViewGestureRecognizerTests) -- (mbgl::Map &)mbglMap; - - (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch; - (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate; - (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap; diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index afaa1223cef..5ea267abafc 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -426,6 +426,12 @@ std::vector Map::latLngsForPixels(const std::vector& s return ret; } +#pragma mark - Transform + +TransformState Map::getTransfromState() const { + return impl->transform.getState(); +} + #pragma mark - Annotations void Map::addAnnotationImage(std::unique_ptr image) { diff --git a/src/mbgl/map/map_impl.hpp b/src/mbgl/map/map_impl.hpp index abcfa972f68..16ce40ff48c 100644 --- a/src/mbgl/map/map_impl.hpp +++ b/src/mbgl/map/map_impl.hpp @@ -76,4 +76,9 @@ class Map::Impl final : public style::Observer, public RendererObserver { std::unique_ptr stillImageRequest; }; +// Forward declaration of this method is required for the MapProjection class +CameraOptions cameraForLatLngs(const std::vector& latLngs, + const Transform& transform, + const EdgeInsets& padding); + } // namespace mbgl diff --git a/src/mbgl/map/map_projection.cpp b/src/mbgl/map/map_projection.cpp new file mode 100644 index 00000000000..c1420dcfb97 --- /dev/null +++ b/src/mbgl/map/map_projection.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +namespace mbgl { + +MapProjection::MapProjection(const Map& map) + : transform(std::make_unique(map.getTransfromState())) {} + +MapProjection::~MapProjection() = default; + +ScreenCoordinate MapProjection::pixelForLatLng(const LatLng& latLng) const { + // The implementation is just a copy from map.cpp + LatLng unwrappedLatLng = latLng.wrapped(); + unwrappedLatLng.unwrapForShortestPath(transform->getLatLng()); + return transform->latLngToScreenCoordinate(unwrappedLatLng); +} + +LatLng MapProjection::latLngForPixel(const ScreenCoordinate& pixel) const { + // The implementation is just a copy from map.cpp + return transform->screenCoordinateToLatLng(pixel); +} + +void MapProjection::setCamera(const CameraOptions& camera) { + transform->jumpTo(camera); +} + +CameraOptions MapProjection::getCamera() const { + return transform->getCameraOptions(nullopt); +} + +void MapProjection::setVisibleCoordinates(const std::vector& latLngs, + const EdgeInsets& padding) { + transform->jumpTo(mbgl::cameraForLatLngs(latLngs, *transform, padding) + .withBearing(-transform->getBearing() * util::RAD2DEG) + .withPitch(transform->getPitch() * util::RAD2DEG)); +} + +} // namespace mbgl