Skip to content

Commit

Permalink
Improve usage of bitmap to mapbox image conversions (#2841)
Browse files Browse the repository at this point in the history
  • Loading branch information
jush authored Nov 8, 2024
1 parent 274375d commit 572a41d
Show file tree
Hide file tree
Showing 35 changed files with 308 additions and 263 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ Mapbox welcomes participation and contributions from everyone.
* Change the signature of experimental `MapboxMap.queryRenderedFeatures(RenderedQueryGeometry, TypedFeaturesetDescriptor, Value?, QueryRenderedFeaturesetFeaturesCallback)` to `MapboxMap.queryRenderedFeatures(TypedFeaturesetDescriptor, RenderedQueryGeometry?, Value?, QueryRenderedFeaturesetFeaturesCallback)`. `RenderedQueryGeometry` being NULL is equivalent to passing a bounding box encompassing the entire map viewport.
* [compose] Change the signature of experimental `MapState.queryRenderedFeatures(RenderedQueryGeometry, TypedFeaturesetDescriptor, Expression?): List` to `MapState.queryRenderedFeatures(TypedFeaturesetDescriptor, RenderedQueryGeometry?, Expression?): List`. `RenderedQueryGeometry` being NULL is equivalent to passing a bounding box encompassing the entire map viewport.

## Features ✨ and improvements 🏁
* Annotate `Bitmap.toMapboxImage()` and related as delicate API due to its native memory allocation.

## Bug fixes 🐞
* Disable false-positive lint error "Incorrect number of expressions".

* Fix possible out of memory in native heap during annotation manager annotation updates (`AnnotationManager.update(...)`).


# 11.7.2 November 05, 2024
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.mapbox.maps.testapp.examples

import android.content.Context
import android.graphics.Bitmap
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.appcompat.app.AppCompatActivity
import com.mapbox.maps.MapboxMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.mapbox.maps.Image
import com.mapbox.maps.MapboxDelicateApi
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.layers.generated.rasterLayer
import com.mapbox.maps.extension.style.sources.generated.ImageSource
Expand All @@ -17,22 +17,23 @@ import com.mapbox.maps.extension.style.style
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.databinding.ActivityAnimatedImagesourceBinding
import com.mapbox.maps.testapp.utils.BitmapUtils.bitmapFromDrawableRes
import com.mapbox.maps.toMapboxImage
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

/**
* Load a raster image to a style using ImageSource and display it on a map as
* animated weather data using RasterLayer.
*/
class AnimatedImageSourceActivity : AppCompatActivity() {

private val handler: Handler = Handler(Looper.getMainLooper())
private lateinit var mapboxMap: MapboxMap
private lateinit var runnable: Runnable

@OptIn(MapboxDelicateApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityAnimatedImagesourceBinding.inflate(layoutInflater)
setContentView(binding.root)
mapboxMap = binding.mapView.mapboxMap
val mapboxMap = binding.mapView.mapboxMap
mapboxMap.loadStyle(
style(style = Style.STANDARD) {
+imageSource(ID_IMAGE_SOURCE) {
Expand All @@ -48,49 +49,27 @@ class AnimatedImageSourceActivity : AppCompatActivity() {
+rasterLayer(ID_IMAGE_LAYER, ID_IMAGE_SOURCE) { }
}
)
}

override fun onStart() {
super.onStart()
val drawables: List<Image> = listOf(
bitmapFromDrawableRes(R.drawable.southeast_radar_0).toMapboxImage(),
bitmapFromDrawableRes(R.drawable.southeast_radar_1).toMapboxImage(),
bitmapFromDrawableRes(R.drawable.southeast_radar_2).toMapboxImage(),
bitmapFromDrawableRes(R.drawable.southeast_radar_3).toMapboxImage(),
)
var drawableIndex = 0
mapboxMap.getStyle {
val imageSource: ImageSource = it.getSourceAs(ID_IMAGE_SOURCE)!!
runnable = RefreshImageRunnable(applicationContext, imageSource, handler)
handler.postDelayed(runnable, 100)
}
}

override fun onStop() {
super.onStop()
if (::runnable.isInitialized) {
handler.removeCallbacks(runnable)
}
}

private class RefreshImageRunnable constructor(
appContext: Context,
private val imageSource: ImageSource,
private val handler: Handler
) :
Runnable {
private val drawables: Array<Bitmap?> = arrayOfNulls(4)
private var drawableIndex: Int

override fun run() {
drawables[drawableIndex++]?.let { bitmap ->
imageSource.updateImage(bitmap)
if (drawableIndex > 3) {
drawableIndex = 0
// Create a new coroutine in the lifecycleScope
lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
repeatOnLifecycle(Lifecycle.State.STARTED) {
while (isActive) {
imageSource.updateImage(drawables[drawableIndex++])
drawableIndex %= drawables.size
delay(1000L)
}
}
}
handler.postDelayed(this, 1000)
}

init {
drawables[0] = bitmapFromDrawableRes(appContext, R.drawable.southeast_radar_0)
drawables[1] = bitmapFromDrawableRes(appContext, R.drawable.southeast_radar_1)
drawables[2] = bitmapFromDrawableRes(appContext, R.drawable.southeast_radar_2)
drawables[3] = bitmapFromDrawableRes(appContext, R.drawable.southeast_radar_3)
drawableIndex = 1
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ class CircleLayerClusteringActivity : AppCompatActivity() {

addClusteredGeoJsonSource(it)

bitmapFromDrawableRes(this, R.drawable.ic_cross)?.let { bitmap ->
it.addImage(CROSS_ICON_ID, bitmap, true)
}
it.addImage(CROSS_ICON_ID, bitmapFromDrawableRes(R.drawable.ic_cross), true)

Toast.makeText(
this@CircleLayerClusteringActivity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.*
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.IntDef
import androidx.annotation.NonNull
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mapbox.android.gestures.*
import com.mapbox.android.gestures.AndroidGesturesManager
import com.mapbox.android.gestures.MoveGestureDetector
import com.mapbox.android.gestures.RotateGestureDetector
import com.mapbox.android.gestures.ShoveGestureDetector
import com.mapbox.android.gestures.StandardScaleGestureDetector
import com.mapbox.geojson.Point
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.MapboxMap
Expand All @@ -25,10 +33,15 @@ import com.mapbox.maps.plugin.annotation.annotations
import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager
import com.mapbox.maps.plugin.annotation.generated.PointAnnotationOptions
import com.mapbox.maps.plugin.annotation.generated.createPointAnnotationManager
import com.mapbox.maps.plugin.gestures.*
import com.mapbox.maps.plugin.gestures.GesturesPlugin
import com.mapbox.maps.plugin.gestures.OnMoveListener
import com.mapbox.maps.plugin.gestures.OnRotateListener
import com.mapbox.maps.plugin.gestures.OnScaleListener
import com.mapbox.maps.plugin.gestures.OnShoveListener
import com.mapbox.maps.plugin.gestures.gestures
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.databinding.ActivityGesturesBinding
import java.util.*
import com.mapbox.maps.testapp.utils.BitmapUtils.bitmapFromDrawableRes

/**
* Test activity showcasing APIs around gestures implementation.
Expand Down Expand Up @@ -138,7 +151,7 @@ class GesturesActivity : AppCompatActivity() {
.build()
)
mapboxMap.loadStyle(Style.STANDARD) {
it.addImage(MARKER_IMAGE_ID, ContextCompat.getDrawable(this, R.drawable.ic_red_marker)!!.toBitmap())
it.addImage(MARKER_IMAGE_ID, bitmapFromDrawableRes(R.drawable.ic_red_marker))
}

binding.mapView.waitForLayout {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ class ImageSourceActivity : AppCompatActivity() {
+rasterLayer(ID_IMAGE_LAYER, ID_IMAGE_SOURCE) {}
}
) {
bitmapFromDrawableRes(this, R.drawable.miami_beach)?.let { bitmap ->
val imageSource: ImageSource = it.getSourceAs(ID_IMAGE_SOURCE)!!
imageSource.updateImage(bitmap)
}
val imageSource: ImageSource = it.getSourceAs(ID_IMAGE_SOURCE)!!
imageSource.updateImage(bitmapFromDrawableRes(R.drawable.miami_beach))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,13 @@ class RuntimeStylingActivity : AppCompatActivity() {
style.addLayer(raster)
}

@OptIn(MapboxDelicateApi::class)
private fun addLayerWithoutStyleExtension(style: Style) {
val bitmap = ContextCompat.getDrawable(this, R.drawable.android_symbol)?.toBitmap(64, 64)
val bitmap = ContextCompat.getDrawable(this, R.drawable.android_symbol)!!.toBitmap(64, 64)
val expected = style.addStyleImage(
"myImage",
1f,
bitmap!!.toMapboxImage(),
bitmap.toMapboxImage(),
false,
mutableListOf(),
mutableListOf(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ import androidx.appcompat.app.AppCompatActivity
import com.mapbox.geojson.Feature
import com.mapbox.geojson.FeatureCollection
import com.mapbox.geojson.Point
import com.mapbox.maps.*
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.MapInitOptions
import com.mapbox.maps.MapView
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.Style
import com.mapbox.maps.extension.style.layers.addLayer
import com.mapbox.maps.extension.style.layers.generated.symbolLayer
import com.mapbox.maps.extension.style.sources.addSource
import com.mapbox.maps.extension.style.sources.generated.GeoJsonSource
import com.mapbox.maps.extension.style.sources.generated.geoJsonSource
import com.mapbox.maps.extension.style.sources.getSource
import com.mapbox.maps.logE
import com.mapbox.maps.logW
import com.mapbox.maps.plugin.animation.MapAnimationOptions.Companion.mapAnimationOptions
import com.mapbox.maps.plugin.animation.flyTo
import com.mapbox.maps.testapp.R
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.mapbox.maps.testapp.examples

import android.graphics.*
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,23 @@ import android.view.animation.LinearInterpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.core.animation.addListener
import com.mapbox.geojson.Point
import com.mapbox.maps.*
import com.mapbox.maps.CoordinateBounds
import com.mapbox.maps.MapLoaded
import com.mapbox.maps.MapLoadedCallback
import com.mapbox.maps.MapboxMap
import com.mapbox.maps.dsl.cameraOptions
import com.mapbox.maps.extension.style.layers.properties.generated.IconPitchAlignment
import com.mapbox.maps.plugin.annotation.annotations
import com.mapbox.maps.plugin.annotation.generated.*
import com.mapbox.maps.plugin.annotation.generated.PointAnnotation
import com.mapbox.maps.plugin.annotation.generated.PointAnnotationManager
import com.mapbox.maps.plugin.annotation.generated.PointAnnotationOptions
import com.mapbox.maps.plugin.annotation.generated.createPointAnnotationManager
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.databinding.ActivityAnnotationBinding
import com.mapbox.maps.testapp.utils.BitmapUtils.bitmapFromDrawableRes
import com.mapbox.maps.toCameraOptions
import com.mapbox.turf.TurfMeasurement
import java.util.*
import java.util.Random

/**
* Example showing how to add point annotations and animate them
Expand Down Expand Up @@ -49,45 +56,32 @@ class AnimatePointAnnotationActivity : AppCompatActivity(), MapLoadedCallback {
zoom(12.0)
}
)
loadStyle(Style.STANDARD)
subscribeMapLoaded(this@AnimatePointAnnotationActivity)
}

mapboxMap.subscribeMapLoaded(this@AnimatePointAnnotationActivity)
binding.deleteAll.visibility = View.GONE
binding.changeStyle.visibility = View.GONE
binding.changeSlot.visibility = View.GONE
}

override fun run(eventData: MapLoaded) {
pointAnnotationManager = binding.mapView.annotations.createPointAnnotationManager().apply {
this.iconPitchAlignment = IconPitchAlignment.MAP
bitmapFromDrawableRes(
this@AnimatePointAnnotationActivity,
R.drawable.ic_car_top
)?.let {
val noAnimationOptionList = mutableListOf<PointAnnotationOptions>()
for (i in 0 until noAnimateCarNum) {
noAnimationOptionList.add(
PointAnnotationOptions()
.withPoint(getPointInBounds())
.withIconImage(it)
)
}
create(noAnimationOptionList)
iconPitchAlignment = IconPitchAlignment.MAP
val carTop = bitmapFromDrawableRes(R.drawable.ic_car_top)
val noAnimationOptionList = List(noAnimateCarNum) {
PointAnnotationOptions()
.withPoint(getPointInBounds())
.withIconImage(carTop)
}
bitmapFromDrawableRes(
this@AnimatePointAnnotationActivity,
R.drawable.ic_taxi_top
)?.let {
val animationOptionList = mutableListOf<PointAnnotationOptions>()
for (i in 0 until animateCarNum) {
animationOptionList.add(
PointAnnotationOptions()
.withPoint(getPointInBounds())
.withIconImage(it)
)
}
animateCarList = create(animationOptionList)
create(noAnimationOptionList)

val taxiTop = bitmapFromDrawableRes(R.drawable.ic_taxi_top)
val animationOptionList = List(animateCarNum) {
PointAnnotationOptions()
.withPoint(getPointInBounds())
.withIconImage(taxiTop)
}
animateCarList = create(animationOptionList)
}
animateCars()
}
Expand All @@ -99,20 +93,19 @@ class AnimatePointAnnotationActivity : AppCompatActivity(), MapLoadedCallback {

private fun animateCars() {
cleanAnimation()
for (i in 0 until animateCarNum) {
val carEvaluator = CarEvaluator()
animateCarList.forEach { animatedCar ->
val nextPoint = getPointInBounds()
val animator =
ValueAnimator.ofObject(
CarEvaluator(),
animateCarList[i].point,
carEvaluator,
animatedCar.point,
nextPoint
).setDuration(animateDuration)
animateCarList[i].iconRotate = TurfMeasurement.bearing(animateCarList[i].point, nextPoint)
animatedCar.iconRotate = TurfMeasurement.bearing(animatedCar.point, nextPoint)
animator.interpolator = LinearInterpolator()
animator.addUpdateListener { valueAnimator ->
(valueAnimator.animatedValue as Point).let {
animateCarList[i].point = it
}
animatedCar.point = valueAnimator.animatedValue as Point
}
animator.start()
animators.add(animator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;

import androidx.annotation.OptIn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableKt;
Expand All @@ -24,6 +25,7 @@
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.maps.ExtensionUtils;
import com.mapbox.maps.MapView;
import com.mapbox.maps.MapboxDelicateApi;
import com.mapbox.maps.MapboxMap;
import com.mapbox.maps.Style;
import com.mapbox.maps.extension.style.expressions.generated.Expression;
Expand Down Expand Up @@ -342,6 +344,7 @@ private void addRasterLayer(Style style) {
LayerUtils.addLayer(style, raster);
}

@OptIn(markerClass = MapboxDelicateApi.class)
private void addLayerWithoutStyleExtension(Style style) {
final Drawable drawable = ContextCompat.getDrawable(this, R.drawable.android_symbol);
final Bitmap bitmap = DrawableKt.toBitmap(drawable, 64, 64, null);
Expand Down
Loading

0 comments on commit 572a41d

Please sign in to comment.