Validate utility network topology

View on GitHub

Demonstrates the workflow of getting the network state and validating the topology of a utility network.

Image of Validate utility network topology

Use case

Dirty areas are generated where edits to utility network features have not been evaluated against the network rules. Tracing across this area could result in an error or return inaccurate results. Validating the utility network updates the network topology with the edited feature data, maintaining consistency between the features and topology. Querying the network state allows you to determine if there are dirty areas or errors in a utility network, and if it supports network topology.

How to use the sample

Select a feature to make edits and then tap "Apply" to send edits to the server.

  • Tap "Get state" to check if validate is required or if tracing is available.
  • Tap "Validate" to validate network topology and clear dirty areas.
  • Tap "Trace" to run a trace.

How it works

  1. Create and load a Map with a web map item URL.
  2. Load the UtilityNetwork from the web map and switch its ServiceGeodatabase to a new branch version.
  3. Add LabelDefinitions for the fields that will be updated on a feature edit.
  4. Add the UtilityNetwork.dirtyAreaTable to the map to visualize dirty areas or errors.
  5. Set a default starting location and trace parameters to stop traversability on an open device.
  6. Get the UtilityNetworkCapabilities from the UtilityNetworkDefinition and use these values to enable or disable the 'Get State', 'Validate', and 'Trace' buttons.
  7. When an ArcGISFeature is selected for editing, populate the choice list for the field value using the field's CodedValueDomain.codedValues.
  8. When "Apply" is tapped, update the value of the selected feature's attribute value with the selected CodedValue.code and call ServiceGeodatabase.applyEdits().
  9. When "Get State" is tapped, call UtilityNetwork.state and print the results.
  10. When "Validate" is tapped, get the current map extent and call UtilityNetwork.validateNetworkTopology(forExtent:executionType:).
  11. When "Trace" is tapped, call UtilityNetwork.trace(using:) with the predefined parameters and select all features returned.
  12. When "Clear" or "Cancel" are tapped, clear all selected features on each layer in the map and close the attribute picker.

Relevant API

  • UtilityElement
  • UtilityElementTraceResult
  • UtilityNetwork
  • UtilityNetworkCapabilities
  • UtilityNetworkState
  • UtilityNetworkValidationJob
  • UtilityTraceConfiguration
  • UtilityTraceParameters
  • UtilityTraceResult

About the data

The Naperville electric feature service contains a utility network that can be used to query the network state and validate network topology before tracing. The Naperville electric webmap uses the same feature service endpoint and is shown in this sample. Authentication is required and handled within the sample code.

Additional information

Starting from 200.4, an Advanced Editing extension is required for editing a utility network in the following cases:

  • Stand-alone mobile geodatabase that is exported from ArcGIS Pro 2.7 or higher
  • Sync-enabled mobile geodatabase that is generated from an ArcGIS Enterprise Feature Service 11.2 or higher
  • Web map or service geodatabase that points to an ArcGIS Enterprise Feature Service 11.2 or higher

Please refer to the "Advanced Editing" section in the extension license table in License and deployment for details.

Tags

dirty areas, edit, network topology, online, state, trace, utility network, validate

Sample Code

ValidateUtilityNetworkTopologyView.swiftValidateUtilityNetworkTopologyView.swiftValidateUtilityNetworkTopologyView.Model.swiftValidateUtilityNetworkTopologyView.Views.swift
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
// Copyright 2024 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
//
//   https://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.

import ArcGIS
import SwiftUI

struct ValidateUtilityNetworkTopologyView: View {
    /// The view model for the sample.
    @StateObject private var model = Model()

    /// The visible area on the map.
    @State private var visibleArea: ArcGIS.Polygon?

    /// The operation on the model currently being executed.
    @State private var selectedOperation: ModelOperation = .setup

    /// A Boolean value indicating whether a model operation is in progress.
    @State private var operationIsRunning = false

    /// A Boolean value indicating whether the edit feature sheet is presented.
    @State private var editSheetIsPresented = false

    /// A Boolean value indicating whether the details of the status message are presented.
    @State private var statusDetailsArePresented = false

    /// The error shown in the error alert.
    @State private var error: Error?

    var body: some View {
        MapViewReader { mapViewProxy in
            MapView(map: model.map, graphicsOverlays: [model.graphicsOverlay])
                .onVisibleAreaChanged { visibleArea = $0 }
                .onSingleTapGesture { screenPoint, _ in
                    selectedOperation = .selectFeature(screenPoint: screenPoint)
                }
                .contentInsets(.init(top: 0, leading: 0, bottom: 350, trailing: 0))
                .task(id: selectedOperation) {
                    operationIsRunning = true
                    defer { operationIsRunning = false }

                    do {
                        switch selectedOperation {
                        case .setup:
                            try await model.setup()

                        case .getState:
                            try await model.getState()

                        case .trace:
                            try await model.trace()

                        case .validateNetworkTopology:
                            guard let extent = visibleArea?.extent else { return }
                            try await model.validate(forExtent: extent)

                        case .selectFeature(let screenPoint):
                            // Identify the tapped layers using the map view proxy.
                            let identifyResults = try await mapViewProxy.identifyLayers(
                                screenPoint: screenPoint!,
                                tolerance: 5
                            )
                            model.selectFeature(from: identifyResults)

                            // Present the sheet to edit the feature if one was selected.
                            if let feature = model.feature {
                                editSheetIsPresented = true

                                guard let featureCenter = feature.geometry?.extent.center else { return }
                                await mapViewProxy.setViewpointCenter(featureCenter)
                            } else {
                                model.statusMessage = "No feature identified. Tap on a feature."
                            }

                        case .applyEdits:
                            try await model.applyEdits()

                        case .clearSelection:
                            model.clearSelection()
                            model.statusMessage = "Selection cleared."
                        }
                    } catch {
                        model.statusMessage = selectedOperation.errorMessage
                        self.error = error
                    }
                }
        }
        .overlay(alignment: .top) {
            CollapsibleText(text: $model.statusMessage)
                .frame(maxWidth: .infinity, alignment: .center)
                .padding(8)
                .background(.ultraThinMaterial, ignoresSafeAreaEdges: .horizontal)
        }
        .overlay(alignment: .center) {
            if operationIsRunning {
                ProgressView()
                    .padding()
                    .background(.ultraThickMaterial)
                    .cornerRadius(10)
                    .shadow(radius: 50)
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .bottomBar) {
                Button("Get State") { selectedOperation = .getState }
                    .disabled(!model.canGetState)
                Spacer()
                Button("Trace") { selectedOperation = .trace }
                    .disabled(!model.canTrace)
                Spacer()
                Button("Validate") { selectedOperation = .validateNetworkTopology }
                    .disabled(!model.canValidateNetworkTopology)
                Spacer()
                Button("Clear") { selectedOperation = .clearSelection }
                    .disabled(!model.canClearSelection)
                    .popover(isPresented: $editSheetIsPresented) {
                        EditFeatureView(model: model, operationSelection: $selectedOperation)
                            .onDisappear {
                                if selectedOperation != .applyEdits {
                                    // Clear the selection if the sheet was dismissed without applying.
                                    selectedOperation = .clearSelection
                                }
                            }
                            .presentationDetents([.fraction(0.5)])
                            .frame(idealWidth: 320, idealHeight: 240)
                    }
            }
        }
        .errorAlert(presentingError: $error)
    }
}

extension ValidateUtilityNetworkTopologyView {
    /// An enumeration representing an operation run on the view model.
    enum ModelOperation: Equatable {
        /// Setup the model.
        case setup
        /// Get the state of the utility network.
        case getState
        /// Run a utility network trace.
        case trace
        /// Validate the utility network topology.
        case validateNetworkTopology
        /// Select a feature on the map at a given screen point.
        case selectFeature(screenPoint: CGPoint? = nil)
        /// Apply the edits to the feature to the service.
        case applyEdits
        /// Clear the selected feature(s).
        case clearSelection

        /// The message to display if the operations fails.
        var errorMessage: String {
            switch self {
            case .setup:
                "Initialization failed."
            case .getState:
                "Get state failed."
            case .trace:
                "Trace failed. \nTap 'Get State' to check the updated network state."
            case .selectFeature:
                "Select feature failed."
            case .validateNetworkTopology:
                "Validate network topology failed."
            case .applyEdits:
                "Apply edits failed."
            case .clearSelection:
                ""
            }
        }
    }
}

#Preview {
    NavigationStack {
        ValidateUtilityNetworkTopologyView()
    }
}

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