Add runtime styling APIs on MapSnapshotter (#268)

* Add styleable snapshotter.

* Add style API for snapshotter

* Add observer interface to snapshotter

* WIP Add styling to snapshotter activity

* Update interface and implement addlayer functions.

* Deprecate methods instead of removing

* Implement addSource functions.

* Implement addImage function

* Still use the previous callback interface.

* Expose onStyleDidloaded interface

* Add setStyle method

* Start native snapshotter from start method

* Fix snapshotter test.

* Fix check style

* Keep the original interface

* Update gl-native

* Add demo for snapshotter heatmap layer

* bump gl-native

Co-authored-by: Alexander Shalamov <alexander.shalamov@mapbox.com>
This commit is contained in:
Kevin Li
2020-03-25 09:59:38 +08:00
committed by GitHub
parent 02b5e60f86
commit da6d8a2b06
19 changed files with 565 additions and 88 deletions

View File

@@ -3,7 +3,7 @@ package com.mapbox.mapboxsdk.maps;
import androidx.annotation.Keep;
@Keep
class Image {
public class Image {
private final byte[] buffer;
private final float pixelRatio;
private final String name;

View File

@@ -5,12 +5,6 @@ import android.graphics.Bitmap;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import androidx.annotation.UiThread;
import android.text.TextUtils;
import android.view.View;
@@ -44,6 +38,13 @@ import com.mapbox.mapboxsdk.style.expressions.Expression;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Size;
import androidx.annotation.UiThread;
/**
* The general class to interact with in the Android Mapbox SDK. It exposes the entry point for all
* methods related to the MapView. You cannot instantiate {@link MapboxMap} object directly, rather,
@@ -93,6 +94,13 @@ public final class MapboxMap {
this.developerAnimationStartedListeners = developerAnimationStartedListeners;
}
/**
* Trigger the mapview to repaint.
*/
public void triggerRepaint() {
nativeMapView.triggerRepaint();
}
void initialise(@NonNull Context context, @NonNull MapboxMapOptions options) {
transform.initialise(this, options);
uiSettings.initialise(context, options);

View File

@@ -237,6 +237,8 @@ interface NativeMap {
float getPixelRatio();
void triggerRepaint();
//
// Deprecated Annotations API
//

View File

@@ -1003,6 +1003,11 @@ final class NativeMapView implements NativeMap {
return pixelRatio;
}
@Override
public void triggerRepaint() {
nativeTriggerRepaint();
}
@NonNull
@Override
public RectF getDensityDependantRectangle(final RectF rectangle) {
@@ -1476,6 +1481,9 @@ final class NativeMapView implements NativeMap {
return nativePtr;
}
@Keep
private native void nativeTriggerRepaint();
//
// Snapshot
//

View File

@@ -340,7 +340,7 @@ public class Style {
*/
public void addImage(@NonNull final String name, @NonNull Bitmap bitmap, boolean sdf) {
validateState("addImage");
nativeMap.addImages(new Image[]{toImage(new Builder.ImageWrapper(name, bitmap, sdf))});
nativeMap.addImages(new Image[] {toImage(new Builder.ImageWrapper(name, bitmap, sdf))});
}
/**
@@ -932,23 +932,23 @@ public class Style {
return this;
}
String getUri() {
public String getUri() {
return styleUri;
}
String getJson() {
public String getJson() {
return styleJson;
}
List<Source> getSources() {
public List<Source> getSources() {
return sources;
}
List<LayerWrapper> getLayers() {
public List<LayerWrapper> getLayers() {
return layers;
}
List<ImageWrapper> getImages() {
public List<ImageWrapper> getImages() {
return images;
}
@@ -963,18 +963,30 @@ public class Style {
return new Style(this, nativeMap);
}
static class ImageWrapper {
public static class ImageWrapper {
Bitmap bitmap;
String id;
boolean sdf;
ImageWrapper(String id, Bitmap bitmap, boolean sdf) {
public ImageWrapper(String id, Bitmap bitmap, boolean sdf) {
this.id = id;
this.bitmap = bitmap;
this.sdf = sdf;
}
static ImageWrapper[] convertToImageArray(HashMap<String, Bitmap> bitmapHashMap, boolean sdf) {
public Bitmap getBitmap() {
return bitmap;
}
public String getId() {
return id;
}
public boolean isSdf() {
return sdf;
}
public static ImageWrapper[] convertToImageArray(HashMap<String, Bitmap> bitmapHashMap, boolean sdf) {
ImageWrapper[] images = new ImageWrapper[bitmapHashMap.size()];
List<String> keyList = new ArrayList<>(bitmapHashMap.keySet());
for (int i = 0; i < bitmapHashMap.size(); i++) {
@@ -985,43 +997,59 @@ public class Style {
}
}
class LayerWrapper {
public class LayerWrapper {
Layer layer;
LayerWrapper(Layer layer) {
this.layer = layer;
}
public Layer getLayer() {
return layer;
}
}
class LayerAboveWrapper extends LayerWrapper {
public class LayerAboveWrapper extends LayerWrapper {
String aboveLayer;
LayerAboveWrapper(Layer layer, String aboveLayer) {
super(layer);
this.aboveLayer = aboveLayer;
}
public String getAboveLayer() {
return aboveLayer;
}
}
class LayerBelowWrapper extends LayerWrapper {
public class LayerBelowWrapper extends LayerWrapper {
String belowLayer;
LayerBelowWrapper(Layer layer, String belowLayer) {
super(layer);
this.belowLayer = belowLayer;
}
public String getBelowLayer() {
return belowLayer;
}
}
class LayerAtWrapper extends LayerWrapper {
public class LayerAtWrapper extends LayerWrapper {
int index;
LayerAtWrapper(Layer layer, int index) {
super(layer);
this.index = index;
}
public int getIndex() {
return index;
}
}
}
private static Image toImage(Builder.ImageWrapper imageWrapper) {
public static Image toImage(Builder.ImageWrapper imageWrapper) {
Bitmap bitmap = imageWrapper.bitmap;
if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false);

View File

@@ -7,11 +7,6 @@ import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Handler;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.content.res.ResourcesCompat;
import android.text.Html;
import android.text.TextUtils;
import android.util.DisplayMetrics;
@@ -28,12 +23,23 @@ import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.Image;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.storage.FileSource;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.mapboxsdk.utils.FontUtils;
import com.mapbox.mapboxsdk.utils.ThreadUtils;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.core.content.res.ResourcesCompat;
import static com.mapbox.mapboxsdk.maps.Style.toImage;
/**
* The map snapshotter creates a large of the map, rendered
* off the UI thread. The snapshotter itself must be used on
@@ -44,6 +50,21 @@ public class MapSnapshotter {
private static final String TAG = "Mbgl-MapSnapshotter";
private static final int LOGO_MARGIN_DP = 4;
// Holds the pointer to JNI NativeMapView
@Keep
private long nativePtr = 0;
private final Context context;
private boolean fullyLoaded = false;
private Options options;
@Nullable
private SnapshotReadyCallback callback;
@Nullable
private ErrorHandler errorHandler;
/**
* Get notified on snapshot completion.
*
@@ -57,7 +78,6 @@ public class MapSnapshotter {
* @param snapshot the snapshot
*/
void onSnapshotReady(MapSnapshot snapshot);
}
/**
@@ -77,18 +97,6 @@ public class MapSnapshotter {
void onError(String error);
}
private static final int LOGO_MARGIN_DP = 4;
// Holds the pointer to JNI NativeMapView
@Keep
private long nativePtr = 0;
private final Context context;
@Nullable
private SnapshotReadyCallback callback;
@Nullable
private ErrorHandler errorHandler;
/**
* MapSnapshotter options
*/
@@ -96,13 +104,12 @@ public class MapSnapshotter {
private float pixelRatio = 1;
private int width;
private int height;
private String styleUri = Style.MAPBOX_STREETS;
private String styleJson;
private LatLngBounds region;
private CameraPosition cameraPosition;
private boolean showLogo = true;
private String localIdeographFontFamily = MapboxConstants.DEFAULT_FONT;
private String apiBaseUrl;
private Style.Builder builder;
/**
* @param width the width of the image
@@ -117,22 +124,41 @@ public class MapSnapshotter {
}
/**
* @param uri The style URI to use
* Set a style builder to snapshotter, the contents in builder like layers/sources/images will be applied
* to snapshotter.
*
* @param builder The builder will applied to snapshotter
* @return the mutated {@link Options}
*/
@NonNull
public Options withStyleBuilder(Style.Builder builder) {
this.builder = builder;
return this;
}
public Style.Builder getBuilder() {
return builder;
}
/**
* @param uri The style URI to use
* @return the mutated {@link Options}
* @deprecated use {@link #withStyleBuilder(Style.Builder)} instead
*/
@NonNull
public Options withStyle(String uri) {
this.styleUri = uri;
withStyleBuilder(new Style.Builder().fromUri(uri));
return this;
}
/**
* @param styleJson The style json to use
* @return the mutated {@link Options}
* @deprecated use {@link #withStyleBuilder(Style.Builder)} instead
*/
@NonNull
public Options withStyleJson(String styleJson) {
this.styleJson = styleJson;
withStyleBuilder(new Style.Builder().fromJson(styleJson));
return this;
}
@@ -187,6 +213,7 @@ public class MapSnapshotter {
* Default system fonts are defined in &#x27;/system/etc/fonts.xml&#x27;
* Default font for local ideograph font family is {@link MapboxConstants#DEFAULT_FONT}.
* </p>
*
* @param fontFamily font family for local ideograph generation.
* @return the mutated {@link Options}
*/
@@ -204,6 +231,7 @@ public class MapSnapshotter {
* &#x27;/system/etc/fonts.xml&#x27;. Default font for local ideograph font family is
* {@link MapboxConstants#DEFAULT_FONT}.
* </p>
*
* @param fontFamilies font families for local ideograph generation.
* @return the mutated {@link Options}
*/
@@ -274,14 +302,22 @@ public class MapSnapshotter {
*/
@Deprecated
public String getStyleUrl() {
return styleUri;
return builder == null ? Style.MAPBOX_STREETS : builder.getUri();
}
/**
* @return the style uri
*/
public String getStyleUri() {
return styleUri;
return builder == null ? Style.MAPBOX_STREETS : builder.getUri();
}
/**
* @return the style json
*/
@Nullable
public String getStyleJson() {
return builder == null ? null : builder.getJson();
}
/**
@@ -329,6 +365,7 @@ public class MapSnapshotter {
public MapSnapshotter(@NonNull Context context, @NonNull Options options) {
checkThread();
this.context = context.getApplicationContext();
this.options = options;
TelemetryDefinition telemetry = Mapbox.getTelemetry();
if (telemetry != null) {
telemetry.onAppUserTurnstileEvent();
@@ -340,10 +377,11 @@ public class MapSnapshotter {
}
nativeInitialize(this, fileSource, options.pixelRatio, options.width,
options.height, options.styleUri, options.styleJson, options.region, options.cameraPosition,
options.height, options.getStyleUri(), options.getStyleJson(), options.region, options.cameraPosition,
options.showLogo, options.localIdeographFontFamily);
}
/**
* Starts loading and rendering the snapshot. The callback will be fired
* on the calling thread.
@@ -412,6 +450,57 @@ public class MapSnapshotter {
@Keep
public native void setStyleJson(String styleJson);
/**
* Adds the layer to the map. The layer must be newly created and not added to the snapshotter before
*
* @param layer the layer to add
* @param below the layer id to add this layer before
*/
private void addLayerBelow(Layer layer, String below) {
nativeAddLayerBelow(layer.getNativePtr(), below);
}
/**
* Adds the layer to the map. The layer must be newly created and not added to the snapshotter before
*
* @param layer the layer to add
* @param above the layer id to add this layer above
*/
private void addLayerAbove(@NonNull Layer layer, @NonNull String above) {
nativeAddLayerAbove(layer.getNativePtr(), above);
}
/**
* Adds the layer to the snapshotter at the specified index. The layer must be newly
* created and not added to the snapshotter before
*
* @param layer the layer to add
* @param index the index to insert the layer at
*/
private void addLayerAt(Layer layer, int index) {
nativeAddLayerAt(layer.getNativePtr(), index);
}
/**
* Adds the source to the map. The source must be newly created and not added to the map before
*
* @param source the source to add
*/
private void addSource(Source source) {
nativeAddSource(source, source.getNativePtr());
}
/**
* Adds an image to be used in the snapshotter's style
*
* @param name the name of the image
* @param bitmap the pre-multiplied Bitmap
* @param sdf the flag indicating image is an SDF or template image
*/
private void addImage(@NonNull final String name, @NonNull Bitmap bitmap, boolean sdf) {
nativeAddImages(new Image[] {toImage(new Style.Builder.ImageWrapper(name, bitmap, sdf))});
}
/**
* Must be called in on the thread
* the object was created on.
@@ -609,6 +698,54 @@ public class MapSnapshotter {
}
}
/**
* Called by JNI peer when snapshot style is ready.
*/
@Keep
protected void onDidFailLoadingStyle(String reason) {
onSnapshotFailed(reason);
}
/**
* Called by JNI peer when snapshot style is loaded.
*/
@Keep
protected void onDidFinishLoadingStyle() {
if (!fullyLoaded) {
fullyLoaded = true;
Style.Builder builder = options.getBuilder();
if (builder != null) {
for (Source source : builder.getSources()) {
nativeAddSource(source, source.getNativePtr());
}
for (Style.Builder.LayerWrapper layerWrapper : builder.getLayers()) {
if (layerWrapper instanceof Style.Builder.LayerAtWrapper) {
addLayerAt(layerWrapper.getLayer(), ((Style.Builder.LayerAtWrapper) layerWrapper).getIndex());
} else if (layerWrapper instanceof Style.Builder.LayerAboveWrapper) {
addLayerAbove(layerWrapper.getLayer(), ((Style.Builder.LayerAboveWrapper) layerWrapper).getAboveLayer());
} else if (layerWrapper instanceof Style.Builder.LayerBelowWrapper) {
addLayerBelow(layerWrapper.getLayer(), ((Style.Builder.LayerBelowWrapper) layerWrapper).getBelowLayer());
} else {
addLayerBelow(layerWrapper.getLayer(), MapboxConstants.LAYER_ID_ANNOTATIONS);
}
}
for (Style.Builder.ImageWrapper image : builder.getImages()) {
addImage(image.getId(), image.getBitmap(), image.isSdf());
}
}
}
}
/**
* Called by JNI peer when snapshot style image is missing.
*/
@Keep
protected void onStyleImageMissing(String imageName) {
onSnapshotFailed("style image is missing: " + imageName);
}
private void checkThread() {
ThreadUtils.checkThread(TAG);
}
@@ -631,6 +768,21 @@ public class MapSnapshotter {
@Keep
protected native void nativeCancel();
@Keep
private native void nativeAddLayerBelow(long layerPtr, String below);
@Keep
private native void nativeAddLayerAbove(long layerPtr, String above);
@Keep
private native void nativeAddLayerAt(long layerPtr, int index);
@Keep
private native void nativeAddSource(Source source, long sourcePtr);
@Keep
private native void nativeAddImages(Image[] images);
@Override
@Keep
protected native void finalize() throws Throwable;
@@ -640,7 +792,7 @@ public class MapSnapshotter {
private Bitmap small;
private float scale;
public Logo(Bitmap large, Bitmap small, float scale) {
Logo(Bitmap large, Bitmap small, float scale) {
this.large = large;
this.small = small;
this.scale = scale;

View File

@@ -15,21 +15,24 @@ public class CustomLayer extends Layer {
initialize(id, host);
}
/**
* Triggers map re-paint.
*
* @deprecated Use {@link MapboxMap#triggerRepaint()} instead.
*/
@Deprecated
@Keep
public void update() {
}
@Keep
CustomLayer(long nativePtr) {
super(nativePtr);
}
public void update() {
nativeUpdate();
}
@Keep
protected native void initialize(String id, long host);
@Keep
protected native void nativeUpdate();
@Override
@Keep
protected native void finalize() throws Throwable;