diff options
author | Tobrun <tobrun.van.nuland@gmail.com> | 2017-10-24 17:43:53 -0700 |
---|---|---|
committer | Tobrun <tobrun.van.nuland@gmail.com> | 2017-10-29 08:53:23 -0700 |
commit | 00e645f67df037393b86e96848900cee8e23c32f (patch) | |
tree | 204efff8870ac018d253460ee066945dc022a502 | |
parent | 72a6eb8b5c317e4cc3d30a998af3ba09a2fc5138 (diff) | |
download | qtlocation-mapboxgl-upstream/tvn-add-images-api.tar.gz |
[android] - add bulk addImages api on MapboxMapupstream/tvn-add-images-api
10 files changed, 278 insertions, 49 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java new file mode 100644 index 0000000000..b2f6cef3b0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Image.java @@ -0,0 +1,17 @@ +package com.mapbox.mapboxsdk.maps; + +class Image { + private final byte[] buffer; + private final float pixelRatio; + private final String name; + private final int width; + private final int height; + + public Image(byte[] buffer, float pixelRatio, String name, int width, int height) { + this.buffer = buffer; + this.pixelRatio = pixelRatio; + this.name = name; + this.width = width; + this.height = height; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index 6a88470ee7..f6d3b6d135 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -47,6 +47,7 @@ import com.mapbox.services.commons.geojson.Feature; import com.mapbox.services.commons.geojson.Geometry; import java.lang.reflect.ParameterizedType; +import java.util.HashMap; import java.util.List; import timber.log.Timber; @@ -467,6 +468,13 @@ public final class MapboxMap { } /** + * Adds an images to be used in the map's style + */ + public void addImages(@NonNull HashMap<String, Bitmap> images) { + nativeMapView.addImages(images); + } + + /** * Removes an image from the map's style * * @param name the name of the image to remove @@ -1670,9 +1678,9 @@ public final class MapboxMap { /** * Get a camera position that fits a provided shape with a given bearing and padding. * - * @param geometry the geometry to constrain the map with - * @param bearing the bearing at which to compute the geometry's bounds - * @param padding the padding to apply to the bounds + * @param geometry the geometry to constrain the map with + * @param bearing the bearing at which to compute the geometry's bounds + * @param padding the padding to apply to the bounds * @return the camera position that fits the bounds and padding */ public CameraPosition getCameraForGeometry(Geometry geometry, double bearing, int[] padding) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index bd8a54783e..621aaa25c3 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -4,6 +4,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.PointF; import android.graphics.RectF; +import android.os.AsyncTask; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -34,7 +35,9 @@ import com.mapbox.services.commons.geojson.Geometry; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import timber.log.Timber; @@ -746,6 +749,7 @@ final class NativeMapView { if (isDestroyedOn("addImage")) { return; } + // Check/correct config if (image.getConfig() != Bitmap.Config.ARGB_8888) { image = image.copy(Bitmap.Config.ARGB_8888, false); @@ -762,6 +766,14 @@ final class NativeMapView { nativeAddImage(name, image.getWidth(), image.getHeight(), pixelRatio, buffer.array()); } + public void addImages(@NonNull HashMap<String, Bitmap> bitmapHashMap) { + if (isDestroyedOn("addImages")) { + return; + } + //noinspection unchecked + new BitmapImageConversionTask(this).execute(bitmapHashMap); + } + public void removeImage(String name) { if (isDestroyedOn("removeImage")) { return; @@ -1006,6 +1018,8 @@ final class NativeMapView { private native void nativeAddImage(String name, int width, int height, float pixelRatio, byte[] array); + private native void nativeAddImages(Image[] images); + private native void nativeRemoveImage(String name); private native Bitmap nativeGetImage(String name); @@ -1093,4 +1107,55 @@ final class NativeMapView { }); } + + + // + // Image conversion + // + + private static class BitmapImageConversionTask extends AsyncTask<HashMap<String, Bitmap>, Void, List<Image>> { + + private NativeMapView nativeMapView; + + BitmapImageConversionTask(NativeMapView nativeMapView) { + this.nativeMapView = nativeMapView; + } + + @Override + protected List<Image> doInBackground(HashMap<String, Bitmap>... params) { + HashMap<String, Bitmap> bitmapHashMap = params[0]; + + List<Image> images = new ArrayList<>(); + ByteBuffer buffer; + String name; + Bitmap bitmap; + + for (Map.Entry<String, Bitmap> stringBitmapEntry : bitmapHashMap.entrySet()) { + name = stringBitmapEntry.getKey(); + bitmap = stringBitmapEntry.getValue(); + + if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); + } + + buffer = ByteBuffer.allocate(bitmap.getByteCount()); + bitmap.copyPixelsToBuffer(buffer); + + float density = bitmap.getDensity() == Bitmap.DENSITY_NONE ? Bitmap.DENSITY_NONE : bitmap.getDensity(); + float pixelRatio = density / DisplayMetrics.DENSITY_DEFAULT; + + images.add(new Image(buffer.array(), pixelRatio, name, bitmap.getWidth(), bitmap.getHeight())); + } + + return images; + } + + @Override + protected void onPostExecute(List<Image> images) { + super.onPostExecute(images); + if (nativeMapView != null && !nativeMapView.isDestroyedOn("nativeAddImages")) { + nativeMapView.nativeAddImages(images.toArray(new Image[images.size()])); + } + } + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java index 0eaccfef0c..6e9e45cfaa 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java @@ -1,13 +1,14 @@ package com.mapbox.mapboxsdk.testapp.activity.style; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.support.v7.app.AppCompatActivity; import android.graphics.Color; import android.graphics.PointF; +import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; - +import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -32,7 +33,7 @@ import com.mapbox.services.commons.geojson.custom.PositionDeserializer; import com.mapbox.services.commons.models.Position; import java.io.IOException; - +import java.util.HashMap; import java.util.List; import timber.log.Timber; @@ -64,49 +65,10 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR } @Override - public void onMapReady(MapboxMap map) { + public void onMapReady(final MapboxMap map) { mapboxMap = map; - try { - // read local geojson from raw folder - String tinyCountriesJson = ResourceUtils.readRawResource(this, R.raw.tiny_countries); - - // convert geojson to a model - FeatureCollection featureCollection = new GsonBuilder() - .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) - .registerTypeAdapter(Position.class, new PositionDeserializer()) - .create().fromJson(tinyCountriesJson, FeatureCollection.class); - - // add a geojson to the map - Source source = new GeoJsonSource(SOURCE_ID, featureCollection); - mapboxMap.addSource(source); - - // for each feature add a symbolLayer - for (Feature feature : featureCollection.getFeatures()) { - String countryName = feature.getStringProperty(FEATURE_ID); - - // create View - TextView textView = new TextView(this); - textView.setBackgroundColor(getResources().getColor(R.color.blueAccent)); - textView.setPadding(10, 5, 10, 5); - textView.setTextColor(Color.WHITE); - textView.setText(countryName); - - // create bitmap from view - mapboxMap.addImage(countryName, SymbolGenerator.generate(textView)); - } - - // create layer use - mapboxMap.addLayer(new SymbolLayer(LAYER_ID, SOURCE_ID) - .withProperties( - iconImage("{" + FEATURE_ID + "}"), // { } is a token notation - iconAllowOverlap(false) - ) - ); - - addSymbolClickListener(); - } catch (IOException exception) { - Timber.e(exception); - } + addSymbolClickListener(); + new LoadDataTask(map, SymbolGeneratorActivity.this).execute(); } private void addSymbolClickListener() { @@ -213,4 +175,91 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR return bitmap; } } -} + + private static class LoadDataTask extends AsyncTask<Void, Void, FeatureCollection> { + + private final MapboxMap mapboxMap; + private final Context context; + + LoadDataTask(MapboxMap mapboxMap, Context context) { + this.mapboxMap = mapboxMap; + this.context = context; + } + + @Override + protected FeatureCollection doInBackground(Void... params) { + try { + // read local geojson from raw folder + String tinyCountriesJson = ResourceUtils.readRawResource(context, R.raw.tiny_countries); + + // convert geojson to a model + FeatureCollection featureCollection = new GsonBuilder() + .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) + .registerTypeAdapter(Position.class, new PositionDeserializer()) + .create().fromJson(tinyCountriesJson, FeatureCollection.class); + + return featureCollection; + } catch (IOException exception) { + return null; + } + } + + + @Override + protected void onPostExecute(FeatureCollection featureCollection) { + super.onPostExecute(featureCollection); + if (featureCollection == null) { + return; + } + + // add a geojson to the map + Source source = new GeoJsonSource(SOURCE_ID, featureCollection); + mapboxMap.addSource(source); + + // create layer use + mapboxMap.addLayer(new SymbolLayer(LAYER_ID, SOURCE_ID) + .withProperties( + iconImage("{" + FEATURE_ID + "}"), // { } is a token notation + iconAllowOverlap(false) + ) + ); + + new GenerateSymbolTask(mapboxMap, context).execute(featureCollection); + } + } + + private static class GenerateSymbolTask extends AsyncTask<FeatureCollection, Void, HashMap<String, Bitmap>> { + + private MapboxMap mapboxMap; + private Context context; + + GenerateSymbolTask(MapboxMap mapboxMap, Context context) { + this.mapboxMap = mapboxMap; + this.context = context; + } + + @SuppressWarnings("WrongThread") + @Override + protected HashMap<String, Bitmap> doInBackground(FeatureCollection... params) { + FeatureCollection featureCollection = params[0]; + + HashMap<String, Bitmap> imagesMap = new HashMap<>(); + for (Feature feature : featureCollection.getFeatures()) { + String countryName = feature.getStringProperty(FEATURE_ID); + TextView textView = new TextView(context); + textView.setBackgroundColor(context.getResources().getColor(R.color.blueAccent)); + textView.setPadding(10, 5, 10, 5); + textView.setTextColor(Color.WHITE); + textView.setText(countryName); + imagesMap.put(countryName, SymbolGenerator.generate(textView)); + } + return imagesMap; + } + + @Override + protected void onPostExecute(HashMap<String, Bitmap> bitmapHashMap) { + super.onPostExecute(bitmapHashMap); + mapboxMap.addImages(bitmapHashMap); + } + } +}
\ No newline at end of file diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 8dd537d36e..e6813d5a22 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -143,6 +143,8 @@ add_library(mbgl-android STATIC platform/android/src/style/conversion/types_string_values.hpp platform/android/src/map/camera_position.cpp platform/android/src/map/camera_position.hpp + platform/android/src/map/image.cpp + platform/android/src/map/image.hpp # Style conversion Java -> C++ platform/android/src/style/android_conversion.hpp diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 6acb6a3664..f6ddb8b6ee 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -171,6 +171,7 @@ void registerNatives(JavaVM *vm) { // Map CameraPosition::registerNative(env); + Image::registerNative(env); // Connectivity ConnectivityListener::registerNative(env); diff --git a/platform/android/src/map/image.cpp b/platform/android/src/map/image.cpp new file mode 100644 index 0000000000..5f5c90eddd --- /dev/null +++ b/platform/android/src/map/image.cpp @@ -0,0 +1,44 @@ +#include <mbgl/style/image.hpp> +#include <mbgl/util/exception.hpp> +#include "image.hpp" + +namespace mbgl { +namespace android { + +mbgl::style::Image Image::getImage(jni::JNIEnv& env, jni::Object<Image> image) { + static auto widthField = Image::javaClass.GetField<jni::jint>(env, "width"); + static auto heightField = Image::javaClass.GetField<jni::jint>(env, "height"); + static auto pixelRatioField = Image::javaClass.GetField<jni::jfloat>(env, "pixelRatio"); + static auto bufferField = Image::javaClass.GetField<jni::Array<jbyte>>(env, "buffer"); + static auto nameField = Image::javaClass.GetField<jni::String>(env, "name"); + + auto height = image.Get(env, heightField); + auto width = image.Get(env, widthField); + auto pixelRatio = image.Get(env, pixelRatioField); + auto pixels = image.Get(env, bufferField); + auto name = jni::Make<std::string>(env, image.Get(env, nameField)); + + jni::NullCheck(env, &pixels); + std::size_t size = pixels.Length(env); + + mbgl::PremultipliedImage premultipliedImage({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) }); + if (premultipliedImage.bytes() != uint32_t(size)) { + throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); + } + + jni::GetArrayRegion(env, *pixels, 0, size, reinterpret_cast<jbyte*>(premultipliedImage.data.get())); + + return mbgl::style::Image {name, std::move(premultipliedImage), pixelRatio}; +} + +void Image::registerNative(jni::JNIEnv &env) { + // Lookup the class + Image::javaClass = *jni::Class<Image>::Find(env).NewGlobalRef(env).release(); +} + +jni::Class<Image> Image::javaClass; + + +} // namespace android +} // namespace mb + diff --git a/platform/android/src/map/image.hpp b/platform/android/src/map/image.hpp new file mode 100644 index 0000000000..1513e13ee7 --- /dev/null +++ b/platform/android/src/map/image.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include <mbgl/util/noncopyable.hpp> + +#include <jni/jni.hpp> +#include <mbgl/style/image.hpp> + +namespace mbgl { +namespace android { + +class Image : private mbgl::util::noncopyable { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/maps/Image"; }; + + static mbgl::style::Image getImage(jni::JNIEnv&, jni::Object<Image>); + + static jni::Class<Image> javaClass; + + static void registerNative(jni::JNIEnv&); + +}; + + +} // namespace android +} // namespace mbgl
\ No newline at end of file diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 24a35a7068..7be1373e1d 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -49,6 +49,7 @@ #include "java/util.hpp" #include "geometry/lat_lng_bounds.hpp" #include "map/camera_position.hpp" +#include "map/image.hpp" #include "style/light.hpp" #include "bitmap_factory.hpp" @@ -904,6 +905,18 @@ void NativeMapView::addImage(JNIEnv& env, jni::String name, jni::jint w, jni::ji float(scale))); } +void NativeMapView::addImages(JNIEnv& env, jni::Array<jni::Object<mbgl::android::Image>> jimages) { + jni::NullCheck(env, &jimages); + std::size_t len = jimages.Length(env); + + for (std::size_t i = 0; i < len; i++) { + jni::Object<mbgl::android::Image> jimage = jimages.Get(env, i); + auto image = mbgl::android::Image::getImage(env, jimage); + map->getStyle().addImage(std::make_unique<mbgl::style::Image>(image)); + jni::DeleteLocalRef(env, jimage); + } +} + void NativeMapView::removeImage(JNIEnv& env, jni::String name) { map->getStyle().removeImage(jni::Make<std::string>(env, name)); } @@ -1017,6 +1030,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::removeSourceById, "nativeRemoveSourceById"), METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), METHOD(&NativeMapView::addImage, "nativeAddImage"), + METHOD(&NativeMapView::addImages, "nativeAddImages"), METHOD(&NativeMapView::removeImage, "nativeRemoveImage"), METHOD(&NativeMapView::getImage, "nativeGetImage"), METHOD(&NativeMapView::setLatLngBounds, "nativeSetLatLngBounds"), diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 4d226d0fa9..0e2c051a31 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -22,6 +22,7 @@ #include "style/sources/sources.hpp" #include "geometry/lat_lng_bounds.hpp" #include "map/camera_position.hpp" +#include "map/image.hpp" #include "style/light.hpp" #include "bitmap.hpp" @@ -235,6 +236,8 @@ public: void addImage(JNIEnv&, jni::String, jni::jint, jni::jint, jni::jfloat, jni::Array<jbyte>); + void addImages(JNIEnv&, jni::Array<jni::Object<mbgl::android::Image>>); + void removeImage(JNIEnv&, jni::String); jni::Object<Bitmap> getImage(JNIEnv&, jni::String); |