This sample demonstrates how to keep the viewpoints of two GeoViews (a 2D MapView and a 3D SceneView) synchronized with each other.
Use case
You might need to synchronize GeoView viewpoints if you had two map views in one application - a main map and an inset. An inset map view could display all the layers at their full extent and contain a hollow rectangular graphic that represents the visible extent of the main map view. As you zoom or pan in the main map view, the extent graphic in the inset map would adjust accordingly.
How to use the sample
Interact with the MapView or SceneView by zooming or panning. The other MapView or SceneView will automatically focus on the same viewpoint.
How it works
- The ViewModel creates two containers: an ArcGISMap and an ArcGISScene. Both are initialized with the same initial Viewpoint (center coordinate and scale).
- The ViewModel also exposes a MapViewProxy and a SceneViewProxy. These proxies provide convenience functions to set viewpoints on the map/scene from the ViewModel without needing direct MapView/SceneView references.
- Each GeoView composable registers two callbacks:
- onNavigationChanged: notifies when the user is actively navigating (panning/zooming/rotating).
- onViewpointChangedForCenterAndScale: notifies about center-and-scale viewpoint updates.
- When the MapView viewpoint changes and the SceneView is not currently navigating, the ViewModel sets the SceneViewProxy to the new viewpoint. Symmetrically, when the SceneView viewpoint changes and the MapView is not navigating, the ViewModel sets the MapViewProxy to that viewpoint.
Relevant API
- GeoView
- MapView
- SceneView
- Viewpoint
About the data
This application provides two different perspectives of the arcGISImagery basemap, A 2D MapView as well as a 3D SceneView, displayed side by side.
Tags
3D, automatic refresh, event, event handler, events, extent, interaction, interactions, pan, zoom
Sample Code
package com.esri.arcgismaps.sample.matchviewpointofgeoviews.screens
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import com.arcgismaps.toolkit.geoviewcompose.SceneViewProxy
class MatchViewpointOfGeoViewsViewModel : ViewModel() {
val arcGISMap = ArcGISMap(BasemapStyle.ArcGISImagery)
val arcGISScene = ArcGISScene(BasemapStyle.ArcGISImagery)
val mapViewProxy = MapViewProxy()
val sceneViewProxy = SceneViewProxy()
// Initial viewpoint
private val initialViewpoint = Viewpoint(
center = Point(-13637000.0, 4550000.0, SpatialReference.webMercator()),
scale = 100_000.0
)
// Track navigation state
var isMapNavigating by mutableStateOf(false)
var isSceneNavigating by mutableStateOf(false)
init {
arcGISMap.initialViewpoint = initialViewpoint
arcGISScene.initialViewpoint = initialViewpoint
}
fun updateMapIsNavigating(navigating: Boolean) {
isMapNavigating = navigating
}
fun updateSceneIsNavigating(navigating: Boolean) {
isSceneNavigating = navigating
}
fun onMapViewpointChanged(newViewpoint: Viewpoint) {
if (!isSceneNavigating) {
sceneViewProxy.setViewpoint(newViewpoint)
}
}
fun onSceneViewpointChanged(newViewpoint: Viewpoint) {
if (!isMapNavigating) {
mapViewProxy.setViewpoint(newViewpoint)
}
}
}