A utility network container allows a dense collection of features to be represented by a single feature, which can be used to reduce map clutter.
Use case
Offering a container view for features aids in the review for valid, structural attachment, and containment relationships. It also helps determine if a dataset has an association role set. Container views often model a cluster of electrical devices on a pole top or inside a cabinet or vault.
How to use the sample
Tap a container feature to show all features inside the container. The container is shown as a polygon graphic with the content features contained within. The viewpoint and scale of the map are also changed to the container's extent. Connectivity and attachment associations inside the container are shown as dotted lines.
How it works
- Load a web map that includes ArcGIS Pro Subtype Group Layers with only container features visible (i.e. fuse bank, switch bank, transformer bank, hand hole, and junction box).
- Create a
MapView
and add theonSingleTapGesture(perform:)
modifier to detect tap events. - Create and load a
UtilityNetwork
. - Add a
GraphicsOverlay
for displaying a container view. - Identify the tapped feature and create an
UtilityElement
from it. - Get the associations for this element using
UtilityNetwork.associations(for:ofKind:)
. - Turn-off the visibility of all of the map's
operationalLayers
. - Get the features for the
UtilityElement
(s) from the associations usingUtilityNetwork.features(for:)
. - Add a
Graphic
with the same geometry and symbol as these features. - Add another
Graphic
that represents this extent and zoom to this extent with some buffer. - Get associations for this extent using
UtilityNetwork.associations(forExtent:ofKind:)
. - Add a
Graphic
to represent the association geometry between them using a symbol that distinguishes betweenattachment
andconnectivity
association type. - Turn-on the visibility of all
operationalLayers
, clear theGraphic
objects, and zoom out to previous extent to exit container view.
Relevant API
- SubtypeFeatureLayer
- UtilityAssociation
- UtilityAssociation.Kind
- UtilityElement
- UtilityNetwork
About the data
The Naperville electric network feature service, hosted on ArcGIS Online, contains a utility network used to find associations shown in this sample. The Naperville Electric Containers web map portal item, uses the same feature service endpoint, but displays only container features.
Tags
associations, connectivity association, containment association, structural attachment associations, utility network
Sample Code
// Copyright 2023 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 DisplayContentOfUtilityNetworkContainerView: View {
/// The display scale of this environment.
@Environment(\.displayScale) private var displayScale
/// The view model for the sample.
@StateObject private var model = Model()
/// A Boolean value indicating whether the map view interaction is enabled.
@State private var isMapViewUserInteractionEnabled = true
/// A Boolean value indicating whether the legends are shown.
@State private var isShowingLegend = false
/// A Boolean value indicating whether the content within the container is shown.
@State private var isShowingContainer = false
/// The map point where the map was tapped.
@State private var mapPoint: Point?
/// The point to identify a graphic.
@State private var screenPoint: CGPoint?
/// The viewpoint before the map view zooms into the container's extent.
@State private var previousViewpoint: Viewpoint?
/// The current viewpoint of the map view.
@State private var viewpoint = Viewpoint(latitude: 41.80, longitude: -88.16, scale: 4e3)
var body: some View {
MapViewReader { proxy in
MapView(map: model.map, graphicsOverlays: [model.graphicsOverlay])
.interactionModes(isMapViewUserInteractionEnabled ? .all : [])
.onViewpointChanged(kind: .boundingGeometry) { viewpoint = $0 }
.onSingleTapGesture { screenPoint, mapPoint in
self.screenPoint = screenPoint
self.mapPoint = mapPoint
}
.overlay(alignment: .top) {
Text(model.statusMessage)
.padding(.vertical, 6)
.frame(maxWidth: .infinity)
.background(.thinMaterial, ignoresSafeAreaEdges: .horizontal)
}
.toolbar {
ToolbarItemGroup(placement: .bottomBar) {
Button("Show Legend") {
isShowingLegend = true
}
.disabled(model.legendItems.isEmpty)
.popover(isPresented: $isShowingLegend) {
sheetContent
.presentationDetents([.fraction(0.5)])
.frame(idealWidth: 320, idealHeight: 380)
}
.task(id: displayScale) {
// Updates the legend info when display scale changes.
await model.updateLegendInfoItems(displayScale: displayScale)
}
Spacer()
Button("Exit Container View") {
resetMapView(proxy)
model.statusMessage = "Tap on a container to see its content."
}
.disabled(!isShowingContainer)
}
}
.task {
// Loads the utility network.
do {
try await model.loadUtilityNetwork()
model.statusMessage = "Tap on a container to see its content."
} catch {
model.statusMessage = "An error occurred while loading the network."
}
}
.task(id: screenPoint) {
guard let screenPoint else { return }
// The identify results from the touch point.
guard let identifyResults = try? await proxy.identifyLayers(screenPoint: screenPoint, tolerance: 5) else { return }
// The features identified are as part of its sublayer's result.
guard let layerResult = identifyResults.first(where: { $0.layerContent is SubtypeFeatureLayer }) else { return }
// The top selected feature.
guard let containerFeature = (layerResult.sublayerResults
.lazy
.flatMap { $0.geoElements.compactMap { $0 as? ArcGISFeature } })
.first
else {
return
}
do {
// Displays the container feature's content.
try await model.handleIdentifiedFeature(containerFeature)
} catch {
model.statusMessage = "An error occurred while getting the associations."
resetMapView(proxy)
return
}
// Sets UI states to focus on the container's content.
model.setOperationalLayersVisibility(isVisible: false)
// Turns off user interaction to avoid straying away from the container view.
isMapViewUserInteractionEnabled = false
previousViewpoint = viewpoint
if let extent = model.graphicsOverlay.extent {
await proxy.setViewpointGeometry(extent, padding: 20)
isShowingContainer = true
}
}
}
}
/// A helper method to reset the map view.
/// - Parameter proxy: The map view proxy.
private func resetMapView(_ proxy: MapViewProxy) {
model.setOperationalLayersVisibility(isVisible: true)
model.graphicsOverlay.removeAllGraphics()
isShowingContainer = false
isMapViewUserInteractionEnabled = true
Task {
if let previousViewpoint {
await proxy.setViewpoint(previousViewpoint)
}
}
}
/// The legends list.
private var sheetContent: some View {
NavigationStack {
List(model.legendItems, id: \.name) { legend in
Label {
Text(legend.name)
} icon: {
Image(uiImage: legend.image)
}
}
.navigationTitle("Legend")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
isShowingLegend = false
}
}
}
}
.frame(idealWidth: 320, idealHeight: 428)
}
}
#Preview {
NavigationStack {
DisplayContentOfUtilityNetworkContainerView()
}
}