From 286d91bd138929430f9fd44b00de61326ef4080d Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Mon, 2 Oct 2017 17:22:36 +0300 Subject: [PATCH 01/10] [core] map snapshotter - add mutators --- platform/default/mbgl/map/map_snapshotter.cpp | 85 ++++++++++++++++++- platform/default/mbgl/map/map_snapshotter.hpp | 17 ++++ 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp index 95c46344fec..058e03c71b6 100644 --- a/platform/default/mbgl/map/map_snapshotter.cpp +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -20,6 +20,18 @@ class MapSnapshotter::Impl { const optional region, const optional programCacheDir); + void setStyleURL(std::string styleURL); + std::string getStyleURL() const; + + void setSize(Size); + Size getSize() const; + + void setCameraOptions(CameraOptions); + CameraOptions getCameraOptions() const; + + void setRegion(LatLngBounds); + LatLngBounds getRegion() const; + void snapshot(ActorRef); private: @@ -44,9 +56,7 @@ MapSnapshotter::Impl::Impl(FileSource& fileSource, // Set region, if specified if (region) { - mbgl::EdgeInsets insets = { 0, 0, 0, 0 }; - std::vector latLngs = { region->southwest(), region->northeast() }; - map.jumpTo(map.cameraForLatLngs(latLngs, insets)); + this->setRegion(*region); } } @@ -56,6 +66,41 @@ void MapSnapshotter::Impl::snapshot(ActorRef callback) }); } +void MapSnapshotter::Impl::setStyleURL(std::string styleURL) { + map.getStyle().loadURL(styleURL); +} + +std::string MapSnapshotter::Impl::getStyleURL() const { + return map.getStyle().getURL(); +} + +void MapSnapshotter::Impl::setSize(Size size) { + map.setSize(size); +} + +Size MapSnapshotter::Impl::getSize() const { + return map.getSize(); +} + +void MapSnapshotter::Impl::setCameraOptions(CameraOptions cameraOptions) { + map.jumpTo(cameraOptions); +} + +CameraOptions MapSnapshotter::Impl::getCameraOptions() const { + EdgeInsets insets; + return map.getCameraOptions(insets); +} + +void MapSnapshotter::Impl::setRegion(LatLngBounds region) { + mbgl::EdgeInsets insets = { 0, 0, 0, 0 }; + std::vector latLngs = { region.southwest(), region.northeast() }; + map.jumpTo(map.cameraForLatLngs(latLngs, insets)); +} + +LatLngBounds MapSnapshotter::Impl::getRegion() const { + return map.latLngBoundsForCamera(getCameraOptions()); +} + MapSnapshotter::MapSnapshotter(FileSource& fileSource, Scheduler& scheduler, const std::string& styleURL, @@ -70,7 +115,39 @@ MapSnapshotter::MapSnapshotter(FileSource& fileSource, MapSnapshotter::~MapSnapshotter() = default; void MapSnapshotter::snapshot(ActorRef callback) { - impl->actor().invoke(&Impl::snapshot, std::move(callback)); + impl->actor().invoke(&Impl::snapshot, std::move(callback)); +} + +void MapSnapshotter::setStyleURL(const std::string& styleURL) { + impl->actor().invoke(&Impl::setStyleURL, styleURL); +} + +std::string MapSnapshotter::getStyleURL() const { + return impl->actor().ask(&Impl::getStyleURL).get(); +} + +void MapSnapshotter::setSize(const Size& size) { + impl->actor().invoke(&Impl::setSize, size); +} + +Size MapSnapshotter::getSize() const { + return impl->actor().ask(&Impl::getSize).get(); +} + +void MapSnapshotter::setCameraOptions(const CameraOptions& options) { + impl->actor().invoke(&Impl::setCameraOptions, options); +} + +CameraOptions MapSnapshotter::getCameraOptions() const { + return impl->actor().ask(&Impl::getCameraOptions).get(); +} + +void MapSnapshotter::setRegion(const LatLngBounds& bounds) { + impl->actor().invoke(&Impl::setRegion, std::move(bounds)); +} + +LatLngBounds MapSnapshotter::getRegion() const { + return impl->actor().ask(&Impl::getRegion).get(); } } // namespace mbgl diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp index 0afa848fd8a..2450fa76127 100644 --- a/platform/default/mbgl/map/map_snapshotter.hpp +++ b/platform/default/mbgl/map/map_snapshotter.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -16,6 +17,10 @@ class FileSource; class Size; class LatLngBounds; +namespace style { +class Style; +} // namespace style + class MapSnapshotter { public: MapSnapshotter(FileSource& fileSource, @@ -29,6 +34,18 @@ class MapSnapshotter { ~MapSnapshotter(); + void setStyleURL(const std::string& styleURL); + std::string getStyleURL() const; + + void setSize(const Size&); + Size getSize() const; + + void setCameraOptions(const CameraOptions&); + CameraOptions getCameraOptions() const; + + void setRegion(const LatLngBounds&); + LatLngBounds getRegion() const; + using Callback = std::function; void snapshot(ActorRef); From 3b5676cd9d324bfef5f853ffb39963312e006407 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Mon, 2 Oct 2017 17:22:56 +0300 Subject: [PATCH 02/10] [android] map snapshotter - add mutators --- .../mapboxsdk/snapshotter/MapSnapshotter.java | 34 +++++- .../src/main/AndroidManifest.xml | 10 ++ .../snapshot/MapSnapshotterReuseActivity.java | 110 ++++++++++++++++++ .../layout/activity_map_snapshotter_reuse.xml | 24 ++++ .../src/main/res/values/descriptions.xml | 1 + .../src/main/res/values/titles.xml | 1 + .../src/snapshotter/map_snapshotter.cpp | 25 +++- .../src/snapshotter/map_snapshotter.hpp | 8 ++ platform/default/mbgl/map/map_snapshotter.cpp | 1 + 9 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterReuseActivity.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_snapshotter_reuse.xml diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java index 72df86d80da..8118f9ad92d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -184,7 +184,7 @@ public void start(@NonNull MapboxMap.SnapshotReadyCallback callback) { * Starts loading and rendering the snapshot. The callbacks will be fired * on the calling thread. * - * @param callback the callback to use when the snapshot is ready + * @param callback the callback to use when the snapshot is ready * @param errorHandler the error handler to use on snapshot errors */ public void start(@NonNull MapboxMap.SnapshotReadyCallback callback, ErrorHandler errorHandler) { @@ -197,12 +197,42 @@ public void start(@NonNull MapboxMap.SnapshotReadyCallback callback, ErrorHandle nativeStart(); } + /** + * Updates the snapshotter with a new size + * + * @param width the width + * @param height the height + */ + public native void setSize(int width, int height); + + /** + * Updates the snapshotter with a new {@link CameraPosition} + * + * @param cameraPosition the camera position + */ + public native void setCameraPosition(CameraPosition cameraPosition); + + /** + * Updates the snapshotter with a new {@link LatLngBounds} + * + * @param region the region + */ + public native void setRegion(LatLngBounds region); + + /** + * Updates the snapshotter with a new style url + * + * @param styleUrl the style url + */ + public native void setStyleUrl(String styleUrl); + + /** * Must be called in on the thread * the object was created on. */ public void cancel() { - callback = null; + reset(); nativeCancel(); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index bf97749b9e4..d6237bc161d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -377,6 +377,16 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> + + + + + + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 4d1f7eac388..94dc669781d 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -60,6 +60,7 @@ Shows how to animate georeferenced images Show 2 MapView on screen with a bottom sheet Show a static bitmap taken with the MapSnapshotter + Show how to reuse a MapSnapshotter instance Use Android SDK Animators to animate camera position changes Use Android SDK Views as symbols \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 8f394d0eb4f..4942bcab36c 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -60,6 +60,7 @@ Animated Image Source Bottom sheet Map Snapshotter + Map Snapshotter Reuse Animator animation SymbolGenerator \ No newline at end of file diff --git a/platform/android/src/snapshotter/map_snapshotter.cpp b/platform/android/src/snapshotter/map_snapshotter.cpp index d64218d11a8..74e43c70a1f 100644 --- a/platform/android/src/snapshotter/map_snapshotter.cpp +++ b/platform/android/src/snapshotter/map_snapshotter.cpp @@ -81,9 +81,26 @@ void MapSnapshotter::start(JNIEnv&) { void MapSnapshotter::cancel(JNIEnv&) { MBGL_VERIFY_THREAD(tid); - snapshotCallback.reset(); - snapshotter.reset(); +} + + +void MapSnapshotter::setStyleUrl(JNIEnv& env, jni::String styleURL) { + snapshotter->setStyleURL(jni::Make(env, styleURL)); +} + +void MapSnapshotter::setSize(JNIEnv&, jni::jint width, jni::jint height) { + auto size = mbgl::Size { static_cast(width), static_cast(height) }; + snapshotter->setSize(size); +} + +void MapSnapshotter::setCameraPosition(JNIEnv& env, jni::Object position) { + auto options = CameraPosition::getCameraOptions(env, position); + snapshotter->setCameraOptions(options); +} + +void MapSnapshotter::setRegion(JNIEnv& env, jni::Object region) { + snapshotter->setRegion(LatLngBounds::getLatLngBounds(env, region)); } // Static methods // @@ -101,6 +118,10 @@ void MapSnapshotter::registerNative(jni::JNIEnv& env) { std::make_unique, jni::Object, jni::jfloat, jni::jint, jni::jint, jni::String, jni::Object, jni::Object, jni::String>, "nativeInitialize", "finalize", + METHOD(&MapSnapshotter::setStyleUrl, "setStyleUrl"), + METHOD(&MapSnapshotter::setSize, "setSize"), + METHOD(&MapSnapshotter::setCameraPosition, "setCameraPosition"), + METHOD(&MapSnapshotter::setRegion, "setRegion"), METHOD(&MapSnapshotter::start, "nativeStart"), METHOD(&MapSnapshotter::cancel, "nativeCancel") ); diff --git a/platform/android/src/snapshotter/map_snapshotter.hpp b/platform/android/src/snapshotter/map_snapshotter.hpp index 093f589c059..fa8a2d7a5a9 100644 --- a/platform/android/src/snapshotter/map_snapshotter.hpp +++ b/platform/android/src/snapshotter/map_snapshotter.hpp @@ -40,6 +40,14 @@ class MapSnapshotter { ~MapSnapshotter(); + void setStyleUrl(JNIEnv&, jni::String styleURL); + + void setSize(JNIEnv&, jni::jint width, jni::jint height); + + void setCameraPosition(JNIEnv&, jni::Object position); + + void setRegion(JNIEnv&, jni::Object region); + void start(JNIEnv&); void cancel(JNIEnv&); diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp index 058e03c71b6..e542e2fb548 100644 --- a/platform/default/mbgl/map/map_snapshotter.cpp +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -76,6 +76,7 @@ std::string MapSnapshotter::Impl::getStyleURL() const { void MapSnapshotter::Impl::setSize(Size size) { map.setSize(size); + frontend.setSize(size); } Size MapSnapshotter::Impl::getSize() const { From 3020c731fcea85d8e4f9ba99ecb3deb45498f3c8 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Thu, 5 Oct 2017 14:19:07 +0300 Subject: [PATCH 03/10] [darwin] map snapshotter - snapshotter mutability --- platform/darwin/src/MGLMapSnapshotter.h | 40 ++++++++++- platform/darwin/src/MGLMapSnapshotter.mm | 87 ++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 615d39bee4d..8bc929e3a92 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -46,7 +46,7 @@ MGL_EXPORT @property (nonatomic) MGLMapCamera *camera; /** - The cooordinate rectangle that encompasses the bounds to capture. + The coordinate rectangle that encompasses the bounds to capture. If this property is non-empty and the camera property is non-nil, the camera’s center coordinate and altitude are ignored in favor of this property’s value. @@ -138,6 +138,44 @@ MGL_EXPORT */ - (void)cancel; +/** + The zoom level. + + The default zoom level is 0. This overwrites the camera zoom level if set. + */ +@property (nonatomic) double zoomLevel; + +/** + A camera representing the viewport visible in the snapshot. + + If this property is non-nil and the `coordinateBounds` property is set to a non-empty + coordinate bounds, the camera’s center coordinate and altitude are ignored in favor + of the `coordinateBounds` property. + */ +@property (nonatomic) MGLMapCamera *camera; + +/** + The coordinate rectangle that encompasses the bounds to capture. + + If this property is non-empty and the camera property is non-nil, the camera’s + center coordinate and altitude are ignored in favor of this property’s value. + */ +@property (nonatomic) MGLCoordinateBounds coordinateBounds; + +/** + URL of the map style to snapshot. + + The URL may be a full HTTP or HTTPS URL, a Mapbox URL indicating the style’s + map ID (`mapbox://styles/{user}/{style`}), or a path to a local file relative + to the application’s resource path. Specify `nil` for the default style. + */ +@property (nonatomic) NSURL* styleURL; + +/** + The size of the output image, measured in points. + */ +@property (nonatomic) CGSize size; + /** Indicates whether as snapshot is currently being generated. */ diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 835e1995f33..9d32ecf72c2 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -26,7 +26,7 @@ @implementation MGLMapSnapshotOptions -- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize) size; +- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGLMapCamera *)camera size:(CGSize) size { self = [super init]; if (self) { @@ -60,7 +60,7 @@ @implementation MGLMapSnapshotter { std::unique_ptr> _snapshotCallback; } -- (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; +- (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options { self = [super init]; if (self) { @@ -102,12 +102,12 @@ - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; return self; } -- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion; +- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion { [self startWithQueue:dispatch_get_main_queue() completionHandler:completion]; } -- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion; +- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion { if ([self isLoading]) { [NSException raise:NSInternalInconsistencyException @@ -180,10 +180,87 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot }); } -- (void)cancel; +- (void)cancel { _snapshotCallback.reset(); _mbglMapSnapshotter.reset(); } +- (NSURL *)styleURL +{ + NSString *styleURLString = @(_mbglMapSnapshotter->getStyleURL().c_str()); + return styleURLString && styleURLString.length > 0 ? [NSURL URLWithString:styleURLString] : nil; +} + +- (void)setStyleURL:(NSURL *)url +{ + _mbglMapSnapshotter->setStyleURL(std::string([url.absoluteString UTF8String])); +} + +- (CGSize)size +{ + mbgl::Size size = _mbglMapSnapshotter->getSize(); + return CGSizeMake(size.width, size.height); +} + +- (void)setSize:(CGSize)size +{ + _mbglMapSnapshotter->setSize({ + static_cast(MAX(size.width, MGLSnapshotterMinimumPixelSize)), + static_cast(MAX(size.height, MGLSnapshotterMinimumPixelSize)) + }); +} + +- (MGLMapCamera *)camera +{ + mbgl::CameraOptions cameraOptions = _mbglMapSnapshotter->getCameraOptions(); + CGFloat pitch = *cameraOptions.pitch; + CLLocationDirection heading = mbgl::util::wrap(*cameraOptions.angle, 0., 360.); + CLLocationDistance distance = MGLAltitudeForZoomLevel(*cameraOptions.zoom, pitch, cameraOptions.center->latitude(), [self size]); + return [MGLMapCamera cameraLookingAtCenterCoordinate:MGLLocationCoordinate2DFromLatLng(*cameraOptions.center) + fromDistance:distance + pitch:pitch + heading:heading]; +} + +- (void)setCamera:(MGLMapCamera *)camera +{ + mbgl::CameraOptions cameraOptions; + CLLocationCoordinate2D center; + if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) { + cameraOptions.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); + center = camera.centerCoordinate; + } else { + // Center is optional, but always set. + center = MGLLocationCoordinate2DFromLatLng(*_mbglMapSnapshotter->getCameraOptions().center); + } + + cameraOptions.angle = MAX(0, camera.heading) * mbgl::util::DEG2RAD; + cameraOptions.zoom = MAX(0, MGLZoomLevelForAltitude(camera.altitude, camera.pitch, center.latitude, [self size])); + cameraOptions.pitch = MAX(0, camera.pitch); +} + +- (double)zoomLevel +{ + mbgl::CameraOptions cameraOptions = _mbglMapSnapshotter->getCameraOptions(); + return MGLAltitudeForZoomLevel(*cameraOptions.zoom, *cameraOptions.pitch, cameraOptions.center->latitude(), [self size]); +} + +- (void)setZoomLevel:(double)zoomLevel +{ + mbgl::CameraOptions cameraOptions = _mbglMapSnapshotter->getCameraOptions(); + cameraOptions.zoom = zoomLevel; + _mbglMapSnapshotter->setCameraOptions(cameraOptions); +} + +- (MGLCoordinateBounds)coordinateBounds +{ + return MGLCoordinateBoundsFromLatLngBounds(_mbglMapSnapshotter->getRegion()); +} + +- (void)setCoordinateBounds:(MGLCoordinateBounds)coordinateBounds +{ + _mbglMapSnapshotter->setRegion(MGLLatLngBoundsFromCoordinateBounds(coordinateBounds)); +} + @end From 311ca6ef24e60b3097b51d8efbd7a03cee4ead99 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Mon, 9 Oct 2017 16:47:32 +0300 Subject: [PATCH 04/10] [default] headless frontend - allow access to the transform state --- platform/default/mbgl/gl/headless_frontend.cpp | 10 ++++++++++ platform/default/mbgl/gl/headless_frontend.hpp | 3 +++ 2 files changed, 13 insertions(+) diff --git a/platform/default/mbgl/gl/headless_frontend.cpp b/platform/default/mbgl/gl/headless_frontend.cpp index 5d2932258a3..2cbb624bd09 100644 --- a/platform/default/mbgl/gl/headless_frontend.cpp +++ b/platform/default/mbgl/gl/headless_frontend.cpp @@ -1,6 +1,8 @@ #include #include +#include #include +#include #include namespace mbgl { @@ -83,4 +85,12 @@ PremultipliedImage HeadlessFrontend::render(Map& map) { return result; } +optional HeadlessFrontend::getTransformState() const { + if (updateParameters) { + return updateParameters->transformState; + } else { + return {}; + } +} + } // namespace mbgl diff --git a/platform/default/mbgl/gl/headless_frontend.hpp b/platform/default/mbgl/gl/headless_frontend.hpp index 33503bc13b1..4d1116904ed 100644 --- a/platform/default/mbgl/gl/headless_frontend.hpp +++ b/platform/default/mbgl/gl/headless_frontend.hpp @@ -14,6 +14,7 @@ class Scheduler; class Renderer; class RendererBackend; class Map; +class TransformState; class HeadlessFrontend : public RendererFrontend { public: @@ -34,6 +35,8 @@ class HeadlessFrontend : public RendererFrontend { PremultipliedImage readStillImage(); PremultipliedImage render(Map&); + optional getTransformState() const; + private: Size size; float pixelRatio; From 8a58ef81c026068e7979f31378ba36e34e935d9f Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Mon, 9 Oct 2017 16:49:29 +0300 Subject: [PATCH 05/10] [default] map snapshotter - add function to get screen coordinate from latlng for snapshot - Wraps the TransformState for the snapshot so that the snapshotter itself is free to be re-used or destroyed --- platform/default/mbgl/map/map_snapshotter.cpp | 21 ++++++++++++++++++- platform/default/mbgl/map/map_snapshotter.hpp | 4 +++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp index e542e2fb548..3e930c31f19 100644 --- a/platform/default/mbgl/map/map_snapshotter.cpp +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -3,9 +3,11 @@ #include #include #include +#include #include #include #include +#include namespace mbgl { @@ -62,7 +64,24 @@ MapSnapshotter::Impl::Impl(FileSource& fileSource, void MapSnapshotter::Impl::snapshot(ActorRef callback) { map.renderStill([this, callback = std::move(callback)] (std::exception_ptr error) mutable { - callback.invoke(&MapSnapshotter::Callback::operator(), error, error ? PremultipliedImage() : frontend.readStillImage()); + + // Create lambda that captures the current transform state + // and can be used to translate for geographic to screen + // coordinates + assert (frontend.getTransformState()); + PointForFn pointForFn { [=, center=map.getLatLng(), transformState = *frontend.getTransformState()] (const LatLng& latLng) { + LatLng unwrappedLatLng = latLng.wrapped(); + unwrappedLatLng.unwrapForShortestPath(center); + Transform transform { transformState }; + return transform.latLngToScreenCoordinate(unwrappedLatLng); + }}; + + callback.invoke( + &MapSnapshotter::Callback::operator(), + error, + error ? PremultipliedImage() : frontend.readStillImage(), + std::move(pointForFn) + ); }); } diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp index 2450fa76127..b3cfb3058f7 100644 --- a/platform/default/mbgl/map/map_snapshotter.hpp +++ b/platform/default/mbgl/map/map_snapshotter.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -46,7 +47,8 @@ class MapSnapshotter { void setRegion(const LatLngBounds&); LatLngBounds getRegion() const; - using Callback = std::function; + using PointForFn = std::function; + using Callback = std::function; void snapshot(ActorRef); private: From 8cd4fcb27ee2a8909f8d0c3fd76a086508fbbb37 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Mon, 9 Oct 2017 16:54:32 +0300 Subject: [PATCH 06/10] [android] map snapshotter - wrap snapshot in native peer for access to the pointFor functionality --- .../mapboxsdk/snapshotter/MapSnapshot.java | 45 +++++++++++ .../mapboxsdk/snapshotter/MapSnapshotter.java | 33 +++++--- .../src/main/AndroidManifest.xml | 10 +++ .../snapshot/MapSnapshotterActivity.java | 9 +-- .../MapSnapshotterMarkerActivity.java | 77 +++++++++++++++++++ .../snapshot/MapSnapshotterReuseActivity.java | 9 +-- .../activity_map_snapshotter_marker.xml | 14 ++++ .../src/main/res/values/descriptions.xml | 1 + .../src/main/res/values/titles.xml | 1 + platform/android/config.cmake | 10 ++- platform/android/src/jni.cpp | 2 + .../android/src/snapshotter/map_snapshot.cpp | 57 ++++++++++++++ .../android/src/snapshotter/map_snapshot.hpp | 41 ++++++++++ .../src/snapshotter/map_snapshotter.cpp | 12 +-- 14 files changed, 293 insertions(+), 28 deletions(-) create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterMarkerActivity.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_snapshotter_marker.xml create mode 100644 platform/android/src/snapshotter/map_snapshot.cpp create mode 100644 platform/android/src/snapshotter/map_snapshot.hpp diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java new file mode 100644 index 00000000000..2d1412aeda3 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java @@ -0,0 +1,45 @@ +package com.mapbox.mapboxsdk.snapshotter; + +import android.graphics.Bitmap; +import android.graphics.PointF; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +/** + * A completed snapshot. + * + * @see MapSnapshotter + */ +public class MapSnapshot { + + private long nativePtr = 0; + private Bitmap bitmap; + + /** + * Created from native side + */ + private MapSnapshot(long nativePtr, Bitmap bitmap) { + this.nativePtr = nativePtr; + this.bitmap = bitmap; + } + + /** + * @return the bitmap + */ + public Bitmap getBitmap() { + return bitmap; + } + + /** + * Calculate the point in pixels on the Image from geographical coordinates. + * + * @param latLng the geographical coordinates + * @return the point on the image + */ + public native PointF pixelForLatLng(LatLng latLng); + + // Unused, needed for peer binding + private native void initialize(); + + protected native void finalize(); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java index 8118f9ad92d..37d05fc3280 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -12,7 +12,6 @@ import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.Style; import com.mapbox.mapboxsdk.geometry.LatLngBounds; -import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.storage.FileSource; /** @@ -23,11 +22,27 @@ @UiThread public class MapSnapshotter { + /** + * Get notified on snapshot completion. + * + * @see MapSnapshotter#start(SnapshotReadyCallback, ErrorHandler) + */ + public interface SnapshotReadyCallback { + + /** + * Called when the snapshot is complete. + * + * @param snapshot the snapshot + */ + void onSnapshotReady(MapSnapshot snapshot); + + } + /** * Can be used to get notified of errors * in snapshot generation * - * @see MapSnapshotter#start(MapboxMap.SnapshotReadyCallback, ErrorHandler) + * @see MapSnapshotter#start(SnapshotReadyCallback, ErrorHandler) */ public interface ErrorHandler { @@ -46,7 +61,7 @@ public interface ErrorHandler { private long nativePtr = 0; private final Context context; - private MapboxMap.SnapshotReadyCallback callback; + private SnapshotReadyCallback callback; private ErrorHandler errorHandler; /** @@ -176,7 +191,7 @@ public MapSnapshotter(@NonNull Context context, @NonNull Options options) { * * @param callback the callback to use when the snapshot is ready */ - public void start(@NonNull MapboxMap.SnapshotReadyCallback callback) { + public void start(@NonNull SnapshotReadyCallback callback) { this.start(callback, null); } @@ -187,7 +202,7 @@ public void start(@NonNull MapboxMap.SnapshotReadyCallback callback) { * @param callback the callback to use when the snapshot is ready * @param errorHandler the error handler to use on snapshot errors */ - public void start(@NonNull MapboxMap.SnapshotReadyCallback callback, ErrorHandler errorHandler) { + public void start(@NonNull SnapshotReadyCallback callback, ErrorHandler errorHandler) { if (this.callback != null) { throw new IllegalStateException("Snapshotter was already started"); } @@ -247,12 +262,12 @@ protected void addOverlay(Bitmap original) { * Called by JNI peer when snapshot is ready. * Always called on the origin (main) thread. * - * @param bitmap the generated snapshot + * @param snapshot the generated snapshot */ - protected void onSnapshotReady(Bitmap bitmap) { + protected void onSnapshotReady(MapSnapshot snapshot) { if (callback != null) { - addOverlay(bitmap); - callback.onSnapshotReady(bitmap); + addOverlay(snapshot.getBitmap()); + callback.onSnapshotReady(snapshot); reset(); } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index d6237bc161d..7f955cb45c3 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -387,6 +387,16 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> + + + + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 94dc669781d..a2bf1d85961 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -61,6 +61,7 @@ Show 2 MapView on screen with a bottom sheet Show a static bitmap taken with the MapSnapshotter Show how to reuse a MapSnapshotter instance + Show how to add a marker to a Snapshot Use Android SDK Animators to animate camera position changes Use Android SDK Views as symbols \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 4942bcab36c..1fb2f6ba7fd 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -61,6 +61,7 @@ Bottom sheet Map Snapshotter Map Snapshotter Reuse + Map Snapshot with marker Animator animation SymbolGenerator \ No newline at end of file diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 53f9b40d7fa..4af7a4293df 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -59,7 +59,7 @@ macro(mbgl_platform_core) PRIVATE platform/android/src/android_renderer_frontend.cpp PRIVATE platform/android/src/android_renderer_frontend.hpp - # Snapshots + # Snapshots (core) PRIVATE platform/default/mbgl/gl/headless_backend.cpp PRIVATE platform/default/mbgl/gl/headless_backend.hpp PRIVATE platform/default/mbgl/gl/headless_frontend.cpp @@ -68,8 +68,6 @@ macro(mbgl_platform_core) PRIVATE platform/default/mbgl/map/map_snapshotter.hpp PRIVATE platform/linux/src/headless_backend_egl.cpp PRIVATE platform/linux/src/headless_display_egl.cpp - PRIVATE platform/android/src/snapshotter/map_snapshotter.cpp - PRIVATE platform/android/src/snapshotter/map_snapshotter.hpp ) target_include_directories(mbgl-core @@ -293,6 +291,12 @@ add_library(mbgl-android STATIC platform/android/src/offline/offline_region_status.cpp platform/android/src/offline/offline_region_status.hpp + # Snapshots (SDK) + platform/android/src/snapshotter/map_snapshotter.cpp + platform/android/src/snapshotter/map_snapshotter.hpp + platform/android/src/snapshotter/map_snapshot.cpp + platform/android/src/snapshotter/map_snapshot.hpp + # Main jni bindings platform/android/src/attach_env.cpp platform/android/src/attach_env.hpp diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 6acb6a36642..f39aeb63741 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -50,6 +50,7 @@ #include "style/sources/sources.hpp" #include "style/light.hpp" #include "snapshotter/map_snapshotter.hpp" +#include "snapshotter/map_snapshot.hpp" namespace mbgl { namespace android { @@ -185,6 +186,7 @@ void registerNatives(JavaVM *vm) { // Snapshotter MapSnapshotter::registerNative(env); + MapSnapshot::registerNative(env); } } // namespace android diff --git a/platform/android/src/snapshotter/map_snapshot.cpp b/platform/android/src/snapshotter/map_snapshot.cpp new file mode 100644 index 00000000000..09e83bbb8ae --- /dev/null +++ b/platform/android/src/snapshotter/map_snapshot.cpp @@ -0,0 +1,57 @@ +#include "map_snapshot.hpp" + +#include "../bitmap.hpp" + +#include + +namespace mbgl { +namespace android { + +MapSnapshot::MapSnapshot(float pixelRatio_, MapSnapshot::PointForFn pointForFn_) + : pixelRatio(pixelRatio_) + , pointForFn(std::move(pointForFn_)) { +} + +MapSnapshot::~MapSnapshot() = default; + +jni::Object MapSnapshot::pixelForLatLng(jni::JNIEnv& env, jni::Object jLatLng) { + ScreenCoordinate point = pointForFn(LatLng::getLatLng(env, jLatLng)); + return PointF::New(env, point.x * pixelRatio, point.y * pixelRatio); +} + + +// Static methods // + +jni::Object MapSnapshot::New(JNIEnv& env, + PremultipliedImage&& image, + float pixelRatio, + mbgl::MapSnapshotter::PointForFn pointForFn) { + // Create the bitmap + auto bitmap = Bitmap::CreateBitmap(env, std::move(image)); + + // Create the Mapsnapshot peers + static auto constructor = javaClass.GetConstructor>(env); + auto nativePeer = std::make_unique(pixelRatio, pointForFn); + return javaClass.New(env, constructor, reinterpret_cast(nativePeer.release()), bitmap); +} + +jni::Class MapSnapshot::javaClass; + +void MapSnapshot::registerNative(jni::JNIEnv& env) { + // Lookup the class + MapSnapshot::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + +#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer(env, MapSnapshot::javaClass, + "nativePtr", + std::make_unique, + "initialize", + "finalize", + METHOD(&MapSnapshot::pixelForLatLng, "pixelForLatLng") + ); +} + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/snapshotter/map_snapshot.hpp b/platform/android/src/snapshotter/map_snapshot.hpp new file mode 100644 index 00000000000..6d60d49728a --- /dev/null +++ b/platform/android/src/snapshotter/map_snapshot.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include + +#include "../geometry/lat_lng.hpp" +#include "../graphics/pointf.hpp" + +namespace mbgl { +namespace android { + +class MapSnapshot { +public: + + using PointForFn = mbgl::MapSnapshotter::PointForFn; + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/snapshotter/MapSnapshot"; }; + + static void registerNative(jni::JNIEnv&); + + static jni::Object New(JNIEnv& env, + PremultipliedImage&& image, + float pixelRatio, + PointForFn pointForFn); + + MapSnapshot(jni::JNIEnv&) {}; + MapSnapshot(float pixelRatio, PointForFn); + ~MapSnapshot(); + + jni::Object pixelForLatLng(jni::JNIEnv&, jni::Object); + +private: + static jni::Class javaClass; + + float pixelRatio; + mbgl::MapSnapshotter::PointForFn pointForFn; +}; + +} // namespace android +} // namespace mbgl \ No newline at end of file diff --git a/platform/android/src/snapshotter/map_snapshotter.cpp b/platform/android/src/snapshotter/map_snapshotter.cpp index 74e43c70a1f..a13e91ccd3b 100644 --- a/platform/android/src/snapshotter/map_snapshotter.cpp +++ b/platform/android/src/snapshotter/map_snapshotter.cpp @@ -8,7 +8,7 @@ #include #include "../attach_env.hpp" -#include "../bitmap.hpp" +#include "map_snapshot.hpp" namespace mbgl { namespace android { @@ -58,7 +58,7 @@ MapSnapshotter::~MapSnapshotter() = default; void MapSnapshotter::start(JNIEnv&) { MBGL_VERIFY_THREAD(tid); - snapshotCallback = std::make_unique>(*Scheduler::GetCurrent(), [this](std::exception_ptr err, PremultipliedImage image) { + snapshotCallback = std::make_unique>(*Scheduler::GetCurrent(), [this](std::exception_ptr err, PremultipliedImage image, mbgl::MapSnapshotter::PointForFn pointForFn) { MBGL_VERIFY_THREAD(tid); android::UniqueEnv _env = android::AttachEnv(); @@ -67,12 +67,12 @@ void MapSnapshotter::start(JNIEnv&) { static auto onSnapshotFailed = javaClass.GetMethod(*_env, "onSnapshotFailed"); javaPeer->Call(*_env, onSnapshotFailed, jni::Make(*_env, util::toString(err))); } else { - // Create the bitmap - auto bitmap = Bitmap::CreateBitmap(*_env, std::move(image)); + // Create the wrapper + auto mapSnapshot = android::MapSnapshot::New(*_env, std::move(image), pixelRatio, pointForFn); // invoke callback - static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); - javaPeer->Call(*_env, onSnapshotReady, bitmap); + static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady"); + javaPeer->Call(*_env, onSnapshotReady, mapSnapshot); } }); From 8c1b6bdb626a00f1f48a12e3a18c83d701b6509d Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Mon, 9 Oct 2017 19:50:21 +0300 Subject: [PATCH 07/10] [darwin][ios][macos] map snapshotter - add MGLMapSnapshot wrapper --- platform/darwin/src/MGLMapSnapshotter.h | 62 ++++++++++++------- platform/darwin/src/MGLMapSnapshotter.mm | 34 +++++++++- .../test/MGLDocumentationExampleTests.swift | 18 ++++++ platform/ios/app/MBXSnapshotsViewController.m | 4 +- platform/macos/app/MapDocument.m | 6 +- 5 files changed, 94 insertions(+), 30 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 8bc929e3a92..b6228269cf0 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -69,23 +69,42 @@ MGL_EXPORT @end +/** + An image generated by a snapshotter object. + */ +@interface MGLMapSnapshot : NSObject + #if TARGET_OS_IPHONE /** - A block to processes the result or error of a snapshot request. - - @param snapshot The `UIImage` that was generated or `nil` if an error occurred. - @param error The error that occured or `nil` when successful. + Converts the specified map coordinate to a point in the coordinate space of the image. */ -typedef void (^MGLMapSnapshotCompletionHandler)(UIImage* _Nullable snapshot, NSError* _Nullable error); +- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + The image of the map’s content. + */ +@property(nonatomic, readonly) UIImage *image; #else +/** + Converts the specified map coordinate to a point in the coordinate space of the image. + */ +- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate; + +/** + The image of the map’s content. + */ +@property(nonatomic, readonly) NSImage *image; +#endif + +@end + /** A block to processes the result or error of a snapshot request. - @param snapshot The `NSImage` that was generated or `nil` if an error occurred. - @param error The eror that occured or `nil` when succesful. + @param snapshot The `MGLMapSnapshot` that was generated or `nil` if an error occurred. + @param error The error that occured or `nil` when successful. */ -typedef void (^MGLMapSnapshotCompletionHandler)(NSImage* _Nullable snapshot, NSError* _Nullable error); -#endif +typedef void (^MGLMapSnapshotCompletionHandler)(MGLMapSnapshot* _Nullable snapshot, NSError* _Nullable error); /** An immutable utility object for capturing map-based images. @@ -93,20 +112,18 @@ typedef void (^MGLMapSnapshotCompletionHandler)(NSImage* _Nullable snapshot, NSE ### Example ```swift - var camera = MGLMapCamera() - camera.centerCoordinate = CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365) - camera.pitch = 20 + let camera = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365), fromDistance: 100, pitch: 20, heading: 0) - var options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL(), camera: camera, size: CGSize(width: 320, height: 480)) + let options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL(), camera: camera, size: CGSize(width: 320, height: 480)) options.zoomLevel = 10 - var snapshotter = MGLMapSnapshotter(options: options) - snapshotter.start { (image, error) in - if error { - // error handler - } else { - // image handler - } + let snapshotter = MGLMapSnapshotter(options: options) + snapshotter.start { (snapshot, error) in + if error != nil { + // error handler + } else { + // image handler + } } ``` */ @@ -141,7 +158,8 @@ MGL_EXPORT /** The zoom level. - The default zoom level is 0. This overwrites the camera zoom level if set. + The default zoom level is 0. If this property is non-zero and the camera property + is non-nil, the camera’s altitude is ignored in favor of this property’s value. */ @property (nonatomic) double zoomLevel; @@ -169,7 +187,7 @@ MGL_EXPORT map ID (`mapbox://styles/{user}/{style`}), or a path to a local file relative to the application’s resource path. Specify `nil` for the default style. */ -@property (nonatomic) NSURL* styleURL; +@property (nonatomic, nullable) NSURL *styleURL; /** The size of the output image, measured in points. diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 9d32ecf72c2..193548ac11c 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -49,6 +49,33 @@ - (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGL @end +@interface MGLMapSnapshot() +- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn; + +@property (nonatomic) CGFloat scale; +@end + +@implementation MGLMapSnapshot { + mbgl::MapSnapshotter::PointForFn _pointForFn; +} +- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn +{ + self = [super init]; + if (self) { + _pointForFn = std::move(pointForFn); + _scale = scale; + _image = image; + } + return self; +} + +- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate +{ + mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate)); + return CGPointMake(sc.x * self.scale, sc.y * self.scale); +} +@end + @interface MGLMapSnapshotter() @property (nonatomic) MGLMapSnapshotOptions *options; @end @@ -117,7 +144,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot _loading = true; dispatch_async(queue, ^{ - _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image) { + _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::PointForFn pointForFn) { _loading = false; if (mbglError) { NSString *description = @(mbgl::util::toString(mbglError).c_str()); @@ -171,7 +198,8 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot // Dispatch result to origin queue dispatch_async(queue, ^{ - completion(compositedImage, nil); + MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn]; + completion(snapshot, nil); }); }); } @@ -189,7 +217,7 @@ - (void)cancel - (NSURL *)styleURL { NSString *styleURLString = @(_mbglMapSnapshotter->getStyleURL().c_str()); - return styleURLString && styleURLString.length > 0 ? [NSURL URLWithString:styleURLString] : nil; + return styleURLString.length ? [NSURL URLWithString:styleURLString] : nil; } - (void)setStyleURL:(NSURL *)url diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 42c656f203d..8762af9ba42 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -278,6 +278,24 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { //#-end-example-code } + func testMGLMapSnapshotter() { + //#-example-code + let camera = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365), fromDistance: 100, pitch: 20, heading: 0) + + let options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL(), camera: camera, size: CGSize(width: 320, height: 480)) + options.zoomLevel = 10 + + let snapshotter = MGLMapSnapshotter(options: options) + snapshotter.start { (snapshot, error) in + if error != nil { + // error handler + } else { + // image handler + } + } + //#-end-example-code + } + // For testMGLMapView(). func myCustomFunction() {} } diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m index ab5ad97c904..3bf93d8721c 100644 --- a/platform/ios/app/MBXSnapshotsViewController.m +++ b/platform/ios/app/MBXSnapshotsViewController.m @@ -51,11 +51,11 @@ - (MGLMapSnapshotter*) startSnapshotterForImageView:(UIImageView*) imageView coo // Create and start the snapshotter MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; - [snapshotter startWithCompletionHandler: ^(UIImage *image, NSError *error) { + [snapshotter startWithCompletionHandler: ^(MGLMapSnapshot* snapshot, NSError *error) { if (error) { NSLog(@"Could not load snapshot: %@", [error localizedDescription]); } else { - imageView.image = image; + imageView.image = snapshot.image; } }]; diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index feef53062b5..0df6b105184 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -167,7 +167,7 @@ - (IBAction)takeSnapshot:(id)sender { // Create and start the snapshotter snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; - [snapshotter startWithCompletionHandler:^(NSImage *image, NSError *error) { + [snapshotter startWithCompletionHandler:^(MGLMapSnapshot *snapshot, NSError *error) { if (error) { NSLog(@"Could not load snapshot: %@", error.localizedDescription); } else { @@ -182,7 +182,7 @@ - (IBAction)takeSnapshot:(id)sender { NSURL *fileURL = panel.URL; NSBitmapImageRep *bitmapRep; - for (NSImageRep *imageRep in image.representations) { + for (NSImageRep *imageRep in snapshot.image.representations) { if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapRep = (NSBitmapImageRep *)imageRep; break; // stop on first bitmap rep we find @@ -190,7 +190,7 @@ - (IBAction)takeSnapshot:(id)sender { } if (!bitmapRep) { - bitmapRep = [NSBitmapImageRep imageRepWithData:image.TIFFRepresentation]; + bitmapRep = [NSBitmapImageRep imageRepWithData:snapshot.image.TIFFRepresentation]; } CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileURL.pathExtension, NULL /* inConformingToUTI */); From d6518fb7e40fe407347155c9b780333f8d8179ae Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Fri, 27 Oct 2017 20:05:45 +0300 Subject: [PATCH 08/10] [core] map snapshotter - add attributions to snapshotter callback --- platform/default/mbgl/map/map_snapshotter.cpp | 11 +++++++++++ platform/default/mbgl/map/map_snapshotter.hpp | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp index 3e930c31f19..7b4ec5913bb 100644 --- a/platform/default/mbgl/map/map_snapshotter.cpp +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -76,10 +76,21 @@ void MapSnapshotter::Impl::snapshot(ActorRef callback) return transform.latLngToScreenCoordinate(unwrappedLatLng); }}; + // Collect all source attributions + std::vector attributions; + for (auto source : map.getStyle().getSources()) { + auto attribution = source->getAttribution(); + if (attribution) { + attributions.push_back(*attribution); + } + } + + // Invoke callback callback.invoke( &MapSnapshotter::Callback::operator(), error, error ? PremultipliedImage() : frontend.readStillImage(), + std::move(attributions), std::move(pointForFn) ); }); diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp index b3cfb3058f7..985396e5a36 100644 --- a/platform/default/mbgl/map/map_snapshotter.hpp +++ b/platform/default/mbgl/map/map_snapshotter.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace mbgl { @@ -48,7 +49,8 @@ class MapSnapshotter { LatLngBounds getRegion() const; using PointForFn = std::function; - using Callback = std::function; + using Attributions = std::vector; + using Callback = std::function; void snapshot(ActorRef); private: From d65676db51b14f7d11d447428c65a00c999b4d7c Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Fri, 27 Oct 2017 20:06:26 +0300 Subject: [PATCH 09/10] [darwin] map snapshotter - expose attributions --- platform/darwin/src/MGLMapSnapshotter.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 193548ac11c..a00521a87a8 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -144,7 +144,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot _loading = true; dispatch_async(queue, ^{ - _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, 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) { _loading = false; if (mbglError) { NSString *description = @(mbgl::util::toString(mbglError).c_str()); From 1c4b6a78328f0f39992fd39df68a9b609c32c059 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Fri, 27 Oct 2017 20:08:47 +0300 Subject: [PATCH 10/10] [android] map snapshotter - expose attributions --- .../mapboxsdk/snapshotter/MapSnapshot.java | 11 +++++++- platform/android/src/jni/collection.hpp | 26 +++++++++++++++++++ .../android/src/snapshotter/map_snapshot.cpp | 6 +++-- .../android/src/snapshotter/map_snapshot.hpp | 4 +++ .../src/snapshotter/map_snapshotter.cpp | 6 +++-- 5 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 platform/android/src/jni/collection.hpp diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java index 2d1412aeda3..aefa962402d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshot.java @@ -14,13 +14,15 @@ public class MapSnapshot { private long nativePtr = 0; private Bitmap bitmap; + private String[] attributions; /** * Created from native side */ - private MapSnapshot(long nativePtr, Bitmap bitmap) { + private MapSnapshot(long nativePtr, Bitmap bitmap, String[] attributions) { this.nativePtr = nativePtr; this.bitmap = bitmap; + this.attributions = attributions; } /** @@ -38,6 +40,13 @@ public Bitmap getBitmap() { */ public native PointF pixelForLatLng(LatLng latLng); + /** + * @return The attributions for the sources of this snapshot. + */ + protected String[] getAttributions() { + return attributions; + } + // Unused, needed for peer binding private native void initialize(); diff --git a/platform/android/src/jni/collection.hpp b/platform/android/src/jni/collection.hpp new file mode 100644 index 00000000000..5f94ec29ce5 --- /dev/null +++ b/platform/android/src/jni/collection.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include +#include + +namespace jni { + +inline Array MakeAnything(ThingToMake>, JNIEnv& env, const std::vector& vector) +{ + static auto clazz = *Class::Find(env).NewGlobalRef(env).release(); + auto result = Array::New(env, vector.size(), clazz); + + std::size_t index = 0; + for (auto&& item : vector) { + auto element = Make(env, item); + result.Set(env, index, element); + DeleteLocalRef(env, element); + index++; + } + + return result; +} + +} diff --git a/platform/android/src/snapshotter/map_snapshot.cpp b/platform/android/src/snapshotter/map_snapshot.cpp index 09e83bbb8ae..843a8a487a3 100644 --- a/platform/android/src/snapshotter/map_snapshot.cpp +++ b/platform/android/src/snapshotter/map_snapshot.cpp @@ -1,6 +1,7 @@ #include "map_snapshot.hpp" #include "../bitmap.hpp" +#include "../jni/collection.hpp" #include @@ -25,14 +26,15 @@ jni::Object MapSnapshot::pixelForLatLng(jni::JNIEnv& env, jni::Object MapSnapshot::New(JNIEnv& env, PremultipliedImage&& image, float pixelRatio, + std::vector attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { // Create the bitmap auto bitmap = Bitmap::CreateBitmap(env, std::move(image)); // Create the Mapsnapshot peers - static auto constructor = javaClass.GetConstructor>(env); + static auto constructor = javaClass.GetConstructor, jni::Array>(env); auto nativePeer = std::make_unique(pixelRatio, pointForFn); - return javaClass.New(env, constructor, reinterpret_cast(nativePeer.release()), bitmap); + return javaClass.New(env, constructor, reinterpret_cast(nativePeer.release()), bitmap, jni::Make>(env, attributions)); } jni::Class MapSnapshot::javaClass; diff --git a/platform/android/src/snapshotter/map_snapshot.hpp b/platform/android/src/snapshotter/map_snapshot.hpp index 6d60d49728a..64090bb48b3 100644 --- a/platform/android/src/snapshotter/map_snapshot.hpp +++ b/platform/android/src/snapshotter/map_snapshot.hpp @@ -7,6 +7,9 @@ #include "../geometry/lat_lng.hpp" #include "../graphics/pointf.hpp" +#include +#include + namespace mbgl { namespace android { @@ -22,6 +25,7 @@ class MapSnapshot { static jni::Object New(JNIEnv& env, PremultipliedImage&& image, float pixelRatio, + std::vector attributions, PointForFn pointForFn); MapSnapshot(jni::JNIEnv&) {}; diff --git a/platform/android/src/snapshotter/map_snapshotter.cpp b/platform/android/src/snapshotter/map_snapshotter.cpp index a13e91ccd3b..3c6623af40a 100644 --- a/platform/android/src/snapshotter/map_snapshotter.cpp +++ b/platform/android/src/snapshotter/map_snapshotter.cpp @@ -58,7 +58,9 @@ MapSnapshotter::~MapSnapshotter() = default; void MapSnapshotter::start(JNIEnv&) { MBGL_VERIFY_THREAD(tid); - snapshotCallback = std::make_unique>(*Scheduler::GetCurrent(), [this](std::exception_ptr err, PremultipliedImage image, mbgl::MapSnapshotter::PointForFn pointForFn) { + snapshotCallback = std::make_unique>( + *Scheduler::GetCurrent(), + [this](std::exception_ptr err, PremultipliedImage image, std::vector attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { MBGL_VERIFY_THREAD(tid); android::UniqueEnv _env = android::AttachEnv(); @@ -68,7 +70,7 @@ void MapSnapshotter::start(JNIEnv&) { javaPeer->Call(*_env, onSnapshotFailed, jni::Make(*_env, util::toString(err))); } else { // Create the wrapper - auto mapSnapshot = android::MapSnapshot::New(*_env, std::move(image), pixelRatio, pointForFn); + auto mapSnapshot = android::MapSnapshot::New(*_env, std::move(image), pixelRatio, attributions, pointForFn); // invoke callback static auto onSnapshotReady = javaClass.GetMethod)>(*_env, "onSnapshotReady");