Skip to content

Commit

Permalink
Implement map projection functionality (mapbox#254)
Browse files Browse the repository at this point in the history
* Implement map projection functionality

* Add missing MGL_EXPORT

* Remove the automatic copyright header
  • Loading branch information
OlexandrStepanov authored Mar 14, 2022
1 parent ea234ed commit 6db27f5
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions include/mbgl/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
namespace mbgl {

class RendererFrontend;
class TransformState;

namespace style {
class Image;
Expand Down Expand Up @@ -110,6 +111,9 @@ class Map : private util::noncopyable {
std::vector<ScreenCoordinate> pixelsForLatLngs(const std::vector<LatLng>&) const;
std::vector<LatLng> latLngsForPixels(const std::vector<ScreenCoordinate>&) const;

// Transform
TransformState getTransfromState() const;

// Annotations
void addAnnotationImage(std::unique_ptr<style::Image>);
void removeAnnotationImage(const std::string&);
Expand Down
30 changes: 30 additions & 0 deletions include/mbgl/map/map_projection.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <mbgl/map/camera.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/geo.hpp>

#include <memory>

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<LatLng>&, const EdgeInsets&);

private:
std::unique_ptr<Transform> transform;
};

} // namespace mbgl
92 changes: 92 additions & 0 deletions platform/ios/platform/darwin/test/MGLMapProjectionTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#import <Mapbox/Mapbox.h>
#import <XCTest/XCTest.h>
#import <TargetConditionals.h>

#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
16 changes: 16 additions & 0 deletions platform/ios/platform/ios/ios.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1069,6 +1074,9 @@
CAE7AD5420F46EF5003B6782 /* MGLMapSnapshotterSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MGLMapSnapshotterSwiftTests.swift; sourceTree = "<group>"; };
CAFB3C13234505D500399265 /* MGLMapSnapshotter_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter_Private.h; sourceTree = "<group>"; };
CF75A91422D85E860058A5C4 /* MGLLoggingConfiguration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLoggingConfiguration.mm; sourceTree = "<group>"; };
D42DE1E2275F7FEB00B4446E /* MGLMapProjection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLMapProjection.h; sourceTree = "<group>"; };
D42DE1E3275F7FEB00B4446E /* MGLMapProjection.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapProjection.mm; sourceTree = "<group>"; };
D4D7412D2760EEBA00102513 /* MGLMapProjectionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MGLMapProjectionTests.m; path = ../../darwin/test/MGLMapProjectionTests.m; sourceTree = "<group>"; };
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 = "<group>"; };
D717FE7B25F1955A0054E06E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -2016,6 +2025,8 @@
DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */,
4BC5769125E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.h */,
4BC5769225E3D3D6006E06EB /* MGLUserLocationAnnotationViewStyle.m */,
D42DE1E2275F7FEB00B4446E /* MGLMapProjection.h */,
D42DE1E3275F7FEB00B4446E /* MGLMapProjection.mm */,
);
name = Kit;
path = src;
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
75 changes: 75 additions & 0 deletions platform/ios/platform/ios/src/MGLMapProjection.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 6db27f5

Please sign in to comment.