Skip to content

Commit

Permalink
fix viewannotationoptions frame method
Browse files Browse the repository at this point in the history
  • Loading branch information
ank27 committed Nov 1, 2022
1 parent e05cb7c commit 9692f51
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Mapbox welcomes participation and contributions from everyone.
# main
## Features ✨ and improvements 🏁
* Introduce view annotation `ViewAnnotationManager.annotations` API to access list of added view annotations. ([1751](https://github.com/mapbox/mapbox-maps-android/pull/1751))
* Introduce view annotation `ViewAnnotationManager.cameraForAnnotations` API to get camera options for given view annotations list. ([1753](https://github.com/mapbox/mapbox-maps-android/pull/1753))

## Bug fixes 🐞
Fix an issue when touch events didn't pass through clickable view annotations. ([1745](https://github.com/mapbox/mapbox-maps-android/pull/1745))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.mapbox.geojson.Point
import com.mapbox.maps.*
import com.mapbox.maps.plugin.animation.flyTo
import com.mapbox.maps.plugin.gestures.*
import com.mapbox.maps.testapp.R
import com.mapbox.maps.testapp.databinding.ActivityViewAnnotationShowcaseBinding
Expand All @@ -25,6 +27,7 @@ class ViewAnnotationBasicAddActivity : AppCompatActivity(), OnMapClickListener {

private lateinit var mapboxMap: MapboxMap
private lateinit var viewAnnotationManager: ViewAnnotationManager
private val viewAnnotationViews = mutableListOf<View>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -45,6 +48,17 @@ class ViewAnnotationBasicAddActivity : AppCompatActivity(), OnMapClickListener {
Toast.makeText(this@ViewAnnotationBasicAddActivity, STARTUP_TEXT, Toast.LENGTH_LONG).show()
}
}

binding.fabReframe.setOnClickListener {
if (viewAnnotationViews.isNotEmpty()) {
val cameraOptions = viewAnnotationManager.cameraForAnnotations(viewAnnotationViews)
cameraOptions?.let {
mapboxMap.flyTo(it)
}
} else {
Toast.makeText(this@ViewAnnotationBasicAddActivity, ADD_VIEW_ANNOTATION_TEXT, Toast.LENGTH_LONG).show()
}
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
Expand Down Expand Up @@ -80,10 +94,12 @@ class ViewAnnotationBasicAddActivity : AppCompatActivity(), OnMapClickListener {
allowOverlap(true)
}
)
viewAnnotationViews.add(viewAnnotation)
ItemCalloutViewBinding.bind(viewAnnotation).apply {
textNativeView.text = "lat=%.2f\nlon=%.2f".format(point.latitude(), point.longitude())
closeNativeView.setOnClickListener {
viewAnnotationManager.removeViewAnnotation(viewAnnotation)
viewAnnotationViews.remove(viewAnnotation)
}
selectButton.setOnClickListener { b ->
val button = b as Button
Expand All @@ -109,5 +125,6 @@ class ViewAnnotationBasicAddActivity : AppCompatActivity(), OnMapClickListener {
private companion object {
const val SELECTED_ADD_COEF_PX = 25
const val STARTUP_TEXT = "Click on a map to add a view annotation."
const val ADD_VIEW_ANNOTATION_TEXT = "Add view annotations to re-frame map camera"
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/layout/activity_view_annotation_showcase.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,14 @@
app:layout_constraintEnd_toEndOf="parent"
tools:backgroundTint="@color/blue"/>

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_reframe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@drawable/ic_baseline_refresh_24"
app:layout_constraintBottom_toTopOf="@+id/fab_style_toggle"
app:layout_constraintEnd_toEndOf="parent"
tools:backgroundTint="@color/blue"/>

</androidx.constraintlayout.widget.ConstraintLayout>
1 change: 1 addition & 0 deletions sdk/api/metalava.txt
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ package com.mapbox.maps.viewannotation {
method public android.view.View addViewAnnotation(@LayoutRes int resId, com.mapbox.maps.ViewAnnotationOptions options);
method public void addViewAnnotation(@LayoutRes int resId, com.mapbox.maps.ViewAnnotationOptions options, androidx.asynclayoutinflater.view.AsyncLayoutInflater asyncInflater, kotlin.jvm.functions.Function1<? super android.view.View,kotlin.Unit> asyncInflateCallback);
method public void addViewAnnotation(android.view.View view, com.mapbox.maps.ViewAnnotationOptions options);
method public com.mapbox.maps.CameraOptions? cameraForAnnotations(java.util.List<? extends android.view.View> annotations, com.mapbox.maps.EdgeInsets? edgeInsets = null, Double? bearing = null, Double? pitch = null);
method public java.util.Map<android.view.View,com.mapbox.maps.ViewAnnotationOptions> getAnnotations();
method public android.view.View? getViewAnnotationByFeatureId(String featureId);
method public com.mapbox.maps.ViewAnnotationOptions? getViewAnnotationOptionsByFeatureId(String featureId);
Expand Down
5 changes: 5 additions & 0 deletions sdk/api/sdk.api
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ public abstract interface class com/mapbox/maps/viewannotation/ViewAnnotationMan
public abstract fun addViewAnnotation (ILcom/mapbox/maps/ViewAnnotationOptions;)Landroid/view/View;
public abstract fun addViewAnnotation (ILcom/mapbox/maps/ViewAnnotationOptions;Landroidx/asynclayoutinflater/view/AsyncLayoutInflater;Lkotlin/jvm/functions/Function1;)V
public abstract fun addViewAnnotation (Landroid/view/View;Lcom/mapbox/maps/ViewAnnotationOptions;)V
public abstract fun cameraForAnnotations (Ljava/util/List;Lcom/mapbox/maps/EdgeInsets;Ljava/lang/Double;Ljava/lang/Double;)Lcom/mapbox/maps/CameraOptions;
public abstract fun getAnnotations ()Ljava/util/Map;
public abstract fun getViewAnnotationByFeatureId (Ljava/lang/String;)Landroid/view/View;
public abstract fun getViewAnnotationOptionsByFeatureId (Ljava/lang/String;)Lcom/mapbox/maps/ViewAnnotationOptions;
Expand All @@ -602,6 +603,10 @@ public abstract interface class com/mapbox/maps/viewannotation/ViewAnnotationMan
public final class com/mapbox/maps/viewannotation/ViewAnnotationManager$Companion {
}

public final class com/mapbox/maps/viewannotation/ViewAnnotationManager$DefaultImpls {
public static synthetic fun cameraForAnnotations$default (Lcom/mapbox/maps/viewannotation/ViewAnnotationManager;Ljava/util/List;Lcom/mapbox/maps/EdgeInsets;Ljava/lang/Double;Ljava/lang/Double;ILjava/lang/Object;)Lcom/mapbox/maps/CameraOptions;
}

public final class com/mapbox/maps/viewannotation/ViewAnnotationOptionsKtxKt {
public static final fun viewAnnotationOptions (Lkotlin/jvm/functions/Function1;)Lcom/mapbox/maps/ViewAnnotationOptions;
}
Expand Down
116 changes: 116 additions & 0 deletions sdk/src/main/java/com/mapbox/maps/ViewAnnotationManagerImpl.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.mapbox.maps

import android.graphics.Rect
import android.os.Looper
import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import androidx.annotation.*
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import androidx.core.view.isVisible
import com.mapbox.bindgen.Expected
import com.mapbox.geojson.Point
import com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName
import com.mapbox.maps.extension.style.projection.generated.getProjection
import com.mapbox.maps.viewannotation.*
import com.mapbox.maps.viewannotation.ViewAnnotation
import com.mapbox.maps.viewannotation.ViewAnnotation.Companion.USER_FIXED_DIMENSION
import com.mapbox.maps.viewannotation.ViewAnnotationVisibility
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.collections.Map
import kotlin.math.abs

internal class ViewAnnotationManagerImpl(
mapView: MapView,
Expand All @@ -35,6 +41,7 @@ internal class ViewAnnotationManagerImpl(
}

private val annotationMap = ConcurrentHashMap<String, ViewAnnotation>()

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal val idLookupMap = ConcurrentHashMap<View, String>()
private val currentlyDrawnViewIdSet = mutableSetOf<String>()
Expand Down Expand Up @@ -156,6 +163,115 @@ internal class ViewAnnotationManagerImpl(
}
}

override fun cameraForAnnotations(
annotations: List<View>,
edgeInsets: EdgeInsets?,
bearing: Double?,
pitch: Double?
): CameraOptions? {
if (mapboxMap.style?.getProjection()?.name == ProjectionName.GLOBE || annotations.isEmpty()) {
return null
}
val viewAnnotationOptions = annotations.mapNotNull {
if (it.isVisible) return@mapNotNull getViewAnnotationOptionsByView(it) else null
}.filter { it.visible != false }

if (viewAnnotationOptions.isEmpty()) return null
val coordinates = coordinatesFromAnnotations(viewAnnotationOptions)
val paddings = calculateEdgeInsets(viewAnnotationOptions, edgeInsets)
return mapboxMap.cameraForCoordinates(
coordinates,
paddings,
bearing,
pitch
)
}

/**
* Function to get coordinates from list of [ViewAnnotationOptions].
*/
private fun coordinatesFromAnnotations(annotationOptions: List<ViewAnnotationOptions>): List<Point> {
val coordinatesList = mutableListOf<Point>()
annotationOptions.forEach {
it.geometry?.let { geometry ->
coordinatesList.add(geometry as Point)
}
}
return coordinatesList
}

/**
* Calculate paddings to show viewAnnotations.
* Get the topMost, leftMost, rightMost, bottomMost annotation options and apply paddings accordingly.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun calculateEdgeInsets(
viewAnnotationOptions: List<ViewAnnotationOptions>,
edgeInsets: EdgeInsets? = null
): EdgeInsets {
val filteredViewAnnotations = viewAnnotationOptions.filter { it.geometry != null }
val topAnnotation =
filteredViewAnnotations.maxByOrNull { (it.geometry!! as Point).latitude() }
val bottomAnnotation =
filteredViewAnnotations.minByOrNull { (it.geometry!! as Point).latitude() }
val leftAnnotation =
filteredViewAnnotations.minByOrNull { (it.geometry!! as Point).longitude() }
val rightAnnotation =
filteredViewAnnotations.maxByOrNull { (it.geometry!! as Point).longitude() }

return EdgeInsets(
(edgeInsets?.top ?: 0).toDouble()
.plus(abs(getViewAnnotationOptionsFrame(topAnnotation)?.top ?: 0)),
(edgeInsets?.left ?: 0).toDouble()
.plus(abs(getViewAnnotationOptionsFrame(leftAnnotation)?.left ?: 0)),
(edgeInsets?.bottom ?: 0).toDouble()
.plus(getViewAnnotationOptionsFrame(bottomAnnotation)?.bottom ?: 0),
(edgeInsets?.right ?: 0).toDouble()
.plus(getViewAnnotationOptionsFrame(rightAnnotation)?.right ?: 0)
)
}

/**
* Get [Rect] from [ViewAnnotationOptions]'s geometry, width and height.
* This function takes [ViewAnnotationOptions.geometry] as the center of rectangle and
* use width, height and offset values to calculate [Rect] associated.
*
* @return [Rect] associated with [ViewAnnotationOptions]
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun getViewAnnotationOptionsFrame(viewAnnotationOptions: ViewAnnotationOptions?): Rect? {
viewAnnotationOptions?.let { options ->
if (options.width != null && options.height != null) {
val offsetWidth = if (options.width!! > 0) (options.width!! * 0.5).toInt() else 0
val offsetHeight = if (options.height!! > 0) (options.height!! * 0.5).toInt() else 0
// create a dummy rect with center assume at 0,0 with offsetWidth and offsetHeight.
val rect = Rect(
-offsetWidth,
-offsetHeight,
offsetWidth,
offsetHeight
)

// offset rect with respect to anchor defined in viewannotation options.
when (options.anchor ?: ViewAnnotationAnchor.CENTER) {
ViewAnnotationAnchor.TOP -> rect.offset(0, offsetHeight)
ViewAnnotationAnchor.TOP_LEFT -> rect.offset(offsetWidth, offsetHeight)
ViewAnnotationAnchor.TOP_RIGHT -> rect.offset(-offsetWidth, offsetHeight)
ViewAnnotationAnchor.BOTTOM -> rect.offset(0, -offsetHeight)
ViewAnnotationAnchor.BOTTOM_LEFT -> rect.offset(offsetWidth, -offsetHeight)
ViewAnnotationAnchor.BOTTOM_RIGHT -> rect.offset(-offsetWidth, -offsetHeight)
ViewAnnotationAnchor.LEFT -> rect.offset(offsetWidth, 0)
ViewAnnotationAnchor.RIGHT -> rect.offset(-offsetWidth, 0)
else -> rect.offset(0, 0)
}
// add view annotation option's offsetX and offsetY field to offset the rect.
rect.offset(options.offsetX ?: 0, options.offsetY ?: 0)
return rect
}
}
return null
}

/**
* We will have two calls of this callback:
* - first from render thread with actual position list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import androidx.asynclayoutinflater.view.AsyncLayoutInflater
import com.mapbox.geojson.Feature
import com.mapbox.geojson.Geometry
import com.mapbox.geojson.Point
import com.mapbox.maps.CameraOptions
import com.mapbox.maps.EdgeInsets
import com.mapbox.maps.MapView
import com.mapbox.maps.ViewAnnotationOptions

Expand Down Expand Up @@ -166,6 +168,32 @@ interface ViewAnnotationManager {
*/
val annotations: Map<View, ViewAnnotationOptions>

/**
* Return camera options bound to given view annotation list, padding, bearing and pitch values.
* Annotations with [ViewAnnotationOptions.visible] set to false will be excluded from the calculations of [CameraOptions].
* Annotations with only [View.VISIBLE] will be included in the calculations for [CameraOptions]
*
* Note: This API isn't supported by Globe projection and will return NULL.
* Calling this API immediately after adding the view is a no-op.
* Please refer to [OnViewAnnotationUpdatedListener] documentation for understanding the exact moment of time when
* view annotation is positioned.
*
* @param annotations view annotation list to be shown. Annotations should be added beforehand
* with [ViewAnnotationManager.addViewAnnotation] API.
* @param edgeInsets paddings to apply.
* @param bearing camera bearing to apply.
* @param pitch camera pitch to apply.
*
* @return [CameraOptions] object or NULL if [annotations] list is empty.
*
*/
fun cameraForAnnotations(
annotations: List<View>,
edgeInsets: EdgeInsets? = null,
bearing: Double? = null,
pitch: Double? = null
): CameraOptions?

/**
* Static methods and variables.
*/
Expand Down
Loading

0 comments on commit 9692f51

Please sign in to comment.