Skip to content

Commit

Permalink
Changes after PR review
Browse files Browse the repository at this point in the history
  • Loading branch information
01smito01 committed Dec 19, 2024
1 parent 98ebd87 commit 2ccdd6d
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,23 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

class MapViewModel(application: Application) : AndroidViewModel(application) {
// Create an empty map, to be updated once data is loaded from the feature table
var arcGISMap by mutableStateOf(ArcGISMap())

// Create a message dialog view model for handling error messages
val messageDialogVM = MessageDialogViewModel()
private val provisionPath: String = application.getExternalFilesDir(null)?.path.toString() +
File.separator + application.getString(R.string.add_features_with_contingent_values_app_name)

// graphics overlay used to add feature graphics to the map
val graphicsOverlay = GraphicsOverlay()
private val cacheDir: File = application.cacheDir

// state flow of UI state
// flow of UI state
private val _featureEditState = MutableStateFlow(FeatureEditState())
val featureEditState = _featureEditState.asStateFlow()

private val cacheDir: File = application.cacheDir
// create an empty map, to be updated once data is loaded from the feature table
var arcGISMap by mutableStateOf(ArcGISMap())

private val provisionPath: String = application.getExternalFilesDir(null)?.path.toString() +
File.separator + application.getString(R.string.add_features_with_contingent_values_app_name)
// create a message dialog view model for handling error messages
val messageDialogVM = MessageDialogViewModel()

// graphics overlay used to add feature graphics to the map
val graphicsOverlay = GraphicsOverlay()

// offline vector tiled layer to be used as a basemap
private val fillmoreVectorTileLayer = ArcGISVectorTiledLayer("$provisionPath/FillmoreTopographicMap.vtpk")
Expand All @@ -84,6 +84,20 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
// instance of the feature table retrieved from the geodatabase, updates when new feature is added
private var featureTable: ArcGISFeatureTable? = null

// create outline for the buffer symbol
private val lineSymbol = SimpleLineSymbol(
style = SimpleLineSymbolStyle.Solid,
color = Color.black,
width = 2f
)

// create the buffer symbol
private val bufferSymbol = SimpleFillSymbol(
style = SimpleFillSymbolStyle.ForwardDiagonal,
color = Color.red,
outline = lineSymbol
)

init {
// create a temporary directory for use with the geodatabase file
createGeodatabaseCacheDir()
Expand All @@ -92,31 +106,31 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
// retrieve and load the offline mobile geodatabase file from the cache directory
geodatabase.load().getOrElse {
messageDialogVM.showMessageDialog(
"Error loading GeoDatabase",
it.message.toString()
title = "Error loading GeoDatabase",
description = it.message.toString()
)
}

// get the first geodatabase feature table
val featureTable = geodatabase.featureTables.firstOrNull()
?: return@launch messageDialogVM.showMessageDialog(
"Error",
"No feature table found in geodatabase"
title = "Error",
description = "No feature table found in geodatabase"
)

// load the geodatabase feature table
featureTable.load().getOrElse {
return@launch messageDialogVM.showMessageDialog(
"Error loading feature table",
it.message.toString()
title = "Error loading feature table",
description = it.message.toString()
)
}

// get the contingent values definition from the feature table and load it
featureTable.contingentValuesDefinition.load().getOrElse {
messageDialogVM.showMessageDialog(
"Error",
"Error loading the contingent values definition"
title = "Error",
description = it.message.toString()
)
}

Expand All @@ -126,15 +140,15 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
// get the full extent of the feature layer
val extent = featureLayer.fullExtent
?: return@launch messageDialogVM.showMessageDialog(
"Error",
"Error retrieving extent of the feature layer"
title = "Error",
description = "Error retrieving extent of the feature layer"
)

// set the basemap to the offline vector tiled layer, and viewpoint to the feature layer extent
arcGISMap = ArcGISMap(Basemap(fillmoreVectorTileLayer)).apply {
initialViewpoint = Viewpoint(boundingGeometry = extent as Geometry)
operationalLayers.add(featureLayer)
}
arcGISMap.operationalLayers.add(featureLayer)

// keep the instance of the feature table
this@MapViewModel.featureTable = featureTable
Expand Down Expand Up @@ -187,8 +201,8 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
graphicsOverlay.graphics.addAll(graphics)
} else {
messageDialogVM.showMessageDialog(
"Error",
"No features found with BufferSize > 0"
title = "Error",
description = "No features found with BufferSize > 0"
)
}
}
Expand All @@ -198,88 +212,46 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
* Creates and returns a graphic using the attributes of the given [feature].
*/
private fun createBufferGraphic(feature: Feature): Graphic {
// create the outline for the buffer symbol
val lineSymbol = SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Color.black, 2f)
// create the buffer symbol
val bufferSymbol =
SimpleFillSymbol(SimpleFillSymbolStyle.ForwardDiagonal, Color.red, lineSymbol)
// get the feature's buffer size
val bufferSize = feature.attributes["BufferSize"] as Int
// get a polygon using the feature's buffer size and geometry
val polygon =
feature.geometry?.let { GeometryEngine.bufferOrNull(it, bufferSize.toDouble()) }
val polygon = feature.geometry?.let {
GeometryEngine.bufferOrNull(
geometry = it,
distance = bufferSize.toDouble()
)
}

// create a graphic using the geometry and fill symbol
return Graphic(polygon, bufferSymbol)
return Graphic(geometry = polygon, symbol = bufferSymbol)
}

/**
* Retrieve the status fields, add the fields to a ContingentValueDomain.
* Used to display options in the UI.
* Create a new feature with the status attribute selected by the user.
*/
private fun ArcGISFeatureTable.statusFieldCodedValues(): List<CodedValue> {
val statusField = fields.find { field -> field.name == "Status" }
val codedValueDomain = statusField?.domain as CodedValueDomain
return codedValueDomain.codedValues
}

fun onStatusAttributeSelected(codedValue: CodedValue) = featureTable?.let { featureTable ->
viewModelScope.launch {

feature = featureTable.createFeature() as ArcGISFeature
feature?.attributes?.set("Status", codedValue.code)
feature?.attributes?.set(key = "Status", value = codedValue.code)

featureTable.let {
_featureEditState.value = FeatureEditState(
status = codedValue,
statusAttributes = it.statusFieldCodedValues(),
protectionAttributes = it.protectionFieldCodedValues()
)
}
_featureEditState.value = FeatureEditState(
selectedStatusAttribute = codedValue,
statusAttributes = featureTable.statusFieldCodedValues(),
protectionAttributes = featureTable.protectionFieldCodedValues()
)
}
}

private fun ArcGISFeatureTable.protectionFieldCodedValues(): List<CodedValue> {
// get the contingent value results with the feature for the protection field
val contingentValuesResult = feature?.let {
getContingentValuesOrNull(it, "Protection")
}

// get the list of contingent values by field group
val contingentValues = contingentValuesResult?.byFieldGroup?.get("ProtectionFieldGroup")

// convert the list of ContingentValues to a list of CodedValue
val protectionCodedValues: List<CodedValue> =
contingentValues?.map { (it as ContingentCodedValue).codedValue }
?: listOf<CodedValue>().also {
messageDialogVM.showMessageDialog(
"Error",
"Error getting coded values by field group"
)
}

return protectionCodedValues
}

fun onProtectionAttributeSelected(codedValue: CodedValue) {
feature?.attributes?.set("Protection", codedValue.code)
feature?.attributes?.set(key = "Protection", value = codedValue.code)
_featureEditState.value = _featureEditState.value.copy(
protection = codedValue,
bufferRange = featureTable?.bufferRange()
selectedProtectionAttribute = codedValue,
bufferRange = featureTable?.bufferRange().toIntRange()
)
}

private fun ArcGISFeatureTable.bufferRange(): ContingentRangeValue? {
val contingentValueResult = feature?.let {
getContingentValuesOrNull(it, "BufferSize")
}

return contingentValueResult?.byFieldGroup?.get("BufferSizeFieldGroup")?.get(0) as? ContingentRangeValue
}

fun onBufferSizeSelected(bufferSize: Int) {
feature?.attributes?.set("BufferSize", bufferSize)
_featureEditState.value = _featureEditState.value.copy(buffer = bufferSize)
feature?.attributes?.set(key = "BufferSize", value = bufferSize)
_featureEditState.value = _featureEditState.value.copy(selectedBufferSize = bufferSize)
}

/**
Expand All @@ -290,8 +262,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
val resources = getApplication<Application>().resources
// check if all the features have been set
if (featureTable == null) {
messageDialogVM.showMessageDialog(resources.getString(R.string.input_all_values))
return
return messageDialogVM.showMessageDialog(resources.getString(R.string.input_all_values))
}

// validate the feature's contingencies
Expand Down Expand Up @@ -320,8 +291,8 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
violation -> violation.fieldGroup.name
}
messageDialogVM.showMessageDialog(
"Invalid contingent values",
"${contingencyViolations.size} violations found:\n" + violations
title = "Invalid contingent values",
description = "${contingencyViolations.size} violations found:\n" + violations
)
}

Expand All @@ -333,21 +304,81 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
*/
fun clearFeature() {
feature = null
_featureEditState.value = featureTable?.let {FeatureEditState(statusAttributes = it.statusFieldCodedValues())}
?: return messageDialogVM.showMessageDialog("Feature Table not loaded")
_featureEditState.value = featureTable?.let { FeatureEditState(statusAttributes = it.statusFieldCodedValues()) }
?: return
}

/**
* Retrieves the possible status field values from the feature table,
* and add them to a ContingentValueDomain.
*/
private fun ArcGISFeatureTable.statusFieldCodedValues(): List<CodedValue> {
val statusField = fields.find { field -> field.name == "Status" }
val codedValueDomain = statusField?.domain as CodedValueDomain
return codedValueDomain.codedValues
}

/**
* Retrieves the possible protection field values from the feature table, contingent on the
* current status field value.
*/
private fun ArcGISFeatureTable.protectionFieldCodedValues(): List<CodedValue> {
// get the contingent value results with the feature for the protection field
val contingentValuesResult = feature?.let {
getContingentValuesOrNull(feature = it, field = "Protection")
}

// get the list of contingent values by field group
val contingentValues = contingentValuesResult?.byFieldGroup?.get("ProtectionFieldGroup")

// convert the list of ContingentValues to a list of CodedValue
val protectionCodedValues: List<CodedValue> =
contingentValues?.map { (it as ContingentCodedValue).codedValue }
?: listOf<CodedValue>().also {
messageDialogVM.showMessageDialog(
title = "Error",
description = "Error getting coded values by field group"
)
}

return protectionCodedValues
}

/**
* Retrieves the buffer size from the feature table, contingent on the status and protection field values.
*/
private fun ArcGISFeatureTable.bufferRange(): ContingentRangeValue? {
val contingentValueResult = feature?.let {
getContingentValuesOrNull(it, "BufferSize")
}

return contingentValueResult?.byFieldGroup?.get("BufferSizeFieldGroup")?.get(0) as? ContingentRangeValue
}

/**
* Converts this [ContingentRangeValue] to an [IntRange].
*/
private fun ContingentRangeValue?.toIntRange() : IntRange {
val bufferRange = if (this != null) {
val min = this.minValue as Int
val max = this.maxValue as Int
min..max
} else {
0..0
}
return bufferRange
}
}

/**
* Currently selected status, protection, and buffer attributes for the feature under construction,
* used to update the UI.
*/
data class FeatureEditState(
val status: CodedValue? = null,
val protection: CodedValue? = null,
val buffer: Int = 0,
val selectedStatusAttribute: CodedValue? = null,
val statusAttributes: List<CodedValue> = listOf(),
val selectedProtectionAttribute: CodedValue? = null,
val protectionAttributes: List<CodedValue> = listOf(),
val bufferRange: ContingentRangeValue? = null
val selectedBufferSize: Int = 0,
val bufferRange: IntRange = 0..0
)
Loading

0 comments on commit 2ccdd6d

Please sign in to comment.