mapbox-maps-android
mapbox-maps-android copied to clipboard
[Compose] Make cameraForCoordinates accessible into MapViewportState()
New Feature
When you configure your map composable you can only
mapViewportState = MapViewportState().apply {
setCameraOptions {
zoom(9.5)
center(state.polygonCenter.getCenter().let { Point.fromLngLat(it.long, it.lat) })
}
}
Therefore, you need to then add something like that
MapEffect(state.polygonCenter) {
val padding = 32.dp.value.toDouble()
it.camera.flyTo(it.mapboxMap.cameraForCoordinates(path, EdgeInsets(padding, padding, padding, padding)))
}
The idea would be to be able to do something like
mapViewportState = MapViewportState().apply {
val padding = 32.dp.value.toDouble()
setCameraOptions(cameraForCoordinates(path, EdgeInsets(padding, padding, padding, padding)))
}
And therefore no to have to add a MapEffect to recenter the camera
@CoreFloDev Thanks for the suggestion! It looks reasonable to be part of the MapViewportState API, we will consider adding it in upcoming releases.
No offence, but why is it taking so long? Should I use reflection to address the issue?
@CoreFloDev this issue got lost from our sight because we forgot to create our internal issue tracking it. Sorry for that. I have created an internal issue now and we will expose this (and perhaps some other methods that make sense) in the next beta release with new features (coming out in 4 weeks from now).
Any news ?
Hi @ravenfeld @CoreFloDev sorry for the late reply, I forgot to post the update here but the API is already landed in 11.9.0 release, please feel free to give it a try.
I tried to use it to initialize MapBox with the position by putting it in a LaunchEffect, but it didn't work because the LaunchEffect is executed before the map is initialized, so I used MapEffect, and since it produces a MapView, I used MapBox directly. How do I initialize the camera position based on geometry?
@ravenfeld could you share some code snippets on what you want to achieve? The operation is suspend and will execute when the MapViewportState is set to the map. So it should work as expected even if used in LaunchedEffect, see our example at https://github.com/mapbox/mapbox-maps-android/blob/4ee4360aa2c5ece39309fc5213a602136e00e731/compose-app/src/main/java/com/mapbox/maps/compose/testapp/examples/style/InteractionsActivity.kt#L68
val mapViewportState = rememberMapViewportState {
setCameraOptions(
cameraForCoordinates(points)
)
}
I would like to be able to do this so that the camera is in the correct position from the card init
@ravenfeld the init block of rememberMapViewportState will execute only once during initialisation(also it does not provide a coroutine scope), and MapViewportState.cameraForCoordinates is a suspend function that need to be run in a coroutine scope. So to make your logic work, you would need to have something as follows:
val mapViewportState = rememberMapViewportState()
LaunchedEffect(Unit) {
val camera = mapViewportState.cameraForCoordinates(
listOf(Point.fromLngLat(-73.99, 40.72)),
cameraOptions {
padding(
EdgeInsets(100.0, 100.0, 100.0, 100.0)
)
},
)
mapViewportState.setCameraOptions(camera)
}
Could you give it a try?
It works, but it's called after a MapEffect and therefore conflicts with camera animations. But that's more related to the fact that Mapbox doesn't provide animations for relive.
In any case, it's a shame not to be able to do it at init, because with the LaunchEffect, it's done after the map's init, and so we see the initial position before going to the camera for coordinates.
@ravenfeld you can also call it without MapEffect and directly inside the init block of the rememberMapViewportState, essentially you just need a coroutine scope to run the cameraForCoordinates API, e.g.
val coroutineScope = rememberCoroutineScope()
val mapViewportState = rememberMapViewportState {
coroutineScope.launch {
setCameraOptions(
cameraForCoordinates(
listOf(Point.fromLngLat(-73.99, 40.72), Point.fromLngLat(-73.99, 40.82)),
cameraOptions {
padding(
EdgeInsets(100.0, 100.0, 100.0, 100.0)
)
}
)
)
}
}
Having said that, internally we have to suspend the cameraForCoordinates call since we need to wait for the MapView's initialisation and have the correct width and height of the MapView to be able to calculate the valid camera.
But that's more related to the fact that Mapbox doesn't provide animations for relive.
I think this is a valid feature request, we will discuss internally and see how it fits into our roadmap, thanks for bring it up! It would be helpful if you can also clarify your specific use case and requirement so that we can take it into consideration.
I need to use a MapView to launch animations that aren't exposed by MapViewportState, and I've noticed that the camera isn't in the initial position I'd like if I use the LaunchEffect, which is why I was thinking of something like this.
Even with the LaunchEffect, we can see a flash because the map is initialized at 0,0 and then moves according to the command given in the LaunchEffect.
val mapViewportState = rememberMapViewportState {
setCameraOptions(
cameraForCoordinates(points)
)
}
MapboxMap(
mapViewportState = mapViewportState
) {
MapEffect(Unit) { mapView ->
//Launch animations such as fly or others. I am making an application to do relive for information.
}
}
Even with the LaunchEffect, we can see a flash because the map is initialized at 0,0 and then moves according to the command given in the LaunchEffect.
There might be some race condition during the map initialisation and setCamera(cameraForCoordinate) call, we will take a deeper look at this issue.
In the mean time, as a workaround, you can synchronise the initial camera and style loading by introducing another mutable state, for example:
val coroutineScope = rememberCoroutineScope()
var initialCameraReady by remember {
mutableStateOf(false)
}
val mapViewportState = rememberMapViewportState {
coroutineScope.launch {
setCameraOptions(
cameraForCoordinates(
listOf(Point.fromLngLat(-73.99, 40.72), Point.fromLngLat(-73.99, 40.82)),
)
)
initialCameraReady = true
}
}
...
MapboxMap(
modifier = Modifier.fillMaxSize(),
mapViewportState = mapViewportState,
style = {
if (initialCameraReady) {
MapboxStandardStyle() // Or other style you are trying to load..
}
}
)
currently what works well
MapboxMap() {
MapEffect(Unit) { mapView ->
mapView.mapboxMap.setBounds(
CameraBoundsOptions.Builder()
.maxPitch(PITCH_MAX)
.maxZoom(ZOOM_MAX)
.build()
)
val camera = mapView.mapboxMap.cameraForCoordinates(
coordinates = path,
camera = cameraOptions {
pitch(PITCH_STARTED)
padding(ROUTE_PADDING.toEdgeInsets(density = density))
},
coordinatesPadding = null,
maxZoom = null,
offset = null
)
mapView.mapboxMap.setCamera(camera)
.....