Skip to content

Commit

Permalink
Create and edit geometries sample basic implementation (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
darryl-lynch authored Jan 29, 2025
2 parents 7645920 + 089e455 commit 95bafb4
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 0 deletions.
1 change: 1 addition & 0 deletions samples/create-and-edit-geometries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Create and edit geometries
19 changes: 19 additions & 0 deletions samples/create-and-edit-geometries/README.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"category": "Edit and Manage Data",
"description": "TODO",
"formal_name": "CreateAndEditGeometries",
"ignore": false,
"images": [
"create-and-edit-geometries.png"
],
"keywords": [ ],
"language": "kotlin",
"redirect_from": "",
"relevant_apis": [ ],
"snippets": [
"src/main/java/com/esri/arcgismaps/sample/createandeditgeometries/CreateAndEditGeometriesViewModel.kt",
"src/main/java/com/esri/arcgismaps/sample/createandeditgeometries/CreateAndEditGeometriesScreen.kt",
"src/main/java/com/esri/arcgismaps/sample/createandeditgeometries/MainActivity.kt"
],
"title": "Create and edit geometries"
}
22 changes: 22 additions & 0 deletions samples/create-and-edit-geometries/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
alias(libs.plugins.arcgismaps.android.library)
alias(libs.plugins.arcgismaps.android.library.compose)
alias(libs.plugins.arcgismaps.kotlin.sample)
alias(libs.plugins.gradle.secrets)
}

secrets {
// this file doesn't contain secrets, it just provides defaults which can be committed into git.
defaultPropertiesFileName = "secrets.defaults.properties"
}

android {
namespace = "com.esri.arcgismaps.sample.createandeditgeometries"
buildFeatures {
buildConfig = true
}
}

dependencies {
// Only module specific dependencies needed here
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions samples/create-and-edit-geometries/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />

<application><activity
android:exported="true"
android:name=".MainActivity"
android:label="@string/display_composable_map_view_app_name">

</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* Copyright 2025 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.createandeditgeometries

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
import com.esri.arcgismaps.sample.createandeditgeometries.screens.CreateAndEditGeometriesScreen

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// authentication with an API key or named user is
// required to access basemaps and other location services
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN)

setContent {
SampleAppTheme {
CreateAndEditGeometriesApp()
}
}
}

@Composable
private fun CreateAndEditGeometriesApp() {
Surface(color = MaterialTheme.colorScheme.background) {
CreateAndEditGeometriesScreen(
sampleName = getString(R.string.create_and_edit_geometries_app_name)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* Copyright 2025 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.createandeditgeometries.components

import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.geometry.GeometryType
import com.arcgismaps.geometry.Multipoint
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.Polygon
import com.arcgismaps.geometry.Polyline
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.view.Graphic
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
import com.arcgismaps.mapping.view.geometryeditor.GeometryEditor
import com.arcgismaps.mapping.view.geometryeditor.GeometryEditorStyle
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.launch

class CreateAndEditGeometriesViewModel(application: Application) : AndroidViewModel(application) {
// create a map with the imagery basemap style
val arcGISMap by mutableStateOf(
ArcGISMap(BasemapStyle.ArcGISImagery).apply {
// a viewpoint centered at the island of Inis Meáin (Aran Islands) in Ireland
initialViewpoint = Viewpoint(
latitude = 53.08230,
longitude = -9.5920,
scale = 5000.0
)
}
)

// create a message dialog view model for handling error messages
val messageDialogVM = MessageDialogViewModel()

// create a MapViewProxy that will be used to identify features in the MapView and set the viewpoint
val mapViewProxy = MapViewProxy()

// create a geometryEditorStyle
private val geometryEditorStyle = GeometryEditorStyle()
// create a graphic to hold graphics identified on tap
private var identifiedGraphic = Graphic()
// create a graphics overlay
val graphicsOverlay = GraphicsOverlay()
// create a geometry editor
val geometryEditor = GeometryEditor()

init {
viewModelScope.launch {
// load the map
arcGISMap.load().onFailure { error ->
messageDialogVM.showMessageDialog(
title = "Failed to load map",
description = error.message.toString()
)
}
}
}

/**
* Starts the GeometryEditor using the selected [GeometryType].
*/
fun startEditor(selectedGeometry: GeometryType) {
if (!geometryEditor.isStarted.value) {
geometryEditor.start(selectedGeometry)
}
}

/**
* Stops the GeometryEditor and updates the identified graphic or calls [createGraphic].
*/
fun stopEditor() {
// check if there was a previously identified graphic
if (identifiedGraphic.geometry != null) {
// update the identified graphic
identifiedGraphic.geometry = geometryEditor.stop()
// deselect the identified graphic
identifiedGraphic.isSelected = false
} else if (geometryEditor.isStarted.value) {
// create a graphic from the geometry that was being edited
createGraphic()
}
}

/**
* Creates a graphic from the geometry and adds it to the GraphicsOverlay.
*/
private fun createGraphic() {
// stop the geometry editor and get its final geometry state
val geometry = geometryEditor.stop()
?: return messageDialogVM.showMessageDialog(
title = "Error!",
description = "Error stopping editing session"
)

// create a graphic to represent the new geometry
val graphic = Graphic(geometry)

// give the graphic an appropriate fill based on the geometry type
when (geometry) {
is Point, is Multipoint -> graphic.symbol = geometryEditorStyle.vertexSymbol
is Polyline -> graphic.symbol = geometryEditorStyle.lineSymbol
is Polygon -> graphic.symbol = geometryEditorStyle.fillSymbol
else -> {}
}
// add the graphic to the graphics overlay
graphicsOverlay.graphics.add(graphic)
// deselect the graphic
graphic.isSelected = false
}

/**
* Identifies the graphic at the tapped screen coordinate in the provided [singleTapConfirmedEvent]
* and starts the GeometryEditor using the identified graphic's geometry. Hide the BottomSheet on
* [singleTapConfirmedEvent].
*/
fun identify(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
viewModelScope.launch {
// attempt to identify a graphic at the location the user tapped
val graphicsResult = mapViewProxy.identifyGraphicsOverlays(
screenCoordinate = singleTapConfirmedEvent.screenCoordinate,
tolerance = 10.0.dp,
returnPopupsOnly = false
).getOrNull()

if (!geometryEditor.isStarted.value) {
if (graphicsResult != null) {
if (graphicsResult.isNotEmpty()) {
// get the tapped graphic
identifiedGraphic = graphicsResult.first().graphics.first()
// select the graphic
identifiedGraphic.isSelected = true
// start the geometry editor with the identified graphic
identifiedGraphic.geometry?.let {
geometryEditor.start(it)
}
}
}
// reset the identified graphic back to null
identifiedGraphic.geometry = null
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* Copyright 2025 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.createandeditgeometries.screens

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Create
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.arcgismaps.geometry.GeometryType

/**
* Composable component to display the menu buttons.
*/
@Composable
fun ButtonMenu(
isGeometryEditorStarted: Boolean,
onStartEditingButtonClick: (GeometryType) -> Unit,
onStopEditingButtonClick: () -> Unit
) {
val rowModifier = Modifier
.padding(12.dp)
.fillMaxWidth()

Row(
modifier = rowModifier
) {
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
) {
IconButton(
enabled = !isGeometryEditorStarted,
onClick = { expanded = !expanded }
) {
Icon(imageVector = Icons.Default.Create, contentDescription = "Start")
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text("Point") },
onClick = {
onStartEditingButtonClick(GeometryType.Point)
expanded = false
}
)
DropdownMenuItem(
text = { Text("Multipoint") },
onClick = {
onStartEditingButtonClick(GeometryType.Multipoint)
expanded = false
}
)
DropdownMenuItem(
text = { Text("Polyline") },
onClick = {
onStartEditingButtonClick(GeometryType.Polyline)
expanded = false
}
)
DropdownMenuItem(
text = { Text("Polygon") },
onClick = {
onStartEditingButtonClick(GeometryType.Polygon)
expanded = false
}
)
}
}
IconButton(
enabled = isGeometryEditorStarted,
onClick = { onStopEditingButtonClick() }
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "Save Edits")
}
}
}
Loading

0 comments on commit 95bafb4

Please sign in to comment.