Perform valve isolation trace

View on GitHubSample viewer app

Run a filtered trace to locate operable features that will isolate an area from the flow of network resources.

Image of a utility network with an isolation trace applied to it

Use case

Determine the set of operable features required to stop a network's resource, effectively isolating an area of the network. For example, you can choose to return only accessible and operable valves: ones that are not paved over or rusted shut.

How to use the sample

Create and set the configuration's filter barriers by selecting a category. Toggle 'Include isolated features' as required. Tap 'Trace' to run a subnetwork-based isolation trace.

How it works

  1. Create a MapView.
  2. Create and load a ServiceGeodatabase with a feature service URL and get tables with their layer IDs.
  3. Create an ArcGISMap object that contains FeatureLayer(s) created from the service geodatabase's tables.
  4. Create and load an UtilityNetwork with the same feature service URL and map.
  5. Create a default starting location from a given asset type and global ID.
  6. Add a GraphicsOverlay with an Graphic that represents this starting location.
  7. Populate the choice list for the filter barriers from the categories property of UtilityNetworkDefinition.
  8. Get a default UtilityTraceConfiguration from a given tier in a domain network. Set its filter property with an UtilityTraceFilter object.
  9. When "Trace" is tapped,
    • Create a new UtilityCategoryComparison with the selected category and UtilityCategoryComparisonOperator.EXISTS.
    • Assign this condition to traceConfiguration.filter.barriers from the default configuration from step 7. Update this configuration's isIncludeIsolatedFeatures property.
    • Create a UtilityTraceParameters with UtilityTraceType.ISOLATION and the default starting location from step 4.
    • Set its UtilityTraceConfiguration with this configuration and then, run a UtilityNetwork.traceAsync(traceParameters).
  10. For every FeatureLayer in the map, create QueryParameters and add any of the UtilityElementTraceResult.elements whose NetworkSource.name matches the feature layer's FeatureTable.tableName. Use the query parameters to select the features with featureLayer.selectFeaturesAsync(...)

Relevant API

  • ServiceGeodatabase
  • UtilityCategory
  • UtilityCategoryComparison
  • UtilityCategoryComparisonOperator
  • UtilityElement
  • UtilityElementTraceResult
  • UtilityNetwork
  • UtilityNetworkDefinition
  • UtilityTier
  • UtilityTraceFilter
  • UtilityTraceParameters
  • UtilityTraceResult
  • UtilityTraceType

About the data

The Naperville gas network feature service, hosted on ArcGIS Online, contains a utility network used to run the isolation trace shown in this sample.

Tags

category comparison, condition barriers, isolated features, network analysis, subnetwork trace, trace configuration, trace filter, utility network

Sample Code

MainActivity.kt
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
/*
 * Copyright 2020 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.arcgisruntime.sample.performvalveisolationtrace

import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.*
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SwitchCompat
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment
import com.esri.arcgisruntime.data.ArcGISFeature
import com.esri.arcgisruntime.data.QueryParameters
import com.esri.arcgisruntime.data.ServiceGeodatabase
import com.esri.arcgisruntime.geometry.GeometryEngine
import com.esri.arcgisruntime.geometry.Point
import com.esri.arcgisruntime.geometry.Polyline
import com.esri.arcgisruntime.layers.FeatureLayer
import com.esri.arcgisruntime.loadable.LoadStatus
import com.esri.arcgisruntime.mapping.ArcGISMap
import com.esri.arcgisruntime.mapping.BasemapStyle
import com.esri.arcgisruntime.mapping.Viewpoint
import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener
import com.esri.arcgisruntime.mapping.view.Graphic
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay
import com.esri.arcgisruntime.security.UserCredential
import com.esri.arcgisruntime.mapping.view.MapView
import com.esri.arcgisruntime.sample.performvalveisolationtrace.databinding.ActivityMainBinding
import com.esri.arcgisruntime.sample.performvalveisolationtrace.databinding.SpinnerTextItemBinding
import com.esri.arcgisruntime.symbology.SimpleMarkerSymbol
import com.esri.arcgisruntime.symbology.SimpleRenderer
import com.esri.arcgisruntime.utilitynetworks.UtilityCategory
import com.esri.arcgisruntime.utilitynetworks.UtilityCategoryComparison
import com.esri.arcgisruntime.utilitynetworks.UtilityCategoryComparisonOperator
import com.esri.arcgisruntime.utilitynetworks.UtilityElement
import com.esri.arcgisruntime.utilitynetworks.UtilityElementTraceResult
import com.esri.arcgisruntime.utilitynetworks.UtilityNetwork
import com.esri.arcgisruntime.utilitynetworks.UtilityNetworkDefinition
import com.esri.arcgisruntime.utilitynetworks.UtilityNetworkSource
import com.esri.arcgisruntime.utilitynetworks.UtilityTerminal
import com.esri.arcgisruntime.utilitynetworks.UtilityTraceConfiguration
import com.esri.arcgisruntime.utilitynetworks.UtilityTraceFilter
import com.esri.arcgisruntime.utilitynetworks.UtilityTraceParameters
import com.esri.arcgisruntime.utilitynetworks.UtilityTraceType
import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.util.UUID
import java.util.*
import kotlin.math.roundToInt

class MainActivity : AppCompatActivity() {

    private val TAG = MainActivity::class.java.simpleName

    private val activityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    private val mapView: MapView by lazy {
        activityMainBinding.mapView
    }

    private val fab: FloatingActionButton by lazy {
        activityMainBinding.fab
    }

    private val traceControlsTextView: TextView by lazy {
        activityMainBinding.traceControlsTextView
    }

    private val progressBar: ProgressBar by lazy {
        activityMainBinding.progressBar
    }

    private val traceTypeSpinner: Spinner by lazy {
        activityMainBinding.traceTypeSpinner
    }

    private val traceButton: Button by lazy {
        activityMainBinding.traceButton
    }

    private val resetButton: Button by lazy {
        activityMainBinding.resetButton
    }

    private val includeIsolatedSwitch: SwitchCompat by lazy {
        activityMainBinding.includeIsolatedSwitch
    }

    // objects that implement Loadable must be class fields to prevent being garbage collected before loading
    private val featureServiceUrl =
        "https://sampleserver7.arcgisonline.com/server/rest/services/UtilityNetwork/NapervilleGas/FeatureServer"
    private val utilityNetwork by lazy {
        UtilityNetwork(featureServiceUrl)
    }

    // create a graphics overlay for the starting location and add it to the map view
    private val startingLocationGraphicsOverlay by lazy {
        GraphicsOverlay()
    }

    private val filterBarriersGraphicsOverlay by lazy {
        GraphicsOverlay()
    }

    private var utilityTraceParameters: UtilityTraceParameters? = null

    private val serviceGeodatabase by lazy {
        ServiceGeodatabase(featureServiceUrl)
    }

    // create and apply renderers for the starting point graphics overlay
    private val startingPointSymbol: SimpleMarkerSymbol by lazy {
        SimpleMarkerSymbol(
            SimpleMarkerSymbol.Style.CROSS,
            Color.GREEN,
            25f
        )
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(activityMainBinding.root)

        // authentication with an API key or named user is required to access basemaps and other
        // location services
        ArcGISRuntimeEnvironment.setApiKey(BuildConfig.API_KEY)

        loadServiceGeodatabase()

        // create a map with the utility network distribution line and device layers
        val map = ArcGISMap(BasemapStyle.ARCGIS_STREETS_NIGHT).apply {
            //  create and load the utility network
            addDoneLoadingListener {
                utilityNetworks.add(utilityNetwork)
            }
        }

        mapView.apply {
            this.map = map
            // set the starting location graphic overlay's renderer and add it to the map view
            startingLocationGraphicsOverlay.renderer = SimpleRenderer(startingPointSymbol)
            graphicsOverlays.add(startingLocationGraphicsOverlay)

            // set the viewpoint to a specific location in Naperville, Illinois
            setViewpointAsync(Viewpoint(Point(-9812712.321100, 5124260.390000, 0.000100), 5000.0))

            // make sure the fab doesn't obscure the attribution bar
            addAttributionViewLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
                val layoutParams = fab.layoutParams as CoordinatorLayout.LayoutParams
                layoutParams.bottomMargin += bottom - oldBottom
            }
            // close the options sheet when the map is tapped
            onTouchListener = object : DefaultMapViewOnTouchListener(this@MainActivity, mapView) {
                override fun onTouch(view: View?, event: MotionEvent?): Boolean {
                    if (fab.isExpanded) {
                        fab.isExpanded = false
                    }
                    return super.onTouch(view, event)
                }

                override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
                    // only pass taps to identify nearest utility element once the utility network has loaded
                    if (utilityNetwork.loadStatus == LoadStatus.LOADED) {
                        identifyNearestUtilityElement(
                            android.graphics.Point(e.x.roundToInt(), e.y.roundToInt())
                        )
                        traceTypeSpinner.isEnabled = false
                        return true
                    }
                    return false
                }
            }
        }
        // show the options sheet when the floating action button is clicked
        fab.setOnClickListener {
            fab.isExpanded = !fab.isExpanded
        }
    }

    /*** Load the service geodatabase and initialize the layers.
     */
    private fun loadServiceGeodatabase() {
        // set user credentials to authenticate with the service
        // NOTE: a licensed user is required to perform utility network operations
        serviceGeodatabase.credential = UserCredential("viewer01", "I68VGU^nMurF")
        serviceGeodatabase.addDoneLoadingListener {
            if (serviceGeodatabase.loadStatus == LoadStatus.LOADED) {
                // The  gas device layer ./0 and gas line layer ./3 are created from the service geodatabase.
                val gasDeviceLayerTable = serviceGeodatabase.getTable(0)
                val gasLineLayerTable = serviceGeodatabase.getTable(3)
                // load the utility network data from the feature service and create feature layers
                val deviceLayer = FeatureLayer(gasDeviceLayerTable)
                val distributionLineLayer = FeatureLayer(gasLineLayerTable)
                // add the utility network feature layers to the map for display
                mapView.map.operationalLayers.add(deviceLayer)
                mapView.map.operationalLayers.add(distributionLineLayer)
                loadUtilityNetwork()
            } else {
                val error =
                    "Error loading service geodatabase: " + serviceGeodatabase.loadError.cause?.message
                Toast.makeText(this, error, Toast.LENGTH_LONG).show()
                Log.e(TAG, error)
            }
        }
        serviceGeodatabase.loadAsync()
    }

    /**
     * Create and load a utility network from the string resource url and initialize a starting point
     * from it.
     */
    private fun loadUtilityNetwork(): UtilityNetwork {
        utilityNetwork.loadAsync()
        utilityNetwork.addDoneLoadingListener {
            if (utilityNetwork.loadStatus == LoadStatus.LOADED) {
                // get a trace configuration from a tier
                val networkDefinition = utilityNetwork.definition
                val domainNetwork = networkDefinition.getDomainNetwork("Pipeline")
                val tier = domainNetwork.getTier("Pipe Distribution System")
                val traceConfiguration = tier.traceConfiguration

                // create a trace filter
                traceConfiguration.filter = UtilityTraceFilter()

                // get a default starting location
                val networkSource = networkDefinition.getNetworkSource("Gas Device")
                val assetGroup = networkSource.getAssetGroup("Meter")
                val assetType = assetGroup.getAssetType("Customer")
                val startingLocation = utilityNetwork.createElement(
                    assetType,
                    UUID.fromString("98A06E95-70BE-43E7-91B7-E34C9D3CB9FF")
                )
                // create new base trace parameters
                utilityTraceParameters = UtilityTraceParameters(
                    UtilityTraceType.ISOLATION,
                    Collections.singletonList(startingLocation)
                )

                // get a list of features for the starting location element
                val elementFeaturesFuture =
                    utilityNetwork.fetchFeaturesForElementsAsync(listOf(startingLocation))
                elementFeaturesFuture.addDoneListener {
                    try {
                        val startingLocationFeatures = elementFeaturesFuture.get()
                        if (startingLocationFeatures.isNotEmpty()) {
                            // get the geometry of the first feature for the starting location as a point
                            (startingLocationFeatures[0].geometry as? Point)?.let { startingLocationGeometryPoint ->

                                // create a graphic for the starting location and add it to the graphics overlay
                                val startingLocationGraphic =
                                    Graphic(startingLocationGeometryPoint, startingPointSymbol)
                                startingLocationGraphicsOverlay.graphics.add(startingLocationGraphic)

                                // create a graphics overlay for filter barriers and add it to the map view
                                mapView.graphicsOverlays.add(filterBarriersGraphicsOverlay)

                                // create and apply a renderer for the filter barriers graphics overlay
                                val barrierPointSymbol = SimpleMarkerSymbol(
                                    SimpleMarkerSymbol.Style.CROSS, Color.RED, 25f
                                )
                                filterBarriersGraphicsOverlay.renderer = SimpleRenderer(
                                    barrierPointSymbol
                                )

                                populateCategorySpinner(networkDefinition)

                                traceButton.setOnClickListener {
                                    fab.isExpanded = false
                                    performTrace(
                                        utilityNetwork,
                                        traceConfiguration
                                    )
                                }

                                resetButton.setOnClickListener {
                                    reset(
                                    )
                                }
                            }
                        } else {
                            val message = "Starting location features not found."
                            Log.i(TAG, message)
                            Toast.makeText(
                                this,
                                message,
                                Toast.LENGTH_LONG
                            ).show()
                        }
                    } catch (e: Exception) {
                        val error = "Error loading starting location feature: ${e.message}"
                        Log.e(TAG, error)
                        Toast.makeText(this, error, Toast.LENGTH_LONG).show()
                    }
                }
            } else {
                val error = "Error loading utility network: ${utilityNetwork.loadError}"
                Log.e(TAG, error)
                Toast.makeText(this, error, Toast.LENGTH_LONG).show()
            }
        }
        return utilityNetwork
    }

    private fun identifyNearestUtilityElement(screenPoint: android.graphics.Point) {

        traceControlsTextView.text = getString(R.string.add_another_filter_barrier)

        // ensure the utility network is loaded before processing clicks on the map view
        if (utilityNetwork.loadStatus == LoadStatus.LOADED) {

            // show the progress indicator
            progressBar.visibility = View.VISIBLE

            // identify the feature to be used
            val identifyLayerResultsFuture = mapView.identifyLayersAsync(screenPoint, 10.0, false)
            identifyLayerResultsFuture.addDoneListener {
                try {
                    // get the result of the query
                    val identifyLayerResults = identifyLayerResultsFuture.get()

                    // return if no features are identified
                    if (identifyLayerResults.isNotEmpty()) {

                        // retrieve the identify result elements as ArcGISFeatures
                        val elements = identifyLayerResults.map { it.elements[0] as ArcGISFeature }

                        // create utility elements from the list of ArcGISFeature elements
                        val utilityElements = elements.map { utilityNetwork.createElement(it) }

                        // get a reference to the closest junction if there is one
                        val junction =
                            utilityElements.firstOrNull { it.networkSource.sourceType == UtilityNetworkSource.Type.JUNCTION }

                        // get a reference to the closest edge if there is one
                        val edge =
                            utilityElements.firstOrNull { it.networkSource.sourceType == UtilityNetworkSource.Type.EDGE }

                        // preferentially select junctions, otherwise an edge
                        val utilityElement = junction ?: edge

                        // retrieve the first result and get its contents
                        if (junction != null) {

                            // check if the feature has a terminal configuration and multiple terminals
                            if (junction.assetType.terminalConfiguration != null) {
                                val utilityTerminalConfiguration =
                                    junction.assetType.terminalConfiguration
                                val terminals =
                                    utilityTerminalConfiguration.terminals
                                if (terminals.size > 1) {
                                    // prompt the user to select a terminal for this feature
                                    promptForTerminalSelection(junction, terminals)
                                }
                            }
                        } else if (edge != null) {

                            // get the geometry of the identified feature as a polyline, and remove the z component
                            val polyline =
                                GeometryEngine.removeZ(elements[0].geometry) as Polyline

                            // get the clicked map point
                            val mapPoint = mapView.screenToLocation(screenPoint)

                            // compute how far the clicked location is along the edge feature
                            val fractionAlongEdge =
                                GeometryEngine.fractionAlong(polyline, mapPoint, -1.0)
                            if (fractionAlongEdge.isNaN()) {
                                Toast.makeText(
                                    this,
                                    "Cannot add starting location or barrier here.",
                                    Toast.LENGTH_LONG
                                ).show()
                                return@addDoneListener
                            }

                            // set the fraction along edge
                            edge.fractionAlongEdge = fractionAlongEdge

                            // update the status label text
                            Toast.makeText(
                                this,
                                "Fraction along edge: " + edge.fractionAlongEdge.roundToInt(),
                                Toast.LENGTH_LONG
                            ).show()
                        }

                        // add the element to the list of filter barriers
                        utilityTraceParameters?.filterBarriers?.add(utilityElement)

                        // get the clicked map point
                        val mapPoint = mapView.screenToLocation(screenPoint)

                        // find the closest coordinate on the selected element to the clicked point
                        val proximityResult =
                            GeometryEngine.nearestCoordinate(elements[0].geometry, mapPoint)

                        // create a graphic for the new utility element
                        val utilityElementGraphic = Graphic().apply {
                            // set the graphic's geometry to the coordinate on the element and add it to the graphics overlay
                            geometry = proximityResult.coordinate
                        }

                        // add utility element graphic to the filter barriers graphics overlay
                        filterBarriersGraphicsOverlay.graphics.add(utilityElementGraphic)
                    }
                } catch (e: Exception) {
                    val error = "Error identifying tapped feature: " + e.message
                    Log.e(TAG, error)
                    Toast.makeText(this, error, Toast.LENGTH_LONG).show()
                } finally {
                    progressBar.visibility = View.GONE
                }
            }
        }
    }

    /**
     * Prompts the user to select a terminal from a provided list.
     *
     * @param terminals a list of terminals for the user to choose from
     * @return the user's selected terminal
     */
    private fun promptForTerminalSelection(
        utilityElement: UtilityElement,
        terminals: List<UtilityTerminal>
    ) {
        // get a list of terminal names from the terminals
        val terminalNames = terminals.map { it.name }
        AlertDialog.Builder(this).apply {
            setTitle("Select utility terminal:")
            setItems(terminalNames.toTypedArray()) { _, which ->
                // apply the selected terminal
                val terminal = terminals[which]
                utilityElement.terminal = terminal
                // show the terminals name in the status label
                val terminalName = if (terminal.name != null) terminal.name else "default"
                Toast.makeText(
                    this@MainActivity,
                    "Feature added at terminal: $terminalName",
                    Toast.LENGTH_LONG
                ).show()
            }.show()
        }
    }

    /**
     * Performs a valve isolation trace according to the defined trace configuration and starting* location, and selects the resulting features on the map.
     *
     * @param utilityNetwork the utility network to perform the trace on
     * @param traceConfiguration the trace configuration to apply to the trace

     */
    private fun performTrace(
        utilityNetwork: UtilityNetwork,
        traceConfiguration: UtilityTraceConfiguration

    ) {
        progressBar.visibility = View.VISIBLE
        // create a category comparison for the trace
        // NOTE: UtilityNetworkAttributeComparison or UtilityCategoryComparisonOperator.DOES_NOT_EXIST
        // can also be used. These conditions can be joined with either UtilityTraceOrCondition or UtilityTraceAndCondition.
        val utilityCategoryComparison = UtilityCategoryComparison(
            traceTypeSpinner.selectedItem as UtilityCategory,
            UtilityCategoryComparisonOperator.EXISTS
        )
        // set the category comparison to the barriers of the configuration's trace filter
        traceConfiguration.apply {
            filter = UtilityTraceFilter()
            filter.barriers = utilityCategoryComparison

            // set the configuration to include or leave out isolated features
            isIncludeIsolatedFeatures = includeIsolatedSwitch.isChecked
        }

        // build parameters for the isolation trace
        utilityTraceParameters!!.traceConfiguration = traceConfiguration

        // run the trace and get the result
        val utilityTraceResultsFuture = utilityNetwork.traceAsync(utilityTraceParameters)
        utilityTraceResultsFuture.addDoneListener {
            try {
                // get the first element of the trace result if it is not null
                (utilityTraceResultsFuture.get()[0] as? UtilityElementTraceResult)?.let { utilityElementTraceResult ->
                    if (utilityElementTraceResult.elements.isNotEmpty()) {
                        // iterate over the map's feature layers
                        mapView.map.operationalLayers.filterIsInstance<FeatureLayer>()
                            .forEach { featureLayer ->
                                // clear any selections from a previous trace
                                featureLayer.clearSelection()

                                val queryParameters = QueryParameters()
                                // for each utility element in the trace, check if its network source is the same as
                                // the feature table, and if it is, add it to the query parameters to be selected
                                utilityElementTraceResult.elements.filter { it.networkSource.name == featureLayer.featureTable.tableName }
                                    .forEach { utilityElement ->
                                        queryParameters.objectIds.add(utilityElement.objectId)
                                    }

                                // select features that match the query
                                featureLayer.selectFeaturesAsync(
                                    queryParameters,
                                    FeatureLayer.SelectionMode.NEW
                                )
                            }
                    } else {
                        // iterate over the map's feature layers
                        mapView.map.operationalLayers.filterIsInstance<FeatureLayer>()
                            .forEach { featureLayer ->
                                // clear any selections from a previous trace
                                featureLayer.clearSelection()
                            }
                        // trace result is empty
                        val message = "Utility Element Trace Result had no elements!"
                        Log.i(TAG, message)
                        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
                    }
                }
                // hide the progress bar when the trace is completed or failed
                progressBar.visibility = View.GONE
            } catch (e: Exception) {
                val error = "Error loading utility trace results: ${e.message}"
                Log.e(TAG, error)
                Toast.makeText(this, error, Toast.LENGTH_LONG).show()
            }
        }
    }

    private fun reset() {
        traceControlsTextView.text = getString(R.string.choose_category_for_filter_barrier)
        traceTypeSpinner.isEnabled = true
        mapView.map.operationalLayers.forEach { layer ->
            (layer as? FeatureLayer)?.clearSelection()
        }
        utilityTraceParameters?.filterBarriers?.clear()
        filterBarriersGraphicsOverlay.graphics.clear()
    }

    /**
     * Initialize the category selection spinner using a utility network definition.
     *
     * @param networkDefinition the utility network definition to populate the spinner
     */
    private fun populateCategorySpinner(
        networkDefinition: UtilityNetworkDefinition
    ) {
        // populate the spinner with utility categories as the data and their names as the text
        traceTypeSpinner.adapter =
            object : ArrayAdapter<UtilityCategory>(
                this,
                R.layout.spinner_text_item,
                networkDefinition.categories
            ) {
                override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
                    val binding = SpinnerTextItemBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false
                    )

                    binding.textView.text = (getItem(position) as UtilityCategory).name
                    return binding.root
                }

                override fun getDropDownView(
                    position: Int,
                    convertView: View?,
                    parent: ViewGroup
                ): View = getView(position, convertView, parent)
            }
    }

    override fun onResume() {
        super.onResume()
        mapView.resume()
    }

    override fun onPause() {
        mapView.pause()
        super.onPause()
    }

    override fun onDestroy() {
        mapView.dispose()
        super.onDestroy()
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.