diff --git a/show-coordinates-in-multiple-formats/README.md b/show-coordinates-in-multiple-formats/README.md index ec4d846ab..d251ec65f 100644 --- a/show-coordinates-in-multiple-formats/README.md +++ b/show-coordinates-in-multiple-formats/README.md @@ -24,6 +24,10 @@ Tap on the map to see a marker with the tapped location's coordinate formatted i * CoordinateFormatter.LatitudeLongitudeFormat * CoordinateFormatter.UtmConversionMode +## Additional information + +This sample uses the GeoCompose Toolkit module to be able to implement a Composable MapView. + ## Tags -convert, coordinate, decimal degrees, degree minutes seconds, format, latitude, longitude, USNG, UTM +convert, coordinate, decimal degrees, degree minutes seconds, format, geocompose, latitude, longitude, toolkit, USNG, UTM diff --git a/show-coordinates-in-multiple-formats/README.metadata.json b/show-coordinates-in-multiple-formats/README.metadata.json index a7d84439b..4b49ffe9d 100644 --- a/show-coordinates-in-multiple-formats/README.metadata.json +++ b/show-coordinates-in-multiple-formats/README.metadata.json @@ -14,8 +14,10 @@ "decimal degrees", "degree minutes seconds", "format", + "geocompose", "latitude", "longitude", + "toolkit", "CoordinateFormatter", "CoordinateFormatter.LatitudeLongitudeFormat", "CoordinateFormatter.UtmConversionMode" @@ -29,7 +31,6 @@ ], "snippets": [ "src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/MainActivity.kt", - "src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/ComposeMapView.kt", "src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/MapViewModel.kt", "src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/screens/CoordinatesLayout.kt", "src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/screens/MainScreen.kt" diff --git a/show-coordinates-in-multiple-formats/build.gradle b/show-coordinates-in-multiple-formats/build.gradle index 883bd6517..754be9566 100644 --- a/show-coordinates-in-multiple-formats/build.gradle +++ b/show-coordinates-in-multiple-formats/build.gradle @@ -45,4 +45,7 @@ dependencies { implementation "androidx.compose.ui:ui-tooling" implementation "androidx.compose.ui:ui-tooling-preview" implementation project(path: ':samples-lib') + // Toolkit dependencies + implementation(platform("com.esri:arcgis-maps-kotlin-toolkit-bom:$arcgisToolkitVersion")) + implementation('com.esri:arcgis-maps-kotlin-toolkit-geo-compose') } diff --git a/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/ComposeMapView.kt b/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/ComposeMapView.kt deleted file mode 100644 index f91491bf4..000000000 --- a/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/ComposeMapView.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* Copyright 2023 Esri - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.esri.arcgismaps.sample.showcoordinatesinmultipleformats.components - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.viewinterop.AndroidView -import androidx.lifecycle.LifecycleOwner -import com.arcgismaps.mapping.view.MapView -import com.arcgismaps.mapping.view.SingleTapConfirmedEvent -import kotlinx.coroutines.launch - -/** - * Wraps the MapView in a Composable function. - */ -@Composable -fun ComposeMapView( - modifier: Modifier = Modifier, - mapViewModel: MapViewModel, - onSingleTap: (SingleTapConfirmedEvent) -> Unit = {} -) { - // get an instance of the current lifecycle owner - val lifecycleOwner = LocalLifecycleOwner.current - // get an instance of the ViewModel's MapViewState - val mapViewState = mapViewModel.mapViewState - // create and add MapView to the activity lifecycle - val mapView = createMapViewInstance(lifecycleOwner) - - // wrap the MapView as an AndroidView - AndroidView( - modifier = modifier, - factory = { mapView }, - // recomposes the MapView on changes in the MapViewState - update = { mapView -> - mapView.apply { - map = mapViewState.arcGISMap - mapView.graphicsOverlays.clear() - mapView.graphicsOverlays.add(mapViewState.graphicsOverlay) - } - } - ) - - // launch coroutine functions in the composition's CoroutineContext - LaunchedEffect(Unit) { - launch { - mapView.onSingleTapConfirmed.collect { - onSingleTap(it) - } - } - } -} - -/** - * Create the MapView instance and add it to the Activity lifecycle - */ -@Composable -fun createMapViewInstance(lifecycleOwner: LifecycleOwner): MapView { - // create the MapView - val mapView = MapView(LocalContext.current) - // add the side effects for MapView composition - DisposableEffect(lifecycleOwner) { - lifecycleOwner.lifecycle.addObserver(mapView) - onDispose { - lifecycleOwner.lifecycle.removeObserver(mapView) - } - } - return mapView -} diff --git a/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/MapViewModel.kt b/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/MapViewModel.kt index f87589128..4869b23c1 100644 --- a/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/MapViewModel.kt +++ b/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/components/MapViewModel.kt @@ -27,20 +27,12 @@ import com.arcgismaps.geometry.LatitudeLongitudeFormat import com.arcgismaps.geometry.Point import com.arcgismaps.geometry.SpatialReference import com.arcgismaps.geometry.UtmConversionMode -import com.arcgismaps.mapping.ArcGISMap -import com.arcgismaps.mapping.Basemap -import com.arcgismaps.mapping.BasemapStyle -import com.arcgismaps.mapping.layers.ArcGISTiledLayer import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle import com.arcgismaps.mapping.view.Graphic -import com.arcgismaps.mapping.view.GraphicsOverlay import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel -import com.esri.arcgismaps.sample.showcoordinatesinmultipleformats.R class MapViewModel(application: Application) : AndroidViewModel(application) { - // set the MapView state - val mapViewState = MapViewState() var decimalDegrees by mutableStateOf("") private set @@ -53,10 +45,13 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { var usng by mutableStateOf("") private set + // create a ViewModel to handle dialog interactions + val messageDialogVM: MessageDialogViewModel = MessageDialogViewModel() + // set up a graphic to indicate where the coordinates relate to, with an initial location - private val initialPoint = Point(0.0, 0.0, SpatialReference.wgs84()) + val initialPoint = Point(0.0, 0.0, SpatialReference.wgs84()) - private val coordinateLocation = Graphic( + val coordinateLocationGraphic = Graphic( geometry = initialPoint, symbol = SimpleMarkerSymbol( style = SimpleMarkerSymbolStyle.Cross, @@ -65,42 +60,12 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { ) ) - // create a ViewModel to handle dialog interactions - val messageDialogVM: MessageDialogViewModel = MessageDialogViewModel() - - init { - // create a map that has the WGS 84 coordinate system and set this into the map - val basemapLayer = ArcGISTiledLayer(application.getString(R.string.basemap_url)) - val map = ArcGISMap(Basemap(basemapLayer)) - mapViewState.arcGISMap = map - mapViewState.graphicsOverlay.graphics.add(coordinateLocation) - - // update the coordinate notations using the initial point - toCoordinateNotationFromPoint(initialPoint) - } - - /** - * Updates the tapped graphic and coordinate notations using the [tappedPoint] - */ - fun onMapTapped(tappedPoint: Point?) { - if (tappedPoint != null) { - // update the tapped location graphic - coordinateLocation.geometry = tappedPoint - mapViewState.graphicsOverlay.graphics.apply { - clear() - add(coordinateLocation) - } - // update the coordinate notations using the tapped point - toCoordinateNotationFromPoint(tappedPoint) - } - } - /** * Uses CoordinateFormatter to update the UI with coordinate notation strings based on the * given [newLocation] point to convert to coordinate notations */ - private fun toCoordinateNotationFromPoint(newLocation: Point) { - coordinateLocation.geometry = newLocation + fun toCoordinateNotationFromPoint(newLocation: Point) { + coordinateLocationGraphic.geometry = newLocation // use CoordinateFormatter to convert to Latitude Longitude, formatted as Decimal Degrees decimalDegrees = CoordinateFormatter.toLatitudeLongitudeOrNull( point = newLocation, @@ -206,11 +171,3 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { usng = inputString } } - -/** - * Class that represents the MapView's current state - */ -class MapViewState { - var arcGISMap: ArcGISMap by mutableStateOf(ArcGISMap(BasemapStyle.ArcGISNavigationNight)) - var graphicsOverlay: GraphicsOverlay by mutableStateOf(GraphicsOverlay()) -} diff --git a/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/screens/MainScreen.kt b/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/screens/MainScreen.kt index 037cefe92..269436cad 100644 --- a/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/screens/MainScreen.kt +++ b/show-coordinates-in-multiple-formats/src/main/java/com/esri/arcgismaps/sample/showcoordinatesinmultipleformats/screens/MainScreen.kt @@ -21,11 +21,19 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel +import com.arcgismaps.mapping.ArcGISMap +import com.arcgismaps.mapping.Basemap +import com.arcgismaps.mapping.layers.ArcGISTiledLayer +import com.arcgismaps.mapping.view.GraphicsOverlay +import com.arcgismaps.toolkit.geocompose.MapView +import com.arcgismaps.toolkit.geocompose.rememberGraphicsOverlayCollection import com.esri.arcgismaps.sample.sampleslib.components.MessageDialog import com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar -import com.esri.arcgismaps.sample.showcoordinatesinmultipleformats.components.ComposeMapView +import com.esri.arcgismaps.sample.showcoordinatesinmultipleformats.R import com.esri.arcgismaps.sample.showcoordinatesinmultipleformats.components.MapViewModel /** @@ -35,21 +43,47 @@ import com.esri.arcgismaps.sample.showcoordinatesinmultipleformats.components.Ma fun MainScreen(sampleName: String) { // create a ViewModel to handle MapView interactions val mapViewModel: MapViewModel = viewModel() + // create a map that has the WGS 84 coordinate system and set this into the map + val basemapLayer = ArcGISTiledLayer(LocalContext.current.applicationContext.getString(R.string.basemap_url)) + val arcGISMap = ArcGISMap(Basemap(basemapLayer)) + // graphics overlay for the MapView to draw the graphics + val graphicsOverlay = remember { GraphicsOverlay() } + // the collection of graphics overlays used by the MapView + val graphicsOverlayCollection = rememberGraphicsOverlayCollection().apply { + add(graphicsOverlay) + } + + graphicsOverlay.graphics.add(mapViewModel.coordinateLocationGraphic) + // update the coordinate notations using the initial point + mapViewModel.toCoordinateNotationFromPoint(mapViewModel.initialPoint) Scaffold( topBar = { SampleTopAppBar(title = sampleName) }, content = { Column( - modifier = Modifier.fillMaxSize().padding(it) + modifier = Modifier + .fillMaxSize() + .padding(it) ) { // layout to display the coordinate text fields. CoordinatesLayout(mapViewModel = mapViewModel) - // composable function that wraps the MapView - ComposeMapView( + MapView( modifier = Modifier.fillMaxSize(), - mapViewModel = mapViewModel, - onSingleTap = { singleTapConfirmedEvent -> - mapViewModel.onMapTapped(singleTapConfirmedEvent.mapPoint) + arcGISMap = arcGISMap, + graphicsOverlays = graphicsOverlayCollection, + onSingleTapConfirmed = { singleTapConfirmedEvent -> + // retrieve the map point on MapView tapped + val tappedPoint = singleTapConfirmedEvent.mapPoint + if (tappedPoint != null) { + // update the tapped location graphic + mapViewModel.coordinateLocationGraphic.geometry = tappedPoint + graphicsOverlay.graphics.apply { + clear() + add(mapViewModel.coordinateLocationGraphic) + } + // update the coordinate notations using the tapped point + mapViewModel.toCoordinateNotationFromPoint(tappedPoint) + } } )