From 56ab3318a300b3675e902fd13690252d87739a27 Mon Sep 17 00:00:00 2001 From: Julian Rex Date: Wed, 27 Jun 2018 10:08:16 -0400 Subject: [ios] [macos] Added `-[MGLSnapshot coordinateForPoint:]` and associated test. --- platform/darwin/src/MGLMapSnapshotter.h | 11 +++ platform/darwin/src/MGLMapSnapshotter.mm | 51 ++++++++++-- platform/ios/CHANGELOG.md | 4 + .../Snapshotter Tests/MGLMapSnapshotterTest.m | 90 ++++++++++++++++++++++ platform/macos/CHANGELOG.md | 1 + 5 files changed, 149 insertions(+), 8 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 976213c8ba..a5ddcac0ac 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -85,6 +85,11 @@ MGL_EXPORT */ - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; +/** + Converts the specified image point to a map coordinate. + */ +- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point; + /** The image of the map’s content. */ @@ -96,6 +101,12 @@ MGL_EXPORT */ - (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; +/** + Converts the specified image point to a map coordinate. + */ +- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point; + + /** The image of the map’s content. */ diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index d9fa044217..d9d16cc5be 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -53,31 +53,59 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; @end @interface MGLMapSnapshot() -- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn; +- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn; @property (nonatomic) CGFloat scale; @end @implementation MGLMapSnapshot { mbgl::MapSnapshotter::PointForFn _pointForFn; + mbgl::MapSnapshotter::LatLngForFn _latLngForFn; } -- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn +- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn { self = [super init]; if (self) { _pointForFn = std::move(pointForFn); + _latLngForFn = std::move(latLngForFn); _scale = scale; _image = image; } return self; } +#if TARGET_OS_IPHONE + - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate { mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate)); return CGPointMake(sc.x, sc.y); } + +- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point +{ + mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); + return MGLLocationCoordinate2DFromLatLng(latLng); +} + +#else + +- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate +{ + mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate)); + return NSMakePoint(sc.x, self.image.size.height - sc.y); +} + +- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point +{ + auto screenCoord = mbgl::ScreenCoordinate(point.x, self.image.size.height - point.y); + mbgl::LatLng latLng = _latLngForFn(screenCoord); + return MGLLocationCoordinate2DFromLatLng(latLng); +} + +#endif + @end @interface MGLMapSnapshotter() @@ -123,7 +151,11 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; __weak __typeof__(self) weakSelf = self; // mbgl::Scheduler::GetCurrent() scheduler means "run callback on current (ie UI/main) thread" // capture weakSelf to avoid retain cycle if callback is never called (ie snapshot cancelled) - _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { + + _snapshotCallback = std::make_unique>( + *mbgl::Scheduler::GetCurrent(), + [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn) { + __typeof__(self) strongSelf = weakSelf; // If self had died, _snapshotCallback would have been destroyed and this block would not be executed NSCAssert(strongSelf, @"Snapshot callback executed after being destroyed."); @@ -147,7 +179,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; mglImage.size = NSMakeSize(mglImage.size.width / strongSelf.options.scale, mglImage.size.height / strongSelf.options.scale); #endif - [strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue completionHandler:completion]; + [strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn latLngForFn:latLngForFn queue:queue completionHandler:completion]; } strongSelf->_snapshotCallback = NULL; }); @@ -158,7 +190,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; _mbglMapSnapshotter->snapshot(_snapshotCallback->self()); } -+ (void)drawAttributedSnapshotWorker:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn queue:(dispatch_queue_t)queue scale:(CGFloat)scale size:(CGSize)size completionHandler:(MGLMapSnapshotCompletionHandler)completion { ++ (void)drawAttributedSnapshotWorker:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn queue:(dispatch_queue_t)queue scale:(CGFloat)scale size:(CGSize)size completionHandler:(MGLMapSnapshotCompletionHandler)completion { NSArray* attributionInfo = [MGLMapSnapshotter generateAttributionInfos:attributions]; @@ -282,12 +314,15 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; #endif // Dispatch result to origin queue dispatch_async(queue, ^{ - MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:scale pointForFn:pointForFn]; + MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage + scale:scale + pointForFn:pointForFn + latLngForFn:latLngForFn]; completion(snapshot, nil); }); } -- (void)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn queue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion { +- (void)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn queue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion { // Process image watermark in a work queue dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); @@ -297,7 +332,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; // pointForFn is a copyable std::function that captures state by value: see MapSnapshotter::Impl::snapshot dispatch_async(workQueue, ^{ // Call a class method to ensure we're not accidentally capturing self - [MGLMapSnapshotter drawAttributedSnapshotWorker:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue scale:scale size:size completionHandler:completion]; + [MGLMapSnapshotter drawAttributedSnapshotWorker:attributions snapshotImage:mglImage pointForFn:pointForFn latLngForFn:latLngForFn queue:queue scale:scale size:size completionHandler:completion]; }); } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 4d052e0269..f69000a600 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -21,6 +21,10 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Improved caching performance. ([#12072](https://github.com/mapbox/mapbox-gl-native/pull/12072)) +### Other changes + +* Added `-[MGLMapSnapshot coordinateForPoint:]` that returns a map coordinate for a specified snapshot image point. ([#12221](https://github.com/mapbox/mapbox-gl-native/pull/12221)) + ## 4.0.3 - June 22, 2018 * Fixed a crash in `-[MGLStyle localizeLabelsIntoLocale:]` on iOS 9._x_. ([#12123](https://github.com/mapbox/mapbox-gl-native/pull/12123)) diff --git a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m index 6a698121c9..22f7fc5911 100644 --- a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m +++ b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m @@ -234,4 +234,94 @@ NSString* validAccessToken() { [self waitForExpectations:@[expectation] timeout:60.0]; } +- (void)testSnapshotPointConversion { + if (!validAccessToken()) { + return; + } + + CGSize size = self.mapView.bounds.size; + + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; + expectation.expectedFulfillmentCount = 1; + expectation.assertForOverFulfill = YES; + + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + XCTAssertNotNil(snapshotter); + + __weak __typeof__(self) weakself = self; + + [snapshotter startWithCompletionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + + __typeof__(self) myself = weakself; + + MGLTestAssertNotNil(myself, snapshot); + + CGPoint point = [snapshot pointForCoordinate:coord]; + + CGFloat epsilon = 0.000001; + + MGLTestAssertEqualWithAccuracy(myself, point.x, size.width/2.0, epsilon); + MGLTestAssertEqualWithAccuracy(myself, point.y, size.height/2.0, epsilon); + + CLLocationCoordinate2D coord2 = [snapshot coordinateForPoint:point]; + + MGLTestAssertEqualWithAccuracy(myself, coord.latitude, coord2.latitude, epsilon); + MGLTestAssertEqualWithAccuracy(myself, coord.longitude, coord2.longitude, epsilon); + + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:5.0]; +} + +- (void)testSnapshotPointConversionCoordinateOrdering { + if (!validAccessToken()) { + return; + } + + CGSize size = self.mapView.bounds.size; + + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; + expectation.expectedFulfillmentCount = 1; + expectation.assertForOverFulfill = YES; + + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + XCTAssertNotNil(snapshotter); + + __weak __typeof__(self) weakself = self; + + [snapshotter startWithCompletionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + + __typeof__(self) myself = weakself; + + CGFloat epsilon = 0.000001; + + MGLTestAssertNotNil(myself, snapshot); + + CLLocationCoordinate2D coordTL = [snapshot coordinateForPoint:CGPointZero]; + + MGLTestAssert(myself, coordTL.longitude < coord.longitude); + MGLTestAssert(myself, coordTL.latitude > coord.latitude); + + // And check point + CGPoint tl = [snapshot pointForCoordinate:coordTL]; + MGLTestAssertEqualWithAccuracy(myself, tl.x, 0.0, epsilon); + MGLTestAssertEqualWithAccuracy(myself, tl.y, 0.0, epsilon); + + CLLocationCoordinate2D coordBR = [snapshot coordinateForPoint:CGPointMake(size.width, size.height)]; + + MGLTestAssert(myself, coordBR.longitude > coord.longitude); + MGLTestAssert(myself, coordBR.latitude < coord.latitude); + + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:5.0]; +} + + @end diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index e12ac427d8..d9615f97e7 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -4,6 +4,7 @@ * Added `-[MGLMapView camera:fittingShape:edgePadding:]` and `-[MGLMapView camera:fittingCoordinateBounds:edgePadding:]` allowing you specify the pitch and direction for the calculated camera. ([#12213](https://github.com/mapbox/mapbox-gl-native/pull/12213)) * `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` can automatically localize labels into Japanese or Korean based on the system’s language settings. ([#12286](https://github.com/mapbox/mapbox-gl-native/pull/12286)) +* Added `-[MGLMapSnapshot coordinateForPoint:]` that returns a map coordinate for a specified snapshot image point. Fixed a bug in `-[MGLMapShapshot pointForCoordinate:]` where incorrect points were returned. ([#12221](https://github.com/mapbox/mapbox-gl-native/pull/12221)) ## 0.7.2 - June 22, 2018 -- cgit v1.2.1