Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Compose] - AnnotatedLayerFeature not working in ViewAnnotation #2344

Open
shrikanth-zuper opened this issue Apr 12, 2024 · 4 comments
Open
Labels
bug 🪲 Something isn't working compose

Comments

@shrikanth-zuper
Copy link

Environment

  • Android OS version: 14
  • Devices affected: All devices
  • Maps SDK Version: 11.3.0

Observed behavior and steps to reproduce

I'm currently displaying a ViewAnnotation for a PointAnnotation. In order to hide the ViewAnnotation when that PointAnnotation is clustered, I'm using the annotatedLayerFeature to set the layer and feature id.

As seen from sample apps and other documentations, setting annotatedLayerFeature should ideally result in a behaviour where ViewAnnotation will be displayed only when that marker is visible. This is working as expected in iOS, but in Android it's not working properly. ViewAnnotation is not at all displayed when using annotatedLayerFeature.

MapboxMap {
    // Rendering markers (Point Annotations)
    PointAnnotationGroup(
        annotations = markers.invoke().map { marker ->
            (PointAnnotationOptions.fromFeature(
                Feature.fromGeometry(
                    marker.point,
                    null,
                    marker.id
                )
            ) ?: PointAnnotationOptions().withPoint(marker.point.toPoint()))
                .withIconImage(marker.icon)
        },
        annotationConfig = AnnotationConfig(
            layerId = "PointAnnotationLayer",
            annotationSourceOptions = AnnotationSourceOptions(
                clusterOptions = ClusterOptions(
                    textColor = Color.WHITE,
                    textSize = 16.0,
                    colorLevels = listOf(
                        Pair(0, Color.BLACK)
                    )
                )
            )
        )
    )

    // Rendering tool windows (View Annotations)
    markers.invoke().forEach { marker ->
        ViewAnnotation(
            options = viewAnnotationOptions {
                geometry(marker.point.toPoint())
                // This has no effect
                annotatedLayerFeature(
                    layerId = "PointAnnotationLayer"
                ) {
                    featureId(marker.id)
                }
                annotationAnchor {
                    anchor(ViewAnnotationAnchor.TOP)
                    offsetY(160.0)
                }
                allowOverlap(false)
            },
            content = marker.toolWindow
        )
    }
}

Expected behavior

When using annotatedLayerFeature option in ViewAnnotation, that ViewAnnotation should be displayed when that marker is visible and it should be hidden when the marker is clustered. This is working properly in iOS.

Notes / preliminary analysis

This feature is working properly in iOS. Also noticed that there is an option to send id for PointAnnotation in iOS. There is no such option in Android to set the id in composable side as well as in PointerAnnotationOption. I tried creating PointAnnotationOption using the fromFeature method, while checking this method I noticed that we are not at all using the id value from the feature.

@shrikanth-zuper shrikanth-zuper added the bug 🪲 Something isn't working label Apr 12, 2024
@shrikanth-zuper shrikanth-zuper changed the title [Compose] - AnnotatedLayerFeature not working in ViewAnnotation [Compose] - AnnotatedFeature not working in ViewAnnotation Apr 12, 2024
@shrikanth-zuper shrikanth-zuper changed the title [Compose] - AnnotatedFeature not working in ViewAnnotation [Compose] - AnnotatedLayerFeature not working in ViewAnnotation Apr 12, 2024
@hiroaki404
Copy link

I have same probrem.

Feature.fromGeometry(
    marker.point,
    null,
    marker.id
)

We can't specify annotation id when create annotation. I think Annotation Id is created randomly.

@pengdev
Copy link
Member

pengdev commented May 7, 2024

Thanks for the report, I've created an internal ticket to expose feature id when creating annotation, so to align with iOS implementation.

@pengdev
Copy link
Member

pengdev commented May 7, 2024

As seen from sample apps and other documentations, setting annotatedLayerFeature should ideally result in a behaviour where ViewAnnotation will be displayed only when that marker is visible

@shrikanth-zuper Regarding this specific question, I don't think using annotatedLayerFeature is the most performant solution here, as it requires ViewAnnotation to be created for every markers at the same time, which is heavy operation on main thread and potentially cause an ANR if the amount of markers is large.

I'd suggest to use query rendered features and show ViewAnnotations on the visible PointAnnotations. You might find the following code snippets helpful, which will also be added to the compose test app in upcoming releases:

  /**
   * Show view annotation on top of visible point annotations.
   */
  @Composable
  @MapboxMapComposable
  private fun ViewAnnotationsOnVisiblePoints(screenSize: IntSize) {
    val visibleAnnotations = remember {
      mutableStateListOf<Feature>()
    }
    MapEffect(screenSize) { mapView ->
      val mapboxMap = mapView.mapboxMap
      // query rendered features for the whole map on the map idle event
      mapboxMap.mapIdleEvents.conflate().collect {
        // schedule the query rendered features on render thread to avoid blocking main thread.
        mapView.queueEvent(
          event = {
            mapboxMap.queryRenderedFeatures(
              RenderedQueryGeometry(
                ScreenBox(
                  ScreenCoordinate(
                              0.0,
                              0.0
                  ),
                  ScreenCoordinate(screenSize.width.toDouble(), screenSize.height.toDouble())
                )
              ),
              options = RenderedQueryOptions(listOf(ANNOTATION_GROUP_LAYER_ID), null)
            ) { expected ->
              expected.value?.let { queriedRenderedFeatures ->
                val features = queriedRenderedFeatures.map { it.queriedFeature.feature }
                // Update only the diff of the features to avoid unnecessary removing/adding of view annotations.
                visibleAnnotations.removeAll { !features.contains(it) }
                visibleAnnotations.addAll(features.filter { !visibleAnnotations.contains(it) })
              }
            }
          },
          needRender = false
        )
      }
    }
    visibleAnnotations.mapNotNull { it.geometry() as? Point }.forEach {
      key(it.latitude(), it.longitude()) {
        ViewAnnotation(
          viewAnnotationOptions {
            geometry(it)
            allowOverlap(true)
            annotationAnchor {
              anchor(ViewAnnotationAnchor.BOTTOM)
            }
          }
        ) {
          Text(text = "\uD83D\uDD25")
        }
      }
    }
  }

@shrikanth-zuper
Copy link
Author

Hey @pengdev,

Thank you so much for this RenderedQuery solution. Now I'm able to hide the ViewAnnotations when PointAnnotations are clustered. Although there is one issue. I tried this solution and found out that mapIdleEvents is emitted every few 100 ms. Due to this mapView.queueEvent is getting called every few 100 ms when the map is idle unless the user moves the camera. Also visibleAnnotations are getting cleared and updated constantly during this process.

I have tried an alternate solution where I used the MapEvents -> onMapIdle and onCameraChanged callbacks to identify when the map is idle and query the visible features only once and update the list. Is this solution efficient?

    val visibleAnnotations = remember {
        mutableStateListOf<Feature>()
    }
    var isMapIdle by remember {
        mutableStateOf(false)
    }
    MapboxMap(
        modifier = Modifier.fillMaxSize(),
        mapViewportState = mapboxViewPortState,
        mapEvents = MapEvents(
            onMapIdle = {
                Timber.d("MAPBOX: MAP IDLE")
                isMapIdle = true
            },
            onCameraChanged = {
                Timber.d("MAPBOX: CAMERA CHANGED")
                isMapIdle = false
            }
        ),
    ) {
        MapEffect(isMapIdle, size) { mapView ->
            val mapboxMap = mapView.mapboxMap
            if(isMapIdle) {
                // schedule the query rendered features on render thread to avoid blocking main thread.
                mapView.queueEvent(
                    event = {
                        mapboxMap.queryRenderedFeatures(
                            RenderedQueryGeometry(
                                ScreenBox(
                                    ScreenCoordinate(
                                        0.0,
                                        0.0
                                    ),
                                    ScreenCoordinate(size.width.toDouble(), size.height.toDouble())
                                )
                            ),
                            options = RenderedQueryOptions(listOf(ANNOTATION_GROUP_LAYER_ID), null)
                        ) { expected ->
                            expected.value?.let { queriedRenderedFeatures ->
                                val features = queriedRenderedFeatures.map { it.queriedFeature.feature }
                                // Update only the diff of the features to avoid unnecessary removing/adding of view annotations.
                                visibleAnnotations.removeAll { !features.contains(it) }
                                visibleAnnotations.addAll(features.filter { !visibleAnnotations.contains(it) })
                            }
                        }
                    },
                    needRender = false
                )
            }
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🪲 Something isn't working compose
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants