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;

View File

@@ -5,13 +5,14 @@ import androidx.test.rule.ActivityTestRule
import com.mapbox.mapboxsdk.camera.CameraPosition
import com.mapbox.mapboxsdk.geometry.LatLng
import com.mapbox.mapboxsdk.maps.Style
import com.mapbox.mapboxsdk.style.layers.BackgroundLayer
import com.mapbox.mapboxsdk.style.layers.PropertyFactory
import com.mapbox.mapboxsdk.testapp.activity.FeatureOverviewActivity
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import junit.framework.Assert.assertNotNull
import org.junit.Assert
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -20,7 +21,6 @@ import org.junit.runner.RunWith
* Integration test that validates if a snapshotter creation
*/
@RunWith(AndroidJUnit4::class)
@Ignore("Ignored until https://github.com/mapbox/mapbox-gl-native/issues/11669 is resolved.")
class MapSnapshotterTest {
@Rule
@@ -31,18 +31,22 @@ class MapSnapshotterTest {
@Test
fun mapSnapshotter() {
var mapSnapshotter: MapSnapshotter?
rule.activity.runOnUiThread {
val bg = BackgroundLayer("rand_tint")
bg.setProperties(PropertyFactory.backgroundColor("rgba(255,128,0,0.7)"))
val options = MapSnapshotter.Options(512, 512)
.withPixelRatio(1.0f)
.withStyle(Style.SATELLITE_STREETS)
.withStyleBuilder(Style.Builder().fromUri(Style.SATELLITE_STREETS)
.withLayerAbove(bg, "country-label"))
.withCameraPosition(
CameraPosition.Builder()
.zoom(12.0)
.target(LatLng(51.145495, 5.742234))
.build()
)
val mapSnapshotter = MapSnapshotter(rule.activity, options)
mapSnapshotter.start({
mapSnapshotter = MapSnapshotter(rule.activity, options)
mapSnapshotter!!.start({
assertNotNull(it)
assertNotNull(it.bitmap)
countDownLatch.countDown()

View File

@@ -363,9 +363,21 @@
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.snapshot.MapSnapshotterMarkerActivity"
android:description="@string/description_map_snapshotter_marker"
android:label="@string/activity_map_snapshotter_marker">
android:name=".activity.snapshot.MapSnapshotterBitMapOverlayActivity"
android:description="@string/description_map_snapshotter_bitmap_overlay"
android:label="@string/activity_map_snapshotter_bitmap_overlay">
<meta-data
android:name="@string/category"
android:value="@string/category_imagegenerator" />
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity" />
</activity>
<activity
android:name=".activity.snapshot.MapSnapshotterHeatMapActivity"
android:description="@string/description_map_snapshotter_heatmap_layer"
android:exported="true"
android:label="@string/activity_map_snapshotter_heatmap_layer">
<meta-data
android:name="@string/category"
android:value="@string/category_imagegenerator" />

View File

@@ -68,8 +68,8 @@ public class CustomLayerActivity extends AppCompatActivity {
}
private void updateLayer() {
if (customLayer != null) {
customLayer.update();
if (mapboxMap != null) {
mapboxMap.triggerRepaint();
}
}

View File

@@ -1,5 +1,6 @@
package com.mapbox.mapboxsdk.testapp.activity.render;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter;
public class RenderTestDefinition {
@@ -75,7 +76,7 @@ public class RenderTestDefinition {
public MapSnapshotter.Options toOptions() {
return new MapSnapshotter
.Options(getWidth(), getHeight())
.withStyleJson(styleJson)
.withStyleBuilder(new Style.Builder().fromJson(styleJson))
.withPixelRatio(getPixelRatio())
.withLogo(false);
}

View File

@@ -1,31 +1,64 @@
package com.mapbox.mapboxsdk.testapp.activity.snapshot;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.ViewTreeObserver;
import android.widget.GridLayout;
import android.widget.ImageView;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter;
import com.mapbox.mapboxsdk.style.layers.Property;
import com.mapbox.mapboxsdk.style.layers.RasterLayer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.style.sources.RasterSource;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.utils.BitmapUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import timber.log.Timber;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase;
import static com.mapbox.mapboxsdk.style.expressions.Expression.toBool;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAnchor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
/**
* Test activity showing how to use a the {@link com.mapbox.mapboxsdk.snapshotter.MapSnapshotter}
*/
public class MapSnapshotterActivity extends AppCompatActivity {
private static final String ID_FEATURE_PROPERTY = "id";
private static final String SELECTED_FEATURE_PROPERTY = "selected";
private static final String TITLE_FEATURE_PROPERTY = "title";
// layer & source constants
private static final String MARKER_SOURCE = "marker-source";
private static final String MARKER_LAYER = "marker-layer";
private GridLayout grid;
public GridLayout grid;
private List<MapSnapshotter> snapshotters = new ArrayList<>();
@Override
@@ -58,6 +91,9 @@ public class MapSnapshotterActivity extends AppCompatActivity {
}
private void startSnapShot(final int row, final int column) {
// Optionally the style
Style.Builder builder = new Style.Builder()
.fromUri((column + row) % 2 == 0 ? Style.MAPBOX_STREETS : Style.DARK);
// Define the dimensions
MapSnapshotter.Options options = new MapSnapshotter.Options(
@@ -66,9 +102,6 @@ public class MapSnapshotterActivity extends AppCompatActivity {
)
// Optionally the pixel ratio
.withPixelRatio(1)
// Optionally the style
.withStyle((column + row) % 2 == 0 ? Style.MAPBOX_STREETS : Style.DARK)
.withLocalIdeographFontFamily(MapboxConstants.DEFAULT_FONT);
// Optionally the visible region
@@ -88,14 +121,52 @@ public class MapSnapshotterActivity extends AppCompatActivity {
: new LatLng(randomInRange(-80, 80), randomInRange(-160, 160)))
.bearing(randomInRange(0, 360))
.tilt(randomInRange(0, 60))
.zoom(randomInRange(0, 20))
.zoom(randomInRange(0, 10))
.padding(1, 1, 1, 1)
.build()
);
}
if (row == 0 && column == 0) {
// Add a source
Source source = new RasterSource("my-raster-source", "mapbox://mapbox.satellite", 512);
builder.withLayerAbove(new RasterLayer("satellite-layer", "my-raster-source"), "country-label");
builder.withSource(source);
} else if (row == 0 && column == 2) {
Bitmap carBitmap = BitmapUtils.getBitmapFromDrawable(
getResources().getDrawable(R.drawable.ic_directions_car_black));
// marker source
FeatureCollection markerCollection = FeatureCollection.fromFeatures(new Feature[] {
Feature.fromGeometry(Point.fromLngLat(4.91638, 52.34673), featureProperties("2", "Car"))
});
Source markerSource = new GeoJsonSource(MARKER_SOURCE, markerCollection);
// marker layer
SymbolLayer markerSymbolLayer = new SymbolLayer(MARKER_LAYER, MARKER_SOURCE)
.withProperties(
iconImage(get(TITLE_FEATURE_PROPERTY)),
iconIgnorePlacement(true),
iconAllowOverlap(true),
iconSize(switchCase(toBool(get(SELECTED_FEATURE_PROPERTY)), literal(1.5f), literal(1.0f))),
iconAnchor(Property.ICON_ANCHOR_BOTTOM),
iconColor(Color.BLUE)
);
builder.withImage("Car", Objects.requireNonNull(carBitmap), false)
.withSources(markerSource)
.withLayers(markerSymbolLayer);
options.withCameraPosition(new CameraPosition.Builder()
.target(new LatLng(5.537109374999999,
52.07950600379697))
.zoom(1)
.padding(1, 1, 1, 1)
.build()
);
}
options.withStyleBuilder(builder);
MapSnapshotter snapshotter = new MapSnapshotter(MapSnapshotterActivity.this, options);
snapshotter.start(snapshot -> {
Timber.i("Got the snapshot");
ImageView imageView = new ImageView(MapSnapshotterActivity.this);
@@ -125,4 +196,11 @@ public class MapSnapshotterActivity extends AppCompatActivity {
return (random.nextFloat() * (max - min)) + min;
}
private JsonObject featureProperties(@NonNull String id, @NonNull String title) {
JsonObject object = new JsonObject();
object.add(ID_FEATURE_PROPERTY, new JsonPrimitive(id));
object.add(TITLE_FEATURE_PROPERTY, new JsonPrimitive(title));
object.add(SELECTED_FEATURE_PROPERTY, new JsonPrimitive(false));
return object;
}
}

View File

@@ -6,26 +6,29 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshot;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter;
import com.mapbox.mapboxsdk.testapp.R;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import timber.log.Timber;
/**
* Test activity showing how to use a the {@link MapSnapshotter} and overlay
* {@link android.graphics.Bitmap}s on top.
*/
public class MapSnapshotterMarkerActivity extends AppCompatActivity implements MapSnapshotter.SnapshotReadyCallback {
public class MapSnapshotterBitMapOverlayActivity extends AppCompatActivity
implements MapSnapshotter.SnapshotReadyCallback {
private MapSnapshotter mapSnapshotter;
private MapSnapshot mapSnapshot;
@@ -49,10 +52,10 @@ public class MapSnapshotterMarkerActivity extends AppCompatActivity implements M
getApplicationContext(),
new MapSnapshotter
.Options(Math.min(container.getMeasuredWidth(), 1024), Math.min(container.getMeasuredHeight(), 1024))
.withStyle(Style.OUTDOORS)
.withStyleBuilder(new Style.Builder().fromUri(Style.OUTDOORS))
.withCameraPosition(new CameraPosition.Builder().target(new LatLng(52.090737, 5.121420)).zoom(15).build())
);
mapSnapshotter.start(MapSnapshotterMarkerActivity.this);
mapSnapshotter.start(MapSnapshotterBitMapOverlayActivity.this);
}
});
}

View File

@@ -0,0 +1,174 @@
package com.mapbox.mapboxsdk.testapp.activity.snapshot;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshot;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter;
import com.mapbox.mapboxsdk.style.layers.HeatmapLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.style.sources.Source;
import com.mapbox.mapboxsdk.testapp.R;
import org.jetbrains.annotations.NotNull;
import java.net.URI;
import java.net.URISyntaxException;
import androidx.appcompat.app.AppCompatActivity;
import timber.log.Timber;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.heatmapDensity;
import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate;
import static com.mapbox.mapboxsdk.style.expressions.Expression.linear;
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb;
import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba;
import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapIntensity;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapOpacity;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapRadius;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapWeight;
/**
* Test activity showing how to use a the {@link MapSnapshotter} and heatmap layer on it.
*/
public class MapSnapshotterHeatMapActivity extends AppCompatActivity implements MapSnapshotter.SnapshotReadyCallback {
private static final String EARTHQUAKE_SOURCE_URL = "https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson";
private static final String EARTHQUAKE_SOURCE_ID = "earthquakes";
private static final String HEATMAP_LAYER_ID = "earthquakes-heat";
private static final String HEATMAP_LAYER_SOURCE = "earthquakes";
private MapSnapshotter mapSnapshotter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map_snapshotter_marker);
final View container = findViewById(R.id.container);
container.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//noinspection deprecation
container.getViewTreeObserver().removeGlobalOnLayoutListener(this);
Timber.i("Starting snapshot");
Style.Builder builder = new Style.Builder().fromUri(Style.OUTDOORS)
.withSource(getEarthquakeSource())
.withLayerAbove(getHeatmapLayer(), "waterway-label");
mapSnapshotter = new MapSnapshotter(
getApplicationContext(),
new MapSnapshotter
.Options(container.getMeasuredWidth(), container.getMeasuredHeight())
.withStyleBuilder(builder)
.withCameraPosition(new CameraPosition.Builder()
.target(new LatLng(15, -94))
.zoom(5)
.padding(1, 1, 1, 1)
.build()
)
);
mapSnapshotter.start(MapSnapshotterHeatMapActivity.this);
}
});
}
@NotNull
private HeatmapLayer getHeatmapLayer() {
HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID);
layer.setMaxZoom(9);
layer.setSourceLayer(HEATMAP_LAYER_SOURCE);
layer.setProperties(
// Color ramp for heatmap. Domain is 0 (low) to 1 (high).
// Begin color ramp at 0-stop with a 0-transparency color
// to create a blur-like effect.
heatmapColor(
interpolate(
linear(), heatmapDensity(),
literal(0), rgba(33, 102, 172, 0),
literal(0.2), rgb(103, 169, 207),
literal(0.4), rgb(209, 229, 240),
literal(0.6), rgb(253, 219, 199),
literal(0.8), rgb(239, 138, 98),
literal(1), rgb(178, 24, 43)
)
),
// Increase the heatmap weight based on frequency and property magnitude
heatmapWeight(
interpolate(
linear(), get("mag"),
stop(0, 0),
stop(6, 1)
)
),
// Increase the heatmap color weight weight by zoom level
// heatmap-intensity is a multiplier on top of heatmap-weight
heatmapIntensity(
interpolate(
linear(), zoom(),
stop(0, 1),
stop(9, 3)
)
),
// Adjust the heatmap radius by zoom level
heatmapRadius(
interpolate(
linear(), zoom(),
stop(0, 2),
stop(9, 20)
)
),
// Transition from heatmap to circle layer by zoom level
heatmapOpacity(
interpolate(
linear(), zoom(),
stop(7, 1),
stop(9, 0)
)
)
);
return layer;
}
@Override
protected void onStop() {
super.onStop();
mapSnapshotter.cancel();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onSnapshotReady(MapSnapshot snapshot) {
Timber.i("Snapshot ready");
ImageView imageView = findViewById(R.id.snapshot_image);
imageView.setImageBitmap(snapshot.getBitmap());
}
private Source getEarthquakeSource() {
Source source = null;
try {
source = new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URI(EARTHQUAKE_SOURCE_URL));
} catch (URISyntaxException uriSyntaxException) {
Timber.e(uriSyntaxException, "That's not an url... ");
}
return source;
}
}

View File

@@ -7,6 +7,7 @@ import android.view.ViewTreeObserver;
import android.widget.ImageView;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshot;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter;
import com.mapbox.mapboxsdk.testapp.R;
@@ -48,7 +49,7 @@ public class MapSnapshotterLocalStyleActivity extends AppCompatActivity
getApplicationContext(),
new MapSnapshotter
.Options(Math.min(container.getMeasuredWidth(), 1024), Math.min(container.getMeasuredHeight(), 1024))
.withStyleJson(styleJson)
.withStyleBuilder(new Style.Builder().fromJson(styleJson))
.withCameraPosition(new CameraPosition.Builder().target(new LatLng(52.090737, 5.121420)).zoom(18).build())
);
mapSnapshotter.start(MapSnapshotterLocalStyleActivity.this, error -> Timber.e(error));

View File

@@ -1,20 +1,21 @@
package com.mapbox.mapboxsdk.testapp.activity.snapshot;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshot;
import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter;
import com.mapbox.mapboxsdk.testapp.R;
import java.util.Random;
import androidx.appcompat.app.AppCompatActivity;
/**
* Test activity showing how to use a the {@link MapSnapshotter}
*/
@@ -49,7 +50,7 @@ public class MapSnapshotterReuseActivity extends AppCompatActivity implements Ma
mapSnapshotter = new MapSnapshotter(
getApplicationContext(),
new MapSnapshotter.Options(512, 512)
new MapSnapshotter.Options(512, 512).withStyleBuilder(new Style.Builder().fromUri(getRandomStyle()))
);
mapSnapshotter.start(MapSnapshotterReuseActivity.this);

View File

@@ -61,7 +61,8 @@
<string name="description_bottom_sheet">Show 2 MapView on screen with a bottom sheet</string>
<string name="description_map_snapshotter">Show a static bitmap taken with the MapSnapshotter</string>
<string name="description_map_snapshotter_reuse">Show how to reuse a MapSnapshotter instance</string>
<string name="description_map_snapshotter_marker">Show how to add a marker to a Snapshot</string>
<string name="description_map_snapshotter_bitmap_overlay">Show how to add a bitmap overlay to a Snapshot</string>
<string name="description_map_snapshotter_heatmap_layer">Show how to add a heatmap layer to a Snapshot</string>
<string name="description_map_snapshotter_local_style">Show how to load a local style with a Snapshot</string>
<string name="description_camera_animator">Use Android SDK Animators to animate camera position changes</string>
<string name="description_symbol_generator">Use Android SDK Views as symbols</string>

View File

@@ -59,7 +59,8 @@
<string name="activity_bottom_sheet">Bottom sheet</string>
<string name="activity_map_snapshotter">Map Snapshotter</string>
<string name="activity_map_snapshotter_reuse">Map Snapshotter Reuse</string>
<string name="activity_map_snapshotter_marker">Map Snapshot with marker</string>
<string name="activity_map_snapshotter_bitmap_overlay">Map Snapshot with bitmap overlay</string>
<string name="activity_map_snapshotter_heatmap_layer">Map Snapshot with heatmap layer</string>
<string name="activity_map_snapshotter_local_style">Map Snapshot with local style</string>
<string name="activity_camera_animator">Animator animation</string>
<string name="activity_symbol_generator">SymbolGenerator</string>