Take a web map offline with additional options for each layer.

Use case
When taking a web map offline, you may adjust the data (such as layers or tiles) that is downloaded by using custom parameter overrides. This can be used to reduce the extent of the map or the download size of the offline map. It can also be used to highlight specific data by removing irrelevant data. Additionally, this workflow allows you to take features offline that don’t have a geometry - for example, features whose attributes have been populated in the office, but still need a site survey for their geometry.
How to use the sample
To modify the overrides parameters:
- Use the min/max scale input fields to adjust the level IDs to be taken offline for the streets basemap.
- Use the extent buffer distance input field to set the buffer radius for the streets basemap.
- Check the checkboxes for the feature operational layers you want to include in the offline map.
- Use the min hydrant flow rate input field to only download features with a flow rate higher than this value.
- Select the “Water Pipes” checkbox if you want to crop the water pipe features to the extent of the map.
After you have set up the overrides to your liking, tap the “Generate offline map” button to start the download. A progress bar will display. Tap the “Cancel” button if you want to stop the download. When the download is complete, the view will display the offline map. Pan around to see that it is cropped to the download area’s extent.
How it works
- Load a web map from a
PortalItem. - Create an
OfflineMapTaskwith the map. - Generate default task parameters using the extent area you want to download with
offlineMapTask.createDefaultGenerateOfflineMapParameters(areaOfInterest). - Generate additional “override” parameters using the default parameters with
offlineMapTask.createGenerateOfflineMapParameterOverrides(generateOfflineMapParameters). - For the basemap:
- Get the parameters
OfflineMapParametersKeyfor the basemap layer. - Get the
ExportTileCacheParametersfor the basemap layer withparameterOverrides.exportTileCacheParameters[basemapParamKey]. - Set the level IDs you want to download with
exportTileCacheParameters.levelIds.add(...). - To buffer the extent, use
exportTileCacheParameters.areaOfInterestwhere bufferedGeometry can be calculated with theGeometryEngine.
- Get the parameters
- To remove operational layers from the download:
- Create an
OfflineParametersKeywith the operational layer. - Get the generate geodatabase layer options using the key with
parameterOverrides.generateGeodatabaseParameters[key].layerOptions - Use the
GenerateLayerOptionlist to remove the layer if the layer option’s ID matches the layer’s ID.
- Create an
- To filter the features downloaded in an operational layer:
- Get the layer options for the operational layer using the directions in step 6.
- For the desired layer, set the filter clause with
generateLayerOption.whereClause(...)and set the query option withgenerateLayerOption.queryOption = GenerateLayerQueryOption.UseFilter.
- To not crop a layer’s features to the extent of the offline map (default is true):
- Set
layerOption.generateLayerOption = true.
- Set
- Create a
GenerateOfflineMapJobwithofflineMapTask.createGenerateOfflineMapJob(parameters, offlineMapPath, parameterOverrides. Start the GenerateOfflineMapJob withofflineMapJob.start(). You can collect on the job’s progress to update a progress bar in your UI. - When the job is done, check
offlineMapJob.result().onSuccess { ... }to get a reference to theofflineMap.
Relevant API
- ExportTileCacheParameters
- GenerateGeodatabaseParameters
- GenerateLayerOption
- GenerateOfflineMapJob
- GenerateOfflineMapParameterOverrides
- GenerateOfflineMapParameters
- OfflineMapParametersKey
- OfflineMapTask
Additional information
For applications where you just need to take all layers offline, use the standard workflow (using only GenerateOfflineMapParameters). For a simple example of how you take a map offline, please consult the Generate offline map sample.
Tags
adjust, download, extent, filter, LOD, offline, override, parameters, reduce, scale range, setting
Sample Code
/* 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.generateofflinemapwithcustomparameters
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.runtime.Composableimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppThemeimport com.esri.arcgismaps.sample.generateofflinemapwithcustomparameters.screens.GenerateOfflineMapWithCustomParametersScreen
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)
enableEdgeToEdge() setContent { SampleAppTheme { GenerateOfflineMapWithCustomParametersApp() } } }
@Composable private fun GenerateOfflineMapWithCustomParametersApp() { Surface(color = MaterialTheme.colorScheme.background) { GenerateOfflineMapWithCustomParametersScreen( sampleName = getString(R.string.generate_offline_map_with_custom_parameters_app_name) ) } }}/* 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.generateofflinemapwithcustomparameters.components
import android.app.Applicationimport androidx.compose.material3.SnackbarHostStateimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableIntStateOfimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.setValueimport androidx.compose.ui.unit.IntSizeimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.Colorimport com.arcgismaps.LoadStatusimport com.arcgismaps.data.ServiceFeatureTableimport com.arcgismaps.geometry.Envelopeimport com.arcgismaps.geometry.GeometryEngineimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.PortalItemimport com.arcgismaps.mapping.layers.FeatureLayerimport com.arcgismaps.mapping.symbology.SimpleLineSymbolimport com.arcgismaps.mapping.symbology.SimpleLineSymbolStyleimport com.arcgismaps.mapping.view.Graphicimport com.arcgismaps.mapping.view.GraphicsOverlayimport com.arcgismaps.mapping.view.ScreenCoordinateimport com.arcgismaps.portal.Portalimport com.arcgismaps.tasks.geodatabase.GenerateGeodatabaseParametersimport com.arcgismaps.tasks.geodatabase.GenerateLayerQueryOptionimport com.arcgismaps.tasks.offlinemaptask.GenerateOfflineMapJobimport com.arcgismaps.tasks.offlinemaptask.GenerateOfflineMapParameterOverridesimport com.arcgismaps.tasks.offlinemaptask.GenerateOfflineMapParametersimport com.arcgismaps.tasks.offlinemaptask.OfflineMapParametersKeyimport com.arcgismaps.tasks.offlinemaptask.OfflineMapTaskimport com.arcgismaps.toolkit.geoviewcompose.MapViewProxyimport com.esri.arcgismaps.sample.generateofflinemapwithcustomparameters.Rimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.launchimport java.io.File
class GenerateOfflineMapWithCustomParametersViewModel(private val application: Application) : AndroidViewModel(application) {
private val provisionPath: String by lazy { application.getExternalFilesDir(null)?.path.toString() + File.separator + application.getString(R.string.generate_offline_map_with_custom_parameters_app_name) }
val mapViewProxy = MapViewProxy()
// View model to handle popup dialogs val messageDialogVM = MessageDialogViewModel()
// Define map that returns an ArcGISMap var arcGISMap = ArcGISMap() private set
// Define the download area graphic private val downloadAreaGraphic = Graphic().apply { symbol = SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Color.red, 2f) }
// Create a graphics overlay for the map view val graphicsOverlay = GraphicsOverlay().apply { graphics.add(downloadAreaGraphic) }
// Defined to send messages related to offlineMapJob val snackbarHostState = SnackbarHostState()
// Determinate job progress loading dialog visibility state var showJobProgressDialog by mutableStateOf(false) private set
// Determinate job progress percentage var offlineMapJobProgress by mutableIntStateOf(0) private set
var showResetButton by mutableStateOf(false) private set
private var generateOfflineMapJob: GenerateOfflineMapJob? = null
// Create an IntSize to retrieve dimensions of the map private var mapViewSize by mutableStateOf(IntSize(0, 0))
fun updateMapViewSize(size: IntSize) { mapViewSize = size }
init { setUpMap() }
/** * Use map view's size to determine dimensions of the map to get the download offline area * and use [MapViewProxy] to assist in converting screen points to map points */ fun calculateDownloadOfflineArea() { // Ensure the map is loaded before calculating the download area if (arcGISMap.loadStatus.value == LoadStatus.Loaded) { // Upper left corner of the area to take offline val minScreenPoint = ScreenCoordinate(200.0, 200.0) // Lower right corner of the downloaded area val maxScreenPoint = ScreenCoordinate( x = mapViewSize.width - 200.0, y = mapViewSize.height - 200.0 ) // Convert screen points to map points val minPoint = mapViewProxy.screenToLocationOrNull(minScreenPoint) val maxPoint = mapViewProxy.screenToLocationOrNull(maxScreenPoint) // Create an envelope to set the download area's geometry using the defined bounds if (minPoint != null && maxPoint != null) { val envelope = Envelope(minPoint, maxPoint) downloadAreaGraphic.geometry = envelope } } }
/** * Sets up a portal item and displays map area to take offline */ private fun setUpMap() { // Create a portal item with the itemId of the web map val portal = Portal("https://www.arcgis.com") val portalItem = PortalItem(portal, "acc027394bc84c2fb04d1ed317aac674")
// Clear, then add the download graphic to the graphics overlay graphicsOverlay.graphics.clear() graphicsOverlay.graphics.add(downloadAreaGraphic)
arcGISMap = ArcGISMap(portalItem) viewModelScope.launch(Dispatchers.Main) { arcGISMap.load().onFailure { messageDialogVM.showMessageDialog( title = it.message.toString() ) } } showResetButton = false }
/** * Define the [GenerateOfflineMapParameters] for the offline map job and add the custom * [GenerateOfflineMapParameterOverrides] using the given override values. */ fun defineGenerateOfflineMapParameters( minScale: Int, maxScale: Int, bufferDistance: Int, isIncludeSystemValvesEnabled: Boolean, isIncludeServiceConnectionsEnabled: Boolean, minHydrantFlowRate: Int, isCropWaterPipesEnabled: Boolean ) { // Create an offline map offlineMapTask with the map val offlineMapTask = OfflineMapTask(arcGISMap) // The current area of interest displayed on the map val downloadArea = downloadAreaGraphic.geometry ?: return viewModelScope.launch { // Create default generate offline map parameters from the offline map task val generateOfflineMapParameters = offlineMapTask.createDefaultGenerateOfflineMapParameters(areaOfInterest = downloadArea).getOrElse { messageDialogVM.showMessageDialog( title = "Error", description = "Failed to create default generate offline map parameters" ) return@launch }.apply { // Return a job failure if generate offline map encounters an error continueOnErrors = false } // Create parameter overrides for greater control offlineMapTask.createGenerateOfflineMapParameterOverrides(generateOfflineMapParameters) .onSuccess { parameterOverrides -> // Set basemap scale and area of interest setBasemapScaleAndAreaOfInterest(parameterOverrides, minScale, maxScale, bufferDistance) // Exclude system valve layer if (!isIncludeSystemValvesEnabled) { excludeLayerFromDownload(parameterOverrides, getFeatureLayer("System Valve")) } // Exclude service connection layer if (!isIncludeServiceConnectionsEnabled) { excludeLayerFromDownload(parameterOverrides, getFeatureLayer("Service Connection")) } // Crop pipes layer if (isCropWaterPipesEnabled) { getGenerateGeodatabaseParameters( parameterOverrides, getFeatureLayer("Main") )?.layerOptions?.forEach { it.useGeometry = true } } // Get a reference to the hydrant layer getFeatureLayer("Hydrant")?.let { hydrantLayer -> // Get it's service layer id val serviceLayerId = getServiceLayerId(hydrantLayer) getGenerateGeodatabaseParameters( parameterOverrides, getFeatureLayer(hydrantLayer.name) )?.layerOptions?.filter { it.layerId == serviceLayerId }?.forEach { it.whereClause = "FLOW >= $minHydrantFlowRate" it.queryOption = GenerateLayerQueryOption.UseFilter } } // Start a an offline map job from the task and parameters createOfflineMapJob(offlineMapTask, generateOfflineMapParameters, parameterOverrides) } } }
/** * Generate an offline map job with the given [OfflineMapTask], [GenerateOfflineMapParameters] and * [GenerateOfflineMapParameterOverrides]. */ private fun createOfflineMapJob( offlineMapTask: OfflineMapTask, generateOfflineMapParameters: GenerateOfflineMapParameters, parameterOverrides: GenerateOfflineMapParameterOverrides ) { // Store the offline map in the app's scoped storage directory val offlineMapPath = provisionPath + File.separator + "OfflineMap" val offlineMapFile = File(offlineMapPath)
// Delete any offline map already present offlineMapFile.deleteRecursively() // Make the relevant directories for the offline map offlineMapFile.mkdirs()
// Report any errors that occur during the offline map job viewModelScope.launch(Dispatchers.Main) { offlineMapTask.load().onFailure { error -> messageDialogVM.showMessageDialog( title = error.message.toString(), description = error.cause.toString() ) } }
// Create an offline map job with the download directory path and parameters and start the job generateOfflineMapJob = offlineMapTask.createGenerateOfflineMapJob( parameters = generateOfflineMapParameters, downloadDirectoryPath = offlineMapPath, overrides = parameterOverrides )
runOfflineMapJob() }
/** * Starts the [GenerateOfflineMapJob], shows the progress dialog and displays the result offline map to the MapView. */ private fun runOfflineMapJob() { // Show the Job Progress Dialog showJobProgressDialog = true with(viewModelScope) { // Create a flow-collection for the job's progress launch(Dispatchers.Main) { generateOfflineMapJob?.progress?.collect { progress -> // Display the current job's progress value offlineMapJobProgress = progress } } launch(Dispatchers.IO) { // Start the job and wait for Job result generateOfflineMapJob?.start() generateOfflineMapJob?.result()?.onSuccess { // Set the offline map result as the displayed map and clear the red bounding box graphic arcGISMap = it.offlineMap showResetButton = true graphicsOverlay.graphics.clear() // Dismiss the progress dialog showJobProgressDialog = false // Show user where map was locally saved snackbarHostState.showSnackbar(message = "Map saved at: " + generateOfflineMapJob?.downloadDirectoryPath) }?.onFailure { throwable -> messageDialogVM.showMessageDialog( title = throwable.message.toString(), description = throwable.cause.toString() ) showJobProgressDialog = false } } } }
/** * Cancel the offline map job. */ fun cancelOfflineMapJob() { with(viewModelScope) { launch(Dispatchers.IO) { generateOfflineMapJob?.cancel() } launch(Dispatchers.Main) { snackbarHostState.showSnackbar(message = "User canceled.") } } }
/** * Set basemap scale and area of interest using the given values */ private fun setBasemapScaleAndAreaOfInterest( parameterOverrides: GenerateOfflineMapParameterOverrides, minScale: Int, maxScale: Int, bufferDistance: Int ) { // Get the first basemap layer arcGISMap.basemap.value?.baseLayers?.first()?.let { basemapLayer -> // Use the basemap layer to make an offline map parameters key val key = OfflineMapParametersKey(basemapLayer) // Create export tile cache parameters val exportTileCacheParameters = parameterOverrides.exportTileCacheParameters[key]?.apply { // Create a new list of levels in the scale range requested by the user levelIds.clear() levelIds.addAll((minScale until maxScale).toList()) } downloadAreaGraphic.geometry?.let { downloadArea -> // Set the area of interest to the original download area plus a buffer exportTileCacheParameters?.areaOfInterest = GeometryEngine.bufferOrNull(downloadArea, bufferDistance.toDouble()) } } }
/** * Remove the layer named from the generate layer options list in the generate geodatabase parameters. */ private fun excludeLayerFromDownload( parameterOverrides: GenerateOfflineMapParameterOverrides, targetFeatureLayer: FeatureLayer? ) { // Get the layer's id val targetLayerId = getServiceLayerId(featureLayer = targetFeatureLayer) // Get the layer's layer options getGenerateGeodatabaseParameters(parameterOverrides, targetFeatureLayer)?.apply { // Remove the target layer layerOptions.remove(layerOptions.find { it.layerId == targetLayerId }) } }
/** * Helper function to add the [parameterOverrides] to the generate geodatabase parameters * using the given [targetFeatureLayer] to create the key. */ private fun getGenerateGeodatabaseParameters( parameterOverrides: GenerateOfflineMapParameterOverrides, targetFeatureLayer: FeatureLayer? ): GenerateGeodatabaseParameters? { // Get the named feature layer targetFeatureLayer?.let { val key = OfflineMapParametersKey(it) // Return the layer's geodatabase parameters options return parameterOverrides.generateGeodatabaseParameters[key] } return null }
/** * Helper function to get a feature layer by it's name. */ private fun getFeatureLayer(layerName: String): FeatureLayer? { return arcGISMap.operationalLayers.find { it.name == layerName } as? FeatureLayer }
/** * Helper function to get the service layer id for the given feature layer. */ private fun getServiceLayerId(featureLayer: FeatureLayer?): Long? { return (featureLayer?.featureTable as? ServiceFeatureTable)?.layerInfo?.serviceLayerId }
/** * Clear the preview map and set up mapView again */ fun reset() { // Add the download graphic to the graphics overlay graphicsOverlay.graphics.clear() // Set up the portal item to take offline setUpMap() }}/* 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.generateofflinemapwithcustomparameters.screens
import androidx.compose.foundation.backgroundimport androidx.compose.foundation.layout.Arrangementimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.fillMaxWidthimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.wrapContentSizeimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.filled.Settingsimport androidx.compose.material3.Buttonimport androidx.compose.material3.Checkboximport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.FloatingActionButtonimport androidx.compose.material3.HorizontalDividerimport androidx.compose.material3.Iconimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.ModalBottomSheetimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Sliderimport androidx.compose.material3.SnackbarHostimport androidx.compose.material3.Textimport androidx.compose.material3.rememberModalBottomSheetStateimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableFloatStateOfimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Alignment.Companion.CenterVerticallyimport androidx.compose.ui.Modifierimport androidx.compose.ui.layout.onSizeChangedimport androidx.compose.ui.unit.dpimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.mapping.view.MapViewInteractionOptionsimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.esri.arcgismaps.sample.generateofflinemapwithcustomparameters.components.GenerateOfflineMapWithCustomParametersViewModelimport com.esri.arcgismaps.sample.sampleslib.components.JobLoadingDialogimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
/** * Main screen layout for the sample app */@OptIn(ExperimentalMaterial3Api::class)@Composablefun GenerateOfflineMapWithCustomParametersScreen(sampleName: String) { // Create a ViewModel to handle MapView interactions val mapViewModel: GenerateOfflineMapWithCustomParametersViewModel = viewModel()
// Set up the bottom sheet controls val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) var showBottomSheet by remember { mutableStateOf(false) }
Scaffold(snackbarHost = { SnackbarHost(hostState = mapViewModel.snackbarHostState) }, topBar = { SampleTopAppBar(title = sampleName) }, content = { Column( modifier = Modifier .fillMaxSize() .padding(it) ) { MapView(modifier = Modifier .weight(1f) .fillMaxWidth() // Retrieve the size of the Composable MapView .onSizeChanged { size -> mapViewModel.updateMapViewSize(size) }, arcGISMap = mapViewModel.arcGISMap, graphicsOverlays = listOf(mapViewModel.graphicsOverlay), mapViewInteractionOptions = MapViewInteractionOptions(isRotateEnabled = false), mapViewProxy = mapViewModel.mapViewProxy, onLayerViewStateChanged = { mapViewModel.calculateDownloadOfflineArea() }, onViewpointChangedForCenterAndScale = { mapViewModel.calculateDownloadOfflineArea() }) // Show bottom sheet with override parameter options if (showBottomSheet) { ModalBottomSheet( modifier = Modifier.wrapContentSize(), onDismissRequest = { showBottomSheet = false }, sheetState = sheetState ) { OverrideParametersMenu(defineParameters = mapViewModel::defineGenerateOfflineMapParameters, setBottomSheetVisibility = { showBottomSheet = it }) } } // Display progress dialog while generating an offline map if (mapViewModel.showJobProgressDialog) { JobLoadingDialog( title = "Generating offline map...", progress = mapViewModel.offlineMapJobProgress, cancelJobRequest = { mapViewModel.cancelOfflineMapJob() }, ) }
// Display a dialog if the sample encounters an error mapViewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } if (mapViewModel.showResetButton) { Button( onClick = mapViewModel::reset, modifier = Modifier.align(Alignment.CenterHorizontally) ) { Text("Reset to online map") } } } }, // Floating action button to show the parameter overrides bottom sheet floatingActionButton = { if (!showBottomSheet && !mapViewModel.showResetButton) { FloatingActionButton(modifier = Modifier.padding(bottom = 36.dp, end = 12.dp), onClick = { showBottomSheet = true }) { Icon( imageVector = Icons.Filled.Settings, contentDescription = "Show parameter overrides menu" ) } } })}
@Composablefun OverrideParametersMenu( defineParameters: (Int, Int, Int, Boolean, Boolean, Int, Boolean) -> Unit, setBottomSheetVisibility: (Boolean) -> Unit) { // Collection of parameter overrides to set in this composable var minScale by remember { mutableFloatStateOf(15f) } var maxScale by remember { mutableFloatStateOf(20f) } var extentBufferDistance by remember { mutableFloatStateOf(150f) } var includeSystemValves by remember { mutableStateOf(false) } var includeServiceConnections by remember { mutableStateOf(false) } var minHydrantFlowRate by remember { mutableFloatStateOf(500f) } var cropToWaterPipeExtent by remember { mutableStateOf(false) }
Column( modifier = Modifier .wrapContentSize() .background(MaterialTheme.colorScheme.background) .padding(12.dp) ) { Text( text = "Override parameters", style = MaterialTheme.typography.titleMedium, modifier = Modifier.align(Alignment.CenterHorizontally) ) // Adjust basemap section Text(text = "Adjust basemap", style = MaterialTheme.typography.labelLarge) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = CenterVertically ) { Text(text = "Min scale level:", style = MaterialTheme.typography.labelMedium) Text(text = "${minScale.toInt()}") } Slider( value = minScale, // Don't let the min scale exceed the max scale onValueChange = { minScale = it if (minScale >= maxScale) { maxScale = minScale + 1 } }, valueRange = 0f..22f, modifier = Modifier.padding(start = 12.dp, end = 12.dp), steps = 21 ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = CenterVertically ) { Text(text = "Max scale level:", style = MaterialTheme.typography.labelMedium) Text(text = "${maxScale.toInt()}") } Slider( value = maxScale, // Don't let the max scale exceed the min scale onValueChange = { maxScale = it if (maxScale <= minScale) { minScale = maxScale - 1 } }, valueRange = 0f..23f, modifier = Modifier.padding(start = 12.dp, end = 12.dp), steps = 22 ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = CenterVertically, ) { Text(text = "Extent buffer distance:", style = MaterialTheme.typography.labelMedium) Text(text = "${extentBufferDistance.toInt()}m") } Slider( value = extentBufferDistance, onValueChange = { extentBufferDistance = it }, valueRange = 0f..500f, modifier = Modifier.padding(start = 12.dp, end = 12.dp), ) HorizontalDivider(modifier = Modifier.padding(8.dp)) // Include layers section Text(text = "Include layers", style = MaterialTheme.typography.labelLarge) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start, verticalAlignment = CenterVertically ) { Checkbox(checked = includeSystemValves, onCheckedChange = { includeSystemValves = it }) Text(text = "System valves") } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start, verticalAlignment = CenterVertically ) { Checkbox(checked = includeServiceConnections, onCheckedChange = { includeServiceConnections = it }) Text(text = "Service connections") } HorizontalDivider(modifier = Modifier.padding(8.dp)) // Filter feature layer section Text(text = "Filter feature layer", style = MaterialTheme.typography.labelLarge) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = CenterVertically ) { Text(text = "Min hydrant flow rate:", style = MaterialTheme.typography.labelMedium) Text(text = "${minHydrantFlowRate.toInt()} GPM") } Slider( value = minHydrantFlowRate, onValueChange = { minHydrantFlowRate = it }, valueRange = 0f..2000f, modifier = Modifier.padding(start = 12.dp, end = 12.dp), ) HorizontalDivider(modifier = Modifier.padding(8.dp)) // Crop layers to extent section Text(text = "Crop layers to extent", style = MaterialTheme.typography.labelLarge) Row( modifier = Modifier.wrapContentSize(), horizontalArrangement = Arrangement.Start, verticalAlignment = CenterVertically ) { Checkbox(checked = cropToWaterPipeExtent, onCheckedChange = { cropToWaterPipeExtent = it }) Text(text = "Water pipes") } Button( onClick = { // Call defineParameters in the view model with the parameter overrides defineParameters( minScale.toInt(), maxScale.toInt(), extentBufferDistance.toInt(), includeSystemValves, includeServiceConnections, minHydrantFlowRate.toInt(), cropToWaterPipeExtent ) setBottomSheetVisibility(false) }, modifier = Modifier.align(Alignment.CenterHorizontally) ) { Text("Generate offline map") } }}